diff --git a/src/ja/README.md b/src/ja/README.md index 3349560424..77dc1676fb 100644 --- a/src/ja/README.md +++ b/src/ja/README.md @@ -1,23 +1,30 @@ --- home: true heroImage: https://raw.githubusercontent.com/sanic-org/sanic-assets/master/png/sanic-framework-logo-400x97.png -heroText: 速く構築。速く動作。 +heroText: 速く構築。 速く動作。 tagline: 次世代Python webサーバー/フレームワーク actionText: さあ始めよう → actionLink: /ja/guide/ features: -- title: シンプルで軽量 - details: スマートデフォルトと肥大化のない直感的なAPIを使用し、アプリの構築に直接取り掛かることができます。 -- title: 自由な発想と柔軟な対応 - details: ツールに制限をかけずに構築する方法を構築します。 -- title: パフォーマンスとスケーラブル - details: スピードとスケーラビリティを重視して、ゼロから作り上げました。大小さまざまなWebアプリケーションを動かすことができます。 -- title: 生産準備完了 - details: ウェブサーバーがバンドルされており、特別な準備なしにwebアプリケーションを起動することができます。 -- title: 数百万人からの信頼 - details: Sanicは、PyPIで全体的に最も人気のあるフレームワークの1つであり、1番の非同期対応フレームワークです。 -- title: コミュニティ主導 - details: このプロジェクトはコミュニティのために、コミュニティによって維持、運営されています。 + - + title: シンプルで軽量 + details: スマートデフォルトと肥大化のない直感的なAPIを使用し、アプリの構築に直接取り掛かることができます。 + - + title: 自由な発想と柔軟な対応 + details: ツールに制限をかけずに構築する方法を構築します。 + - + title: パフォーマンスとスケーラブル + details: スピードとスケーラビリティを重視して、ゼロから作り上げました。 大小さまざまなWebアプリケーションを動かすことができます。 + - + title: 生産準備完了 + details: ウェブサーバーがバンドルされており、特別な準備なしにwebアプリケーションを起動することができます。 + - + title: 数百万人からの信頼 + details: Sanicは、PyPIで全体的に最も人気のあるフレームワークの1つであり、1番の非同期対応フレームワークです。 + - + title: コミュニティ主導 + details: このプロジェクトはコミュニティのために、コミュニティによって維持、運営されています。 pageClass: landing-page logo: false --- + diff --git a/src/ja/guide/README.md b/src/ja/guide/README.md index 7d0cb35511..356de381da 100644 --- a/src/ja/guide/README.md +++ b/src/ja/guide/README.md @@ -4,156 +4,96 @@ pageClass: intro # 紹介 -Sanicは、Python 3.7以上のWebサーバーとWebフレームワークで、高速に機能するように書かれています。Python 3.5で追加された非同期/待機構文の使用が可能になり、コードがノンブロッキングでスピーディーになります。 +Sanicは、Python 3.7以上のWebサーバーとWebフレームワークで、高速に機能するように書かれています。 Python 3.5で追加された非同期/待機構文の使用が可能になり、コードがノンブロッキングでスピーディーになります。 -| | | -|---------|-------------------------------------------------------------------------------------------------------------------------| -| 構築 | [![Build Status][]][1] [![AppVeyor Build Status][]][2] [![Codecov]][3]   | -| ドキュメント | [![Documentation]][4] | -| パッケージ | [![PyPI][]][5] [![PyPI version][]][5] [![PyPI Wheel][]][6] [![Supported implementations][]][6] [![Code style black]][7] | -| サポート | [![Forums][]][8] [![Discord][]][9] [![Awesome Sanic List]][10] | -| ステータス | [![Downloads][]][11] [![Downloads][12]][11] | +| | | +| ------ | ------------------------------------------------------------------------------------------------------------ | +| 構築 | [![Build Status][1]][1] [![AppVeyor Build Status][3]][2] [![Codecov]][3] | +| ドキュメント | [![Documentation]][4] | +| パッケージ | [![PyPI][7]][5] [![PyPI バージョン][9]][5] [![PyPI ホイール][11]][6] [![サポートされている実装][13]][6] [![Code style black]][7] | +| サポート | [![フォーラム][16]][8] [![Discord][18]][9] [![Awesome Sanic List]][10] | +| ステータス | [![ダウンロード][21]][11] [![ダウンロード][23]][11] | ## これは何? まず第一に、水に飛び込む前に、サニックは他のフレームワークとは異なることを知っておく必要があります。 -その最初の文では、Sanicは**フレームワーク**と**ウェブサーバー**の両方であるため、大きな間違いがあります。展開セクションでは、これについてもう少し話します。 +その最初の文では、Sanicは**フレームワーク**と**ウェブサーバー**の両方であるため、大きな間違いがあります。 展開セクションでは、これについてもう少し話します。 -しかし、箱から出してすぐにSanicには、本番グレードのWebアプリケーションの作成、展開、拡張に必要なものがすべて付属していることを忘れないでください。:rocket: +しかし、箱から出してすぐにSanicには、本番グレードのWebアプリケーションの作成、展開、拡張に必要なものがすべて付属していることを忘れないでください。 :rocket: -## 目的 +## 目標 > 構築、拡張、最終的に拡張が簡単な高性能HTTPサーバーを起動して実行する簡単な方法を提供する。 ## 機能 ---:1 -- 速いウェブサーバーを構築する +### コア + +- **_速い_**ウェブサーバーを構築する - 生産準備完了 - 非常にスケーラブル - ASGI準拠 - シンプルで直感的なAPIデザイン - コミュニティによって、コミュニティのために + :--:1 +### Sanic Extensions [[詳細](../plugins/sanic-ext/getting-started.md)] + +- CORSの保護 +- Jinjaによるテンプレートのレンダリング +- ルートハンドラへの依存性インジェクション +- Redocや Swagger を使用した OpenAPI ドキュメント +- 定義済み、エンドポイント固有のレスポンスシリアライザー +- リクエストのクエリ引数とボディ入力の検証 +- `HEAD`、`OPTIONS`、`TRACE` のエンドポイントの自動作成 + :--- ## スポンサー -確認 [open collective](https://opencollective.com/sanic-org) to learn more about helping to fund Sanic. +Sanicへの資金援助の詳細については、 [open collective](https://opencollective.com/sanic-org) を参照してください。 ## コミュニティーに参加 -議論のメインチャンネルは[コミュニティフォーラム](https://community.sanicframework.org/)にあります。ライブディスカッションとチャット用の[Discord Server](https://discord.gg/FARQzAEMAA)もあります。 +議論のメインチャンネルは[コミュニティフォーラム](https://community.sanicframework.org/)にあります。 ライブディスカッションとチャット用の[Discord Server](https://discord.gg/FARQzAEMAA)もあります。 -Stackoverflow `[sanic]`タグは、プロジェクトのメンテナによって[積極的に監視](https://stackoverflow.com/questions/tagged/sanic)です。 +Stackoverflowの`[sanic]` タグは [積極的に](https://stackoverflow.com/questions/tagged/sanic) プロジェクト管理者によって監視されています。 -## リビューション +## コントリビューション -私たちは常に新しい貢献を喜んでいます。私たちは[始めるために探している人のために良いマークされた問題](https://github.com/sanic-org/sanic/issues?q=is%3Aopen+is%3Aissue+label%3Abeginner)、ようこそ[フォーラムの質問/回答/ディスカッション](https://community.sanicframework.org/)。[貢献ガイドライン](https://github.com/sanic-org/sanic/blob/master/CONTRIBUTING.rst)をご覧ください。 +私たちは常に新しい貢献を喜んでいます。 私たちは[始めるために探している人のために良いマークされた問題](https://github.com/sanic-org/sanic/issues?q=is%3Aopen+is%3Aissue+label%3Abeginner)、ようこそ[フォーラムの質問/回答/ディスカッション](https://community.sanicframework.org/)。 [貢献ガイドライン](https://github.com/sanic-org/sanic/blob/master/CONTRIBUTING.rst)をご覧ください。 -## 私たちは誰 +## 我々について - + -[構築ステータス]: https://travis-ci.com/sanic-org/sanic.svg?branch=master +[1]: https://travis-ci.com/sanic-org/sanic.svg?branch=master [1]: https://travis-ci.com/sanic-org/sanic -[AppVeyorビルドステータス]: https://ci.appveyor.com/api/projects/status/d8pt3ids0ynexi8c/branch/master?svg=true +[3]: https://ci.appveyor.com/api/projects/status/d8pt3ids0ynexi8c/branch/master?svg=true [2]: https://ci.appveyor.com/project/sanic-org/sanic -[Codecov]: https://codecov.io/gh/sanic-org/sanic/branch/master/graph/badge.svg [3]: https://codecov.io/gh/sanic-org/sanic -[ドキュメンテーション]: https://readthedocs.org/projects/sanic/badge/?version=latest [4]: http://sanic.readthedocs.io/en/latest/?badge=latest -[PyPI]: https://img.shields.io/pypi/v/sanic.svg +[7]: https://img.shields.io/pypi/v/sanic.svg [5]: https://pypi.python.org/pypi/sanic/ -[PyPI version]: https://img.shields.io/pypi/pyversions/sanic.svg -[PyPI Wheel]: https://img.shields.io/pypi/wheel/sanic.svg -[6]: https://pypi.python.org/pypi/sanic -[Supported implementations]: https://img.shields.io/pypi/implementation/sanic.svg -[Code style black]: https://img.shields.io/badge/code%20style-black-000000.svg -[7]: https://github.com/ambv/black -[Forums]: https://img.shields.io/badge/forums-community-ff0068.svg -[8]: https://community.sanicframework.org/ -[Discord]: https://img.shields.io/discord/812221182594121728?logo=discord -[9]: https://discord.gg/FARQzAEMAA -[Awesome Sanic List]: https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg -[10]: https://github.com/mekicha/awesome-sanic -[Downloads]: https://pepy.tech/badge/sanic/month -[11]: https://pepy.tech/project/sanic -[12]: https://pepy.tech/badge/sanic/week | -| ドキュメント | [![Documentation]][4] | -| パッケージ | [![PyPI][]][5] [![PyPI version][]][5] [![PyPI Wheel][]][6] [![Supported implementations][]][6] [![Code style black]][7] | -| サポート | [![Forums][]][8] [![Discord][]][9] [![Awesome Sanic List]][10] | -| ステータス | [![Downloads][]][11] [![Downloads][12]][11] | - -## これは何? - -まず第一に、水に飛び込む前に、サニックは他のフレームワークとは異なることを知っておく必要があります。 - -その最初の文では、Sanicは**フレームワーク**と**ウェブサーバー**の両方であるため、大きな間違いがあります。展開セクションでは、これについてもう少し話します。 - -しかし、箱から出してすぐにSanicには、本番グレードのWebアプリケーションの作成、展開、拡張に必要なものがすべて付属していることを忘れないでください。:rocket: - -## Goal - -> 構築、拡張、最終的に拡張が簡単な高性能HTTPサーバーを起動して実行する簡単な方法を提供する。 -## Features - ----:1 - -- 速いウェブサーバーを構築する -- 生産準備完了 -- 非常にスケーラブル -- ASGI準拠 -- シンプルで直感的なAPIデザイン -- コミュニティによって、コミュニティのために -:--:1 - -:--- - - - -## スポンサー - -確認 [open collective](https://opencollective.com/sanic-org) to learn more about helping to fund Sanic. - - -## コミュニティーに参加 - -議論のメインチャンネルは[コミュニティフォーラム](https://community.sanicframework.org/)にあります。ライブディスカッションとチャット用の[Discord Server](https://discord.gg/FARQzAEMAA)もあります。 - -Stackoverflow `[sanic]`タグは、プロジェクトのメンテナによって[積極的に監視](https://stackoverflow.com/questions/tagged/sanic)です。 - -## Contribution - -私たちは常に新しい貢献を喜んでいます。私たちは[始めるために探している人のために良いマークされた問題](https://github.com/sanic-org/sanic/issues?q=is%3Aopen+is%3Aissue+label%3Abeginner)、ようこそ[フォーラムの質問/回答/ディスカッション](https://community.sanicframework.org/)。[貢献ガイドライン](https://github.com/sanic-org/sanic/blob/master/CONTRIBUTING.rst)をご覧ください。 - -[構築ステータス]: https://travis-ci.com/sanic-org/sanic.svg?branch=master -[1]: https://travis-ci.com/sanic-org/sanic -[AppVeyorビルドステータス]: https://ci.appveyor.com/api/projects/status/d8pt3ids0ynexi8c/branch/master?svg=true -[2]: https://ci.appveyor.com/project/sanic-org/sanic -[Codecov]: https://codecov.io/gh/sanic-org/sanic/branch/master/graph/badge.svg -[3]: https://codecov.io/gh/sanic-org/sanic -[ドキュメンテーション]: https://readthedocs.org/projects/sanic/badge/?version=latest -[4]: http://sanic.readthedocs.io/en/latest/?badge=latest -[PyPI]: https://img.shields.io/pypi/v/sanic.svg +[9]: https://img.shields.io/pypi/pyversions/sanic.svg [5]: https://pypi.python.org/pypi/sanic/ -[PyPI version]: https://img.shields.io/pypi/pyversions/sanic.svg -[PyPI Wheel]: https://img.shields.io/pypi/wheel/sanic.svg +[11]: https://img.shields.io/pypi/wheel/sanic.svg +[6]: https://pypi.python.org/pypi/sanic +[13]: https://img.shields.io/pypi/implementation/sanic.svg [6]: https://pypi.python.org/pypi/sanic -[Supported implementations]: https://img.shields.io/pypi/implementation/sanic.svg -[Code style black]: https://img.shields.io/badge/code%20style-black-000000.svg [7]: https://github.com/ambv/black -[Forums]: https://img.shields.io/badge/forums-community-ff0068.svg +[16]: https://img.shields.io/badge/forums-community-ff0068.svg [8]: https://community.sanicframework.org/ -[Discord]: https://img.shields.io/discord/812221182594121728?logo=discord +[18]: https://img.shields.io/discord/812221182594121728?logo=discord [9]: https://discord.gg/FARQzAEMAA -[Awesome Sanic List]: https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg [10]: https://github.com/mekicha/awesome-sanic -[Downloads]: https://pepy.tech/badge/sanic/month +[21]: https://pepy.tech/badge/sanic/month +[11]: https://pepy.tech/project/sanic +[23]: https://pepy.tech/badge/sanic/week [11]: https://pepy.tech/project/sanic -[12]: https://pepy.tech/badge/sanic/week diff --git a/src/ja/guide/advanced/class-based-views.md b/src/ja/guide/advanced/class-based-views.md index 773a6652cf..ebda613566 100644 --- a/src/ja/guide/advanced/class-based-views.md +++ b/src/ja/guide/advanced/class-based-views.md @@ -8,8 +8,7 @@ APIを設計する際の一般的なパターンは、HTTPメソッドに依存する同じエンドポイントに複数の機能を持つことです。 -これらのオプションは両方とも機能しますが、優れた設計慣行ではなく、プロジェクトが成長するにつれて時間の経過とともに維持するのが難しい場合があります。 -:--:1 +これらのオプションは両方とも機能しますが、優れたデザインプラクティスではなく、プロジェクトが成長するにつれて時間の経過とともに維持するのが難しい場合があります。 :--:1 ```python @app.get("/foo") async def foo_get(request): @@ -38,18 +37,17 @@ async def bar(request): ### 解決策 -クラスベースのビューは、単に要求に対する応答動作を実装するクラスです。これらは、同じエンドポイントで異なるHTTPリクエストタイプの処理を区分する方法を提供します。 -:--:1 +クラスベースのビューは、要求に対する応答動作を実装するシンプルなクラスです。 これらは、同じエンドポイントで異なるHTTPリクエストタイプの処理を区分する方法を提供します。 :--:1 ```python from sanic.views import HTTPMethodView class FooBar(HTTPMethodView): async def get(self, request): ... - + async def post(self, request): ... - + async def put(self, request): ... @@ -59,13 +57,13 @@ app.add_route(FooBar.as_view(), "/foobar") ## ビューの定義 -クラスベースのビューは、`HTTPMethodView` をサブクラス化する必要があります。その後、対応するHTTPメソッドの名前でクラスメソッドを実装できます。定義されたメソッドを持たない要求を受信すると、`405: Method not allowed` 応答が生成されます。 +クラスベースのビューは、`HTTPMethodView` のサブクラスである必要があります。 その後、対応するHTTPメソッドの名前でクラスメソッドを実装できます。 定義されたメソッドを持たない要求を受信すると、`405: Method not allowed` 応答が生成されます。 ---:1 -エンドポイントにクラスベースのビューを登録するには、`app.add_route`メソッドが使用されます。最初の引数は、メソッド `as_view` が呼び出された定義されたクラスで、2 番目の引数は URL エンドポイントである必要があります。 +エンドポイントにクラスベースのビューを登録するために、 `app.add_route` メソッドが使用できます。 最初の引数は、メソッド `as_view` が呼び出された定義済みクラスで、2番目の引数はURLエンドポイントである必要があります。 -利用可能な方法は: +利用可能なメソッドは: - get - post @@ -73,8 +71,7 @@ app.add_route(FooBar.as_view(), "/foobar") - patch - delete - head -- options -:--:1 +- options :--:1 ```python from sanic.views import HTTPMethodView from sanic.response import text @@ -84,7 +81,7 @@ class SimpleView(HTTPMethodView): def get(self, request): return text("I am get method") - # You can also use async syntax + # async構文も利用可能 async def post(self, request): return text("I am post method") @@ -105,8 +102,7 @@ app.add_route(SimpleView.as_view(), "/") ---:1 -[ルーティングセクション](/guide/basics/routing.md)で説明されているとおりにパスパラメータを使用できます。 -:--:1 +[ルーティングセクション](/guide/basics/routing.md)で説明されているとおりにパスパラメータを使用できます。 :--:1 ```python class NameView(HTTPMethodView): @@ -119,7 +115,7 @@ app.add_route(NameView.as_view(), "/") ## デコレーター -[デコレータセクション](/guide/best-practices/decorators.md)で説明したように、デコレータを使用してエンドポイントに機能を追加する必要があります。 あなたはCBVと二つ選択肢があります。 +[デコレータセクション](/guide/best-practices/decorators.md)で説明したように、デコレータを使用してエンドポイントに機能を追加する必要があることがあります。 CBVによる二つの選択肢があります: 1. ビュー内の _全ての_ HTTPメソッドに適用する 2. ビュー内のHTTPメソッドに個別に適用する @@ -128,19 +124,18 @@ app.add_route(NameView.as_view(), "/") ---:1 -### すべての方法に適用する +### すべてのメソッドに適用する -クラスにデコレータを追加する場合は、`デコレータ`クラス変数を設定できます。これらは、`as_view`が呼び出されるとクラスに適用されます。 -:--:1 +クラスにデコレータを追加する場合は、 `decorators` クラス変数を設定できます。 これらは、 `as_view` が呼び出されるとクラスに適用されます。 :--:1 ```python class ViewWithDecorator(HTTPMethodView): decorators = [some_decorator_here] def get(self, request, name): - return text("Hello I have a decorator") + return text("やあ、デコレーター持ってるよ") def post(self, request, name): - return text("Hello I also have a decorator") + return text("やあ、僕もデコレーター持ってるよ") app.add_route(ViewWithDecorator.as_view(), "/url") ``` @@ -148,10 +143,9 @@ app.add_route(ViewWithDecorator.as_view(), "/url") ---:1 -### 個々の方法に適用する +### 個々のメソッドに適用する -しかし、すべてのメソッドではなく、いくつかのメソッドを飾りたい場合は、ここに示すようにできます。 -:--:1 +しかし、すべてのメソッドではなく、いくつかのメソッドを飾りたい場合は、ここに示すように使うことができます。 :--:1 ```python class ViewWithSomeDecorator(HTTPMethodView): @@ -161,7 +155,7 @@ class ViewWithSomeDecorator(HTTPMethodView): return text("Hello I have a decorator") def post(self, request, name): - return text("Hello I don"t have any decorators") + return text("Hello I do not have any decorators") @some_decorator_here def patch(self, request, name): @@ -169,11 +163,10 @@ class ViewWithSomeDecorator(HTTPMethodView): ``` :--- -## URLを作成 +## URLの生成 ---:1 -これは、クラス名がエンドポイントの一部であることを除いて、[他のURLの生成](/guide/basics/routing.md#generating-a-url)と同じように機能します。 -:--:1 +これは、クラス名がエンドポイントの一部であることを除いて、 [他のURLの生成](/guide/basics/routing.md#generating-a-url) と同じように機能します。 :--:1 ```python @app.route("/") def index(request): @@ -183,7 +176,7 @@ def index(request): class SpecialClassView(HTTPMethodView): def get(self, request): - return text("Hello from the Special Class View!") + return text("Special Class Viewからこんにちは!") app.add_route(SpecialClassView.as_view(), "/special_class_view") diff --git a/src/ja/guide/advanced/proxy-headers.md b/src/ja/guide/advanced/proxy-headers.md index 9848992f01..9dab8f2bfd 100644 --- a/src/ja/guide/advanced/proxy-headers.md +++ b/src/ja/guide/advanced/proxy-headers.md @@ -1,11 +1,11 @@ -# プロキシ構成 +# プロキシ設定 -リバースプロキシサーバー(nginxなど)を使用する場合、`request.ip`の値にはプロキシのIP(通常は`127.0.0.1`が含まれます。ほとんどの場合、これはあなたが望むものではありません。 +リバースプロキシサーバー(nginxなど)を使用する場合、 `request.ip` の値にはプロキシのIP(通常は `127.0.0.1` )が含まれます。 ほとんどの場合、これはあなたが望むもの **ではありません** 。 -Sanicは、`request.remote_addr`として利用可能な真のクライアントIPを決定するためにプロキシヘッダーを使用するように構成できます。完全な外部URLは、ヘッダーフィールド_if available_からも構築されます。 +Sanicは、 `request.remote_addr` として利用可能な本当のクライアントIPを決定するためにプロキシヘッダーを使用するように設定できます。 完全な外部URLは、ヘッダーフィールド _if available_ からも構築されます。 ::: ヒント 注意 -適切な予防措置がなければ、悪意のあるクライアントはプロキシヘッダーを使用して独自のIPを偽装することができます。このような問題を回避するために、Sanicは明示的に有効になっていない限り、プロキシヘッダーを使用しません。 +適切な予防措置がなければ、悪意のあるクライアントはプロキシヘッダーを使用して独自のIPを偽装することができます。 このような問題を回避するために、Sanicは明示的に有効になっていない限り、プロキシヘッダーを使用しません。 ::: ---:1 @@ -14,42 +14,37 @@ Sanicは、`request.remote_addr`として利用可能な真のクライアント - `FORWARDED_SECRET` - `REAL_IP_HEADER` -- `PROXIES_COUNT` -:--:1 +- `PROXIES_COUNT` :--:1 ```python -app.config.FORWARDED_SECRET = "super-duper-secret" -app.config.REAL_IP_HEADER = "CF-Connecting-IP" +app.config.FORWARDED_SECRET = "めっちゃくちゃのシークレット" +app.config.REAL_IP_HEADER = "CF接続IP" app.config.PROXIES_COUNT = 2 ``` :--- -## 転送されたヘッダー - -`Forwarded`ヘッダーを使用するには、信頼できるプロキシサーバーに知られている値に`app.config.FORWARDED_SECRET`を設定する必要があります。この秘密は、特定のプロキシサーバーを安全に識別するために使用されます。 +## Forwarded ヘッダー -29 +`Forwarded` ヘッダーを使用するには、信頼できるプロキシサーバーに設定されている値を`app.config.FORWARDED_SECRET`に設定する必要があります。 このシークレットは、特定のプロキシサーバーを安全に識別するために使用されます。 -30 +Sanicはシークレットキーのない要素を無視し、シークレットが設定されていない場合はヘッダーを解析することもありません。 -Sanicは秘密鍵のない要素を無視し、秘密が設定されていない場合、ヘッダーを解析することさえしません。 +信頼された転送要素が見つかると、他のすべてのプロキシヘッダは無視されます。クライアントに関する完全な情報をすでに運んでいるからです。 -他のすべてのプロキシヘッダーは、クライアントに関する完全な情報をすでに持っているため、信頼できる転送された要素が見つかると無視されます。 - -`Forwarded`ヘッダーの詳細については、関連する[MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded)および[Nginx](https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/)の記事をお読みください。 +`Forwarded` ヘッダーの詳細については、関連する [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded) および [Nginx](https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/) の記事を参照してください。 ## 従来のプロキシヘッダー -### IP-Header +### IP ヘッダー -プロキシが既知のヘッダーのIPアドレスを転送すると、「REAL_IP_HEADER」設定値でそれが何であるかをSanicに伝えることができます。 +プロキシが既知のヘッダーにIPアドレスを転送するとき `REAL_IP_HEADER` の値を指定すると、Sanicにそれが何かを教えることができます。 ### X-Forwarded-For -このヘッダーには、通常、プロキシの各レイヤーを介したIPアドレスのチェーンが含まれています。`PROXIES_COUNT`を設定すると、クライアントの実際のIPアドレスを取得する深さがSanicに指示されます。この値は、チェーン内のIPアドレスの_expected_数に等しいはずです。 +このヘッダーには、通常、プロキシの各レイヤーを介したIPアドレスのチェーンが含まれています。 `PROXIES_COUNT`を設定すると、クライアントの実際のIPアドレスを取得する深さがSanicに指示されます。 この値は、チェーン内のIPアドレスの _期待される_ 数に等しいはずです。 -### Other X-headers +### その他のXヘッダー -クライアントIPが次のいずれかの方法で見つかった場合、SanicはURL部分に次のヘッダーを使用します。 +クライアントIPがこれらのメソッドのいずれかで見つかった場合、Sanicは以下のURLパーツのヘッダを使用します: - x-forwarded-proto - x-forwarded-host @@ -57,9 +52,9 @@ Sanicは秘密鍵のない要素を無視し、秘密が設定されていない - x-forwarded-path - x-scheme -## 例えば +## 例 -次の例では、すべての要求はエンドポイントが次のようになります。 +以下の例では、すべてのリクエストはエンドポイントが次のようになると仮定します。 ```python @app.route("/fwd") async def forwarded(request): @@ -76,8 +71,8 @@ async def forwarded(request): ---:1 --- -##### 例えば 1 -FORWARDED_SECRETが設定されていない場合、xヘッダーは尊重されるべきです +##### 例1 +FORWARDED_SECRETが設定されていない場合、Xヘッダは尊重されます。 ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" @@ -92,7 +87,7 @@ $ curl localhost:8000/fwd \ ``` :--:1 ```bash -# curl response +# curlの応答 { "remote_addr": "127.0.0.2", "scheme": "ws", @@ -108,8 +103,8 @@ $ curl localhost:8000/fwd \ --- ---:1 -##### 例えば 2 -FORWARDED_SECRETが設定されました +##### 例2 +FORWARDED_SECRETが設定された場合 ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" @@ -125,7 +120,7 @@ $ curl localhost:8000/fwd \ ``` :--:1 ```bash -# curl response +# curlの応答 { "remote_addr": "[::2]", "scheme": "https", @@ -144,8 +139,8 @@ $ curl localhost:8000/fwd \ --- ---:1 -##### 例えば 3 -空の転送ヘッダー - > Xヘッダーを使用する +##### 例3 +空の転送ヘッダー -> Xヘッダーを使用する ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" @@ -160,7 +155,7 @@ $ curl localhost:8000/fwd \ ``` :--:1 ```bash -# curl response +# curlの応答 { "remote_addr": "127.0.0.2", "scheme": "ws", @@ -176,8 +171,8 @@ $ curl localhost:8000/fwd \ --- ---:1 -##### Example 4 -Header present but not matching anything +##### 例4 +ヘッダーが存在しますが、何も一致しない場合。 ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" @@ -203,8 +198,8 @@ $ curl localhost:8000/fwd \ --- ---:1 -##### Example 5 -Forwarded header present but no matching secret -> use X-headers +##### 例5 +Forwarded ヘッダーは存在するが、一致するシークレットはない場合 -> Xヘッダを使用 ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" @@ -217,7 +212,7 @@ $ curl localhost:8000/fwd \ ``` :--:1 ```bash -# curl response +# curlの応答 { "remote_addr": "127.0.0.2", "scheme": "http", @@ -232,8 +227,8 @@ $ curl localhost:8000/fwd \ --- ---:1 -##### Example 6 -Different formatting and hitting both ends of the header +##### 例6 +フォーマットが異なり、ヘッダーの両端がヒットする場合 ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" @@ -245,7 +240,7 @@ $ curl localhost:8000/fwd \ ``` :--:1 ```bash -# curl response +# curlの応答 { "remote_addr": "127.0.0.4", "scheme": "http", @@ -262,8 +257,8 @@ $ curl localhost:8000/fwd \ --- ---:1 -##### Example 7 -Test escapes (modify this if you see anyone implementing quoted-pairs) +##### 例7 +エスケープをテストする場合(quoted-pairsを実装していたら修正する)。 ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" @@ -275,7 +270,7 @@ $ curl localhost:8000/fwd \ ``` :--:1 ```bash -# curl response +# curlの応答 { "remote_addr": "test", "scheme": "http", @@ -292,8 +287,8 @@ $ curl localhost:8000/fwd \ --- ---:1 -##### Example 8 -Secret insulated by malformed field #1 +##### 例8 +不正なフィールドによってシークレットが隔離される例 #1 ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" @@ -305,7 +300,7 @@ $ curl localhost:8000/fwd \ ``` :--:1 ```bash -# curl response +# curlの応答 { "remote_addr": "test", "scheme": "http", @@ -321,8 +316,8 @@ $ curl localhost:8000/fwd \ --- ---:1 -##### Example 9 -Secret insulated by malformed field #2 +##### 例9 +不正なフィールドによってシークレットが隔離される例 #2 ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" @@ -334,7 +329,7 @@ $ curl localhost:8000/fwd \ ``` :--:1 ```bash -# curl response +# curlの応答 { "remote_addr": "", "scheme": "wss", @@ -350,8 +345,8 @@ $ curl localhost:8000/fwd \ --- ---:1 -##### Example 10 -Unexpected termination should not lose existing acceptable values +##### 例10 +予期しない終了で、既存の許容可能な値を失うことはありません。 ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" @@ -363,7 +358,7 @@ $ curl localhost:8000/fwd \ ``` :--:1 ```bash -# curl response +# curlの応答 { "remote_addr": "", "scheme": "wss", @@ -379,8 +374,8 @@ $ curl localhost:8000/fwd \ --- ---:1 -##### Example 11 -Field normalization +##### 例11 +フィールドの正規化 ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" @@ -392,7 +387,7 @@ $ curl localhost:8000/fwd \ ``` :--:1 ```bash -# curl response +# curlの応答 { "remote_addr": "", "scheme": "wss", @@ -411,8 +406,8 @@ $ curl localhost:8000/fwd \ --- ---:1 -##### Example 12 -Using "by" field as secret +##### 例12 +"by"フィールドをシークレットとして使用する場合。 ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" @@ -424,7 +419,7 @@ $ curl localhost:8000/fwd \ ``` :--:1 ```bash -# curl response +# curlの応答 { "remote_addr": "1.2.3.4", "scheme": "http", diff --git a/src/ja/guide/advanced/signals.md b/src/ja/guide/advanced/signals.md index 359f64a5a4..5a1d30a35a 100644 --- a/src/ja/guide/advanced/signals.md +++ b/src/ja/guide/advanced/signals.md @@ -1,4 +1,4 @@ -# Signals +# シグナル シグナルは、アプリケーションのある部分が別の部分に何かが起こったことを伝える方法を提供します。 @@ -16,11 +16,9 @@ async def handle_registration(request): }) ``` -## signalを追加する +## シグナルを追加する ----:1 -信号を追加するためのAPIは、ルートの追加と非常によく似ています。 -:--:1 +---:1 シグナルを追加するためのAPIは、ルートの追加と非常によく似ています。 :--:1 ```python async def my_signal_handler(): print("something happened") @@ -29,104 +27,110 @@ app.add_signal(my_signal_handler, "something.happened.ohmy") ``` :--- ----:1 -ただし、おそらくもう少し便利な方法は、組み込みのデコレータを使用することです。 -:--:1 +---:1 ただし、おそらくもう少し便利な方法は、組み込みのデコレータを使用することです。 :--:1 ```python @app.signal("something.happened.ohmy") async def my_signal_handler(): - print("something happened") + print("何かが起こった") ``` :--- ----:1 -信号は設計図で宣言することもできます -:--:1 +---:1 シグナルに条件(conditions)が必要な場合は、ハンドラを追加する際に必ず追加してください。 :--:1 +```python +async def my_signal_handler1(): + print("何かが起こった") + +app.add_signal( + my_signal_handler, + "something.happened.ohmy1", + conditions={"some_condition": "value"} +) + +@app.signal("something.happened.ohmy2", conditions={"some_condition": "value"}) +async def my_signal_handler2(): + print("何かが起こった") +``` +:--- + +---:1 シグナルはblueprintsで宣言することもできます。 :--:1 ```python bp = Blueprint("foo") @bp.signal("something.happened.ohmy") async def my_signal_handler(): - print("something happened") + print("何かが起こった") ``` :--- -## signalsを設計する - -新しい信号を作成することに加えて、Sanic自体からディスパッチされる組み込み信号がいくつかあります。これらの信号は、開発者に要求とサーバーのライフサイクルに機能を追加する機会を増やすために存在します。 +## ビルトインシグナル ----:1 -新しいシグナルを作成することに加えて、Sanic自体からディスパッチされる多くの組み込みシグナルがあります。これらのシグナルは、開発者がリクエストとサーバーのライフサイクルに機能を追加する機会を増やすために存在します。 +新しいシグナルを作成することに加えて、Sanic自体からディスパッチされる組み込み信号がいくつかあります。 これらの信号は、開発者に要求とサーバーのライフサイクルに機能を追加する機会を増やすために存在します。 -他のシグナルと同じように、アプリケーションまたはブループリントインスタンスにアタッチできます。 - -:--:1 +*Added in v21.9* +---:1 You can attach them just like any other signal to an application or blueprint instance. :--:1 ```python @app.signal("http.lifecycle.complete") async def my_signal_handler(conn_info): - print("Connection has been closed") + print("接続が閉じた") ``` :--- これらの信号は、ハンドラーが取る引数、およびアタッチする条件(存在する場合)とともに、利用可能な信号です。 -| イベント名 | 変数 | 条件 | -| -------------------------- | ------------------------------- | --------------------------------------------------------- | -| `http.routing.before` | request | | -| `http.routing.after` | request, route, kwargs, handler | | -| `http.lifecycle.begin` | conn_info | | -| `http.lifecycle.read_head` | head | | -| `http.lifecycle.request` | request | | -| `http.lifecycle.handle` | request | | -| `http.lifecycle.read_body` | body | | -| `http.lifecycle.exception` | request, exception | | -| `http.lifecycle.response` | request, response | | -| `http.lifecycle.send` | data | | -| `http.lifecycle.complete` | conn_info | | -| `http.middleware.before` | request, response | `{"attach_to": "request"}` or `{"attach_to": "response"}` | -| `http.middleware.after` | request, response | `{"attach_to": "request"}` or `{"attach_to": "response"}` | -| `server.init.before` | app, loop | | -| `server.init.after` | app, loop | | -| `server.shutdown.before` | app, loop | | -| `server.shutdown.after` | app, loop | | - -::: new NEW in v21.12 ----:1 -ビルトインシグナルを使いやすくするために、許可されたビルトインをすべて含む `Enum` オブジェクトが用意されています。最近の IDE では、イベント名の完全なリストを文字列として覚えておく必要がないので、これは便利です。 -:--:1 +| イベント名 | 変数 | 条件 | +| ------------------------ | ------------------------------- | ---------------------------------------------------------- | +| `http.routing.before` | request | | +| `http.routing.after` | request, route, kwargs, handler | | +| `http.handler.before` | request | | +| `http.handler.after` | request | | +| `http.lifycle.begin` | conn_info | | +| `http.lifycle.read_head` | head | | +| `http.lifycle.request` | request | | +| `http.lifycle.handle` | request | | +| `http.lifycle.read_body` | body | | +| `http.lifycle.exception` | request, exception | | +| `http.lifycle.response` | request, response | | +| `http.lifycle.send` | data | | +| `http.lifycle.complete` | conn_info | | +| `http.middleware.before` | request, response | `{"attach_to": "request"}` または `{"attach_to": "response"}` | +| `http.middleware.after` | request, response | `{"attach_to": "request"}` または `{"attach_to": "response"}` | +| `server.init.before` | app, loop | | +| `server.init.after` | app, loop | | +| `server.shutdown.before` | app, loop | | +| `server.shutdown.after` | app, loop | | + +Version 22.9 added `http.handler.before` and `http.handler.after`. + +::: new NEW in v21.12 ---:1 ビルトインシグナルを使いやすくするために、許可されたビルトインをすべて含む `Enum` オブジェクトが用意されています。 最近の IDE では、イベント名の完全なリストを文字列として覚えておく必要がないので、これは便利です。 :--:1 + +*Added in v21.12* :--:1 ```python from sanic.signals import Event @app.signal(Event.HTTP_LIFECYCLE_COMPLETE) async def my_signal_handler(conn_info): - print("Connection has been closed") + print("接続が閉じた") ``` :--- ## イベント ----:1 -信号は_event_に基づいています。イベントは、単に次のパターンの文字列です。 -:--:1 +---:1 信号は_event_に基づいています。 イベントは、単に次のパターンの文字列です。 :--:1 ``` namespace.reference.action ``` :--- -::: 次に -イベントには3つの部分が必要です。何を使うべきかわからない場合は、次のパターンを試してください。 +::: 次に イベントには3つの部分が必要です。 何を使うべきかわからない場合は、次のパターンを試してください。 -- `my_app.something.happened` -- `sanic.notice.hello` -::: +- `my_app.something.happen` +- `sanic.notice.hello` ::: ### イベントパラメータ ----:1 -イベントは「動的」であり、[pathパラメータ](../basics/routing.md#path-parameters)と同じ構文を使用して宣言できます。これにより、任意の値に基づいてマッチングできます。 -:--:1 +---:1 イベントは「動的」であり、[pathパラメータ](../basics/routing.md#path-parameters)と同じ構文を使用して宣言できます。 これにより、任意の値に基づいてマッチングできます。 :--:1 :--:1 ```python @app.signal("foo.bar.") async def signal_handler(thing): @@ -135,32 +139,26 @@ async def signal_handler(thing): @app.get("/") async def trigger(request): await app.dispatch("foo.bar.baz") - return response.text("Done.") + return response.text("終了。") ``` :--- -許可された型定義の詳細については、[pathパラメータ](../basics/routing.md#path-parameters)をチェックしてください。 +---:1 シグナルハンドラの実行に加えて、アプリケーションはイベントがトリガーされるのを待つことができます。 :--:1 -::: 注意 -イベントの3番目の部分(「アクション」)のみが動的です。 +::: 注意 イベントの3番目の部分(「アクション」)のみが動的です。 - `foo.bar.` :ok: -- `foo..baz` :x: -::: +- `foo..baz` :x: ::: ### 待つ ----:1 -シグナルハンドラの実行に加えて、アプリケーションはイベントがトリガーされるのを待つことができます。 -:--:1 +---:1 アプリケーションは、シグナルハンドラを実行するだけでなく、イベントがトリガーされるのを待つこともできます。 :--:1 ```python await app.event("foo.bar.baz") ``` :--- ----:1 -**大事**: 待つことはブロッキング機能です。したがって、これを[バックグラウンドタスク](../basics/tasks.md)で実行する必要があります。 -:--:1 +---:1 **重要**: 待つことはブロッキング機能です。 したがって、これを[バックグラウンドタスク](../basics/tasks.md)で実行する必要があります。 :--:1 ```python async def wait_for_event(app): while True: @@ -174,9 +172,7 @@ async def after_server_start(app, loop): ``` :--- ----:1 -イベントが動的パスで定義されている場合は、`*`を使用して任意のアクションをキャッチできます。 -:--:1 +---:1 イベントが動的パスで定義されている場合は、`*`を使用して任意のアクションをキャッチできます。 :--:1 ```python @app.signal("foo.bar.") @@ -186,16 +182,14 @@ await app.event("foo.bar.*") ``` :--- -## Dispatching +## ディスパッチ *将来的には、Sanicは開発者がライフサイクルイベントに参加するのを支援するために、いくつかのイベントを自動的にディスパッチします。* ----:1 -イベントをDispatchすると、2つのことを行います。 +---:1 イベントをディスパッチすると、2つのことを行います。 1. イベントで定義されたシグナルハンドラを実行し、 -2. イベントが完了するまで「待っている」ことをすべて解決します。 -:--:1 +2. イベントが完了するまで「待っている」ことをすべて解決します。 :--:1 ```python @app.signal("foo.bar.") async def foo_bar(thing): @@ -208,11 +202,9 @@ thing=baz ``` :--- -### Context +### コンテキスト ----:1 -場合によっては、信号ハンドラに余分な情報を渡す必要があるかもしれません。上記の最初の例では、電子メール登録プロセスにユーザーの電子メールアドレスを指定してほしかった。 -:--:1 +---:1 場合によっては、信号ハンドラに余分な情報を渡す必要があるかもしれません。 例として最初に挙げられるのは、電子メール登録プロセスにユーザーの電子メールアドレスを指定してほしい場合です。 :--:1 ```python @app.signal("user.registration.created") async def send_registration_email(**context): @@ -229,16 +221,14 @@ await app.dispatch( :--- ::: tip FYI -信号はバックグラウンドタスクでディスパッチされます。 +シグナルはバックグラウンドタスクでディスパッチされます。 ::: ### Blueprints -青写真信号のdispatchは、[middleware](../basics/middleware.md)と同様に機能します。アプリレベルから行われるものは、青写真まで流れ落ちます。ただし、青写真でディスパッチすると、その青写真で定義されている信号のみが実行されます。 +Blueprintシグナルのディスパッチは、[middleware](../basics/middleware.md)と同様に機能します。 アプリレベルから行われるものは、blueprintまで流れ落ちます。 ただし、blueprintでディスパッチすると、そのblueprintで定義されているシグナルのみが実行されます。 ----:1 -おそらく、例は説明しやすいです: -:--:1 +---:1 おそらく例は説明しやすいでしょう: :--:1 ```python bp = Blueprint("bp") @@ -257,9 +247,7 @@ def bp_signal(): ``` :--- ----:1 -`app.dispatch("foo.bar.baz")`を実行すると、両方の信号が実行されます。 -:--:1 +---:1 `app.dispatch("foo.bar.baz")`を実行すると、両方のシグナルが実行されます。 :--:1 ```python await app.dispatch("foo.bar.baz") assert app_counter == 1 @@ -267,9 +255,7 @@ assert bp_counter == 1 ``` :--- ----:1 -`bp.dispatch("foo.bar.baz")`を実行すると、ブループリント信号のみが実行されます。 -:--:1 +---:1 `bp.dispatch("foo.bar.baz")`を実行すると、ブループリント信号のみが実行されます。 :--:1 ```python await bp.dispatch("foo.bar.baz") assert app_counter == 1 diff --git a/src/ja/guide/advanced/streaming.md b/src/ja/guide/advanced/streaming.md index 96cc648296..e3d7925162 100644 --- a/src/ja/guide/advanced/streaming.md +++ b/src/ja/guide/advanced/streaming.md @@ -1,6 +1,6 @@ -# Streaming +# ストリーミング -## Request streaming +## ストリーミングのリクエスト Sanicを使用すると、クライアントから送信されたデータをストリーミングして、バイトが到着するとデータの処理を開始できます。 @@ -8,8 +8,7 @@ Sanicを使用すると、クライアントから送信されたデータをス エンドポイントで有効にすると、`await request.stream.read()`を使用してリクエストボディをストリーミングできます。 -そのメソッドは、本文が完了すると「なし」を返します。 -:--:1 +そのメソッドは、ボディが完了すると `None` を返します。 :--:1 ```python from sanic.views import stream @@ -28,8 +27,7 @@ class SimpleView(HTTPMethodView): ---:1 -また、デコレータのキーワード引数で有効にすることもできます。 -:--:1 +また、デコレータのキーワード引数で有効にすることもできます。 :--:1 ```python @app.post("/stream", stream=True) async def handler(request): @@ -41,8 +39,7 @@ async def handler(request): ---:1 -... or the `add_route()` method. -:--:1 +または `add_route()` メソッドを使う。 :--:1 ```python bp.add_route( bp_handler, @@ -54,17 +51,13 @@ bp.add_route( :--- ::: tip FYI -Post、put、patchデコレータのみがストリーム引数を持っています。 +post、put、patchデコレータのみがストリーム引数を持っています。 ::: -## Response streaming - ----:1 +## ストリーミングの応答 -Sanicを使用すると、「StreamingHTTPResponse」のインスタンスを使用してクライアントにコンテンツをストリーミングできます。`sanic.response.stream`コンビニエンスメソッドもあります。 +---:1 Sanicではクライアントにコンテンツをストリーミングできます。 :--:1 -このメソッドは、クライアントへの書き込みを制御できるオブジェクトを渡されるコルーチンコールバックを受け入れます。 -:--:1 ```python from sanic.response import stream @@ -78,7 +71,7 @@ async def test(request): ``` :--- -これは、データベースなどの外部サービスに由来するクライアントにコンテンツをストリーミングする場合に便利です。たとえば、`asyncpg`が提供する非同期カーソルを使用して、データベースレコードをクライアントにストリーミングできます。 +これは、データベースなどの外部サービスに由来するクライアントにコンテンツをストリーミングする場合に便利です。 たとえば、`asyncpg`が提供する非同期カーソルを使用して、データベースレコードをクライアントにストリーミングできます。 ```python @app.route("/") @@ -92,37 +85,19 @@ async def index(request): return stream(stream_from_db) ``` -::: tip FYI -クライアントが HTTP/1.1 をサポートしている場合、Sanic は [チャンク転送エンコーディング](https://en.wikipedia.org/wiki/Chunked_transfer_encoding) を使用します。ストリーム関数のチャンクオプションを使用して明示的に有効または無効にできます。 -::: - ----:1 -`stream`を使用したコルーチンコールバックパターンは必要ありません。これはストリーミングの*古いスタイル*であり、新しいインラインストリーミングに置き換える必要があります。これで、ハンドラーで応答を直接ストリーミングできるようになりました。 -:--:1 -```python -@app.route("/") -async def test(request): - response = await request.respond(content_type="text/csv") - await response.send("foo,") - await response.send("bar") - await response.eof() - return response -``` -:--- +`await response.eof()` を呼び出すことで、ストリームを明示的に終了させることができます。 上記の例では、`await response.eof()` は `await response.send("", True)` を置き換える便利なメソッドとして呼び出されます。 ハンドラがクライアントに送り返すものが何も残っていないと判断した場合、** 1回** *after*を呼び出す必要があります。 Sanicサーバで使用するのは*任意*ですが、SanicをASGIモードで動作させている場合は、**必ず**明示的にストリームを終了させなければなりません。 -上記の例では、`await response.eof()` は `await response.send("", True)` を置き換える便利なメソッドとして呼び出されます。ハンドラがクライアントに送り返すものが何も残っていないと判断した場合、** 1回** *after*を呼び出す必要があります。 +*Calling `eof` became optional in v21.6* - -## File streaming +## ファイルストリーミング ---:1 -Sanicは、大きなファイルを送信する場合に便利な `sanic.response.file_stream` 関数を提供します。`StreamingHTTPResponse`オブジェクトを返し、デフォルトでチャンク転送エンコーディングを使用します。このため、Sanicは応答に`Content-Length` HTTPヘッダーを追加しません。 +Sanicは、大きなファイルを送信する場合に便利な `sanic.response.file_stream` 関数を提供します。 `StreamingHTTPResponse`オブジェクトを返し、デフォルトでチャンク転送エンコーディングを使用します。 このため、Sanicは応答に`Content-Length` HTTPヘッダーを追加しません。 -典型的なユースケースは、ビデオファイルをストリーミングしている可能性があります。 -:--:1 +典型的なユースケースは、ビデオファイルをストリーミングしている場合です。 :--:1 ```python @app.route("/mp4") async def handler_file_stream(request): diff --git a/src/ja/guide/advanced/versioning.md b/src/ja/guide/advanced/versioning.md index ab78986576..042a50c8a2 100644 --- a/src/ja/guide/advanced/versioning.md +++ b/src/ja/guide/advanced/versioning.md @@ -1,40 +1,38 @@ -# Versioning +# バージョン進行 -API構築では、エンドポイントにバージョンを追加するのが標準プラクティスです。これにより、APIを断りなく変更しようとすると、互換性のないエンドポイントを簡単に区別できます。 +API構築では、エンドポイントにバージョンを追加するのが標準プラクティスです。 これにより、APIを断りなく変更しようとすると、互換性のないエンドポイントを簡単に区別できます。 バージョンを追加すると、エンドポイントに`/v{version}` URLプレフィックスが追加されます。 -バージョンは `int`、`float`、または `str` にすることができます。許容値: +バージョンは `int`、`float`、または `str` にすることができます。 許容値: - `1`, `2`, `3` - `1.1`, `2.25`, `3.0` - `"1"`, `"v1"`, `"v1.1"` -## Per route +## ルートごとのバージョン ---:1 -バージョン番号をルートに直接渡すことができます。 -:--:1 +バージョン番号をルートに直接渡すことができます。 :--:1 ```python # /v1/text @app.route("/text", version=1) def handle_request(request): - return response.text("Hello world! Version 1") + return response.text("Hello world! バージョン1") # /v2/text @app.route("/text", version=2) def handle_request(request): - return response.text("Hello world! Version 2") + return response.text("Hello world! バージョン2") ``` :--- -## Per Blueprint +## Blueprintごとのバージョン ---:1 -ブループリントにバージョン番号を渡すこともできます。これは、そのブループリント内のすべてのルートに適用されます。 -:--:1 +Blueprintにバージョン番号を渡すこともできます。 これは、そのBlueprint内のすべてのルートに適用されます。 :--:1 ```python bp = Blueprint("test", url_prefix="/foo", version=1) @@ -45,22 +43,17 @@ def handle_request(request): ``` :--- -## Per Blueprint Group +## Blueprintグループごとのバージョン ----:1 -バージョン管理を簡素化するために、ブループリントにバージョン番号を提供できます -グループ。青写真がまだ上書きされていない場合、その下にグループ化されたすべての青写真にも同じことが継承されます -ブループリントインスタンスの作成中に指定された値と同じ情報。 +---:1 バージョン化されたブループリントの管理を簡素化するために、グループにバージョン番号を提供できます。 Blueprintインスタンスを作成する際に指定された値で同じ情報を上書きしない場合、 その下方でグループ化されたすべてのblueprintにも同じ情報が継承されます。 -バージョンの管理にブループリントグループを使用する場合は、バージョンプレフィックスを登録されているルート。 +バージョン管理にblueprintグループを使用する場合、登録中の経路にVersionプレフィックスを適用するには、以下の順序で行います。 1. ルートレベルの設定 -2. ブループリントレベルの構成 -3. ブループリントグループレベルの構成 - -より尖ったバージョニング仕様を見つけたら、より一般的なバージョニング仕様よりもそれを選びますブループリントまたはブループリントグループの下で提供 +2. Blueprintレベルの構成 +3. Blueprintグループレベルの構成 -:--:1 +もし、より尖ったバージョン管理仕様が見つかれば、BlueprintやBlueprintグループの下で提供される一般的なバージョン管理仕様よりも、そちらを選ぶことになります。 :--:1 ```python from sanic.blueprints import Blueprint from sanic.response import json @@ -98,34 +91,30 @@ async def handle_endpoint_2_bp2(request): ``` :--- -## Version prefix +## バージョンプレフィックス -上記のように、ルートに適用される `version` は、生成された URI パスの最初のセグメントを **常に** です。したがって、バージョンの前にパスセグメントを追加できるように、`version`引数が渡されるすべての場所で、`version_prefix`を渡すこともできます。 +上で見たように、ルートに適用される`バージョン`は、**常に**生成されたURIパスの最初のセグメントになります。 したがって、バージョンの前にパスセグメントを追加できるように、`version`引数が渡されるすべての場所で、`version_prefix`を渡すこともできます。 `version_prefix`引数は次のように定義できます。 -- `app.route` and `bp.route` decorators (and all the convenience decorators also) -- `Blueprint` instantiation -- `Blueprint.group` constructor -- `BlueprintGroup` instantiation -- `app.blueprint` registration +- `app.route` と `bp.route` デコレータ (そして、すべての便利なデコレータ) +- `Blueprint`のインスタンス化 +- `Blueprint.group`コンストラクタ +- `BlueprintGroup`のインスタンス化 +- `app.blueprint`の登録 -複数の場所に定義がある場合、より具体的な定義はより一般的な定義を上書きします。このリストはその階層を提供します。 +複数の場所に定義がある場合、より具体的な定義はより一般的な定義を上書きします。 このリストはその階層を提供します。 `version_prefix`のデフォルト値は`/v`です。 ----:1 -頻繁に要求される機能は、バージョン管理されたルートを `/api` にマウントできるようにすることです。これは「version_prefix」で簡単に実現できます。 -:--:1 +---:1 頻繁に要求される機能は、バージョン管理されたルートを `/api` にマウントできるようにすることです。 これは`version_prefix`で簡単に実現できます。 :--:1 ```python # /v1/my/path app.route("/my/path", version=1, version_prefix="/api/v") ``` :--- ----:1 -おそらく、より説得力のある使用法は、すべての「/api」ルートを単一の「BlueprintGroup」にロードすることです。 -:--:1 +---:1 おそらく、より説得力のある使用法は、すべての`/api`ルートを単一の`BlueprintGroup`にロードすることです。 :--:1 ```python # /v1/my/path app = Sanic(__name__) @@ -144,13 +133,14 @@ app.blueprint(api) したがって、ルートのURIは次のとおりです。 ``` -version_prefix + version + url_prefix + URI definition +version_prefix + version + url_prefix + URI定義 ``` -::: tip -`url_prefix`と同様に、`version_prefix`内にパスパラメータを定義することができます。これを行うことは完全に合法です。すべてのルートには、そのパラメータがハンドラーに注入されることを覚えておいてください。 +::: tip `url_prefix`と同様に、`version_prefix`内にパスパラメータを定義することができます。 これを行うのは完全に正当なことです。 すべてのルートには、そのパラメータがハンドラーに注入されることを覚えておいてください。 ```python version_prefix="//v" ``` ::: + +*Added in v21.6* diff --git a/src/ja/guide/advanced/websockets.md b/src/ja/guide/advanced/websockets.md index 607a7265b7..e3bd2ad898 100644 --- a/src/ja/guide/advanced/websockets.md +++ b/src/ja/guide/advanced/websockets.md @@ -3,40 +3,41 @@ Sanicは、[websockets](https://websockets.readthedocs.io/en/stable/)の上に使いやすい抽象化を提供します。 -## Routing +## ルーティング ---:1 -Websocketハンドラーは、通常のハンドラと同様にルーターに接続できます。 -:--:1 +Websocketハンドラーは、通常のハンドラと同様にルーターに接続できます。 :--:1 ```python -async def feed(request, ws): +from sanic import Request, Websocket + +async def feed(request: Request, ws: Websocket): pass app.add_websocket_route(feed, "/feed") ``` ```python +from sanic import Request, Websocket + @app.websocket("/feed") -async def feed(request, ws): +async def feed(request: Request, ws: Websocket): pass ``` :--- -## Handler +## ハンドラ ----:1 - -通常、Websocketハンドラはループを開きたいと思うでしょう。 +---:1 Typically, a websocket handler will want to hold open a loop. その後、ハンドラーに注入された2番目のオブジェクトで `send()` メソッドと `recv()` メソッドを使用できます。 -この例は、受信したクライアントメッセージにエコーバックする単純なエンドポイントです。 -:--:1 +この例は、受信したクライアントメッセージにエコーするシンプルなエンドポイントです。 :--:1 ```python +from sanic import Request, Websocket @app.websocket("/feed") -async def feed(request, ws): +async def feed(request: Request, ws: Websocket): while True: data = "hello!" print("Sending: " + data) @@ -45,14 +46,27 @@ async def feed(request, ws): print("Received: " + data) ``` :--- -## Configuration -詳細については、[構成セクション](/guide/deployment/configuration.md)を参照してください。 +---:1 You can simplify your loop by just iterating over the `Websocket` object in a for loop. + +*Added in v22.9* :--:1 +```python +from sanic import Request, Websocket + +@app.websocket("/feed") +async def feed(request: Request, ws: Websocket): + async for msg in ws: + await ws.send(msg) +``` +:--- + + +## 設定 + +See [configuration section](/guide/deployment/configuration.md) for more details, however the defaults are shown below. + ```python app.config.WEBSOCKET_MAX_SIZE = 2 ** 20 -app.config.WEBSOCKET_MAX_QUEUE = 32 -app.config.WEBSOCKET_READ_LIMIT = 2 ** 16 -app.config.WEBSOCKET_WRITE_LIMIT = 2 ** 16 app.config.WEBSOCKET_PING_INTERVAL = 20 app.config.WEBSOCKET_PING_TIMEOUT = 20 ``` diff --git a/src/ja/guide/basics/README.md b/src/ja/guide/basics/README.md index 25dcc52044..b11abc9793 100644 --- a/src/ja/guide/basics/README.md +++ b/src/ja/guide/basics/README.md @@ -1 +1 @@ -# Basics +# ベーシック diff --git a/src/ja/guide/basics/app.md b/src/ja/guide/basics/app.md index 54fc47b61a..7a62b15dd7 100644 --- a/src/ja/guide/basics/app.md +++ b/src/ja/guide/basics/app.md @@ -1,10 +1,8 @@ -# Sanic Application +# Sanicアプリケーション -## Instance +## インスタンス化 ----:1 -最も基本的な構成要素は、`Sanic()`インスタンスです。これは必須ではありませんが、カスタムはこれを`server.py`というファイルでインスタンス化します。 -:--:1 +---:1 最も基本的な構成要素は、`Sanic()`インスタンスです。 これは必須ではありませんが、カスタムはこれを`server.py`というファイルでインスタンス化します。 :--:1 ```python # /path/to/server.py @@ -14,34 +12,31 @@ app = Sanic("My Hello, world app") ``` :--- -## Application context +## アプリケーションコンテキスト -ほとんどのアプリケーションでは、コード・ベースのさまざまな部分でデータやオブジェクトを共有/再利用する必要があります。最も一般的な例はDB接続です。 ----:1 -v21.3より前のバージョンのSanicでは、これは通常、属性をアプリケーションインスタンスにアタッチすることによって行われていました。 -:--:1 +ほとんどのアプリケーションでは、コード・ベースのさまざまな部分でデータやオブジェクトを共有/再利用する必要があります。 最も一般的な例はDB接続です。 ---:1 v21.3より前のバージョンのSanicでは、これは通常、属性をアプリケーションインスタンスにアタッチすることによって行われていました。 :--:1 + +---:1 Sanicのv21.3以前のバージョンでは、これは一般的にアプリケーションのインスタンスに属性を添付することによって行われました。 :--:1 ```python -# Raises a warning as deprecated feature in 21.3 +# 21.3では非推奨として警告を出します。 app = Sanic("MyApp") app.db = Database() ``` :--- ----:1 -これにより、名前の競合に関する潜在的な問題が発生したり、[要求コンテキスト](./request.md#context)オブジェクトv 21.3では、アプリケーションレベルのコンテキストオブジェクトが導入されました。 -:--:1 +---:1 これにより、名前の競合に関する潜在的な問題が発生したり、[要求コンテキスト](./request.md#context)オブジェクトv 21.3では、アプリケーションレベルのコンテキストオブジェクトが導入されました。 :--:1 ```python -# Correct way to attach objects to the application +# アプリケーションにオブジェクトを添付する正しい方法 app = Sanic("MyApp") app.ctx.db = Database() ``` :--- -## App Registry +## アプリケーションの登録 ---:1 -Sanicインスタンスをインスタンス化すると、後でSanicアプリケーションレジストリから取得できます。これは、他の方法ではアクセスできない場所からSanicインスタンスにアクセスする必要がある場合などに便利です。 -:--:1 + +Sanicインスタンスをインスタンス化すると、後でSanicアプリケーションレジストリから取得できます。 これは、他の方法ではアクセスできない場所からSanicインスタンスにアクセスする必要がある場合などに便利です。 :--:1 :--:1 ```python # ./path/to/server.py from sanic import Sanic @@ -56,8 +51,8 @@ app = Sanic.get_app("my_awesome_server") :--- ---:1 -存在しないアプリケーションで`Sanic.get_app("non-existing")`を呼び出すと、デフォルトで`SanicException'が発生します。代わりに、その名前の新しいSanicインスタンスを返すようにメソッドに強制できます。 -:--:1 + +---:1 存在しないアプリケーションで`Sanic.get_app("non-existing")`を呼び出すと、デフォルトで`SanicException'が発生します。 代わりに、その名前の新しいSanicインスタンスを返すようにメソッドに強制できます。 :--:1 :--:1 ```python app = Sanic.get_app( "non-existing", @@ -66,9 +61,7 @@ app = Sanic.get_app( ``` :--- ----:1 -**Sanicインスタンスが1つしか登録されていない**場合、引数なしで`Sanic.get_app () `を呼び出すと、そのインスタンスが返されます。 -:--:1 +---:1 **Sanicインスタンスが1つしか登録されていない**場合、引数なしで`Sanic.get_app ()`を呼び出すと、そのインスタンスが返されます。 :--:1 ```python Sanic("My only app") @@ -76,11 +69,9 @@ app = Sanic.get_app() ``` :--- -## Configuration +## 構成 ----:1 -Sanicは、Sanicインスタンスのconfig属性に設定を保持します。構成は、ドット表記を使用するか、辞書のように変更できます。 -:--:1 +---:1 Sanicは、`Sanic`インスタンスの`config`属性に設定を保持します。 設定は、 ドット表記**または**辞書の**どちらかを**使用して変更できます。 ---:1 ```python app = Sanic('myapp') @@ -96,29 +87,27 @@ app.config.update(db_settings) ``` :--- -::: tip Heads up -構成キーは大文字でなければなりません。しかし、これは主に規約によるもので、ほとんどの場合小文字で動作します。 +::: tip Heads up 構成キーは大文字で_なければなりません_。 しかし、これは主に規約によるもので、ほとんどの場合小文字で動作します。 ``` -app.config.GOOD = "yay!" -app.config.bad = "boo" +app.config.GOOD = "yay!" app.config.bad = "boo" ``` ::: -[設定の詳細](/guide/deployment/configuration.md)については後で説明します。 +カスタム設定の最も単純な形式は、独自のオブジェクトを直接そのSanicアプリケーションインスタンスに渡すことです。 -## Customization +## カスタマイズ Sanicアプリケーションインスタンスは、インスタンス化時にさまざまな方法でアプリケーションのニーズに合わせてカスタマイズできます。 -### Custom configuration +### カスタムな構成 ---:1 -カスタム設定の最も単純な形式は、独自のオブジェクトを直接そのSanicアプリケーションインスタンスに渡すことです。 +この最も単純なカスタム設定の方法は、独自のオブジェクトを直接Sanicアプリケーションのインスタンスに渡すことです。 -カスタム設定オブジェクトを作成する場合は、Sanicの`Config`オプションをサブクラス化して、その動作を継承することを強くお勧めします。このオプションを使用して、プロパティを追加することも、独自のカスタムロジックセットを追加することもできます。 +カスタム設定オブジェクトを作成する場合は、Sanicの`Config`オプションをサブクラス化して、その動作を継承することを強くお勧めします。 このオプションを使用して、プロパティを追加することも、独自のカスタムロジックセットを追加することもできます。 -:--:1 +*Added in v21.6* :--:1 ```python from sanic.config import Config @@ -129,10 +118,7 @@ app = Sanic(..., config=MyConfig()) ``` :--- ----:1 - -この機能の有用な例は、 [supported](../deployment/configuration.md#using-sanic-update-config)とは異なる形式の設定ファイルを使用する場合です。 -:--:1 +この機能の有用な例は、 [supported](../deployment/configuration.md#using-sanic-update-config)とは異なる形式の設定ファイルを使用する場合です。 :--:1 ---:1 ```python from sanic import Sanic, text from sanic.config import Config @@ -165,12 +151,10 @@ toml_config = TomlConfig(path="/path/to/config.toml") app = Sanic(toml_config.APP_NAME, config=toml_config) ``` :--- -### Custom context ----:1 - -デフォルトでは、アプリケーション・コンテキストは [`SimpleNamespace()`](https://docs.python.org/3/library/types.html#types.SimpleNamespace) であり、必要なプロパティを設定できます。ただし、代わりに任意のオブジェクトを渡すこともできます。 +### カスタム コンテキスト +---:1 By default, the application context is a [`SimpleNamespace()`](https://docs.python.org/3/library/types.html#types.SimpleNamespace) that allows you to set any properties you want on it. ただし、代わりに任意のオブジェクトを渡すこともできます。 -:--:1 +*Added in v21.6* :--:1 ```python app = Sanic(..., ctx=1) ``` @@ -180,22 +164,20 @@ app = Sanic(..., ctx={}) ``` ```python -class MyContext: +class CustomContext: ... app = Sanic(..., ctx=MyContext()) ``` :--- -### Custom requests ----:1 -独自の 「Request」 クラスを用意し、デフォルトの代わりにそれを使用するようにSanicに指示すると便利な場合があります。たとえば、デフォルトの`request.id`ジェネレータを変更する場合です。 +### カスタムリクエスト +---:1 独自の 「Request」 クラスを用意し、デフォルトの代わりにそれを使用するようにSanicに指示すると便利な場合があります。 たとえば、デフォルトの`request.id`ジェネレータを変更する場合です。 -::: tip Important +::: tip 重要 クラスのインスタンスではなく、*クラス*を渡すことを覚えておくことが重要です。 -::: -:--:1 +::: :--:1 ```python import time @@ -217,20 +199,49 @@ async def handler(request): ``` :--- -### Custom error handler +### カスタムエラー処理 ----:1 -詳細については、[exception handling](../best-practices/exceptions.md#custom-error-handling)を参照してください。 -:--:1 +---:1 詳細については、[exception handling](../best-practices/exceptions.md#custom-error-handling)を参照してください。 :--:1 ```python from sanic.handlers import ErrorHandler class CustomErrorHandler(ErrorHandler): def default(self, request, exception): - ''' handles errors that have no error handlers assigned ''' - # You custom error handling logic... + ''' エラーハンドラが割り当てられていないエラーを処理する ''' + # あなた独自のエラーハンドリングロジック... return super().default(request, exception) app = Sanic(..., error_handler=CustomErrorHandler()) ``` :--- + +### Custom dumps function + +---:1 It may sometimes be necessary or desirable to provide a custom function that serializes an object to JSON data. :--:1 +```python +import ujson + +dumps = partial(ujson.dumps, escape_forward_slashes=False) +app = Sanic(__name__, dumps=dumps) +``` +:--- + +---:1 Or, perhaps use another library or create your own. :--:1 +```python +from orjson import dumps + +app = Sanic(__name__, dumps=dumps) +``` +:--- + +### Custom loads function + +---:1 Similar to `dumps`, you can also provide a custom function for deserializing data. + +*Added in v22.9* :--:1 +```python +from orjson import loads + +app = Sanic(__name__, loads=loads) +``` +:--- diff --git a/src/ja/guide/basics/cookies.md b/src/ja/guide/basics/cookies.md index 7350a88b4a..4eb9e3f33a 100644 --- a/src/ja/guide/basics/cookies.md +++ b/src/ja/guide/basics/cookies.md @@ -1,30 +1,28 @@ -# Cookies +# クッキー -## Reading +## 読み込み ---:1 -クッキーは、`Request`オブジェクトの`cookies`辞書を介してアクセスできます。 -:--:1 +クッキーは、`Request`オブジェクトの`cookies`辞書を介してアクセスできます。 :--:1 :--:1 ```python @app.route("/cookie") async def test(request): test_cookie = request.cookies.get("test") - return text("Test cookie: {}".format(test_cookie)) + return text("テストクッキー: {}".format(test_cookie)) ``` :--- -## Writing +## 書き込み ---:1 -応答を返すとき、クッキーは`Response`オブジェクトに設定できます: `response.cookies`。このオブジェクトは、応答ヘッダーを自動的に書き込む特別な種類の辞書である「CookieJar」のインスタンスです。 -:--:1 +応答を返すとき、クッキーは`Response`オブジェクトに設定できます: `response.cookies`。 このオブジェクトは、応答ヘッダーを自動的に書き込む特別な種類の辞書である「CookieJar」のインスタンスです。 :--:1 ---:1 ```python @app.route("/cookie") async def test(request): - response = text("There's a cookie up in this response") + response = text("このレスポンスにはクッキーがあります") response.cookies["test"] = "It worked!" response.cookies["test"]["domain"] = ".yummy-yummy-cookie.com" response.cookies["test"]["httponly"] = True @@ -35,24 +33,23 @@ async def test(request): 応答クッキーは辞書の値のように設定でき、次のパラメータを使用できます。 - `expires: datetime` - クライアントのブラウザでクッキーが期限切れになる時間。 -- `path: str` - このクッキーが適用されるURLのサブセット。デフォルトは `/` です。 +- `path: str` - このクッキーが適用されるURLのサブセット。 デフォルトは `/` です。 - `comment: str` - コメント(メタデータ)。 -- `domain: str` - クッキーが有効なドメインを指定します。明示的に指定されたドメインは常にドットで始まる必要があります。 +- `domain: str` - クッキーが有効なドメインを指定します。 明示的に指定されたドメインは常にドットで始まる必要があります。 - `max-age: int` - クッキーが存息する秒数。 - `secure: bool` - クッキーがHTTPS経由でのみ送信されるかどうかを指定します。 - `httponly: bool` - クッキーをJavaScriptで読み取ることができないかどうかを指定します。 - `samesite: str` - デフォルトはブラウザに依存し、仕様状態(Lax、Strict、None)は有効な値です。 -## Deleting +## 削除 ---:1 -クッキーは意味的または明示的に削除できます。 -:--:1 +クッキーは意味的または明示的に削除できます。 :--:1 :--:1 ```python @app.route("/cookie") async def test(request): - response = text("Time to eat some cookies muahaha") + response = text("クッキーを食べる時間だ!ハハハ") # This cookie will be set to expire in 0 seconds del response.cookies["kill_me"] @@ -71,6 +68,6 @@ async def test(request): ``` :--- -## Eating +## 食べる 私はクッキーが好きです:🍪: diff --git a/src/ja/guide/basics/handlers.md b/src/ja/guide/basics/handlers.md index 4c15b683a4..81502f892f 100644 --- a/src/ja/guide/basics/handlers.md +++ b/src/ja/guide/basics/handlers.md @@ -1,6 +1,6 @@ # Handlers -次の重要な構成要素は、あなたの_handlers_です。これらは「ビュー」とも呼ばれます。 +次の重要な構成要素は、あなたの_handlers_です。 これらは「ビュー」とも呼ばれます。 Sanicでは、ハンドラは、少なくとも「Request`インスタンスを引数として受け取り、`HTTPResponse`インスタンス、または同じことを行うコルーチンのいずれかを返す呼び出し可能物です。 @@ -10,10 +10,9 @@ Sanicでは、ハンドラは、少なくとも「Request`インスタンスを え? :confused: -これは**関数**です。同期または非同期のいずれかです。 +これは**関数**です。 同期または非同期のいずれかです。 -ハンドラの仕事は、エンドポイントに応答し、何かをすることです。これはあなたのビジネスロジックの大半が行く場所です。 -:--:1 +ハンドラの仕事は、エンドポイントに応答し、何かをすることです。 これはあなたのビジネスロジックの大半が行く場所です。 :--:1 :--:1 ```python def i_am_a_handler(request): return HTTPResponse() @@ -23,19 +22,17 @@ async def i_am_ALSO_a_handler(request): ``` :--- -::: tip Heads up -ロジックのカプセル化の詳細については、[クラスベースのビュー](/guide/advanced/class-based-views.md) をチェックアウトしてください。 -::: +::: tip Heads up ロジックのカプセル化の詳細については、[クラスベースのビュー](/guide/advanced/class-based-views.md) をチェックアウトしてください。 ::: ---:1 -次に、エンドポイントに配線するだけです。[すぐにルーティング](./routing.md)について詳しく学びます。 +次に、エンドポイントに配線するだけです。 +::: ---:1 Then, all you need to do is wire it up to an endpoint. [すぐにルーティング](./routing.md)について詳しく学びます。 実際の例を見てみましょう。 - アプリインスタンスで便利なデコレータを使用します: `@app.get()` - そして、応答オブジェクトを生成するための便利な便利なメソッド: `text()` -達成されたミッション :muscle: -:--:1 +達成されたミッション :muscle: :--:1 ```python from sanic.response import text @@ -53,13 +50,12 @@ async def foo_handler(request): 同期しているハンドラーを書くことは完全に可能です。 -この例では、_blocking_ `time.sleep()`を使用して100msの処理時間をシミュレートしています。おそらく、これはDB、またはサードパーティのウェブサイトからデータを取得することを表します。 +この例では、_blocking_ `time.sleep()`を使用して100msの処理時間をシミュレートしています。 おそらく、これはDB、またはサードパーティのウェブサイトからデータを取得することを表します。 4つのワーカープロセスと共通のベンチマークツールを使用する: - **956** 30.10秒のリクエスト -- または、約**31.76**リクエスト/秒 -:--:1 +- または、約**31.76**リクエスト/秒 :--:1 ```python @app.get("/sync") def sync_handler(request): @@ -79,11 +75,11 @@ def sync_handler(request): :flushed: -さて...これはとんでもなく劇的な結果です。そして、あなたが見るベンチマークは本質的に非常に偏っています。この例は、ウェブの世界で「async/await」の利点をオーバーザトップで示すことを目的としています。結果は確かに異なります。Sanicやその他の非同期Pythonライブラリなどのツールは、物事をより速くする魔法の弾丸ではありません。彼らはそれらをより効率的にします。 +さて...これはとんでもなく劇的な結果です。 そして、あなたが見るベンチマークは本質的に非常に偏っています。 この例は、ウェブの世界で「async/await」の利点をオーバーザトップで示すことを目的としています。 結果は確かに異なります。 Sanicやその他の非同期Pythonライブラリなどのツールは、物事をより速くする魔法の弾丸ではありません。 彼らはそれらをより効率的にします。 この例では、1つのリクエストがスリープしている間、別のリクエストと別のリクエスト、別のリクエスト、別のリクエスト、別のリクエストを開始できるため、非同期バージョンの方がはるかに優れています。 -しかし、これがポイントです!Sanicは、利用可能なリソースを取り出し、それらからパフォーマンスを絞り出すため、高速です。多くのリクエストを同時に処理できるため、1秒あたりのリクエストが増えます。 +But, this is the point! しかし、これがポイントです!Sanicは、利用可能なリソースを取り出し、それらからパフォーマンスを絞り出すため、高速です。 多くのリクエストを同時に処理できるため、1秒あたりのリクエストが増えます。 :--:1 ```python @@ -96,9 +92,9 @@ async def async_handler(request): ::: 警告 よくある間違い! -こんなことしないで!ウェブサイトにpingを送信する必要があります。何を使いますか?`pip install your-fav-request-library` :see_no_evil: +Don't do this! こんなことしないで!ウェブサイトにpingを送信する必要があります。 What do you use? 何を使いますか?`pip install your-fav-request-library` :see_no_evil: -その代わり、`async/await` が可能なクライアントを使用してみてください。あなたのサーバーはあなたに感謝するでしょう。ブロッキングツールの使用は避け、非同期のエコシステムでうまく機能するものを選びましょう。もし推薦が必要なら、[Awesome Sanic](https://github.com/mekicha/awesome-sanic)をチェックしてください。 +その代わり、`async/await` が可能なクライアントを使用してみてください。 あなたのサーバーはあなたに感謝するでしょう。 ブロッキングツールの使用は避け、非同期のエコシステムでうまく機能するものを選びましょう。 もし推薦が必要なら、[Awesome Sanic](https://github.com/mekicha/awesome-sanic)をチェックしてください。 Sanicは、テストパッケージ(sanic-testing)内で[httpx](https://www.python-httpx.org/)を使用しています。 :wink:. diff --git a/src/ja/guide/basics/headers.md b/src/ja/guide/basics/headers.md index fb9497da86..75ecc275b5 100644 --- a/src/ja/guide/basics/headers.md +++ b/src/ja/guide/basics/headers.md @@ -1,10 +1,10 @@ # Headers -リクエストおよびレスポンスのヘッダーは、それぞれ`Request`オブジェクトと`HTTPResponse`オブジェクトで使用できます。単一のキーが複数の値を持つことを可能にする [`multidict`パッケージ] (https://multidict.readthedocs.io/en/stable/multidict.html#cimultidict) を利用します。 +リクエストおよびレスポンスのヘッダーは、それぞれ`Request`オブジェクトと`HTTPResponse`オブジェクトで使用できます。 単一のキーが複数の値を持つことを可能にする [`multidict`パッケージ] (https://multidict.readthedocs.io/en/stable/multidict.html#cimultidict) を利用します。 ::: tip FYI -ヘッダキーは、解析時に*小文字*に変換されます。ヘッダーでは大文字と小文字は区別されません。 +ヘッダキーは、解析時に*小文字*に変換されます。 ヘッダーでは大文字と小文字は区別されません。 ::: @@ -44,18 +44,17 @@ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4 #### Proxy headers -Sanicはプロキシヘッダを特別に扱います。詳細については、 [proxy headers] (/guide/advanced/proxy-headers.md) セクションを参照してください。 +Sanicはプロキシヘッダを特別に扱います。 詳細については、 \[proxy headers\] (/guide/advanced/proxy-headers.md) セクションを参照してください。 #### Host header and dynamic URL construction -*実効ホスト*は`request.host`を介して使用できます。これは、プロキシ転送されたホストを優先し、サーバ名の設定によって強制される可能性があるため、必ずしもホストヘッダーと同じではありません。 +*実効ホスト*は`request.host`を介して使用できます。 これは、プロキシ転送されたホストを優先し、サーバ名の設定によって強制される可能性があるため、必ずしもホストヘッダーと同じではありません。 -Webアプリケーションは通常、どのようにデプロイされても同じように機能できるように、このアクセサを使用する必要があります。実際のホストヘッダーは、必要に応じて`request.headers` +Webアプリケーションは通常、どのようにデプロイされても同じように機能できるように、このアクセサを使用する必要があります。 実際のホストヘッダーは、必要に応じて`request.headers` 実効ホストは、ハンドラの外部アドレスを決定するためにリクエストを使用する`request.url_for`を介して動的URL構築でも使用されます。 -::: tip Be wary of malicious clients -これらのURLは、誤ったホストヘッダーを送信することで操作できます。これが懸念される場合は、代わりに`app.url_for`を使用する必要があります。 +::: tip Be wary of malicious clients これらのURLは、誤ったホストヘッダーを送信することで操作できます。 これが懸念される場合は、代わりに`app.url_for`を使用する必要があります。 ::: ::: :--:1 @@ -90,7 +89,7 @@ $ curl localhost:8000/hosts ---:1 #### Other headers -すべてのリクエストヘッダーは`request.headers`で使用でき、辞書形式でアクセスできます。大文字はヘッダーでは考慮されず、大文字または小文字のキーを使用してアクセスできます。 +すべてのリクエストヘッダーは`request.headers`で使用でき、辞書形式でアクセスできます。 大文字はヘッダーでは考慮されず、大文字または小文字のキーを使用してアクセスできます。 :--:1 @@ -145,18 +144,16 @@ $ curl localhost:9999/headers -H "Foo: one" -H "FOO: two"|jq :--- -::: tip FYI -💡 request.headersオブジェクトは、辞書のタイプの1つで、各値はリストです。これは、HTTPでは1つのキーを再利用して複数の値を送信できるためです。 +::: tip FYI 💡 request.headersオブジェクトは、辞書のタイプの1つで、各値はリストです。 これは、HTTPでは1つのキーを再利用して複数の値を送信できるためです。 -ほとんどの場合、リストではなく最初の要素にアクセスするには、.get () または.getone () メソッドを使用します。すべての項目のリストが必要な場合は、.getall () を使用できます。 +ほとんどの場合、リストではなく最初の要素にアクセスするには、.get () または.getone () メソッドを使用します。 すべての項目のリストが必要な場合は、.getall () を使用できます。 ::: ::: #### Request ID ---:1 -多くの場合、 「X-Request-ID」 ヘッダーを使用してリクエストを追跡すると便利です。次の方法で簡単にアクセスできます。 -`request.id`. +多くの場合、 「X-Request-ID」 ヘッダーを使用してリクエストを追跡すると便利です。 次の方法で簡単にアクセスできます。 `request.id`. :--:1 @@ -205,7 +202,7 @@ async def add_csp(request, response): ---:1 -一般的な [ミドルウェア] (middleware.md) は、すべての応答に`X-Request-ID`ヘッダーを追加することです。前述のように、`request.id`は着信要求からIDを提供します。ただし、リクエストヘッダーにIDが指定されていない場合でも、自動的にIDが指定されます。 +一般的な \[ミドルウェア\] (middleware.md) は、すべての応答に`X-Request-ID`ヘッダーを追加することです。 前述のように、`request.id`は着信要求からIDを提供します。 ただし、リクエストヘッダーにIDが指定されていない場合でも、自動的にIDが指定されます。 [詳細については、APIドキュメントを参照してください。](https://sanic.readthedocs.io/en/latest/sanic/api_reference.html#sanic.request.Request.id) diff --git a/src/ja/guide/basics/listeners.md b/src/ja/guide/basics/listeners.md index 008a168b8e..7ba0b59587 100644 --- a/src/ja/guide/basics/listeners.md +++ b/src/ja/guide/basics/listeners.md @@ -1,20 +1,27 @@ # Listeners -Sanicは、アプリケーションのライフサイクルにオペレーションを注入する6つの機会を提供します。 +Sanicは、アプリケーションのライフサイクルにオペレーションを注入する6つの機会を提供します。 This does not include the [signals](../advanced/signals.md), which allow further injection customization. -メインのSanicプロセスで**のみ**を実行するものが2つあります(つまり、`sanic server.app`への呼び出しごとに1回です。)。 +メインのSanicプロセスで**のみ**を実行するものが2つあります(つまり、`sanic server.app`への呼び出しごとに1回です。 - `main_process_start` - `main_process_stop` サーバの起動時または終了時にスタートアップ/ティアダウンコードを実行できるようにするには、4つの方法があります。 +- `reload_process_start` +- `reload_process_stop` + +*Added `reload_process_start` and `reload_process_stop` in v22.3* + +There are four (4) that enable you to execute startup/teardown code as your server starts or closes. + - `before_server_start` - `after_server_start` - `before_server_stop` - `after_server_stop` -ワーカプロセスのライフサイクルは次のようになります。 +関数をリスナーとして設定するプロセスは、ルートの宣言に似ています。 ```mermaid sequenceDiagram @@ -60,16 +67,27 @@ loop end Note over Process: exit ``` + +The reloader process live outside of this worker process inside of a process that is responsible for starting and stopping the Sanic processes. Consider the following example: + +```python +async def setup_db(app, loop): + app.ctx.db = await db_setup() + +app.register_listener(setup_db, "before_server_start") +``` + +If this application were run with auto-reload turned on, the `reload_start` function would be called once. This is contrasted with `main_start`, which would be run every time a file is save and the reloader restarts the applicaition process. + ## Attaching a listener ---:1 -関数をリスナーとして設定するプロセスは、ルートの宣言に似ています。 +`Sanic`アプリインスタンスにも便利なデコレーターがある。 :--:1 -注入される2つの引数は、現在実行中の`Sanic()`インスタンスと現在実行中のループです。 -:--:1 +The currently running `Sanic()` instance is injected into the listener. :--:1 ```python -async def setup_db(app, loop): +async def setup_db(app): app.ctx.db = await db_setup() app.register_listener(setup_db, "before_server_start") @@ -78,8 +96,15 @@ app.register_listener(setup_db, "before_server_start") ---:1 -`Sanic`アプリインスタンスにも便利なデコレーターがある。 -:--:1 +The `Sanic` app instance also has a convenience decorator. :--:1 +```python +@app.before_server_start +async def setup_db(app, loop): + app.ctx.db = await db_setup() +``` +:--- + +---:1 Prior to v22.3, both the application instance and the current event loop were injected into the function. However, only the application instance is injected by default. If your function signature will accept both, then both the application and the loop will be injected as shown here. :--:1 ```python @app.listener("before_server_start") async def setup_db(app, loop): @@ -89,12 +114,12 @@ async def setup_db(app, loop): ---:1 -デコレーターをさらに短くすることができます。これは、オートコンプリート機能を備えたIDEがある場合に便利です。 +デコレーターをさらに短くすることができます。 これは、オートコンプリート機能を備えたIDEがある場合に便利です。 :--:1 ```python @app.before_server_start -async def setup_db(app, loop): +async def setup_db(app): app.ctx.db = await db_setup() ``` :--- @@ -103,16 +128,16 @@ async def setup_db(app, loop): リスナーは、起動時に宣言された順序で実行され、ティアダウン時に宣言された順序とは逆の順序で実行されます。 -| | Phase | Order | -|-----------------------|-----------------|---------| -| `main_process_start` | main startup | regular :smiley: | -| `before_server_start` | worker startup | regular :smiley: | -| `after_server_start` | worker startup | regular :smiley: | +| | Phase | Order | +| --------------------- | --------------- | ---------------------------- | +| `main_process_start` | main startup | regular :smiley: | +| `before_server_start` | worker startup | regular :smiley: | +| `after_server_start` | worker startup | regular :smiley: | | `before_server_stop` | worker shutdown | reverse :upside_down_face: | | `after_server_stop` | worker shutdown | reverse :upside_down_face: | | `main_process_stop` | main shutdown | reverse :upside_down_face: | -次の設定では、2人のワーカーを実行した場合にコンソールにこのメッセージが表示されます。 +実際には、`before_server_start`ハンドラの最初のリスナーがデータベース接続を設定すると、その後に登録されたリスナーは、起動時と停止時の両方でその接続が有効であることを信頼できます。 ---:1 @@ -182,18 +207,16 @@ async def listener_8(app, loop): - `pid: 1111111` - Worker 1 - `pid: 1222222` - Worker 2 -*この例では、1つのワーカーをすべてグループ化し、次に別のワーカーをすべてグループ化していますが、実際にはこれらのワーカーは別々のプロセスで実行されているため、プロセス間の順序付けは保証されません。ただし、1人のワーカーが常にその順序を維持することは確実です。* -:--- +*この例では、1つのワーカーをすべてグループ化し、次に別のワーカーをすべてグループ化していますが、実際にはこれらのワーカーは別々のプロセスで実行されているため、プロセス間の順序付けは保証されません。 ただし、1人のワーカーが常にその順序を維持することは確実です。 * :--- -::: tip FYI -The practical result of this is that if the first listener in `before_server_start` handler setups a database connection, listeners that are registered after it can rely upon that connection being alive both when they are started and stopped. -::: +::: tip FYI The practical result of this is that if the first listener in `before_server_start` handler setups a database connection, listeners that are registered after it can rely upon that connection being alive both when they are started and stopped. ::: ## ASGI Mode -実際には、`before_server_start`ハンドラの最初のリスナーがデータベース接続を設定すると、その後に登録されたリスナーは、起動時と停止時の両方でその接続が有効であることを信頼できます。 +If you are running your application with an ASGI server, then make note of the following changes: -- `main_process_start`と`main_process_stop`は**無視されます。** -- `before_server_start`はできるだけ早く動きます。,そして`after_server_start`の前に実行されます。, しかし、技術的には、サーバーはすでにその時点で実行されています - `after_server_stop`はできるだけ遅く実行され、`before_server_stop`の後になりますが、技術的には、サーバーはまだその時点で実行されています +- `main_process_start`と`main_process_stop`は**無視されます。 ** +- `before_server_start`はできるだけ早く動きます。 ,そして`after_server_start`の前に実行されます。 , しかし、技術的には、サーバーはすでにその時点で実行されています +- `after_server_stop` will run as late as it can, and will be after `before_server_stop`, but technically, the server is still running at that point diff --git a/src/ja/guide/basics/middleware.md b/src/ja/guide/basics/middleware.md index da8f1449be..96e42c977b 100644 --- a/src/ja/guide/basics/middleware.md +++ b/src/ja/guide/basics/middleware.md @@ -32,8 +32,7 @@ Note over Worker: Deliver response ---:1 -これはもう見慣れたものになっているはずです。必要なのは、 「リクエスト」 または 「レスポンス」 のどちらでミドルウェアを実行するかを宣言することだけです。 -:--:1 +これはもう見慣れたものになっているはずです。 All you need to do is declare when you would like the middleware to execute: on the `request` or on the `response`. :--:1 ```python async def extract_user(request): request.ctx.user = await extract_user_from_request(request) @@ -44,8 +43,7 @@ app.register_middleware(extract_user, "request") ---:1 -ここでも、`Sanic`アプリインスタンスには便利なデコレーターもある。 -:--:1 +ここでも、`Sanic`アプリインスタンスには便利なデコレーターもある。 :--:1 :--:1 ```python @app.middleware("request") async def extract_user(request): @@ -55,8 +53,7 @@ async def extract_user(request): ---:1 -レスポンス・ミドルウェアは`request`と`response`の両方の引数を受け取ります。 -:--:1 +レスポンス・ミドルウェアは`request`と`response`の両方の引数を受け取ります。 :--:1 :--:1 ```python @app.middleware('response') async def prevent_xss(request, response): @@ -66,7 +63,10 @@ async def prevent_xss(request, response): ---:1 -デコレーターをさらに短くすることができます。これは、オートコンプリート機能を備えたIDEがある場合に便利です。 +デコレーターをさらに短くすることができます。 これは、オートコンプリート機能を備えたIDEがある場合に便利です。 :--:1 + +This is the preferred usage, and is what we will use going forward. + :--:1 ```python @app.on_request @@ -90,21 +90,20 @@ async def prevent_xss(request, response): 1. Request middleware: `add_key` 2. Route handler: `index` 3. Response middleware: `prevent_xss` -4. Response middleware: `custom_banner` -:--:1 +4. Response middleware: `custom_banner` :--:1 ```python -@app.middleware("request") +@app.on_request async def add_key(request): # Arbitrary data may be stored in request context: request.ctx.foo = "bar" -@app.middleware("response") +@app.on_response async def custom_banner(request, response): response.headers["Server"] = "Fake-Server" -@app.middleware("response") +@app.on_response async def prevent_xss(request, response): response.headers["x-xss-protection"] = "1; mode=block" @@ -117,16 +116,14 @@ async def index(request): :--- ----:1 -`request.match_info`を変更できます。たとえば、ミドルウェアで`a-slug`を`a_slug`に変換するために使用できる便利な機能。 -:--:1 +---:1 `request.match_info`を変更できます。 たとえば、ミドルウェアで`a-slug`を`a_slug`に変換するために使用できる便利な機能。 :--:1 :--:1 ```python @app.on_request def convert_slug_to_underscore(request: Request): - request._match_info["slug"] = request._match_info["slug"].replace("-", "_") + request.match_info["slug"] = request.match_info["slug"].replace("-", "_") -@app.get("/") +@app.get("/") async def handler(request, slug): return text(slug) ``` @@ -139,51 +136,47 @@ foo_bar_baz ---:1 -ミドルウェアが`HTTPResponse`オブジェクトを返す場合、リクエストは処理を停止し、レスポンスが返されます。これがルートハンドラに到達する前のリクエストで発生した場合、ハンドラは**not**呼び出されます。レスポンスを返すと、それ以降のミドルウェアの実行も妨げられます。 +ミドルウェアが`HTTPResponse`オブジェクトを返す場合、リクエストは処理を停止し、レスポンスが返されます。 これがルートハンドラに到達する前のリクエストで発生した場合、ハンドラは**not**呼び出されます。 レスポンスを返すと、それ以降のミドルウェアの実行も妨げられます。 -::: tip -`None`値を返すと、ミドルウェアハンドラの実行を停止して、リクエストを通常どおりに処理できます。これは、アーリー・リターンを使用して、そのミドルウェア・ハンドラー内の要求の処理を回避する場合に便利です。 -::: -:--:1 +::: tip `None`値を返すと、ミドルウェアハンドラの実行を停止して、リクエストを通常どおりに処理できます。 This can be useful when using early return to avoid processing requests inside of that middleware handler. ::: :--:1 ```python -@app.middleware("request") +@app.on_request async def halt_request(request): return text("I halted the request") -@app.middleware("response") +@app.on_response async def halt_response(request, response): return text("I halted the response") ``` :--- -#### Order of execution +## Order of execution -リクエスト・ミドルウェアは、宣言された順序で実行されます。レスポンス・ミドルウェアは、**逆順で実行されます**. +リクエスト・ミドルウェアは、宣言された順序で実行されます。 レスポンス・ミドルウェアは、**逆順で実行されます**. 次の設定では、コンソールにこのメッセージが表示されます。 ---:1 - ```python -@app.middleware("request") +@app.on_request async def middleware_1(request): print("middleware_1") -@app.middleware("request") +@app.on_request async def middleware_2(request): print("middleware_2") -@app.middleware("response") +@app.on_response async def middleware_3(request, response): print("middleware_3") -@app.middleware("response") +@app.on_response async def middleware_4(request, response): print("middleware_4") - + @app.get("/handler") async def handler(request): print("~ handler ~") @@ -199,3 +192,19 @@ middleware_3 [INFO][127.0.0.1:44788]: GET http://localhost:8000/handler 200 5 ``` :--- + +### Middleware priority + +---:1 You can modify the order of execution of middleware by assigning it a higher priority. This happens inside of the middleware definition. The higher the value, the earlier it will execute relative to other middleware. The default priority for middleware is `0`. :--:1 +```python +@app.on_request +async def low_priority(request): + ... + +@app.on_request(priority=99) +async def high_priority(request): + ... +``` +:--- + +*Added in v22.9* diff --git a/src/ja/guide/basics/request.md b/src/ja/guide/basics/request.md index eeeeedf7ae..d5e72040a0 100644 --- a/src/ja/guide/basics/request.md +++ b/src/ja/guide/basics/request.md @@ -1,6 +1,6 @@ # Request -`Request`インスタンスには、そのパラメータに関する有用な情報が**たくさん**含まれています。詳細については、[APIドキュメント](https://sanic.readthedocs.io/) を参照してください。 +`Request`インスタンスには、そのパラメータに関する有用な情報が**たくさん**含まれています。 詳細については、[APIドキュメント](https://sanic.readthedocs.io/) を参照してください。 ## Body @@ -58,11 +58,9 @@ bar ['bar'] ``` -::: tip FYI -:bulb: `request.form`オブジェクトは、各値がリストであるディクショナリの、いくつかのタイプの1つです。これは、HTTPでは1つのキーを再利用して複数の値を送信できるためです。 +::: tip FYI :bulb: The `request.files` object is one of a few types that is a dictionary with each value being a list. This is because HTTP allows a single key to be reused to send multiple values. -ほとんどの場合、リストではなく最初の要素にアクセスするには、`.get()`メソッドを使用します。すべての項目のリストが必要な場合は、`.getlist()`を使用できます。 -::: +Most of the time you will want to use the `.get()` method to access the first element and not a list. If you do want a list of all items, you can use `.getlist()`. ::: ::: tab Uploaded @@ -86,10 +84,9 @@ File(type='application/octet-stream', body=b'hello\n', name='TEST') >>> print(request.files.getlist("my_file")) [File(type='application/octet-stream', body=b'hello\n', name='TEST')] ``` -::: tip FYI -:bulb: The `request.files` object is one of a few types that is a dictionary with each value being a list. This is because HTTP allows a single key to be reused to send multiple values. +::: tip FYI :bulb: `request.args`オブジェクトは、各値がリストになっているディクショナリのタイプの1つです。 これは、HTTPでは1つのキーを再利用して複数の値を送信できるためです。 -Most of the time you will want to use the `.get()` method to access the first element and not a list. If you do want a list of all items, you can use `.getlist()`. +ほとんどの場合、リストではなく最初の要素にアクセスするには、`.get()`メソッドを使用します。 すべての項目のリストが必要な場合は、`.getlist()`を使用できます。 ::: ::: :::: @@ -100,7 +97,7 @@ Most of the time you will want to use the `.get()` method to access the first el `request.ctx`オブジェクトは、リクエストに関して必要な情報を格納するためのプレイグラウンドです。 -これは、認証されたユーザーの詳細などのアイテムを格納するためによく使用されます。[ミドルウェア](./middleware.md)を使用しますが、ここでは簡単な例を示します。 +これは、認証されたユーザーの詳細などのアイテムを格納するためによく使用されます。 [ミドルウェア](./middleware.md)を使用しますが、ここでは簡単な例を示します。 ```python @app.on_request @@ -112,18 +109,17 @@ async def hi_my_name_is(request): return text("Hi, my name is {}".format(request.ctx.user.name)) ``` -典型的な使用例は、データベースから取得したユーザー・オブジェクトを認証ミドルウェアに格納することです。追加されたキーは、以降のすべてのミドルウェアおよびリクエストの実行中にハンドラからアクセスできます。 +典型的な使用例は、データベースから取得したユーザー・オブジェクトを認証ミドルウェアに格納することです。 追加されたキーは、以降のすべてのミドルウェアおよびリクエストの実行中にハンドラからアクセスできます。 -カスタムコンテキストは、アプリケーションおよび拡張用に予約されています。サニック自身はそれを利用しない。 +カスタムコンテキストは、アプリケーションおよび拡張用に予約されています。 サニック自身はそれを利用しない。 ### Connection context ---:1 -多くの場合、APIは同じクライアントに対して複数の同時 (または連続) リクエストを処理する必要があります。たとえば、データを取得するために複数のエンドポイントにクエリを実行する必要があるプログレッシブウェブアプリでは、これが頻繁に発生します。 - +多くの場合、APIは同じクライアントに対して複数の同時 (または連続) リクエストを処理する必要があります。 たとえば、データを取得するために複数のエンドポイントにクエリを実行する必要があるプログレッシブウェブアプリでは、これが頻繁に発生します。 -HTTPプロトコルでは、[keep alive headers](../ deployment/configuration.md#keep-alive-timeout)を使用した接続によって発生するオーバーヘッド時間を緩和する必要があります。 +HTTPプロトコルでは、\[keep alive headers\](../ deployment/configuration.md#keep-alive-timeout)を使用した接続によって発生するオーバーヘッド時間を緩和する必要があります。 複数のリクエストが1つの接続を共有する場合、Sanicはそれらのリクエストが状態を共有できるようにコンテキストオブジェクトを提供します。 @@ -150,9 +146,7 @@ request.conn_info.ctx.foo=3 ## Parameters ----:1 -パスから抽出された値は、パラメータとして、より具体的にはキーワード引数としてハンドラに注入されます。詳細については、 [ルーティングセクション](./routing.md)。 -:--:1 +---:1 パスから抽出された値は、パラメータとして、より具体的にはキーワード引数としてハンドラに注入されます。 詳細については、 [ルーティングセクション](./routing.md)。 :--:1 :--:1 ```python @app.route('/tag/') async def tag_handler(request, tag): @@ -190,8 +184,51 @@ key1=val1&key2=val2&key1=val3 ``` -::: tip FYI -:bulb: `request.args`オブジェクトは、各値がリストになっているディクショナリのタイプの1つです。これは、HTTPでは1つのキーを再利用して複数の値を送信できるためです。 +::: tip FYI :bulb: `request.form`オブジェクトは、各値がリストであるディクショナリの、いくつかのタイプの1つです。 これは、HTTPでは1つのキーを再利用して複数の値を送信できるためです。 -ほとんどの場合、リストではなく最初の要素にアクセスするには、`.get()`メソッドを使用します。すべての項目のリストが必要な場合は、`.getlist()`を使用できます。 +ほとんどの場合、リストではなく最初の要素にアクセスするには、`.get()`メソッドを使用します。 すべての項目のリストが必要な場合は、`.getlist()`を使用できます。 ::: ::: + +## Current request getter + +Sometimes you may find that you need access to the current request in your application in a location where it is not accessible. A typical example might be in a `logging` format. You can use `Request.get_current()` to fetch the current request (if any). + +```python +import logging + +from sanic import Request, Sanic, json +from sanic.exceptions import SanicException +from sanic.log import LOGGING_CONFIG_DEFAULTS + +LOGGING_FORMAT = ( + "%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: " + "%(request_id)s %(request)s %(message)s %(status)d %(byte)d" +) + +old_factory = logging.getLogRecordFactory() + + +def record_factory(*args, **kwargs): + record = old_factory(*args, **kwargs) + record.request_id = "" + + try: + request = Request.get_current() + except SanicException: + ... + else: + record.request_id = str(request.id) + + return record + + +logging.setLogRecordFactory(record_factory) + +LOGGING_CONFIG_DEFAULTS["formatters"]["access"]["format"] = LOGGING_FORMAT + +app = Sanic("Example", log_config=LOGGING_CONFIG_DEFAULTS) +``` + +In this example, we are adding the `request.id` to every access log message. + +*Added in v22.6* diff --git a/src/ja/guide/basics/response.md b/src/ja/guide/basics/response.md index 91915b5fd3..1305fc6893 100644 --- a/src/ja/guide/basics/response.md +++ b/src/ja/guide/basics/response.md @@ -1,8 +1,28 @@ -# Response +# レスポンス -すべての[ハンドラ](./handlers.md) **は**レスポンス・オブジェクトを返し、[ミドルウェア](./middleware.md)は、オプションで応答オブジェクトを返すことができます。 +All [handlers](./handlers.md)* **must** return a response object, and [middleware](./middleware.md) may optionally return a response object. -## Methods +To clarify that statement: +- unless the handler is a streaming endpoint, the return value must be an instance of `sanic.HTTPResponse` (to learn more about this exception see [streaming responses](../advanced/streaming.md#response-streaming)) +- if a middleware returns a response object, that will be used instead of whatever the handler would do (see [middleware](./middleware.md) to learn more) + +A most basic handler would look like the following. The `HTTPResponse` object will allow you to set the status, body, and headers to be returned to the client. + +```python +from sanic import HTTPResponse, Sanic + +app = Sanic("TestApp") + + +@app.route("") +def handler(_): + return HTTPResponse() +``` + +However, usually it is easier to use one of the convenience methods discussed below. + + +## メソッド レスポンス・オブジェクトを生成する最も簡単な方法は、9つの便利なメソッドのいずれかを使用することです。 @@ -11,14 +31,14 @@ ::: tab Text **Default Content-Type**: `text/plain; charset=utf-8` -**Description**: Returns plain text +**Description**: プレーンテキストを返す ```python from sanic.response import text @app.route("/") async def handler(request): - return text("Hi 😎") + return text("やあ 😎") ``` ::: ::: tab HTML @@ -91,28 +111,6 @@ You can also choose to override the file name: file("/path/to/whatever.png", filename="super-awesome-incredible.png") ``` ::: -::: tab Streaming - -**Default Content-Type**: `text/plain; charset=utf-8` -**Description**: Streams data to a client - -```python -from sanic.response import stream - -@app.route("/") -async def handler(request): - return stream(streaming_fn) - -async def streaming_fn(response): - await response.write('foo') - await response.write('bar') -``` -By default, Sanic will stream back to the client using chunked encoding if the client supports it. You can disable this: - -```python -stream(streaming_fn, chunked=False) -``` -::: ::: tab "File Streaming" **Default Content-Type**: N/A @@ -126,8 +124,7 @@ async def handler(request): return await file_stream("/path/to/whatever.mp4") ``` -Like the `file()` method, `file_stream()` will attempt to determine the mime type of the file. -::: +Like the `file()` method, `file_stream()` will attempt to determine the mime type of the file. ::: ::: tab Raw **Default Content-Type**: `application/octet-stream` @@ -168,13 +165,12 @@ async def handler(request): return empty() ``` -Defaults to a `204` status. -::: +Defaults to a `204` status. ::: :::: ## Default status -レスポンスのデフォルトのHTTPステータス・コードは`200`です。変更が必要な場合は、レスポンス方式で行うことができます。 +レスポンスのデフォルトのHTTPステータス・コードは`200`です。 変更が必要な場合は、レスポンス方式で行うことができます。 ```python @@ -183,3 +179,39 @@ async def create_new(request): new_thing = await do_create(request) return json({"created": True, "id": new_thing.thing_id}, status=201) ``` + +::: new NEW in v22.12 +## Returning JSON data + +Starting in v22.12, When you use the `sanic.json` convenience method, it will return a subclass of `HTTPResponse` called `JSONResponse`. This object will have several convenient methods available to modify common JSON body. + +```python +from sanic import json + +resp = json(...) +``` + +- `resp.set_body()` - Set the body of the JSON object to the value passed +- `resp.append()` - Append a value to the body like `list.append` (only works if the root JSON is an array) +- `resp.extend()` - Extend a value to the body like `list.extend` (only works if the root JSON is an array) +- `resp.update()` - Update the body with a value like `dict.update` (only works if the root JSON is an object) +- `resp.pop()` - Pop a value like `list.pop` or `dict.pop` (only works if the root JSON is an array or an object) + +::: warning The raw Python object is stored on the `JSONResponse` object as `raw_body`. While it is safe to overwrite this value with a new one, you should **not** attempt to mutate it. You should instead use the methods listed above. + +```python +resp = json({"foo": "bar"}) + +# This is OKAY +resp.raw_body = {"foo": "bar", "something": "else"} + +# This is better +resp.set_body({"foo": "bar", "something": "else"}) + +# This is also works well +resp.update({"something": "else"}) + +# This is NOT OKAY +resp.raw_body.update({"something": "else"}) +``` +::: diff --git a/src/ja/guide/basics/routing.md b/src/ja/guide/basics/routing.md index 9f0a31d335..172eb88c87 100644 --- a/src/ja/guide/basics/routing.md +++ b/src/ja/guide/basics/routing.md @@ -4,8 +4,7 @@ これまでに様々な形の装飾家を見てきました。 -でも何なの?どうやって使うのか? -:--:1 +But what is it? And how do we use it? :--:1 ```python @app.route("/stairway") ... @@ -24,8 +23,7 @@ ハンドラをエンドポイントに接続する最も基本的な方法は、`app.add_route()`を使用することです。 -詳細については、[API docs](https://sanic.readthedocs.io/en/stable/sanic/api_reference.html#sanic.app.Sanic.url_for)を参照してください。 -:--:1 +詳細については、[API docs](https://sanic.readthedocs.io/en/stable/sanic/api_reference.html#sanic.app.Sanic.url_for)を参照してください。 :--:1 :--:1 ```python async def handler(request): return text("OK") @@ -36,8 +34,7 @@ app.add_route(handler, "/test") ---:1 -デフォルトでは、ルートはHTTP`GET`コールとして使用できます。1つ以上のHTTPメソッドに応答するようにハンドラを変更できます。 -:--:1 +デフォルトでは、ルートはHTTP`GET`コールとして使用できます。 1つ以上のHTTPメソッドに応答するようにハンドラを変更できます。 :--:1 :--:1 ```python app.add_route( handler, @@ -49,8 +46,7 @@ app.add_route( ---:1 -デコレータ構文を使用する場合、前の例はこれと同じです。 -:--:1 +でも何なの?どうやって使うのか? :--:1 ```python @app.route('/test', methods=["POST", "PUT"]) async def handler(request): @@ -71,8 +67,7 @@ async def handler(request): return text('OK') ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET) -::: +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET) ::: ::: tab POST ```python @@ -81,8 +76,7 @@ async def handler(request): return text('OK') ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) -::: +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) ::: ::: tab PUT ```python @@ -91,8 +85,7 @@ async def handler(request): return text('OK') ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT) -::: +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT) ::: ::: tab PATCH ```python @@ -101,8 +94,7 @@ async def handler(request): return text('OK') ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH) -::: +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH) ::: ::: tab DELETE ```python @@ -111,8 +103,7 @@ async def handler(request): return text('OK') ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE) -::: +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE) ::: ::: tab HEAD ```python @@ -121,8 +112,7 @@ async def handler(request): return empty() ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD) -::: +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD) ::: ::: tab OPTIONS ```python @@ -131,12 +121,10 @@ async def handler(request): return empty() ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS) -::: +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS) ::: :::: -::: warning -デフォルトでは、Sanic は安全でない HTTP メソッド (`POST`、`PUT`、`PATCH`) で受信したリクエストボディ**のみ**を消費します。他のメソッドでHTTPリクエストのデータを受け取りたい場合は、以下の2つのオプションのいずれかを実行する必要があります。 +::: warning デフォルトでは、Sanic は安全でない HTTP メソッド (`POST`、`PUT`、`PATCH`) で受信したリクエストボディ**のみ**を消費します。 他のメソッドでHTTPリクエストのデータを受け取りたい場合は、以下の2つのオプションのいずれかを実行する必要があります。 **オプション#1 - `ignore_body`を使用してSanicにボディを消費するように指示する。** ```python @@ -157,8 +145,7 @@ async def handler(request: Request): ---:1 -Sanicでは、パターンマッチングやURLパスからの値の抽出が可能です。これらのパラメータは、キーワード引数としてルートハンドラに挿入されます。 -:--:1 +Sanicでは、パターンマッチングやURLパスからの値の抽出が可能です。 これらのパラメータは、キーワード引数としてルートハンドラに挿入されます。 :--:1 :--:1 ```python @app.get("/tag/") async def tag_handler(request, tag): @@ -168,8 +155,7 @@ async def tag_handler(request, tag): ---:1 -パラメータの型を宣言できます。これはマッチング時に強制され、変数を型キャストします。 -:--:1 +パラメータの型を宣言できます。 これはマッチング時に強制され、変数を型キャストします。 :--:1 :--:1 ```python @app.get("/foo/") async def uuid_handler(request, foo_id: UUID): @@ -194,10 +180,31 @@ async def handler(request, foo: str): - `/path/to/Bob` - `/path/to/Python%203` +Beginning in v22.3 `str` will *not* match on empty strings. See `strorempty` for this behavior. -以前のバージョンのSanicでは、この形式は非推奨となり、v21.12で削除される予定です。 ::: -::: tab int +::: tab strorempty + +```python +async def handler(request, ws): + messgage = "Start" + while True: + await ws.send(message) + message = ws.recv() + +app.add_websocket_route(handler, "/test") +``` +**Regular expression applied**: `r"[^/]*")` +**Cast type**: `str` +**Example matches**: +- `/path/to/Bob` +- `/path/to/Python%203` +- `/path/to/` + +Unlike the `str` path parameter type, `strorempty` can also match on an empty string path segment. + +以前のバージョンのSanicでは、この形式は非推奨となり、v21.12で削除される予定です。 ::: +::: tab alpha ```python @app.route("/path/to/") @@ -210,8 +217,7 @@ async def handler(request, foo: int): - `/path/to/10` - `/path/to/-10` -_Does not match float, hex, octal, etc_ -::: +_Does not match float, hex, octal, etc_ ::: ::: tab float ```python @@ -226,7 +232,6 @@ async def handler(request, foo: float): - `/path/to/-10` - `/path/to/1.5` -以前のバージョンのSanicでは、この形式は非推奨となり、v21.12で削除される予定です。 ::: ::: tab alpha @@ -241,8 +246,7 @@ async def handler(request, foo: str): - `/path/to/Bob` - `/path/to/Python` -_Does not match a digit, or a space or other special character_ -::: +_Does not match a digit, or a space or other special character_ ::: ::: tab slug ```python @@ -256,7 +260,7 @@ async def handler(request, article: str): - `/path/to/some-news-story` - `/path/to/or-has-digits-123` -::: +*Added in v21.6* ::: ::: tab path ```python @@ -271,10 +275,9 @@ async def handler(request, foo: str): - `/path/to/hello.txt` - `/path/to/hello/world.txt` -::: warning -これは`/`で一致するため、`path`を使用するパターンを慎重に徹底的にテストして、別のエンドポイント向けのトラフィックをキャプチャしないようにする必要があります。 -::: +::: warning これは`/`で一致するため、`path`を使用するパターンを慎重に徹底的にテストして、別のエンドポイント向けのトラフィックをキャプチャしないようにする必要があります。 ::: ::: tab ymd +::: ::: tab ymd ```python @app.route("/path/to/") @@ -284,8 +287,7 @@ async def handler(request, foo: datetime.date): **Regular expression applied**: `r"^([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))"` **Cast type**: `datetime.date` **Example matches**: -- `/path/to/2021-03-28` -::: +- `/path/to/2021-03-28` ::: ::: tab uuid @@ -301,10 +303,153 @@ async def handler(request, foo: UUID): ::: +複雑なルーティングと比較すると、上記の例は単純すぎることが多く、まったく異なるルーティングマッチングパターンを使用するため、ここではregexマッチングの高度な使用方法について詳しく説明します。 + +```python +@app.route("/path/to/") +async def handler(request, foo: str, ext: str): + ... +``` +ルートの一部を照合する場合があります。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ definition + + example + + filename + + extension +
+ \ + + page.txt + + "page" + + "txt" +
+ \ + + cat.jpg + + "cat" + + "jpg" +
+ \ + + + png\ + + gif\ + + svg> | cat.jpg | "cat" | "jpg" +
+ + + 123.txt + + 123 + + "txt" +
+ + + + png\ + + gif\ + + svg> | 123.svg | 123 | "svg" +
+ + + 3.14.tar.gz + + 3.14 + + "tar.gz" +
+ +File extensions can be matched using the special `ext` parameter type. It uses a special format that allows you to specify other types of parameter types as the file name, and one or more specific extensions as shown in the example table above. + +さらに、これらはすべて許容可能です。 + +また、名前付き一致グループを使用する場合は、セグメントラベルと同じである必要があります。 + ::: tab regex ```python -@app.route(r"/path/to/") +@app.route(r"/path/to/") async def handler(request, foo: str): ... ``` @@ -313,9 +458,9 @@ async def handler(request, foo: str): **Example matches**: - `/path/to/2021-01-01` -これにより、ユースケースの特定のマッチング・パターンを自由に定義できます。 -この例では、YYYY-MM-DD形式の日付を探しています。 +This gives you the freedom to define specific matching patterns for your use case. +これにより、ユースケースの特定のマッチング・パターンを自由に定義できます。 この例では、YYYY-MM-DD形式の日付を探しています。 :::: @@ -323,9 +468,9 @@ async def handler(request, foo: str): -複雑なルーティングと比較すると、上記の例は単純すぎることが多く、まったく異なるルーティングマッチングパターンを使用するため、ここではregexマッチングの高度な使用方法について詳しく説明します。 +More often than not, compared with complex routing, the above example is too simple, and we use a completely different routing matching pattern, so here we will explain the advanced usage of regex matching in detail. -ルートの一部を照合する場合があります。 +Sometimes, you want to match a part of a route: ```text /image/123456789.jpg @@ -337,7 +482,7 @@ async def handler(request, foo: str): app.route(r"/image/\d+)\.jpg>") ``` -さらに、これらはすべて許容可能です。 +また、1つのクエリキーに複数の値を渡すこともサポートされています。 :--:1 ```python @app.get(r"/") # matching on the full pattern @@ -346,7 +491,7 @@ app.route(r"/image/\d+)\.jpg>") @app.get(r"/[a-z]{3}).(?:txt)>") # defining a single named matching group, with one or more non-matching groups ``` -また、名前付き一致グループを使用する場合は、セグメントラベルと同じである必要があります。 +Also, if using a named matching group, it must be the same as the segment label. ```python @app.get(r"/\d+).jpg>") # OK @@ -359,14 +504,13 @@ app.route(r"/image/\d+)\.jpg>") ---:1 -Sanicは、ハンドラメソッド名`app.url_for()`に基づいてURLを生成するメソッドを提供しています。これは、アプリケーションへのURLパスをハードコーディングしない場合に便利です。代わりに、ハンドラ名を参照することができます。 -:--:1 +Sanicは、ハンドラメソッド名`app.url_for()`に基づいてURLを生成するメソッドを提供しています。 これは、アプリケーションへのURLパスをハードコーディングしない場合に便利です。 代わりに、ハンドラ名を参照することができます。 :--:1 ```python @app.route('/') async def index(request): # generate a URL for the endpoint `post_handler` url = app.url_for('post_handler', post_id=5) - + # Redirect to `/posts/5` return redirect(url) @@ -378,8 +522,7 @@ async def post_handler(request, post_id): ---:1 -任意の数のキーワード引数を渡すことができます。_not_a要求パラメータであるものはすべて、クエリ文字列の一部として実装されます。 -:--:1 +任意の数のキーワード引数を渡すことができます。 _not_a要求パラメータであるものはすべて、クエリ文字列の一部として実装されます。 :--:1 :--:1 ```python >>> app.url_for( "post_handler", @@ -393,8 +536,7 @@ async def post_handler(request, post_id): ---:1 -また、1つのクエリキーに複数の値を渡すこともサポートされています。 -:--:1 +Also supported is passing multiple values for a single query key. :--:1 ```python >>> app.url_for( "post_handler", @@ -430,8 +572,7 @@ See [API Docs]() for more details. ---:1 -カスタムルート名は、ルートの登録時に`name`引数を渡すことで使用できます。 -:--:1 +カスタムルート名は、ルートの登録時に`name`引数を渡すことで使用できます。 :--:1 :--:1 ```python @app.get("/get", name="get_handler") def handler(request): @@ -441,8 +582,7 @@ def handler(request): ---:1 -ここで、このカスタム名を使用してURLを取得します。 -:--:1 +ここで、このカスタム名を使用してURLを取得します。 :--:1 ```python >>> app.url_for("get_handler", foo="bar") '/get?foo=bar' @@ -453,14 +593,13 @@ def handler(request): ---:1 -WebsocketルーティングはHTTPメソッドと同様に動作します。 -:--:1 +WebsocketルーティングはHTTPメソッドと同様に動作します。 :--:1 :--:1 ```python async def handler(request, ws): - messgage = "Start" + message = "Start" while True: await ws.send(message) - message = ws.recv() + message = await ws.recv() app.add_websocket_route(handler, "/test") ``` @@ -468,15 +607,14 @@ app.add_websocket_route(handler, "/test") ---:1 -便利なデコレーターも付いています。 -:--:1 +It also has a convenience decorator. :--:1 ```python @app.websocket("/test") async def handler(request, ws): - messgage = "Start" + message = "Start" while True: await ws.send(message) - message = ws.recv() + message = await ws.recv() ``` :--- @@ -487,7 +625,7 @@ async def handler(request, ws): ---:1 -Sanicルートは、末尾のスラッシュがあるかどうかに厳密に一致するように設定できます。これはいくつかのレベルで設定でき、次の優先順位に従います。 +Sanicルートは、末尾のスラッシュがあるかどうかに厳密に一致するように設定できます。 これはいくつかのレベルで設定でき、次の優先順位に従います。 1. Route 2. Blueprint @@ -542,8 +680,7 @@ Sanicから静的ファイルを提供するには、`app.static()`を使用し 1. ファイルが提供されるルート 2. サーバー上のファイルへのパス -詳しくは[API docs]()を見てください。 -:--:1 +詳しくは[API docs]()を見てください。 :--:1 :--:1 ```python app.static("/static", "/path/to/directory") ``` @@ -551,8 +688,7 @@ app.static("/static", "/path/to/directory") ---:1 -個々のファイルを提供することもできます。 -:--:1 +個々のファイルを提供することもできます。 :--:1 :--:1 ```python app.static("/", "/path/to/index.html") ``` @@ -560,8 +696,7 @@ app.static("/", "/path/to/index.html") ---:1 -また、エンドポイントに名前を付けると便利な場合もあります。 -:--:1 +また、エンドポイントに名前を付けると便利な場合もあります。 :--:1 ```python app.static( "/user/uploads", @@ -573,8 +708,7 @@ app.static( ---:1 -URLの取得は、ハンドラと同様に機能します。ただし、ディレクトリ内に特定のファイルが必要な場合は、`filename`引数を追加することもできます。 -:--:1 +URLの取得は、ハンドラと同様に機能します。 ただし、ディレクトリ内に特定のファイルが必要な場合は、`filename`引数を追加することもできます。 :--:1 :--:1 ```python >>> app.url_for( "static", @@ -582,7 +716,7 @@ URLの取得は、ハンドラと同様に機能します。ただし、ディ filename="file.txt", ) '/static/file.txt' - +``` ```python >>> app.url_for( "static", @@ -594,8 +728,7 @@ URLの取得は、ハンドラと同様に機能します。ただし、ディ ``` :--- -::: tip -複数の`static()`ルートを使用する場合は、手動で名前を付けることを推奨します。これにより、バグを発見するのが難しい可能性がほぼ確実に軽減されます。 +::: tip 複数の`static()`ルートを使用する場合は、手動で名前を付けることを推奨します。 これにより、バグを発見するのが難しい可能性がほぼ確実に軽減されます。 ```python app.static("/user/uploads", "/path/to/uploads", name="uploads") @@ -604,11 +737,8 @@ app.static("/user/profile", "/path/to/profile", name="profile_pics") ::: ## ルートコンテキスト -::: new NEW in v21.12 ----:1 -ルートが定義されるとき、`ctx_` という接頭辞を持つキーワード引数をいくつでも追加することができます。これらの値はルートの `ctx` オブジェクトにインジェクションされます。 -:--:1 +---:1 ルートが定義されるとき、`ctx_` という接頭辞を持つキーワード引数をいくつでも追加することができます。 これらの値はルートの `ctx` オブジェクトにインジェクションされます。 :--:1 :--:1 ```python @app.get("/1", ctx_label="something") async def handler1(request): @@ -627,4 +757,4 @@ async def do_something(request): if request.route.ctx.label == "something": ... ``` -:--- +:--- *Added in v21.12* diff --git a/src/ja/guide/basics/tasks.md b/src/ja/guide/basics/tasks.md index a5b0cf7248..d9e41ab486 100644 --- a/src/ja/guide/basics/tasks.md +++ b/src/ja/guide/basics/tasks.md @@ -1,7 +1,7 @@ # バックグラウンドタスク ## Tasksを作成 -非同期Pythonで[tasks](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task)を使用することは、望ましいことが多く、非常に便利です。Sanicは、現在実行中の**ループにタスクを追加する便利な方法を提供します。これは`asyncio.create_task`。'App'ループの実行前にタスクを追加する方法については、次のセクションを参照してください。 +非同期Pythonで[tasks](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task)を使用することは、望ましいことが多く、非常に便利です。 Sanicは、現在実行中の**ループにタスクを追加する便利な方法を提供します。 これは`asyncio.create_task`。 'App'ループの実行前にタスクを追加する方法については、次のセクションを参照してください。 ```python async def notify_server_started_after_five_seconds(): @@ -13,8 +13,7 @@ app.add_task(notify_server_started_after_five_seconds()) ---:1 -Sanicは自動的にアプリを注入し、タスクへの引数として渡す。 -:--:1 +Sanicは自動的にアプリを注入し、タスクへの引数として渡す。 :--:1 :--:1 ```python async def auto_inject(app): await asyncio.sleep(5) @@ -26,8 +25,7 @@ app.add_task(auto_inject) ---:1 -または、`app`引数を明示的に渡すこともできます。 -:--:1 +または、`app`引数を明示的に渡すこともできます。 :--:1 :--:1 ```python async def explicit_inject(app): await asyncio.sleep(5) @@ -39,33 +37,32 @@ app.add_task(explicit_inject(app)) ## `app.run`の前にタスクを追加します -Appを実行する前にバックグラウンドタスクを追加することができます。`app.run`の前に置きます。Appが実行される前にタスクを追加するには、コルーチンオブジェクト(つまり、。`async`callable)を渡さずに、単に呼び出し可能オブジェクトを渡すことをお勧めします。Sanicは各ワーカーにコルーチンオブジェクトを作成します**.注:追加されるタスクは`before_server_start`ジョブとして実行されるため、すべてのワーカーで実行されます (メインプロセスでは実行されません) 。これには特定の結果が伴います。詳細については、[この問題](https://github.com/sanic-org/sanic/issues/2139) の [このコメント](https://github.com/sanic-org/sanic/issues/2139#issuecomment-868993668) を参照してください。 +Appを実行する前にバックグラウンドタスクを追加することができます。 `app.run`の前に置きます。 Appが実行される前にタスクを追加するには、コルーチンオブジェクト(つまり、。 `async`callable)を渡さずに、単に呼び出し可能オブジェクトを渡すことをお勧めします。 Sanicは各ワーカーにコルーチンオブジェクトを作成します**.注:追加されるタスクは`before_server_start`ジョブとして実行されるため、すべてのワーカーで実行されます (メインプロセスでは実行されません) 。 Note: the tasks that are added such are run as `before_server_start` jobs and thus run on every worker (and not in the main process). これには特定の結果が伴います。 詳細については、[この問題](https://github.com/sanic-org/sanic/issues/2139) の [このコメント](https://github.com/sanic-org/sanic/issues/2139#issuecomment-868993668) を参照してください。 -メイン・プロセスに作業を追加するには、[`@app.main_process_start`](./listeners.md)。注意:この作業が完了するまで、就業者は開始しません。 +メイン・プロセスに作業を追加するには、[`@app.main_process_start`](./listeners.md)。 注意:この作業が完了するまで、就業者は開始しません。 ---:1 -`app.run`の前にタスクを追加する例 -:---:1 +`app.run`の前にタスクを追加する例 :---:1 ```python async def slow_work(...): ... +async def even_slower(num): + ... + app = Sanic(...) app.add_task(slow_work) # Note: we are passing the callable and not coroutine object `slow_work(...)` app.run(...) +app.add_task(even_slower(10)) # ... or we can call the function and pass the coroutine. +app.run(...) ``` -::: tip -上記の`slow_work`にパラメータを渡すには、`functools.partial`を使用します。 -::: ## Named tasks -::: new NEW in v21.12 -_Python 3.8以上でのみサポートされます_ +_This is only supported in Python 3.8+_ ----:1 -タスクを作成するときに、`name`を指定することで、Sanicにそのタスクを追跡するように依頼することができます。 +::: new NEW in v21.12 _Python 3.8以上でのみサポートされます_ :--:1 ```python @@ -73,8 +70,7 @@ app.add_task(slow_work, name="slow_task") ``` :--- ----:1 -これで、アプリケーションのどこからでも `get_task` を使って、そのタスクのインスタンスを取得できるようになりました。 +---:1 これで、アプリケーションのどこからでも `get_task` を使って、そのタスクのインスタンスを取得できるようになりました。 :--:1 ```python @@ -82,8 +78,7 @@ task = app.get_task("slow_task") ``` :--- ----:1 -そのタスクをキャンセルする必要がある場合は、 `cancel_task` を使って行うことができます。必ず `await` してください。 +---:1 そのタスクをキャンセルする必要がある場合は、 `cancel_task` を使って行うことができます。 必ず `await` してください。 :--:1 ```python @@ -91,8 +86,7 @@ await app.cancel_task("slow_task") ``` :--- ----:1 -登録されたすべてのタスクは `app.tasks` プロパティで確認することができます。キャンセルされたタスクが一杯になるのを防ぐために、`app.purge_tasks`を実行して、完了したタスクやキャンセルされたタスクを消去するとよいでしょう。 +---:1 登録されたすべてのタスクは `app.tasks` プロパティで確認することができます。 キャンセルされたタスクが一杯になるのを防ぐために、`app.purge_tasks`を実行して、完了したタスクやキャンセルされたタスクを消去するとよいでしょう。 :--:1 ```python @@ -100,27 +94,8 @@ app.purge_tasks() ``` :--- +This pattern can be particularly useful with `websockets`: + このパターンは、特に `websocket` で有効です: -```python -async def receiver(ws): - while True: - message = await ws.recv() - if not message: - break - print(f"Received: {message}") - -@app.websocket("/feed") -async def feed(request, ws): - task_name = f"receiver:{request.id}" - request.app.add_task(receiver(ws), name=task_name) - try: - while True: - await request.app.event("my.custom.event") - await ws.send("A message") - finally: - # When the websocket closes, let's cleanup the task - await request.app.cancel_task(task_name) - request.app.purge_tasks() -``` -::: +@app.websocket("/feed") async def feed(request, ws): task_name = f"receiver:{request.id}" request.app.add_task(receiver(ws), name=task_name) try: while True: await request.app.event("my.custom.event") await ws.send("A message") finally: # When the websocket closes, let's cleanup the task await request.app.cancel_task(task_name) request.app.purge_tasks() ::: *Added in v21.12* diff --git a/src/ja/guide/best-practices/blueprints.md b/src/ja/guide/best-practices/blueprints.md index 41c2547f70..e263593c86 100644 --- a/src/ja/guide/best-practices/blueprints.md +++ b/src/ja/guide/best-practices/blueprints.md @@ -2,7 +2,7 @@ ## 概要 -設計図は、アプリケーション内のサブルーティングに使用できるオブジェクトです。設計図では、アプリケーション・インスタンスにルートを追加する代わりに、ルートを追加するための同様のメソッドを定義し、それを柔軟でプラグ可能な方法でアプリケーションに登録します。 +設計図は、アプリケーション内のサブルーティングに使用できるオブジェクトです。 設計図では、アプリケーション・インスタンスにルートを追加する代わりに、ルートを追加するための同様のメソッドを定義し、それを柔軟でプラグ可能な方法でアプリケーションに登録します。 設計図は、アプリケーション・ロジックを複数のグループまたは責任領域に分けることができる大規模なアプリケーションに特に役立ちます。 @@ -10,8 +10,7 @@ ---:1 -まず、設計図を作成する必要があります。これは、多くの同じデコレータを持つ`Sanic()`アプリケーションインスタンスと非常に似たAPIを持っています。 -:--:1 +まず、設計図を作成する必要があります。 これは、多くの同じデコレータを持つ`Sanic()`アプリケーションインスタンスと非常に似たAPIを持っています。 :--:1 :--:1 ```python # ./my_blueprint.py from sanic.response import json @@ -28,8 +27,7 @@ async def bp_root(request): ---:1 -次に、アプリケーションインスタンスに登録します。 -:--:1 +次に、アプリケーションインスタンスに登録します。 :--:1 :--:1 ```python from sanic import Sanic from my_blueprint import bp @@ -43,9 +41,7 @@ Blueprintには、websocketsを実装するための同じ`websocket()`デコレ ---:1 -::: new NEW in v21.12 -v21.12 以降では、ブループリントはオブジェクトを追加する前でも後でも登録することができます。以前は、登録時にブループリントにアタッチされたオブジェクトのみがアプリケーション インスタンスにロードされました。 -:--:1 +::: new NEW in v21.12 v21.12 以降では、ブループリントはオブジェクトを追加する前でも後でも登録することができます。 以前は、登録時にブループリントにアタッチされたオブジェクトのみがアプリケーション インスタンスにロードされました。 :--:1 :--:1 ```python app.blueprint(bp) @@ -56,12 +52,7 @@ async def bp_root(request): :--- ## Copying ----:1 - -`copy()`メソッドを使用すると、設計図とそれにアタッチされているすべてのものを新しいインスタンスにコピーできます。唯一必要な引数は、新しい`name`を渡すことです。ただし、これを使用して、古い設計図の値をオーバーライドすることもできます。 - -:--:1 - +---:1 Blueprints along with everything that is attached to them can be copied to new instances using the `copy()` method. 唯一必要な引数は、新しい`name`を渡すことです。 ただし、これを使用して、古い設計図の値をオーバーライドすることもできます。 :--:1 ```python v1 = Blueprint("Version1", version=1) @@ -81,12 +72,13 @@ Available routes: /v2/something ``` - :--- +*Added in v21.9* + ## Blueprintグループ -設計図は、リストまたはタプルの一部として登録することもでき、レジストラは、設計図のサブシーケンスを再帰的に巡回し、それに従って登録する。Blueprint.groupメソッドは、このプロセスを単純化するために提供されており、フロントエンドから見えるものを模倣する`モック`バックエンドディレクトリ構造を可能にする。次の (かなり不自然な) 例を考えてみましょう。 +設計図は、リストまたはタプルの一部として登録することもでき、レジストラは、設計図のサブシーケンスを再帰的に巡回し、それに従って登録する。 Blueprint.groupメソッドは、このプロセスを単純化するために提供されており、フロントエンドから見えるものを模倣する`モック`バックエンドディレクトリ構造を可能にする。 次の (かなり不自然な) 例を考えてみましょう。 ```text api/ @@ -189,8 +181,7 @@ app.blueprint(api) ---:1 -Blueprintsは、エンドポイント専用に登録されたミドルウェアを持つこともできます。 -:--:1 +Blueprintsは、エンドポイント専用に登録されたミドルウェアを持つこともできます。 :--:1 :--:1 ```python @bp.middleware async def print_on_request(request): @@ -208,8 +199,7 @@ async def halt_response(request, response): ---:1 -同様に、設計図グループを使用すると、ネストされた設計図グループ全体にミドルウェアを適用できます。 -:--:1 +同様に、設計図グループを使用すると、ネストされた設計図グループ全体にミドルウェアを適用できます。 :--:1 :--:1 ```python bp1 = Blueprint("bp1", url_prefix="/bp1") bp2 = Blueprint("bp2", url_prefix="/bp2") @@ -241,8 +231,7 @@ app.blueprint(group) ---:1 -他の[例外処理](./exceptions.md)を使用して、設計図固有のハンドラーを定義できます。 -:--:1 +他の[例外処理](./exceptions.md)を使用して、設計図固有のハンドラーを定義できます。 :--:1 :--:1 ```python @bp.exception(NotFound) def ignore_404s(request, exception): @@ -254,8 +243,7 @@ def ignore_404s(request, exception): ---:1 -Blueprintsは独自の静的ハンドラを持つこともできます。 -:--:1 +Blueprintsは独自の静的ハンドラを持つこともできます。 :--:1 ```python bp = Blueprint("bp", url_prefix="/bp") bp.static("/web/path", "/folder/to/serve") @@ -265,8 +253,7 @@ bp.static("/web/path", "/folder/to/server", name="uploads") ---:1 -これは、`url_for()`を使用して取得できます。詳細については、[ルーティング](/guide/basics/routing.md) を参照してください。 -:--:1 +これは、`url_for()`を使用して取得できます。 詳細については、[ルーティング](/guide/basics/routing.md) を参照してください。 :--:1 :--:1 ```python >>> print(app.url_for("static", name="bp.uploads", filename="file.txt")) '/bp/web/path/file.txt' @@ -277,8 +264,7 @@ bp.static("/web/path", "/folder/to/server", name="uploads") ---:1 -Blueprintsは[listeners](/guide/basics/listeners.md)も実装できます。 -:--:1 +Blueprintsは[listeners](/guide/basics/listeners.md)も実装できます。 :--:1 :--:1 ```python @bp.listener("before_server_start") async def before_server_start(app, loop): @@ -296,8 +282,7 @@ async def after_server_stop(app, loop): ---:1 -`version`は、ルートの前に`/v1`または`/v2`のように付加されます。 -:--:1 +`version`は、ルートの前に`/v1`または`/v2`のように付加されます。 :--:1 ```python auth1 = Blueprint("auth", url_prefix="/auth", version=1) auth2 = Blueprint("auth", url_prefix="/auth", version=2) @@ -306,8 +291,7 @@ auth2 = Blueprint("auth", url_prefix="/auth", version=2) ---:1 -アプリケーションに設計図を登録すると、ルート`/v1/auth`と`/v2/auth`は個々の設計図を指すようになり、各APIバージョンのサブサイトを作成できるようになります。 -:--:1 +アプリケーションに設計図を登録すると、ルート`/v1/auth`と`/v2/auth`は個々の設計図を指すようになり、各APIバージョンのサブサイトを作成できるようになります。 :--:1 :--:1 ```python from auth_blueprints import auth1, auth2 @@ -319,8 +303,7 @@ app.blueprint(auth2) ---:1 -設計図を`BlueprintGroup`エンティティの下にグループ化し、同じ時間。 -:--:1 +設計図を`BlueprintGroup`エンティティの下にグループ化し、同じ時間。 :--:1 :--:1 ```python auth = Blueprint("auth", url_prefix="/auth") metrics = Blueprint("metrics", url_prefix="/metrics") @@ -334,11 +317,9 @@ group = Blueprint.group([auth, metrics], version="v1") ## 合成可能 -`Blueprint`は複数のグループに登録することができ、`BlueprintGroup`自体もそれぞれ登録してさらにネストすることができる。これにより、無限の可能性を持つ`Blueprint`コンポジションが作成されます。 +`Blueprint`は複数のグループに登録することができ、`BlueprintGroup`自体もそれぞれ登録してさらにネストすることができる。 これにより、無限の可能性を持つ`Blueprint`コンポジションが作成されます。 ----:1 -この例を見て、2つのハンドラーが実際には5つの異なるルートとしてマウントされていることを確認してください。 -:--:1 +*Added in v21.6* ---:1 Take a look at this example and see how the two handlers are actually mounted as five (5) distinct routes. :--:1 ```python app = Sanic(__name__) blueprint_1 = Blueprint("blueprint_1", url_prefix="/bp1") diff --git a/src/ja/guide/best-practices/decorators.md b/src/ja/guide/best-practices/decorators.md index 2f5016fe28..b643972d99 100644 --- a/src/ja/guide/best-practices/decorators.md +++ b/src/ja/guide/best-practices/decorators.md @@ -4,8 +4,7 @@ ---:1 -そのため、複数のデコレータを持つSanicビューハンドラが表示されることがよくあります。 -:--:1 +そのため、複数のデコレータを持つSanicビューハンドラが表示されることがよくあります。 :--:1 :--:1 ```python @app.get("/orders") @authorized("view_order") @@ -21,7 +20,7 @@ async def get_order_details(request, params, user): ここに、デコレーターの作成に役立つ初期テンプレートがあります。 -この例では、ユーザーが特定のエンドポイントへのアクセスを許可されていることを確認するとします。ハンドラー関数をラップし、クライアントがリソースへのアクセスを許可されているかどうか要求を検査し、適切な応答を送信するデコレータを作成できます。 +この例では、ユーザーが特定のエンドポイントへのアクセスを許可されていることを確認するとします。 ハンドラー関数をラップし、クライアントがリソースへのアクセスを許可されているかどうか要求を検査し、適切な応答を送信するデコレータを作成できます。 ```python from functools import wraps from sanic.response import json @@ -54,7 +53,7 @@ async def test(request): ## テンプレート -デコレーターは、Sanicでアプリケーションを構築するための**基本**です。これにより、コードの移植性と保守性が向上します。 +デコレーターは、Sanicでアプリケーションを構築するための**基本**です。 これにより、コードの移植性と保守性が向上します。 Pythonの禅を言い換えてこう言います: 「 (装飾家は) 素晴らしいアイデアの一つです--もっといろいろやってみましょう!" @@ -62,7 +61,7 @@ Pythonの禅を言い換えてこう言います: 「 (装飾家は) 素晴ら ---:1 -これらのインポート文を忘れずに追加してください。必要ではありませんが、`@wraps`を使用すると、ファンクションのメタデータの一部をそのまま保持できます。[docs](https://docs.python.org/3/library/functools.html#functools.wraps) を参照してください。また、ここでは 「isawaitable」 パターンを使用して、通常の関数または非同期関数によるルートハンドラの実行を許可します。 +これらのインポート文を忘れずに追加してください。 Although it is *not* necessary, using `@wraps` helps keep some of the metadata of your function intact. [docs](https://docs.python.org/3/library/functools.html#functools.wraps) を参照してください。 また、ここでは 「isawaitable」 パターンを使用して、通常の関数または非同期関数によるルートハンドラの実行を許可します。 :--:1 @@ -77,7 +76,7 @@ from functools import wraps ---:1 -多くの場合、*常に*引数を必要とするデコレータが必要になります。したがって、この関数が実装されると、常に関数を呼び出します。 +多くの場合、*常に*引数を必要とするデコレータが必要になります。 したがって、この関数が実装されると、常に関数を呼び出します。 ```python @app.get("/") @@ -111,7 +110,7 @@ def foobar(arg1, arg2): ---:1 -引数を取らないデコレータが必要な場合もあります。このような場合、それを呼び出す必要がないのは便利です。 +引数を取らないデコレータが必要な場合もあります。 このような場合、それを呼び出す必要がないのは便利です。 ```python @app.get("/") @@ -145,7 +144,7 @@ def foobar(func): ---:1 -呼び出し可能かどうかを指定できるデコレータが必要な場合は、このパターンに従います。キーワードのみの引数を使用する必要はありませんが、実装が簡単になる場合があります。 +呼び出し可能かどうかを指定できるデコレータが必要な場合は、このパターンに従います。 キーワードのみの引数を使用する必要はありませんが、実装が簡単になる場合があります。 ```python @app.get("/") diff --git a/src/ja/guide/best-practices/exceptions.md b/src/ja/guide/best-practices/exceptions.md index 5a512390b3..6ee2d2b825 100644 --- a/src/ja/guide/best-practices/exceptions.md +++ b/src/ja/guide/best-practices/exceptions.md @@ -2,9 +2,9 @@ ## Sanicの例外を使う -場合によっては、ハンドラの実行を停止し、ステータスコード応答を返すようにSanicに指示する必要があります。このために 「SanicException」 を発生させることができ、残りはSanicが行います。 +場合によっては、ハンドラの実行を停止し、ステータスコード応答を返すようにSanicに指示する必要があります。 このために 「SanicException」 を発生させることができ、残りはSanicが行います。 -オプションの`status_code`引数を渡すことができます。デフォルトでは、SanicExceptionは内部サーバエラー500応答を返します。 +オプションの`status_code`引数を渡すことができます。 デフォルトでは、SanicExceptionは内部サーバエラー500応答を返します。 ```python from sanic.exceptions import SanicException @@ -14,7 +14,7 @@ async def no_no(request): raise SanicException("Something went wrong.", status_code=501) ``` -Sanicはいくつかの標準的な例外を提供しています。これらはそれぞれ、応答で適切なHTTPステータスコードを自動的に生成します。詳しくは[APIリファレンスを確認](https://sanic.readthedocs.io/en/latest/sanic/api_reference.html#module-sanic.exceptions)を参照してください。 +Sanicはいくつかの標準的な例外を提供しています。 これらはそれぞれ、応答で適切なHTTPステータスコードを自動的に生成します。 詳しくは[APIリファレンスを確認](https://sanic.readthedocs.io/en/latest/sanic/api_reference.html#module-sanic.exceptions)を参照してください。 ---:1 @@ -45,7 +45,7 @@ async def login(request): ## 例外プロパティ -Sanicのすべての例外は `SanicException` から派生します。このクラスにはいくつかのプロパティがあり、開発者がアプリケーション全体で一貫して例外を報告できるように支援します。 +Sanicのすべての例外は `SanicException` から派生します。 このクラスにはいくつかのプロパティがあり、開発者がアプリケーション全体で一貫して例外を報告できるように支援します。 - `message` - `status_code` @@ -58,8 +58,7 @@ Sanicのすべての例外は `SanicException` から派生します。このク ---:1 ### `message` -`message` プロパティは、Python の他の例外と同様に、表示されるメッセージを制御します。特に便利なのは、クラス定義で `message` プロパティを設定できることで、アプリケーション全体の言語を簡単に標準化することができることです。 -:--:1 +`message` プロパティは、Python の他の例外と同様に、表示されるメッセージを制御します。 特に便利なのは、クラス定義で `message` プロパティを設定できることで、アプリケーション全体の言語を簡単に標準化することができることです。 :--:1 ```python class CustomError(SanicException): message = "Something bad happened" @@ -73,8 +72,7 @@ raise CustomError("Override the default message with something else") ---:1 ### `status_code` -このプロパティは、例外が発生したときの応答コードを設定するために用います。これは、通常クライアントから来る悪い情報への対応である、カスタムの400シリーズ例外を作成するときに特に有用です。 -:--:1 +このプロパティは、例外が発生したときの応答コードを設定するために用います。 これは、通常クライアントから来る悪い情報への対応である、カスタムの400シリーズ例外を作成するときに特に有用です。 :--:1 :--:1 ```python class TeapotError(SanicException): status_code = 418 @@ -89,8 +87,7 @@ raise TeapotError(status_code=400) ---:1 ### `quiet` -デフォルトでは、例外は Sanic によって `error_logger` に出力されます。特に例外ハンドラでイベントのトリガとして例外を使っている場合、これは望ましくないことがあります ([次のセクションを参照してください](./exceptions.md#handling))。`quiet=True`を使用すると、ログ出力を抑制することができます。 -:--:1 +デフォルトでは、例外は Sanic によって `error_logger` に出力されます。 特に例外ハンドラでイベントのトリガとして例外を使っている場合、これは望ましくないことがあります ([次のセクションを参照してください](./exceptions.md#handling))。 `quiet=True`を使用すると、ログ出力を抑制することができます。 :--:1 :--:1 ```python class SilentError(SanicException): message = "Something happened, but not shown in logs" @@ -102,35 +99,31 @@ raise InvalidUsage("blah blah", quiet=True) ``` :--- ----:1 -::: new NEW in v21.12 -デバッグ中に、`quiet=True` プロパティをグローバルに無視したいことがあるかもしれません。このプロパティに関係なく、Sanicにすべての例外をログアウトさせるには、 `NOISY_EXCEPTIONS` を使用します。 -::: -:--:1 +---:1 Sometimes while debugging you may want to globally ignore the `quiet=True` property. このプロパティに関係なく、Sanicにすべての例外をログアウトさせるには、 `NOISY_EXCEPTIONS` を使用します。 ::: :--:1 + +*Added in v21.12* :--:1 ```python app.config.NOISY_EXCEPTIONS = True ``` :--- ---:1 -::: new NEW in v21.12 ### `extra` -[文脈上の例外](./exceptions.md#contextual-exceptions)を参照してください。 -::: -:--:1 +---:1 ::: new NEW in v21.12 + +*Added in v21.12* :--:1 ```python raise SanicException(..., extra={"name": "Adam"}) ``` :--- ---:1 -::: new NEW in v21.12 ### `context` -[文脈上の例外](./exceptions.md#contextual-exceptions)を参照してください。 -::: -:--:1 +Sanicはこのためのデコレータを提供しており、これはSanic標準の例外だけでなく、アプリケーションがスローする可能性のある**任意の**例外にも適用されます。 + +*Added in v21.12* :--:1 ```python raise SanicException(..., context={"foo": "bar"}) ``` @@ -139,13 +132,13 @@ raise SanicException(..., context={"foo": "bar"}) ## 取り扱い -Sanicはエラーページを表示することで例外を自動的に処理するので、多くの場合、ユーザ自身が処理する必要はありません。ただし、例外が発生したときの処理をより詳細に制御したい場合は、ハンドラを自分で実装できます。 +Sanicはエラーページを表示することで例外を自動的に処理するので、多くの場合、ユーザ自身が処理する必要はありません。 ただし、例外が発生したときの処理をより詳細に制御したい場合は、ハンドラを自分で実装できます。 -Sanicはこのためのデコレータを提供しており、これはSanic標準の例外だけでなく、アプリケーションがスローする可能性のある**任意の**例外にも適用されます。 +Sanic provides a decorator for this, which applies to not only the Sanic standard exceptions, but **any** exception that your application might throw. ---:1 -ハンドラを追加する最も簡単な方法は、`@app.exception()`を使用して1つ以上の例外を渡すことです。 +`Exception`をキャッチすることで、catchallハンドラを作成することもできます。 :--:1 @@ -161,7 +154,7 @@ async def ignore_404s(request, exception): ---:1 -`Exception`をキャッチすることで、catchallハンドラを作成することもできます。 +`app.error_handler.add()`を使用してエラー・ハンドラを追加することもできます。 :--:1 @@ -175,7 +168,7 @@ async def catch_anything(request, exception): ---:1 -`app.error_handler.add()`を使用してエラー・ハンドラを追加することもできます。 +ハンドラを追加する最も簡単な方法は、`@app.exception()`を使用して1つ以上の例外を渡すことです。 :--:1 @@ -190,14 +183,11 @@ app.error_handler.add(Exception, server_error_handler) ## 組み込みのエラー処理 -Sanicには、例外用にHTML、JSON、およびテキストの3つの形式が用意されています。以下の [Fallback handler](#fallback-handler) セクションに例があります。 - ----:1 - -route_which形式を使用するには、`error_format`キーワード引数を使用します。 +Sanicには、例外用にHTML、JSON、およびテキストの3つの形式が用意されています。 以下の [Fallback handler](#fallback-handler) セクションに例があります。 -:--:1 +---:1 You can control _per route_ which format to use with the `error_format` keyword argument. +*Added in v21.9* :--:1 ```python @app.request("/", error_format="text") async def handler(request): @@ -209,7 +199,7 @@ async def handler(request): ## カスタムエラー処理 -場合によっては、デフォルトで提供される機能にさらにエラー処理機能を追加する必要があります。この場合、次のようにSanicのデフォルトのエラーハンドラをサブクラス化できます。 +場合によっては、デフォルトで提供される機能にさらにエラー処理機能を追加する必要があります。 この場合、次のようにSanicのデフォルトのエラーハンドラをサブクラス化できます。 ```python from sanic.handlers import ErrorHandler @@ -225,7 +215,7 @@ app.error_handler = CustomErrorHandler() ## ハンドルフォールバック -Sanicには3つのフォールバック例外ハンドラがあります。 +Sanic comes with three fallback exception handlers: 1. HTML (*default*) 2. Text @@ -386,15 +376,14 @@ content-type: application/json ### 自動 -Sanicには、使用するフォールバックオプションを推測するためのオプションも用意されています。これはまだ**実験的な機能です**. +Sanicには、使用するフォールバックオプションを推測するためのオプションも用意されています。 これはまだ**実験的な機能です**. ```python app.config.FALLBACK_ERROR_FORMAT = "auto" ``` ## 文脈上の例外 -::: new NEW in v21.12 -デフォルトの例外メッセージにより、アプリケーション全体で一貫して例外を発生させる機能を簡素化できます。 +---:1 **制作** ```python class TeapotError(SanicException): @@ -404,11 +393,13 @@ class TeapotError(SanicException): raise TeapotError ``` -しかし、これには2つのことが欠けています。 +:--:1 **開発** 1. ダイナミックで予測可能なメッセージのフォーマット 2. エラーメッセージに追加のコンテキストを追加する機能(詳細は後述します) +*Added in v21.12* + ### `Extra` を使用した動的で予測可能なメッセージ Sanic の例外は `extra` キーワード引数を使って発生させることができ、発生した例外インスタンスに追加情報を提供することができます。 @@ -424,30 +415,25 @@ class TeapotError(SanicException): raise TeapotError(extra={"name": "Adam"}) ``` -この新しい機能では、例外インスタンスに `extra` メタを渡すことができます。これは、上記の例のように、メッセージテキストに動的なデータを渡すのに特に便利です。この `extra` 情報オブジェクトは、 `PRODUCTION` モードでは抑制され**、 `DEVELOPMENT` モードでは表示されます。 +この新しい機能では、例外インスタンスに `extra` メタを渡すことができます。 これは、上記の例のように、メッセージテキストに動的なデータを渡すのに特に便利です。 この `extra` 情報オブジェクトは、 `PRODUCTION` モードでは抑制され**、 `DEVELOPMENT` モードでは表示されます。 ----:1 -**制作** +:--:1 **開発** -![image](https://user-images.githubusercontent.com/166269/139014161-cda67cd1-843f-4ad2-9fa1-acb94a59fc4d.png) -:--:1 -**開発** +![image](https://user-images.githubusercontent.com/166269/139014161-cda67cd1-843f-4ad2-9fa1-acb94a59fc4d.png) ---:1 **制作** -![image](https://user-images.githubusercontent.com/166269/139014121-0596b084-b3c5-4adb-994e-31ba6eba6dad.png) -:--- +![image](https://user-images.githubusercontent.com/166269/139014121-0596b084-b3c5-4adb-994e-31ba6eba6dad.png) :--- ### エラーメッセージに `context` を追加する -Sanic の例外は、 `context` 引数とともに発生させて、何が起こったのかについてユーザーに意図した情報を渡すこともできます。これは、マイクロサービスや、JSON形式のエラーメッセージを渡すことを目的としたAPIを作成するときに特に便利です。この使用例では、クライアントに詳細を返すために、解析可能なエラーメッセージだけでなく、例外の周りにいくつかのコンテキストを持ちたいと思います。 +Sanic の例外は、 `context` 引数とともに発生させて、何が起こったのかについてユーザーに意図した情報を渡すこともできます。 これは、マイクロサービスや、JSON形式のエラーメッセージを渡すことを目的としたAPIを作成するときに特に便利です。 この使用例では、クライアントに詳細を返すために、解析可能なエラーメッセージだけでなく、例外の周りにいくつかのコンテキストを持ちたいと思います。 ```python raise TeapotError(context={"foo": "bar"}) ``` -これは、(利用可能であれば)常にエラーで渡される**ようにしたい**情報です。以下のような感じです。 +これは、(利用可能であれば)常にエラーで渡される**ようにしたい**情報です。 以下のような感じです。 ----:1 -**制作** +---:1 **PRODUCTION** ```json { @@ -459,8 +445,7 @@ raise TeapotError(context={"foo": "bar"}) } } ``` -:--:1 -**開発** +:--:1 **DEVELOPMENT** ```json { @@ -495,4 +480,3 @@ raise TeapotError(context={"foo": "bar"}) } ``` :--- -::: diff --git a/src/ja/guide/best-practices/logging.md b/src/ja/guide/best-practices/logging.md index 1b308c3930..b4909ab716 100644 --- a/src/ja/guide/best-practices/logging.md +++ b/src/ja/guide/best-practices/logging.md @@ -1,13 +1,12 @@ # Logging -Sanicでは、[Python logging API](https://docs.python.org/3/howto/logging.html)に基づいて、さまざまなタイプのロギング(アクセスログ、エラーログ)を要求に対して実行できます。新しい設定を作成する場合は、Pythonロギングに関する基本的な知識が必要です。 +Sanicでは、[Python logging API](https://docs.python.org/3/howto/logging.html)に基づいて、さまざまなタイプのロギング(アクセスログ、エラーログ)を要求に対して実行できます。 新しい設定を作成する場合は、Pythonロギングに関する基本的な知識が必要です。 ## クイックスタート ---:1 -デフォルト設定を使用する簡単な例を次に示します。 -:--:1 +デフォルト設定を使用する簡単な例を次に示します。 :--:1 ```python from sanic import Sanic from sanic.log import logger @@ -48,27 +47,24 @@ if __name__ == "__main__": app.run(access_log=False) ``` -::: tip FYI -Pythonでのロギングは比較的安価な操作です。ただし、多数の要求を処理していて、パフォーマンスが懸念される場合は、アクセスログをログアウトする時間が増え、非常にコストがかかります。 +::: tip FYI Pythonでのロギングは比較的安価な操作です。 ただし、多数の要求を処理していて、パフォーマンスが懸念される場合は、アクセスログをログアウトする時間が増え、非常にコストがかかります。 -これは、 (nginxのような) プロキシの背後にSanicを置き、そこでアクセスログを取る良い機会です。`access_log`を無効にすると、全体的なパフォーマンスが*大幅に*向上します。 +これは、 (nginxのような) プロキシの背後にSanicを置き、そこでアクセスログを取る良い機会です。 `access_log`を無効にすると、全体的なパフォーマンスが*大幅に*向上します。 -最適な運用パフォーマンスを得るには、`debug`と`access_log`を無効にした状態で、`app.run(debug=False、access_log=False)`でSanicを実行することをお勧めします。 -::: +最適な運用パフォーマンスを得るには、`debug`と`access_log`を無効にした状態で、`app.run(debug=False、access_log=False)`でSanicを実行することをお勧めします。 ::: ## 構成 -Sanicのデフォルトのロギング設定は、`sanic.log。LOGGING_CONFIG_DEFAULTS`を参照してください。 +Sanicのデフォルトのロギング設定は、`sanic.log。 LOGGING_CONFIG_DEFAULTS`を参照してください。 ----:1 -sanicで使用されるロガーは3つあり、独自のロギング設定を作成する場合は定義する必要があります。 +---:1 sanicで使用されるロガーは3つあり、独自のロギング設定を作成する場合は定義する必要があります。 -| **Logger Name** | **Use Case** | -|-----------------|-------------------------------| +| **Logger Name** | **Use Case** | +| --------------- | ------------------ | | `sanic.root` | 内部メッセージのログに使用されます。 | -| `sanic.error` | エラーログの記録に使用されます。 | +| `sanic.error` | エラーログの記録に使用されます。 | | `sanic.access` | アクセスログの記録に使用されます。 | -:--:1 + :--:1 :--- @@ -76,12 +72,12 @@ sanicで使用されるロガーは3つあり、独自のロギング設定を Pythonが提供するデフォルトのパラメータ(`asctime`,`levelname`,`message`)に加えて、SanicはAccess Loggerに追加のパラメータを提供します。 -| Log Context Parameter | Parameter Value | Datatype | -|-----------------------|---------------------------------------|----------| -| `host` | `request.ip` | `str` | -| `request` | `request.method + " " + request.url` | `str` | -| `status` | `response` | `int` | -| `byte` | `len(response.body)` | `int` | +| Log Context Parameter | Parameter Value | Datatype | +| --------------------- | ------------------------------------ | -------- | +| `host` | `request.ip` | `str` | +| `request` | `request.method + " " + request.url` | `str` | +| `status` | `response` | `int` | +| `byte` | `len(response.body)` | `int` | diff --git a/src/ja/guide/deployment/app-loader.md b/src/ja/guide/deployment/app-loader.md new file mode 100644 index 0000000000..960138e3a9 --- /dev/null +++ b/src/ja/guide/deployment/app-loader.md @@ -0,0 +1,75 @@ +# Dynamic Applications + +Running Sanic has been optimized to work with the CLI. If you have not read it yet, you should read [Running Sanic](./running.md#sanic-server) to become familiar with the options. + +---:1 This includes running it as a global scope object... :--:1 +```python +# server.py +app = Sanic("TestApp") + +@app.get("/") +async def handler(request: Request): + return json({"foo": "bar"}) +``` +``` +sanic path.to.server:app +``` +:--- + + +---:1 ...or, a factory function that creates the `Sanic` application object. :--:1 +```python +# server.py +def create_app(): + app = Sanic("TestApp") + + @app.get("/") + async def handler(request: Request): + return json({"foo": "bar"}) + + return app +``` +``` +sanic path.to.server:create_app --factory +``` +:--- + + +**Sometimes, this is not enough ... :thinking:** + +Introduced in [v22.9](../release-notes/v22.9.md), Sanic has an `AppLoader` object that is responsible for creating an application in the various [worker processes](./manager.md#how-sanic-server-starts-processes). You can take advantage of this if you need to create a more dynamic startup experience for your application. + +---:1 An `AppLoader` can be passed a callable that returns a `Sanic` instance. That `AppLoader` could be used with the low-level application running API. :--:1 +```python +import sys +from functools import partial + +from sanic import Request, Sanic, json +from sanic.worker.loader import AppLoader + + +def attach_endpoints(app: Sanic): + @app.get("/") + async def handler(request: Request): + return json({"app_name": request.app.name}) + + +def create_app(app_name: str) -> Sanic: + app = Sanic(app_name) + attach_endpoints(app) + return app + + +if __name__ == "__main__": + app_name = sys.argv[-1] + loader = AppLoader(factory=partial(create_app, app_name)) + app = loader.load() + app.prepare(port=9999, dev=True) + Sanic.serve(primary=app, app_loader=loader) +``` +``` +$ python path/to/server.py MyTestAppName +``` +:--- + +In the above example, the `AppLoader` is created with a `factory` that can be used to create copies of the same application across processes. When doing this, you should explicitly use the `Sanic.serve` pattern shown above so that the `AppLoader` that you create is not replaced. diff --git a/src/ja/guide/deployment/configuration.md b/src/ja/guide/deployment/configuration.md index ebe0a8be97..d103de406e 100644 --- a/src/ja/guide/deployment/configuration.md +++ b/src/ja/guide/deployment/configuration.md @@ -5,8 +5,7 @@ ---:1 -Sanicは、アプリケーションオブジェクトのconfig属性に設定を保持します。構成オブジェクトは、ドット表記法を使用して、または辞書のように変更できる単なるオブジェクトです。 -:--:1 +Sanicは、アプリケーションオブジェクトのconfig属性に設定を保持します。 構成オブジェクトは、ドット表記法を使用して、または辞書のように変更できる単なるオブジェクトです。 :--:1 ```python app = Sanic("myapp") app.config.DB_NAME = "appdb" @@ -16,8 +15,7 @@ app.config["DB_USER"] = "appuser" ---:1 -また、通常の辞書と同様に`update()`メソッドを使用することもできます。 -:--:1 +また、通常の辞書と同様に`update()`メソッドを使用することもできます。 :--:1 ```python db_settings = { 'DB_HOST': 'localhost', @@ -28,9 +26,7 @@ app.config.update(db_settings) ``` :--- -::: tip -Sanicでは、設定値には**大文字で名前を付けるのが一般的です**.実際、大文字と小文字を混在させると奇妙な動作をすることがあります。 -::: +::: tip Sanicでは、設定値には**大文字で名前を付ける**のが一般的です。実際、大文字と小文字を混在させると奇妙な動作をすることがあります。 確かに、大文字と小文字の名前を混ぜ始めると、奇妙な動作が発生することがあります。 ::: ## 読み込み中 @@ -38,8 +34,7 @@ Sanicでは、設定値には**大文字で名前を付けるのが一般的で ---:1 -`SANIC_`プレフィックスで定義された環境変数は、Sanicの設定に適用されます。たとえば、設定`SANIC_REQUEST_TIMEOUT`はアプリケーションによって自動的にロードされ、`REQUEST_TIMEOUT`構成変数に渡されます。 -:--:1 +`SANIC_`プレフィックスで定義された環境変数は、Sanicの設定に適用されます。 たとえば、設定`SANIC_REQUEST_TIMEOUT`はアプリケーションによって自動的にロードされ、`REQUEST_TIMEOUT`構成変数に渡されます。 :--:1 ```bash $ export SANIC_REQUEST_TIMEOUT=10 ``` @@ -51,13 +46,12 @@ $ export SANIC_REQUEST_TIMEOUT=10 ---:1 -起動時にSanicが要求するプレフィクスを変更できます。 -:--:1 +起動時にSanicが要求するプレフィクスを変更できます。 :--:1 ```bash $ export MYAPP_REQUEST_TIMEOUT=10 ``` ```python ->>> app = Sanic(__name__, load_env='MYAPP_') +>>> app = Sanic(__name__, env_prefix='MYAPP_') >>> print(app.config.REQUEST_TIMEOUT) 10 ``` @@ -65,8 +59,7 @@ $ export MYAPP_REQUEST_TIMEOUT=10 ---:1 -環境変数のロードを完全に無効にすることもできます。 -:--:1 +環境変数のロードを完全に無効にすることもできます。 :--:1 ```python app = Sanic(__name__, load_env=False) ``` @@ -74,14 +67,13 @@ app = Sanic(__name__, load_env=False) ### Sanic.update_configを使用する -`Sanic`インスタンスには、config:`app.update_config`をロードするための_very_versatileメソッドがあります。ファイル、辞書、クラスなど、あらゆる種類のオブジェクトへのパスを指定できます。 +`Sanic`インスタンスには、config:`app.update_config`をロードするための_very_versatileメソッドがあります。 ファイル、辞書、クラスなど、あらゆる種類のオブジェクトへのパスを指定できます。 -#### From a file +#### ファイルから ---:1 -たとえば、次のような`my_config.py`ファイルがあるとします。 -:--:1 +たとえば、次のような`my_config.py`ファイルがあるとします。 :--:1 ```python # my_config.py A = 1 @@ -91,8 +83,7 @@ B = 2 ---:1 -パスを`app.update_config`に渡すことで、これを構成値としてロードできます。 -:--:1 +パスを`app.update_config`に渡すことで、これを構成値としてロードできます。 :--:1 ```python >>> app.update_config("/path/to/my_config.py") >>> print(app.config.A) @@ -102,8 +93,7 @@ B = 2 ---:1 -このパスはbashスタイルの環境変数も受け付けます。 -:--:1 +このパスはbashスタイルの環境変数も受け付けます。 :--:1 ```bash $ export my_path="/path/to" ``` @@ -112,15 +102,12 @@ app.update_config("${my_path}/my_config.py") ``` :--- -::: tip -環境変数は`${environment_variable}`の形式で指定する必要があり、`$environment_variable`は展開されません ("プレーン"テキストとして扱われます) 。 -::: +::: tip 環境変数は`${environment_variable}`の形式で指定する必要があり、`$environment_variable`は展開されません ("プレーン"テキストとして扱われます) 。 ::: #### 辞書から ---:1 -`app.update_config`メソッドは、通常の辞書でも動作します。 -:--:1 +`app.update_config`メソッドは、通常の辞書でも動作します。 :--:1 ```python app.update_config({"A": 1, "B": 2}) ``` @@ -130,8 +117,7 @@ app.update_config({"A": 1, "B": 2}) ---:1 -独自の構成クラスを定義して、`app.update_config` -:--:1 +独自の構成クラスを定義して、`app.update_config`に渡すこともできます。 :--:1 ```python class MyConfig: A = 1 @@ -143,8 +129,7 @@ app.update_config(MyConfig) ---:1 -インスタンス化も可能です。 -:--:1 +インスタンス化することもできます。 :--:1 ```python app.update_config(MyConfig()) ``` @@ -152,7 +137,7 @@ app.update_config(MyConfig()) ### 型キャスティング -環境変数から読み込む場合、Sanicは値を期待されるPythonの型にキャストしようとします。これは特に以下に当てはまります。 +環境変数から読み込む場合、Sanicは値を期待されるPythonの型にキャストしようとします。 これは特に以下に当てはまります。 - `int` - `float` @@ -163,76 +148,79 @@ app.update_config(MyConfig()) - **`True`**: `y`, `yes`, `yep`, `yup`, `t`, `true`, `on`, `enable`, `enabled`, `1` - **`False`**: `n`, `no`, `f`, `false`, `off`, `disable`, `disabled`, `0` -::: new NEW in v21.12 +If a value cannot be cast, it will default to a `str`. ----:1 -さらに、Sanicは追加のタイプコンバータを使用して、追加の型をキャストするように設定することができます。これは、値を返すか、`ValueError`を発生させる任意のcallableでなければならない。 -:--:1 +---:1 さらに、Sanicは追加のタイプコンバータを使用して、追加の型をキャストするように設定することができます。 これは、値を返すか、`ValueError`を発生させる任意のcallableでなければならない。 + +*Added in v21.12* :--:1 ```python app = Sanic(..., config=Config(converters=[UUID])) ``` :--- -::: ## 組み込み値 -| **変数** | **デフォルト** | **説明** | -|--|--|--| -| ACCESS_LOG | True | アクセスログを無効または有効にする。 -| AUTO_EXTEND ^ | True | [Sanic Extensions](../../plugins/sanic-ext/getting-started.md) が既存の仮想環境内にある場合にロードするかどうかを制御する | -| AUTO_RELOAD | True | ファイルが変更されたときにアプリケーションが自動的にリロードするかどうかを制御します。 | -| EVENT_AUTOREGISTER | True | `True` のとき、存在しないシグナルに対して `app.event()` メソッドを使用すると、自動的にシグナルを生成して例外を発生させないEVENT_AUTOREGISTER.index.index. | -| FALLBACK_ERROR_FORMAT|html| 例外が発生し処理されなかった場合のエラー応答のフォーマット | -| FORWARDED_FOR_HEADER| X-Forwarded-For| クライアントとプロキシのIPを含む「X-Forwarded-For」HTTPヘッダの名前です。 | -| FORWARDED_SECRET|なし|特定のプロキシサーバーを安全に識別するために使用される(下記参照) | -| GRACEFUL_SHUTDOWN_TIMEOUT | 15.0 | アイドルでない接続を強制終了するまでの時間(秒) | -| KEEP_ALIVE | True | Falseの場合、キープアライブを無効にする。 | -| KEEP_ALIVE_TIMEOUT | 5 | TCP接続を開いたままにする時間(秒) | -| MOTD ^ | True | 起動時にMOTD(今日のメッセージ)を表示するかどうか | -| MOTD_DISPLAY ^ | {} | MOTDに任意のデータを追加表示するためのキー/バリュー・ペア | -| NOISY_EXCEPTIONS ^ | False | すべての `quiet` 例外を強制的にログに記録する | -| PROXIES_COUNT | なし | アプリの前にあるプロキシサーバーの数(例:nginx) | -| REAL_IP_HEADER | なし | 本当のクライアントIPを含む「X-Real-IP」HTTPヘッダーの名前 | REAL_IP_HEADER | なし | -| REGISTER | True | アプリのレジストリを有効にするかどうか。 | -| REQUEST_BUFFER_SIZE | 65536 | リクエストが一時停止するまでのリクエストバッファサイズ、デフォルトは64Kib | -| REQUEST_ID_HEADER | X-Request-ID | リクエスト/相関 ID を含む HTTP ヘッダー "X-Request-ID" の名前 | -| REQUEST_MAX_SIZE | 100000000 | リクエストの大きさ (バイト)、デフォルトは100メガバイトです。 | -| REQUEST_TIMEOUT | 60 | リクエストが到着するまでの時間(秒)です。 | -| RESPONSE_TIMEOUT | 60 | レスポンス処理にかかる時間(秒)です。 | -| USE_UVLOOP | True | ループポリシーをオーバーライドして `uvloop` を使用するかどうかを指定します。`app.run`でのみサポートされる。 | -| WEBSOCKET_MAX_SIZE | 2^20 | 受信メッセージの最大サイズ (バイト) | -| WEBSOCKET_PING_INTERVAL | 20 | Pingフレームはping_interval秒ごとに送信されます。 | -| WEBSOCKET_PING_TIMEOUT | 20 |ping_timeout秒後にPongを受信しない場合、接続を終了します。| - -::: new NEW in v21.12 -新要素: `AUTO_EXTEND`, `MOTD`, `MOTD_DISPLAY`, `NOISY_EXCEPTIONS` -::: +| **変数** | **デフォルト** | **説明** | +| --------------------------- | --------------- | ------------------------------------------------------------------------------------------------------- | +| ACCESS_LOG | True | アクセスログを無効または有効にする。 | +| AUTO_EXTEND | True | [Sanic Extensions](../../plugins/sanic-ext/getting-started.md) が既存の仮想環境内にある場合にロードするかどうかを制御する | +| AUTO_RELOAD | True | ファイルが変更されたときにアプリケーションが自動的にリロードするかどうかを制御します。 | +| EVENT_AUTOREGISTER | True | `True` のとき、存在しないシグナルに対して `app.event()` メソッドを使用すると、自動的にシグナルを生成して例外を発生させないEVENT_AUTOREGISTER.index.index. | +| FALLBACK_ERROR_FORMAT | html | 例外が発生し処理されなかった場合のエラー応答のフォーマット | +| FORWARDED_FOR_HEADER | X-Forwarded-For | クライアントとプロキシのIPを含む「X-Forwarded-For」HTTPヘッダの名前です。 | +| FORWARDED_SECRET | None | 特定のプロキシサーバーを安全に識別するために使用される(下記参照) | +| GRACEFUL_SHUTDOWN_TIMEOUT | 15.0 | アイドルでない接続を強制終了するまでの時間(秒) | +| INSPECTOR | False | Whether to enable the Inspector | +| INSPECTOR_HOST | localhost | The host for the Inspector | +| INSPECTOR_PORT | 6457 | The port for the Inspector | +| INSPECTOR_TLS_KEY | - | The TLS key for the Inspector | +| INSPECTOR_TLS_CERT | - | The TLS certificate for the Inspector | +| INSPECTOR_API_KEY | - | The API key for the Inspector | +| KEEP_ALIVE_TIMEOUT | 5 | TCP接続を開いたままにする時間(秒) | +| KEEP_ALIVE | True | Falseの場合、キープアライブを無効にする。 | +| MOTD | True | 起動時にMOTD(今日のメッセージ)を表示するかどうか | +| MOTD_DISPLAY | {} | MOTDに任意のデータを追加表示するためのキー/バリュー・ペア | +| NOISY_EXCEPTIONS | False | すべての `quiet` 例外を強制的にログに記録する | +| PROXIES_COUNT | None | アプリの前にあるプロキシサーバーの数(例:nginx) | +| REAL_IP_HEADER | None | 本当のクライアントIPを含む「X-Real-IP」HTTPヘッダーの名前 | REAL_IP_HEADER | なし | +| REGISTER | True | アプリのレジストリを有効にするかどうか。 | +| REQUEST_BUFFER_SIZE | 65536 | リクエストが一時停止するまでのリクエストバッファサイズ、デフォルトは64Kib | +| REQUEST_ID_HEADER | X-Request-ID | リクエスト/相関 ID を含む HTTP ヘッダー "X-Request-ID" の名前 | +| REQUEST_MAX_SIZE | 100000000 | リクエストの大きさ (バイト)、デフォルトは100メガバイトです。 | +| REQUEST_TIMEOUT | 60 | リクエストが到着するまでの時間(秒)です。 | +| RESPONSE_TIMEOUT | 60 | レスポンス処理にかかる時間(秒)です。 | +| USE_UVLOOP | True | ループポリシーをオーバーライドして `uvloop` を使用するかどうかを指定します。 `app.run`でのみサポートされる。 | +| WEBSOCKET_MAX_SIZE | 2^20 | 受信メッセージの最大サイズ (バイト) | +| WEBSOCKET_PING_INTERVAL | 20 | Pingフレームはping_interval秒ごとに送信されます。 | +| WEBSOCKET_PING_TIMEOUT | 20 | ping_timeout秒後にPongを受信しない場合、接続を終了します。 | ::: tip FYI -- `USE_UVLOOP` の値は、Gunicorn で実行されている場合、無視されます。サポートされていないプラットフォーム (Windows) では、デフォルトは `False` である。 -- ASGI モードでは `WEBSOCKET_` 値は無視されます。 -::: +- `USE_UVLOOP` の値は、Gunicorn で実行されている場合、無視されます。 サポートされていないプラットフォーム (Windows) では、デフォルトは `False` である。 +- ASGI モードでは `WEBSOCKET_` 値は無視されます。 ::: +- v21.12 added: `AUTO_EXTEND`, `MOTD`, `MOTD_DISPLAY`, `NOISY_EXCEPTIONS` +- v22.9 added: `INSPECTOR` +- v22.12 added: `INSPECTOR_HOST`, `INSPECTOR_PORT`, `INSPECTOR_TLS_KEY`, `INSPECTOR_TLS_CERT`, `INSPECTOR_API_KEY` ::: ## タイムアウト -### リクエストタイムアウト +### REQUEST_TIMEOUT -要求タイムアウトは、新しいオープンTCP接続がSanicバックエンドサーバーに渡された瞬間と、HTTP要求全体を受信した瞬間の間の時間を測定します。もし時間が `REQUEST_TIMEOUT` 値(秒)を超えたら、これはクライアントエラーとみなされ、Sanicは `HTTP 408` 応答を生成してクライアントに送信する。クライアントが日常的に非常に大きなリクエストペイロードを渡したり、リクエストを非常にゆっくりとアップロードする場合は、このパラメータの値を高く設定してください。 +要求タイムアウトは、新しいオープンTCP接続がSanicバックエンドサーバーに渡された瞬間と、HTTP要求全体を受信した瞬間の間の時間を測定します。 もし時間が `REQUEST_TIMEOUT` 値(秒)を超えたら、これはクライアントエラーとみなされ、Sanicは `HTTP 408` 応答を生成してクライアントに送信する。 クライアントが日常的に非常に大きなリクエストペイロードを渡したり、リクエストを非常にゆっくりとアップロードする場合は、このパラメータの値を高く設定してください。 -### 応答タイムアウト +### RESPONSE_TIMEOUT -レスポンスタイムアウトは、SanicサーバーがSanicアプリにHTTPリクエストを渡した瞬間から、HTTPレスポンスがクライアントに送信されるまでの時間を測定します。時間が `RESPONSE_TIMEOUT` 値 (秒) を超えると、サーバーエラーと見なされるため、Sanic は `HTTP 503` 応答を生成してクライアントに送信します。レスポンスの生成を遅らせるような長時間稼働のプロセスがあるようなアプリケーションでは、このパラメータの値を高く設定してください。 +レスポンスタイムアウトは、SanicサーバーがSanicアプリにHTTPリクエストを渡した瞬間から、HTTPレスポンスがクライアントに送信されるまでの時間を測定します。 時間が `RESPONSE_TIMEOUT` 値 (秒) を超えると、サーバーエラーと見なされるため、Sanic は `HTTP 503` 応答を生成してクライアントに送信します。 レスポンスの生成を遅らせるような長時間稼働のプロセスがあるようなアプリケーションでは、このパラメータの値を高く設定してください。 -### キープアライブタイムアウト +### KEEP_ALIVE_TIMEOUT -#### Keep Alive とは何ですか?また、キープアライブタイムアウトの値は何をするのでしょうか? +#### Keep Alive とは何ですか? また、キープアライブタイムアウトの値は何をするのでしょうか? -キープアライブとは、HTTP 1.1 で導入された HTTP の機能です。HTTPリクエストを送信するとき、クライアント(通常はウェブブラウザアプリケーション)は `Keep-Alive` ヘッダを設定して、HTTPサーバ(Sanic)が応答を送信した後もTCP接続を閉じないように指示することができます。これにより、クライアントは後続のHTTPリクエストを送信するために既存のTCP接続を再利用することができ、クライアントとサーバーの両方にとってより効率的なネットワークトラフィックを確保することができます。 +キープアライブとは、HTTP 1.1 で導入された HTTP の機能です。 HTTPリクエストを送信するとき、クライアント(通常はウェブブラウザアプリケーション)は `Keep-Alive` ヘッダを設定して、HTTPサーバ(Sanic)が応答を送信した後もTCP接続を閉じないように指示することができます。 これにより、クライアントは後続のHTTPリクエストを送信するために既存のTCP接続を再利用することができ、クライアントとサーバーの両方にとってより効率的なネットワークトラフィックを確保することができます。 -Sanicでは、`KEEP_ALIVE`設定変数がデフォルトで `True` に設定されています。アプリケーションでこの機能を必要としない場合は、`False`に設定すると、リクエストの `Keep-Alive` ヘッダに関係なく、レスポンスが送信された後にすべてのクライアント接続を直ちに終了するようになります。 +Sanicでは、`KEEP_ALIVE`設定変数がデフォルトで `True` に設定されています。 アプリケーションでこの機能を必要としない場合は、`False`に設定すると、リクエストの `Keep-Alive` ヘッダに関係なく、レスポンスが送信された後にすべてのクライアント接続を直ちに終了するようになります。 -サーバーが TCP 接続を開いたままにする時間は、サーバー自身が決定します。Sanic では、その値は `KEEP_ALIVE_TIMEOUT` 値を使用して設定されます。デフォルトでは、5秒に設定されています。これは Apache HTTP サーバーと同じデフォルト設定で、クライアントが新しいリクエストを送信するのに十分な時間を与えることと、一度に多くのコネクションをオープンしないことのバランスが取れています。クライアントが、その時間だけ開いたままの TCP 接続をサポートするブラウザを使用していることが分かっている場合を除き、75 秒を超えないようにしてください。 +サーバーが TCP 接続を開いたままにする時間は、サーバー自身が決定します。 Sanic では、その値は `KEEP_ALIVE_TIMEOUT` 値を使用して設定されます。 デフォルトでは、5秒に設定されています。 これは Apache HTTP サーバーと同じデフォルト設定で、クライアントが新しいリクエストを送信するのに十分な時間を与えることと、一度に多くのコネクションをオープンしないことのバランスが取れています。 クライアントが、その時間だけ開いたままの TCP 接続をサポートするブラウザを使用していることが分かっている場合を除き、75 秒を超えないようにしてください。 参考までに @@ -246,4 +234,4 @@ Sanicでは、`KEEP_ALIVE`設定変数がデフォルトで `True` に設定さ ## プロキシ設定 -プロキシ設定の項](/guide/advanced/proxy-headers.md)を参照してください。 +[プロキシ設定のセクション](/guide/advanced/proxy-headers.md)を参照してください。 diff --git a/src/ja/guide/deployment/development.md b/src/ja/guide/deployment/development.md index df8759e7a2..b4bdd0b10a 100644 --- a/src/ja/guide/deployment/development.md +++ b/src/ja/guide/deployment/development.md @@ -2,7 +2,7 @@ まず、Sanicに統合されているウェブサーバは、単なる開発サーバではありません。 -デバッグ・モードで*not*であれば、本番環境で使用できます。 +It is production ready out-of-the-box, *unless you enable in debug mode*. ## デバッグ・モード @@ -24,14 +24,74 @@ if __name__ == "__main__": ::: warning Sanicのデバッグモードは、サーバのパフォーマンスを低下させるため、開発環境でのみ有効にすることをお勧めします。 -::: +::: ::: ## 自動的に読み込む ---:1 -Sanicでは、Automatic Reloaderを手動で (デバッグモードから独立して) 有効または無効にする方法を提供しています。`auto_reload`引数は、自動リローダーをアクティブまたは非アクティブにします。 -:--:1 +Sanicでは、Automatic Reloaderを手動で (デバッグモードから独立して) 有効または無効にする方法を提供しています。 `auto_reload`引数は、自動リローダーをアクティブまたは非アクティブにします。 Pythonファイルが変更されるたびに、リローダーはアプリケーションを自動的に再起動します。 これは開発中に非常に便利です。 :--:1 ```python app.run(auto_reload=True) ``` :--- + +---:1 If you have additional directories that you would like to automatically reload on file save (for example, a directory of HTML templates), you can add that at run time. :--:1 +```python +app.run(auto_reload=True, reload_dir="/path/to/templates") +# or multiple directories +app.run(auto_reload=True, reload_dir=["/path/to/one", "/path/to/two"]) +``` +:--- + +## Best of both worlds +---:1 If you would like to be in debug mode **and** have the Automatic Reloader running, you can pass `dev=True`. This is equivalent to **debug + auto reload**. + +*Added in v22.3* :--:1 +```python +app.run(dev=True) +``` +:--- + +## Automatic TLS certificate + +When running in `DEBUG` mode, you can ask Sanic to handle setting up localhost temporary TLS certificates. This is helpful if you want to access your local development environment with `https://`. + +This functionality is provided by either [mkcert](https://github.com/FiloSottile/mkcert) or [trustme](https://github.com/python-trio/trustme). Both are good choices, but there are some differences. `trustme` is a Python library and can be installed into your environment with `pip`. This makes for easy envrionment handling, but it is not compatible when running a HTTP/3 server. `mkcert` might be a more involved installation process, but can install a local CA and make it easier to use. + +---:1 You can choose which platform to use by setting `config.LOCAL_CERT_CREATOR`. When set to `"auto"`, it will select either option, preferring `mkcert` if possible. :--:1 +```python +app.config.LOCAL_CERT_CREATOR = "auto" +app.config.LOCAL_CERT_CREATOR = "mkcert" +app.config.LOCAL_CERT_CREATOR = "trustme" +``` +:--- + + +---:1 Automatic TLS can be enabled at Sanic server run time: :--:1 +``` +$ sanic path.to.server:app --auto-tls --debug +``` + +```python +app.run(debug=True, auto_tls=True) +``` +:--- + +::: warning + +Localhost TLS certificates (like those generated by both `mkcert` and `trustme`) are **NOT** suitable for production environments. If you are not familiar with how to obtain a *real* TLS certificate, checkout the [How to...](../how-to/tls.md) section. ::: + +*Added in v22.6* + +## CLI + +It should be noted that all of these have an equivalent in the Sanic CLI: + +``` +Development: + --debug Run the server in debug mode + -r, --reload, --auto-reload Watch source directory for file changes and reload on changes + -R PATH, --reload-dir PATH Extra directories to watch and reload on changes + -d, --dev debug + auto reload + --auto-tls Create a temporary TLS certificate for local development (requires mkcert or trustme) +``` diff --git a/src/ja/guide/deployment/docker.md b/src/ja/guide/deployment/docker.md index c597eaa5eb..4f757cd309 100644 --- a/src/ja/guide/deployment/docker.md +++ b/src/ja/guide/deployment/docker.md @@ -1 +1,183 @@ # Docker + +## Introduction + +For a long time, the environment has always been a difficult problem for deployment. If there are conflicting configurations in your project, you have to spend a lot of time resolving them. Fortunately, virtualization provides us with a good solution. Docker is one of them. If you don't know Docker, you can visit [Docker official website](https://www.docker.com/) to learn more. + +## Build Image + +Let's start with a simple project. We will use a Sanic project as an example. Assume the project path is `/path/to/SanicDocker`. + +---:1 + +The directory structure looks like this: + +:--:1 + +```text +# /path/to/SanicDocker +SanicDocker +├── requirements.txt +├── dockerfile +└── server.py +``` + +:--- + +---:1 + +And the `server.py` code looks like this: + +:--:1 + +```python +app = Sanic("MySanicApp") + +@app.get('/') +async def hello(request): + return text("OK!") + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8000) +``` + +:--- + +::: tip + +Please note that the host cannot be 127.0.0.1 . In docker container, 127.0.0.1 is the default network interface of the container, only the container can communicate with other containers. more information please visit [Docker network](https://docs.docker.com/engine/reference/commandline/network/) + +::: + +Code is ready, let's write the `Dockerfile`: + +```Dockerfile + +FROM sanicframework/sanic:3.8-latest + +WORKDIR /sanic + +COPY . . + +RUN pip install -r requirements.txt + +EXPOSE 8000 + +CMD ["python", "server.py"] +``` + +Run the following command to build the image: + +```shell +docker build -t my-sanic-image . +``` + +## Start Container + +---:1 + +After the image built, we can start the container use `my-sanic-image`: + +:--:1 + +```shell +docker run --name mysanic -p 8000:8000 -d my-sanic-image +``` + +:--- + +---:1 + +Now we can visit `http://localhost:8000` to see the result: + +:--:1 + +```text +OK! +``` + +:--- + +## Use docker-compose + +If your project consist of multiple services, you can use [docker-compose](https://docs.docker.com/compose/) to manage them. + +for example, we will deploy `my-sanic-image` and `nginx`, achieve through nginx access sanic server. + +---:1 + +First of all, we need prepare nginx configuration file. create a file named `mysanic.conf`: + +:--:1 + +```nginx +server { + listen 80; + listen [::]:80; + location / { + proxy_pass http://mysanic:8000/; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection upgrade; + proxy_set_header Accept-Encoding gzip; + } +} +``` + +:--- + +---:1 + +Then, we need to prepare `docker-compose.yml` file. The content follows: + +:--:1 + +```yml +version: "3" + +services: + mysanic: + image: my-sanic-image + ports: + - "8000:8000" + restart: always + + mynginx: + image: nginx:1.13.6-alpine + ports: + - "80:80" + depends_on: + - mysanic + volumes: + - ./mysanic.conf:/etc/nginx/conf.d/mysanic.conf + restart: always + +networks: + default: + driver: bridge +``` + +:--- + +---:1 + +After that, we can start them: + +:--:1 + +```shell +docker-compose up -d +``` + +:--- + +---:1 + +Now, we can visit `http://localhost:80` to see the result: + +:--:1 + +```text +OK! +``` + +:--- diff --git a/src/ja/guide/deployment/inspector.md b/src/ja/guide/deployment/inspector.md new file mode 100644 index 0000000000..81cacb3496 --- /dev/null +++ b/src/ja/guide/deployment/inspector.md @@ -0,0 +1,160 @@ +# Inspector + +The Sanic Inspector is a feature of Sanic Server. It is *only* available when running Sanic with the built-in [worker manager](./manager.md). + +It is an HTTP application that *optionally* runs in the background of your application to allow you to interact with the running instance of your application. + +::: tip INFO +The Inspector was introduced in limited capacity in v22.9, but the documentation on this page assumes you are using v22.12 or higher. +::: + +## Getting Started + +The inspector is disabled by default. To enable it, you have two options. + +---:1 Set a flag when creating your application instance. :--:1 +```python +app = Sanic("TestApp", inspector=True) +``` +:--- + +---:1 Or, set a configuration value. :--:1 +```python +app = Sanic("TestApp") +app.config.INSPECTOR = True +``` +:--- + +::: warning +If you are using the configuration value, it *must* be done early and before the main worker process starts. This means that it should either be an environment variable, or it should be set shortly after creating the application instance as shown above. +::: + +## Using the Inspector + +Once the inspector is running, you will have access to it via the CLI or by directly accessing its web API via HTTP. + +---:1 **Via CLI** +``` +$ sanic inspect +``` +:--:1 **Via HTTP** +``` +$ curl http://localhost:6457 +``` +:--- + +::: tip +Remember, the Inspector is not running on your Sanic application. It is a seperate process, with a seperate application, and exposed on a seperate socket. +::: + +## Built-in Commands + +The Inspector comes with the following built-in commands. + +| CLI Command | HTTP Action | Description | +| ------------------ | ---------------------------------------- | ------------------------------------------------------------------------ | +| `inspect` | `GET /` | Display basic details about the running application. | +| `inspect reload` | `POST /reload` | Trigger a reload of all server workers. | +| `inspect shutdown` | `POST /shutdown` | Trigger a shutdown of all processes. | +| `inspect scale N` | `POST /scale`
`{"replicas": N}` | Scale the number of workers. Where `N` is the target number of replicas. | + +## Custom Commands + +The Inspector is easily extendable to add custom commands (and endpoints). + +---:1 Subclass the `Inspector` class and create arbitrary methods. As long as the method name is not preceded by an underscore (`_`), then the name of the method will be a new subcommand on the inspector. :--:1 +```python +from sanic import json +from sanic.worker.inspector import Inspector + + +class MyInspector(Inspector): + async def something(self, *args, **kwargs): + print(args) + print(kwargs) + + +app = Sanic("TestApp", inspector_class=MyInspector, inspector=True) +``` +:--- + +This will expose custom methods in the general pattern: + +- CLI: `sanic inspect ` +- HTTP: `POST /` + +It is important to note that the arguments that the new method accepts are derived from how you intend to use the command. For example, the above `something` method accepts all positional and keyword based parameters. + +---:1 In the CLI, the positional and keyword parameters are passed as either positional or keyword arguments to your method. All values will be a `str` with the following exceptions: + +- A keyword parameter with no assigned value will be: `True` +- Unless the parameter is prefixed with `no-`, then it will be: `False` :--:1 +``` +$ sanic inspect something one two three --four --no-five --six=6 +``` +In your application log console, you will see: +``` +('one', 'two', 'three') +{'four': True, 'five': False, 'six': '6'} +``` +:--- + +---:1 The same can be achieved by hitting the API directly. You can pass arguments to the method by exposing them in a JSON payload. The only thing to note is that the positional arguments should be exposed as `{"args": [...]}`. :--:1 +``` +$ curl http://localhost:6457/something \ + --json '{"args":["one", "two", "three"], "four":true, "five":false, "six":6}' +``` +In your application log console, you will see: +``` +('one', 'two', 'three') +{'four': True, 'five': False, 'six': 6} +``` +:--- + + +## Using in production + +::: warning +Before exposing the Inspector on a product, please consider all of the options in this section carefully. +::: + +When running Inspector on a remote production instance, you can protect the endpoints by requiring TLS encryption, and requiring API key authentication. + +### TLS encryption + +---:1 To the Inspector HTTP instance over TLS, pass the paths to your certificate and key. :--:1 +```python +app.config.INSPECTOR_TLS_CERT = "/path/to/cert.pem" +app.config.INSPECTOR_TLS_KEY = "/path/to/key.pem" +``` +:--- + +---:1 This will require use of the `--secure` flag, or `https://`. :--:1 +``` +$ sanic insect --secure --host= +``` +``` +$ curl https://:6457 +``` +:--- + +### API Key Authentication + +---:1 You can secure the API with bearer token authentication. :--:1 +```python +app.config.INSPECTOR_API_KEY = "Super-Secret-200" +``` +:--- + +---:1 This will require the `--api-key` parameter, or bearer token authorization header. :--:1 +``` +$ sanic inspect --api-key=Super-Secret-200 +``` +``` +$ curl http://localhost:6457 -H "Authorization: Bearer Super-Secret-200" +``` +:--- + +## Configuration + +See [configuration](./configuration.md) diff --git a/src/ja/guide/deployment/manager.md b/src/ja/guide/deployment/manager.md new file mode 100644 index 0000000000..4c4153b14d --- /dev/null +++ b/src/ja/guide/deployment/manager.md @@ -0,0 +1,290 @@ +# Worker Manager + +The worker manager and its functionality was introduced in version 22.9. + +*The details of this section are intended for more advanced usages and **not** necessary to get started.* + +The purpose of the manager is to create consistency and flexibility between development and production environments. Whether you intend to run a single worker, or multiple workers, whether with, or without auto-reload: the experience will be the same. + + +In general it looks like this: + +![](https://user-images.githubusercontent.com/166269/178677618-3b4089c3-6c6a-4ecc-8d7a-7eba2a7f29b0.png) + +When you run Sanic, the main process instantiates a `WorkerManager`. That manager is in charge of running one or more `WorkerProcess`. There generally are two kinds of processes: + +- server processes, and +- non-server processes. + +For the sake of ease, the User Guide generally will use the term "worker" or "worker process" to mean a server process, and "Manager" to mean the single worker manager running in your main process. + +## How Sanic Server starts processes + +Sanic will start processes using the [spawn](https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods) start method. This means that for every process/worker, the global scope of your application will be run on its own thread. The practical impact of this that *if* you do not run Sanic with the CLI, you will need to nest the execution code inside a block to make sure it only runs on `__main__`. + +```python +if __name__ == "__main__": + app.run() +``` + +If you do not, you are likely to see an error message like this: + +``` +sanic.exceptions.ServerError: Sanic server could not start: [Errno 98] Address already in use. + +This may have happened if you are running Sanic in the global scope and not inside of a `if __name__ == "__main__"` block. + +See more information: https://sanic.dev/en/guide/deployment/manager.html#how-sanic-server-starts-processes +``` + +The likely fix for this problem is nesting your Sanic run call inside of the `__name__ == "__main__"` block. If you continue to receive this message after nesting, or if you see this while using the CLI, then it means the port you are trying to use is not available on your machine and you must select another port. + +### Starting a worker + +All worker processes *must* send an acknowledgement when starting. This happens under the hood, and you as a developer do not need to do anything. However, the Manager will exit with a status code `1` if one or more workers do not send that `ack` message, or a worker process throws an exception while trying to start. If no exceptions are encountered, the Manager will wait for up to thirty (30) seconds for the acknowledgement. + +---:1 In the situation when you know that you will need more time to start, you can monkeypatch the Manager. The threshold does not include anything inside of a listener, and is limited to the execution time of everything in the global scope of your application. + +If you run into this issue, it may indicate a need to look deeper into what is causing the slow startup. :--:1 +```python +from sanic.worker.manager import WorkerManager + +WorkerManager.THRESHOLD = 100 # Value is in 0.1s +``` +:--- + +See [worker ack](#worker-ack) for more information. + +---:1 As stated above, Sanic will use [spawn](https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods) to start worker processes. If you would like to change this behavior and are aware of the implications of using different start methods, you can modify as shown here. :--:1 +```python +from sanic import Sanic + +Sanic.start_method = "fork" +``` +:--- + + +### Worker ack + +When all of your workers are running in a subprocess a potential problem is created: deadlock. This can occur when the child processes cease to function, but the main process is unaware that this happened. Therefore, Sanic servers will automatically send an `ack` message (short for acknowledge) to the main process after startup. + +In version 22.9, the `ack` timeout was short and limited to `5s`. In version 22.12, the timeout was lengthened to `30s`. If your application is shutting down after thirty seconds then it might be necessary to manually increase this threshhold. + +---:1 The value of `WorkerManager.THRESHOLD` is in `0.1s` increments. Therefore, to set it to one minute, you should set the value to `600`. + +This value should be set as early as possible in your application, and should ideally happen in the global scope. Setting it after the main process has started will not work. :--:1 +```python +from sanic.worker.manager import WorkerManager + +WorkerManager.THRESHOLD = 600 +``` +:--- + + + + +::: new NEW in v22.12 +### Zero downtime restarts + +By default, when restarting workers, Sanic will teardown the existing process first before starting a new one. + +If you are intending to use the restart functionality in production then you may be interested in having zero-downtime reloading. This can be accomplished by forcing the reloader to change the order to start a new process, wait for it to [ack](#worker-ack), and then teardown the old process. + +---:1 From the multiplexer, use the `zero_downtime` argument :--:1 +```python +app.m.restart(zero_downtime=True) +``` +:--- + +*Added in v22.12* +::: + +## Using shared context between worker processes + +Python provides a few methods for [exchanging objects](https://docs.python.org/3/library/multiprocessing.html#exchanging-objects-between-processes), [synchronizing](https://docs.python.org/3/library/multiprocessing.html#synchronization-between-processes), and [sharing state](https://docs.python.org/3/library/multiprocessing.html#sharing-state-between-processes) between processes. This usually involves objects from the `multiprocessing` and `ctypes` modules. + +If you are familiar with these objects and how to work with them, you will be happy to know that Sanic provides an API for sharing these objects between your worker processes. If you are not familiar, you are encouraged to read through the Python documentation linked above and try some of the examples before proceeding with implementing shared context. + +Similar to how [application context](../basics/app.md#application-context) allows an applicaiton to share state across the lifetime of the application with `app.ctx`, shared context provides the same for the special objects mentioned above. This context is available as `app.shared_ctx` and should **ONLY** be used to share objects intended for this purpose. + +The `shared_ctx` will: + +- *NOT* share regular objects like `int`, `dict`, or `list` +- *NOT* share state between Sanic instances running on different machines +- *NOT* share state to non-worker processes +- **only** share state between server workers managed by the same Manager + +Attaching an inappropriate object to `shared_ctx` will likely result in a warning, and not an error. You should be careful to not accidentally add an unsafe object to `shared_ctx` as it may not work as expected. If you are directed here because of one of those warnings, you might have accidentally used an unsafe object in `shared_ctx`. + +---:1 In order to create a shared object you **must** create it in the main process and attach it inside of the `main_process_start` listener. :--:1 +```python +from multiprocessing import Queue + +@app.main_process_start +async def main_process_start(app): + app.shared_ctx.queue = Queue() +``` +:--- + +Trying to attach to the `shared_ctx` object outside of this listener may result in a `RuntimeError`. + +---:1 After creating the objects in the `main_process_start` listener and attaching to the `shared_ctx`, they will be available in your workers wherever the application instance is available (example: listeners, middleware, request handlers). :--:1 +```python +from multiprocessing import Queue + +@app.get("") +async def handler(request): + request.app.shared_ctx.queue.put(1) + ... +``` +:--- + +## Access to the multiplexer + +The application instance has access to an object that provides access to interacting with the Manager and other worker processes. The object is attached as the `app.multiplexer` property, but it is more easily accessed by its alias: `app.m`. + +---:1 For example, you can get access to the current worker state. :--:1 +```python +@app.on_request +async def print_state(request: Request): + print(request.app.m.name) + print(request.app.m.pid) + print(request.app.m.state) +``` +``` +Sanic-Server-0-0 +99999 +{'server': True, 'state': 'ACKED', 'pid': 99999, 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), 'starts': 2, 'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc)} +``` +:--- + +---:1 The `multiplexer` also has access to terminate the Manager, or restart worker processes :--:1 +```python +# shutdown the entire application and all processes +app.m.name.terminate() + +# restart the current worker only +app.m.name.restart() + +# restart specific workers only (comma delimited) +app.m.name.restart("Sanic-Server-4-0,Sanic-Server-7-0") + +# restart ALL workers +app.m.name.restart(all_workers=True) # Available v22.12+ +``` +:--- + +## Worker state + +---:1 As shown above, the `multiplexer` has access to report upon the state of the current running worker. However, it also contains the state for ALL processes running. :--:1 +```python +@app.on_request +async def print_state(request: Request): + print(request.app.m.workers) +``` +``` +{ + 'Sanic-Main': {'pid': 99997}, + 'Sanic-Server-0-0': { + 'server': True, + 'state': 'ACKED', + 'pid': 9999, + 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), + 'starts': 2, + 'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc) + }, + 'Sanic-Reloader-0': { + 'server': False, + 'state': 'STARTED', + 'pid': 99998, + 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), + 'starts': 1 + } +} +``` +:--- + + +## Built-in non-server processes + +As mentioned, the Manager also has the ability to run non-server processes. Sanic comes with two built-in types of non-server processes, and allows for [creating custom processes](#running-custom-processes). + +The two built-in processes are + +- the [auto-reloader](./development.md#automatic-reloader), optionally enabled to watch the file system for changes and trigger a restart +- [inspector](#inspector), optionally enabled to provide external access to the state of the running instance + +## Inspector + +Sanic has the ability to expose the state and the functionality of the `multiplexer` to the CLI. Currently, this requires the CLI command to be run on the same machine as the running Sanic instance. By default the inspector is disabled. + +---:1 To enable it, set the config value to `True`. :--:1 +```python +app.config.INSPECTOR = True +``` +:--- + +You will now have access to execute any of these CLI commands: + +``` +sanic inspect reload Trigger a reload of the server workers +sanic inspect shutdown Shutdown the application and all processes +sanic inspect scale N Scale the number of workers to N +sanic inspect Run a custom command +``` + +![](https://user-images.githubusercontent.com/166269/190099384-2f2f3fae-22d5-4529-b279-8446f6b5f9bd.png) + +---:1 This works by exposing a small HTTP service on your machine. You can control the location using configuration values: :--:1 +```python +app.config.INSPECTOR_HOST = "localhost" +app.config.INSPECTOR_PORT = 6457 +``` +:--- + +[Learn more](./inspector.md) to find out what is possible with the Inspector. + +## Running custom processes + +To run a managed custom process on Sanic, you must create a callable. If that process is meant to be long-running, then it should handle a shutdown call by a `SIGINT` or `SIGTERM` signal. + +---:1 The simplest method for doing that in Python will be to just wrap your loop in `KeyboardInterrupt`. + +If you intend to run another application, like a bot, then it is likely that it already has capability to handle this signal and you likely do not need to do anything. :--:1 +```python +from time import sleep + +def my_process(foo): + try: + while True: + sleep(1) + except KeyboardInterrupt: + print("done") +``` +:--- + +---:1 That callable must be registered in the `main_process_ready` listener. It is important to note that is is **NOT** the same location that you should register [shared context](#using-shared-context-between-worker-processes) objects. :--:1 +```python +@app.main_process_ready +async def ready(app: Sanic, _): +# app.manager.manage(, , ) + app.manager.manage("MyProcess", my_process, {"foo": "bar"}) +``` +:--- + +## Single process mode + +---:1 If you would like to opt out of running multiple processes, you can run Sanic in a single process only. In this case, the Manager will not run. You will also not have access to any features that require processes (auto-reload, the inspector, etc). :--:1 +```python +if __name__ == "__main__": + app.run(single_process=True) +``` +```python +if __name__ == "__main__": + app.prepare(single_process=True) + Sanic.serve_single() +``` +``` +sanic path.to.server:app --single-process +``` +:--- diff --git a/src/ja/guide/deployment/nginx.md b/src/ja/guide/deployment/nginx.md index 4bde3db20e..3e72545b13 100644 --- a/src/ja/guide/deployment/nginx.md +++ b/src/ja/guide/deployment/nginx.md @@ -3,16 +3,14 @@ ## イントロダクション -Sanicはインターネット上で直接実行できますが、プロキシを使用すると便利な場合があります。 -Nginxのようなサーバーを目の前にします。これは、同じIP上で複数の仮想ホストを実行したり、単一のSanicアプリに加えてNodeJSやその他のサービスを提供したり、静的ファイルを効率的に提供したりする場合に特に便利です。 -SSLとHTTP/2も、このようなプロキシに簡単に実装できます。 +Sanicはインターネット上で直接実行できますが、プロキシを使用すると便利な場合があります。 Nginxのようなサーバーを目の前にします。 これは、同じIP上で複数の仮想ホストを実行したり、単一のSanicアプリに加えてNodeJSやその他のサービスを提供したり、静的ファイルを効率的に提供したりする場合に特に便利です。 SSLとHTTP/2も、このようなプロキシに簡単に実装できます。 SSL and HTTP/2 are also easily implemented on such proxy. -私たちはSanicのアプリを`127.0.0.1:8000`でローカルのみにサービスを提供するように設定していますが、Nginxのインストールはドメイン`example.com`上のパブリックインターネットにサービスを提供する責任があります。静的ファイルは`/var/www/`。 +私たちはSanicのアプリを`127.0.0.1:8000`でローカルのみにサービスを提供するように設定していますが、Nginxのインストールはドメイン`example.com`上のパブリックインターネットにサービスを提供する責任があります。 静的ファイルは`/var/www/`。 ## プロキシSanicアプリ -信頼できるプロキシを識別するために使用される秘密キーを使用してアプリを設定し、実際のクライアントIPおよびその他の情報を識別できるようにする必要があります。これにより、IPアドレスやその他の詳細を偽装するために、インターネット上で偽のヘッダーを送信するすべてのユーザーを保護できます。任意のランダムな文字列を選択し、アプリとNginx configの両方で設定します。 +信頼できるプロキシを識別するために使用される秘密キーを使用してアプリを設定し、実際のクライアントIPおよびその他の情報を識別できるようにする必要があります。 これにより、IPアドレスやその他の詳細を偽装するために、インターネット上で偽のヘッダーを送信するすべてのユーザーを保護できます。 任意のランダムな文字列を選択し、アプリとNginx configの両方で設定します。 ```python from sanic import Sanic @@ -33,8 +31,7 @@ if __name__ == "__main__": app.run(host="127.0.0.1", port=8000, workers=8, access_log=False) ``` -これはシステムサービスになるので、コードを次の場所に保存します。 -`/srv/sanicexample/sanicexample.py` +これはシステムサービスになるので、コードを次の場所に保存します。 `/srv/sanicexample/sanicexample.py` テストでは、ターミナルでアプリを実行します。 @@ -42,7 +39,7 @@ if __name__ == "__main__": 高速透過プロキシを可能にするためにはかなり多くの設定が必要ですが、これらの大部分は修正する必要がないので、私に我慢してください。 -HTTPキープアライブを有効にするには、アップストリームサーバを個別の`upstream`ブロックで設定する必要があります。これにより、パフォーマンスが大幅に向上します。そこで、`proxy_pass`ディレクティブでアップストリームアドレスを直接指定する代わりに、これを使用します。この例では、アップストリーム・セクションの名前`server_name`、つまりパブリック・ドメイン名であり、これも`Host`ヘッダーでSanicに渡されます。必要に応じて名前を変更できます。ロード・バランシングとフェイルオーバーのために、複数のサーバを用意することもできます。 +HTTPキープアライブを有効にするには、アップストリームサーバを個別の`upstream`ブロックで設定する必要があります。 これにより、パフォーマンスが大幅に向上します。 そこで、`proxy_pass`ディレクティブでアップストリームアドレスを直接指定する代わりに、これを使用します。 この例では、アップストリーム・セクションの名前`server_name`、つまりパブリック・ドメイン名であり、これも`Host`ヘッダーでSanicに渡されます。 必要に応じて名前を変更できます。 ロード・バランシングとフェイルオーバーのために、複数のサーバを用意することもできます。 `example.com`の2つの出現箇所を実際のドメイン名に変更し、`YOUR SECRET`の代わりにアプリ用に選択したシークレットを使用します。 @@ -77,9 +74,7 @@ server { } ``` -Cookieの可視性の問題および検索エンジンでのアドレスの一貫性を回避するには、次の手順を実行します。 -すべての訪問者を1つの真のドメインにリダイレクトすることをお勧めします。 -HTTPS: +Cookieの可視性の問題および検索エンジンでのアドレスの一貫性を回避するには、次の手順を実行します。 すべての訪問者を1つの真のドメインにリダイレクトすることをお勧めします。 HTTPS: ```nginx # Redirect all HTTP to HTTPS with no-WWW @@ -101,9 +96,7 @@ server { 上記の設定セクションは、`/etc/nginx/sites-available/default`または他のサイト設定に配置できます (新しい設定セクションを作成する場合は、必ず`sites-enabled`にシンボリックリンクしてください) 。 -SSL証明書がメイン設定で設定されていることを確認します。 -それぞれに`ssl_certificate`ディレクティブと`ssl_certificate_key`ディレクティブを追加します。 -SSLでリスニングする`server`セクション。 +SSL証明書がメイン設定で設定されていることを確認します。 それぞれに`ssl_certificate`ディレクティブと`ssl_certificate_key`ディレクティブを追加します。 SSLでリスニングする`server`セクション。 さらに、これらすべてを`nginx/conf.d/forwarded.conf`にコピー&ペーストします。 @@ -146,8 +139,7 @@ map $http_forwarded $proxy_add_forwarded { } ``` -::: tip Note -`conf.d`と`sites-available`を使用しないインストールの場合、上記の設定はすべて、メインの`nginx.conf`の`http`セクション内に配置することもできます。 +::: tip Note `conf.d`と`sites-available`を使用しないインストールの場合、上記の設定はすべて、メインの`nginx.conf`の`http`セクション内に配置することもできます。 ::: ::: 変更後にNginxの設定をリロード: @@ -156,21 +148,21 @@ map $http_forwarded $proxy_add_forwarded { sudo nginx -s reload ``` -これで、`https://example.com/`でアプリを接続できるようになります。404エラーなどはすべてSanicのエラーページで処理され、静的ファイルが与えられたパスに存在する時はいつでもNginxによって提供されます。 +これで、`https://example.com/`でアプリを接続できるようになります。 404エラーなどはすべてSanicのエラーページで処理され、静的ファイルが与えられたパスに存在する時はいつでもNginxによって提供されます。 ## SSL certificates -サーバで有効な証明書をまだ設定していない場合は、ここで設定します。`certbot`と`python 3-certbot-nginx`をインストールしてください。 +サーバで有効な証明書をまだ設定していない場合は、ここで設定します。 `certbot`と`python 3-certbot-nginx`をインストールしてください。 ```bash certbot --nginx -d example.com -d www.example.com ``` -``_ +Reference: [Using Free Let’s Encrypt SSL/TLS Certificates with NGINX](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/) ## サービスとして実行 -このパートは`systemd`に基づくLinuxディストリビューション用です。ユニットファイル`/etc/systemd/system/sanicexample.service`を作成します。 +このパートは`systemd`に基づくLinuxディストリビューション用です。 ユニットファイル`/etc/systemd/system/sanicexample.service`を作成します。 ```text [Unit] diff --git a/src/ja/guide/deployment/running.md b/src/ja/guide/deployment/running.md index 47e2fd9b18..91d0174d3c 100644 --- a/src/ja/guide/deployment/running.md +++ b/src/ja/guide/deployment/running.md @@ -1,66 +1,96 @@ # Sanicの実行 -Sanicには、独自の内部Webサーバが付属しています。ほとんどの状況では、これがデプロイメントに適した方法です。さらに、Sanicは、ASGI対応ウェブサーバーにバンドルされたASGIアプリとして、またはgunicornを使用してデプロイすることもできます。 +Sanicには、独自の内部Webサーバが付属しています。 ほとんどの状況では、これがデプロイメントに適した方法です。 さらに、Sanicは、ASGI対応ウェブサーバーにバンドルされたASGIアプリとして、またはgunicornを使用してデプロイすることもできます。 ## Sanicサーバー -`sanic.Sanic`の場合、次のキーワード引数を使用してrunメソッドを呼び出すことができます。 - -| Parameter | Default | Description | -| :-------------: | :------------: | :---------------------------------------------------------------------------------------- | -| **host** | `"127.0.0.1"` | サーバーをホストするアドレス。 | -| **port** | `8000` | サーバをホストするポート。 | -| **unix** | `None` | サーバをホストするUnixソケット名 (TCPではありません) | -| **debug** | `False` | デバッグ出力をイネーブルにします (サーバの速度が低下します) | -| **ssl** | `None` | workersのSSL暗号化のためのSSLContext。 | -| **sock** | `None` | サーバーがからの接続を受け入れるためのソケット。 | -| **workers** | `1` | 作成するworkersもしくはprocessの数 | -| **loop** | `None` | 非同期互換イベントループ。何も指定しないのであれば、独自のイベントループが作成されます | -| **protocol** | `HttpProtocol` | asyncio.protocolのサブクラス。 | -| **access_log** | `True` | 要求の処理でログオンを有効にします (サーバの処理速度が大幅に低下します) 。 | +There are two main ways to run Sanic Server: ----:1 +1. Using `app.run` +1. Using the [CLI](#sanic-cli) -上記の例では、パフォーマンスを向上させるためにアクセスログをオフにしました。 - -:--:1 +When using `app.run` you will just call your Python file like any other script. +---:1 `app.run` must be properly nested inside of a name-main block. :--:1 ```python # server.py -app = Sanic("My App") -app.run(host='0.0.0.0', port=1337, access_log=False) -``` +app = Sanic("MyApp") +if __name__ == "__main__": + app.run() +``` :--- ----:1 -`app.run(...)`を持つPythonスクリプトを実行します。 -:--:1 +`sanic.Sanic`の場合、次のキーワード引数を使用してrunメソッドを呼び出すことができます。 + +| Parameter | Default | Description | +|:--------------------:|:--------------:|:----------------------------------------------------------------------------------- | +| **host** | `"127.0.0.1"` | サーバーをホストするアドレス。 | +| **port** | `8000` | サーバをホストするポート。 | +| **unix** | `None` | サーバをホストするUnixソケット名 (TCPではありません) | +| **debug** | `False` | デバッグ出力をイネーブルにします (サーバの速度が低下します) | +| **ssl** | `None` | workersのSSL暗号化のためのSSLContext。 | +| **sock** | `None` | サーバーがからの接続を受け入れるためのソケット。 | +| **workers** | `1` | 作成するworkersもしくはprocessの数 Cannot be used with fast. | +| **loop** | `None` | 非同期互換イベントループ。 何も指定しないのであれば、独自のイベントループが作成されます | +| **protocol** | `HttpProtocol` | asyncio.protocolのサブクラス。 | +| **access_log** | `True` | 要求の処理でログオンを有効にします (サーバの処理速度が大幅に低下します) 。 | +| **reload_dir** | `None` | A path or list of paths to directories the auto-reloader should watch. | +| **noisy_exceptions** | `None` | Whether to set noisy exceptions globally. None means leave as default. | +| **motd** | `True` | Whether to display the startup message. | +| **motd_display** | `None` | A dict with extra key/value information to display in the startup message | +| **fast** | `False` | Whether to maximize worker processes. Cannot be used with workers. | +| **verbosity** | `0` | Level of logging detail. Max is 2. | +| **auto_tls** | `False` | Whether to auto-create a TLS certificate for local development. Not for production. | +| **single_process** | `False` | Whether to run Sanic in a single process. | + +---:1 In the above example, we decided to turn off the access log in order to increase performance. :--:1 +```python +# server.py +app = Sanic("MyApp") +if __name__ == "__main__": + app.run(host='0.0.0.0', port=1337, access_log=False) +``` +:--- + +---:1 Now, just execute the python script that has `app.run(...)` :--:1 ```bash python server.py ``` +:--- + +For a slightly more advanced implementation, it is good to know that `app.run` will call `app.prepare` and `Sanic.serve` under the hood. +---:1 Therefore, these are equivalent: :--:1 +```python +if __name__ == "__main__": + app.run(host='0.0.0.0', port=1337, access_log=False) +``` +```python +if __name__ == "__main__": + app.prepare(host='0.0.0.0', port=1337, access_log=False) + Sanic.serve() +``` :--- ### Workers ----:1 -デフォルトでは、Sanicは1つのCPUコアだけを使用してメインプロセスをリッスンします。具体的には、run引数にworkersの数を指定します。 -:--:1 +---:1 By default, Sanic runs a main process and a single worker process (see [worker manager](./manager.md) for more details). + +具体的には、run引数にworkersの数を指定します。 :--:1 :--:1 ```python app.run(host='0.0.0.0', port=1337, workers=4) ``` :--- -Sanicは自動的に複数のプロセスを起動し、それらの間でトラフィックをルーティングする。使用可能なプロセッサと同じ数のworkersを推奨します。 +Sanicは自動的に複数のプロセスを起動し、それらの間でトラフィックをルーティングする。 使用可能なプロセッサと同じ数のworkersを推奨します。 -::: new NEW in v21.12 ----:1 -CPUの性能を最大限に引き出す最も簡単な方法は、 `fast` オプションを使用することです。これは、システムの制約を考慮して、自動的に最大数のワーカーを実行します。 -:--:1 +CPUの性能を最大限に引き出す最も簡単な方法は、 `fast` オプションを使用することです。 これは、システムの制約を考慮して、自動的に最大数のワーカーを実行します。 :--:1 + +*Added in v21.12* :--:1 ```python app.run(host='0.0.0.0', port=1337, fast=True) ``` @@ -68,9 +98,8 @@ app.run(host='0.0.0.0', port=1337, fast=True) $ sanic server:app --host=0.0.0.0 --port=1337 --fast ``` :--- -::: -古いバージョンのSanicで `fast` オプションがない場合、LinuxベースのOSでよくある確認方法を試してみてください。。 +古いバージョンのSanicで `fast` オプションがない場合、LinuxベースのOSでよくある確認方法を試してみてください。 ``` $ nproc @@ -84,30 +113,44 @@ workers = multiprocessing.cpu_count() app.run(..., workers=workers) ``` +In version 22.9, Sanic introduced a new worker manager to provide more consistency and flexibility between development and production servers. Read [about the manager](./manager.md) for more details about workers. + +---:1 If you only want to run Sanic with a single process, specify `single_process` in the run arguments. This means that auto-reload, and the worker manager will be unavailable. :--:1 +```python +app.run(host='0.0.0.0', port=1337, single_process=True) +``` +:--- + ### viaコマンドの実行 #### Sanic CLI ----:1 -コマンドラインから起動できるシンプルなCLIも用意されています。 +---:1 コマンドラインから起動できるシンプルなCLIも用意されています。 -たとえば、`server.py`という名前のファイルでアプリケーションとしてSanicを初期化した場合、次のようにサーバを実行できます。 -:--:1 +たとえば、`server.py`という名前のファイルでアプリケーションとしてSanicを初期化した場合、次のようにサーバを実行できます。 :--:1 ```bash sanic server.app --host=0.0.0.0 --port=1337 --workers=4 ``` -:--- +:--- すべてのオプションを表示するには、`sanic --help`を使用します。 + +::: details Sanic CLI help output + ```text $ sanic --help -usage: sanic [-h] [--version] [--factory] [-s] [-H HOST] [-p PORT] [-u UNIX] [--cert CERT] [--key KEY] [--tls DIR] [--tls-strict-host] - [-w WORKERS | --fast] [--access-logs | --no-access-logs] [--debug] [-d] [-r] [-R PATH] [--motd | --no-motd] [-v] +usage: sanic [-h] [--version] + [--factory | -s | --inspect | --inspect-raw | --trigger-reload | --trigger-shutdown] + [--http {1,3}] [-1] [-3] [-H HOST] [-p PORT] [-u UNIX] + [--cert CERT] [--key KEY] [--tls DIR] [--tls-strict-host] + [-w WORKERS | --fast | --single-process] [--legacy] + [--access-logs | --no-access-logs] [--debug] [-r] [-R PATH] [-d] + [--auto-tls] [--coffee | --no-coffee] [--motd | --no-motd] [-v] [--noisy-exceptions | --no-noisy-exceptions] module - ▄███ █████ ██ ▄█▄ ██ █ █ ▄██████████ + ▄███ █████ ██ ▄█▄ ██ █ █ ▄██████████ ██ █ █ █ ██ █ █ ██ ▀███████ ███▄ ▀ █ █ ██ ▄ █ ██ ██ █████████ █ ██ █ █ ▄▄ @@ -129,67 +172,85 @@ usage: sanic [-h] [--version] [--factory] [-s] [-H HOST] [-p PORT] [-u UNIX] [-- Required ======== Positional: - module Path to your Sanic app. Example: path.to.server:app - If running a Simple Server, path to directory to serve. Example: ./ + module Path to your Sanic app. Example: path.to.server:app + If running a Simple Server, path to directory to serve. Example: ./ Optional ======== General: - -h, --help show this help message and exit - --version show program's version number and exit + -h, --help show this help message and exit + --version show program's version number and exit Application: - --factory Treat app as an application factory, i.e. a () -> callable - -s, --simple Run Sanic as a Simple Server, and serve the contents of a directory - (module arg should be a path) + --factory Treat app as an application factory, i.e. a () -> callable + -s, --simple Run Sanic as a Simple Server, and serve the contents of a directory + (module arg should be a path) + --inspect Inspect the state of a running instance, human readable + --inspect-raw Inspect the state of a running instance, JSON output + --trigger-reload Trigger worker processes to reload + --trigger-shutdown Trigger all processes to shutdown + + HTTP version: + --http {1,3} Which HTTP version to use: HTTP/1.1 or HTTP/3. Value should + be either 1, or 3. [default 1] + -1 Run Sanic server using HTTP/1.1 + -3 Run Sanic server using HTTP/3 Socket binding: - -H HOST, --host HOST Host address [default 127.0.0.1] - -p PORT, --port PORT Port to serve on [default 8000] - -u UNIX, --unix UNIX location of unix socket + -H HOST, --host HOST + Host address [default 127.0.0.1] + -p PORT, --port PORT + Port to serve on [default 8000] + -u UNIX, --unix UNIX + location of unix socket TLS certificate: - --cert CERT Location of fullchain.pem, bundle.crt or equivalent - --key KEY Location of privkey.pem or equivalent .key file - --tls DIR TLS certificate folder with fullchain.pem and privkey.pem - May be specified multiple times to choose multiple certificates - --tls-strict-host Only allow clients that send an SNI matching server certs + --cert CERT Location of fullchain.pem, bundle.crt or equivalent + --key KEY Location of privkey.pem or equivalent .key file + --tls DIR TLS certificate folder with fullchain.pem and privkey.pem + May be specified multiple times to choose multiple certificates + --tls-strict-host Only allow clients that send an SNI matching server certs Worker: - -w WORKERS, --workers WORKERS Number of worker processes [default 1] - --fast Set the number of workers to max allowed - --access-logs Display access logs - --no-access-logs No display access logs + -w WORKERS, --workers WORKERS + Number of worker processes [default 1] + --fast Set the number of workers to max allowed + --single-process Do not use multiprocessing, run server in a single process + --legacy Use the legacy server manager + --access-logs Display access logs + --no-access-logs No display access logs Development: - --debug Run the server in debug mode - -d, --dev Currently is an alias for --debug. But starting in v22.3, - --debug will no longer automatically trigger auto_restart. - However, --dev will continue, effectively making it the - same as debug + auto_reload. - -r, --reload, --auto-reload Watch source directory for file changes and reload on changes - -R PATH, --reload-dir PATH Extra directories to watch and reload on changes + --debug Run the server in debug mode + -r, --reload, --auto-reload + Watch source directory for file changes and reload on changes + -R PATH, --reload-dir PATH + Extra directories to watch and reload on changes + -d, --dev debug + auto reload + --auto-tls Create a temporary TLS certificate for local development (requires mkcert or trustme) Output: - --motd Show the startup display - --no-motd No show the startup display - -v, --verbosity Control logging noise, eg. -vv or --verbosity=2 [default 0] - --noisy-exceptions Output stack traces for all exceptions - --no-noisy-exceptions No output stack traces for all exceptions + --coffee Uhm, coffee? + --no-coffee No uhm, coffee? + --motd Show the startup display + --no-motd No show the startup display + -v, --verbosity Control logging noise, eg. -vv or --verbosity=2 [default 0] + --noisy-exceptions Output stack traces for all exceptions + --no-noisy-exceptions + No output stack traces for all exceptions ``` +::: #### モジュールとして ----:1 -モジュールとして直接呼び出すこともできます。 -:--:1 +---:1 モジュールとして直接呼び出すこともできます。 :--:1 :--:1 ```bash python -m sanic server.app --host=0.0.0.0 --port=1337 --workers=4 ``` :--- -::: tip FYI -どちらの方法(CLIまたはモジュール)でも、Pythonファイルの`app.run()`を*呼び出さない*でください。もしも実行する場合は、インタプリタによって直接実行される場合にのみ実行されるようにラップしてください。 +::: tip FYI どちらの方法(CLIまたはモジュール)でも、Pythonファイルの`app.run()`を*呼び出さない*でください。 もしも実行する場合は、インタプリタによって直接実行される場合にのみ実行されるようにラップしてください。 + ```python if __name__ == '__main__': app.run(host='0.0.0.0', port=1337, workers=4) @@ -197,30 +258,26 @@ if __name__ == '__main__': ::: -#### Sanicシンプルサーバー +### Sanicシンプルサーバー ----:1 -提供する必要がある静的ファイルのディレクトリだけがある場合もあります。これは特に、localhostサーバーを素早く立ち上げる場合に便利です。SanicにはSimple Serverが付属しており、ディレクトリを指定するだけです。 -:--:1 +---:1 提供する必要がある静的ファイルのディレクトリだけがある場合もあります。 これは特に、localhostサーバーを素早く立ち上げる場合に便利です。 SanicにはSimple Serverが付属しており、ディレクトリを指定するだけです。 :--:1 :--:1 ```bash sanic ./path/to/dir --simple ``` :--- ----:1 -自動リロードと組み合わせることもできます。 -:--:1 +---:1 自動リロードと組み合わせることもできます。 :--:1 :--:1 ```bash sanic ./path/to/dir --simple --reload --reload-dir=./path/to/dir ``` :--- -## ASGI +*Added in v21.6* -SanicもASGIに準拠しています。つまり、任意のASGI Webサーバを使用してSanicを実行できます。ASGIの3つの主要な実装は、[Daphne](http://github.com/django/daphne),[Uvicorn](https://www.uvicorn.org/),と[Hypercorn](https://pgjones.gitlab.io/hypercorn/index.html)です。 +### HTTP/3 -実行方法は次のようになります。 -※適切な実行方法についてはドキュメントを参照してください。 + +Sanic server offers HTTP/3 support using [aioquic](https://github.com/aiortc/aioquic). This **must** be installed to use HTTP/3: ``` daphne myapp:app @@ -228,10 +285,68 @@ uvicorn myapp:app hypercorn myapp:app ``` +``` +pip install sanic[http3] +``` + ASGIを使用する際には、次の点に注意してください。 -1. Sanic Webサーバを使用する場合、websocketsは`websockets`パッケージを使用して実行されますが。ASGIモードでは、websocketsはASGIサーバで管理されるため、このパッケージは必要ありません。 -2. ASGIライフスパンプロトコルは、スタートアップとシャットダウンの2つのサーバイベントだけをサポートします。Sanicには、起動前、起動後、シャットダウン前、シャットダウン後の4つがあります。したがって、ASGIモードでは、スタートアップイベントとシャットダウンイベントは連続して実行され、実際にはサーバプロセスの開始と終了の前後では実行されません(なぜなら現在、ASGIサーバによって制御されているためです)。したがって、`after_server_start`および`before_server_stop`を使用することをお薦めします。 +---:1 +``` +$ sanic path.to.server:app --http=3 +``` + +``` +$ sanic path.to.server:app -3 +``` +:--:1 + +```python +app.run(version=3) +``` +:--- + +To run both an HTTP/3 and HTTP/1.1 server simultaneously, you can use [application multi-serve](../release-notes/v22.3.html#application-multi-serve) introduced in v22.3. This will automatically add an [Alt-Svc](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Alt-Svc) header to your HTTP/1.1 requests to let the client know that it is also available as HTTP/3. + + +---:1 +``` +$ sanic path.to.server:app --http=3 --http=1 +``` + +``` +$ sanic path.to.server:app -3 -1 +``` +:--:1 + +```python +app.prepare(version=3) +app.prepare(version=1) +Sanic.serve() +``` +:--- + +Because HTTP/3 requires TLS, you cannot start a HTTP/3 server without a TLS certificate. You should [set it up yourself](../how-to/tls.html) or use `mkcert` if in `DEBUG` mode. Currently, automatic TLS setup for HTTP/3 is not compatible with `trustme`. See [development](./development.md) for more details. + +*Added in v22.6* + +## ASGI + +SanicもASGIに準拠しています。 つまり、任意のASGI Webサーバを使用してSanicを実行できます。 ASGIの3つの主要な実装は、[Daphne](http://github.com/django/daphne),[Uvicorn](https://www.uvicorn.org/),と[Hypercorn](https://pgjones.gitlab.io/hypercorn/index.html)です。 + +::: warning Daphne does not support the ASGI `lifespan` protocol, and therefore cannot be used to run Sanic. See [Issue #264](https://github.com/django/daphne/issues/264) for more details. ::: + +実行方法は次のようになります。 ※適切な実行方法についてはドキュメントを参照してください。 + +``` +uvicorn myapp:app +hypercorn myapp:app +``` + +A couple things to note when using ASGI: + +1. Sanic Webサーバを使用する場合、websocketsは`websockets`パッケージを使用して実行されますが。 ASGIモードでは、websocketsはASGIサーバで管理されるため、このパッケージは必要ありません。 +2. ASGIライフスパンプロトコルは、スタートアップとシャットダウンの2つのサーバイベントだけをサポートします。 Sanicには、起動前、起動後、シャットダウン前、シャットダウン後の4つがあります。 したがって、ASGIモードでは、スタートアップイベントとシャットダウンイベントは連続して実行され、実際にはサーバプロセスの開始と終了の前後では実行されません(なぜなら現在、ASGIサーバによって制御されているためです)。 したがって、`after_server_start`および`before_server_stop`を使用することをお薦めします。 ### Trio @@ -244,37 +359,30 @@ hypercorn -k trio myapp:app ## Gunicorn -[Gunicorn](http://gunicorn.org/)("Green Unicorn")は、UNIXベースのオペレーティング・システム用のWSGI HTTP Serverです。これはRubyのUnicornプロジェクトから移植されたフォーク前のworkerモデルです。 +[Gunicorn](http://gunicorn.org/)("Green Unicorn")は、UNIXベースのオペレーティング・システム用のWSGI HTTP Serverです。 これはRubyのUnicornプロジェクトから移植されたフォーク前のworkerモデルです。 -GunicornでSanicアプリケーションを実行するには、特殊な`sanic.worker.GunicornWorker`を実行する必要があります: +In order to run Sanic application with Gunicorn, you need to use it with the adapter from [uvicorn](https://www.uvicorn.org/). Make sure uvicorn is installed and run it with `uvicorn.workers.UvicornWorker` for Gunicorn worker-class argument: ```bash -gunicorn myapp:app --bind 0.0.0.0:1337 --worker-class sanic.worker.GunicornWorker +gunicorn myapp:app --bind 0.0.0.0:1337 --worker-class uvicorn.workers.UvicornWorker ``` -アプリケーションでメモリリークが発生した場合、Gunicornを設定して、指定した数のリクエストを処理したあとでワーカーを正常に再起動できます。これは、メモリリークの影響を制限するのに役立つ便利な方法です。 - 詳細については、[Gunicorn Docs](http://docs.gunicorn.org/en/latest/settings.html#max-requests) を参照してください。 -::: warning -`gunicorn`経由でSanicを実行すると、`async/await`のパフォーマンス上の利点の多くを失うことになります。慎重に検討してから選択してください。Gunicornには多くの設定オプションが用意されていますが、Sanicを最速で動作させるには最適な選択肢ではありません。 +::: warning It is generally advised to not use `gunicorn` unless you need it. The Sanic Server is primed for running Sanic in production. Weigh your considerations carefully before making this choice. Gunicornには多くの設定オプションが用意されていますが、Sanicを最速で動作させるには最適な選択肢ではありません。 ::: ::: ## パフォーマンスに関する考慮事項 ----:1 -本番環境で実行する場合は、`debug`を必ずオフにしてください。 -:--:1 +---:1 本番環境で実行する場合は、`debug`を必ずオフにしてください。 :--:1 :--:1 ```python app.run(..., debug=False) ``` :--- ----:1 -また、`access_log`をオフにすると、Sanicは最も高速に動作します。 +---:1 また、`access_log`をオフにすると、Sanicは最も高速に動作します。 -それでもアクセスログが必要だが、このパフォーマンス向上を享受したい場合は、[Nginxをプロキシとして](./nginx.md)使用すればアクセスログを処理できます。Pythonが処理できるものよりもはるかに高速になります。 -:--:1 +それでもアクセスログが必要だが、このパフォーマンス向上を享受したい場合は、[Nginxをプロキシとして](./nginx.md)使用すればアクセスログを処理できます。 Pythonが処理できるものよりもはるかに高速になります。 :--:1 :--:1 ```python app.run(..., access_log=False) ``` diff --git a/src/ja/guide/getting-started.md b/src/ja/guide/getting-started.md index 8f35d9eb4d..00f89f6c17 100644 --- a/src/ja/guide/getting-started.md +++ b/src/ja/guide/getting-started.md @@ -1,6 +1,6 @@ # スタートアップ -最初に、Python 3.7以降が実行されていることを確認します。現在、はPythonバージョン3.7、3.8、および3.9で動作することがわかっています。 +最初に、Python 3.7以降が実行されていることを確認します。 現在、はPythonバージョン3.7、3.8、および3.9で動作することがわかっています。 ## インストール @@ -8,14 +8,14 @@ pip install sanic ``` -## hello, worldアプリケーション +## Hello, worldアプリケーション ---:1 多くのデコレーター・ベースのフレームワークのいずれかを使用したことがある人であれば、このフレームワークはおそらく皆さんにとって馴染みのあるものです。 ::: tip -もしあなたがFlaskや他のフレームワークから来ているのであれば、いくつかの重要な指摘があります。Sanicはパフォーマンス、柔軟性、使いやすさを目指しています。これらの指針は、APIとその動作に目に見える影響を与えます。 +もしあなたがFlaskや他のフレームワークから来ているのであれば、いくつかの重要な指摘があります。 Sanicはパフォーマンス、柔軟性、使いやすさを目指しています。 これらの指針は、APIとその動作に目に見える影響を与えます。 ::: @@ -35,33 +35,29 @@ async def hello_world(request): :--- -### 重要なノート +### ご注意ください -- すべてのリクエストハンドラは、sync(`def hello_world`)またはasync(`async def hello_world`)のいずれかになります。明確な理由がない限り、常に`async'を使ってください。 -- `request`オブジェクトは、常にハンドラの最初の引数です。他のフレームワークは、インポートされるコンテキスト変数でこれを渡します。`async`の世界では、これはあまりうまく動作しないでしょうし、それについて明示的に説明することは (よりクリーンでよりパフォーマンス的であることは言うまでもありませんが) はるかに簡単です。 -- 応答タイプを使用する必要があります。他の多くのフレームワークでは、`return"Hello, world。"`またはthis:`return {"foo":"bar"}`です。しかし、この暗黙の呼び出しを行うには、チェーン内のどこかで、意味を判断するために貴重な時間を費やす必要があります。この容易さを犠牲にして、Sanicは明示的なコールを要求することにしました。 +- すべてのリクエストハンドラは、sync(`def hello_world`)またはasync(`async def hello_world`)のいずれかになります。 明確な理由がない限り、常に`async'を使ってください。 +- `request`オブジェクトは、常にハンドラの最初の引数です。 他のフレームワークは、インポートされるコンテキスト変数でこれを渡します。 `async`の世界では、これはあまりうまく動作しないでしょうし、それについて明示的に説明することは (よりクリーンでよりパフォーマンス的であることは言うまでもありませんが) はるかに簡単です。 +- レスポンスタイプを**必ず**使用してください。 他の多くのフレームワークでは、`return "Hello, world."`または`return {"foo":"bar"}`のようにreturnするのを許可します。 しかし、この暗黙の呼び出しを行うには、チェーン内のどこかで、意味を判断するために貴重な時間を費やす必要があります。 この容易さを犠牲にして、Sanicは明示的なコールを要求することにしました。 ### 実行 ----:1 -上記のファイルを`server.py`として保存します。そして、それを起動します。 -:--:1 +---:1 上記のファイルを`server.py`として保存しましょう。 そして、起動してみます。 :--:1 ```bash sanic server.app ``` :--- -::: tip -この**別の**重要な違いです。他のフレームワークには、組み込みの開発サーバーが付属しており、開発用であることを明示的に示しています。逆のことは、Sanicにも言える。 +::: tip これは**もう一つの**重要な他との違いです。 他のフレームワークには、組み込みの開発サーバーが付属しており、開発_専用_であることを明示的に示しています。 その逆はサニックに当てはまります。 -**パッケージ化されたサーバーは、実稼働に対応しています。** -::: +**パッケージ化されたサーバーは、本番環境での実行に対応しています。 ** ::: ## Sanicエクステンション -Sanicは、意図的にクリーンで偏見のない機能リストを目指しています。プロジェクトは、特定の方法でアプリケーションを構築することを要求したくないし、特定の開発パターンを処方することを避けようとしている。コアリポジトリの要件を満たさない追加機能を追加するために、コミュニティによって構築・維持されているサードパーティプラグインが多数あります。 +Sanicは、意図的にクリーンで偏見のない機能リストを目指しています。 プロジェクトは、特定の方法でアプリケーションを構築することを要求したくないし、特定の開発パターンを処方することを避けようとしている。 コアリポジトリの要件を満たさない追加機能を追加するために、コミュニティによって構築・維持されているサードパーティプラグインが多数あります。 -しかし、**API開発者を助けるために**、Sanic組織は[Sanicエクステンション](../plugins/sanic-ext/getting-started.md) という公式プラグインを維持しており、以下を含むあらゆる種類のグッズを提供しています。 +しかし、**API開発者を助けるために**、Sanic組織は[Sanic Extensions](../plugins/sanic-ext/getting-started.md) という公式プラグインを維持しており、以下を含むあらゆる種類のグッズを提供しています。 - Redoc および/または Swagger による **OpenAPI** ドキュメンテーション - CORS**保護** @@ -70,7 +66,6 @@ Sanicは、意図的にクリーンで偏見のない機能リストを目指し - `HEAD`、`OPTIONS`、`TRACE`のエンドポイントを自動作成する。 - 定義済み、エンドポイント固有のレスポンスシリアライザー -::: new NEW in v21.12 設定方法としては、Sanicと一緒にインストールするのが望ましいですが、パッケージ単体でインストールすることも可能です。 ---:1 @@ -83,10 +78,9 @@ $ pip install sanic sanic-ext ``` :--- -v21.12から、Sanicが同じ環境であれば、Sanic Extensionsを自動的にセットアップするようになりました。また、2つの追加アプリケーションプロパティにアクセスできるようになります。 +v21.12から、Sanicが同じ環境であれば、Sanic Extensionsを自動的にセットアップするようになりました。 また、2つの追加アプリケーションプロパティにアクセスできるようになります: - `app.extend()` - Sanic Extensionsを設定するために使用されます。 - `app.ext` - アプリケーションにアタッチされている `Extend` インスタンスです。 -::: -プラグインの使用方法および作業方法の詳細については、[プラグイン ドキュメント](../plugins/sanic-ext/getting-started.md) を参照してください。 +See [the plugin documentation](../plugins/sanic-ext/getting-started.md) for more information about how to use and work with the plugin diff --git a/src/ja/guide/how-to/authentication.md b/src/ja/guide/how-to/authentication.md index 0620baf7c6..25beb3e893 100644 --- a/src/ja/guide/how-to/authentication.md +++ b/src/ja/guide/how-to/authentication.md @@ -2,7 +2,7 @@ > 認証と認可を制御するにはどうすればよいですか。 -これは、いくつかのスニペットに詰め込むのが非常に複雑な問題です。しかし、これはこの問題に取り組む方法についてのアイデアを提供するはずです。この例では [JWT](https://jwt.io/)を使用していますが、この概念はセッションやその他のスキームにも同様に適用できます。 +これは、いくつかのスニペットに詰め込むのが非常に複雑な問題です。 しかし、これはこの問題に取り組む方法についてのアイデアを提供するはずです。 この例では [JWT](https://jwt.io/)を使用していますが、この概念はセッションやその他のスキームにも同様に適用できます。 :::: tabs ::: tab server.py @@ -75,8 +75,8 @@ def protected(wrapped): return decorator(wrapped) ``` -このデコレーターのパターンは、 [decorators page](/en/guide/best-practices/decorators.md) から取得されます。 -::: +このデコレーターのパターンは、 [decorators page](/en/guide/best-practices/decorators.md) から取得されます。 ::: +:::: ::: :::: ```bash @@ -86,6 +86,14 @@ content-length: 21 connection: keep-alive content-type: text/plain; charset=utf-8 +You are unauthorized. + +$ curl localhost:9999/secret -i +HTTP/1.1 401 Unauthorized +content-length: 21 +connection: keep-alive +content-type: text/plain; charset=utf-8 + あなたは許可されていません。 $ curl localhost:9999/login -X POST 7 ↵ diff --git a/src/ja/guide/how-to/autodiscovery.md b/src/ja/guide/how-to/autodiscovery.md index 74663a4228..0abea61ea4 100644 --- a/src/ja/guide/how-to/autodiscovery.md +++ b/src/ja/guide/how-to/autodiscovery.md @@ -7,11 +7,11 @@ title: Autodiscovery > アプリケーションの構築に使用しているコンポーネントを自動検出するにはどうすればよいですか。 -アプリケーションを構築する際に直面する最初の問題の1つは、プロジェクトを 「どのように」 構築するかということです。Sanicはルートハンドラ、ミドルウェア、リスナーの登録にデコレータを多用しています。また、設計図を作成した後、アプリケーションにマウントする必要があります。 +アプリケーションを構築する際に直面する最初の問題の1つは、プロジェクトを 「どのように」 構築するかということです。 Sanicはルートハンドラ、ミドルウェア、リスナーの登録にデコレータを多用しています。 また、設計図を作成した後、アプリケーションにマウントする必要があります。 -可能な解決策は、**すべて**がインポートされ、Sanicインスタンスに適用される単一のファイルです。もう1つは、グローバル変数としてSanicインスタンスを渡す方法です。どちらの解決策にも欠点があります。 +可能な解決策は、**すべて**がインポートされ、Sanicインスタンスに適用される単一のファイルです。 もう1つは、グローバル変数としてSanicインスタンスを渡す方法です。 どちらの解決策にも欠点があります。 -もう1つの方法は、自動検出です。アプリケーションをモジュール(すでにインポートされている、または文字列)に向け、すべてを接続します。 +もう1つの方法は、自動検出です。 アプリケーションをモジュール(すでにインポートされている、または文字列)に向け、すべてを接続します。 :::: tabs ::: tab server.py diff --git a/src/ja/guide/how-to/cors.md b/src/ja/guide/how-to/cors.md index fe445ab72b..419f0fbe27 100644 --- a/src/ja/guide/how-to/cors.md +++ b/src/ja/guide/how-to/cors.md @@ -7,6 +7,8 @@ title: CORS > アプリケーションをCORS用に構成する方法は? +The best solution is to use [Sanic Extensions](../../plugins/sanic-ext/http/cors.md). However, if you would like to build your own version, you could use this limited example. + :::: tabs ::: tab server.py ```python diff --git a/src/ja/guide/how-to/orm.md b/src/ja/guide/how-to/orm.md index 00ea7e576e..9cd1a82b96 100644 --- a/src/ja/guide/how-to/orm.md +++ b/src/ja/guide/how-to/orm.md @@ -2,44 +2,152 @@ > SQLAlchemyをSanicと共に使用する方法? -すべてのORMツールはSanicで動作しますが、非同期ORMツールはSanicのパフォーマンスに影響します。 -これをサポートするormパッケージがいくつかあります。 +すべてのORMツールはSanicで動作しますが、非同期ORMツールはSanicのパフォーマンスに影響します。 これをサポートするormパッケージがいくつかあります。 There are some orm packages who support -現在、非同期性をサポートするORMはたくさんあります。一般的なライブラリには、次の2つがあります。 +At present, there are many ORMs that support Python's `async`/`await` keywords. Some possible choices include: +- [Mayim](https://ahopkins.github.io/mayim/) - [SQLAlchemy 1.4](https://docs.sqlalchemy.org/en/14/changelog/changelog_14.html) - [tortoise-orm](https://github.com/tortoise/tortoise-orm) のSanicアプリケーションへの統合は非常に簡単です。 -## SQLAlchemy +## Mayim + +Mayim ships with [an extension for Sanic Extensions](https://ahopkins.github.io/mayim/guide/extensions.html#sanic), which makes it super simple to get started with Sanic. It is certainly possible to run Mayim with Sanic without the extension, but it is recommended because it handles all of the [lifecycle events](https://sanic.dev/en/guide/basics/listeners.html) and [dependency injections](https://sanic.dev/en/plugins/sanic-ext/injection.html). -[SQLAlchemy 1.4関数](https://docs.sqlalchemy.org/en/14/changelog/changelog_14.html)が`asyncio`のネイティブサポートを追加したので、SanicはついにSQLAlchemyでうまく動作するようになりました。SQLAlchemyプロジェクトでは、この機能はまだ*ベータ*と見なされていることに注意してください。 +---:1 +### 依存関係 +まず、必要な依存関係をインストールする必要があります。 See [Mayim docs](https://ahopkins.github.io/mayim/guide/install.html#postgres) for the installation needed for your DB driver. :--:1 +```shell +pip install sanic-ext +pip install mayim[postgres] +``` +:--- ---:1 +### ORMモデルの定義 -### 依存関係 +Mayim allows you to use whatever you want for models. Whether it is [dataclasses](https://docs.python.org/3/library/dataclasses.html), [pydantic](https://pydantic-docs.helpmanual.io/), [attrs](https://www.attrs.org/en/stable/), or even just plain `dict` objects. Since it works very nicely [out of the box with Pydantic](https://ahopkins.github.io/mayim/guide/pydantic.html), that is what we will use here. :--:1 +```python +# ./models.py +from pydantic import BaseModel -まず、必要な依存関係をインストールする必要があります。以前は、インストールされる依存関係は`sqlalchemy'と`pymysql'でしたが、現在は`sqlalchemy'と`aiomysql'が必要です。 -:--:1 +class City(BaseModel): + id: int + name: str + district: str + population: int -```shell -pip install -U sqlalchemy -pip install -U aiomysql + +class Country(BaseModel): + code: str + name: str + continent: str + region: str + capital: City ``` +:--- +---:1 +### Define SQL + +If you are unfamiliar, Mayim is different from other ORMs in that it is one-way, SQL-first. This means you define your own queries either inline, or in a separate `.sql` file, which is what we will do here. :--:1 +```sql +-- ./queries/select_all_countries.sql +SELECT country.code, + country.name, + country.continent, + country.region, + ( + SELECT row_to_json(q) + FROM ( + SELECT city.id, + city.name, + city.district, + city.population + ) q + ) capital +FROM country + JOIN city ON country.capital = city.id +ORDER BY country.name ASC +LIMIT $limit OFFSET $offset; +``` :--- ---:1 +### Sanicアプリケーションと非同期エンジンの作成 -### ORMモデルの定義 +We need to create the app instance and attach the `SanicMayimExtension` with any executors. :--:1 +```python +# ./server.py +from sanic import Sanic, Request, json +from sanic_ext import Extend +from mayim.executor import PostgresExecutor +from mayim.extensions import SanicMayimExtension +from models import Country + + +class CountryExecutor(PostgresExecutor): + async def select_all_countries( + self, limit: int = 4, offset: int = 0 + ) -> list[Country]: + ... + + +app = Sanic("Test") +Extend.register( + SanicMayimExtension( + executors=[CountryExecutor], + dsn="postgres://...", + ) +) +``` +:--- -ORMモデルの作成は同じままです。 +---:1 +### ルートの登録 +Because we are using Mayim's extension for Sanic, we have the automatic `CountryExecutor` injection into the route handler. It makes for an easy, type-annotated development experience. :--:1 +```python +@app.get("/") +async def handler(request: Request, executor: CountryExecutor): + countries = await executor.select_all_countries() + return json({"countries": [country.dict() for country in co +``` +:--- + +---:1 +### リクエストを送信 :--:1 +```sh +curl 'http://127.0.0.1:8000' +{"countries":[{"code":"AFG","name":"Afghanistan","continent":"Asia","region":"Southern and Central Asia","capital":{"id":1,"name":"Kabul","district":"Kabol","population":1780000}},{"code":"ALB","name":"Albania","continent":"Europe","region":"Southern Europe","capital":{"id":34,"name":"Tirana","district":"Tirana","population":270000}},{"code":"DZA","name":"Algeria","continent":"Africa","region":"Northern Africa","capital":{"id":35,"name":"Alger","district":"Alger","population":2168000}},{"code":"ASM","name":"American Samoa","continent":"Oceania","region":"Polynesia","capital":{"id":54,"name":"Fagatogo","district":"Tutuila","population":2323}}]} +``` +:--- + + +## SQLAlchemy + +[SQLAlchemy 1.4関数](https://docs.sqlalchemy.org/en/14/changelog/changelog_14.html)が`asyncio`のネイティブサポートを追加したので、SanicはついにSQLAlchemyでうまく動作するようになりました。 SQLAlchemyプロジェクトでは、この機能はまだ*ベータ*と見なされていることに注意してください。 + + +---:1 +### 依存関係 + +First, we need to install the required dependencies. 以前は、インストールされる依存関係は`sqlalchemy'と`pymysql'でしたが、現在は`sqlalchemy'と`aiomysql'が必要です。 :--:1 +```shell +pip install -U sqlalchemy +pip install -U aiomysql +``` +:--- + +---:1 +### ORMモデルの定義 +ORMモデルの作成は同じままです。 :--:1 ```python # ./models.py from sqlalchemy import INTEGER, Column, ForeignKey, String @@ -69,17 +177,12 @@ class Car(BaseModel): user_id = Column(ForeignKey("person.id")) user = relationship("Person", back_populates="cars") ``` - :--- ---:1 - ### Sanicアプリケーションと非同期エンジンの作成 -ここではデータベースとしてmysqlを使用しますが、PostgreSQL/SQLiteを選択することもできます。ドライバを`aiomysql`から`asyncpg`/`aiosqlite`に変更することに注意してください。 -:--:1 - - +ここではデータベースとしてmysqlを使用しますが、PostgreSQL/SQLiteを選択することもできます。 ドライバを`aiomysql`から`asyncpg`/`aiosqlite`に変更することに注意してください。 :--:1 :--:1 ```python # ./server.py from sanic import Sanic @@ -89,20 +192,14 @@ app = Sanic("my_app") bind = create_async_engine("mysql+aiomysql://root:root@localhost/test", echo=True) ``` - :--- ---:1 - ### ミドルウェアの登録 リクエスト・ミドルウェアは、使用可能な`AsyncSession`オブジェクトを作成し、それを`request.ctx`および`_base_model_session_ctx`に設定します。 -スレッドセーフな変数`_base_model_session_ctx`を使用すると、`request.ctx`からセッション・オブジェクトをフェッチするかわりにセッション・オブジェクトを使用できます。 - - -:--:1 - +スレッドセーフな変数`_base_model_session_ctx`を使用すると、`request.ctx`からセッション・オブジェクトをフェッチするかわりにセッション・オブジェクトを使用できます。 :--:1 ```python # ./server.py from contextvars import ContextVar @@ -110,11 +207,13 @@ from contextvars import ContextVar from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import sessionmaker +_sessionmaker = sessionmaker(bind, AsyncSession, expire_on_commit=False) + _base_model_session_ctx = ContextVar("session") @app.middleware("request") async def inject_session(request): - request.ctx.session = sessionmaker(bind, AsyncSession, expire_on_commit=False)() + request.ctx.session = _sessionmaker() request.ctx.session_ctx_token = _base_model_session_ctx.set(request.ctx.session) @@ -124,17 +223,12 @@ async def close_session(request, response): _base_model_session_ctx.reset(request.ctx.session_ctx_token) await request.ctx.session.close() ``` - :--- ---:1 - ### ルートの登録 -sqlalchemyの公式ドキュメントによると、`session.query`は2.0年にレガシーになり、ORMオブジェクトをクエリする2.0の方法は`select`を使用します。 - -:--:1 - +sqlalchemyの公式ドキュメントによると、`session.query`は2.0年にレガシーになり、ORMオブジェクトをクエリする2.0の方法は`select`を使用します。 :--:1 ```python # ./server.py from sqlalchemy import select @@ -167,11 +261,11 @@ async def get_user(request, pk): return json(person.to_dict()) ``` - :--- +---:1 ### リクエストを送信 - +:--:1 ```sh curl --location --request POST 'http://127.0.0.1:8000/user' {"name":"foo","cars":[{"brand":"Tesla"}]} @@ -181,32 +275,24 @@ curl --location --request POST 'http://127.0.0.1:8000/user' curl --location --request GET 'http://127.0.0.1:8000/user/1' {"name":"foo","cars":[{"brand":"Tesla"}]} ``` +:--- ## Tortoise-ORM ---:1 +### Dependencies -### 依存関係 - -tortoise-ormの依存関係は非常に単純で、tortoise-ormをインストールするだけです。 - -:--:1 - +tortoise-ormの依存関係は非常に単純で、tortoise-ormをインストールするだけです。 :--:1 ```shell pip install -U tortoise-orm ``` - :--- ---:1 +### Define ORM Model -### ORMモデルの定義 - -Djangoに精通している方であれば、この部分は非常に馴染みがあるはずです。 - -:--:1 - +Djangoに精通している方であれば、この部分は非常に馴染みがあるはずです。 :--:1 ```python # ./models.py from tortoise import Model, fields @@ -219,18 +305,13 @@ class Users(Model): def __str__(self): return f"I am {self.name}" ``` - :--- ---:1 +### Create Sanic App and Async Engine -### Sanicアプリケーションと非同期エンジンの作成 - -Tortoise-ormは、ユーザーにとって便利な登録インタフェースのセットを提供し、これを使用してデータベース接続を簡単に作成できます。 - -:--:1 - +Tortoise-ormは、ユーザーにとって便利な登録インタフェースのセットを提供し、これを使用してデータベース接続を簡単に作成できます。 :--:1 ```python # ./main.py @@ -245,17 +326,12 @@ register_tortoise( ) ``` - :--- ---:1 - -### ルートの登録 - +### Register Routes :--:1 - ```python - # ./main.py from models import Users @@ -276,11 +352,11 @@ async def get_user(request, pk): if __name__ == "__main__": app.run(port=5000) ``` - :--- -### リクエストを送信 - +---:1 +### Send Requests +:--:1 ```sh curl --location --request POST 'http://127.0.0.1:8000/user' {"users":["I am foo", "I am bar"]} @@ -290,3 +366,5 @@ curl --location --request POST 'http://127.0.0.1:8000/user' curl --location --request GET 'http://127.0.0.1:8000/user/1' {"user": "I am foo"} ``` +:--- + diff --git a/src/ja/guide/how-to/static-redirects.md b/src/ja/guide/how-to/static-redirects.md index deffd6d200..713abc5032 100644 --- a/src/ja/guide/how-to/static-redirects.md +++ b/src/ja/guide/how-to/static-redirects.md @@ -22,8 +22,7 @@ REDIRECTS = { # This function will return another function # that will return the configured value -# regardless of the arguments passed to it. -def get_static_function(value:typing.Any) -> typing.Callable[..., typing.Any]: +# regardless of the arguments passed to it. def get_static_function(value:typing.Any) -> typing.Callable[..., typing.Any]: return lambda *_, **__: value ### ROUTING ### @@ -102,9 +101,7 @@ body { ``` ::: -::: tab files/lake.jpg -![](./assets/images/lake.jpg) -::: +::: tab files/lake.jpg ![](./assets/images/lake.jpg) ::: :::: また、コミュニティからいくつかのリソースをチェックアウトします。 diff --git a/src/ja/guide/how-to/tls.md b/src/ja/guide/how-to/tls.md index 21b9a3acca..2f36179066 100644 --- a/src/ja/guide/how-to/tls.md +++ b/src/ja/guide/how-to/tls.md @@ -6,8 +6,7 @@ TLS証明書がない場合は、[このページの最後を参照](./tls.md#ge ## 単一ドメイン、単一証明書 ----:1 -Sanicに証明書ファイルを自動的に読み込ませます。証明書ファイルは、指定されたフォルダーに `fullchain.pem` と `privkey.pem` という名前で格納する必要があります。 +---:1 Let Sanic automatically load your certificate files, which need to be named `fullchain.pem` and `privkey.pem` in the given folder: :--:1 ```sh @@ -18,12 +17,10 @@ sudo sanic myserver:app -H :: -p 443 \ app.run("::", 443, ssl="/etc/letsencrypt/live/example.com/") ``` :--- - ----:1 -または、certとkeyのファイル名を別々に辞書として渡すことも可能です。 -さらに、鍵が暗号化されている場合は `password` を追加することができ、パスワード以外のすべてのフィールドは `request.conn_info.cert` に渡されます。 -:--:1 +---:1 または、certとkeyのファイル名を別々に辞書として渡すことも可能です。 + +さらに、鍵が暗号化されている場合は `password` を追加することができ、パスワード以外のすべてのフィールドは `request.conn_info.cert` に渡されます。 :--:1 :--:1 ```python ssl = { "cert": "/path/to/fullchain.pem", @@ -34,9 +31,7 @@ app.run(host="0.0.0.0", port=8443, ssl=ssl) ``` :--- ----:1 -また、どの暗号アルゴリズムが許可されるかなどの詳細を完全に制御したい場合は、[`ssl.SSLContext`](https://docs.python.org/3/library/ssl.html)を渡すこともできます。デフォルトでは、Sanicは安全なアルゴリズムのみを許可しており、非常に古いデバイスからのアクセスを制限する可能性があります。 -:--:1 +---:1 また、どの暗号アルゴリズムが許可されるかなどの詳細を完全に制御したい場合は、[`ssl.SSLContext`](https://docs.python.org/3/library/ssl.html)を渡すこともできます。 デフォルトでは、Sanicは安全なアルゴリズムのみを許可しており、非常に古いデバイスからのアクセスを制限する可能性があります。 :--:1 :--:1 ```python import ssl @@ -50,16 +45,9 @@ app.run(host="0.0.0.0", port=8443, ssl=context) ## 複数のドメインで別々の証明書を使用 ----:1 -複数の証明書のリストが提供されることがあり、その場合、Sanicはユーザーが接続しているホスト名に一致する証明書を選択する。これはTLSハンドシェイクの初期に発生するため、Sanicはまだクライアントにパケットを送信していません。 - -クライアントがSNI(サーバー名表示)を送信しない場合、クライアントブラウザ上では名前の不一致によるTLSエラーで失敗する可能性が高いにもかかわらず、リストの最初の証明書が使用されることになります。このフォールバックを防ぎ、既知のホスト名を持たないクライアントを直ちに切断するには、リストの最初のエントリとして `None` を追加してください。`tls-strict-host` は同等のCLIオプションです。 - -::: tip -また、適切なDNS名ではなくIPアドレスに接続する人に対して、あなたの証明書、真のホスト名、サイトのコンテンツを明らかにしたくない場合は、単一の証明書の前に`None`を使用することができます。 -::: +---:1 複数の証明書のリストが提供されることがあり、その場合、Sanicはユーザーが接続しているホスト名に一致する証明書を選択する。 これはTLSハンドシェイクの初期に発生するため、Sanicはまだクライアントにパケットを送信していません。 -:--:1 +クライアントがSNI(サーバー名表示)を送信しない場合、クライアントブラウザ上では名前の不一致によるTLSエラーで失敗する可能性が高いにもかかわらず、リストの最初の証明書が使用されることになります。 このフォールバックを防ぎ、既知のホスト名を持たないクライアントを直ちに切断するには、リストの最初のエントリとして `None` を追加してください。 `tls-strict-host` は同等のCLIオプションです。 ---:1 ```python ssl = ["certs/example.com/", "certs/bigcorp.test/"] app.run(host="0.0.0.0", port=8443, ssl=ssl) @@ -72,8 +60,10 @@ sanic myserver:app ``` :--- ----:1 -リスト上で辞書を使用することができます。これにより、証明書がどのドメインに一致するかを指定することもできますが、証明書自体に存在する名前をここから制御することはできません。名前が指定されない場合、証明書自体の名前が使用されます。 +::: tip また、適切なDNS名ではなくIPアドレスに接続する人に対して、あなたの証明書、真のホスト名、サイトのコンテンツを明らかにしたくない場合は、単一の証明書の前に`None`を使用することができます。 ::: +::: + +---:1 リスト上で辞書を使用することができます。 これにより、証明書がどのドメインに一致するかを指定することもできますが、証明書自体に存在する名前をここから制御することはできません。 名前が指定されない場合、証明書自体の名前が使用されます。 メインドメイン **example.com** と **bigcorp.test** のサブドメインへの接続のみを許可する場合。 @@ -97,11 +87,12 @@ app.run(host="0.0.0.0", port=8443, ssl=ssl) * `.cert` - 現在アクティブな証明書の証明書情報およびdictフィールド (dict) * `.server_name` - クライアントが送信した SNI (文字列、空白の場合もある) -すべての `conn_info` フィールドは、時間経過とともに多くのリクエストが発生する可能性のある接続ごとにあることに注意してください。サーバーの前でプロキシを使用している場合、同じパイプにあるこれらのリクエストは異なるユーザーからのものである可能性さえあります。 +すべての `conn_info` フィールドは、時間経過とともに多くのリクエストが発生する可能性のある接続ごとにあることに注意してください。 サーバーの前でプロキシを使用している場合、同じパイプにあるこれらのリクエストは異なるユーザーからのものである可能性さえあります。 ## HTTP を HTTPS にリダイレクトし、証明書のリクエストは HTTP のままにする 通常の HTTPS サーバに加えて、リダイレクト用のサーバ `http_redir.py` を起動します。 + ```python from sanic import Sanic, exceptions, response @@ -118,7 +109,8 @@ def redirect_everything_else(request, exception): return response.text("Bad Request. Please use HTTPS!", status=400) ``` -HTTPS サーバーとは別の systemd ユニットとしてセットアップするのがベストです。HTTPS サーバはまだ実行できませんが、証明書を要求する間は HTTP を実行する必要があるかもしれません。IPv4 と IPv6 で起動します。 +HTTPS サーバーとは別の systemd ユニットとしてセットアップするのがベストです。 HTTPS サーバはまだ実行できませんが、証明書を要求する間は HTTP を実行する必要があるかもしれません。 IPv4 と IPv6 で起動します。 + ``` sanic http_redir:app -H 0.0.0.0 -p 80 sanic http_redir:app -H :: -p 80 @@ -155,7 +147,7 @@ async def runner(app, app_server): ## ドメイン名用の証明書を取得する -[Let's Encrypt](https://letsencrypt.org/)から無料で証明書を取得することができます。パッケージマネージャで[certbot](https://certbot.eff.org/)をインストールし、証明書を要求してください。 +[Let's Encrypt](https://letsencrypt.org/)から無料で証明書を取得することができます。 パッケージマネージャで[certbot](https://certbot.eff.org/)をインストールし、証明書を要求してください。 ```sh sudo certbot certonly --key-type ecdsa --preferred-chain "ISRG Root X1" -d example.com -d www.example.com @@ -163,4 +155,4 @@ sudo certbot certonly --key-type ecdsa --preferred-chain "ISRG Root X1" -d examp 複数のドメイン名を `-d` 引数で追加することができ、すべて1つの証明書に保存され、ここでリストした **最初のドメイン** に従って `/etc/letsencrypt/live/example.com/` に保存されます。 -鍵の種類と優先するチェーンのオプションは、最小限のサイズの証明書ファイルを取得するために必要で、サーバーを*できるだけ速く*動作させるために不可欠なものです。Let's Encrypt が新しい EC チェーンをすべての主要なブラウザで信頼されるようになるまで、チェーンにはまだ 1 つの RSA 証明書が含まれます(おそらく 2023 年頃)。 +鍵の種類と優先するチェーンのオプションは、最小限のサイズの証明書ファイルを取得するために必要で、サーバーを*できるだけ速く*動作させるために不可欠なものです。 Let's Encrypt が新しい EC チェーンをすべての主要なブラウザで信頼されるようになるまで、チェーンにはまだ 1 つの RSA 証明書が含まれます(おそらく 2023 年頃)。 diff --git a/src/ja/guide/how-to/toc.md b/src/ja/guide/how-to/toc.md index 0f6f129714..00645d9292 100644 --- a/src/ja/guide/how-to/toc.md +++ b/src/ja/guide/how-to/toc.md @@ -1,21 +1,13 @@ # 目次 -よくある質問やユーザー・ケースに答えるために、十分に機能する例をまとめました。ほとんどの場合、例は最小限のものですが、完全で実行可能なソリューションである必要があります。 +よくある質問やユーザー・ケースに答えるために、十分に機能する例をまとめました。 ほとんどの場合、例は最小限のものですが、完全で実行可能なソリューションである必要があります。 -| Page | How do I ... | -|:-----|:------------| -| [Applicationのマウント](./mounting.md) | ... どうやってルートより上のパスにアプリケーションをマウントする? | -| [Authentication](./authentication.md) | ... どうやって認証と許可を制御しますか? | -| [Autodiscovery](./autodiscovery.md) | ... どうやってアプリケーションの構築に使用しているコンポーネントを自動検出しますか? | -| [CORS](./cors.md) | ... どうやってCORS用にアプリケーションを構成しますか? | -| CSRF | *Coming soon* | -| Databases | *Coming soon* | -| IPv6 | *Coming soon* | -| Request ID Logging | *Coming soon* | -| Request validation | *Coming soon* | -| Serialization | *Coming soon* | -| Server Sent Events | *Coming soon* | -| [ORM](./orm) | ... どうやってSanicでORMを使う? | -| Task queues | *Coming soon* | -| [TLS/SSL/HTTPS](./tls.md) | ... どうやってHTTPS経由でSanicを実行する? ... どうやってHTTPをHTTPSにリダイレクトしますか? | -| Websocket feeds | *Coming soon* | +| Page | How do I ... | +|:------------------------------------------- |:-------------------------------------------------------------- | +| [Applicationのマウント](./mounting.md) | ... どうやってルートより上のパスにアプリケーションをマウントする? | +| [Authentication](./authentication.md) | ... どうやって認証と許可を制御しますか? | +| [Autodiscovery](./autodiscovery.md) | ... どうやってアプリケーションの構築に使用しているコンポーネントを自動検出しますか? | +| [CORS](./cors.md) | ... どうやってCORS用にアプリケーションを構成しますか? | +| [ORM](./orm) | ... どうやってSanicでORMを使う? | +| ["Static" Redirects](./static-redirects.md) | ... configure static redirects | +| [TLS/SSL/HTTPS](./tls.md) | ... run Sanic via HTTPS?
... redirect HTTP to HTTPS? | diff --git a/src/ja/guide/release-notes/v21.12.md b/src/ja/guide/release-notes/v21.12.md index ec3691ce87..35110f24f0 100644 --- a/src/ja/guide/release-notes/v21.12.md +++ b/src/ja/guide/release-notes/v21.12.md @@ -1,20 +1,20 @@ -# Version 21.12 +# バージョン21.12 (LTS) [[toc]] ## 概要 -このリリースは、バージョン21の[リリースサイクル](../../org/policies.md#release-schedule)の最終リリースとなります。バージョン21は今後長期サポートに入り、2023年12月までの2年間サポートされる予定です。 +このリリースは、バージョン21の[リリースサイクル](../../org/policies.md#release-schedule)の最終リリースとなります。 バージョン21は今後長期サポートに入り、2023年12月までの2年間サポートされる予定です。 ## 知っておきたい -詳しくは[Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html)をご覧ください。注目すべき新機能・不具合、アップグレード対象... +詳しくは[Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html)をご覧ください。 注目すべき新機能・不具合、アップグレード対象... ### アプリケーション名とブループリント名の厳格化 -v21.6](./v21.6.md#stricter-application-and-blueprint-names-and-deprecation) では、アプリケーションとブループリントの名前は新しい制限に適合することが求められました。この変更は、起動時に強制されるようになりました。 +v21.6](./v21.6.md#stricter-application-and-blueprint-names-and-deprecation) では、アプリケーションとブループリントの名前は新しい制限に適合することが求められました。 この変更は、起動時に強制されるようになりました。 -以下の制約に従った名前で**なければいけません。** +以下の制約に従った名前で**なければいけません。 ** 1. 英数字(`a-zA-Z0-9`)のみ使用可能です。 2. ハイフン(`-`)またはアンダースコア(`_`)を含むことができます。 @@ -22,7 +22,7 @@ v21.6](./v21.6.md#stricter-application-and-blueprint-names-and-deprecation) で ### 厳格なアプリケーションとブループリントのプロパティ -`Sanic` や `Blueprint` オブジェクトのプロパティを直接設定できる古い寛容さは、非推奨となり、今後は許可されなくなりました。必ず `ctx` オブジェクトを使用してください。 +`Sanic` や `Blueprint` オブジェクトのプロパティを直接設定できる古い寛容さは、非推奨となり、今後は許可されなくなりました。 必ず `ctx` オブジェクトを使用してください。 ```python app = Sanic("MyApp") @@ -39,7 +39,7 @@ app.ctx.db = Database() ### ストリーミング応答のアップグレード (まだの場合) -レスポンスメソッド `sanic.response.stream` は **非推奨** となり、v22.6 で削除される予定です。まだ旧式のストリーミングレスポンスを使用している場合は、アップグレードをお願いします。 +レスポンスメソッド `sanic.response.stream` は **非推奨** となり、v22.6 で削除される予定です。 まだ旧式のストリーミングレスポンスを使用している場合は、アップグレードをお願いします。 **旧 - 非推奨** @@ -67,9 +67,9 @@ async def test(request: Request): await response.send("bar") ``` -### CLIのオーバーホールとMOTD(本日のメッセージ)について +### CLIのオーバーホールとMOTD (メッセージ・オブ・ザ・デイ) について -SanicのCLIはかなり大規模なアップグレードを受けました。これは `app.run()` と同等にするために多くの新機能を追加しています。また、新しい MOTD 表示も含まれており、実行中の環境について素早く、一目でわかるハイライトを提供します。MOTDはTTYを意識しているので、サーバーのログではあまり冗長になりません。これは主にアプリケーション開発時の利便性を目的としています。 +SanicのCLIはかなり大規模なアップグレードを受けました。 これは `app.run()` と同等にするために多くの新機能を追加しています。 また、新しい MOTD 表示も含まれており、実行中の環境について素早く、一目でわかるハイライトを提供します。 MOTDはTTYを意識しているので、サーバーのログではあまり冗長になりません。 これは主にアプリケーション開発時の利便性を目的としています。 ``` $ sanic --help @@ -151,9 +151,9 @@ Optional ### サーバーの実行モードと `debug` に来る変更点 -現在、`DEV` と `PRODUCTION` の2つの実行モードがあります。デフォルトでは、Sanicサーバーは `PRODUCTION` モードで実行されます。これはデプロイメントを想定している。 +現在、`DEV` と `PRODUCTION` の2つの実行モードがあります。 デフォルトでは、Sanicサーバーは `PRODUCTION` モードで実行されます。 これはデプロイメントを想定している。 -現在、`DEV`モードは古いSanicのバージョンで `debug=True` が動作する方式と非常によく似ています。しかし、v22.3では しかし、v22.3では、`debug=True`は自動リロードを有効にしなくなりました**。デバッグと自動リロードを行いたい場合は、`DEV`モードを有効にする必要があります。 +現在、`DEV`モードは古いSanicのバージョンで `debug=True` が動作する方式と非常によく似ています。 ただし、v22.3では。 `debug=True` は自動リロードを有効に**しなくなりました**。 デバッグと自動リロードを行いたい場合は、`DEV`モードを有効にする必要があります。 **開発** @@ -179,12 +179,12 @@ v22.3から、`PRODUCTION`モードはデフォルトでアクセスログを有 変更点の概要は以下の通りです。 -| フラグ | モード | トレースバック | ログ | アクセスログ | 再読込 | 最大ワーカー | -|---------|-------|------------|---------|-------------|--------|-------------| -| --debug | DEBUG | はい | DEBUG | はい | ^1 | | -| | PROD | いいえ | INFO ^2 | ^3 | | | -| --dev | DEBUG | はい | DEBUG | はい | はい | | -| --fast | | | | | | はい | +| フラグ | モード | トレースバック | ログ | アクセスログ | 再読込 | 最大ワーカー | +| ------- | ----- | ------- | ------- | ------ | --- | ------ | +| --debug | DEBUG | はい | DEBUG | はい | ^1 | | +| | PROD | いいえ | INFO ^2 | ^3 | | | +| --dev | DEBUG | はい | DEBUG | はい | はい | | +| --fast | | | | | | はい | - ^1 `--debug` で自動リロードを非推奨とし、22.3 で削除する。 @@ -205,7 +205,7 @@ app.run(fast=True) ### ファーストクラスのSanic Extensionsサポート -[Sanic Extensions](../../plugins/sanic-ext/getting-started.md) は、特に API 開発者のために意図された多くの追加機能を提供します。パッケージが環境にある限り、追加設定なしで、それが提供するすべての機能を簡単に実装できるようになりました。これらの機能は以下の通りです。 +[Sanic Extensions](../../plugins/sanic-ext/getting-started.md) は、特に API 開発者のために意図された多くの追加機能を提供します。 パッケージが環境にある限り、追加設定なしで、それが提供するすべての機能を簡単に実装できるようになりました。 これらの機能は以下の通りです。 - `HEAD`、`OPTIONS`、`TRACE` のエンドポイントの自動作成 - CORS保護 @@ -229,9 +229,9 @@ $ pip install sanic sanic-ext :--- -その後、追加の設定は必要ありません。Sanic Extensionsはアプリケーションにアタッチされ、**これ以上の構成は必要なく**、すべての追加機能を提供します。 +その後、追加の設定は必要ありません。 Sanic Extensionsはアプリケーションにアタッチされ、**これ以上の設定は必要なく**、すべての追加機能を提供します。 -もし、動作方法を変更したり、追加の設定を提供したい場合は、`app.extend`を使用してSanic Extensionsを変更することができます。以下の例は同等です。`Config` オブジェクトは、IDE 開発に役立つ型アノテーションを提供するためのものです。 +もし、動作方法を変更したり、追加の設定を提供したい場合は、`app.extend`を使用してSanic Extensionsを変更することができます。 以下の例は同等です。 `Config` オブジェクトは、IDE 開発に役立つ型アノテーションを提供するためのものです。 ---:1 ```python @@ -249,7 +249,7 @@ app.config.OAS_URL_PREFIX = "/apidocs" ---:1 ```python -# This is optional, not required +# これは任意で、必須ではありません from sanic_ext import Config app = Sanic("MyApp") @@ -266,7 +266,7 @@ app.extend(config=Config(oas_url_prefix="/apidocs")) ```python class TeapotError(SanicException): status_code = 418 - message = "Sorry, I cannot brew coffee" + message = "ごめんね、コーヒーは作れないよ" raise TeapotError ``` @@ -284,55 +284,49 @@ class TeapotError(SanicException): @property def message(self): - return f"Sorry {self.extra['name']}, I cannot make you coffee" + return f"ごめんね {self.extra['name']}, コーヒーは作れないよ" -raise TeapotError(extra={"name": "Adam"}) +raise TeapotError(extra={"name": "Masao"}) ``` -新機能として、例外インスタンスに `extra` メタを渡すことができるようになりました。この `extra` 情報オブジェクトは、 `PRODUCTION` モードでは**抑制されます**が、 `DEVELOPMENT` モードでは表示されます。 +新機能として、例外インスタンスに `extra` メタを渡せるようになりました。 この `extra` 情報オブジェクトは、 `PRODUCTION` モードでは**抑制されます**が、 `DEVELOPMENT` モードでは表示されます。 ----:1 -**PRODUCTION** +---:1 **PRODUCTIONモード** -![image](https://user-images.githubusercontent.com/166269/139014161-cda67cd1-843f-4ad2-9fa1-acb94a59fc4d.png) -:--:1 -**DEVELOPMENT** +![画像](https://user-images.githubusercontent.com/166269/139014161-cda67cd1-843f-4ad2-9fa1-acb94a59fc4d.png) :--:1 **DEVELOPMENTモード** -![image](https://user-images.githubusercontent.com/166269/139014121-0596b084-b3c5-4adb-994e-31ba6eba6dad.png) -:--- +![画像](https://user-images.githubusercontent.com/166269/139014121-0596b084-b3c5-4adb-994e-31ba6eba6dad.png) :--- 2.の話に戻ります: _エラーメッセージにコンテキストを追加する機能_ -これは、マイクロサービスや、エラーメッセージをJSON形式で返すつもりのAPIを作るときに特に有効です。この使用例では、クライアントに詳細を返すために、解析可能なエラーメッセージだけでなく、例外の周りに何らかのコンテキストを持たせたいと考えています。 +これは、マイクロサービスや、エラーメッセージをJSON形式で返すつもりのAPIを作るときに特に有効です。 この使用例では、クライアントに詳細を返すために、解析可能なエラーメッセージだけでなく、例外の周りに何らかのコンテキストを持たせたいと考えています。 ```python raise TeapotError(context={"foo": "bar"}) ``` -これは、(利用可能であれば)常にエラーで**渡されるようにしたい**情報です。以下のような感じです。 +これは、(利用可能であれば)常にエラーで**渡されるようにしたい**情報です。 以下のような感じです。 ----:1 -**PRODUCTION** +---:1 **PRODUCTIONモード** ```json { "description": "I'm a teapot", "status": 418, - "message": "Sorry Adam, I cannot make you coffee", + "message": "ごめんね Masao, コーヒーは作れないよ", "context": { "foo": "bar" } } ``` -:--:1 -**DEVELOPMENT** +:--:1 **DEVELOPMENTモード** ```json { "description": "I'm a teapot", "status": 418, - "message": "Sorry Adam, I cannot make you coffee", + "message": "ごめんね Masao, コーヒーは作れないよ", "context": { "foo": "bar" }, @@ -382,7 +376,7 @@ app.cancel_task("dummy_task") ### 定義におけるルートコンテキストのkwargs -ルートが定義されるとき、`ctx_` というプレフィックスを持つキーワード引数をいくつでも追加することができます。これらの値は、ルートの `ctx` オブジェクトに注入されます。 +ルートが定義されるとき、`ctx_` というプレフィックスを持つキーワード引数をいくつでも追加することができます。 これらの値は、ルートの `ctx` オブジェクトに注入されます。 ```python @app.get("/1", ctx_label="something") @@ -405,13 +399,13 @@ async def do_something(request): ### ブループリントはいつでも登録可能 -Sanicの以前のバージョンでは、ブループリントをアプリケーションにアタッチできるタイミングに厳密な順序がありました。もし、`app.blueprint(bp)`を、すべてのオブジェクトをBlueprintインスタンスにアタッチする*前に*実行すると、それらは見逃されることになります。 +Sanicの以前のバージョンでは、ブループリントをアプリケーションにアタッチできるタイミングに厳密な順序がありました。 もし、`app.blueprint(bp)`を、すべてのオブジェクトをBlueprintインスタンスにアタッチする*前に*実行すると、それらは見逃されることになります。 現在では、いつでもブループリントをアタッチすることができ、それにアタッチされたすべてのものがスタートアップ時に含まれます。 -# うるさい例外 (すべての例外をログに強制的に記録) +### シグナルイベントを `Enum` で表す -新しい設定値として `NOISY_EXCEPTIONS` があります。この値が `False` (デフォルト) の場合、Sanic はすべての `SanicException` の `quiet` プロパティを尊重します。つまり、`quiet=True` の例外はログ出力に表示されません。 +新しい設定値として `NOISY_EXCEPTIONS` があります。 この値が `False` (デフォルト) の場合、Sanic はすべての `SanicException` の `quiet` プロパティを尊重します。 つまり、`quiet=True` の例外はログ出力に表示されません。 しかし、 `NOISY_EXCEPTIONS=True` に設定すると、 `quiet` の値に関係なく、すべての例外がログに記録されるようになります。 @@ -421,9 +415,9 @@ Sanicの以前のバージョンでは、ブループリントをアプリケー app.config.NOISY_EXCEPTIONS = True ``` -### シグナルイベントを `Enum` で表す +### `Enum`型としてのシグナルイベント -便宜上、すべてのビルトインシグナル値を持つ `Enum` が存在します。 +便宜上、すべてのビルトインシグナル値には `Enum` が存在します。 ```python from sanic.signals import Event @@ -435,7 +429,7 @@ async def connection_opened(conn_info): ### 環境変数のカスタム型キャスト -デフォルトでは、Sanic は環境変数を `config` インスタンスに適用する際に、 `int`, `float`, または `bool` 値に変換します。これを独自のコンバータで拡張することができる。 +デフォルトでは、Sanic は環境変数を `config` インスタンスに適用する際に、 `int`, `float`, または `bool` 値に変換します。 これを独自のコンバータで拡張することができる。 ```python app = Sanic(..., config=Config(converters=[UUID])) @@ -471,37 +465,22 @@ app.run( 詳しくは[sanicbook.com](https://sanicbook.com)でご覧ください。 -> ウェブアプリケーションのパフォーマンスとスケーラビリティを向上させるために、Sanicを使った作業の実践的な知識を身につけましょう。その一方で、アプリを大幅にオーバーエンジニアリングすることなく、変化するビジネスニーズに合わせてカスタマイズする方法を学び、開発スキルをレベルアップします。 +> ウェブアプリケーションのパフォーマンスとスケーラビリティを向上させるために、Sanicを使った作業の実践的な知識を身につけましょう。 その一方で、アプリを大幅にオーバーエンジニアリングすることなく、変化するビジネスニーズに合わせてカスタマイズする方法を学び、開発スキルをレベルアップします。 -書籍の収益の一部はSanic Community Organizationに入り、Sanicの開発・運営資金として活用されます。つまり、この本を買うことも、Sanicを支援する方法のひとつなのです。 +書籍の収益の一部はSanic Community Organizationに入り、Sanicの開発・運営資金として活用されます。 つまり、この本を買うことも、Sanicを支援する方法のひとつなのです。 ### ドキュメントのダークモード -まだお気づきでないようですが、このSanicのウェブサイトはネイティブのダークモードでご利用いただけるようになりました。ページの右上にあるテーマを切り替えることができます。 +まだお気づきでないようですが、このSanicのウェブサイトはネイティブのダークモードでご利用いただけるようになりました。 ページの右上にあるテーマを切り替えることができます。 ## 謝礼 今回のリリースに参加された皆さん、ありがとうございました。 :clap: -[@adarsharegmi](https://github.com/adarsharegmi) -[@ahopkins](https://github.com/ahopkins) -[@ashleysommer](https://github.com/ashleysommer) -[@ChihweiLHBird](https://github.com/ChihweiLHBird) -[@cnicodeme](https://github.com/cnicodeme) -[@kianmeng](https://github.com/kianmeng) -[@meysam81](https://github.com/meysam81) -[@nuxion](https://github.com/nuxion) -[@prryplatypus](https://github.com/prryplatypus) -[@realDragonium](https://github.com/realDragonium) -[@SaidBySolo](https://github.com/SaidBySolo) -[@sjsadowski](https://github.com/sjsadowski) -[@Tronic](https://github.com/tronic) -[@Varriount](https://github.com/Varriount) -[@vltr](https://github.com/vltr) -[@whos4n3](https://github.com/whos4n3) +[@adarsharegmi](https://github.com/adarsharegmi) [@ahopkins](https://github.com/ahopkins) [@ashleysommer](https://github.com/ashleysommer) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@cnicodeme](https://github.com/cnicodeme) [@kianmeng](https://github.com/kianmeng) [@meysam81](https://github.com/meysam81) [@nuxion](https://github.com/nuxion) [@prryplatypus](https://github.com/prryplatypus) [@realDragonium](https://github.com/realDragonium) [@SaidBySolo](https://github.com/SaidBySolo) [@sjsadowski](https://github.com/sjsadowski) [@Tronic](https://github.com/tronic) [@Varriount](https://github.com/Varriount) [@vltr](https://github.com/vltr) [@whos4n3](https://github.com/whos4n3) そして、ドキュメントの同期と中国語への翻訳を維持してくれた[@miss85246](https://github.com/miss85246) と [@ZinkLu](https://github.com/ZinkLu) には、多大なる感謝を捧げます。 --- -もし、このプロジェクトを楽しんでいただけるなら、ぜひ貢献をご検討ください。もちろんコードの貢献は大歓迎ですが、どんな形の貢献も大歓迎です。ドキュメントを書いたり、使用例を紹介したり、会話に参加してあなたの声を伝えたり、もし可能であれば、[金銭的な貢献](https://opencollective.com/sanic-org/)も検討してみてください。 +もし、このプロジェクトを楽しんでいただけるなら、ぜひ貢献をご検討ください。 もちろんコードの貢献は大歓迎ですが、どんな形の貢献も大歓迎です。 ドキュメントを書いたり、使用例を紹介したり、会話に参加してあなたの声を伝えたり、もし可能であれば、[金銭的な貢献](https://opencollective.com/sanic-org/)も検討してみてください。 diff --git a/src/ja/guide/release-notes/v21.3.md b/src/ja/guide/release-notes/v21.3.md index 176c973f3e..a22934f371 100644 --- a/src/ja/guide/release-notes/v21.3.md +++ b/src/ja/guide/release-notes/v21.3.md @@ -6,18 +6,16 @@ Sanicが速くなりました。 -まあ、すでに速かったんですけどね。しかし、v21リリースの最初のイテレーションで、いくつかの主要なマイルストーンを取り入れ、目に見える改善を行いました。これらは、何年も前から温めていたアイデアを、ようやくリリースバージョンに取り入れたものです。 +まあ、すでに速かったんですけどね。 しかし、v21リリースの最初のイテレーションで、いくつかの主要なマイルストーンを取り入れ、目に見える改善を行いました。 これらは、何年も前から温めていたアイデアを、ようやくリリースバージョンに取り入れたものです。 -::: warning Breaking changes -バージョン21.3では、多くの新機能が導入されています。しかし、いくつかの破壊的な変更も含まれています。このため、これらの変更は前回のLTSの後に導入されました。もし、削除されたものに依存している場合は、アップグレードできるようになるまで v20.12LTS を使い続ける必要があります。 +::: warning 破壊的変更 バージョン21.3では、多くの新機能が導入されています。 しかし、いくつかの破壊的変更(訳注: ほとんどの人がプログラムを書き換える必要がある変更) も含まれています。 このため、これらの変更は前回のLTSの後に導入されました。 もし、削除されたものに依存している場合は、アップグレードできるようになるまで v20.12LTS を使い続ける必要があります。 ```bash pip install "sanic>=20.12,<20.13" pip freeze > requirements.txt ``` -ほとんどの典型的なインストールでは、問題なくアップグレードできるはずです。 -::: +ほとんどの典型的なインストールでは、問題なくアップグレードできるはずです。 ::: ## 知っておきたいこと @@ -25,21 +23,21 @@ pip freeze > requirements.txt ### Python 3.7以上必須 -このバージョンでPython 3.6のサポートが終了します。バージョン 20.12LTS は 2022 年 12 月の EOL まで、バージョン 19.12LTS は 2021 年 12 月の EOL まで Python 3.6 のサポートを継続します。 +このバージョンでPython 3.6のサポートが終了します。 バージョン 20.12LTS は 2022 年 12 月の EOL まで、バージョン 19.12LTS は 2021 年 12 月の EOL まで Python 3.6 のサポートを継続します。 [LTSポリシー](../project/policies.md#long-term-support-v-interim-releases)についてもっと読む。 ### 一級市民としてのストリーミング -最も大きな速度向上は、リクエストとレスポンスのサイクルを1つのフローに統一したことです。以前は、通常のサイクルとストリーミングサイクルの間に違いがありました。これは、互換性のためにAPIは同じままですが、フードの下で簡素化されました。正味の利点は、**すべての** リクエストが新しい利点を見ることができるようになったことです。 +最も大きな速度向上は、リクエストとレスポンスのサイクルを1つのフローに統一したことです。 以前は、通常のサイクルとストリーミングサイクルの間に違いがありました。 これは、互換性のためにAPIは同じままですが、フードの下で簡素化されました。 正味の利点は、**すべての** リクエストが新しい利点を見ることができるようになったことです。 [ストリーミングの変更](../advanced/streaming.md#response-streaming)についてもっと読む。 ### ルーターの見直し -古いSanicのルーターは正規表現をベースにしていました。さらに、それは実行時に修正するのが難しく、いくつかのパフォーマンスの問題をもたらした多くの癖に悩まされていました。この変更は何年もかけて行われ、現在では [起動時にルータをコンパイルされたツリーに変換する](https://community.sanicframework.org/t/a-fast-new-router/649/41)ようになっています。今年中のさらなる改良にご期待ください。 +古いSanicのルーターは正規表現をベースにしていました。 さらに、それは実行時に修正するのが難しく、いくつかのパフォーマンスの問題をもたらした多くの癖に悩まされていました。 この変更は何年もかけて行われ、現在では [起動時にルータをコンパイルされたツリーに変換する](https://community.sanicframework.org/t/a-fast-new-router/649/41)ようになっています。 今年中のさらなる改良にご期待ください。 -外向きのAPIは後方互換性を保っています。しかし、特にルーター内部の何かにアクセスしていた場合、いくつかの変更に気づくことが多いでしょう。例えば +外向きのAPIは後方互換性を保っています。 しかし、特にルーター内部の何かにアクセスしていた場合、いくつかの変更に気づくことが多いでしょう。 例えば 1. `Router.get()` に新しい戻り値が追加されました。 2. `Route`は、`namedtuple`ではなく、ちゃんとしたクラスオブジェクトになりました。 @@ -47,13 +45,13 @@ pip freeze > requirements.txt 4. ルートにマッチする新しいパターン `` が追加されました。 5. 少なくとも1つのルートが定義されていないと、アプリケーションを起動することはできません。 -ルーターは独自のリポジトリに置かれるようになりました。[sanic-org/sanic-router](https://github.com/sanic-org/sanic-router) また、独自の [standalone package on PyPI](https://pypi.org/project/sanic-routing/) に配置されています。 +[sanic-org/sanic-router](https://github.com/sanic-org/sanic-router) また、独自の [standalone package on PyPI](https://pypi.org/project/sanic-routing/) に配置されています。 ### シグナルAPI ⭐️ -_BETA機能: APIはv21.6で完成予定です_。 +__BETA機能: APIはv21.6で完成予定です_。* -新しいルーターの副次的な利点は、それが[新しいシグナルAPI](https://github.com/sanic-org/sanic/issues/1630)をも動かす二重の役割を果たすことです。この機能は現在公開されており、おそらく公開されるAPIは最終的に変更されることはないでしょう。 +新しいルーターの副次的な利点は、それが[新しいシグナルAPI](https://github.com/sanic-org/sanic/issues/1630)をも動かす二重の役割を果たすことです。 この機能は現在公開されており、おそらく公開されるAPIは最終的に変更されることはないでしょう。 この機能の核となる考えは以下の通りです。 @@ -63,7 +61,7 @@ _BETA機能: APIはv21.6で完成予定です_。 このAPIでは、3つの新しいメソッドを導入しています。 -- `@app.signal(...)` - シグナルハンドラを定義するためのメソッドです。これは、ルートに非常によく似た外観と動作をします。シグナルがディスパッチされるたびに、このハンドラが実行されます。 +- `@app.signal(...)` - シグナルハンドラを定義するためのメソッドです。 これは、ルートに非常によく似た外観と動作をします。 シグナルがディスパッチされるたびに、このハンドラが実行されます。 - `app.event(...)` - イベントが発生するまで実行を一時停止するために、アプリケーションの任意の場所で使用できる待ち受けです。 - `app.dispatch(...)` - イベントをトリガーして、シグナルハンドラを実行させます。 @@ -74,9 +72,9 @@ async def signal_handler(thing, **kwargs): async def wait_for_event(app): while True: - print("> waiting") + print("> 待機中") await app.event("foo.bar.*") - print("> event found\n") + print("> イベント発見\n") @app.after_server_start async def after_server_start(app, loop): @@ -85,18 +83,18 @@ async def after_server_start(app, loop): @app.get("/") async def trigger(request): await app.dispatch("foo.bar.baz") - return response.text("Done.") + return response.text("完了。") ``` ### ルートネーミング -以前は、ルートは`route.name`と`route.endpoint`の両方で参照されていました。似たようなものですが、両者は若干異なっていました。現在では、すべてのルートは**一貫した**名前空間と参照方法を持つようになりました。 +以前は、ルートは`route.name`と`route.endpoint`の両方で参照されていました。 似たようなものですが、両者は若干異なっていました。 現在では、すべてのルートは**一貫した**名前空間と参照方法を持つようになりました。 ``` .[オプショナル:<ブループリント名>.]<ハンドラ名> ``` -この新しい「名前」は、`route.name`というプロパティに割り当てられます。私たちは`route.endpoint`を非推奨としており、v21.9でこのプロパティを削除する予定です。それまでは、`route.name`のエイリアスになります。 +この新しい「名前」は、`route.name`というプロパティに割り当てられます。 私たちは`route.endpoint`を非推奨としており、v21.9でこのプロパティを削除する予定です。 それまでは、`route.name`のエイリアスになります。 また、静的ルート、websocketルート、blueprintルートなどに使用されていたネーミングプレフィックスが削除されました。 @@ -105,20 +103,20 @@ async def trigger(request): IDEのオートコンプリートを支援する新しいデコレーターをいくつか追加しました。 ```python -# Alias to @app.listener("...") +# @app.listener("...")のエイリアス @app.before_server_start @app.after_server_stop @app.before_server_start @app.after_server_stop -# Alias to @app.middleware("...") +# @app.middleware("...")のエイリアス @app.on_request @app.on_response ``` ### ルート内の引用符の解除 -非アスキー文字を使用するルートがある場合、Sanicはあなたのためにテキストを`unquote`しないでしょう。ルート定義にそうするように特別に指示する必要があります。 +非アスキー文字を使用するルートがある場合、Sanicはあなたのためにテキストを`unquote`しないでしょう。 ルート定義にそうするように特別に指示する必要があります。 ```python @app.route("/overload/", methods=["GET"], unquote=True) @@ -133,7 +131,7 @@ assert response.text == "OK2 您好" ### `Request.match_info` を変更する。 -これまで `match_info` はマッチしたパスパラメータのデータを提供してきました。ミドルウェアなどで、これを変更できるようになりました。 +これまで `match_info` はマッチしたパスパラメータのデータを提供してきました。 ミドルウェアなどで、これを変更できるようになりました。 ```python @app.on_request @@ -156,7 +154,7 @@ routesの引数`version`に以下のタイプが指定できるようになり ``` ### ボディを使った安全なメソッド処理 -`GET`、`HEAD`、`OPTIONS`、`DELETE` のルートハンドラは、渡された HTTP のボディをデコードしません。これをオーバーライドすることができます。 +`GET`、`HEAD`、`OPTIONS`、`DELETE` のルートハンドラは、渡された HTTP のボディをデコードしません。 これをオーバーライドすることができます。 ```python @app.delete(..., ignore_body=False) @@ -164,7 +162,7 @@ routesの引数`version`に以下のタイプが指定できるようになり ### アプリケーション、ブループリント、ブループリント・グループのパリティ -`Sanic` クラスと `Blueprint` クラスは共通のベースを使用しています。以前は、多くの機能が重複していたため、両者の間で若干異なる実装になっていました。現在は、どちらも同じベースクラスを継承しているため、開発者やプラグインはより一貫した API を使用できるようになりました。 +`Sanic` クラスと `Blueprint` クラスは共通のベースを使用しています。 以前は、多くの機能が重複していたため、両者の間で若干異なる実装になっていました。 現在は、どちらも同じベースクラスを継承しているため、開発者やプラグインはより一貫した API を使用できるようになりました。 また、Blueprint Groups は `version` や `strict_slashes` キーワード引数のような一般的な URL 拡張をサポートするようになりました。 @@ -174,13 +172,13 @@ routesの引数`version`に以下のタイプが指定できるようになり ### `testing` ライブラリを削除 -Sanicの内部テストクライアントは削除されました。現在は独自のリポジトリに置かれています。[sanic-org/sanic-testing](https://github.com/sanic-org/sanic-testing) にあり、また、独自の [standalone package on PyPI](https://pypi.org/project/sanic-testing/) にもあります。 +Sanicの内部テストクライアントは削除されました。 現在は独自のリポジトリに置かれています。 [sanic-org/sanic-testing](https://github.com/sanic-org/sanic-testing) にあり、また、独自の [standalone package on PyPI](https://pypi.org/project/sanic-testing/) にもあります。 -`sanic-testing` がインストールされていれば、これまで通り `Sanic()` アプリケーションインスタンスで利用することができます。そのため、 ** 唯一の** 変更点は、テストスイートの要件に `sanic-testing` を追加することです。 +`sanic-testing` がインストールされていれば、これまで通り `Sanic()` アプリケーションインスタンスで利用することができます。 そのため、 ** 唯一の** 変更点は、テストスイートの要件に `sanic-testing` を追加することです。 ### アプリケーションと接続レベルのコンテキスト (`ctx`) オブジェクト -バージョン19.9で`request.ctx`APIを[追加](https://github.com/sanic-org/sanic/pull/1666/files)しました。この便利な構造により、 (例えばミドルウェアの) リクエストオブジェクトにプロパティやデータをアタッチし、 アプリケーション内の他の場所でその情報を再利用することが簡単にできるようになりました。 +バージョン19.9で`request.ctx`APIを[追加](https://github.com/sanic-org/sanic/pull/1666/files)しました。 この便利な構造により、 (例えばミドルウェアの) リクエストオブジェクトにプロパティやデータをアタッチし、 アプリケーション内の他の場所でその情報を再利用することが簡単にできるようになりました。 同様に、このコンセプトは2つの場所で拡張されています。 @@ -189,21 +187,21 @@ Sanicの内部テストクライアントは削除されました。現在は独 #### アプリケーションコンテキスト -一般的なユースケースは、アプリのインスタンスにプロパティをアタッチすることです。一貫性を保つため、そして Sanic プロパティとの名前の衝突の問題を避けるために、`ctx` オブジェクトは現在 `Sanic` インスタンスに存在します。 +一般的なユースケースは、アプリのインスタンスにプロパティをアタッチすることです。 一貫性を保つため、そして Sanic プロパティとの名前の衝突の問題を避けるために、`ctx` オブジェクトは現在 `Sanic` インスタンスに存在します。 ```python @app.before_server_startup async def startup_db(app, _): - # WRONG + # 間違い app.db = await connect_to_db() - # CORRECT + # 正解 app.ctx.db = await connect_to_db() ``` #### 接続コンテキスト -クライアントがキープアライブヘッダを送信すると、Sanicはトランスポートソケットを[一定期間オープンしたままにしようとします](../deployment/configuration.md#keep-alive-timeout)。クライアントがキープアライブヘッダを送信すると、Sanicはトランスポートソケットを一定期間オープンしたままにしようとします。そのトランスポートオブジェクトは今、`ctx`オブジェクトを利用できるようになりました。これは事実上、1つのクライアントからの複数のリクエスト(トランスポートレイヤーが再利用されている)が状態を共有することができることを意味します。 +クライアントがキープアライブヘッダを送信すると、Sanicはトランスポートソケットを[一定期間オープンしたままにしようとします](../deployment/configuration.md#keep-alive-timeout)。 そのトランスポートオブジェクトは今、`ctx`オブジェクトを利用できるようになりました。 これは事実上、1つのクライアントからの複数のリクエスト(トランスポートレイヤーが再利用されている)が状態を共有することができることを意味します。 ```python @app.on_request @@ -233,15 +231,15 @@ request.conn_info.ctx.foo=3 ### 新しいフロントページ 🎉 -ドキュメントを2つに分けました。コードベース内部のdocstringsは、これまで通りReadTheDocsにshinxのdocをビルドしていきます。しかし、それはAPIドキュメントに限定されるでしょう。新しいフロントページには、"Sanicユーザーガイド"が置かれます。 +ドキュメントを2つに分けました。 コードベース内部のdocstringsは、これまで通りReadTheDocsにshinxのdocをビルドしていきます。 しかし、それはAPIドキュメントに限定されるでしょう。 新しいフロントページには、"Sanicユーザーガイド"が置かれます。 -新しいサイトはVuepressで動作します。寄付を歓迎します。また、ドキュメントの翻訳のお手伝いも募集しています。 +新しいサイトはVuepressで動作します。 寄付を歓迎します。 また、ドキュメントの翻訳のお手伝いも募集しています。 この一環として、RTDのドキュメントもリフレッシュし、APIドキュメントのみに変更しました。 ### チャットは Discord に移動 -Gitterチャットルームは廃止に一歩近づきました。その代わりに、私たちは[Discordサーバ](https://discord.gg/FARQzAEMAA)を開設しました。 +Gitterチャットルームは廃止に一歩近づきました。 その代わりに、私たちは[Discordサーバー](https://discord.gg/FARQzAEMAA)を開設しました。 ### オープンコレクティブ @@ -249,11 +247,11 @@ Sanicコミュニティ組織は、Sanicの開発を財政的に支援したい ### 2021年版リリース管理者 -2019年、2020年ともにリリースマネージャーを務めてくれた@sjsadowskiと@yunstanfordに感謝します。今年のリリースマネージャーは@ahopkinsと@vltrです。 +2019年、2020年ともにリリースマネージャーを務めてくれた@sjsadowskiと@yunstanfordに感謝します。 今年のリリースマネージャーは@ahopkinsと@vltrです。 ## 謝礼 -今回のリリースに参加された皆様、ありがとうございました。:clap: +今回のリリースに参加された皆様、ありがとうございました。 :clap: [@ahopkins](https://github.com/ahopkins) [@akshgpt7](https://github.com/akshgpt7) [@artcg](https://github.com/artcg) [@ashleysommer](https://github.com/ashleysommer) [@elis-k](https://github.com/elis-k) [@harshanarayana](https://github.com/harshanarayana) [@sjsadowski](https://github.com/sjsadowski) [@tronic](https://github.com/tronic) [@vltr](https://github.com/vltr), diff --git a/src/ja/guide/release-notes/v21.6.md b/src/ja/guide/release-notes/v21.6.md index 7e52446c97..b6aac24857 100644 --- a/src/ja/guide/release-notes/v21.6.md +++ b/src/ja/guide/release-notes/v21.6.md @@ -4,17 +4,17 @@ ## 概要 -これは、バージョン21の[リリースサイクル](../project/policies.md#release-schedule)の2回目のリリースです。12月の長期サポート版でバージョン21が「確定」する前に、9月にもう1回リリースされる予定です。21.3 からは、ルーターが独自のパッケージに移動したことにお気づきかもしれません。[`sanic-routing`](https://pypi.org/project/sanic-routing) です。この変更は当分続きそうです。このリリースから、必要な最小バージョンは 0.7.0 になりました。 +これは、バージョン21の[リリースサイクル](../project/policies.md#release-schedule)の2回目のリリースです。 12月の長期サポート版でバージョン21が「確定」する前に、9月にもう1回リリースされる予定です。 21.3 からは、ルーターが独自のパッケージに移動したことにお気づきかもしれません。 [`sanic-routing`](https://pypi.org/project/sanic-routing) です。 この変更は当分続きそうです。 このリリースから、必要な最小バージョンは 0.7.0 になりました。 ## 知っておきたいこと -詳しくは[変更履歴](https://sanic.readthedocs.io/en/stable/sanic/changelog.html)をご覧ください。注目すべき新機能・不具合、アップグレード対象など... +詳しくは[変更履歴](https://sanic.readthedocs.io/en/stable/sanic/changelog.html)をご覧ください。 注目すべき新機能・不具合、アップグレード対象など... ### `StreamingHTTPResponse`の非推奨化 -`StreamingHTTPResponse` の使用は非推奨となり、21.12リリースで削除される予定です。これは `sanic.response.stream` と `sanic.response.file_stream` の両方に影響し、どちらもフード内で `StreamingHTTPResponse` をインスタンス化します。 +`StreamingHTTPResponse` の使用は非推奨となり、21.12リリースで削除される予定です。 これは `sanic.response.stream` と `sanic.response.file_stream` の両方に影響し、どちらもフード内で `StreamingHTTPResponse` をインスタンス化します。 -正確な移行経路はまだ決定されていませんが、`sanic.response.stream` と `sanic.response.file_stream` は何らかの形で v21.12 に便利な演算子として存在し続けることになります。9 月のリリースまでに最終決定する予定ですので、この夏中に詳細をご確認ください。 +正確な移行経路はまだ決定されていませんが、`sanic.response.stream` と `sanic.response.file_stream` は何らかの形で v21.12 に便利な演算子として存在し続けることになります。 9 月のリリースまでに最終決定する予定ですので、この夏中に詳細をご確認ください。 ### `CompositionView`の非推奨化 @@ -34,11 +34,11 @@ async def handler(request, foo: str, bar: float): ### バージョン0.7 ルーターアップグレード -これは多くのバグフィックスを含み、v0.6 よりも幅広いエッジケースをより優雅に扱えるようになりました。もし、サポートされていないパターンがあれば、[報告してください](https://github.com/sanic-org/sanic-routing/issues). 解決された問題のいくつかは `sanic-routing`の[リリースノート](https://github.com/sanic-org/sanic-routing/releases) で見ることができます。 +これは多くのバグフィックスを含み、v0.6 よりも幅広いエッジケースをより優雅に扱えるようになりました。 もし、サポートされていないパターンがあれば、[報告してください](https://github.com/sanic-org/sanic-routing/issues). 解決された問題のいくつかは `sanic-routing`の[リリースノート](https://github.com/sanic-org/sanic-routing/releases) で見ることができます。 ### `eof()`によるインラインストリーミング -バージョン21.3には、[ストリーミングの処理方法の大きな変更](https://sanicframework.org/en/guide/release-notes/v21.3.html#what-to-know)が含まれていて、導入されたパターンがデフォルトにります(下記参照)。便利なことに、新しい `response.eof()` メソッドが含まれています。これは、最終的なデータがクライアントにプッシュされた時点で呼び出されるべきものです。 +バージョン21.3には、[ストリーミングの処理方法の大きな変更](https://sanic.dev/en/guide/release-notes/v21.3.html#what-to-know)が含まれています。 そして、導入されたパターンがデフォルトにります(下記参照)。 便利なことに、新しい `response.eof()` メソッドが含まれています。 これは、最終的なデータがクライアントにプッシュされた時点で呼び出されるべきものです。 ```python @app.route("/") @@ -60,7 +60,7 @@ async def article(request, article_slug: str): ... ``` -スラッグは小文字のアルファベットか数字で構成されていなければなりません。ハイフン(`-`)を含むことができますが、最初の文字にはできません。 +スラッグは小文字のアルファベットか数字で構成されていなければなりません。 ハイフン(`-`)を含むことができますが、最初の文字にはできません。 ``` this-is-a-slug @@ -80,11 +80,11 @@ NOT-a-slug Pythonの変数の命名規則と似ていますが、ハイフン(`-`)を許容することが追加されています。 -より緩やかな基準は廃止されました。21.12 からは、非適合は起動時のエラーになります。 +より緩やかな基準は廃止されました。 21.12 からは、非適合は起動時のエラーになります。 ### `Route` オブジェクトへの新しいアクセス: `route.uri`. -v21.3 の `Route` オブジェクトは `uri` 属性を持たなくなりました。その代わりに、`route.path`というクローズを取得することができました。しかし、 `sanic-routing` の動作の関係で、 `path` プロパティには先頭の `/` が*ありません*。これは修正され、現在は `route.uri` にスラッシュが含まれるようになりました。 +v21.3 の `Route` オブジェクトは `uri` 属性を持たなくなりました。 その代わりに、`route.path`というクローズを取得することができました。 しかし、 `sanic-routing` の動作の関係で、 `path` プロパティには先頭の `/` が*ありません*。 これは修正され、現在は `route.uri` にスラッシュが含まれるようになりました。 ```python route.uri == f"/{route.path}" @@ -92,9 +92,9 @@ route.uri == f"/{route.path}" ### IP に影響を与える`Request`オブジェクトの新しいアクセッサ -着信リクエストのIPアドレスにアクセスするために、Sanicはリクエストオブジェクトに `request.ip` という便利なアクセッサを持っていました。これは新しいものではなく、開いているHTTP接続についての詳細を提供するオブジェクト `request.conn_info` から来ています。 +着信リクエストのIPアドレスにアクセスするために、Sanicはリクエストオブジェクトに `request.ip` という便利なアクセッサを持っていました。 これは新しいものではなく、開いているHTTP接続についての詳細を提供するオブジェクト `request.conn_info` から来ています。 -現在のバージョンでは、この `conn_info` オブジェクトに新しい `client_ip` アクセッサが追加されています。IPv4 の場合は、特に違いは感じないでしょう。しかし、IPv6アプリケーションでは、新しいアクセサはアドレスの "unwrapped "バージョンを提供します。次のような例を考えてみましょう。 +現在のバージョンでは、この `conn_info` オブジェクトに新しい `client_ip` アクセッサが追加されています。 IPv4 の場合は、特に違いは感じないでしょう。 しかし、IPv6アプリケーションでは、新しいアクセサはアドレスの "unwrapped "バージョンを提供します。 次のような例を考えてみましょう。 ```python @app.get("/") @@ -123,7 +123,7 @@ $ curl http://\[::1\]:8000 ### `Config` と `Sanic.ctx` オブジェクトの交互使用 -独自のconfigとcontextオブジェクトをSanicアプリケーションに渡すことができるようになりました。カスタムコンフィギュレーションは `sanic.config.Config` のサブクラスで**あるべきです**。コンテキストオブジェクトは、何の制限もなく、好きなものを使用できます。 +独自のconfigとcontextオブジェクトをSanicアプリケーションに渡すことができるようになりました。 カスタムコンフィギュレーションは `sanic.config.Config` のサブクラスで**あるべきです**。 コンテキストオブジェクトは、何の制限もなく、好きなものを使用できます。 ```python class CustomConfig(Config): @@ -180,15 +180,13 @@ $ sanic path.to:create_app --factory #### Sanicシンプルサーバー -Sanic CLIには、ディレクトリをウェブサーバーとして提供するためのシンプルなパターンが含まれるようになりました。これは、ディレクトリルートにある `index.html` を探します。 +Sanic CLIには、ディレクトリをウェブサーバーとして提供するためのシンプルなパターンが含まれるようになりました。 これは、ディレクトリルートにある `index.html` を探します。 ```bash $ sanic ./path/to/dir --simple ``` -::: warning -この機能はまだ初期*ベータ*モードです。この機能は、今後変更される可能性があります。 -::: +::: warning この機能はまだ初期*ベータ*モードです。 スコープを変更する可能性があります。 ::: #### リロード先ディレクトリの追加 @@ -198,13 +196,11 @@ $ sanic ./path/to/dir --simple sanic ... --reload-dir=/path/to/foo --reload-dir=/path/to/bar ``` -::: tip -アプリケーション・ディレクトリでこれをインクルードする必要は*ありません*。Sanic はアプリケーション内の Python ファイルが変更されると、自動的にリロードします。静的なファイルが更新されたときに、アプリケーションをリッスンして更新したい場合は、 `reload-dir` 引数を使用する必要があります。 -::: +::: tip アプリケーション・ディレクトリでこれをインクルードする必要は*ありません*。 Sanic はアプリケーション内の Python ファイルが変更されると、自動的にリロードします。 静的なファイルが更新されたときに、アプリケーションをリッスンして更新したい場合は、 `reload-dir` 引数を使用する必要があります。 ::: ### バージョンプレフィックス -`version`を追加する場合、ルートには `/v` というプレフィックスが付きます。これは常にパスの先頭になります。これは新しいものではありません。 +`version`を追加する場合、ルートには `/v` というプレフィックスが付きます。 これは常にパスの先頭になります。 これは新しいものではありません。 ```python # /v1/my/path @@ -240,13 +236,13 @@ async def signal_handler(): await app.event("do.something.complete") ``` -### 無限に再利用可能で入れ子にできる `Blueprint` と `BlueprintGroup` です。 +### 無限に再利用可能でネストができる`Blueprint` と `BlueprintGroup` -1つの `Blueprint` を複数のグループに割り当てて再利用することはできません。また、グループ自体も他のグループと無限にネストすることができます。これにより、無限の構成が可能になります。 +1つの `Blueprint` を複数のグループに割り当てて再利用することはできません。 また、グループ自体も他のグループと無限にネストすることができます。 これにより、無限の構成が可能になります。 ### HTTP メソッドを `Enum` として使用できるように -Sanicに`sanic.HTTPMethod` が追加されました。これは `Enum` で、文字列と同じように使用することができます。 +Sanicに`sanic.HTTPMethod` が追加されました。 これは `Enum` で、文字列と同じように使用することができます。 ```python from sanic import Sanic, HTTPMethod @@ -295,11 +291,11 @@ class DummyView(HTTPMethodView, attach=Sanic.get_app(), uri="/"): ### Discordとサポートフォーラム -まだコミュニティに参加していない方は、[Discord server](https://discord.gg/FARQzAEMAA) と [Community Forums](https://community.sanicframework.org/) に参加することで一員になることができます。また、Twitterで[@sanicframework](https://twitter.com/sanicframework)をフォローしてください。 +まだコミュニティに参加していない方は、[Discord server](https://discord.gg/FARQzAEMAA) と [Community Forums](https://community.sanicframework.org/) に参加することで一員になることができます。 また、Twitterで[@sanicframework](https://twitter.com/sanicframework)をフォローしてください。 ### SCO 2022年選挙 -夏🏝 / 冬❄️(あなたのいる半球による)がやってきましたね。それは、SCOの選挙を行うことを意味します。今年は、以下のポジションがあります。 +夏🏝 / 冬❄️(あなたのいる半球による)がやってきましたね。 それは、SCOの選挙を行うことを意味します。 今年は、以下のポジションがあります。 - 運営評議会メンバー(任期2年) - ステアリング・カウンシル・メンバー(任期2年) @@ -311,32 +307,18 @@ class DummyView(HTTPMethodView, attach=Sanic.get_app(), uri="/"): もっと詳しく知りたい方は、SCO [roles and responsibilities](../project/scope.md#roles-and-responsibilities) や、Adam Hopkins の Discord を読んでみてください。 -ノミネートは9月1日に開始されます。詳細は追ってフォーラムでお知らせします。 +ノミネートは9月1日に開始されます。 詳細は追ってフォーラムでお知らせします。 ### 新しいプロジェクトが進行中 -SCOの傘下に新しいプロジェクトを追加しました。[`sanic-ext`](https://github.com/sanic-org/sanic-ext) です。このプロジェクトはまだリリースされておらず、活発に開発が行われています。このプロジェクトの目標は、最終的に [`sanic-openapi`](https://github.com/sanic-org/sanic-openapi) を入力検証、CORS 処理、HTTP 自動メソッドハンドラなど、ウェブアプリケーション開発者向けのより多くの機能を提供するものに置き換えることにあります。もし、お手伝いいただけるようでしたら、Discord でお知らせください。このプロジェクトの最初のリリースは、9月のリリースの前のいつか(できれば)になることを期待しています。 +SCOの傘下に新しいプロジェクトを追加しました。 [`sanic-ext`](https://github.com/sanic-org/sanic-ext) です。 このプロジェクトはまだリリースされておらず、活発に開発が行われています。 このプロジェクトの目標は、最終的に [`sanic-openapi`](https://github.com/sanic-org/sanic-openapi) を入力検証、CORS 処理、HTTP 自動メソッドハンドラなど、ウェブアプリケーション開発者向けのより多くの機能を提供するものに置き換えることにあります。 あなたがプロジェクトを助けることに興味を持っているなら、Discordでお知らせください。 このプロジェクトの最初のリリースは、9月のリリースの前のいつか(できれば)になることを期待しています。 ## 謝礼 今回のリリースに参加された皆様、ありがとうございました。 :clap: -[@aaugustin](https://github.com/aaugustin) -[@ahopkins](https://github.com/ahopkins) -[@ajaygupta2790](https://github.com/ajaygupta2790) -[@ashleysommer](https://github.com/ashleysommer) -[@ENT8R](https://github.com/ent8r) -[@fredlllll](https://github.com/fredlllll) -[@graingert](https://github.com/graingert) -[@harshanarayana](https://github.com/harshanarayana) -[@jdraymon](https://github.com/jdraymon) -[@Kyle-Verhoog](https://github.com/kyle-verhoog) -[@sanjeevanahilan](https://github.com/sanjeevanahilan) -[@sjsadowski](https://github.com/sjsadowski) -[@Tronic](https://github.com/tronic) -[@vltr](https://github.com/vltr) -[@ZinkLu](https://github.com/zinklu) +[@aaugustin](https://github.com/aaugustin) [@ahopkins](https://github.com/ahopkins) [@ajaygupta2790](https://github.com/ajaygupta2790) [@ashleysommer](https://github.com/ashleysommer) [@ENT8R](https://github.com/ent8r) [@fredlllll](https://github.com/fredlllll) [@graingert](https://github.com/graingert) [@harshanarayana](https://github.com/harshanarayana) [@jdraymon](https://github.com/jdraymon) [@Kyle-Verhoog](https://github.com/kyle-verhoog) [@sanjeevanahilan](https://github.com/sanjeevanahilan) [@sjsadowski](https://github.com/sjsadowski) [@Tronic](https://github.com/tronic) [@vltr](https://github.com/vltr) [@ZinkLu](https://github.com/zinklu) --- -もし、このプロジェクトを楽しんでいただけるなら、ぜひ貢献をご検討ください。もちろん、コードの貢献も大好きですが、どんな形の貢献も大歓迎です。ドキュメントを書いたり、ユースケースを見せたり、会話に参加してあなたの声を伝えたり、もし可能なら[金銭的貢献](https://opencollective.com/sanic-org/)も検討してください。 +もし、このプロジェクトを楽しんでいただけるなら、ぜひ貢献をご検討ください。 もちろん、コードの貢献も大好きですが、どんな形の貢献も大歓迎です。 ドキュメントを書いたり、ユースケースを見せたり、会話に参加してあなたの声を伝えたり、もし可能なら[金銭的貢献](https://opencollective.com/sanic-org/)も検討してください。 diff --git a/src/ja/guide/release-notes/v21.9.md b/src/ja/guide/release-notes/v21.9.md index f3f7790ec4..f1a2ac0161 100644 --- a/src/ja/guide/release-notes/v21.9.md +++ b/src/ja/guide/release-notes/v21.9.md @@ -4,19 +4,19 @@ ## 概要 -これはバージョン21の3つ目の[リリースサイクル](../../org/policies.md#release-schedule)です。 バージョン21は、12月の長期サポート版リリースで「確定」する予定です。 +これはバージョン21の3つ目の[リリースサイクル](../../org/policies.md#release-schedule)です。 バージョン21は、12月の長期サポート版リリースで「確定」する予定です。 ## 知っておきたい -詳しくは[Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html)をご覧ください。注目すべき新機能・不具合、アップグレード対象など... +詳しくは[Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html)をご覧ください。 注目すべき新機能・不具合、アップグレード対象など... ### 設定値の削除: `WEBSOCKET_READ_LIMIT`、`WEBSOCKET_WRITE_LIMIT`、`WEBSOCKET_MAX_QUEUE` -ウェブソケット実装の全面的な見直しに伴い、これらの設定値は削除されました。現在、これらを置き換える予定はありません。 +ウェブソケット実装の全面的な見直しに伴い、これらの設定値は削除されました。 現在、これらを置き換える予定はありません。 ### `FALLBACK_ERROR_FORMAT`のデフォルト値の非推奨 -エラーハンドラが添付されていない場合、Sanicはフォールバックのフォーマットタイプとして`html`を使用してきました。これは非推奨となり、v22.3から`text`に変更される予定です。この値は `auto` に変更されましたが、変更前の v21.12LTS までは、最後の手段として HTML を使用し続けるでしょう。 +エラーハンドラが添付されていない場合、Sanicはフォールバックのフォーマットタイプとして`html`を使用してきました。 これは非推奨となり、v22.3から`text`に変更される予定です。 この値は `auto` に変更されましたが、変更前の v21.12LTS までは、最後の手段として HTML を使用し続けるでしょう。 ### `ErrorHandler.lookup`署名の非推奨 @@ -34,14 +34,14 @@ def lookup(self, exception, route_name: Optional[str]): - `CompositionView` - `load_env` (代わりに `env_prefix` を使用) -- サニックオブジェクト(アプリケーションインスタンス、ブループリント、ルート)は、以下に準拠した英数字を使用する必要があります。`^[a-zA-Z][a-zA-Z0-9_\-]*$` -- アプリケーションとブループリントのインスタンスへのオブジェクトの任意の割り当て (代わりに `ctx` を使用します。この削除は 21.9 から 21.12 にバンプされました) +- サニックオブジェクト(アプリケーションインスタンス、ブループリント、ルート)は、以下に準拠した英数字を使用する必要があります。 `^[a-zA-Z][a-zA-Z0-9_\-]*$` +- アプリケーションとブループリントのインスタンスへのオブジェクトの任意の割り当て (代わりに `ctx` を使用します。 この削除は 21.9 から 21.12 にバンプされました) ### ウェブソケットの全面見直し -ウェブソケット接続の取り扱いについて、大きな見直しが行われました。[@augustin](https://github.com/aaugustin) のおかげで、[`websockets`](https://websockets.readthedocs.io/en/stable/index.html) に新しい実装が追加され、Sanic が自分自身でウェブソケット接続の I/O を処理することができるようになりました。そのため、Sanicは最小バージョンを `websockets>=10.0` に引き上げました。 +ウェブソケット接続の取り扱いについて、大きな見直しが行われました。 [@augustin](https://github.com/aaugustin) のおかげで、[`websockets`](https://websockets.readthedocs.io/en/stable/index.html) に新しい実装が追加され、Sanic が自分自身でウェブソケット接続の I/O を処理することができるようになりました。 そのため、Sanicは最小バージョンを `websockets>=10.0` に引き上げました。 -この変更は、Sanicのウェブソケットハンドラに関するいくつかの奇妙な点が修正されたことを除けば、開発者にはほとんど気づかれないはずです。例えば、誰かが切断したときに `CancellError` を自分で捕らえることができるようになったはずです。 +この変更は、Sanicのウェブソケットハンドラに関するいくつかの奇妙な点が修正されたことを除けば、開発者にはほとんど気づかれないはずです。 例えば、誰かが切断したときに `CancellError` を自分で捕らえることができるようになったはずです。 ```python @app.websocket("/") @@ -50,14 +50,14 @@ async def handler(request, ws): while True: await asyncio.sleep(0.25) except asyncio.CancelledError: - print("User closed connection") + print("ユーザーがconnectionを閉じました") ``` ### ビルトイン信号 -バージョン [21.3](./v21.3.md) で [signals](../advanced/signals.md) が導入されました。現在、Sanicは**コードベース自体から**シグナルイベントをディスパッチします。これは、開発者が以前よりもはるかに近いレベルでリクエスト/レスポンスサイクルにフックする能力を持っていることを意味します。 +バージョン [21.3](./v21.3.md) で [signals](../advanced/signals.md) が導入されました。 現在、Sanicは**コードベース自体から**シグナルイベントをディスパッチします。 これは、開発者が以前よりもはるかに近いレベルでリクエスト/レスポンスサイクルにフックする能力を持っていることを意味します。 -これまでは、何らかのロジックを注入しようとすると、ミドルウェアに限られていたのです。統合シグナルは「スーパーミドルウェア」だと考えてください。現在ディスパッチされるイベントは以下の通りです。 +これまでは、何らかのロジックを注入しようとすると、ミドルウェアに限られていたのです。 統合シグナルは「スーパーミドルウェア」だと考えてください。 現在ディスパッチされるイベントは以下の通りです。 - `http.lifecycle.begin` - `http.lifecycle.complete` @@ -77,13 +77,11 @@ async def handler(request, ws): - `server.shutdown.after` - `server.shutdown.before` -::: tip Note -`server`シグナルは、4つのメインサーバリスナーイベントと同じです。実際、これらのリスナーはシグナルを実装するための便利なラッパーに過ぎません。 -::: +::: tip Note `server`シグナルは、4つのメインサーバリスナーイベントと同じです。 実際、これらのリスナー自身が実装を知らせるための便利なラッパーになっています。 ::: ### より賢い`自動`例外フォーマット -Sanicは現在、エンドポイントとクライアントに基づいた適切な例外フォーマットで応答しようとします。たとえば、エンドポイントが常に `sanic.response.json` オブジェクトを返す場合、例外はすべて自動的に JSON でフォーマットされます。同じことが、 `text` と `html` のレスポンスにも当てはまります。 +Sanicは現在、エンドポイントとクライアントに基づいた適切な例外フォーマットで応答しようとします。 たとえば、エンドポイントが常に `sanic.response.json` オブジェクトを返す場合、例外はすべて自動的に JSON でフォーマットされます。 同じことが、 `text` と `html` のレスポンスにも当てはまります。 さらに、ルート定義により、ルートごとに使用するフォーマッターを_明示的に_制御できるようになりました。 @@ -95,7 +93,7 @@ async def handler(request): ### ブループリントのコピー -ブループリントは、新しいインスタンスにコピーすることができます。これは、ルートやミドルウェアなど、それに付随するすべてのものを引き継ぎます。 +ブループリントは、新しいインスタンスにコピーすることができます。 これは、ルートやミドルウェアなど、それに付随するすべてのものを引き継ぎます。 ```python v1 = Blueprint("Version1", version=1) @@ -116,18 +114,18 @@ app.blueprint(v2) ``` ### ブループリントグループの便利なメソッド -ブループリントグループは、通常のブループリントと同じすべてのメソッドを使用できるようになりました。これにより、ブループリントのコピーと合わせて、ブループリントは非常にコンポーザブルで柔軟なものになるはずです。 +ブループリントグループは、通常のブループリントと同じすべてのメソッドを使用できるようになりました。 これにより、ブループリントのコピーと合わせて、ブループリントは非常にコンポーザブルで柔軟なものになるはずです。 ### Acceptヘッダーの解析 -Sanicの`Request`オブジェクトは `Accept` ヘッダーをパースして、クライアントの content-type の優先順位のリストを提供することができます。単純にアクセッサとしてアクセスすることができます。 +Sanicの`Request`オブジェクトは `Accept` ヘッダーをパースして、クライアントの content-type の優先順位のリストを提供することができます。 単純にアクセッサとしてアクセスすることができます。 ```python print(request.accept) # ["*/*"] ``` -また、ワイルドカードでの照合も可能です。例えば、受信したリクエストに含まれるものとして: +また、ワイルドカードでの照合も可能です。 例えば、受信したリクエストに含まれるものとして: ``` Accept: */* @@ -141,7 +139,7 @@ Accept: */* ### デフォルトの例外メッセージ -`SanicException` から派生した例外はすべて、デフォルトの例外メッセージを定義できるようになりました。これにより、複数の場所で同じ例外を再利用する際に、例外が提供するメッセージに関するDRY問題が発生しなくなり、より便利で保守しやすくなりました。 +`SanicException` から派生した例外はすべて、デフォルトの例外メッセージを定義できるようになりました。 これにより、複数の場所で同じ例外を再利用する際に、例外が提供するメッセージに関するDRY問題が発生しなくなり、より便利で保守しやすくなりました。 ```python class TeaError(SanicException): @@ -153,7 +151,7 @@ raise TeaError ### 型アノテーションの利便機能 -Pythonの型アノテーションを利用して、パスパラメータの型を制御することが可能になりました。次のようにする代わりに、 +Pythonの型アノテーションを利用して、パスパラメータの型を制御することが可能になりました。 次のようにする代わりに、 ```python @app.route("///") @@ -183,19 +181,19 @@ static("/", "/path/to/some/file", resource_type="file")) ### `sanic-ext` のリリースと `sanic-openapi` の非推奨化 -サニックの基本理念のひとつは、「独裁者ではなく、道具であること」です。このホームページのトップページにあるように、 +サニックの基本理念のひとつは、「独裁者ではなく、道具であること」です。 このホームページのトップページにあるように、 > ツールに制約されることなく、構築したい方法で構築。 -これは、(特にWeb API開発者が)使用する多くの一般的な機能が、`sanic`リポジトリに存在しないことを意味します。これには良い理由があります。独創的であることは、開発者に自由と柔軟性を与えます。 +これは、(特にWeb API開発者が)使用する多くの一般的な機能が、`sanic`リポジトリに存在しないことを意味します。 これには良い理由があります。 独創的であることは、開発者に自由と柔軟性を与えます。 -しかし、時には同じものをビルドしたり再構築したりする必要がないようにしたいものです。Sanicはこれまで、プラグインでギャップを埋めるために、コミュニティの素晴らしいサポートに本当に頼ってきました。 +しかし、時には同じものをビルドしたり再構築したりする必要がないようにしたいものです。 Sanicはこれまで、プラグインでギャップを埋めるために、コミュニティの素晴らしいサポートに本当に頼ってきました。 -初期から、公式の `sanic-openapi` パッケージがあり、アプリケーションに基づいた OpenAPI ドキュメントを作成する機能を提供していました。しかし、このプロジェクトは何年も悩まされ、メインプロジェクトほどの優先度は与えられていませんでした。 +初期から、公式の `sanic-openapi` パッケージがあり、アプリケーションに基づいた OpenAPI ドキュメントを作成する機能を提供していました。 しかし、このプロジェクトは何年も悩まされ、メインプロジェクトほどの優先度は与えられていませんでした。 -v21.9 のリリースから、SCOは`sanic-openapi`パッケージを非推奨とし、メンテナンスモードへ移行します。これは、現在の将来を維持するために必要なアップデートは継続されますが、新しい機能の拡張は行われないことを意味します。 +v21.9 のリリースから、SCOは`sanic-openapi`パッケージを非推奨とし、メンテナンスモードへ移行します。 これは、現在の将来を維持するために必要なアップデートは継続されますが、新しい機能の拡張は行われないことを意味します。 -その代わりとして、 `sanic-ext` という新しいプロジェクトが導入されます。このパッケージは、OAS3ドキュメントを構築する機能を提供するだけでなく、API開発者がアプリケーションで必要とする多くのギャップを埋めることができます。例えば、CORSをセットアップし、必要に応じて`HEAD`と`OPTIONS`レスポンスを自動で有効にすることができます。また、標準ライブラリのデータクラスやPydanticモデルを用いて、入力されたデータを検証することができます。 +その代わりとして、 `sanic-ext` という新しいプロジェクトが導入されます。 このパッケージは、OAS3ドキュメントを構築する機能を提供するだけでなく、API開発者がアプリケーションで必要とする多くのギャップを埋めることができます。 例えば、CORSをセットアップし、必要に応じて`HEAD`と`OPTIONS`レスポンスを自動で有効にすることができます。 また、標準ライブラリのデータクラスやPydanticモデルを用いて、入力されたデータを検証することができます。 機能一覧は以下の通りです。 - CORS保護 @@ -205,30 +203,19 @@ v21.9 のリリースから、SCOは`sanic-openapi`パッケージを非推奨 - 依存性注入 - レスポンスシリアライゼーション -このプロジェクトは今のところまだ `alpha`モードであり、変更される可能性があります。実運用に耐えうるものと考えていますが、機能追加を続ける中でAPIを変更する必要があるかもしれません。 +このプロジェクトは今のところまだ `alpha`モードであり、変更される可能性があります。 実運用に耐えうるものと考えていますが、機能追加を続ける中でAPIを変更する必要があるかもしれません。 詳しくは [ドキュメント](../../plugins/sanic-ext/getting-started.md) をご覧ください。 ## 感謝 -今回のリリースに参加された皆様、ありがとうございました。:clap: - -[@aaugustin](https://github.com/aaugustin) -[@ahopkins](https://github.com/ahopkins) -[@ashleysommer](https://github.com/ashleysommer) -[@cansarigol3megawatt](https://github.com/cansarigol3megawatt) -[@ChihweiLHBird](https://github.com/ChihweiLHBird) -[@gluhar2006](https://github.com/gluhar2006) -[@komar007](https://github.com/komar007) -[@ombe1229](https://github.com/ombe1229) -[@prryplatypus](https://github.com/prryplatypus) -[@SaidBySolo](https://github.com/SaidBySolo) -[@Tronic](https://github.com/tronic) -[@vltr](https://github.com/vltr) +今回のリリースに参加された皆様、ありがとうございました。 :clap: + +[@aaugustin](https://github.com/aaugustin) [@ahopkins](https://github.com/ahopkins) [@ashleysommer](https://github.com/ashleysommer) [@cansarigol3megawatt](https://github.com/cansarigol3megawatt) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@gluhar2006](https://github.com/gluhar2006) [@komar007](https://github.com/komar007) [@ombe1229](https://github.com/ombe1229) [@prryplatypus](https://github.com/prryplatypus) [@SaidBySolo](https://github.com/SaidBySolo) [@Tronic](https://github.com/tronic) [@vltr](https://github.com/vltr) そして、ドキュメントの同期と中国語への翻訳を維持してくれた[@miss85246](https://github.com/miss85246) と [@ZinkLu](https://github.com/ZinkLu) には、多大なる感謝を捧げます。 --- -もし、このプロジェクトを楽しんでいただけるなら、ぜひ貢献をご検討ください。もちろん、コードの貢献も大好きですが、どんな形の貢献も大歓迎です。ドキュメントを書いたり、ユースケースを見せたり、会話に参加してあなたの声を伝えたり、もし可能なら[金銭的貢献](https://opencollective.com/sanic-org/)も検討してください。 +もし、このプロジェクトを楽しんでいただけるなら、ぜひ貢献をご検討ください。 もちろん、コードの貢献も大好きですが、どんな形の貢献も大歓迎です。 ドキュメントを書いたり、ユースケースを見せたり、会話に参加してあなたの声を伝えたり、もし可能なら[金銭的貢献](https://opencollective.com/sanic-org/)も検討してください。 diff --git a/src/ja/guide/release-notes/v22.12.md b/src/ja/guide/release-notes/v22.12.md new file mode 100644 index 0000000000..50b3dd96ad --- /dev/null +++ b/src/ja/guide/release-notes/v22.12.md @@ -0,0 +1,176 @@ +# Version 22.12 + +[[toc]] + +## Introduction + +This is the final release of the version 22 [release cycle](../../org/policies.md#release-schedule). As such it is a **long-term support** release, and will be supported as stated in the [policies](../../org/policies.md#long-term-support-v-interim-releases). + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + +### 🚨 *BREAKING CHANGE* - Sanic Inspector is now an HTTP server + +Sanic v22.9 introduced the [Inspector](./v22.9.md#inspector) to allow live inspection of a running Sanic instance. This feature relied upon opening a TCP socket and communicating over a custom protocol. That basic TCP protocol has been dropped in favor of running a full HTTP service in its place. [Learn more about the Inspector](../deployment/inspector.md). + +The current release introduces a new HTTP server and a refreshed CLI experience. This enables several new features highlighted here. Perhaps the most significant change, however, is to move all of the Inspector's commands to a subparser on the CLI instance. + +``` +$ sanic inspect --help + + ▄███ █████ ██ ▄█▄ ██ █ █ ▄██████████ + ██ █ █ █ ██ █ █ ██ + ▀███████ ███▄ ▀ █ █ ██ ▄ █ ██ + ██ █████████ █ ██ █ █ ▄▄ + ████ ████████▀ █ █ █ ██ █ ▀██ ███████ + +Optional +======== + General: + -h, --help show this help message and exit + --host HOST, -H HOST Inspector host address [default 127.0.0.1] + --port PORT, -p PORT Inspector port [default 6457] + --secure, -s Whether to access the Inspector via TLS encryption + --api-key API_KEY, -k API_KEY Inspector authentication key + --raw Whether to output the raw response information + + Subcommands: + Run one or none of the below subcommands. Using inspect without a subcommand will fetch general information about the state of the application instance. + + Or, you can optionally follow inspect with a subcommand. If you have created a custom Inspector instance, then you can run custom commands. See https://sanic.dev/en/guide/deployment/inspector.html for more details. + + {reload,shutdown,scale,} + reload Trigger a reload of the server workers + shutdown Shutdown the application and all processes + scale Scale the number of workers + Run a custom command +``` + +#### CLI remote access now available + +The `host` and `port` of the Inspector are now explicitly exposed on the CLI as shown above. Previously in v22.9, they were inferred by reference to the application instance. Because of this change, it will be more possible to expose the Inspector on live production instances and access from a remote installation of the CLI. + +For example, you can check your running production deployment from your local development machine. + +``` +$ sanic inspect --host=1.2.3.4 +``` + +::: warning +For **production** instances, make sure you are _using TLS and authentication_ described below. +::: + +#### TLS encryption now available + +You can secure your remote Inspector access by providing a TLS certificate to encrypt the web traffic. + +```python +app.config.INSPECTOR_TLS_CERT = "/path/to/cert.pem" +app.config.INSPECTOR_TLS_KEY = "/path/to/key.pem" +``` + +To access an encrypted installation via the CLI, use the `--secure` flag. + +``` +$ sanic inspect --secure +``` + +#### Authentication now available + +To control access to the remote Inspector, you can protect the endpoints using an API key. + +```python +app.config.INSPECTOR_API_KEY = "Super-Secret-200" +``` + +To access a protected installation via the CLI, use the `--api-key` flag. + +``` +$ sanic inspect --api-key=Super-Secret-200 +``` + +This is equivalent to the header: `Authorization: Bearer `. + +``` +$ curl http://localhost:6457 -H "Authorization: Bearer Super-Secret-200" +``` + +### Scale number of running server workers + +The Inspector is now capable of scaling the number of worker processes. For example, to scale to 3 replicas, use the following command: + +``` +$ sanic inspect scale 3 +``` + +### Extend Inspector with custom commands + +The Inspector is now fully extendable to allow for adding custom commands to the CLI. For more information see [Custom Commands](../deployment/inspector.md#custom-commands). + +``` +$ sanic inspect foo --bar +``` + +### Early worker exit on failure + +The process manager shipped with v22.9 had a very short startup timeout. This was to protect against deadlock. This was increased to 30s, and a new mechanism has been added to fail early if there is a crash in a worker process on startup. + +### Introduce `JSONResponse` with convenience methods to update a JSON response body + +The `sanic.response.json` convenience method now returns a new subclass of `HTTPResponse` appropriately named: `JSONResponse`. This new type has some convenient methods for handling changes to a response body after its creation. + +```python +resp = json({"foo": "bar"}) +resp.update({"another": "value"}) +``` + +See [Returning JSON Data](../basics/response.md#returning-json-data) for more information. + +### Updates to downstream requirements: `uvloop` and `websockets` + +Minimum `uvloop` was set to `0.15.0`. Changes were added to make Sanic compliant with `websockets` version `11.0`. + +### Force exit on 2nd `ctrl+c` + +On supporting operating systems, the existing behavior is for Sanic server to try to perform a graceful shutdown when hitting `ctrl+c`. This new release will perform an immediate shutdown on subsequent `ctrl+c` after the initial shutdown has begun. + +### Deprecations and Removals + +1. *DEPRECATED* - The `--inspect*` commands introduced in v22.9 have been replaced with a new subcommand parser available as `inspect`. The flag versions will continue to operate until v23.3. You are encouraged to use the replacements. While this short deprecation period is a deviation from the standard two-cycles, we hope this change will be minimally disruptive. + ``` + OLD sanic ... --inspect + NEW sanic ... inspect + + OLD sanic ... --inspect-raw + NEW sanic ... inspect --raw + + OLD sanic ... --inspect-reload + NEW sanic ... inspect reload + + OLD sanic ... --inspect-shutdown + NEW sanic ... inspect shutdown + ``` + +## News + +The Sanic Community Organization will be headed by a new Steering Council for 2023. There are two returning and two new members. + +[@ahopkins](https://github.com/ahopkins) *returning* \ +[@prryplatypus](https://github.com/prryplatypus) *returning* \ +[@sjsadowski](https://github.com/sjsadowski) *NEW* \ +[@Tronic](https://github.com/Tronic) *NEW* + +The 2023 release managers are [@ahopkins](https://github.com/ahopkins) and [@sjsadowski](https://github.com/sjsadowski). + +If you are interested in getting more involved with Sanic, contact us on the [Discord server](https://discord.gg/FARQzAEMAA). + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@aaugustin](https://github.com/aaugustin) [@ahopkins](https://github.com/ahopkins) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@kijk2869](https://github.com/kijk2869) [@LiraNuna](https://github.com/LiraNuna) [@prryplatypus](https://github.com/prryplatypus) [@sjsadowski](https://github.com/sjsadowski) [@todddialpad](https://github.com/todddialpad) [@Tronic](https://github.com/Tronic) + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/ja/guide/release-notes/v22.3.md b/src/ja/guide/release-notes/v22.3.md new file mode 100644 index 0000000000..b5ff915c1b --- /dev/null +++ b/src/ja/guide/release-notes/v22.3.md @@ -0,0 +1,321 @@ +# バージョン22.3 + +[[toc]] + +## 概要 + +このリリースは、バージョン22の[リリースサイクル](../../org/policies.md#release-schedule)の最初のリリースとなります。 すべての標準SCOライブラリが同じリリースサイクルに入り、同じバージョンのパターンに従います。 パッケージは以下のとおりです: + +- [`sanic-routing`](https://github.com/sanic-org/sanic-routing) +- [`sanic-testing`](https://github.com/sanic-org/sanic-testing) +- [`sanic-ext`](https://github.com/sanic-org/sanic-ext) + +## 知っておきたいこと + +詳しくは[変更履歴](https://sanic.readthedocs.io/en/stable/sanic/changelog.html)をご覧ください。 注目すべき新機能・不具合、アップグレード対象など... + +### アプリケーションのマルチサービス + +Sanicサーバーには、同じプロセスで複数のアプリケーションを並行して実行できるAPIが追加されました。 これは、1つまたは複数のアプリケーションインスタンスに `app.prepare(...)` を呼び出すことによって行われます。 毎回ユニークなホスト/ポートの組み合わせにバインドする必要があります。 そして、 `Sanic.serve()` を呼び出してアプリケーションに応答します。 + +```python +app = Sanic("One") +app2 = Sanic("Two") + +app.prepare(port=9999) +app.prepare(port=9998) +app.prepare(port=9997) +app2.prepare(port=8888) +app2.prepare(port=8887) + +Sanic.serve() +``` + +上記のスニペットには、同時に実行され、複数のポートにバインドされる2つのアプリケーションがあります。 この機能は CLIではサポートされて*いません*。 + +このパターンは、 `app.run(...)` を実行する代わりに使用されます。 `app.run` は上記のパターンの省略形であり、まだ完全にサポートされていることに注意してください。 + +### 👶 *BETA FEATURE* - 新しいパスパラメータタイプ: ファイル拡張子 + +非常に一般的なパターンは、動的にファイルを生成するルートを作成することです。 エンドポイントは、拡張子のあるファイル上で一致するように意図されています。 ファイルに一致する新しいパスパラメータがあります: ``. + +```python +@app.get("/path/to/") +async def handler(request, filename, ext): + ... +``` + +これはファイル拡張子で終わるパターンをキャッチします。 ただし、拡張子を指定し、ファイル名に他のパスパラメータ型を使用することで、これを展開することができます。 + +例えば、 `.jpg` のファイルを数字のみでキャッチしたい場合: + +```python +@app.get("/path/to/") +async def handler(request, filename, ext): + ... +``` + +可能性のある例: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 定義 + + 例 + + ファイル名 + + 拡張子 +
+ \ + + page.txt + + "page" + + "txt" +
+ \ + + cat.jpg + + "cat" + + "jpg" +
+ \ + + + png\ + + gif\ + + svg> | cat.jpg | "cat" | "jpg" +
+ + + 123.txt + + 123 + + "txt" +
+ + + + png\ + + gif\ + + svg> | 123.svg | 123 | "svg" +
+ + + 3.14.tar.gz + + 3.14 + + "tar.gz" +
+ +### 🚨 *破壊的変更* - パスパラメータの空でない文字列の一致 + +動的パスパラメータは空でない文字列にのみマッチします。 + +以前は、動的な文字列パラメータ (`/` または `/`) を持つルートは、任意の文字列にマッチします。 空の文字列も含めて。 現在では、空でない文字列にのみマッチするようになりました。 古い動作を維持するには、新しいパラメータ type: `/` を使用する必要があります。 + +```python +@app.get("/path/to/") +async def handler(request, foo) + ... +``` + +### 🚨 *破壊的変更* - `sanic.worker.GunicornWorker` が削除されました + +非推奨のポリシーからの離脱のため、Sanic サーバをマルチサーブにアップグレードするプロセスの一部として `GunicornWorker` が削除されました。 この決定は、それが存在している間にも、Sanicを展開するための最適な戦略ではなかったため、少しずつ行われました。 + +`gunicorn` を使って Sanic をデプロイする場合は、[`uvicorn`で実装されている戦略](https://www.uvicorn.org/#running-with-gunicorn)を使って展開することが推奨されます。 これは ASGI アプリケーションとして `uvicorn` を通じて効果的に実行されます。 `uvicorn` をインストールすることで、このパターンにアップグレードできます: + +``` +pip install uvicorn +``` + +次に、次のようなパターンで実行できるはずです。 + +``` +gunicorn path.to.sanic:app -k uvicorn.workers.UvicornWorker +``` + +### 承認ヘッダーの解析 + +`Authorization` ヘッダーは、しばらくの間、部分的に解析可能できました。 以下の2つの形式のいずれかにあるヘッダーにアクセスするために、 `request.token` を使用することができました。 + +``` +Authorization: Token +Authorization: Bearer +``` + +Sanic が `BASIC` のようなより多くの資格情報型を解析できるようになりました: + +``` +Authorization: Basic Z2lsLWJhdGVzOnBhc3N3b3JkMTIz +``` + +これは`request.credentials` としてアクセスできるようになりました: + +```python +print(request.credentials) +# Credentials(auth_type='Basic', token='Z2lsLWJhdGVzOnBhc3N3b3JkMTIz', _username='gil-bates', _password='password123') +``` + +### CLI引数のオプションでアプリケーションファクトリへ注入 + +Sanicは、解析されたCLI引数を使用している場合、ファクトリに注入しようとします。 + +```python +def create_app(args): + app = Sanic("MyApp") + print(args) + return app +``` +``` +$sanic p:create_app --factory +Namespace(module='p:create_app', factory=True, simple=False, host='127.0.0.1', port=8000, unix='', cert=None, key=None, tls=None, tlshost=False, workers=1, fast=False, access_log=False, debug=False, auto_reload=False, path=None, dev=False, motd=True, verbosity=None, noisy_exceptions=False) +``` + +CLIを`--factory`で実行している場合、コマンドに任意の引数を渡すオプションもあり、その引数は`Namespace`に注入されることになります。 + +``` +sanic p:create_app --factory --foo=bar +Namespace(module='p:create_app', factory=True, simple=False, host='127.0.0.1', port=8000, unix='', cert=None, key=None, tls=None, tlshost=False, workers=1, fast=False, access_log=False, debug=False, auto_reload=False, path=None, dev=False, motd=True, verbosity=None, noisy_exceptions=False, foo='bar') +``` + +### 新しい再読み込みプロセスリスナーイベント + +Sanic サーバを自動再ロードして実行する場合、再読み込みプロセスで*のみ*リスナーをトリガーする 2 つの新しいイベントがあります。 + +- `reload_process_start` +- `reload_process_stop` + +これらは、リローダーが実行されている場合にのみトリガーされます。 + +```python +@app.reload_process_start +async def reload_start(*_): + print(">>>>>> リロード開始 <<<<<<") + + +@app.reload_process_stop +async def reload_stop(*_): + print(">>>>>> リロード停止 <<<<<<") +``` + +### イベントループのリスナーにおける引数任意化 + +リスナーの`loop`引数は省略できます。 これらの例はどちらも期待通りに動作します: + +```python +@app.before_server_start +async def without(app): + ... + +@app.before_server_start +async def with(app, loop): + ... +``` + +### 削除 - デバッグモードは自動的にリローダーを開始しないように + +`--debug` または `debug=True`で実行すると、Sanic サーバーは自動的に自動リローダーを起動しません。 このデバッグで両方を行う機能は v21 で非推奨となり、このリリースで削除されました。 デバッグモードと auto-reload の*両方*を使用したい場合は、 `--dev` または `dev=True` を使用できます。 + +**dev = debugモード + 自動再読み込み** + +### 非推奨 - 小文字環境変数の読み込み + +Sanicは接頭辞付きの環境変数を設定値として読み込みます。 プレフィックスが一致している限り、大文字と小文字は区別されません。 ただし、キーは大文字にすべきという慣習は常にありました。 これは非推奨であり、値が大文字でない場合は警告が表示されます。 v22.9では、大文字と接頭辞付きキーのみがロードされます。 + +## ニュース + +### Packt が Sanic Web 開発に関する新しい本を公開 + +---:1 **@ahopkins**による[Sanic](https://github.com/ahopkins)Pythonウェブ開発に関する新しい本があります。 本書はSCOによって承認されており、すべての売り上げの一部は、Sanicのさらなる発展のためにSCOに直接行きます。 + +[sanicbook.com](https://sanicbook.com/)で詳細を学ぶことができます :--:1 ![Python Web Development with Sanic](https://sanicbook.com/images/SanicCoverFinal.png) :--- + +## ありがとうございます + +今回のリリースに参加された皆様、ありがとうございました。 :clap: + +[@aericson](https://github.com/aericson) [@ahankinson](https://github.com/ahankinson) [@ahopkins](https://github.com/ahopkins) [@ariebovenberg](https://github.com/ariebovenberg) [@ashleysommer](https://github.com/ashleysommer) [@Bluenix2](https://github.com/Bluenix2) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@dotlambda](https://github.com/dotlambda) [@eric-spitler](https://github.com/eric-spitler) [@howzitcdf](https://github.com/howzitcdf) [@jonra1993](https://github.com/jonra1993) [@prryplatypus](https://github.com/prryplatypus) [@raphaelauv](https://github.com/raphaelauv) [@SaidBySolo](https://github.com/SaidBySolo) [@SerGeRybakov](https://github.com/SerGeRybakov) [@Tronic](https://github.com/Tronic) + + +--- + +もし、このプロジェクトを楽しんでいただけるなら、ぜひ貢献をご検討ください。 もちろん、コードの貢献も大好きですが、どんな形の貢献も大歓迎です。 ドキュメントを書いたり、使用例を紹介したり、会話に参加してあなたの声を伝えたり、もし可能であれば、[金銭的な貢献](https://opencollective.com/sanic-org/)も検討してみてください。 diff --git a/src/ja/guide/release-notes/v22.6.md b/src/ja/guide/release-notes/v22.6.md new file mode 100644 index 0000000000..8cf6cb6c9c --- /dev/null +++ b/src/ja/guide/release-notes/v22.6.md @@ -0,0 +1,152 @@ +# バージョン22.6 + +[[toc]] + +## 概要 + +これは、バージョン22の[リリースサイクル](../../org/policies.md#release-schedule)の2回目のリリースです。 バージョン22は、12月の長期サポート版リリースで「確定」する予定です。 + +## 知っておきたいこと + +詳しくは[変更履歴](https://sanic.readthedocs.io/en/stable/sanic/changelog.html)をご覧ください。 注目すべき新機能・不具合、アップグレード対象など... + + +### `DEBUG`モードでの自動TLS設定 + +Sanicサーバーは、[mkcert](https://github.com/FiloSottile/mkcert)または[trustme](https://github.com/python-trio/trustme)を使用して TLS 証明書を自動的にセットアップできます。 この証明書はローカル開発環境で`https://localhost`(または別のローカルアドレス) を有効にします。 `mkcert`または`trustme`を自分でインストールする必要があります。 + +---:1 +``` +$ sanic path.to.server:app --auto-tls --debug +``` +:--:1 + +```python +app.run(debug=True, auto_tls=True) +``` +:--- + +この機能は、 `ASGI` モードまたは `PRODUCTION` モードで実行している場合は使用できません。 Sanic を本番環境で実行する場合、正規のベンダーを通じて購入された実際の TLS 証明書を使用する必要があります。 または[Let's Encrypt](https://letsencrypt.org/)を使用してください。 + + +### HTTP/3サーバー :rocket: + +2022 年6 月に IETF は、HTTP/3 の仕様である [RFC 9114](https://www.rfc-editor.org/rfc/rfc9114.html)を最終決定し発行しました。 In short, HTTP/3 is a **very** different protocol than HTTP/1.1 and HTTP/2 because it implements HTTP over UDP instead of TCP. The new HTTP protocol promises faster webpage load times and solving some of the problems of the older standards. You are encouraged to [read more about](https://http3-explained.haxx.se/) this new web technology. You likely will need to install a [capable client](https://curl.se/docs/http3.html) as traditional tooling will not work. + +Sanic server offers HTTP/3 support using [aioquic](https://github.com/aiortc/aioquic). This **must** be installed: + +``` +pip install sanic aioquic +``` + +``` +pip install sanic[http3] +``` + +To start HTTP/3, you must explicitly request it when running your application. + +---:1 +``` +$ sanic path.to.server:app --http=3 +``` + +``` +$ sanic path.to.server:app -3 +``` +:--:1 + +```python +app.run(version=3) +``` +:--- + +To run both an HTTP/3 and HTTP/1.1 server simultaneously, you can use [application multi-serve](./v22.3.html#application-multi-serve) introduced in v22.3. + + +---:1 +``` +$ sanic path.to.server:app --http=3 --http=1 +``` + +``` +$ sanic path.to.server:app -3 -1 +``` +:--:1 + +```python +app.prepre(version=3) +app.prepre(version=1) +Sanic.serve() +``` +:--- + +Because HTTP/3 requires TLS, you cannot start a HTTP/3 server without a TLS certificate. You should [set it up yourself](../how-to/tls.html) or use `mkcert` if in `DEBUG` mode. Currently, automatic TLS setup for HTTP/3 is not compatible with `trustme`. + +**:baby: This feature is being released as an *EARLY RELEASE FEATURE*.** It is **not** yet fully compliant with the HTTP/3 specification, lacking some features like [websockets](https://websockets.spec.whatwg.org/), [webtransport](https://w3c.github.io/webtransport/), and [push responses](https://http3-explained.haxx.se/en/h3/h3-push). Instead the intent of this release is to bring the existing HTTP request/response cycle towards feature parity with HTTP/3. Over the next several releases, more HTTP/3 features will be added and the API for it finalized. + +### Consistent exception naming + +Some of the Sanic exceptions have been renamed to be more compliant with standard HTTP response names. + +- `InvalidUsage` >> `BadRequest` +- `MethodNotSupported` >> `MethodNotAllowed` +- `ContentRangeError` >> `RangeNotSatisfiable` + +All old names have been aliased and will remain backwards compatible. + +### Current request getter + +Similar to the API to access an application (`Sanic.get_app()`), there is a new method for retrieving the current request when outside of a request handler. + +```python +from sanic import Request + +Request.get_current() +``` + +### Improved API support for setting cache control headers + +The `file` response helper has some added parameters to make it easier to handle setting of the [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) header. + +```python +file( + ..., + last_modified=..., + max_age=..., + no_store=..., +) +``` + + +### Custom `loads` function + +Just like Sanic supports globally setting a custom `dumps`, you can now set a global custom `loads`. + +```python +from orjson import loads + +app = Sanic("Test", loads=loads) +``` + + +### Deprecations and Removals + +1. *REMOVED* - Applications may no longer opt-out of the application registry +1. *REMOVED* - Custom exception handlers will no longer run after some part of an exception has been sent +1. *REMOVED* - Fallback error formats cannot be set on the `ErrorHandler` and must **only** be set in the `Config` +1. *REMOVED* - Setting a custom `LOGO` for startup is no longer allowed +1. *REMOVED* - The old `stream` response convenience method has been removed +1. *REMOVED* - `AsyncServer.init` is removed and no longer an alias of `AsyncServer.app.state.is_started` + + + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@ahopkins](https://github.com/ahopkins) [@amitay87](https://github.com/amitay87) [@ashleysommer](https://github.com/ashleysommer) [@azimovMichael](https://github.com/azimovMichael) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@kijk2869](https://github.com/kijk2869) [@prryplatypus](https://github.com/prryplatypus) [@SaidBySolo](https://github.com/SaidBySolo) [@sjsadowski](https://github.com/sjsadowski) [@timmo001](https://github.com/timmo001) [@zozzz](https://github.com/zozzz) + + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/ja/guide/release-notes/v22.9.md b/src/ja/guide/release-notes/v22.9.md new file mode 100644 index 0000000000..33b265ea83 --- /dev/null +++ b/src/ja/guide/release-notes/v22.9.md @@ -0,0 +1,293 @@ +# Version 22.9 + +[[toc]] + +## Introduction + +This is the third release of the version 22 [release cycle](../../org/policies.md#release-schedule). Version 22 will be "finalized" in the December long-term support version release. + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + + +### :warning: *IMPORTANT* - New worker manager :rocket: + +Sanic server has been overhauled to provide more consistency and flexbility in how it operates. More details about the motivations are outlined in [PR #2499](https://github.com/sanic-org/sanic/pull/2499) and discussed in a live stream [discussion held on YouTube](https://youtu.be/m8HCO8NK7HE). + +This **does NOT apply** to Sanic in ASGI mode + +#### Overview of the changes + +- The worker servers will **always** run in a child process. + - Previously this could change depending upon one versus many workers, and the usage or not of the reloader. This should lead to much more predictable development environments that more closely match their production counterparts. +- Multi-workers is **now supported on Windows**. + - This was impossible because Sanic relied upon the `multiprocessing` module using `fork`, which is not available on Windows. + - Now, Sanic will always use `spawn`. This does have some noticeable differences, particularly if you are running Sanic in the global scope with `app.run` (see below re: upgrade issues). +- The application instance now has a new `multiplexer` object that can be used to restart one or many workers. This could, for example, be triggered by a request. +- There is a new Inspector that can provide details on the state of your server. +- Sanic worker manager can run arbitrary processes. + - This allows developers to add any process they want from within Sanic. + - Possible use cases: + - Health monitor, see [Sanic Extensions]() + - Logging queue, see [Sanic Extensions]() + - Background worker queue in a seperate process + - Running another application, like a bot +- There is a new listener called: `main_process_ready`. It really should only be used for adding arbitrary processes to Sanic. +- Passing shared objects between workers. + - Python does allow some types of objects to share state between processes, whether through shared memory, pipes, etc. + - Sanic will now allow these types of object to be shared on the `app.shared_ctx` object. + - Since this feature relies upon Pythons `multiprocessing` library, it obviously will only work to share state between Sanic worker instances that are instantiated from the same execution. This is *not* meant to provide an API for horizontal scaling across multiple machines for example. + +#### Adding a shared context object + +To share an object between worker processes, it *MUST* be assigned inside of the `main_process_start` listener. + +```python +from multiprocessing import Queue + +@app.main_process_start +async def main_process_start(app): + app.shared_ctx.queue = Queue() +``` + +All objects on `shared_ctx` will be available now within each worker process. + +```python +@app.before_server_starts +async def before_server_starts(app): + assert isinstance(app.shared_ctx.queue, Queue) + +@app.on_request +async def on_request(request): + assert isinstance(request.app.shared_ctx.queue, Queue) + +@app.get("/") +async def handler(request): + assert isinstance(request.app.shared_ctx.queue, Queue) +``` + +*NOTE: Sanic will not stop you from registering an unsafe object, but may warn you. Be careful not to just add a regular list object, for example, and expect it to work. You should have an understanding of how to share state between processes.* + +#### Running arbitrary processes + +Sanic can run any arbitrary process for you. It should be capable of being stopped by a `SIGINT` or `SIGTERM` OS signal. + +These processes should be registered inside of the `main_process_ready` listener. + +```python +@app.main_process_ready +async def ready(app: Sanic, _): + app.manager.manage("MyProcess", my_process, {"foo": "bar"}) +# app.manager.manage(, , ) +``` + +#### Inspector + +Sanic ships with an optional Inspector, which is a special process that allows for the CLI to inspect the running state of an application and issue commands. It currently will only work if the CLI is being run on the same machine as the Sanic instance. + +``` +sanic path.to:app --inspect +``` + +![Sanic inspector](https://user-images.githubusercontent.com/166269/190099384-2f2f3fae-22d5-4529-b279-8446f6b5f9bd.png) + +The new CLI commands are: + +``` + --inspect Inspect the state of a running instance, human readable + --inspect-raw Inspect the state of a running instance, JSON output + --trigger-reload Trigger worker processes to reload + --trigger-shutdown Trigger all processes to shutdown +``` + +This is not enabled by default. In order to have it available, you must opt in: + +```python +app.config.INSPECTOR = True +``` + +*Note: [Sanic Extensions]() provides a [custom request](../basics/app.md#custom-requests) class that will add a request counter to the server state. + +#### Application multiplexer + +Many of the same information and functionality is available on the application instance itself. There is a new `multiplexer` object on the application instance that has the ability to restart one or more workers, and fetch information about the current state. + +You can access it as `app.multiplexer`, or more likely by its short alias `app.m`. + +```python +@app.on_request +async def print_state(request: Request): + print(request.app.m.state) +``` + +#### Potential upgrade issues + +Because of the switch from `fork` to `spawn`, if you try running the server in the global scope you will receive an error. If you see something like this: + +``` +sanic.exceptions.ServerError: Sanic server could not start: [Errno 98] Address already in use. +This may have happened if you are running Sanic in the global scope and not inside of a `if __name__ == "__main__"` block. +``` + +... then the change is simple. Make sure `app.run` is inside a block. + +```python +if __name__ == "__main__": + app.run(port=9999, dev=True) +``` + +#### Opting out of the new functionality + +If you would like to run Sanic without the new process manager, you may easily use the legacy runners. Please note that support for them **will be removed** in the future. A date has not yet been set, but will likely be sometime in 2023. + +To opt out of the new server and use the legacy, choose the appropriate method depending upon how you run Sanic: + +---:1 If you use the CLI... :--:1 +``` +sanic path.to:app --legacy +``` +:--- + +---:1 If you use `app.run`... :--:1 +``` +app.run(..., legacy=True) +``` +:--- + +---:1 If you `app.prepare`... :--:1 +``` +app.prepare(...) +Sanic.serve_legacy() +``` +:--- + +Similarly, you can force Sanic to run in a single process. This however means there will not be any access to the auto-reloader. + +---:1 If you use the CLI... :--:1 +``` +sanic path.to:app --single-process +``` +:--- + +---:1 If you use `app.run`... :--:1 +``` +app.run(..., single_process=True) +``` +:--- + +---:1 If you `app.prepare`... :--:1 +``` +app.prepare(...) +Sanic.serve_single() +``` +:--- +### Middleware priority + +Middleware is executed in an order based upon when it was defined. Request middleware are executed in sequence and response middleware in reverse. This could have an unfortunate impact if your ordering is strictly based upon import ordering with global variables for example. + +A new addition is to break-out of the strict construct and allow a priority to be assigned to a middleware. The higher the number for a middleware definition, the earlier in the sequence it will be executed. This applies to **both** request and response middleware. + +```python +@app.on_request +async def low_priority(_): + ... + +@app.on_request(priority=10) +async def high_priority(_): + ... +``` + +In the above example, even though `low_priority` is defined first, `high_priority` will run first. + +### Custom `loads` function + + +Sanic has supported the ability to add a [custom `dumps` function](https://sanic.readthedocs.io/en/stable/sanic/api/app.html#sanic.app.Sanic) when instantiating an app. The same functionality has been extended to `loads`, which will be used when deserializing. + +```python +from json import loads + +Sanic("Test", loads=loads) +``` + +### Websocket objects are now iterable + + +Rather than calling `recv` in a loop on a `Websocket` object, you can iterate on it in a `for` loop. + + +```python +from sanic import Request, Websocket + +@app.websocket("/ws") +async def ws_echo_handler(request: Request, ws: Websocket): + async for msg in ws: + await ws.send(msg) +``` + +### Appropriately respond with 304 on static files + +When serving a static file, the Sanic server can respond appropriately to a request with `If-Modified-Since` using a `304` response instead of resending a file. + +### Two new signals to wrap handler execution + +Two new [signals](../advanced/signals.md) have been added that wrap the execution of a request handler. + +- `http.handler.before` - runs after request middleware but before the route handler +- `http.handler.after` - runs after the route handler + - In *most* circumstances, this also means that it will run before response middleware. However, if you call `request.respond` from inside of a route handler, then your middleware will come first + +### New Request properties for HTTP method information + +The HTTP specification defines which HTTP methods are: safe, idempotent, and cacheable. New properties have been added that will respond with a boolean flag to help identify the request property based upon the method. + +```python +request.is_safe +request.is_idempotent +request.is_cacheable +``` + +### 🚨 *BREAKING CHANGE* - Improved cancel request exception + +In prior version of Sanic, if a `CancelledError` was caught it could bubble up and cause the server to respond with a `503`. This is not always the desired outcome, and it prevented the usage of that error in other circumstances. As a result, Sanic will now use a subclass of `CancelledError` called: `RequestCancelled` for this functionality. It likely should have little impact unless you were explicitly relying upon the old behavior. + + +For more details on the specifics of these properties, checkout the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods). + +### New deprecation warning filter + +You can control the level of deprecation warnings from Sanic using [standard library warning filter values](https://docs.python.org/3/library/warnings.html#the-warnings-filter). Default is `"once"`. + +```python +app.config.DEPRECATION_FILTER = "ignore" +``` + +### Deprecations and Removals + +1. *DEPRECATED* - Duplicate route names have been deprecated and will be removed in v23.3 +1. *DEPRECATED* - Registering duplicate exception handlers has been deprecated and will be removed in v23.3 +1. *REMOVED* - `route.ctx` not set by Sanic, and is a blank object for users, therefore ... + - `route.ctx.ignore_body` >> `route.extra.ignore_body` + - `route.ctx.stream` >> `route.extra.stream` + - `route.ctx.hosts` >> `route.extra.hosts` + - `route.ctx.static` >> `route.extra.static` + - `route.ctx.error_format` >> `route.extra.error_format` + - `route.ctx.websocket` >> `route.extra.websocket` +1. *REMOVED* - `app.debug` is READ-ONLY +1. *REMOVED* - `app.is_running` removed +1. *REMOVED* - `app.is_stopping` removed +1. *REMOVED* - `Sanic._uvloop_setting` removed +1. *REMOVED* - Prefixed environment variables will be ignored if not uppercase + + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@ahopkins](https://github.com/ahopkins) [@azimovMichael](https://github.com/azimovMichael) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@huntzhan](https://github.com/huntzhan) [@monosans](https://github.com/monosans) [@prryplatypus](https://github.com/prryplatypus) [@SaidBySolo](https://github.com/SaidBySolo) [@seemethere](https://github.com/seemethere) [@sjsadowski](https://github.com/sjsadowski) [@timgates42](https://github.com/timgates42) [@Tronic](https://github.com/Tronic) + + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/ja/help.md b/src/ja/help.md index 7ac86e30a7..cc94df9ad5 100644 --- a/src/ja/help.md +++ b/src/ja/help.md @@ -4,7 +4,7 @@ layout: BlankLayout # ヘルプが必要? -開発者の活発なコミュニティとして、私たちはお互いサポートするよう心がけています。もし助けが必要な場合は、以下のいずれかを試してみてください。 +開発者の活発なコミュニティとして、私たちはお互いサポートするよう心がけています。 もし助けが必要な場合は、以下のいずれかを試してみてください。 ---:1 diff --git a/src/ja/org/feature_requests.md b/src/ja/org/feature_requests.md new file mode 100644 index 0000000000..39fe8507e4 --- /dev/null +++ b/src/ja/org/feature_requests.md @@ -0,0 +1,9 @@ +# 機能提案 + +[新しい機能を提案する](https://github.com/sanic-org/sanic/issues/new?assignees=&labels=feature+request&template=feature_request.md) + +機能提案に投票するには、 [Github Issues](https://github.com/sanic-org/sanic/issues?q=is%3Aissue+is%3Aopen+label%3A%22feature+request%22%2CRFC+sort%3Areactions-%2B1-desc) にてリアクションを追加してください。 + +--- + + diff --git a/src/ja/org/policies.md b/src/ja/org/policies.md index 413752a978..f677b2eb35 100644 --- a/src/ja/org/policies.md +++ b/src/ja/org/policies.md @@ -8,13 +8,14 @@ Sanicは[カレンダーバージョン進行](https://calver.org/)、通称"cal YY.MM.MICRO ``` -基本的に、バージョンは``YY.MM``形式で参照されます。`MICRO`番号は`0`から順番に増えるパッチバージョンを表します。 +基本的に、バージョンは`YY.MM`形式で参照されます。 `MICRO`番号は`0`から順番に増えるパッチバージョンを表します。 ## リリーススケジュール -1年につき4回(3,6,9,12月)のリリーススケジュールがあります。したがって、年間でリリースされるバージョンは、`YY.3`, `YY.6`, `YY.9`, `YY.12`の4つです。 +1年につき4回(3,6,9,12月)のリリーススケジュールがあります。 したがって、年間でリリースされるバージョンは、`YY.3`, `YY.6`, `YY.9`, `YY.12`の4つです。 このリリーススケジュールでは以下のものを提供します。 + - 予測可能なリリース間隔、 - 機能を定期的にリリースできる比較的短い開発ウィンドウ、 - コントロールされた[非推奨通知](#deprecation)、そして、 @@ -24,36 +25,40 @@ YY.MM.MICRO ### 長期サポートと暫定リリース -Sanicは毎年12月に長期サポートリリース(通称LTS)をリリースします。LTSリリースはバグ修正やセキュリティアップデートを**24ヶ月間**サポートします。年間を通じて中間リリースは3か月ごとに行われ、次のリリースまでサポートされます。 - -| Version | LTS | サポート状況 | -| ------- | ------------- | ----------------------- | -| 21.9 | | :white_check_mark: | -| 21.6 | | :x: | -| 21.3 | | :x: | -| 20.12 | 2022年12月まで | :white_check_mark: | -| 20.9 | | :x: | -| 20.6 | | :x: | -| 20.3 | | :x: | -| 19.12 | 2021年12月まで | :ballot_box_with_check: | -| 19.9 | | :x: | -| 19.6 | | :x: | -| 19.3 | | :x: | -| 18.12 | | :x: | -| 0.8.3 | | :x: | -| 0.7.0 | | :x: | -| 0.6.0 | | :x: | -| 0.5.4 | | :x: | -| 0.4.1 | | :x: | -| 0.3.1 | | :x: | -| 0.2.0 | | :x: | -| 0.1.9 | | :x: | - -:ballot_box_with_check: = セキュリティ/バグ修正のみ -:white_check_mark: = フルサポート +Sanicは毎年12月に長期サポートリリース(通称LTS)をリリースします。 LTSリリースはバグ修正やセキュリティアップデートを**24ヶ月間**サポートします。 年間を通じて中間リリースは3か月ごとに行われ、次のリリースまでサポートされます。 + +| バージョン | LTS | サポート状況 | +| ---------- | ------------- | ------------------------- | +| 22.12 | until 2024-12 | :white_check_mark: | +| 22.9 | | :x: | +| 22.6 | | :x: | +| 22.3 | | :x: | +| 2021年12月まで | 2022年12月まで | :ballot_box_with_check: | +| 21.9 | | :x: | +| 21.6 | | :x: | +| 21.3 | | :x: | +| 20.12 | | :x: | +| 20.9 | | :x: | +| 20.6 | | :x: | +| 20.3 | | :x: | +| 19.12 | | :x: | +| 19.9 | | :x: | +| 19.6 | | :x: | +| 19.3 | | :x: | +| 18.12 | | :x: | +| 0.8.3 | | :x: | +| 0.7.0 | | :x: | +| 0.6.0 | | :x: | +| 0.5.4 | | :x: | +| 0.4.1 | | :x: | +| 0.3.1 | | :x: | +| 0.2.0 | | :x: | +| 0.1.9 | | :x: | + +:ballot_box_with_check: = セキュリティ/バグ修正のみ :white_check_mark: = フルサポート ## 非推奨 機能が非推奨になる前、またはAPIに破壊的更新が導入される前に、その機能は発表され、2つのリリースサイクルを通じて非推奨の警告とともに表示されます。 LTSリリースでは非推奨になりません。 -正当な理由がある場合、これらのガイドラインの範囲外で重大な変更または機能の削除が行われる可能性があります。これらの状況は稀なはずです。たとえば、主要なセキュリティ問題を削減するための代替手段が利用できない場合などに発生する可能性があります。 +正当な理由がある場合、これらのガイドラインの範囲外で重大な変更または機能の削除が行われる可能性があります。 これらの状況は稀なはずです。 たとえば、主要なセキュリティ問題を削減するための代替手段が利用できない場合などに発生する可能性があります。 diff --git a/src/ja/org/scope.md b/src/ja/org/scope.md index 9d06af6490..a2cf81b505 100644 --- a/src/ja/org/scope.md +++ b/src/ja/org/scope.md @@ -3,138 +3,138 @@ title: S.C.O.P.E --- -サニックコミュニティ組織ポリシーE-manual -=================================== +サニックコミュニティ組織ポリシー電子マニュアル +============================================ -2019年12月、バージョン1 +2019年12月、version 1 目標 ---- +----- Sanicプロジェクトを中心に、持続可能なコミュニティ主導の組織を作り、それを推進すること: (1) 安定性と予測可能性、(2) 迅速な反復と強化サイクル、(3) 外部貢献者の関与、(4) 全体的に信頼できるソフトウェア、(5) コミュニティメンバーにとって安全でやりがいのある環境、などを推進するコミュニティ主導の組織を作る。 概要 ---- +-------- -本ポリシーは、Sanic Community Organization(以下、「SCO」という)の経営モデルである。SCOは、実力主義、合意主義に基づくコミュニティ組織で、採択されたすべてのプロジェクトに責任を持ちます。いずれかのプロジェクトに興味を持つ人は誰でもコミュニティに参加し、コミュニティやプロジェクトに貢献し、意思決定プロセスに参加することができます。この文書では、その参加方法と、プロジェクト・コミュニティ内で功労を得るための方法について説明します。 +本ポリシーは、Sanic Community Organization(以下、「SCO」という)の経営モデルである。 SCOは、実力主義、合意主義に基づくコミュニティ組織で、採択されたすべてのプロジェクトに責任を持ちます。 いずれかのプロジェクトに興味を持つ人は誰でもコミュニティに参加し、コミュニティやプロジェクトに貢献し、意思決定プロセスに参加することができます。 この文書では、その参加方法と、プロジェクト・コミュニティ内で功労を得るための方法について説明します。 構造 ---- +--------- -SCOには複数の**プロジェクト**があります。各プロジェクトは、Sanic コミュニティ傘下の単一の GitHub リポジトリによって表現されています。これらのプロジェクトは**ユーザ**によって使用され、**貢献者**によって開発され、**コア開発者**によって管理され、**リリースマネージャ**によってリリースされ、最終的に**運営評議会**によって監督される。もしこれがPythonプロジェクトやPEP 8016に似ていると思われるなら、それは意図的にそのように設計されているからです。 +SCOには複数の**プロジェクト**があります。 各プロジェクトは、Sanic コミュニティ傘下の単一の GitHub リポジトリによって表現されています。 これらのプロジェクトは**ユーザ**によって使用され、**貢献者**によって開発され、**コア開発者**によって管理され、**リリースマネージャ**によってリリースされ、最終的に**運営評議会**によって監督される。 もしこれがPythonプロジェクトやPEP 8016に似ていると思われるなら、それは意図的にそのように設計されているからです。 役割と責任 --------- +-------------------------- ### ユーザー -ユーザーは、プロジェクトに必要なコミュニティメンバーです。彼らは、開発者であり、パッケージをダウンロードしインストールする人員でもあります。ユーザーはコミュニティの **最も重要な** メンバーであり、彼らなしではプロジェクトは目的を持ちません。ユーザーは誰でもなることができ、プロジェクトが採用するライセンスは適切なオープンソースライセンスでなければならない。 +ユーザーは、プロジェクトに必要なコミュニティメンバーです。 彼らは、開発者であり、パッケージをダウンロードしインストールする人員でもあります。 ユーザーはコミュニティの **最も重要な** メンバーであり、彼らなしではプロジェクトは目的を持ちません。 ユーザーは誰でもなることができ、プロジェクトが採用するライセンスは適切なオープンソースライセンスでなければならない。 _SCOは、ユーザーにできるだけプロジェクトやコミュニティに参加するよう求めています。_ -ユーザーの貢献により、プロジェクトチームは、そのユーザーのニーズを確実に満たすことができるようになります。一般的なユーザー貢献には、以下のようなものがある(ただし、これらに限定されない)。 +ユーザーの貢献により、プロジェクトチームは、そのユーザーのニーズを確実に満たすことができるようになります。 一般的なユーザー貢献には、以下のようなものがある(ただし、これらに限定されない)。 -* プロジェクトについての伝道(例:ウェブサイト上のリンク、口コミによる認知度の向上) -* 新しいユーザーの視点から、開発者に長所と短所を知らせる。 -* 精神的なサポート("ありがとう"の一言が大きな力になります) -* 金銭的な支援(オープンソースのソフトウェアですが、開発者は食べていく必要があります。) +* プロジェクトについての伝道(例:ウェブサイト上のリンク、口コミによる認知度の向上) +* 新しいユーザーの視点から、開発者に長所と短所を知らせる。 +* 精神的なサポート("ありがとう"の一言が大きな力になります) +* 金銭的な支援(オープンソースのソフトウェアですが、開発者は食べていく必要があります。 ) -SCO、そのプロジェクト、そのコミュニティに関わり続けるユーザーは、多くの場合、より深く関わるようになります。そのようなユーザは、次のセクションで説明するように、自分自身が貢献者になることに気づくかもしれません。 +SCO、そのプロジェクト、そのコミュニティに関わり続けるユーザーは、多くの場合、より深く関わるようになります。 そのようなユーザは、次のセクションで説明するように、自分自身が貢献者になることに気づくかもしれません。 ### コントリビューター -コントリビューターは、1つまたは複数のプロジェクトに具体的な方法で貢献するコミュニティのメンバーです。誰でもコントリビューターとなることができ、コントリビューターにはさまざまな形態があります。貢献と要件は、各プロジェクトが個別に定める貢献ポリシーによって管理されます。 +コントリビューターは、1つまたは複数のプロジェクトに具体的な方法で貢献するコミュニティのメンバーです。 誰でもコントリビューターとなることができ、コントリビューターにはさまざまな形態があります。 貢献と要件は、各プロジェクトが個別に定める貢献ポリシーによって管理されます。 プロジェクトに対するコミットメントを**期待せず**、特定のスキルを**要求せず**、選考過程も**行いません**。 また、ユーザーとしての活動だけでなく、以下のような活動も行っている場合があります。 -* 新規ユーザーのサポート(新規ユーザーをサポートするには、既存のユーザーが最適であることが多い)。 -* バグ報告 -* 要件定義 -* グラフィックやウェブデザインの提供 -* プログラミング -* 使用例 -* プロジェクトのインフラを支援する -* ドキュメント作成 -* バグ修正 -* 機能追加 -* 建設的な意見を提供し、コミュニティの議論に参加する +* 新規ユーザーのサポート(新規ユーザーをサポートするには、既存のユーザーが最適であることが多い)。 +* バグ報告 +* 要件定義 +* グラフィックやウェブデザインの提供 +* プログラミング +* 使用例 +* プロジェクトのインフラを支援する +* ドキュメント作成 +* バグ修正 +* 機能追加 +* 建設的な意見を提供し、コミュニティの議論に参加する -コントリビューターは、GitHubやコミュニティ・フォーラムを通じてプロジェクトに参加します。コントリビューターはプルリクエストを通じてプロジェクトに変更を加え、それをコミュニティ全体がプロジェクトに取り入れるかどうか検討します。コミュニティ・フォーラムは、最初のコントリビューションを行う際に助けを求めるのに最も適切な場所です。 +コントリビューターは、GitHubやコミュニティ・フォーラムを通じてプロジェクトに参加します。 コントリビューターはプルリクエストを通じてプロジェクトに変更を加え、それをコミュニティ全体がプロジェクトに取り入れるかどうか検討します。 コミュニティ・フォーラムは、最初のコントリビューションを行う際に助けを求めるのに最も適切な場所です。 -実際、コントリビューターの最も重要な役割の1つは、**単にコミュニティの会話に参加すること**かもしれません。プロジェクトの方向性に関するほとんどの決定は、コンセンサスによって行われます。これについては、以下で詳しく説明します。しかし、一般的には、貢献者が(行動規範の範囲内で)自由に発言し、**自分の意見や経験を述べる**ことは、プロジェクトの健全性と方向性のために役立ち、合意形成の推進を助けることになります。 +実際、コントリビューターの最も重要な役割の1つは、**単にコミュニティの会話に参加すること**かもしれません。 プロジェクトの方向性に関するほとんどの決定は、コンセンサスによって行われます。 これについては、以下で詳しく説明します。 しかし、一般的には、貢献者が(行動規範の範囲内で)自由に発言し、**自分の意見や経験を述べる**ことは、プロジェクトの健全性と方向性のために役立ち、合意形成の推進を助けることになります。 -コントリビューターがプロジェクトで経験を積み、親しみを持つようになると、コミュニティ内での知名度やコミュニティへの貢献度が高まります。ある段階では、コア開発者チームに推薦されるかもしれません。 +コントリビューターがプロジェクトで経験を積み、親しみを持つようになると、コミュニティ内での知名度やコミュニティへの貢献度が高まります。 ある段階では、コア開発者チームに推薦されるかもしれません。 ### コア開発者 -SCO傘下の各プロジェクトは、それぞれコア開発者のチームを持っています。彼らはそのプロジェクトの責任者です。 +SCO傘下の各プロジェクトは、それぞれコア開発者のチームを持っています。 彼らはそのプロジェクトの責任者です。 _コア開発者とは?_ -コア開発者は、コミュニティとの継続的な関わりを通じて、プロジェクトの継続的な発展にコミットしていることを示したコミュニティのメンバーです。コア開発者になると、プロジェクトのリソースに直接アクセスできるようになるため、貢献者はより簡単にプロジェクトに関連した活動を行うことができるようになります。彼らは、フォークからのプルリクエストによって変更を提出することなく、プロジェクトのリポジトリに直接変更を加えることができます。 +コア開発者は、コミュニティとの継続的な関わりを通じて、プロジェクトの継続的な発展にコミットしていることを示したコミュニティのメンバーです。 コア開発者になると、プロジェクトのリソースに直接アクセスできるようになるため、貢献者はより簡単にプロジェクトに関連した活動を行うことができるようになります。 彼らは、フォークからのプルリクエストによって変更を提出することなく、プロジェクトのリポジトリに直接変更を加えることができます。 -これは、コア開発者が自由に好きなことをできるという意味ではありません。実際、コア開発者はコントリビューター以上にパッケージの最終的なリリースに対して直接的な権限を持っていません。この栄誉は、プロジェクトの目的と目標を健全に尊重しているコミュニティの大切なメンバーであることを示していますが、彼らの仕事は、公式リリースに採用される前に、コミュニティによってレビューされ続けています。 +これは、コア開発者が自由に好きなことをできるという意味ではありません。 実際、コア開発者はコントリビューター以上にパッケージの最終的なリリースに対して直接的な権限を持っていません。 この栄誉は、プロジェクトの目的と目標を健全に尊重しているコミュニティの大切なメンバーであることを示していますが、彼らの仕事は、公式リリースに採用される前に、コミュニティによってレビューされ続けています。 _コア開発者がプロジェクトでできることは?_ -プロジェクトによって、この役割の定義は若干異なるかもしれません。しかし、この呼称の一般的な使い方は、ある個人がコミュニティ内で信頼されるようになり、ある程度のコントロールを与えられるようになったというものです。これは、保護されていないブランチへのプッシュ権や、プルリクエストの承認に発言権を持つという形で実現されます。 +プロジェクトによって、この役割の定義は若干異なるかもしれません。 しかし、この呼称の一般的な使い方は、ある個人がコミュニティ内で信頼されるようになり、ある程度のコントロールを与えられるようになったというものです。 これは、保護されていないブランチへのプッシュ権や、プルリクエストの承認に発言権を持つという形で実現されます。 -プロジェクトでは、すべての貢献がコミュニティ全体によってレビューされることを保証するために、さまざまなコミュニケーションメカニズムを採用しています。これには、GitHubが提供するツールや、コミュニティフォーラムが含まれます。貢献者がコア開発者として招待されるまでに、ユーザーとして、そして貢献者として、さまざまなツールやワークフローに慣れておく必要があります。 +プロジェクトでは、すべての貢献がコミュニティ全体によってレビューされることを保証するために、さまざまなコミュニケーションメカニズムを採用しています。 これには、GitHubが提供するツールや、コミュニティフォーラムが含まれます。 貢献者がコア開発者として招待されるまでに、ユーザーとして、そして貢献者として、さまざまなツールやワークフローに慣れておく必要があります。 _コア開発者になるには?_ コア開発者になるには、チームプレーヤーとしてプロジェクトに積極的に参加する意欲と能力があればよく、特別な要件はありません。 -一般的に、コア開発者候補は、プロジェクトやその目的、戦略について理解していることを示す必要があります。また、一定期間にわたってプロジェクトに貴重な貢献をしてきたことも必要です。ただし、技術やその他のスキルの要件はありません。 +一般的に、コア開発者候補は、プロジェクトやその目的、戦略について理解していることを示す必要があります。 また、一定期間にわたってプロジェクトに貴重な貢献をしてきたことも必要です。 ただし、技術やその他のスキルの要件はありません。 -新しいコア開発者は、**既存のコア開発者によっていつでも推薦される**ことができます。少なくとも年に二回 (4月と10月)、運営評議会による投票が行われます。投票は秘密投票で行われるべきです。そのプロジェクトの既存の各コア開発者は、投票用紙に書かれた候補者の数に等しい数の票を受け取ります。例えば、候補者が四人いた場合、既存の各コア開発者は四票を持ちます。コア開発者はそれらの票を好きなように投じることができますが、 ひとつの候補者に複数回投票することはできません。候補者は、投票数 (投票資格のある人の数ではありません) の三分の二の承認を受けなければなりません。コア開発者が承認した後は、運営評議会がそのノミニーを承認し、最終決定する責任を負います。運営評議会は、ノミニーがコア開発者の称号を受けるに十分な功労があるかどうかを判断する権利を持ちません。しかし、コミュニティの健全性を保つために必要であれば、投票を無効とする権利を有します。 +新しいコア開発者は、**既存のコア開発者によっていつでも推薦される**ことができます。 少なくとも年に二回 (4月と10月)、運営評議会による投票が行われます。 投票は秘密投票で行われるべきです。 そのプロジェクトの既存の各コア開発者は、投票用紙に書かれた候補者の数に等しい数の票を受け取ります。 例えば、候補者が四人いた場合、既存の各コア開発者は四票を持ちます。 コア開発者はそれらの票を好きなように投じることができますが、 ひとつの候補者に複数回投票することはできません。 候補者は、投票数 (投票資格のある人の数ではありません) の三分の二の承認を受けなければなりません。 コア開発者が承認した後は、運営評議会がそのノミニーを承認し、最終決定する責任を負います。 運営評議会は、ノミニーがコア開発者の称号を受けるに十分な功労があるかどうかを判断する権利を持ちません。 しかし、コミュニティの健全性を保つために必要であれば、投票を無効とする権利を有します。 -投票が行われた後、集計された投票結果はコミュニティフォーラムで公開されます。ノミニーは、自分に対する無効の説明について要求する権利を有します。コア開発者として認められなかったノミニーは、 将来再びされることができます。 +投票が行われた後、集計された投票結果はコミュニティフォーラムで公開されます。 ノミニーは、自分に対する無効の説明について要求する権利を有します。 コア開発者として認められなかったノミニーは、 将来再びされることができます。 -コア開発者であることは、権利ではなく特権であることを認識することが重要です。その特権は獲得しなければならず、いったん獲得した特権は、極端な場合には運営評議会 (次のセクションを参照) によって削除されることがあります。しかし、通常の状況下では、コア開発者の肩書きは、その個人がプロジェクトやコミュニティとの関わりを続けたいと望む限り存在します。 +コア開発者であることは、権利ではなく特権であることを認識することが重要です。 その特権は獲得しなければならず、いったん獲得した特権は、極端な場合には運営評議会 (次のセクションを参照) によって削除されることがあります。 しかし、通常の状況下では、コア開発者の肩書きは、その個人がプロジェクトやコミュニティとの関わりを続けたいと望む限り存在します。 -特にプロジェクトの戦略的方向性や長期的健全性に関して平均以上の貢献度を示したコミッターは、運営評議会のメンバー、またはリリースマネージャに推薦されることがあります。この役割については後述します。 +特にプロジェクトの戦略的方向性や長期的健全性に関して平均以上の貢献度を示したコミッターは、運営評議会のメンバー、またはリリースマネージャに推薦されることがあります。 この役割については後述します。 _コア開発者の権利と責任は?_ -議論されているように、決定すべきことの大半は合意形成によるものです。ある問題がより論争的になった、または重大な決定を下す必要がある特定の状況では、リリースマネージャまたは運営評議会は、以下に詳しく説明するRFCプロセスの実施を決定する(または要求する)ことができます。 +議論されているように、決定すべきことの大半は合意形成によるものです。 ある問題がより論争的になった、または重大な決定を下す必要がある特定の状況では、リリースマネージャまたは運営評議会は、以下に詳しく説明するRFCプロセスの実施を決定する(または要求する)ことができます。 -また、コミュニティの運営に発言力を持つことは、コア開発者の責務です。すべてのプロジェクトのすべてのコア開発者は、運営評議会のメンバーに推薦され、その選挙で投票することができます。 +また、コミュニティの運営に発言力を持つことは、コア開発者の責務です。 すべてのプロジェクトのすべてのコア開発者は、運営評議会のメンバーに推薦され、その選挙で投票することができます。 -このポリシー (「SCOPE」) は、アクティブなコア開発者の3分の2の権限でのみ変更できます。ただし採択後6ヶ月間は、コア開発者はアクティブなコア開発者の単純多数の権限で変更する権利を留保します。 +このポリシー (「SCOPE」) は、アクティブなコア開発者の3分の2の権限でのみ変更できます。 ただし採択後6ヶ月間は、コア開発者はアクティブなコア開発者の単純多数の権限で変更する権利を留保します。 _コア開発者が活動停止になった場合は?_ -すべてのコア開発者がプロジェクトに参加し、定期的に活動し続けることが望まれます。しかし、そのような約束が現実的でない、あるいは不可能な場合があることも理解されています。 +すべてのコア開発者がプロジェクトに参加し、定期的に活動し続けることが望まれます。 しかし、そのような約束が現実的でない、あるいは不可能な場合があることも理解されています。 -したがって、運営評議会は参加を奨励する義務を負うとともに、参加する意思や能力がなくなったコア開発者を非活動状態にする責任を負います。この主な目的は、**その人の行動を罰すること**ではなく、アクティブであり続ける人のために開発プロセスを継続することを支援することです。 +したがって、運営評議会は参加を奨励する義務を負うとともに、参加する意思や能力がなくなったコア開発者を非活動状態にする責任を負います。 この主な目的は、**その人の行動を罰すること**ではなく、アクティブであり続ける人のために開発プロセスを継続することを支援することです。 -このため、「活動停止」したコア開発者はリポジトリへのコミット権を持たず、いかなる投票にも参加しないものとします。選挙の投票権を得るには、コア開発者は **前回予定されていたプロジェクトのリリースの時点で** アクティブである必要があります。 +このため、「活動停止」したコア開発者はリポジトリへのコミット権を持たず、いかなる投票にも参加しないものとします。 選挙の投票権を得るには、コア開発者は **前回予定されていたプロジェクトのリリースの時点で** アクティブである必要があります。 活動停止中のメンバーは、いつでもその地位を復活させるよう運営評議会に要求でき、 その要求があれば運営評議会はそのコア開発者を再び活動可能にするものとします。 アクティブな状態を一定期間維持できないことが分かっている個人は、運営評議会と連絡を取り合い、必要に応じて非アクティブを宣言することが求められます。 -「活動的な」コア開発者とは、過去六か月間に有意義な形で参加した人です。それ以上の定義は、運営評議会の裁量に委ねられます。 +「活動的な」コア開発者とは、過去六か月間に有意義な形で参加した人です。 それ以上の定義は、運営評議会の裁量に委ねられます。 ### リリースマネージャー -コア開発者は、非保護ブランチへのコミットおよびマージを行うためのアクセス権のみを有するものとします。masterブランチおよびその他の保護されたブランチは、そのプロジェクトのリリース管理チームによって管理されます。リリース管理者は、コア開発チームによってコア開発チームから選出され、全リリースサイクルの間、その役割を果たすものとします。 +コア開発者は、非保護ブランチへのコミットおよびマージを行うためのアクセス権のみを有するものとします。 masterブランチおよびその他の保護されたブランチは、そのプロジェクトのリリース管理チームによって管理されます。 リリース管理者は、コア開発チームによってコア開発チームから選出され、全リリースサイクルの間、その役割を果たすものとします。 -各コア開発チームは、各リリースサイクルに対して何人のリリースマネージャを置くかを決定することができます。責任を分担し、一人に過度の負担を強いないために、1つのリリースサイクルには少なくとも2人のリリースマネージャがいることが強く推奨されます。しかし、マネージャーの数が多すぎて、彼らの努力が妨げられるようなことがあってはなりません。 +各コア開発チームは、各リリースサイクルに対して何人のリリースマネージャを置くかを決定することができます。 責任を分担し、一人に過度の負担を強いないために、1つのリリースサイクルには少なくとも2人のリリースマネージャがいることが強く推奨されます。 しかし、マネージャーの数が多すぎて、彼らの努力が妨げられるようなことがあってはなりません。 リリース管理チームの主な職務は以下の通りです。 -* 技術的な議論を監視し、促進することによって、開発サイクルを前進させる。 -* リリースカレンダーを作成し、パッケージのリリースに必要なアクションを実行します。 -* masterブランチおよびその他の保護されたブランチへのプルリクエストの承認 -* masterブランチおよび他のprotectedブランチへのプルリクエストをマージします。 +* 技術的な議論を監視し、促進することによって、開発サイクルを前進させる。 +* リリースカレンダーを作成し、パッケージのリリースに必要なアクションを実行します。 +* masterブランチおよびその他の保護されたブランチへのプルリクエストの承認 +* masterブランチおよび他のprotectedブランチへのプルリクエストをマージします。 -リリースマネージャーは、貢献の基準を満たし、コミュニティによって受け入れられたプルリクエストに対して、**拒否権やマージを差し控える権限を持っていません**。何が開発されるべきかを決めるのは彼らの責任ではなく、コミュニティの決定が実行され、プロジェクトが前進していることを確認するためのものです。 +リリースマネージャーは、貢献の基準を満たし、コミュニティによって受け入れられたプルリクエストに対して、**拒否権やマージを差し控える権限を持っていません**。 何が開発されるべきかを決めるのは彼らの責任ではなく、コミュニティの決定が実行され、プロジェクトが前進していることを確認するためのものです。 -時には、コンセンサスで達成できない決定をする必要があるかもしれません。その場合、リリースマネージャーは、その決定をRFCプロセスに削除するよう求める権限を持っています。これは(後述するように必要な場合を除き)定期的に行われるべきではなく、より共同的な合意形成戦略を優先するため、この使用は控えるべきでしょう。 +時には、コンセンサスで達成できない決定をする必要があるかもしれません。 その場合、リリースマネージャーは、その決定をRFCプロセスに削除するよう求める権限を持っています。 これは(後述するように必要な場合を除き)定期的に行われるべきではなく、より共同的な合意形成戦略を優先するため、この使用は控えるべきでしょう。 すべてのプロジェクトに同じ要件があるわけではないので、プロジェクトにおけるリリースマネージャに関する詳細は、本ポリシーの付録、またはプロジェクトの貢献ガイドラインに記載されるものとします。 @@ -142,7 +142,7 @@ _コア開発者が活動停止になった場合は?_ ### 運営協議会 -運営評議会は、「プロジェクトオーナー」として特定され、SCOの資源と資産を管理する個人で構成される運営組織です。彼らの最終的な目標は、障害物を取り除き、必要に応じてメンバーを支援することによって、プロジェクトの円滑な運営を確保することです。また、コミュニティーの中で定期的に発言することが期待されています。 +運営評議会は、「プロジェクトオーナー」として特定され、SCOの資源と資産を管理する個人で構成される運営組織です。 彼らの最終的な目標は、障害物を取り除き、必要に応じてメンバーを支援することによって、プロジェクトの円滑な運営を確保することです。 また、コミュニティーの中で定期的に発言することが期待されています。 _運営協議会は何ができる?_ @@ -150,11 +150,11 @@ _運営協議会は何ができる?_ ただし、運営協議会は組織として以下のような能力を有しています。 -* すべてのRFCの受理、差し戻し、および拒否 -* コミュニティ行動規範の実施 -* リポジトリ、サーバ、フォーラム、統合サービスなどのコミュニティ資産を管理する(または、そのような権限を他の誰かに委譲する)。 -* 極端な場合、コア開発者の解任を含め、本ポリシーで認められている他のあらゆる強制的な手段を取ることができます。 -* コミュニティ傘下のプロジェクトを採用または削除する +* すべてのRFCの受理、差し戻し、および拒否 +* コミュニティ行動規範の実施 +* リポジトリ、サーバ、フォーラム、統合サービスなどのコミュニティ資産を管理する(または、そのような権限を他の誰かに委譲する)。 +* 極端な場合、コア開発者の解任を含め、本ポリシーで認められている他のあらゆる強制的な手段を取ることができます。 +* コミュニティ傘下のプロジェクトを採用または削除する 運営協議会は、可能な限りその権限を他の意欲的なコミュニティメンバーに委譲することが強く望まれます。 @@ -164,11 +164,11 @@ _運営協議会には何人いる?_ 4人です。 -4票の委員会は、多数決を破る方法がなく、デッドロックに陥る可能性があるように思えますが、運営協議会はできるだけ投票をしないように推奨されています。その代わり、合意形成に努めるべきであり、議決が必要な場合は3名の同意票を必要とします。 +4票の委員会は、多数決を破る方法がなく、デッドロックに陥る可能性があるように思えますが、運営協議会はできるだけ投票をしないように推奨されています。 その代わり、合意形成に努めるべきであり、議決が必要な場合は3名の同意票を必要とします。 _運営協議会のメンバーは何年間在籍する?_ -1期は、1月から始まる暦年2年間とします。任期は、毎年、前年の審議会から継続する委員が2名となるようにずらされます。 +1期は、1月から始まる暦年2年間とします。 任期は、毎年、前年の審議会から継続する委員が2名となるようにずらされます。 従って、就任時の投票では、2年任期が2名、1年任期が2名の役職が用意されています。 @@ -176,17 +176,17 @@ _運営協議会のメンバーは何年間在籍する?_ _運営協議会は誰が運営しているのか?_ -運営協議会が選出された後、グループ全体で議長として活動する1名を決定するものとします。議長は、運営協議会の他のメンバーに対する追加的な権利や権限を有しません。 +運営協議会が選出された後、グループ全体で議長として活動する1名を決定するものとします。 議長は、運営協議会の他のメンバーに対する追加的な権利や権限を有しません。 -議長の役割は、単に調整役および進行役です。議長は、すべてのガバナンス・プロセスが順守されていることを確認することが期待されます。この役職はより管理的、事務的なものであり、議長が議題を設定し、グループの議論を調整することが期待されます。 +議長の役割は、単に調整役および進行役です。 議長は、すべてのガバナンス・プロセスが順守されていることを確認することが期待されます。 この役職はより管理的、事務的なものであり、議長が議題を設定し、グループの議論を調整することが期待されます。 _議員はどのように選出されるのですか?_ 年に一度、各プロジェクトの **すべての適格なコア開発者** は、運営評議会のメンバーを選出する権利があります。 -指名は 9 月 1 日から始まり、9 月 30 日に締め切られます。その後、投票は 10 月 1 日から始まり 10 月 31 日に締め切られます。その年の Sanic フレームワークの 6 月のリリース日にアクティブなすべてのコア開発者は、運営評議会の空席につき 1 票を得る資格を得ます。明確にするために、投票資格を得るために、コア開発者は Sanic フレームワークのコア開発者である必要はなく、むしろその日にそれぞれのプロジェクトで活動している必要があります。 +指名は 9 月 1 日から始まり、9 月 30 日に締め切られます。 その後、投票は 10 月 1 日から始まり 10 月 31 日に締め切られます。 その年の Sanic フレームワークの 6 月のリリース日にアクティブなすべてのコア開発者は、運営評議会の空席につき 1 票を得る資格を得ます。 明確にするために、投票資格を得るために、コア開発者は Sanic フレームワークのコア開発者である必要はなく、むしろその日にそれぞれのプロジェクトで活動している必要があります。 -投票のトップ受信者は、勝者として宣言されるものとします。もし同点であれば、ランダムに決定される前に、同点の候補者自身が論争を解決することが強く推奨されます。 +投票のトップ受信者は、勝者として宣言されるものとします。 もし同点であれば、ランダムに決定される前に、同点の候補者自身が論争を解決することが強く推奨されます。 運営協議会の設立投票については、上位2名の得票者が2年間、次の2名の得票者が1年間の任期で就任するものとします。 @@ -194,7 +194,7 @@ _議員はどのように選出されるのですか?_ _欠員が出た場合は?_ -任期中に運営協議会に欠員が出た場合は、前回の選挙で次に得票数の多かった者が残りの任期を務めることを申し出るべきです。この方法で空席を埋めることができない場合、運営協議会はその空席を埋めるための最も適切な方法(任命、投票、その他の方法のいずれか)を決定することができます。 +任期中に運営協議会に欠員が出た場合は、前回の選挙で次に得票数の多かった者が残りの任期を務めることを申し出るべきです。 この方法で空席を埋めることができない場合、運営協議会はその空席を埋めるための最も適切な方法(任命、投票、その他の方法のいずれか)を決定することができます。 運営協議会のメンバーが活動しなくなった場合、その人物は直ちに運営協議会から外され、その席は空席となります。 @@ -202,31 +202,31 @@ _欠員が出た場合は?_ _運営協議会はどのように業務を遂行する?_ -運営協議会は、できる限りオープンに事業や議論を行います。コミュニティーのメンバーであれば、誰でもその会話に加わることができるようにします。しかし、時には非公開で議論を行うことが必要または適切である場合もあります。会話のための適切な場を選択することは、議長の管理業務の一部です。 +運営協議会は、できる限りオープンに事業や議論を行います。 コミュニティーのメンバーであれば、誰でもその会話に加わることができるようにします。 しかし、時には非公開で議論を行うことが必要または適切である場合もあります。 会話のための適切な場を選択することは、議長の管理業務の一部です。 -運営方法の詳細は本方針の範囲外ですが、運営審議会は、少なくとも四半期に一度、「リアルタイムの」話し合いの場を持つよう努めることが推奨されます。これは、ビデオ会議、ライブチャット、またはその他の適切な手段で実現できます。 +運営方法の詳細は本方針の範囲外ですが、運営審議会は、少なくとも四半期に一度、「リアルタイムの」話し合いの場を持つよう努めることが推奨されます。 これは、ビデオ会議、ライブチャット、またはその他の適切な手段で実現できます。 サポート ------- +------- -コミュニティの参加者は全員、プロジェクト管理インフラ内のユーザーをサポートすることが推奨されています。このサポートは、コミュニティを成長させる方法として提供されます。サポートを求める人は、プロジェクト内のすべてのサポート活動は自発的なものであり、したがって時間の許す限り提供されるものであることを認識する必要があります。保証された応答時間や結果を必要とするユーザは、コミュニティのメンバーからサポート契約を購入する必要があります。しかし、このプロジェクトに参加し、他のユーザーをサポートすることを望むユーザーにとっては、コミュニティのサポートチャネルは理想的です。 +コミュニティの参加者は全員、プロジェクト管理インフラ内のユーザーをサポートすることが推奨されています。 このサポートは、コミュニティを成長させる方法として提供されます。 サポートを求める人は、プロジェクト内のすべてのサポート活動は自発的なものであり、したがって時間の許す限り提供されるものであることを認識する必要があります。 保証された応答時間や結果を必要とするユーザは、コミュニティのメンバーからサポート契約を購入する必要があります。 しかし、このプロジェクトに参加し、他のユーザーをサポートすることを望むユーザーにとっては、コミュニティのサポートチャネルは理想的です。 意思決定プロセス -------------- +----------------------- -プロジェクトの将来についての決定は、最も新しいユーザーから最も経験豊かなメンバーまで、コミュニティのすべてのメンバーとの議論を通じて行われます。誰もが声を上げることができるのです。 +プロジェクトの将来についての決定は、最も新しいユーザーから最も経験豊かなメンバーまで、コミュニティのすべてのメンバーとの議論を通じて行われます。 誰もが声を上げることができるのです。 -プロジェクト管理に関する機密性のない議論は、すべてコミュニティフォーラムやその他の指定されたチャンネルで行われます。時折、機密性の高い議論が非公開で行われることもあります。 +プロジェクト管理に関する機密性のない議論は、すべてコミュニティフォーラムやその他の指定されたチャンネルで行われます。 時折、機密性の高い議論が非公開で行われることもあります。 -プロジェクトが終わりのない議論や継続的な投票によって停滞しないように、プロジェクトは**レイジーコンセンサス**というポリシーを運用しています。これにより、正式な投票に頼ることなく、大部分の決定を行うことができます。**重要な決定事項** (以下に定義) については、別途 Request for Comment (RFC) プロセスがあります。 +プロジェクトが終わりのない議論や継続的な投票によって停滞しないように、プロジェクトは**レイジーコンセンサス**というポリシーを運用しています。 これにより、正式な投票に頼ることなく、大部分の決定を行うことができます。 **重要な決定事項** (以下に定義) については、別途 Request for Comment (RFC) プロセスがあります。 ### 技術的意思決定 プルリクエストと技術的な決定は、一般的に以下のカテゴリーに分類されるべきです。 -* **Routine**: ドキュメントの修正、クリーンアップや追加テストのためのコード変更。機能的な変更はありません。 -* **Minor**: コードベースへの変更は、バグを修正するか、または些細な機能を導入する。破壊的な変更はありません。 -* **Major**。既存のAPIを破壊または非推奨とする、自明ではない方法で操作を変更する、または重要な機能を追加するコードベースへの変更。 +* **Routine**: ドキュメントの修正、クリーンアップや追加テストのためのコード変更。 機能的な変更はありません。 +* **Minor**: コードベースへの変更は、バグを修正するか、または些細な機能を導入する。 破壊的な変更はありません。 +* **Major**。 既存のAPIを破壊または非推奨とする、自明ではない方法で操作を変更する、または重要な機能を追加するコードベースへの変更。 一般に、リポジトリへの変更がマージ前に適切な承認を受けるようにすることは、リリースマネージャの責任です。 @@ -241,24 +241,24 @@ _運営協議会はどのように業務を遂行する?_ * 投票(議論しても意見がまとまらない場合) * 決定 -コミュニティメンバーは誰でも、コミュニティで検討するための提案を行うことができます。新しいアイデアについて議論を始めるには、コミュニティフォーラムの適切なチャンネルにメッセージを投稿するか、GitHub にそのアイデアを実装したプルリクエストを送信してください。これにより、そのアイデアのレビューが行われ、必要であれば議論が行われます。 +コミュニティメンバーは誰でも、コミュニティで検討するための提案を行うことができます。 新しいアイデアについて議論を始めるには、コミュニティフォーラムの適切なチャンネルにメッセージを投稿するか、GitHub にそのアイデアを実装したプルリクエストを送信してください。 これにより、そのアイデアのレビューが行われ、必要であれば議論が行われます。 -このレビューとディスカッションの目的は、コントリビューションに対する承認を得ることです。プロジェクトコミュニティのほとんどの人はビジョンを共有しているので、合意に達するための議論はほとんど必要ないことが多いです。 +このレビューとディスカッションの目的は、コントリビューションに対する承認を得ることです。 プロジェクトコミュニティのほとんどの人はビジョンを共有しているので、合意に達するための議論はほとんど必要ないことが多いです。 -一般に、誰も明確に反対しない限り、その提案やパッチはコミュニティの支持を得たものとして認識されます。これはレイジーコンセンサスと呼ばれるもので、つまり、明確に意見を述べなかった人は、暗黙のうちにその提案の実現に同意していることになるのです。 +一般に、誰も明確に反対しない限り、その提案やパッチはコミュニティの支持を得たものとして認識されます。 これはレイジーコンセンサスと呼ばれるもので、つまり、明確に意見を述べなかった人は、暗黙のうちにその提案の実現に同意していることになるのです。 -レイジーコンセンサスは、SCOの中で非常に重要な概念です。ある提案に異論がない人が自分の立場を表明するのに時間をかける必要がなく、他の人もそのようなメッセージを読むのに時間をかける必要がないため、このプロセスによって大勢の人が効率よく合意に達することができるのです。 +レイジーコンセンサスは、SCOの中で非常に重要な概念です。 ある提案に異論がない人が自分の立場を表明するのに時間をかける必要がなく、他の人もそのようなメッセージを読むのに時間をかける必要がないため、このプロセスによって大勢の人が効率よく合意に達することができるのです。 -ダラダラとした合意形成を効果的に行うためには、その提案に異論がないと判断するまでに適切な時間を置く必要があリマス。これは状況によって多少異なりますが、一般的には72時間が妥当であると考えられています。この要件により、誰もが提案を読み、咀嚼し、反応するための十分な時間が与えられます。この期間は、場所や時間の制約に関係なく、すべての参加者ができるだけ参加できるように選択されます。議論の進行役(議長であれ、リリースマネージャであれ、該当する場合)は、このような合意形成のための適切な時間の長さを決定する責任を負うものとします。 +ダラダラとした合意形成を効果的に行うためには、その提案に異論がないと判断するまでに適切な時間を置く必要があリマス。 これは状況によって多少異なりますが、一般的には72時間が妥当であると考えられています。 この要件により、誰もが提案を読み、咀嚼し、反応するための十分な時間が与えられます。 この期間は、場所や時間の制約に関係なく、すべての参加者ができるだけ参加できるように選択されます。 議論の進行役(議長であれ、リリースマネージャであれ、該当する場合)は、このような合意形成のための適切な時間の長さを決定する責任を負うものとします。 -いわゆる定型的な決定に関して前に書いたように、リリースマネージャはより短い時間内に決定を下す権利を持ちます。このような場合、怠惰な合意は黙示されるものとします。 +いわゆる定型的な決定に関して前に書いたように、リリースマネージャはより短い時間内に決定を下す権利を持ちます。 このような場合、怠惰な合意は黙示されるものとします。 ### コメント要求(RFC) -運営協議会は、RFCのプロセスを監督する責任を負います。このプロセスは、コミュニティの全メンバーに対して開かれた議論を維持し、提案を検討する十分な時間、メンバーが返答し、有意義な議論を行うための時間を確保します。 +運営協議会は、RFCのプロセスを監督する責任を負います。 このプロセスは、コミュニティの全メンバーに対して開かれた議論を維持し、提案を検討する十分な時間、メンバーが返答し、有意義な議論を行うための時間を確保します。 -最終的な決定は運営協議会に委ねられます。しかし、コミュニティ内に存在するコンセンサスに反する決定を運営協議会が採択することは、強く推奨されません。コンセンサスとプロジェクト全体およびコミュニティの目標との間に矛盾がある場合、時折このようなことが起こるかもしれません。 +最終的な決定は運営協議会に委ねられます。 しかし、コミュニティ内に存在するコンセンサスに反する決定を運営協議会が採択することは、強く推奨されません。 コンセンサスとプロジェクト全体およびコミュニティの目標との間に矛盾がある場合、時折このようなことが起こるかもしれません。 -RFCは、運営評議会が定める公開の方法で運営評議会に提出することにより開始されます。議論は、一般的には運営協議会、特に議長によって継続され、促進されます。 +RFCは、運営評議会が定める公開の方法で運営評議会に提出することにより開始されます。 議論は、一般的には運営協議会、特に議長によって継続され、促進されます。 運営協議会が適切と考える状況においては、RFCのプロセスを放棄し、怠惰な合意を優先させることができます。 diff --git a/src/ja/plugins/sanic-ext/configuration.md b/src/ja/plugins/sanic-ext/configuration.md index f8719470b9..fe0c2bc379 100644 --- a/src/ja/plugins/sanic-ext/configuration.md +++ b/src/ja/plugins/sanic-ext/configuration.md @@ -1,3 +1,243 @@ # 設定 -_ドキュメンテーションは2022年10月公開予定_ +Sanic Extensionsは、 [Sanic](../../guide/deployment/configuration.md)と同じ方法で設定できます。 これにより、Sanic Extensionsの設定が非常に簡単になります。 + +```python +app = Sanic("MyApp") +app.config.OAS_URL_PREFIX = "/apidocs" +``` + +しかし、検討すべき設定オプションはいくつかあります。 + +## マニュアル`拡張` + +---:1 Sanic Extensionsは自動的にアプリケーションにアタッチしますが、手動で `拡張`を選択することができます。 これを行うと、すべての設定値をキーワード引数(小文字) として渡すことができます。 :--: +```python +app = Sanic("MyApp") +app.extend(oas_url_prefix="/apidocs") +``` +:--- + +---:1 または、代わりに、 `dict` で一度にすべてを渡すことができます。 :--: +```python +app = Sanic("MyApp") +app.extend(config={"oas_url_prefix": "/apidocs"}) +``` +:--- + +---:1 どちらのソリューションも、構成設定の名前がIDEによって検出できないという事実に苦しんでいます。 したがって、使用できる型注釈付きオブジェクトもあります。 これは、開発の経験に役立つはずです。 :--: +```python +from sanic_ext import Config + +app = Sanic("MyApp") +app.extend(config=Config(oas_url_prefix="/apidocs")) +``` +:--- + +## 設定 + +### `cors` + +- **型**: `bool` +- **デフォルト**: `True` +- **説明**: CORS保護を有効にするかどうか + +### `cors_allow_headers` + +- **型**: `str` +- **デフォルト**: `"*"` +- **説明**: `access-control-allow-headers`ヘッダーの値 + +### `cors_always_send` + +- **型**: `bool` +- **デフォルト**: `True` +- **説明**: 常に`access-control-allow-origin`ヘッダーを送信するかどうか + +### `cors_automatic_options` + +- **型**: `bool` +- **デフォルト**: `True` +- **説明**: `OPTIONS` ルートの *まだ定義されていない* エンドポイントを自動的に生成するかどうか + +### `cors_expose_headers` + +- **型**: `str` +- **デフォルト**: `""` +- **説明**: `access-control-expose-headers`ヘッダーの値 + +### `cors_max_age` + +- **型**: `int` +- **デフォルト**: `5` +- **説明**: `access-control-max-age`ヘッダーの値 + +### `cors_methods` + +- **型**: `str` +- **デフォルト**: `""` +- **説明**: `access-control-allow-methods`ヘッダーの値 + +### `cors_origins` + +- **型**: `str` +- **デフォルト**: `""` +- **説明**: `access-control-allow-origin`ヘッダーの値 + +::: warning `*` をここに置く場合は注意してください。 セキュリティの問題である可能性があるため、何をしているかを知っていない限り、これをしないでください。 ::: + +### `cors_send_wildcard` + +- **型**: `bool` +- **デフォルト**: `False` +- **説明**: リクエスト元の代わりにワイルドカードを送信するかどうか + +### `cors_supports_credentials` + +- **型**: `bool` +- **デフォルト**: `False` +- **説明**: `access-control-allow-credentials`ヘッダーの値 + +### `cors_vary_header` + +- **型**: `bool` +- **デフォルト**: `True` +- **説明**: `vary` (訳注:「変化」) ヘッダーを追加するかどうか + +### `http_all_methods` + +- **型**: `bool` +- **デフォルト**: `True` +- **説明**: HTTP `CONNECT` と `TRACE` メソッドを allowable として追加する。 + +### `http_auto_head` + +- **型**: `bool` +- **デフォルト**: `True` +- **説明**: 任意の `GET` ルートに `HEAD` ハンドラーを自動的に追加する。 + +### `http_auto_options` + +- **型**: `bool` +- **デフォルト**: `True` +- **説明**: 自動的に `OPTIONS` ハンドラーを任意のルートに追加する + +### `http_auto_trace` + +- **型**: `bool` +- **デフォルト**: `False` +- **説明**: 自動的に `TRACE` ハンドラーを任意のルートに追加する + +### `oas` + +- **型**: `bool` +- **デフォルト**: `True` +- **説明**: OpenAPI仕様の生成を有効にするか + +### `oas_autodoc` + +- **型**: `bool` +- **デフォルト**: `True` +- **説明**: ルート関数のdocstringからOpenAPIの詳細を自動的に抽出するかどうか + +### `oas_ignore_head` + +- **型**: `bool` +- **デフォルト**: `True` +- **説明**: `True`なら, OpenAPI 仕様に `HEAD` エンドポイントを追加しません + +### `oas_ignore_options` + +- **型**: `bool` +- **デフォルト**: `True` +- **説明**: `True`なら, OpenAPI 仕様に `OPTIONS` エンドポイントを追加しません + +### `oas_path_to_redoc_html` + +- **型**: `Optional[str]` +- **デフォルト**: `None` +- **説明**: 既存の Redoc HTML をオーバーライドする HTML ファイルへのパス + +### `oas_path_to_swagger_html` + +- **型**: `Optional[str]` +- **デフォルト**: `None` +- **説明**: 既存の Swagger HTML をオーバーライドする HTML ファイルへのパス + +### `oas_ui_default` + +- **型**: `Optional[str]` +- **デフォルト**: `"redoc"` +- **説明**: `oas_url_prefix`エンドポイントにどのOASドキュメントを提供するか; `None`の場合、その場所にドキュメントはありません + +### `oas_ui_redoc` + +- **型**: `bool` +- **デフォルト**: `True` +- **説明**: Redoc UI を有効にするかどうか + +### `oas_ui_swagger` + +- **型**: `bool` +- **デフォルト**: `True` +- **説明**: Swagger UI を有効にするかどうか + +### `oas_ui_swagger_version` + +- **型**: `str` +- **デフォルト**: `"4.1.0"` +- **説明**: 使用するSwaggerバージョン + +### `oas_uri_to_config` + +- **型**: `str` +- **デフォルト**: `"/swagger-config"` +- **説明**: Swaggerの設定を提供するパス + +### `oas_uri_to_json` + +- **型**: `str` +- **デフォルト**: `"/openapi.json"` +- **説明**: OpenAPI JSON を提供するパス + +### `oas_uri_to_redoc` + +- **型**: `str` +- **デフォルト**: `"/redoc"` +- **説明**: Redocのパス + +### `oas_uri_to_swagger` + +- **型**: `str` +- **デフォルト**: `"/swagger"` +- **説明**: Swagger のパス + +### `oas_url_prefix` + +- **型**: `str` +- **デフォルト**: `"/docs"` +- **説明**: OASドキュメントのすべてのwitllが添付するブループリントのURL接頭辞。 + +### `swagger_ui_configuration` + +- **型**: `Dict[str, Any]` +- **デフォルト**: `{"apisSorter": "alpha", "operationsSorter": "alpha", "docExpansion": "full"}` +- **説明**: フロントエンドに提供される Swagger ドキュメント + +### `templating_enable_async` + +- **型**: `bool` +- **デフォルト**: `True` +- **説明**: Jinjaの `Environment` で `enable_async` を設定するかどうか + +### `templating_path_to_templates` + +- **型**: `Union[str, os.PathLike, Sequence[Union[str, os.PathLike]]]` +- **デフォルト**: `templates` +- **説明**: テンプレートファイルがある場所への単一のパス、または複数のパス + +### `trace_excluded_headers` + +- **型**: `Sequence[str]` +- **デフォルト**: `("authorization", "cookie")` +- **説明**: `TRACE` リクエストに対するレスポンスから抑制されるヘッダー。 diff --git a/src/ja/plugins/sanic-ext/convenience.md b/src/ja/plugins/sanic-ext/convenience.md index 731343f4fe..bb92c3aeeb 100644 --- a/src/ja/plugins/sanic-ext/convenience.md +++ b/src/ja/plugins/sanic-ext/convenience.md @@ -2,12 +2,7 @@ ## シリアライザーの修正 ----:1 - -アプリケーションを開発していると、常に同じようなレスポンスを返すルートが存在することがよくあります。このような場合、エンドポイントで返されるシリアライザーをあらかじめ定義しておけば、あとはコンテンツを返すだけでよいのです。 - -:--:1 - +---:1 Often when developing an application, there will be certain routes that always return the same sort of response. このような場合、エンドポイントで返されるシリアライザーをあらかじめ定義しておけば、あとはコンテンツを返すだけでよいのです。 :--:1 ```python from sanic_ext import serializer @@ -18,16 +13,10 @@ async def hello_world(request, name: str): return "hello " * int(name) return f"Hello, {name}" ``` - :--- - ----:1 - -`@serializer` デコレータは、ステータスコードを追加することもできます。 - -:--:1 +---:1 The `serializer` decorator also can add status codes. :--:1 ```python from sanic_ext import serializer @@ -40,13 +29,7 @@ async def create_something(request): ## カスタムシリアライザー - ----:1 - -`@serializer` デコレータを使用すると、有効な型 (`HTTPResonse`) を返す限りにおいて、独自のカスタム関数を渡すことも可能です。 - -:--:1 - +---:1 Using the `@serializer` decorator, you can also pass your own custom functions as long as they also return a valid type (`HTTPResonse`). :--:1 ```python def message(retval, request, action, status): return json( @@ -62,25 +45,42 @@ def message(retval, request, action, status): @app.post("/") @serializer(message) async def do_action(request, action: str): - return "This is a message" + return "これはメッセージです" ``` - :--- ----:1 - -さて、文字列だけを返すと、素敵なシリアル化された出力が返されるはずです。 - -:--:1 +---:1 Now, returning just a string should return a nice serialized output. :--:1 ```python $ curl localhost:8000/eat_cookies -X POST { "request_id": "ef81c45b-235c-46dd-9dbd-b550f8fa77f9", "action": "eat_cookies", - "message": "This is a message" + "message": "これはメッセージです" } ``` +:--- + + +## Request counter +---:1 Sanic Extensions comes with a subcleass of `Request` that can be setup to automatically keep track of the number of requests processed per worker process. To enable this, you should pass the `CountedRequest` class to your application contructor. :--:1 +```python +from sanic_ext import CountedRequest + +app = Sanic(..., request_class=CountedRequest) +``` :--- + +---:1 You will now have access to the number of requests served during the lifetime of the worker process. :--:1 +```python +@app.get("/") +async def handler(request: CountedRequest): + return json({"count": request.count}) +``` +:--- + +If possible, the request count will also be added to the [worker state](../../guide/deployment/manager.md#worker-state). + +![](https://user-images.githubusercontent.com/166269/190922460-43bd2cfc-f81a-443b-b84f-07b6ce475cbf.png) diff --git a/src/ja/plugins/sanic-ext/custom.md b/src/ja/plugins/sanic-ext/custom.md new file mode 100644 index 0000000000..e374052a3d --- /dev/null +++ b/src/ja/plugins/sanic-ext/custom.md @@ -0,0 +1,84 @@ +# Custom extensions + +It is possible to create your own custom extensions. + +Version 22.9 added the `Extend.register` [method](#extension-preregistration). This makes it extremely easy to add custom expensions to an application. + +## Anatomy of an extension + +All extensions must subclass `Extension`. + +### Required + +- `name`: By convention, the name is an all-lowercase string +- `startup`: A method that runs when the extension is added + +### Optional + +- `label`: A method that returns additional information about the extension in the MOTD +- `included`: A method that returns a boolean whether the extension should be enabled or not (could be used for example to check config state) + +### Example + +```python +from sanic import Request, Sanic, json +from sanic_ext import Extend, Extension + +app = Sanic(__name__) +app.config.MONITOR = True + + +class AutoMonitor(Extension): + name = "automonitor" + + def startup(self, bootstrap) -> None: + if self.included(): + self.app.before_server_start(self.ensure_monitor_set) + self.app.on_request(self.monitor) + + @staticmethod + async def monitor(request: Request): + if request.route and request.route.ctx.monitor: + print("....") + + @staticmethod + async def ensure_monitor_set(app: Sanic): + for route in app.router.routes: + if not hasattr(route.ctx, "monitor"): + route.ctx.monitor = False + + def label(self): + has_monitor = [ + route + for route in self.app.router.routes + if getattr(route.ctx, "monitor", None) + ] + return f"{len(has_monitor)} endpoint(s)" + + def included(self): + return self.app.config.MONITOR + + +Extend.register(AutoMonitor) + + +@app.get("/", ctx_monitor=True) +async def handler(request: Request): + return json({"foo": "bar"}) +``` + + +## Extension preregistration + +---:1 `Extend.register` simplifies the addition of custom extensions. :--:1 +```python +from sanic_ext import Extend, Extension + +class MyCustomExtension(Extension): + ... + +Extend.register(MyCustomExtension()) +``` +:--- + +*Added in v22.9* diff --git a/src/ja/plugins/sanic-ext/getting-started.md b/src/ja/plugins/sanic-ext/getting-started.md index ed42f5cdb3..6f1ebdf035 100644 --- a/src/ja/plugins/sanic-ext/getting-started.md +++ b/src/ja/plugins/sanic-ext/getting-started.md @@ -1,6 +1,6 @@ # スタートアップ -Sanic Extensionsは、SCOが開発し、保守している*公式サポート*のプラグインです。このプロジェクトの主な目的は、Web API と Web アプリケーションの開発を容易にするための追加機能を提供することです。 +Sanic Extensionsは、SCOが開発し、保守している*公式サポート*のプラグインです。 このプロジェクトの主な目的は、Web API と Web アプリケーションの開発を容易にするための追加機能を提供することです。 ## 機能 @@ -10,6 +10,7 @@ Sanic Extensionsは、SCOが開発し、保守している*公式サポート* - ルートハンドラへの引数挿入 - RedocやSwaggerを使ったOpenAPIドキュメンテーション - リクエストのクエリ引数とボディ入力のバリデーション +- `HEAD`、`OPTIONS`、`TRACE` のエンドポイントの自動作成 ## 最低要件 @@ -34,12 +35,9 @@ pip install sanic-ext Sanic Extensionsは、特別な操作なしに、たくさんの機能を有効にしてくれます。 -::: new NEW in v21.12 ----:1 -Sanic Extensions (v21.12+) をセットアップするために必要なこと: **何もない**。環境にインストールされていれば、セットアップが完了し、すぐに使えるようになっています。 +---:1 To setup Sanic Extensions (v21.12+), you need to do: **nothing**. 環境でインストールされている場合は、セットアップして実行する準備ができています。 -このコードは、[Sanic Getting Started page](../../guide/getting-started.md) にある Hello, world アプリを変更せずにそのまま使用しています_。 -:--:1 +This code is the Hello, world app in the [Sanic Getting Started page](../../guide/getting-started.md) _without any changes_, but using Sanic Extensions with `sanic-ext` installed in the background. :--:1 ```python from sanic import Sanic from sanic.response import text @@ -50,16 +48,14 @@ app = Sanic("MyHelloWorldApp") async def hello_world(request): return text("Hello, world.") ``` + :--- -::: ----:1 -**古い非推奨の設定** +---:1 **_古い非推奨の設定_** v21.9 では、`Extend` でインスタンス化するのが最も簡単な方法です。 -Sanic Getting Started page](../../guide/getting-started.md) の Hello, world アプリを見返してみると、ここで追加されているのはハイライトした2行だけであることがわかると思います。 -:--:1 +[Sanic Getting Started page](../../guide/getting-started.md) の Hello, world アプリを見返してみると、ここで追加されているのはハイライトした2行だけであることがわかると思います。 :--:1 ```python{3,6} from sanic import Sanic @@ -75,4 +71,4 @@ async def hello_world(request): ``` :--- -どのように設定されているかに関わらず、これでOpenAPIのドキュメントを閲覧し、機能の一部を確認することができるはずです。[http://localhost:8000/docs](http://localhost:8000/docs) +どのように設定されているかに関わらず、これでOpenAPIのドキュメントを閲覧し、機能の一部を確認できるはずです: [http://localhost:8000/docs](http://localhost:8000/docs) diff --git a/src/ja/plugins/sanic-ext/health-monitor.md b/src/ja/plugins/sanic-ext/health-monitor.md new file mode 100644 index 0000000000..b4e2abd370 --- /dev/null +++ b/src/ja/plugins/sanic-ext/health-monitor.md @@ -0,0 +1,62 @@ +# Health monitor + +The health monitor requires both `sanic>=22.9` and `sanic-ext>=22.9`. + +You can setup Sanic Extensions to monitor the health of your worker processes. This requires that you not be in [single process mode](../../guide/deployment/manager.md#single-process-mode). + +## Setup + +---:1 Out of the box, the health monitor is disabled. You will need to opt-in if you would like to use it. :--:1 +```python +app.config.HEALTH = True +``` +:--- + +## How does it work + +The monitor sets up a new background process that will periodically receive acknowledgements of liveliness from each worker process. If a worker process misses a report too many times, then the monitor will restart that one worker. + +## Diagnostics endpoint + +---:1 The health monitor will also enable a diagnostics endpoint that outputs the [worker state](../../guide/deployment/manager.md#worker-state). By default is id disabled. + +::: warning +The diagnostics endpoint is not secured. If you are deploying it in a production environment, you should take steps to protect it with a proxy server if you are using one. If not, you may want to consider disabling this feature in production since it will leak details about your server state. +::: +:--:1 +``` +$ curl http://localhost:8000/__health__ +{ + 'Sanic-Main': {'pid': 99997}, + 'Sanic-Server-0-0': { + 'server': True, + 'state': 'ACKED', + 'pid': 9999, + 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), + 'starts': 2, + 'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc) + }, + 'Sanic-Reloader-0': { + 'server': False, + 'state': 'STARTED', + 'pid': 99998, + 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), + 'starts': 1 + } +} +``` +:--- + + +## Configuration + +| Key | Type | Default | Description | +| -------------------------- | ------ | --------------- | --------------------------------------------------------------------------- | +| HEALTH | `bool` | `False` | Whether to enable this extension. | +| HEALTH_ENDPOINT | `bool` | `False` | Whether to enable the diagnostics endpoint. | +| HEALTH_MAX_MISSES | `int` | `3` | The number of consecutive misses before a worker process is restarted. | +| HEALTH_MISSED_THRESHHOLD | `int` | `10` | The number of seconds the monitor checks for worker process health. | +| HEALTH_MONITOR | `bool` | `True` | Whether to enable the health monitor. | +| HEALTH_REPORT_INTERVAL | `int` | `5` | The number of seconds between reporting each acknowledgement of liveliness. | +| HEALTH_URI_TO_INFO | `str` | `""` | The URI path of the diagnostics endpoint. | +| HEALTH_URL_PREFIX | `str` | `"/__health__"` | The URI prefix of the diagnostics blueprint. | diff --git a/src/ja/plugins/sanic-ext/http/cors.md b/src/ja/plugins/sanic-ext/http/cors.md index 493b522369..3bec6980af 100644 --- a/src/ja/plugins/sanic-ext/http/cors.md +++ b/src/ja/plugins/sanic-ext/http/cors.md @@ -1,16 +1,16 @@ # CORSの保護 -Cross-Origin Resource Sharing(別名:CORS)は、それだけで*巨大な*トピックです。この文書では、それが何であるかについて十分に詳しく説明することはできません。セキュリティ上の問題や解決策の背景にある理論について理解するために、ご自身で調査されることを強くお勧めします。[MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)が最初の一歩として最適です。 +Cross-Origin Resource Sharing(別名:CORS)は、それだけで*巨大な*トピックです。 この文書では、それが何であるかについて十分に詳しく説明することはできません。 セキュリティ上の問題や解決策の背景にある理論について理解するために、ご自身で調査されることを強くお勧めします。 [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)が最初の一歩として最適です。 -超簡単に説明すると、CORS 保護は、Web ページが別のドメインから情報にアクセスする方法とタイミングを容易にするために、ブラウザが使用するフレームワークです。これは、単一ページのアプリケーションを構築している人に非常に関連性の高いものです。フロントエンドは `https://portal.myapp.com` のようなドメインにありますが、バックエンドには `https://api.myapp.com` からアクセスする必要があることがよくあります。 +超簡単に説明すると、CORS 保護は、Web ページが別のドメインから情報にアクセスする方法とタイミングを容易にするために、ブラウザが使用するフレームワークです。 これは、単一ページのアプリケーションを構築している人に非常に関連性の高いものです。 フロントエンドは `https://portal.myapp.com` のようなドメインにありますが、バックエンドには `https://api.myapp.com` からアクセスする必要があることがよくあります。 -この実装は[`sanic-cors`](https://github.com/ashleysommer/sanic-cors)に強く影響を受けており、さらに[`flask-cors`](https://github.com/corydolphin/flask-cors) がベースになっています。したがって、`sanic-cors`を`sanic-ext`でほぼ完全に置き換えることができるでしょう。 +この実装は[`sanic-cors`](https://github.com/ashleysommer/sanic-cors)に強く影響を受けており、さらに[`flask-cors`](https://github.com/corydolphin/flask-cors) がベースになっています。 したがって、`sanic-cors`を`sanic-ext`でほぼ完全に置き換えることができるでしょう。 ## 基本的な実装 ---:1 -[自動エンドポイントの例](methods.md#options) にあるように、Sanic Extensions は特に何もしなくても自動的に CORS 保護を有効にします。しかし、それは箱から出してもあまり多くのものを提供しません。 +[自動エンドポイントの例](methods.md#options) にあるように、Sanic Extensions は特に何もしなくても自動的に CORS 保護を有効にします。 しかし、それは箱から出してもあまり多くのものを提供しません。 最低でも、`config.CORS_ORIGINS` をアプリケーションにアクセスする意図されたオリジンに設定することを **強く** お勧めします。 @@ -39,28 +39,28 @@ connection: keep-alive ## コンフィグレーション -CORS 対策の真の威力は、設定を開始してから発揮されます。以下は、すべてのオプションの表です。 +CORS 対策の真の威力は、設定を開始してから発揮されます。 以下は、すべてのオプションの表です。 -| キー | タイプ | デフォルト | 説明 | -|--|--|--|--| -| `CORS_ALLOW_HEADERS` | `str` または `List[str]` | `"*"` | `access-control-allow-headers` に表示されるヘッダのリストです。 | -| `CORS_ALWAYS_SEND` | `bool` | `True` | `True` の場合、常に `access-control-allow-origin` に値を設定します。`False` の場合、 `Origin` ヘッダがある場合にのみ設定されます。 | -| `CORS_AUTOMATIC_OPTIONS` | `bool` | `True` | 受信したプリフライトリクエストに対して、 `access-control-allow-headers`, `access-control-max-age`, `access-control-allow-methods` へ自動的に値を設定するかどうかを指定することができます。`False` の場合、これらの値は `@cors` デコレーターでデコレートされたルートにのみ設定されます。 | -| `CORS_EXPOSE_HEADERS` | `str` または `List[str]` | `""` | `access-control-expose-headers` ヘッダに設定するヘッダの指定リストです。 | -| `CORS_MAX_AGE` | `str`, `int`, `timedelta` | `0` | `access-control-max-age` ヘッダーを使用してプリフライトリマインダーをキャッシュできる最大秒数です。虚偽の値を指定すると、このヘッダは設定されません。 | -| `CORS_METHODS` | `str` または `List[str]` | `""` | 許可されたオリジンがアクセスできる HTTP メソッド (`access-control-allow-methods`ヘッダーで設定)。 | -| `CORS_ORIGINS` | `str`, `List[str]`, `re.Pattern` | `"*"` | `access-control-allow-origin` ヘッダーで設定した、リソースへのアクセスを許可するオリジンを指定します。 | -| `CORS_SEND_WILDCARD` | `bool` | `False` | `True` の場合、 `origin` リクエストヘッダの代わりにワイルドカード `*` オリジンを送信します。 | -| `CORS_SUPPORTS_CREDENTIALS` | `bool` | `False` | `access-control-allow-credentials` ヘッダを設定するかどうか。 | -| `CORS_VARY_HEADER` | `bool` | `True` | 適切な場合に `vary` ヘッダーを追加するかどうか。 | +| キー | 型 | デフォルト | 説明 | +| --------------------------- | -------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `CORS_ALLOW_HEADERS` | `str` または `List[str]` | `"*"` | `access-control-allow-headers` に表示されるヘッダのリストです。 | +| `CORS_ALWAYS_SEND` | `bool` | `True` | `True` の場合、常に `access-control-allow-origin` に値を設定します。 `False` の場合、 `Origin` ヘッダがある場合にのみ設定されます。 | +| `CORS_AUTOMATIC_OPTIONS` | `bool` | `True` | 受信したプリフライトリクエストに対して、 `access-control-allow-headers`, `access-control-max-age`, `access-control-allow-methods` へ自動的に値を設定するかどうかを指定することができます。 `False` の場合、これらの値は `@cors` デコレーターでデコレートされたルートにのみ設定されます。 | +| `CORS_EXPOSE_HEADERS` | `str` または `List[str]` | `""` | `access-control-expose-headers` ヘッダに設定するヘッダの指定リストです。 | +| `CORS_MAX_AGE` | `str`, `int`, `timedelta` | `0` | `access-control-max-age` ヘッダーを使用してプリフライトリマインダーをキャッシュできる最大秒数です。 虚偽の値を指定すると、このヘッダは設定されません。 | +| `CORS_METHODS` | `str` または `List[str]` | `""` | 許可されたオリジンがアクセスできる HTTP メソッド (`access-control-allow-methods`ヘッダーで設定)。 | +| `CORS_ORIGINS` | `str`, `List[str]`, `re.Pattern` | `"*"` | `access-control-allow-origin` ヘッダーで設定した、リソースへのアクセスを許可するオリジンを指定します。 | +| `CORS_SEND_WILDCARD` | `bool` | `False` | `True` の場合、 `origin` リクエストヘッダの代わりにワイルドカード `*` オリジンを送信します。 | +| `CORS_SUPPORTS_CREDENTIALS` | `bool` | `False` | `access-control-allow-credentials` ヘッダを設定するかどうか。 | +| `CORS_VARY_HEADER` | `bool` | `True` | 適切な場合に `vary` ヘッダーを追加するかどうか。 | -*簡潔にするために、上記で `List[str]` と記述している場合は、 `list`, `set`, `frozenset`, または `tuple` のインスタンスであれば、何でも構いません。また、値が `str` の場合は、カンマで区切られたリストでも構いません*。 +**簡潔にするために、上記で `List[str]` と記述している場合は、 `list`, `set`, `frozenset`, または `tuple` のインスタンスであれば、何でも構いません。 また、値が `str` の場合は、カンマで区切られたリストでも構いません*。* ## ルートレベル・オーバーライド ---:1 -特定のルートに対して、アプリ全体の設定を上書きすることが必要な場合があります。これを可能にするために、`@sanic_ext.cors()` デコレーターを使用して、異なるルート固有の値を設定することができます。 +特定のルートに対して、アプリ全体の設定を上書きすることが必要な場合があります。 これを可能にするために、`@sanic_ext.cors()` デコレーターを使用して、異なるルート固有の値を設定することができます。 このデコレータでオーバーライドできる値は、以下のとおりです。 diff --git a/src/ja/plugins/sanic-ext/http/methods.md b/src/ja/plugins/sanic-ext/http/methods.md index f9d246c5e8..c8dcb02c0f 100644 --- a/src/ja/plugins/sanic-ext/http/methods.md +++ b/src/ja/plugins/sanic-ext/http/methods.md @@ -2,18 +2,16 @@ ## 自動エンドポイント -デフォルトの動作は、すべての`GET`ルートに`HEAD`エンドポイントを、そして全ルートに`OPTIONS`エンドポイントを自動的に生成することです。 -さらに、`TRACE`エンドポイントを自動的に生成するオプションもあります。しかし、これらは初期状態では有効ではありません。 +デフォルトの動作は、すべての`GET`ルートに`HEAD`エンドポイントを、そして全ルートに`OPTIONS`エンドポイントを自動的に生成することです。 さらに、`TRACE`エンドポイントを自動的に生成するオプションもあります。 しかし、これらは初期状態では有効ではありません。 ::::tabs :::tab HEAD -- **Configuration**: `AUTO_HEAD` (default `True`) -- **MDN**: [Read more](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD) +- **設定**: `AUTO_HEAD` (デフォルト `True`) +- **MDN**: [詳細を読む](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD) -`HEAD` リクエストはヘッダーを提供し、それ以外は `GET` リクエストが提供するものと同じレスポンスを提供します。 -しかし、実際にはボディを返しません。 +`HEAD` リクエストはヘッダーを提供し、それ以外は `GET` リクエストが提供するものと同じレスポンスを提供します。 しかし、実際にはボディを返しません。 ```python @app.get("/") @@ -36,8 +34,8 @@ content-type: text/plain; charset=utf-8 :::tab OPTIONS -- **Configuration**: `AUTO_OPTIONS` (default `True`) -- **MDN**: [Read more](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS) +- **設定**: `AUTO_OPTIONS` (デフォルト `True`) +- **MDN**: [詳細を読む](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS) `OPTIONS` リクエストは、クライアントが与えられたエンドポイントとの通信をどのように許可されるかの詳細を受信者に提供します。 @@ -49,8 +47,7 @@ async def hello_world(request): 上記のルート定義があれば、Sanic Extensionsはここで見られるように`HEAD`レスポンスを有効にします。 -この例では、`access-control-allow-origins`も表示されていることに注意することが重要です。 -これは、[CORS保護](cors.md)がデフォルトで有効になっているためです。 +この例では、`access-control-allow-origins`も表示されていることに注意することが重要です。 これは、[CORS保護](cors.md)がデフォルトで有効になっているためです。 これは、 [CORS 保護](cors.md) がデフォルトで有効になっているためです。 ``` $ curl localhost:8000 -X OPTIONS -i @@ -60,15 +57,14 @@ access-control-allow-origin: * connection: keep-alive ``` -::: tip Sanic Extensionsがこれらのルートを自動的にセットアップしてくれるとしても、手動で `@app.options` ルートを作成することにした場合、それは オーバーライド**されません**。 -::: +::: tip Sanic Extensionsがこれらのルートを自動的にセットアップしてくれるとしても、手動で `@app.options` ルートを作成することにした場合、それは オーバーライド*されません*。 ::: :::tab TRACE -- **Configuration**: `AUTO_TRACE` (default `False`) -- **MDN**: [Read more](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/TRACE) +- **構成**: `AUTO_TRACE` (デフォルト `False`) +- **MDN**: [詳細を読む](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/TRACE) -デフォルトでは、`TRACE`エンドポイントは自動的に作成**されません**。しかし、Sanic Extensions では、必要であれば作成することができます。これはバニラSanicでは許可されていないことである。 +デフォルトでは、`TRACE`エンドポイントは自動的に作成**されません**。 しかし、Sanic Extensions では、必要であれば作成することができます。 これはバニラSanicでは許可されていないことです。 ```python @app.route("/", methods=["trace"]) @@ -81,7 +77,7 @@ async def handler(request): ```python from sanic_ext import Extend, Config -Extend(app, config=Config(auto_trace=True)) +app.extend(config=Config(http_auto_trace=True)) ``` さて、いくつかのエンドポイントが設定されていると仮定して、以下のようにトレースすることができます。 @@ -94,9 +90,7 @@ User-Agent: curl/7.76.1 Accept: */* ``` -::: tip `AUTO_TRACE` を設定すると、特にアプリケーションがプロキシの後ろに配置されている場合、非常に便利です。 -は、プロキシがどのように動作しているかを判断するのに役立ちます。 -::: +::: tip `AUTO_TRACE` を設定すると、特にアプリケーションがプロキシの後ろに配置されている場合、非常に便利です。 は、プロキシがどのように動作しているかを判断するのに役立ちます。 ::: :::: @@ -116,11 +110,9 @@ Accept: */* ---:1 -しかし、さらに2つの「標準的な」HTTPメソッドがあります: `TRACE` と `CONNECT` です。 -Sanic Extensions は、これらのメソッドを使用したエンドポイントの構築を可能にするもので、他の方法では許可されません。 +しかし、さらに2つの「標準的な」HTTPメソッドがあります: `TRACE` と `CONNECT` です。 Sanic Extensions は、これらのメソッドを使用したエンドポイントの構築を可能にするもので、他の方法では許可されません。 -これは便利なメソッドである `@app.trace` や `@app.connect` を有効にするものではないことに注意してください。 -この例で示されているように、`@app.route`を使用する必要があります。 +これは便利なメソッドである `@app.trace` や `@app.connect` を有効にするもの*ではない*ことに注意してください。 この例で示されているように、`@app.route`を使用する必要があります。 :--:1 diff --git a/src/ja/plugins/sanic-ext/injection.md b/src/ja/plugins/sanic-ext/injection.md index 2de93dd596..3eceb03e36 100644 --- a/src/ja/plugins/sanic-ext/injection.md +++ b/src/ja/plugins/sanic-ext/injection.md @@ -1,13 +1,13 @@ # 依存性インジェクション -依存性インジェクションは、定義された関数シグネチャに基づいてルートハンドラに引数を追加する方法です。具体的には、ハンドラ内の引数の**型アノテーション**を調べます。これは、次のようなさまざまなケースで役に立ちます。 +依存性インジェクションは、定義された関数シグネチャに基づいてルートハンドラに引数を追加する方法です。 具体的には、ハンドラ内の引数の**型アノテーション**を調べます。 これは、次のようなさまざまなケースで役に立ちます。 - リクエストヘッダに基づいてオブジェクトを取得する(現在のセッションユーザーのように)。 - 特定のオブジェクトを特定のタイプにリキャストする - リクエストオブジェクトを使用したデータのプリフェッチ - サービスの自動挿入 -`Extend` インスタンスには、依存性インジェクションに使用する 2 つの基本的なメソッドがあります。低レベルの `add_dependency` と高レベルの `dependency` です。 +`Extend` インスタンスには、依存性インジェクションに使用する 2 つの基本的なメソッドがあります。 低レベルの `add_dependency` と高レベルの `dependency` です。 **下位レベル**: `app.ext.add_dependency(...)` @@ -21,27 +21,20 @@ ここでは、いくつかの使用例を探ってみましょう。 -::: warning -v21.12より前に依存性インジェクションを使用していた場合、低レベルAPIメソッドは `injection` と呼ばれていました。その後、 `add_dependency` に名前が変更され、 v21.12 からは `injection` は `add_dependency` のエイリアスになっています。`injection` メソッドは非推奨となりv22.6で削除されます。 -::: +::: warning v21.12より前に依存性インジェクションを使用していた場合、低レベルAPIメソッドは `injection` と呼ばれていました。 その後、 `add_dependency` に名前が変更され、 v21.12 からは `injection` は `add_dependency` のエイリアスになっています。 `injection` メソッドは非推奨となりv22.6で削除されます。 ::: ## 基本的な実装 最も単純な使用例は、単に値を再キャストすることです。 ----:1 - -これは、マッチしたパスパラメータに基づいて生成したいモデルがある場合に便利です。 - -:--:1 - +---:1 This could be useful if you have a model that you want to generate based upon the matched path parameters. :--:1 ```python @dataclass class IceCream: flavor: str def __str__(self) -> str: - return f"{self.flavor.title()} (Yum!)" + return f"{self.flavor.title()} (おいしい!)" app.ext.add_dependency(IceCream) @@ -49,18 +42,16 @@ app.ext.add_dependency(IceCream) @app.get("/") async def ice_cream(request, flavor: IceCream): - return text(f"You chose: {flavor}") + return text(f"あなたの選択: {flavor}") ``` ``` $ curl localhost:8000/chocolate -You chose Chocolate (Yum!) +あなたの選択: Chocolate (おいしい!) ``` :--- ----:1 -これは、コンストラクタにキーワード引数 `type` を渡すことで動作します。前の例はこれと同等です。 -:--:1 +---:1 これは、コンストラクタにキーワード引数 `type` を渡すことで動作します。 前の例はこれと同等です。 :--:1 ```python flavor = IceCream(flavor="chocolate") ``` @@ -68,14 +59,9 @@ flavor = IceCream(flavor="chocolate") ## 追加コンストラクタ ----:1 - -時には、コンストラクタを渡す必要があるかもしれません。これは関数でもいいですし、クラスメソッドでコンストラクタとして動作させることもできます。この例では、最初に `Person.create` を呼び出すインジェクションを作成します。 - -また、この例で重要なのは、実際に**2個**のオブジェクトをインジェクションしていることです! もちろん、このようにする必要はありませんが、関数のサインに基づいてオブジェクトをインジェクトすることになります。 - -:--:1 +---:1 Sometimes you may need to also pass a constructor. これは関数でもいいですし、クラスメソッドでコンストラクタとして動作させることもできます。 この例では、最初に `Person.create` を呼び出すインジェクションを作成します。 +また、この例で重要なのは、実際に**2個**のオブジェクトをインジェクションしていることです! もちろん、このようにする必要はありませんが、関数のサインに基づいてオブジェクトをインジェクトすることになります。 :--:1 ```python @dataclass class PersonID: @@ -90,7 +76,7 @@ class Person: @classmethod async def create(cls, request: Request, person_id: int): - return cls(person_id=PersonID(person_id), name="noname", age=111) + return cls(person_id=PersonID(person_id), name="名無し", age=111) @@ -107,24 +93,46 @@ async def person_details( ``` $ curl localhost:8000/person/123 PersonID(person_id=123) -Person(person_id=PersonID(person_id=123), name='noname', age=111) +Person(person_id=PersonID(person_id=123), name='名無し', age=111) ``` :--- -この例のように `ext.add_dependency` に `constructor` が渡されると、それが呼び出されます。そうでない場合は、 `type` を呼び出してオブジェクトを生成します。`constructor`を渡す際に注意すべき点がいくつかある。 +この例のように `ext.add_dependency` に `constructor` が渡されると、それが呼び出されます。 そうでない場合は、 `type` を呼び出してオブジェクトを生成します。 `constructor`を渡す際に注意すべき点がいくつかある。 -1. `request: request` の位置専用引数が期待されます。例として、上記の `Person.create` メソッドを参照してください。 +1. A positional `request: Request` argument is *usually* expected. See the `Person.create` method above as an example using a `request` and [arbitrary constructors](#arbitrary-constructors) for how to use a callable that does not require a `request`. 1. マッチしたすべてのパスパラメータは、キーワード引数として注入されます。 -1. 依存関係は、連鎖したり、ネストしたりすることができます。先ほどの例で、`Person`データクラスが`PersonID`を持っていることに注目しましたか?これは、 `PersonID` が最初に呼び出され、その値が `Person.create` を呼び出す際のキーワード引数に追加されることを意味します。 +1. 依存関係は、連鎖したり、ネストしたりすることができます。 先ほどの例で、`Person`データクラスが`PersonID`を持っていることに注目しましたか? これは、 `PersonID` が最初に呼び出され、その値が `Person.create` を呼び出す際のキーワード引数に追加されることを意味します。 -## `Request`からのオブジェクト +## Arbitrary constructors ----:1 +---:1 Sometimes you may want to construct your injectable _without_ the `Request` object. This is useful if you have arbitrary classes or functions that create your objects. If the callable does have any required arguments, then they should themselves be injectable objects. + +This is very useful if you have services or other types of objects that should only exist for the lifetime of a single request. For example, you might use this pattern to pull a single connection from your database pool. :--:1 +```python +class Alpha: + ... + + +class Beta: + def __init__(self, alpha: Alpha) -> None: + self.alpha = alpha -時には、リクエストから詳細を抽出し、前処理を行いたい場合があります。例えば、リクエストのJSONをPythonのオブジェクトにキャストし、DBクエリに基づいていくつかの追加ロジックを追加することができます。 +app.ext.add_dependency(Alpha) +app.ext.add_dependency(Beta) -::: warning -このメソッドを使うつもりなら、Sanic がリクエストボディを読む機会がある**前**に、 インジェクションが実際に起こるということに注意すべきです。ヘッダはすでに消費されているはずです。したがって、ボディにアクセスしたい場合、この例で見られるように手動で消費する必要があります。 +@app.get("/beta") +async def handler(request: Request, beta: Beta): + assert isinstance(beta.alpha, Alpha) +``` +:--- + +*Added in v22.9* + +## `Request`からのオブジェクト + +---:1 Sometimes you may want to extract details from the request and preprocess them. 例えば、リクエストのJSONをPythonのオブジェクトにキャストし、DBクエリに基づいていくつかの追加ロジックを追加することができます。 + +::: warning このメソッドを使うつもりなら、Sanic がリクエストボディを読む機会がある**前**に、 インジェクションが実際に起こるということに注意すべきです。 ヘッダはすでに消費されているはずです。 したがって、ボディにアクセスしたい場合、この例で見られるように手動で消費する必要があります。 ```python await request.receive_body() @@ -136,14 +144,12 @@ await request.receive_body() - ミドルウェアを使用して、前処理と `request.ctx` への追加を行う。 - デコレータを使用して、前処理とリクエストハンドラへの引数の注入を行う。 -この例では、`compule_profile` コンストラクタで `Request` オブジェクトを使用して、偽の DB クエリを実行して `UserProfile` オブジェクトを生成し、それを返します。 - :--:1 - - ```python - @dataclass +この例では、`compule_profile` コンストラクタで `Request` オブジェクトを使用して、偽の DB クエリを実行して `UserProfile` オブジェクトを生成し、それを返します。 :--:1 +```python +@dataclass class User: name: str - + @dataclass class UserProfile: @@ -188,27 +194,23 @@ async def update_profile(request, profile: UserProfile): ``` ``` -$ curl localhost:8000/profile -X PATCH -d '{"name": "Alice", "birthday": "2000-01-01"}' +$ curl localhost:8000/profile -X PATCH -d '{"name": "Taro", "birthday": "2000-01-01"}' { - "name":"Alice", - "age":21, - "email":"alice@something.com" + "name":"Taro", + "age":22, + "email":"Taro@something.com" } ``` :--- ## サービスのインジェクション -データベースのコネクションプールのようなものを作成して、それを `app.ctx` オブジェクトに格納するのはよくあるパターンです。これにより、アプリケーション全体でそれらを利用できるようになり、確かに便利です。しかし、1つの欠点は、型付けされたオブジェクトを扱うことができなくなることです。これを解決するために、依存性注入を使用することができます。まず、これまでの例で使ってきたような低レベルの `add_dependency` を使って、そのコンセプトを紹介します。しかし、より高いレベルの `dependency` メソッドを使用することで、より良い方法があります。 +データベースのコネクションプールのようなものを作成して、それを `app.ctx` オブジェクトに格納するのはよくあるパターンです。 これにより、アプリケーション全体でそれらを利用できるようになり、確かに便利です。 しかし、1つの欠点は、型付けされたオブジェクトを扱うことができなくなることです。 これを解決するために、依存性注入を使用することができます。 まず、これまでの例で使ってきたような低レベルの `add_dependency` を使って、そのコンセプトを紹介します。 しかし、より高いレベルの `dependency` メソッドを使用することで、より良い方法があります。 ---:1 - ### `add_dependency` を使った低レベル API -これは [最後の例](#objects-from-the-request) と非常によく似た動作で、ゴールは `Request` オブジェクトから何かを抽出することです。この例では、データベースオブジェクトが `app.ctx` インスタンスに作成され、依存性注入のコンストラクタで返されています。 - -:--:1 - +これは [最後の例](#objects-from-the-request) と非常によく似た動作で、ゴールは `Request` オブジェクトから何かを抽出することです。 この例では、データベースオブジェクトが `app.ctx` インスタンスに作成され、依存性注入のコンストラクタで返されています。 :--:1 ```python class FakeConnection: async def execute(self, query: str, **arguments): @@ -236,19 +238,14 @@ async def handler(request, conn: FakeConnection): $ curl localhost:8000/ result ``` - :--- ---:1 - ### 上位APIを使った`dependency` -依存性インジェクションを追加する際に利用できる実際の *オブジェクト* があるので、より高レベルの `dependency` メソッドを使用することができます。これにより、パターンを書くのがより簡単になります。 - -このメソッドは、アプリケーションインスタンスのライフタイムを通じて存在し、リクエストに依存しないものをインジェクションしたい場合に常に使用する必要があります。サービスやサードパーティークライアント、コネクションプールなどはリクエストに依存しないので、非常に便利です。 - -:--:1 +依存性インジェクションを追加する際に利用できる実際の *オブジェクト* があるので、より高レベルの `dependency` メソッドを使用することができます。 これにより、パターンを書くのがより簡単になります。 +このメソッドは、アプリケーションインスタンスのライフタイムを通じて存在し、リクエストに依存しないものをインジェクションしたい場合に常に使用する必要があります。 サービスやサードパーティークライアント、コネクションプールなどはリクエストに依存しないので、非常に便利です。 :--:1 ```python class FakeConnection: async def execute(self, query: str, **arguments): @@ -270,5 +267,63 @@ async def handler(request, conn: FakeConnection): $ curl localhost:8000/ result ``` +:--- + +## Generic types + +Be carefule when using a [generic type](https://docs.python.org/3/library/typing.html#typing.Generic). The way that Sanic's dependency injection works is by matching the entire type definition. Therefore, `Foo` is not the same as `Foo[str]`. This can be particularly tricky when trying to use the [higher-level `dependency` method](#the-higher-level-api-using-dependency) since the type is inferred. + +---:1 For example, this will **NOT** work as expected since there is no definition for `Test[str]`. :--:1 +```python{12,16} +import typing +from sanic import Sanic, text + +T = typing.TypeVar("T") + + +class Test(typing.Generic[T]): + test: T + + +app = Sanic("testapp") +app.ext.dependency(Test()) + + +@app.get("/") +def test(request, test: Test[str]): + ... +``` +:--- + +---:1 To get this example to work, you will need to add an explicit definition for the type you intend to be injected. :--:1 +```python{13} +import typing +from sanic import Sanic, text + +T = typing.TypeVar("T") + + +class Test(typing.Generic[T]): + test: T + + +app = Sanic("testapp") +_singleton = Test() +app.ext.add_dependency(Test[str], lambda: _singleton) + +@app.get("/") +def test(request, test: Test[str]): + ... +``` +:--- + +## Configuration + +---:1 By default, dependencies will be injected after the `http.routing.after` [signal](../../guide/advanced/signals.md#built-in-signals). Starting in v22.9, you can change this to the `http.handler.before` signal. :--:1 +```python +app.config.INJECTION_SIGNAL = "http.handler.before" +``` :--- + +*Added in v22.9* diff --git a/src/ja/plugins/sanic-ext/logger.md b/src/ja/plugins/sanic-ext/logger.md new file mode 100644 index 0000000000..aa8b18c141 --- /dev/null +++ b/src/ja/plugins/sanic-ext/logger.md @@ -0,0 +1,26 @@ +# Background logger + +The background logger requires both `sanic>=22.9` and `sanic-ext>=22.9`. + +You can setup Sanic Extensions to log all of your messages from a background process. This requires that you not be in [single process mode](../../guide/deployment/manager.md#single-process-mode). + +Logging can sometimes be an expensive operation. By pushing all logging off to a background process, you can potentially gain some performance benefits. + +## Setup + +---:1 Out of the box, the background logger is disabled. You will need to opt-in if you would like to use it. :--:1 +```python +app.config.LOGGING = True +``` +:--- + +## How does it work + +When enabled, the extension will create a `multoprocessing.Queue`. It will remove all handlers on the [default Sanic loggers](../../guide/best-practices/logging.md) and replace them with a [`QueueHandler`](https://docs.python.org/3/library/logging.handlers.html#queuehandler). When a message is logged, it will be pushed into the queue by the handler, and read by the background process to the log handlers that were originally in place. This means you can still configure logging as normal and it should "just work." + +## Configuration + +| Key | Type | Default | Description | +| ------------------------ | ------ | ------- | ------------------------------------------------------- | +| LOGGING | `bool` | `False` | Whether to enable this extension. | +| LOGGING_QUEUE_MAX_SIZE | `int` | `4096` | The max size of the queue before messages are rejected. | diff --git a/src/ja/plugins/sanic-ext/openapi/advanced.md b/src/ja/plugins/sanic-ext/openapi/advanced.md index 23969ec2e3..dc0bda329b 100644 --- a/src/ja/plugins/sanic-ext/openapi/advanced.md +++ b/src/ja/plugins/sanic-ext/openapi/advanced.md @@ -1,6 +1,6 @@ # 高機能 -_ドキュメンテーションは2021年10月に追加予定_ +_Documentation coming EOQ1 2023_ ## CBV diff --git a/src/ja/plugins/sanic-ext/openapi/autodoc.md b/src/ja/plugins/sanic-ext/openapi/autodoc.md index 5afed58d1e..0b930368cb 100644 --- a/src/ja/plugins/sanic-ext/openapi/autodoc.md +++ b/src/ja/plugins/sanic-ext/openapi/autodoc.md @@ -4,9 +4,7 @@ ## 概要と説明 ----:1 -関数のdocstringは、要約と説明を作成するために使用されます。この例からわかるように、docstringは最初の行を要約として、残りの文字列を説明として使用するように解析されます。 -:--:1 +---:1 関数のdocstringは、要約と説明を作成するために使用されます。 この例からわかるように、docstringは最初の行を要約として、残りの文字列を説明として使用するように解析されます。 :--:1 ```python @app.get("/foo") async def handler(request, something: str): @@ -25,7 +23,7 @@ async def handler(request, something: str): "/foo": { "get": { "summary": "これはシンプルなfooハンドラです。", - "description": "docstringの内部で**markdown**を使用することも可能であることを知っておくと
便利です。

- one
- two
- three", + "description": "docstringの内部で**markdown**を使用することも可能であることを知っておくと
便利です。

- いち
- に
- さん", "responses": { "default": { "description": "OK" @@ -40,11 +38,9 @@ async def handler(request, something: str): ## 動作レベルYAML ----:1 -docstringに有効なOpenAPIのYAMLを追加することで、これを拡張することができます。単に `openapi:` を含む行を追加し、その後にあなたのYAMLを追加します。 +---:1 docstringに有効なOpenAPIのYAMLを追加することで、これを拡張することができます。 単に `openapi:` を含む行を追加し、その後にあなたのYAMLを追加します。 -この例で示されている `---` は必要ありません。これは YAML が docstring の個別のセクションであることを視覚的に識別するために存在します。 -:--:1 +この例で示されている `---` は必要ありません。 これは YAML が docstring の個別のセクションであることを視覚的に識別するために存在します。 :--:1 ```python @app.get("/foo") async def handler(request, something: str): @@ -77,8 +73,8 @@ async def handler(request, something: str): "/foo": { "get": { "operationId": "fooDots", - "summary": "これはシンプルなfooハンドラです。", - "description": "いくつかの詳細を追加しておきます。", + "summary": "これはシンプルなfooハンドラです。 ", + "description": "いくつかの詳細を追加しておきます。 ", "tags": [ "いち", "に" @@ -113,18 +109,16 @@ YAML ドキュメントとデコレータの両方が使用された場合、 ## docstringの除外 ----:1 -関数が、ドキュメント内で消費されることを意図していないdocstringを含むことがあります。 +---:1 関数が、ドキュメント内で消費されることを意図していないdocstringを含むことがあります。 **方法 1**: `app.config.OAS_AUTODOC = False`で自動ドキュメンテーションを全体的にオフにする。 -**方法 2**: `@openapi.no_autodoc` デコレータを使用し、単一のハンドラに対してこの機能を無効にする。 -:--:1 +**方法 2**: `@openapi.no_autodoc` デコレータを使用し、単一のハンドラに対してこの機能を無効にする。 :--:1 ```python @app.get("/foo") @openapi.no_autodoc async def handler(request, something: str): - """このdocstringは情報専用。autodocしないでね。 + """このdocstringは情報専用。 autodocしないでね。 """ return text("...") ``` diff --git a/src/ja/plugins/sanic-ext/openapi/basic.md b/src/ja/plugins/sanic-ext/openapi/basic.md index 6672702ff3..ab1d6832fd 100644 --- a/src/ja/plugins/sanic-ext/openapi/basic.md +++ b/src/ja/plugins/sanic-ext/openapi/basic.md @@ -1,12 +1,10 @@ # 基本事項 -::: tip Side note -Sanic ExtensionsのOpenAPI実装は、[`sanic-openapi`](https://github.com/sanic-org/sanic-openapi)のOAS3実装がベースになっています。実際、Sanic Extensionsはそのプロジェクトの後継であり、Sanic Extensionsのリリースと同時にメンテナンスモードに入りました。もしあなたが以前 OAS3 を `sanic-openapi` で使っていたなら、Sanic Extensions にアップグレードするのは簡単なはずです。残念ながら、このプロジェクトは OAS2 仕様をサポートして*いません*。 -::: +::: tip ちょこっとメモ Sanic ExtensionsのOpenAPI実装は、[`sanic-openapi`](https://github.com/sanic-org/sanic-openapi)のOAS3実装がベースになっています。 実際、Sanic Extensionsはそのプロジェクトの後継であり、Sanic Extensionsのリリースと同時にメンテナンスモードに入りました。 もしあなたが以前 OAS3 を `sanic-openapi` で使っていたなら、Sanic Extensions にアップグレードするのは簡単なはずです。 残念ながら、このプロジェクトは OAS2 仕様をサポートして*いません*。 ::: ---:1 -Sanic Extensionsは、[v3.0 OpenAPI仕様](https://swagger.io/specification/)を用いて自動生成されたAPIドキュメントをそのまま提供します。必要なのは、Sanic Extensionsをインスタンス化することによって、アプリケーションを`拡張`することだけです。 +Sanic Extensionsは、[v3.0 OpenAPI仕様](https://swagger.io/specification/)を用いて自動生成されたAPIドキュメントをそのまま提供します。 特別なことは何もする必要がありません。 :--:1 @@ -20,10 +18,13 @@ Extend(app) :--- -これで、既存のアプリケーションに基づいた美しいドキュメントがすでに生成されていることになります: +これを行うと、既存のアプリケーションに基づいてすでに生成された美しいドキュメントが表示されます。 -[http://localhost:8000/docs](http://localhost:8000/docs) +- [http://localhost:8000/docs](http://localhost:8000/docs) +- [http://localhost:8000/docs/redoc](http://localhost:8000/docs/redoc) +- [http://localhost:8000/docs/swagger](http://localhost:8000/docs/swagger) +[設定のセクション](../configuration.md) をチェックして、ドキュメントのルート変更について学びましょう。 2 つの UI のいずれかをオフにして、 `/docs`ルートでどのUIが利用できるかをカスタマイズすることもできます。 ---:1 @@ -38,4 +39,28 @@ Extend(app) ![Swagger UI](~@assets/images/sanic-ext-swagger.png) + +:--- + +## 仕様メタデータの変更 + +---:1 メタデータを変更したい場合は、 `describe` メソッドを使用してください。 + +この例では、`dedent`は`description`引数を使用して、複数行の文字列を少しクリーンにします。 これは必要ありません。任意の文字列値を渡すことができます。 :--:1 +```python +from textwrap import dedent + +app.ext.openapi.describe( + "Testing API", + version="1.2.3", + description=dedent( + """ + # 情報 + こちらは説明です。 いくつかの _拡張的_ な文章を追加するには良い場所ですよ。 + + **markdown** がサポートされています。 + """ + ), +) +``` :--- diff --git a/src/ja/plugins/sanic-ext/openapi/decorators.md b/src/ja/plugins/sanic-ext/openapi/decorators.md index cd6043d702..f0bab2b5ff 100644 --- a/src/ja/plugins/sanic-ext/openapi/decorators.md +++ b/src/ja/plugins/sanic-ext/openapi/decorators.md @@ -1,10 +1,10 @@ # デコレーター -スキーマにコンテンツを追加するための主なメカニズムは、エンドポイントを装飾することです。もしあなたが過去に `sanic-openapi` を使ったことがあるなら、これは馴染みのあるものでしょう。デコレータとその引数は [OAS v3.0 仕様](https://swagger.io/specification/) とほぼ同じです。 +スキーマにコンテンツを追加するための主なメカニズムは、エンドポイントを装飾することです。 もしあなたが過去に `sanic-openapi` を使ったことがあるなら、これは馴染みのあるものでしょう。 デコレータとその引数は [OAS v3.0 仕様](https://swagger.io/specification/) とほぼ同じです。 ---:1 -表示されるすべての例は、ルート定義の周りにラップされます。これらを作成する際には、Sanic ルート・デコレーター (`@app.route` や `@app.get` など) が一番外側のデコレーターであることを確認する必要があります。つまり、最初にそれを置き、その後に以下のデコレータを1つ以上置く必要があります。 +表示されるすべての例は、ルート定義の周りにラップされます。 これらを作成する際には、Sanic ルート・デコレーター (`@app.route` や `@app.get` など) が一番外側のデコレーターであることを確認する必要があります。 つまり、最初にそれを置き、その後に以下のデコレータを1つ以上置く必要があります。 :--:1 @@ -13,8 +13,8 @@ from sanic_ext import openapi @app.get("/path/to/") -@openapi.summary("This is a summary") -@openapi.description("This is a description") +@openapi.summary("これは概要です") +@openapi.description("これは説明です") async def handler(request, somethind: str): ... ``` @@ -23,7 +23,7 @@ async def handler(request, somethind: str): ---:1 -また、以下の例の多くはモデルオブジェクトを参照していることがわかります。シンプルにするために、例では `UserProfile` を使用し、以下のようになります。重要なのは、型付けされたクラスであれば何でも良いということです。`dataclass`や他の種類のモデルオブジェクトであることは容易に想像がつくでしょう。 +また、以下の例の多くはモデルオブジェクトを参照していることがわかります。 シンプルにするために、例では `UserProfile` を使用し、以下のようになります。 重要なのは、型付けされたクラスであれば何でも良いということです。 `dataclass`や他の種類のモデルオブジェクトであることは容易に想像がつくでしょう。 :--:1 @@ -40,33 +40,34 @@ class UserProfile: ### `@opanepi.definition` -`@openapi.definition`デコレーターを使用すると、パス上の操作のすべての部分を一度に定義することができます。これは、他のデコレーターと同じように操作の定義を作成できるオムニバムデコレーターです。複数のフィールド固有のデコレータを使うか、あるいは単一のデコレータを使うかは、 開発者のスタイルによります。 +`@openapi.definition`デコレーターを使用すると、パス上の操作のすべての部分を一度に定義することができます。 これは、他のデコレーターと同じように操作の定義を作成できるオムニバムデコレーターです。 複数のフィールド固有のデコレータを使うか、あるいは単一のデコレータを使うかは、 開発者のスタイルによります。 このフィールドは、操作の定義を容易にするために、意図的に複数のタイプを受け入れる寛容なものとなっています。 -**Arguments** - -| フィールド | タイプ | -| ------------- | --------------------------------------------------------------------------| -| `body` | **dict, RequestBody, *ユーザー定義モデル*** | -| `deprecated` | **bool** | -| `description` | **str** | -| `document` | **str, ExternalDocumentation** | -| `exclude` | **bool** | -| `operation` | **str** | -| `parameter` | **dict, Parameter, *ユーザー定義モデル*, [dict], [Parameter], [*ユーザー定義モデル*]** | +**引数** + +| フィールド | 型 | +| ------------- | ------------------------------------------------------------------ | +| `body` | ***dict, RequestBody, ***ユーザー定義モデル****** | +| `deprecated` | **bool** | +| `description` | **str** | +| `document` | **str, ExternalDocumentation** | +| `exclude` | **bool** | +| `operation` | **str** | +| `parameter` | **str, dict, Parameter, [str], [dict], [Parameter]** | | `response` | **dict, Response, *ユーザー定義モデル*, [dict], [Response], [*ユーザー定義モデル*]** | -| `summary` | **str** | -| `tag` | **str, Tag, [str], [Tag]** | +| `summary` | **str** | +| `tag` | **str, Tag, [str], [Tag]** | +| `secured` | **Dict[str, Any]** | -**Examples** +**例** ---:1 ```python @openapi.definition( body=RequestBody(UserProfile, required=True), - summary="User profile update", + summary="ユーザープロファイルの更新", tag="one", response=[Success, Response(Failure, status=400)], ) @@ -76,7 +77,7 @@ class UserProfile: :--- -*その他の例については、以下の例を参照してください。以下のデコレータの値は、対応するキーワード引数で使用することができます。* +*その他の例については、以下の例を参照してください。 以下のデコレータの値は、対応するキーワード引数で使用することができます。* ## フィールド固有デコレーター @@ -88,7 +89,7 @@ class UserProfile: **引数** -| フィールド | タイプ | +| フィールド | 型 | | ----------- | ---------------------------------- | | **content** | ***ユーザー定義モデル*, dict, RequestBody** | @@ -118,6 +119,10 @@ class UserProfile: @openapi.body(RequestBody(UserProfile)) ``` +```python +@openapi.body({"application/json": {"description": ...}}) +``` + :--- ::: @@ -126,7 +131,7 @@ class UserProfile: **引数** -*None* +*なし* **例** @@ -150,7 +155,7 @@ class UserProfile: **引数** -| フィールド | タイプ | +| フィールド | 型 | | ------ | ------- | | `text` | **str** | @@ -160,13 +165,13 @@ class UserProfile: ```python @openapi.description( - """This is a **description**. + """これは**説明**です。 -## You can use `markdown` +## `markdown`も使えます。 -- And -- make -- lists. +- そして +- リストも +- 作れます。 """ ) ``` @@ -181,7 +186,7 @@ class UserProfile: **引数** -| フィールド | タイプ | +| フィールド | 型 | | ------------- | ------- | | `url` | **str** | | `description` | **str** | @@ -210,7 +215,7 @@ class UserProfile: **引数** -| フィールド | タイプ | デフォルト | +| フィールド | 型 | デフォルト | | ------ | ------------- | -------- | | `flag` | **bool** | **True** | | `bp` | **Blueprint** | | @@ -239,7 +244,7 @@ openapi.exclude(bp=some_blueprint) **引数** -| フィールド | タイプ | +| フィールド | タイプ | | ------ | ------- | | `name` | **str** | @@ -261,11 +266,11 @@ openapi.exclude(bp=some_blueprint) **引数** -| フィールド | タイプ | デフォルト | -| ---------- | ----------------------------------------- | ----------- | -| `name` | **str** | | -| `schema` | ***type*** | **str** | -| `location` | **"query", "header", "path" or "cookie"** | **"query"** | +| フィールド | 型 | デフォルト | +| ---------- | ------------------------------------------- | ----------- | +| `name` | **str** | | +| `schema` | ***type*** | **str** | +| `location` | **"query", "header", "path" もしくは "cookie"** | **"query"** | **例** @@ -299,19 +304,19 @@ openapi.exclude(bp=some_blueprint) `Responce`オブジェクトを使用する場合は、他の引数を渡してはいけません。 -| フィールド | タイプ | -| ------------- | ----------------------------- | -| `status` | **int** | -| `content` | ***type*, *YourModel*, dict** | -| `description` | **str** | -| `response` | **Response** | +| フィールド | 型 | +| ------------- | -------------------------- | +| `status` | **int** | +| `content` | ***型*, *ユーザー定義モデル*, dict** | +| `description` | **str** | +| `response` | **Response** | **例** ---:1 ```python -@openapi.response(200, str, "This is endpoint returns a string") +@openapi.response(200, str, "stringを返すエンドポイントです") ``` ```python @@ -346,7 +351,7 @@ openapi.exclude(bp=some_blueprint) { "application/json": UserProfile, }, - "Description...", + "説明...", ) ``` @@ -358,7 +363,7 @@ openapi.exclude(bp=some_blueprint) **引数** -| フィールド | タイプ | +| フィールド | 型 | | ------ | ------- | | `text` | **str** | @@ -367,7 +372,7 @@ openapi.exclude(bp=some_blueprint) ---:1 ```python -@openapi.summary("This is an endpoint") +@openapi.summary("これはエンドポイントです") ``` :--:1 @@ -380,7 +385,7 @@ openapi.exclude(bp=some_blueprint) **引数** -| フィールド | タイプ | +| フィールド | 型 | | ------- | ------------ | | `*args` | **str, Tag** | @@ -400,4 +405,75 @@ openapi.exclude(bp=some_blueprint) :--- +::: + +:::tab secured + +**引数** + +| フィールド | 型 | +| ----------------- | ----------------------- | +| `*args, **kwargs` | **str, Dict[str, Any]** | + +**例** + +---:1 +```python +@openapi.secured() +``` +:--:1 :--- + +---:1 +```python +@openapi.secured("foo") +``` +:--:1 +```python +@openapi.secured("token1", "token2") +``` +:--- + +---:1 +```python +@openapi.secured({"my_api_key": []}) +``` +:--:1 +```python +@openapi.secured(my_api_key=[]) +``` +:--- + +`add_security_scheme` を使用することを忘れないでください。 詳細は [セキュリティ](./security.md) を参照してください。 + +::: + :::: + +## Integration with Pydantic + +Pydantic models have the ability to [generate OpenAPI schema](https://pydantic-docs.helpmanual.io/usage/schema/). + +---:1 To take advantage of Pydantic model schema generation, pass the output in place of the schema. :--:1 +```python +from sanic import Sanic, json +from sanic_ext import validate, openapi +from pydantic import BaseModel, Field + +class Test(BaseModel): + foo: str = Field(description="Foo Description", example="FOOO") + bar: str = "test" + + +app = Sanic("test") + +@app.get("/") +@openapi.definition( + body={'application/json': Test.schema()}, +) +@validate(json=Test) +async def get(request): + return json({}) +``` +:--- + +*Added in v22.9* diff --git a/src/ja/plugins/sanic-ext/openapi/security.md b/src/ja/plugins/sanic-ext/openapi/security.md index bfc8cc666c..2e0e7fa7e7 100644 --- a/src/ja/plugins/sanic-ext/openapi/security.md +++ b/src/ja/plugins/sanic-ext/openapi/security.md @@ -6,17 +6,15 @@ _セキュリティはv21.12.2以降で利用可能です。_ ## スキームを文書化する ----:1 -まず最初に、1つ以上のセキュリティスキームを定義する必要があります。 こんな感じに定義するのが基本パターンとなります。: +---:1 まず最初に、1つ以上のセキュリティスキームを定義する必要があります。 こんな感じに定義するのが基本パターンとなります。 : ```python add_security_scheme("", "") ``` -The `type` should correspond to one of the allowed security schemes: `"apiKey"`, `"http"`, `"oauth2"`, `"openIdConnect"`. You can then pass appropriate keyword arguments as allowed by the specification. +`type`は、許可されたセキュリティスキームのいずれかに対応する必要があります: `"apiKey"`, `"http"`, `"oauth2"`, `"openIdConnect"` その後、仕様で許可されているキーワード引数を渡すことができます。 -You should consult the [OpenAPI Specification](https://swagger.io/specification/) for details on what values are appropriate. -:--:1 +適切な値の詳細については、 [OpenAPIの仕様](https://swagger.io/specification/) を参照してください。 :--:1 ```python app.ext.openapi.add_security_scheme("api_key", "apiKey") app.ext.openapi.add_security_scheme( @@ -38,9 +36,9 @@ app.ext.openapi.add_security_scheme( "implicit": { "authorizationUrl": "http://example.com/auth", "scopes": { - "on:two": "something", - "three:four": "something else", - "threefour": "something else...", + "on:two": "何か", + "three:four": "それ以外", + "threefour": "それ以外...", }, } }, @@ -50,8 +48,7 @@ app.ext.openapi.add_security_scheme( ## エンドポイントを文書化する ----:1 -2つのオプションがあり、_all_ endpointsをdocumentします。 +---:1 2つのオプションがあり、1つは_全ての_エンドポイントを文章化することです。 :--:1 ```python @@ -60,9 +57,7 @@ app.ext.openapi.secured("token") ``` :--- ----:1 -あるいは、特定の経路だけを記録する。 -:--:1 +---:1 もしくは、特定のルートだけを記録できます。 :--:1 ```python @app.route("/one") async def handler1(request): diff --git a/src/ja/plugins/sanic-ext/openapi/ui.md b/src/ja/plugins/sanic-ext/openapi/ui.md index 3e774831a8..606aead9a9 100644 --- a/src/ja/plugins/sanic-ext/openapi/ui.md +++ b/src/ja/plugins/sanic-ext/openapi/ui.md @@ -1,6 +1,6 @@ # UI -Sanic Extensionsには、RedocとSwaggerの両方のインターフェイスが付属しています。どちらかを使うか、両方を使うかを選択することができます。以下のエンドポイントが自動でセットアップされており、素の `/docs` はRedocを表しています。 +Sanic Extensionsには、RedocとSwaggerの両方のインターフェイスが付属しています。 どちらかを使うか、両方を使うかを選択することができます。 以下のエンドポイントが自動でセットアップされており、素の `/docs` はRedocを表しています。 - `/docs` - `/docs/openapi.json` @@ -10,17 +10,17 @@ Sanic Extensionsには、RedocとSwaggerの両方のインターフェイスが ## 設定オプション -| **キー** | **タイプ** | **デフォルト** | **説明** | -| -------------------------- | --------------- | ------------------- | ------------------------------------------------------------ | -| `OAS_IGNORE_HEAD` | `bool` | `True` | `HEAD`エンドポイントを表示するかどうかを指定する。 | -| `OAS_IGNORE_OPTIONS` | `bool` | `True` | `OPTIONS` 端末を表示するかどうかを指定する。 | -| `OAS_PATH_TO_REDOC_HTML` | `Optional[str]` | `None` | デフォルトの Redoc HTML を上書きするための HTML へのパス。 | -| `OAS_PATH_TO_SWAGGER_HTML` | `Optional[str]` | `None` | デフォルトの Swagger HTML をオーバーライドするための HTML へのパス。 | -| `OAS_UI_DEFAULT` | `Optional[str]` | `"redoc"` | `redoc`または `swagger` に設定することができ、ベースルートに表示するUIを制御します。`None` に設定すると、ベースルートは設定されません。| -| `OAS_UI_REDOC` | `bool` | `True` | Redoc UI を有効にするかどうか。 | -| `OAS_UI_SWAGGER` | `bool` | `True` | Swagger UI を使用可能にするかどうか。 | -| `OAS_URI_TO_CONFIG` | `str` | `"/openapi-config"` | Swagger が使用する OpenAPI コンフィグへの URI パス | -| `OAS_URI_TO_JSON` | `str` | `"/openapi.json"` | JSON ドキュメントへの URI パス。 | -| `OAS_URI_TO_REDOC` | `str` | `"/redoc"` | Redoc への URI パス。 | -| `OAS_URI_TO_SWAGGER` | `str` | `"/swagger"` | Swagger への URI パス。 | -| `OAS_URL_PREFIX` | `str` | `"/docs"` | Blueprint for OpenAPI ドキュメントに使用する URL のプレフィックス。 | +| **キー** | **型** | **デフォルト** | **説明** | +| -------------------------- | --------------- | ------------------- | ---------------------------------------------------------------------------------------------------------- | +| `OAS_IGNORE_HEAD` | `bool` | `True` | `HEAD`エンドポイントを表示するかどうかを指定する。 | +| `OAS_IGNORE_OPTIONS` | `bool` | `True` | `OPTIONS` 端末を表示するかどうかを指定する。 | +| `OAS_PATH_TO_REDOC_HTML` | `Optional[str]` | `None` | デフォルトの Redoc HTML を上書きするための HTML へのパス。 | +| `OAS_PATH_TO_SWAGGER_HTML` | `Optional[str]` | `None` | デフォルトの Swagger HTML をオーバーライドするための HTML へのパス。 | +| `OAS_UI_DEFAULT` | `Optional[str]` | `"redoc"` | `redoc`または `swagger` に設定することができ、ベースルートに表示するUIを制御します。 ベースルート上に表示する UI を制御します。 `None` に設定すると、ベースルートは設定されません。 | +| `OAS_UI_REDOC` | `bool` | `True` | Redoc UI を有効にするかどうか。 | +| `OAS_UI_SWAGGER` | `bool` | `True` | Swagger UI を使用可能にするかどうか。 | +| `OAS_URI_TO_CONFIG` | `str` | `"/openapi-config"` | Swagger が使用する OpenAPI コンフィグへの URI パス | +| `OAS_URI_TO_JSON` | `str` | `"/openapi.json"` | JSON ドキュメントへの URI パス。 | +| `OAS_URI_TO_REDOC` | `str` | `"/redoc"` | Redoc への URI パス。 | +| `OAS_URI_TO_SWAGGER` | `str` | `"/swagger"` | Swagger への URI パス。 | +| `OAS_URL_PREFIX` | `str` | `"/docs"` | Blueprint for OpenAPI ドキュメントに使用する URL のプレフィックス。 | diff --git a/src/ja/plugins/sanic-ext/templating.md b/src/ja/plugins/sanic-ext/templating.md new file mode 100644 index 0000000000..210a3a0f57 --- /dev/null +++ b/src/ja/plugins/sanic-ext/templating.md @@ -0,0 +1,132 @@ +# テンプレートの使用 + +Sanic Extensionsは、テンプレートをルートハンドラに簡単に統合するのに役立ちます。 + + +## 依存関係 + +**現在、 [Jinja](https://github.com/pallets/jinja/) のみサポートしています。** + +テンプレートの作成に慣れていない場合は、まず [Jinjaドキュメント](https://jinja.palletsprojects.com/en/3.1.x/) を読んでください。 + +Sanic Extensionsは、Jinjaが環境にインストールされている場合、自動的にセットアップしてロードします。 したがって、必要なセットアップはJinjaのインストールだけです: + +``` +pip install Jinja2 +``` + +## ファイルからテンプレートをレンダリングする + +3つの方法があります: + +1. テンプレートファイルを事前にロードするデコレータを使用する +1. レンダリングされた `HTTPResponse` オブジェクトを返す +1. `LazyResponse` を生成するハイブリッドなパターン + +`./templates/foo.html` というファイルがあるとしましょう。 + +```html + + + + + 僕のwebページ + + + +

Hello, world!!!!

+
    + {% for item in seq %} +
  • {{ item }}
  • + {% endfor %} +
+ + + +``` + +Sanic + Jinjaでどのようにレンダリングできるか見てみましょう。 + +### 方法1 - デコレータ + +---:1 このアプローチの利点は、テンプレートを起動時にあらかじめ定義できることです。 これは、ハンドラで起こるフェッチがより少ないことを意味し、したがって最速の方法になります。 :--:1 +```python +@app.get("/") +@app.ext.template("foo.html") +async def handler(request: Request): + return {"seq": ["one", "two"]} +``` +:--- + +### 方法2 - 戻り値オブジェクト + +---:1 これは、コアSanicの`text`、`json`、`html`、`file`などのパターンを模倣することを意味します。 直接制御しているので、レスポンスオブジェクトに最も深いカスタマイズを可能にします。 他の `HTTPResponse` オブジェクトと同じように、ヘッダー、クッキーなどを制御できます。 :--:1 +```python +from sanic_ext import render + +@app.get("/alt") +async def handler(request: Request): + return await render( + "foo.html", context={"seq": ["three", "four"]}, status=400 + ) +``` +:--- + +### 方法3 - ハイブリッド/遅延型 + +---:1 このアプローチでは、テンプレートはハンドラ内ではなく、前面に定義されます(パフォーマンスのため)。 次に、 `render` 関数は、デコレータ内で適切な `HTTPResponse` を構築するために使用できる `LazyResponse` を返します。 :--:1 +```python +from sanic_ext import render + +@app.get("/") +@app.ext.template("foo.html") +async def handler(request: Request): + return await render(context={"seq": ["five", "six"]}, status=400) +``` +:--- + +## 文字列からテンプレートをレンダリングする + +---:1 HTMLファイルから_読み取らない_で、Pythonコードの中でテンプレートを書きたい(もしくは生成したい)ことがあります。 この場合でも、上記の `render` 関数を使用することができます。 `template_source` を使用するだけです。 :--:1 +```python +from sanic_ext import render +from textwrap import dedent + +@app.get("/") +async def handler(request): + template = dedent(""" + + + + + 僕のWebページ + + + +

こんにちは、世界!!!!

+
    + {% for item in seq %} +
  • {{ item }}
  • + {% endfor %} +
+ + + + """) + return await render( + template_source=template, + context={"seq": ["three", "four"]}, + app=app, + ) +``` +:--- + +::: tip この例では、 `textwrap.dedent` を使用して、マルチライン文字列の各行の先頭の空白を削除します。 必要ではありませんが、コードと生成されたソースの両方をきれいに保つためにちょうどいいタッチです。 ::: + +## 開発とオートリロード + +オートリロードがオンになっている場合、テンプレートファイルへの変更によりサーバーの再読み込みが実行されます。 + +## 設定 + +`templating_enable_async` および `templating_path_to_templates` を [設定](./configuration.md#settings) で参照してください。 diff --git a/src/ja/plugins/sanic-ext/validation.md b/src/ja/plugins/sanic-ext/validation.md index 0e8eb44554..7fef7115f4 100644 --- a/src/ja/plugins/sanic-ext/validation.md +++ b/src/ja/plugins/sanic-ext/validation.md @@ -1,16 +1,16 @@ -# バリデーション +# 検証 -Webアプリケーションで最もよく実装される機能の1つが、ユーザー入力の検証です。明らかな理由により、これはセキュリティ上の問題であるだけでなく、単なるグッドプラクティスでもあります。データが期待通りのものであることを確認し、そうでない場合は `400` 応答を投げるようにしたいものです。 +Webアプリケーションで最もよく実装される機能の1つが、ユーザー入力の検証です。 明らかな理由により、これはセキュリティ上の問題であるだけでなく、単なるグッドプラクティスでもあります。 データが期待通りのものであることを確認し、そうでない場合は `400` 応答を投げるようにしたいものです。 -## インプリメンテーション +## 実装 ### データクラスによる検証 -[データクラス](https://docs.python.org/3/library/dataclasses.html) の導入により、Pythonは定義されたスキーマを満たすオブジェクトをとても簡単に作成することができるようになりました。しかし、標準ライブラリは型チェックの検証のみをサポートし、実行時の検証はサポート**していません**。Sanic Extensionsは`dataclasses`を使って、入力されたリクエストに対して実行時の検証を行う機能を追加します。 +[データクラス](https://docs.python.org/3/library/dataclasses.html) の導入により、Pythonは定義されたスキーマを満たすオブジェクトをとても簡単に作成することができるようになりました。 しかし、標準ライブラリは型チェックの検証のみをサポートし、実行時の検証はサポート**していません**。 Sanic Extensions adds the ability to do runtime validations on incoming requests using `dataclasses` out of the box. `pydantic` または `attrs` がインストールされている場合は、それらのライブラリのいずれかを使用することもできます。 ---:1 -まず、モデルを定義する。 +まず、モデルを定義します。 :--:1 @@ -24,7 +24,7 @@ class SearchParams: ---:1 -そして、ルートに添付。 +そして、それをルートに添付してください。 :--:1 @@ -41,7 +41,7 @@ async def handler(request, query: SearchParams): ---:1 -これで、受信したリクエストのバリデーションが行われたはずです。 +これで、受信したリクエストの検証が行われるはずです。 :--:1 @@ -60,15 +60,12 @@ $ curl localhost:8000/search\?q=python ### Pydanticを使ったバリデーション -::: warning -現在、PydanticモデルをサポートしているのはJSONボディの検証のみです。 -::: -Pydanticモデルの使用もできます。 +Pydanticモデルも使用できます。 ---:1 -まず、モデルを定義する。 +まず、モデルを定義します。 :--:1 @@ -82,7 +79,7 @@ class Person(BaseModel): ---:1 -そして、ルートに添付。 +そして、それをルートに添付してください。 :--:1 @@ -98,7 +95,7 @@ async def handler(request, body: Person): ---:1 -これで、受信したリクエストのバリデーションが行われたはずです。 +これで、受信したリクエストの検証が行われるはずです。 :--:1 @@ -109,12 +106,61 @@ $ curl localhost:8000/person -d '{"name": "Alice", "age": 21}' -X POST :--- -## 何が検証できるのか? +### Attrsによる検証 + + +Attrsも使用できます。 + +---:1 + +まず、モデルを定義します。 + +:--:1 + +```python +@attrs.define +class Person: + name: str + age: int + +``` -`validate` デコレータを使用すると、3つの場所から入力されたユーザーデータを検証することができます。JSON の本文(`request.json`)、フォームの本文(`request.form`)、そしてクエリパラメータ(`request.args`) です。 +:--- ---:1 -予想通り、デコレータのキーワード引数を使ってモデルをアタッチすることができます。 + +そして、それをルートに添付してください。 + +:--:1 + +```python +from sanic_ext import validate + +@app.post("/person") +@validate(json=Person) +async def handler(request, body: Person): + return json(attrs.asdict(body)) +``` +:--- + +---:1 + +これで、受信したリクエストの検証が行われるはずです。 + +:--:1 + +``` +$ curl localhost:8000/person -d '{"name": "Alice", "age": 21}' -X POST +{"name":"Alice","age":21} +``` + +:--- + +## 何が検証できるのか? + +The `validate` decorator can be used to validate incoming user data from three places: JSON body data (`request.json`), form body data (`request.form`), and query parameters (`request.args`). + +---:1 あなたが期待するように、デコレータのキーワード引数を使用してモデルを添付することができます。 :--:1 ```python diff --git a/src/ja/plugins/sanic-testing/clients.md b/src/ja/plugins/sanic-testing/clients.md index 40912aa10d..3c8db52f4b 100644 --- a/src/ja/plugins/sanic-testing/clients.md +++ b/src/ja/plugins/sanic-testing/clients.md @@ -4,37 +4,34 @@ ## 通常同期クライアント: `SanicTestClient` -`SanicTestClient` は、あなたのローカルネットワーク上で実際のバージョンの Sanic Server を実行し、テストを実行します。エンドポイントを呼び出すたびに、アプリケーションのバージョンがスピンアップされ、ホスト OS 上のソケットにバインドされます。そして、`httpx`を使用して、そのアプリケーションに直接呼び出しを行います。 +`SanicTestClient` は、あなたのローカルネットワーク上で実際のバージョンの Sanic Server を実行し、テストを実行します。 エンドポイントを呼び出すたびに、アプリケーションのバージョンがスピンアップされ、ホスト OS 上のソケットにバインドされます。 そして、`httpx`を使用して、そのアプリケーションに直接呼び出しを行います。 これは Sanic アプリケーションがテストされる典型的な方法です。 ----:1 -Sanic Testing をインストールすると、通常の `SanicTestClient` をそのまま使用できるようになります。これは、Sanicが縁の下であなたのために裏の働きを行うからです。 -:--: +---:1 Sanic Testing をインストールすると、通常の `SanicTestClient` をそのまま使用できるようになります。 これは、Sanicが縁の下であなたのために裏の働きを行うからです。 :--: ```python app.test_client.get("/path/to/endpoint") ``` :--- ----:1 -しかし、クライアントを自分でインスタンス化することが望ましいと思われるかもしれません。 -:--: +---:1 しかし、クライアントを自分でインスタンス化することが望ましいと思われるかもしれません。 :--: ```python from sanic_testing.testing import SanicTestClient + test_client = SanicTestClient(app) test_client.get("/path/to/endpoint") ``` :--- ----:1 -テストクライアントを起動するための第三の選択肢は、 `TestManager` を使用することです。これは、`SanicTestClient` と `SanicASGITestClient` の両方をセットアップするための便利なオブジェクトです。 +---:1 テストクライアントを起動するための第三の選択肢は、 `TestManager` を使用することです。 これは、`SanicTestClient` と `SanicASGITestClient` の両方をセットアップするための便利なオブジェクトです。 :--: ```python from sanic_testing import TestManager + mgr = TestManager(app) app.test_client.get("/path/to/endpoint") -# or +# もしくは mgr.test_client.get("/path/to/endpoint") ``` :--- @@ -51,7 +48,7 @@ mgr.test_client.get("/path/to/endpoint") - `SanicTestClient.websocket` - `SanicTestClient.request` -これらのメソッドは、 `httpx` を使用するときと *ほとんど* 同じように使用することができます。`httpx` に渡すような引数はすべて受け入れられますが、 **ひとつだけ注意点があります** 。もし、 `test_client.request` を使用していて、HTTPメソッドを手動で指定したい場合は、以下のようにし、`http_method`を使用します。 +これらのメソッドは、 `httpx` を使用するときと *ほとんど* 同じように使用することができます。 `httpx` に渡すような引数はすべて受け入れられますが、 **ひとつだけ注意点があります** 。 もし、 `test_client.request` を使用していて、HTTPメソッドを手動で指定したい場合は、以下のようにし、`http_method`を使用します。 ```python test_client.request("/path/to/endpoint", http_method="get") @@ -59,11 +56,9 @@ test_client.request("/path/to/endpoint", http_method="get") ## ASGI非同期クライアント: `SanicASGITestClient` -リクエストごとにサーバーを起動する `SanicTestClient` とは異なり、`SanicASGITestClient` はそうではありません。その代わり、`httpx` ライブラリを利用して、Sanic を ASGI アプリケーションとして実行し、内部に到達してルートハンドラを実行する。 +リクエストごとにサーバーを起動する `SanicTestClient` とは異なり、`SanicASGITestClient` はそうしません。 代わりに、 `httpx` ライブラリを利用して、SanicをASGIアプリケーションとして実行し、内部からルートハンドラを実行します。 ----:1 -このテストクライアントは、`SanicTestClient` と同じメソッドを提供し、一般的に動作します。唯一の違いは、各コールに `await` を追加する必要があることです。 -:--: +---:1 このテストクライアントは、`SanicTestClient` と全て同じメソッドを提供し、基本的に同じ動作をします。 唯一の違いは、各コールに `await` を追加する必要があることです。 :--: ```python await app.test_client.get("/path/to/endpoint") ``` @@ -71,36 +66,33 @@ await app.test_client.get("/path/to/endpoint") `SanicASGITestClient` は `SanicTestClient` と全く同じ3つの方法で使用することができます。 -::: tip Note -`SanicASGITestClient` は ASGI アプリケーションにのみ使用する必要はありません。同様に、`SanicTestClient` は、同期エンドポイントのみをテストする必要はありません。これらのクライアントはどちらも *あらゆる* Sanic アプリケーションをテストすることが可能です。 -::: +::: tip メモ `SanicASGITestClient` はASGIアプリケーションにのみ使用する必要はありません。 同様に、`SanicTestClient` は、同期エンドポイントのみをテストする必要はありません。 これらのクライアントはどちらも *あらゆる* Sanic アプリケーションをテストすることが可能です。 ::: ## 永続的なサービスクライアント: `ReusableClient` -このクライアントは `SanicTestClient` と同様の前提で動作し、アプリケーションのインスタンスを立ち上げ、実際に HTTP リクエストを送信します。しかし、`SanicTestClient` とは異なり、`ReusableClient` を使用する場合は、アプリケーションのライフサイクルを制御することができます。 +このクライアントは `SanicTestClient` と同様の前提で動作し、アプリケーションのインスタンスを立ち上げ、実際に HTTP リクエストを送信します。 しかし、`SanicTestClient` とは異なり、`ReusableClient` を使用する場合は、アプリケーションのライフサイクルを制御することができます。 -つまり、すべてのリクエストで新しい Web サーバーが起動されるわけではありません。その代わり、必要に応じてサーバーを起動・停止し、同じインスタンスに対して複数のリクエストを行うことができます。 +つまり、すべてのリクエストで新しい Web サーバーが起動されるわけではありません。 その代わり、必要に応じてサーバーを起動・停止し、同じインスタンスに対して複数のリクエストを行うことができます。 ----:1 -他の2つのクライアントとは異なり、このクライアントは使用するためにインスタンス化**しなければなりません**。 -:--: +---:1 他の2つのクライアントとは異なり、このクライアントは使用するためにインスタンス化**しなければなりません**。 :--: ```python from sanic_testing.reusable import ReusableClient + client = ReusableClient(app) ``` :--- ----:1 -作成したら、クライアントをコンテキスト・マネージャの内部で使用することになります。マネージャの範囲外に出ると、サーバはシャットダウンします。 -:--: +---:1 作成したら、クライアントをコンテキスト・マネージャの内部で使用することになります。 マネージャの範囲外に出ると、サーバはシャットダウンします。 :--: ```python from sanic_testing.reusable import ReusableClient + def test_multiple_endpoints_on_same_server(app): client = ReusableClient(app) with client: _, response = client.get("/path/to/1") assert response.status == 200 + _, response = client.get("/path/to/2") assert response.status == 200 ``` diff --git a/src/ja/plugins/sanic-testing/getting-started.md b/src/ja/plugins/sanic-testing/getting-started.md index deb77c504c..588acb5931 100644 --- a/src/ja/plugins/sanic-testing/getting-started.md +++ b/src/ja/plugins/sanic-testing/getting-started.md @@ -1,6 +1,6 @@ # スタートアップ -Sanic Testing は Sanic の *公式* テストクライアントです。主な用途は、Sanicプロジェクト自体のテストを強化することです。しかし、それはまた、あなたのAPIテストを素早く立ち上げ、実行するための使いやすいクライアントとして意図されています。 +Sanic Testing は Sanic の *公式* テストクライアントです。 主な用途は、Sanicプロジェクト自体のテストを強化することです。 しかし、それはまた、あなたのAPIテストを素早く立ち上げ、実行するための使いやすいクライアントとして意図されています。 ## 最低要件 @@ -29,15 +29,21 @@ pip install sanic-testing ```python import pytest from sanic import Sanic, response + + @pytest.fixture def app(): sanic_app = Sanic("TestSanic") + @sanic_app.get("/") def basic(request): return response.text("foo") + return sanic_app + def test_basic_test_client(app): request, response = app.test_client.get("/") + assert request.method.lower() == "get" assert response.body == b"foo" assert response.status == 200 @@ -51,21 +57,26 @@ def test_basic_test_client(app): pip install pytest-asyncio ``` -その後、非同期テストを作成し、ASGIクライアントを使用することができます。 +その後、非同期テストを作成し、ASGIクライアントを使用することができます: ```python import pytest from sanic import Sanic, response + @pytest.fixture def app(): sanic_app = Sanic(__name__) + @sanic_app.get("/") def basic(request): return response.text("foo") + return sanic_app + @pytest.mark.asyncio async def test_basic_asgi_client(app): request, response = await app.asgi_client.get("/") + assert request.method.lower() == "get" assert response.body == b"foo" assert response.status == 200 diff --git a/src/ko/README.md b/src/ko/README.md index 07d5d10e93..22cb76eb64 100644 --- a/src/ko/README.md +++ b/src/ko/README.md @@ -2,22 +2,28 @@ home: true heroImage: https://raw.githubusercontent.com/huge-success/sanic-assets/master/png/sanic-framework-logo-400x97.png heroText: Build fast. Run Fast. -tagline: 비동기 Python 3.7+ 웹 서버/프레임워크 +tagline: 차세대 비동기 Python 웹 서버/프레임워크 actionText: 시작하기 → actionLink: /ko/guide/ features: -- title: 간단하고 가볍게 - details: 스마트한 기본값과 부풀림이 없는 직관적인 API를 통해 앱을 구축하기 위해 바로 작업을 시작할 수 있습니다. -- title: 제약받지 않고 유연하게 - details: 툴링에 제약을 받지 않고 원하는 방식으로 설계할 수 있습니다. -- title: 성능 및 확장성 - details: 초기부터 속도와 확장성을 주 삼아 설계되었습니다. 크고 작은 웹 애플리케이션을 지원할 준비가 되어있습니다. -- title: 프로덕션 준비 - details: 웹 애플리케이션을 구동할 준비가 된 웹 서버가 기본으로 제공됩니다. -- title: 수백만 명의 신뢰 - details: Sanic은 PyPI에서 전반적으로 가장 인기있는 프레임 워크 중 하나이며 비동기를 지원하는 최고의 프레임워크입니다. -- title: 커뮤니티 중심 - details: 이 프로젝트는 커뮤니티를 위해 커뮤니티에서 유지되고 운영됩니다. + - + title: 간단하고 가볍게 + details: 스마트한 기본값과 부풀림이 없는 직관적인 API를 통해 앱을 구축하기 위해 바로 작업을 시작할 수 있습니다. + - + title: 제약받지 않고 유연하게 + details: 툴링에 제약을 받지 않고 원하는 방식으로 설계할 수 있습니다. + - + title: 성능 및 확장성 + details: 초기부터 속도와 확장성을 주 삼아 설계되었습니다. 크고 작은 웹 애플리케이션을 지원할 준비가 되어있습니다. + - + title: 프로덕션 준비 + details: 웹 애플리케이션을 구동할 준비가 된 웹 서버가 기본으로 제공됩니다. + - + title: 수백만 명의 신뢰 + details: Sanic은 PyPI에서 전반적으로 가장 인기있는 프레임 워크 중 하나이며 비동기를 지원하는 최고의 프레임워크입니다. + - + title: 커뮤니티 중심 + details: 이 프로젝트는 커뮤니티에서 유지되고 커뮤니티를 위해 운영됩니다. pageClass: landing-page logo: false --- diff --git a/src/ko/guide/README.md b/src/ko/guide/README.md index c44485e726..f333f8cf64 100644 --- a/src/ko/guide/README.md +++ b/src/ko/guide/README.md @@ -6,13 +6,13 @@ pageClass: intro Sanic은 빠르게 작동하도록 작성된 Python 3.7+ 웹 서버 및 웹 프레임워크입니다. Python 3.5에 추가된 async / await 구문을 사용할 수 있어 코드가 차단(Blocking)되지 않고 빠릅니다. -| | | -| ------- | ----------------------------------------------------------------------------------------------------------------------- | -| Build | [![Build Status][]][1] [![AppVeyor Build Status][]][2] [![Codecov]][3] | -| Docs | [![Documentation]][4] | -| Package | [![PyPI][]][5] [![PyPI version][]][5] [![PyPI Wheel][]][6] [![Supported implementations][]][6] [![Code style black]][7] | -| Support | [![Forums][]][8] [![Discord][]][9] [![Awesome Sanic List]][10] | -| Stats | [![Downloads][]][11] [![Downloads][12]][11] | +| | | +| ------- | ----------------------------------------------------------------------------------------------------------------------------- | +| Build | [![Build Status][1]][1] [![AppVeyor Build Status][3]][2] [![Codecov]][3] | +| Docs | [![Documentation]][4] | +| Package | [![PyPI][7]][5] [![PyPI version][9]][5] [![PyPI Wheel][11]][6] [![Supported implementations][13]][6] [![Code style black]][7] | +| Support | [![Forums][16]][8] [![Discord][18]][9] [![Awesome Sanic List]][10] | +| Stats | [![Downloads][21]][11] [![Downloads][23]][11] | ## 이게 뭔가요? @@ -25,25 +25,40 @@ Sanic에 뛰어들기 전에, Sanic이 다른 프레임워크들과 다르다는 ## 목표 > 구축, 확장 및 궁극적으로 확장이 쉬운 고성능 HTTP 서버를 시작하고 실행하는 간단한 방법을 제공합니다. - ## 기능들 ---:1 +### Core + - _빠른_ 웹 서버 내장 - 프로덕션 준비 - 뛰어난 확장성 - ASGI 준수 - 간단하고 직관적인 API 디자인 -- 커뮤니티에 의한, 커뮤니티를 위해 +- 커뮤니티에 의한, 커뮤니티를 위해 :--:1 + :--:1 +### Sanic Extensions [[learn more](../plugins/sanic-ext/getting-started.md)] + +- CORS protection +- Template rendering with Jinja +- Dependency injection into route handlers +- OpenAPI documentation with Redoc and/or Swagger +- Predefined, endpoint-specific response serializers +- Request query arguments and body input validation +- Auto create `HEAD`, `OPTIONS`, and `TRACE` endpoints + :--- + + ## 스폰서 [오픈 콜렉티브](https://opencollective.com/sanic-org)를 확인하여 Sanic 자금 지원에 대해 자세히 알아보세요. + ## 커뮤니티에 참가 토론을 위한 메인 채널은 [커뮤니티 포럼](https://community.sanicframework.org/)입니다. 또한 [디스코드 서버](https://discord.gg/FARQzAEMAA)에서 실시간 토론 및 채팅이 가능합니다. @@ -54,30 +69,31 @@ Stackoverflow의 `[sanic]` 태그는 프로젝트 유지보수자들에 의해 [ 우리는 새로운 기여를 받을 때마다 기쁩니다. 저희는 [기여하려는 사용자들에게 유용하도록 이슈에 마크를 해두었습니다](https://github.com/sanic-org/sanic/issues?q=is%3Aopen+is%3Aissue+label%3Abeginner), 그리고 [포럼에서의 질문/답변/토론](https://community.sanicframework.org/)을 환영합니다. 저희의 [기여 가이드라인](https://github.com/sanic-org/sanic/blob/master/CONTRIBUTING.rst)을 확인해주세요. -[Build Status]: https://travis-ci.com/sanic-org/sanic.svg?branch=master +## Who we are + + + +[1]: https://travis-ci.com/sanic-org/sanic.svg?branch=master [1]: https://travis-ci.com/sanic-org/sanic -[AppVeyor Build Status]: https://ci.appveyor.com/api/projects/status/d8pt3ids0ynexi8c/branch/master?svg=true +[3]: https://ci.appveyor.com/api/projects/status/d8pt3ids0ynexi8c/branch/master?svg=true [2]: https://ci.appveyor.com/project/sanic-org/sanic -[Codecov]: https://codecov.io/gh/sanic-org/sanic/branch/master/graph/badge.svg [3]: https://codecov.io/gh/sanic-org/sanic -[Documentation]: https://readthedocs.org/projects/sanic/badge/?version=latest [4]: http://sanic.readthedocs.io/en/latest/?badge=latest -[PyPI]: https://img.shields.io/pypi/v/sanic.svg +[7]: https://img.shields.io/pypi/v/sanic.svg [5]: https://pypi.python.org/pypi/sanic/ -[PyPI version]: https://img.shields.io/pypi/pyversions/sanic.svg -[PyPI Wheel]: https://img.shields.io/pypi/wheel/sanic.svg +[9]: https://img.shields.io/pypi/pyversions/sanic.svg +[5]: https://pypi.python.org/pypi/sanic/ +[11]: https://img.shields.io/pypi/wheel/sanic.svg +[6]: https://pypi.python.org/pypi/sanic +[13]: https://img.shields.io/pypi/implementation/sanic.svg [6]: https://pypi.python.org/pypi/sanic -[Supported implementations]: https://img.shields.io/pypi/implementation/sanic.svg -[Code style black]: https://img.shields.io/badge/code%20style-black-000000.svg [7]: https://github.com/ambv/black -[Forums]: https://img.shields.io/badge/forums-community-ff0068.svg +[16]: https://img.shields.io/badge/forums-community-ff0068.svg [8]: https://community.sanicframework.org/ -[Discord]: https://img.shields.io/discord/812221182594121728?logo=discord +[18]: https://img.shields.io/discord/812221182594121728?logo=discord [9]: https://discord.gg/FARQzAEMAA -[Awesome Sanic List]: https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg [10]: https://github.com/mekicha/awesome-sanic -[Downloads]: https://pepy.tech/badge/sanic/month +[21]: https://pepy.tech/badge/sanic/month +[11]: https://pepy.tech/project/sanic +[23]: https://pepy.tech/badge/sanic/week [11]: https://pepy.tech/project/sanic -[12]: https://pepy.tech/badge/sanic/week -[Try CodeStream]: https://alt-images.codestream.com/codestream_logo_sanicorg.png -[99]: https://codestream.com/?utm_source=github&utm_campaign=sanicorg&utm_medium=banner diff --git a/src/ko/guide/advanced/class-based-views.md b/src/ko/guide/advanced/class-based-views.md new file mode 100644 index 0000000000..378ce1910b --- /dev/null +++ b/src/ko/guide/advanced/class-based-views.md @@ -0,0 +1,184 @@ +# Class Based Views + +## Why use them? + +---:1 + +### The problem + +A common pattern when designing an API is to have multiple functionality on the same endpoint that depends upon the HTTP method. + +While both of these options work, they are not good design practices and may be hard to maintain over time as your project grows. :--:1 +```python +@app.get("/foo") +async def foo_get(request): + ... + +@app.post("/foo") +async def foo_post(request): + ... + +@app.put("/foo") +async def foo_put(request): + ... + +@app.route("/bar", methods=["GET", "POST", "PATCH"]) +async def bar(request): + if request.method == "GET": + ... + elif request.method == "POST": + ... + elif request.method == "PATCH": + ... +``` +:--- + +---:1 + +### The solution + +Class-based views are simply classes that implement response behavior to requests. They provide a way to compartmentalize handling of different HTTP request types at the same endpoint. :--:1 +```python +from sanic.views import HTTPMethodView + +class FooBar(HTTPMethodView): + async def get(self, request): + ... + + async def post(self, request): + ... + + async def put(self, request): + ... + +app.add_route(FooBar.as_view(), "/foobar") +``` +:--- + +## Defining a view + +A class-based view should subclass `HTTPMethodView`. You can then implement class methods with the name of the corresponding HTTP method. If a request is received that has no defined method, a `405: Method not allowed` response will be generated. + +---:1 + +To register a class-based view on an endpoint, the `app.add_route` method is used. The first argument should be the defined class with the method `as_view` invoked, and the second should be the URL endpoint. + +The available methods are: + +- get +- post +- put +- patch +- delete +- head +- options :--:1 +```python +from sanic.views import HTTPMethodView +from sanic.response import text + +class SimpleView(HTTPMethodView): + + def get(self, request): + return text("I am get method") + + # You can also use async syntax + async def post(self, request): + return text("I am post method") + + def put(self, request): + return text("I am put method") + + def patch(self, request): + return text("I am patch method") + + def delete(self, request): + return text("I am delete method") + +app.add_route(SimpleView.as_view(), "/") +``` +:--- + +## Path parameters + +---:1 + +You can use path parameters exactly as discussed in [the routing section](/guide/basics/routing.md). :--:1 +```python +class NameView(HTTPMethodView): + + def get(self, request, name): + return text("Hello {}".format(name)) + +app.add_route(NameView.as_view(), "/") +``` +:--- + +## Decorators + +As discussed in [the decorators section](/guide/best-practices/decorators.md), often you will need to add functionality to endpoints with the use of decorators. You have two options with CBV: + +1. Apply to _all_ HTTP methods in the view +2. Apply individually to HTTP methods in the view + +Let's see what the options look like: + +---:1 + +### Apply to all methods + +If you want to add any decorators to the class, you can set the `decorators` class variable. These will be applied to the class when `as_view` is called. :--:1 +```python +class ViewWithDecorator(HTTPMethodView): + decorators = [some_decorator_here] + + def get(self, request, name): + return text("Hello I have a decorator") + + def post(self, request, name): + return text("Hello I also have a decorator") + +app.add_route(ViewWithDecorator.as_view(), "/url") +``` +:--- + +---:1 + +### Apply to individual methods + +But if you just want to decorate some methods and not all methods, you can as shown here. :--:1 +```python +class ViewWithSomeDecorator(HTTPMethodView): + + @staticmethod + @some_decorator_here + def get(request, name): + return text("Hello I have a decorator") + + def post(self, request, name): + return text("Hello I do not have any decorators") + + @some_decorator_here + def patch(self, request, name): + return text("Hello I have a decorator") +``` +:--- + +## Generating a URL +---:1 + +This works just like [generating any other URL](/guide/basics/routing.md#generating-a-url), except that the class name is a part of the endpoint. :--:1 +```python +@app.route("/") +def index(request): + url = app.url_for("SpecialClassView") + return redirect(url) + + +class SpecialClassView(HTTPMethodView): + def get(self, request): + return text("Hello from the Special Class View!") + + +app.add_route(SpecialClassView.as_view(), "/special_class_view") +``` +:--- diff --git a/src/ko/guide/advanced/proxy-headers.md b/src/ko/guide/advanced/proxy-headers.md new file mode 100644 index 0000000000..f682a8626e --- /dev/null +++ b/src/ko/guide/advanced/proxy-headers.md @@ -0,0 +1,435 @@ +# Proxy configuration + +When you use a reverse proxy server (e.g. nginx), the value of `request.ip` will contain the IP of a proxy, typically `127.0.0.1`. Almost always, this is **not** what you will want. + +Sanic may be configured to use proxy headers for determining the true client IP, available as `request.remote_addr`. The full external URL is also constructed from header fields _if available_. + +::: tip Heads up +Without proper precautions, a malicious client may use proxy headers to spoof its own IP. To avoid such issues, Sanic does not use any proxy headers unless explicitly enabled. +::: + +---:1 + +Services behind reverse proxies must configure one or more of the following [configuration values](/guide/deployment/configuration.md): + +- `FORWARDED_SECRET` +- `REAL_IP_HEADER` +- `PROXIES_COUNT` :--:1 +```python +app.config.FORWARDED_SECRET = "super-duper-secret" +app.config.REAL_IP_HEADER = "CF-Connecting-IP" +app.config.PROXIES_COUNT = 2 +``` +:--- + +## Forwarded header + +In order to use the `Forwarded` header, you should set `app.config.FORWARDED_SECRET` to a value known to the trusted proxy server. The secret is used to securely identify a specific proxy server. + +Sanic ignores any elements without the secret key, and will not even parse the header if no secret is set. + +All other proxy headers are ignored once a trusted forwarded element is found, as it already carries complete information about the client. + +To learn more about the `Forwarded` header, read the related [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded) and [Nginx](https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/) articles. + +## Traditional proxy headers + +### IP Headers + +When your proxy forwards you the IP address in a known header, you can tell Sanic what that is with the `REAL_IP_HEADER` config value. + +### X-Forwarded-For + +This header typically contains a chain of IP addresses through each layer of a proxy. Setting `PROXIES_COUNT` tells Sanic how deep to look to get an actual IP address for the client. This value should equal the _expected_ number of IP addresses in the chain. + +### Other X-headers + +If a client IP is found by one of these methods, Sanic uses the following headers for URL parts: + +- x-forwarded-proto +- x-forwarded-host +- x-forwarded-port +- x-forwarded-path +- x-scheme + +## Examples + +In the following examples, all requests will assume that the endpoint looks like this: +```python +@app.route("/fwd") +async def forwarded(request): + return json( + { + "remote_addr": request.remote_addr, + "scheme": request.scheme, + "server_name": request.server_name, + "server_port": request.server_port, + "forwarded": request.forwarded, + } + ) +``` +---:1 +--- + +##### Example 1 +Without configured FORWARDED_SECRET, x-headers should be respected +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: for=1.1.1.1, for=injected;host=", for="[::2]";proto=https;host=me.tld;path="/app/";secret=mySecret,for=broken;;secret=b0rked, for=127.0.0.3;scheme=http;port=1234' \ + -H "X-Real-IP: 127.0.0.2" \ + -H "X-Forwarded-For: 127.0.1.1" \ + -H "X-Scheme: ws" \ + -H "Host: local.site" | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "127.0.0.2", + "scheme": "ws", + "server_name": "local.site", + "server_port": 80, + "forwarded": { + "for": "127.0.0.2", + "proto": "ws" + } +} +``` +:--- +--- +---:1 + +##### Example 2 +FORWARDED_SECRET now configured +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: for=1.1.1.1, for=injected;host=", for="[::2]";proto=https;host=me.tld;path="/app/";secret=mySecret,for=broken;;secret=b0rked, for=127.0.0.3;scheme=http;port=1234' \ + -H "X-Real-IP: 127.0.0.2" \ + -H "X-Forwarded-For: 127.0.1.1" \ + -H "X-Scheme: ws" \ + -H "Host: local.site" | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "[::2]", + "scheme": "https", + "server_name": "me.tld", + "server_port": 443, + "forwarded": { + "for": "[::2]", + "proto": "https", + "host": "me.tld", + "path": "/app/", + "secret": "mySecret" + } +} +``` +:--- +--- +---:1 + +##### Example 3 +Empty Forwarded header -> use X-headers +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H "X-Real-IP: 127.0.0.2" \ + -H "X-Forwarded-For: 127.0.1.1" \ + -H "X-Scheme: ws" \ + -H "Host: local.site" | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "127.0.0.2", + "scheme": "ws", + "server_name": "local.site", + "server_port": 80, + "forwarded": { + "for": "127.0.0.2", + "proto": "ws" + } +} +``` +:--- +--- +---:1 + +##### Example 4 +Header present but not matching anything +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H "Forwarded: nomatch" | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "", + "scheme": "http", + "server_name": "localhost", + "server_port": 8000, + "forwarded": {} +} + +``` +:--- +--- +---:1 + +##### Example 5 +Forwarded header present but no matching secret -> use X-headers +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H "Forwarded: for=1.1.1.1;secret=x, for=127.0.0.1" \ + -H "X-Real-IP: 127.0.0.2" | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "127.0.0.2", + "scheme": "http", + "server_name": "localhost", + "server_port": 8000, + "forwarded": { + "for": "127.0.0.2" + } +} +``` +:--- +--- +---:1 + +##### Example 6 +Different formatting and hitting both ends of the header +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: Secret="mySecret";For=127.0.0.4;Port=1234' | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "127.0.0.4", + "scheme": "http", + "server_name": "localhost", + "server_port": 1234, + "forwarded": { + "secret": "mySecret", + "for": "127.0.0.4", + "port": 1234 + } +} +``` +:--- +--- +---:1 + +##### Example 7 +Test escapes (modify this if you see anyone implementing quoted-pairs) +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: for=test;quoted="\,x=x;y=\";secret=mySecret' | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "test", + "scheme": "http", + "server_name": "localhost", + "server_port": 8000, + "forwarded": { + "for": "test", + "quoted": "\\,x=x;y=\\", + "secret": "mySecret" + } +} +``` +:--- +--- +---:1 + +##### Example 8 +Secret insulated by malformed field #1 +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: for=test;secret=mySecret;b0rked;proto=wss;' | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "test", + "scheme": "http", + "server_name": "localhost", + "server_port": 8000, + "forwarded": { + "for": "test", + "secret": "mySecret" + } +} +``` +:--- +--- +---:1 + +##### Example 9 +Secret insulated by malformed field #2 +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: for=test;b0rked;secret=mySecret;proto=wss' | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "", + "scheme": "wss", + "server_name": "localhost", + "server_port": 8000, + "forwarded": { + "secret": "mySecret", + "proto": "wss" + } +} +``` +:--- +--- +---:1 + +##### Example 10 +Unexpected termination should not lose existing acceptable values +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: b0rked;secret=mySecret;proto=wss' | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "", + "scheme": "wss", + "server_name": "localhost", + "server_port": 8000, + "forwarded": { + "secret": "mySecret", + "proto": "wss" + } +} +``` +:--- +--- +---:1 + +##### Example 11 +Field normalization +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: PROTO=WSS;BY="CAFE::8000";FOR=unknown;PORT=X;HOST="A:2";PATH="/With%20Spaces%22Quoted%22/sanicApp?key=val";SECRET=mySecret' | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "", + "scheme": "wss", + "server_name": "a", + "server_port": 2, + "forwarded": { + "proto": "wss", + "by": "[cafe::8000]", + "host": "a:2", + "path": "/With Spaces\"Quoted\"/sanicApp?key=val", + "secret": "mySecret" + } +} +``` +:--- +--- +---:1 + +##### Example 12 +Using "by" field as secret +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "_proxySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: for=1.2.3.4; by=_proxySecret' | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "1.2.3.4", + "scheme": "http", + "server_name": "localhost", + "server_port": 8000, + "forwarded": { + "for": "1.2.3.4", + "by": "_proxySecret" + } +} + +``` +:--- diff --git a/src/ko/guide/advanced/signals.md b/src/ko/guide/advanced/signals.md new file mode 100644 index 0000000000..acf934ee91 --- /dev/null +++ b/src/ko/guide/advanced/signals.md @@ -0,0 +1,264 @@ +# Signals + +Signals provide a way for one part of your application to tell another part that something happened. + +```python +@app.signal("user.registration.created") +async def send_registration_email(**context): + await send_email(context["email"], template="registration") + +@app.post("/register") +async def handle_registration(request): + await do_registration(request) + await request.app.dispatch( + "user.registration.created", + context={"email": request.json.email} + }) +``` + +## Adding a signal + +---:1 The API for adding a signal is very similar to adding a route. :--:1 +```python +async def my_signal_handler(): + print("something happened") + +app.add_signal(my_signal_handler, "something.happened.ohmy") +``` +:--- + +---:1 But, perhaps a slightly more convenient method is to use the built-in decorators. :--:1 +```python +@app.signal("something.happened.ohmy") +async def my_signal_handler(): + print("something happened") +``` +:--- + +---:1 If the signal requires conditions, make sure to add them while adding the handler. :--:1 +```python +async def my_signal_handler1(): + print("something happened") + +app.add_signal( + my_signal_handler, + "something.happened.ohmy1", + conditions={"some_condition": "value"} +) + +@app.signal("something.happened.ohmy2", conditions={"some_condition": "value"}) +async def my_signal_handler2(): + print("something happened") +``` +:--- + +---:1 Signals can also be declared on blueprints :--:1 +```python +bp = Blueprint("foo") + +@bp.signal("something.happened.ohmy") +async def my_signal_handler(): + print("something happened") +``` +:--- + +## Built-in signals + +In addition to creating a new signal, there are a number of built-in signals that are dispatched from Sanic itself. These signals exist to provide developers with more opportunities to add functionality into the request and server lifecycles. + +*Added in v21.9* + +---:1 You can attach them just like any other signal to an application or blueprint instance. :--:1 +```python +@app.signal("http.lifecycle.complete") +async def my_signal_handler(conn_info): + print("Connection has been closed") +``` +:--- + +These signals are the signals that are available, along with the arguments that the handlers take, and the conditions that attach (if any). + + +| Event name | Arguments | Conditions | +| -------------------------- | ------------------------------- | --------------------------------------------------------- | +| `http.routing.before` | request | | +| `http.routing.after` | request, route, kwargs, handler | | +| `http.handler.before` | request | | +| `http.handler.after` | request | | +| `http.lifecycle.begin` | conn_info | | +| `http.lifecycle.read_head` | head | | +| `http.lifecycle.request` | request | | +| `http.lifecycle.handle` | request | | +| `http.lifecycle.read_body` | body | | +| `http.lifecycle.exception` | request, exception | | +| `http.lifecycle.response` | request, response | | +| `http.lifecycle.send` | data | | +| `http.lifecycle.complete` | conn_info | | +| `http.middleware.before` | request, response | `{"attach_to": "request"}` or `{"attach_to": "response"}` | +| `http.middleware.after` | request, response | `{"attach_to": "request"}` or `{"attach_to": "response"}` | +| `server.init.before` | app, loop | | +| `server.init.after` | app, loop | | +| `server.shutdown.before` | app, loop | | +| `server.shutdown.after` | app, loop | | + +Version 22.9 added `http.handler.before` and `http.handler.after`. + +---:1 To make using the built-in signals easier, there is an `Enum` object that contains all of the allowed built-ins. With a modern IDE this will help so that you do not need to remember the full list of event names as strings. + +*Added in v21.12* :--:1 +```python +from sanic.signals import Event + +@app.signal(Event.HTTP_LIFECYCLE_COMPLETE) +async def my_signal_handler(conn_info): + print("Connection has been closed") +``` +:--- + +## Events + +---:1 Signals are based off of an _event_. An event, is simply a string in the following pattern: :--:1 +``` +namespace.reference.action +``` +:--- + +::: tip Events must have three parts. If you do not know what to use, try these patterns: + +- `my_app.something.happened` +- `sanic.notice.hello` ::: + +### Event parameters + +---:1 An event can be "dynamic" and declared using the same syntax as [path parameters](../basics/routing.md#path-parameters). This allows matching based upon arbitrary values. :--:1 +```python +@app.signal("foo.bar.") +async def signal_handler(thing): + print(f"[signal_handler] {thing=}") + +@app.get("/") +async def trigger(request): + await app.dispatch("foo.bar.baz") + return response.text("Done.") +``` +:--- + +Checkout [path parameters](../basics/routing.md#path-parameters) for more information on allowed type definitions. + +::: warning Only the third part of an event (the "action") may be dynamic: + +- `foo.bar.` :ok: +- `foo..baz` :x: ::: + +### Waiting + +---:1 In addition to executing a signal handler, your application can wait for an event to be triggered. :--:1 +```python +await app.event("foo.bar.baz") +``` +:--- + +---:1 **IMPORTANT**: waiting is a blocking function. Therefore, you likely will want this to run in a [background task](../basics/tasks.md). :--:1 +```python +async def wait_for_event(app): + while True: + print("> waiting") + await app.event("foo.bar.baz") + print("> event found\n") + +@app.after_server_start +async def after_server_start(app, loop): + app.add_task(wait_for_event(app)) +``` +:--- + +---:1 If your event was defined with a dynamic path, you can use `*` to catch any action. :--:1 +```python +@app.signal("foo.bar.") + +... + +await app.event("foo.bar.*") +``` +:--- + +## Dispatching + +*In the future, Sanic will dispatch some events automatically to assist developers to hook into life cycle events.* + +---:1 Dispatching an event will do two things: + +1. execute any signal handlers defined on the event, and +2. resolve anything that is "waiting" for the event to complete. :--:1 +```python +@app.signal("foo.bar.") +async def foo_bar(thing): + print(f"{thing=}") + +await app.dispatch("foo.bar.baz") +``` +``` +thing=baz +``` +:--- + +### Context + +---:1 Sometimes you may find the need to pass extra information into the signal handler. In our first example above, we wanted our email registration process to have the email address for the user. :--:1 +```python +@app.signal("user.registration.created") +async def send_registration_email(**context): + print(context) + +await app.dispatch( + "user.registration.created", + context={"hello": "world"} +) +``` +``` +{'hello': 'world'} +``` +:--- + +::: tip FYI +Signals are dispatched in a background task. +::: + +### Blueprints + +Dispatching blueprint signals works similar in concept to [middleware](../basics/middleware.md). Anything that is done from the app level, will trickle down to the blueprints. However, dispatching on a blueprint, will only execute the signals that are defined on that blueprint. + +---:1 Perhaps an example is easier to explain: :--:1 +```python +bp = Blueprint("bp") + +app_counter = 0 +bp_counter = 0 + +@app.signal("foo.bar.baz") +def app_signal(): + nonlocal app_counter + app_counter += 1 + +@bp.signal("foo.bar.baz") +def bp_signal(): + nonlocal bp_counter + bp_counter += 1 +``` +:--- + +---:1 Running `app.dispatch("foo.bar.baz")` will execute both signals. :--:1 +```python +await app.dispatch("foo.bar.baz") +assert app_counter == 1 +assert bp_counter == 1 +``` +:--- + +---:1 Running `bp.dispatch("foo.bar.baz")` will execute only the blueprint signal. :--:1 +```python +await bp.dispatch("foo.bar.baz") +assert app_counter == 1 +assert bp_counter == 2 +``` +:--- diff --git a/src/ko/guide/advanced/streaming.md b/src/ko/guide/advanced/streaming.md new file mode 100644 index 0000000000..14e1415be1 --- /dev/null +++ b/src/ko/guide/advanced/streaming.md @@ -0,0 +1,134 @@ +# Streaming + +## Request streaming + +Sanic allows you to stream data sent by the client to begin processing data as the bytes arrive. + +---:1 + +When enabled on an endpoint, you can stream the request body using `await request.stream.read()`. + +That method will return `None` when the body is completed. :--:1 +```python +from sanic.views import stream + +class SimpleView(HTTPMethodView): + @stream + async def post(self, request): + result = "" + while True: + body = await request.stream.read() + if body is None: + break + result += body.decode("utf-8") + return text(result) +``` +:--- + +---:1 + +It also can be enabled with a keyword argument in the decorator... :--:1 +```python +@app.post("/stream", stream=True) +async def handler(request): + ... + body = await request.stream.read() + ... +``` +:--- + +---:1 + +... or the `add_route()` method. :--:1 +```python +bp.add_route( + bp_handler, + "/bp_stream", + methods=["POST"], + stream=True, +) +``` +:--- + +::: tip FYI +Only post, put and patch decorators have stream argument. +::: + +## Response streaming + +---:1 Sanic allows you to stream content to the client. :--:1 + +```python +@app.route("/") +async def test(request): + response = await request.respond(content_type="text/csv") + await response.send("foo,") + await response.send("bar") + + # Optionally, you can explicitly end the stream by calling: + await response.eof() +``` +:--- + +This is useful in situations where you want to stream content to the client that originates in an external service, like a database. For example, you can stream database records to the client with the asynchronous cursor that `asyncpg` provides. + +```python +@app.route("/") +async def index(request): + response = await request.respond() + conn = await asyncpg.connect(database='test') + async with conn.transaction(): + async for record in conn.cursor('SELECT generate_series(0, 10)'): + await response.send(record[0]) +``` + + + +You can explicitly end a stream by calling `await response.eof()`. It a convenience method to replace `await response.send("", True)`. It should be called **one time** *after* your handler has determined that it has nothing left to send back to the client. While it is *optional* to use with Sanic server, if you are running Sanic in ASGI mode, then you **must** explicitly terminate the stream. + +*Calling `eof` became optional in v21.6* + +## File streaming + +---:1 + +Sanic provides `sanic.response.file_stream` function that is useful when you want to send a large file. It returns a `StreamingHTTPResponse` object and will use chunked transfer encoding by default; for this reason Sanic doesn’t add `Content-Length` HTTP header in the response. + +A typical use case might be streaming an video file. :--:1 +```python +@app.route("/mp4") +async def handler_file_stream(request): + return await response.file_stream( + "/path/to/sample.mp4", + chunk_size=1024, + mime_type="application/metalink4+xml", + headers={ + "Content-Disposition": 'Attachment; filename="nicer_name.meta4"', + "Content-Type": "application/metalink4+xml", + }, + ) +``` +:--- + +---:1 + +If you want to use the `Content-Length` header, you can disable chunked transfer encoding and add it manually simply by adding the `Content-Length` header. + +:--:1 +```python +from aiofiles import os as async_os +from sanic.response import file_stream + +@app.route("/") +async def index(request): + file_path = "/srv/www/whatever.png" + + file_stat = await async_os.stat(file_path) + headers = {"Content-Length": str(file_stat.st_size)} + + return await file_stream( + file_path, + headers=headers, + ) +``` +:--- diff --git a/src/ko/guide/advanced/versioning.md b/src/ko/guide/advanced/versioning.md new file mode 100644 index 0000000000..f7b7e08cb7 --- /dev/null +++ b/src/ko/guide/advanced/versioning.md @@ -0,0 +1,146 @@ +# Versioning + +It is standard practice in API building to add versions to your endpoints. This allows you to easily differentiate incompatible endpoints when you try and change your API down the road in a breaking manner. + +Adding a version will add a `/v{version}` url prefix to your endpoints. + +The version can be a `int`, `float`, or `str`. Acceptable values: + +- `1`, `2`, `3` +- `1.1`, `2.25`, `3.0` +- `"1"`, `"v1"`, `"v1.1"` + +## Per route + +---:1 + +You can pass a version number to the routes directly. :--:1 +```python +# /v1/text +@app.route("/text", version=1) +def handle_request(request): + return response.text("Hello world! Version 1") + +# /v2/text +@app.route("/text", version=2) +def handle_request(request): + return response.text("Hello world! Version 2") +``` +:--- + +## Per Blueprint + +---:1 + +You can also pass a version number to the blueprint, which will apply to all routes in that blueprint. :--:1 +```python +bp = Blueprint("test", url_prefix="/foo", version=1) + +# /v1/foo/html +@bp.route("/html") +def handle_request(request): + return response.html("

Hello world!

") +``` +:--- + +## Per Blueprint Group + +---:1 In order to simplify the management of the versioned blueprints, you can provide a version number in the blueprint group. The same will be inherited to all the blueprint grouped under it if the blueprints don't already override the same information with a value specified while creating a blueprint instance. + +When using blueprint groups for managing the versions, the following order is followed to apply the Version prefix to the routes being registered. + +1. Route Level configuration +2. Blueprint level configuration +3. Blueprint Group level configuration + +If we find a more pointed versioning specification, we will pick that over the more generic versioning specification provided under the Blueprint or Blueprint Group :--:1 +```python +from sanic.blueprints import Blueprint +from sanic.response import json + +bp1 = Blueprint( + name="blueprint-1", + url_prefix="/bp1", + version=1.25, +) +bp2 = Blueprint( + name="blueprint-2", + url_prefix="/bp2", +) + +group = Blueprint.group( + [bp1, bp2], + url_prefix="/bp-group", + version="v2", +) + +# GET /v1.25/bp-group/bp1/endpoint-1 +@bp1.get("/endpoint-1") +async def handle_endpoint_1_bp1(request): + return json({"Source": "blueprint-1/endpoint-1"}) + +# GET /v2/bp-group/bp2/endpoint-2 +@bp2.get("/endpoint-1") +async def handle_endpoint_1_bp2(request): + return json({"Source": "blueprint-2/endpoint-1"}) + +# GET /v1/bp-group/bp2/endpoint-2 +@bp2.get("/endpoint-2", version=1) +async def handle_endpoint_2_bp2(request): + return json({"Source": "blueprint-2/endpoint-2"}) +``` +:--- + +## Version prefix + +As seen above, the `version` that is applied to a route is **always** the first segment in the generated URI path. Therefore, to make it possible to add path segments before the version, every place that a `version` argument is passed, you can also pass `version_prefix`. + +The `version_prefix` argument can be defined in: + +- `app.route` and `bp.route` decorators (and all the convenience decorators also) +- `Blueprint` instantiation +- `Blueprint.group` constructor +- `BlueprintGroup` instantiation +- `app.blueprint` registration + +If there are definitions in multiple places, a more specific definition overrides a more general. This list provides that hierarchy. + +The default value of `version_prefix` is `/v`. + +---:1 An often requested feature is to be able to mount versioned routes on `/api`. This can easily be accomplished with `version_prefix`. :--:1 +```python +# /v1/my/path +app.route("/my/path", version=1, version_prefix="/api/v") +``` +:--- + +---:1 Perhaps a more compelling usage is to load all `/api` routes into a single `BlueprintGroup`. :--:1 +```python +# /v1/my/path +app = Sanic(__name__) +v2ip = Blueprint("v2ip", url_prefix="/ip", version=2) +api = Blueprint.group(v2ip, version_prefix="/api/version") + +# /api/version2/ip +@v2ip.get("/") +async def handler(request): + return text(request.ip) + +app.blueprint(api) +``` +:--- + +We can therefore learn that a route's URI is: + +``` +version_prefix + version + url_prefix + URI definition +``` + +::: tip Just like with `url_prefix`, it is possible to define path parameters inside a `version_prefix`. It is perfectly legitimate to do this. Just remember that every route will have that parameter injected into the handler. + +```python +version_prefix="//v" +``` +::: + +*Added in v21.6* diff --git a/src/ko/guide/advanced/websockets.md b/src/ko/guide/advanced/websockets.md new file mode 100644 index 0000000000..bc8976c068 --- /dev/null +++ b/src/ko/guide/advanced/websockets.md @@ -0,0 +1,72 @@ +# Websockets + +Sanic provides an easy to use abstraction on top of [websockets](https://websockets.readthedocs.io/en/stable/). + + +## Routing + +---:1 + +Websocket handlers can be hooked up to the router similar to regular handlers. :--:1 +```python +from sanic import Request, Websocket + +async def feed(request: Request, ws: Websocket): + pass + +app.add_websocket_route(feed, "/feed") +``` +```python +from sanic import Request, Websocket + +@app.websocket("/feed") +async def feed(request: Request, ws: Websocket): + pass +``` +:--- + +## Handler + + +---:1 Typically, a websocket handler will want to hold open a loop. + +It can then use the `send()` and `recv()` methods on the second object injected into the handler. + +This example is a simple endpoint that echos back to the client messages that it receives. :--:1 +```python +from sanic import Request, Websocket + +@app.websocket("/feed") +async def feed(request: Request, ws: Websocket): + while True: + data = "hello!" + print("Sending: " + data) + await ws.send(data) + data = await ws.recv() + print("Received: " + data) +``` +:--- + +---:1 You can simplify your loop by just iterating over the `Websocket` object in a for loop. + +*Added in v22.9* :--:1 +```python +from sanic import Request, Websocket + +@app.websocket("/feed") +async def feed(request: Request, ws: Websocket): + async for msg in ws: + await ws.send(msg) +``` +:--- + + +## Configuration + +See [configuration section](/guide/deployment/configuration.md) for more details, however the defaults are shown below. + +```python +app.config.WEBSOCKET_MAX_SIZE = 2 ** 20 +app.config.WEBSOCKET_PING_INTERVAL = 20 +app.config.WEBSOCKET_PING_TIMEOUT = 20 +``` diff --git a/src/ko/guide/basics/app.md b/src/ko/guide/basics/app.md index 84fddd4d9a..b6112bca7e 100644 --- a/src/ko/guide/basics/app.md +++ b/src/ko/guide/basics/app.md @@ -2,10 +2,7 @@ ## 인스턴스(Instance) ----:1 -가장 기본적인 구성 요소는 `Sanic()` 인스턴스입니다. 꼭 필요한 것은 아니지만 `server.py`라는 파일에서 이를 인스턴스화하는 것이 암묵적인 규칙입니다. -:--:1 - +---:1 가장 기본적인 구성 요소는 `Sanic()` 인스턴스입니다. 꼭 필요한 것은 아니지만 `server.py`라는 파일에서 이를 인스턴스화하는 것이 암묵적인 규칙입니다. :--:1 ```python # /path/to/server.py @@ -13,45 +10,31 @@ from sanic import Sanic app = Sanic("My Hello, world app") ``` - :--- ## 애플리케이션 컨텍스트(Application context) 대부분의 애플리케이션은 코드베이스의 여러 부분에서 데이터 또는 객체를 공유/재사용할 것입니다. 가장 흔한 예로는 DB 연결이 있겠네요. ----:1 -v21.3 이전의 Sanic 버전에서는 일반적으로 애플리케이션 인스턴스에 속성을 추가하는 방식을 사용했습니다. -:--:1 - +---:1 In versions of Sanic prior to v21.3, this was commonly done by attaching an attribute to the application instance :--:1 ```python -# 21.3에서는 사용하지 않는 기능으로 경고를 발생시킵니다. -app = Sanic("MyApp") +# 21.3에서는 사용하지 않는 기능으로 경고를 발생시킵니다. app = Sanic("MyApp") app.db = Database() ``` - :--- ----:1 -하지만 이로 인해 이름 충돌 등의 잠재적인 문제가 발생할 수 있고 [요청 컨텍스트](./request.md#context)객체와 일관성을 유지하기 위해 v21.3에서는 응용 프로그램 수준 컨텍스트 객체를 도입했습니다. -:--:1 - +---:1 하지만 이로 인해 이름 충돌 등의 잠재적인 문제가 발생할 수 있고 [요청 컨텍스트](./request.md#context)객체와 일관성을 유지하기 위해 v21.3에서는 응용 프로그램 수준 컨텍스트 객체를 도입했습니다. :--:1 ```python - -# 객체를 응용프로그램에 추가하는 올바른 방법입니다. -app = Sanic("MyApp") +# 객체를 응용프로그램에 추가하는 올바른 방법입니다. app = Sanic("MyApp") app.ctx.db = Database() ``` - :--- ## 앱 레지스트리(App Registry) ---:1 -Sanic 인스턴스를 인스턴스화하면 Sanic 앱 레지스트리에서 나중에 검색할 수 있습니다. 이 기능은 달리 액세스할 수 없는 위치에서 Sanic 인스턴스에 액세스해야 하는 경우 등에서 유용할 것입니다. -:--:1 - +Sanic 인스턴스를 인스턴스화하면 Sanic 앱 레지스트리에서 나중에 검색할 수 있습니다. 이 기능은 달리 액세스할 수 없는 위치에서 Sanic 인스턴스에 액세스해야 하는 경우 등에서 유용할 것입니다. :--:1 ```python # ./path/to/server.py from sanic import Sanic @@ -63,44 +46,30 @@ from sanic import Sanic app = Sanic.get_app("my_awesome_server") ``` - :--- ---:1 -존재하지 않는 앱에서 `Sanic.get_app'("non-existing")`을 호출하면 기본적으로 `SanicException`이 발생합니다. 대신 메소드가 해당 이름의 새 Sanic 인스턴스를 반환하도록 강제할 수 있습니다. -:--:1 - +존재하지 않는 앱에서 `Sanic.get_app'("non-existing")`을 호출하면 기본적으로 `SanicException`이 발생합니다. 대신 메소드가 해당 이름의 새 Sanic 인스턴스를 반환하도록 강제할 수 있습니다. :--:1 ```python app = Sanic.get_app( "non-existing", force_create=True, ) ``` - :--- ----:1 -등록된 Sanic 인스턴스가 **하나만** 있는 경우 인수 없이 `Sanic.get_app()`을 호출하면 해당 인스턴스가 반환됩니다. -:--:1 - +---:1 등록된 Sanic 인스턴스가 **하나만** 있는 경우 인수 없이 `Sanic.get_app()`을 호출하면 해당 인스턴스가 반환됩니다. :--:1 ```python Sanic("My only app") app = Sanic.get_app() ``` - :--- ## 구성(Configuration) ----:1 - -Sanic은 `Sanic` 인스턴스의 `config` 속성에 구성을 보유합니다. -구성은 **속성** 또는 **딕셔너리** 를 사용하여 구성을 수정할 수 있습니다. - -:--:1 - +Sanic은 `Sanic` 인스턴스의 `config` 속성에 구성을 보유합니다. 구성은 **속성** 또는 **딕셔너리** 를 사용하여 구성을 수정할 수 있습니다. :--:1 ```python app = Sanic('myapp') @@ -114,34 +83,29 @@ db_settings = { } app.config.update(db_settings) ``` - :--- -::: tip 주의 사항 -구성 키는 _대문자_ 여야 합니다. 하지만 이건 거의 암묵적인 규칙일 뿐이며 소문자도 대부분의 경우에는 잘 작동할 것입니다. - -```py -app.config.GOOD = "yay!" -app.config.bad = "boo" +::: tip 주의 사항 구성 키는 _대문자_ 여야 합니다. 하지만 이건 거의 암묵적인 규칙일 뿐이며 소문자도 대부분의 경우에는 잘 작동할 것입니다. +``` +app.config.GOOD = "yay!" app.config.bad = "boo" ``` - ::: 나중에 [구성에 대한 자세한 내용](/guide/deployment/configuration.md)에서 자세히 소개됩니다. + ## 커스터마이징(Customization) -Sanic 애플리케이션 인스턴스는 인스턴스화 할 경우 다양한 방식으로 애플리케이션 요구사항에 맞게 사용자 정의할 수 있습니다. +The Sanic application instance can be customized for your application needs in a variety of ways at instantiation. ### 사용자 정의 구성(Custom configuration) - ---:1 -이 가장 간단한 형태의 사용자 지정 구성은 Sanic 응용 프로그램 인스턴스에 직접 객체를 전달하는 것입니다. +Sanic 애플리케이션 인스턴스는 인스턴스화 할 경우 다양한 방식으로 애플리케이션 요구사항에 맞게 사용자 정의할 수 있습니다. 사용자 지정 구성 객체를 생성하는 경우 Sanic `Config` 옵션을 하위 클래스로 지정하여 해당 동작을 상속하는 것이 *매우* 권장됩니다. 속성을 추가하거나 고유한 사용자 정의 로직을 추가하는 데 이 옵션을 사용할 수 있습니다. -:--:1 +*Added in v21.6* :--:1 ```python from sanic.config import Config @@ -152,9 +116,7 @@ app = Sanic(..., config=MyConfig()) ``` :--- ----:1 -이 기능의 유용한 예는 [지원하는](../deployment/configuration.md#using-sanic-update-config)다른 형식으로 구성 파일을 사용하려는 경우입니다. -:--:1 +---:1 이 기능의 유용한 예는 [지원하는](../deployment/configuration.md#using-sanic-update-config)다른 형식으로 구성 파일을 사용하려는 경우입니다. :--:1 ```python from sanic import Sanic, text from sanic.config import Config @@ -188,11 +150,9 @@ app = Sanic(toml_config.APP_NAME, config=toml_config) ``` :--- ### 사용자 정의 컨텍스트(Custom context) ----:1 +---:1 By default, the application context is a [`SimpleNamespace()`](https://docs.python.org/3/library/types.html#types.SimpleNamespace) that allows you to set any properties you want on it. 그러나 대신 객체를 전달할 수도 있습니다. -기본적으로 애플리케이션 컨텍스트는 원하는 속성을 설정할 수 있는 [`SimpleNamespace()`](https://docs.python.org/3/library/types.html#types.SimpleNamespace)입니다. 그러나 대신 객체를 전달할 수도 있습니다. - -:--:1 +*Added in v21.6* :--:1 ```python app = Sanic(..., ctx=1) ``` @@ -208,18 +168,14 @@ class MyContext: app = Sanic(..., ctx=MyContext()) ``` :--- - ### 사용자 정의 요청(Custom requests) - ----:1 -때로는 자신만의 `Request` 클래스를 갖고 Sanic에게 기본값 대신 사용하도록 지시하는 것이 도움이 됩니다. 한 가지 예는 기본 `request.id` 생성기를 수정하려는 경우입니다. +---:1 때로는 자신만의 `Request` 클래스를 갖고 Sanic에게 기본값 대신 사용하도록 지시하는 것이 도움이 됩니다. 한 가지 예는 기본 `request.id` 생성기를 수정하려는 경우입니다. ::: tip 중요 클래스의 인스턴스가 아닌 *class*를 전달한다는 것을 기억하는 것이 중요합니다. -::: -:--:1 +::: :--:1 ```python import time @@ -243,9 +199,7 @@ async def handler(request): ### 사용자 정의 에러 핸들러(Custom error handler) ----:1 -자세한 내용은 [exception handling](../best-practices/exceptions.md#custom-error-handling)를 참조하세요. -:--:1 +---:1 자세한 내용은 [exception handling](../best-practices/exceptions.md#custom-error-handling)를 참조하세요. :--:1 ```python from sanic.handlers import ErrorHandler @@ -259,3 +213,33 @@ app = Sanic(..., error_handler=CustomErrorHandler()) ``` :--- +### Custom dumps function + +---:1 It may sometimes be necessary or desirable to provide a custom function that serializes an object to JSON data. :--:1 +```python +import ujson + +dumps = partial(ujson.dumps, escape_forward_slashes=False) +app = Sanic(__name__, dumps=dumps) +``` +:--- + +---:1 Or, perhaps use another library or create your own. :--:1 +```python +from orjson import dumps + +app = Sanic(__name__, dumps=dumps) +``` +:--- + +### Custom loads function + +---:1 Similar to `dumps`, you can also provide a custom function for deserializing data. + +*Added in v22.9* :--:1 +```python +from orjson import loads + +app = Sanic(__name__, loads=loads) +``` +:--- diff --git a/src/ko/guide/basics/cookies.md b/src/ko/guide/basics/cookies.md index 24c8cdacdc..c030cd7757 100644 --- a/src/ko/guide/basics/cookies.md +++ b/src/ko/guide/basics/cookies.md @@ -4,27 +4,21 @@ ---:1 -쿠키는 `Request` 객체의 `cookies` 사전(dict)을 통해 액세스 할 수 있습니다. - -:--:1 - +쿠키는 `Request` 객체의 `cookies` 사전(dict)을 통해 액세스 할 수 있습니다. :--:1 ```python @app.route("/cookie") async def test(request): test_cookie = request.cookies.get("test") return text("Test cookie: {}".format(test_cookie)) ``` - :--- + ## 쓰기(Writing) ---:1 -응답을 반환할 때 `Response`객체인 `response.cookies`에 쿠키를 설정할 수 있습니다. 이 객체는 응답 헤더를 자동으로 작성하는 특별한 종류의 사전(dict)인 `CookieJar`의 인스턴스입니다. - -:--:1 - +응답을 반환할 때 `Response`객체인 `response.cookies`에 쿠키를 설정할 수 있습니다. 이 객체는 응답 헤더를 자동으로 작성하는 특별한 종류의 사전(dict)인 `CookieJar`의 인스턴스입니다. :--:1 ```python @app.route("/cookie") async def test(request): @@ -34,10 +28,9 @@ async def test(request): response.cookies["test"]["httponly"] = True return response ``` - :--- -응답 쿠키는 사전 값과 같이 설정할 수 있으며 다음 매개변수들을 사용할 수 있습니다.: +:--:1 - `expires: datetime` - 클라이언트의 브라우저에서 쿠키가 만료되는 시간입니다. - `path: str` - 이 쿠키가 적용되는 URL의 하위 집합입니다. 기본값은 `/`입니다. @@ -52,10 +45,7 @@ async def test(request): ---:1 -쿠키는 의미적으로 또는 명시적으로 제거할 수 있습니다. - -:--:1 - +Cookies can be removed semantically or explicitly. :--:1 ```python @app.route("/cookie") async def test(request): @@ -76,9 +66,8 @@ async def test(request): return response ``` - :--- ## 먹기(Eating) -저는 쿠키를 좋아합니다 :cookie: +쿠키는 의미적으로 또는 명시적으로 제거할 수 있습니다. diff --git a/src/ko/guide/basics/handlers.md b/src/ko/guide/basics/handlers.md index 5fc4ad53ea..d4447f3441 100644 --- a/src/ko/guide/basics/handlers.md +++ b/src/ko/guide/basics/handlers.md @@ -4,16 +4,15 @@ Sanic에서 핸들러는 `Request` 인스턴스를 인수로 취하는 모든 호출 가능한 함수(Callable) 입니다, 그리고 `HTTP Response` 인스턴스 또는 같은 작업을 수행하는 코루틴(coroutine)을 반환합니다. + + ---:1 어라? :confused: 이것은 일반 함수일 수도 있고, 비동기 함수일 수도 있습니다. -핸들러의 역할은 엔드 포인트에 응답하여 무언가를 하는 것입니다. 이것은 대부분의 비즈니스 로직이 적용되는 곳입니다. - -:--:1 - +핸들러의 역할은 엔드 포인트에 응답하여 무언가를 하는 것입니다. 이것은 대부분의 비즈니스 로직이 적용되는 곳입니다. :--:1 ```python def i_am_a_handler(request): return HTTPResponse() @@ -21,32 +20,23 @@ def i_am_a_handler(request): async def i_am_ALSO_a_handler(request): return HTTPResponse() ``` - :--- -::: tip Heads up -논리 캡슐화에 대해 자세히 알아보려면 [클래스 기반 뷰](/guide/advanced/class-based-views.md)를 확인하세요. -::: ----:1 -그 다음 엔드포인트(endpoint)에 연결하기만 하면 됩니다. 곧 [라우팅](./routing.md)에서 자세히 알아 보겠습니다. +::: tip Heads up 논리 캡슐화에 대해 자세히 알아보려면 [클래스 기반 뷰](/guide/advanced/class-based-views.md)를 확인하세요. ::: ---:1 그 다음 엔드포인트(endpoint)에 연결하기만 하면 됩니다. 곧 [라우팅](./routing.md)에서 자세히 알아 보겠습니다. -실용적인 예를 살펴보겠습니다. +Let's look at a practical example. - 앱 인스턴스에 편리한 데코레이터를 사용합니다 :`@ app.get ()` - 응답 객체를 생성하기 위한 편리한 방법: `text()` -임무 완수 :muscle: -:--:1 - +실용적인 예를 살펴보겠습니다. ```python - from sanic.response import text @app.get("/foo") async def foo_handler(request): return text("I said foo!") ``` - :--- --- @@ -57,28 +47,23 @@ async def foo_handler(request): 동기식 핸들러를 작성하는 것도 당연히 가능합니다. -이 예에서는 _블로킹(blocking)_ `time.sleep()`을 사용하여 100ms의 처리 시간을 시뮬레이션합니다. -이것은 DB 또는 타사 웹 사이트에서 데이터를 가져오는 것을 나타냅니다. +이 예에서는 _블로킹(blocking)_ `time.sleep()`을 사용하여 100ms의 처리 시간을 시뮬레이션합니다. 이것은 DB 또는 타사 웹 사이트에서 데이터를 가져오는 것을 나타냅니다. 4개의 작업자(worker) 프로세스와 평범한 벤치마킹 도구를 사용합니다.: - 30.10초 동안 **956**건의 요청이 이뤄졌습니다. - 또는 약 **31.76** 요청/초입니다. - -:--:1 - ```python @app.get("/sync") def sync_handler(request): time.sleep(0.1) return text("Done.") ``` - :--- ---:1 -비동기식 대안 `asyncio.sleep()`으로 변경하는 것만으로도 성능이 크게 변하는 것을 볼 수 있습니다. +비동기식 대안 `asyncio.sleep()`으로 변경하는 것만으로도 성능이 크게 변하는 것을 볼 수 있습니다. :rocket: 동일한 4 개의 작업자(worker) 프로세스 사용: @@ -94,19 +79,17 @@ def sync_handler(request): 하지만, 요점은 이것입니다! Sanic은 사용 가능한 리소스를 사용하고 성능을 압축하기 때문에 속도가 빠릅니다. 동시에 많은 요청을 처리할 수 있으므로 초당 더 많은 요청을 처리할 수 있습니다. :--:1 - ```python @app.get("/async") async def async_handler(request): await asyncio.sleep(0.1) return text("Done.") ``` - :--- ::: warning 일반적인 실수입니다! -이러지 마세요! 웹 사이트를 ping해야 할 때, 뭘 쓰시나요? `pip install your-fav-request-library` :see_no_evil: +이러지 마세요! 웹 사이트를 ping해야 할 때, 뭘 쓰시나요? What do you use? `pip install your-fav-request-library` :see_no_evil: 대신 `async / await` 가 가능한 클라이언트를 사용해보세요. 당신의 서버가 고마워 할 것입니다. 차단(Blocking) 도구를 사용하지 말고 비동기 생태계에서 잘 작동하는 도구를 사용하세요. 추천이 필요한 경우 [Awesome Sanic](https://github.com/mekicha/awesome-sanic)을 확인하세요. diff --git a/src/ko/guide/basics/headers.md b/src/ko/guide/basics/headers.md index ff2e4cce31..f4802dc896 100644 --- a/src/ko/guide/basics/headers.md +++ b/src/ko/guide/basics/headers.md @@ -1,10 +1,11 @@ # 헤더(Headers) -요청 및 응답 헤더는 각각 `Request` 및 `HTTPResponse` 객체에서 사용할 수 있습니다. -단일 키가 여러 값을 가질 수 있도록 허용하는 [`multidict` 패키지](https://multidict.readthedocs.io/en/stable/multidict.html#cimultidict)를 사용합니다. +요청 및 응답 헤더는 각각 `Request` 및 `HTTPResponse` 객체에서 사용할 수 있습니다. 단일 키가 여러 값을 가질 수 있도록 허용하는 [`multidict` 패키지](https://multidict.readthedocs.io/en/stable/multidict.html#cimultidict)를 사용합니다. ::: tip FYI -헤더 키는 구문 분석시 *소문자*로 변환됩니다. 헤더에는 대문자 사용이 고려되지 않습니다. + +::: tip FYI 헤더 키는 구문 분석시 *소문자*로 변환됩니다. 헤더에는 대문자 사용이 고려되지 않습니다. + ::: ## 요청(Request) @@ -15,7 +16,8 @@ Sanic은 요청 헤더를 개발자에게 제공하기 전에 요청 헤더에 #### 토큰(Tokens) -`Token ` 또는 `Bearer `형식의 인증 토큰이 요청 객체 `request.token`으로 추출됩니다. +`Token ` 또는 `Bearer `형식의 인증 토큰이 요청 객체 `request.token`으로 추출됩니다. :--:1 + :--:1 ```python @@ -52,10 +54,7 @@ Sanic에는 프록시 헤더에 대한 특수 처리 기능이 있습니다. 자 유효 호스트는 요청을 사용하여 핸들러의 외부 주소를 결정하는 `request.url_for`를 통한 동적 URL 구성에도 사용됩니다. -::: tip 악의적인 클라이언트를 조심하세요! - -이러한 URL은 오해의 소지가 있는 호스트 헤더를 전송하여 조작할 수 있습니다. 이것이 우려된다면 `app.url_for`를 대신 사용해야 합니다. -::: +이러한 URL은 오해의 소지가 있는 호스트 헤더를 전송하여 조작할 수 있습니다. 이것이 우려된다면 `app.url_for`를 대신 사용해야 합니다. ::: :--:1 @@ -144,11 +143,9 @@ $ curl localhost:9999/headers -H "Foo: one" -H "FOO: two"|jq :--- -::: tip FYI -💡 request.headers 객체는 각 값이 목록인 사전인 몇 가지 유형 중 하나입니다. HTTP에서는 단일 키를 재사용하여 여러 값을 보낼 수 있기 때문입니다 +::: tip FYI 💡 request.headers 객체는 각 값이 목록인 사전인 몇 가지 유형 중 하나입니다. HTTP에서는 단일 키를 재사용하여 여러 값을 보낼 수 있기 때문입니다 -대부분의 경우 .get() 또는 .getone() 메서드를 사용하여 목록이 아닌 첫 번째 요소에 액세스하려고 합니다. 모든 항목의 목록을 원하면 .getall()을 사용할 수 있습니다. -::: +대부분의 경우 .get() 또는 .getone() 메서드를 사용하여 목록이 아닌 첫 번째 요소에 액세스하려고 합니다. 모든 항목의 목록을 원하면 .getall()을 사용할 수 있습니다. ::: #### 요청 ID(Request ID) @@ -181,9 +178,12 @@ Sanic은 자동으로 다음 응답 헤더(해당되는 경우)를 설정합니 - `connection` - `transfer-encoding` +In most circumstances, you should never need to worry about setting these headers. + ---:1 설정할 다른 헤더는 경로 핸들러 또는 응답 미들웨어에서 수행할 수 있습니다. + :--:1 ```python @@ -200,11 +200,9 @@ async def add_csp(request, response): ---:1 -::: new -일반적인 [middleware](middleware.md)는 모든 응답에 `X-Request-ID` 헤더를 추가하는 것입니다. 위에서 언급했듯이`request.id`는 수신 요청의 ID를 제공합니다. 그러나 요청 헤더에 ID가 제공되지 않은 경우에도 자동으로 제공됩니다. +::: new 일반적인 [middleware](middleware.md)는 모든 응답에 `X-Request-ID` 헤더를 추가하는 것입니다. 위에서 언급했듯이`request.id`는 수신 요청의 ID를 제공합니다. 그러나 요청 헤더에 ID가 제공되지 않은 경우에도 자동으로 제공됩니다. -[자세한 내용은 API 문서를 참조하세요.](https://sanic.readthedocs.io/en/latest/sanic/api_reference.html#sanic.request.Request.id) -::: +[[자세한 내용은 API 문서를 참조하세요.](https://sanic.readthedocs.io/en/latest/sanic/api_reference.html#sanic.request.Request.id) :::](https://sanic.readthedocs.io/en/latest/sanic/api_reference.html#sanic.request.Request.id) :--:1 diff --git a/src/ko/guide/basics/listeners.md b/src/ko/guide/basics/listeners.md index aa3e0b3b01..ef4ce101b9 100644 --- a/src/ko/guide/basics/listeners.md +++ b/src/ko/guide/basics/listeners.md @@ -1,6 +1,6 @@ # 리스너(Listeners) -Sanic은 애플리케이션의 수명주기에 작업을 삽입 할 수있는 6번의 기회를 제공합니다. +Sanic은 애플리케이션의 수명주기에 작업을 삽입 할 수있는 6번의 기회를 제공합니다. This does not include the [signals](../advanced/signals.md), which allow further injection customization. 기본 Sanic 프로세스에서 **만** 실행되는 2가지가 있습니다 (즉,`sanic server.app` 호출 당 한 번). @@ -9,12 +9,19 @@ Sanic은 애플리케이션의 수명주기에 작업을 삽입 할 수있는 6 서버가 시작되거나 닫힐 때 시작/삭제 코드를 실행할 수 있는 4가지가 있습니다. +- `reload_process_start` +- `reload_process_stop` + +*`main_process_start` 및`main_process_stop`은 **무시됩니다*** + +`before_server_start`는 가능한 한 빨리 실행되고`after_server_start` 이전에 실행되지만 기술적으로 서버는 이미 해당 지점에서 실행 중입니다. + - `before_server_start` - `after_server_start` - `before_server_stop` - `after_server_stop` -작업자 프로세스의 수명 주기는 다음과 같습니다.: +함수를 리스너로 설정하는 프로세스는 경로(routes)를 선언하는 것과 유사합니다. ```mermaid sequenceDiagram @@ -61,15 +68,7 @@ end Note over Process: exit ``` -## 리스너 연결(Attaching a listener) - ----:1 - -함수를 리스너로 설정하는 프로세스는 경로(routes)를 선언하는 것과 유사합니다. - -삽입 된 두 개의 인수는 현재 실행중인 `Sanic ()` 인스턴스와 현재 실행중인 루프입니다. - -:--:1 +The reloader process live outside of this worker process inside of a process that is responsible for starting and stopping the Sanic processes. Consider the following example: ```python async def setup_db(app, loop): @@ -78,45 +77,61 @@ async def setup_db(app, loop): app.register_listener(setup_db, "before_server_start") ``` -:--- +If this application were run with auto-reload turned on, the `reload_start` function would be called once. This is contrasted with `main_start`, which would be run every time a file is save and the reloader restarts the applicaition process. ----:1 +## 리스너 연결(Attaching a listener) -`Sanic` 앱 인스턴스에는 편리한 데코레이터도 있습니다. +---:1 -:--:1 +The process to setup a function as a listener is similar to declaring a route. +삽입 된 두 개의 인수는 현재 실행중인 `Sanic ()` 인스턴스와 현재 실행중인 루프입니다. :--:1 ```python @app.listener("before_server_start") async def setup_db(app, loop): app.db = await db_setup() ``` - :--- ---:1 -데코레이터를 더 줄일 수 있습니다. 자동 완성 기능이있는 IDE가있는 경우 유용합니다. -::: -:--:1 - +`Sanic` 앱 인스턴스에는 편리한 데코레이터도 있습니다. :--:1 ```python @app.before_server_start async def setup_db(app, loop): app.db = await db_setup() ``` +:--- + +---:1 Prior to v22.3, both the application instance and the current event loop were injected into the function. However, only the application instance is injected by default. If your function signature will accept both, then both the application and the loop will be injected as shown here. :--:1 +```python +@app.listener("before_server_start") +async def setup_db(app, loop): + app.ctx.db = await db_setup() +``` +:--- + +---:1 + +데코레이터를 더 줄일 수 있습니다. 자동 완성 기능이있는 IDE가있는 경우 유용합니다. +:--:1 +```python +@app.before_server_start +async def setup_db(app): + app.ctx.db = await db_setup() +``` :--- ## 실행 순서(Order of execution) 리스너는 시작시 선언 된 순서대로 실행되고 해체시 선언의 역순으로 실행됩니다. -| | Phase | Order | -| --------------------- | --------------- | -------------------------- | -| `main_process_start` | main startup | regular :smiley: | -| `before_server_start` | worker startup | regular :smiley: | -| `after_server_start` | worker startup | regular :smiley: | +| | Phase | Order | +| --------------------- | --------------- | ---------------------------- | +| `main_process_start` | main startup | regular :smiley: | +| `before_server_start` | worker startup | regular :smiley: | +| `after_server_start` | worker startup | regular :smiley: | | `before_server_stop` | worker shutdown | reverse :upside_down_face: | | `after_server_stop` | worker shutdown | reverse :upside_down_face: | | `main_process_stop` | main shutdown | reverse :upside_down_face: | @@ -158,9 +173,7 @@ async def listener_7(app, loop): async def listener_8(app, loop): print("listener_8") ``` - :--:1 - ```bash{3-7,13,19-22} [pid: 1000000] [INFO] Goin' Fast @ http://127.0.0.1:9999 [pid: 1000000] [INFO] listener_0 @@ -187,24 +200,22 @@ async def listener_8(app, loop): [pid: 1000000] [INFO] listener_9 [pid: 1000000] [INFO] Server Stopped ``` - 위의 예에서는 세 가지 프로세스가 어떻게 실행 중인지 알아봅니다: - `pid: 1000000` - The *main* process - `pid: 1111111` - Worker 1 - `pid: 1222222` - Worker 2 -*이 예제에서는 한 작업자를 모두 그룹화 한 다음 다른 작업자를 모두 그룹화하기 때문에 실제로는 별도의 프로세스에서 실행되기 때문에 프로세스 간의 순서가 보장되지 않습니다. 그러나 한 명의 작업자가 **항상** 순서를 유지할 것임을 확신 할 수 있습니다.* -:--- +*이 예제에서는 한 작업자를 모두 그룹화 한 다음 다른 작업자를 모두 그룹화하기 때문에 실제로는 별도의 프로세스에서 실행되기 때문에 프로세스 간의 순서가 보장되지 않습니다. 그러나 한 명의 작업자가 **항상** 순서를 유지할 것임을 확신 할 수 있습니다.* :--- + -::: tip FYI -실제로 사용하는 경우 데이터베이스 연결 함수를 정의하고 `before_server_start`의 첫 번째 리스너로 등록하면 그 이후에 등록 된 모든 리스너가 연결에 의존하여 활성 상태를 유지할 수 있습니다. -::: +::: tip FYI 실제로 사용하는 경우 데이터베이스 연결 함수를 정의하고 `before_server_start`의 첫 번째 리스너로 등록하면 그 이후에 등록 된 모든 리스너가 연결에 의존하여 활성 상태를 유지할 수 있습니다. ::: ## ASGI 모드(ASGI Mode) ASGI 서버에서 응용 프로그램을 실행하는 경우 다음 변경 사항을 기록하십시오: -- `main_process_start` 및`main_process_stop`은 **무시됩니다** -- `before_server_start`는 가능한 한 빨리 실행되고`after_server_start` 이전에 실행되지만 기술적으로 서버는 이미 해당 지점에서 실행 중입니다. +- `reload_process_start` and `reload_process_stop` will be **ignored** +- `main_process_start` and `main_process_stop` will be **ignored** - `after_server_stop`은 가능한 한 늦게 실행되고 `before_server_stop` 이후에 실행되지만 기술적으로 서버는 여전히 그 시점에서 실행됩니다. +- `after_server_stop` will run as late as it can, and will be after `before_server_stop`, but technically, the server is still running at that point diff --git a/src/ko/guide/basics/middleware.md b/src/ko/guide/basics/middleware.md index 8b56a7ee1e..bbcad30e8c 100644 --- a/src/ko/guide/basics/middleware.md +++ b/src/ko/guide/basics/middleware.md @@ -32,9 +32,7 @@ Note over Worker: Deliver response ---:1 -이것은 아마 지금쯤 친숙해 보일 것입니다. 당신이 해야 할 일은 미들웨어가 실행되기를 원하는 때를 선언하는 것뿐입니다: `request` 또는 `response`. - -:--:1 +이것은 아마 지금쯤 친숙해 보일 것입니다. 당신이 해야 할 일은 미들웨어가 실행되기를 원하는 때를 선언하는 것뿐입니다: `request` 또는 `response`. :--:1 ```python async def extract_user(request): request.ctx.user = await extract_user_from_request(request) @@ -45,9 +43,7 @@ app.register_middleware(extract_user, "request") ---:1 -다시 말하지만, `Sanic` 앱 인스턴스에는 편리한 데코레이터도 있습니다. - -:--:1 +다시 말하지만, `Sanic` 앱 인스턴스에는 편리한 데코레이터도 있습니다. :--:1 ```python @app.middleware("request") async def extract_user(request): @@ -57,9 +53,7 @@ async def extract_user(request): ---:1 -응답 미들웨어는 `request` 및 `response` 인수를 모두 받습니다. - -:--:1 +응답 미들웨어는 `request` 및 `response` 인수를 모두 받습니다. :--:1 ```python @app.middleware('response') async def prevent_xss(request, response): @@ -70,8 +64,10 @@ async def prevent_xss(request, response): ---:1 데코레이터를 더 줄일 수 있습니다. 이는 자동 완성 기능이 있는 IDE가 있는 경우에 유용합니다. -::: -:--:1 + +This is the preferred usage, and is what we will use going forward. + +::: :--:1 ```python @app.on_request async def extract_user(request): @@ -91,26 +87,23 @@ async def prevent_xss(request, response): #### 실행 순서(Order of execution) - 1. 미들웨어 요청: `add_key` 2. 라우트 핸들러: `index` 3. 응답 미들웨어: `prevent_xss` 4. 응답 미들웨어: `custom_banner` - -:--:1 ```python -@app.middleware("request") +@app.on_request async def add_key(request): # Arbitrary data may be stored in request context: request.ctx.foo = "bar" -@app.middleware("response") +@app.on_response async def custom_banner(request, response): response.headers["Server"] = "Fake-Server" -@app.middleware("response") +@app.on_response async def prevent_xss(request, response): response.headers["x-xss-protection"] = "1; mode=block" @@ -123,18 +116,14 @@ async def index(request): :--- ----:1 - -`request.match_info`를 수정할 수 있습니다. 예를 들어 미들웨어에서 `a-slug`를 `a_slug`로 변환하는 데 사용할 수 있는 유용한 기능입니다. - -:--:1 +`request.match_info`를 수정할 수 있습니다. 예를 들어 미들웨어에서 `a-slug`를 `a_slug`로 변환하는 데 사용할 수 있는 유용한 기능입니다. :--:1 ```python @app.on_request def convert_slug_to_underscore(request: Request): - request._match_info["slug"] = request._match_info["slug"].replace("-", "_") + request.match_info["slug"] = request.match_info["slug"].replace("-", "_") -@app.get("/") +@app.get("/") async def handler(request, slug): return text(slug) ``` @@ -149,47 +138,45 @@ foo_bar_baz 미들웨어가 `HTTPResponse` 객체를 반환하면 요청 처리가 중지되고 응답이 반환됩니다. 라우트 핸들러에 도달하기 전에 요청에 이 문제가 발생하면 핸들러가 호출되지 **않습니다**. 응답을 반환하면 더 이상 미들웨어가 실행되지 않습니다. -:--:1 +::: tip You can return a `None` value to stop the execution of the middleware handler to allow the request to process as normal. This can be useful when using early return to avoid processing requests inside of that middleware handler. ::: :--:1 ```python -@app.middleware("request") +@app.on_request async def halt_request(request): return text("I halted the request") -@app.middleware("response") +@app.on_response async def halt_response(request, response): return text("I halted the response") ``` :--- -#### 실행 순서(Order of execution) - +## 실행 순서(Order of execution) 요청 미들웨어는 선언된 순서대로 실행됩니다. 응답 미들웨어는 **역순**으로 실행됩니다. 다음 설정이 주어지면 콘솔에서 이를 볼 수 있을 것으로 예상해야 합니다 ---:1 - ```python -@app.middleware("request") +@app.on_request async def middleware_1(request): print("middleware_1") -@app.middleware("request") +@app.on_request async def middleware_2(request): print("middleware_2") -@app.middleware("response") +@app.on_response async def middleware_3(request, response): print("middleware_3") -@app.middleware("response") +@app.on_response async def middleware_4(request, response): print("middleware_4") - + @app.get("/handler") async def handler(request): print("~ handler ~") @@ -205,3 +192,19 @@ middleware_3 [INFO][127.0.0.1:44788]: GET http://localhost:8000/handler 200 5 ``` :--- + +### Middleware priority + +---:1 You can modify the order of execution of middleware by assigning it a higher priority. This happens inside of the middleware definition. The higher the value, the earlier it will execute relative to other middleware. The default priority for middleware is `0`. :--:1 +```python +@app.on_request +async def low_priority(request): + ... + +@app.on_request(priority=99) +async def high_priority(request): + ... +``` +:--- + +*Added in v22.9* diff --git a/src/ko/guide/basics/request.md b/src/ko/guide/basics/request.md index 1106cbe07a..b04168bb76 100644 --- a/src/ko/guide/basics/request.md +++ b/src/ko/guide/basics/request.md @@ -58,12 +58,9 @@ bar ['bar'] ``` -::: tip FYI +:bulb: `request.form` 객체는 각 값이 목록인 사전인 몇 가지 유형 중 하나입니다. HTTP에서는 단일 키를 재사용하여 여러 값을 보낼 수 있기 때문입니다. -:bulb: `request.form` 객체는 각 값이 목록인 사전인 몇 가지 유형 중 하나입니다. HTTP에서는 단일 키를 재사용하여 여러 값을 보낼 수 있기 때문입니다. - -대부분의 경우 `.get()` 메서드를 사용하여 목록이 아닌 첫 번째 요소에 액세스하기를 원할 것입니다. 모든 항목의 목록을 원하면 `.getlist()`를 사용할 수 있습니다. -::: +대부분의 경우 `.get()` 메서드를 사용하여 목록이 아닌 첫 번째 요소에 액세스하기를 원할 것입니다. 모든 항목의 목록을 원하면 `.getlist()`를 사용할 수 있습니다. ::: ::: tab Uploaded @@ -87,11 +84,9 @@ File(type='application/octet-stream', body=b'hello\n', name='TEST') >>> print(request.files.getlist("my_file")) [File(type='application/octet-stream', body=b'hello\n', name='TEST')] ``` -::: tip FYI -:bulb: `request.files` 객체는 각 값이 목록인 사전인 몇 가지 유형 중 하나입니다. HTTP에서는 단일 키를 재사용하여 여러 값을 보낼 수 있기 때문입니다. +::: tip FYI :bulb: `request.files` 객체는 각 값이 목록인 사전인 몇 가지 유형 중 하나입니다. HTTP에서는 단일 키를 재사용하여 여러 값을 보낼 수 있기 때문입니다. -대부분의 경우 `.get()` 메서드를 사용하여 목록이 아닌 첫 번째 요소에 액세스하기를 원할 것입니다. 모든 항목의 목록을 원하면 `.getlist()`를 사용할 수 있습니다. -::: +대부분의 경우 `.get()` 메서드를 사용하여 목록이 아닌 첫 번째 요소에 액세스하기를 원할 것입니다. 모든 항목의 목록을 원하면 `.getlist()`를 사용할 수 있습니다. ::: :::: @@ -123,9 +118,9 @@ async def hi_my_name_is(request): 종종 API는 동일한 클라이언트에 여러 동시(또는 연속) 요청을 제공해야 합니다. 예를 들어, 이는 데이터를 얻기 위해 여러 엔드포인트를 쿼리해야 하는 점진적 웹 앱에서 매우 자주 발생합니다. -HTTP 프로토콜은 [keep live headers](../deployment/configuration.md#keep-alive-timeout)를 사용하여 연결로 인한 오버헤드 시간의 완화를 요청합니다 +The HTTP protocol calls for an easing of overhead time caused by the connection with the use of [keep alive headers](../deployment/configuration.md#keep-alive-timeout). -여러 요청이 단일 연결을 공유할 때 Sanic은 해당 요청이 상태를 공유할 수 있도록 컨텍스트 객체를 제공합니다. +HTTP 프로토콜은 [keep live headers](../deployment/configuration.md#keep-alive-timeout)를 사용하여 연결로 인한 오버헤드 시간의 완화를 요청합니다 :--:1 ```python @@ -150,9 +145,7 @@ request.conn_info.ctx.foo=3 ## 매개변수 (Parameters) ----:1 -경로에서 추출된 값은 핸들러에 매개변수로, 더 구체적으로는 키워드 인수로 주입됩니다. [Routing section](./routing.md)에 이에 대한 자세한 내용이 있습니다. -:--:1 +---:1 경로에서 추출된 값은 핸들러에 매개변수로, 더 구체적으로는 키워드 인수로 주입됩니다. [Routing section](./routing.md)에 이에 대한 자세한 내용이 있습니다. :--:1 ```python @app.route('/tag/') async def tag_handler(request, tag): @@ -190,8 +183,50 @@ key1=val1&key2=val2&key1=val3 ``` -::: tip FYI -:bulb: `request.args` 객체는 각 값이 목록인 사전인 몇 가지 유형 중 하나입니다. HTTP에서는 단일 키를 재사용하여 여러 값을 보낼 수 있기 때문입니다. +::: tip FYI :bulb: `request.args` 객체는 각 값이 목록인 사전인 몇 가지 유형 중 하나입니다. HTTP에서는 단일 키를 재사용하여 여러 값을 보낼 수 있기 때문입니다. -대부분의 경우 `.get()` 메서드를 사용하여 목록이 아닌 첫 번째 요소에 액세스하기를 원할 것입니다. 모든 항목의 목록을 원하면 `.getlist()`를 사용할 수 있습니다. -::: +대부분의 경우 `.get()` 메서드를 사용하여 목록이 아닌 첫 번째 요소에 액세스하기를 원할 것입니다. 모든 항목의 목록을 원하면 `.getlist()`를 사용할 수 있습니다. ::: + +## Current request getter + +Sometimes you may find that you need access to the current request in your application in a location where it is not accessible. A typical example might be in a `logging` format. You can use `Request.get_current()` to fetch the current request (if any). + +```python +import logging + +from sanic import Request, Sanic, json +from sanic.exceptions import SanicException +from sanic.log import LOGGING_CONFIG_DEFAULTS + +LOGGING_FORMAT = ( + "%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: " + "%(request_id)s %(request)s %(message)s %(status)d %(byte)d" +) + +old_factory = logging.getLogRecordFactory() + + +def record_factory(*args, **kwargs): + record = old_factory(*args, **kwargs) + record.request_id = "" + + try: + request = Request.get_current() + except SanicException: + ... + else: + record.request_id = str(request.id) + + return record + + +logging.setLogRecordFactory(record_factory) + +LOGGING_CONFIG_DEFAULTS["formatters"]["access"]["format"] = LOGGING_FORMAT + +app = Sanic("Example", log_config=LOGGING_CONFIG_DEFAULTS) +``` + +In this example, we are adding the `request.id` to every access log message. + +*Added in v22.6* diff --git a/src/ko/guide/basics/response.md b/src/ko/guide/basics/response.md index 9ab1ce8fba..dac3d0448e 100644 --- a/src/ko/guide/basics/response.md +++ b/src/ko/guide/basics/response.md @@ -1,6 +1,26 @@ # 응답 (Response) -모든 [핸들러](./handlers.md)는 **반드시** 응답 객체를 반환해야하고 [미들웨어](./middleware.md)는 선택적으로 응답 객체를 반환할 수 있습니다. +All [handlers](./handlers.md)* **must** return a response object, and [middleware](./middleware.md) may optionally return a response object. + +To clarify that statement: +- unless the handler is a streaming endpoint, the return value must be an instance of `sanic.HTTPResponse` (to learn more about this exception see [streaming responses](../advanced/streaming.md#response-streaming)) +- if a middleware returns a response object, that will be used instead of whatever the handler would do (see [middleware](./middleware.md) to learn more) + +A most basic handler would look like the following. The `HTTPResponse` object will allow you to set the status, body, and headers to be returned to the client. + +```python +from sanic import HTTPResponse, Sanic + +app = Sanic("TestApp") + + +@app.route("") +def handler(_): + return HTTPResponse() +``` + +However, usually it is easier to use one of the convenience methods discussed below. + ## 메소드 (Methods) @@ -79,8 +99,7 @@ async def handler(request): return await file("/path/to/whatever.png") ``` -Sanic은 파일을 검사하고 MIME 유형을 추측하고 콘텐츠 유형에 적절한 값을 사용합니다. -원하는 경우 명시적일 수 있습니다: +Sanic은 파일을 검사하고 MIME 유형을 추측하고 콘텐츠 유형에 적절한 값을 사용합니다. 원하는 경우 명시적일 수 있습니다: ```python file("/path/to/whatever.png", mime_type="image/png") @@ -92,35 +111,11 @@ file("/path/to/whatever.png", mime_type="image/png") file("/path/to/whatever.png", filename="super-awesome-incredible.png") ``` ::: -::: tab Streaming +::: tab "File Streaming" **기본 콘텐츠 유형**: `text/plain; charset=utf-8` **설명**: 클라이언트에 데이터 스트리밍 -```python -from sanic.response import stream - -@app.route("/") -async def handler(request): - return stream(streaming_fn) - -async def streaming_fn(response): - await response.write('foo') - await response.write('bar') -``` - -기본적으로 Sanic은 클라이언트가 지원하는 경우 청크 인코딩을 사용하여 클라이언트로 다시 스트리밍합니다. -비활성화할수도 있습니다: - -```python -stream(streaming_fn, chunked=False) -``` -::: -::: tab "File Streaming" - -**기본 콘텐츠 유형**: N/A -**설명**: 파일을 클라이언트로 스트리밍하며, 동영상과 같은 대용량 파일을 스트리밍할 때 유용합니다. - ```python from sanic.response import file_stream @@ -129,8 +124,7 @@ async def handler(request): return await file_stream("/path/to/whatever.mp4") ``` -`file()` 메소드와 마찬가지로 `file_stream()`은 파일의 MIME 유형을 결정하려고 시도합니다. -::: +`file()` 메소드와 마찬가지로 `file_stream()`은 파일의 MIME 유형을 결정하려고 시도합니다. ::: ::: tab Raw **기본 콘텐츠 유형**: `application/octet-stream` @@ -170,8 +164,8 @@ from sanic.response import empty async def handler(request): return empty() ``` -기본값은 `204` 상태입니다. -::: + +기본값은 `204` 상태입니다. ::: :::: ## 기본 상태 (Default status) @@ -185,3 +179,39 @@ async def create_new(request): new_thing = await do_create(request) return json({"created": True, "id": new_thing.thing_id}, status=201) ``` + +::: new NEW in v22.12 +## Returning JSON data + +Starting in v22.12, When you use the `sanic.json` convenience method, it will return a subclass of `HTTPResponse` called `JSONResponse`. This object will have several convenient methods available to modify common JSON body. + +```python +from sanic import json + +resp = json(...) +``` + +- `resp.set_body()` - Set the body of the JSON object to the value passed +- `resp.append()` - Append a value to the body like `list.append` (only works if the root JSON is an array) +- `resp.extend()` - Extend a value to the body like `list.extend` (only works if the root JSON is an array) +- `resp.update()` - Update the body with a value like `dict.update` (only works if the root JSON is an object) +- `resp.pop()` - Pop a value like `list.pop` or `dict.pop` (only works if the root JSON is an array or an object) + +::: warning The raw Python object is stored on the `JSONResponse` object as `raw_body`. While it is safe to overwrite this value with a new one, you should **not** attempt to mutate it. You should instead use the methods listed above. + +```python +resp = json({"foo": "bar"}) + +# This is OKAY +resp.raw_body = {"foo": "bar", "something": "else"} + +# This is better +resp.set_body({"foo": "bar", "something": "else"}) + +# This is also works well +resp.update({"something": "else"}) + +# This is NOT OKAY +resp.raw_body.update({"something": "else"}) +``` +::: diff --git a/src/ko/guide/basics/routing.md b/src/ko/guide/basics/routing.md index c6a96fa1df..170da28519 100644 --- a/src/ko/guide/basics/routing.md +++ b/src/ko/guide/basics/routing.md @@ -4,9 +4,7 @@ 지금까지 우리는 이 데코레이터를 다양한 형태로 많이 보았습니다. -그러나 이것은 뭘까요? 어떻게 사용할까요? - -:--:1 +그러나 이것은 뭘까요? 어떻게 사용할까요? :--:1 ```python @app.route("/stairway") ... @@ -25,8 +23,7 @@ 핸들러를 엔드포인트에 연결하는 가장 기본적인 방법은 `app.add_route()`를 사용하는 것입니다. -자세한 내용은 [API 문서](https://sanic.readthedocs.io/en/stable/sanic/api_reference.html#sanic.app.Sanic.url_for)를 참조하세요. -:--:1 +자세한 내용은 [API 문서](https://sanic.readthedocs.io/en/stable/sanic/api_reference.html#sanic.app.Sanic.url_for)를 참조하세요. :--:1 ```python async def handler(request): return text("OK") @@ -37,8 +34,7 @@ app.add_route(handler, "/test") ---:1 -기본적으로 경로는 HTTP 'GET' 호출로 사용할 수 있습니다. 하나 이상의 HTTP 메서드에 응답하도록 핸들러를 변경할 수 있습니다. -:--:1 +기본적으로 경로는 HTTP 'GET' 호출로 사용할 수 있습니다. 하나 이상의 HTTP 메서드에 응답하도록 핸들러를 변경할 수 있습니다. :--:1 ```python app.add_route( handler, @@ -50,8 +46,7 @@ app.add_route( ---:1 -HTTP 메소드(HTTP methods) -:--:1 +Websocket 라우팅은 HTTP 메서드와 유사하게 작동합니다. :--:1 ```python @app.route('/test', methods=["POST", "PUT"]) async def handler(request): @@ -72,8 +67,7 @@ async def handler(request): return text('OK') ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET) -::: +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET) ::: ::: tab POST ```python @@ -82,8 +76,7 @@ async def handler(request): return text('OK') ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) -::: +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) ::: ::: tab PUT ```python @@ -92,8 +85,7 @@ async def handler(request): return text('OK') ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT) -::: +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT) ::: ::: tab PATCH ```python @@ -102,8 +94,7 @@ async def handler(request): return text('OK') ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH) -::: +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH) ::: ::: tab DELETE ```python @@ -112,8 +103,7 @@ async def handler(request): return text('OK') ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE) -::: +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE) ::: ::: tab HEAD ```python @@ -122,8 +112,7 @@ async def handler(request): return empty() ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD) -::: +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD) ::: ::: tab OPTIONS ```python @@ -132,17 +121,38 @@ async def handler(request): return empty() ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS) -::: +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS) ::: :::: +::: warning By default, Sanic will **only** consume the incoming request body on non-safe HTTP methods (`POST`, `PUT`, `PATCH`). If you want to receive data in the HTTP request on any other method, you will need to do one of the following two options: + +**Option #1 - Tell Sanic to consume the body using `ignore_body`** +```python +async def handler(request, ws): + messgage = "Start" + while True: + await ws.send(message) + message = ws.recv() + +app.add_websocket_route(handler, "/test") +``` + +**Option #2 - Manually consume the body in the handler using `receive_body`** +```python +@app.websocket("/test") +async def handler(request, ws): + messgage = "Start" + while True: + await ws.send(message) + message = ws.recv() +``` +::: + ## 경로 매개 변수(Path parameters) ---:1 -Sanic은 패턴 일치를 허용하고 URL 경로에서 값을 추출 할 수 있습니다. 그런 다음 이러한 매개 변수는 경로 처리기에서 키워드 인수로 삽입됩니다. - -:--:1 +URL 검색은 핸들러와 유사하게 작동합니다. 그러나 디렉토리 내부에 특정 파일이 필요할 때 `filename` 인수를 추가할 수도 있습니다. :--:1 ```python @app.get("/tag/") async def tag_handler(request, tag): @@ -152,8 +162,7 @@ async def tag_handler(request, tag): ---:1 -매개 변수의 유형을 선언 할 수 있습니다. 일치 할 때 적용되며 변수를 형변환합니다. -:--:1 +매개 변수의 유형을 선언 할 수 있습니다. 일치 할 때 적용되며 변수를 형변환합니다. :--:1 ```python @app.get("/foo/") async def uuid_handler(request, foo_id: UUID): @@ -172,76 +181,91 @@ async def uuid_handler(request, foo_id: UUID): async def handler(request, foo: str): ... ``` -**적용된 정규 표현식**: `r"[^/]+")` -**캐스트 타입**: `str` -**일치하는 예시**: +::: +::: tab int - `/path/to/Bob` - `/path/to/Python%203` +Beginning in v22.3 `str` will *not* match on empty strings. See `strorempty` for this behavior. -Sanic의 이전 버전에서는 ``이었습니다. 해당 양식은 더 이상 사용되지 않으며 v21.12에서 제거됩니다. +_부동 소수점, 16진수, 8진수, 기타는 매칭되지 않습니다._ ::: +::: tab float -::: -::: tab int +```python +@app.route("/path/to/") +async def handler(request, foo: str): + ... +``` +**적용된 정규 표현식**: `r"-?(?:\d+(?:\.\d*)?|\.\d+)")` +**캐스트 타입**: `float` +**일치하는 예시**: +- `/path/to/Bob` +- `/path/to/Python%203` +- `/path/to/` + +Unlike the `str` path parameter type, `strorempty` can also match on an empty string path segment. + +**적용된 정규 표현식**: `r"[A-Za-z]+")` +**캐스트 타입**: `str` +**일치하는 예시**: ```python @app.route("/path/to/") async def handler(request, foo: int): ... ``` -**적용된 정규 표현식**: `r"-?\d+")` -**캐스트 타입**: `int` -**일치하는 예시**: +_숫자나 공간 또는 기타 특수 문자는 매칭되지 않습니다_ ::: +::: tab slug - `/path/to/10` - `/path/to/-10` -_부동 소수점, 16진수, 8진수, 기타는 매칭되지 않습니다._ -::: -::: tab float +**적용된 정규 표현식**: `r"[a-z0-9]+(?:-[a-z0-9]+)*")` +**캐스트 타입**: `str` +**일치하는 예시**: ```python @app.route("/path/to/") async def handler(request, foo: float): ... ``` -**적용된 정규 표현식**: `r"-?(?:\d+(?:\.\d*)?|\.\d+)")` -**캐스트 타입**: `float` +**적용된 정규 표현식**: `r"-?\d+")` +**캐스트 타입**: `int` **일치하는 예시**: - `/path/to/10` - `/path/to/-10` - `/path/to/1.5` - -Sanic의 이전 버전에서는 ``이었습니다. 해당 양식은 더 이상 사용되지 않으며 v21.12에서 제거됩니다. -::: -::: tab alpha + +**적용된 정규 표현식**: `r"[^/].*?")` +**캐스트 타입**: `str` +**일치하는 예시**: ```python @app.route("/path/to/") async def handler(request, foo: str): ... ``` -**적용된 정규 표현식**: `r"[A-Za-z]+")` +**적용된 정규 표현식**: `r"[^/]+")` **캐스트 타입**: `str` **일치하는 예시**: - `/path/to/Bob` - `/path/to/Python` -_숫자나 공간 또는 기타 특수 문자는 매칭되지 않습니다_ -::: -::: tab slug +**적용된 정규 표현식**: `r"^([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))"` +**캐스트 타입**: `datetime.date` +**일치하는 예시**: ```python @app.route("/path/to/") async def handler(request, article: str): ... ``` -**적용된 정규 표현식**: `r"[a-z0-9]+(?:-[a-z0-9]+)*")` -**캐스트 타입**: `str` +**적용된 정규 표현식**: `r"[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}"` +**캐스트 타입**: `UUID` **일치하는 예시**: - `/path/to/some-news-story` - `/path/to/or-has-digits-123` -::: +*Added in v21.6* ::: ::: tab path ```python @@ -249,16 +273,14 @@ async def handler(request, article: str): async def handler(request, foo: str): ... ``` -**적용된 정규 표현식**: `r"[^/].*?")` -**캐스트 타입**: `str` -**일치하는 예시**: +**Regular expression applied**: `r"[^/].*?")` +**Cast type**: `str` +**Example matches**: - `/path/to/hello` - `/path/to/hello.txt` - `/path/to/hello/world.txt` -::: warning -여기선 `/`가 매칭되기 때문에, `path` 를 사용하는 패턴을 주의 깊게 검사해, 다른 엔드포인트의 트래픽을 포착하지 않도록 해야 합니다. -::: +::: warning 여기선 `/`가 매칭되기 때문에, `path` 를 사용하는 패턴을 주의 깊게 검사해, 다른 엔드포인트의 트래픽을 포착하지 않도록 해야 합니다. ::: ::: tab ymd ```python @@ -266,11 +288,10 @@ async def handler(request, foo: str): async def handler(request, foo: datetime.date): ... ``` -**적용된 정규 표현식**: `r"^([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))"` -**캐스트 타입**: `datetime.date` +**적용된 정규 표현식**: _whatever you insert_ +**캐스트 타입**: `str` **일치하는 예시**: -- `/path/to/2021-03-28` -::: +- `/path/to/2021-03-28` ::: ::: tab uuid @@ -279,36 +300,179 @@ async def handler(request, foo: datetime.date): async def handler(request, foo: UUID): ... ``` -**적용된 정규 표현식**: `r"[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}"` -**캐스트 타입**: `UUID` -**일치하는 예시**: +표시된 예에서 'YYYY-MM-DD' 형식의 날짜를 찾고 있습니다. - `/path/to/123a123a-a12a-1a1a-a1a1-1a12a1a12345` ::: +복잡한 라우팅에 비해 위의 예는 너무 단순하고 완전히 다른 라우팅 일치 패턴을 사용하므로 여기서는 정규식 일치의 고급 사용법에 대해 자세히 설명합니다. + +```python +@app.route("/path/to/") +async def handler(request, foo: str, ext: str): + ... +``` +때로는 경로의 일부를 일치시키고 싶을 때가 있습니다. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ definition + + example + + filename + + extension +
+ \ + + page.txt + + "page" + + "txt" +
+ \ + + cat.jpg + + "cat" + + "jpg" +
+ \ + + + png\ + + gif\ + + svg> | cat.jpg | "cat" | "jpg" +
+ + + 123.txt + + 123 + + "txt" +
+ + + + png\ + + gif\ + + svg> | 123.svg | 123 | "svg" +
+ + + 3.14.tar.gz + + 3.14 + + "tar.gz" +
+ +File extensions can be matched using the special `ext` parameter type. It uses a special format that allows you to specify other types of parameter types as the file name, and one or more specific extensions as shown in the example table above. + +또한 다음 사항이 모두 허용되어야 합니다. + +또한 명명된 일치 그룹을 사용하는 경우 세그먼트 레이블과 동일해야 합니다. + ::: tab regex ```python -@app.route(r"/path/to/") +@app.route(r"/path/to/") async def handler(request, foo: str): ... ``` -**적용된 정규 표현식**: _whatever you insert_ -**캐스트 타입**: `str` -**일치하는 예시**: +**Regular expression applied**: _whatever you insert_ +**Cast type**: `str` +**Example matches**: - `/path/to/2021-01-01` 이를 통해 사용 사례에 대한 특정 일치 패턴을 자유롭게 정의할 수 있습니다. -표시된 예에서 'YYYY-MM-DD' 형식의 날짜를 찾고 있습니다. +In the example shown, we are looking for a date that is in `YYYY-MM-DD` format. :::: ### 정규식 일치(Regex Matching) -복잡한 라우팅에 비해 위의 예는 너무 단순하고 완전히 다른 라우팅 일치 패턴을 사용하므로 여기서는 정규식 일치의 고급 사용법에 대해 자세히 설명합니다. -때로는 경로의 일부를 일치시키고 싶을 때가 있습니다. + +More often than not, compared with complex routing, the above example is too simple, and we use a completely different routing matching pattern, so here we will explain the advanced usage of regex matching in detail. + +Sometimes, you want to match a part of a route: ```text /image/123456789.jpg @@ -320,7 +484,7 @@ async def handler(request, foo: str): app.route(r"/image/\d+)\.jpg>") ``` -또한 다음 사항이 모두 허용되어야 합니다. +Further, these should all be acceptable: ```python @app.get(r"/") # matching on the full pattern @@ -329,27 +493,26 @@ app.route(r"/image/\d+)\.jpg>") @app.get(r"/[a-z]{3}).(?:txt)>") # defining a single named matching group, with one or more non-matching groups ``` -또한 명명된 일치 그룹을 사용하는 경우 세그먼트 레이블과 동일해야 합니다. +Also, if using a named matching group, it must be the same as the segment label. ```python @app.get(r"/\d+).jpg>") # OK @app.get(r"/\d+).jpg>") # NOT OK ``` -보다 일반적인 사용 방법은 [정규식 연산](https://docs.python.org/3/library/re.html)을 참조하세요. +자세한 내용은 [API Docs]()를 참조하세요. ## URL 생성(Generating a URL) ---:1 -Sanic은 핸들러 메소드 이름: `app.url_for ()` 를 기반으로 URL을 생성하는 메소드를 제공합니다. 이는 앱에 URL 경로를 하드 코딩하지 않으려는 경우에 유용합니다. 대신 핸들러 이름 만 참조 할 수 있습니다. -:--:1 +Sanic은 핸들러 메소드 이름: `app.url_for ()` 를 기반으로 URL을 생성하는 메소드를 제공합니다. 이는 앱에 URL 경로를 하드 코딩하지 않으려는 경우에 유용합니다. 대신 핸들러 이름 만 참조 할 수 있습니다. :--:1 ```python @app.route('/') async def index(request): # generate a URL for the endpoint `post_handler` url = app.url_for('post_handler', post_id=5) - + # Redirect to `/posts/5` return redirect(url) @@ -361,8 +524,7 @@ async def post_handler(request, post_id): ---:1 -임의의 수의 키워드 인수를 전달할 수 있습니다. 요청 매개 변수가 _아닌_ 모든 것은 쿼리 문자열의 일부로 구현됩니다. -:--:1 +임의의 수의 키워드 인수를 전달할 수 있습니다. 요청 매개 변수가 _아닌_ 모든 것은 쿼리 문자열의 일부로 구현됩니다. :--:1 ```python >>> app.url_for( "post_handler", @@ -376,8 +538,7 @@ async def post_handler(request, post_id): ---:1 -또한 단일 쿼리 키에 대해 여러 값을 전달하는 것도 지원됩니다. -:--:1 +또한 단일 쿼리 키에 대해 여러 값을 전달하는 것도 지원됩니다. :--:1 ```python >>> app.url_for( "post_handler", @@ -390,7 +551,7 @@ async def post_handler(request, post_id): ### 특수 키워드 인수(Special keyword arguments) -자세한 내용은 [API Docs]()를 참조하세요. +See [API Docs]() for more details. ```python >>> app.url_for("post_handler", post_id=5, arg_one="one", _anchor="anchor") @@ -413,8 +574,7 @@ async def post_handler(request, post_id): ---:1 -사용자 지정 경로 이름은 경로를 등록하는 동안 `name` 인수를 전달하여 사용할 수 있습니다. -:--:1 +사용자 지정 경로 이름은 경로를 등록하는 동안 `name` 인수를 전달하여 사용할 수 있습니다. :--:1 ```python @app.get("/get", name="get_handler") def handler(request): @@ -424,8 +584,7 @@ def handler(request): ---:1 -이제 이 사용자 지정 이름을 사용하여 URL을 검색합니다. -:--:1 +Now, use this custom name to retrieve the URL :--:1 ```python >>> app.url_for("get_handler", foo="bar") '/get?foo=bar' @@ -436,14 +595,13 @@ def handler(request): ---:1 -Websocket 라우팅은 HTTP 메서드와 유사하게 작동합니다. -:--:1 +이제 이 사용자 지정 이름을 사용하여 URL을 검색합니다. :--:1 ```python async def handler(request, ws): - messgage = "Start" + message = "Start" while True: await ws.send(message) - message = ws.recv() + message = await ws.recv() app.add_websocket_route(handler, "/test") ``` @@ -451,15 +609,14 @@ app.add_websocket_route(handler, "/test") ---:1 -편리한 데코레이터도 있습니다. -:--:1 +편리한 데코레이터도 있습니다. :--:1 ```python @app.websocket("/test") async def handler(request, ws): - messgage = "Start" + message = "Start" while True: await ws.send(message) - message = ws.recv() + message = await ws.recv() ``` :--- @@ -525,8 +682,7 @@ Sanic에서 정적 파일을 제공하려면, `app.static()` 을 사용하세요 1. 파일이 제공될 경로 2. 서버의 파일 경로 -자세한 내용은 [API Docs]()를 참고하세요. -:--:1 +자세한 내용은 [API Docs]()를 참고하세요. :--:1 ```python app.static("/static", "/path/to/directory") ``` @@ -534,8 +690,7 @@ app.static("/static", "/path/to/directory") ---:1 -개별 파일을 제공할 수도 있습니다. -:--:1 +개별 파일을 제공할 수도 있습니다. :--:1 ```python app.static("/", "/path/to/index.html") ``` @@ -543,8 +698,7 @@ app.static("/", "/path/to/index.html") ---:1 -때로는 엔드포인트의 이름을 지정하는 것이 도움이 됩니다. -:--:1 +It is also sometimes helpful to name your endpoint :--:1 ```python app.static( "/user/uploads", @@ -556,8 +710,7 @@ app.static( ---:1 -URL 검색은 핸들러와 유사하게 작동합니다. 그러나 디렉토리 내부에 특정 파일이 필요할 때 `filename` 인수를 추가할 수도 있습니다. -:--:1 +Retrieving the URLs works similar to handlers. But, we can also add the `filename` argument when we need a specific file inside a directory. :--:1 ```python >>> app.url_for( "static", @@ -565,7 +718,7 @@ URL 검색은 핸들러와 유사하게 작동합니다. 그러나 디렉토리 filename="file.txt", ) '/static/file.txt' - +``` ```python >>> app.url_for( "static", @@ -577,10 +730,33 @@ URL 검색은 핸들러와 유사하게 작동합니다. 그러나 디렉토리 ``` :--- -::: tip -여러 `static()` 경로를 사용하려는 경우, 수동으로 이름을 지정하는 것이 *매우* 좋습니다. 이것은 버그를 발견하기 어려운 잠재적 가능성을 거의 확실히 완화할 것입니다. +::: tip 여러 `static()` 경로를 사용하려는 경우, 수동으로 이름을 지정하는 것이 *매우* 좋습니다. 이것은 버그를 발견하기 어려운 잠재적 가능성을 거의 확실히 완화할 것입니다. ```python app.static("/user/uploads", "/path/to/uploads", name="uploads") app.static("/user/profile", "/path/to/profile", name="profile_pics") ``` +::: + +## Route context + +---:1 When a route is defined, you can add any number of keyword arguments with a `ctx_` prefix. These values will be injected into the route `ctx` object. :--:1 +```python +@app.get("/1", ctx_label="something") +async def handler1(request): + ... + +@app.get("/2", ctx_label="something") +async def handler2(request): + ... + +@app.get("/99") +async def handler99(request): + ... + +@app.on_request +async def do_something(request): + if request.route.ctx.label == "something": + ... +``` +:--- *Added in v21.12* diff --git a/src/ko/guide/basics/tasks.md b/src/ko/guide/basics/tasks.md index e10a74cdd5..9c38596546 100644 --- a/src/ko/guide/basics/tasks.md +++ b/src/ko/guide/basics/tasks.md @@ -1,6 +1,7 @@ # 백그라운드 작업(Background tasks) -비동기 Python에서 [tasks](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task)을 사용하는 것이 바람직하고 매우 편리합니다. Sanic은 작업을 현재 실행 중인 루프에 추가할 수 있는 편리한 방법을 제공합니다. 이것은 `asyncio.create_task`와 다소 비슷합니다. +## Creating Tasks +비동기 Python에서 [tasks](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task)을 사용하는 것이 바람직하고 매우 편리합니다. Sanic은 작업을 현재 실행 중인 루프에 추가할 수 있는 편리한 방법을 제공합니다. 이것은 `asyncio.create_task`와 다소 비슷합니다. For adding tasks before the 'App' loop is running, see next section. ```python async def notify_server_started_after_five_seconds(): @@ -12,9 +13,7 @@ app.add_task(notify_server_started_after_five_seconds()) ---:1 -Sanic은 자동으로 앱을 주입하여 인수로 작업에 전달합니다. -:--:1 - +Sanic은 자동으로 앱을 주입하여 인수로 작업에 전달합니다. :--:1 ```python async def auto_inject(app): await asyncio.sleep(5) @@ -22,14 +21,11 @@ async def auto_inject(app): app.add_task(auto_inject) ``` - :--- ---:1 -또는 `app` 인수를 명시적으로 전달할 수 있습니다. -:--:1 - +또는 `app` 인수를 명시적으로 전달할 수 있습니다. :--:1 ```python async def explicit_inject(app): await asyncio.sleep(5) @@ -37,26 +33,69 @@ async def explicit_inject(app): app.add_task(explicit_inject(app)) ``` - :--- ## `app.run` 전에 작업 추가 (Adding tasks before `app.run`) -앱이 실행되기 전에 백그라운드 작업을 추가할 수 있습니다. `app.run` 전에. 앱이 실행되기 전에 작업을 추가하려면, 코루틴 객체를 전달하지 않는 것이 좋습니다. (즉, `async` 콜러블을 호출하여 생성된 것), 대신 콜러블을 전달하면 Sanic은 **각 작업자**에 코루틴 객체를 생성합니다. 참고: 추가된 작업은 'before_server_start' 작업으로 실행되므로 모든 작업자(메인 프로세스가 아님 에서 실행됩니다.이것은 특정한 결과를 가져옵니다. 자세한 내용은 이 [issue](https://github.com/sanic-org/sanic/issues/2139)에 대한 [comment](https://github.com/sanic-org/sanic/issues/2139#issuecomment-868993668)을 읽으십시오. +앱이 실행되기 전에 백그라운드 작업을 추가할 수 있습니다. `app.run` 전에. To add a task before the App is run, it is recommended to not pass the coroutine object (ie. one created by calling the `async` callable), but instead just pass the callable and Sanic will create the coroutine object on **each worker**. 참고: 추가된 작업은 'before_server_start' 작업으로 실행되므로 모든 작업자(메인 프로세스가 아님 에서 실행됩니다.이것은 특정한 결과를 가져옵니다. 자세한 내용은 이 [issue](https://github.com/sanic-org/sanic/issues/2139)에 대한 [comment](https://github.com/sanic-org/sanic/issues/2139#issuecomment-868993668)을 읽으십시오. 메인 프로세스에 작업을 추가하려면 [`@app.main_process_start`](./listeners.md)에 작업을 추가하는 것이 좋습니다. 참고: 작업자는 이 작업이 완료될 때까지 시작되지 않습니다. + ---:1 -`app.run` 전에 작업을 추가하는 예 -:---:1 +`app.run` 전에 작업을 추가하는 예 :---:1 ```python async def slow_work(...): ... +async def even_slower(num): + ... + app = Sanic(...) app.add_task(slow_work) # Note: we are passing the callable and not coroutine object `slow_work(...)` app.run(...) +app.add_task(even_slower(10)) # ... or we can call the function and pass the coroutine. +app.run(...) ``` -::: tip -위의 `slow_work`에 매개변수를 전달하려면 `functools.partial`을 사용할 수 있습니다. -::: \ No newline at end of file + +## Named tasks + +_This is only supported in Python 3.8+_ + +---:1 When creating a task, you can ask Sanic to keep track of it for you by providing a `name`. + +:--:1 +```python +app.add_task(slow_work, name="slow_task") +``` +:--- + +---:1 You can now retrieve that task instance from anywhere in your application using `get_task`. + +:--:1 +```python +task = app.get_task("slow_task") +``` +:--- + +---:1 If that task needs to be cancelled, you can do that with `cancel_task`. Make sure that you `await` it. + +:--:1 +```python +await app.cancel_task("slow_task") +``` +:--- + +---:1 All registered tasks can be found in the `app.tasks` property. To prevent cancelled tasks from filling up, you may want to run `app.purge_tasks` that will clear out any completed or cancelled tasks. + +:--:1 +```python +app.purge_tasks() +``` +:--- + +This pattern can be particularly useful with `websockets`: + +```python async def receiver(ws): while True: message = await ws.recv() if not message: break print(f"Received: {message}") + +@app.websocket("/feed") async def feed(request, ws): task_name = f"receiver:{request.id}" request.app.add_task(receiver(ws), name=task_name) try: while True: await request.app.event("my.custom.event") await ws.send("A message") finally: # When the websocket closes, let's cleanup the task await request.app.cancel_task(task_name) request.app.purge_tasks() ::: *Added in v21.12* diff --git a/src/ko/guide/best-practices/README.md b/src/ko/guide/best-practices/README.md new file mode 100644 index 0000000000..874ca7f3c6 --- /dev/null +++ b/src/ko/guide/best-practices/README.md @@ -0,0 +1 @@ +# Best Practices diff --git a/src/ko/guide/best-practices/blueprints.md b/src/ko/guide/best-practices/blueprints.md new file mode 100644 index 0000000000..6de1ceb47e --- /dev/null +++ b/src/ko/guide/best-practices/blueprints.md @@ -0,0 +1,369 @@ +# Blueprints + +## Overview + +Blueprints are objects that can be used for sub-routing within an application. Instead of adding routes to the application instance, blueprints define similar methods for adding routes, which are then registered with the application in a flexible and pluggable manner. + +Blueprints are especially useful for larger applications, where your application logic can be broken down into several groups or areas of responsibility. + +## Creating and registering + +---:1 + +First, you must create a blueprint. It has a very similar API as the `Sanic()` app instance with many of the same decorators. :--:1 +```python +# ./my_blueprint.py +from sanic.response import json +from sanic import Blueprint + +bp = Blueprint("my_blueprint") + +@bp.route("/") +async def bp_root(request): + return json({"my": "blueprint"}) +``` +:--- + + +---:1 + +Next, you register it with the app instance. :--:1 +```python +from sanic import Sanic +from my_blueprint import bp + +app = Sanic(__name__) +app.blueprint(bp) +``` +:--- + +Blueprints also have the same `websocket()` decorator and `add_websocket_route` method for implementing websockets. + +---:1 + +Beginning in v21.12, a Blueprint may be registered before or after adding objects to it. Previously, only objects attached to the Blueprint at the time of registration would be loaded into application instance. :--:1 +```python +app.blueprint(bp) + +@bp.route("/") +async def bp_root(request): + ... +``` +:--- +## Copying + +---:1 Blueprints along with everything that is attached to them can be copied to new instances using the `copy()` method. The only required argument is to pass it a new `name`. However, you could also use this to override any of the values from the old blueprint. :--:1 +```python +v1 = Blueprint("Version1", version=1) + +@v1.route("/something") +def something(request): + pass + +v2 = v1.copy("Version2", version=2) + +app.blueprint(v1) +app.blueprint(v2) +``` + +``` +Available routes: +/v1/something +/v2/something + +``` +:--- + +*Added in v21.9* + +## Blueprint groups + +Blueprints may also be registered as part of a list or tuple, where the registrar will recursively cycle through any sub-sequences of blueprints and register them accordingly. The Blueprint.group method is provided to simplify this process, allowing a ‘mock’ backend directory structure mimicking what’s seen from the front end. Consider this (quite contrived) example: + +```text +api/ +├──content/ +│ ├──authors.py +│ ├──static.py +│ └──__init__.py +├──info.py +└──__init__.py +app.py +``` + +---:1 + +#### First blueprint + +:--:1 +```python +# api/content/authors.py +from sanic import Blueprint + +authors = Blueprint("content_authors", url_prefix="/authors") +``` +:--- + +---:1 + +#### Second blueprint + +:--:1 +```python +# api/content/static.py +from sanic import Blueprint + +static = Blueprint("content_static", url_prefix="/static") +``` +:--- + +---:1 + +#### Blueprint group + +:--:1 +```python +# api/content/__init__.py +from sanic import Blueprint +from .static import static +from .authors import authors + +content = Blueprint.group(static, authors, url_prefix="/content") +``` +:--- + +---:1 + +#### Third blueprint + +:--:1 +```python +# api/info.py +from sanic import Blueprint + +info = Blueprint("info", url_prefix="/info") +``` +:--- + +---:1 + +#### Another blueprint group + +:--:1 +```python +# api/__init__.py +from sanic import Blueprint +from .content import content +from .info import info + +api = Blueprint.group(content, info, url_prefix="/api") +``` +:--- + +---:1 + +#### Main server + +All blueprints are now registered + +:--:1 +```python +# app.py +from sanic import Sanic +from .api import api + +app = Sanic(__name__) +app.blueprint(api) +``` +:--- + +## Middleware + +---:1 + +Blueprints can also have middleware that is specifically registered for its endpoints only. :--:1 +```python +@bp.middleware +async def print_on_request(request): + print("I am a spy") + +@bp.middleware("request") +async def halt_request(request): + return text("I halted the request") + +@bp.middleware("response") +async def halt_response(request, response): + return text("I halted the response") +``` +:--- + +---:1 + +Similarly, using blueprint groups, it is possible to apply middleware to an entire group of nested blueprints. :--:1 +```python +bp1 = Blueprint("bp1", url_prefix="/bp1") +bp2 = Blueprint("bp2", url_prefix="/bp2") + +@bp1.middleware("request") +async def bp1_only_middleware(request): + print("applied on Blueprint : bp1 Only") + +@bp1.route("/") +async def bp1_route(request): + return text("bp1") + +@bp2.route("/") +async def bp2_route(request, param): + return text(param) + +group = Blueprint.group(bp1, bp2) + +@group.middleware("request") +async def group_middleware(request): + print("common middleware applied for both bp1 and bp2") + +# Register Blueprint group under the app +app.blueprint(group) +``` +:--- + +## Exceptions + +---:1 + +Just like other [exception handling](./exceptions.md), you can define blueprint specific handlers. :--:1 +```python +@bp.exception(NotFound) +def ignore_404s(request, exception): + return text("Yep, I totally found the page: {}".format(request.url)) +``` +:--- + +## Static files + +---:1 + +Blueprints can also have their own static handlers :--:1 +```python +bp = Blueprint("bp", url_prefix="/bp") +bp.static("/web/path", "/folder/to/serve") +bp.static("/web/path", "/folder/to/server", name="uploads") +``` +:--- + +---:1 + +Which can then be retrieved using `url_for()`. See [routing](/guide/basics/routing.md) for more information. :--:1 +```python +>>> print(app.url_for("static", name="bp.uploads", filename="file.txt")) +'/bp/web/path/file.txt' +``` +:--- + +## Listeners + +---:1 + +Blueprints can also implement [listeners](/guide/basics/listeners.md). :--:1 +```python +@bp.listener("before_server_start") +async def before_server_start(app, loop): + ... + +@bp.listener("after_server_stop") +async def after_server_stop(app, loop): + ... +``` +:--- + +## Versioning + +As discussed in the [versioning section](/guide/advanced/versioning.md), blueprints can be used to implement different versions of a web API. + +---:1 + +The `version` will be prepended to the routes as `/v1` or `/v2`, etc. :--:1 +```python +auth1 = Blueprint("auth", url_prefix="/auth", version=1) +auth2 = Blueprint("auth", url_prefix="/auth", version=2) +``` +:--- + +---:1 + +When we register our blueprints on the app, the routes `/v1/auth` and `/v2/auth` will now point to the individual blueprints, which allows the creation of sub-sites for each API version. :--:1 +```python +from auth_blueprints import auth1, auth2 + +app = Sanic(__name__) +app.blueprint(auth1) +app.blueprint(auth2) +``` +:--- + +---:1 + +It is also possible to group the blueprints under a `BlueprintGroup` entity and version multiple of them together at the same time. :--:1 +```python +auth = Blueprint("auth", url_prefix="/auth") +metrics = Blueprint("metrics", url_prefix="/metrics") + +group = Blueprint.group(auth, metrics, version="v1") + +# This will provide APIs prefixed with the following URL path +# /v1/auth/ and /v1/metrics +``` +:--- + +## Composable + +A `Blueprint` may be registered to multiple groups, and each of `BlueprintGroup` itself could be registered and nested further. This creates a limitless possibility `Blueprint` composition. + +*Added in v21.6* ---:1 Take a look at this example and see how the two handlers are actually mounted as five (5) distinct routes. :--:1 +```python +app = Sanic(__name__) +blueprint_1 = Blueprint("blueprint_1", url_prefix="/bp1") +blueprint_2 = Blueprint("blueprint_2", url_prefix="/bp2") +group = Blueprint.group( + blueprint_1, + blueprint_2, + version=1, + version_prefix="/api/v", + url_prefix="/grouped", + strict_slashes=True, +) +primary = Blueprint.group(group, url_prefix="/primary") + + +@blueprint_1.route("/") +def blueprint_1_default_route(request): + return text("BP1_OK") + + +@blueprint_2.route("/") +def blueprint_2_default_route(request): + return text("BP2_OK") + + +app.blueprint(group) +app.blueprint(primary) +app.blueprint(blueprint_1) + +# The mounted paths: +# /api/v1/grouped/bp1/ +# /api/v1/grouped/bp2/ +# /api/v1/primary/grouped/bp1 +# /api/v1/primary/grouped/bp2 +# /bp1 + +``` +:--- + + +## Generating a URL + +When generating a url with `url_for()`, the endpoint name will be in the form: + +```text +{blueprint_name}.{handler_name} +``` diff --git a/src/ko/guide/best-practices/decorators.md b/src/ko/guide/best-practices/decorators.md new file mode 100644 index 0000000000..35a0772086 --- /dev/null +++ b/src/ko/guide/best-practices/decorators.md @@ -0,0 +1,182 @@ +# Decorators + +One of the best ways to create a consistent and DRY web API is to make use of decorators to remove functionality from the handlers, and make it repeatable across your views. + +---:1 + +Therefore, it is very common to see a Sanic view handler with several decorators on it. :--:1 +```python +@app.get("/orders") +@authorized("view_order") +@validate_list_params() +@inject_user() +async def get_order_details(request, params, user): + ... +``` +:--- + + +## Example + +Here is a starter template to help you create decorators. + +In this example, let’s say you want to check that a user is authorized to access a particular endpoint. You can create a decorator that wraps a handler function, checks a request if the client is authorized to access a resource, and sends the appropriate response. +```python +from functools import wraps +from sanic.response import json + +def authorized(): + def decorator(f): + @wraps(f) + async def decorated_function(request, *args, **kwargs): + # run some method that checks the request + # for the client's authorization status + is_authorized = await check_request_for_authorization_status(request) + + if is_authorized: + # the user is authorized. + # run the handler method and return the response + response = await f(request, *args, **kwargs) + return response + else: + # the user is not authorized. + return json({"status": "not_authorized"}, 403) + return decorated_function + return decorator + + +@app.route("/") +@authorized() +async def test(request): + return json({"status": "authorized"}) +``` + +## Templates + +Decorators are **fundamental** to building applications with Sanic. They increase the portability and maintainablity of your code. + +In paraphrasing the Zen of Python: "[decorators] are one honking great idea -- let's do more of those!" + +To make it easier to implement them, here are three examples of copy/pastable code to get you started. + +---:1 + +Don't forget to add these import statements. Although it is *not* necessary, using `@wraps` helps keep some of the metadata of your function intact. [See docs](https://docs.python.org/3/library/functools.html#functools.wraps). Also, we use the `isawaitable` pattern here to allow the route handlers to by regular or asynchronous functions. + +:--:1 + +```python +from inspect import isawaitable +from functools import wraps +``` + +:--- + +### With args + +---:1 + +Often, you will want a decorator that will *always* need arguments. Therefore, when it is implemented you will always be calling it. + +```python +@app.get("/") +@foobar(1, 2) +async def handler(request: Request): + return text("hi") +``` + +:--:1 + +```python +def foobar(arg1, arg2): + def decorator(f): + @wraps(f) + async def decorated_function(request, *args, **kwargs): + + response = f(request, *args, **kwargs) + if isawaitable(response): + response = await response + + return response + + return decorated_function + + return decorator +``` + +:--- + +### Without args + +---:1 + +Sometimes you want a decorator that will not take arguments. When this is the case, it is a nice convenience not to have to call it + +```python +@app.get("/") +@foobar +async def handler(request: Request): + return text("hi") +``` + +:--:1 + +```python +def foobar(func): + def decorator(f): + @wraps(f) + async def decorated_function(request, *args, **kwargs): + + response = f(request, *args, **kwargs) + if isawaitable(response): + response = await response + + return response + + return decorated_function + + return decorator(func) +``` + +:--- + +### With or Without args + +---:1 + +If you want a decorator with the ability to be called or not, you can follow this pattern. Using keyword only arguments is not necessary, but might make implementation simpler. + +```python +@app.get("/") +@foobar(arg1=1, arg2=2) +async def handler(request: Request): + return text("hi") +``` + +```python +@app.get("/") +@foobar +async def handler(request: Request): + return text("hi") +``` + +:--:1 + +```python +def foobar(maybe_func=None, *, arg1=None, arg2=None): + def decorator(f): + @wraps(f) + async def decorated_function(request, *args, **kwargs): + + response = f(request, *args, **kwargs) + if isawaitable(response): + response = await response + + return response + + return decorated_function + + return decorator(maybe_func) if maybe_func else decorator +``` + +:--- diff --git a/src/ko/guide/best-practices/exceptions.md b/src/ko/guide/best-practices/exceptions.md new file mode 100644 index 0000000000..42c2a44936 --- /dev/null +++ b/src/ko/guide/best-practices/exceptions.md @@ -0,0 +1,482 @@ +# Exceptions + +## Using Sanic exceptions + +Sometimes you just need to tell Sanic to halt execution of a handler and send back a status code response. You can raise a `SanicException` for this and Sanic will do the rest for you. + +You can pass an optional `status_code` argument. By default, a SanicException will return an internal server error 500 response. + +```python +from sanic.exceptions import SanicException + +@app.route("/youshallnotpass") +async def no_no(request): + raise SanicException("Something went wrong.", status_code=501) +``` + +Sanic provides a number of standard exceptions. They each automatically will raise the appropriate HTTP status code in your response. [Check the API reference](https://sanic.readthedocs.io/en/latest/sanic/api_reference.html#module-sanic.exceptions) for more details. + +---:1 + +The more common exceptions you _should_ implement yourself include: + +- `InvalidUsage` (400) +- `Unauthorized` (401) +- `Forbidden` (403) +- `NotFound` (404) +- `ServerError` (500) + +:--:1 + +```python +from sanic import exceptions + +@app.route("/login") +async def login(request): + user = await some_login_func(request) + if not user: + raise exceptions.NotFound( + f"Could not find user with username={request.json.username}" + ) + ... +``` + +:--- + +## Exception properties + +All exceptions in Sanic derive from `SanicException`. That class has a few properties on it that assist the developer in consistently reporting their exceptions across an application. + +- `message` +- `status_code` +- `quiet` +- `context` +- `extra` + +All of these properties can be passed to the exception when it is created, but the first three can also be used as class variables as we will see. + +---:1 +### `message` + +The `message` property obviously controls the message that will be displayed as with any other exception in Python. What is particularly useful is that you can set the `message` property on the class definition allowing for easy standardization of language across an application :--:1 +```python +class CustomError(SanicException): + message = "Something bad happened" + +raise CustomError +# or +raise CustomError("Override the default message with something else") +``` +:--- + +---:1 +### `status_code` + +This property is used to set the response code when the exception is raised. This can particularly be useful when creating custom 400 series exceptions that are usually in response to bad information coming from the client. :--:1 +```python +class TeapotError(SanicException): + status_code = 418 + message = "Sorry, I cannot brew coffee" + +raise TeapotError +# or +raise TeapotError(status_code=400) +``` +:--- + +---:1 +### `quiet` + +By default, exceptions will be output by Sanic to the `error_logger`. Sometimes this may not be desirable, especially if you are using exceptions to trigger events in exception handlers (see [the following section](./exceptions.md#handling)). You can suppress the log output using `quiet=True`. :--:1 +```python +class SilentError(SanicException): + message = "Something happened, but not shown in logs" + quiet = True + +raise SilentError +# or +raise InvalidUsage("blah blah", quiet=True) +``` +:--- + +---:1 Sometimes while debugging you may want to globally ignore the `quiet=True` property. You can force Sanic to log out all exceptions regardless of this property using `NOISY_EXCEPTIONS` + +*Added in v21.12* :--:1 +```python +app.config.NOISY_EXCEPTIONS = True +``` +:--- + +---:1 +### `extra` + +See [contextual exceptions](./exceptions.md#contextual-exceptions) + +*Added in v21.12* :--:1 +```python +raise SanicException(..., extra={"name": "Adam"}) +``` +:--- + +---:1 +### `context` + +See [contextual exceptions](./exceptions.md#contextual-exceptions) + +*Added in v21.12* :--:1 +```python +raise SanicException(..., context={"foo": "bar"}) +``` +:--- + + +## Handling + +Sanic handles exceptions automatically by rendering an error page, so in many cases you don't need to handle them yourself. However, if you would like more control on what to do when an exception is raised, you can implement a handler yourself. + +Sanic provides a decorator for this, which applies to not only the Sanic standard exceptions, but **any** exception that your application might throw. + +---:1 + +The easiest method to add a handler is to use `@app.exception()` and pass it one or more exceptions. + +:--:1 + +```python +from sanic.exceptions import NotFound + +@app.exception(NotFound, SomeCustomException) +async def ignore_404s(request, exception): + return text("Yep, I totally found the page: {}".format(request.url)) +``` + +:--- + +---:1 + +You can also create a catchall handler by catching `Exception`. + +:--:1 + +```python +@app.exception(Exception) +async def catch_anything(request, exception): + ... +``` + +:--- + +---:1 + +You can also use `app.error_handler.add()` to add error handlers. + +:--:1 + +```python +async def server_error_handler(request, exception): + return text("Oops, server error", status=500) + +app.error_handler.add(Exception, server_error_handler) +``` + +:--- + +## Built-in error handling + +Sanic ships with three formats for exceptions: HTML, JSON, and text. You can see examples of them below in the [Fallback handler](#fallback-handler) section. + +---:1 You can control _per route_ which format to use with the `error_format` keyword argument. + +*Added in v21.9* :--:1 +```python +@app.request("/", error_format="text") +async def handler(request): + ... +``` + +:--- + + +## Custom error handling + +In some cases, you might want to add some more error handling functionality to what is provided by default. In that case, you can subclass Sanic's default error handler as such: + +```python +from sanic.handlers import ErrorHandler + +class CustomErrorHandler(ErrorHandler): + def default(self, request, exception): + ''' handles errors that have no error handlers assigned ''' + # You custom error handling logic... + return super().default(request, exception) + +app.error_handler = CustomErrorHandler() +``` + +## Fallback handler + +Sanic comes with three fallback exception handlers: + +1. HTML (*default*) +2. Text +3. JSON + +These handlers present differing levels of detail depending upon whether your application is in [debug mode](/guide/deployment/development.md) or not. + +### HTML + +```python +app.config.FALLBACK_ERROR_FORMAT = "html" +``` + +---:1 + +```python +app.config.DEBUG = True +``` + +![Error](~@assets/images/error-html-debug.png) + +:--:1 + +```python +app.config.DEBUG = False +``` + +![Error](~@assets/images/error-html-no-debug.png) + +:--- + +### Text + +```python +app.config.FALLBACK_ERROR_FORMAT = "text" +``` + +---:1 + +```python +app.config.DEBUG = True +``` + +```bash +$ curl localhost:8000/exc -i +HTTP/1.1 500 Internal Server Error +content-length: 590 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +⚠️ 500 — Internal Server Error +============================== +That time when that thing broke that other thing? That happened. + +ServerError: That time when that thing broke that other thing? That happened. while handling path /exc +Traceback of __BASE__ (most recent call last): + + ServerError: That time when that thing broke that other thing? That happened. + File /path/to/sanic/app.py, line 986, in handle_request + response = await response + + File /path/to/server.py, line 222, in exc + raise ServerError( +``` + +:--:1 + +```python +app.config.DEBUG = False +``` + +```bash +$ curl localhost:8000/exc -i +HTTP/1.1 500 Internal Server Error +content-length: 134 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +⚠️ 500 — Internal Server Error +============================== +That time when that thing broke that other thing? That happened. +``` + +:--- + +### JSON + +```python +app.config.FALLBACK_ERROR_FORMAT = "json" +``` + +---:1 + +```python +app.config.DEBUG = True +``` + +```bash +$ curl localhost:8000/exc -i +HTTP/1.1 500 Internal Server Error +content-length: 129 +connection: keep-alive +content-type: application/json + +{ + "description": "Internal Server Error", + "status": 500, + "message": "That time when that thing broke that other thing? That happened.", + "path": "/exc", + "args": {}, + "exceptions": [ + { + "type": "ServerError", + "exception": "That time when that thing broke that other thing? That happened.", + "frames": [ + { + "file": "/path/to/sanic/app.py", + "line": 986, + "name": "handle_request", + "src": "response = await response" + }, + { + "file": "/path/to/server.py", + "line": 222, + "name": "exc", + "src": "raise ServerError(" + } + ] + } + ] +} + + +``` + +:--:1 + +```python +app.config.DEBUG = False +``` + +```bash +$ curl localhost:8000/exc -i +HTTP/1.1 500 Internal Server Error +content-length: 530 +connection: keep-alive +content-type: application/json + +{ + "description": "Internal Server Error", + "status": 500, + "message": "That time when that thing broke that other thing? That happened." +} + +``` + +:--- + +### Auto + +Sanic also provides an option for guessing which fallback option to use. This is still an **experimental feature**. + +```python +app.config.FALLBACK_ERROR_FORMAT = "auto" +``` +## Contextual Exceptions + +Default exception messages that simplify the ability to consistently raise exceptions throughout your application. + +```python +class TeapotError(SanicException): + status_code = 418 + message = "Sorry, I cannot brew coffee" + +raise TeapotError +``` + +But this lacks two things: + +1. A dynamic and predictable message format +2. The ability to add additional context to an error message (more on this in a moment) + +*Added in v21.12* + +### Dynamic and predictable message using `extra` + +Sanic exceptions can be raised using `extra` keyword arguments to provide additional information to a raised exception instance. + +```python +class TeapotError(SanicException): + status_code = 418 + + @property + def message(self): + return f"Sorry {self.extra['name']}, I cannot make you coffee" + +raise TeapotError(extra={"name": "Adam"}) +``` + +The new feature allows the passing of `extra` meta to the exception instance, which can be particularly useful as in the above example to pass dynamic data into the message text. This `extra` info object **will be suppressed** when in `PRODUCTION` mode, but displayed in `DEVELOPMENT` mode. + +---:1 **PRODUCTION** + +![image](https://user-images.githubusercontent.com/166269/139014161-cda67cd1-843f-4ad2-9fa1-acb94a59fc4d.png) :--:1 **DEVELOPMENT** + +![image](https://user-images.githubusercontent.com/166269/139014121-0596b084-b3c5-4adb-994e-31ba6eba6dad.png) :--- + +### Additional `context` to an error message + +Sanic exceptions can also be raised with a `context` argument to pass intended information along to the user about what happened. This is particularly useful when creating microservices or an API intended to pass error messages in JSON format. In this use case, we want to have some context around the exception beyond just a parseable error message to return details to the client. + +```python +raise TeapotError(context={"foo": "bar"}) +``` + +This is information **that we want** to always be passed in the error (when it is available). Here is what it should look like: + +---:1 **PRODUCTION** + +```json +{ + "description": "I'm a teapot", + "status": 418, + "message": "Sorry Adam, I cannot make you coffee", + "context": { + "foo": "bar" + } +} +``` +:--:1 **DEVELOPMENT** + +```json +{ + "description": "I'm a teapot", + "status": 418, + "message": "Sorry Adam, I cannot make you coffee", + "context": { + "foo": "bar" + }, + "path": "/", + "args": {}, + "exceptions": [ + { + "type": "TeapotError", + "exception": "Sorry Adam, I cannot make you coffee", + "frames": [ + { + "file": "handle_request", + "line": 83, + "name": "handle_request", + "src": "" + }, + { + "file": "/tmp/p.py", + "line": 17, + "name": "handler", + "src": "raise TeapotError(" + } + ] + } + ] +} +``` +:--- diff --git a/src/ko/guide/best-practices/logging.md b/src/ko/guide/best-practices/logging.md new file mode 100644 index 0000000000..427244df96 --- /dev/null +++ b/src/ko/guide/best-practices/logging.md @@ -0,0 +1,89 @@ +# Logging + +Sanic allows you to do different types of logging (access log, error log) on the requests based on the [Python logging API](https://docs.python.org/3/howto/logging.html). You should have some basic knowledge on Python logging if you want to create a new configuration. + +## Quick Start + +---:1 + +A simple example using default settings would be like this: :--:1 +```python +from sanic import Sanic +from sanic.log import logger +from sanic.response import text + +app = Sanic('logging_example') + +@app.route('/') +async def test(request): + logger.info('Here is your log') + return text('Hello World!') + +if __name__ == "__main__": + app.run(debug=True, access_log=True) +``` +:--- + +After the server is running, you should see logs like this. +```text +[2021-01-04 15:26:26 +0200] [1929659] [INFO] Goin' Fast @ http://127.0.0.1:8000 +[2021-01-04 15:26:26 +0200] [1929659] [INFO] Starting worker [1929659] +``` + +You can send a request to server and it will print the log messages. +```text +[2021-01-04 15:26:28 +0200] [1929659] [INFO] Here is your log +[2021-01-04 15:26:28 +0200] - (sanic.access)[INFO][127.0.0.1:44228]: GET http://localhost:8000/ 200 -1 +``` + +## Changing Sanic loggers + +To use your own logging config, simply use `logging.config.dictConfig`, or pass `log_config` when you initialize Sanic app. + +```python +app = Sanic('logging_example', log_config=LOGGING_CONFIG) + +if __name__ == "__main__": + app.run(access_log=False) +``` + +::: tip FYI Logging in Python is a relatively cheap operation. However, if you are serving a high number of requests and performance is a concern, all of that time logging out access logs adds up and becomes quite expensive. + +This is a good opportunity to place Sanic behind a proxy (like nginx) and to do your access logging there. You will see a *significant* increase in overall performance by disabling the `access_log`. + +For optimal production performance, it is advised to run Sanic with `debug` and `access_log` disabled: `app.run(debug=False, access_log=False)` ::: + +## Configuration + +Sanic's default logging configuration is: `sanic.log.LOGGING_CONFIG_DEFAULTS`. + +---:1 There are three loggers used in sanic, and must be defined if you want to create your own logging configuration: + +| **Logger Name** | **Use Case** | +| --------------- | ------------------------------ | +| `sanic.root` | Used to log internal messages. | +| `sanic.error` | Used to log error logs. | +| `sanic.access` | Used to log access logs. | + :--:1 + +:--- + +### Log format + +In addition to default parameters provided by Python (`asctime`, `levelname`, `message`), Sanic provides additional parameters for access logger with. + +| Log Context Parameter | Parameter Value | Datatype | +| --------------------- | ------------------------------------ | -------- | +| `host` | `request.ip` | `str` | +| `request` | `request.method + " " + request.url` | `str` | +| `status` | `response` | `int` | +| `byte` | `len(response.body)` | `int` | + + + + +The default access log format is: + +```text +%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: %(request)s %(message)s %(status)d %(byte)d +``` diff --git a/src/ko/guide/best-practices/testing.md b/src/ko/guide/best-practices/testing.md new file mode 100644 index 0000000000..a7ac6fdaf6 --- /dev/null +++ b/src/ko/guide/best-practices/testing.md @@ -0,0 +1,3 @@ +# Testing + +See [sanic-testing](../../plugins/sanic-testing/getting-started.md) diff --git a/src/ko/guide/deployment/README.md b/src/ko/guide/deployment/README.md new file mode 100644 index 0000000000..d36c65e59e --- /dev/null +++ b/src/ko/guide/deployment/README.md @@ -0,0 +1 @@ +# Deployment diff --git a/src/ko/guide/deployment/app-loader.md b/src/ko/guide/deployment/app-loader.md new file mode 100644 index 0000000000..960138e3a9 --- /dev/null +++ b/src/ko/guide/deployment/app-loader.md @@ -0,0 +1,75 @@ +# Dynamic Applications + +Running Sanic has been optimized to work with the CLI. If you have not read it yet, you should read [Running Sanic](./running.md#sanic-server) to become familiar with the options. + +---:1 This includes running it as a global scope object... :--:1 +```python +# server.py +app = Sanic("TestApp") + +@app.get("/") +async def handler(request: Request): + return json({"foo": "bar"}) +``` +``` +sanic path.to.server:app +``` +:--- + + +---:1 ...or, a factory function that creates the `Sanic` application object. :--:1 +```python +# server.py +def create_app(): + app = Sanic("TestApp") + + @app.get("/") + async def handler(request: Request): + return json({"foo": "bar"}) + + return app +``` +``` +sanic path.to.server:create_app --factory +``` +:--- + + +**Sometimes, this is not enough ... :thinking:** + +Introduced in [v22.9](../release-notes/v22.9.md), Sanic has an `AppLoader` object that is responsible for creating an application in the various [worker processes](./manager.md#how-sanic-server-starts-processes). You can take advantage of this if you need to create a more dynamic startup experience for your application. + +---:1 An `AppLoader` can be passed a callable that returns a `Sanic` instance. That `AppLoader` could be used with the low-level application running API. :--:1 +```python +import sys +from functools import partial + +from sanic import Request, Sanic, json +from sanic.worker.loader import AppLoader + + +def attach_endpoints(app: Sanic): + @app.get("/") + async def handler(request: Request): + return json({"app_name": request.app.name}) + + +def create_app(app_name: str) -> Sanic: + app = Sanic(app_name) + attach_endpoints(app) + return app + + +if __name__ == "__main__": + app_name = sys.argv[-1] + loader = AppLoader(factory=partial(create_app, app_name)) + app = loader.load() + app.prepare(port=9999, dev=True) + Sanic.serve(primary=app, app_loader=loader) +``` +``` +$ python path/to/server.py MyTestAppName +``` +:--- + +In the above example, the `AppLoader` is created with a `factory` that can be used to create copies of the same application across processes. When doing this, you should explicitly use the `Sanic.serve` pattern shown above so that the `AppLoader` that you create is not replaced. diff --git a/src/ko/guide/deployment/configuration.md b/src/ko/guide/deployment/configuration.md new file mode 100644 index 0000000000..9b00ab3886 --- /dev/null +++ b/src/ko/guide/deployment/configuration.md @@ -0,0 +1,237 @@ +# Configuration + +## Basics + + +---:1 + +Sanic holds the configuration in the config attribute of the application object. The configuration object is merely an object that can be modified either using dot-notation or like a dictionary. :--:1 +```python +app = Sanic("myapp") +app.config.DB_NAME = "appdb" +app.config["DB_USER"] = "appuser" +``` +:--- + +---:1 + +You can also use the `update()` method like on regular dictionaries. :--:1 +```python +db_settings = { + 'DB_HOST': 'localhost', + 'DB_NAME': 'appdb', + 'DB_USER': 'appuser' +} +app.config.update(db_settings) +``` +:--- + +::: tip It is standard practice in Sanic to name your config values in **uppercase letters**. Indeed, you may experience weird behaviors if you start mixing uppercase and lowercase names. ::: + +## Loading + +### Environment variables + +---:1 + +Any environment variables defined with the `SANIC_` prefix will be applied to the Sanic config. For example, setting `SANIC_REQUEST_TIMEOUT` will be loaded by the application automatically and fed into the `REQUEST_TIMEOUT` config variable. :--:1 +```bash +$ export SANIC_REQUEST_TIMEOUT=10 +``` +```python +>>> print(app.config.REQUEST_TIMEOUT) +10 +``` +:--- + +---:1 + +You can change the prefix that Sanic is expecting at startup. :--:1 +```bash +$ export MYAPP_REQUEST_TIMEOUT=10 +``` +```python +>>> app = Sanic(__name__, env_prefix='MYAPP_') +>>> print(app.config.REQUEST_TIMEOUT) +10 +``` +:--- + +---:1 + +You can also disable environment variable loading completely. :--:1 +```python +app = Sanic(__name__, load_env=False) +``` +:--- + +### Using Sanic.update_config + +The `Sanic` instance has a _very_ versatile method for loading config: `app.update_config`. You can feed it a path to a file, a dictionary, a class, or just about any other sort of object. + +#### From a file + +---:1 + +Let's say you have `my_config.py` file that looks like this. :--:1 +```python +# my_config.py +A = 1 +B = 2 +``` +:--- + +---:1 + +You can load this as config values by passing its path to `app.update_config`. :--:1 +```python +>>> app.update_config("/path/to/my_config.py") +>>> print(app.config.A) +1 +``` +:--- + +---:1 + +This path also accepts bash style environment variables. :--:1 +```bash +$ export my_path="/path/to" +``` +```python +app.update_config("${my_path}/my_config.py") +``` +:--- + +::: tip Just remember that you have to provide environment variables in the format `${environment_variable}` and that `$environment_variable` is not expanded (is treated as "plain" text). ::: +#### From a dict + +---:1 + +The `app.update_config` method also works on plain dictionaries. :--:1 +```python +app.update_config({"A": 1, "B": 2}) +``` +:--- + +#### From a class or object + +---:1 + +You can define your own config class, and pass it to `app.update_config` :--:1 +```python +class MyConfig: + A = 1 + B = 2 + +app.update_config(MyConfig) +``` +:--- + +---:1 + +It even could be instantiated. :--:1 +```python +app.update_config(MyConfig()) +``` +:--- + +### Type casting + +When loading from environment variables, Sanic will attempt to cast the values to expected Python types. This particularly applies to: + +- `int` +- `float` +- `bool` + +In regards to `bool`, the following _case insensitive_ values are allowed: + +- **`True`**: `y`, `yes`, `yep`, `yup`, `t`, `true`, `on`, `enable`, `enabled`, `1` +- **`False`**: `n`, `no`, `f`, `false`, `off`, `disable`, `disabled`, `0` + +If a value cannot be cast, it will default to a `str`. + +---:1 Additionally, Sanic can be configured to cast additional types using additional type converters. This should be any callable that returns the value or raises a `ValueError`. + +*Added in v21.12* :--:1 +```python +app = Sanic(..., config=Config(converters=[UUID])) +``` +:--- + +## Builtin values + + +| **Variable** | **Default** | **Description** | +| --------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| ACCESS_LOG | True | Disable or enable access log | +| AUTO_EXTEND | True | Control whether [Sanic Extensions](../../plugins/sanic-ext/getting-started.md) will load if it is in the existing virtual environment | +| AUTO_RELOAD | True | Control whether the application will automatically reload when a file changes | +| EVENT_AUTOREGISTER | True | When `True` using the `app.event()` method on a non-existing signal will automatically create it and not raise an exception | +| FALLBACK_ERROR_FORMAT | html | Format of error response if an exception is not caught and handled | +| FORWARDED_FOR_HEADER | X-Forwarded-For | The name of "X-Forwarded-For" HTTP header that contains client and proxy ip | +| FORWARDED_SECRET | None | Used to securely identify a specific proxy server (see below) | +| GRACEFUL_SHUTDOWN_TIMEOUT | 15.0 | How long to wait to force close non-idle connection (sec) | +| INSPECTOR | False | Whether to enable the Inspector | +| INSPECTOR_HOST | localhost | The host for the Inspector | +| INSPECTOR_PORT | 6457 | The port for the Inspector | +| INSPECTOR_TLS_KEY | - | The TLS key for the Inspector | +| INSPECTOR_TLS_CERT | - | The TLS certificate for the Inspector | +| INSPECTOR_API_KEY | - | The API key for the Inspector | +| KEEP_ALIVE_TIMEOUT | 5 | How long to hold a TCP connection open (sec) | +| KEEP_ALIVE | True | Disables keep-alive when False | +| MOTD | True | Whether to display the MOTD (message of the day) at startup | +| MOTD_DISPLAY | {} | Key/value pairs to display additional, arbitrary data in the MOTD | +| NOISY_EXCEPTIONS | False | Force all `quiet` exceptions to be logged | +| PROXIES_COUNT | None | The number of proxy servers in front of the app (e.g. nginx; see below) | +| REAL_IP_HEADER | None | The name of "X-Real-IP" HTTP header that contains real client ip | +| REGISTER | True | Whether the app registry should be enabled | +| REQUEST_BUFFER_SIZE | 65536 | Request buffer size before request is paused, default is 64 Kib | +| REQUEST_ID_HEADER | X-Request-ID | The name of "X-Request-ID" HTTP header that contains request/correlation ID | +| REQUEST_MAX_SIZE | 100000000 | How big a request may be (bytes), default is 100 megabytes | +| REQUEST_TIMEOUT | 60 | How long a request can take to arrive (sec) | +| RESPONSE_TIMEOUT | 60 | How long a response can take to process (sec) | +| USE_UVLOOP | True | Whether to override the loop policy to use `uvloop`. Supported only with `app.run`. | +| WEBSOCKET_MAX_SIZE | 2^20 | Maximum size for incoming messages (bytes) | +| WEBSOCKET_PING_INTERVAL | 20 | A Ping frame is sent every ping_interval seconds. | +| WEBSOCKET_PING_TIMEOUT | 20 | Connection is closed when Pong is not received after ping_timeout seconds | + +::: tip FYI +- The `USE_UVLOOP` value will be ignored if running with Gunicorn. Defaults to `False` on non-supported platforms (Windows). +- The `WEBSOCKET_` values will be ignored if in ASGI mode. +- v21.12 added: `AUTO_EXTEND`, `MOTD`, `MOTD_DISPLAY`, `NOISY_EXCEPTIONS` +- v22.9 added: `INSPECTOR` +- v22.12 added: `INSPECTOR_HOST`, `INSPECTOR_PORT`, `INSPECTOR_TLS_KEY`, `INSPECTOR_TLS_CERT`, `INSPECTOR_API_KEY` ::: + +## Timeouts + +### REQUEST_TIMEOUT + +A request timeout measures the duration of time between the instant when a new open TCP connection is passed to the Sanic backend server, and the instant when the whole HTTP request is received. If the time taken exceeds the `REQUEST_TIMEOUT` value (in seconds), this is considered a Client Error so Sanic generates an `HTTP 408` response and sends that to the client. Set this parameter's value higher if your clients routinely pass very large request payloads or upload requests very slowly. + +### RESPONSE_TIMEOUT + +A response timeout measures the duration of time between the instant the Sanic server passes the HTTP request to the Sanic App, and the instant a HTTP response is sent to the client. If the time taken exceeds the `RESPONSE_TIMEOUT` value (in seconds), this is considered a Server Error so Sanic generates an `HTTP 503` response and sends that to the client. Set this parameter's value higher if your application is likely to have long-running process that delay the generation of a response. + +### KEEP_ALIVE_TIMEOUT + +#### What is Keep Alive? And what does the Keep Alive Timeout value do? + +`Keep-Alive` is a HTTP feature introduced in `HTTP 1.1`. When sending a HTTP request, the client (usually a web browser application) can set a `Keep-Alive` header to indicate the http server (Sanic) to not close the TCP connection after it has send the response. This allows the client to reuse the existing TCP connection to send subsequent HTTP requests, and ensures more efficient network traffic for both the client and the server. + +The `KEEP_ALIVE` config variable is set to `True` in Sanic by default. If you don't need this feature in your application, set it to `False` to cause all client connections to close immediately after a response is sent, regardless of the `Keep-Alive` header on the request. + +The amount of time the server holds the TCP connection open is decided by the server itself. In Sanic, that value is configured using the `KEEP_ALIVE_TIMEOUT` value. By default, it is set to 5 seconds. This is the same default setting as the Apache HTTP server and is a good balance between allowing enough time for the client to send a new request, and not holding open too many connections at once. Do not exceed 75 seconds unless you know your clients are using a browser which supports TCP connections held open for that long. + +For reference: + +* Apache httpd server default keepalive timeout = 5 seconds +* Nginx server default keepalive timeout = 75 seconds +* Nginx performance tuning guidelines uses keepalive = 15 seconds +* IE (5-9) client hard keepalive limit = 60 seconds +* Firefox client hard keepalive limit = 115 seconds +* Opera 11 client hard keepalive limit = 120 seconds +* Chrome 13+ client keepalive limit > 300+ seconds + +## Proxy configuration + +See [proxy configuration section](/guide/advanced/proxy-headers.md) diff --git a/src/ko/guide/deployment/development.md b/src/ko/guide/deployment/development.md new file mode 100644 index 0000000000..881a0be291 --- /dev/null +++ b/src/ko/guide/deployment/development.md @@ -0,0 +1,97 @@ +# Development + +The first thing that should be mentioned is that the webserver that is integrated into Sanic is **not** just a development server. + +It is production ready out-of-the-box, *unless you enable in debug mode*. + +## Debug mode + +By setting the debug mode, Sanic will be more verbose in its output and will disable several run-time optimizations. + +```python +from sanic import Sanic +from sanic.response import json + +app = Sanic(__name__) + +@app.route("/") +async def hello_world(request): + return json({"hello": "world"}) + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=1234, debug=True) +``` + +::: warning +Sanic's debug mode will slow down the server's performance and is therefore advised to enable it only in development environments. +::: +## Automatic Reloader + +---:1 + +Sanic offers a way to enable or disable the Automatic Reloader. The `auto_reload` argument will activate or deactivate the Automatic Reloader. Every time a Python file is changed, the reloader will restart your application automatically. This is very convenient while developing. :--:1 +```python +app.run(auto_reload=True) +``` +:--- + +---:1 If you have additional directories that you would like to automatically reload on file save (for example, a directory of HTML templates), you can add that at run time. :--:1 +```python +app.run(auto_reload=True, reload_dir="/path/to/templates") +# or multiple directories +app.run(auto_reload=True, reload_dir=["/path/to/one", "/path/to/two"]) +``` +:--- + +## Best of both worlds +---:1 If you would like to be in debug mode **and** have the Automatic Reloader running, you can pass `dev=True`. This is equivalent to **debug + auto reload**. + +*Added in v22.3* :--:1 +```python +app.run(dev=True) +``` +:--- + +## Automatic TLS certificate + +When running in `DEBUG` mode, you can ask Sanic to handle setting up localhost temporary TLS certificates. This is helpful if you want to access your local development environment with `https://`. + +This functionality is provided by either [mkcert](https://github.com/FiloSottile/mkcert) or [trustme](https://github.com/python-trio/trustme). Both are good choices, but there are some differences. `trustme` is a Python library and can be installed into your environment with `pip`. This makes for easy envrionment handling, but it is not compatible when running a HTTP/3 server. `mkcert` might be a more involved installation process, but can install a local CA and make it easier to use. + +---:1 You can choose which platform to use by setting `config.LOCAL_CERT_CREATOR`. When set to `"auto"`, it will select either option, preferring `mkcert` if possible. :--:1 +```python +app.config.LOCAL_CERT_CREATOR = "auto" +app.config.LOCAL_CERT_CREATOR = "mkcert" +app.config.LOCAL_CERT_CREATOR = "trustme" +``` +:--- + + +---:1 Automatic TLS can be enabled at Sanic server run time: :--:1 +``` +$ sanic path.to.server:app --auto-tls --debug +``` + +```python +app.run(debug=True, auto_tls=True) +``` +:--- + +::: warning + +Localhost TLS certificates (like those generated by both `mkcert` and `trustme`) are **NOT** suitable for production environments. If you are not familiar with how to obtain a *real* TLS certificate, checkout the [How to...](../how-to/tls.md) section. ::: + +*Added in v22.6* + +## CLI + +It should be noted that all of these have an equivalent in the Sanic CLI: + +``` +Development: + --debug Run the server in debug mode + -r, --reload, --auto-reload Watch source directory for file changes and reload on changes + -R PATH, --reload-dir PATH Extra directories to watch and reload on changes + -d, --dev debug + auto reload + --auto-tls Create a temporary TLS certificate for local development (requires mkcert or trustme) +``` diff --git a/src/ko/guide/deployment/docker.md b/src/ko/guide/deployment/docker.md new file mode 100644 index 0000000000..47f4966b27 --- /dev/null +++ b/src/ko/guide/deployment/docker.md @@ -0,0 +1,183 @@ +# Docker Deployment + +## Introduction + +For a long time, the environment has always been a difficult problem for deployment. If there are conflicting configurations in your project, you have to spend a lot of time resolving them. Fortunately, virtualization provides us with a good solution. Docker is one of them. If you don't know Docker, you can visit [Docker official website](https://www.docker.com/) to learn more. + +## Build Image + +Let's start with a simple project. We will use a Sanic project as an example. Assume the project path is `/path/to/SanicDocker`. + +---:1 + +The directory structure looks like this: + +:--:1 + +```text +# /path/to/SanicDocker +SanicDocker +├── requirements.txt +├── dockerfile +└── server.py +``` + +:--- + +---:1 + +And the `server.py` code looks like this: + +:--:1 + +```python +app = Sanic("MySanicApp") + +@app.get('/') +async def hello(request): + return text("OK!") + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8000) +``` + +:--- + +::: tip + +Please note that the host cannot be 127.0.0.1 . In docker container, 127.0.0.1 is the default network interface of the container, only the container can communicate with other containers. more information please visit [Docker network](https://docs.docker.com/engine/reference/commandline/network/) + +::: + +Code is ready, let's write the `Dockerfile`: + +```Dockerfile + +FROM sanicframework/sanic:3.8-latest + +WORKDIR /sanic + +COPY . . + +RUN pip install -r requirements.txt + +EXPOSE 8000 + +CMD ["python", "server.py"] +``` + +Run the following command to build the image: + +```shell +docker build -t my-sanic-image . +``` + +## Start Container + +---:1 + +After the image built, we can start the container use `my-sanic-image`: + +:--:1 + +```shell +docker run --name mysanic -p 8000:8000 -d my-sanic-image +``` + +:--- + +---:1 + +Now we can visit `http://localhost:8000` to see the result: + +:--:1 + +```text +OK! +``` + +:--- + +## Use docker-compose + +If your project consist of multiple services, you can use [docker-compose](https://docs.docker.com/compose/) to manage them. + +for example, we will deploy `my-sanic-image` and `nginx`, achieve through nginx access sanic server. + +---:1 + +First of all, we need prepare nginx configuration file. create a file named `mysanic.conf`: + +:--:1 + +```nginx +server { + listen 80; + listen [::]:80; + location / { + proxy_pass http://mysanic:8000/; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection upgrade; + proxy_set_header Accept-Encoding gzip; + } +} +``` + +:--- + +---:1 + +Then, we need to prepare `docker-compose.yml` file. The content follows: + +:--:1 + +```yml +version: "3" + +services: + mysanic: + image: my-sanic-image + ports: + - "8000:8000" + restart: always + + mynginx: + image: nginx:1.13.6-alpine + ports: + - "80:80" + depends_on: + - mysanic + volumes: + - ./mysanic.conf:/etc/nginx/conf.d/mysanic.conf + restart: always + +networks: + default: + driver: bridge +``` + +:--- + +---:1 + +After that, we can start them: + +:--:1 + +```shell +docker-compose up -d +``` + +:--- + +---:1 + +Now, we can visit `http://localhost:80` to see the result: + +:--:1 + +```text +OK! +``` + +:--- diff --git a/src/ko/guide/deployment/inspector.md b/src/ko/guide/deployment/inspector.md new file mode 100644 index 0000000000..81cacb3496 --- /dev/null +++ b/src/ko/guide/deployment/inspector.md @@ -0,0 +1,160 @@ +# Inspector + +The Sanic Inspector is a feature of Sanic Server. It is *only* available when running Sanic with the built-in [worker manager](./manager.md). + +It is an HTTP application that *optionally* runs in the background of your application to allow you to interact with the running instance of your application. + +::: tip INFO +The Inspector was introduced in limited capacity in v22.9, but the documentation on this page assumes you are using v22.12 or higher. +::: + +## Getting Started + +The inspector is disabled by default. To enable it, you have two options. + +---:1 Set a flag when creating your application instance. :--:1 +```python +app = Sanic("TestApp", inspector=True) +``` +:--- + +---:1 Or, set a configuration value. :--:1 +```python +app = Sanic("TestApp") +app.config.INSPECTOR = True +``` +:--- + +::: warning +If you are using the configuration value, it *must* be done early and before the main worker process starts. This means that it should either be an environment variable, or it should be set shortly after creating the application instance as shown above. +::: + +## Using the Inspector + +Once the inspector is running, you will have access to it via the CLI or by directly accessing its web API via HTTP. + +---:1 **Via CLI** +``` +$ sanic inspect +``` +:--:1 **Via HTTP** +``` +$ curl http://localhost:6457 +``` +:--- + +::: tip +Remember, the Inspector is not running on your Sanic application. It is a seperate process, with a seperate application, and exposed on a seperate socket. +::: + +## Built-in Commands + +The Inspector comes with the following built-in commands. + +| CLI Command | HTTP Action | Description | +| ------------------ | ---------------------------------------- | ------------------------------------------------------------------------ | +| `inspect` | `GET /` | Display basic details about the running application. | +| `inspect reload` | `POST /reload` | Trigger a reload of all server workers. | +| `inspect shutdown` | `POST /shutdown` | Trigger a shutdown of all processes. | +| `inspect scale N` | `POST /scale`
`{"replicas": N}` | Scale the number of workers. Where `N` is the target number of replicas. | + +## Custom Commands + +The Inspector is easily extendable to add custom commands (and endpoints). + +---:1 Subclass the `Inspector` class and create arbitrary methods. As long as the method name is not preceded by an underscore (`_`), then the name of the method will be a new subcommand on the inspector. :--:1 +```python +from sanic import json +from sanic.worker.inspector import Inspector + + +class MyInspector(Inspector): + async def something(self, *args, **kwargs): + print(args) + print(kwargs) + + +app = Sanic("TestApp", inspector_class=MyInspector, inspector=True) +``` +:--- + +This will expose custom methods in the general pattern: + +- CLI: `sanic inspect ` +- HTTP: `POST /` + +It is important to note that the arguments that the new method accepts are derived from how you intend to use the command. For example, the above `something` method accepts all positional and keyword based parameters. + +---:1 In the CLI, the positional and keyword parameters are passed as either positional or keyword arguments to your method. All values will be a `str` with the following exceptions: + +- A keyword parameter with no assigned value will be: `True` +- Unless the parameter is prefixed with `no-`, then it will be: `False` :--:1 +``` +$ sanic inspect something one two three --four --no-five --six=6 +``` +In your application log console, you will see: +``` +('one', 'two', 'three') +{'four': True, 'five': False, 'six': '6'} +``` +:--- + +---:1 The same can be achieved by hitting the API directly. You can pass arguments to the method by exposing them in a JSON payload. The only thing to note is that the positional arguments should be exposed as `{"args": [...]}`. :--:1 +``` +$ curl http://localhost:6457/something \ + --json '{"args":["one", "two", "three"], "four":true, "five":false, "six":6}' +``` +In your application log console, you will see: +``` +('one', 'two', 'three') +{'four': True, 'five': False, 'six': 6} +``` +:--- + + +## Using in production + +::: warning +Before exposing the Inspector on a product, please consider all of the options in this section carefully. +::: + +When running Inspector on a remote production instance, you can protect the endpoints by requiring TLS encryption, and requiring API key authentication. + +### TLS encryption + +---:1 To the Inspector HTTP instance over TLS, pass the paths to your certificate and key. :--:1 +```python +app.config.INSPECTOR_TLS_CERT = "/path/to/cert.pem" +app.config.INSPECTOR_TLS_KEY = "/path/to/key.pem" +``` +:--- + +---:1 This will require use of the `--secure` flag, or `https://`. :--:1 +``` +$ sanic insect --secure --host= +``` +``` +$ curl https://:6457 +``` +:--- + +### API Key Authentication + +---:1 You can secure the API with bearer token authentication. :--:1 +```python +app.config.INSPECTOR_API_KEY = "Super-Secret-200" +``` +:--- + +---:1 This will require the `--api-key` parameter, or bearer token authorization header. :--:1 +``` +$ sanic inspect --api-key=Super-Secret-200 +``` +``` +$ curl http://localhost:6457 -H "Authorization: Bearer Super-Secret-200" +``` +:--- + +## Configuration + +See [configuration](./configuration.md) diff --git a/src/ko/guide/deployment/kubernetes.md b/src/ko/guide/deployment/kubernetes.md new file mode 100644 index 0000000000..8d6340de1c --- /dev/null +++ b/src/ko/guide/deployment/kubernetes.md @@ -0,0 +1 @@ +# Kubernetes diff --git a/src/ko/guide/deployment/manager.md b/src/ko/guide/deployment/manager.md new file mode 100644 index 0000000000..4c4153b14d --- /dev/null +++ b/src/ko/guide/deployment/manager.md @@ -0,0 +1,290 @@ +# Worker Manager + +The worker manager and its functionality was introduced in version 22.9. + +*The details of this section are intended for more advanced usages and **not** necessary to get started.* + +The purpose of the manager is to create consistency and flexibility between development and production environments. Whether you intend to run a single worker, or multiple workers, whether with, or without auto-reload: the experience will be the same. + + +In general it looks like this: + +![](https://user-images.githubusercontent.com/166269/178677618-3b4089c3-6c6a-4ecc-8d7a-7eba2a7f29b0.png) + +When you run Sanic, the main process instantiates a `WorkerManager`. That manager is in charge of running one or more `WorkerProcess`. There generally are two kinds of processes: + +- server processes, and +- non-server processes. + +For the sake of ease, the User Guide generally will use the term "worker" or "worker process" to mean a server process, and "Manager" to mean the single worker manager running in your main process. + +## How Sanic Server starts processes + +Sanic will start processes using the [spawn](https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods) start method. This means that for every process/worker, the global scope of your application will be run on its own thread. The practical impact of this that *if* you do not run Sanic with the CLI, you will need to nest the execution code inside a block to make sure it only runs on `__main__`. + +```python +if __name__ == "__main__": + app.run() +``` + +If you do not, you are likely to see an error message like this: + +``` +sanic.exceptions.ServerError: Sanic server could not start: [Errno 98] Address already in use. + +This may have happened if you are running Sanic in the global scope and not inside of a `if __name__ == "__main__"` block. + +See more information: https://sanic.dev/en/guide/deployment/manager.html#how-sanic-server-starts-processes +``` + +The likely fix for this problem is nesting your Sanic run call inside of the `__name__ == "__main__"` block. If you continue to receive this message after nesting, or if you see this while using the CLI, then it means the port you are trying to use is not available on your machine and you must select another port. + +### Starting a worker + +All worker processes *must* send an acknowledgement when starting. This happens under the hood, and you as a developer do not need to do anything. However, the Manager will exit with a status code `1` if one or more workers do not send that `ack` message, or a worker process throws an exception while trying to start. If no exceptions are encountered, the Manager will wait for up to thirty (30) seconds for the acknowledgement. + +---:1 In the situation when you know that you will need more time to start, you can monkeypatch the Manager. The threshold does not include anything inside of a listener, and is limited to the execution time of everything in the global scope of your application. + +If you run into this issue, it may indicate a need to look deeper into what is causing the slow startup. :--:1 +```python +from sanic.worker.manager import WorkerManager + +WorkerManager.THRESHOLD = 100 # Value is in 0.1s +``` +:--- + +See [worker ack](#worker-ack) for more information. + +---:1 As stated above, Sanic will use [spawn](https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods) to start worker processes. If you would like to change this behavior and are aware of the implications of using different start methods, you can modify as shown here. :--:1 +```python +from sanic import Sanic + +Sanic.start_method = "fork" +``` +:--- + + +### Worker ack + +When all of your workers are running in a subprocess a potential problem is created: deadlock. This can occur when the child processes cease to function, but the main process is unaware that this happened. Therefore, Sanic servers will automatically send an `ack` message (short for acknowledge) to the main process after startup. + +In version 22.9, the `ack` timeout was short and limited to `5s`. In version 22.12, the timeout was lengthened to `30s`. If your application is shutting down after thirty seconds then it might be necessary to manually increase this threshhold. + +---:1 The value of `WorkerManager.THRESHOLD` is in `0.1s` increments. Therefore, to set it to one minute, you should set the value to `600`. + +This value should be set as early as possible in your application, and should ideally happen in the global scope. Setting it after the main process has started will not work. :--:1 +```python +from sanic.worker.manager import WorkerManager + +WorkerManager.THRESHOLD = 600 +``` +:--- + + + + +::: new NEW in v22.12 +### Zero downtime restarts + +By default, when restarting workers, Sanic will teardown the existing process first before starting a new one. + +If you are intending to use the restart functionality in production then you may be interested in having zero-downtime reloading. This can be accomplished by forcing the reloader to change the order to start a new process, wait for it to [ack](#worker-ack), and then teardown the old process. + +---:1 From the multiplexer, use the `zero_downtime` argument :--:1 +```python +app.m.restart(zero_downtime=True) +``` +:--- + +*Added in v22.12* +::: + +## Using shared context between worker processes + +Python provides a few methods for [exchanging objects](https://docs.python.org/3/library/multiprocessing.html#exchanging-objects-between-processes), [synchronizing](https://docs.python.org/3/library/multiprocessing.html#synchronization-between-processes), and [sharing state](https://docs.python.org/3/library/multiprocessing.html#sharing-state-between-processes) between processes. This usually involves objects from the `multiprocessing` and `ctypes` modules. + +If you are familiar with these objects and how to work with them, you will be happy to know that Sanic provides an API for sharing these objects between your worker processes. If you are not familiar, you are encouraged to read through the Python documentation linked above and try some of the examples before proceeding with implementing shared context. + +Similar to how [application context](../basics/app.md#application-context) allows an applicaiton to share state across the lifetime of the application with `app.ctx`, shared context provides the same for the special objects mentioned above. This context is available as `app.shared_ctx` and should **ONLY** be used to share objects intended for this purpose. + +The `shared_ctx` will: + +- *NOT* share regular objects like `int`, `dict`, or `list` +- *NOT* share state between Sanic instances running on different machines +- *NOT* share state to non-worker processes +- **only** share state between server workers managed by the same Manager + +Attaching an inappropriate object to `shared_ctx` will likely result in a warning, and not an error. You should be careful to not accidentally add an unsafe object to `shared_ctx` as it may not work as expected. If you are directed here because of one of those warnings, you might have accidentally used an unsafe object in `shared_ctx`. + +---:1 In order to create a shared object you **must** create it in the main process and attach it inside of the `main_process_start` listener. :--:1 +```python +from multiprocessing import Queue + +@app.main_process_start +async def main_process_start(app): + app.shared_ctx.queue = Queue() +``` +:--- + +Trying to attach to the `shared_ctx` object outside of this listener may result in a `RuntimeError`. + +---:1 After creating the objects in the `main_process_start` listener and attaching to the `shared_ctx`, they will be available in your workers wherever the application instance is available (example: listeners, middleware, request handlers). :--:1 +```python +from multiprocessing import Queue + +@app.get("") +async def handler(request): + request.app.shared_ctx.queue.put(1) + ... +``` +:--- + +## Access to the multiplexer + +The application instance has access to an object that provides access to interacting with the Manager and other worker processes. The object is attached as the `app.multiplexer` property, but it is more easily accessed by its alias: `app.m`. + +---:1 For example, you can get access to the current worker state. :--:1 +```python +@app.on_request +async def print_state(request: Request): + print(request.app.m.name) + print(request.app.m.pid) + print(request.app.m.state) +``` +``` +Sanic-Server-0-0 +99999 +{'server': True, 'state': 'ACKED', 'pid': 99999, 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), 'starts': 2, 'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc)} +``` +:--- + +---:1 The `multiplexer` also has access to terminate the Manager, or restart worker processes :--:1 +```python +# shutdown the entire application and all processes +app.m.name.terminate() + +# restart the current worker only +app.m.name.restart() + +# restart specific workers only (comma delimited) +app.m.name.restart("Sanic-Server-4-0,Sanic-Server-7-0") + +# restart ALL workers +app.m.name.restart(all_workers=True) # Available v22.12+ +``` +:--- + +## Worker state + +---:1 As shown above, the `multiplexer` has access to report upon the state of the current running worker. However, it also contains the state for ALL processes running. :--:1 +```python +@app.on_request +async def print_state(request: Request): + print(request.app.m.workers) +``` +``` +{ + 'Sanic-Main': {'pid': 99997}, + 'Sanic-Server-0-0': { + 'server': True, + 'state': 'ACKED', + 'pid': 9999, + 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), + 'starts': 2, + 'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc) + }, + 'Sanic-Reloader-0': { + 'server': False, + 'state': 'STARTED', + 'pid': 99998, + 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), + 'starts': 1 + } +} +``` +:--- + + +## Built-in non-server processes + +As mentioned, the Manager also has the ability to run non-server processes. Sanic comes with two built-in types of non-server processes, and allows for [creating custom processes](#running-custom-processes). + +The two built-in processes are + +- the [auto-reloader](./development.md#automatic-reloader), optionally enabled to watch the file system for changes and trigger a restart +- [inspector](#inspector), optionally enabled to provide external access to the state of the running instance + +## Inspector + +Sanic has the ability to expose the state and the functionality of the `multiplexer` to the CLI. Currently, this requires the CLI command to be run on the same machine as the running Sanic instance. By default the inspector is disabled. + +---:1 To enable it, set the config value to `True`. :--:1 +```python +app.config.INSPECTOR = True +``` +:--- + +You will now have access to execute any of these CLI commands: + +``` +sanic inspect reload Trigger a reload of the server workers +sanic inspect shutdown Shutdown the application and all processes +sanic inspect scale N Scale the number of workers to N +sanic inspect Run a custom command +``` + +![](https://user-images.githubusercontent.com/166269/190099384-2f2f3fae-22d5-4529-b279-8446f6b5f9bd.png) + +---:1 This works by exposing a small HTTP service on your machine. You can control the location using configuration values: :--:1 +```python +app.config.INSPECTOR_HOST = "localhost" +app.config.INSPECTOR_PORT = 6457 +``` +:--- + +[Learn more](./inspector.md) to find out what is possible with the Inspector. + +## Running custom processes + +To run a managed custom process on Sanic, you must create a callable. If that process is meant to be long-running, then it should handle a shutdown call by a `SIGINT` or `SIGTERM` signal. + +---:1 The simplest method for doing that in Python will be to just wrap your loop in `KeyboardInterrupt`. + +If you intend to run another application, like a bot, then it is likely that it already has capability to handle this signal and you likely do not need to do anything. :--:1 +```python +from time import sleep + +def my_process(foo): + try: + while True: + sleep(1) + except KeyboardInterrupt: + print("done") +``` +:--- + +---:1 That callable must be registered in the `main_process_ready` listener. It is important to note that is is **NOT** the same location that you should register [shared context](#using-shared-context-between-worker-processes) objects. :--:1 +```python +@app.main_process_ready +async def ready(app: Sanic, _): +# app.manager.manage(, , ) + app.manager.manage("MyProcess", my_process, {"foo": "bar"}) +``` +:--- + +## Single process mode + +---:1 If you would like to opt out of running multiple processes, you can run Sanic in a single process only. In this case, the Manager will not run. You will also not have access to any features that require processes (auto-reload, the inspector, etc). :--:1 +```python +if __name__ == "__main__": + app.run(single_process=True) +``` +```python +if __name__ == "__main__": + app.prepare(single_process=True) + Sanic.serve_single() +``` +``` +sanic path.to.server:app --single-process +``` +:--- diff --git a/src/ko/guide/deployment/nginx.md b/src/ko/guide/deployment/nginx.md new file mode 100644 index 0000000000..18fb68c4a3 --- /dev/null +++ b/src/ko/guide/deployment/nginx.md @@ -0,0 +1,186 @@ +# Nginx Deployment + +## Introduction + + +Although Sanic can be run directly on Internet, it may be useful to use a proxy server such as Nginx in front of it. This is particularly useful for running multiple virtual hosts on the same IP, serving NodeJS or other services beside a single Sanic app, and it also allows for efficient serving of static files. SSL and HTTP/2 are also easily implemented on such proxy. + +We are setting the Sanic app to serve only locally at `127.0.0.1:8000`, while the Nginx installation is responsible for providing the service to public Internet on domain `example.com`. Static files will be served from `/var/www/`. + + +## Proxied Sanic app + +The app needs to be setup with a secret key used to identify a trusted proxy, so that real client IP and other information can be identified. This protects against anyone on the Internet sending fake headers to spoof their IP addresses and other details. Choose any random string and configure it both on the app and in Nginx config. + +```python +from sanic import Sanic +from sanic.response import text + +app = Sanic("proxied_example") +app.config.FORWARDED_SECRET = "YOUR SECRET" + +@app.get("/") +def index(request): + # This should display external (public) addresses: + return text( + f"{request.remote_addr} connected to {request.url_for('index')}\n" + f"Forwarded: {request.forwarded}\n" + ) + +if __name__ == "__main__": + app.run(host="127.0.0.1", port=8000, workers=8, access_log=False) +``` + +Since this is going to be a system service, save your code to `/srv/sanicexample/sanicexample.py`. + +For testing, run your app in a terminal. + +## Nginx configuration + +Quite much configuration is required to allow fast transparent proxying, but for the most part these don't need to be modified, so bear with me. + +Upstream servers need to be configured in a separate `upstream` block to enable HTTP keep-alive, which can drastically improve performance, so we use this instead of directly providing an upstream address in `proxy_pass` directive. In this example, the upstream section is named by `server_name`, i.e. the public domain name, which then also gets passed to Sanic in the `Host` header. You may change the naming as you see fit. Multiple servers may also be provided for load balancing and failover. + +Change the two occurrences of `example.com` to your true domain name, and instead of `YOUR SECRET` use the secret you chose for your app. + +```nginx +upstream example.com { + keepalive 100; + server 127.0.0.1:8000; + #server unix:/tmp/sanic.sock; +} + +server { + server_name example.com; + listen 443 ssl http2 default_server; + listen [::]:443 ssl http2 default_server; + # Serve static files if found, otherwise proxy to Sanic + location / { + root /var/www; + try_files $uri @sanic; + } + location @sanic { + proxy_pass http://$server_name; + # Allow fast streaming HTTP/1.1 pipes (keep-alive, unbuffered) + proxy_http_version 1.1; + proxy_request_buffering off; + proxy_buffering off; + # Proxy forwarding (password configured in app.config.FORWARDED_SECRET) + proxy_set_header forwarded "$proxy_forwarded;secret=\"YOUR SECRET\""; + # Allow websockets and keep-alive (avoid connection: close) + proxy_set_header connection "upgrade"; + proxy_set_header upgrade $http_upgrade; + } +} +``` + +To avoid cookie visibility issues and inconsistent addresses on search engines, it is a good idea to redirect all visitors to one true domain, always using HTTPS: + +```nginx +# Redirect all HTTP to HTTPS with no-WWW +server { + listen 80 default_server; + listen [::]:80 default_server; + server_name ~^(?:www\.)?(.*)$; + return 301 https://$1$request_uri; +} + +# Redirect WWW to no-WWW +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name ~^www\.(.*)$; + return 301 $scheme://$1$request_uri; +} +``` + +The above config sections may be placed in `/etc/nginx/sites-available/default` or in other site configs (be sure to symlink them to `sites-enabled` if you create new ones). + +Make sure that your SSL certificates are configured in the main config, or add the `ssl_certificate` and `ssl_certificate_key` directives to each `server` section that listens on SSL. + +Additionally, copy&paste all of this into `nginx/conf.d/forwarded.conf`: + +```nginx +# RFC 7239 Forwarded header for Nginx proxy_pass + +# Add within your server or location block: +# proxy_set_header forwarded "$proxy_forwarded;secret=\"YOUR SECRET\""; + +# Configure your upstream web server to identify this proxy by that password +# because otherwise anyone on the Internet could spoof these headers and fake +# their real IP address and other information to your service. + + +# Provide the full proxy chain in $proxy_forwarded +map $proxy_add_forwarded $proxy_forwarded { + default "$proxy_add_forwarded;by=\"_$hostname\";proto=$scheme;host=\"$http_host\";path=\"$request_uri\""; +} + +# The following mappings are based on +# https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/ + +map $remote_addr $proxy_forwarded_elem { + # IPv4 addresses can be sent as-is + ~^[0-9.]+$ "for=$remote_addr"; + + # IPv6 addresses need to be bracketed and quoted + ~^[0-9A-Fa-f:.]+$ "for=\"[$remote_addr]\""; + + # Unix domain socket names cannot be represented in RFC 7239 syntax + default "for=unknown"; +} + +map $http_forwarded $proxy_add_forwarded { + # If the incoming Forwarded header is syntactically valid, append to it + "~^(,[ \\t]*)*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*([ \\t]*,([ \\t]*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*)?)*$" "$http_forwarded, $proxy_forwarded_elem"; + + # Otherwise, replace it + default "$proxy_forwarded_elem"; +} +``` + +::: tip Note For installs that don't use `conf.d` and `sites-available`, all of the above configs may also be placed inside the `http` section of the main `nginx.conf`. ::: + +Reload Nginx config after changes: + +```bash +sudo nginx -s reload +``` + +Now you should be able to connect your app on `https://example.com/`. Any 404 errors and such will be handled by Sanic's error pages, and whenever a static file is present at a given path, it will be served by Nginx. + +## SSL certificates + +If you haven't already configured valid certificates on your server, now is a good time to do so. Install `certbot` and `python3-certbot-nginx`, then run + +```bash +certbot --nginx -d example.com -d www.example.com +``` + +Reference: [Using Free Let’s Encrypt SSL/TLS Certificates with NGINX](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/) + +## Running as a service + +This part is for Linux distributions based on `systemd`. Create a unit file `/etc/systemd/system/sanicexample.service` + +```text +[Unit] +Description=Sanic Example + +[Service] +User=nobody +WorkingDirectory=/srv/sanicexample +ExecStart=/usr/bin/env python3 sanicexample.py +Restart=always + +[Install] +WantedBy=multi-user.target +``` + +Then reload service files, start your service and enable it on boot: + +```bash +sudo systemctl daemon-reload +sudo systemctl start sanicexample +sudo systemctl enable sanicexample +``` diff --git a/src/ko/guide/deployment/running.md b/src/ko/guide/deployment/running.md new file mode 100644 index 0000000000..d948035b44 --- /dev/null +++ b/src/ko/guide/deployment/running.md @@ -0,0 +1,386 @@ +# Running Sanic + +Sanic ships with its own internal web server. Under most circumstances, this is the preferred method for deployment. In addition, you can also deploy Sanic as an ASGI app bundled with an ASGI-able web server, or using gunicorn. + +## Sanic Server + +There are two main ways to run Sanic Server: + +1. Using `app.run` +1. Using the [CLI](#sanic-cli) + +When using `app.run` you will just call your Python file like any other script. + +---:1 `app.run` must be properly nested inside of a name-main block. :--:1 +```python +# server.py +app = Sanic("MyApp") + +if __name__ == "__main__": + app.run() +``` +:--- + + + +After defining an instance of `sanic.Sanic`, we can call the run method with the following keyword arguments: + +| Parameter | Default | Description | +|:--------------------:|:--------------:|:----------------------------------------------------------------------------------------- | +| **host** | `"127.0.0.1"` | Address to host the server on. | +| **port** | `8000` | Port to host the server on. | +| **unix** | `None` | Unix socket name to host the server on (instead of TCP). | +| **debug** | `False` | Enables debug output (slows server). | +| **ssl** | `None` | SSLContext for SSL encryption of worker(s). | +| **sock** | `None` | Socket for the server to accept connections from. | +| **workers** | `1` | Number of worker processes to spawn. Cannot be used with fast. | +| **loop** | `None` | An asyncio-compatible event loop. If none is specified, Sanic creates its own event loop. | +| **protocol** | `HttpProtocol` | Subclass of asyncio.protocol. | +| **access_log** | `True` | Enables log on handling requests (significantly slows server). | +| **reload_dir** | `None` | A path or list of paths to directories the auto-reloader should watch. | +| **noisy_exceptions** | `None` | Whether to set noisy exceptions globally. None means leave as default. | +| **motd** | `True` | Whether to display the startup message. | +| **motd_display** | `None` | A dict with extra key/value information to display in the startup message | +| **fast** | `False` | Whether to maximize worker processes. Cannot be used with workers. | +| **verbosity** | `0` | Level of logging detail. Max is 2. | +| **auto_tls** | `False` | Whether to auto-create a TLS certificate for local development. Not for production. | +| **single_process** | `False` | Whether to run Sanic in a single process. | + +---:1 In the above example, we decided to turn off the access log in order to increase performance. :--:1 +```python +# server.py +app = Sanic("MyApp") + +if __name__ == "__main__": + app.run(host='0.0.0.0', port=1337, access_log=False) +``` +:--- + +---:1 Now, just execute the python script that has `app.run(...)` :--:1 +```bash +python server.py +``` +:--- + +For a slightly more advanced implementation, it is good to know that `app.run` will call `app.prepare` and `Sanic.serve` under the hood. + +---:1 Therefore, these are equivalent: :--:1 +```python +if __name__ == "__main__": + app.run(host='0.0.0.0', port=1337, access_log=False) +``` +```python +if __name__ == "__main__": + app.prepare(host='0.0.0.0', port=1337, access_log=False) + Sanic.serve() +``` +:--- + +### Workers + +---:1 By default, Sanic runs a main process and a single worker process (see [worker manager](./manager.md) for more details). + +To crank up the juice, just specify the number of workers in the run arguments. :--:1 +```python +app.run(host='0.0.0.0', port=1337, workers=4) +``` +:--- + +Sanic will automatically spin up multiple processes and route traffic between them. We recommend as many workers as you have available processors. + +---:1 The easiest way to get the maximum CPU performance is to use the `fast` option. This will automatically run the maximum number of workers given the system constraints. + +*Added in v21.12* :--:1 +```python +app.run(host='0.0.0.0', port=1337, fast=True) +``` +```python +$ sanic server:app --host=0.0.0.0 --port=1337 --fast +``` +:--- + +In older versions of Sanic without the `fast` option, a common way to check this on Linux based operating systems: + +``` +$ nproc +``` + +Or, let Python do it: + +```python +import multiprocessing +workers = multiprocessing.cpu_count() +app.run(..., workers=workers) +``` + +In version 22.9, Sanic introduced a new worker manager to provide more consistency and flexibility between development and production servers. Read [about the manager](./manager.md) for more details about workers. + +---:1 If you only want to run Sanic with a single process, specify `single_process` in the run arguments. This means that auto-reload, and the worker manager will be unavailable. :--:1 +```python +app.run(host='0.0.0.0', port=1337, single_process=True) +``` +:--- + +### Running via command + +#### Sanic CLI + +---:1 Sanic also has a simple CLI to launch via command line. + +For example, if you initialized Sanic as app in a file named `server.py`, you could run the server like so: :--:1 +```bash +sanic server.app --host=0.0.0.0 --port=1337 --workers=4 +``` + +:--- + +Use `sanic --help` to see all the options. + +::: details Sanic CLI help output + +```text +$ sanic --help +usage: sanic [-h] [--version] + [--factory | -s | --inspect | --inspect-raw | --trigger-reload | --trigger-shutdown] + [--http {1,3}] [-1] [-3] [-H HOST] [-p PORT] [-u UNIX] + [--cert CERT] [--key KEY] [--tls DIR] [--tls-strict-host] + [-w WORKERS | --fast | --single-process] [--legacy] + [--access-logs | --no-access-logs] [--debug] [-r] [-R PATH] [-d] + [--auto-tls] [--coffee | --no-coffee] [--motd | --no-motd] [-v] + [--noisy-exceptions | --no-noisy-exceptions] + module + + ▄███ █████ ██ ▄█▄ ██ █ █ ▄██████████ + ██ █ █ █ ██ █ █ ██ + ▀███████ ███▄ ▀ █ █ ██ ▄ █ ██ + ██ █████████ █ ██ █ █ ▄▄ + ████ ████████▀ █ █ █ ██ █ ▀██ ███████ + + To start running a Sanic application, provide a path to the module, where + app is a Sanic() instance: + + $ sanic path.to.server:app + + Or, a path to a callable that returns a Sanic() instance: + + $ sanic path.to.factory:create_app --factory + + Or, a path to a directory to run as a simple HTTP server: + + $ sanic ./path/to/static --simple + +Required +======== + Positional: + module Path to your Sanic app. Example: path.to.server:app + If running a Simple Server, path to directory to serve. Example: ./ + +Optional +======== + General: + -h, --help show this help message and exit + --version show program's version number and exit + + Application: + --factory Treat app as an application factory, i.e. a () -> callable + -s, --simple Run Sanic as a Simple Server, and serve the contents of a directory + (module arg should be a path) + --inspect Inspect the state of a running instance, human readable + --inspect-raw Inspect the state of a running instance, JSON output + --trigger-reload Trigger worker processes to reload + --trigger-shutdown Trigger all processes to shutdown + + HTTP version: + --http {1,3} Which HTTP version to use: HTTP/1.1 or HTTP/3. Value should + be either 1, or 3. [default 1] + -1 Run Sanic server using HTTP/1.1 + -3 Run Sanic server using HTTP/3 + + Socket binding: + -H HOST, --host HOST + Host address [default 127.0.0.1] + -p PORT, --port PORT + Port to serve on [default 8000] + -u UNIX, --unix UNIX + location of unix socket + + TLS certificate: + --cert CERT Location of fullchain.pem, bundle.crt or equivalent + --key KEY Location of privkey.pem or equivalent .key file + --tls DIR TLS certificate folder with fullchain.pem and privkey.pem + May be specified multiple times to choose multiple certificates + --tls-strict-host Only allow clients that send an SNI matching server certs + + Worker: + -w WORKERS, --workers WORKERS + Number of worker processes [default 1] + --fast Set the number of workers to max allowed + --single-process Do not use multiprocessing, run server in a single process + --legacy Use the legacy server manager + --access-logs Display access logs + --no-access-logs No display access logs + + Development: + --debug Run the server in debug mode + -r, --reload, --auto-reload + Watch source directory for file changes and reload on changes + -R PATH, --reload-dir PATH + Extra directories to watch and reload on changes + -d, --dev debug + auto reload + --auto-tls Create a temporary TLS certificate for local development (requires mkcert or trustme) + + Output: + --coffee Uhm, coffee? + --no-coffee No uhm, coffee? + --motd Show the startup display + --no-motd No show the startup display + -v, --verbosity Control logging noise, eg. -vv or --verbosity=2 [default 0] + --noisy-exceptions Output stack traces for all exceptions + --no-noisy-exceptions + No output stack traces for all exceptions +``` +::: + +#### As a module + +---:1 It can also be called directly as a module. :--:1 +```bash +python -m sanic server.app --host=0.0.0.0 --port=1337 --workers=4 +``` +:--- + +::: tip FYI With either method (CLI or module), you shoud *not* invoke `app.run()` in your Python file. If you do, make sure you wrap it so that it only executes when directly run by the interpreter. + +```python +if __name__ == '__main__': + app.run(host='0.0.0.0', port=1337, workers=4) +``` +::: + + +### Sanic Simple Server + +---:1 Sometimes you just have a directory of static files that need to be served. This especially can be handy for quickly standing up a localhost server. Sanic ships with a Simple Server, where you only need to point it at a directory. :--:1 +```bash +sanic ./path/to/dir --simple +``` +:--- + +---:1 This could also be paired with auto-reloading. :--:1 +```bash +sanic ./path/to/dir --simple --reload --reload-dir=./path/to/dir +``` +:--- + +*Added in v21.6* + +### HTTP/3 + + +Sanic server offers HTTP/3 support using [aioquic](https://github.com/aiortc/aioquic). This **must** be installed to use HTTP/3: + +``` +pip install sanic aioquic +``` + +``` +pip install sanic[http3] +``` + +To start HTTP/3, you must explicitly request it when running your application. + +---:1 +``` +$ sanic path.to.server:app --http=3 +``` + +``` +$ sanic path.to.server:app -3 +``` +:--:1 + +```python +app.run(version=3) +``` +:--- + +To run both an HTTP/3 and HTTP/1.1 server simultaneously, you can use [application multi-serve](../release-notes/v22.3.html#application-multi-serve) introduced in v22.3. This will automatically add an [Alt-Svc](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Alt-Svc) header to your HTTP/1.1 requests to let the client know that it is also available as HTTP/3. + + +---:1 +``` +$ sanic path.to.server:app --http=3 --http=1 +``` + +``` +$ sanic path.to.server:app -3 -1 +``` +:--:1 + +```python +app.prepare(version=3) +app.prepare(version=1) +Sanic.serve() +``` +:--- + +Because HTTP/3 requires TLS, you cannot start a HTTP/3 server without a TLS certificate. You should [set it up yourself](../how-to/tls.html) or use `mkcert` if in `DEBUG` mode. Currently, automatic TLS setup for HTTP/3 is not compatible with `trustme`. See [development](./development.md) for more details. + +*Added in v22.6* + +## ASGI + +Sanic is also ASGI-compliant. This means you can use your preferred ASGI webserver to run Sanic. The three main implementations of ASGI are [Daphne](http://github.com/django/daphne), [Uvicorn](https://www.uvicorn.org/), and [Hypercorn](https://pgjones.gitlab.io/hypercorn/index.html). + +::: warning Daphne does not support the ASGI `lifespan` protocol, and therefore cannot be used to run Sanic. See [Issue #264](https://github.com/django/daphne/issues/264) for more details. ::: + +Follow their documentation for the proper way to run them, but it should look something like: + +``` +uvicorn myapp:app +hypercorn myapp:app +``` + +A couple things to note when using ASGI: + +1. When using the Sanic webserver, websockets will run using the `websockets` package. In ASGI mode, there is no need for this package since websockets are managed in the ASGI server. +2. The ASGI lifespan protocol , supports only two server events: startup and shutdown. Sanic has four: before startup, after startup, before shutdown, and after shutdown. Therefore, in ASGI mode, the startup and shutdown events will run consecutively and not actually around the server process beginning and ending (since that is now controlled by the ASGI server). Therefore, it is best to use `after_server_start` and `before_server_stop`. + +### Trio + +Sanic has experimental support for running on Trio with: + +``` +hypercorn -k trio myapp:app +``` + + +## Gunicorn + +[Gunicorn](http://gunicorn.org/) ("Green Unicorn") is a WSGI HTTP Server for UNIX based operating systems. It is a pre-fork worker model ported from Ruby’s Unicorn project. + +In order to run Sanic application with Gunicorn, you need to use it with the adapter from [uvicorn](https://www.uvicorn.org/). Make sure uvicorn is installed and run it with `uvicorn.workers.UvicornWorker` for Gunicorn worker-class argument: + +```bash +gunicorn myapp:app --bind 0.0.0.0:1337 --worker-class uvicorn.workers.UvicornWorker +``` + +See the [Gunicorn Docs](http://docs.gunicorn.org/en/latest/settings.html#max-requests) for more information. + +::: warning It is generally advised to not use `gunicorn` unless you need it. The Sanic Server is primed for running Sanic in production. Weigh your considerations carefully before making this choice. Gunicorn does provide a lot of configuration options, but it is not the best choice for getting Sanic to run at its fastest. ::: + +## Performance considerations + +---:1 When running in production, make sure you turn off `debug`. :--:1 +```python +app.run(..., debug=False) +``` +:--- + +---:1 Sanic will also perform fastest if you turn off `access_log`. + +If you still require access logs, but want to enjoy this performance boost, consider using [Nginx as a proxy](./nginx.md), and letting that handle your access logging. It will be much faster than anything Python can handle. :--:1 +```python +app.run(..., access_log=False) +``` +:--- diff --git a/src/ko/guide/deployment/server-choice.md b/src/ko/guide/deployment/server-choice.md new file mode 100644 index 0000000000..9af6345235 --- /dev/null +++ b/src/ko/guide/deployment/server-choice.md @@ -0,0 +1 @@ +# Choosing a server diff --git a/src/ko/guide/getting-started.md b/src/ko/guide/getting-started.md index 3c67beaa0c..62c952d028 100644 --- a/src/ko/guide/getting-started.md +++ b/src/ko/guide/getting-started.md @@ -18,6 +18,8 @@ pip install sanic Flask 또는 다른 프레임워크를 사용하다 온 경우 몇 가지 짚고 넘어가야 할 중요한 것들이 있습니다. 기억하세요, Sanic은 성능, 유연성 및 사용 편의성을 목표로 합니다. 이러한 지침 원칙은 API 및 작동 방식에 실질적인 영향을 미칩니다. ::: + + :--:1 ```python @@ -41,18 +43,44 @@ async def hello_world(request): ### 실행(Running) ----:1 -위에서 작성한 파일을 `server.py`로 저장하고 실행해 보겠습니다. -:--:1 - +---:1 위에서 작성한 파일을 `server.py`로 저장하고 실행해 보겠습니다. And launch it. :--:1 ```bash sanic server.app ``` +:--- + +::: tip **또 다른** 중요한 차이점입니다. 다른 프레임워크는 개발 서버가 내장되어 있으며 개발 전용이라고 _명시적_ 으로 말합니다. Sanic은 그 반대입니다. + +**프로덕션 환경에서 사용할 수 있는 서버가 준비되있습니다.** ::: + +## Sanic Extensions + +Sanic intentionally aims for a clean and unopinionated feature list. The project does not want to require you to build your application in a certain way, and tries to avoid prescribing specific development patterns. There are a number of third-party plugins that are built and maintained by the community to add additional features that do not otherwise meet the requirements of the core repository. + +However, in order **to help API developers**, the Sanic organization maintains an official plugin called [Sanic Extensions](../plugins/sanic-ext/getting-started.md) to provide all sorts of goodies, including: +- **OpenAPI** documentation with Redoc and/or Swagger +- **CORS** protection +- **Dependency injection** into route handlers +- Request query arguments and body input **validation** +- Auto create `HEAD`, `OPTIONS`, and `TRACE` endpoints +- Predefined, endpoint-specific response serializers + +The preferred method to set it up is to install it along with Sanic, but you can also install the packages on their own. + +---:1 +``` +$ pip install sanic[ext] +``` +:--:1 +``` +$ pip install sanic sanic-ext +``` :--- -::: tip -**또 다른** 중요한 차이점입니다. 다른 프레임워크는 개발 서버가 내장되어 있으며 개발 전용이라고 _명시적_ 으로 말합니다. Sanic은 그 반대입니다. +Starting in v21.12, Sanic will automatically setup Sanic Extensions if it is in the same environment. You will also have access to two additional application properties: -**프로덕션 환경에서 사용할 수 있는 서버가 준비되있습니다.** -::: +- `app.extend()` - used to configure Sanic Extensions +- `app.ext` - the `Extend` instance attached to the application + +See [the plugin documentation](../plugins/sanic-ext/getting-started.md) for more information about how to use and work with the plugin diff --git a/src/ko/guide/how-to/README.md b/src/ko/guide/how-to/README.md new file mode 100644 index 0000000000..8e66fc0483 --- /dev/null +++ b/src/ko/guide/how-to/README.md @@ -0,0 +1 @@ +# How to ... diff --git a/src/ko/guide/how-to/authentication.md b/src/ko/guide/how-to/authentication.md new file mode 100644 index 0000000000..9d80fd4c0b --- /dev/null +++ b/src/ko/guide/how-to/authentication.md @@ -0,0 +1,113 @@ +# Authentication + +> How do I control authentication and authorization? + +This is an _extremely_ complicated subject to cram into a few snippets. But, this should provide you with an idea on ways to tackle this problem. This example uses [JWTs](https://jwt.io/), but the concepts should be equally applicable to sessions or some other scheme. + +:::: tabs +::: tab server.py +```python +from sanic import Sanic, text + +from auth import protected +from login import login + +app = Sanic("AuthApp") +app.config.SECRET = "KEEP_IT_SECRET_KEEP_IT_SAFE" +app.blueprint(login) + + +@app.get("/secret") +@protected +async def secret(request): + return text("To go fast, you must be fast.") +``` +::: +::: tab login.py +```python +import jwt +from sanic import Blueprint, text + +login = Blueprint("login", url_prefix="/login") + + +@login.post("/") +async def do_login(request): + token = jwt.encode({}, request.app.config.SECRET) + return text(token) +``` +::: +::: tab auth.py +```python +from functools import wraps + +import jwt +from sanic import text + + +def check_token(request): + if not request.token: + return False + + try: + jwt.decode( + request.token, request.app.config.SECRET, algorithms=["HS256"] + ) + except jwt.exceptions.InvalidTokenError: + return False + else: + return True + + +def protected(wrapped): + def decorator(f): + @wraps(f) + async def decorated_function(request, *args, **kwargs): + is_authenticated = check_token(request) + + if is_authenticated: + response = await f(request, *args, **kwargs) + return response + else: + return text("You are unauthorized.", 401) + + return decorated_function + + return decorator(wrapped) +``` +This decorator pattern is taken from the [decorators page](/en/guide/best-practices/decorators.md). ::: +:::: + +```bash +$ curl localhost:9999/secret -i +HTTP/1.1 401 Unauthorized +content-length: 21 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +You are unauthorized. + +$ curl localhost:9999/login -X POST 7 ↵ +eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.rjxS7ztIGt5tpiRWS8BGLUqjQFca4QOetHcZTi061DE + +$ curl localhost:9999/secret -i -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.rjxS7ztIGt5tpiRWS8BGLUqjQFca4QOetHcZTi061DE" +HTTP/1.1 200 OK +content-length: 29 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +To go fast, you must be fast. + +$ curl localhost:9999/secret -i -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.BAD" +HTTP/1.1 401 Unauthorized +content-length: 21 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +You are unauthorized. +``` + +Also, checkout some resources from the community: + +- Awesome Sanic - [Authorization](https://github.com/mekicha/awesome-sanic/blob/master/README.md#authentication) & [Session](https://github.com/mekicha/awesome-sanic/blob/master/README.md#session) +- [EuroPython 2020 - Overcoming access control in web APIs](https://www.youtube.com/watch?v=Uqgoj43ky6A) diff --git a/src/ko/guide/how-to/autodiscovery.md b/src/ko/guide/how-to/autodiscovery.md new file mode 100644 index 0000000000..4cf319357b --- /dev/null +++ b/src/ko/guide/how-to/autodiscovery.md @@ -0,0 +1,161 @@ +--- +title: Autodiscovery +--- + + +# Autodiscovery of Blueprints, Middleware, and Listeners + +> How do I autodiscover the components I am using to build my application? + +One of the first problems someone faces when building an application, is *how* to structure the project. Sanic makes heavy use of decorators to register route handlers, middleware, and listeners. And, after creating blueprints, they need to be mounted to the application. + +A possible solution is a single file in which **everything** is imported and applied to the Sanic instance. Another is passing around the Sanic instance as a global variable. Both of these solutions have their drawbacks. + +An alternative is autodiscovery. You point your application at modules (already imported, or strings), and let it wire everything up. + +:::: tabs +::: tab server.py +```python +from sanic import Sanic +from sanic.response import empty + +import blueprints +from utility import autodiscover + +app = Sanic("auto", register=True) +autodiscover( + app, + blueprints, + "parent.child", + "listeners.something", + recursive=True, +) + +app.route("/")(lambda _: empty()) +``` +```bash +[2021-03-02 21:37:02 +0200] [880451] [INFO] Goin' Fast @ http://127.0.0.1:9999 +[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something +[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something @ nested +[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something @ level1 +[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something @ level3 +[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something inside __init__.py +[2021-03-02 21:37:02 +0200] [880451] [INFO] Starting worker [880451] +``` +::: +::: tab utility.py +```python + +from glob import glob +from importlib import import_module, util +from inspect import getmembers +from pathlib import Path +from types import ModuleType +from typing import Union + +from sanic.blueprints import Blueprint + + +def autodiscover( + app, *module_names: Union[str, ModuleType], recursive: bool = False +): + mod = app.__module__ + blueprints = set() + _imported = set() + + def _find_bps(module): + nonlocal blueprints + + for _, member in getmembers(module): + if isinstance(member, Blueprint): + blueprints.add(member) + + for module in module_names: + if isinstance(module, str): + module = import_module(module, mod) + _imported.add(module.__file__) + _find_bps(module) + + if recursive: + base = Path(module.__file__).parent + for path in glob(f"{base}/**/*.py", recursive=True): + if path not in _imported: + name = "module" + if "__init__" in path: + *_, name, __ = path.split("/") + spec = util.spec_from_file_location(name, path) + specmod = util.module_from_spec(spec) + _imported.add(path) + spec.loader.exec_module(specmod) + _find_bps(specmod) + + for bp in blueprints: + app.blueprint(bp) +``` +::: +::: tab blueprints/level1.py +```python +from sanic import Blueprint +from sanic.log import logger + +level1 = Blueprint("level1") + + +@level1.after_server_start +def print_something(app, loop): + logger.debug("something @ level1") +``` +::: +::: tab blueprints/one/two/level3.py +```python +from sanic import Blueprint +from sanic.log import logger + +level3 = Blueprint("level3") + + +@level3.after_server_start +def print_something(app, loop): + logger.debug("something @ level3") +``` +::: +::: tab listeners/something.py +```python +from sanic import Sanic +from sanic.log import logger + +app = Sanic.get_app("auto") + + +@app.after_server_start +def print_something(app, loop): + logger.debug("something") +``` +::: +::: tab parent/child/__init__.py +```python +from sanic import Blueprint +from sanic.log import logger + +bp = Blueprint("__init__") + + +@bp.after_server_start +def print_something(app, loop): + logger.debug("something inside __init__.py") +``` +::: +::: tab parent/child/nested.py +```python +from sanic import Blueprint +from sanic.log import logger + +nested = Blueprint("nested") + + +@nested.after_server_start +def print_something(app, loop): + logger.debug("something @ nested") +``` +::: +:::: diff --git a/src/ko/guide/how-to/cors.md b/src/ko/guide/how-to/cors.md new file mode 100644 index 0000000000..d1db877a9a --- /dev/null +++ b/src/ko/guide/how-to/cors.md @@ -0,0 +1,137 @@ +--- +title: CORS +--- + + +# Cross-origin resource sharing (CORS) + +> How do I configure my application for CORS? + +The best solution is to use [Sanic Extensions](../../plugins/sanic-ext/http/cors.md). However, if you would like to build your own version, you could use this limited example. + +:::: tabs +::: tab server.py +```python +from sanic import Sanic, text + +from cors import add_cors_headers +from options import setup_options + +app = Sanic("app") + + +@app.route("/", methods=["GET", "POST"]) +async def do_stuff(request): + return text("...") + + +# Add OPTIONS handlers to any route that is missing it +app.register_listener(setup_options, "before_server_start") + +# Fill in CORS headers +app.register_middleware(add_cors_headers, "response") +``` +::: +::: tab cors.py +```python +from typing import Iterable + + +def _add_cors_headers(response, methods: Iterable[str]) -> None: + allow_methods = list(set(methods)) + if "OPTIONS" not in allow_methods: + allow_methods.append("OPTIONS") + headers = { + "Access-Control-Allow-Methods": ",".join(allow_methods), + "Access-Control-Allow-Origin": "mydomain.com", + "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Headers": ( + "origin, content-type, accept, " + "authorization, x-xsrf-token, x-request-id" + ), + } + response.headers.extend(headers) + + +def add_cors_headers(request, response): + if request.method != "OPTIONS": + methods = [method for method in request.route.methods] + _add_cors_headers(response, methods) +``` +::: +::: tab options.py +```python +from collections import defaultdict +from typing import Dict, FrozenSet + +from sanic import Sanic, response +from sanic.router import Route + +from cors import _add_cors_headers + + +def _compile_routes_needing_options( + routes: Dict[str, Route] +) -> Dict[str, FrozenSet]: + needs_options = defaultdict(list) + # This is 21.12 and later. You will need to change this for older versions. + for route in routes.values(): + if "OPTIONS" not in route.methods: + needs_options[route.uri].extend(route.methods) + + return { + uri: frozenset(methods) for uri, methods in dict(needs_options).items() + } + + +def _options_wrapper(handler, methods): + def wrapped_handler(request, *args, **kwargs): + nonlocal methods + return handler(request, methods) + + return wrapped_handler + + +async def options_handler(request, methods) -> response.HTTPResponse: + resp = response.empty() + _add_cors_headers(resp, methods) + return resp + + +def setup_options(app: Sanic, _): + app.router.reset() + needs_options = _compile_routes_needing_options(app.router.routes_all) + for uri, methods in needs_options.items(): + app.add_route( + _options_wrapper(options_handler, methods), + uri, + methods=["OPTIONS"], + ) + app.router.finalize() +``` +::: +:::: +``` +$ curl localhost:9999/ -i +HTTP/1.1 200 OK +Access-Control-Allow-Methods: OPTIONS,POST,GET +Access-Control-Allow-Origin: mydomain.com +Access-Control-Allow-Credentials: true +Access-Control-Allow-Headers: origin, content-type, accept, authorization, x-xsrf-token, x-request-id +content-length: 3 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +... + +$ curl localhost:9999/ -i -X OPTIONS +HTTP/1.1 204 No Content +Access-Control-Allow-Methods: GET,POST,OPTIONS +Access-Control-Allow-Origin: mydomain.com +Access-Control-Allow-Credentials: true +Access-Control-Allow-Headers: origin, content-type, accept, authorization, x-xsrf-token, x-request-id +connection: keep-alive +``` +Also, checkout some resources from the community: + +- [Awesome Sanic](https://github.com/mekicha/awesome-sanic/blob/master/README.md#frontend) diff --git a/src/ko/guide/how-to/csrf.md b/src/ko/guide/how-to/csrf.md new file mode 100644 index 0000000000..7f982ff12f --- /dev/null +++ b/src/ko/guide/how-to/csrf.md @@ -0,0 +1 @@ +csrf diff --git a/src/ko/guide/how-to/db.md b/src/ko/guide/how-to/db.md new file mode 100644 index 0000000000..60ca822eac --- /dev/null +++ b/src/ko/guide/how-to/db.md @@ -0,0 +1 @@ +connecting to data sources diff --git a/src/ko/guide/how-to/decorators.md b/src/ko/guide/how-to/decorators.md new file mode 100644 index 0000000000..9ef318134e --- /dev/null +++ b/src/ko/guide/how-to/decorators.md @@ -0,0 +1 @@ +decorators diff --git a/src/ko/guide/how-to/mounting.md b/src/ko/guide/how-to/mounting.md new file mode 100644 index 0000000000..918c422cdb --- /dev/null +++ b/src/ko/guide/how-to/mounting.md @@ -0,0 +1,52 @@ +# Application Mounting + +> How do I mount my application at some path above the root? + +```python +# server.py +from sanic import Sanic, text + +app = Sanic("app") +app.config.SERVER_NAME = "example.com/api" + + +@app.route("/foo") +def handler(request): + url = app.url_for("handler", _external=True) + return text(f"URL: {url}") +``` + +```yaml +# docker-compose.yml +version: "3.7" +services: + app: + image: nginx:alpine + ports: + - 80:80 + volumes: + - type: bind + source: ./conf + target: /etc/nginx/conf.d/default.conf +``` + +```nginx +# conf +server { + listen 80; + + # Computed data service + location /api/ { + proxy_pass http://:9999/; + proxy_set_header Host example.com; + } +} +``` +```bash +$ docker-compose up -d +$ sanic server.app --port=9999 --host=0.0.0.0 +``` +```bash +$ curl localhost/api/foo +URL: http://example.com/api/foo +``` diff --git a/src/ko/guide/how-to/orm.md b/src/ko/guide/how-to/orm.md new file mode 100644 index 0000000000..eb73022681 --- /dev/null +++ b/src/ko/guide/how-to/orm.md @@ -0,0 +1,370 @@ +# ORM + +> How do I use SQLAlchemy with Sanic ? + +All ORM tools can work with Sanic, but non-async ORM tool have a impact on Sanic performance. There are some orm packages who support + +At present, there are many ORMs that support Python's `async`/`await` keywords. Some possible choices include: + +- [Mayim](https://ahopkins.github.io/mayim/) +- [SQLAlchemy 1.4](https://docs.sqlalchemy.org/en/14/changelog/changelog_14.html) +- [tortoise-orm](https://github.com/tortoise/tortoise-orm) + +Integration in to your Sanic application is fairly simple: + +## Mayim + +Mayim ships with [an extension for Sanic Extensions](https://ahopkins.github.io/mayim/guide/extensions.html#sanic), which makes it super simple to get started with Sanic. It is certainly possible to run Mayim with Sanic without the extension, but it is recommended because it handles all of the [lifecycle events](https://sanic.dev/en/guide/basics/listeners.html) and [dependency injections](https://sanic.dev/en/plugins/sanic-ext/injection.html). + +---:1 +### Dependencies + +First, we need to install the required dependencies. See [Mayim docs](https://ahopkins.github.io/mayim/guide/install.html#postgres) for the installation needed for your DB driver. :--:1 +```shell +pip install sanic-ext +pip install mayim[postgres] +``` +:--- + +---:1 +### Define ORM Model + +Mayim allows you to use whatever you want for models. Whether it is [dataclasses](https://docs.python.org/3/library/dataclasses.html), [pydantic](https://pydantic-docs.helpmanual.io/), [attrs](https://www.attrs.org/en/stable/), or even just plain `dict` objects. Since it works very nicely [out of the box with Pydantic](https://ahopkins.github.io/mayim/guide/pydantic.html), that is what we will use here. :--:1 +```python +# ./models.py +from pydantic import BaseModel + + +class City(BaseModel): + id: int + name: str + district: str + population: int + + +class Country(BaseModel): + code: str + name: str + continent: str + region: str + capital: City +``` +:--- + +---:1 +### Define SQL + +If you are unfamiliar, Mayim is different from other ORMs in that it is one-way, SQL-first. This means you define your own queries either inline, or in a separate `.sql` file, which is what we will do here. :--:1 +```sql +-- ./queries/select_all_countries.sql +SELECT country.code, + country.name, + country.continent, + country.region, + ( + SELECT row_to_json(q) + FROM ( + SELECT city.id, + city.name, + city.district, + city.population + ) q + ) capital +FROM country + JOIN city ON country.capital = city.id +ORDER BY country.name ASC +LIMIT $limit OFFSET $offset; +``` +:--- + +---:1 +### Create Sanic App and Async Engine + +We need to create the app instance and attach the `SanicMayimExtension` with any executors. :--:1 +```python +# ./server.py +from sanic import Sanic, Request, json +from sanic_ext import Extend +from mayim.executor import PostgresExecutor +from mayim.extensions import SanicMayimExtension +from models import Country + + +class CountryExecutor(PostgresExecutor): + async def select_all_countries( + self, limit: int = 4, offset: int = 0 + ) -> list[Country]: + ... + + +app = Sanic("Test") +Extend.register( + SanicMayimExtension( + executors=[CountryExecutor], + dsn="postgres://...", + ) +) +``` +:--- + +---:1 +### Register Routes + +Because we are using Mayim's extension for Sanic, we have the automatic `CountryExecutor` injection into the route handler. It makes for an easy, type-annotated development experience. :--:1 +```python +@app.get("/") +async def handler(request: Request, executor: CountryExecutor): + countries = await executor.select_all_countries() + return json({"countries": [country.dict() for country in co +``` +:--- + +---:1 +### Send Requests +:--:1 +```sh +curl 'http://127.0.0.1:8000' +{"countries":[{"code":"AFG","name":"Afghanistan","continent":"Asia","region":"Southern and Central Asia","capital":{"id":1,"name":"Kabul","district":"Kabol","population":1780000}},{"code":"ALB","name":"Albania","continent":"Europe","region":"Southern Europe","capital":{"id":34,"name":"Tirana","district":"Tirana","population":270000}},{"code":"DZA","name":"Algeria","continent":"Africa","region":"Northern Africa","capital":{"id":35,"name":"Alger","district":"Alger","population":2168000}},{"code":"ASM","name":"American Samoa","continent":"Oceania","region":"Polynesia","capital":{"id":54,"name":"Fagatogo","district":"Tutuila","population":2323}}]} +``` +:--- + + +## SQLAlchemy + +Because [SQLAlchemy 1.4](https://docs.sqlalchemy.org/en/14/changelog/changelog_14.html) has added native support for `asyncio`, Sanic can finally work well with SQLAlchemy. Be aware that this functionality is still considered *beta* by the SQLAlchemy project. + + +---:1 +### Dependencies + +First, we need to install the required dependencies. In the past, the dependencies installed were `sqlalchemy` and `pymysql`, but now `sqlalchemy` and `aiomysql` are needed. :--:1 +```shell +pip install -U sqlalchemy +pip install -U aiomysql +``` +:--- + +---:1 +### Define ORM Model + +ORM model creation remains the same. :--:1 +```python +# ./models.py +from sqlalchemy import INTEGER, Column, ForeignKey, String +from sqlalchemy.orm import declarative_base, relationship + +Base = declarative_base() + + +class BaseModel(Base): + __abstract__ = True + id = Column(INTEGER(), primary_key=True) + + +class Person(BaseModel): + __tablename__ = "person" + name = Column(String()) + cars = relationship("Car") + + def to_dict(self): + return {"name": self.name, "cars": [{"brand": car.brand} for car in self.cars]} + + +class Car(BaseModel): + __tablename__ = "car" + + brand = Column(String()) + user_id = Column(ForeignKey("person.id")) + user = relationship("Person", back_populates="cars") +``` +:--- + +---:1 +### Create Sanic App and Async Engine + +Here we use mysql as the database, and you can also choose PostgreSQL/SQLite. Pay attention to changing the driver from `aiomysql` to `asyncpg`/`aiosqlite`. :--:1 +```python +# ./server.py +from sanic import Sanic +from sqlalchemy.ext.asyncio import create_async_engine + +app = Sanic("my_app") + +bind = create_async_engine("mysql+aiomysql://root:root@localhost/test", echo=True) +``` +:--- + +---:1 +### Register Middlewares + +The request middleware creates an usable `AsyncSession` object and set it to `request.ctx` and `_base_model_session_ctx`. + +Thread-safe variable `_base_model_session_ctx` helps you to use the session object instead of fetching it from `request.ctx`. :--:1 +```python +# ./server.py +from contextvars import ContextVar + +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import sessionmaker + +_sessionmaker = sessionmaker(bind, AsyncSession, expire_on_commit=False) + +_base_model_session_ctx = ContextVar("session") + +@app.middleware("request") +async def inject_session(request): + request.ctx.session = _sessionmaker() + request.ctx.session_ctx_token = _base_model_session_ctx.set(request.ctx.session) + + +@app.middleware("response") +async def close_session(request, response): + if hasattr(request.ctx, "session_ctx_token"): + _base_model_session_ctx.reset(request.ctx.session_ctx_token) + await request.ctx.session.close() +``` +:--- + +---:1 +### Register Routes + +According to sqlalchemy official docs, `session.query` will be legacy in 2.0, and the 2.0 way to query an ORM object is using `select`. :--:1 +```python +# ./server.py +from sqlalchemy import select +from sqlalchemy.orm import selectinload +from sanic.response import json + +from models import Car, Person + + +@app.post("/user") +async def create_user(request): + session = request.ctx.session + async with session.begin(): + car = Car(brand="Tesla") + person = Person(name="foo", cars=[car]) + session.add_all([person]) + return json(person.to_dict()) + + +@app.get("/user/") +async def get_user(request, pk): + session = request.ctx.session + async with session.begin(): + stmt = select(Person).where(Person.id == pk).options(selectinload(Person.cars)) + result = await session.execute(stmt) + person = result.scalar() + + if not person: + return json({}) + + return json(person.to_dict()) +``` +:--- + +---:1 +### Send Requests +:--:1 +```sh +curl --location --request POST 'http://127.0.0.1:8000/user' +{"name":"foo","cars":[{"brand":"Tesla"}]} +``` + +```sh +curl --location --request GET 'http://127.0.0.1:8000/user/1' +{"name":"foo","cars":[{"brand":"Tesla"}]} +``` +:--- + + +## Tortoise-ORM + +---:1 +### Dependencies + +tortoise-orm's dependency is very simple, you just need install tortoise-orm. :--:1 +```shell +pip install -U tortoise-orm +``` +:--- + +---:1 +### Define ORM Model + +If you are familiar with Django, you should find this part very familiar. :--:1 +```python +# ./models.py +from tortoise import Model, fields + + +class Users(Model): + id = fields.IntField(pk=True) + name = fields.CharField(50) + + def __str__(self): + return f"I am {self.name}" +``` +:--- + + +---:1 +### Create Sanic App and Async Engine + +Tortoise-orm provides a set of registration interface, which is convenient for users, and you can use it to create database connection easily. :--:1 +```python +# ./main.py + +from models import Users +from tortoise.contrib.sanic import register_tortoise + +app = Sanic(__name__) + + +register_tortoise( + app, db_url="mysql://root:root@localhost/test", modules={"models": ["models"]}, generate_schemas=True +) + +``` +:--- + +---:1 +### Register Routes +:--:1 +```python +# ./main.py + +from models import Users +from sanic import Sanic, response + + +@app.route("/user") +async def list_all(request): + users = await Users.all() + return response.json({"users": [str(user) for user in users]}) + + +@app.route("/user/") +async def get_user(request, pk): + user = await Users.query(pk=pk) + return response.json({"user": str(user)}) + +if __name__ == "__main__": + app.run(port=5000) +``` +:--- + +---:1 +### Send Requests +:--:1 +```sh +curl --location --request POST 'http://127.0.0.1:8000/user' +{"users":["I am foo", "I am bar"]} +``` + +```sh +curl --location --request GET 'http://127.0.0.1:8000/user/1' +{"user": "I am foo"} +``` +:--- + diff --git a/src/ko/guide/how-to/serialization.md b/src/ko/guide/how-to/serialization.md new file mode 100644 index 0000000000..0dfc62d35b --- /dev/null +++ b/src/ko/guide/how-to/serialization.md @@ -0,0 +1 @@ +# Serialization diff --git a/src/ko/guide/how-to/server-sent-events.md b/src/ko/guide/how-to/server-sent-events.md new file mode 100644 index 0000000000..7daf86745e --- /dev/null +++ b/src/ko/guide/how-to/server-sent-events.md @@ -0,0 +1 @@ +sse diff --git a/src/ko/guide/how-to/static-redirects.md b/src/ko/guide/how-to/static-redirects.md new file mode 100644 index 0000000000..5566050c74 --- /dev/null +++ b/src/ko/guide/how-to/static-redirects.md @@ -0,0 +1,110 @@ +# "Static" Redirects + +> How do I configure static redirects? + +:::: tabs +::: tab app.py +```python +### SETUP ### +import typing +import sanic, sanic.response + +# Create the Sanic app +app = sanic.Sanic(__name__) + +# This dictionary represents your "static" +# redirects. For example, these values +# could be pulled from a configuration file. +REDIRECTS = { + '/':'/hello_world', # Redirect '/' to '/hello_world' + '/hello_world':'/hello_world.html' # Redirect '/hello_world' to 'hello_world.html' +} + +# This function will return another function +# that will return the configured value +# regardless of the arguments passed to it. +def get_static_function(value:typing.Any) -> typing.Callable[..., typing.Any]: + return lambda *_, **__: value + +### ROUTING ### +# Iterate through the redirects +for src, dest in REDIRECTS.items(): + # Create the redirect response object + response:sanic.HTTPResponse = sanic.response.redirect(dest) + + # Create the handler function. Typically, + # only a sanic.Request object is passed + # to the function. This object will be + # ignored. + handler = get_static_function(response) + + # Route the src path to the handler + app.route(src)(handler) + +# Route some file and client resources +app.static('/files/', 'files') +app.static('/', 'client') + +### RUN ### +if __name__ == '__main__': + app.run( + '127.0.0.1', + 10000 + ) +``` +::: + +::: tab client/hello_world.html +```html + + + + + + + Hello World + + + +
+ Hello world! +
+ + +``` +::: + +::: tab client/hello_world.css +```css +#hello_world { + width: 1000px; + margin-left: auto; + margin-right: auto; + margin-top: 100px; + + padding: 100px; + color: aqua; + text-align: center; + font-size: 100px; + font-family: monospace; + + background-color: rgba(0, 0, 0, 0.75); + + border-radius: 10px; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.75); +} + +body { + background-image: url("/files/lake.jpg"); + background-repeat: no-repeat; + background-size: cover; +} +``` +::: + +::: tab files/lake.jpg ![](./assets/images/lake.jpg) ::: +:::: + +Also, checkout some resources from the community: + +- [Static Routing Example](https://github.com/Perzan/sanic-static-routing-example) diff --git a/src/ko/guide/how-to/task-queue.md b/src/ko/guide/how-to/task-queue.md new file mode 100644 index 0000000000..c5ee7c54a3 --- /dev/null +++ b/src/ko/guide/how-to/task-queue.md @@ -0,0 +1 @@ +task queue diff --git a/src/ko/guide/how-to/tls.md b/src/ko/guide/how-to/tls.md new file mode 100644 index 0000000000..5299134cb1 --- /dev/null +++ b/src/ko/guide/how-to/tls.md @@ -0,0 +1,158 @@ +# TLS/SSL/HTTPS + +> How do I run Sanic via HTTPS? + +If you do not have TLS certificates yet, [see the end of this page](./tls.md#get-certificates-for-your-domain-names). + +## Single domain and single certificate + +---:1 Let Sanic automatically load your certificate files, which need to be named `fullchain.pem` and `privkey.pem` in the given folder: + +:--:1 +```sh +sudo sanic myserver:app -H :: -p 443 \ + --tls /etc/letsencrypt/live/example.com/ +``` +```python +app.run("::", 443, ssl="/etc/letsencrypt/live/example.com/") +``` +:--- + +---:1 Or, you can pass cert and key filenames separately as a dictionary: + +Additionally, `password` may be added if the key is encrypted, all fields except for the password are passed to `request.conn_info.cert`. :--:1 +```python +ssl = { + "cert": "/path/to/fullchain.pem", + "key": "/path/to/privkey.pem", + "password": "for encrypted privkey file", # Optional +} +app.run(host="0.0.0.0", port=8443, ssl=ssl) +``` +:--- + +---:1 Alternatively, [`ssl.SSLContext`](https://docs.python.org/3/library/ssl.html) may be passed, if you need full control over details such as which crypto algorithms are permitted. By default Sanic only allows secure algorithms, which may restrict access from very old devices. :--:1 +```python +import ssl + +context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) +context.load_cert_chain("certs/fullchain.pem", "certs/privkey.pem") + +app.run(host="0.0.0.0", port=8443, ssl=context) +``` +:--- + + +## Multiple domains with separate certificates + +---:1 A list of multiple certificates may be provided, in which case Sanic chooses the one matching the hostname the user is connecting to. This occurs so early in the TLS handshake that Sanic has not sent any packets to the client yet. + +If the client sends no SNI (Server Name Indication), the first certificate on the list will be used even though on the client browser it will likely fail with a TLS error due to name mismatch. To prevent this fallback and to cause immediate disconnection of clients without a known hostname, add `None` as the first entry on the list. `--tls-strict-host` is the equivalent CLI option. :--:1 +```python +ssl = ["certs/example.com/", "certs/bigcorp.test/"] +app.run(host="0.0.0.0", port=8443, ssl=ssl) +``` +```sh +sanic myserver:app + --tls certs/example.com/ + --tls certs/bigcorp.test/ + --tls-strict-host +``` +:--- + +::: tip You may also use `None` in front of a single certificate if you do not wish to reveal your certificate, true hostname or site content to anyone connecting to the IP address instead of the proper DNS name. ::: + +---:1 Dictionaries can be used on the list. This allows also specifying which domains a certificate matches to, although the names present on the certificate itself cannot be controlled from here. If names are not specified, the names from the certificate itself are used. + +To only allow connections to the main domain **example.com** and only to subdomains of **bigcorp.test**: + +:--:1 +```python +ssl = [ + None, # No fallback if names do not match! + { + "cert": "certs/example.com/fullchain.pem", + "key": "certs/example.com/privkey.pem", + "names": ["example.com", "*.bigcorp.test"], + } +] +app.run(host="0.0.0.0", port=8443, ssl=ssl) +``` +:--- + +## Accessing TLS information in handlers via `request.conn_info` fields + +* `.ssl` - is the connection secure (bool) +* `.cert` - certificate info and dict fields of the currently active cert (dict) +* `.server_name` - the SNI sent by the client (str, may be empty) + +Do note that all `conn_info` fields are per connection, where there may be many requests over time. If a proxy is used in front of your server, these requests on the same pipe may even come from different users. + +## Redirect HTTP to HTTPS, with certificate requests still over HTTP + +In addition to your normal server(s) running HTTPS, run another server for redirection, `http_redir.py`: + +```python +from sanic import Sanic, exceptions, response + +app = Sanic("http_redir") + +# Serve ACME/certbot files without HTTPS, for certificate renewals +app.static("/.well-known", "/var/www/.well-known", resource_type="dir") + +@app.exception(exceptions.NotFound, exceptions.MethodNotSupported) +def redirect_everything_else(request, exception): + server, path = request.server_name, request.path + if server and path.startswith("/"): + return response.redirect(f"https://{server}{path}", status=308) + return response.text("Bad Request. Please use HTTPS!", status=400) +``` + +It is best to setup this as a systemd unit separate of your HTTPS servers. You may need to run HTTP while initially requesting your certificates, while you cannot run the HTTPS server yet. Start for IPv4 and IPv6: + +``` +sanic http_redir:app -H 0.0.0.0 -p 80 +sanic http_redir:app -H :: -p 80 +``` + +Alternatively, it is possible to run the HTTP redirect application from the main application: + +```python +# app == Your main application +# redirect == Your http_redir application +@app.before_server_start +async def start(app, _): + app.ctx.redirect = await redirect.create_server( + port=80, return_asyncio_server=True + ) + app.add_task(runner(redirect, app.ctx.redirect)) + + +@app.before_server_stop +async def stop(app, _): + await app.ctx.redirect.close() + + +async def runner(app, app_server): + app.is_running = True + try: + app.signalize() + app.finalize() + app.state.is_started = True + await app_server.serve_forever() + finally: + app.is_running = False + app.is_stopping = True +``` + +## Get certificates for your domain names + +You can get free certificates from [Let's Encrypt](https://letsencrypt.org/). Install [certbot](https://certbot.eff.org/) via your package manager, and request a certificate: + +```sh +sudo certbot certonly --key-type ecdsa --preferred-chain "ISRG Root X1" -d example.com -d www.example.com +``` + +Multiple domain names may be added by further `-d` arguments, all stored into a single certificate which gets saved to `/etc/letsencrypt/live/example.com/` as per **the first domain** that you list here. + +The key type and preferred chain options are necessary for getting a minimal size certificate file, essential for making your server run as *fast* as possible. The chain will still contain one RSA certificate until when Let's Encrypt gets their new EC chain trusted in all major browsers, possibly around 2023. diff --git a/src/ko/guide/how-to/toc.md b/src/ko/guide/how-to/toc.md new file mode 100644 index 0000000000..4afddd9afe --- /dev/null +++ b/src/ko/guide/how-to/toc.md @@ -0,0 +1,13 @@ +# Table of Contents + +We have compiled fully working examples to answer common questions and user cases. For the most part, the examples are as minimal as possible, but should be complete and runnable solutions. + +| Page | How do I ... | +|:------------------------------------------- |:------------------------------------------------------------------- | +| [Application mounting](./mounting.md) | ... mount my application at some path above the root? | +| [Authentication](./authentication.md) | ... control authentication and authorization? | +| [Autodiscovery](./autodiscovery.md) | ... autodiscover the components I am using to build my application? | +| [CORS](./cors.md) | ... configure my application for CORS? | +| [ORM](./orm) | ... use an ORM with Sanic? | +| ["Static" Redirects](./static-redirects.md) | ... configure static redirects | +| [TLS/SSL/HTTPS](./tls.md) | ... run Sanic via HTTPS?
... redirect HTTP to HTTPS? | diff --git a/src/ko/guide/how-to/validation.md b/src/ko/guide/how-to/validation.md new file mode 100644 index 0000000000..d45b2dea4c --- /dev/null +++ b/src/ko/guide/how-to/validation.md @@ -0,0 +1 @@ +validation diff --git a/src/ko/guide/how-to/websocket-feed.md b/src/ko/guide/how-to/websocket-feed.md new file mode 100644 index 0000000000..ae2abc2def --- /dev/null +++ b/src/ko/guide/how-to/websocket-feed.md @@ -0,0 +1 @@ +websocket feed diff --git a/src/ko/guide/release-notes/v21.12.md b/src/ko/guide/release-notes/v21.12.md new file mode 100644 index 0000000000..d2a443b404 --- /dev/null +++ b/src/ko/guide/release-notes/v21.12.md @@ -0,0 +1,486 @@ +# Version 21.12 (LTS) + +[[toc]] + +## Introduction + +This is the final release of the version 21 [release cycle](../../org/policies.md#release-schedule). Version 21 will now enter long-term support and will be supported for two years until December 2023. + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + +### Strict application and blueprint names + +In [v21.6](./v21.6.md#stricter-application-and-blueprint-names-and-deprecation) application and blueprint names were required to conform to a new set of restrictions. That change is now being enforced at startup time. + +Names **must**: + +1. Only use alphanumeric characters (`a-zA-Z0-9`) +2. May contain a hyphen (`-`) or an underscore (`_`) +3. Must begin with a letter or underscore (`a-zA-Z_`) + +### Strict application and blueprint properties + +The old leniency to allow directly setting properties of a `Sanic` or `Blueprint` object was deprecated and no longer allowed. You must use the `ctx` object. + +```python +app = Sanic("MyApp") +app.ctx.db = Database() +``` + +### Removals + +The following deprecated features no longer exist: + +- `sanic.exceptions.abort` +- `sanic.views.CompositionView` +- `sanic.response.StreamingHTTPResponse` + +### Upgrade your streaming responses (if not already) + +The `sanic.response.stream` response method has been **deprecated** and will be removed in v22.6. If you are sill using an old school streaming response, please upgrade it. + +**OLD - Deprecated** + +```python +async def sample_streaming_fn(response): + await response.write("foo,") + await response.write("bar") + +@app.route("/") +async def test(request: Request): + return stream(sample_streaming_fn, content_type="text/csv") +``` + +**Current** + +```python +async def sample_streaming_fn(response): + await response.write("foo,") + await response.write("bar") + +@app.route("/") +async def test(request: Request): + response = await request.respond(content_type="text/csv") + await response.send("foo,") + await response.send("bar") +``` + +### CLI overhaul and MOTD (Message of the Day) + +The Sanic CLI has received a fairly extensive upgrade. It adds a bunch of new features to make it on par with `app.run()`. It also includes a new MOTD display to provide quick, at-a-glance highlights about your running environment. The MOTD is TTY-aware, and therefore will be less verbose in server logs. It is mainly intended as a convenience during application development. + +``` +$ sanic --help +usage: sanic [-h] [--version] [--factory] [-s] [-H HOST] [-p PORT] [-u UNIX] [--cert CERT] [--key KEY] [--tls DIR] [--tls-strict-host] + [-w WORKERS | --fast] [--access-logs | --no-access-logs] [--debug] [-d] [-r] [-R PATH] [--motd | --no-motd] [-v] + [--noisy-exceptions | --no-noisy-exceptions] + module + + ▄███ █████ ██ ▄█▄ ██ █ █ ▄██████████ + ██ █ █ █ ██ █ █ ██ + ▀███████ ███▄ ▀ █ █ ██ ▄ █ ██ + ██ █████████ █ ██ █ █ ▄▄ + ████ ████████▀ █ █ █ ██ █ ▀██ ███████ + + To start running a Sanic application, provide a path to the module, where + app is a Sanic() instance: + + $ sanic path.to.server:app + + Or, a path to a callable that returns a Sanic() instance: + + $ sanic path.to.factory:create_app --factory + + Or, a path to a directory to run as a simple HTTP server: + + $ sanic ./path/to/static --simple + +Required +======== + Positional: + module Path to your Sanic app. Example: path.to.server:app + If running a Simple Server, path to directory to serve. Example: ./ + +Optional +======== + General: + -h, --help show this help message and exit + --version show program's version number and exit + + Application: + --factory Treat app as an application factory, i.e. a () -> callable + -s, --simple Run Sanic as a Simple Server, and serve the contents of a directory + (module arg should be a path) + + Socket binding: + -H HOST, --host HOST Host address [default 127.0.0.1] + -p PORT, --port PORT Port to serve on [default 8000] + -u UNIX, --unix UNIX location of unix socket + + TLS certificate: + --cert CERT Location of fullchain.pem, bundle.crt or equivalent + --key KEY Location of privkey.pem or equivalent .key file + --tls DIR TLS certificate folder with fullchain.pem and privkey.pem + May be specified multiple times to choose multiple certificates + --tls-strict-host Only allow clients that send an SNI matching server certs + + Worker: + -w WORKERS, --workers WORKERS Number of worker processes [default 1] + --fast Set the number of workers to max allowed + --access-logs Display access logs + --no-access-logs No display access logs + + Development: + --debug Run the server in debug mode + -d, --dev Currently is an alias for --debug. But starting in v22.3, + --debug will no longer automatically trigger auto_restart. + However, --dev will continue, effectively making it the + same as debug + auto_reload. + -r, --reload, --auto-reload Watch source directory for file changes and reload on changes + -R PATH, --reload-dir PATH Extra directories to watch and reload on changes + + Output: + --motd Show the startup display + --no-motd No show the startup display + -v, --verbosity Control logging noise, eg. -vv or --verbosity=2 [default 0] + --noisy-exceptions Output stack traces for all exceptions + --no-noisy-exceptions No output stack traces for all exceptions +``` + +### Server running modes and changes coming to `debug` + +There are now two running modes: `DEV` and `PRODUCTION`. By default, Sanic server will run under `PRODUCTION` mode. This is intended for deployments. + +Currently, `DEV` mode will operate very similarly to how `debug=True` does in older Sanic versions. However, in v22.3. `debug=True` will **no longer** enable auto-reload. If you would like to have debugging and auto-reload, you should enable `DEV` mode. + +**DEVELOPMENT** + +``` +$ sanic server:app --dev +``` + +```python +app.run(debug=True, auto_reload=True) +``` + +**PRODUCTION** + +``` +$ sanic server:app +``` + +```python +app.run() +``` + +Beginning in v22.3, `PRODUCTION` mode will no longer enable access logs by default. + +A summary of the changes are as follows: + +| Flag | Mode | Tracebacks | Logging | Access logs | Reload | Max workers | +| ------- | ----- | ---------- | ------- | ----------- | ------ | ----------- | +| --debug | DEBUG | yes | DEBUG | yes | ^1 | | +| | PROD | no | INFO ^2 | ^3 | | | +| --dev | DEBUG | yes | DEBUG | yes | yes | | +| --fast | | | | | | yes | + + +- ^1 `--debug` to deprecate auto-reloading and remove in 22.3 +- ^2 After 22.3 this moves to WARNING +- ^3 After 22.3: no + +### Max allowed workers + +You can easily spin up the maximum number of allowed workers using `--fast`. + +``` +$ sanic server:app --fast +``` + +```python +app.run(fast=True) +``` + +### First-class Sanic Extensions support + +[Sanic Extensions](../../plugins/sanic-ext/getting-started.md) provides a number of additional features specifically intended for API developers. You can now easily implement all of the functionality it has to offer without additional setup as long as the package is in the environment. These features include: + +- Auto create `HEAD`, `OPTIONS`, and `TRACE` endpoints +- CORS protection +- Predefined, endpoint-specific response serializers +- Dependency injection into route handlers +- OpenAPI documentation with Redoc and/or Swagger +- Request query arguments and body input validation + +The preferred method is to install it along with Sanic, but you can also install the packages on their own. + +---:1 +``` +$ pip install sanic[ext] +``` + +:--:1 + +``` +$ pip install sanic sanic-ext +``` + +:--- + +After that, no additional configuration is required. Sanic Extensions will be attached to your application and provide all of the additional functionality with **no further configuration**. + +If you want to change how it works, or provide additional configuration, you can change Sanic extensions using `app.extend`. The following examples are equivalent. The `Config` object is to provide helpful type annotations for IDE development. + +---:1 +```python +# This is optional, not required +app = Sanic("MyApp") +app.extend(config={"oas_url_prefix": "/apidocs"}) +``` +:--: +```python +# This is optional, not required +app = Sanic("MyApp") +app.config.OAS_URL_PREFIX = "/apidocs" +``` +:--- + +---:1 +```python +# This is optional, not required +from sanic_ext import Config + +app = Sanic("MyApp") +app.extend(config=Config(oas_url_prefix="/apidocs")) +``` +:--: + +:--- + +### Contextual exceptions + +In [v21.9](./v21.9.md#default-exception-messages) we added default messages to exceptions that simplify the ability to consistently raise exceptions throughout your application. + +```python +class TeapotError(SanicException): + status_code = 418 + message = "Sorry, I cannot brew coffee" + +raise TeapotError +``` + +But this lacked two things: + +1. A dynamic and predictable message format +2. The ability to add additional context to an error message (more on this in a moment) + +The current release allows any Sanic exception to have additional information to when raised to provide context when writing an error message: + +```python +class TeapotError(SanicException): + status_code = 418 + + @property + def message(self): + return f"Sorry {self.extra['name']}, I cannot make you coffee" + +raise TeapotError(extra={"name": "Adam"}) +``` + +The new feature allows the passing of `extra` meta to the exception instance. This `extra` info object **will be suppressed** when in `PRODUCTION` mode, but displayed in `DEVELOPMENT` mode. + +---:1 **PRODUCTION** + +![image](https://user-images.githubusercontent.com/166269/139014161-cda67cd1-843f-4ad2-9fa1-acb94a59fc4d.png) :--:1 **DEVELOPMENT** + +![image](https://user-images.githubusercontent.com/166269/139014121-0596b084-b3c5-4adb-994e-31ba6eba6dad.png) :--- + +Getting back to item 2 from above: _The ability to add additional context to an error message_ + +This is particularly useful when creating microservices or an API that you intend to pass error messages back in JSON format. In this use case, we want to have some context around the exception beyond just a parseable error message to return details to the client. + + +```python +raise TeapotError(context={"foo": "bar"}) +``` + +This is information **that we want** to always be passed in the error (when it is available). Here is what it should look like: + +---:1 **PRODUCTION** + +```json +{ + "description": "I'm a teapot", + "status": 418, + "message": "Sorry Adam, I cannot make you coffee", + "context": { + "foo": "bar" + } +} +``` +:--:1 **DEVELOPMENT** + +```json +{ + "description": "I'm a teapot", + "status": 418, + "message": "Sorry Adam, I cannot make you coffee", + "context": { + "foo": "bar" + }, + "extra": { + "name": "Adam", + "more": "lines", + "complex": { + "one": "two" + } + }, + "path": "/", + "args": {}, + "exceptions": [ + { + "type": "TeapotError", + "exception": "Sorry Adam, I cannot make you coffee", + "frames": [ + { + "file": "handle_request", + "line": 83, + "name": "handle_request", + "src": "" + }, + { + "file": "/tmp/p.py", + "line": 17, + "name": "handler", + "src": "raise TeapotError(" + } + ] + } + ] +} +``` +:--- + +### Background task management + +When using the `app.add_task` method to create a background task, there now is the option to pass an optional `name` keyword argument that allows it to be fetched, or cancelled. + +```python +app.add_task(dummy, name="dummy_task") +task = app.get_task("dummy_task") + +app.cancel_task("dummy_task") +``` + +### Route context kwargs in definitions + +When a route is defined, you can add any number of keyword arguments with a `ctx_` prefix. These values will be injected into the route `ctx` object. + +```python +@app.get("/1", ctx_label="something") +async def handler1(request): + ... + +@app.get("/2", ctx_label="something") +async def handler2(request): + ... + +@app.get("/99") +async def handler99(request): + ... + +@app.on_request +async def do_something(request): + if request.route.ctx.label == "something": + ... +``` + +### Blueprints can be registered at any time + +In previous versions of Sanic, there was a strict ordering of when a Blueprint could be attached to an application. If you ran `app.blueprint(bp)` *before* attaching all objects to the Blueprint instance, they would be missed. + +Now, you can attach a Blueprint at anytime and everything attached to it will be included at startup. + +### Noisy exceptions (force all exceptions to logs) + +There is a new `NOISY_EXCEPTIONS` config value. When it is `False` (which is the default), Sanic will respect the `quiet` property of any `SanicException`. This means that an exception with `quiet=True` will not be displayed to the log output. + +However, when setting `NOISY_EXCEPTIONS=True`, all exceptions will be logged regardless of the `quiet` value. + +This can be helpful when debugging. + +```python +app.config.NOISY_EXCEPTIONS = True +``` + +### Signal events as `Enum` + +There is an `Enum` with all of the built-in signal values for convenience. + +```python +from sanic.signals import Event + +@app.signal(Event.HTTP_LIFECYCLE_BEGIN) +async def connection_opened(conn_info): + ... +``` + +### Custom type casting of environment variables + +By default, Sanic will convert an `int`, `float`, or a `bool` value when applying environment variables to the `config` instance. You can extend this with your own converter: + +```python +app = Sanic(..., config=Config(converters=[UUID])) +``` + +### Disable `uvloop` by configuration value + +The usage of `uvloop` can be controlled by configuration value: + + +```python +app.config.USE_UVLOOP = False +``` + +### Run Sanic server with multiple TLS certificates + +Sanic can be run with multiple TLS certificates: + +```python +app.run( + ssl=[ + "/etc/letsencrypt/live/example.com/", + "/etc/letsencrypt/live/mysite.example/", + ] +) +``` + +## News + +### Coming Soon: Python Web Development with Sanic + +A book about Sanic is coming soon by one of the core developers, [@ahopkins](https://github.com/ahopkins). + +Learn more at [sanicbook.com](https://sanicbook.com). + +> Get equipped with the practical knowledge of working with Sanic to increase the performance and scalability of your web applications. While doing that, we will level-up your development skills as you learn to customize your application to meet the changing business needs without having to significantly over-engineer the app. + +A portion of book proceeds goes into the Sanic Community Organization to help fund the development and operation of Sanic. So, buying the book is another way you can support Sanic. + +### Dark mode for the docs + +If you have not already noticed, this Sanic website is now available in a native dark mode. You can toggle the theme at the top right of the page. + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@adarsharegmi](https://github.com/adarsharegmi) [@ahopkins](https://github.com/ahopkins) [@ashleysommer](https://github.com/ashleysommer) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@cnicodeme](https://github.com/cnicodeme) [@kianmeng](https://github.com/kianmeng) [@meysam81](https://github.com/meysam81) [@nuxion](https://github.com/nuxion) [@prryplatypus](https://github.com/prryplatypus) [@realDragonium](https://github.com/realDragonium) [@SaidBySolo](https://github.com/SaidBySolo) [@sjsadowski](https://github.com/sjsadowski) [@Tronic](https://github.com/tronic) [@Varriount](https://github.com/Varriount) [@vltr](https://github.com/vltr) [@whos4n3](https://github.com/whos4n3) + +And, a special thank you to [@miss85246](https://github.com/miss85246) and [@ZinkLu](https://github.com/ZinkLu) for their tremendous work keeping the documentation synced and translated into Chinese. + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/ko/guide/release-notes/v21.3.md b/src/ko/guide/release-notes/v21.3.md new file mode 100644 index 0000000000..bca0729af6 --- /dev/null +++ b/src/ko/guide/release-notes/v21.3.md @@ -0,0 +1,262 @@ +# Version 21.3 + +[[toc]] + +## Introduction + +Sanic is now faster. + +Well, it already was fast. But with the first iteration of the v21 release, we incorporated a few major milestones that have made some tangible improvements. These encompass some ideas that have been in the works for years, and have finally made it into the released version. + +::: warning Breaking changes Version 21.3 introduces a lot of new features. But, it also includes some breaking changes. This is why these changes were introduced after the last LTS. If you rely upon something that has been removed, you should continue to use v20.12LTS until you are able to upgrade. + +```bash +pip install "sanic>=20.12,<20.13" +pip freeze > requirements.txt +``` + +For most typical installations, you should be able to upgrade without a problem. ::: + +## What to know + +Notable new or breaking features, and what to upgrade... + +### Python 3.7+ Only + +This version drops Python 3.6 support. Version 20.12LTS will continue to support Python 3.6 until its EOL in December, 2022, and version 19.12LTS will support it until its EOL in December, 2021. + +Read more about our [LTS policy](../project/policies.md#long-term-support-v-interim-releases). + +### Streaming as first class citizen + +The biggest speed improvement came from unifying the request/response cycle into a single flow. Previously, there was a difference between regular cycles, and streaming cycles. This has been simplified under the hood, even though the API is staying the same right now for compatibility. The net benefit is that **all** requests now should see a new benefit. + +Read more about [streaming changes](../advanced/streaming.md#response-streaming). + +### Router overhaul + +The old Sanic router was based upon regular expressions. In addition it suffered from a number of quirks that made it hard to modify at run time, and resulted in some performance issues. This change has been years in the making and now [converts the router to a compiled tree at startup](https://community.sanicframework.org/t/a-fast-new-router/649/41). Look for additional improvements throughout the year. + +The outward facing API has kept backwards compatibility. However, if you were accessing anything inside the router specifically, you many notice some changes. For example: + +1. `Router.get()` has a new return value +2. `Route` is now a proper class object and not a `namedtuple` +3. If building the router manually, you will need to call `Router.finalize()` before it is usable +4. There is a new `` pattern that can be matched in your routes +5. You cannot startup an application without at least one route defined + +The router is now located in its own repository: [sanic-org/sanic-router](https://github.com/sanic-org/sanic-router) and is also its own [standalone package on PyPI](https://pypi.org/project/sanic-routing/). + +### Signals API ⭐️ + +_BETA Feature: API to be finalized in v21.6_ + +A side benefit of the new router is that it can do double duty also powering the [new signals API](https://github.com/sanic-org/sanic/issues/1630). This feature is being released for public usage now, and likely the public API will not change in its final form. + +The core ideas of this feature are: + +1. to allow the developer greater control and access to plugging into the server and request lifecycles, +2. to provide new tools to synchronize and send messages through your application, and +3. to ultimately further increase performance. + +The API introduces three new methods: + +- `@app.signal(...)` - For defining a signal handler. It looks and operates very much like a route. Whenever that signal is dispatched, this handler will be executed. +- `app.event(...)` - An awaitable that can be used anywhere in your application to pause execution until the event is triggered. +- `app.dispatch(...)` - Trigger an event and cause the signal handlers to execute. + +```python +@app.signal("foo.bar.") +async def signal_handler(thing, **kwargs): + print(f"[signal_handler] {thing=}", kwargs) + +async def wait_for_event(app): + while True: + print("> waiting") + await app.event("foo.bar.*") + print("> event found\n") + +@app.after_server_start +async def after_server_start(app, loop): + app.add_task(wait_for_event(app)) + +@app.get("/") +async def trigger(request): + await app.dispatch("foo.bar.baz") + return response.text("Done.") +``` + +### Route naming + +Routes used to be referenced by both `route.name` and `route.endpoint`. While similar, they were slightly different. Now, all routes will be **consistently** namespaced and referenced. + +``` +.[optional:.] +``` + +This new "name" is assigned to the property `route.name`. We are deprecating `route.endpoint`, and will remove that property in v21.9. Until then, it will be an alias for `route.name`. + +In addition, naming prefixes that had been in use for things like static, websocket, and blueprint routes have been removed. + +### New decorators + +Several new convenience decorators to help IDEs with autocomplete. + +```python +# Alias to @app.listener("...") +@app.before_server_start +@app.after_server_stop +@app.before_server_start +@app.after_server_stop + +# Alias to @app.middleware("...") +@app.on_request +@app.on_response +``` + +### Unquote in route + +If you have a route that uses non-ascii characters, Sanic will no longer `unquote` the text for you. You will need to specifically tell the route definition that it should do so. + +```python +@app.route("/overload/", methods=["GET"], unquote=True) +async def handler2(request, param): + return text("OK2 " + param) + +request, response = app.test_client.get("/overload/您好") +assert response.text == "OK2 您好" +``` + +If you forget to do so, your text will remain encoded. + +### Alter `Request.match_info` + +The `match_info` has always provided the data for the matched path parameters. You now have access to modify that, for example in middleware. + +```python +@app.on_request +def convert_to_snake_case(request): + request.match_info = to_snake(request.match_info) +``` + +### Version types in routes + +The `version` argument in routes can now be: + +- `str` +- `int` +- `float` + +```python +@app.route("/foo", version="2.1.1") +@app.route("/foo", version=2) +@app.route("/foo", version=2.1) +``` +### Safe method handling with body + +Route handlers for `GET`, `HEAD`, `OPTIONS` and `DELETE` will not decode any HTTP body passed to it. You can override this: + +```python +@app.delete(..., ignore_body=False) +``` + +### Application, Blueprint and Blueprint Group parity + +The `Sanic` and `Blueprint` classes share a common base. Previously they duplicated a lot of functionality, that lead to slightly different implementations between them. Now that they both inherit the same base class, developers and plugins should have a more consistent API to work with. + +Also, Blueprint Groups now also support common URL extensions like the `version` and `strict_slashes` keyword arguments. + +### Dropped `httpx` from dependencies + +There is no longer a dependency on `httpx`. + +### Removed `testing` library + +Sanic internal testing client has been removed. It is now located in its own repository: [sanic-org/sanic-testing](https://github.com/sanic-org/sanic-testing) and is also its own [standalone package on PyPI](https://pypi.org/project/sanic-testing/). + +If you have `sanic-testing` installed, it will be available and usable on your `Sanic()` application instances as before. So, the **only** change you will need to make is to add `sanic-testing` to your test suite requirements. + +### Application and connection level context (`ctx`) objects + +Version 19.9 [added ](https://github.com/sanic-org/sanic/pull/1666/files) the `request.ctx` API. This helpful construct easily allows for attaching properties and data to a request object (for example, in middleware), and reusing the information elsewhere int he application. + +Similarly, this concept is being extended in two places: + +1. the application instance, and +2. a transport connection. + +#### Application context + +A common use case is to attach properties to the app instance. For the sake of consistency, and to avoid the issue of name collision with Sanic properties, the `ctx` object now exists on `Sanic` instances. + +```python +@app.before_server_startup +async def startup_db(app, _): + # WRONG + app.db = await connect_to_db() + + # CORRECT + app.ctx.db = await connect_to_db() +``` + +#### Connection context + +When a client sends a keep alive header, Sanic will attempt to keep the transport socket [open for a period of time](../deployment/configuration.md#keep-alive-timeout). That transport object now has a `ctx` object available on it. This effectively means that multiple requests from a single client (where the transport layer is being reused) may share state. + +```python +@app.on_request +async def increment_foo(request): + if not hasattr(request.conn_info.ctx, "foo"): + request.conn_info.ctx.foo = 0 + request.conn_info.ctx.foo += 1 + +@app.get("/") +async def count_foo(request): + return text(f"request.conn_info.ctx.foo={request.conn_info.ctx.foo}") +``` + +```bash +$ curl localhost:8000 localhost:8000 localhost:8000 +request.conn_info.ctx.foo=1 +request.conn_info.ctx.foo=2 +request.conn_info.ctx.foo=3 +``` + +::: warning +Connection level context is an experimental feature, and should be finalized in v21.6. +::: + +## News + + +### A NEW frontpage 🎉 + +We have split the documentation into two. The docstrings inside the codebase will still continue to build sphinx docs to ReadTheDocs. However, it will be limited to API documentation. The new frontpage will house the "Sanic User Guide". + +The new site runs on Vuepress. Contributions are welcome. We also invite help in translating the documents. + +As a part of this, we also freshened up the RTD documentation and changed it to API docs only. + +### Chat has moved to Discord + +The Gitter chatroom has taken one step closer to being phased out. In its place we opened a [Discord server](https://discord.gg/FARQzAEMAA). + +### Open Collective + +The Sanic Community Organization has [opened a page on Open Collective](https://opencollective.com/sanic-org) to enable anyone that would like to financially support the development of Sanic. + +### 2021 Release Managers + +Thank you to @sjsadowski and @yunstanford for acting as release managers for both 2019 and 2020. This year's release managers are @ahopkins and @vltr. + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@ahopkins](https://github.com/ahopkins) [@akshgpt7](https://github.com/akshgpt7) [@artcg](https://github.com/artcg) [@ashleysommer](https://github.com/ashleysommer) [@elis-k](https://github.com/elis-k) [@harshanarayana](https://github.com/harshanarayana) [@sjsadowski](https://github.com/sjsadowski) [@tronic](https://github.com/tronic) [@vltr](https://github.com/vltr), + +To [@ConnorZhang](https://github.com/miss85246) and [@ZinkLu](https://github.com/ZinkLu) for translating our documents into Chinese, + +--- + +Make sure to checkout the changelog to get links to all the PRs, etc. diff --git a/src/ko/guide/release-notes/v21.6.md b/src/ko/guide/release-notes/v21.6.md new file mode 100644 index 0000000000..388a97d211 --- /dev/null +++ b/src/ko/guide/release-notes/v21.6.md @@ -0,0 +1,324 @@ +# Version 21.6 + +[[toc]] + +## Introduction + +This is the second release of the version 21 [release cycle](../project/policies.md#release-schedule). There will be one more release in September before version 21 is "finalized" in the December long-term support version. One thing users may have noticed starting in 21.3, the router was moved to its own package: [`sanic-routing`](https://pypi.org/project/sanic-routing). This change is likely to stay for now. Starting with this release, the minimum required version is 0.7.0. + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + +### Deprecation of `StreamingHTTPResponse` + +The use of `StreamingHTTPResponse` has been deprecated and will be removed in the 21.12 release. This impacts both `sanic.response.stream` and `sanic.response.file_stream`, which both under the hood instantiate `StreamingHTTPResponse`. + +Although the exact migration path has yet to be determined, `sanic.response.stream` and `sanic.response.file_stream` will continue to exist in v21.12 in some form as convenience operators. Look for more details throughout this Summer as we hope to have this finalized by the September release. + +### Deprecation of `CompositionView` + +Usage of `CompositionView` has been deprecated and will be removed in 21.12. + +### Deprecation of path parameter types: `string` and `number` + +Going forward, you should use `str` and `float` for path param types instead of `string` and `number`. + +```python +@app.get("//") +async def handler(request, foo: str, bar: float): + ... +``` + +Existing `string` and `number` types are aliased and will continue to work, but will be removed in v21.12. + +### Version 0.7 router upgrades + +This includes a number of bug fixes and more gracefully handles a wider array of edge cases than v0.6. If you experience any patterns that are not supported, [please report them](https://github.com/sanic-org/sanic-routing/issues). You can see some of the issues resolved on the `sanic-routing` [release notes](https://github.com/sanic-org/sanic-routing/releases). + +### Inline streaming with `eof()` + +Version 21.3 included [big changes in how streaming is handled](https://sanic.dev/en/guide/release-notes/v21.3.html#what-to-know). The pattern introduced will become the default (see below). As a convenience, a new `response.eof()` method has been included. It should be called once the final data has been pushed to the client: + +```python +@app.route("/") +async def test(request): + response = await request.respond(content_type="text/csv") + await response.send("foo,") + await response.send("bar") + await response.eof() + return response +``` + +### New path parameter type: `slug` + +You can now specify a dynamic path segment as a `slug` with appropriate matching: + +```python +@app.get("/articles/") +async def article(request, article_slug: str): + ... +``` + +Slugs must consist of lowercase letters or digits. They may contain a hyphen (`-`), but it cannot be the first character. + +``` +this-is-a-slug +with-123-is-also-a-slug +111-at-start-is-a-slug +NOT-a-slug +-NOT-a-slug +``` + +### Stricter application and blueprint names, and deprecation + +Your application and `Blueprint` instances must conform to a stricter set of requirements: + +1. Only consisting of alphanumeric characters +2. May contain a hyphen (`-`) or an underscore (`_`) +3. Must begin with a letter (uppercase or lowercase) + +The naming convention is similar to Python variable naming conventions, with the addition of allowing hyphens (`-`). + +The looser standard has been deprecatated. Beginning in 21.12, non-conformance will be a startup time error. + +### A new access on `Route` object: `route.uri` + +The `Route` object in v21.3 no longer had a `uri` attribute. Instead, the closes you could get was `route.path`. However, because of how `sanic-routing` works, the `path` property does *not* have a leading `/`. This has been corrected so that now there is a `route.uri` with a leading slash: + +```python +route.uri == f"/{route.path}" +``` + +### A new accessor on `Request` object impacting IPs + +To access the IP address of the incoming request, Sanic has had a convenience accessor on the request object: `request.ip`. That is not new, and comes from an underlying object that provides details about the open HTTP connection: `request.conn_info`. + +The current version adds a new `client_ip` accessor to that `conn_info` object. For IPv4, you will not notice a difference. However, for IPv6 applications, the new accessor will provide an "unwrapped" version of the address. Consider the following example: + +```python +@app.get("/") +async def handler(request): + return json( + { + "request.ip": request.ip, + "request.conn_info.client": request.conn_info.client, + "request.conn_info.client_ip": request.conn_info.client_ip, + } + ) + + +app.run(sock=my_ipv6_sock) +``` + +```bash +$ curl http://\[::1\]:8000 +{ + "request.ip": "::1", + "request.conn_info.client": "[::1]", + "request.conn_info.client_ip": "::1" +} + +``` + +### Alternate `Config` and `Sanic.ctx` objects + +You can now pass your own config and context objects to your Sanic applications. A custom configuration *should* be a subclass of `sanic.config.Config`. The context object can be anything you want, with no restrictions whatsoever. + +```python +class CustomConfig(Config): + ... + +config = CustomConfig() +app = Sanic("custom", config=config) +assert isinstance(app.config, CustomConfig) +``` + +And... + +```python +class CustomContext: + ... + +ctx = CustomContext() +app = Sanic("custom", ctx=ctx) +assert isinstance(app.ctx, CustomContext) +``` + +### Sanic CLI improvements + +1. New flag for existing feature: `--auto-reload` +2. Some new shorthand flags for existing arguments +3. New feature: `--factory` +4. New feature: `--simple` +5. New feature: `--reload-dir` + +#### Factory applications + +For applications that follow the factory pattern (a function that returns a `sanic.Sanic` instance), you can now launch your application from the Sanic CLI using the `--factory` flag. + +```python +from sanic import Blueprint, Sanic, text + +bp = Blueprint(__file__) + +@bp.get("/") +async def handler(request): + return text("😎") + +def create_app() -> Sanic: + app = Sanic(__file__) + app.blueprint(bp) + return app +``` + +You can now run it: + +```bash +$ sanic path.to:create_app --factory +``` + +#### Sanic Simple Server + +Sanic CLI now includes a simple pattern to serve a directory as a web server. It will look for an `index.html` at the directory root. + +```bash +$ sanic ./path/to/dir --simple +``` + +::: warning This feature is still in early *beta* mode. It is likely to change in scope. ::: + +#### Additional reload directories + +When using either `debug` or `auto-reload`, you can include additional directories for Sanic to watch for new files. + +```bash +sanic ... --reload-dir=/path/to/foo --reload-dir=/path/to/bar +``` + +::: tip You do *NOT* need to include this on your application directory. Sanic will automatically reload when any Python file in your application changes. You should use the `reload-dir` argument when you want to listen and update your application when static files are updated. ::: + +### Version prefix + +When adding `version`, your route is prefixed with `/v`. This will always be at the beginning of the path. This is not new. + +```python +# /v1/my/path +app.route("/my/path", version=1) +``` + +Now, you can alter the prefix (and therefore add path segments *before* the version). + +```python +# /api/v1/my/path +app.route("/my/path", version=1, version_prefix="/api/v") +``` + +The `version_prefix` argument is can be defined in: + +- `app.route` and `bp.route` decorators (and all the convenience decorators also) +- `Blueprint` instantiation +- `Blueprint.group` constructor +- `BlueprintGroup` instantiation +- `app.blueprint` registration + +### Signal event auto-registration + +Setting `config.EVENT_AUTOREGISTER` to `True` will allow you to await any signal event even if it has not previously been defined with a signal handler. + +```python +@app.signal("do.something.start") +async def signal_handler(): + await do_something() + await app.dispatch("do.something.complete") + +# somethere else in your app: +await app.event("do.something.complete") +``` + +### Infinitely reusable and nestable `Blueprint` and `BlueprintGroup` + +A single `Blueprint` may not be assigned and reused to multiple groups. The groups themselves can also by infinitely nested into one or more other groups. This allows for an unlimited range of composition. + +### HTTP methods as `Enum` + +Sanic now has `sanic.HTTPMethod`, which is an `Enum`. It can be used interchangeably with strings: + +```python +from sanic import Sanic, HTTPMethod + +@app.route("/", methods=["post", "PUT", HTTPMethod.PATCH]) +async def handler(...): + ... +``` + +### Expansion of `HTTPMethodView` + +Class based views may be attached now in one of three ways: + +**Option 1 - Existing** +```python +class DummyView(HTTPMethodView): + ... + +app.add_route(DummyView.as_view(), "/dummy") +``` + +**Option 2 - From `attach` method** +```python +class DummyView(HTTPMethodView): + ... + +DummyView.attach(app, "/") +``` + +**Option 3 - From class definition at `__init_subclass__`** +```python +class DummyView(HTTPMethodView, attach=app, uri="/"): + ... +``` + +Options 2 and 3 are useful if your CBV is located in another file: + +```python +from sanic import Sanic, HTTPMethodView + +class DummyView(HTTPMethodView, attach=Sanic.get_app(), uri="/"): + ... +``` + +## News + +### Discord and support forums + +If you have not already joined our community, you can become a part by joining the [Discord server](https://discord.gg/FARQzAEMAA) and the [Community Forums](https://community.sanicframework.org/). Also, follow [@sanicframework](https://twitter.com/sanicframework) on Twitter. + +### SCO 2022 elections + +The Summer 🏝/Winter ❄️ (choose your Hemisphere) is upon us. That means we will be holding elections for the SCO. This year, we will have the following positions to fill: + +- Steering Council Member (2 year term) +- Steering Council Member (2 year term) +- Steering Council Member (1 year term) +- Release Manager v22 +- Release Manager v22 + +[@vltr](https://github.com/vltr) will be staying on to complete his second year on the Steering Council. + +If you are interested in learning more, you can read about the SCO [roles and responsibilities](../project/scope.md#roles-and-responsibilities), or Adam Hopkins on Discord. + +Nominations will begin September 1. More details will be available on the Forums as we get closer. + +### New project underway + +We have added a new project to the SCO umbrella: [`sanic-ext`](https://github.com/sanic-org/sanic-ext). It is not yet released, and in heavy active development. The goal for the project will ultimately be to replace [`sanic-openapi`](https://github.com/sanic-org/sanic-openapi) with something that provides more features for web application developers, including input validation, CORS handling, and HTTP auto-method handlers. If you are interested in helping out, let us know on Discord. Look for an initial release of this project sometime (hopefully) before the September release. + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@aaugustin](https://github.com/aaugustin) [@ahopkins](https://github.com/ahopkins) [@ajaygupta2790](https://github.com/ajaygupta2790) [@ashleysommer](https://github.com/ashleysommer) [@ENT8R](https://github.com/ent8r) [@fredlllll](https://github.com/fredlllll) [@graingert](https://github.com/graingert) [@harshanarayana](https://github.com/harshanarayana) [@jdraymon](https://github.com/jdraymon) [@Kyle-Verhoog](https://github.com/kyle-verhoog) [@sanjeevanahilan](https://github.com/sanjeevanahilan) [@sjsadowski](https://github.com/sjsadowski) [@Tronic](https://github.com/tronic) [@vltr](https://github.com/vltr) [@ZinkLu](https://github.com/zinklu) + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able, [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/ko/guide/release-notes/v21.9.md b/src/ko/guide/release-notes/v21.9.md new file mode 100644 index 0000000000..bc11399481 --- /dev/null +++ b/src/ko/guide/release-notes/v21.9.md @@ -0,0 +1,221 @@ +# Version 21.9 + +[[toc]] + +## Introduction + +This is the third release of the version 21 [release cycle](../../org/policies.md#release-schedule). Version 21 will be "finalized" in the December long-term support version release. + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + +### Removal of config values: `WEBSOCKET_READ_LIMIT`, `WEBSOCKET_WRITE_LIMIT` and `WEBSOCKET_MAX_QUEUE` + +With the complete overhaul of the websocket implementation, these configuration values were removed. There currently is not a plan to replace them. + +### Deprecation of default value of `FALLBACK_ERROR_FORMAT` + +When no error handler is attached, Sanic has used `html` as the fallback format-type. This has been deprecated and will change to `text` starting in v22.3. While the value of this has changed to `auto`, it will still continue to use HTML as the last resort thru v21.12LTS before changing. + +### `ErrorHandler.lookup` signature deprecation + +The `ErrorHandler.lookup` now **requires** two positional arguments: + +```python +def lookup(self, exception, route_name: Optional[str]): +``` + +A non-conforming method will cause Blueprint-specific exception handlers to not properly attach. + +### Reminder of upcoming removals + +As a reminder, the following items have already been deprecated, and will be removed in version 21.12LTS + +- `CompositionView` +- `load_env` (use `env_prefix` instead) +- Sanic objects (application instances, blueprints, and routes) must by alphanumeric conforming to: `^[a-zA-Z][a-zA-Z0-9_\-]*$` +- Arbitrary assignment of objects to application and blueprint instances (use `ctx` instead; removal of this has been bumped from 21.9 to 21.12) + +### Overhaul of websockets + +There has been a huge overhaul to the handling of websocket connections. Thanks to [@aaugustin](https://github.com/aaugustin) the [`websockets`](https://websockets.readthedocs.io/en/stable/index.html) now has a new implementation that allows Sanic to handle the I/O of websocket connections on its own. Therefore, Sanic has bumped the minimum version to `websockets>=10.0`. + +The change should mostly be unnoticeable to developers, except that some of the oddities around websocket handlers in Sanic have been corrected. For example, you now should be able to catch the `CancellError` yourself when someone disconnects: + +```python +@app.websocket("/") +async def handler(request, ws): + try: + while True: + await asyncio.sleep(0.25) + except asyncio.CancelledError: + print("User closed connection") +``` + +### Built-in signals + +Version [21.3](./v21.3.md) introduced [signals](../advanced/signals.md). Now, Sanic dispatches signal events **from within the codebase** itself. This means that developers now have the ability to hook into the request/response cycle at a much closer level than before. + +Previously, if you wanted to inject some logic you were limited to middleware. Think of integrated signals as _super_-middleware. The events that are dispatched now include: + +- `http.lifecycle.begin` +- `http.lifecycle.complete` +- `http.lifecycle.exception` +- `http.lifecycle.handle` +- `http.lifecycle.read_body` +- `http.lifecycle.read_head` +- `http.lifecycle.request` +- `http.lifecycle.response` +- `http.lifecycle.send` +- `http.middleware.after` +- `http.middleware.before` +- `http.routing.after` +- `http.routing.before` +- `server.init.after` +- `server.init.before` +- `server.shutdown.after` +- `server.shutdown.before` + +::: tip Note The `server` signals are the same as the four (4) main server listener events. In fact, those listeners themselves are now just convenience wrappers to signal implementations. ::: + +### Smarter `auto` exception formatting + +Sanic will now try to respond with an appropriate exception format based upon the endpoint and the client. For example, if your endpoint always returns a `sanic.response.json` object, then any exceptions will automatically be formatted in JSON. The same is true for `text` and `html` responses. + +Furthermore, you now can _explicitly_ control which formatter to use on a route-by-route basis using the route definition: + +```python +@app.route("/", error_format="json") +async def handler(request): + pass +``` + +### Blueprint copying + +Blueprints can be copied to new instances. This will carry forward everything attached to it, like routes, middleware, etc. + +```python +v1 = Blueprint("Version1", version=1) + +@v1.route("/something") +def something(request): + pass + +v2 = v1.copy("Version2", version=2) + +app.blueprint(v1) +app.blueprint(v2) +``` + +``` +/v1/something +/v2/something +``` +### Blueprint group convenience methods + +Blueprint groups should now have all of the same methods available to them as regular Blueprints. With this, along with Blueprint copying, Blueprints should now be very composable and flexible. + +### Accept header parsing + +Sanic `Request` objects can parse an `Accept` header to provide an ordered list of the client's content-type preference. You can simply access it as an accessor: + +```python +print(request.accept) +# ["*/*"] +``` + +It also is capable of handling wildcard matching. For example, assuming the incoming request included: + +``` +Accept: */* +``` + +Then, the following is `True`: + +```python +"text/plain" in request.accept +``` + +### Default exception messages + +Any exception that derives from `SanicException` can now define a default exception message. This makes it more convenient and maintainable to reuse the same exception in multiple places without running into DRY issues with the message that the exception provides. + +```python +class TeaError(SanicException): + message = "Tempest in a teapot" + + +raise TeaError +``` + +### Type annotation conveniences + +It is now possible to control the path parameter types using Python's type annotations. Instead of doing this: + +```python +@app.route("///") +def handler(request: Request, one: int, two: float, three: UUID): + ... +``` + +You can now simply do this: + +```python +@app.route("///") +def handler(request: Request, one: int, two: float, three: UUID): + ... +``` + +Both of these examples will result in the same routing principles to be applied. + +### Explicit static resource type + +You can now explicitly tell a `static` endpoint whether it is supposed to treat the resource as a file or a directory: + +```python +static("/", "/path/to/some/file", resource_type="file")) +``` + +## News + +### Release of `sanic-ext` and deprecation of `sanic-openapi` + +One of the core principles of Sanic is that it is meant to be a tool, not a dictator. As the frontpage of this website states: + +> Build the way you want to build without letting your tooling constrain you. + +This means that a lot of common features used (specifically by Web API developers) do not exist in the `sanic` repository. This is for good reason. Being unopinionated provides the developer freedom and flexibility. + +But, sometimes you do not want to have to build and rebuild the same things. Sanic has until now really relied upon the awesome support of the community to fill in the gaps with plugins. + +From the early days, there has been an official `sanic-openapi` package that offered the ability to create OpenAPI documentation based upon your application. But, that project has been plagued over the years and has not been given as much priority as the main project. + +Starting with the release of v21.9, the SCO is deprecating the `sanic-openapi` package and moving it to maintenance mode. This means that it will continue to get updates as needed to maintain it for the current future, but it will not receive any new feature enhancements. + +A new project called `sanic-ext` is taking its place. This package provides not only the ability to build OAS3 documentation, but fills in many of the gaps that API developers may want in their applications. For example, out of the box it will setup CORS, and auto enable `HEAD` and `OPTIONS` responses where needed. It also has the ability validate incoming data using either standard library Dataclasses or Pydantic models. + +The list of goodies includes: +- CORS protection +- incoming request validation +- auto OAS3 documentation using Redoc and/or Swagger UI +- auto `HEAD`, `OPTIONS`, and `TRACE` responses +- dependency injection +- response serialization + +This project is still in `alpha` mode for now and is subject to change. While it is considered to be production capable, there may be some need to change the API as we continue to add features. + +Checkout the [documentation](../../plugins/sanic-ext/getting-started.md) for more details. + + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@aaugustin](https://github.com/aaugustin) [@ahopkins](https://github.com/ahopkins) [@ashleysommer](https://github.com/ashleysommer) [@cansarigol3megawatt](https://github.com/cansarigol3megawatt) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@gluhar2006](https://github.com/gluhar2006) [@komar007](https://github.com/komar007) [@ombe1229](https://github.com/ombe1229) [@prryplatypus](https://github.com/prryplatypus) [@SaidBySolo](https://github.com/SaidBySolo) [@Tronic](https://github.com/tronic) [@vltr](https://github.com/vltr) + +And, a special thank you to [@miss85246](https://github.com/miss85246) and [@ZinkLu](https://github.com/ZinkLu) for their tremendous work keeping the documentation synced and translated into Chinese. + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able, [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/ko/guide/release-notes/v22.12.md b/src/ko/guide/release-notes/v22.12.md new file mode 100644 index 0000000000..50b3dd96ad --- /dev/null +++ b/src/ko/guide/release-notes/v22.12.md @@ -0,0 +1,176 @@ +# Version 22.12 + +[[toc]] + +## Introduction + +This is the final release of the version 22 [release cycle](../../org/policies.md#release-schedule). As such it is a **long-term support** release, and will be supported as stated in the [policies](../../org/policies.md#long-term-support-v-interim-releases). + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + +### 🚨 *BREAKING CHANGE* - Sanic Inspector is now an HTTP server + +Sanic v22.9 introduced the [Inspector](./v22.9.md#inspector) to allow live inspection of a running Sanic instance. This feature relied upon opening a TCP socket and communicating over a custom protocol. That basic TCP protocol has been dropped in favor of running a full HTTP service in its place. [Learn more about the Inspector](../deployment/inspector.md). + +The current release introduces a new HTTP server and a refreshed CLI experience. This enables several new features highlighted here. Perhaps the most significant change, however, is to move all of the Inspector's commands to a subparser on the CLI instance. + +``` +$ sanic inspect --help + + ▄███ █████ ██ ▄█▄ ██ █ █ ▄██████████ + ██ █ █ █ ██ █ █ ██ + ▀███████ ███▄ ▀ █ █ ██ ▄ █ ██ + ██ █████████ █ ██ █ █ ▄▄ + ████ ████████▀ █ █ █ ██ █ ▀██ ███████ + +Optional +======== + General: + -h, --help show this help message and exit + --host HOST, -H HOST Inspector host address [default 127.0.0.1] + --port PORT, -p PORT Inspector port [default 6457] + --secure, -s Whether to access the Inspector via TLS encryption + --api-key API_KEY, -k API_KEY Inspector authentication key + --raw Whether to output the raw response information + + Subcommands: + Run one or none of the below subcommands. Using inspect without a subcommand will fetch general information about the state of the application instance. + + Or, you can optionally follow inspect with a subcommand. If you have created a custom Inspector instance, then you can run custom commands. See https://sanic.dev/en/guide/deployment/inspector.html for more details. + + {reload,shutdown,scale,} + reload Trigger a reload of the server workers + shutdown Shutdown the application and all processes + scale Scale the number of workers + Run a custom command +``` + +#### CLI remote access now available + +The `host` and `port` of the Inspector are now explicitly exposed on the CLI as shown above. Previously in v22.9, they were inferred by reference to the application instance. Because of this change, it will be more possible to expose the Inspector on live production instances and access from a remote installation of the CLI. + +For example, you can check your running production deployment from your local development machine. + +``` +$ sanic inspect --host=1.2.3.4 +``` + +::: warning +For **production** instances, make sure you are _using TLS and authentication_ described below. +::: + +#### TLS encryption now available + +You can secure your remote Inspector access by providing a TLS certificate to encrypt the web traffic. + +```python +app.config.INSPECTOR_TLS_CERT = "/path/to/cert.pem" +app.config.INSPECTOR_TLS_KEY = "/path/to/key.pem" +``` + +To access an encrypted installation via the CLI, use the `--secure` flag. + +``` +$ sanic inspect --secure +``` + +#### Authentication now available + +To control access to the remote Inspector, you can protect the endpoints using an API key. + +```python +app.config.INSPECTOR_API_KEY = "Super-Secret-200" +``` + +To access a protected installation via the CLI, use the `--api-key` flag. + +``` +$ sanic inspect --api-key=Super-Secret-200 +``` + +This is equivalent to the header: `Authorization: Bearer `. + +``` +$ curl http://localhost:6457 -H "Authorization: Bearer Super-Secret-200" +``` + +### Scale number of running server workers + +The Inspector is now capable of scaling the number of worker processes. For example, to scale to 3 replicas, use the following command: + +``` +$ sanic inspect scale 3 +``` + +### Extend Inspector with custom commands + +The Inspector is now fully extendable to allow for adding custom commands to the CLI. For more information see [Custom Commands](../deployment/inspector.md#custom-commands). + +``` +$ sanic inspect foo --bar +``` + +### Early worker exit on failure + +The process manager shipped with v22.9 had a very short startup timeout. This was to protect against deadlock. This was increased to 30s, and a new mechanism has been added to fail early if there is a crash in a worker process on startup. + +### Introduce `JSONResponse` with convenience methods to update a JSON response body + +The `sanic.response.json` convenience method now returns a new subclass of `HTTPResponse` appropriately named: `JSONResponse`. This new type has some convenient methods for handling changes to a response body after its creation. + +```python +resp = json({"foo": "bar"}) +resp.update({"another": "value"}) +``` + +See [Returning JSON Data](../basics/response.md#returning-json-data) for more information. + +### Updates to downstream requirements: `uvloop` and `websockets` + +Minimum `uvloop` was set to `0.15.0`. Changes were added to make Sanic compliant with `websockets` version `11.0`. + +### Force exit on 2nd `ctrl+c` + +On supporting operating systems, the existing behavior is for Sanic server to try to perform a graceful shutdown when hitting `ctrl+c`. This new release will perform an immediate shutdown on subsequent `ctrl+c` after the initial shutdown has begun. + +### Deprecations and Removals + +1. *DEPRECATED* - The `--inspect*` commands introduced in v22.9 have been replaced with a new subcommand parser available as `inspect`. The flag versions will continue to operate until v23.3. You are encouraged to use the replacements. While this short deprecation period is a deviation from the standard two-cycles, we hope this change will be minimally disruptive. + ``` + OLD sanic ... --inspect + NEW sanic ... inspect + + OLD sanic ... --inspect-raw + NEW sanic ... inspect --raw + + OLD sanic ... --inspect-reload + NEW sanic ... inspect reload + + OLD sanic ... --inspect-shutdown + NEW sanic ... inspect shutdown + ``` + +## News + +The Sanic Community Organization will be headed by a new Steering Council for 2023. There are two returning and two new members. + +[@ahopkins](https://github.com/ahopkins) *returning* \ +[@prryplatypus](https://github.com/prryplatypus) *returning* \ +[@sjsadowski](https://github.com/sjsadowski) *NEW* \ +[@Tronic](https://github.com/Tronic) *NEW* + +The 2023 release managers are [@ahopkins](https://github.com/ahopkins) and [@sjsadowski](https://github.com/sjsadowski). + +If you are interested in getting more involved with Sanic, contact us on the [Discord server](https://discord.gg/FARQzAEMAA). + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@aaugustin](https://github.com/aaugustin) [@ahopkins](https://github.com/ahopkins) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@kijk2869](https://github.com/kijk2869) [@LiraNuna](https://github.com/LiraNuna) [@prryplatypus](https://github.com/prryplatypus) [@sjsadowski](https://github.com/sjsadowski) [@todddialpad](https://github.com/todddialpad) [@Tronic](https://github.com/Tronic) + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/ko/guide/release-notes/v22.3.md b/src/ko/guide/release-notes/v22.3.md new file mode 100644 index 0000000000..9222b48985 --- /dev/null +++ b/src/ko/guide/release-notes/v22.3.md @@ -0,0 +1,321 @@ +# Version 22.3 + +[[toc]] + +## Introduction + +This is the first release of the version 22 [release cycle](../../org/policies.md#release-schedule). All of the standard SCO libraries are now entering the same release cycle and will follow the same versioning pattern. Those packages are: + +- [`sanic-routing`](https://github.com/sanic-org/sanic-routing) +- [`sanic-testing`](https://github.com/sanic-org/sanic-testing) +- [`sanic-ext`](https://github.com/sanic-org/sanic-ext) + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + +### Application multi-serve + +The Sanic server now has an API to allow you to run multiple applications side-by-side in the same process. This is done by calling `app.prepare(...)` on one or more application instances, one or many times. Each time it should be bound to a unique host/port combination. Then, you begin serving the applications by calling `Sanic.serve()`. + +```python +app = Sanic("One") +app2 = Sanic("Two") + +app.prepare(port=9999) +app.prepare(port=9998) +app.prepare(port=9997) +app2.prepare(port=8888) +app2.prepare(port=8887) + +Sanic.serve() +``` + +In the above snippet, there are two applications that will be run concurrently and bound to multiple ports. This feature is *not* supported in the CLI. + +This pattern is meant to be an alternative to running `app.run(...)`. It should be noted that `app.run` is now just a shorthand for the above pattern and is still fully supported. + +### 👶 *BETA FEATURE* - New path parameter type: file extensions + +A very common pattern is to create a route that dynamically generates a file. The endpoint is meant to match on a file with an extension. There is a new path parameter to match files: ``. + +```python +@app.get("/path/to/") +async def handler(request, filename, ext): + ... +``` + +This will catch any pattern that ends with a file extension. You may, however want to expand this by specifying which extensions, and also by using other path parameter types for the file name. + +For example, if you want to catch a `.jpg` file that is only numbers: + +```python +@app.get("/path/to/") +async def handler(request, filename, ext): + ... +``` + +Some potential examples: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ definition + + example + + filename + + extension +
+ \ + + page.txt + + "page" + + "txt" +
+ \ + + cat.jpg + + "cat" + + "jpg" +
+ \ + + + png\ + + gif\ + + svg> | cat.jpg | "cat" | "jpg" +
+ + + 123.txt + + 123 + + "txt" +
+ + + + png\ + + gif\ + + svg> | 123.svg | 123 | "svg" +
+ + + 3.14.tar.gz + + 3.14 + + "tar.gz" +
+ +### 🚨 *BREAKING CHANGE* - Path parameter matching of non-empty strings + +A dynamic path parameter will only match on a non-empty string. + +Previously a route with a dynamic string parameter (`/` or `/`) would match on any string, including empty strings. It will now only match a non-empty string. To retain the old behavior, you should use the new parameter type: `/`. + +```python +@app.get("/path/to/") +async def handler(request, foo) + ... +``` + +### 🚨 *BREAKING CHANGE* - `sanic.worker.GunicornWorker` has been removed + +Departing from our normal deprecation policy, the `GunicornWorker` was removed as a part of the process of upgrading the Sanic server to include multi-serve. This decision was made largely in part because even while it existed it was not an optimal strategy for deploying Sanic. + +If you want to deploy Sanic using `gunicorn`, then you are advised to do it using [the strategy implemented by `uvicorn`](https://www.uvicorn.org/#running-with-gunicorn). This will effectively run Sanic as an ASGI application through `uvicorn`. You can upgrade to this pattern by installing `uvicorn`: + +``` +pip install uvicorn +``` + +Then, you should be able to run it with a pattern like this: + +``` +gunicorn path.to.sanic:app -k uvicorn.workers.UvicornWorker +``` + +### Authorization header parsing + +The `Authorization` header has been partially parseable for some time now. You have been able to use `request.token` to gain access to a header that was in one of the following two forms: + +``` +Authorization: Token +Authorization: Bearer +``` + +Sanic can now parse more credential types like `BASIC`: + +``` +Authorization: Basic Z2lsLWJhdGVzOnBhc3N3b3JkMTIz +``` + +This can be accessed now as `request.credentials`: + +```python +print(request.credentials) +# Credentials(auth_type='Basic', token='Z2lsLWJhdGVzOnBhc3N3b3JkMTIz', _username='gil-bates', _password='password123') +``` + +### CLI arguments optionally injected into application factory + +Sanic will now attempt to inject the parsed CLI arguments into your factory if you are using one. + +```python +def create_app(args): + app = Sanic("MyApp") + print(args) + return app +``` +``` +$sanic p:create_app --factory +Namespace(module='p:create_app', factory=True, simple=False, host='127.0.0.1', port=8000, unix='', cert=None, key=None, tls=None, tlshost=False, workers=1, fast=False, access_log=False, debug=False, auto_reload=False, path=None, dev=False, motd=True, verbosity=None, noisy_exceptions=False) +``` + +If you are running the CLI with `--factory`, you also have the option of passing arbitrary arguments to the command, which will be injected into the argument `Namespace`. + +``` +sanic p:create_app --factory --foo=bar +Namespace(module='p:create_app', factory=True, simple=False, host='127.0.0.1', port=8000, unix='', cert=None, key=None, tls=None, tlshost=False, workers=1, fast=False, access_log=False, debug=False, auto_reload=False, path=None, dev=False, motd=True, verbosity=None, noisy_exceptions=False, foo='bar') +``` + +### New reloader process listener events + +When running Sanic server with auto-reload, there are two new events that trigger a listener *only* on the reloader process: + +- `reload_process_start` +- `reload_process_stop` + +These are only triggered if the reloader is running. + +```python +@app.reload_process_start +async def reload_start(*_): + print(">>>>>> reload_start <<<<<<") + + +@app.reload_process_stop +async def reload_stop(*_): + print(">>>>>> reload_stop <<<<<<") +``` + +### The event loop is no longer a required argument of a listener + +You can leave out the `loop` argument of a listener. Both of these examples work as expected: + +```python +@app.before_server_start +async def without(app): + ... + +@app.before_server_start +async def with(app, loop): + ... +``` + +### Removal - Debug mode does not automatically start the reloader + +When running with `--debug` or `debug=True`, the Sanic server will not automatically start the auto-reloader. This feature of doing both on debug was deprecated in v21 and removed in this release. If you would like to have *both* debug mode and auto-reload, you can use `--dev` or `dev=True`. + +**dev = debug mode + auto reloader** + +### Deprecation - Loading of lower case environment variables + +Sanic loads prefixed environment variables as configuration values. It has not distinguished between uppercase and lowercase as long as the prefix matches. However, it has always been the convention that the keys should be uppercase. This is deprecated and you will receive a warning if the value is not uppercase. In v22.9 only uppercase and prefixed keys will be loaded. + +## News + +### Packt publishes new book on Sanic web development + +---:1 There is a new book on **Python Web Development with Sanic** by [@ahopkins](https://github.com/ahopkins). The book is endorsed by the SCO and part of the proceeds of all sales go directly to the SCO for further development of Sanic. + +You can learn more at [sanicbook.com](https://sanicbook.com/) :--:1 ![Python Web Development with Sanic](https://sanicbook.com/images/SanicCoverFinal.png) :--- + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@aericson](https://github.com/aericson) [@ahankinson](https://github.com/ahankinson) [@ahopkins](https://github.com/ahopkins) [@ariebovenberg](https://github.com/ariebovenberg) [@ashleysommer](https://github.com/ashleysommer) [@Bluenix2](https://github.com/Bluenix2) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@dotlambda](https://github.com/dotlambda) [@eric-spitler](https://github.com/eric-spitler) [@howzitcdf](https://github.com/howzitcdf) [@jonra1993](https://github.com/jonra1993) [@prryplatypus](https://github.com/prryplatypus) [@raphaelauv](https://github.com/raphaelauv) [@SaidBySolo](https://github.com/SaidBySolo) [@SerGeRybakov](https://github.com/SerGeRybakov) [@Tronic](https://github.com/Tronic) + + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/ko/guide/release-notes/v22.6.md b/src/ko/guide/release-notes/v22.6.md new file mode 100644 index 0000000000..f2779fcb19 --- /dev/null +++ b/src/ko/guide/release-notes/v22.6.md @@ -0,0 +1,152 @@ +# Version 22.6 + +[[toc]] + +## Introduction + +This is the second release of the version 22 [release cycle](../../org/policies.md#release-schedule). Version 22 will be "finalized" in the December long-term support version release. + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + + +### Automatic TLS setup in `DEBUG` mode + +The Sanic server can automatically setup a TLS certificate using either [mkcert](https://github.com/FiloSottile/mkcert) or [trustme](https://github.com/python-trio/trustme). This certificate will enable `https://localhost` (or another local address) for local development environments. You must install either `mkcert` or `trustme` on your own for this to work. + +---:1 +``` +$ sanic path.to.server:app --auto-tls --debug +``` +:--:1 + +```python +app.run(debug=True, auto_tls=True) +``` +:--- + +This feature is not available when running in `ASGI` mode, or in `PRODUCTION` mode. When running Sanic in production, you should be using a real TLS certificate either purchased through a legitimate vendor, or using [Let's Encrypt](https://letsencrypt.org/). + + +### HTTP/3 Server :rocket: + +In June 2022, the IETF finalized and published [RFC 9114](https://www.rfc-editor.org/rfc/rfc9114.html), the specification for HTTP/3. In short, HTTP/3 is a **very** different protocol than HTTP/1.1 and HTTP/2 because it implements HTTP over UDP instead of TCP. The new HTTP protocol promises faster webpage load times and solving some of the problems of the older standards. You are encouraged to [read more about](https://http3-explained.haxx.se/) this new web technology. You likely will need to install a [capable client](https://curl.se/docs/http3.html) as traditional tooling will not work. + +Sanic server offers HTTP/3 support using [aioquic](https://github.com/aiortc/aioquic). This **must** be installed: + +``` +pip install sanic aioquic +``` + +``` +pip install sanic[http3] +``` + +To start HTTP/3, you must explicitly request it when running your application. + +---:1 +``` +$ sanic path.to.server:app --http=3 +``` + +``` +$ sanic path.to.server:app -3 +``` +:--:1 + +```python +app.run(version=3) +``` +:--- + +To run both an HTTP/3 and HTTP/1.1 server simultaneously, you can use [application multi-serve](./v22.3.html#application-multi-serve) introduced in v22.3. + + +---:1 +``` +$ sanic path.to.server:app --http=3 --http=1 +``` + +``` +$ sanic path.to.server:app -3 -1 +``` +:--:1 + +```python +app.prepre(version=3) +app.prepre(version=1) +Sanic.serve() +``` +:--- + +Because HTTP/3 requires TLS, you cannot start a HTTP/3 server without a TLS certificate. You should [set it up yourself](../how-to/tls.html) or use `mkcert` if in `DEBUG` mode. Currently, automatic TLS setup for HTTP/3 is not compatible with `trustme`. + +**:baby: This feature is being released as an *EARLY RELEASE FEATURE*.** It is **not** yet fully compliant with the HTTP/3 specification, lacking some features like [websockets](https://websockets.spec.whatwg.org/), [webtransport](https://w3c.github.io/webtransport/), and [push responses](https://http3-explained.haxx.se/en/h3/h3-push). Instead the intent of this release is to bring the existing HTTP request/response cycle towards feature parity with HTTP/3. Over the next several releases, more HTTP/3 features will be added and the API for it finalized. + +### Consistent exception naming + +Some of the Sanic exceptions have been renamed to be more compliant with standard HTTP response names. + +- `InvalidUsage` >> `BadRequest` +- `MethodNotSupported` >> `MethodNotAllowed` +- `ContentRangeError` >> `RangeNotSatisfiable` + +All old names have been aliased and will remain backwards compatible. + +### Current request getter + +Similar to the API to access an application (`Sanic.get_app()`), there is a new method for retrieving the current request when outside of a request handler. + +```python +from sanic import Request + +Request.get_current() +``` + +### Improved API support for setting cache control headers + +The `file` response helper has some added parameters to make it easier to handle setting of the [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) header. + +```python +file( + ..., + last_modified=..., + max_age=..., + no_store=..., +) +``` + + +### Custom `loads` function + +Just like Sanic supports globally setting a custom `dumps`, you can now set a global custom `loads`. + +```python +from orjson import loads + +app = Sanic("Test", loads=loads) +``` + + +### Deprecations and Removals + +1. *REMOVED* - Applications may no longer opt-out of the application registry +1. *REMOVED* - Custom exception handlers will no longer run after some part of an exception has been sent +1. *REMOVED* - Fallback error formats cannot be set on the `ErrorHandler` and must **only** be set in the `Config` +1. *REMOVED* - Setting a custom `LOGO` for startup is no longer allowed +1. *REMOVED* - The old `stream` response convenience method has been removed +1. *REMOVED* - `AsyncServer.init` is removed and no longer an alias of `AsyncServer.app.state.is_started` + + + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@ahopkins](https://github.com/ahopkins) [@amitay87](https://github.com/amitay87) [@ashleysommer](https://github.com/ashleysommer) [@azimovMichael](https://github.com/azimovMichael) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@kijk2869](https://github.com/kijk2869) [@prryplatypus](https://github.com/prryplatypus) [@SaidBySolo](https://github.com/SaidBySolo) [@sjsadowski](https://github.com/sjsadowski) [@timmo001](https://github.com/timmo001) [@zozzz](https://github.com/zozzz) + + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/ko/guide/release-notes/v22.9.md b/src/ko/guide/release-notes/v22.9.md new file mode 100644 index 0000000000..33b265ea83 --- /dev/null +++ b/src/ko/guide/release-notes/v22.9.md @@ -0,0 +1,293 @@ +# Version 22.9 + +[[toc]] + +## Introduction + +This is the third release of the version 22 [release cycle](../../org/policies.md#release-schedule). Version 22 will be "finalized" in the December long-term support version release. + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + + +### :warning: *IMPORTANT* - New worker manager :rocket: + +Sanic server has been overhauled to provide more consistency and flexbility in how it operates. More details about the motivations are outlined in [PR #2499](https://github.com/sanic-org/sanic/pull/2499) and discussed in a live stream [discussion held on YouTube](https://youtu.be/m8HCO8NK7HE). + +This **does NOT apply** to Sanic in ASGI mode + +#### Overview of the changes + +- The worker servers will **always** run in a child process. + - Previously this could change depending upon one versus many workers, and the usage or not of the reloader. This should lead to much more predictable development environments that more closely match their production counterparts. +- Multi-workers is **now supported on Windows**. + - This was impossible because Sanic relied upon the `multiprocessing` module using `fork`, which is not available on Windows. + - Now, Sanic will always use `spawn`. This does have some noticeable differences, particularly if you are running Sanic in the global scope with `app.run` (see below re: upgrade issues). +- The application instance now has a new `multiplexer` object that can be used to restart one or many workers. This could, for example, be triggered by a request. +- There is a new Inspector that can provide details on the state of your server. +- Sanic worker manager can run arbitrary processes. + - This allows developers to add any process they want from within Sanic. + - Possible use cases: + - Health monitor, see [Sanic Extensions]() + - Logging queue, see [Sanic Extensions]() + - Background worker queue in a seperate process + - Running another application, like a bot +- There is a new listener called: `main_process_ready`. It really should only be used for adding arbitrary processes to Sanic. +- Passing shared objects between workers. + - Python does allow some types of objects to share state between processes, whether through shared memory, pipes, etc. + - Sanic will now allow these types of object to be shared on the `app.shared_ctx` object. + - Since this feature relies upon Pythons `multiprocessing` library, it obviously will only work to share state between Sanic worker instances that are instantiated from the same execution. This is *not* meant to provide an API for horizontal scaling across multiple machines for example. + +#### Adding a shared context object + +To share an object between worker processes, it *MUST* be assigned inside of the `main_process_start` listener. + +```python +from multiprocessing import Queue + +@app.main_process_start +async def main_process_start(app): + app.shared_ctx.queue = Queue() +``` + +All objects on `shared_ctx` will be available now within each worker process. + +```python +@app.before_server_starts +async def before_server_starts(app): + assert isinstance(app.shared_ctx.queue, Queue) + +@app.on_request +async def on_request(request): + assert isinstance(request.app.shared_ctx.queue, Queue) + +@app.get("/") +async def handler(request): + assert isinstance(request.app.shared_ctx.queue, Queue) +``` + +*NOTE: Sanic will not stop you from registering an unsafe object, but may warn you. Be careful not to just add a regular list object, for example, and expect it to work. You should have an understanding of how to share state between processes.* + +#### Running arbitrary processes + +Sanic can run any arbitrary process for you. It should be capable of being stopped by a `SIGINT` or `SIGTERM` OS signal. + +These processes should be registered inside of the `main_process_ready` listener. + +```python +@app.main_process_ready +async def ready(app: Sanic, _): + app.manager.manage("MyProcess", my_process, {"foo": "bar"}) +# app.manager.manage(, , ) +``` + +#### Inspector + +Sanic ships with an optional Inspector, which is a special process that allows for the CLI to inspect the running state of an application and issue commands. It currently will only work if the CLI is being run on the same machine as the Sanic instance. + +``` +sanic path.to:app --inspect +``` + +![Sanic inspector](https://user-images.githubusercontent.com/166269/190099384-2f2f3fae-22d5-4529-b279-8446f6b5f9bd.png) + +The new CLI commands are: + +``` + --inspect Inspect the state of a running instance, human readable + --inspect-raw Inspect the state of a running instance, JSON output + --trigger-reload Trigger worker processes to reload + --trigger-shutdown Trigger all processes to shutdown +``` + +This is not enabled by default. In order to have it available, you must opt in: + +```python +app.config.INSPECTOR = True +``` + +*Note: [Sanic Extensions]() provides a [custom request](../basics/app.md#custom-requests) class that will add a request counter to the server state. + +#### Application multiplexer + +Many of the same information and functionality is available on the application instance itself. There is a new `multiplexer` object on the application instance that has the ability to restart one or more workers, and fetch information about the current state. + +You can access it as `app.multiplexer`, or more likely by its short alias `app.m`. + +```python +@app.on_request +async def print_state(request: Request): + print(request.app.m.state) +``` + +#### Potential upgrade issues + +Because of the switch from `fork` to `spawn`, if you try running the server in the global scope you will receive an error. If you see something like this: + +``` +sanic.exceptions.ServerError: Sanic server could not start: [Errno 98] Address already in use. +This may have happened if you are running Sanic in the global scope and not inside of a `if __name__ == "__main__"` block. +``` + +... then the change is simple. Make sure `app.run` is inside a block. + +```python +if __name__ == "__main__": + app.run(port=9999, dev=True) +``` + +#### Opting out of the new functionality + +If you would like to run Sanic without the new process manager, you may easily use the legacy runners. Please note that support for them **will be removed** in the future. A date has not yet been set, but will likely be sometime in 2023. + +To opt out of the new server and use the legacy, choose the appropriate method depending upon how you run Sanic: + +---:1 If you use the CLI... :--:1 +``` +sanic path.to:app --legacy +``` +:--- + +---:1 If you use `app.run`... :--:1 +``` +app.run(..., legacy=True) +``` +:--- + +---:1 If you `app.prepare`... :--:1 +``` +app.prepare(...) +Sanic.serve_legacy() +``` +:--- + +Similarly, you can force Sanic to run in a single process. This however means there will not be any access to the auto-reloader. + +---:1 If you use the CLI... :--:1 +``` +sanic path.to:app --single-process +``` +:--- + +---:1 If you use `app.run`... :--:1 +``` +app.run(..., single_process=True) +``` +:--- + +---:1 If you `app.prepare`... :--:1 +``` +app.prepare(...) +Sanic.serve_single() +``` +:--- +### Middleware priority + +Middleware is executed in an order based upon when it was defined. Request middleware are executed in sequence and response middleware in reverse. This could have an unfortunate impact if your ordering is strictly based upon import ordering with global variables for example. + +A new addition is to break-out of the strict construct and allow a priority to be assigned to a middleware. The higher the number for a middleware definition, the earlier in the sequence it will be executed. This applies to **both** request and response middleware. + +```python +@app.on_request +async def low_priority(_): + ... + +@app.on_request(priority=10) +async def high_priority(_): + ... +``` + +In the above example, even though `low_priority` is defined first, `high_priority` will run first. + +### Custom `loads` function + + +Sanic has supported the ability to add a [custom `dumps` function](https://sanic.readthedocs.io/en/stable/sanic/api/app.html#sanic.app.Sanic) when instantiating an app. The same functionality has been extended to `loads`, which will be used when deserializing. + +```python +from json import loads + +Sanic("Test", loads=loads) +``` + +### Websocket objects are now iterable + + +Rather than calling `recv` in a loop on a `Websocket` object, you can iterate on it in a `for` loop. + + +```python +from sanic import Request, Websocket + +@app.websocket("/ws") +async def ws_echo_handler(request: Request, ws: Websocket): + async for msg in ws: + await ws.send(msg) +``` + +### Appropriately respond with 304 on static files + +When serving a static file, the Sanic server can respond appropriately to a request with `If-Modified-Since` using a `304` response instead of resending a file. + +### Two new signals to wrap handler execution + +Two new [signals](../advanced/signals.md) have been added that wrap the execution of a request handler. + +- `http.handler.before` - runs after request middleware but before the route handler +- `http.handler.after` - runs after the route handler + - In *most* circumstances, this also means that it will run before response middleware. However, if you call `request.respond` from inside of a route handler, then your middleware will come first + +### New Request properties for HTTP method information + +The HTTP specification defines which HTTP methods are: safe, idempotent, and cacheable. New properties have been added that will respond with a boolean flag to help identify the request property based upon the method. + +```python +request.is_safe +request.is_idempotent +request.is_cacheable +``` + +### 🚨 *BREAKING CHANGE* - Improved cancel request exception + +In prior version of Sanic, if a `CancelledError` was caught it could bubble up and cause the server to respond with a `503`. This is not always the desired outcome, and it prevented the usage of that error in other circumstances. As a result, Sanic will now use a subclass of `CancelledError` called: `RequestCancelled` for this functionality. It likely should have little impact unless you were explicitly relying upon the old behavior. + + +For more details on the specifics of these properties, checkout the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods). + +### New deprecation warning filter + +You can control the level of deprecation warnings from Sanic using [standard library warning filter values](https://docs.python.org/3/library/warnings.html#the-warnings-filter). Default is `"once"`. + +```python +app.config.DEPRECATION_FILTER = "ignore" +``` + +### Deprecations and Removals + +1. *DEPRECATED* - Duplicate route names have been deprecated and will be removed in v23.3 +1. *DEPRECATED* - Registering duplicate exception handlers has been deprecated and will be removed in v23.3 +1. *REMOVED* - `route.ctx` not set by Sanic, and is a blank object for users, therefore ... + - `route.ctx.ignore_body` >> `route.extra.ignore_body` + - `route.ctx.stream` >> `route.extra.stream` + - `route.ctx.hosts` >> `route.extra.hosts` + - `route.ctx.static` >> `route.extra.static` + - `route.ctx.error_format` >> `route.extra.error_format` + - `route.ctx.websocket` >> `route.extra.websocket` +1. *REMOVED* - `app.debug` is READ-ONLY +1. *REMOVED* - `app.is_running` removed +1. *REMOVED* - `app.is_stopping` removed +1. *REMOVED* - `Sanic._uvloop_setting` removed +1. *REMOVED* - Prefixed environment variables will be ignored if not uppercase + + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@ahopkins](https://github.com/ahopkins) [@azimovMichael](https://github.com/azimovMichael) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@huntzhan](https://github.com/huntzhan) [@monosans](https://github.com/monosans) [@prryplatypus](https://github.com/prryplatypus) [@SaidBySolo](https://github.com/SaidBySolo) [@seemethere](https://github.com/seemethere) [@sjsadowski](https://github.com/sjsadowski) [@timgates42](https://github.com/timgates42) [@Tronic](https://github.com/Tronic) + + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/ko/org/README.md b/src/ko/org/README.md new file mode 100644 index 0000000000..dab306f45e --- /dev/null +++ b/src/ko/org/README.md @@ -0,0 +1 @@ +# Project diff --git a/src/ko/org/feature_requests.md b/src/ko/org/feature_requests.md new file mode 100644 index 0000000000..5c57ea1f02 --- /dev/null +++ b/src/ko/org/feature_requests.md @@ -0,0 +1,9 @@ +# Feature Requests + +[Create new feature request](https://github.com/sanic-org/sanic/issues/new?assignees=&labels=feature+request&template=feature_request.md) + +To vote on a feature request, visit the [GitHub Issues](https://github.com/sanic-org/sanic/issues?q=is%3Aissue+is%3Aopen+label%3A%22feature+request%22%2CRFC+sort%3Areactions-%2B1-desc) and add a reaction + +--- + + diff --git a/src/ko/org/policies.md b/src/ko/org/policies.md new file mode 100644 index 0000000000..d21c22806b --- /dev/null +++ b/src/ko/org/policies.md @@ -0,0 +1,64 @@ +# Policies + +## Versioning + +Sanic uses [calendar versioning](https://calver.org/), aka "calver". To be more specific, the pattern follows: + +``` +YY.MM.MICRO +``` + +Generally, versions are referred to in their `YY.MM` form. The `MICRO` number indicates an incremental patch version, starting at `0`. + +## Release Schedule + +There are four (4) scheduled releases per year: March, June, September, and December. Therefore, there are four (4) released versions per year: `YY.3`, `YY.6`, `YY.9`, and `YY.12`. + +This release schedule provides: + +- a predictable release cadence, +- relatively short development windows allowing features to be regularly released, +- controlled [deprecations](#deprecation), and +- consistent stability with a yearly LTS. + +We also use the yearly release cycle in conjunction with our governance model, covered by the [S.C.O.P.E.](./scope.md) + +### Long term support v Interim releases + +Sanic releases a long term support release (aka "LTS") once a year in December. The LTS releases receive bug fixes and security updates for **24 months**. Interim releases throughout the year occur every three months, and are supported until the subsequent release. + +| Version | LTS | Supported | +| ------- | ------------- | ------------------------- | +| 22.12 | until 2024-12 | :white_check_mark: | +| 22.9 | | :x: | +| 22.6 | | :x: | +| 22.3 | | :x: | +| 21.12 | until 2023-12 | :ballot_box_with_check: | +| 21.9 | | :x: | +| 21.6 | | :x: | +| 21.3 | | :x: | +| 20.12 | | :x: | +| 20.9 | | :x: | +| 20.6 | | :x: | +| 20.3 | | :x: | +| 19.12 | | :x: | +| 19.9 | | :x: | +| 19.6 | | :x: | +| 19.3 | | :x: | +| 18.12 | | :x: | +| 0.8.3 | | :x: | +| 0.7.0 | | :x: | +| 0.6.0 | | :x: | +| 0.5.4 | | :x: | +| 0.4.1 | | :x: | +| 0.3.1 | | :x: | +| 0.2.0 | | :x: | +| 0.1.9 | | :x: | + +:ballot_box_with_check: = security fixes :white_check_mark: = full support + +## Deprecation + +Before a feature is deprecated, or breaking changes are introduced into the API, it shall be publicized and shall appear with deprecation warnings through two release cycles. No deprecations shall be made in an LTS release. + +Breaking changes or feature removal may happen outside of these guidelines when absolutely warranted. These circumstances should be rare. For example, it might happen when no alternative is available to curtail a major security issue. diff --git a/src/ko/org/scope.md b/src/ko/org/scope.md new file mode 100644 index 0000000000..1af529361a --- /dev/null +++ b/src/ko/org/scope.md @@ -0,0 +1,264 @@ +--- +title: S.C.O.P.E +--- + + +Sanic Community Organization Policy E-manual +============================================ + +December 2019, version 1 + +Goals +----- + +To create a sustainable, community-driven organization around the Sanic projects that promote: (1) stability and predictability, (2) quick iteration and enhancement cycles, (3) engagement from outside contributors, (4) overall reliable software, and (5) a safe, rewarding environment for the community members. + +Overview +-------- + +This Policy is the governance model for the Sanic Community Organization (“SCO”). The SCO is a meritocratic, consensus-based community organization responsible for all projects adopted by it. Anyone with an interest in one of the projects can join the community, contribute to the community or projects, and participate in the decision making process. This document describes how that participation takes place and how to set about earning merit within the project community. + +Structure +--------- + +The SCO has multiple **projects**. Each project is represented by a single GitHub repository under the Sanic community umbrella. These projects are used by **users**, developed by **contributors**, governed by **core developers**, released by **release managers**, and ultimately overseen by a **steering council**. If this sounds similar to the Python project and PEP 8016 that is because it is intentionally designed that way. + +Roles and responsibilities +-------------------------- + +### Users + +Users are community members who have a need for the projects. They are the developers and personnel that download and install the packages. Users are the **most important** members of the community and without them the projects would have no purpose. Anyone can be a user and the licenses adopted by the projects shall be appropriate open source licenses. + +_The SCO asks its users to participate in the project and community as much as possible._ + +User contributions enable the project team to ensure that they are satisfying the needs of those users. Common user contributions include (but are not limited to): + +* evangelizing about the project (e.g. a link on a website and word-of-mouth awareness raising) +* informing developers of strengths and weaknesses from a new user perspective +* providing moral support (a ‘thank you’ goes a long way) +* providing financial support (the software is open source, but its developers need to eat) + +Users who continue to engage with the SCO, its projects, and its community will often become more and more involved. Such users may find themselves becoming contributors, as described in the next section. + +### Contributors + +Contributors are community members who contribute in concrete ways to one or more of the projects. Anyone can become a contributor and contributions can take many forms. Contributions and requirements are governed by each project separately by a contribution policy. + +There is **no expectation** of commitment to the project, **no specific skill requirements** and **no selection process**. + +In addition to their actions as users, contributors may also find themselves doing one or more of the following: + +* supporting new users (existing users are often the best people to support new users) +* reporting bugs +* identifying requirements +* providing graphics and web design +* Programming +* example use cases +* assisting with project infrastructure +* writing documentation +* fixing bugs +* adding features +* providing constructive opinions and engaging in community discourse + +Contributors engage with the projects through GitHub and the Community Forums. They submit changes to the projects itself via pull requests, which will be considered for inclusion in the project by the community at large. The Community Forums are the most appropriate place to ask for help when making that first contribution. + +Indeed one of the most important roles of a contributor may be to **simply engage in the community conversation**. Most decisions about the direction of a project are made by consensus. This is discussed in more detail below. In general, however, it is helpful for the health and direction of the projects for the contributors to **speak freely** (within the confines of the code of conduct) and **express their opinions and experiences** to help drive the consensus building. + +As contributors gain experience and familiarity with a project, their profile within, and commitment to, the community will increase. At some stage, they may find themselves being nominated for a core developer team. + +### Core Developer + +Each project under the SCO umbrella has its own team of core developers. They are the people in charge of that project. + +_What is a core developer?_ + +Core developers are community members who have shown that they are committed to the continued development of the project through ongoing engagement with the community. Being a core developer allows contributors to more easily carry on with their project related activities by giving them direct access to the project’s resources. They can make changes directly to the project repository without having to submit changes via pull requests from a fork. + +This does not mean that a core developer is free to do what they want. In fact, core developers have no more direct authority over the final release of a package than do contributors. While this honor does indicate a valued member of the community who has demonstrated a healthy respect for the project’s aims and objectives, their work continues to be reviewed by the community before acceptance in an official release. + +_What can a core developer do on a project?_ + +Each project might define this role slightly differently. However, the general usage of this designation is that an individual has risen to a level of trust within the community such that they now are given some control. This comes in the form of push rights to non-protected branches, and the ability to have a voice in the approval of pull requests. + +The projects employ various communication mechanisms to ensure that all contributions are reviewed by the community as a whole. This includes tools provided by GitHub, as well as the Community Forums. By the time a contributor is invited to become a core developer, they should be familiar with the various tools and workflows as a user and then as a contributor. + +_How to become a core developer?_ + +Anyone can become a core developer; there are no special requirements, other than to have shown a willingness and ability to positively participate in the project as a team player. + +Typically, a potential core developer will need to show that they have an understanding of the project, its objectives and its strategy. They will also have provided valuable contributions to the project over a period of time. However, there is **no technical or other skill** requirement for eligibility. + +New core developers can be **nominated by any existing core developer** at any time. At least twice a year (April and October) there will be a ballot process run by the Steering Council. Voting should be done by secret ballot. Each existing core developer for that project receives a number of votes equivalent to the number of nominees on the ballot. For example, if there are four nominees, then each existing core developer has four votes. The core developer may cast those votes however they choose, but may not vote for a single nominee more than once. A nominee must receive two-thirds approval from the number of cast ballots (not the number of eligible ballots). Once accepted by the core developers, it is the responsibility of the Steering Council to approve and finalize the nomination. The Steering Council does not have the right to determine whether a nominee is meritorious enough to receive the core developer title. However, they do retain the right to override a vote in cases where the health of the community would so require. + +Once the vote has been held, the aggregated voting results are published on the Community Forums. The nominee is entitled to request an explanation of any override against them. A nominee that fails to be admitted as a core developer may be nominated again in the future. + +It is important to recognize that being a core developer is a privilege, not a right. That privilege must be earned and once earned it can be removed by the Steering Council (see next section) in extreme circumstances. However, under normal circumstances the core developer title exists for as long as the individual wishes to continue engaging with the project and community. + +A committer who shows an above-average level of contribution to the project, particularly with respect to its strategic direction and long-term health, may be nominated to become a member of the Steering Council, or a Release Manager. This role is described below. + +_What are the rights and responsibilities of core developers?_ + +As discussed, the majority of decisions to be made are by consensus building. In certain circumstances where an issue has become more contentious, or a major decision needs to be made, the Release Manager or Steering Council may decide (or be required) to implement the RFC process, which is outlined in more detail below. + +It is also incumbent upon core developers to have a voice in the governance of the community. All core developers for all of the projects have the ability to be nominated to be on the Steering Council and vote in their elections. + +This Policy (the “SCOPE”) may only be changed under the authority of two-thirds of active core developers, except that in the first six (6) months after adoption, the core developers reserve the right to make changes under the authority of a simple majority of active core developers. + +_What if a core developer becomes inactive?_ + +It is hoped that all core developers participate and remain active on a regular basis in their projects. However, it is also understood that such commitments may not be realistic or possible from time to time. + +Therefore, the Steering Council has the duty to encourage participation and the responsibility to place core developers into an inactive status if they are no longer willing or capable to participate. The main purpose of this is **not to punish** a person for behavior, but to help the development process to continue for those that do remain active. + +To this end, a core developer that becomes “inactive” shall not have commit rights to a repository, and shall not participate in any votes. To be eligible to vote in an election, a core developer **must have been active** at the time of the previous scheduled project release. + +Inactive members may ask the Steering Council to reinstate their status at any time, and upon such request the Steering Council shall make the core developer active again. + +Individuals that know they will be unable to maintain their active status for a period are asked to be in communication with the Steering Council and declare themselves inactive if necessary. + +An “active” core developer is an individual that has participated in a meaningful way during the previous six months. Any further definition is within the discretion of the Steering Council. + +### Release Manager + +Core developers shall have access only to make commits and merges on non-protected branches. The “master” branch and other protected branches are controlled by the release management team for that project. Release managers shall be elected from the core development team by the core development team, and shall serve for a full release cycle. + +Each core developer team may decide how many release managers to have for each release cycle. It is highly encouraged that there be at least two release managers for a release cycle to help divide the responsibilities and not force too much effort upon a single person. However, there also should not be so many managers that their efforts are impeded. + +The main responsibilities of the release management team include: + +* push the development cycle forward by monitoring and facilitating technical discussions +* establish a release calendar and perform actions required to release packages +* approve pull requests to the master branch and other protected branches +* merge pull requests to the master branch and other protected branches + +The release managers **do not have the authority to veto or withhold a merge** of a pull request that otherwise meets contribution criteria and has been accepted by the community. It is not their responsibility to decide what should be developed, but rather that the decisions of the community are carried out and that the project is being moved forward. + +From time to time, a decision may need to be made that cannot be achieved through consensus. In that case, the release managers have the authority to call upon the removal of the decision to the RFC process. This should not occur regularly (unless required as discussed below), and its use should be discouraged in favor of the more communal consensus building strategy. + +Since not all projects have the same requirements, the specifics governing release managers on a project shall be set forth in an Appendix to this Policy, or in the project’s contribution guidelines. + +If necessary, the Steering Council has the right to remove a release manager that is derelict in their duties, or for other good cause. + +### Steering Council + +The Steering Council is the governing body consisting of those individuals identified as the “project owner” and having control of the resources and assets of the SCO. Their ultimate goal is to ensure the smooth operation of the projects by removing impediments, and assisting the members as needed. It is expected that they will be regular voices in the community. + +_What can the Steering Council do?_ + +The members of the Steering Council **do not individually have any more authority than any other core developer**, and shall not have any additional rights to make decisions, commits, merges, or the like on a project. + +However, as a body, the Steering Council has the following capacity: + +* accept, remand, and reject all RFCs +* enforce the community code of conduct +* administer community assets such as repositories, servers, forums, integration services, and the like (or, to delegate such authority to someone else) +* place core developers into inactive status where appropriate take any other enforcement measures afforded to it in this Policy, including, in extreme cases, removing core developers +* adopt or remove projects from the community umbrella + +It is highly encouraged that the Steering Council delegate its authority as much as possible, and where appropriate, to other willing community members. + +The Steering Council **does not have the authority** to change this Policy. + +_How many members are on the Steering Council?_ + +Four. + +While it seems like a committee with four votes may potentially end in a deadlock with no way to break a majority vote, the Steering Council is discouraged from voting as much as possible. Instead, it should try to work by consensus, and requires three consenting votes when it is necessary to vote on a matter. + +_How long do members serve on the Steering Council?_ + +A single term shall be for two calendar years starting in January. Terms shall be staggered so that each year there are two members continuing from the previous year’s council. + +Therefore, the inaugural vote shall have two positions available for a two year term, and two positions available for a one year term. + +There are no limits to the number of terms that can be served, and it is possible for an individual to serve consecutive terms. + +_Who runs the Steering Council?_ + +After the Steering Council is elected, the group shall collectively decide upon one person to act as the Chair. The Chair does not have any additional rights or authority over any other member of the Steering Council. + +The role of the Chair is merely as a coordinator and facilitator. The Chair is expected to ensure that all governance processes are adhered to. The position is more administrative and clerical, and is expected that the Chair sets agendas and coordinates discussion of the group. + +_How are council members elected?_ + +Once a year, **all eligible core developers** for each of the projects shall have the right to elect members to the Steering Council. + +Nominations shall be open from September 1 and shall close on September 30. After that, voting shall begin on October 1 and shall close on October 31. Every core developer active on the date of the June release of the Sanic Framework for that year shall be eligible to receive one vote per vacant seat on the Steering Council. For the sake of clarity, to be eligible to vote, a core developer **does not** need to be a core developer on Sanic Framework, but rather just have been active within their respective project on that date. + +The top recipients of votes shall be declared the winners. If there is any tie, it is highly encouraged that the tied nominees themselves resolve the dispute before a decision is made at random. + +In regards to the inaugural vote of the Steering Council, the top two vote-recipients shall serve for two years, and the next two vote-recipients shall assume the one-year seats. + +To be an eligible candidate for the Steering Council, the individual must have been a core developer in active status on at least one project for the previous twelve months. + +_What if there is a vacancy?_ + +If a vacancy on the Steering Council exists during a term, then the next highest vote-recipient in the previous election shall be offered to complete the remainder of the term. If one cannot be found this way, the Steering Council may decide the most appropriate course of action to fill the seat (whether by appointment, vote, or other means). + +If a member of the Steering Council becomes inactive, then that individual shall be removed from the Steering Council immediately and the seat shall become vacant. + +In extreme cases, the body of all core developers has the right to bring a vote to remove a member of the Steering Council for cause by a two-thirds majority of all eligible voting core developers. + +_How shall the Steering Council conduct its business?_ + +As much as possible, the Steering Council shall conduct its business and discussions in the open. Any member of the community should be allowed to enter the conversation with them. However, at times it may be necessary or appropriate for discussions to be held privately. Selecting the proper venue for conversations is part of the administrative duties of the Chair. + +While the specifics of how to operate are beyond the scope of the Policy, it is encouraged that the Steering Council attempt to meet at least one time per quarter in a “real-time” discussion. This could be achieved via video conferencing, live chatting, or other appropriate means. + +Support +------- + +All participants in the community are encouraged to provide support for users within the project management infrastructure. This support is provided as a way of growing the community. Those seeking support should recognize that all support activity within the project is voluntary and is therefore provided as and when time allows. A user requiring guaranteed response times or results should therefore seek to purchase a support contract from a community member. However, for those willing to engage with the project on its own terms, and willing to help support other users, the community support channels are ideal. + +Decision making process +----------------------- + +Decisions about the future of the projects are made through discussion with all members of the community, from the newest user to the most experienced member. Everyone has a voice. + +All non-sensitive project management discussion takes place on the community forums, or other designated channels. Occasionally, sensitive discussions may occur in private. + +In order to ensure that the project is not bogged down by endless discussion and continual voting, the project operates a policy of **lazy consensus**. This allows the majority of decisions to be made without resorting to a formal vote. For any **major decision** (as defined below), there is a separate Request for Comment (RFC) process. + +### Technical decisions + +Pull requests and technical decisions should generally fall into the following categories. + +* **Routine**: Documentation fixes, code changes that are for cleanup or additional testing. No functionality changes. +* **Minor**: Changes to the code base that either fix a bug, or introduce a trivial feature. No breaking changes. +* **Major**: Any change to the code base that breaks or deprecates existing API, alters operation in a non-trivial manner, or adds a significant feature. + +It is generally the responsibility of the release managers to make sure that changes to the repositories receive the proper authorization before merge. + +The release managers retain the authority to individually review and accept routine decisions that meet standards for code quality without additional input. + +### Lazy consensus + +Decision making (whether by the community or Steering Council) typically involves the following steps: + +* proposal +* discussion +* vote (if consensus is not reached through discussion) +* decision + +Any community member can make a proposal for consideration by the community. In order to initiate a discussion about a new idea, they should post a message on the appropriate channel on the Community forums, or submit a pull request implementing the idea on GitHub. This will prompt a review and, if necessary, a discussion of the idea. + +The goal of this review and discussion is to gain approval for the contribution. Since most people in the project community have a shared vision, there is often little need for discussion in order to reach consensus. + +In general, as long as nobody explicitly opposes a proposal or patch, it is recognized as having the support of the community. This is called lazy consensus; that is, those who have not stated their opinion explicitly have implicitly agreed to the implementation of the proposal. + +Lazy consensus is a very important concept within the SCO. It is this process that allows a large group of people to efficiently reach consensus, as someone with no objections to a proposal need not spend time stating their position, and others need not spend time reading such messages. + +For lazy consensus to be effective, it is necessary to allow an appropriate amount of time before assuming that there are no objections to the proposal. This is somewhat dependent upon the circumstances, but it is generally assumed that 72 hours is reasonable. This requirement ensures that everyone is given enough time to read, digest and respond to the proposal. This time period is chosen so as to be as inclusive as possible of all participants, regardless of their location and time commitments. The facilitators of discussion (whether it be the Chair or the Release Managers, where applicable) shall be charged with determining the proper length of time for such consensus to be reached. + +As discussed above regarding so-called routine decisions, the release managers have the right to make decisions within a shorter period of time. In such cases, lazy consensus shall be implied. + +### Request for Comment (RFC) + +The Steering Council shall be in charge of overseeing the RFC process. It shall be a process that remains open to debate to all members of the community, and shall allow for ample time to consider a proposal and for members to respond and engage in meaningful discussion. + +The final decision is vested with the Steering Council. However, it is strongly discouraged that the Steering Council adopt a decision that is contrary to any consensus that may exist in the community. From time to time this may happen if there is a conflict between consensus and the overall project and community goals. + +An RFC shall be initiated by submission to the Steering Council in the public manner as set forth by the Steering Council. Debate shall continue and be facilitated by the Steering Council in general, and the Chair specifically. + +In circumstances that the Steering Council feels it is appropriate, the RFC process may be waived in favor of lazy consensus. diff --git a/src/ko/plugins/sanic-ext/configuration.md b/src/ko/plugins/sanic-ext/configuration.md new file mode 100644 index 0000000000..fe4600d412 --- /dev/null +++ b/src/ko/plugins/sanic-ext/configuration.md @@ -0,0 +1,243 @@ +# Configuration + +Sanic Extensions can be configured in all of the same ways that [you can configure Sanic](../../guide/deployment/configuration.md). That makes configuring Sanic Extensions very easy. + +```python +app = Sanic("MyApp") +app.config.OAS_URL_PREFIX = "/apidocs" +``` + +However, there are a few more configuration options that should be considered. + +## Manual `extend` + +---:1 Even though Sanic Extensions will automatically attach to your application, you can manually choose `extend`. When you do that, you can pass all of the configuration values as a keyword arguments (lowercase). :--: +```python +app = Sanic("MyApp") +app.extend(oas_url_prefix="/apidocs") +``` +:--- + +---:1 Or, alternatively they could be passed all at once as a single `dict`. :--: +```python +app = Sanic("MyApp") +app.extend(config={"oas_url_prefix": "/apidocs"}) +``` +:--- + +---:1 Both of these solutions suffers from the fact that the names of the configuration settings are not discoverable by an IDE. Therefore, there is also a type annotated object that you can use. This should help the development experience. :--: +```python +from sanic_ext import Config + +app = Sanic("MyApp") +app.extend(config=Config(oas_url_prefix="/apidocs")) +``` +:--- + +## Settings + +### `cors` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to enable CORS protection + +### `cors_allow_headers` + +- **Type**: `str` +- **Default**: `"*"` +- **Description**: Value of the header: `access-control-allow-headers` + +### `cors_always_send` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to always send the header: `access-control-allow-origin` + +### `cors_automatic_options` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to automatically generate `OPTIONS` endpoints for routes that do *not* already have one defined + +### `cors_expose_headers` + +- **Type**: `str` +- **Default**: `""` +- **Description**: Value of the header: `access-control-expose-headers` + +### `cors_max_age` + +- **Type**: `int` +- **Default**: `5` +- **Description**: Value of the header: `access-control-max-age` + +### `cors_methods` + +- **Type**: `str` +- **Default**: `""` +- **Description**: Value of the header: `access-control-access-control-allow-methods` + +### `cors_origins` + +- **Type**: `str` +- **Default**: `""` +- **Description**: Value of the header: `access-control-allow-origin` + +::: warning Be very careful if you place `*` here. Do not do this unless you know what you are doing as it can be a security issue. ::: + +### `cors_send_wildcard` + +- **Type**: `bool` +- **Default**: `False` +- **Description**: Whether to send a wildcard origin instead of the incoming request origin + +### `cors_supports_credentials` + +- **Type**: `bool` +- **Default**: `False` +- **Description**: Value of the header: `access-control-allow-credentials` + +### `cors_vary_header` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to add the `vary` header + +### `http_all_methods` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Adds the HTTP `CONNECT` and `TRACE` methods as allowable + +### `http_auto_head` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Automatically adds `HEAD` handlers to any `GET` routes + +### `http_auto_options` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Automatically adds `OPTIONS` handlers to any routes without + +### `http_auto_trace` + +- **Type**: `bool` +- **Default**: `False` +- **Description**: Automatically adds `TRACE` handlers to any routes without + +### `oas` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to enable OpenAPI specification generation + +### `oas_autodoc` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to automatically extract OpenAPI details from the docstring of a route function + +### `oas_ignore_head` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: WHen `True`, it will not add `HEAD` endpoints into the OpenAPI specification + +### `oas_ignore_options` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: WHen `True`, it will not add `OPTIONS` endpoints into the OpenAPI specification + +### `oas_path_to_redoc_html` + +- **Type**: `Optional[str]` +- **Default**: `None` +- **Description**: Path to HTML file to override the existing Redoc HTML + +### `oas_path_to_swagger_html` + +- **Type**: `Optional[str]` +- **Default**: `None` +- **Description**: Path to HTML file to override the existing Swagger HTML + +### `oas_ui_default` + +- **Type**: `Optional[str]` +- **Default**: `"redoc"` +- **Description**: Which OAS documentation to serve on the bare `oas_url_prefix` endpoint; when `None` there will be no documentation at that location + +### `oas_ui_redoc` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to enable the Redoc UI + +### `oas_ui_swagger` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to enable the Swagger UI + +### `oas_ui_swagger_version` + +- **Type**: `str` +- **Default**: `"4.1.0"` +- **Description**: Which Swagger version to use + +### `oas_uri_to_config` + +- **Type**: `str` +- **Default**: `"/swagger-config"` +- **Description**: Path to serve the Swagger configurtaion + +### `oas_uri_to_json` + +- **Type**: `str` +- **Default**: `"/openapi.json"` +- **Description**: Path to serve the OpenAPI JSON + +### `oas_uri_to_redoc` + +- **Type**: `str` +- **Default**: `"/redoc"` +- **Description**: Path to Redoc + +### `oas_uri_to_swagger` + +- **Type**: `str` +- **Default**: `"/swagger"` +- **Description**: Path to Swagger + +### `oas_url_prefix` + +- **Type**: `str` +- **Default**: `"/docs"` +- **Description**: URL prefix for the Blueprint that all of the OAS documentation witll attach to + +### `swagger_ui_configuration` + +- **Type**: `Dict[str, Any]` +- **Default**: `{"apisSorter": "alpha", "operationsSorter": "alpha", "docExpansion": "full"}` +- **Description**: The Swagger documentation to be served to the frontend + +### `templating_enable_async` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to set `enable_async` on the Jinja `Environment` + +### `templating_path_to_templates` + +- **Type**: `Union[str, os.PathLike, Sequence[Union[str, os.PathLike]]]` +- **Default**: `templates` +- **Description**: A single path, or multiple paths to where your template files are located + +### `trace_excluded_headers` + +- **Type**: `Sequence[str]` +- **Default**: `("authorization", "cookie")` +- **Description**: Which headers should be suppresed from responses to `TRACE` requests diff --git a/src/ko/plugins/sanic-ext/convenience.md b/src/ko/plugins/sanic-ext/convenience.md new file mode 100644 index 0000000000..b53fb06b5a --- /dev/null +++ b/src/ko/plugins/sanic-ext/convenience.md @@ -0,0 +1,86 @@ +# Convenience + +## Fixed serializer + +---:1 Often when developing an application, there will be certain routes that always return the same sort of response. When this is the case, you can predefine the return serializer and on the endpoint, and then all that needs to be returned is the content. :--:1 +```python +from sanic_ext import serializer + +@app.get("/") +@serializer(text) +async def hello_world(request, name: str): + if name.isnumeric(): + return "hello " * int(name) + return f"Hello, {name}" +``` +:--- + + +---:1 The `serializer` decorator also can add status codes. :--:1 +```python +from sanic_ext import serializer + +@app.post("/") +@serializer(text, status=202) +async def create_something(request): + ... +``` +:--- + +## Custom serializer + +---:1 Using the `@serializer` decorator, you can also pass your own custom functions as long as they also return a valid type (`HTTPResonse`). :--:1 +```python +def message(retval, request, action, status): + return json( + { + "request_id": str(request.id), + "action": action, + "message": retval, + }, + status=status, + ) + + +@app.post("/") +@serializer(message) +async def do_action(request, action: str): + return "This is a message" +``` +:--- + +---:1 Now, returning just a string should return a nice serialized output. :--:1 + +```python +$ curl localhost:8000/eat_cookies -X POST +{ + "request_id": "ef81c45b-235c-46dd-9dbd-b550f8fa77f9", + "action": "eat_cookies", + "message": "This is a message" +} + +``` +:--- + + +## Request counter + +---:1 Sanic Extensions comes with a subcleass of `Request` that can be setup to automatically keep track of the number of requests processed per worker process. To enable this, you should pass the `CountedRequest` class to your application contructor. :--:1 +```python +from sanic_ext import CountedRequest + +app = Sanic(..., request_class=CountedRequest) +``` +:--- + +---:1 You will now have access to the number of requests served during the lifetime of the worker process. :--:1 +```python +@app.get("/") +async def handler(request: CountedRequest): + return json({"count": request.count}) +``` +:--- + +If possible, the request count will also be added to the [worker state](../../guide/deployment/manager.md#worker-state). + +![](https://user-images.githubusercontent.com/166269/190922460-43bd2cfc-f81a-443b-b84f-07b6ce475cbf.png) diff --git a/src/ko/plugins/sanic-ext/custom.md b/src/ko/plugins/sanic-ext/custom.md new file mode 100644 index 0000000000..e374052a3d --- /dev/null +++ b/src/ko/plugins/sanic-ext/custom.md @@ -0,0 +1,84 @@ +# Custom extensions + +It is possible to create your own custom extensions. + +Version 22.9 added the `Extend.register` [method](#extension-preregistration). This makes it extremely easy to add custom expensions to an application. + +## Anatomy of an extension + +All extensions must subclass `Extension`. + +### Required + +- `name`: By convention, the name is an all-lowercase string +- `startup`: A method that runs when the extension is added + +### Optional + +- `label`: A method that returns additional information about the extension in the MOTD +- `included`: A method that returns a boolean whether the extension should be enabled or not (could be used for example to check config state) + +### Example + +```python +from sanic import Request, Sanic, json +from sanic_ext import Extend, Extension + +app = Sanic(__name__) +app.config.MONITOR = True + + +class AutoMonitor(Extension): + name = "automonitor" + + def startup(self, bootstrap) -> None: + if self.included(): + self.app.before_server_start(self.ensure_monitor_set) + self.app.on_request(self.monitor) + + @staticmethod + async def monitor(request: Request): + if request.route and request.route.ctx.monitor: + print("....") + + @staticmethod + async def ensure_monitor_set(app: Sanic): + for route in app.router.routes: + if not hasattr(route.ctx, "monitor"): + route.ctx.monitor = False + + def label(self): + has_monitor = [ + route + for route in self.app.router.routes + if getattr(route.ctx, "monitor", None) + ] + return f"{len(has_monitor)} endpoint(s)" + + def included(self): + return self.app.config.MONITOR + + +Extend.register(AutoMonitor) + + +@app.get("/", ctx_monitor=True) +async def handler(request: Request): + return json({"foo": "bar"}) +``` + + +## Extension preregistration + +---:1 `Extend.register` simplifies the addition of custom extensions. :--:1 +```python +from sanic_ext import Extend, Extension + +class MyCustomExtension(Extension): + ... + +Extend.register(MyCustomExtension()) +``` +:--- + +*Added in v22.9* diff --git a/src/ko/plugins/sanic-ext/getting-started.md b/src/ko/plugins/sanic-ext/getting-started.md new file mode 100644 index 0000000000..958f905fb1 --- /dev/null +++ b/src/ko/plugins/sanic-ext/getting-started.md @@ -0,0 +1,74 @@ +# Getting Started + +Sanic Extensions is an *officially supported* plugin developed, and maintained by the SCO. The primary goal of this project is to add additional features to help Web API and Web application development easier. + +## Features + +- CORS protection +- Template rendering with Jinja +- Dependency injection into route handlers +- OpenAPI documentation with Redoc and/or Swagger +- Predefined, endpoint-specific response serializers +- Request query arguments and body input validation +- Auto create `HEAD`, `OPTIONS`, and `TRACE` endpoints + +## Minimum requirements + +- **Python**: 3.8+ +- **Sanic**: 21.9+ + +## Install + +The best method is to just install Sanic Extensions along with Sanic itself: + +```bash +pip install sanic[ext] +``` + +You can of course also just install it by itself. + +```bash +pip install sanic-ext +``` + +## Extend your application + +Out of the box, Sanic Extensions will enable a bunch of features for you. + +---:1 To setup Sanic Extensions (v21.12+), you need to do: **nothing**. If it is installed in the environment, it is setup and ready to go. + +This code is the Hello, world app in the [Sanic Getting Started page](../../guide/getting-started.md) _without any changes_, but using Sanic Extensions with `sanic-ext` installed in the background. :--:1 +```python +from sanic import Sanic +from sanic.response import text + +app = Sanic("MyHelloWorldApp") + +@app.get("/") +async def hello_world(request): + return text("Hello, world.") +``` + +:--- + +---:1 **_OLD DEPRECATED SETUP_** + +In v21.9, the easiest way to get started is to instantiate it with `Extend`. + +If you look back at the Hello, world app in the [Sanic Getting Started page](../../guide/getting-started.md), you will see the only additions here are the two highlighted lines. :--:1 + +```python{3,6} +from sanic import Sanic +from sanic.response import text +from sanic_ext import Extend + +app = Sanic("MyHelloWorldApp") +Extend(app) + +@app.get("/") +async def hello_world(request): + return text("Hello, world.") +``` +:--- + +Regardless of how it is setup, you should now be able to view the OpenAPI documentation and see some of the functionality in action: [http://localhost:8000/docs](http://localhost:8000/docs). diff --git a/src/ko/plugins/sanic-ext/health-monitor.md b/src/ko/plugins/sanic-ext/health-monitor.md new file mode 100644 index 0000000000..b4e2abd370 --- /dev/null +++ b/src/ko/plugins/sanic-ext/health-monitor.md @@ -0,0 +1,62 @@ +# Health monitor + +The health monitor requires both `sanic>=22.9` and `sanic-ext>=22.9`. + +You can setup Sanic Extensions to monitor the health of your worker processes. This requires that you not be in [single process mode](../../guide/deployment/manager.md#single-process-mode). + +## Setup + +---:1 Out of the box, the health monitor is disabled. You will need to opt-in if you would like to use it. :--:1 +```python +app.config.HEALTH = True +``` +:--- + +## How does it work + +The monitor sets up a new background process that will periodically receive acknowledgements of liveliness from each worker process. If a worker process misses a report too many times, then the monitor will restart that one worker. + +## Diagnostics endpoint + +---:1 The health monitor will also enable a diagnostics endpoint that outputs the [worker state](../../guide/deployment/manager.md#worker-state). By default is id disabled. + +::: warning +The diagnostics endpoint is not secured. If you are deploying it in a production environment, you should take steps to protect it with a proxy server if you are using one. If not, you may want to consider disabling this feature in production since it will leak details about your server state. +::: +:--:1 +``` +$ curl http://localhost:8000/__health__ +{ + 'Sanic-Main': {'pid': 99997}, + 'Sanic-Server-0-0': { + 'server': True, + 'state': 'ACKED', + 'pid': 9999, + 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), + 'starts': 2, + 'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc) + }, + 'Sanic-Reloader-0': { + 'server': False, + 'state': 'STARTED', + 'pid': 99998, + 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), + 'starts': 1 + } +} +``` +:--- + + +## Configuration + +| Key | Type | Default | Description | +| -------------------------- | ------ | --------------- | --------------------------------------------------------------------------- | +| HEALTH | `bool` | `False` | Whether to enable this extension. | +| HEALTH_ENDPOINT | `bool` | `False` | Whether to enable the diagnostics endpoint. | +| HEALTH_MAX_MISSES | `int` | `3` | The number of consecutive misses before a worker process is restarted. | +| HEALTH_MISSED_THRESHHOLD | `int` | `10` | The number of seconds the monitor checks for worker process health. | +| HEALTH_MONITOR | `bool` | `True` | Whether to enable the health monitor. | +| HEALTH_REPORT_INTERVAL | `int` | `5` | The number of seconds between reporting each acknowledgement of liveliness. | +| HEALTH_URI_TO_INFO | `str` | `""` | The URI path of the diagnostics endpoint. | +| HEALTH_URL_PREFIX | `str` | `"/__health__"` | The URI prefix of the diagnostics blueprint. | diff --git a/src/ko/plugins/sanic-ext/http/cors.md b/src/ko/plugins/sanic-ext/http/cors.md new file mode 100644 index 0000000000..1149c04d0e --- /dev/null +++ b/src/ko/plugins/sanic-ext/http/cors.md @@ -0,0 +1,86 @@ +# CORS protection + +Cross-Origin Resource Sharing (aka CORS) is a *huge* topic by itself. The documentation here cannot go into enough detail about *what* it is. You are highly encouraged to do some research on your own to understand the security problem presented by it, and the theory behind the solutions. [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) are a great first step. + +In super brief terms, CORS protection is a framework that browsers use to facilitate how and when a web page can access information from another domain. It is extremely relevant to anyone building a single-page application. Often times your frontend might be on a domain like `https://portal.myapp.com`, but it needs to access the backend from `https://api.myapp.com`. + +The implementation here is heavily inspired by [`sanic-cors`](https://github.com/ashleysommer/sanic-cors), which is in turn based upon [`flask-cors`](https://github.com/corydolphin/flask-cors). It is therefore very likely that you can achieve a near drop-in replacement of `sanic-cors` with `sanic-ext`. + +## Basic implementation + +---:1 + +As shown in the example in the [auto-endpoints example](methods.md#options), Sanic Extensions will automatically enable CORS protection without further action. But, it does not offer too much out of the box. + +At a *bare minimum*, it is **highly** recommended that you set `config.CORS_ORIGINS` to the intended origin(s) that will be accessing the application. + +:--:1 +```python +from sanic import Sanic, text +from sanic_ext import Extend + +app = Sanic(__name__) +app.config.CORS_ORIGINS = "http://foobar.com,http://bar.com" +Extend(app) + +@app.get("/") +async def hello_world(request): + return text("Hello, world.") +``` + +``` +$ curl localhost:8000 -X OPTIONS -i +HTTP/1.1 204 No Content +allow: GET,HEAD,OPTIONS +access-control-allow-origin: http://foobar.com +connection: keep-alive +``` +:--- + +## Configuration + +The true power of CORS protection, however, comes into play once you start configuring it. Here is a table of all of the options. + +| Key | Type | Default | Description | +| --------------------------- | -------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `CORS_ALLOW_HEADERS` | `str` or `List[str]` | `"*"` | The list of headers that will appear in `access-control-allow-headers`. | +| `CORS_ALWAYS_SEND` | `bool` | `True` | When `True`, will always set a value for `access-control-allow-origin`. When `False`, will only set it if there is an `Origin` header. | +| `CORS_AUTOMATIC_OPTIONS` | `bool` | `True` | When the incoming preflight request is received, whether to automatically set values for `access-control-allow-headers`, `access-control-max-age`, and `access-control-allow-methods` headers. If `False` these values will only be set on routes that are decorated with the `@cors` decorator. | +| `CORS_EXPOSE_HEADERS` | `str` or `List[str]` | `""` | Specific list of headers to be set in `access-control-expose-headers` header. | +| `CORS_MAX_AGE` | `str`, `int`, `timedelta` | `0` | The maximum number of seconds the preflight response may be cached using the `access-control-max-age` header. A falsey value will cause the header to not be set. | +| `CORS_METHODS` | `str` or `List[str]` | `""` | The HTTP methods that the allowed origins can access, as set on the `access-control-allow-methods` header. | +| `CORS_ORIGINS` | `str`, `List[str]`, `re.Pattern` | `"*"` | The origins that are allowed to access the resource, as set on the `access-control-allow-origin` header. | +| `CORS_SEND_WILDCARD` | `bool` | `False` | If `True`, will send the wildcard `*` origin instead of the `origin` request header. | +| `CORS_SUPPORTS_CREDENTIALS` | `bool` | `False` | Whether to set the `access-control-allow-credentials` header. | +| `CORS_VARY_HEADER` | `bool` | `True` | Whether to add `vary` header, when appropriate. | + +*For the sake of brevity, where the above says `List[str]` any instance of a `list`, `set`, `frozenset`, or `tuple` will be acceptable. Alternatively, if the value is a `str`, it can be a comma delimited list.* + +## Route level overrides + +---:1 + +It may sometimes be necessary to override app-wide settings for a specific route. To allow for this, you can use the `@sanic_ext.cors()` decorator to set different route-specific values. + +The values that can be overridden with this decorator are: + +- `origins` +- `expose_headers` +- `allow_headers` +- `allow_methods` +- `supports_credentials` +- `max_age` + +:--:1 +```python +from sanic_ext import cors + +app.config.CORS_ORIGINS = "https://foo.com" + + +@app.get("/", host="bar.com") +@cors(origins="https://bar.com") +async def hello_world(request): + return text("Hello, world.") +``` +:--- diff --git a/src/ko/plugins/sanic-ext/http/methods.md b/src/ko/plugins/sanic-ext/http/methods.md new file mode 100644 index 0000000000..59cfdcd8d2 --- /dev/null +++ b/src/ko/plugins/sanic-ext/http/methods.md @@ -0,0 +1,125 @@ +# HTTP Methods + +## Auto-endpoints + +The default behavior is to automatically generate `HEAD` endpoints for all `GET` routes, and `OPTIONS` endpoints for all routes. Additionally, there is the option to automatically generate `TRACE` endpoints. However, these are not enabled by default. + +::::tabs + +:::tab HEAD + +- **Configuration**: `AUTO_HEAD` (default `True`) +- **MDN**: [Read more](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD) + +A `HEAD` request provides the headers and an otherwise identical response to what a `GET` request would provide. However, it does not actually return the body. + +```python +@app.get("/") +async def hello_world(request): + return text("Hello, world.") +``` + +Given the above route definition, Sanic Extensions will enable `HEAD` responses, as seen here. + +``` +$ curl localhost:8000 --head +HTTP/1.1 200 OK +access-control-allow-origin: * +content-length: 13 +connection: keep-alive +content-type: text/plain; charset=utf-8 +``` + +::: + +:::tab OPTIONS + +- **Configuration**: `AUTO_OPTIONS` (default `True`) +- **MDN**: [Read more](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS) + +`OPTIONS` requests provide the recipient with details about how the client is allowed to communicate with a given endpoint. + +```python +@app.get("/") +async def hello_world(request): + return text("Hello, world.") +``` + +Given the above route definition, Sanic Extensions will enable `OPTIONS` responses, as seen here. + +It is important to note that we also see `access-control-allow-origins` in this example. This is because the [CORS protection](cors.md) is enabled by default. + +``` +$ curl localhost:8000 -X OPTIONS -i +HTTP/1.1 204 No Content +allow: GET,HEAD,OPTIONS +access-control-allow-origin: * +connection: keep-alive +``` + +::: tip Even though Sanic Extensions will setup these routes for you automatically, if you decide to manually create an `@app.options` route, it will *not* be overridden. ::: + +:::tab TRACE + +- **Configuration**: `AUTO_TRACE` (default `False`) +- **MDN**: [Read more](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/TRACE) + +By default, `TRACE` endpoints will **not** be automatically created. However, Sanic Extensions **will allow** you to create them if you wanted. This is something that is not allowed in vanilla Sanic. + +```python +@app.route("/", methods=["trace"]) +async def handler(request): + ... +``` + +To enable auto-creation of these endpoints, you must first enable them when extending Sanic. + +```python +from sanic_ext import Extend, Config + +app.extend(config=Config(http_auto_trace=True)) +``` + +Now, assuming you have some endpoints setup, you can trace them as shown here: + +``` +$ curl localhost:8000 -X TRACE +TRACE / HTTP/1.1 +Host: localhost:9999 +User-Agent: curl/7.76.1 +Accept: */* +``` + +::: tip Setting up `AUTO_TRACE` can be super helpful, especially when your application is deployed behind a proxy since it will help you determine how the proxy is behaving. ::: + +:::: + +## Additional method support + +Vanilla Sanic allows you to build endpoints with the following HTTP methods: + +- [GET](/en/guide/basics/routing.html#get) +- [POST](/en/guide/basics/routing.html#post) +- [PUT](/en/guide/basics/routing.html#put) +- [HEAD](/en/guide/basics/routing.html#head) +- [OPTIONS](/en/guide/basics/routing.html#options) +- [PATCH](/en/guide/basics/routing.html#patch) +- [DELETE](/en/guide/basics/routing.html#delete) + +See [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) for more. + +---:1 + +There are, however, two more "standard" HTTP methods: `TRACE` and `CONNECT`. Sanic Extensions will allow you to build endpoints using these methods, which would otherwise not be allowed. + +It is worth pointing out that this will *NOT* enable convenience methods: `@app.trace` or `@app.connect`. You need to use `@app.route` as shown in the example here. + +:--:1 + +```python +@app.route("/", methods=["trace", "connect"]) +async def handler(_): + return empty() +``` + +:--- diff --git a/src/ko/plugins/sanic-ext/injection.md b/src/ko/plugins/sanic-ext/injection.md new file mode 100644 index 0000000000..0b3d073601 --- /dev/null +++ b/src/ko/plugins/sanic-ext/injection.md @@ -0,0 +1,329 @@ +# Dependency Injection + +Dependency injection is a method to add arguments to a route handler based upon the defined function signature. Specifically, it looks at the **type annotations** of the arguments in the handler. This can be useful in a number of cases like: + +- Fetching an object based upon request headers (like the current session user) +- Recasting certain objects into a specific type +- Using the request object to prefetch data +- Auto inject services + +The `Extend` instance has two basic methods on it used for dependency injection: a lower level `add_dependency`, and a higher level `dependency`. + +**Lower level**: `app.ext.add_dependency(...)` + +- `type: Type,`: some unique class that will be the type of the oject +- `constructor: Optional[Callable[..., Any]],` (OPTIONAL): a function that will return that type + +**Higher level**: `app.ext.dependency(...)` + +- `obj: Any`: any object that you would like injected +- `name: Optional[str]`: some name that could alternately be used as a reference + +Let's explore some use cases here. + +::: warning If you used dependency injection prior to v21.12, the lower level API method was called `injection`. It has since been renamed to `add_dependency` and starting in v21.12 `injection` is an alias for `add_dependency`. The `injection` method has been deprecated for removal in v22.6. ::: + +## Basic implementation + +The simplest use case would be simply to recast a value. + +---:1 This could be useful if you have a model that you want to generate based upon the matched path parameters. :--:1 +```python +@dataclass +class IceCream: + flavor: str + + def __str__(self) -> str: + return f"{self.flavor.title()} (Yum!)" + + +app.ext.add_dependency(IceCream) + + +@app.get("/") +async def ice_cream(request, flavor: IceCream): + return text(f"You chose: {flavor}") +``` + +``` +$ curl localhost:8000/chocolate +You chose Chocolate (Yum!) +``` +:--- + +---:1 This works by passing a keyword argument to the constructor of the `type` argument. The previous example is equivalent to this. :--:1 +```python +flavor = IceCream(flavor="chocolate") +``` +:--- + +## Additional constructors + +---:1 Sometimes you may need to also pass a constructor. This could be a function, or perhaps even a classmethod that acts as a constructor. In this example, we are creating an injection that will call `Person.create` first. + +Also important to note on this example, we are actually injecting **two (2)** objects! It of course does not need to be this way, but we will inject objects based upon the function signature. :--:1 +```python +@dataclass +class PersonID: + person_id: int + + +@dataclass +class Person: + person_id: PersonID + name: str + age: int + + @classmethod + async def create(cls, request: Request, person_id: int): + return cls(person_id=PersonID(person_id), name="noname", age=111) + + + +app.ext.add_dependency(Person, Person.create) +app.ext.add_dependency(PersonID) + +@app.get("/person/") +async def person_details( + request: Request, person_id: PersonID, person: Person +): + return text(f"{person_id}\n{person}") +``` + +``` +$ curl localhost:8000/person/123 +PersonID(person_id=123) +Person(person_id=PersonID(person_id=123), name='noname', age=111) +``` +:--- + +When a `constructor` is passed to `ext.add_dependency` (like in this example) that will be called. If not, then the object will be created by calling the `type`. A couple of important things to note about passing a `constructor`: + +1. A positional `request: Request` argument is *usually* expected. See the `Person.create` method above as an example using a `request` and [arbitrary constructors](#arbitrary-constructors) for how to use a callable that does not require a `request`. +1. All matched path parameters are injected as keyword arguments. +1. Dependencies can be chained and nested. Notice how in the previous example the `Person` dataclass has a `PersonID`? That means that `PersonID` will be called first, and that value is added to the keyword arguments when calling `Person.create`. + +## Arbitrary constructors + +---:1 Sometimes you may want to construct your injectable _without_ the `Request` object. This is useful if you have arbitrary classes or functions that create your objects. If the callable does have any required arguments, then they should themselves be injectable objects. + +This is very useful if you have services or other types of objects that should only exist for the lifetime of a single request. For example, you might use this pattern to pull a single connection from your database pool. :--:1 +```python +class Alpha: + ... + + +class Beta: + def __init__(self, alpha: Alpha) -> None: + self.alpha = alpha + +app.ext.add_dependency(Alpha) +app.ext.add_dependency(Beta) + +@app.get("/beta") +async def handler(request: Request, beta: Beta): + assert isinstance(beta.alpha, Alpha) +``` +:--- + +*Added in v22.9* + +## Objects from the `Request` + +---:1 Sometimes you may want to extract details from the request and preprocess them. You could, for example, cast the request JSON to a Python object, and then add some additional logic based upon DB queries. + +::: warning If you plan to use this method, you should note that the injection actually happens *before* Sanic has had a chance to read the request body. The headers should already have been consumed. So, if you do want access to the body, you will need to manually consume as seen in this example. + +```python +await request.receive_body() +``` +::: + +This could be used in cases where you otherwise might: + +- use middleware to preprocess and add something to the `request.ctx` +- use decorators to preprocess and inject arguments into the request handler + +In this example, we are using the `Request` object in the `compule_profile` constructor to run a fake DB query to generate and return a `UserProfile` object. :--:1 +```python +@dataclass +class User: + name: str + + +@dataclass +class UserProfile: + user: User + age: int = field(default=0) + email: str = field(default="") + + def __json__(self): + return ujson.dumps( + { + "name": self.user.name, + "age": self.age, + "email": self.email, + } + ) + + +async def fake_request_to_db(body): + today = date.today() + email = f'{body["name"]}@something.com'.lower() + difference = today - date.fromisoformat(body["birthday"]) + age = int(difference.days / 365) + return UserProfile( + User(body["name"]), + age=age, + email=email, + ) + + +async def compile_profile(request: Request): + await request.receive_body() + profile = await fake_request_to_db(request.json) + return profile + + +app.ext.add_dependency(UserProfile, compile_profile) + + +@app.patch("/profile") +async def update_profile(request, profile: UserProfile): + return json(profile) +``` + +``` +$ curl localhost:8000/profile -X PATCH -d '{"name": "Alice", "birthday": "2000-01-01"}' +{ + "name":"Alice", + "age":21, + "email":"alice@something.com" +} +``` +:--- + +## Injecting services + +It is a common pattern to create things like database connection pools and store them on the `app.ctx` object. This makes them available throughout your application, which is certainly a convenience. One downside, however, is that you no longer have a typed object to work with. You can use dependency injections to fix this. First we will show the concept using the lower level `add_dependency` like we have been using in the previous examples. But, there is a better way using the higher level `dependency` method. + +---:1 +### The lower level API using `add_dependency` + +This works very similar to the [last example](#objects-from-the-request) where the goal is the extract something from the `Request` object. In this example, a database object was created on the `app.ctx` instance, and is being returned in the dependency injection constructor. :--:1 +```python +class FakeConnection: + async def execute(self, query: str, **arguments): + return "result" + + +@app.before_server_start +async def setup_db(app, _): + app.ctx.db_conn = FakeConnection() + app.ext.add_dependency(FakeConnection, get_db) + + +def get_db(request: Request): + return request.app.ctx.db_conn + + + + +@app.get("/") +async def handler(request, conn: FakeConnection): + response = await conn.execute("...") + return text(response) +``` +``` +$ curl localhost:8000/ +result +``` +:--- + +---:1 +### The higher level API using `dependency` + +Since we have an actual *object* that is available when adding the dependency injection, we can use the higher level `dependency` method. This will make the pattern much easier to write. + +This method should always be used when you want to inject something that exists throughout the lifetime of the application instance and is not request specific. It is very useful for services, third party clients, and connection pools since they are not request specific. :--:1 +```python +class FakeConnection: + async def execute(self, query: str, **arguments): + return "result" + + +@app.before_server_start +async def setup_db(app, _): + db_conn = FakeConnection() + app.ext.dependency(db_conn) + + +@app.get("/") +async def handler(request, conn: FakeConnection): + response = await conn.execute("...") + return text(response) +``` +``` +$ curl localhost:8000/ +result +``` +:--- + +## Generic types + +Be carefule when using a [generic type](https://docs.python.org/3/library/typing.html#typing.Generic). The way that Sanic's dependency injection works is by matching the entire type definition. Therefore, `Foo` is not the same as `Foo[str]`. This can be particularly tricky when trying to use the [higher-level `dependency` method](#the-higher-level-api-using-dependency) since the type is inferred. + +---:1 For example, this will **NOT** work as expected since there is no definition for `Test[str]`. :--:1 +```python{12,16} +import typing +from sanic import Sanic, text + +T = typing.TypeVar("T") + + +class Test(typing.Generic[T]): + test: T + + +app = Sanic("testapp") +app.ext.dependency(Test()) + + +@app.get("/") +def test(request, test: Test[str]): + ... +``` +:--- + +---:1 To get this example to work, you will need to add an explicit definition for the type you intend to be injected. :--:1 +```python{13} +import typing +from sanic import Sanic, text + +T = typing.TypeVar("T") + + +class Test(typing.Generic[T]): + test: T + + +app = Sanic("testapp") +_singleton = Test() +app.ext.add_dependency(Test[str], lambda: _singleton) + + +@app.get("/") +def test(request, test: Test[str]): + ... +``` +:--- + +## Configuration + +---:1 By default, dependencies will be injected after the `http.routing.after` [signal](../../guide/advanced/signals.md#built-in-signals). Starting in v22.9, you can change this to the `http.handler.before` signal. :--:1 +```python +app.config.INJECTION_SIGNAL = "http.handler.before" +``` +:--- + +*Added in v22.9* diff --git a/src/ko/plugins/sanic-ext/logger.md b/src/ko/plugins/sanic-ext/logger.md new file mode 100644 index 0000000000..aa8b18c141 --- /dev/null +++ b/src/ko/plugins/sanic-ext/logger.md @@ -0,0 +1,26 @@ +# Background logger + +The background logger requires both `sanic>=22.9` and `sanic-ext>=22.9`. + +You can setup Sanic Extensions to log all of your messages from a background process. This requires that you not be in [single process mode](../../guide/deployment/manager.md#single-process-mode). + +Logging can sometimes be an expensive operation. By pushing all logging off to a background process, you can potentially gain some performance benefits. + +## Setup + +---:1 Out of the box, the background logger is disabled. You will need to opt-in if you would like to use it. :--:1 +```python +app.config.LOGGING = True +``` +:--- + +## How does it work + +When enabled, the extension will create a `multoprocessing.Queue`. It will remove all handlers on the [default Sanic loggers](../../guide/best-practices/logging.md) and replace them with a [`QueueHandler`](https://docs.python.org/3/library/logging.handlers.html#queuehandler). When a message is logged, it will be pushed into the queue by the handler, and read by the background process to the log handlers that were originally in place. This means you can still configure logging as normal and it should "just work." + +## Configuration + +| Key | Type | Default | Description | +| ------------------------ | ------ | ------- | ------------------------------------------------------- | +| LOGGING | `bool` | `False` | Whether to enable this extension. | +| LOGGING_QUEUE_MAX_SIZE | `int` | `4096` | The max size of the queue before messages are rejected. | diff --git a/src/ko/plugins/sanic-ext/openapi.md b/src/ko/plugins/sanic-ext/openapi.md new file mode 100644 index 0000000000..7e3314aa8c --- /dev/null +++ b/src/ko/plugins/sanic-ext/openapi.md @@ -0,0 +1,7 @@ +# Openapi + +- Adding documentation with decorators +- Documenting CBV +- Using autodoc +- Rendering docs with redoc/swagger +- Validation diff --git a/src/ko/plugins/sanic-ext/openapi/advanced.md b/src/ko/plugins/sanic-ext/openapi/advanced.md new file mode 100644 index 0000000000..46c3c45ec6 --- /dev/null +++ b/src/ko/plugins/sanic-ext/openapi/advanced.md @@ -0,0 +1,10 @@ +# Advanced + +_Documentation coming EOQ1 2023_ + +## CBV + +## Blueprints + + +## Components diff --git a/src/ko/plugins/sanic-ext/openapi/autodoc.md b/src/ko/plugins/sanic-ext/openapi/autodoc.md new file mode 100644 index 0000000000..a382e51329 --- /dev/null +++ b/src/ko/plugins/sanic-ext/openapi/autodoc.md @@ -0,0 +1,125 @@ +# Auto-documentation + +To make documenting endpoints easier, Sanic Extensions will use a function's docstring to populate your documentation. + +## Summary and description + +---:1 A function's docstring will be used to create the summary and description. As you can see from this example here, the docstring has been parsed to use the first line as the summary, and the remainder of the string as the description. :--:1 +```python +@app.get("/foo") +async def handler(request, something: str): + """This is a simple foo handler + + It is helpful to know that you could also use **markdown** inside your + docstrings. + + - one + - two + - three""" + return text(">>>") +``` +```json +"paths": { + "/foo": { + "get": { + "summary": "This is a simple foo handler", + "description": "It is helpful to know that you could also use **markdown** inside your
docstrings.

- one
- two
- three", + "responses": { + "default": { + "description": "OK" + } + }, + "operationId": "get_handler" + } + } +} +``` +:--- + +## Operation level YAML + +---:1 You can expand upon this by adding valid OpenAPI YAML to the docstring. Simply add a line that contains `openapi:`, followed by your YAML. + +The `---` shown in the example is *not* necessary. It is just there to help visually identify the YAML as a distinct section of the docstring. :--:1 +```python +@app.get("/foo") +async def handler(request, something: str): + """This is a simple foo handler + + Now we will add some more details + + openapi: + --- + operationId: fooDots + tags: + - one + - two + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: Just some dots + """ + return text("...") +``` +```json +"paths": { + "/foo": { + "get": { + "operationId": "fooDots", + "summary": "This is a simple foo handler", + "description": "Now we will add some more details", + "tags": [ + "one", + "two" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "How many items to return at one time (max 100)", + "required": false, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Just some dots" + } + } + } + } +} +``` + +:--- + +::: tip +When both YAML documentation and decorators are used, it is the content from the decorators that will take priority when generating the documentation. +::: + +## Excluding docstrings + +---:1 Sometimes a function may contain a docstring that is not meant to be consumed inside the documentation. + +**Option 1**: Globally turn off auto-documentation `app.config.OAS_AUTODOC = False` + +**Option 2**: Disable it for the single handler with the `@openapi.no_autodoc` decorator :--:1 +```python +@app.get("/foo") +@openapi.no_autodoc +async def handler(request, something: str): + """This is a docstring about internal info only. Do not parse it. + """ + return text("...") +``` +:--- diff --git a/src/ko/plugins/sanic-ext/openapi/basic.md b/src/ko/plugins/sanic-ext/openapi/basic.md new file mode 100644 index 0000000000..d820b8c7c7 --- /dev/null +++ b/src/ko/plugins/sanic-ext/openapi/basic.md @@ -0,0 +1,66 @@ +# Basics + +::: tip Side note The OpenAPI implementation in Sanic Extensions is based upon the OAS3 implementation from [`sanic-openapi`](https://github.com/sanic-org/sanic-openapi). In fact, Sanic Extensions is in a large way the successor to that project, which entered maintenance mode upon the release of Sanic Extensions. If you were previously using OAS3 with `sanic-openapi` you should have an easy path to upgrading to Sanic Extensions. Unfortunately, this project does *NOT* support the OAS2 specification. ::: + +---:1 + +Out of the box, Sanic Extensions provides automatically generated API documentation using the [v3.0 OpenAPI specification](https://swagger.io/specification/). There is nothing special that you need to do + +:--:1 + +```python +from sanic import Sanic + +app = Sanic("MyApp") + +# Add all of your views +``` + +:--- + +After doing this, you will now have beautiful documentation already generated for you based upon your existing application: + +- [http://localhost:8000/docs](http://localhost:8000/docs) +- [http://localhost:8000/docs/redoc](http://localhost:8000/docs/redoc) +- [http://localhost:8000/docs/swagger](http://localhost:8000/docs/swagger) + +Checkout the [section on configuration](../configuration.md) to learn about changing the routes for the docs. You can also turn off one of the two UIs, and customize which UI will be available on the `/docs` route. + +---:1 + +Using [Redoc](https://github.com/Redocly/redoc) + +![Redoc](~@assets/images/sanic-ext-redoc.png) + + +:--:1 + +or [Swagger UI](https://github.com/swagger-api/swagger-ui) + +![Swagger UI](~@assets/images/sanic-ext-swagger.png) + + +:--- + +## Changing specification metadata + +---:1 If you want to change any of the metada, you should use the `describe` method. + +In this example `dedent` is being used with the `description` argument to make multi-line strings a little cleaner. This is not necessary, you can pass any string value here. :--:1 +```python +from textwrap import dedent + +app.ext.openapi.describe( + "Testing API", + version="1.2.3", + description=dedent( + """ + # Info + This is a description. It is a good place to add some _extra_ doccumentation. + + **MARKDOWN** is supported. + """ + ), +) +``` +:--- diff --git a/src/ko/plugins/sanic-ext/openapi/decorators.md b/src/ko/plugins/sanic-ext/openapi/decorators.md new file mode 100644 index 0000000000..e4a04f15c2 --- /dev/null +++ b/src/ko/plugins/sanic-ext/openapi/decorators.md @@ -0,0 +1,479 @@ +# Decorators + +The primary mechanism for adding content to your schema is by decorating your endpoints. If you have used `sanic-openapi` in the past, this should be familiar to you. The decorators and their arguments match closely the [OAS v3.0 specification](https://swagger.io/specification/). + +---:1 + +All of the examples show will wrap around a route definition. When you are creating these, you should make sure that your Sanic route decorator (`@app.route`, `@app.get`, etc) is the outermost decorator. That is to say that you should put that first and then one or more of the below decorators after. + +:--:1 + +```python +from sanic_ext import openapi + + +@app.get("/path/to/") +@openapi.summary("This is a summary") +@openapi.description("This is a description") +async def handler(request, somethind: str): + ... +``` + +:--- + +---:1 + +You will also see a lot of the below examples reference a model object. For the sake of simplicity, the examples will use `UserProfile` that will look like this. The point is that it can be any well-typed class. You could easily imagine this being a `dataclass` or some other kind of model object. + +:--:1 + +```python +class UserProfile: + name: str + age: int + email: str +``` + +:--- + +## Definition decorator + +### `@openapi.definition` + +The `@openapi.definition` decorator allows you to define all parts of an operations on a path at once. It is an omnibums decorator in that it has the same capabilities to create operation definitions as the rest of the decorators. Using multiple field-specific decorators or a single decorator is a style choice for you the developer. + +The fields are purposely permissive in accepting multiple types to make it easiest for you to define your operation. + +**Arguments** + +| Field | Type | +| ------------- | ---------------------------------------------------- | +| `body` | ***dict, RequestBody, ***YourModel****** | +| `deprecated` | **bool** | +| `description` | **str** | +| `document` | **str, ExternalDocumentation** | +| `exclude` | **bool** | +| `operation` | **str** | +| `parameter` | **str, dict, Parameter, [str], [dict], [Parameter]** | +| `response` | **dict, Response, *YourModel*, [dict], [Response]** | +| `summary` | **str** | +| `tag` | **str, Tag, [str], [Tag]** | +| `secured` | **Dict[str, Any]** | + +**Examples** + +---:1 + +```python +@openapi.definition( + body=RequestBody(UserProfile, required=True), + summary="User profile update", + tag="one", + response=[Success, Response(Failure, status=400)], +) +``` + +:--:1 + +:--- + +*See below examples for more examples. Any of the values for the below decorators can be used in the corresponding keyword argument.* + +## Field-specific decorators + +All the following decorators are based on `@openapi` + +::::tabs + +:::tab body + +**Arguments** + +| Field | Type | +| ----------- | ---------------------------------- | +| **content** | ***YourModel*, dict, RequestBody** | + +**Examples** + +---:1 + +```python +@openapi.body(UserProfile) +``` + +```python +@openapi.body({"application/json": UserProfile}) +``` + +```python +@openapi.body(RequestBody({"application/json": UserProfile})) +``` + +:--:1 + +```python +@openapi.body({"content": UserProfile}) +``` + +```python +@openapi.body(RequestBody(UserProfile)) +``` + +```python +@openapi.body({"application/json": {"description": ...}}) +``` + +:--- + +::: + +:::tab deprecated + +**Arguments** + +*None* + +**Examples** + +---:1 + +```python +@openapi.deprecated() +``` + +:--:1 + +```python +@openapi.deprecated +``` + +:--- + +::: + +:::tab description + +**Arguments** + +| Field | Type | +| ------ | ------- | +| `text` | **str** | + +**Examples** + +---:1 + +```python +@openapi.description( + """This is a **description**. + +## You can use `markdown` + +- And +- make +- lists. +""" +) +``` + +:--:1 + +:--- + +::: + +:::tab document + +**Arguments** + +| Field | Type | +| ------------- | ------- | +| `url` | **str** | +| `description` | **str** | + +**Examples** + +---:1 + +```python +@openapi.document("http://example.com/docs") +``` + +:--:1 + +```python +@openapi.document(ExternalDocumentation("http://example.com/more")) +``` + +:--- + +::: + +:::tab exclude + +Can be used on route definitions like all of the other decorators, or can be called on a Blueprint + +**Arguments** + +| Field | Type | Default | +| ------ | ------------- | -------- | +| `flag` | **bool** | **True** | +| `bp` | **Blueprint** | | + +**Examples** + +---:1 + +```python +@openapi.exclude() +``` + +:--:1 + +```python +openapi.exclude(bp=some_blueprint) +``` + +:--- + +::: + +:::tab operation + +Sets the operation ID. + +**Arguments** + +| Field | Type | +| ------ | ------- | +| `name` | **str** | + +**Examples** + +---:1 + +```python +@openapi.operation("doNothing") +``` + +:--:1 + +:--- + +::: + +:::tab parameter + +**Arguments** + +| Field | Type | Default | +| ---------- | ----------------------------------------- | ----------- | +| `name` | **str** | | +| `schema` | ***type*** | **str** | +| `location` | **"query", "header", "path" or "cookie"** | **"query"** | + +**Examples** + +---:1 + +```python +@openapi.parameter("thing") +``` + +```python +@openapi.parameter(parameter=Parameter("foobar", deprecated=True)) +``` + +:--:1 + +```python +@openapi.parameter("Authorization", str, "header") +``` + +```python +@openapi.parameter("thing", required=True, allowEmptyValue=False) +``` + +:--- + +::: + +:::tab response + +**Arguments** + +If using a `Response` object, you should not pass any other arguments. + +| Field | Type | +| ------------- | ----------------------------- | +| `status` | **int** | +| `content` | ***type*, *YourModel*, dict** | +| `description` | **str** | +| `response` | **Response** | + +**Examples** + +---:1 + +```python +@openapi.response(200, str, "This is endpoint returns a string") +``` + +```python +@openapi.response(200, {"text/plain": str}, "...") +``` + +```python +@openapi.response(response=Response(UserProfile, description="...")) +``` + +```python +@openapi.response( + response=Response( + { + "application/json": UserProfile, + }, + description="...", + status=201, + ) +) +``` + +:--:1 + +```python +@openapi.response(200, UserProfile, "...") +``` + +```python +@openapi.response( + 200, + { + "application/json": UserProfile, + }, + "Description...", +) +``` + +:--- + +::: + +:::tab summary + +**Arguments** + +| Field | Type | +| ------ | ------- | +| `text` | **str** | + +**Examples** + +---:1 + +```python +@openapi.summary("This is an endpoint") +``` + +:--:1 + +:--- + +::: + +:::tab tag + +**Arguments** + +| Field | Type | +| ------- | ------------ | +| `*args` | **str, Tag** | + +**Examples** + +---:1 + +```python +@openapi.tag("foo") +``` + +:--:1 + +```python +@openapi.tag("foo", Tag("bar")) +``` + +:--- + +::: + +:::tab secured + +**Arguments** + +| Field | Type | +| ----------------- | ----------------------- | +| `*args, **kwargs` | **str, Dict[str, Any]** | + +**Examples** + +---:1 +```python +@openapi.secured() +``` +:--:1 :--- + +---:1 +```python +@openapi.secured("foo") +``` +:--:1 +```python +@openapi.secured("token1", "token2") +``` +:--- + +---:1 +```python +@openapi.secured({"my_api_key": []}) +``` +:--:1 +```python +@openapi.secured(my_api_key=[]) +``` +:--- + +Do not forget to use `add_security_scheme`. See [security](./security.md) for more details. + +::: + +:::: + +## Integration with Pydantic + +Pydantic models have the ability to [generate OpenAPI schema](https://pydantic-docs.helpmanual.io/usage/schema/). + +---:1 To take advantage of Pydantic model schema generation, pass the output in place of the schema. :--:1 +```python +from sanic import Sanic, json +from sanic_ext import validate, openapi +from pydantic import BaseModel, Field + +class Test(BaseModel): + foo: str = Field(description="Foo Description", example="FOOO") + bar: str = "test" + + +app = Sanic("test") + +@app.get("/") +@openapi.definition( + body={'application/json': Test.schema()}, +) +@validate(json=Test) +async def get(request): + return json({}) +``` +:--- + +*Added in v22.9* diff --git a/src/ko/plugins/sanic-ext/openapi/security.md b/src/ko/plugins/sanic-ext/openapi/security.md new file mode 100644 index 0000000000..b73b15207e --- /dev/null +++ b/src/ko/plugins/sanic-ext/openapi/security.md @@ -0,0 +1,86 @@ +# Security Schemes + +To document authentication schemes, there are two steps. + +_Security is only available starting in v21.12.2_ + +## Document the scheme + +---:1 The first thing that you need to do is define one or more security schemes. The basic pattern will be to define it as: + +```python +add_security_scheme("", "") +``` + +The `type` should correspond to one of the allowed security schemes: `"apiKey"`, `"http"`, `"oauth2"`, `"openIdConnect"`. You can then pass appropriate keyword arguments as allowed by the specification. + +You should consult the [OpenAPI Specification](https://swagger.io/specification/) for details on what values are appropriate. :--:1 +```python +app.ext.openapi.add_security_scheme("api_key", "apiKey") +app.ext.openapi.add_security_scheme( + "token", + "http", + scheme="bearer", + bearer_format="JWT", +) +app.ext.openapi.add_security_scheme("token2", "http") +app.ext.openapi.add_security_scheme( + "oldschool", + "http", + scheme="basic", +) +app.ext.openapi.add_security_scheme( + "oa2", + "oauth2", + flows={ + "implicit": { + "authorizationUrl": "http://example.com/auth", + "scopes": { + "on:two": "something", + "three:four": "something else", + "threefour": "something else...", + }, + } + }, +) +``` +:--- + +## Document the endpoints + +---:1 There are two options, document _all_ endpoints. + +:--:1 +```python +app.ext.openapi.secured() +app.ext.openapi.secured("token") +``` +:--- + +---:1 Or, document only specific routes. :--:1 +```python +@app.route("/one") +async def handler1(request): + """ + openapi: + --- + security: + - foo: [] + """ + + +@app.route("/two") +@openapi.secured("foo") +@openapi.secured({"bar": []}) +@openapi.secured(baz=[]) +async def handler2(request): + ... + + +@app.route("/three") +@openapi.definition(secured="foo") +@openapi.definition(secured={"bar": []}) +async def handler3(request): + ... +``` +:--- diff --git a/src/ko/plugins/sanic-ext/openapi/ui.md b/src/ko/plugins/sanic-ext/openapi/ui.md new file mode 100644 index 0000000000..81591fc1da --- /dev/null +++ b/src/ko/plugins/sanic-ext/openapi/ui.md @@ -0,0 +1,26 @@ +# UI + +Sanic Extensions comes with both Redoc and Swagger interfaces. You have a choice to use one, or both of them. Out of the box, the following endpoints are setup for you, with the bare `/docs` displaying Redoc. + +- `/docs` +- `/docs/openapi.json` +- `/docs/redoc` +- `/docs/swagger` +- `/docs/openapi-config` + +## Config options + +| **Key** | **Type** | **Default** | **Desctiption** | +| -------------------------- | --------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| `OAS_IGNORE_HEAD` | `bool` | `True` | Whether to display `HEAD` endpoints. | +| `OAS_IGNORE_OPTIONS` | `bool` | `True` | Whether to display `OPTIONS` endpoints. | +| `OAS_PATH_TO_REDOC_HTML` | `Optional[str]` | `None` | Path to HTML to override the default Redoc HTML | +| `OAS_PATH_TO_SWAGGER_HTML` | `Optional[str]` | `None` | Path to HTML to override the default Swagger HTML | +| `OAS_UI_DEFAULT` | `Optional[str]` | `"redoc"` | Can be set to `redoc` or `swagger`. Controls which UI to display on the base route. If set to `None`, then the base route will not be setup. | +| `OAS_UI_REDOC` | `bool` | `True` | Whether to enable Redoc UI. | +| `OAS_UI_SWAGGER` | `bool` | `True` | Whether to enable Swagger UI. | +| `OAS_URI_TO_CONFIG` | `str` | `"/openapi-config"` | URI path to the OpenAPI config used by Swagger | +| `OAS_URI_TO_JSON` | `str` | `"/openapi.json"` | URI path to the JSON document. | +| `OAS_URI_TO_REDOC` | `str` | `"/redoc"` | URI path to Redoc. | +| `OAS_URI_TO_SWAGGER` | `str` | `"/swagger"` | URI path to Swagger. | +| `OAS_URL_PREFIX` | `str` | `"/docs"` | URL prefix to use for the Blueprint for OpenAPI docs. | diff --git a/src/ko/plugins/sanic-ext/templating.md b/src/ko/plugins/sanic-ext/templating.md new file mode 100644 index 0000000000..91eea27c7b --- /dev/null +++ b/src/ko/plugins/sanic-ext/templating.md @@ -0,0 +1,132 @@ +# Templating + +Sanic Extensions can easily help you integrate templates into your route handlers. + + +## Dependencies + +**Currently, we only support [Jinja](https://github.com/pallets/jinja/).** + +[Read the Jinja docs first](https://jinja.palletsprojects.com/en/3.1.x/) if you are unfamiliar with how to create templates. + +Sanic Extensions will automatically setup and load Jinja for you if it is installed in your environment. Therefore, the only setup that you need to do is install Jinja: + +``` +pip install Jinja2 +``` + +## Rendering a template from a file + +There are three (3) ways for you: + +1. Using a decorator to pre-load the template file +1. Returning a rendered `HTTPResponse` object +1. Hybrid pattern that creates a `LazyResponse` + +Let's imagine you have a file called `./templates/foo.html`: + +```html + + + + + My Webpage + + + +

Hello, world!!!!

+
    + {% for item in seq %} +
  • {{ item }}
  • + {% endfor %} +
+ + + +``` + +Let's see how you could render it with Sanic + Jinja. + +### Option 1 - as a decorator + +---:1 The benefit of this approach is that the templates can be predefined at startup time. This will mean that less fetching needs to happen in the handler, and should therefore be the fastest option. :--:1 +```python +@app.get("/") +@app.ext.template("foo.html") +async def handler(request: Request): + return {"seq": ["one", "two"]} +``` +:--- + +### Option 2 - as a return object + +---:1 This is meant to mimic the `text`, `json`, `html`, `file`, etc pattern of core Sanic. It will allow the most customization to the response object since it has direct control of it. Just like in other `HTTPResponse` objects, you can control headers, cookies, etc. :--:1 +```python +from sanic_ext import render + +@app.get("/alt") +async def handler(request: Request): + return await render( + "foo.html", context={"seq": ["three", "four"]}, status=400 + ) +``` +:--- + +### Option 3 - hybrid/lazy + +---:1 In this approach, the template is defined up front and not inside the handler (for performance). Then, the `render` function returns a `LazyResponse` that can be used to build a proper `HTTPResponse` inside the decorator. :--:1 +```python +from sanic_ext import render + +@app.get("/") +@app.ext.template("foo.html") +async def handler(request: Request): + return await render(context={"seq": ["five", "six"]}, status=400) +``` +:--- + +## Rendering a template from a string + +---:1 Sometimes you may want to write (or generate) your template inside of Python code and _not_ read it from an HTML file. In this case, you can still use the `render` function we saw above. Just use `template_source`. :--:1 +```python +from sanic_ext import render +from textwrap import dedent + +@app.get("/") +async def handler(request): + template = dedent(""" + + + + + My Webpage + + + +

Hello, world!!!!

+
    + {% for item in seq %} +
  • {{ item }}
  • + {% endfor %} +
+ + + + """) + return await render( + template_source=template, + context={"seq": ["three", "four"]}, + app=app, + ) +``` +:--- + +::: tip In this example, we use `textwrap.dedent` to remove the whitespace in the beginning of each line of the multi-line string. It is not necessary, but just a nice touch to keep both the code and the generated source clean. ::: + +## Development and auto-reload + +If auto-reload is turned on, then changes to your template files should trigger a reload of the server. + +## Configuration + +See `templating_enable_async` and `templating_path_to_templates` in [settings](./configuration.md#settings). diff --git a/src/ko/plugins/sanic-ext/validation.md b/src/ko/plugins/sanic-ext/validation.md new file mode 100644 index 0000000000..0aa293cbaa --- /dev/null +++ b/src/ko/plugins/sanic-ext/validation.md @@ -0,0 +1,173 @@ +# Validation + +One of the most commonly implemented features of a web application is user-input validation. For obvious reasons, this is not only a security issue, but also just plain good practice. You want to make sure your data conforms to expectations, and throw a `400` response when it does not. + +## Implementation + +### Validation with Dataclasses + +With the introduction of [Data Classes](https://docs.python.org/3/library/dataclasses.html), Python made it super simple to create objects that meet a defined schema. However, the standard library only supports type checking validation, **not** runtime validation. Sanic Extensions adds the ability to do runtime validations on incoming requests using `dataclasses` out of the box. If you also have either `pydantic` or `attrs` installed, you can alternatively use one of those libraries. + +---:1 + +First, define a model. + +:--:1 + +```python +@dataclass +class SearchParams: + q: str +``` + +:--- + +---:1 + +Then, attach it to your route + +:--:1 + +```python +from sanic_ext import validate + +@app.route("/search") +@validate(query=SearchParams) +async def handler(request, query: SearchParams): + return json(asdict(query)) +``` + +:--- + +---:1 + +You should now have validation on the incoming request. + +:--:1 + +``` +$ curl localhost:8000/search +⚠️ 400 — Bad Request +==================== +Invalid request body: SearchParams. Error: missing a required argument: 'q' +``` +``` +$ curl localhost:8000/search\?q=python +{"q":"python"} +``` + +:--- + +### Validation with Pydantic + + +You can use Pydantic models also. + +---:1 + +First, define a model. + +:--:1 + +```python +class Person(BaseModel): + name: str + age: int +``` + +:--- + +---:1 + +Then, attach it to your route + +:--:1 + +```python +from sanic_ext import validate + +@app.post("/person") +@validate(json=Person) +async def handler(request, body: Person): + return json(body.dict()) +``` +:--- + +---:1 + +You should now have validation on the incoming request. + +:--:1 + +``` +$ curl localhost:8000/person -d '{"name": "Alice", "age": 21}' -X POST +{"name":"Alice","age":21} +``` + +:--- + +### Validation with Attrs + + +You can use Attrs also. + +---:1 + +First, define a model. + +:--:1 + +```python +@attrs.define +class Person: + name: str + age: int + +``` + +:--- + +---:1 + +Then, attach it to your route + +:--:1 + +```python +from sanic_ext import validate + +@app.post("/person") +@validate(json=Person) +async def handler(request, body: Person): + return json(attrs.asdict(body)) +``` +:--- + +---:1 + +You should now have validation on the incoming request. + +:--:1 + +``` +$ curl localhost:8000/person -d '{"name": "Alice", "age": 21}' -X POST +{"name":"Alice","age":21} +``` + +:--- + +## What can be validated? + +The `validate` decorator can be used to validate incoming user data from three places: JSON body data (`request.json`), form body data (`request.form`), and query parameters (`request.args`). + +---:1 As you might expect, you can attach your model using the keyword arguments of the decorator. + +:--:1 +```python +@validate( + json=ModelA, + query=ModelB, + form=ModelC, +) +``` +:--- diff --git a/src/ko/plugins/sanic-testing/clients.md b/src/ko/plugins/sanic-testing/clients.md new file mode 100644 index 0000000000..ee02576ecc --- /dev/null +++ b/src/ko/plugins/sanic-testing/clients.md @@ -0,0 +1,99 @@ +# Test Clients + +There are three different test clients available to you, each of them presents different capabilities. + +## Regular sync client: `SanicTestClient` + +The `SanicTestClient` runs an actual version of the Sanic Server on your local network to run its tests. Each time it calls an endpoint it will spin up a version of the application and bind it to a socket on the host OS. Then, it will use `httpx` to make calls directly to that application. + +This is the typical way that Sanic applications are tested. + +---:1 Once installing Sanic Testing, the regular `SanicTestClient` can be used without further setup. This is because Sanic does the leg work for you under the hood. :--: +```python +app.test_client.get("/path/to/endpoint") +``` +:--- + +---:1 However, you may find it desirable to instantiate the client yourself. :--: +```python +from sanic_testing.testing import SanicTestClient + +test_client = SanicTestClient(app) +test_client.get("/path/to/endpoint") +``` +:--- + +---:1 A third option for starting the test client is to use the `TestManager`. This is a convenience object that sets up both the `SanicTestClient` and the `SanicASGITestClient`. + +:--: +```python +from sanic_testing import TestManager + +mgr = TestManager(app) +app.test_client.get("/path/to/endpoint") +# or +mgr.test_client.get("/path/to/endpoint") +``` +:--- + +You can make a request by using one of the following methods + +- `SanicTestClient.get` +- `SanicTestClient.post` +- `SanicTestClient.put` +- `SanicTestClient.patch` +- `SanicTestClient.delete` +- `SanicTestClient.options` +- `SanicTestClient.head` +- `SanicTestClient.websocket` +- `SanicTestClient.request` + +You can use these methods *almost* identically as you would when using `httpx`. Any argument that you would pass to `httpx` will be accepted, **with one caveat**: If you are using `test_client.request` and want to manually specify the HTTP method, you should use: `http_method`: + +```python +test_client.request("/path/to/endpoint", http_method="get") +``` + +## ASGI async client: `SanicASGITestClient` + +Unlike the `SanicTestClient` that spins up a server on every request, the `SanicASGITestClient` does not. Instead it makes use of the `httpx` library to execute Sanic as an ASGI application to reach inside and execute the route handlers. + +---:1 This test client provides all of the same methods and generally works as the `SanicTestClient`. The only difference is that you will need to add an `await` to each call: :--: +```python +await app.test_client.get("/path/to/endpoint") +``` +:--- + +The `SanicASGITestClient` can be used in the exact same three ways as the `SanicTestClient`. + +::: tip Note The `SanicASGITestClient` does not need to only be used with ASGI applications. The same way that the `SanicTestClient` does not need to only test sync endpoints. Both of these clients are capable of testing *any* Sanic application. ::: + +## Persistent service client: `ReusableClient` + +This client works under a similar premise as the `SanicTestClient` in that it stands up an instance of your application and makes real HTTP requests to it. However, unlike the `SanicTestClient`, when using the `ReusableClient` you control the lifecycle of the application. + +That means that every request **does not** start a new web server. Instead you will start the server and stop it as needed and can make multiple requests to the same running instance. + +---:1 Unlike the other two clients, you **must** instantiate this client for use: :--: +```python +from sanic_testing.reusable import ReusableClient + +client = ReusableClient(app) +``` +:--- + + +---:1 Once created, you will use the client inside of a context manager. Once outside of the scope of the manager, the server will shutdown. :--: +```python +from sanic_testing.reusable import ReusableClient + +def test_multiple_endpoints_on_same_server(app): + client = ReusableClient(app) + with client: + _, response = client.get("/path/to/1") + assert response.status == 200 + + _, response = client.get("/path/to/2") + assert response.status == 200 +``` +:--- diff --git a/src/ko/plugins/sanic-testing/getting-started.md b/src/ko/plugins/sanic-testing/getting-started.md new file mode 100644 index 0000000000..e9f2c8c4e7 --- /dev/null +++ b/src/ko/plugins/sanic-testing/getting-started.md @@ -0,0 +1,83 @@ +# Getting Started + +Sanic Testing is the *official* testing client for Sanic. Its primary use is to power the tests of the Sanic project itself. However, it is also meant as an easy-to-use client for getting your API tests up and running quickly. + +## Minimum requirements + +- **Python**: 3.7+ +- **Sanic**: 21.3+ + +Versions of Sanic older than 21.3 have this module integrated into Sanic itself as `sanic.testing`. + +## Install + +Sanic Testing can be installed from PyPI: + +``` +pip install sanic-testing +``` + +## Basic Usage + +As long as the `sanic-testing` package is in the environment, there is nothing you need to do to start using it. + + +### Writing a sync test + +In order to use the test client, you just need to access the property `test_client` on your application instance: + +```python +import pytest +from sanic import Sanic, response + + +@pytest.fixture +def app(): + sanic_app = Sanic("TestSanic") + + @sanic_app.get("/") + def basic(request): + return response.text("foo") + + return sanic_app + +def test_basic_test_client(app): + request, response = app.test_client.get("/") + + assert request.method.lower() == "get" + assert response.body == b"foo" + assert response.status == 200 +``` + +### Writing an async test + +In order to use the async test client in `pytest`, you should install the `pytest-asyncio` plugin. + +``` +pip install pytest-asyncio +``` + +You can then create an async test and use the ASGI client: + +```python +import pytest +from sanic import Sanic, response + +@pytest.fixture +def app(): + sanic_app = Sanic(__name__) + + @sanic_app.get("/") + def basic(request): + return response.text("foo") + + return sanic_app + +@pytest.mark.asyncio +async def test_basic_asgi_client(app): + request, response = await app.asgi_client.get("/") + + assert request.method.lower() == "get" + assert response.body == b"foo" + assert response.status == 200 +``` diff --git a/src/pt/README.md b/src/pt/README.md new file mode 100644 index 0000000000..6a10d2edf7 --- /dev/null +++ b/src/pt/README.md @@ -0,0 +1,30 @@ +--- +home: true +heroImage: https://raw.githubusercontent.com/huge-success/sanic-assets/master/png/sanic-framework-logo-400x97.png +heroText: Build fast. Run fast. +tagline: Next generation Python web server/framework +actionText: Get Started → +actionLink: /en/guide/ +features: + - + title: Simple and lightweight + details: Intuitive API with smart defaults and no bloat allows you to get straight to work building your app. + - + title: Unopinionated and flexible + details: Build the way you want to build without letting your tooling constrain you. + - + title: Performant and scalable + details: Built from the ground up with speed and scalability as a main concern. It is ready to power web applications big and small. + - + title: Production ready + details: Out of the box, it comes bundled with a web server ready to power your web applications. + - + title: Trusted by millions + details: Sanic is one of the overall most popular frameworks on PyPI, and the top async enabled framework + - + title: Community driven + details: The project is maintained and run by the community for the community. +pageClass: landing-page +logo: false +--- + diff --git a/src/pt/guide/README.md b/src/pt/guide/README.md new file mode 100644 index 0000000000..cf9ee5545a --- /dev/null +++ b/src/pt/guide/README.md @@ -0,0 +1,99 @@ +--- +pageClass: intro +--- + +# Introduction + +Sanic is a Python 3.7+ web server and web framework that’s written to go fast. It allows the usage of the async/await syntax added in Python 3.5, which makes your code non-blocking and speedy. + +| | | +| ------- | ----------------------------------------------------------------------------------------------------------------------------- | +| Build | [![Build Status][1]][1] [![AppVeyor Build Status][3]][2] [![Codecov]][3] | +| Docs | [![Documentation]][4] | +| Package | [![PyPI][7]][5] [![PyPI version][9]][5] [![PyPI Wheel][11]][6] [![Supported implementations][13]][6] [![Code style black]][7] | +| Support | [![Forums][16]][8] [![Discord][18]][9] [![Awesome Sanic List]][10] | +| Stats | [![Downloads][21]][11] [![Downloads][23]][11] | + +## What is it? + +First things first, before you jump in the water, you should know that Sanic is different than other frameworks. + +Right there in that first sentence there is a huge mistake because Sanic is _both_ a **framework** and a **web server**. In the deployment section we will talk a little bit more about this. + +But, remember, out of the box Sanic comes with everything you need to write, deploy, and scale a production grade web application. :rocket: + +## Goal + +> To provide a simple way to get up and running a highly performant HTTP server that is easy to build, to expand, and ultimately to scale. +## Features + +---:1 + +### Core + +- Built in, **_fast_** web server +- Production ready +- Highly scalable +- ASGI compliant +- Simple and intuitive API design +- By the community, for the community + +:--:1 + +### Sanic Extensions [[learn more](../plugins/sanic-ext/getting-started.md)] + +- CORS protection +- Template rendering with Jinja +- Dependency injection into route handlers +- OpenAPI documentation with Redoc and/or Swagger +- Predefined, endpoint-specific response serializers +- Request query arguments and body input validation +- Auto create `HEAD`, `OPTIONS`, and `TRACE` endpoints + +:--- + + + +## Sponsor + +Check out [open collective](https://opencollective.com/sanic-org) to learn more about helping to fund Sanic. + + +## Join the Community + +The main channel for discussion is at the [community forums](https://community.sanicframework.org/). There also is a [Discord Server](https://discord.gg/FARQzAEMAA) for live discussion and chat. + +The Stackoverflow `[sanic]` tag is [actively monitored](https://stackoverflow.com/questions/tagged/sanic) by project maintainers. + +## Contribution + +We are always happy to have new contributions. We have [marked issues good for anyone looking to get started](https://github.com/sanic-org/sanic/issues?q=is%3Aopen+is%3Aissue+label%3Abeginner), and welcome [questions/answers/discussion on the forums](https://community.sanicframework.org/). Please take a look at our [Contribution guidelines](https://github.com/sanic-org/sanic/blob/master/CONTRIBUTING.rst). + +## Who we are + + + +[1]: https://travis-ci.com/sanic-org/sanic.svg?branch=master +[1]: https://travis-ci.com/sanic-org/sanic +[3]: https://ci.appveyor.com/api/projects/status/d8pt3ids0ynexi8c/branch/master?svg=true +[2]: https://ci.appveyor.com/project/sanic-org/sanic +[3]: https://codecov.io/gh/sanic-org/sanic +[4]: http://sanic.readthedocs.io/en/latest/?badge=latest +[7]: https://img.shields.io/pypi/v/sanic.svg +[5]: https://pypi.python.org/pypi/sanic/ +[9]: https://img.shields.io/pypi/pyversions/sanic.svg +[5]: https://pypi.python.org/pypi/sanic/ +[11]: https://img.shields.io/pypi/wheel/sanic.svg +[6]: https://pypi.python.org/pypi/sanic +[13]: https://img.shields.io/pypi/implementation/sanic.svg +[6]: https://pypi.python.org/pypi/sanic +[7]: https://github.com/ambv/black +[16]: https://img.shields.io/badge/forums-community-ff0068.svg +[8]: https://community.sanicframework.org/ +[18]: https://img.shields.io/discord/812221182594121728?logo=discord +[9]: https://discord.gg/FARQzAEMAA +[10]: https://github.com/mekicha/awesome-sanic +[21]: https://pepy.tech/badge/sanic/month +[11]: https://pepy.tech/project/sanic +[23]: https://pepy.tech/badge/sanic/week +[11]: https://pepy.tech/project/sanic diff --git a/src/pt/guide/advanced/README.md b/src/pt/guide/advanced/README.md new file mode 100644 index 0000000000..931fae06c0 --- /dev/null +++ b/src/pt/guide/advanced/README.md @@ -0,0 +1 @@ +# Advanced diff --git a/src/pt/guide/advanced/class-based-views.md b/src/pt/guide/advanced/class-based-views.md new file mode 100644 index 0000000000..378ce1910b --- /dev/null +++ b/src/pt/guide/advanced/class-based-views.md @@ -0,0 +1,184 @@ +# Class Based Views + +## Why use them? + +---:1 + +### The problem + +A common pattern when designing an API is to have multiple functionality on the same endpoint that depends upon the HTTP method. + +While both of these options work, they are not good design practices and may be hard to maintain over time as your project grows. :--:1 +```python +@app.get("/foo") +async def foo_get(request): + ... + +@app.post("/foo") +async def foo_post(request): + ... + +@app.put("/foo") +async def foo_put(request): + ... + +@app.route("/bar", methods=["GET", "POST", "PATCH"]) +async def bar(request): + if request.method == "GET": + ... + elif request.method == "POST": + ... + elif request.method == "PATCH": + ... +``` +:--- + +---:1 + +### The solution + +Class-based views are simply classes that implement response behavior to requests. They provide a way to compartmentalize handling of different HTTP request types at the same endpoint. :--:1 +```python +from sanic.views import HTTPMethodView + +class FooBar(HTTPMethodView): + async def get(self, request): + ... + + async def post(self, request): + ... + + async def put(self, request): + ... + +app.add_route(FooBar.as_view(), "/foobar") +``` +:--- + +## Defining a view + +A class-based view should subclass `HTTPMethodView`. You can then implement class methods with the name of the corresponding HTTP method. If a request is received that has no defined method, a `405: Method not allowed` response will be generated. + +---:1 + +To register a class-based view on an endpoint, the `app.add_route` method is used. The first argument should be the defined class with the method `as_view` invoked, and the second should be the URL endpoint. + +The available methods are: + +- get +- post +- put +- patch +- delete +- head +- options :--:1 +```python +from sanic.views import HTTPMethodView +from sanic.response import text + +class SimpleView(HTTPMethodView): + + def get(self, request): + return text("I am get method") + + # You can also use async syntax + async def post(self, request): + return text("I am post method") + + def put(self, request): + return text("I am put method") + + def patch(self, request): + return text("I am patch method") + + def delete(self, request): + return text("I am delete method") + +app.add_route(SimpleView.as_view(), "/") +``` +:--- + +## Path parameters + +---:1 + +You can use path parameters exactly as discussed in [the routing section](/guide/basics/routing.md). :--:1 +```python +class NameView(HTTPMethodView): + + def get(self, request, name): + return text("Hello {}".format(name)) + +app.add_route(NameView.as_view(), "/") +``` +:--- + +## Decorators + +As discussed in [the decorators section](/guide/best-practices/decorators.md), often you will need to add functionality to endpoints with the use of decorators. You have two options with CBV: + +1. Apply to _all_ HTTP methods in the view +2. Apply individually to HTTP methods in the view + +Let's see what the options look like: + +---:1 + +### Apply to all methods + +If you want to add any decorators to the class, you can set the `decorators` class variable. These will be applied to the class when `as_view` is called. :--:1 +```python +class ViewWithDecorator(HTTPMethodView): + decorators = [some_decorator_here] + + def get(self, request, name): + return text("Hello I have a decorator") + + def post(self, request, name): + return text("Hello I also have a decorator") + +app.add_route(ViewWithDecorator.as_view(), "/url") +``` +:--- + +---:1 + +### Apply to individual methods + +But if you just want to decorate some methods and not all methods, you can as shown here. :--:1 +```python +class ViewWithSomeDecorator(HTTPMethodView): + + @staticmethod + @some_decorator_here + def get(request, name): + return text("Hello I have a decorator") + + def post(self, request, name): + return text("Hello I do not have any decorators") + + @some_decorator_here + def patch(self, request, name): + return text("Hello I have a decorator") +``` +:--- + +## Generating a URL +---:1 + +This works just like [generating any other URL](/guide/basics/routing.md#generating-a-url), except that the class name is a part of the endpoint. :--:1 +```python +@app.route("/") +def index(request): + url = app.url_for("SpecialClassView") + return redirect(url) + + +class SpecialClassView(HTTPMethodView): + def get(self, request): + return text("Hello from the Special Class View!") + + +app.add_route(SpecialClassView.as_view(), "/special_class_view") +``` +:--- diff --git a/src/pt/guide/advanced/proxy-headers.md b/src/pt/guide/advanced/proxy-headers.md new file mode 100644 index 0000000000..f682a8626e --- /dev/null +++ b/src/pt/guide/advanced/proxy-headers.md @@ -0,0 +1,435 @@ +# Proxy configuration + +When you use a reverse proxy server (e.g. nginx), the value of `request.ip` will contain the IP of a proxy, typically `127.0.0.1`. Almost always, this is **not** what you will want. + +Sanic may be configured to use proxy headers for determining the true client IP, available as `request.remote_addr`. The full external URL is also constructed from header fields _if available_. + +::: tip Heads up +Without proper precautions, a malicious client may use proxy headers to spoof its own IP. To avoid such issues, Sanic does not use any proxy headers unless explicitly enabled. +::: + +---:1 + +Services behind reverse proxies must configure one or more of the following [configuration values](/guide/deployment/configuration.md): + +- `FORWARDED_SECRET` +- `REAL_IP_HEADER` +- `PROXIES_COUNT` :--:1 +```python +app.config.FORWARDED_SECRET = "super-duper-secret" +app.config.REAL_IP_HEADER = "CF-Connecting-IP" +app.config.PROXIES_COUNT = 2 +``` +:--- + +## Forwarded header + +In order to use the `Forwarded` header, you should set `app.config.FORWARDED_SECRET` to a value known to the trusted proxy server. The secret is used to securely identify a specific proxy server. + +Sanic ignores any elements without the secret key, and will not even parse the header if no secret is set. + +All other proxy headers are ignored once a trusted forwarded element is found, as it already carries complete information about the client. + +To learn more about the `Forwarded` header, read the related [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded) and [Nginx](https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/) articles. + +## Traditional proxy headers + +### IP Headers + +When your proxy forwards you the IP address in a known header, you can tell Sanic what that is with the `REAL_IP_HEADER` config value. + +### X-Forwarded-For + +This header typically contains a chain of IP addresses through each layer of a proxy. Setting `PROXIES_COUNT` tells Sanic how deep to look to get an actual IP address for the client. This value should equal the _expected_ number of IP addresses in the chain. + +### Other X-headers + +If a client IP is found by one of these methods, Sanic uses the following headers for URL parts: + +- x-forwarded-proto +- x-forwarded-host +- x-forwarded-port +- x-forwarded-path +- x-scheme + +## Examples + +In the following examples, all requests will assume that the endpoint looks like this: +```python +@app.route("/fwd") +async def forwarded(request): + return json( + { + "remote_addr": request.remote_addr, + "scheme": request.scheme, + "server_name": request.server_name, + "server_port": request.server_port, + "forwarded": request.forwarded, + } + ) +``` +---:1 +--- + +##### Example 1 +Without configured FORWARDED_SECRET, x-headers should be respected +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: for=1.1.1.1, for=injected;host=", for="[::2]";proto=https;host=me.tld;path="/app/";secret=mySecret,for=broken;;secret=b0rked, for=127.0.0.3;scheme=http;port=1234' \ + -H "X-Real-IP: 127.0.0.2" \ + -H "X-Forwarded-For: 127.0.1.1" \ + -H "X-Scheme: ws" \ + -H "Host: local.site" | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "127.0.0.2", + "scheme": "ws", + "server_name": "local.site", + "server_port": 80, + "forwarded": { + "for": "127.0.0.2", + "proto": "ws" + } +} +``` +:--- +--- +---:1 + +##### Example 2 +FORWARDED_SECRET now configured +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: for=1.1.1.1, for=injected;host=", for="[::2]";proto=https;host=me.tld;path="/app/";secret=mySecret,for=broken;;secret=b0rked, for=127.0.0.3;scheme=http;port=1234' \ + -H "X-Real-IP: 127.0.0.2" \ + -H "X-Forwarded-For: 127.0.1.1" \ + -H "X-Scheme: ws" \ + -H "Host: local.site" | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "[::2]", + "scheme": "https", + "server_name": "me.tld", + "server_port": 443, + "forwarded": { + "for": "[::2]", + "proto": "https", + "host": "me.tld", + "path": "/app/", + "secret": "mySecret" + } +} +``` +:--- +--- +---:1 + +##### Example 3 +Empty Forwarded header -> use X-headers +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H "X-Real-IP: 127.0.0.2" \ + -H "X-Forwarded-For: 127.0.1.1" \ + -H "X-Scheme: ws" \ + -H "Host: local.site" | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "127.0.0.2", + "scheme": "ws", + "server_name": "local.site", + "server_port": 80, + "forwarded": { + "for": "127.0.0.2", + "proto": "ws" + } +} +``` +:--- +--- +---:1 + +##### Example 4 +Header present but not matching anything +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H "Forwarded: nomatch" | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "", + "scheme": "http", + "server_name": "localhost", + "server_port": 8000, + "forwarded": {} +} + +``` +:--- +--- +---:1 + +##### Example 5 +Forwarded header present but no matching secret -> use X-headers +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H "Forwarded: for=1.1.1.1;secret=x, for=127.0.0.1" \ + -H "X-Real-IP: 127.0.0.2" | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "127.0.0.2", + "scheme": "http", + "server_name": "localhost", + "server_port": 8000, + "forwarded": { + "for": "127.0.0.2" + } +} +``` +:--- +--- +---:1 + +##### Example 6 +Different formatting and hitting both ends of the header +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: Secret="mySecret";For=127.0.0.4;Port=1234' | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "127.0.0.4", + "scheme": "http", + "server_name": "localhost", + "server_port": 1234, + "forwarded": { + "secret": "mySecret", + "for": "127.0.0.4", + "port": 1234 + } +} +``` +:--- +--- +---:1 + +##### Example 7 +Test escapes (modify this if you see anyone implementing quoted-pairs) +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: for=test;quoted="\,x=x;y=\";secret=mySecret' | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "test", + "scheme": "http", + "server_name": "localhost", + "server_port": 8000, + "forwarded": { + "for": "test", + "quoted": "\\,x=x;y=\\", + "secret": "mySecret" + } +} +``` +:--- +--- +---:1 + +##### Example 8 +Secret insulated by malformed field #1 +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: for=test;secret=mySecret;b0rked;proto=wss;' | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "test", + "scheme": "http", + "server_name": "localhost", + "server_port": 8000, + "forwarded": { + "for": "test", + "secret": "mySecret" + } +} +``` +:--- +--- +---:1 + +##### Example 9 +Secret insulated by malformed field #2 +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: for=test;b0rked;secret=mySecret;proto=wss' | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "", + "scheme": "wss", + "server_name": "localhost", + "server_port": 8000, + "forwarded": { + "secret": "mySecret", + "proto": "wss" + } +} +``` +:--- +--- +---:1 + +##### Example 10 +Unexpected termination should not lose existing acceptable values +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: b0rked;secret=mySecret;proto=wss' | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "", + "scheme": "wss", + "server_name": "localhost", + "server_port": 8000, + "forwarded": { + "secret": "mySecret", + "proto": "wss" + } +} +``` +:--- +--- +---:1 + +##### Example 11 +Field normalization +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: PROTO=WSS;BY="CAFE::8000";FOR=unknown;PORT=X;HOST="A:2";PATH="/With%20Spaces%22Quoted%22/sanicApp?key=val";SECRET=mySecret' | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "", + "scheme": "wss", + "server_name": "a", + "server_port": 2, + "forwarded": { + "proto": "wss", + "by": "[cafe::8000]", + "host": "a:2", + "path": "/With Spaces\"Quoted\"/sanicApp?key=val", + "secret": "mySecret" + } +} +``` +:--- +--- +---:1 + +##### Example 12 +Using "by" field as secret +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "_proxySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: for=1.2.3.4; by=_proxySecret' | jq +``` +:--:1 +```bash +# curl response +{ + "remote_addr": "1.2.3.4", + "scheme": "http", + "server_name": "localhost", + "server_port": 8000, + "forwarded": { + "for": "1.2.3.4", + "by": "_proxySecret" + } +} + +``` +:--- diff --git a/src/pt/guide/advanced/signals.md b/src/pt/guide/advanced/signals.md new file mode 100644 index 0000000000..acf934ee91 --- /dev/null +++ b/src/pt/guide/advanced/signals.md @@ -0,0 +1,264 @@ +# Signals + +Signals provide a way for one part of your application to tell another part that something happened. + +```python +@app.signal("user.registration.created") +async def send_registration_email(**context): + await send_email(context["email"], template="registration") + +@app.post("/register") +async def handle_registration(request): + await do_registration(request) + await request.app.dispatch( + "user.registration.created", + context={"email": request.json.email} + }) +``` + +## Adding a signal + +---:1 The API for adding a signal is very similar to adding a route. :--:1 +```python +async def my_signal_handler(): + print("something happened") + +app.add_signal(my_signal_handler, "something.happened.ohmy") +``` +:--- + +---:1 But, perhaps a slightly more convenient method is to use the built-in decorators. :--:1 +```python +@app.signal("something.happened.ohmy") +async def my_signal_handler(): + print("something happened") +``` +:--- + +---:1 If the signal requires conditions, make sure to add them while adding the handler. :--:1 +```python +async def my_signal_handler1(): + print("something happened") + +app.add_signal( + my_signal_handler, + "something.happened.ohmy1", + conditions={"some_condition": "value"} +) + +@app.signal("something.happened.ohmy2", conditions={"some_condition": "value"}) +async def my_signal_handler2(): + print("something happened") +``` +:--- + +---:1 Signals can also be declared on blueprints :--:1 +```python +bp = Blueprint("foo") + +@bp.signal("something.happened.ohmy") +async def my_signal_handler(): + print("something happened") +``` +:--- + +## Built-in signals + +In addition to creating a new signal, there are a number of built-in signals that are dispatched from Sanic itself. These signals exist to provide developers with more opportunities to add functionality into the request and server lifecycles. + +*Added in v21.9* + +---:1 You can attach them just like any other signal to an application or blueprint instance. :--:1 +```python +@app.signal("http.lifecycle.complete") +async def my_signal_handler(conn_info): + print("Connection has been closed") +``` +:--- + +These signals are the signals that are available, along with the arguments that the handlers take, and the conditions that attach (if any). + + +| Event name | Arguments | Conditions | +| -------------------------- | ------------------------------- | --------------------------------------------------------- | +| `http.routing.before` | request | | +| `http.routing.after` | request, route, kwargs, handler | | +| `http.handler.before` | request | | +| `http.handler.after` | request | | +| `http.lifecycle.begin` | conn_info | | +| `http.lifecycle.read_head` | head | | +| `http.lifecycle.request` | request | | +| `http.lifecycle.handle` | request | | +| `http.lifecycle.read_body` | body | | +| `http.lifecycle.exception` | request, exception | | +| `http.lifecycle.response` | request, response | | +| `http.lifecycle.send` | data | | +| `http.lifecycle.complete` | conn_info | | +| `http.middleware.before` | request, response | `{"attach_to": "request"}` or `{"attach_to": "response"}` | +| `http.middleware.after` | request, response | `{"attach_to": "request"}` or `{"attach_to": "response"}` | +| `server.init.before` | app, loop | | +| `server.init.after` | app, loop | | +| `server.shutdown.before` | app, loop | | +| `server.shutdown.after` | app, loop | | + +Version 22.9 added `http.handler.before` and `http.handler.after`. + +---:1 To make using the built-in signals easier, there is an `Enum` object that contains all of the allowed built-ins. With a modern IDE this will help so that you do not need to remember the full list of event names as strings. + +*Added in v21.12* :--:1 +```python +from sanic.signals import Event + +@app.signal(Event.HTTP_LIFECYCLE_COMPLETE) +async def my_signal_handler(conn_info): + print("Connection has been closed") +``` +:--- + +## Events + +---:1 Signals are based off of an _event_. An event, is simply a string in the following pattern: :--:1 +``` +namespace.reference.action +``` +:--- + +::: tip Events must have three parts. If you do not know what to use, try these patterns: + +- `my_app.something.happened` +- `sanic.notice.hello` ::: + +### Event parameters + +---:1 An event can be "dynamic" and declared using the same syntax as [path parameters](../basics/routing.md#path-parameters). This allows matching based upon arbitrary values. :--:1 +```python +@app.signal("foo.bar.") +async def signal_handler(thing): + print(f"[signal_handler] {thing=}") + +@app.get("/") +async def trigger(request): + await app.dispatch("foo.bar.baz") + return response.text("Done.") +``` +:--- + +Checkout [path parameters](../basics/routing.md#path-parameters) for more information on allowed type definitions. + +::: warning Only the third part of an event (the "action") may be dynamic: + +- `foo.bar.` :ok: +- `foo..baz` :x: ::: + +### Waiting + +---:1 In addition to executing a signal handler, your application can wait for an event to be triggered. :--:1 +```python +await app.event("foo.bar.baz") +``` +:--- + +---:1 **IMPORTANT**: waiting is a blocking function. Therefore, you likely will want this to run in a [background task](../basics/tasks.md). :--:1 +```python +async def wait_for_event(app): + while True: + print("> waiting") + await app.event("foo.bar.baz") + print("> event found\n") + +@app.after_server_start +async def after_server_start(app, loop): + app.add_task(wait_for_event(app)) +``` +:--- + +---:1 If your event was defined with a dynamic path, you can use `*` to catch any action. :--:1 +```python +@app.signal("foo.bar.") + +... + +await app.event("foo.bar.*") +``` +:--- + +## Dispatching + +*In the future, Sanic will dispatch some events automatically to assist developers to hook into life cycle events.* + +---:1 Dispatching an event will do two things: + +1. execute any signal handlers defined on the event, and +2. resolve anything that is "waiting" for the event to complete. :--:1 +```python +@app.signal("foo.bar.") +async def foo_bar(thing): + print(f"{thing=}") + +await app.dispatch("foo.bar.baz") +``` +``` +thing=baz +``` +:--- + +### Context + +---:1 Sometimes you may find the need to pass extra information into the signal handler. In our first example above, we wanted our email registration process to have the email address for the user. :--:1 +```python +@app.signal("user.registration.created") +async def send_registration_email(**context): + print(context) + +await app.dispatch( + "user.registration.created", + context={"hello": "world"} +) +``` +``` +{'hello': 'world'} +``` +:--- + +::: tip FYI +Signals are dispatched in a background task. +::: + +### Blueprints + +Dispatching blueprint signals works similar in concept to [middleware](../basics/middleware.md). Anything that is done from the app level, will trickle down to the blueprints. However, dispatching on a blueprint, will only execute the signals that are defined on that blueprint. + +---:1 Perhaps an example is easier to explain: :--:1 +```python +bp = Blueprint("bp") + +app_counter = 0 +bp_counter = 0 + +@app.signal("foo.bar.baz") +def app_signal(): + nonlocal app_counter + app_counter += 1 + +@bp.signal("foo.bar.baz") +def bp_signal(): + nonlocal bp_counter + bp_counter += 1 +``` +:--- + +---:1 Running `app.dispatch("foo.bar.baz")` will execute both signals. :--:1 +```python +await app.dispatch("foo.bar.baz") +assert app_counter == 1 +assert bp_counter == 1 +``` +:--- + +---:1 Running `bp.dispatch("foo.bar.baz")` will execute only the blueprint signal. :--:1 +```python +await bp.dispatch("foo.bar.baz") +assert app_counter == 1 +assert bp_counter == 2 +``` +:--- diff --git a/src/pt/guide/advanced/streaming.md b/src/pt/guide/advanced/streaming.md new file mode 100644 index 0000000000..14e1415be1 --- /dev/null +++ b/src/pt/guide/advanced/streaming.md @@ -0,0 +1,134 @@ +# Streaming + +## Request streaming + +Sanic allows you to stream data sent by the client to begin processing data as the bytes arrive. + +---:1 + +When enabled on an endpoint, you can stream the request body using `await request.stream.read()`. + +That method will return `None` when the body is completed. :--:1 +```python +from sanic.views import stream + +class SimpleView(HTTPMethodView): + @stream + async def post(self, request): + result = "" + while True: + body = await request.stream.read() + if body is None: + break + result += body.decode("utf-8") + return text(result) +``` +:--- + +---:1 + +It also can be enabled with a keyword argument in the decorator... :--:1 +```python +@app.post("/stream", stream=True) +async def handler(request): + ... + body = await request.stream.read() + ... +``` +:--- + +---:1 + +... or the `add_route()` method. :--:1 +```python +bp.add_route( + bp_handler, + "/bp_stream", + methods=["POST"], + stream=True, +) +``` +:--- + +::: tip FYI +Only post, put and patch decorators have stream argument. +::: + +## Response streaming + +---:1 Sanic allows you to stream content to the client. :--:1 + +```python +@app.route("/") +async def test(request): + response = await request.respond(content_type="text/csv") + await response.send("foo,") + await response.send("bar") + + # Optionally, you can explicitly end the stream by calling: + await response.eof() +``` +:--- + +This is useful in situations where you want to stream content to the client that originates in an external service, like a database. For example, you can stream database records to the client with the asynchronous cursor that `asyncpg` provides. + +```python +@app.route("/") +async def index(request): + response = await request.respond() + conn = await asyncpg.connect(database='test') + async with conn.transaction(): + async for record in conn.cursor('SELECT generate_series(0, 10)'): + await response.send(record[0]) +``` + + + +You can explicitly end a stream by calling `await response.eof()`. It a convenience method to replace `await response.send("", True)`. It should be called **one time** *after* your handler has determined that it has nothing left to send back to the client. While it is *optional* to use with Sanic server, if you are running Sanic in ASGI mode, then you **must** explicitly terminate the stream. + +*Calling `eof` became optional in v21.6* + +## File streaming + +---:1 + +Sanic provides `sanic.response.file_stream` function that is useful when you want to send a large file. It returns a `StreamingHTTPResponse` object and will use chunked transfer encoding by default; for this reason Sanic doesn’t add `Content-Length` HTTP header in the response. + +A typical use case might be streaming an video file. :--:1 +```python +@app.route("/mp4") +async def handler_file_stream(request): + return await response.file_stream( + "/path/to/sample.mp4", + chunk_size=1024, + mime_type="application/metalink4+xml", + headers={ + "Content-Disposition": 'Attachment; filename="nicer_name.meta4"', + "Content-Type": "application/metalink4+xml", + }, + ) +``` +:--- + +---:1 + +If you want to use the `Content-Length` header, you can disable chunked transfer encoding and add it manually simply by adding the `Content-Length` header. + +:--:1 +```python +from aiofiles import os as async_os +from sanic.response import file_stream + +@app.route("/") +async def index(request): + file_path = "/srv/www/whatever.png" + + file_stat = await async_os.stat(file_path) + headers = {"Content-Length": str(file_stat.st_size)} + + return await file_stream( + file_path, + headers=headers, + ) +``` +:--- diff --git a/src/pt/guide/advanced/versioning.md b/src/pt/guide/advanced/versioning.md new file mode 100644 index 0000000000..f7b7e08cb7 --- /dev/null +++ b/src/pt/guide/advanced/versioning.md @@ -0,0 +1,146 @@ +# Versioning + +It is standard practice in API building to add versions to your endpoints. This allows you to easily differentiate incompatible endpoints when you try and change your API down the road in a breaking manner. + +Adding a version will add a `/v{version}` url prefix to your endpoints. + +The version can be a `int`, `float`, or `str`. Acceptable values: + +- `1`, `2`, `3` +- `1.1`, `2.25`, `3.0` +- `"1"`, `"v1"`, `"v1.1"` + +## Per route + +---:1 + +You can pass a version number to the routes directly. :--:1 +```python +# /v1/text +@app.route("/text", version=1) +def handle_request(request): + return response.text("Hello world! Version 1") + +# /v2/text +@app.route("/text", version=2) +def handle_request(request): + return response.text("Hello world! Version 2") +``` +:--- + +## Per Blueprint + +---:1 + +You can also pass a version number to the blueprint, which will apply to all routes in that blueprint. :--:1 +```python +bp = Blueprint("test", url_prefix="/foo", version=1) + +# /v1/foo/html +@bp.route("/html") +def handle_request(request): + return response.html("

Hello world!

") +``` +:--- + +## Per Blueprint Group + +---:1 In order to simplify the management of the versioned blueprints, you can provide a version number in the blueprint group. The same will be inherited to all the blueprint grouped under it if the blueprints don't already override the same information with a value specified while creating a blueprint instance. + +When using blueprint groups for managing the versions, the following order is followed to apply the Version prefix to the routes being registered. + +1. Route Level configuration +2. Blueprint level configuration +3. Blueprint Group level configuration + +If we find a more pointed versioning specification, we will pick that over the more generic versioning specification provided under the Blueprint or Blueprint Group :--:1 +```python +from sanic.blueprints import Blueprint +from sanic.response import json + +bp1 = Blueprint( + name="blueprint-1", + url_prefix="/bp1", + version=1.25, +) +bp2 = Blueprint( + name="blueprint-2", + url_prefix="/bp2", +) + +group = Blueprint.group( + [bp1, bp2], + url_prefix="/bp-group", + version="v2", +) + +# GET /v1.25/bp-group/bp1/endpoint-1 +@bp1.get("/endpoint-1") +async def handle_endpoint_1_bp1(request): + return json({"Source": "blueprint-1/endpoint-1"}) + +# GET /v2/bp-group/bp2/endpoint-2 +@bp2.get("/endpoint-1") +async def handle_endpoint_1_bp2(request): + return json({"Source": "blueprint-2/endpoint-1"}) + +# GET /v1/bp-group/bp2/endpoint-2 +@bp2.get("/endpoint-2", version=1) +async def handle_endpoint_2_bp2(request): + return json({"Source": "blueprint-2/endpoint-2"}) +``` +:--- + +## Version prefix + +As seen above, the `version` that is applied to a route is **always** the first segment in the generated URI path. Therefore, to make it possible to add path segments before the version, every place that a `version` argument is passed, you can also pass `version_prefix`. + +The `version_prefix` argument can be defined in: + +- `app.route` and `bp.route` decorators (and all the convenience decorators also) +- `Blueprint` instantiation +- `Blueprint.group` constructor +- `BlueprintGroup` instantiation +- `app.blueprint` registration + +If there are definitions in multiple places, a more specific definition overrides a more general. This list provides that hierarchy. + +The default value of `version_prefix` is `/v`. + +---:1 An often requested feature is to be able to mount versioned routes on `/api`. This can easily be accomplished with `version_prefix`. :--:1 +```python +# /v1/my/path +app.route("/my/path", version=1, version_prefix="/api/v") +``` +:--- + +---:1 Perhaps a more compelling usage is to load all `/api` routes into a single `BlueprintGroup`. :--:1 +```python +# /v1/my/path +app = Sanic(__name__) +v2ip = Blueprint("v2ip", url_prefix="/ip", version=2) +api = Blueprint.group(v2ip, version_prefix="/api/version") + +# /api/version2/ip +@v2ip.get("/") +async def handler(request): + return text(request.ip) + +app.blueprint(api) +``` +:--- + +We can therefore learn that a route's URI is: + +``` +version_prefix + version + url_prefix + URI definition +``` + +::: tip Just like with `url_prefix`, it is possible to define path parameters inside a `version_prefix`. It is perfectly legitimate to do this. Just remember that every route will have that parameter injected into the handler. + +```python +version_prefix="//v" +``` +::: + +*Added in v21.6* diff --git a/src/pt/guide/advanced/websockets.md b/src/pt/guide/advanced/websockets.md new file mode 100644 index 0000000000..bc8976c068 --- /dev/null +++ b/src/pt/guide/advanced/websockets.md @@ -0,0 +1,72 @@ +# Websockets + +Sanic provides an easy to use abstraction on top of [websockets](https://websockets.readthedocs.io/en/stable/). + + +## Routing + +---:1 + +Websocket handlers can be hooked up to the router similar to regular handlers. :--:1 +```python +from sanic import Request, Websocket + +async def feed(request: Request, ws: Websocket): + pass + +app.add_websocket_route(feed, "/feed") +``` +```python +from sanic import Request, Websocket + +@app.websocket("/feed") +async def feed(request: Request, ws: Websocket): + pass +``` +:--- + +## Handler + + +---:1 Typically, a websocket handler will want to hold open a loop. + +It can then use the `send()` and `recv()` methods on the second object injected into the handler. + +This example is a simple endpoint that echos back to the client messages that it receives. :--:1 +```python +from sanic import Request, Websocket + +@app.websocket("/feed") +async def feed(request: Request, ws: Websocket): + while True: + data = "hello!" + print("Sending: " + data) + await ws.send(data) + data = await ws.recv() + print("Received: " + data) +``` +:--- + +---:1 You can simplify your loop by just iterating over the `Websocket` object in a for loop. + +*Added in v22.9* :--:1 +```python +from sanic import Request, Websocket + +@app.websocket("/feed") +async def feed(request: Request, ws: Websocket): + async for msg in ws: + await ws.send(msg) +``` +:--- + + +## Configuration + +See [configuration section](/guide/deployment/configuration.md) for more details, however the defaults are shown below. + +```python +app.config.WEBSOCKET_MAX_SIZE = 2 ** 20 +app.config.WEBSOCKET_PING_INTERVAL = 20 +app.config.WEBSOCKET_PING_TIMEOUT = 20 +``` diff --git a/src/pt/guide/basics/README.md b/src/pt/guide/basics/README.md new file mode 100644 index 0000000000..25dcc52044 --- /dev/null +++ b/src/pt/guide/basics/README.md @@ -0,0 +1 @@ +# Basics diff --git a/src/pt/guide/basics/app.md b/src/pt/guide/basics/app.md new file mode 100644 index 0000000000..089e45e019 --- /dev/null +++ b/src/pt/guide/basics/app.md @@ -0,0 +1,248 @@ +# Sanic Application + +## Instance + +---:1 The most basic building block is the `Sanic()` instance. It is not required, but the custom is to instantiate this in a file called `server.py`. :--:1 +```python +# /path/to/server.py + +from sanic import Sanic + +app = Sanic("MyHelloWorldApp") +``` +:--- + +## Application context + +Most applications will have the need to share/reuse data or objects across different parts of the code base. The most common example is DB connections. + +---:1 In versions of Sanic prior to v21.3, this was commonly done by attaching an attribute to the application instance :--:1 +```python +# Raises a warning as deprecated feature in 21.3 +app = Sanic("MyApp") +app.db = Database() +``` +:--- + +---:1 Because this can create potential problems with name conflicts, and to be consistent with [request context](./request.md#context) objects, v21.3 introduces application level context object. :--:1 +```python +# Correct way to attach objects to the application +app = Sanic("MyApp") +app.ctx.db = Database() +``` +:--- + +## App Registry + +---:1 + +When you instantiate a Sanic instance, that can be retrieved at a later time from the Sanic app registry. This can be useful, for example, if you need to access your Sanic instance from a location where it is not otherwise accessible. :--:1 +```python +# ./path/to/server.py +from sanic import Sanic + +app = Sanic("my_awesome_server") + +# ./path/to/somewhere_else.py +from sanic import Sanic + +app = Sanic.get_app("my_awesome_server") +``` +:--- + +---:1 + +If you call `Sanic.get_app("non-existing")` on an app that does not exist, it will raise `SanicException` by default. You can, instead, force the method to return a new instance of Sanic with that name. :--:1 +```python +app = Sanic.get_app( + "non-existing", + force_create=True, +) +``` +:--- + +---:1 If there is **only one** Sanic instance registered, then calling `Sanic.get_app()` with no arguments will return that instance :--:1 +```python +Sanic("My only app") + +app = Sanic.get_app() +``` +:--- + +## Configuration + +---:1 Sanic holds the configuration in the `config` attribute of the `Sanic` instance. Configuration can be modified **either** using dot-notation **OR** like a dictionary. :--:1 +```python +app = Sanic('myapp') + +app.config.DB_NAME = 'appdb' +app.config['DB_USER'] = 'appuser' + +db_settings = { + 'DB_HOST': 'localhost', + 'DB_NAME': 'appdb', + 'DB_USER': 'appuser' +} +app.config.update(db_settings) +``` +:--- + +::: tip Heads up Config keys _should_ be uppercase. But, this is mainly by convention, and lowercase will work most of the time. +``` +app.config.GOOD = "yay!" +app.config.bad = "boo" +``` +::: + +There is much [more detail about configuration](/guide/deployment/configuration.md) later on. + + +## Customization + +The Sanic application instance can be customized for your application needs in a variety of ways at instantiation. + +### Custom configuration +---:1 + +This simplest form of custom configuration would be to pass your own object directly into that Sanic application instance + +If you create a custom configuration object, it is *highly* recommended that you subclass the Sanic `Config` option to inherit its behavior. You could use this option for adding properties, or your own set of custom logic. + +*Added in v21.6* :--:1 +```python +from sanic.config import Config + +class MyConfig(Config): + FOO = "bar" + +app = Sanic(..., config=MyConfig()) +``` +:--- + +---:1 A useful example of this feature would be if you wanted to use a config file in a form that differs from what is [supported](../deployment/configuration.md#using-sanic-update-config). :--:1 +```python +from sanic import Sanic, text +from sanic.config import Config + +class TomlConfig(Config): + def __init__(self, *args, path: str, **kwargs): + super().__init__(*args, **kwargs) + + with open(path, "r") as f: + self.apply(toml.load(f)) + + def apply(self, config): + self.update(self._to_uppercase(config)) + + def _to_uppercase(self, obj: Dict[str, Any]) -> Dict[str, Any]: + retval: Dict[str, Any] = {} + for key, value in obj.items(): + upper_key = key.upper() + if isinstance(value, list): + retval[upper_key] = [ + self._to_uppercase(item) for item in value + ] + elif isinstance(value, dict): + retval[upper_key] = self._to_uppercase(value) + else: + retval[upper_key] = value + return retval + +toml_config = TomlConfig(path="/path/to/config.toml") +app = Sanic(toml_config.APP_NAME, config=toml_config) +``` +:--- +### Custom context +---:1 By default, the application context is a [`SimpleNamespace()`](https://docs.python.org/3/library/types.html#types.SimpleNamespace) that allows you to set any properties you want on it. However, you also have the option of passing any object whatsoever instead. + +*Added in v21.6* :--:1 +```python +app = Sanic(..., ctx=1) +``` + +```python +app = Sanic(..., ctx={}) +``` + +```python +class MyContext: + ... + +app = Sanic(..., ctx=MyContext()) +``` +:--- +### Custom requests +---:1 It is sometimes helpful to have your own `Request` class, and tell Sanic to use that instead of the default. One example is if you wanted to modify the default `request.id` generator. + +::: tip Important + +It is important to remember that you are passing the *class* not an instance of the class. + +::: :--:1 +```python +import time + +from sanic import Request, Sanic, text + + +class NanoSecondRequest(Request): + @classmethod + def generate_id(*_): + return time.time_ns() + + +app = Sanic(..., request_class=NanoSecondRequest) + + +@app.get("/") +async def handler(request): + return text(str(request.id)) +``` +:--- + +### Custom error handler + +---:1 See [exception handling](../best-practices/exceptions.md#custom-error-handling) for more :--:1 +```python +from sanic.handlers import ErrorHandler + +class CustomErrorHandler(ErrorHandler): + def default(self, request, exception): + ''' handles errors that have no error handlers assigned ''' + # You custom error handling logic... + return super().default(request, exception) + +app = Sanic(..., error_handler=CustomErrorHandler()) +``` +:--- + +### Custom dumps function + +---:1 It may sometimes be necessary or desirable to provide a custom function that serializes an object to JSON data. :--:1 +```python +import ujson + +dumps = partial(ujson.dumps, escape_forward_slashes=False) +app = Sanic(__name__, dumps=dumps) +``` +:--- + +---:1 Or, perhaps use another library or create your own. :--:1 +```python +from orjson import dumps + +app = Sanic(__name__, dumps=dumps) +``` +:--- + +### Custom loads function + +---:1 Similar to `dumps`, you can also provide a custom function for deserializing data. + +*Added in v22.9* :--:1 +```python +from orjson import loads + +app = Sanic(__name__, loads=loads) +``` +:--- diff --git a/src/pt/guide/basics/cookies.md b/src/pt/guide/basics/cookies.md new file mode 100644 index 0000000000..851ee2f6d8 --- /dev/null +++ b/src/pt/guide/basics/cookies.md @@ -0,0 +1,73 @@ +# Cookies + +## Reading + +---:1 + +Cookies can be accessed via the `Request` object’s `cookies` dictionary. :--:1 +```python +@app.route("/cookie") +async def test(request): + test_cookie = request.cookies.get("test") + return text("Test cookie: {}".format(test_cookie)) +``` +:--- + + +## Writing + +---:1 + +When returning a response, cookies can be set on the `Response` object: `response.cookies`. This object is an instance of `CookieJar` which is a special sort of dictionary that automatically will write the response headers for you. :--:1 +```python +@app.route("/cookie") +async def test(request): + response = text("There's a cookie up in this response") + response.cookies["test"] = "It worked!" + response.cookies["test"]["domain"] = ".yummy-yummy-cookie.com" + response.cookies["test"]["httponly"] = True + return response +``` +:--- + +Response cookies can be set like dictionary values and have the following parameters available: + +- `expires: datetime` - The time for the cookie to expire on the client’s browser. +- `path: str` - The subset of URLs to which this cookie applies. Defaults to `/`. +- `comment: str` - A comment (metadata). +- `domain: str` - Specifies the domain for which the cookie is valid. An explicitly specified domain must always start with a dot. +- `max-age: int` - Number of seconds the cookie should live for. +- `secure: bool` - Specifies whether the cookie will only be sent via HTTPS. +- `httponly: bool` - Specifies whether the cookie cannot be read by JavaScript. +- `samesite: str` - Default is browser dependent, specification states (Lax, Strict, and None) are valid values. + +## Deleting + +---:1 + +Cookies can be removed semantically or explicitly. :--:1 +```python +@app.route("/cookie") +async def test(request): + response = text("Time to eat some cookies muahaha") + + # This cookie will be set to expire in 0 seconds + del response.cookies["kill_me"] + + # This cookie will self destruct in 5 seconds + response.cookies["short_life"] = "Glad to be here" + response.cookies["short_life"]["max-age"] = 5 + del response.cookies["favorite_color"] + + # This cookie will remain unchanged + response.cookies["favorite_color"] = "blue" + response.cookies["favorite_color"] = "pink" + del response.cookies["favorite_color"] + + return response +``` +:--- + +## Eating + +I like cookies :cookie: diff --git a/src/pt/guide/basics/handlers.md b/src/pt/guide/basics/handlers.md new file mode 100644 index 0000000000..4f255f3a90 --- /dev/null +++ b/src/pt/guide/basics/handlers.md @@ -0,0 +1,113 @@ +# Handlers + +The next important building block are your _handlers_. These are also sometimes called "views". + +In Sanic, a handler is any callable that takes at least a `Request` instance as an argument, and returns either an `HTTPResponse` instance, or a coroutine that does the same. + + + +---:1 + +Huh? :confused: + +It is a **function**; either synchronous or asynchronous. + +The job of the handler is to respond to an endpoint and do something. This is where the majority of your business logic will go. :--:1 +```python +def i_am_a_handler(request): + return HTTPResponse() + +async def i_am_ALSO_a_handler(request): + return HTTPResponse() +``` +:--- + +::: tip Heads up If you want to learn more about encapsulating your logic, checkout [class based views](/guide/advanced/class-based-views.md). ::: ---:1 Then, all you need to do is wire it up to an endpoint. We'll learn more about [routing soon](./routing.md). + +Let's look at a practical example. + +- We use a convenience decorator on our app instance: `@app.get()` +- And a handy convenience method for generating out response object: `text()` + +Mission accomplished :muscle: :--:1 +```python +from sanic.response import text + +@app.get("/foo") +async def foo_handler(request): + return text("I said foo!") +``` +:--- + +--- + +## A word about _async_... + +---:1 + +It is entirely possible to write handlers that are synchronous. + +In this example, we are using the _blocking_ `time.sleep()` to simulate 100ms of processing time. Perhaps this represents fetching data from a DB, or a 3rd-party website. + +Using four (4) worker processes and a common benchmarking tool: + +- **956** requests in 30.10s +- Or, about **31.76** requests/second :--:1 +```python +@app.get("/sync") +def sync_handler(request): + time.sleep(0.1) + return text("Done.") +``` +:--- + +---:1 + +Just by changing to the asynchronous alternative `asyncio.sleep()`, we see an incredible change in performance. :rocket: + +Using the same four (4) worker processes: + +- **115,590** requests in 30.08s +- Or, about **3,843.17** requests/second + +:flushed: + +Okay... this is a ridiculously overdramatic result. And any benchmark you see is inherently very biased. This example is meant to over-the-top show the benefit of `async/await` in the web world. Results will certainly vary. Tools like Sanic and other async Python libraries are not magic bullets that make things faster. They make them _more efficient_. + +In our example, the asynchronous version is so much better because while one request is sleeping, it is able to start another one, and another one, and another one, and another one... + +But, this is the point! Sanic is fast because it takes the available resources and squeezes performance out of them. It can handle many requests concurrently, which means more requests per second. + +:--:1 +```python +@app.get("/async") +async def async_handler(request): + await asyncio.sleep(0.1) + return text("Done.") +``` +:--- + +::: warning A common mistake! + +Don't do this! You need to ping a website. What do you use? `pip install your-fav-request-library` :see_no_evil: + +Instead, try using a client that is `async/await` capable. Your server will thank you. Avoid using blocking tools, and favor those that play well in the asynchronous ecosystem. If you need recommendations, check out [Awesome Sanic](https://github.com/mekicha/awesome-sanic). + +Sanic uses [httpx](https://www.python-httpx.org/) inside of its testing package (sanic-testing) :wink:. + +::: + +--- + +## A fully annotated handler + +For those that are using type annotations... + +```python +from sanic.response import HTTPResponse, text +from sanic.request import Request + +@app.get("/typed") +async def typed_handler(request: Request) -> HTTPResponse: + return text("Done.") +``` diff --git a/src/pt/guide/basics/headers.md b/src/pt/guide/basics/headers.md new file mode 100644 index 0000000000..4bb06f8d8d --- /dev/null +++ b/src/pt/guide/basics/headers.md @@ -0,0 +1,230 @@ +# Headers + +Request and response headers are available in the `Request` and `HTTPResponse` objects, respectively. They make use of the [`multidict` package](https://multidict.readthedocs.io/en/stable/multidict.html#cimultidict) that allows a single key to have multiple values. + +::: tip FYI + +Header keys are converted to *lowercase* when parsed. Capitalization is not considered for headers. + +::: + +## Request + +Sanic does attempt to do some normalization on request headers before presenting them to the developer, and also make some potentially meaningful extractions for common use cases. + +---:1 + +#### Tokens + +Authorization tokens in the form `Token ` or `Bearer ` are extracted to the request object: `request.token`. + +:--:1 + +```python +@app.route("/") +async def handler(request): + return text(request.token) +``` + +```bash +$ curl localhost:8000 \ + -H "Authorization: Token ABCDEF12345679" +ABCDEF12345679 +``` + +```bash +$ curl localhost:8000 \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c +``` + +:--- + +---:1 + +#### Proxy headers + +Sanic has special handling for proxy headers. See the [proxy headers](/guide/advanced/proxy-headers.md) section for more details. + +#### Host header and dynamic URL construction + +The *effective host* is available via `request.host`. This is not necessarily the same as the host header, as it prefers proxy-forwarded host and can be forced by the server name setting. + +Webapps should generally use this accessor so that they can function the same no matter how they are deployed. The actual host header, if needed, can be found via `request.headers` + +The effective host is also used in dynamic URL construction via `request.url_for`, which uses the request to determine the external address of a handler. + +::: tip Be wary of malicious clients These URLs can be manipulated by sending misleading host headers. `app.url_for` should be used instead if this is a concern. ::: + +:--:1 + +```python +app.config.SERVER_NAME = "https://example.com" + +@app.route("/hosts", name="foo") +async def handler(request): + return json( + { + "effective host": request.host, + "host header": request.headers.get("host"), + "forwarded host": request.forwarded.get("host"), + "you are here": request.url_for("foo"), + } + ) +``` + +```bash +$ curl localhost:8000/hosts +{ + "effective host": "example.com", + "host header": "localhost:8000", + "forwarded host": null, + "you are here": "https://example.com/hosts" +} +``` + +:--- + +---:1 +#### Other headers + +All request headers are available on `request.headers`, and can be accessed in dictionary form. Capitalization is not considered for headers, and can be accessed using either uppercase or lowercase keys. + +:--:1 + +```python +@app.route("/") +async def handler(request): + return json( + { + "foo_weakref": request.headers["foo"], + "foo_get": request.headers.get("Foo"), + "foo_getone": request.headers.getone("FOO"), + "foo_getall": request.headers.getall("fOo"), + "all": list(request.headers.items()), + } + ) +``` + +```bash +$ curl localhost:9999/headers -H "Foo: one" -H "FOO: two"|jq +{ + "foo_weakref": "one", + "foo_get": "one", + "foo_getone": "one", + "foo_getall": [ + "one", + "two" + ], + "all": [ + [ + "host", + "localhost:9999" + ], + [ + "user-agent", + "curl/7.76.1" + ], + [ + "accept", + "*/*" + ], + [ + "foo", + "one" + ], + [ + "foo", + "two" + ] + ] +} +``` + +:--- + +::: tip FYI 💡 The request.headers object is one of a few types that is a dictionary with each value being a list. This is because HTTP allows a single key to be reused to send multiple values. + +Most of the time you will want to use the .get() or .getone() methods to access the first element and not a list. If you do want a list of all items, you can use .getall(). ::: + +#### Request ID + +---:1 + +Often it is convenient or necessary to track a request by its `X-Request-ID` header. You can easily access that as: `request.id`. + +:--:1 + +```python +@app.route("/") +async def handler(request): + return text(request.id) +``` + +```bash +$ curl localhost:8000 \ + -H "X-Request-ID: ABCDEF12345679" +ABCDEF12345679 +``` + +:--- + +## Response + +Sanic will automatically set the following response headers (when appropriate) for you: + +- `content-length` +- `content-type` +- `connection` +- `transfer-encoding` + +In most circumstances, you should never need to worry about setting these headers. + +---:1 + +Any other header that you would like to set can be done either in the route handler, or a response middleware. + +:--:1 + +```python +@app.route("/") +async def handler(request): + return text("Done.", headers={"content-language": "en-US"}) + +@app.middleware("response") +async def add_csp(request, response): + response.headers["content-security-policy"] = "default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self';base-uri 'self';form-action 'self'" +``` + +:--- + +---:1 + +A common [middleware](middleware.md) you might want is to add a `X-Request-ID` header to every response. As stated above: `request.id` will provide the ID from the incoming request. But, even if no ID was supplied in the request headers, one will be automatically supplied for you. + +[See API docs for more details](https://sanic.readthedocs.io/en/latest/sanic/api_reference.html#sanic.request.Request.id) + +:--:1 + +```python +@app.route("/") +async def handler(request): + return text(str(request.id)) + +@app.on_response +async def add_request_id_header(request, response): + response.headers["X-Request-ID"] = request.id +``` + +```bash +$ curl localhost:8000 -i +HTTP/1.1 200 OK +X-Request-ID: 805a958e-9906-4e7a-8fe0-cbe83590431b +content-length: 36 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +805a958e-9906-4e7a-8fe0-cbe83590431b +``` + +:--- diff --git a/src/pt/guide/basics/listeners.md b/src/pt/guide/basics/listeners.md new file mode 100644 index 0000000000..c524e11121 --- /dev/null +++ b/src/pt/guide/basics/listeners.md @@ -0,0 +1,226 @@ +# Listeners + +Sanic provides you with eight (8) opportunities to inject an operation into the life cycle of your application server. This does not include the [signals](../advanced/signals.md), which allow further injection customization. + +There are two (2) that run **only** on your main Sanic process (ie, once per call to `sanic server.app`.) + +- `main_process_start` +- `main_process_stop` + +There are also two (2) that run **only** in a reloader process if auto-reload has been turned on. + +- `reload_process_start` +- `reload_process_stop` + +*Added `reload_process_start` and `reload_process_stop` in v22.3* + +There are four (4) that enable you to execute startup/teardown code as your server starts or closes. + +- `before_server_start` +- `after_server_start` +- `before_server_stop` +- `after_server_stop` + +The life cycle of a worker process looks like this: + +```mermaid +sequenceDiagram +autonumber +participant Process +participant Worker +participant Listener +participant Handler +Note over Process: sanic server.app +loop + Process->>Listener: @app.main_process_start + Listener->>Handler: Invoke event handler +end +Process->>Worker: Run workers +loop Start each worker + loop + Worker->>Listener: @app.before_server_start + Listener->>Handler: Invoke event handler + end + Note over Worker: Server status: started + loop + Worker->>Listener: @app.after_server_start + Listener->>Handler: Invoke event handler + end + Note over Worker: Server status: ready +end +Process->>Worker: Graceful shutdown +loop Stop each worker + loop + Worker->>Listener: @app.before_server_stop + Listener->>Handler: Invoke event handler + end + Note over Worker: Server status: stopped + loop + Worker->>Listener: @app.after_server_stop + Listener->>Handler: Invoke event handler + end + Note over Worker: Server status: closed +end +loop + Process->>Listener: @app.main_process_stop + Listener->>Handler: Invoke event handler +end +Note over Process: exit +``` + +The reloader process live outside of this worker process inside of a process that is responsible for starting and stopping the Sanic processes. Consider the following example: + +```python +@app.reload_process_start +async def reload_start(*_): + print(">>>>>> reload_start <<<<<<") + + +@app.main_process_start +async def main_start(*_): + print(">>>>>> main_start <<<<<<") +``` + +If this application were run with auto-reload turned on, the `reload_start` function would be called once. This is contrasted with `main_start`, which would be run every time a file is save and the reloader restarts the applicaition process. + +## Attaching a listener + +---:1 + +The process to setup a function as a listener is similar to declaring a route. + +The currently running `Sanic()` instance is injected into the listener. :--:1 +```python +async def setup_db(app): + app.ctx.db = await db_setup() + +app.register_listener(setup_db, "before_server_start") +``` +:--- + +---:1 + +The `Sanic` app instance also has a convenience decorator. :--:1 +```python +@app.listener("before_server_start") +async def setup_db(app): + app.ctx.db = await db_setup() +``` +:--- + +---:1 Prior to v22.3, both the application instance and the current event loop were injected into the function. However, only the application instance is injected by default. If your function signature will accept both, then both the application and the loop will be injected as shown here. :--:1 +```python +@app.listener("before_server_start") +async def setup_db(app, loop): + app.ctx.db = await db_setup() +``` +:--- + +---:1 + +You can shorten the decorator even further. This is helpful if you have an IDE with autocomplete. + +:--:1 +```python +@app.before_server_start +async def setup_db(app): + app.ctx.db = await db_setup() +``` +:--- + +## Order of execution + +Listeners are executed in the order they are declared during startup, and reverse order of declaration during teardown + +| | Phase | Order | +| --------------------- | --------------- | ---------------------------- | +| `main_process_start` | main startup | regular :smiley: | +| `before_server_start` | worker startup | regular :smiley: | +| `after_server_start` | worker startup | regular :smiley: | +| `before_server_stop` | worker shutdown | reverse :upside_down_face: | +| `after_server_stop` | worker shutdown | reverse :upside_down_face: | +| `main_process_stop` | main shutdown | reverse :upside_down_face: | + +Given the following setup, we should expect to see this in the console if we run two workers. + +---:1 + +```python +@app.listener("before_server_start") +async def listener_1(app, loop): + print("listener_1") + +@app.before_server_start +async def listener_2(app, loop): + print("listener_2") + +@app.listener("after_server_start") +async def listener_3(app, loop): + print("listener_3") + +@app.after_server_start +async def listener_4(app, loop): + print("listener_4") + +@app.listener("before_server_stop") +async def listener_5(app, loop): + print("listener_5") + +@app.before_server_stop +async def listener_6(app, loop): + print("listener_6") + +@app.listener("after_server_stop") +async def listener_7(app, loop): + print("listener_7") + +@app.after_server_stop +async def listener_8(app, loop): + print("listener_8") +``` +:--:1 +```bash{3-7,13,19-22} +[pid: 1000000] [INFO] Goin' Fast @ http://127.0.0.1:9999 +[pid: 1000000] [INFO] listener_0 +[pid: 1111111] [INFO] listener_1 +[pid: 1111111] [INFO] listener_2 +[pid: 1111111] [INFO] listener_3 +[pid: 1111111] [INFO] listener_4 +[pid: 1111111] [INFO] Starting worker [1111111] +[pid: 1222222] [INFO] listener_1 +[pid: 1222222] [INFO] listener_2 +[pid: 1222222] [INFO] listener_3 +[pid: 1222222] [INFO] listener_4 +[pid: 1222222] [INFO] Starting worker [1222222] +[pid: 1111111] [INFO] Stopping worker [1111111] +[pid: 1222222] [INFO] Stopping worker [1222222] +[pid: 1222222] [INFO] listener_6 +[pid: 1222222] [INFO] listener_5 +[pid: 1222222] [INFO] listener_8 +[pid: 1222222] [INFO] listener_7 +[pid: 1111111] [INFO] listener_6 +[pid: 1111111] [INFO] listener_5 +[pid: 1111111] [INFO] listener_8 +[pid: 1111111] [INFO] listener_7 +[pid: 1000000] [INFO] listener_9 +[pid: 1000000] [INFO] Server Stopped +``` +In the above example, notice how there are three processes running: + +- `pid: 1000000` - The *main* process +- `pid: 1111111` - Worker 1 +- `pid: 1222222` - Worker 2 + +*Just because our example groups all of one worker and then all of another, in reality since these are running on separate processes, the ordering between processes is not guaranteed. But, you can be sure that a single worker will **always** maintain its order.* :--- + + +::: tip FYI The practical result of this is that if the first listener in `before_server_start` handler setups a database connection, listeners that are registered after it can rely upon that connection being alive both when they are started and stopped. ::: + +## ASGI Mode + +If you are running your application with an ASGI server, then make note of the following changes: + +- `reload_process_start` and `reload_process_stop` will be **ignored** +- `main_process_start` and `main_process_stop` will be **ignored** +- `before_server_start` will run as early as it can, and will be before `after_server_start`, but technically, the server is already running at that point +- `after_server_stop` will run as late as it can, and will be after `before_server_stop`, but technically, the server is still running at that point diff --git a/src/pt/guide/basics/middleware.md b/src/pt/guide/basics/middleware.md new file mode 100644 index 0000000000..7b037e8927 --- /dev/null +++ b/src/pt/guide/basics/middleware.md @@ -0,0 +1,210 @@ +# Middleware + +Whereas listeners allow you to attach functionality to the lifecycle of a worker process, middleware allows you to attach functionality to the lifecycle of an HTTP stream. + +You can execute middleware either _before_ the handler is executed, or _after_. + +```mermaid +sequenceDiagram +autonumber +participant Worker +participant Middleware +participant MiddlewareHandler +participant RouteHandler +Note over Worker: Incoming HTTP request +loop + Worker->>Middleware: @app.on_request + Middleware->>MiddlewareHandler: Invoke middleware handler + MiddlewareHandler-->>Worker: Return response (optional) +end +rect rgba(255, 13, 104, .1) +Worker->>RouteHandler: Invoke route handler +RouteHandler->>Worker: Return response +end +loop + Worker->>Middleware: @app.on_response + Middleware->>MiddlewareHandler: Invoke middleware handler + MiddlewareHandler-->>Worker: Return response (optional) +end +Note over Worker: Deliver response +``` +## Attaching middleware + +---:1 + +This should probably look familiar by now. All you need to do is declare when you would like the middleware to execute: on the `request` or on the `response`. :--:1 +```python +async def extract_user(request): + request.ctx.user = await extract_user_from_request(request) + +app.register_middleware(extract_user, "request") +``` +:--- + +---:1 + +Again, the `Sanic` app instance also has a convenience decorator. :--:1 +```python +@app.middleware("request") +async def extract_user(request): + request.ctx.user = await extract_user_from_request(request) +``` +:--- + +---:1 + +Response middleware receives both the `request` and `response` arguments. :--:1 +```python +@app.middleware('response') +async def prevent_xss(request, response): + response.headers["x-xss-protection"] = "1; mode=block" +``` +:--- + +---:1 + +You can shorten the decorator even further. This is helpful if you have an IDE with autocomplete. + +This is the preferred usage, and is what we will use going forward. + +:--:1 +```python +@app.on_request +async def extract_user(request): + ... + +@app.on_response +async def prevent_xss(request, response): + ... +``` +:--- + +## Modification + +---:1 + +Middleware can modify the request or response parameter it is given, _as long as it does not return it_. + +#### Order of execution + +1. Request middleware: `add_key` +2. Route handler: `index` +3. Response middleware: `prevent_xss` +4. Response middleware: `custom_banner` :--:1 +```python +@app.on_request +async def add_key(request): + # Arbitrary data may be stored in request context: + request.ctx.foo = "bar" + + +@app.on_response +async def custom_banner(request, response): + response.headers["Server"] = "Fake-Server" + + +@app.on_response +async def prevent_xss(request, response): + response.headers["x-xss-protection"] = "1; mode=block" + + +@app.get("/") +async def index(request): + return text(request.ctx.foo) + +``` +:--- + + +---:1 You can modify the `request.match_info`. A useful feature that could be used, for example, in middleware to convert `a-slug` to `a_slug`. :--:1 +```python +@app.on_request +def convert_slug_to_underscore(request: Request): + request.match_info["slug"] = request.match_info["slug"].replace("-", "_") + + +@app.get("/") +async def handler(request, slug): + return text(slug) +``` +``` +$ curl localhost:9999/foo-bar-baz +foo_bar_baz +``` +:--- +## Responding early + +---:1 + +If middleware returns a `HTTPResponse` object, the request will stop processing and the response will be returned. If this occurs to a request before the route handler is reached, the handler will **not** be called. Returning a response will also prevent any further middleware from running. + +::: tip You can return a `None` value to stop the execution of the middleware handler to allow the request to process as normal. This can be useful when using early return to avoid processing requests inside of that middleware handler. ::: :--:1 +```python +@app.on_request +async def halt_request(request): + return text("I halted the request") + +@app.on_response +async def halt_response(request, response): + return text("I halted the response") +``` +:--- + +## Order of execution + +Request middleware is executed in the order declared. Response middleware is executed in **reverse order**. + +Given the following setup, we should expect to see this in the console. + +---:1 +```python +@app.on_request +async def middleware_1(request): + print("middleware_1") + + +@app.on_request +async def middleware_2(request): + print("middleware_2") + + +@app.on_response +async def middleware_3(request, response): + print("middleware_3") + + +@app.on_response +async def middleware_4(request, response): + print("middleware_4") + +@app.get("/handler") +async def handler(request): + print("~ handler ~") + return text("Done.") +``` +:--:1 +```bash +middleware_1 +middleware_2 +~ handler ~ +middleware_4 +middleware_3 +[INFO][127.0.0.1:44788]: GET http://localhost:8000/handler 200 5 +``` +:--- + +### Middleware priority + +---:1 You can modify the order of execution of middleware by assigning it a higher priority. This happens inside of the middleware definition. The higher the value, the earlier it will execute relative to other middleware. The default priority for middleware is `0`. :--:1 +```python +@app.on_request +async def low_priority(request): + ... + +@app.on_request(priority=99) +async def high_priority(request): + ... +``` +:--- + +*Added in v22.9* diff --git a/src/pt/guide/basics/request.md b/src/pt/guide/basics/request.md new file mode 100644 index 0000000000..13f8e1b066 --- /dev/null +++ b/src/pt/guide/basics/request.md @@ -0,0 +1,232 @@ +# Request + +The `Request` instance contains **a lot** of helpful information available on its parameters. Refer to the [API documentation](https://sanic.readthedocs.io/) for full details. + +## Body + +:::: tabs +::: tab JSON + +**Parameter**: `request.json` +**Description**: The parsed JSON object + +```bash +$ curl localhost:8000 -d '{"foo": "bar"}' +``` + +```python +>>> print(request.json) +{'foo': 'bar'} +``` +::: + +::: tab Raw + +**Parameter**: `request.body` +**Description**: The raw bytes from the request body + +```bash +$ curl localhost:8000 -d '{"foo": "bar"}' +``` + +```python +>>> print(request.body) +b'{"foo": "bar"}' +``` +::: + +::: tab Form + +**Parameter**: `request.form` +**Description**: The form data + +```bash +$ curl localhost:8000 -d 'foo=bar' +``` + +```python +>>> print(request.body) +b'foo=bar' + +>>> print(request.form) +{'foo': ['bar']} + +>>> print(request.form.get("foo")) +bar + +>>> print(request.form.getlist("foo")) +['bar'] +``` + +::: tip FYI :bulb: The `request.form` object is one of a few types that is a dictionary with each value being a list. This is because HTTP allows a single key to be reused to send multiple values. + +Most of the time you will want to use the `.get()` method to access the first element and not a list. If you do want a list of all items, you can use `.getlist()`. ::: + +::: tab Uploaded + +**Parameter**: `request.files` +**Description**: The files uploaded to the server + +```bash +$ curl -F 'my_file=@/path/to/TEST' http://localhost:8000 +``` + +```python +>>> print(request.body) +b'--------------------------cb566ad845ad02d3\r\nContent-Disposition: form-data; name="my_file"; filename="TEST"\r\nContent-Type: application/octet-stream\r\n\r\nhello\n\r\n--------------------------cb566ad845ad02d3--\r\n' + +>>> print(request.files) +{'my_file': [File(type='application/octet-stream', body=b'hello\n', name='TEST')]} + +>>> print(request.files.get("my_file")) +File(type='application/octet-stream', body=b'hello\n', name='TEST') + +>>> print(request.files.getlist("my_file")) +[File(type='application/octet-stream', body=b'hello\n', name='TEST')] +``` +::: tip FYI :bulb: The `request.files` object is one of a few types that is a dictionary with each value being a list. This is because HTTP allows a single key to be reused to send multiple values. + +Most of the time you will want to use the `.get()` method to access the first element and not a list. If you do want a list of all items, you can use `.getlist()`. ::: + +:::: + +## Context + +### Request context + +The `request.ctx` object is your playground to store whatever information you need to about the request. + +This is often used to store items like authenticated user details. We will get more into [middleware](./middleware.md) later, but here is a simple example. + +```python +@app.on_request +async def run_before_handler(request): + request.ctx.user = await fetch_user_by_token(request.token) + +@app.route('/hi') +async def hi_my_name_is(request): + return text("Hi, my name is {}".format(request.ctx.user.name)) +``` + +A typical use case would be to store the user object acquired from database in an authentication middleware. Keys added are accessible to all later middleware as well as the handler over the duration of the request. + +Custom context is reserved for applications and extensions. Sanic itself makes no use of it. + +### Connection context + +---:1 + +Often times your API will need to serve multiple concurrent (or consecutive) requests to the same client. This happens, for example, very often with progressive web apps that need to query multiple endpoints to get data. + +The HTTP protocol calls for an easing of overhead time caused by the connection with the use of [keep alive headers](../deployment/configuration.md#keep-alive-timeout). + +When multiple requests share a single connection, Sanic provides a context object to allow those requests to share state. + +:--:1 +```python +@app.on_request +async def increment_foo(request): + if not hasattr(request.conn_info.ctx, "foo"): + request.conn_info.ctx.foo = 0 + request.conn_info.ctx.foo += 1 + +@app.get("/") +async def count_foo(request): + return text(f"request.conn_info.ctx.foo={request.conn_info.ctx.foo}") +``` + +```bash +$ curl localhost:8000 localhost:8000 localhost:8000 +request.conn_info.ctx.foo=1 +request.conn_info.ctx.foo=2 +request.conn_info.ctx.foo=3 +``` +:--- + +## Parameters + +---:1 Values that are extracted from the path are injected into the handler as parameters, or more specifically as keyword arguments. There is much more detail about this in the [Routing section](./routing.md). :--:1 +```python +@app.route('/tag/') +async def tag_handler(request, tag): + return text("Tag - {}".format(tag)) +``` +:--- + + +## Arguments + +There are two attributes on the `request` instance to get query parameters: + +- `request.args` +- `request.query_args` + +```bash +$ curl http://localhost:8000\?key1\=val1\&key2\=val2\&key1\=val3 +``` + +```python +>>> print(request.args) +{'key1': ['val1', 'val3'], 'key2': ['val2']} + +>>> print(request.args.get("key1")) +val1 + +>>> print(request.args.getlist("key1")) +['val1', 'val3'] + +>>> print(request.query_args) +[('key1', 'val1'), ('key2', 'val2'), ('key1', 'val3')] + +>>> print(request.query_string) +key1=val1&key2=val2&key1=val3 + +``` + +::: tip FYI :bulb: The `request.args` object is one of a few types that is a dictionary with each value being a list. This is because HTTP allows a single key to be reused to send multiple values. + +Most of the time you will want to use the `.get()` method to access the first element and not a list. If you do want a list of all items, you can use `.getlist()`. ::: + +## Current request getter + +Sometimes you may find that you need access to the current request in your application in a location where it is not accessible. A typical example might be in a `logging` format. You can use `Request.get_current()` to fetch the current request (if any). + +```python +import logging + +from sanic import Request, Sanic, json +from sanic.exceptions import SanicException +from sanic.log import LOGGING_CONFIG_DEFAULTS + +LOGGING_FORMAT = ( + "%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: " + "%(request_id)s %(request)s %(message)s %(status)d %(byte)d" +) + +old_factory = logging.getLogRecordFactory() + + +def record_factory(*args, **kwargs): + record = old_factory(*args, **kwargs) + record.request_id = "" + + try: + request = Request.get_current() + except SanicException: + ... + else: + record.request_id = str(request.id) + + return record + + +logging.setLogRecordFactory(record_factory) + +LOGGING_CONFIG_DEFAULTS["formatters"]["access"]["format"] = LOGGING_FORMAT + +app = Sanic("Example", log_config=LOGGING_CONFIG_DEFAULTS) +``` + +In this example, we are adding the `request.id` to every access log message. + +*Added in v22.6* diff --git a/src/pt/guide/basics/response.md b/src/pt/guide/basics/response.md new file mode 100644 index 0000000000..a637ecba16 --- /dev/null +++ b/src/pt/guide/basics/response.md @@ -0,0 +1,217 @@ +# Response + +All [handlers](./handlers.md)* **must** return a response object, and [middleware](./middleware.md) may optionally return a response object. + +To clarify that statement: +- unless the handler is a streaming endpoint, the return value must be an instance of `sanic.HTTPResponse` (to learn more about this exception see [streaming responses](../advanced/streaming.md#response-streaming)) +- if a middleware returns a response object, that will be used instead of whatever the handler would do (see [middleware](./middleware.md) to learn more) + +A most basic handler would look like the following. The `HTTPResponse` object will allow you to set the status, body, and headers to be returned to the client. + +```python +from sanic import HTTPResponse, Sanic + +app = Sanic("TestApp") + + +@app.route("") +def handler(_): + return HTTPResponse() +``` + +However, usually it is easier to use one of the convenience methods discussed below. + + +## Methods + +The easiest way to generate a response object is to use one of the nine (9) convenience methods. + +:::: tabs + +::: tab Text + +**Default Content-Type**: `text/plain; charset=utf-8` +**Description**: Returns plain text + +```python +from sanic.response import text + +@app.route("/") +async def handler(request): + return text("Hi 😎") +``` +::: +::: tab HTML + +**Default Content-Type**: `text/html; charset=utf-8` +**Description**: Returns an HTML document + +```python +from sanic.response import html + +@app.route("/") +async def handler(request): + return html('
Hi 😎
') +``` +::: +::: tab JSON + +**Default Content-Type**: `application/json` +**Description**: Returns a JSON document + +```python +from sanic.response import json + +@app.route("/") +async def handler(request): + return json({"foo": "bar"}) +``` + +By default, Sanic ships with [`ujson`](https://github.com/ultrajson/ultrajson) as its JSON encoder of choice. It is super simple to change this if you want. + +```python +from orjson import dumps + +json({"foo": "bar"}, dumps=dumps) +``` + +If `ujson` is not installed, it will fall back to the standard library `json` module. + +You may additionally declare which implementation to use globally across your application at initialization: + +```python +from orjson import dumps + +app = Sanic(..., dumps=dumps) +``` +::: +::: tab File + +**Default Content-Type**: N/A +**Description**: Returns a file + + +```python +from sanic.response import file + +@app.route("/") +async def handler(request): + return await file("/path/to/whatever.png") +``` + +Sanic will examine the file, and try and guess its mime type and use an appropriate value for the content type. You could be explicit, if you would like: + +```python +file("/path/to/whatever.png", mime_type="image/png") +``` + +You can also choose to override the file name: + +```python +file("/path/to/whatever.png", filename="super-awesome-incredible.png") +``` +::: +::: tab "File Streaming" + +**Default Content-Type**: N/A +**Description**: Streams a file to a client, useful when streaming large files, like a video + +```python +from sanic.response import file_stream + +@app.route("/") +async def handler(request): + return await file_stream("/path/to/whatever.mp4") +``` + +Like the `file()` method, `file_stream()` will attempt to determine the mime type of the file. ::: +::: tab Raw + +**Default Content-Type**: `application/octet-stream` +**Description**: Send raw bytes without encoding the body + +```python +from sanic.response import raw + +@app.route("/") +async def handler(request): + return raw(b"raw bytes") +``` +::: +::: tab Redirect + +**Default Content-Type**: `text/html; charset=utf-8` +**Description**: Send a `302` response to redirect the client to a different path + +```python +from sanic.response import redirect + +@app.route("/") +async def handler(request): + return redirect("/login") +``` + +::: +::: tab Empty + +**Default Content-Type**: N/A +**Description**: For responding with an empty message as defined by [RFC 2616](https://tools.ietf.org/search/rfc2616#section-7.2.1) + +```python +from sanic.response import empty + +@app.route("/") +async def handler(request): + return empty() +``` + +Defaults to a `204` status. ::: +:::: + +## Default status + +The default HTTP status code for the response is `200`. If you need to change it, it can be done by the response method. + + +```python +@app.post("/") +async def create_new(request): + new_thing = await do_create(request) + return json({"created": True, "id": new_thing.thing_id}, status=201) +``` + +::: new NEW in v22.12 +## Returning JSON data + +Starting in v22.12, When you use the `sanic.json` convenience method, it will return a subclass of `HTTPResponse` called `JSONResponse`. This object will have several convenient methods available to modify common JSON body. + +```python +from sanic import json + +resp = json(...) +``` + +- `resp.set_body()` - Set the body of the JSON object to the value passed +- `resp.append()` - Append a value to the body like `list.append` (only works if the root JSON is an array) +- `resp.extend()` - Extend a value to the body like `list.extend` (only works if the root JSON is an array) +- `resp.update()` - Update the body with a value like `dict.update` (only works if the root JSON is an object) +- `resp.pop()` - Pop a value like `list.pop` or `dict.pop` (only works if the root JSON is an array or an object) + +::: warning The raw Python object is stored on the `JSONResponse` object as `raw_body`. While it is safe to overwrite this value with a new one, you should **not** attempt to mutate it. You should instead use the methods listed above. + +```python +resp = json({"foo": "bar"}) + +# This is OKAY +resp.raw_body = {"foo": "bar", "something": "else"} + +# This is better +resp.set_body({"foo": "bar", "something": "else"}) + +# This is also works well +resp.update({"something": "else"}) + +# This is NOT OKAY +resp.raw_body.update({"something": "else"}) +``` +::: diff --git a/src/pt/guide/basics/routing.md b/src/pt/guide/basics/routing.md new file mode 100644 index 0000000000..38ee1e8ff0 --- /dev/null +++ b/src/pt/guide/basics/routing.md @@ -0,0 +1,757 @@ +# Routing + +---:1 + +So far we have seen a lot of this decorator in different forms. + +But what is it? And how do we use it? :--:1 +```python +@app.route("/stairway") +... + +@app.get("/to") +... + +@app.post("/heaven") +... +``` +:--- + +## Adding a route + +---:1 + +The most basic way to wire up a handler to an endpoint is with `app.add_route()`. + +See [API docs](https://sanic.readthedocs.io/en/stable/sanic/api_reference.html#sanic.app.Sanic.url_for) for more details. :--:1 +```python +async def handler(request): + return text("OK") + +app.add_route(handler, "/test") +``` +:--- + +---:1 + +By default, routes are available as an HTTP `GET` call. You can change a handler to respond to one or more HTTP methods. :--:1 +```python +app.add_route( + handler, + '/test', + methods=["POST", "PUT"], +) +``` +:--- + +---:1 + +Using the decorator syntax, the previous example is identical to this. :--:1 +```python +@app.route('/test', methods=["POST", "PUT"]) +async def handler(request): + return text('OK') +``` +:--- + +## HTTP methods + +Each of the standard HTTP methods has a convenience decorator. + +:::: tabs +::: tab GET + +```python +@app.get('/test') +async def handler(request): + return text('OK') +``` + +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET) ::: +::: tab POST + +```python +@app.post('/test') +async def handler(request): + return text('OK') +``` + +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) ::: +::: tab PUT + +```python +@app.put('/test') +async def handler(request): + return text('OK') +``` + +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT) ::: +::: tab PATCH + +```python +@app.patch('/test') +async def handler(request): + return text('OK') +``` + +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH) ::: +::: tab DELETE + +```python +@app.delete('/test') +async def handler(request): + return text('OK') +``` + +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE) ::: +::: tab HEAD + +```python +@app.head('/test') +async def handler(request): + return empty() +``` + +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD) ::: +::: tab OPTIONS + +```python +@app.options('/test') +async def handler(request): + return empty() +``` + +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS) ::: +:::: + +::: warning By default, Sanic will **only** consume the incoming request body on non-safe HTTP methods (`POST`, `PUT`, `PATCH`). If you want to receive data in the HTTP request on any other method, you will need to do one of the following two options: + +**Option #1 - Tell Sanic to consume the body using `ignore_body`** +```python +@app.delete("/path", ignore_body=False) +async def handler(_): + ... +``` + +**Option #2 - Manually consume the body in the handler using `receive_body`** +```python +@app.delete("/path") +async def handler(request: Request): + await request.receive_body() +``` +::: + +## Path parameters + +---:1 + +Sanic allows for pattern matching, and for extracting values from URL paths. These parameters are then injected as keyword arguments in the route handler. :--:1 +```python +@app.get("/tag/") +async def tag_handler(request, tag): + return text("Tag - {}".format(tag)) +``` +:--- + +---:1 + +You can declare a type for the parameter. This will be enforced when matching, and also will type cast the variable. :--:1 +```python +@app.get("/foo/") +async def uuid_handler(request, foo_id: UUID): + return text("UUID - {}".format(foo_id)) +``` +:--- + +### Supported types + +:::: tabs + +::: tab str + +```python +@app.route("/path/to/") +async def handler(request, foo: str): + ... +``` +**Regular expression applied**: `r"[^/]+")` +**Cast type**: `str` +**Example matches**: +- `/path/to/Bob` +- `/path/to/Python%203` + +Beginning in v22.3 `str` will *not* match on empty strings. See `strorempty` for this behavior. + +::: +::: tab strorempty + +```python +@app.route("/path/to/") +async def handler(request, foo: str): + ... +``` +**Regular expression applied**: `r"[^/]*")` +**Cast type**: `str` +**Example matches**: +- `/path/to/Bob` +- `/path/to/Python%203` +- `/path/to/` + +Unlike the `str` path parameter type, `strorempty` can also match on an empty string path segment. + +*Added in v22.3* ::: +::: tab int + +```python +@app.route("/path/to/") +async def handler(request, foo: int): + ... +``` +**Regular expression applied**: `r"-?\d+")` +**Cast type**: `int` +**Example matches**: +- `/path/to/10` +- `/path/to/-10` + +_Does not match float, hex, octal, etc_ ::: +::: tab float + +```python +@app.route("/path/to/") +async def handler(request, foo: float): + ... +``` +**Regular expression applied**: `r"-?(?:\d+(?:\.\d*)?|\.\d+)")` +**Cast type**: `float` +**Example matches**: +- `/path/to/10` +- `/path/to/-10` +- `/path/to/1.5` + +::: +::: tab alpha + +```python +@app.route("/path/to/") +async def handler(request, foo: str): + ... +``` +**Regular expression applied**: `r"[A-Za-z]+")` +**Cast type**: `str` +**Example matches**: +- `/path/to/Bob` +- `/path/to/Python` + +_Does not match a digit, or a space or other special character_ ::: +::: tab slug + +```python +@app.route("/path/to/") +async def handler(request, article: str): + ... +``` +**Regular expression applied**: `r"[a-z0-9]+(?:-[a-z0-9]+)*")` +**Cast type**: `str` +**Example matches**: +- `/path/to/some-news-story` +- `/path/to/or-has-digits-123` + +*Added in v21.6* ::: +::: tab path + +```python +@app.route("/path/to/") +async def handler(request, foo: str): + ... +``` +**Regular expression applied**: `r"[^/].*?")` +**Cast type**: `str` +**Example matches**: +- `/path/to/hello` +- `/path/to/hello.txt` +- `/path/to/hello/world.txt` + +::: warning Because this will match on `/`, you should be careful and thoroughly test your patterns that use `path` so they do not capture traffic intended for another endpoint. ::: +::: tab ymd + +```python +@app.route("/path/to/") +async def handler(request, foo: datetime.date): + ... +``` +**Regular expression applied**: `r"^([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))"` +**Cast type**: `datetime.date` +**Example matches**: +- `/path/to/2021-03-28` ::: + +::: tab uuid + +```python +@app.route("/path/to/") +async def handler(request, foo: UUID): + ... +``` +**Regular expression applied**: `r"[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}"` +**Cast type**: `UUID` +**Example matches**: +- `/path/to/123a123a-a12a-1a1a-a1a1-1a12a1a12345` + +::: + +::: tab ext + +```python +@app.route("/path/to/") +async def handler(request, foo: str, ext: str): + ... +``` +**Regular expression applied**: n/a +**Cast type**: *varies* +**Example matches**: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ definition + + example + + filename + + extension +
+ \ + + page.txt + + "page" + + "txt" +
+ \ + + cat.jpg + + "cat" + + "jpg" +
+ \ + + + png\ + + gif\ + + svg> | cat.jpg | "cat" | "jpg" +
+ + + 123.txt + + 123 + + "txt" +
+ + + + png\ + + gif\ + + svg> | 123.svg | 123 | "svg" +
+ + + 3.14.tar.gz + + 3.14 + + "tar.gz" +
+ +File extensions can be matched using the special `ext` parameter type. It uses a special format that allows you to specify other types of parameter types as the file name, and one or more specific extensions as shown in the example table above. + +It does *not* support the `path` parameter type. + +*Added in v22.3* ::: + +::: tab regex + +```python +@app.route(r"/path/to/") +async def handler(request, foo: str): + ... +``` +**Regular expression applied**: _whatever you insert_ +**Cast type**: `str` +**Example matches**: +- `/path/to/2021-01-01` + +This gives you the freedom to define specific matching patterns for your use case. + +In the example shown, we are looking for a date that is in `YYYY-MM-DD` format. + +:::: + +### Regex Matching + + + +More often than not, compared with complex routing, the above example is too simple, and we use a completely different routing matching pattern, so here we will explain the advanced usage of regex matching in detail. + +Sometimes, you want to match a part of a route: + +```text +/image/123456789.jpg +``` + +If you wanted to match the file pattern, but only capture the numeric portion, you need to do some regex fun 😄: + +```python +app.route(r"/image/\d+)\.jpg>") +``` + +Further, these should all be acceptable: + +```python +@app.get(r"/") # matching on the full pattern +@app.get(r"/") # defining a single matching group +@app.get(r"/[a-z]{3}).txt>") # defining a single named matching group +@app.get(r"/[a-z]{3}).(?:txt)>") # defining a single named matching group, with one or more non-matching groups +``` + +Also, if using a named matching group, it must be the same as the segment label. + +```python +@app.get(r"/\d+).jpg>") # OK +@app.get(r"/\d+).jpg>") # NOT OK +``` + +For more regular usage methods, please refer to [Regular expression operations](https://docs.python.org/3/library/re.html) + +## Generating a URL + +---:1 + +Sanic provides a method to generate URLs based on the handler method name: `app.url_for()`. This is useful if you want to avoid hardcoding url paths into your app; instead, you can just reference the handler name. :--:1 +```python +@app.route('/') +async def index(request): + # generate a URL for the endpoint `post_handler` + url = app.url_for('post_handler', post_id=5) + + # Redirect to `/posts/5` + return redirect(url) + +@app.route('/posts/') +async def post_handler(request, post_id): + ... +``` +:--- + +---:1 + +You can pass any arbitrary number of keyword arguments. Anything that is _not_ a request parameter will be implemented as a part of the query string. :--:1 +```python +>>> app.url_for( + "post_handler", + post_id=5, + arg_one="one", + arg_two="two", +) +'/posts/5?arg_one=one&arg_two=two' +``` +:--- + +---:1 + +Also supported is passing multiple values for a single query key. :--:1 +```python +>>> app.url_for( + "post_handler", + post_id=5, + arg_one=["one", "two"], +) +'/posts/5?arg_one=one&arg_one=two' +``` +:--- + +### Special keyword arguments + +See [API Docs]() for more details. + +```python +>>> app.url_for("post_handler", post_id=5, arg_one="one", _anchor="anchor") +'/posts/5?arg_one=one#anchor' + +# _external requires you to pass an argument _server or set SERVER_NAME in app.config if not url will be same as no _external +>>> app.url_for("post_handler", post_id=5, arg_one="one", _external=True) +'//server/posts/5?arg_one=one' + +# when specifying _scheme, _external must be True +>>> app.url_for("post_handler", post_id=5, arg_one="one", _scheme="http", _external=True) +'http://server/posts/5?arg_one=one' + +# you can pass all special arguments at once +>>> app.url_for("post_handler", post_id=5, arg_one=["one", "two"], arg_two=2, _anchor="anchor", _scheme="http", _external=True, _server="another_server:8888") +'http://another_server:8888/posts/5?arg_one=one&arg_one=two&arg_two=2#anchor' +``` + +### Customizing a route name + +---:1 + +A custom route name can be used by passing a `name` argument while registering the route. :--:1 +```python +@app.get("/get", name="get_handler") +def handler(request): + return text("OK") +``` +:--- + +---:1 + +Now, use this custom name to retrieve the URL :--:1 +```python +>>> app.url_for("get_handler", foo="bar") +'/get?foo=bar' +``` +:--- + +## Websockets routes + +---:1 + +Websocket routing works similar to HTTP methods. :--:1 +```python +async def handler(request, ws): + message = "Start" + while True: + await ws.send(message) + message = await ws.recv() + +app.add_websocket_route(handler, "/test") +``` +:--- + +---:1 + +It also has a convenience decorator. :--:1 +```python +@app.websocket("/test") +async def handler(request, ws): + message = "Start" + while True: + await ws.send(message) + message = await ws.recv() +``` +:--- + +Read the [websockets section](/guide/advanced/websockets.md) to learn more about how they work. + +## Strict slashes + + +---:1 + +Sanic routes can be configured to strictly match on whether or not there is a trailing slash: `/`. This can be configured at a few levels and follows this order of precedence: + +1. Route +2. Blueprint +3. BlueprintGroup +4. Application + +:--:1 +```python +# provide default strict_slashes value for all routes +app = Sanic(__file__, strict_slashes=True) +``` + +```python +# overwrite strict_slashes value for specific route +@app.get("/get", strict_slashes=False) +def handler(request): + return text("OK") +``` + +```python +# it also works for blueprints +bp = Blueprint(__file__, strict_slashes=True) + +@bp.get("/bp/get", strict_slashes=False) +def handler(request): + return text("OK") +``` + +```python +bp1 = Blueprint(name="bp1", url_prefix="/bp1") +bp2 = Blueprint( + name="bp2", + url_prefix="/bp2", + strict_slashes=False, +) + +# This will enforce strict slashes check on the routes +# under bp1 but ignore bp2 as that has an explicitly +# set the strict slashes check to false +group = Blueprint.group([bp1, bp2], strict_slashes=True) +``` +:--- + +## Static files + +---:1 + +In order to serve static files from Sanic, use `app.static()`. + +The order of arguments is important: + +1. Route the files will be served from +2. Path to the files on the server + +See [API docs]() for more details. :--:1 +```python +app.static("/static", "/path/to/directory") +``` +:--- + +---:1 + +You can also serve individual files. :--:1 +```python +app.static("/", "/path/to/index.html") +``` +:--- + +---:1 + +It is also sometimes helpful to name your endpoint :--:1 +```python +app.static( + "/user/uploads", + "/path/to/uploads", + name="uploads", +) +``` +:--- + +---:1 + +Retrieving the URLs works similar to handlers. But, we can also add the `filename` argument when we need a specific file inside a directory. :--:1 +```python +>>> app.url_for( + "static", + name="static", + filename="file.txt", +) +'/static/file.txt' +``` +```python +>>> app.url_for( + "static", + name="uploads", + filename="image.png", +) +'/user/uploads/image.png' + +``` +:--- + +::: tip If you are going to have multiple `static()` routes, then it is *highly* suggested that you manually name them. This will almost certainly alleviate potential hard to discover bugs. + +```python +app.static("/user/uploads", "/path/to/uploads", name="uploads") +app.static("/user/profile", "/path/to/profile", name="profile_pics") +``` +::: + +## Route context + +---:1 When a route is defined, you can add any number of keyword arguments with a `ctx_` prefix. These values will be injected into the route `ctx` object. :--:1 +```python +@app.get("/1", ctx_label="something") +async def handler1(request): + ... + +@app.get("/2", ctx_label="something") +async def handler2(request): + ... + +@app.get("/99") +async def handler99(request): + ... + +@app.on_request +async def do_something(request): + if request.route.ctx.label == "something": + ... +``` +:--- *Added in v21.12* diff --git a/src/pt/guide/basics/tasks.md b/src/pt/guide/basics/tasks.md new file mode 100644 index 0000000000..effad19130 --- /dev/null +++ b/src/pt/guide/basics/tasks.md @@ -0,0 +1,100 @@ +# Background tasks + +## Creating Tasks +It is often desirable and very convenient to make usage of [tasks](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task) in async Python. Sanic provides a convenient method to add tasks to the currently **running** loop. It is somewhat similar to `asyncio.create_task`. For adding tasks before the 'App' loop is running, see next section. + +```python +async def notify_server_started_after_five_seconds(): + await asyncio.sleep(5) + print('Server successfully started!') + +app.add_task(notify_server_started_after_five_seconds()) +``` + +---:1 + +Sanic will attempt to automatically inject the app, passing it as an argument to the task. :--:1 +```python +async def auto_inject(app): + await asyncio.sleep(5) + print(app.name) + +app.add_task(auto_inject) +``` +:--- + +---:1 + +Or you can pass the `app` argument explicitly. :--:1 +```python +async def explicit_inject(app): + await asyncio.sleep(5) + print(app.name) + +app.add_task(explicit_inject(app)) +``` +:--- + +## Adding tasks before `app.run` + +It is possible to add background tasks before the App is run ie. before `app.run`. To add a task before the App is run, it is recommended to not pass the coroutine object (ie. one created by calling the `async` callable), but instead just pass the callable and Sanic will create the coroutine object on **each worker**. Note: the tasks that are added such are run as `before_server_start` jobs and thus run on every worker (and not in the main process). This has certain consequences, please read [this comment](https://github.com/sanic-org/sanic/issues/2139#issuecomment-868993668) on [this issue](https://github.com/sanic-org/sanic/issues/2139) for further details. + +To add work on the main process, consider adding work to [`@app.main_process_start`](./listeners.md). Note: the workers won't start until this work is completed. + +---:1 + +Example to add a task before `app.run` :---:1 +```python +async def slow_work(): + ... + +async def even_slower(num): + ... + +app = Sanic(...) +app.add_task(slow_work) # Note: we are passing the callable and not coroutine object ... +app.add_task(even_slower(10)) # ... or we can call the function and pass the coroutine. +app.run(...) +``` + +## Named tasks + +_This is only supported in Python 3.8+_ + +---:1 When creating a task, you can ask Sanic to keep track of it for you by providing a `name`. + +:--:1 +```python +app.add_task(slow_work, name="slow_task") +``` +:--- + +---:1 You can now retrieve that task instance from anywhere in your application using `get_task`. + +:--:1 +```python +task = app.get_task("slow_task") +``` +:--- + +---:1 If that task needs to be cancelled, you can do that with `cancel_task`. Make sure that you `await` it. + +:--:1 +```python +await app.cancel_task("slow_task") +``` +:--- + +---:1 All registered tasks can be found in the `app.tasks` property. To prevent cancelled tasks from filling up, you may want to run `app.purge_tasks` that will clear out any completed or cancelled tasks. + +:--:1 +```python +app.purge_tasks() +``` +:--- + +This pattern can be particularly useful with `websockets`: + +```python async def receiver(ws): while True: message = await ws.recv() if not message: break print(f"Received: {message}") + +@app.websocket("/feed") async def feed(request, ws): task_name = f"receiver:{request.id}" request.app.add_task(receiver(ws), name=task_name) try: while True: await request.app.event("my.custom.event") await ws.send("A message") finally: # When the websocket closes, let's cleanup the task await request.app.cancel_task(task_name) request.app.purge_tasks() ::: *Added in v21.12* diff --git a/src/pt/guide/best-practices/README.md b/src/pt/guide/best-practices/README.md new file mode 100644 index 0000000000..874ca7f3c6 --- /dev/null +++ b/src/pt/guide/best-practices/README.md @@ -0,0 +1 @@ +# Best Practices diff --git a/src/pt/guide/best-practices/blueprints.md b/src/pt/guide/best-practices/blueprints.md new file mode 100644 index 0000000000..6de1ceb47e --- /dev/null +++ b/src/pt/guide/best-practices/blueprints.md @@ -0,0 +1,369 @@ +# Blueprints + +## Overview + +Blueprints are objects that can be used for sub-routing within an application. Instead of adding routes to the application instance, blueprints define similar methods for adding routes, which are then registered with the application in a flexible and pluggable manner. + +Blueprints are especially useful for larger applications, where your application logic can be broken down into several groups or areas of responsibility. + +## Creating and registering + +---:1 + +First, you must create a blueprint. It has a very similar API as the `Sanic()` app instance with many of the same decorators. :--:1 +```python +# ./my_blueprint.py +from sanic.response import json +from sanic import Blueprint + +bp = Blueprint("my_blueprint") + +@bp.route("/") +async def bp_root(request): + return json({"my": "blueprint"}) +``` +:--- + + +---:1 + +Next, you register it with the app instance. :--:1 +```python +from sanic import Sanic +from my_blueprint import bp + +app = Sanic(__name__) +app.blueprint(bp) +``` +:--- + +Blueprints also have the same `websocket()` decorator and `add_websocket_route` method for implementing websockets. + +---:1 + +Beginning in v21.12, a Blueprint may be registered before or after adding objects to it. Previously, only objects attached to the Blueprint at the time of registration would be loaded into application instance. :--:1 +```python +app.blueprint(bp) + +@bp.route("/") +async def bp_root(request): + ... +``` +:--- +## Copying + +---:1 Blueprints along with everything that is attached to them can be copied to new instances using the `copy()` method. The only required argument is to pass it a new `name`. However, you could also use this to override any of the values from the old blueprint. :--:1 +```python +v1 = Blueprint("Version1", version=1) + +@v1.route("/something") +def something(request): + pass + +v2 = v1.copy("Version2", version=2) + +app.blueprint(v1) +app.blueprint(v2) +``` + +``` +Available routes: +/v1/something +/v2/something + +``` +:--- + +*Added in v21.9* + +## Blueprint groups + +Blueprints may also be registered as part of a list or tuple, where the registrar will recursively cycle through any sub-sequences of blueprints and register them accordingly. The Blueprint.group method is provided to simplify this process, allowing a ‘mock’ backend directory structure mimicking what’s seen from the front end. Consider this (quite contrived) example: + +```text +api/ +├──content/ +│ ├──authors.py +│ ├──static.py +│ └──__init__.py +├──info.py +└──__init__.py +app.py +``` + +---:1 + +#### First blueprint + +:--:1 +```python +# api/content/authors.py +from sanic import Blueprint + +authors = Blueprint("content_authors", url_prefix="/authors") +``` +:--- + +---:1 + +#### Second blueprint + +:--:1 +```python +# api/content/static.py +from sanic import Blueprint + +static = Blueprint("content_static", url_prefix="/static") +``` +:--- + +---:1 + +#### Blueprint group + +:--:1 +```python +# api/content/__init__.py +from sanic import Blueprint +from .static import static +from .authors import authors + +content = Blueprint.group(static, authors, url_prefix="/content") +``` +:--- + +---:1 + +#### Third blueprint + +:--:1 +```python +# api/info.py +from sanic import Blueprint + +info = Blueprint("info", url_prefix="/info") +``` +:--- + +---:1 + +#### Another blueprint group + +:--:1 +```python +# api/__init__.py +from sanic import Blueprint +from .content import content +from .info import info + +api = Blueprint.group(content, info, url_prefix="/api") +``` +:--- + +---:1 + +#### Main server + +All blueprints are now registered + +:--:1 +```python +# app.py +from sanic import Sanic +from .api import api + +app = Sanic(__name__) +app.blueprint(api) +``` +:--- + +## Middleware + +---:1 + +Blueprints can also have middleware that is specifically registered for its endpoints only. :--:1 +```python +@bp.middleware +async def print_on_request(request): + print("I am a spy") + +@bp.middleware("request") +async def halt_request(request): + return text("I halted the request") + +@bp.middleware("response") +async def halt_response(request, response): + return text("I halted the response") +``` +:--- + +---:1 + +Similarly, using blueprint groups, it is possible to apply middleware to an entire group of nested blueprints. :--:1 +```python +bp1 = Blueprint("bp1", url_prefix="/bp1") +bp2 = Blueprint("bp2", url_prefix="/bp2") + +@bp1.middleware("request") +async def bp1_only_middleware(request): + print("applied on Blueprint : bp1 Only") + +@bp1.route("/") +async def bp1_route(request): + return text("bp1") + +@bp2.route("/") +async def bp2_route(request, param): + return text(param) + +group = Blueprint.group(bp1, bp2) + +@group.middleware("request") +async def group_middleware(request): + print("common middleware applied for both bp1 and bp2") + +# Register Blueprint group under the app +app.blueprint(group) +``` +:--- + +## Exceptions + +---:1 + +Just like other [exception handling](./exceptions.md), you can define blueprint specific handlers. :--:1 +```python +@bp.exception(NotFound) +def ignore_404s(request, exception): + return text("Yep, I totally found the page: {}".format(request.url)) +``` +:--- + +## Static files + +---:1 + +Blueprints can also have their own static handlers :--:1 +```python +bp = Blueprint("bp", url_prefix="/bp") +bp.static("/web/path", "/folder/to/serve") +bp.static("/web/path", "/folder/to/server", name="uploads") +``` +:--- + +---:1 + +Which can then be retrieved using `url_for()`. See [routing](/guide/basics/routing.md) for more information. :--:1 +```python +>>> print(app.url_for("static", name="bp.uploads", filename="file.txt")) +'/bp/web/path/file.txt' +``` +:--- + +## Listeners + +---:1 + +Blueprints can also implement [listeners](/guide/basics/listeners.md). :--:1 +```python +@bp.listener("before_server_start") +async def before_server_start(app, loop): + ... + +@bp.listener("after_server_stop") +async def after_server_stop(app, loop): + ... +``` +:--- + +## Versioning + +As discussed in the [versioning section](/guide/advanced/versioning.md), blueprints can be used to implement different versions of a web API. + +---:1 + +The `version` will be prepended to the routes as `/v1` or `/v2`, etc. :--:1 +```python +auth1 = Blueprint("auth", url_prefix="/auth", version=1) +auth2 = Blueprint("auth", url_prefix="/auth", version=2) +``` +:--- + +---:1 + +When we register our blueprints on the app, the routes `/v1/auth` and `/v2/auth` will now point to the individual blueprints, which allows the creation of sub-sites for each API version. :--:1 +```python +from auth_blueprints import auth1, auth2 + +app = Sanic(__name__) +app.blueprint(auth1) +app.blueprint(auth2) +``` +:--- + +---:1 + +It is also possible to group the blueprints under a `BlueprintGroup` entity and version multiple of them together at the same time. :--:1 +```python +auth = Blueprint("auth", url_prefix="/auth") +metrics = Blueprint("metrics", url_prefix="/metrics") + +group = Blueprint.group(auth, metrics, version="v1") + +# This will provide APIs prefixed with the following URL path +# /v1/auth/ and /v1/metrics +``` +:--- + +## Composable + +A `Blueprint` may be registered to multiple groups, and each of `BlueprintGroup` itself could be registered and nested further. This creates a limitless possibility `Blueprint` composition. + +*Added in v21.6* ---:1 Take a look at this example and see how the two handlers are actually mounted as five (5) distinct routes. :--:1 +```python +app = Sanic(__name__) +blueprint_1 = Blueprint("blueprint_1", url_prefix="/bp1") +blueprint_2 = Blueprint("blueprint_2", url_prefix="/bp2") +group = Blueprint.group( + blueprint_1, + blueprint_2, + version=1, + version_prefix="/api/v", + url_prefix="/grouped", + strict_slashes=True, +) +primary = Blueprint.group(group, url_prefix="/primary") + + +@blueprint_1.route("/") +def blueprint_1_default_route(request): + return text("BP1_OK") + + +@blueprint_2.route("/") +def blueprint_2_default_route(request): + return text("BP2_OK") + + +app.blueprint(group) +app.blueprint(primary) +app.blueprint(blueprint_1) + +# The mounted paths: +# /api/v1/grouped/bp1/ +# /api/v1/grouped/bp2/ +# /api/v1/primary/grouped/bp1 +# /api/v1/primary/grouped/bp2 +# /bp1 + +``` +:--- + + +## Generating a URL + +When generating a url with `url_for()`, the endpoint name will be in the form: + +```text +{blueprint_name}.{handler_name} +``` diff --git a/src/pt/guide/best-practices/decorators.md b/src/pt/guide/best-practices/decorators.md new file mode 100644 index 0000000000..35a0772086 --- /dev/null +++ b/src/pt/guide/best-practices/decorators.md @@ -0,0 +1,182 @@ +# Decorators + +One of the best ways to create a consistent and DRY web API is to make use of decorators to remove functionality from the handlers, and make it repeatable across your views. + +---:1 + +Therefore, it is very common to see a Sanic view handler with several decorators on it. :--:1 +```python +@app.get("/orders") +@authorized("view_order") +@validate_list_params() +@inject_user() +async def get_order_details(request, params, user): + ... +``` +:--- + + +## Example + +Here is a starter template to help you create decorators. + +In this example, let’s say you want to check that a user is authorized to access a particular endpoint. You can create a decorator that wraps a handler function, checks a request if the client is authorized to access a resource, and sends the appropriate response. +```python +from functools import wraps +from sanic.response import json + +def authorized(): + def decorator(f): + @wraps(f) + async def decorated_function(request, *args, **kwargs): + # run some method that checks the request + # for the client's authorization status + is_authorized = await check_request_for_authorization_status(request) + + if is_authorized: + # the user is authorized. + # run the handler method and return the response + response = await f(request, *args, **kwargs) + return response + else: + # the user is not authorized. + return json({"status": "not_authorized"}, 403) + return decorated_function + return decorator + + +@app.route("/") +@authorized() +async def test(request): + return json({"status": "authorized"}) +``` + +## Templates + +Decorators are **fundamental** to building applications with Sanic. They increase the portability and maintainablity of your code. + +In paraphrasing the Zen of Python: "[decorators] are one honking great idea -- let's do more of those!" + +To make it easier to implement them, here are three examples of copy/pastable code to get you started. + +---:1 + +Don't forget to add these import statements. Although it is *not* necessary, using `@wraps` helps keep some of the metadata of your function intact. [See docs](https://docs.python.org/3/library/functools.html#functools.wraps). Also, we use the `isawaitable` pattern here to allow the route handlers to by regular or asynchronous functions. + +:--:1 + +```python +from inspect import isawaitable +from functools import wraps +``` + +:--- + +### With args + +---:1 + +Often, you will want a decorator that will *always* need arguments. Therefore, when it is implemented you will always be calling it. + +```python +@app.get("/") +@foobar(1, 2) +async def handler(request: Request): + return text("hi") +``` + +:--:1 + +```python +def foobar(arg1, arg2): + def decorator(f): + @wraps(f) + async def decorated_function(request, *args, **kwargs): + + response = f(request, *args, **kwargs) + if isawaitable(response): + response = await response + + return response + + return decorated_function + + return decorator +``` + +:--- + +### Without args + +---:1 + +Sometimes you want a decorator that will not take arguments. When this is the case, it is a nice convenience not to have to call it + +```python +@app.get("/") +@foobar +async def handler(request: Request): + return text("hi") +``` + +:--:1 + +```python +def foobar(func): + def decorator(f): + @wraps(f) + async def decorated_function(request, *args, **kwargs): + + response = f(request, *args, **kwargs) + if isawaitable(response): + response = await response + + return response + + return decorated_function + + return decorator(func) +``` + +:--- + +### With or Without args + +---:1 + +If you want a decorator with the ability to be called or not, you can follow this pattern. Using keyword only arguments is not necessary, but might make implementation simpler. + +```python +@app.get("/") +@foobar(arg1=1, arg2=2) +async def handler(request: Request): + return text("hi") +``` + +```python +@app.get("/") +@foobar +async def handler(request: Request): + return text("hi") +``` + +:--:1 + +```python +def foobar(maybe_func=None, *, arg1=None, arg2=None): + def decorator(f): + @wraps(f) + async def decorated_function(request, *args, **kwargs): + + response = f(request, *args, **kwargs) + if isawaitable(response): + response = await response + + return response + + return decorated_function + + return decorator(maybe_func) if maybe_func else decorator +``` + +:--- diff --git a/src/pt/guide/best-practices/exceptions.md b/src/pt/guide/best-practices/exceptions.md new file mode 100644 index 0000000000..42c2a44936 --- /dev/null +++ b/src/pt/guide/best-practices/exceptions.md @@ -0,0 +1,482 @@ +# Exceptions + +## Using Sanic exceptions + +Sometimes you just need to tell Sanic to halt execution of a handler and send back a status code response. You can raise a `SanicException` for this and Sanic will do the rest for you. + +You can pass an optional `status_code` argument. By default, a SanicException will return an internal server error 500 response. + +```python +from sanic.exceptions import SanicException + +@app.route("/youshallnotpass") +async def no_no(request): + raise SanicException("Something went wrong.", status_code=501) +``` + +Sanic provides a number of standard exceptions. They each automatically will raise the appropriate HTTP status code in your response. [Check the API reference](https://sanic.readthedocs.io/en/latest/sanic/api_reference.html#module-sanic.exceptions) for more details. + +---:1 + +The more common exceptions you _should_ implement yourself include: + +- `InvalidUsage` (400) +- `Unauthorized` (401) +- `Forbidden` (403) +- `NotFound` (404) +- `ServerError` (500) + +:--:1 + +```python +from sanic import exceptions + +@app.route("/login") +async def login(request): + user = await some_login_func(request) + if not user: + raise exceptions.NotFound( + f"Could not find user with username={request.json.username}" + ) + ... +``` + +:--- + +## Exception properties + +All exceptions in Sanic derive from `SanicException`. That class has a few properties on it that assist the developer in consistently reporting their exceptions across an application. + +- `message` +- `status_code` +- `quiet` +- `context` +- `extra` + +All of these properties can be passed to the exception when it is created, but the first three can also be used as class variables as we will see. + +---:1 +### `message` + +The `message` property obviously controls the message that will be displayed as with any other exception in Python. What is particularly useful is that you can set the `message` property on the class definition allowing for easy standardization of language across an application :--:1 +```python +class CustomError(SanicException): + message = "Something bad happened" + +raise CustomError +# or +raise CustomError("Override the default message with something else") +``` +:--- + +---:1 +### `status_code` + +This property is used to set the response code when the exception is raised. This can particularly be useful when creating custom 400 series exceptions that are usually in response to bad information coming from the client. :--:1 +```python +class TeapotError(SanicException): + status_code = 418 + message = "Sorry, I cannot brew coffee" + +raise TeapotError +# or +raise TeapotError(status_code=400) +``` +:--- + +---:1 +### `quiet` + +By default, exceptions will be output by Sanic to the `error_logger`. Sometimes this may not be desirable, especially if you are using exceptions to trigger events in exception handlers (see [the following section](./exceptions.md#handling)). You can suppress the log output using `quiet=True`. :--:1 +```python +class SilentError(SanicException): + message = "Something happened, but not shown in logs" + quiet = True + +raise SilentError +# or +raise InvalidUsage("blah blah", quiet=True) +``` +:--- + +---:1 Sometimes while debugging you may want to globally ignore the `quiet=True` property. You can force Sanic to log out all exceptions regardless of this property using `NOISY_EXCEPTIONS` + +*Added in v21.12* :--:1 +```python +app.config.NOISY_EXCEPTIONS = True +``` +:--- + +---:1 +### `extra` + +See [contextual exceptions](./exceptions.md#contextual-exceptions) + +*Added in v21.12* :--:1 +```python +raise SanicException(..., extra={"name": "Adam"}) +``` +:--- + +---:1 +### `context` + +See [contextual exceptions](./exceptions.md#contextual-exceptions) + +*Added in v21.12* :--:1 +```python +raise SanicException(..., context={"foo": "bar"}) +``` +:--- + + +## Handling + +Sanic handles exceptions automatically by rendering an error page, so in many cases you don't need to handle them yourself. However, if you would like more control on what to do when an exception is raised, you can implement a handler yourself. + +Sanic provides a decorator for this, which applies to not only the Sanic standard exceptions, but **any** exception that your application might throw. + +---:1 + +The easiest method to add a handler is to use `@app.exception()` and pass it one or more exceptions. + +:--:1 + +```python +from sanic.exceptions import NotFound + +@app.exception(NotFound, SomeCustomException) +async def ignore_404s(request, exception): + return text("Yep, I totally found the page: {}".format(request.url)) +``` + +:--- + +---:1 + +You can also create a catchall handler by catching `Exception`. + +:--:1 + +```python +@app.exception(Exception) +async def catch_anything(request, exception): + ... +``` + +:--- + +---:1 + +You can also use `app.error_handler.add()` to add error handlers. + +:--:1 + +```python +async def server_error_handler(request, exception): + return text("Oops, server error", status=500) + +app.error_handler.add(Exception, server_error_handler) +``` + +:--- + +## Built-in error handling + +Sanic ships with three formats for exceptions: HTML, JSON, and text. You can see examples of them below in the [Fallback handler](#fallback-handler) section. + +---:1 You can control _per route_ which format to use with the `error_format` keyword argument. + +*Added in v21.9* :--:1 +```python +@app.request("/", error_format="text") +async def handler(request): + ... +``` + +:--- + + +## Custom error handling + +In some cases, you might want to add some more error handling functionality to what is provided by default. In that case, you can subclass Sanic's default error handler as such: + +```python +from sanic.handlers import ErrorHandler + +class CustomErrorHandler(ErrorHandler): + def default(self, request, exception): + ''' handles errors that have no error handlers assigned ''' + # You custom error handling logic... + return super().default(request, exception) + +app.error_handler = CustomErrorHandler() +``` + +## Fallback handler + +Sanic comes with three fallback exception handlers: + +1. HTML (*default*) +2. Text +3. JSON + +These handlers present differing levels of detail depending upon whether your application is in [debug mode](/guide/deployment/development.md) or not. + +### HTML + +```python +app.config.FALLBACK_ERROR_FORMAT = "html" +``` + +---:1 + +```python +app.config.DEBUG = True +``` + +![Error](~@assets/images/error-html-debug.png) + +:--:1 + +```python +app.config.DEBUG = False +``` + +![Error](~@assets/images/error-html-no-debug.png) + +:--- + +### Text + +```python +app.config.FALLBACK_ERROR_FORMAT = "text" +``` + +---:1 + +```python +app.config.DEBUG = True +``` + +```bash +$ curl localhost:8000/exc -i +HTTP/1.1 500 Internal Server Error +content-length: 590 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +⚠️ 500 — Internal Server Error +============================== +That time when that thing broke that other thing? That happened. + +ServerError: That time when that thing broke that other thing? That happened. while handling path /exc +Traceback of __BASE__ (most recent call last): + + ServerError: That time when that thing broke that other thing? That happened. + File /path/to/sanic/app.py, line 986, in handle_request + response = await response + + File /path/to/server.py, line 222, in exc + raise ServerError( +``` + +:--:1 + +```python +app.config.DEBUG = False +``` + +```bash +$ curl localhost:8000/exc -i +HTTP/1.1 500 Internal Server Error +content-length: 134 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +⚠️ 500 — Internal Server Error +============================== +That time when that thing broke that other thing? That happened. +``` + +:--- + +### JSON + +```python +app.config.FALLBACK_ERROR_FORMAT = "json" +``` + +---:1 + +```python +app.config.DEBUG = True +``` + +```bash +$ curl localhost:8000/exc -i +HTTP/1.1 500 Internal Server Error +content-length: 129 +connection: keep-alive +content-type: application/json + +{ + "description": "Internal Server Error", + "status": 500, + "message": "That time when that thing broke that other thing? That happened.", + "path": "/exc", + "args": {}, + "exceptions": [ + { + "type": "ServerError", + "exception": "That time when that thing broke that other thing? That happened.", + "frames": [ + { + "file": "/path/to/sanic/app.py", + "line": 986, + "name": "handle_request", + "src": "response = await response" + }, + { + "file": "/path/to/server.py", + "line": 222, + "name": "exc", + "src": "raise ServerError(" + } + ] + } + ] +} + + +``` + +:--:1 + +```python +app.config.DEBUG = False +``` + +```bash +$ curl localhost:8000/exc -i +HTTP/1.1 500 Internal Server Error +content-length: 530 +connection: keep-alive +content-type: application/json + +{ + "description": "Internal Server Error", + "status": 500, + "message": "That time when that thing broke that other thing? That happened." +} + +``` + +:--- + +### Auto + +Sanic also provides an option for guessing which fallback option to use. This is still an **experimental feature**. + +```python +app.config.FALLBACK_ERROR_FORMAT = "auto" +``` +## Contextual Exceptions + +Default exception messages that simplify the ability to consistently raise exceptions throughout your application. + +```python +class TeapotError(SanicException): + status_code = 418 + message = "Sorry, I cannot brew coffee" + +raise TeapotError +``` + +But this lacks two things: + +1. A dynamic and predictable message format +2. The ability to add additional context to an error message (more on this in a moment) + +*Added in v21.12* + +### Dynamic and predictable message using `extra` + +Sanic exceptions can be raised using `extra` keyword arguments to provide additional information to a raised exception instance. + +```python +class TeapotError(SanicException): + status_code = 418 + + @property + def message(self): + return f"Sorry {self.extra['name']}, I cannot make you coffee" + +raise TeapotError(extra={"name": "Adam"}) +``` + +The new feature allows the passing of `extra` meta to the exception instance, which can be particularly useful as in the above example to pass dynamic data into the message text. This `extra` info object **will be suppressed** when in `PRODUCTION` mode, but displayed in `DEVELOPMENT` mode. + +---:1 **PRODUCTION** + +![image](https://user-images.githubusercontent.com/166269/139014161-cda67cd1-843f-4ad2-9fa1-acb94a59fc4d.png) :--:1 **DEVELOPMENT** + +![image](https://user-images.githubusercontent.com/166269/139014121-0596b084-b3c5-4adb-994e-31ba6eba6dad.png) :--- + +### Additional `context` to an error message + +Sanic exceptions can also be raised with a `context` argument to pass intended information along to the user about what happened. This is particularly useful when creating microservices or an API intended to pass error messages in JSON format. In this use case, we want to have some context around the exception beyond just a parseable error message to return details to the client. + +```python +raise TeapotError(context={"foo": "bar"}) +``` + +This is information **that we want** to always be passed in the error (when it is available). Here is what it should look like: + +---:1 **PRODUCTION** + +```json +{ + "description": "I'm a teapot", + "status": 418, + "message": "Sorry Adam, I cannot make you coffee", + "context": { + "foo": "bar" + } +} +``` +:--:1 **DEVELOPMENT** + +```json +{ + "description": "I'm a teapot", + "status": 418, + "message": "Sorry Adam, I cannot make you coffee", + "context": { + "foo": "bar" + }, + "path": "/", + "args": {}, + "exceptions": [ + { + "type": "TeapotError", + "exception": "Sorry Adam, I cannot make you coffee", + "frames": [ + { + "file": "handle_request", + "line": 83, + "name": "handle_request", + "src": "" + }, + { + "file": "/tmp/p.py", + "line": 17, + "name": "handler", + "src": "raise TeapotError(" + } + ] + } + ] +} +``` +:--- diff --git a/src/pt/guide/best-practices/logging.md b/src/pt/guide/best-practices/logging.md new file mode 100644 index 0000000000..427244df96 --- /dev/null +++ b/src/pt/guide/best-practices/logging.md @@ -0,0 +1,89 @@ +# Logging + +Sanic allows you to do different types of logging (access log, error log) on the requests based on the [Python logging API](https://docs.python.org/3/howto/logging.html). You should have some basic knowledge on Python logging if you want to create a new configuration. + +## Quick Start + +---:1 + +A simple example using default settings would be like this: :--:1 +```python +from sanic import Sanic +from sanic.log import logger +from sanic.response import text + +app = Sanic('logging_example') + +@app.route('/') +async def test(request): + logger.info('Here is your log') + return text('Hello World!') + +if __name__ == "__main__": + app.run(debug=True, access_log=True) +``` +:--- + +After the server is running, you should see logs like this. +```text +[2021-01-04 15:26:26 +0200] [1929659] [INFO] Goin' Fast @ http://127.0.0.1:8000 +[2021-01-04 15:26:26 +0200] [1929659] [INFO] Starting worker [1929659] +``` + +You can send a request to server and it will print the log messages. +```text +[2021-01-04 15:26:28 +0200] [1929659] [INFO] Here is your log +[2021-01-04 15:26:28 +0200] - (sanic.access)[INFO][127.0.0.1:44228]: GET http://localhost:8000/ 200 -1 +``` + +## Changing Sanic loggers + +To use your own logging config, simply use `logging.config.dictConfig`, or pass `log_config` when you initialize Sanic app. + +```python +app = Sanic('logging_example', log_config=LOGGING_CONFIG) + +if __name__ == "__main__": + app.run(access_log=False) +``` + +::: tip FYI Logging in Python is a relatively cheap operation. However, if you are serving a high number of requests and performance is a concern, all of that time logging out access logs adds up and becomes quite expensive. + +This is a good opportunity to place Sanic behind a proxy (like nginx) and to do your access logging there. You will see a *significant* increase in overall performance by disabling the `access_log`. + +For optimal production performance, it is advised to run Sanic with `debug` and `access_log` disabled: `app.run(debug=False, access_log=False)` ::: + +## Configuration + +Sanic's default logging configuration is: `sanic.log.LOGGING_CONFIG_DEFAULTS`. + +---:1 There are three loggers used in sanic, and must be defined if you want to create your own logging configuration: + +| **Logger Name** | **Use Case** | +| --------------- | ------------------------------ | +| `sanic.root` | Used to log internal messages. | +| `sanic.error` | Used to log error logs. | +| `sanic.access` | Used to log access logs. | + :--:1 + +:--- + +### Log format + +In addition to default parameters provided by Python (`asctime`, `levelname`, `message`), Sanic provides additional parameters for access logger with. + +| Log Context Parameter | Parameter Value | Datatype | +| --------------------- | ------------------------------------ | -------- | +| `host` | `request.ip` | `str` | +| `request` | `request.method + " " + request.url` | `str` | +| `status` | `response` | `int` | +| `byte` | `len(response.body)` | `int` | + + + + +The default access log format is: + +```text +%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: %(request)s %(message)s %(status)d %(byte)d +``` diff --git a/src/pt/guide/best-practices/testing.md b/src/pt/guide/best-practices/testing.md new file mode 100644 index 0000000000..a7ac6fdaf6 --- /dev/null +++ b/src/pt/guide/best-practices/testing.md @@ -0,0 +1,3 @@ +# Testing + +See [sanic-testing](../../plugins/sanic-testing/getting-started.md) diff --git a/src/pt/guide/deployment/README.md b/src/pt/guide/deployment/README.md new file mode 100644 index 0000000000..d36c65e59e --- /dev/null +++ b/src/pt/guide/deployment/README.md @@ -0,0 +1 @@ +# Deployment diff --git a/src/pt/guide/deployment/app-loader.md b/src/pt/guide/deployment/app-loader.md new file mode 100644 index 0000000000..960138e3a9 --- /dev/null +++ b/src/pt/guide/deployment/app-loader.md @@ -0,0 +1,75 @@ +# Dynamic Applications + +Running Sanic has been optimized to work with the CLI. If you have not read it yet, you should read [Running Sanic](./running.md#sanic-server) to become familiar with the options. + +---:1 This includes running it as a global scope object... :--:1 +```python +# server.py +app = Sanic("TestApp") + +@app.get("/") +async def handler(request: Request): + return json({"foo": "bar"}) +``` +``` +sanic path.to.server:app +``` +:--- + + +---:1 ...or, a factory function that creates the `Sanic` application object. :--:1 +```python +# server.py +def create_app(): + app = Sanic("TestApp") + + @app.get("/") + async def handler(request: Request): + return json({"foo": "bar"}) + + return app +``` +``` +sanic path.to.server:create_app --factory +``` +:--- + + +**Sometimes, this is not enough ... :thinking:** + +Introduced in [v22.9](../release-notes/v22.9.md), Sanic has an `AppLoader` object that is responsible for creating an application in the various [worker processes](./manager.md#how-sanic-server-starts-processes). You can take advantage of this if you need to create a more dynamic startup experience for your application. + +---:1 An `AppLoader` can be passed a callable that returns a `Sanic` instance. That `AppLoader` could be used with the low-level application running API. :--:1 +```python +import sys +from functools import partial + +from sanic import Request, Sanic, json +from sanic.worker.loader import AppLoader + + +def attach_endpoints(app: Sanic): + @app.get("/") + async def handler(request: Request): + return json({"app_name": request.app.name}) + + +def create_app(app_name: str) -> Sanic: + app = Sanic(app_name) + attach_endpoints(app) + return app + + +if __name__ == "__main__": + app_name = sys.argv[-1] + loader = AppLoader(factory=partial(create_app, app_name)) + app = loader.load() + app.prepare(port=9999, dev=True) + Sanic.serve(primary=app, app_loader=loader) +``` +``` +$ python path/to/server.py MyTestAppName +``` +:--- + +In the above example, the `AppLoader` is created with a `factory` that can be used to create copies of the same application across processes. When doing this, you should explicitly use the `Sanic.serve` pattern shown above so that the `AppLoader` that you create is not replaced. diff --git a/src/pt/guide/deployment/configuration.md b/src/pt/guide/deployment/configuration.md new file mode 100644 index 0000000000..9b00ab3886 --- /dev/null +++ b/src/pt/guide/deployment/configuration.md @@ -0,0 +1,237 @@ +# Configuration + +## Basics + + +---:1 + +Sanic holds the configuration in the config attribute of the application object. The configuration object is merely an object that can be modified either using dot-notation or like a dictionary. :--:1 +```python +app = Sanic("myapp") +app.config.DB_NAME = "appdb" +app.config["DB_USER"] = "appuser" +``` +:--- + +---:1 + +You can also use the `update()` method like on regular dictionaries. :--:1 +```python +db_settings = { + 'DB_HOST': 'localhost', + 'DB_NAME': 'appdb', + 'DB_USER': 'appuser' +} +app.config.update(db_settings) +``` +:--- + +::: tip It is standard practice in Sanic to name your config values in **uppercase letters**. Indeed, you may experience weird behaviors if you start mixing uppercase and lowercase names. ::: + +## Loading + +### Environment variables + +---:1 + +Any environment variables defined with the `SANIC_` prefix will be applied to the Sanic config. For example, setting `SANIC_REQUEST_TIMEOUT` will be loaded by the application automatically and fed into the `REQUEST_TIMEOUT` config variable. :--:1 +```bash +$ export SANIC_REQUEST_TIMEOUT=10 +``` +```python +>>> print(app.config.REQUEST_TIMEOUT) +10 +``` +:--- + +---:1 + +You can change the prefix that Sanic is expecting at startup. :--:1 +```bash +$ export MYAPP_REQUEST_TIMEOUT=10 +``` +```python +>>> app = Sanic(__name__, env_prefix='MYAPP_') +>>> print(app.config.REQUEST_TIMEOUT) +10 +``` +:--- + +---:1 + +You can also disable environment variable loading completely. :--:1 +```python +app = Sanic(__name__, load_env=False) +``` +:--- + +### Using Sanic.update_config + +The `Sanic` instance has a _very_ versatile method for loading config: `app.update_config`. You can feed it a path to a file, a dictionary, a class, or just about any other sort of object. + +#### From a file + +---:1 + +Let's say you have `my_config.py` file that looks like this. :--:1 +```python +# my_config.py +A = 1 +B = 2 +``` +:--- + +---:1 + +You can load this as config values by passing its path to `app.update_config`. :--:1 +```python +>>> app.update_config("/path/to/my_config.py") +>>> print(app.config.A) +1 +``` +:--- + +---:1 + +This path also accepts bash style environment variables. :--:1 +```bash +$ export my_path="/path/to" +``` +```python +app.update_config("${my_path}/my_config.py") +``` +:--- + +::: tip Just remember that you have to provide environment variables in the format `${environment_variable}` and that `$environment_variable` is not expanded (is treated as "plain" text). ::: +#### From a dict + +---:1 + +The `app.update_config` method also works on plain dictionaries. :--:1 +```python +app.update_config({"A": 1, "B": 2}) +``` +:--- + +#### From a class or object + +---:1 + +You can define your own config class, and pass it to `app.update_config` :--:1 +```python +class MyConfig: + A = 1 + B = 2 + +app.update_config(MyConfig) +``` +:--- + +---:1 + +It even could be instantiated. :--:1 +```python +app.update_config(MyConfig()) +``` +:--- + +### Type casting + +When loading from environment variables, Sanic will attempt to cast the values to expected Python types. This particularly applies to: + +- `int` +- `float` +- `bool` + +In regards to `bool`, the following _case insensitive_ values are allowed: + +- **`True`**: `y`, `yes`, `yep`, `yup`, `t`, `true`, `on`, `enable`, `enabled`, `1` +- **`False`**: `n`, `no`, `f`, `false`, `off`, `disable`, `disabled`, `0` + +If a value cannot be cast, it will default to a `str`. + +---:1 Additionally, Sanic can be configured to cast additional types using additional type converters. This should be any callable that returns the value or raises a `ValueError`. + +*Added in v21.12* :--:1 +```python +app = Sanic(..., config=Config(converters=[UUID])) +``` +:--- + +## Builtin values + + +| **Variable** | **Default** | **Description** | +| --------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| ACCESS_LOG | True | Disable or enable access log | +| AUTO_EXTEND | True | Control whether [Sanic Extensions](../../plugins/sanic-ext/getting-started.md) will load if it is in the existing virtual environment | +| AUTO_RELOAD | True | Control whether the application will automatically reload when a file changes | +| EVENT_AUTOREGISTER | True | When `True` using the `app.event()` method on a non-existing signal will automatically create it and not raise an exception | +| FALLBACK_ERROR_FORMAT | html | Format of error response if an exception is not caught and handled | +| FORWARDED_FOR_HEADER | X-Forwarded-For | The name of "X-Forwarded-For" HTTP header that contains client and proxy ip | +| FORWARDED_SECRET | None | Used to securely identify a specific proxy server (see below) | +| GRACEFUL_SHUTDOWN_TIMEOUT | 15.0 | How long to wait to force close non-idle connection (sec) | +| INSPECTOR | False | Whether to enable the Inspector | +| INSPECTOR_HOST | localhost | The host for the Inspector | +| INSPECTOR_PORT | 6457 | The port for the Inspector | +| INSPECTOR_TLS_KEY | - | The TLS key for the Inspector | +| INSPECTOR_TLS_CERT | - | The TLS certificate for the Inspector | +| INSPECTOR_API_KEY | - | The API key for the Inspector | +| KEEP_ALIVE_TIMEOUT | 5 | How long to hold a TCP connection open (sec) | +| KEEP_ALIVE | True | Disables keep-alive when False | +| MOTD | True | Whether to display the MOTD (message of the day) at startup | +| MOTD_DISPLAY | {} | Key/value pairs to display additional, arbitrary data in the MOTD | +| NOISY_EXCEPTIONS | False | Force all `quiet` exceptions to be logged | +| PROXIES_COUNT | None | The number of proxy servers in front of the app (e.g. nginx; see below) | +| REAL_IP_HEADER | None | The name of "X-Real-IP" HTTP header that contains real client ip | +| REGISTER | True | Whether the app registry should be enabled | +| REQUEST_BUFFER_SIZE | 65536 | Request buffer size before request is paused, default is 64 Kib | +| REQUEST_ID_HEADER | X-Request-ID | The name of "X-Request-ID" HTTP header that contains request/correlation ID | +| REQUEST_MAX_SIZE | 100000000 | How big a request may be (bytes), default is 100 megabytes | +| REQUEST_TIMEOUT | 60 | How long a request can take to arrive (sec) | +| RESPONSE_TIMEOUT | 60 | How long a response can take to process (sec) | +| USE_UVLOOP | True | Whether to override the loop policy to use `uvloop`. Supported only with `app.run`. | +| WEBSOCKET_MAX_SIZE | 2^20 | Maximum size for incoming messages (bytes) | +| WEBSOCKET_PING_INTERVAL | 20 | A Ping frame is sent every ping_interval seconds. | +| WEBSOCKET_PING_TIMEOUT | 20 | Connection is closed when Pong is not received after ping_timeout seconds | + +::: tip FYI +- The `USE_UVLOOP` value will be ignored if running with Gunicorn. Defaults to `False` on non-supported platforms (Windows). +- The `WEBSOCKET_` values will be ignored if in ASGI mode. +- v21.12 added: `AUTO_EXTEND`, `MOTD`, `MOTD_DISPLAY`, `NOISY_EXCEPTIONS` +- v22.9 added: `INSPECTOR` +- v22.12 added: `INSPECTOR_HOST`, `INSPECTOR_PORT`, `INSPECTOR_TLS_KEY`, `INSPECTOR_TLS_CERT`, `INSPECTOR_API_KEY` ::: + +## Timeouts + +### REQUEST_TIMEOUT + +A request timeout measures the duration of time between the instant when a new open TCP connection is passed to the Sanic backend server, and the instant when the whole HTTP request is received. If the time taken exceeds the `REQUEST_TIMEOUT` value (in seconds), this is considered a Client Error so Sanic generates an `HTTP 408` response and sends that to the client. Set this parameter's value higher if your clients routinely pass very large request payloads or upload requests very slowly. + +### RESPONSE_TIMEOUT + +A response timeout measures the duration of time between the instant the Sanic server passes the HTTP request to the Sanic App, and the instant a HTTP response is sent to the client. If the time taken exceeds the `RESPONSE_TIMEOUT` value (in seconds), this is considered a Server Error so Sanic generates an `HTTP 503` response and sends that to the client. Set this parameter's value higher if your application is likely to have long-running process that delay the generation of a response. + +### KEEP_ALIVE_TIMEOUT + +#### What is Keep Alive? And what does the Keep Alive Timeout value do? + +`Keep-Alive` is a HTTP feature introduced in `HTTP 1.1`. When sending a HTTP request, the client (usually a web browser application) can set a `Keep-Alive` header to indicate the http server (Sanic) to not close the TCP connection after it has send the response. This allows the client to reuse the existing TCP connection to send subsequent HTTP requests, and ensures more efficient network traffic for both the client and the server. + +The `KEEP_ALIVE` config variable is set to `True` in Sanic by default. If you don't need this feature in your application, set it to `False` to cause all client connections to close immediately after a response is sent, regardless of the `Keep-Alive` header on the request. + +The amount of time the server holds the TCP connection open is decided by the server itself. In Sanic, that value is configured using the `KEEP_ALIVE_TIMEOUT` value. By default, it is set to 5 seconds. This is the same default setting as the Apache HTTP server and is a good balance between allowing enough time for the client to send a new request, and not holding open too many connections at once. Do not exceed 75 seconds unless you know your clients are using a browser which supports TCP connections held open for that long. + +For reference: + +* Apache httpd server default keepalive timeout = 5 seconds +* Nginx server default keepalive timeout = 75 seconds +* Nginx performance tuning guidelines uses keepalive = 15 seconds +* IE (5-9) client hard keepalive limit = 60 seconds +* Firefox client hard keepalive limit = 115 seconds +* Opera 11 client hard keepalive limit = 120 seconds +* Chrome 13+ client keepalive limit > 300+ seconds + +## Proxy configuration + +See [proxy configuration section](/guide/advanced/proxy-headers.md) diff --git a/src/pt/guide/deployment/development.md b/src/pt/guide/deployment/development.md new file mode 100644 index 0000000000..881a0be291 --- /dev/null +++ b/src/pt/guide/deployment/development.md @@ -0,0 +1,97 @@ +# Development + +The first thing that should be mentioned is that the webserver that is integrated into Sanic is **not** just a development server. + +It is production ready out-of-the-box, *unless you enable in debug mode*. + +## Debug mode + +By setting the debug mode, Sanic will be more verbose in its output and will disable several run-time optimizations. + +```python +from sanic import Sanic +from sanic.response import json + +app = Sanic(__name__) + +@app.route("/") +async def hello_world(request): + return json({"hello": "world"}) + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=1234, debug=True) +``` + +::: warning +Sanic's debug mode will slow down the server's performance and is therefore advised to enable it only in development environments. +::: +## Automatic Reloader + +---:1 + +Sanic offers a way to enable or disable the Automatic Reloader. The `auto_reload` argument will activate or deactivate the Automatic Reloader. Every time a Python file is changed, the reloader will restart your application automatically. This is very convenient while developing. :--:1 +```python +app.run(auto_reload=True) +``` +:--- + +---:1 If you have additional directories that you would like to automatically reload on file save (for example, a directory of HTML templates), you can add that at run time. :--:1 +```python +app.run(auto_reload=True, reload_dir="/path/to/templates") +# or multiple directories +app.run(auto_reload=True, reload_dir=["/path/to/one", "/path/to/two"]) +``` +:--- + +## Best of both worlds +---:1 If you would like to be in debug mode **and** have the Automatic Reloader running, you can pass `dev=True`. This is equivalent to **debug + auto reload**. + +*Added in v22.3* :--:1 +```python +app.run(dev=True) +``` +:--- + +## Automatic TLS certificate + +When running in `DEBUG` mode, you can ask Sanic to handle setting up localhost temporary TLS certificates. This is helpful if you want to access your local development environment with `https://`. + +This functionality is provided by either [mkcert](https://github.com/FiloSottile/mkcert) or [trustme](https://github.com/python-trio/trustme). Both are good choices, but there are some differences. `trustme` is a Python library and can be installed into your environment with `pip`. This makes for easy envrionment handling, but it is not compatible when running a HTTP/3 server. `mkcert` might be a more involved installation process, but can install a local CA and make it easier to use. + +---:1 You can choose which platform to use by setting `config.LOCAL_CERT_CREATOR`. When set to `"auto"`, it will select either option, preferring `mkcert` if possible. :--:1 +```python +app.config.LOCAL_CERT_CREATOR = "auto" +app.config.LOCAL_CERT_CREATOR = "mkcert" +app.config.LOCAL_CERT_CREATOR = "trustme" +``` +:--- + + +---:1 Automatic TLS can be enabled at Sanic server run time: :--:1 +``` +$ sanic path.to.server:app --auto-tls --debug +``` + +```python +app.run(debug=True, auto_tls=True) +``` +:--- + +::: warning + +Localhost TLS certificates (like those generated by both `mkcert` and `trustme`) are **NOT** suitable for production environments. If you are not familiar with how to obtain a *real* TLS certificate, checkout the [How to...](../how-to/tls.md) section. ::: + +*Added in v22.6* + +## CLI + +It should be noted that all of these have an equivalent in the Sanic CLI: + +``` +Development: + --debug Run the server in debug mode + -r, --reload, --auto-reload Watch source directory for file changes and reload on changes + -R PATH, --reload-dir PATH Extra directories to watch and reload on changes + -d, --dev debug + auto reload + --auto-tls Create a temporary TLS certificate for local development (requires mkcert or trustme) +``` diff --git a/src/pt/guide/deployment/docker.md b/src/pt/guide/deployment/docker.md new file mode 100644 index 0000000000..47f4966b27 --- /dev/null +++ b/src/pt/guide/deployment/docker.md @@ -0,0 +1,183 @@ +# Docker Deployment + +## Introduction + +For a long time, the environment has always been a difficult problem for deployment. If there are conflicting configurations in your project, you have to spend a lot of time resolving them. Fortunately, virtualization provides us with a good solution. Docker is one of them. If you don't know Docker, you can visit [Docker official website](https://www.docker.com/) to learn more. + +## Build Image + +Let's start with a simple project. We will use a Sanic project as an example. Assume the project path is `/path/to/SanicDocker`. + +---:1 + +The directory structure looks like this: + +:--:1 + +```text +# /path/to/SanicDocker +SanicDocker +├── requirements.txt +├── dockerfile +└── server.py +``` + +:--- + +---:1 + +And the `server.py` code looks like this: + +:--:1 + +```python +app = Sanic("MySanicApp") + +@app.get('/') +async def hello(request): + return text("OK!") + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8000) +``` + +:--- + +::: tip + +Please note that the host cannot be 127.0.0.1 . In docker container, 127.0.0.1 is the default network interface of the container, only the container can communicate with other containers. more information please visit [Docker network](https://docs.docker.com/engine/reference/commandline/network/) + +::: + +Code is ready, let's write the `Dockerfile`: + +```Dockerfile + +FROM sanicframework/sanic:3.8-latest + +WORKDIR /sanic + +COPY . . + +RUN pip install -r requirements.txt + +EXPOSE 8000 + +CMD ["python", "server.py"] +``` + +Run the following command to build the image: + +```shell +docker build -t my-sanic-image . +``` + +## Start Container + +---:1 + +After the image built, we can start the container use `my-sanic-image`: + +:--:1 + +```shell +docker run --name mysanic -p 8000:8000 -d my-sanic-image +``` + +:--- + +---:1 + +Now we can visit `http://localhost:8000` to see the result: + +:--:1 + +```text +OK! +``` + +:--- + +## Use docker-compose + +If your project consist of multiple services, you can use [docker-compose](https://docs.docker.com/compose/) to manage them. + +for example, we will deploy `my-sanic-image` and `nginx`, achieve through nginx access sanic server. + +---:1 + +First of all, we need prepare nginx configuration file. create a file named `mysanic.conf`: + +:--:1 + +```nginx +server { + listen 80; + listen [::]:80; + location / { + proxy_pass http://mysanic:8000/; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection upgrade; + proxy_set_header Accept-Encoding gzip; + } +} +``` + +:--- + +---:1 + +Then, we need to prepare `docker-compose.yml` file. The content follows: + +:--:1 + +```yml +version: "3" + +services: + mysanic: + image: my-sanic-image + ports: + - "8000:8000" + restart: always + + mynginx: + image: nginx:1.13.6-alpine + ports: + - "80:80" + depends_on: + - mysanic + volumes: + - ./mysanic.conf:/etc/nginx/conf.d/mysanic.conf + restart: always + +networks: + default: + driver: bridge +``` + +:--- + +---:1 + +After that, we can start them: + +:--:1 + +```shell +docker-compose up -d +``` + +:--- + +---:1 + +Now, we can visit `http://localhost:80` to see the result: + +:--:1 + +```text +OK! +``` + +:--- diff --git a/src/pt/guide/deployment/inspector.md b/src/pt/guide/deployment/inspector.md new file mode 100644 index 0000000000..81cacb3496 --- /dev/null +++ b/src/pt/guide/deployment/inspector.md @@ -0,0 +1,160 @@ +# Inspector + +The Sanic Inspector is a feature of Sanic Server. It is *only* available when running Sanic with the built-in [worker manager](./manager.md). + +It is an HTTP application that *optionally* runs in the background of your application to allow you to interact with the running instance of your application. + +::: tip INFO +The Inspector was introduced in limited capacity in v22.9, but the documentation on this page assumes you are using v22.12 or higher. +::: + +## Getting Started + +The inspector is disabled by default. To enable it, you have two options. + +---:1 Set a flag when creating your application instance. :--:1 +```python +app = Sanic("TestApp", inspector=True) +``` +:--- + +---:1 Or, set a configuration value. :--:1 +```python +app = Sanic("TestApp") +app.config.INSPECTOR = True +``` +:--- + +::: warning +If you are using the configuration value, it *must* be done early and before the main worker process starts. This means that it should either be an environment variable, or it should be set shortly after creating the application instance as shown above. +::: + +## Using the Inspector + +Once the inspector is running, you will have access to it via the CLI or by directly accessing its web API via HTTP. + +---:1 **Via CLI** +``` +$ sanic inspect +``` +:--:1 **Via HTTP** +``` +$ curl http://localhost:6457 +``` +:--- + +::: tip +Remember, the Inspector is not running on your Sanic application. It is a seperate process, with a seperate application, and exposed on a seperate socket. +::: + +## Built-in Commands + +The Inspector comes with the following built-in commands. + +| CLI Command | HTTP Action | Description | +| ------------------ | ---------------------------------------- | ------------------------------------------------------------------------ | +| `inspect` | `GET /` | Display basic details about the running application. | +| `inspect reload` | `POST /reload` | Trigger a reload of all server workers. | +| `inspect shutdown` | `POST /shutdown` | Trigger a shutdown of all processes. | +| `inspect scale N` | `POST /scale`
`{"replicas": N}` | Scale the number of workers. Where `N` is the target number of replicas. | + +## Custom Commands + +The Inspector is easily extendable to add custom commands (and endpoints). + +---:1 Subclass the `Inspector` class and create arbitrary methods. As long as the method name is not preceded by an underscore (`_`), then the name of the method will be a new subcommand on the inspector. :--:1 +```python +from sanic import json +from sanic.worker.inspector import Inspector + + +class MyInspector(Inspector): + async def something(self, *args, **kwargs): + print(args) + print(kwargs) + + +app = Sanic("TestApp", inspector_class=MyInspector, inspector=True) +``` +:--- + +This will expose custom methods in the general pattern: + +- CLI: `sanic inspect ` +- HTTP: `POST /` + +It is important to note that the arguments that the new method accepts are derived from how you intend to use the command. For example, the above `something` method accepts all positional and keyword based parameters. + +---:1 In the CLI, the positional and keyword parameters are passed as either positional or keyword arguments to your method. All values will be a `str` with the following exceptions: + +- A keyword parameter with no assigned value will be: `True` +- Unless the parameter is prefixed with `no-`, then it will be: `False` :--:1 +``` +$ sanic inspect something one two three --four --no-five --six=6 +``` +In your application log console, you will see: +``` +('one', 'two', 'three') +{'four': True, 'five': False, 'six': '6'} +``` +:--- + +---:1 The same can be achieved by hitting the API directly. You can pass arguments to the method by exposing them in a JSON payload. The only thing to note is that the positional arguments should be exposed as `{"args": [...]}`. :--:1 +``` +$ curl http://localhost:6457/something \ + --json '{"args":["one", "two", "three"], "four":true, "five":false, "six":6}' +``` +In your application log console, you will see: +``` +('one', 'two', 'three') +{'four': True, 'five': False, 'six': 6} +``` +:--- + + +## Using in production + +::: warning +Before exposing the Inspector on a product, please consider all of the options in this section carefully. +::: + +When running Inspector on a remote production instance, you can protect the endpoints by requiring TLS encryption, and requiring API key authentication. + +### TLS encryption + +---:1 To the Inspector HTTP instance over TLS, pass the paths to your certificate and key. :--:1 +```python +app.config.INSPECTOR_TLS_CERT = "/path/to/cert.pem" +app.config.INSPECTOR_TLS_KEY = "/path/to/key.pem" +``` +:--- + +---:1 This will require use of the `--secure` flag, or `https://`. :--:1 +``` +$ sanic insect --secure --host= +``` +``` +$ curl https://:6457 +``` +:--- + +### API Key Authentication + +---:1 You can secure the API with bearer token authentication. :--:1 +```python +app.config.INSPECTOR_API_KEY = "Super-Secret-200" +``` +:--- + +---:1 This will require the `--api-key` parameter, or bearer token authorization header. :--:1 +``` +$ sanic inspect --api-key=Super-Secret-200 +``` +``` +$ curl http://localhost:6457 -H "Authorization: Bearer Super-Secret-200" +``` +:--- + +## Configuration + +See [configuration](./configuration.md) diff --git a/src/pt/guide/deployment/kubernetes.md b/src/pt/guide/deployment/kubernetes.md new file mode 100644 index 0000000000..8d6340de1c --- /dev/null +++ b/src/pt/guide/deployment/kubernetes.md @@ -0,0 +1 @@ +# Kubernetes diff --git a/src/pt/guide/deployment/manager.md b/src/pt/guide/deployment/manager.md new file mode 100644 index 0000000000..4c4153b14d --- /dev/null +++ b/src/pt/guide/deployment/manager.md @@ -0,0 +1,290 @@ +# Worker Manager + +The worker manager and its functionality was introduced in version 22.9. + +*The details of this section are intended for more advanced usages and **not** necessary to get started.* + +The purpose of the manager is to create consistency and flexibility between development and production environments. Whether you intend to run a single worker, or multiple workers, whether with, or without auto-reload: the experience will be the same. + + +In general it looks like this: + +![](https://user-images.githubusercontent.com/166269/178677618-3b4089c3-6c6a-4ecc-8d7a-7eba2a7f29b0.png) + +When you run Sanic, the main process instantiates a `WorkerManager`. That manager is in charge of running one or more `WorkerProcess`. There generally are two kinds of processes: + +- server processes, and +- non-server processes. + +For the sake of ease, the User Guide generally will use the term "worker" or "worker process" to mean a server process, and "Manager" to mean the single worker manager running in your main process. + +## How Sanic Server starts processes + +Sanic will start processes using the [spawn](https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods) start method. This means that for every process/worker, the global scope of your application will be run on its own thread. The practical impact of this that *if* you do not run Sanic with the CLI, you will need to nest the execution code inside a block to make sure it only runs on `__main__`. + +```python +if __name__ == "__main__": + app.run() +``` + +If you do not, you are likely to see an error message like this: + +``` +sanic.exceptions.ServerError: Sanic server could not start: [Errno 98] Address already in use. + +This may have happened if you are running Sanic in the global scope and not inside of a `if __name__ == "__main__"` block. + +See more information: https://sanic.dev/en/guide/deployment/manager.html#how-sanic-server-starts-processes +``` + +The likely fix for this problem is nesting your Sanic run call inside of the `__name__ == "__main__"` block. If you continue to receive this message after nesting, or if you see this while using the CLI, then it means the port you are trying to use is not available on your machine and you must select another port. + +### Starting a worker + +All worker processes *must* send an acknowledgement when starting. This happens under the hood, and you as a developer do not need to do anything. However, the Manager will exit with a status code `1` if one or more workers do not send that `ack` message, or a worker process throws an exception while trying to start. If no exceptions are encountered, the Manager will wait for up to thirty (30) seconds for the acknowledgement. + +---:1 In the situation when you know that you will need more time to start, you can monkeypatch the Manager. The threshold does not include anything inside of a listener, and is limited to the execution time of everything in the global scope of your application. + +If you run into this issue, it may indicate a need to look deeper into what is causing the slow startup. :--:1 +```python +from sanic.worker.manager import WorkerManager + +WorkerManager.THRESHOLD = 100 # Value is in 0.1s +``` +:--- + +See [worker ack](#worker-ack) for more information. + +---:1 As stated above, Sanic will use [spawn](https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods) to start worker processes. If you would like to change this behavior and are aware of the implications of using different start methods, you can modify as shown here. :--:1 +```python +from sanic import Sanic + +Sanic.start_method = "fork" +``` +:--- + + +### Worker ack + +When all of your workers are running in a subprocess a potential problem is created: deadlock. This can occur when the child processes cease to function, but the main process is unaware that this happened. Therefore, Sanic servers will automatically send an `ack` message (short for acknowledge) to the main process after startup. + +In version 22.9, the `ack` timeout was short and limited to `5s`. In version 22.12, the timeout was lengthened to `30s`. If your application is shutting down after thirty seconds then it might be necessary to manually increase this threshhold. + +---:1 The value of `WorkerManager.THRESHOLD` is in `0.1s` increments. Therefore, to set it to one minute, you should set the value to `600`. + +This value should be set as early as possible in your application, and should ideally happen in the global scope. Setting it after the main process has started will not work. :--:1 +```python +from sanic.worker.manager import WorkerManager + +WorkerManager.THRESHOLD = 600 +``` +:--- + + + + +::: new NEW in v22.12 +### Zero downtime restarts + +By default, when restarting workers, Sanic will teardown the existing process first before starting a new one. + +If you are intending to use the restart functionality in production then you may be interested in having zero-downtime reloading. This can be accomplished by forcing the reloader to change the order to start a new process, wait for it to [ack](#worker-ack), and then teardown the old process. + +---:1 From the multiplexer, use the `zero_downtime` argument :--:1 +```python +app.m.restart(zero_downtime=True) +``` +:--- + +*Added in v22.12* +::: + +## Using shared context between worker processes + +Python provides a few methods for [exchanging objects](https://docs.python.org/3/library/multiprocessing.html#exchanging-objects-between-processes), [synchronizing](https://docs.python.org/3/library/multiprocessing.html#synchronization-between-processes), and [sharing state](https://docs.python.org/3/library/multiprocessing.html#sharing-state-between-processes) between processes. This usually involves objects from the `multiprocessing` and `ctypes` modules. + +If you are familiar with these objects and how to work with them, you will be happy to know that Sanic provides an API for sharing these objects between your worker processes. If you are not familiar, you are encouraged to read through the Python documentation linked above and try some of the examples before proceeding with implementing shared context. + +Similar to how [application context](../basics/app.md#application-context) allows an applicaiton to share state across the lifetime of the application with `app.ctx`, shared context provides the same for the special objects mentioned above. This context is available as `app.shared_ctx` and should **ONLY** be used to share objects intended for this purpose. + +The `shared_ctx` will: + +- *NOT* share regular objects like `int`, `dict`, or `list` +- *NOT* share state between Sanic instances running on different machines +- *NOT* share state to non-worker processes +- **only** share state between server workers managed by the same Manager + +Attaching an inappropriate object to `shared_ctx` will likely result in a warning, and not an error. You should be careful to not accidentally add an unsafe object to `shared_ctx` as it may not work as expected. If you are directed here because of one of those warnings, you might have accidentally used an unsafe object in `shared_ctx`. + +---:1 In order to create a shared object you **must** create it in the main process and attach it inside of the `main_process_start` listener. :--:1 +```python +from multiprocessing import Queue + +@app.main_process_start +async def main_process_start(app): + app.shared_ctx.queue = Queue() +``` +:--- + +Trying to attach to the `shared_ctx` object outside of this listener may result in a `RuntimeError`. + +---:1 After creating the objects in the `main_process_start` listener and attaching to the `shared_ctx`, they will be available in your workers wherever the application instance is available (example: listeners, middleware, request handlers). :--:1 +```python +from multiprocessing import Queue + +@app.get("") +async def handler(request): + request.app.shared_ctx.queue.put(1) + ... +``` +:--- + +## Access to the multiplexer + +The application instance has access to an object that provides access to interacting with the Manager and other worker processes. The object is attached as the `app.multiplexer` property, but it is more easily accessed by its alias: `app.m`. + +---:1 For example, you can get access to the current worker state. :--:1 +```python +@app.on_request +async def print_state(request: Request): + print(request.app.m.name) + print(request.app.m.pid) + print(request.app.m.state) +``` +``` +Sanic-Server-0-0 +99999 +{'server': True, 'state': 'ACKED', 'pid': 99999, 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), 'starts': 2, 'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc)} +``` +:--- + +---:1 The `multiplexer` also has access to terminate the Manager, or restart worker processes :--:1 +```python +# shutdown the entire application and all processes +app.m.name.terminate() + +# restart the current worker only +app.m.name.restart() + +# restart specific workers only (comma delimited) +app.m.name.restart("Sanic-Server-4-0,Sanic-Server-7-0") + +# restart ALL workers +app.m.name.restart(all_workers=True) # Available v22.12+ +``` +:--- + +## Worker state + +---:1 As shown above, the `multiplexer` has access to report upon the state of the current running worker. However, it also contains the state for ALL processes running. :--:1 +```python +@app.on_request +async def print_state(request: Request): + print(request.app.m.workers) +``` +``` +{ + 'Sanic-Main': {'pid': 99997}, + 'Sanic-Server-0-0': { + 'server': True, + 'state': 'ACKED', + 'pid': 9999, + 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), + 'starts': 2, + 'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc) + }, + 'Sanic-Reloader-0': { + 'server': False, + 'state': 'STARTED', + 'pid': 99998, + 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), + 'starts': 1 + } +} +``` +:--- + + +## Built-in non-server processes + +As mentioned, the Manager also has the ability to run non-server processes. Sanic comes with two built-in types of non-server processes, and allows for [creating custom processes](#running-custom-processes). + +The two built-in processes are + +- the [auto-reloader](./development.md#automatic-reloader), optionally enabled to watch the file system for changes and trigger a restart +- [inspector](#inspector), optionally enabled to provide external access to the state of the running instance + +## Inspector + +Sanic has the ability to expose the state and the functionality of the `multiplexer` to the CLI. Currently, this requires the CLI command to be run on the same machine as the running Sanic instance. By default the inspector is disabled. + +---:1 To enable it, set the config value to `True`. :--:1 +```python +app.config.INSPECTOR = True +``` +:--- + +You will now have access to execute any of these CLI commands: + +``` +sanic inspect reload Trigger a reload of the server workers +sanic inspect shutdown Shutdown the application and all processes +sanic inspect scale N Scale the number of workers to N +sanic inspect Run a custom command +``` + +![](https://user-images.githubusercontent.com/166269/190099384-2f2f3fae-22d5-4529-b279-8446f6b5f9bd.png) + +---:1 This works by exposing a small HTTP service on your machine. You can control the location using configuration values: :--:1 +```python +app.config.INSPECTOR_HOST = "localhost" +app.config.INSPECTOR_PORT = 6457 +``` +:--- + +[Learn more](./inspector.md) to find out what is possible with the Inspector. + +## Running custom processes + +To run a managed custom process on Sanic, you must create a callable. If that process is meant to be long-running, then it should handle a shutdown call by a `SIGINT` or `SIGTERM` signal. + +---:1 The simplest method for doing that in Python will be to just wrap your loop in `KeyboardInterrupt`. + +If you intend to run another application, like a bot, then it is likely that it already has capability to handle this signal and you likely do not need to do anything. :--:1 +```python +from time import sleep + +def my_process(foo): + try: + while True: + sleep(1) + except KeyboardInterrupt: + print("done") +``` +:--- + +---:1 That callable must be registered in the `main_process_ready` listener. It is important to note that is is **NOT** the same location that you should register [shared context](#using-shared-context-between-worker-processes) objects. :--:1 +```python +@app.main_process_ready +async def ready(app: Sanic, _): +# app.manager.manage(, , ) + app.manager.manage("MyProcess", my_process, {"foo": "bar"}) +``` +:--- + +## Single process mode + +---:1 If you would like to opt out of running multiple processes, you can run Sanic in a single process only. In this case, the Manager will not run. You will also not have access to any features that require processes (auto-reload, the inspector, etc). :--:1 +```python +if __name__ == "__main__": + app.run(single_process=True) +``` +```python +if __name__ == "__main__": + app.prepare(single_process=True) + Sanic.serve_single() +``` +``` +sanic path.to.server:app --single-process +``` +:--- diff --git a/src/pt/guide/deployment/nginx.md b/src/pt/guide/deployment/nginx.md new file mode 100644 index 0000000000..18fb68c4a3 --- /dev/null +++ b/src/pt/guide/deployment/nginx.md @@ -0,0 +1,186 @@ +# Nginx Deployment + +## Introduction + + +Although Sanic can be run directly on Internet, it may be useful to use a proxy server such as Nginx in front of it. This is particularly useful for running multiple virtual hosts on the same IP, serving NodeJS or other services beside a single Sanic app, and it also allows for efficient serving of static files. SSL and HTTP/2 are also easily implemented on such proxy. + +We are setting the Sanic app to serve only locally at `127.0.0.1:8000`, while the Nginx installation is responsible for providing the service to public Internet on domain `example.com`. Static files will be served from `/var/www/`. + + +## Proxied Sanic app + +The app needs to be setup with a secret key used to identify a trusted proxy, so that real client IP and other information can be identified. This protects against anyone on the Internet sending fake headers to spoof their IP addresses and other details. Choose any random string and configure it both on the app and in Nginx config. + +```python +from sanic import Sanic +from sanic.response import text + +app = Sanic("proxied_example") +app.config.FORWARDED_SECRET = "YOUR SECRET" + +@app.get("/") +def index(request): + # This should display external (public) addresses: + return text( + f"{request.remote_addr} connected to {request.url_for('index')}\n" + f"Forwarded: {request.forwarded}\n" + ) + +if __name__ == "__main__": + app.run(host="127.0.0.1", port=8000, workers=8, access_log=False) +``` + +Since this is going to be a system service, save your code to `/srv/sanicexample/sanicexample.py`. + +For testing, run your app in a terminal. + +## Nginx configuration + +Quite much configuration is required to allow fast transparent proxying, but for the most part these don't need to be modified, so bear with me. + +Upstream servers need to be configured in a separate `upstream` block to enable HTTP keep-alive, which can drastically improve performance, so we use this instead of directly providing an upstream address in `proxy_pass` directive. In this example, the upstream section is named by `server_name`, i.e. the public domain name, which then also gets passed to Sanic in the `Host` header. You may change the naming as you see fit. Multiple servers may also be provided for load balancing and failover. + +Change the two occurrences of `example.com` to your true domain name, and instead of `YOUR SECRET` use the secret you chose for your app. + +```nginx +upstream example.com { + keepalive 100; + server 127.0.0.1:8000; + #server unix:/tmp/sanic.sock; +} + +server { + server_name example.com; + listen 443 ssl http2 default_server; + listen [::]:443 ssl http2 default_server; + # Serve static files if found, otherwise proxy to Sanic + location / { + root /var/www; + try_files $uri @sanic; + } + location @sanic { + proxy_pass http://$server_name; + # Allow fast streaming HTTP/1.1 pipes (keep-alive, unbuffered) + proxy_http_version 1.1; + proxy_request_buffering off; + proxy_buffering off; + # Proxy forwarding (password configured in app.config.FORWARDED_SECRET) + proxy_set_header forwarded "$proxy_forwarded;secret=\"YOUR SECRET\""; + # Allow websockets and keep-alive (avoid connection: close) + proxy_set_header connection "upgrade"; + proxy_set_header upgrade $http_upgrade; + } +} +``` + +To avoid cookie visibility issues and inconsistent addresses on search engines, it is a good idea to redirect all visitors to one true domain, always using HTTPS: + +```nginx +# Redirect all HTTP to HTTPS with no-WWW +server { + listen 80 default_server; + listen [::]:80 default_server; + server_name ~^(?:www\.)?(.*)$; + return 301 https://$1$request_uri; +} + +# Redirect WWW to no-WWW +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name ~^www\.(.*)$; + return 301 $scheme://$1$request_uri; +} +``` + +The above config sections may be placed in `/etc/nginx/sites-available/default` or in other site configs (be sure to symlink them to `sites-enabled` if you create new ones). + +Make sure that your SSL certificates are configured in the main config, or add the `ssl_certificate` and `ssl_certificate_key` directives to each `server` section that listens on SSL. + +Additionally, copy&paste all of this into `nginx/conf.d/forwarded.conf`: + +```nginx +# RFC 7239 Forwarded header for Nginx proxy_pass + +# Add within your server or location block: +# proxy_set_header forwarded "$proxy_forwarded;secret=\"YOUR SECRET\""; + +# Configure your upstream web server to identify this proxy by that password +# because otherwise anyone on the Internet could spoof these headers and fake +# their real IP address and other information to your service. + + +# Provide the full proxy chain in $proxy_forwarded +map $proxy_add_forwarded $proxy_forwarded { + default "$proxy_add_forwarded;by=\"_$hostname\";proto=$scheme;host=\"$http_host\";path=\"$request_uri\""; +} + +# The following mappings are based on +# https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/ + +map $remote_addr $proxy_forwarded_elem { + # IPv4 addresses can be sent as-is + ~^[0-9.]+$ "for=$remote_addr"; + + # IPv6 addresses need to be bracketed and quoted + ~^[0-9A-Fa-f:.]+$ "for=\"[$remote_addr]\""; + + # Unix domain socket names cannot be represented in RFC 7239 syntax + default "for=unknown"; +} + +map $http_forwarded $proxy_add_forwarded { + # If the incoming Forwarded header is syntactically valid, append to it + "~^(,[ \\t]*)*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*([ \\t]*,([ \\t]*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*)?)*$" "$http_forwarded, $proxy_forwarded_elem"; + + # Otherwise, replace it + default "$proxy_forwarded_elem"; +} +``` + +::: tip Note For installs that don't use `conf.d` and `sites-available`, all of the above configs may also be placed inside the `http` section of the main `nginx.conf`. ::: + +Reload Nginx config after changes: + +```bash +sudo nginx -s reload +``` + +Now you should be able to connect your app on `https://example.com/`. Any 404 errors and such will be handled by Sanic's error pages, and whenever a static file is present at a given path, it will be served by Nginx. + +## SSL certificates + +If you haven't already configured valid certificates on your server, now is a good time to do so. Install `certbot` and `python3-certbot-nginx`, then run + +```bash +certbot --nginx -d example.com -d www.example.com +``` + +Reference: [Using Free Let’s Encrypt SSL/TLS Certificates with NGINX](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/) + +## Running as a service + +This part is for Linux distributions based on `systemd`. Create a unit file `/etc/systemd/system/sanicexample.service` + +```text +[Unit] +Description=Sanic Example + +[Service] +User=nobody +WorkingDirectory=/srv/sanicexample +ExecStart=/usr/bin/env python3 sanicexample.py +Restart=always + +[Install] +WantedBy=multi-user.target +``` + +Then reload service files, start your service and enable it on boot: + +```bash +sudo systemctl daemon-reload +sudo systemctl start sanicexample +sudo systemctl enable sanicexample +``` diff --git a/src/pt/guide/deployment/running.md b/src/pt/guide/deployment/running.md new file mode 100644 index 0000000000..d948035b44 --- /dev/null +++ b/src/pt/guide/deployment/running.md @@ -0,0 +1,386 @@ +# Running Sanic + +Sanic ships with its own internal web server. Under most circumstances, this is the preferred method for deployment. In addition, you can also deploy Sanic as an ASGI app bundled with an ASGI-able web server, or using gunicorn. + +## Sanic Server + +There are two main ways to run Sanic Server: + +1. Using `app.run` +1. Using the [CLI](#sanic-cli) + +When using `app.run` you will just call your Python file like any other script. + +---:1 `app.run` must be properly nested inside of a name-main block. :--:1 +```python +# server.py +app = Sanic("MyApp") + +if __name__ == "__main__": + app.run() +``` +:--- + + + +After defining an instance of `sanic.Sanic`, we can call the run method with the following keyword arguments: + +| Parameter | Default | Description | +|:--------------------:|:--------------:|:----------------------------------------------------------------------------------------- | +| **host** | `"127.0.0.1"` | Address to host the server on. | +| **port** | `8000` | Port to host the server on. | +| **unix** | `None` | Unix socket name to host the server on (instead of TCP). | +| **debug** | `False` | Enables debug output (slows server). | +| **ssl** | `None` | SSLContext for SSL encryption of worker(s). | +| **sock** | `None` | Socket for the server to accept connections from. | +| **workers** | `1` | Number of worker processes to spawn. Cannot be used with fast. | +| **loop** | `None` | An asyncio-compatible event loop. If none is specified, Sanic creates its own event loop. | +| **protocol** | `HttpProtocol` | Subclass of asyncio.protocol. | +| **access_log** | `True` | Enables log on handling requests (significantly slows server). | +| **reload_dir** | `None` | A path or list of paths to directories the auto-reloader should watch. | +| **noisy_exceptions** | `None` | Whether to set noisy exceptions globally. None means leave as default. | +| **motd** | `True` | Whether to display the startup message. | +| **motd_display** | `None` | A dict with extra key/value information to display in the startup message | +| **fast** | `False` | Whether to maximize worker processes. Cannot be used with workers. | +| **verbosity** | `0` | Level of logging detail. Max is 2. | +| **auto_tls** | `False` | Whether to auto-create a TLS certificate for local development. Not for production. | +| **single_process** | `False` | Whether to run Sanic in a single process. | + +---:1 In the above example, we decided to turn off the access log in order to increase performance. :--:1 +```python +# server.py +app = Sanic("MyApp") + +if __name__ == "__main__": + app.run(host='0.0.0.0', port=1337, access_log=False) +``` +:--- + +---:1 Now, just execute the python script that has `app.run(...)` :--:1 +```bash +python server.py +``` +:--- + +For a slightly more advanced implementation, it is good to know that `app.run` will call `app.prepare` and `Sanic.serve` under the hood. + +---:1 Therefore, these are equivalent: :--:1 +```python +if __name__ == "__main__": + app.run(host='0.0.0.0', port=1337, access_log=False) +``` +```python +if __name__ == "__main__": + app.prepare(host='0.0.0.0', port=1337, access_log=False) + Sanic.serve() +``` +:--- + +### Workers + +---:1 By default, Sanic runs a main process and a single worker process (see [worker manager](./manager.md) for more details). + +To crank up the juice, just specify the number of workers in the run arguments. :--:1 +```python +app.run(host='0.0.0.0', port=1337, workers=4) +``` +:--- + +Sanic will automatically spin up multiple processes and route traffic between them. We recommend as many workers as you have available processors. + +---:1 The easiest way to get the maximum CPU performance is to use the `fast` option. This will automatically run the maximum number of workers given the system constraints. + +*Added in v21.12* :--:1 +```python +app.run(host='0.0.0.0', port=1337, fast=True) +``` +```python +$ sanic server:app --host=0.0.0.0 --port=1337 --fast +``` +:--- + +In older versions of Sanic without the `fast` option, a common way to check this on Linux based operating systems: + +``` +$ nproc +``` + +Or, let Python do it: + +```python +import multiprocessing +workers = multiprocessing.cpu_count() +app.run(..., workers=workers) +``` + +In version 22.9, Sanic introduced a new worker manager to provide more consistency and flexibility between development and production servers. Read [about the manager](./manager.md) for more details about workers. + +---:1 If you only want to run Sanic with a single process, specify `single_process` in the run arguments. This means that auto-reload, and the worker manager will be unavailable. :--:1 +```python +app.run(host='0.0.0.0', port=1337, single_process=True) +``` +:--- + +### Running via command + +#### Sanic CLI + +---:1 Sanic also has a simple CLI to launch via command line. + +For example, if you initialized Sanic as app in a file named `server.py`, you could run the server like so: :--:1 +```bash +sanic server.app --host=0.0.0.0 --port=1337 --workers=4 +``` + +:--- + +Use `sanic --help` to see all the options. + +::: details Sanic CLI help output + +```text +$ sanic --help +usage: sanic [-h] [--version] + [--factory | -s | --inspect | --inspect-raw | --trigger-reload | --trigger-shutdown] + [--http {1,3}] [-1] [-3] [-H HOST] [-p PORT] [-u UNIX] + [--cert CERT] [--key KEY] [--tls DIR] [--tls-strict-host] + [-w WORKERS | --fast | --single-process] [--legacy] + [--access-logs | --no-access-logs] [--debug] [-r] [-R PATH] [-d] + [--auto-tls] [--coffee | --no-coffee] [--motd | --no-motd] [-v] + [--noisy-exceptions | --no-noisy-exceptions] + module + + ▄███ █████ ██ ▄█▄ ██ █ █ ▄██████████ + ██ █ █ █ ██ █ █ ██ + ▀███████ ███▄ ▀ █ █ ██ ▄ █ ██ + ██ █████████ █ ██ █ █ ▄▄ + ████ ████████▀ █ █ █ ██ █ ▀██ ███████ + + To start running a Sanic application, provide a path to the module, where + app is a Sanic() instance: + + $ sanic path.to.server:app + + Or, a path to a callable that returns a Sanic() instance: + + $ sanic path.to.factory:create_app --factory + + Or, a path to a directory to run as a simple HTTP server: + + $ sanic ./path/to/static --simple + +Required +======== + Positional: + module Path to your Sanic app. Example: path.to.server:app + If running a Simple Server, path to directory to serve. Example: ./ + +Optional +======== + General: + -h, --help show this help message and exit + --version show program's version number and exit + + Application: + --factory Treat app as an application factory, i.e. a () -> callable + -s, --simple Run Sanic as a Simple Server, and serve the contents of a directory + (module arg should be a path) + --inspect Inspect the state of a running instance, human readable + --inspect-raw Inspect the state of a running instance, JSON output + --trigger-reload Trigger worker processes to reload + --trigger-shutdown Trigger all processes to shutdown + + HTTP version: + --http {1,3} Which HTTP version to use: HTTP/1.1 or HTTP/3. Value should + be either 1, or 3. [default 1] + -1 Run Sanic server using HTTP/1.1 + -3 Run Sanic server using HTTP/3 + + Socket binding: + -H HOST, --host HOST + Host address [default 127.0.0.1] + -p PORT, --port PORT + Port to serve on [default 8000] + -u UNIX, --unix UNIX + location of unix socket + + TLS certificate: + --cert CERT Location of fullchain.pem, bundle.crt or equivalent + --key KEY Location of privkey.pem or equivalent .key file + --tls DIR TLS certificate folder with fullchain.pem and privkey.pem + May be specified multiple times to choose multiple certificates + --tls-strict-host Only allow clients that send an SNI matching server certs + + Worker: + -w WORKERS, --workers WORKERS + Number of worker processes [default 1] + --fast Set the number of workers to max allowed + --single-process Do not use multiprocessing, run server in a single process + --legacy Use the legacy server manager + --access-logs Display access logs + --no-access-logs No display access logs + + Development: + --debug Run the server in debug mode + -r, --reload, --auto-reload + Watch source directory for file changes and reload on changes + -R PATH, --reload-dir PATH + Extra directories to watch and reload on changes + -d, --dev debug + auto reload + --auto-tls Create a temporary TLS certificate for local development (requires mkcert or trustme) + + Output: + --coffee Uhm, coffee? + --no-coffee No uhm, coffee? + --motd Show the startup display + --no-motd No show the startup display + -v, --verbosity Control logging noise, eg. -vv or --verbosity=2 [default 0] + --noisy-exceptions Output stack traces for all exceptions + --no-noisy-exceptions + No output stack traces for all exceptions +``` +::: + +#### As a module + +---:1 It can also be called directly as a module. :--:1 +```bash +python -m sanic server.app --host=0.0.0.0 --port=1337 --workers=4 +``` +:--- + +::: tip FYI With either method (CLI or module), you shoud *not* invoke `app.run()` in your Python file. If you do, make sure you wrap it so that it only executes when directly run by the interpreter. + +```python +if __name__ == '__main__': + app.run(host='0.0.0.0', port=1337, workers=4) +``` +::: + + +### Sanic Simple Server + +---:1 Sometimes you just have a directory of static files that need to be served. This especially can be handy for quickly standing up a localhost server. Sanic ships with a Simple Server, where you only need to point it at a directory. :--:1 +```bash +sanic ./path/to/dir --simple +``` +:--- + +---:1 This could also be paired with auto-reloading. :--:1 +```bash +sanic ./path/to/dir --simple --reload --reload-dir=./path/to/dir +``` +:--- + +*Added in v21.6* + +### HTTP/3 + + +Sanic server offers HTTP/3 support using [aioquic](https://github.com/aiortc/aioquic). This **must** be installed to use HTTP/3: + +``` +pip install sanic aioquic +``` + +``` +pip install sanic[http3] +``` + +To start HTTP/3, you must explicitly request it when running your application. + +---:1 +``` +$ sanic path.to.server:app --http=3 +``` + +``` +$ sanic path.to.server:app -3 +``` +:--:1 + +```python +app.run(version=3) +``` +:--- + +To run both an HTTP/3 and HTTP/1.1 server simultaneously, you can use [application multi-serve](../release-notes/v22.3.html#application-multi-serve) introduced in v22.3. This will automatically add an [Alt-Svc](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Alt-Svc) header to your HTTP/1.1 requests to let the client know that it is also available as HTTP/3. + + +---:1 +``` +$ sanic path.to.server:app --http=3 --http=1 +``` + +``` +$ sanic path.to.server:app -3 -1 +``` +:--:1 + +```python +app.prepare(version=3) +app.prepare(version=1) +Sanic.serve() +``` +:--- + +Because HTTP/3 requires TLS, you cannot start a HTTP/3 server without a TLS certificate. You should [set it up yourself](../how-to/tls.html) or use `mkcert` if in `DEBUG` mode. Currently, automatic TLS setup for HTTP/3 is not compatible with `trustme`. See [development](./development.md) for more details. + +*Added in v22.6* + +## ASGI + +Sanic is also ASGI-compliant. This means you can use your preferred ASGI webserver to run Sanic. The three main implementations of ASGI are [Daphne](http://github.com/django/daphne), [Uvicorn](https://www.uvicorn.org/), and [Hypercorn](https://pgjones.gitlab.io/hypercorn/index.html). + +::: warning Daphne does not support the ASGI `lifespan` protocol, and therefore cannot be used to run Sanic. See [Issue #264](https://github.com/django/daphne/issues/264) for more details. ::: + +Follow their documentation for the proper way to run them, but it should look something like: + +``` +uvicorn myapp:app +hypercorn myapp:app +``` + +A couple things to note when using ASGI: + +1. When using the Sanic webserver, websockets will run using the `websockets` package. In ASGI mode, there is no need for this package since websockets are managed in the ASGI server. +2. The ASGI lifespan protocol , supports only two server events: startup and shutdown. Sanic has four: before startup, after startup, before shutdown, and after shutdown. Therefore, in ASGI mode, the startup and shutdown events will run consecutively and not actually around the server process beginning and ending (since that is now controlled by the ASGI server). Therefore, it is best to use `after_server_start` and `before_server_stop`. + +### Trio + +Sanic has experimental support for running on Trio with: + +``` +hypercorn -k trio myapp:app +``` + + +## Gunicorn + +[Gunicorn](http://gunicorn.org/) ("Green Unicorn") is a WSGI HTTP Server for UNIX based operating systems. It is a pre-fork worker model ported from Ruby’s Unicorn project. + +In order to run Sanic application with Gunicorn, you need to use it with the adapter from [uvicorn](https://www.uvicorn.org/). Make sure uvicorn is installed and run it with `uvicorn.workers.UvicornWorker` for Gunicorn worker-class argument: + +```bash +gunicorn myapp:app --bind 0.0.0.0:1337 --worker-class uvicorn.workers.UvicornWorker +``` + +See the [Gunicorn Docs](http://docs.gunicorn.org/en/latest/settings.html#max-requests) for more information. + +::: warning It is generally advised to not use `gunicorn` unless you need it. The Sanic Server is primed for running Sanic in production. Weigh your considerations carefully before making this choice. Gunicorn does provide a lot of configuration options, but it is not the best choice for getting Sanic to run at its fastest. ::: + +## Performance considerations + +---:1 When running in production, make sure you turn off `debug`. :--:1 +```python +app.run(..., debug=False) +``` +:--- + +---:1 Sanic will also perform fastest if you turn off `access_log`. + +If you still require access logs, but want to enjoy this performance boost, consider using [Nginx as a proxy](./nginx.md), and letting that handle your access logging. It will be much faster than anything Python can handle. :--:1 +```python +app.run(..., access_log=False) +``` +:--- diff --git a/src/pt/guide/deployment/server-choice.md b/src/pt/guide/deployment/server-choice.md new file mode 100644 index 0000000000..9af6345235 --- /dev/null +++ b/src/pt/guide/deployment/server-choice.md @@ -0,0 +1 @@ +# Choosing a server diff --git a/src/pt/guide/getting-started.md b/src/pt/guide/getting-started.md new file mode 100644 index 0000000000..80a5435b22 --- /dev/null +++ b/src/pt/guide/getting-started.md @@ -0,0 +1,86 @@ +# Getting Started + +Before we begin, make sure you are running Python 3.7 or higher. Currently, is known to work with Python versions 3.7, 3.8 and 3.9. + +## Install + +```bash +pip install sanic +``` + +## Hello, world application + +---:1 + +If you have ever used one of the many decorator based frameworks, this probably looks somewhat familiar to you. + +::: tip +If you are coming from Flask or another framework, there are a few important things to point out. Remember, Sanic aims for performance, flexibility, and ease of use. These guiding principles have tangible impact on the API and how it works. +::: + + + +:--:1 + +```python +from sanic import Sanic +from sanic.response import text + +app = Sanic("MyHelloWorldApp") + +@app.get("/") +async def hello_world(request): + return text("Hello, world.") +``` + +:--- + +### Important to note + +- Every request handler can either be sync (`def hello_world`) or async (`async def hello_world`). Unless you have a clear reason for it, always go with `async`. +- The `request` object is always the first argument of your handler. Other frameworks pass this around in a context variable to be imported. In the `async` world, this would not work so well and it is far easier (not to mention cleaner and more performant) to be explicit about it. +- You **must** use a response type. MANY other frameworks allow you to have a return value like this: `return "Hello, world."` or this: `return {"foo": "bar"}`. But, in order to do this implicit calling, somewhere in the chain needs to spend valuable time trying to determine what you meant. So, at the expense of this ease, Sanic has decided to require an explicit call. + +### Running + +---:1 Let's save the above file as `server.py`. And launch it. :--:1 +```bash +sanic server.app +``` +:--- + +::: tip This **another** important distinction. Other frameworks come with a built in development server and explicitly say that it is _only_ intended for development use. The opposite is true with Sanic. + +**The packaged server is production ready.** ::: + +## Sanic Extensions + +Sanic intentionally aims for a clean and unopinionated feature list. The project does not want to require you to build your application in a certain way, and tries to avoid prescribing specific development patterns. There are a number of third-party plugins that are built and maintained by the community to add additional features that do not otherwise meet the requirements of the core repository. + +However, in order **to help API developers**, the Sanic organization maintains an official plugin called [Sanic Extensions](../plugins/sanic-ext/getting-started.md) to provide all sorts of goodies, including: + +- **OpenAPI** documentation with Redoc and/or Swagger +- **CORS** protection +- **Dependency injection** into route handlers +- Request query arguments and body input **validation** +- Auto create `HEAD`, `OPTIONS`, and `TRACE` endpoints +- Predefined, endpoint-specific response serializers + +The preferred method to set it up is to install it along with Sanic, but you can also install the packages on their own. + +---:1 +``` +$ pip install sanic[ext] +``` +:--:1 +``` +$ pip install sanic sanic-ext +``` +:--- + +Starting in v21.12, Sanic will automatically setup Sanic Extensions if it is in the same environment. You will also have access to two additional application properties: + +- `app.extend()` - used to configure Sanic Extensions +- `app.ext` - the `Extend` instance attached to the application + +See [the plugin documentation](../plugins/sanic-ext/getting-started.md) for more information about how to use and work with the plugin diff --git a/src/pt/guide/how-to/README.md b/src/pt/guide/how-to/README.md new file mode 100644 index 0000000000..8e66fc0483 --- /dev/null +++ b/src/pt/guide/how-to/README.md @@ -0,0 +1 @@ +# How to ... diff --git a/src/pt/guide/how-to/authentication.md b/src/pt/guide/how-to/authentication.md new file mode 100644 index 0000000000..9d80fd4c0b --- /dev/null +++ b/src/pt/guide/how-to/authentication.md @@ -0,0 +1,113 @@ +# Authentication + +> How do I control authentication and authorization? + +This is an _extremely_ complicated subject to cram into a few snippets. But, this should provide you with an idea on ways to tackle this problem. This example uses [JWTs](https://jwt.io/), but the concepts should be equally applicable to sessions or some other scheme. + +:::: tabs +::: tab server.py +```python +from sanic import Sanic, text + +from auth import protected +from login import login + +app = Sanic("AuthApp") +app.config.SECRET = "KEEP_IT_SECRET_KEEP_IT_SAFE" +app.blueprint(login) + + +@app.get("/secret") +@protected +async def secret(request): + return text("To go fast, you must be fast.") +``` +::: +::: tab login.py +```python +import jwt +from sanic import Blueprint, text + +login = Blueprint("login", url_prefix="/login") + + +@login.post("/") +async def do_login(request): + token = jwt.encode({}, request.app.config.SECRET) + return text(token) +``` +::: +::: tab auth.py +```python +from functools import wraps + +import jwt +from sanic import text + + +def check_token(request): + if not request.token: + return False + + try: + jwt.decode( + request.token, request.app.config.SECRET, algorithms=["HS256"] + ) + except jwt.exceptions.InvalidTokenError: + return False + else: + return True + + +def protected(wrapped): + def decorator(f): + @wraps(f) + async def decorated_function(request, *args, **kwargs): + is_authenticated = check_token(request) + + if is_authenticated: + response = await f(request, *args, **kwargs) + return response + else: + return text("You are unauthorized.", 401) + + return decorated_function + + return decorator(wrapped) +``` +This decorator pattern is taken from the [decorators page](/en/guide/best-practices/decorators.md). ::: +:::: + +```bash +$ curl localhost:9999/secret -i +HTTP/1.1 401 Unauthorized +content-length: 21 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +You are unauthorized. + +$ curl localhost:9999/login -X POST 7 ↵ +eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.rjxS7ztIGt5tpiRWS8BGLUqjQFca4QOetHcZTi061DE + +$ curl localhost:9999/secret -i -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.rjxS7ztIGt5tpiRWS8BGLUqjQFca4QOetHcZTi061DE" +HTTP/1.1 200 OK +content-length: 29 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +To go fast, you must be fast. + +$ curl localhost:9999/secret -i -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.BAD" +HTTP/1.1 401 Unauthorized +content-length: 21 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +You are unauthorized. +``` + +Also, checkout some resources from the community: + +- Awesome Sanic - [Authorization](https://github.com/mekicha/awesome-sanic/blob/master/README.md#authentication) & [Session](https://github.com/mekicha/awesome-sanic/blob/master/README.md#session) +- [EuroPython 2020 - Overcoming access control in web APIs](https://www.youtube.com/watch?v=Uqgoj43ky6A) diff --git a/src/pt/guide/how-to/autodiscovery.md b/src/pt/guide/how-to/autodiscovery.md new file mode 100644 index 0000000000..4cf319357b --- /dev/null +++ b/src/pt/guide/how-to/autodiscovery.md @@ -0,0 +1,161 @@ +--- +title: Autodiscovery +--- + + +# Autodiscovery of Blueprints, Middleware, and Listeners + +> How do I autodiscover the components I am using to build my application? + +One of the first problems someone faces when building an application, is *how* to structure the project. Sanic makes heavy use of decorators to register route handlers, middleware, and listeners. And, after creating blueprints, they need to be mounted to the application. + +A possible solution is a single file in which **everything** is imported and applied to the Sanic instance. Another is passing around the Sanic instance as a global variable. Both of these solutions have their drawbacks. + +An alternative is autodiscovery. You point your application at modules (already imported, or strings), and let it wire everything up. + +:::: tabs +::: tab server.py +```python +from sanic import Sanic +from sanic.response import empty + +import blueprints +from utility import autodiscover + +app = Sanic("auto", register=True) +autodiscover( + app, + blueprints, + "parent.child", + "listeners.something", + recursive=True, +) + +app.route("/")(lambda _: empty()) +``` +```bash +[2021-03-02 21:37:02 +0200] [880451] [INFO] Goin' Fast @ http://127.0.0.1:9999 +[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something +[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something @ nested +[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something @ level1 +[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something @ level3 +[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something inside __init__.py +[2021-03-02 21:37:02 +0200] [880451] [INFO] Starting worker [880451] +``` +::: +::: tab utility.py +```python + +from glob import glob +from importlib import import_module, util +from inspect import getmembers +from pathlib import Path +from types import ModuleType +from typing import Union + +from sanic.blueprints import Blueprint + + +def autodiscover( + app, *module_names: Union[str, ModuleType], recursive: bool = False +): + mod = app.__module__ + blueprints = set() + _imported = set() + + def _find_bps(module): + nonlocal blueprints + + for _, member in getmembers(module): + if isinstance(member, Blueprint): + blueprints.add(member) + + for module in module_names: + if isinstance(module, str): + module = import_module(module, mod) + _imported.add(module.__file__) + _find_bps(module) + + if recursive: + base = Path(module.__file__).parent + for path in glob(f"{base}/**/*.py", recursive=True): + if path not in _imported: + name = "module" + if "__init__" in path: + *_, name, __ = path.split("/") + spec = util.spec_from_file_location(name, path) + specmod = util.module_from_spec(spec) + _imported.add(path) + spec.loader.exec_module(specmod) + _find_bps(specmod) + + for bp in blueprints: + app.blueprint(bp) +``` +::: +::: tab blueprints/level1.py +```python +from sanic import Blueprint +from sanic.log import logger + +level1 = Blueprint("level1") + + +@level1.after_server_start +def print_something(app, loop): + logger.debug("something @ level1") +``` +::: +::: tab blueprints/one/two/level3.py +```python +from sanic import Blueprint +from sanic.log import logger + +level3 = Blueprint("level3") + + +@level3.after_server_start +def print_something(app, loop): + logger.debug("something @ level3") +``` +::: +::: tab listeners/something.py +```python +from sanic import Sanic +from sanic.log import logger + +app = Sanic.get_app("auto") + + +@app.after_server_start +def print_something(app, loop): + logger.debug("something") +``` +::: +::: tab parent/child/__init__.py +```python +from sanic import Blueprint +from sanic.log import logger + +bp = Blueprint("__init__") + + +@bp.after_server_start +def print_something(app, loop): + logger.debug("something inside __init__.py") +``` +::: +::: tab parent/child/nested.py +```python +from sanic import Blueprint +from sanic.log import logger + +nested = Blueprint("nested") + + +@nested.after_server_start +def print_something(app, loop): + logger.debug("something @ nested") +``` +::: +:::: diff --git a/src/pt/guide/how-to/cors.md b/src/pt/guide/how-to/cors.md new file mode 100644 index 0000000000..d1db877a9a --- /dev/null +++ b/src/pt/guide/how-to/cors.md @@ -0,0 +1,137 @@ +--- +title: CORS +--- + + +# Cross-origin resource sharing (CORS) + +> How do I configure my application for CORS? + +The best solution is to use [Sanic Extensions](../../plugins/sanic-ext/http/cors.md). However, if you would like to build your own version, you could use this limited example. + +:::: tabs +::: tab server.py +```python +from sanic import Sanic, text + +from cors import add_cors_headers +from options import setup_options + +app = Sanic("app") + + +@app.route("/", methods=["GET", "POST"]) +async def do_stuff(request): + return text("...") + + +# Add OPTIONS handlers to any route that is missing it +app.register_listener(setup_options, "before_server_start") + +# Fill in CORS headers +app.register_middleware(add_cors_headers, "response") +``` +::: +::: tab cors.py +```python +from typing import Iterable + + +def _add_cors_headers(response, methods: Iterable[str]) -> None: + allow_methods = list(set(methods)) + if "OPTIONS" not in allow_methods: + allow_methods.append("OPTIONS") + headers = { + "Access-Control-Allow-Methods": ",".join(allow_methods), + "Access-Control-Allow-Origin": "mydomain.com", + "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Headers": ( + "origin, content-type, accept, " + "authorization, x-xsrf-token, x-request-id" + ), + } + response.headers.extend(headers) + + +def add_cors_headers(request, response): + if request.method != "OPTIONS": + methods = [method for method in request.route.methods] + _add_cors_headers(response, methods) +``` +::: +::: tab options.py +```python +from collections import defaultdict +from typing import Dict, FrozenSet + +from sanic import Sanic, response +from sanic.router import Route + +from cors import _add_cors_headers + + +def _compile_routes_needing_options( + routes: Dict[str, Route] +) -> Dict[str, FrozenSet]: + needs_options = defaultdict(list) + # This is 21.12 and later. You will need to change this for older versions. + for route in routes.values(): + if "OPTIONS" not in route.methods: + needs_options[route.uri].extend(route.methods) + + return { + uri: frozenset(methods) for uri, methods in dict(needs_options).items() + } + + +def _options_wrapper(handler, methods): + def wrapped_handler(request, *args, **kwargs): + nonlocal methods + return handler(request, methods) + + return wrapped_handler + + +async def options_handler(request, methods) -> response.HTTPResponse: + resp = response.empty() + _add_cors_headers(resp, methods) + return resp + + +def setup_options(app: Sanic, _): + app.router.reset() + needs_options = _compile_routes_needing_options(app.router.routes_all) + for uri, methods in needs_options.items(): + app.add_route( + _options_wrapper(options_handler, methods), + uri, + methods=["OPTIONS"], + ) + app.router.finalize() +``` +::: +:::: +``` +$ curl localhost:9999/ -i +HTTP/1.1 200 OK +Access-Control-Allow-Methods: OPTIONS,POST,GET +Access-Control-Allow-Origin: mydomain.com +Access-Control-Allow-Credentials: true +Access-Control-Allow-Headers: origin, content-type, accept, authorization, x-xsrf-token, x-request-id +content-length: 3 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +... + +$ curl localhost:9999/ -i -X OPTIONS +HTTP/1.1 204 No Content +Access-Control-Allow-Methods: GET,POST,OPTIONS +Access-Control-Allow-Origin: mydomain.com +Access-Control-Allow-Credentials: true +Access-Control-Allow-Headers: origin, content-type, accept, authorization, x-xsrf-token, x-request-id +connection: keep-alive +``` +Also, checkout some resources from the community: + +- [Awesome Sanic](https://github.com/mekicha/awesome-sanic/blob/master/README.md#frontend) diff --git a/src/pt/guide/how-to/csrf.md b/src/pt/guide/how-to/csrf.md new file mode 100644 index 0000000000..7f982ff12f --- /dev/null +++ b/src/pt/guide/how-to/csrf.md @@ -0,0 +1 @@ +csrf diff --git a/src/pt/guide/how-to/db.md b/src/pt/guide/how-to/db.md new file mode 100644 index 0000000000..60ca822eac --- /dev/null +++ b/src/pt/guide/how-to/db.md @@ -0,0 +1 @@ +connecting to data sources diff --git a/src/pt/guide/how-to/decorators.md b/src/pt/guide/how-to/decorators.md new file mode 100644 index 0000000000..9ef318134e --- /dev/null +++ b/src/pt/guide/how-to/decorators.md @@ -0,0 +1 @@ +decorators diff --git a/src/pt/guide/how-to/mounting.md b/src/pt/guide/how-to/mounting.md new file mode 100644 index 0000000000..918c422cdb --- /dev/null +++ b/src/pt/guide/how-to/mounting.md @@ -0,0 +1,52 @@ +# Application Mounting + +> How do I mount my application at some path above the root? + +```python +# server.py +from sanic import Sanic, text + +app = Sanic("app") +app.config.SERVER_NAME = "example.com/api" + + +@app.route("/foo") +def handler(request): + url = app.url_for("handler", _external=True) + return text(f"URL: {url}") +``` + +```yaml +# docker-compose.yml +version: "3.7" +services: + app: + image: nginx:alpine + ports: + - 80:80 + volumes: + - type: bind + source: ./conf + target: /etc/nginx/conf.d/default.conf +``` + +```nginx +# conf +server { + listen 80; + + # Computed data service + location /api/ { + proxy_pass http://:9999/; + proxy_set_header Host example.com; + } +} +``` +```bash +$ docker-compose up -d +$ sanic server.app --port=9999 --host=0.0.0.0 +``` +```bash +$ curl localhost/api/foo +URL: http://example.com/api/foo +``` diff --git a/src/pt/guide/how-to/orm.md b/src/pt/guide/how-to/orm.md new file mode 100644 index 0000000000..eb73022681 --- /dev/null +++ b/src/pt/guide/how-to/orm.md @@ -0,0 +1,370 @@ +# ORM + +> How do I use SQLAlchemy with Sanic ? + +All ORM tools can work with Sanic, but non-async ORM tool have a impact on Sanic performance. There are some orm packages who support + +At present, there are many ORMs that support Python's `async`/`await` keywords. Some possible choices include: + +- [Mayim](https://ahopkins.github.io/mayim/) +- [SQLAlchemy 1.4](https://docs.sqlalchemy.org/en/14/changelog/changelog_14.html) +- [tortoise-orm](https://github.com/tortoise/tortoise-orm) + +Integration in to your Sanic application is fairly simple: + +## Mayim + +Mayim ships with [an extension for Sanic Extensions](https://ahopkins.github.io/mayim/guide/extensions.html#sanic), which makes it super simple to get started with Sanic. It is certainly possible to run Mayim with Sanic without the extension, but it is recommended because it handles all of the [lifecycle events](https://sanic.dev/en/guide/basics/listeners.html) and [dependency injections](https://sanic.dev/en/plugins/sanic-ext/injection.html). + +---:1 +### Dependencies + +First, we need to install the required dependencies. See [Mayim docs](https://ahopkins.github.io/mayim/guide/install.html#postgres) for the installation needed for your DB driver. :--:1 +```shell +pip install sanic-ext +pip install mayim[postgres] +``` +:--- + +---:1 +### Define ORM Model + +Mayim allows you to use whatever you want for models. Whether it is [dataclasses](https://docs.python.org/3/library/dataclasses.html), [pydantic](https://pydantic-docs.helpmanual.io/), [attrs](https://www.attrs.org/en/stable/), or even just plain `dict` objects. Since it works very nicely [out of the box with Pydantic](https://ahopkins.github.io/mayim/guide/pydantic.html), that is what we will use here. :--:1 +```python +# ./models.py +from pydantic import BaseModel + + +class City(BaseModel): + id: int + name: str + district: str + population: int + + +class Country(BaseModel): + code: str + name: str + continent: str + region: str + capital: City +``` +:--- + +---:1 +### Define SQL + +If you are unfamiliar, Mayim is different from other ORMs in that it is one-way, SQL-first. This means you define your own queries either inline, or in a separate `.sql` file, which is what we will do here. :--:1 +```sql +-- ./queries/select_all_countries.sql +SELECT country.code, + country.name, + country.continent, + country.region, + ( + SELECT row_to_json(q) + FROM ( + SELECT city.id, + city.name, + city.district, + city.population + ) q + ) capital +FROM country + JOIN city ON country.capital = city.id +ORDER BY country.name ASC +LIMIT $limit OFFSET $offset; +``` +:--- + +---:1 +### Create Sanic App and Async Engine + +We need to create the app instance and attach the `SanicMayimExtension` with any executors. :--:1 +```python +# ./server.py +from sanic import Sanic, Request, json +from sanic_ext import Extend +from mayim.executor import PostgresExecutor +from mayim.extensions import SanicMayimExtension +from models import Country + + +class CountryExecutor(PostgresExecutor): + async def select_all_countries( + self, limit: int = 4, offset: int = 0 + ) -> list[Country]: + ... + + +app = Sanic("Test") +Extend.register( + SanicMayimExtension( + executors=[CountryExecutor], + dsn="postgres://...", + ) +) +``` +:--- + +---:1 +### Register Routes + +Because we are using Mayim's extension for Sanic, we have the automatic `CountryExecutor` injection into the route handler. It makes for an easy, type-annotated development experience. :--:1 +```python +@app.get("/") +async def handler(request: Request, executor: CountryExecutor): + countries = await executor.select_all_countries() + return json({"countries": [country.dict() for country in co +``` +:--- + +---:1 +### Send Requests +:--:1 +```sh +curl 'http://127.0.0.1:8000' +{"countries":[{"code":"AFG","name":"Afghanistan","continent":"Asia","region":"Southern and Central Asia","capital":{"id":1,"name":"Kabul","district":"Kabol","population":1780000}},{"code":"ALB","name":"Albania","continent":"Europe","region":"Southern Europe","capital":{"id":34,"name":"Tirana","district":"Tirana","population":270000}},{"code":"DZA","name":"Algeria","continent":"Africa","region":"Northern Africa","capital":{"id":35,"name":"Alger","district":"Alger","population":2168000}},{"code":"ASM","name":"American Samoa","continent":"Oceania","region":"Polynesia","capital":{"id":54,"name":"Fagatogo","district":"Tutuila","population":2323}}]} +``` +:--- + + +## SQLAlchemy + +Because [SQLAlchemy 1.4](https://docs.sqlalchemy.org/en/14/changelog/changelog_14.html) has added native support for `asyncio`, Sanic can finally work well with SQLAlchemy. Be aware that this functionality is still considered *beta* by the SQLAlchemy project. + + +---:1 +### Dependencies + +First, we need to install the required dependencies. In the past, the dependencies installed were `sqlalchemy` and `pymysql`, but now `sqlalchemy` and `aiomysql` are needed. :--:1 +```shell +pip install -U sqlalchemy +pip install -U aiomysql +``` +:--- + +---:1 +### Define ORM Model + +ORM model creation remains the same. :--:1 +```python +# ./models.py +from sqlalchemy import INTEGER, Column, ForeignKey, String +from sqlalchemy.orm import declarative_base, relationship + +Base = declarative_base() + + +class BaseModel(Base): + __abstract__ = True + id = Column(INTEGER(), primary_key=True) + + +class Person(BaseModel): + __tablename__ = "person" + name = Column(String()) + cars = relationship("Car") + + def to_dict(self): + return {"name": self.name, "cars": [{"brand": car.brand} for car in self.cars]} + + +class Car(BaseModel): + __tablename__ = "car" + + brand = Column(String()) + user_id = Column(ForeignKey("person.id")) + user = relationship("Person", back_populates="cars") +``` +:--- + +---:1 +### Create Sanic App and Async Engine + +Here we use mysql as the database, and you can also choose PostgreSQL/SQLite. Pay attention to changing the driver from `aiomysql` to `asyncpg`/`aiosqlite`. :--:1 +```python +# ./server.py +from sanic import Sanic +from sqlalchemy.ext.asyncio import create_async_engine + +app = Sanic("my_app") + +bind = create_async_engine("mysql+aiomysql://root:root@localhost/test", echo=True) +``` +:--- + +---:1 +### Register Middlewares + +The request middleware creates an usable `AsyncSession` object and set it to `request.ctx` and `_base_model_session_ctx`. + +Thread-safe variable `_base_model_session_ctx` helps you to use the session object instead of fetching it from `request.ctx`. :--:1 +```python +# ./server.py +from contextvars import ContextVar + +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import sessionmaker + +_sessionmaker = sessionmaker(bind, AsyncSession, expire_on_commit=False) + +_base_model_session_ctx = ContextVar("session") + +@app.middleware("request") +async def inject_session(request): + request.ctx.session = _sessionmaker() + request.ctx.session_ctx_token = _base_model_session_ctx.set(request.ctx.session) + + +@app.middleware("response") +async def close_session(request, response): + if hasattr(request.ctx, "session_ctx_token"): + _base_model_session_ctx.reset(request.ctx.session_ctx_token) + await request.ctx.session.close() +``` +:--- + +---:1 +### Register Routes + +According to sqlalchemy official docs, `session.query` will be legacy in 2.0, and the 2.0 way to query an ORM object is using `select`. :--:1 +```python +# ./server.py +from sqlalchemy import select +from sqlalchemy.orm import selectinload +from sanic.response import json + +from models import Car, Person + + +@app.post("/user") +async def create_user(request): + session = request.ctx.session + async with session.begin(): + car = Car(brand="Tesla") + person = Person(name="foo", cars=[car]) + session.add_all([person]) + return json(person.to_dict()) + + +@app.get("/user/") +async def get_user(request, pk): + session = request.ctx.session + async with session.begin(): + stmt = select(Person).where(Person.id == pk).options(selectinload(Person.cars)) + result = await session.execute(stmt) + person = result.scalar() + + if not person: + return json({}) + + return json(person.to_dict()) +``` +:--- + +---:1 +### Send Requests +:--:1 +```sh +curl --location --request POST 'http://127.0.0.1:8000/user' +{"name":"foo","cars":[{"brand":"Tesla"}]} +``` + +```sh +curl --location --request GET 'http://127.0.0.1:8000/user/1' +{"name":"foo","cars":[{"brand":"Tesla"}]} +``` +:--- + + +## Tortoise-ORM + +---:1 +### Dependencies + +tortoise-orm's dependency is very simple, you just need install tortoise-orm. :--:1 +```shell +pip install -U tortoise-orm +``` +:--- + +---:1 +### Define ORM Model + +If you are familiar with Django, you should find this part very familiar. :--:1 +```python +# ./models.py +from tortoise import Model, fields + + +class Users(Model): + id = fields.IntField(pk=True) + name = fields.CharField(50) + + def __str__(self): + return f"I am {self.name}" +``` +:--- + + +---:1 +### Create Sanic App and Async Engine + +Tortoise-orm provides a set of registration interface, which is convenient for users, and you can use it to create database connection easily. :--:1 +```python +# ./main.py + +from models import Users +from tortoise.contrib.sanic import register_tortoise + +app = Sanic(__name__) + + +register_tortoise( + app, db_url="mysql://root:root@localhost/test", modules={"models": ["models"]}, generate_schemas=True +) + +``` +:--- + +---:1 +### Register Routes +:--:1 +```python +# ./main.py + +from models import Users +from sanic import Sanic, response + + +@app.route("/user") +async def list_all(request): + users = await Users.all() + return response.json({"users": [str(user) for user in users]}) + + +@app.route("/user/") +async def get_user(request, pk): + user = await Users.query(pk=pk) + return response.json({"user": str(user)}) + +if __name__ == "__main__": + app.run(port=5000) +``` +:--- + +---:1 +### Send Requests +:--:1 +```sh +curl --location --request POST 'http://127.0.0.1:8000/user' +{"users":["I am foo", "I am bar"]} +``` + +```sh +curl --location --request GET 'http://127.0.0.1:8000/user/1' +{"user": "I am foo"} +``` +:--- + diff --git a/src/pt/guide/how-to/serialization.md b/src/pt/guide/how-to/serialization.md new file mode 100644 index 0000000000..0dfc62d35b --- /dev/null +++ b/src/pt/guide/how-to/serialization.md @@ -0,0 +1 @@ +# Serialization diff --git a/src/pt/guide/how-to/server-sent-events.md b/src/pt/guide/how-to/server-sent-events.md new file mode 100644 index 0000000000..7daf86745e --- /dev/null +++ b/src/pt/guide/how-to/server-sent-events.md @@ -0,0 +1 @@ +sse diff --git a/src/pt/guide/how-to/static-redirects.md b/src/pt/guide/how-to/static-redirects.md new file mode 100644 index 0000000000..5566050c74 --- /dev/null +++ b/src/pt/guide/how-to/static-redirects.md @@ -0,0 +1,110 @@ +# "Static" Redirects + +> How do I configure static redirects? + +:::: tabs +::: tab app.py +```python +### SETUP ### +import typing +import sanic, sanic.response + +# Create the Sanic app +app = sanic.Sanic(__name__) + +# This dictionary represents your "static" +# redirects. For example, these values +# could be pulled from a configuration file. +REDIRECTS = { + '/':'/hello_world', # Redirect '/' to '/hello_world' + '/hello_world':'/hello_world.html' # Redirect '/hello_world' to 'hello_world.html' +} + +# This function will return another function +# that will return the configured value +# regardless of the arguments passed to it. +def get_static_function(value:typing.Any) -> typing.Callable[..., typing.Any]: + return lambda *_, **__: value + +### ROUTING ### +# Iterate through the redirects +for src, dest in REDIRECTS.items(): + # Create the redirect response object + response:sanic.HTTPResponse = sanic.response.redirect(dest) + + # Create the handler function. Typically, + # only a sanic.Request object is passed + # to the function. This object will be + # ignored. + handler = get_static_function(response) + + # Route the src path to the handler + app.route(src)(handler) + +# Route some file and client resources +app.static('/files/', 'files') +app.static('/', 'client') + +### RUN ### +if __name__ == '__main__': + app.run( + '127.0.0.1', + 10000 + ) +``` +::: + +::: tab client/hello_world.html +```html + + + + + + + Hello World + + + +
+ Hello world! +
+ + +``` +::: + +::: tab client/hello_world.css +```css +#hello_world { + width: 1000px; + margin-left: auto; + margin-right: auto; + margin-top: 100px; + + padding: 100px; + color: aqua; + text-align: center; + font-size: 100px; + font-family: monospace; + + background-color: rgba(0, 0, 0, 0.75); + + border-radius: 10px; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.75); +} + +body { + background-image: url("/files/lake.jpg"); + background-repeat: no-repeat; + background-size: cover; +} +``` +::: + +::: tab files/lake.jpg ![](./assets/images/lake.jpg) ::: +:::: + +Also, checkout some resources from the community: + +- [Static Routing Example](https://github.com/Perzan/sanic-static-routing-example) diff --git a/src/pt/guide/how-to/task-queue.md b/src/pt/guide/how-to/task-queue.md new file mode 100644 index 0000000000..c5ee7c54a3 --- /dev/null +++ b/src/pt/guide/how-to/task-queue.md @@ -0,0 +1 @@ +task queue diff --git a/src/pt/guide/how-to/tls.md b/src/pt/guide/how-to/tls.md new file mode 100644 index 0000000000..5299134cb1 --- /dev/null +++ b/src/pt/guide/how-to/tls.md @@ -0,0 +1,158 @@ +# TLS/SSL/HTTPS + +> How do I run Sanic via HTTPS? + +If you do not have TLS certificates yet, [see the end of this page](./tls.md#get-certificates-for-your-domain-names). + +## Single domain and single certificate + +---:1 Let Sanic automatically load your certificate files, which need to be named `fullchain.pem` and `privkey.pem` in the given folder: + +:--:1 +```sh +sudo sanic myserver:app -H :: -p 443 \ + --tls /etc/letsencrypt/live/example.com/ +``` +```python +app.run("::", 443, ssl="/etc/letsencrypt/live/example.com/") +``` +:--- + +---:1 Or, you can pass cert and key filenames separately as a dictionary: + +Additionally, `password` may be added if the key is encrypted, all fields except for the password are passed to `request.conn_info.cert`. :--:1 +```python +ssl = { + "cert": "/path/to/fullchain.pem", + "key": "/path/to/privkey.pem", + "password": "for encrypted privkey file", # Optional +} +app.run(host="0.0.0.0", port=8443, ssl=ssl) +``` +:--- + +---:1 Alternatively, [`ssl.SSLContext`](https://docs.python.org/3/library/ssl.html) may be passed, if you need full control over details such as which crypto algorithms are permitted. By default Sanic only allows secure algorithms, which may restrict access from very old devices. :--:1 +```python +import ssl + +context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) +context.load_cert_chain("certs/fullchain.pem", "certs/privkey.pem") + +app.run(host="0.0.0.0", port=8443, ssl=context) +``` +:--- + + +## Multiple domains with separate certificates + +---:1 A list of multiple certificates may be provided, in which case Sanic chooses the one matching the hostname the user is connecting to. This occurs so early in the TLS handshake that Sanic has not sent any packets to the client yet. + +If the client sends no SNI (Server Name Indication), the first certificate on the list will be used even though on the client browser it will likely fail with a TLS error due to name mismatch. To prevent this fallback and to cause immediate disconnection of clients without a known hostname, add `None` as the first entry on the list. `--tls-strict-host` is the equivalent CLI option. :--:1 +```python +ssl = ["certs/example.com/", "certs/bigcorp.test/"] +app.run(host="0.0.0.0", port=8443, ssl=ssl) +``` +```sh +sanic myserver:app + --tls certs/example.com/ + --tls certs/bigcorp.test/ + --tls-strict-host +``` +:--- + +::: tip You may also use `None` in front of a single certificate if you do not wish to reveal your certificate, true hostname or site content to anyone connecting to the IP address instead of the proper DNS name. ::: + +---:1 Dictionaries can be used on the list. This allows also specifying which domains a certificate matches to, although the names present on the certificate itself cannot be controlled from here. If names are not specified, the names from the certificate itself are used. + +To only allow connections to the main domain **example.com** and only to subdomains of **bigcorp.test**: + +:--:1 +```python +ssl = [ + None, # No fallback if names do not match! + { + "cert": "certs/example.com/fullchain.pem", + "key": "certs/example.com/privkey.pem", + "names": ["example.com", "*.bigcorp.test"], + } +] +app.run(host="0.0.0.0", port=8443, ssl=ssl) +``` +:--- + +## Accessing TLS information in handlers via `request.conn_info` fields + +* `.ssl` - is the connection secure (bool) +* `.cert` - certificate info and dict fields of the currently active cert (dict) +* `.server_name` - the SNI sent by the client (str, may be empty) + +Do note that all `conn_info` fields are per connection, where there may be many requests over time. If a proxy is used in front of your server, these requests on the same pipe may even come from different users. + +## Redirect HTTP to HTTPS, with certificate requests still over HTTP + +In addition to your normal server(s) running HTTPS, run another server for redirection, `http_redir.py`: + +```python +from sanic import Sanic, exceptions, response + +app = Sanic("http_redir") + +# Serve ACME/certbot files without HTTPS, for certificate renewals +app.static("/.well-known", "/var/www/.well-known", resource_type="dir") + +@app.exception(exceptions.NotFound, exceptions.MethodNotSupported) +def redirect_everything_else(request, exception): + server, path = request.server_name, request.path + if server and path.startswith("/"): + return response.redirect(f"https://{server}{path}", status=308) + return response.text("Bad Request. Please use HTTPS!", status=400) +``` + +It is best to setup this as a systemd unit separate of your HTTPS servers. You may need to run HTTP while initially requesting your certificates, while you cannot run the HTTPS server yet. Start for IPv4 and IPv6: + +``` +sanic http_redir:app -H 0.0.0.0 -p 80 +sanic http_redir:app -H :: -p 80 +``` + +Alternatively, it is possible to run the HTTP redirect application from the main application: + +```python +# app == Your main application +# redirect == Your http_redir application +@app.before_server_start +async def start(app, _): + app.ctx.redirect = await redirect.create_server( + port=80, return_asyncio_server=True + ) + app.add_task(runner(redirect, app.ctx.redirect)) + + +@app.before_server_stop +async def stop(app, _): + await app.ctx.redirect.close() + + +async def runner(app, app_server): + app.is_running = True + try: + app.signalize() + app.finalize() + app.state.is_started = True + await app_server.serve_forever() + finally: + app.is_running = False + app.is_stopping = True +``` + +## Get certificates for your domain names + +You can get free certificates from [Let's Encrypt](https://letsencrypt.org/). Install [certbot](https://certbot.eff.org/) via your package manager, and request a certificate: + +```sh +sudo certbot certonly --key-type ecdsa --preferred-chain "ISRG Root X1" -d example.com -d www.example.com +``` + +Multiple domain names may be added by further `-d` arguments, all stored into a single certificate which gets saved to `/etc/letsencrypt/live/example.com/` as per **the first domain** that you list here. + +The key type and preferred chain options are necessary for getting a minimal size certificate file, essential for making your server run as *fast* as possible. The chain will still contain one RSA certificate until when Let's Encrypt gets their new EC chain trusted in all major browsers, possibly around 2023. diff --git a/src/pt/guide/how-to/toc.md b/src/pt/guide/how-to/toc.md new file mode 100644 index 0000000000..4afddd9afe --- /dev/null +++ b/src/pt/guide/how-to/toc.md @@ -0,0 +1,13 @@ +# Table of Contents + +We have compiled fully working examples to answer common questions and user cases. For the most part, the examples are as minimal as possible, but should be complete and runnable solutions. + +| Page | How do I ... | +|:------------------------------------------- |:------------------------------------------------------------------- | +| [Application mounting](./mounting.md) | ... mount my application at some path above the root? | +| [Authentication](./authentication.md) | ... control authentication and authorization? | +| [Autodiscovery](./autodiscovery.md) | ... autodiscover the components I am using to build my application? | +| [CORS](./cors.md) | ... configure my application for CORS? | +| [ORM](./orm) | ... use an ORM with Sanic? | +| ["Static" Redirects](./static-redirects.md) | ... configure static redirects | +| [TLS/SSL/HTTPS](./tls.md) | ... run Sanic via HTTPS?
... redirect HTTP to HTTPS? | diff --git a/src/pt/guide/how-to/validation.md b/src/pt/guide/how-to/validation.md new file mode 100644 index 0000000000..d45b2dea4c --- /dev/null +++ b/src/pt/guide/how-to/validation.md @@ -0,0 +1 @@ +validation diff --git a/src/pt/guide/how-to/websocket-feed.md b/src/pt/guide/how-to/websocket-feed.md new file mode 100644 index 0000000000..ae2abc2def --- /dev/null +++ b/src/pt/guide/how-to/websocket-feed.md @@ -0,0 +1 @@ +websocket feed diff --git a/src/pt/guide/release-notes/v21.12.md b/src/pt/guide/release-notes/v21.12.md new file mode 100644 index 0000000000..d2a443b404 --- /dev/null +++ b/src/pt/guide/release-notes/v21.12.md @@ -0,0 +1,486 @@ +# Version 21.12 (LTS) + +[[toc]] + +## Introduction + +This is the final release of the version 21 [release cycle](../../org/policies.md#release-schedule). Version 21 will now enter long-term support and will be supported for two years until December 2023. + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + +### Strict application and blueprint names + +In [v21.6](./v21.6.md#stricter-application-and-blueprint-names-and-deprecation) application and blueprint names were required to conform to a new set of restrictions. That change is now being enforced at startup time. + +Names **must**: + +1. Only use alphanumeric characters (`a-zA-Z0-9`) +2. May contain a hyphen (`-`) or an underscore (`_`) +3. Must begin with a letter or underscore (`a-zA-Z_`) + +### Strict application and blueprint properties + +The old leniency to allow directly setting properties of a `Sanic` or `Blueprint` object was deprecated and no longer allowed. You must use the `ctx` object. + +```python +app = Sanic("MyApp") +app.ctx.db = Database() +``` + +### Removals + +The following deprecated features no longer exist: + +- `sanic.exceptions.abort` +- `sanic.views.CompositionView` +- `sanic.response.StreamingHTTPResponse` + +### Upgrade your streaming responses (if not already) + +The `sanic.response.stream` response method has been **deprecated** and will be removed in v22.6. If you are sill using an old school streaming response, please upgrade it. + +**OLD - Deprecated** + +```python +async def sample_streaming_fn(response): + await response.write("foo,") + await response.write("bar") + +@app.route("/") +async def test(request: Request): + return stream(sample_streaming_fn, content_type="text/csv") +``` + +**Current** + +```python +async def sample_streaming_fn(response): + await response.write("foo,") + await response.write("bar") + +@app.route("/") +async def test(request: Request): + response = await request.respond(content_type="text/csv") + await response.send("foo,") + await response.send("bar") +``` + +### CLI overhaul and MOTD (Message of the Day) + +The Sanic CLI has received a fairly extensive upgrade. It adds a bunch of new features to make it on par with `app.run()`. It also includes a new MOTD display to provide quick, at-a-glance highlights about your running environment. The MOTD is TTY-aware, and therefore will be less verbose in server logs. It is mainly intended as a convenience during application development. + +``` +$ sanic --help +usage: sanic [-h] [--version] [--factory] [-s] [-H HOST] [-p PORT] [-u UNIX] [--cert CERT] [--key KEY] [--tls DIR] [--tls-strict-host] + [-w WORKERS | --fast] [--access-logs | --no-access-logs] [--debug] [-d] [-r] [-R PATH] [--motd | --no-motd] [-v] + [--noisy-exceptions | --no-noisy-exceptions] + module + + ▄███ █████ ██ ▄█▄ ██ █ █ ▄██████████ + ██ █ █ █ ██ █ █ ██ + ▀███████ ███▄ ▀ █ █ ██ ▄ █ ██ + ██ █████████ █ ██ █ █ ▄▄ + ████ ████████▀ █ █ █ ██ █ ▀██ ███████ + + To start running a Sanic application, provide a path to the module, where + app is a Sanic() instance: + + $ sanic path.to.server:app + + Or, a path to a callable that returns a Sanic() instance: + + $ sanic path.to.factory:create_app --factory + + Or, a path to a directory to run as a simple HTTP server: + + $ sanic ./path/to/static --simple + +Required +======== + Positional: + module Path to your Sanic app. Example: path.to.server:app + If running a Simple Server, path to directory to serve. Example: ./ + +Optional +======== + General: + -h, --help show this help message and exit + --version show program's version number and exit + + Application: + --factory Treat app as an application factory, i.e. a () -> callable + -s, --simple Run Sanic as a Simple Server, and serve the contents of a directory + (module arg should be a path) + + Socket binding: + -H HOST, --host HOST Host address [default 127.0.0.1] + -p PORT, --port PORT Port to serve on [default 8000] + -u UNIX, --unix UNIX location of unix socket + + TLS certificate: + --cert CERT Location of fullchain.pem, bundle.crt or equivalent + --key KEY Location of privkey.pem or equivalent .key file + --tls DIR TLS certificate folder with fullchain.pem and privkey.pem + May be specified multiple times to choose multiple certificates + --tls-strict-host Only allow clients that send an SNI matching server certs + + Worker: + -w WORKERS, --workers WORKERS Number of worker processes [default 1] + --fast Set the number of workers to max allowed + --access-logs Display access logs + --no-access-logs No display access logs + + Development: + --debug Run the server in debug mode + -d, --dev Currently is an alias for --debug. But starting in v22.3, + --debug will no longer automatically trigger auto_restart. + However, --dev will continue, effectively making it the + same as debug + auto_reload. + -r, --reload, --auto-reload Watch source directory for file changes and reload on changes + -R PATH, --reload-dir PATH Extra directories to watch and reload on changes + + Output: + --motd Show the startup display + --no-motd No show the startup display + -v, --verbosity Control logging noise, eg. -vv or --verbosity=2 [default 0] + --noisy-exceptions Output stack traces for all exceptions + --no-noisy-exceptions No output stack traces for all exceptions +``` + +### Server running modes and changes coming to `debug` + +There are now two running modes: `DEV` and `PRODUCTION`. By default, Sanic server will run under `PRODUCTION` mode. This is intended for deployments. + +Currently, `DEV` mode will operate very similarly to how `debug=True` does in older Sanic versions. However, in v22.3. `debug=True` will **no longer** enable auto-reload. If you would like to have debugging and auto-reload, you should enable `DEV` mode. + +**DEVELOPMENT** + +``` +$ sanic server:app --dev +``` + +```python +app.run(debug=True, auto_reload=True) +``` + +**PRODUCTION** + +``` +$ sanic server:app +``` + +```python +app.run() +``` + +Beginning in v22.3, `PRODUCTION` mode will no longer enable access logs by default. + +A summary of the changes are as follows: + +| Flag | Mode | Tracebacks | Logging | Access logs | Reload | Max workers | +| ------- | ----- | ---------- | ------- | ----------- | ------ | ----------- | +| --debug | DEBUG | yes | DEBUG | yes | ^1 | | +| | PROD | no | INFO ^2 | ^3 | | | +| --dev | DEBUG | yes | DEBUG | yes | yes | | +| --fast | | | | | | yes | + + +- ^1 `--debug` to deprecate auto-reloading and remove in 22.3 +- ^2 After 22.3 this moves to WARNING +- ^3 After 22.3: no + +### Max allowed workers + +You can easily spin up the maximum number of allowed workers using `--fast`. + +``` +$ sanic server:app --fast +``` + +```python +app.run(fast=True) +``` + +### First-class Sanic Extensions support + +[Sanic Extensions](../../plugins/sanic-ext/getting-started.md) provides a number of additional features specifically intended for API developers. You can now easily implement all of the functionality it has to offer without additional setup as long as the package is in the environment. These features include: + +- Auto create `HEAD`, `OPTIONS`, and `TRACE` endpoints +- CORS protection +- Predefined, endpoint-specific response serializers +- Dependency injection into route handlers +- OpenAPI documentation with Redoc and/or Swagger +- Request query arguments and body input validation + +The preferred method is to install it along with Sanic, but you can also install the packages on their own. + +---:1 +``` +$ pip install sanic[ext] +``` + +:--:1 + +``` +$ pip install sanic sanic-ext +``` + +:--- + +After that, no additional configuration is required. Sanic Extensions will be attached to your application and provide all of the additional functionality with **no further configuration**. + +If you want to change how it works, or provide additional configuration, you can change Sanic extensions using `app.extend`. The following examples are equivalent. The `Config` object is to provide helpful type annotations for IDE development. + +---:1 +```python +# This is optional, not required +app = Sanic("MyApp") +app.extend(config={"oas_url_prefix": "/apidocs"}) +``` +:--: +```python +# This is optional, not required +app = Sanic("MyApp") +app.config.OAS_URL_PREFIX = "/apidocs" +``` +:--- + +---:1 +```python +# This is optional, not required +from sanic_ext import Config + +app = Sanic("MyApp") +app.extend(config=Config(oas_url_prefix="/apidocs")) +``` +:--: + +:--- + +### Contextual exceptions + +In [v21.9](./v21.9.md#default-exception-messages) we added default messages to exceptions that simplify the ability to consistently raise exceptions throughout your application. + +```python +class TeapotError(SanicException): + status_code = 418 + message = "Sorry, I cannot brew coffee" + +raise TeapotError +``` + +But this lacked two things: + +1. A dynamic and predictable message format +2. The ability to add additional context to an error message (more on this in a moment) + +The current release allows any Sanic exception to have additional information to when raised to provide context when writing an error message: + +```python +class TeapotError(SanicException): + status_code = 418 + + @property + def message(self): + return f"Sorry {self.extra['name']}, I cannot make you coffee" + +raise TeapotError(extra={"name": "Adam"}) +``` + +The new feature allows the passing of `extra` meta to the exception instance. This `extra` info object **will be suppressed** when in `PRODUCTION` mode, but displayed in `DEVELOPMENT` mode. + +---:1 **PRODUCTION** + +![image](https://user-images.githubusercontent.com/166269/139014161-cda67cd1-843f-4ad2-9fa1-acb94a59fc4d.png) :--:1 **DEVELOPMENT** + +![image](https://user-images.githubusercontent.com/166269/139014121-0596b084-b3c5-4adb-994e-31ba6eba6dad.png) :--- + +Getting back to item 2 from above: _The ability to add additional context to an error message_ + +This is particularly useful when creating microservices or an API that you intend to pass error messages back in JSON format. In this use case, we want to have some context around the exception beyond just a parseable error message to return details to the client. + + +```python +raise TeapotError(context={"foo": "bar"}) +``` + +This is information **that we want** to always be passed in the error (when it is available). Here is what it should look like: + +---:1 **PRODUCTION** + +```json +{ + "description": "I'm a teapot", + "status": 418, + "message": "Sorry Adam, I cannot make you coffee", + "context": { + "foo": "bar" + } +} +``` +:--:1 **DEVELOPMENT** + +```json +{ + "description": "I'm a teapot", + "status": 418, + "message": "Sorry Adam, I cannot make you coffee", + "context": { + "foo": "bar" + }, + "extra": { + "name": "Adam", + "more": "lines", + "complex": { + "one": "two" + } + }, + "path": "/", + "args": {}, + "exceptions": [ + { + "type": "TeapotError", + "exception": "Sorry Adam, I cannot make you coffee", + "frames": [ + { + "file": "handle_request", + "line": 83, + "name": "handle_request", + "src": "" + }, + { + "file": "/tmp/p.py", + "line": 17, + "name": "handler", + "src": "raise TeapotError(" + } + ] + } + ] +} +``` +:--- + +### Background task management + +When using the `app.add_task` method to create a background task, there now is the option to pass an optional `name` keyword argument that allows it to be fetched, or cancelled. + +```python +app.add_task(dummy, name="dummy_task") +task = app.get_task("dummy_task") + +app.cancel_task("dummy_task") +``` + +### Route context kwargs in definitions + +When a route is defined, you can add any number of keyword arguments with a `ctx_` prefix. These values will be injected into the route `ctx` object. + +```python +@app.get("/1", ctx_label="something") +async def handler1(request): + ... + +@app.get("/2", ctx_label="something") +async def handler2(request): + ... + +@app.get("/99") +async def handler99(request): + ... + +@app.on_request +async def do_something(request): + if request.route.ctx.label == "something": + ... +``` + +### Blueprints can be registered at any time + +In previous versions of Sanic, there was a strict ordering of when a Blueprint could be attached to an application. If you ran `app.blueprint(bp)` *before* attaching all objects to the Blueprint instance, they would be missed. + +Now, you can attach a Blueprint at anytime and everything attached to it will be included at startup. + +### Noisy exceptions (force all exceptions to logs) + +There is a new `NOISY_EXCEPTIONS` config value. When it is `False` (which is the default), Sanic will respect the `quiet` property of any `SanicException`. This means that an exception with `quiet=True` will not be displayed to the log output. + +However, when setting `NOISY_EXCEPTIONS=True`, all exceptions will be logged regardless of the `quiet` value. + +This can be helpful when debugging. + +```python +app.config.NOISY_EXCEPTIONS = True +``` + +### Signal events as `Enum` + +There is an `Enum` with all of the built-in signal values for convenience. + +```python +from sanic.signals import Event + +@app.signal(Event.HTTP_LIFECYCLE_BEGIN) +async def connection_opened(conn_info): + ... +``` + +### Custom type casting of environment variables + +By default, Sanic will convert an `int`, `float`, or a `bool` value when applying environment variables to the `config` instance. You can extend this with your own converter: + +```python +app = Sanic(..., config=Config(converters=[UUID])) +``` + +### Disable `uvloop` by configuration value + +The usage of `uvloop` can be controlled by configuration value: + + +```python +app.config.USE_UVLOOP = False +``` + +### Run Sanic server with multiple TLS certificates + +Sanic can be run with multiple TLS certificates: + +```python +app.run( + ssl=[ + "/etc/letsencrypt/live/example.com/", + "/etc/letsencrypt/live/mysite.example/", + ] +) +``` + +## News + +### Coming Soon: Python Web Development with Sanic + +A book about Sanic is coming soon by one of the core developers, [@ahopkins](https://github.com/ahopkins). + +Learn more at [sanicbook.com](https://sanicbook.com). + +> Get equipped with the practical knowledge of working with Sanic to increase the performance and scalability of your web applications. While doing that, we will level-up your development skills as you learn to customize your application to meet the changing business needs without having to significantly over-engineer the app. + +A portion of book proceeds goes into the Sanic Community Organization to help fund the development and operation of Sanic. So, buying the book is another way you can support Sanic. + +### Dark mode for the docs + +If you have not already noticed, this Sanic website is now available in a native dark mode. You can toggle the theme at the top right of the page. + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@adarsharegmi](https://github.com/adarsharegmi) [@ahopkins](https://github.com/ahopkins) [@ashleysommer](https://github.com/ashleysommer) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@cnicodeme](https://github.com/cnicodeme) [@kianmeng](https://github.com/kianmeng) [@meysam81](https://github.com/meysam81) [@nuxion](https://github.com/nuxion) [@prryplatypus](https://github.com/prryplatypus) [@realDragonium](https://github.com/realDragonium) [@SaidBySolo](https://github.com/SaidBySolo) [@sjsadowski](https://github.com/sjsadowski) [@Tronic](https://github.com/tronic) [@Varriount](https://github.com/Varriount) [@vltr](https://github.com/vltr) [@whos4n3](https://github.com/whos4n3) + +And, a special thank you to [@miss85246](https://github.com/miss85246) and [@ZinkLu](https://github.com/ZinkLu) for their tremendous work keeping the documentation synced and translated into Chinese. + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/pt/guide/release-notes/v21.3.md b/src/pt/guide/release-notes/v21.3.md new file mode 100644 index 0000000000..bca0729af6 --- /dev/null +++ b/src/pt/guide/release-notes/v21.3.md @@ -0,0 +1,262 @@ +# Version 21.3 + +[[toc]] + +## Introduction + +Sanic is now faster. + +Well, it already was fast. But with the first iteration of the v21 release, we incorporated a few major milestones that have made some tangible improvements. These encompass some ideas that have been in the works for years, and have finally made it into the released version. + +::: warning Breaking changes Version 21.3 introduces a lot of new features. But, it also includes some breaking changes. This is why these changes were introduced after the last LTS. If you rely upon something that has been removed, you should continue to use v20.12LTS until you are able to upgrade. + +```bash +pip install "sanic>=20.12,<20.13" +pip freeze > requirements.txt +``` + +For most typical installations, you should be able to upgrade without a problem. ::: + +## What to know + +Notable new or breaking features, and what to upgrade... + +### Python 3.7+ Only + +This version drops Python 3.6 support. Version 20.12LTS will continue to support Python 3.6 until its EOL in December, 2022, and version 19.12LTS will support it until its EOL in December, 2021. + +Read more about our [LTS policy](../project/policies.md#long-term-support-v-interim-releases). + +### Streaming as first class citizen + +The biggest speed improvement came from unifying the request/response cycle into a single flow. Previously, there was a difference between regular cycles, and streaming cycles. This has been simplified under the hood, even though the API is staying the same right now for compatibility. The net benefit is that **all** requests now should see a new benefit. + +Read more about [streaming changes](../advanced/streaming.md#response-streaming). + +### Router overhaul + +The old Sanic router was based upon regular expressions. In addition it suffered from a number of quirks that made it hard to modify at run time, and resulted in some performance issues. This change has been years in the making and now [converts the router to a compiled tree at startup](https://community.sanicframework.org/t/a-fast-new-router/649/41). Look for additional improvements throughout the year. + +The outward facing API has kept backwards compatibility. However, if you were accessing anything inside the router specifically, you many notice some changes. For example: + +1. `Router.get()` has a new return value +2. `Route` is now a proper class object and not a `namedtuple` +3. If building the router manually, you will need to call `Router.finalize()` before it is usable +4. There is a new `` pattern that can be matched in your routes +5. You cannot startup an application without at least one route defined + +The router is now located in its own repository: [sanic-org/sanic-router](https://github.com/sanic-org/sanic-router) and is also its own [standalone package on PyPI](https://pypi.org/project/sanic-routing/). + +### Signals API ⭐️ + +_BETA Feature: API to be finalized in v21.6_ + +A side benefit of the new router is that it can do double duty also powering the [new signals API](https://github.com/sanic-org/sanic/issues/1630). This feature is being released for public usage now, and likely the public API will not change in its final form. + +The core ideas of this feature are: + +1. to allow the developer greater control and access to plugging into the server and request lifecycles, +2. to provide new tools to synchronize and send messages through your application, and +3. to ultimately further increase performance. + +The API introduces three new methods: + +- `@app.signal(...)` - For defining a signal handler. It looks and operates very much like a route. Whenever that signal is dispatched, this handler will be executed. +- `app.event(...)` - An awaitable that can be used anywhere in your application to pause execution until the event is triggered. +- `app.dispatch(...)` - Trigger an event and cause the signal handlers to execute. + +```python +@app.signal("foo.bar.") +async def signal_handler(thing, **kwargs): + print(f"[signal_handler] {thing=}", kwargs) + +async def wait_for_event(app): + while True: + print("> waiting") + await app.event("foo.bar.*") + print("> event found\n") + +@app.after_server_start +async def after_server_start(app, loop): + app.add_task(wait_for_event(app)) + +@app.get("/") +async def trigger(request): + await app.dispatch("foo.bar.baz") + return response.text("Done.") +``` + +### Route naming + +Routes used to be referenced by both `route.name` and `route.endpoint`. While similar, they were slightly different. Now, all routes will be **consistently** namespaced and referenced. + +``` +.[optional:.] +``` + +This new "name" is assigned to the property `route.name`. We are deprecating `route.endpoint`, and will remove that property in v21.9. Until then, it will be an alias for `route.name`. + +In addition, naming prefixes that had been in use for things like static, websocket, and blueprint routes have been removed. + +### New decorators + +Several new convenience decorators to help IDEs with autocomplete. + +```python +# Alias to @app.listener("...") +@app.before_server_start +@app.after_server_stop +@app.before_server_start +@app.after_server_stop + +# Alias to @app.middleware("...") +@app.on_request +@app.on_response +``` + +### Unquote in route + +If you have a route that uses non-ascii characters, Sanic will no longer `unquote` the text for you. You will need to specifically tell the route definition that it should do so. + +```python +@app.route("/overload/", methods=["GET"], unquote=True) +async def handler2(request, param): + return text("OK2 " + param) + +request, response = app.test_client.get("/overload/您好") +assert response.text == "OK2 您好" +``` + +If you forget to do so, your text will remain encoded. + +### Alter `Request.match_info` + +The `match_info` has always provided the data for the matched path parameters. You now have access to modify that, for example in middleware. + +```python +@app.on_request +def convert_to_snake_case(request): + request.match_info = to_snake(request.match_info) +``` + +### Version types in routes + +The `version` argument in routes can now be: + +- `str` +- `int` +- `float` + +```python +@app.route("/foo", version="2.1.1") +@app.route("/foo", version=2) +@app.route("/foo", version=2.1) +``` +### Safe method handling with body + +Route handlers for `GET`, `HEAD`, `OPTIONS` and `DELETE` will not decode any HTTP body passed to it. You can override this: + +```python +@app.delete(..., ignore_body=False) +``` + +### Application, Blueprint and Blueprint Group parity + +The `Sanic` and `Blueprint` classes share a common base. Previously they duplicated a lot of functionality, that lead to slightly different implementations between them. Now that they both inherit the same base class, developers and plugins should have a more consistent API to work with. + +Also, Blueprint Groups now also support common URL extensions like the `version` and `strict_slashes` keyword arguments. + +### Dropped `httpx` from dependencies + +There is no longer a dependency on `httpx`. + +### Removed `testing` library + +Sanic internal testing client has been removed. It is now located in its own repository: [sanic-org/sanic-testing](https://github.com/sanic-org/sanic-testing) and is also its own [standalone package on PyPI](https://pypi.org/project/sanic-testing/). + +If you have `sanic-testing` installed, it will be available and usable on your `Sanic()` application instances as before. So, the **only** change you will need to make is to add `sanic-testing` to your test suite requirements. + +### Application and connection level context (`ctx`) objects + +Version 19.9 [added ](https://github.com/sanic-org/sanic/pull/1666/files) the `request.ctx` API. This helpful construct easily allows for attaching properties and data to a request object (for example, in middleware), and reusing the information elsewhere int he application. + +Similarly, this concept is being extended in two places: + +1. the application instance, and +2. a transport connection. + +#### Application context + +A common use case is to attach properties to the app instance. For the sake of consistency, and to avoid the issue of name collision with Sanic properties, the `ctx` object now exists on `Sanic` instances. + +```python +@app.before_server_startup +async def startup_db(app, _): + # WRONG + app.db = await connect_to_db() + + # CORRECT + app.ctx.db = await connect_to_db() +``` + +#### Connection context + +When a client sends a keep alive header, Sanic will attempt to keep the transport socket [open for a period of time](../deployment/configuration.md#keep-alive-timeout). That transport object now has a `ctx` object available on it. This effectively means that multiple requests from a single client (where the transport layer is being reused) may share state. + +```python +@app.on_request +async def increment_foo(request): + if not hasattr(request.conn_info.ctx, "foo"): + request.conn_info.ctx.foo = 0 + request.conn_info.ctx.foo += 1 + +@app.get("/") +async def count_foo(request): + return text(f"request.conn_info.ctx.foo={request.conn_info.ctx.foo}") +``` + +```bash +$ curl localhost:8000 localhost:8000 localhost:8000 +request.conn_info.ctx.foo=1 +request.conn_info.ctx.foo=2 +request.conn_info.ctx.foo=3 +``` + +::: warning +Connection level context is an experimental feature, and should be finalized in v21.6. +::: + +## News + + +### A NEW frontpage 🎉 + +We have split the documentation into two. The docstrings inside the codebase will still continue to build sphinx docs to ReadTheDocs. However, it will be limited to API documentation. The new frontpage will house the "Sanic User Guide". + +The new site runs on Vuepress. Contributions are welcome. We also invite help in translating the documents. + +As a part of this, we also freshened up the RTD documentation and changed it to API docs only. + +### Chat has moved to Discord + +The Gitter chatroom has taken one step closer to being phased out. In its place we opened a [Discord server](https://discord.gg/FARQzAEMAA). + +### Open Collective + +The Sanic Community Organization has [opened a page on Open Collective](https://opencollective.com/sanic-org) to enable anyone that would like to financially support the development of Sanic. + +### 2021 Release Managers + +Thank you to @sjsadowski and @yunstanford for acting as release managers for both 2019 and 2020. This year's release managers are @ahopkins and @vltr. + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@ahopkins](https://github.com/ahopkins) [@akshgpt7](https://github.com/akshgpt7) [@artcg](https://github.com/artcg) [@ashleysommer](https://github.com/ashleysommer) [@elis-k](https://github.com/elis-k) [@harshanarayana](https://github.com/harshanarayana) [@sjsadowski](https://github.com/sjsadowski) [@tronic](https://github.com/tronic) [@vltr](https://github.com/vltr), + +To [@ConnorZhang](https://github.com/miss85246) and [@ZinkLu](https://github.com/ZinkLu) for translating our documents into Chinese, + +--- + +Make sure to checkout the changelog to get links to all the PRs, etc. diff --git a/src/pt/guide/release-notes/v21.6.md b/src/pt/guide/release-notes/v21.6.md new file mode 100644 index 0000000000..388a97d211 --- /dev/null +++ b/src/pt/guide/release-notes/v21.6.md @@ -0,0 +1,324 @@ +# Version 21.6 + +[[toc]] + +## Introduction + +This is the second release of the version 21 [release cycle](../project/policies.md#release-schedule). There will be one more release in September before version 21 is "finalized" in the December long-term support version. One thing users may have noticed starting in 21.3, the router was moved to its own package: [`sanic-routing`](https://pypi.org/project/sanic-routing). This change is likely to stay for now. Starting with this release, the minimum required version is 0.7.0. + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + +### Deprecation of `StreamingHTTPResponse` + +The use of `StreamingHTTPResponse` has been deprecated and will be removed in the 21.12 release. This impacts both `sanic.response.stream` and `sanic.response.file_stream`, which both under the hood instantiate `StreamingHTTPResponse`. + +Although the exact migration path has yet to be determined, `sanic.response.stream` and `sanic.response.file_stream` will continue to exist in v21.12 in some form as convenience operators. Look for more details throughout this Summer as we hope to have this finalized by the September release. + +### Deprecation of `CompositionView` + +Usage of `CompositionView` has been deprecated and will be removed in 21.12. + +### Deprecation of path parameter types: `string` and `number` + +Going forward, you should use `str` and `float` for path param types instead of `string` and `number`. + +```python +@app.get("//") +async def handler(request, foo: str, bar: float): + ... +``` + +Existing `string` and `number` types are aliased and will continue to work, but will be removed in v21.12. + +### Version 0.7 router upgrades + +This includes a number of bug fixes and more gracefully handles a wider array of edge cases than v0.6. If you experience any patterns that are not supported, [please report them](https://github.com/sanic-org/sanic-routing/issues). You can see some of the issues resolved on the `sanic-routing` [release notes](https://github.com/sanic-org/sanic-routing/releases). + +### Inline streaming with `eof()` + +Version 21.3 included [big changes in how streaming is handled](https://sanic.dev/en/guide/release-notes/v21.3.html#what-to-know). The pattern introduced will become the default (see below). As a convenience, a new `response.eof()` method has been included. It should be called once the final data has been pushed to the client: + +```python +@app.route("/") +async def test(request): + response = await request.respond(content_type="text/csv") + await response.send("foo,") + await response.send("bar") + await response.eof() + return response +``` + +### New path parameter type: `slug` + +You can now specify a dynamic path segment as a `slug` with appropriate matching: + +```python +@app.get("/articles/") +async def article(request, article_slug: str): + ... +``` + +Slugs must consist of lowercase letters or digits. They may contain a hyphen (`-`), but it cannot be the first character. + +``` +this-is-a-slug +with-123-is-also-a-slug +111-at-start-is-a-slug +NOT-a-slug +-NOT-a-slug +``` + +### Stricter application and blueprint names, and deprecation + +Your application and `Blueprint` instances must conform to a stricter set of requirements: + +1. Only consisting of alphanumeric characters +2. May contain a hyphen (`-`) or an underscore (`_`) +3. Must begin with a letter (uppercase or lowercase) + +The naming convention is similar to Python variable naming conventions, with the addition of allowing hyphens (`-`). + +The looser standard has been deprecatated. Beginning in 21.12, non-conformance will be a startup time error. + +### A new access on `Route` object: `route.uri` + +The `Route` object in v21.3 no longer had a `uri` attribute. Instead, the closes you could get was `route.path`. However, because of how `sanic-routing` works, the `path` property does *not* have a leading `/`. This has been corrected so that now there is a `route.uri` with a leading slash: + +```python +route.uri == f"/{route.path}" +``` + +### A new accessor on `Request` object impacting IPs + +To access the IP address of the incoming request, Sanic has had a convenience accessor on the request object: `request.ip`. That is not new, and comes from an underlying object that provides details about the open HTTP connection: `request.conn_info`. + +The current version adds a new `client_ip` accessor to that `conn_info` object. For IPv4, you will not notice a difference. However, for IPv6 applications, the new accessor will provide an "unwrapped" version of the address. Consider the following example: + +```python +@app.get("/") +async def handler(request): + return json( + { + "request.ip": request.ip, + "request.conn_info.client": request.conn_info.client, + "request.conn_info.client_ip": request.conn_info.client_ip, + } + ) + + +app.run(sock=my_ipv6_sock) +``` + +```bash +$ curl http://\[::1\]:8000 +{ + "request.ip": "::1", + "request.conn_info.client": "[::1]", + "request.conn_info.client_ip": "::1" +} + +``` + +### Alternate `Config` and `Sanic.ctx` objects + +You can now pass your own config and context objects to your Sanic applications. A custom configuration *should* be a subclass of `sanic.config.Config`. The context object can be anything you want, with no restrictions whatsoever. + +```python +class CustomConfig(Config): + ... + +config = CustomConfig() +app = Sanic("custom", config=config) +assert isinstance(app.config, CustomConfig) +``` + +And... + +```python +class CustomContext: + ... + +ctx = CustomContext() +app = Sanic("custom", ctx=ctx) +assert isinstance(app.ctx, CustomContext) +``` + +### Sanic CLI improvements + +1. New flag for existing feature: `--auto-reload` +2. Some new shorthand flags for existing arguments +3. New feature: `--factory` +4. New feature: `--simple` +5. New feature: `--reload-dir` + +#### Factory applications + +For applications that follow the factory pattern (a function that returns a `sanic.Sanic` instance), you can now launch your application from the Sanic CLI using the `--factory` flag. + +```python +from sanic import Blueprint, Sanic, text + +bp = Blueprint(__file__) + +@bp.get("/") +async def handler(request): + return text("😎") + +def create_app() -> Sanic: + app = Sanic(__file__) + app.blueprint(bp) + return app +``` + +You can now run it: + +```bash +$ sanic path.to:create_app --factory +``` + +#### Sanic Simple Server + +Sanic CLI now includes a simple pattern to serve a directory as a web server. It will look for an `index.html` at the directory root. + +```bash +$ sanic ./path/to/dir --simple +``` + +::: warning This feature is still in early *beta* mode. It is likely to change in scope. ::: + +#### Additional reload directories + +When using either `debug` or `auto-reload`, you can include additional directories for Sanic to watch for new files. + +```bash +sanic ... --reload-dir=/path/to/foo --reload-dir=/path/to/bar +``` + +::: tip You do *NOT* need to include this on your application directory. Sanic will automatically reload when any Python file in your application changes. You should use the `reload-dir` argument when you want to listen and update your application when static files are updated. ::: + +### Version prefix + +When adding `version`, your route is prefixed with `/v`. This will always be at the beginning of the path. This is not new. + +```python +# /v1/my/path +app.route("/my/path", version=1) +``` + +Now, you can alter the prefix (and therefore add path segments *before* the version). + +```python +# /api/v1/my/path +app.route("/my/path", version=1, version_prefix="/api/v") +``` + +The `version_prefix` argument is can be defined in: + +- `app.route` and `bp.route` decorators (and all the convenience decorators also) +- `Blueprint` instantiation +- `Blueprint.group` constructor +- `BlueprintGroup` instantiation +- `app.blueprint` registration + +### Signal event auto-registration + +Setting `config.EVENT_AUTOREGISTER` to `True` will allow you to await any signal event even if it has not previously been defined with a signal handler. + +```python +@app.signal("do.something.start") +async def signal_handler(): + await do_something() + await app.dispatch("do.something.complete") + +# somethere else in your app: +await app.event("do.something.complete") +``` + +### Infinitely reusable and nestable `Blueprint` and `BlueprintGroup` + +A single `Blueprint` may not be assigned and reused to multiple groups. The groups themselves can also by infinitely nested into one or more other groups. This allows for an unlimited range of composition. + +### HTTP methods as `Enum` + +Sanic now has `sanic.HTTPMethod`, which is an `Enum`. It can be used interchangeably with strings: + +```python +from sanic import Sanic, HTTPMethod + +@app.route("/", methods=["post", "PUT", HTTPMethod.PATCH]) +async def handler(...): + ... +``` + +### Expansion of `HTTPMethodView` + +Class based views may be attached now in one of three ways: + +**Option 1 - Existing** +```python +class DummyView(HTTPMethodView): + ... + +app.add_route(DummyView.as_view(), "/dummy") +``` + +**Option 2 - From `attach` method** +```python +class DummyView(HTTPMethodView): + ... + +DummyView.attach(app, "/") +``` + +**Option 3 - From class definition at `__init_subclass__`** +```python +class DummyView(HTTPMethodView, attach=app, uri="/"): + ... +``` + +Options 2 and 3 are useful if your CBV is located in another file: + +```python +from sanic import Sanic, HTTPMethodView + +class DummyView(HTTPMethodView, attach=Sanic.get_app(), uri="/"): + ... +``` + +## News + +### Discord and support forums + +If you have not already joined our community, you can become a part by joining the [Discord server](https://discord.gg/FARQzAEMAA) and the [Community Forums](https://community.sanicframework.org/). Also, follow [@sanicframework](https://twitter.com/sanicframework) on Twitter. + +### SCO 2022 elections + +The Summer 🏝/Winter ❄️ (choose your Hemisphere) is upon us. That means we will be holding elections for the SCO. This year, we will have the following positions to fill: + +- Steering Council Member (2 year term) +- Steering Council Member (2 year term) +- Steering Council Member (1 year term) +- Release Manager v22 +- Release Manager v22 + +[@vltr](https://github.com/vltr) will be staying on to complete his second year on the Steering Council. + +If you are interested in learning more, you can read about the SCO [roles and responsibilities](../project/scope.md#roles-and-responsibilities), or Adam Hopkins on Discord. + +Nominations will begin September 1. More details will be available on the Forums as we get closer. + +### New project underway + +We have added a new project to the SCO umbrella: [`sanic-ext`](https://github.com/sanic-org/sanic-ext). It is not yet released, and in heavy active development. The goal for the project will ultimately be to replace [`sanic-openapi`](https://github.com/sanic-org/sanic-openapi) with something that provides more features for web application developers, including input validation, CORS handling, and HTTP auto-method handlers. If you are interested in helping out, let us know on Discord. Look for an initial release of this project sometime (hopefully) before the September release. + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@aaugustin](https://github.com/aaugustin) [@ahopkins](https://github.com/ahopkins) [@ajaygupta2790](https://github.com/ajaygupta2790) [@ashleysommer](https://github.com/ashleysommer) [@ENT8R](https://github.com/ent8r) [@fredlllll](https://github.com/fredlllll) [@graingert](https://github.com/graingert) [@harshanarayana](https://github.com/harshanarayana) [@jdraymon](https://github.com/jdraymon) [@Kyle-Verhoog](https://github.com/kyle-verhoog) [@sanjeevanahilan](https://github.com/sanjeevanahilan) [@sjsadowski](https://github.com/sjsadowski) [@Tronic](https://github.com/tronic) [@vltr](https://github.com/vltr) [@ZinkLu](https://github.com/zinklu) + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able, [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/pt/guide/release-notes/v21.9.md b/src/pt/guide/release-notes/v21.9.md new file mode 100644 index 0000000000..bc11399481 --- /dev/null +++ b/src/pt/guide/release-notes/v21.9.md @@ -0,0 +1,221 @@ +# Version 21.9 + +[[toc]] + +## Introduction + +This is the third release of the version 21 [release cycle](../../org/policies.md#release-schedule). Version 21 will be "finalized" in the December long-term support version release. + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + +### Removal of config values: `WEBSOCKET_READ_LIMIT`, `WEBSOCKET_WRITE_LIMIT` and `WEBSOCKET_MAX_QUEUE` + +With the complete overhaul of the websocket implementation, these configuration values were removed. There currently is not a plan to replace them. + +### Deprecation of default value of `FALLBACK_ERROR_FORMAT` + +When no error handler is attached, Sanic has used `html` as the fallback format-type. This has been deprecated and will change to `text` starting in v22.3. While the value of this has changed to `auto`, it will still continue to use HTML as the last resort thru v21.12LTS before changing. + +### `ErrorHandler.lookup` signature deprecation + +The `ErrorHandler.lookup` now **requires** two positional arguments: + +```python +def lookup(self, exception, route_name: Optional[str]): +``` + +A non-conforming method will cause Blueprint-specific exception handlers to not properly attach. + +### Reminder of upcoming removals + +As a reminder, the following items have already been deprecated, and will be removed in version 21.12LTS + +- `CompositionView` +- `load_env` (use `env_prefix` instead) +- Sanic objects (application instances, blueprints, and routes) must by alphanumeric conforming to: `^[a-zA-Z][a-zA-Z0-9_\-]*$` +- Arbitrary assignment of objects to application and blueprint instances (use `ctx` instead; removal of this has been bumped from 21.9 to 21.12) + +### Overhaul of websockets + +There has been a huge overhaul to the handling of websocket connections. Thanks to [@aaugustin](https://github.com/aaugustin) the [`websockets`](https://websockets.readthedocs.io/en/stable/index.html) now has a new implementation that allows Sanic to handle the I/O of websocket connections on its own. Therefore, Sanic has bumped the minimum version to `websockets>=10.0`. + +The change should mostly be unnoticeable to developers, except that some of the oddities around websocket handlers in Sanic have been corrected. For example, you now should be able to catch the `CancellError` yourself when someone disconnects: + +```python +@app.websocket("/") +async def handler(request, ws): + try: + while True: + await asyncio.sleep(0.25) + except asyncio.CancelledError: + print("User closed connection") +``` + +### Built-in signals + +Version [21.3](./v21.3.md) introduced [signals](../advanced/signals.md). Now, Sanic dispatches signal events **from within the codebase** itself. This means that developers now have the ability to hook into the request/response cycle at a much closer level than before. + +Previously, if you wanted to inject some logic you were limited to middleware. Think of integrated signals as _super_-middleware. The events that are dispatched now include: + +- `http.lifecycle.begin` +- `http.lifecycle.complete` +- `http.lifecycle.exception` +- `http.lifecycle.handle` +- `http.lifecycle.read_body` +- `http.lifecycle.read_head` +- `http.lifecycle.request` +- `http.lifecycle.response` +- `http.lifecycle.send` +- `http.middleware.after` +- `http.middleware.before` +- `http.routing.after` +- `http.routing.before` +- `server.init.after` +- `server.init.before` +- `server.shutdown.after` +- `server.shutdown.before` + +::: tip Note The `server` signals are the same as the four (4) main server listener events. In fact, those listeners themselves are now just convenience wrappers to signal implementations. ::: + +### Smarter `auto` exception formatting + +Sanic will now try to respond with an appropriate exception format based upon the endpoint and the client. For example, if your endpoint always returns a `sanic.response.json` object, then any exceptions will automatically be formatted in JSON. The same is true for `text` and `html` responses. + +Furthermore, you now can _explicitly_ control which formatter to use on a route-by-route basis using the route definition: + +```python +@app.route("/", error_format="json") +async def handler(request): + pass +``` + +### Blueprint copying + +Blueprints can be copied to new instances. This will carry forward everything attached to it, like routes, middleware, etc. + +```python +v1 = Blueprint("Version1", version=1) + +@v1.route("/something") +def something(request): + pass + +v2 = v1.copy("Version2", version=2) + +app.blueprint(v1) +app.blueprint(v2) +``` + +``` +/v1/something +/v2/something +``` +### Blueprint group convenience methods + +Blueprint groups should now have all of the same methods available to them as regular Blueprints. With this, along with Blueprint copying, Blueprints should now be very composable and flexible. + +### Accept header parsing + +Sanic `Request` objects can parse an `Accept` header to provide an ordered list of the client's content-type preference. You can simply access it as an accessor: + +```python +print(request.accept) +# ["*/*"] +``` + +It also is capable of handling wildcard matching. For example, assuming the incoming request included: + +``` +Accept: */* +``` + +Then, the following is `True`: + +```python +"text/plain" in request.accept +``` + +### Default exception messages + +Any exception that derives from `SanicException` can now define a default exception message. This makes it more convenient and maintainable to reuse the same exception in multiple places without running into DRY issues with the message that the exception provides. + +```python +class TeaError(SanicException): + message = "Tempest in a teapot" + + +raise TeaError +``` + +### Type annotation conveniences + +It is now possible to control the path parameter types using Python's type annotations. Instead of doing this: + +```python +@app.route("///") +def handler(request: Request, one: int, two: float, three: UUID): + ... +``` + +You can now simply do this: + +```python +@app.route("///") +def handler(request: Request, one: int, two: float, three: UUID): + ... +``` + +Both of these examples will result in the same routing principles to be applied. + +### Explicit static resource type + +You can now explicitly tell a `static` endpoint whether it is supposed to treat the resource as a file or a directory: + +```python +static("/", "/path/to/some/file", resource_type="file")) +``` + +## News + +### Release of `sanic-ext` and deprecation of `sanic-openapi` + +One of the core principles of Sanic is that it is meant to be a tool, not a dictator. As the frontpage of this website states: + +> Build the way you want to build without letting your tooling constrain you. + +This means that a lot of common features used (specifically by Web API developers) do not exist in the `sanic` repository. This is for good reason. Being unopinionated provides the developer freedom and flexibility. + +But, sometimes you do not want to have to build and rebuild the same things. Sanic has until now really relied upon the awesome support of the community to fill in the gaps with plugins. + +From the early days, there has been an official `sanic-openapi` package that offered the ability to create OpenAPI documentation based upon your application. But, that project has been plagued over the years and has not been given as much priority as the main project. + +Starting with the release of v21.9, the SCO is deprecating the `sanic-openapi` package and moving it to maintenance mode. This means that it will continue to get updates as needed to maintain it for the current future, but it will not receive any new feature enhancements. + +A new project called `sanic-ext` is taking its place. This package provides not only the ability to build OAS3 documentation, but fills in many of the gaps that API developers may want in their applications. For example, out of the box it will setup CORS, and auto enable `HEAD` and `OPTIONS` responses where needed. It also has the ability validate incoming data using either standard library Dataclasses or Pydantic models. + +The list of goodies includes: +- CORS protection +- incoming request validation +- auto OAS3 documentation using Redoc and/or Swagger UI +- auto `HEAD`, `OPTIONS`, and `TRACE` responses +- dependency injection +- response serialization + +This project is still in `alpha` mode for now and is subject to change. While it is considered to be production capable, there may be some need to change the API as we continue to add features. + +Checkout the [documentation](../../plugins/sanic-ext/getting-started.md) for more details. + + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@aaugustin](https://github.com/aaugustin) [@ahopkins](https://github.com/ahopkins) [@ashleysommer](https://github.com/ashleysommer) [@cansarigol3megawatt](https://github.com/cansarigol3megawatt) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@gluhar2006](https://github.com/gluhar2006) [@komar007](https://github.com/komar007) [@ombe1229](https://github.com/ombe1229) [@prryplatypus](https://github.com/prryplatypus) [@SaidBySolo](https://github.com/SaidBySolo) [@Tronic](https://github.com/tronic) [@vltr](https://github.com/vltr) + +And, a special thank you to [@miss85246](https://github.com/miss85246) and [@ZinkLu](https://github.com/ZinkLu) for their tremendous work keeping the documentation synced and translated into Chinese. + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able, [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/pt/guide/release-notes/v22.12.md b/src/pt/guide/release-notes/v22.12.md new file mode 100644 index 0000000000..50b3dd96ad --- /dev/null +++ b/src/pt/guide/release-notes/v22.12.md @@ -0,0 +1,176 @@ +# Version 22.12 + +[[toc]] + +## Introduction + +This is the final release of the version 22 [release cycle](../../org/policies.md#release-schedule). As such it is a **long-term support** release, and will be supported as stated in the [policies](../../org/policies.md#long-term-support-v-interim-releases). + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + +### 🚨 *BREAKING CHANGE* - Sanic Inspector is now an HTTP server + +Sanic v22.9 introduced the [Inspector](./v22.9.md#inspector) to allow live inspection of a running Sanic instance. This feature relied upon opening a TCP socket and communicating over a custom protocol. That basic TCP protocol has been dropped in favor of running a full HTTP service in its place. [Learn more about the Inspector](../deployment/inspector.md). + +The current release introduces a new HTTP server and a refreshed CLI experience. This enables several new features highlighted here. Perhaps the most significant change, however, is to move all of the Inspector's commands to a subparser on the CLI instance. + +``` +$ sanic inspect --help + + ▄███ █████ ██ ▄█▄ ██ █ █ ▄██████████ + ██ █ █ █ ██ █ █ ██ + ▀███████ ███▄ ▀ █ █ ██ ▄ █ ██ + ██ █████████ █ ██ █ █ ▄▄ + ████ ████████▀ █ █ █ ██ █ ▀██ ███████ + +Optional +======== + General: + -h, --help show this help message and exit + --host HOST, -H HOST Inspector host address [default 127.0.0.1] + --port PORT, -p PORT Inspector port [default 6457] + --secure, -s Whether to access the Inspector via TLS encryption + --api-key API_KEY, -k API_KEY Inspector authentication key + --raw Whether to output the raw response information + + Subcommands: + Run one or none of the below subcommands. Using inspect without a subcommand will fetch general information about the state of the application instance. + + Or, you can optionally follow inspect with a subcommand. If you have created a custom Inspector instance, then you can run custom commands. See https://sanic.dev/en/guide/deployment/inspector.html for more details. + + {reload,shutdown,scale,} + reload Trigger a reload of the server workers + shutdown Shutdown the application and all processes + scale Scale the number of workers + Run a custom command +``` + +#### CLI remote access now available + +The `host` and `port` of the Inspector are now explicitly exposed on the CLI as shown above. Previously in v22.9, they were inferred by reference to the application instance. Because of this change, it will be more possible to expose the Inspector on live production instances and access from a remote installation of the CLI. + +For example, you can check your running production deployment from your local development machine. + +``` +$ sanic inspect --host=1.2.3.4 +``` + +::: warning +For **production** instances, make sure you are _using TLS and authentication_ described below. +::: + +#### TLS encryption now available + +You can secure your remote Inspector access by providing a TLS certificate to encrypt the web traffic. + +```python +app.config.INSPECTOR_TLS_CERT = "/path/to/cert.pem" +app.config.INSPECTOR_TLS_KEY = "/path/to/key.pem" +``` + +To access an encrypted installation via the CLI, use the `--secure` flag. + +``` +$ sanic inspect --secure +``` + +#### Authentication now available + +To control access to the remote Inspector, you can protect the endpoints using an API key. + +```python +app.config.INSPECTOR_API_KEY = "Super-Secret-200" +``` + +To access a protected installation via the CLI, use the `--api-key` flag. + +``` +$ sanic inspect --api-key=Super-Secret-200 +``` + +This is equivalent to the header: `Authorization: Bearer `. + +``` +$ curl http://localhost:6457 -H "Authorization: Bearer Super-Secret-200" +``` + +### Scale number of running server workers + +The Inspector is now capable of scaling the number of worker processes. For example, to scale to 3 replicas, use the following command: + +``` +$ sanic inspect scale 3 +``` + +### Extend Inspector with custom commands + +The Inspector is now fully extendable to allow for adding custom commands to the CLI. For more information see [Custom Commands](../deployment/inspector.md#custom-commands). + +``` +$ sanic inspect foo --bar +``` + +### Early worker exit on failure + +The process manager shipped with v22.9 had a very short startup timeout. This was to protect against deadlock. This was increased to 30s, and a new mechanism has been added to fail early if there is a crash in a worker process on startup. + +### Introduce `JSONResponse` with convenience methods to update a JSON response body + +The `sanic.response.json` convenience method now returns a new subclass of `HTTPResponse` appropriately named: `JSONResponse`. This new type has some convenient methods for handling changes to a response body after its creation. + +```python +resp = json({"foo": "bar"}) +resp.update({"another": "value"}) +``` + +See [Returning JSON Data](../basics/response.md#returning-json-data) for more information. + +### Updates to downstream requirements: `uvloop` and `websockets` + +Minimum `uvloop` was set to `0.15.0`. Changes were added to make Sanic compliant with `websockets` version `11.0`. + +### Force exit on 2nd `ctrl+c` + +On supporting operating systems, the existing behavior is for Sanic server to try to perform a graceful shutdown when hitting `ctrl+c`. This new release will perform an immediate shutdown on subsequent `ctrl+c` after the initial shutdown has begun. + +### Deprecations and Removals + +1. *DEPRECATED* - The `--inspect*` commands introduced in v22.9 have been replaced with a new subcommand parser available as `inspect`. The flag versions will continue to operate until v23.3. You are encouraged to use the replacements. While this short deprecation period is a deviation from the standard two-cycles, we hope this change will be minimally disruptive. + ``` + OLD sanic ... --inspect + NEW sanic ... inspect + + OLD sanic ... --inspect-raw + NEW sanic ... inspect --raw + + OLD sanic ... --inspect-reload + NEW sanic ... inspect reload + + OLD sanic ... --inspect-shutdown + NEW sanic ... inspect shutdown + ``` + +## News + +The Sanic Community Organization will be headed by a new Steering Council for 2023. There are two returning and two new members. + +[@ahopkins](https://github.com/ahopkins) *returning* \ +[@prryplatypus](https://github.com/prryplatypus) *returning* \ +[@sjsadowski](https://github.com/sjsadowski) *NEW* \ +[@Tronic](https://github.com/Tronic) *NEW* + +The 2023 release managers are [@ahopkins](https://github.com/ahopkins) and [@sjsadowski](https://github.com/sjsadowski). + +If you are interested in getting more involved with Sanic, contact us on the [Discord server](https://discord.gg/FARQzAEMAA). + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@aaugustin](https://github.com/aaugustin) [@ahopkins](https://github.com/ahopkins) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@kijk2869](https://github.com/kijk2869) [@LiraNuna](https://github.com/LiraNuna) [@prryplatypus](https://github.com/prryplatypus) [@sjsadowski](https://github.com/sjsadowski) [@todddialpad](https://github.com/todddialpad) [@Tronic](https://github.com/Tronic) + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/pt/guide/release-notes/v22.3.md b/src/pt/guide/release-notes/v22.3.md new file mode 100644 index 0000000000..9222b48985 --- /dev/null +++ b/src/pt/guide/release-notes/v22.3.md @@ -0,0 +1,321 @@ +# Version 22.3 + +[[toc]] + +## Introduction + +This is the first release of the version 22 [release cycle](../../org/policies.md#release-schedule). All of the standard SCO libraries are now entering the same release cycle and will follow the same versioning pattern. Those packages are: + +- [`sanic-routing`](https://github.com/sanic-org/sanic-routing) +- [`sanic-testing`](https://github.com/sanic-org/sanic-testing) +- [`sanic-ext`](https://github.com/sanic-org/sanic-ext) + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + +### Application multi-serve + +The Sanic server now has an API to allow you to run multiple applications side-by-side in the same process. This is done by calling `app.prepare(...)` on one or more application instances, one or many times. Each time it should be bound to a unique host/port combination. Then, you begin serving the applications by calling `Sanic.serve()`. + +```python +app = Sanic("One") +app2 = Sanic("Two") + +app.prepare(port=9999) +app.prepare(port=9998) +app.prepare(port=9997) +app2.prepare(port=8888) +app2.prepare(port=8887) + +Sanic.serve() +``` + +In the above snippet, there are two applications that will be run concurrently and bound to multiple ports. This feature is *not* supported in the CLI. + +This pattern is meant to be an alternative to running `app.run(...)`. It should be noted that `app.run` is now just a shorthand for the above pattern and is still fully supported. + +### 👶 *BETA FEATURE* - New path parameter type: file extensions + +A very common pattern is to create a route that dynamically generates a file. The endpoint is meant to match on a file with an extension. There is a new path parameter to match files: ``. + +```python +@app.get("/path/to/") +async def handler(request, filename, ext): + ... +``` + +This will catch any pattern that ends with a file extension. You may, however want to expand this by specifying which extensions, and also by using other path parameter types for the file name. + +For example, if you want to catch a `.jpg` file that is only numbers: + +```python +@app.get("/path/to/") +async def handler(request, filename, ext): + ... +``` + +Some potential examples: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ definition + + example + + filename + + extension +
+ \ + + page.txt + + "page" + + "txt" +
+ \ + + cat.jpg + + "cat" + + "jpg" +
+ \ + + + png\ + + gif\ + + svg> | cat.jpg | "cat" | "jpg" +
+ + + 123.txt + + 123 + + "txt" +
+ + + + png\ + + gif\ + + svg> | 123.svg | 123 | "svg" +
+ + + 3.14.tar.gz + + 3.14 + + "tar.gz" +
+ +### 🚨 *BREAKING CHANGE* - Path parameter matching of non-empty strings + +A dynamic path parameter will only match on a non-empty string. + +Previously a route with a dynamic string parameter (`/` or `/`) would match on any string, including empty strings. It will now only match a non-empty string. To retain the old behavior, you should use the new parameter type: `/`. + +```python +@app.get("/path/to/") +async def handler(request, foo) + ... +``` + +### 🚨 *BREAKING CHANGE* - `sanic.worker.GunicornWorker` has been removed + +Departing from our normal deprecation policy, the `GunicornWorker` was removed as a part of the process of upgrading the Sanic server to include multi-serve. This decision was made largely in part because even while it existed it was not an optimal strategy for deploying Sanic. + +If you want to deploy Sanic using `gunicorn`, then you are advised to do it using [the strategy implemented by `uvicorn`](https://www.uvicorn.org/#running-with-gunicorn). This will effectively run Sanic as an ASGI application through `uvicorn`. You can upgrade to this pattern by installing `uvicorn`: + +``` +pip install uvicorn +``` + +Then, you should be able to run it with a pattern like this: + +``` +gunicorn path.to.sanic:app -k uvicorn.workers.UvicornWorker +``` + +### Authorization header parsing + +The `Authorization` header has been partially parseable for some time now. You have been able to use `request.token` to gain access to a header that was in one of the following two forms: + +``` +Authorization: Token +Authorization: Bearer +``` + +Sanic can now parse more credential types like `BASIC`: + +``` +Authorization: Basic Z2lsLWJhdGVzOnBhc3N3b3JkMTIz +``` + +This can be accessed now as `request.credentials`: + +```python +print(request.credentials) +# Credentials(auth_type='Basic', token='Z2lsLWJhdGVzOnBhc3N3b3JkMTIz', _username='gil-bates', _password='password123') +``` + +### CLI arguments optionally injected into application factory + +Sanic will now attempt to inject the parsed CLI arguments into your factory if you are using one. + +```python +def create_app(args): + app = Sanic("MyApp") + print(args) + return app +``` +``` +$sanic p:create_app --factory +Namespace(module='p:create_app', factory=True, simple=False, host='127.0.0.1', port=8000, unix='', cert=None, key=None, tls=None, tlshost=False, workers=1, fast=False, access_log=False, debug=False, auto_reload=False, path=None, dev=False, motd=True, verbosity=None, noisy_exceptions=False) +``` + +If you are running the CLI with `--factory`, you also have the option of passing arbitrary arguments to the command, which will be injected into the argument `Namespace`. + +``` +sanic p:create_app --factory --foo=bar +Namespace(module='p:create_app', factory=True, simple=False, host='127.0.0.1', port=8000, unix='', cert=None, key=None, tls=None, tlshost=False, workers=1, fast=False, access_log=False, debug=False, auto_reload=False, path=None, dev=False, motd=True, verbosity=None, noisy_exceptions=False, foo='bar') +``` + +### New reloader process listener events + +When running Sanic server with auto-reload, there are two new events that trigger a listener *only* on the reloader process: + +- `reload_process_start` +- `reload_process_stop` + +These are only triggered if the reloader is running. + +```python +@app.reload_process_start +async def reload_start(*_): + print(">>>>>> reload_start <<<<<<") + + +@app.reload_process_stop +async def reload_stop(*_): + print(">>>>>> reload_stop <<<<<<") +``` + +### The event loop is no longer a required argument of a listener + +You can leave out the `loop` argument of a listener. Both of these examples work as expected: + +```python +@app.before_server_start +async def without(app): + ... + +@app.before_server_start +async def with(app, loop): + ... +``` + +### Removal - Debug mode does not automatically start the reloader + +When running with `--debug` or `debug=True`, the Sanic server will not automatically start the auto-reloader. This feature of doing both on debug was deprecated in v21 and removed in this release. If you would like to have *both* debug mode and auto-reload, you can use `--dev` or `dev=True`. + +**dev = debug mode + auto reloader** + +### Deprecation - Loading of lower case environment variables + +Sanic loads prefixed environment variables as configuration values. It has not distinguished between uppercase and lowercase as long as the prefix matches. However, it has always been the convention that the keys should be uppercase. This is deprecated and you will receive a warning if the value is not uppercase. In v22.9 only uppercase and prefixed keys will be loaded. + +## News + +### Packt publishes new book on Sanic web development + +---:1 There is a new book on **Python Web Development with Sanic** by [@ahopkins](https://github.com/ahopkins). The book is endorsed by the SCO and part of the proceeds of all sales go directly to the SCO for further development of Sanic. + +You can learn more at [sanicbook.com](https://sanicbook.com/) :--:1 ![Python Web Development with Sanic](https://sanicbook.com/images/SanicCoverFinal.png) :--- + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@aericson](https://github.com/aericson) [@ahankinson](https://github.com/ahankinson) [@ahopkins](https://github.com/ahopkins) [@ariebovenberg](https://github.com/ariebovenberg) [@ashleysommer](https://github.com/ashleysommer) [@Bluenix2](https://github.com/Bluenix2) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@dotlambda](https://github.com/dotlambda) [@eric-spitler](https://github.com/eric-spitler) [@howzitcdf](https://github.com/howzitcdf) [@jonra1993](https://github.com/jonra1993) [@prryplatypus](https://github.com/prryplatypus) [@raphaelauv](https://github.com/raphaelauv) [@SaidBySolo](https://github.com/SaidBySolo) [@SerGeRybakov](https://github.com/SerGeRybakov) [@Tronic](https://github.com/Tronic) + + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/pt/guide/release-notes/v22.6.md b/src/pt/guide/release-notes/v22.6.md new file mode 100644 index 0000000000..f2779fcb19 --- /dev/null +++ b/src/pt/guide/release-notes/v22.6.md @@ -0,0 +1,152 @@ +# Version 22.6 + +[[toc]] + +## Introduction + +This is the second release of the version 22 [release cycle](../../org/policies.md#release-schedule). Version 22 will be "finalized" in the December long-term support version release. + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + + +### Automatic TLS setup in `DEBUG` mode + +The Sanic server can automatically setup a TLS certificate using either [mkcert](https://github.com/FiloSottile/mkcert) or [trustme](https://github.com/python-trio/trustme). This certificate will enable `https://localhost` (or another local address) for local development environments. You must install either `mkcert` or `trustme` on your own for this to work. + +---:1 +``` +$ sanic path.to.server:app --auto-tls --debug +``` +:--:1 + +```python +app.run(debug=True, auto_tls=True) +``` +:--- + +This feature is not available when running in `ASGI` mode, or in `PRODUCTION` mode. When running Sanic in production, you should be using a real TLS certificate either purchased through a legitimate vendor, or using [Let's Encrypt](https://letsencrypt.org/). + + +### HTTP/3 Server :rocket: + +In June 2022, the IETF finalized and published [RFC 9114](https://www.rfc-editor.org/rfc/rfc9114.html), the specification for HTTP/3. In short, HTTP/3 is a **very** different protocol than HTTP/1.1 and HTTP/2 because it implements HTTP over UDP instead of TCP. The new HTTP protocol promises faster webpage load times and solving some of the problems of the older standards. You are encouraged to [read more about](https://http3-explained.haxx.se/) this new web technology. You likely will need to install a [capable client](https://curl.se/docs/http3.html) as traditional tooling will not work. + +Sanic server offers HTTP/3 support using [aioquic](https://github.com/aiortc/aioquic). This **must** be installed: + +``` +pip install sanic aioquic +``` + +``` +pip install sanic[http3] +``` + +To start HTTP/3, you must explicitly request it when running your application. + +---:1 +``` +$ sanic path.to.server:app --http=3 +``` + +``` +$ sanic path.to.server:app -3 +``` +:--:1 + +```python +app.run(version=3) +``` +:--- + +To run both an HTTP/3 and HTTP/1.1 server simultaneously, you can use [application multi-serve](./v22.3.html#application-multi-serve) introduced in v22.3. + + +---:1 +``` +$ sanic path.to.server:app --http=3 --http=1 +``` + +``` +$ sanic path.to.server:app -3 -1 +``` +:--:1 + +```python +app.prepre(version=3) +app.prepre(version=1) +Sanic.serve() +``` +:--- + +Because HTTP/3 requires TLS, you cannot start a HTTP/3 server without a TLS certificate. You should [set it up yourself](../how-to/tls.html) or use `mkcert` if in `DEBUG` mode. Currently, automatic TLS setup for HTTP/3 is not compatible with `trustme`. + +**:baby: This feature is being released as an *EARLY RELEASE FEATURE*.** It is **not** yet fully compliant with the HTTP/3 specification, lacking some features like [websockets](https://websockets.spec.whatwg.org/), [webtransport](https://w3c.github.io/webtransport/), and [push responses](https://http3-explained.haxx.se/en/h3/h3-push). Instead the intent of this release is to bring the existing HTTP request/response cycle towards feature parity with HTTP/3. Over the next several releases, more HTTP/3 features will be added and the API for it finalized. + +### Consistent exception naming + +Some of the Sanic exceptions have been renamed to be more compliant with standard HTTP response names. + +- `InvalidUsage` >> `BadRequest` +- `MethodNotSupported` >> `MethodNotAllowed` +- `ContentRangeError` >> `RangeNotSatisfiable` + +All old names have been aliased and will remain backwards compatible. + +### Current request getter + +Similar to the API to access an application (`Sanic.get_app()`), there is a new method for retrieving the current request when outside of a request handler. + +```python +from sanic import Request + +Request.get_current() +``` + +### Improved API support for setting cache control headers + +The `file` response helper has some added parameters to make it easier to handle setting of the [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) header. + +```python +file( + ..., + last_modified=..., + max_age=..., + no_store=..., +) +``` + + +### Custom `loads` function + +Just like Sanic supports globally setting a custom `dumps`, you can now set a global custom `loads`. + +```python +from orjson import loads + +app = Sanic("Test", loads=loads) +``` + + +### Deprecations and Removals + +1. *REMOVED* - Applications may no longer opt-out of the application registry +1. *REMOVED* - Custom exception handlers will no longer run after some part of an exception has been sent +1. *REMOVED* - Fallback error formats cannot be set on the `ErrorHandler` and must **only** be set in the `Config` +1. *REMOVED* - Setting a custom `LOGO` for startup is no longer allowed +1. *REMOVED* - The old `stream` response convenience method has been removed +1. *REMOVED* - `AsyncServer.init` is removed and no longer an alias of `AsyncServer.app.state.is_started` + + + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@ahopkins](https://github.com/ahopkins) [@amitay87](https://github.com/amitay87) [@ashleysommer](https://github.com/ashleysommer) [@azimovMichael](https://github.com/azimovMichael) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@kijk2869](https://github.com/kijk2869) [@prryplatypus](https://github.com/prryplatypus) [@SaidBySolo](https://github.com/SaidBySolo) [@sjsadowski](https://github.com/sjsadowski) [@timmo001](https://github.com/timmo001) [@zozzz](https://github.com/zozzz) + + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/pt/guide/release-notes/v22.9.md b/src/pt/guide/release-notes/v22.9.md new file mode 100644 index 0000000000..33b265ea83 --- /dev/null +++ b/src/pt/guide/release-notes/v22.9.md @@ -0,0 +1,293 @@ +# Version 22.9 + +[[toc]] + +## Introduction + +This is the third release of the version 22 [release cycle](../../org/policies.md#release-schedule). Version 22 will be "finalized" in the December long-term support version release. + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + + +### :warning: *IMPORTANT* - New worker manager :rocket: + +Sanic server has been overhauled to provide more consistency and flexbility in how it operates. More details about the motivations are outlined in [PR #2499](https://github.com/sanic-org/sanic/pull/2499) and discussed in a live stream [discussion held on YouTube](https://youtu.be/m8HCO8NK7HE). + +This **does NOT apply** to Sanic in ASGI mode + +#### Overview of the changes + +- The worker servers will **always** run in a child process. + - Previously this could change depending upon one versus many workers, and the usage or not of the reloader. This should lead to much more predictable development environments that more closely match their production counterparts. +- Multi-workers is **now supported on Windows**. + - This was impossible because Sanic relied upon the `multiprocessing` module using `fork`, which is not available on Windows. + - Now, Sanic will always use `spawn`. This does have some noticeable differences, particularly if you are running Sanic in the global scope with `app.run` (see below re: upgrade issues). +- The application instance now has a new `multiplexer` object that can be used to restart one or many workers. This could, for example, be triggered by a request. +- There is a new Inspector that can provide details on the state of your server. +- Sanic worker manager can run arbitrary processes. + - This allows developers to add any process they want from within Sanic. + - Possible use cases: + - Health monitor, see [Sanic Extensions]() + - Logging queue, see [Sanic Extensions]() + - Background worker queue in a seperate process + - Running another application, like a bot +- There is a new listener called: `main_process_ready`. It really should only be used for adding arbitrary processes to Sanic. +- Passing shared objects between workers. + - Python does allow some types of objects to share state between processes, whether through shared memory, pipes, etc. + - Sanic will now allow these types of object to be shared on the `app.shared_ctx` object. + - Since this feature relies upon Pythons `multiprocessing` library, it obviously will only work to share state between Sanic worker instances that are instantiated from the same execution. This is *not* meant to provide an API for horizontal scaling across multiple machines for example. + +#### Adding a shared context object + +To share an object between worker processes, it *MUST* be assigned inside of the `main_process_start` listener. + +```python +from multiprocessing import Queue + +@app.main_process_start +async def main_process_start(app): + app.shared_ctx.queue = Queue() +``` + +All objects on `shared_ctx` will be available now within each worker process. + +```python +@app.before_server_starts +async def before_server_starts(app): + assert isinstance(app.shared_ctx.queue, Queue) + +@app.on_request +async def on_request(request): + assert isinstance(request.app.shared_ctx.queue, Queue) + +@app.get("/") +async def handler(request): + assert isinstance(request.app.shared_ctx.queue, Queue) +``` + +*NOTE: Sanic will not stop you from registering an unsafe object, but may warn you. Be careful not to just add a regular list object, for example, and expect it to work. You should have an understanding of how to share state between processes.* + +#### Running arbitrary processes + +Sanic can run any arbitrary process for you. It should be capable of being stopped by a `SIGINT` or `SIGTERM` OS signal. + +These processes should be registered inside of the `main_process_ready` listener. + +```python +@app.main_process_ready +async def ready(app: Sanic, _): + app.manager.manage("MyProcess", my_process, {"foo": "bar"}) +# app.manager.manage(, , ) +``` + +#### Inspector + +Sanic ships with an optional Inspector, which is a special process that allows for the CLI to inspect the running state of an application and issue commands. It currently will only work if the CLI is being run on the same machine as the Sanic instance. + +``` +sanic path.to:app --inspect +``` + +![Sanic inspector](https://user-images.githubusercontent.com/166269/190099384-2f2f3fae-22d5-4529-b279-8446f6b5f9bd.png) + +The new CLI commands are: + +``` + --inspect Inspect the state of a running instance, human readable + --inspect-raw Inspect the state of a running instance, JSON output + --trigger-reload Trigger worker processes to reload + --trigger-shutdown Trigger all processes to shutdown +``` + +This is not enabled by default. In order to have it available, you must opt in: + +```python +app.config.INSPECTOR = True +``` + +*Note: [Sanic Extensions]() provides a [custom request](../basics/app.md#custom-requests) class that will add a request counter to the server state. + +#### Application multiplexer + +Many of the same information and functionality is available on the application instance itself. There is a new `multiplexer` object on the application instance that has the ability to restart one or more workers, and fetch information about the current state. + +You can access it as `app.multiplexer`, or more likely by its short alias `app.m`. + +```python +@app.on_request +async def print_state(request: Request): + print(request.app.m.state) +``` + +#### Potential upgrade issues + +Because of the switch from `fork` to `spawn`, if you try running the server in the global scope you will receive an error. If you see something like this: + +``` +sanic.exceptions.ServerError: Sanic server could not start: [Errno 98] Address already in use. +This may have happened if you are running Sanic in the global scope and not inside of a `if __name__ == "__main__"` block. +``` + +... then the change is simple. Make sure `app.run` is inside a block. + +```python +if __name__ == "__main__": + app.run(port=9999, dev=True) +``` + +#### Opting out of the new functionality + +If you would like to run Sanic without the new process manager, you may easily use the legacy runners. Please note that support for them **will be removed** in the future. A date has not yet been set, but will likely be sometime in 2023. + +To opt out of the new server and use the legacy, choose the appropriate method depending upon how you run Sanic: + +---:1 If you use the CLI... :--:1 +``` +sanic path.to:app --legacy +``` +:--- + +---:1 If you use `app.run`... :--:1 +``` +app.run(..., legacy=True) +``` +:--- + +---:1 If you `app.prepare`... :--:1 +``` +app.prepare(...) +Sanic.serve_legacy() +``` +:--- + +Similarly, you can force Sanic to run in a single process. This however means there will not be any access to the auto-reloader. + +---:1 If you use the CLI... :--:1 +``` +sanic path.to:app --single-process +``` +:--- + +---:1 If you use `app.run`... :--:1 +``` +app.run(..., single_process=True) +``` +:--- + +---:1 If you `app.prepare`... :--:1 +``` +app.prepare(...) +Sanic.serve_single() +``` +:--- +### Middleware priority + +Middleware is executed in an order based upon when it was defined. Request middleware are executed in sequence and response middleware in reverse. This could have an unfortunate impact if your ordering is strictly based upon import ordering with global variables for example. + +A new addition is to break-out of the strict construct and allow a priority to be assigned to a middleware. The higher the number for a middleware definition, the earlier in the sequence it will be executed. This applies to **both** request and response middleware. + +```python +@app.on_request +async def low_priority(_): + ... + +@app.on_request(priority=10) +async def high_priority(_): + ... +``` + +In the above example, even though `low_priority` is defined first, `high_priority` will run first. + +### Custom `loads` function + + +Sanic has supported the ability to add a [custom `dumps` function](https://sanic.readthedocs.io/en/stable/sanic/api/app.html#sanic.app.Sanic) when instantiating an app. The same functionality has been extended to `loads`, which will be used when deserializing. + +```python +from json import loads + +Sanic("Test", loads=loads) +``` + +### Websocket objects are now iterable + + +Rather than calling `recv` in a loop on a `Websocket` object, you can iterate on it in a `for` loop. + + +```python +from sanic import Request, Websocket + +@app.websocket("/ws") +async def ws_echo_handler(request: Request, ws: Websocket): + async for msg in ws: + await ws.send(msg) +``` + +### Appropriately respond with 304 on static files + +When serving a static file, the Sanic server can respond appropriately to a request with `If-Modified-Since` using a `304` response instead of resending a file. + +### Two new signals to wrap handler execution + +Two new [signals](../advanced/signals.md) have been added that wrap the execution of a request handler. + +- `http.handler.before` - runs after request middleware but before the route handler +- `http.handler.after` - runs after the route handler + - In *most* circumstances, this also means that it will run before response middleware. However, if you call `request.respond` from inside of a route handler, then your middleware will come first + +### New Request properties for HTTP method information + +The HTTP specification defines which HTTP methods are: safe, idempotent, and cacheable. New properties have been added that will respond with a boolean flag to help identify the request property based upon the method. + +```python +request.is_safe +request.is_idempotent +request.is_cacheable +``` + +### 🚨 *BREAKING CHANGE* - Improved cancel request exception + +In prior version of Sanic, if a `CancelledError` was caught it could bubble up and cause the server to respond with a `503`. This is not always the desired outcome, and it prevented the usage of that error in other circumstances. As a result, Sanic will now use a subclass of `CancelledError` called: `RequestCancelled` for this functionality. It likely should have little impact unless you were explicitly relying upon the old behavior. + + +For more details on the specifics of these properties, checkout the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods). + +### New deprecation warning filter + +You can control the level of deprecation warnings from Sanic using [standard library warning filter values](https://docs.python.org/3/library/warnings.html#the-warnings-filter). Default is `"once"`. + +```python +app.config.DEPRECATION_FILTER = "ignore" +``` + +### Deprecations and Removals + +1. *DEPRECATED* - Duplicate route names have been deprecated and will be removed in v23.3 +1. *DEPRECATED* - Registering duplicate exception handlers has been deprecated and will be removed in v23.3 +1. *REMOVED* - `route.ctx` not set by Sanic, and is a blank object for users, therefore ... + - `route.ctx.ignore_body` >> `route.extra.ignore_body` + - `route.ctx.stream` >> `route.extra.stream` + - `route.ctx.hosts` >> `route.extra.hosts` + - `route.ctx.static` >> `route.extra.static` + - `route.ctx.error_format` >> `route.extra.error_format` + - `route.ctx.websocket` >> `route.extra.websocket` +1. *REMOVED* - `app.debug` is READ-ONLY +1. *REMOVED* - `app.is_running` removed +1. *REMOVED* - `app.is_stopping` removed +1. *REMOVED* - `Sanic._uvloop_setting` removed +1. *REMOVED* - Prefixed environment variables will be ignored if not uppercase + + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@ahopkins](https://github.com/ahopkins) [@azimovMichael](https://github.com/azimovMichael) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@huntzhan](https://github.com/huntzhan) [@monosans](https://github.com/monosans) [@prryplatypus](https://github.com/prryplatypus) [@SaidBySolo](https://github.com/SaidBySolo) [@seemethere](https://github.com/seemethere) [@sjsadowski](https://github.com/sjsadowski) [@timgates42](https://github.com/timgates42) [@Tronic](https://github.com/Tronic) + + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/pt/help.md b/src/pt/help.md new file mode 100644 index 0000000000..3c4f2cf078 --- /dev/null +++ b/src/pt/help.md @@ -0,0 +1,29 @@ +--- +layout: BlankLayout +--- + +# Need some help? + +As an active community of developers, we try to support each other. If you need some help, try one of the following: + +---:1 + +### Discord :speech_balloon: + +Best place to turn for quick answers and live chat + +`#sanic-support` channel on the [Discord server](https://discord.gg/FARQzAEMAA) + +:--:1 + +### Community Forums :busts_in_silhouette: + +Good for sharing snippets of code and longer support queries + +`Questions and Help` category on the [Forums](https://community.sanicframework.org/c/questions-and-help/6) + +:--- + +--- + +We also actively monitor the `[sanic]` tag on [Stack Overflow](https://stackoverflow.com/questions/tagged/sanic). diff --git a/src/pt/org/README.md b/src/pt/org/README.md new file mode 100644 index 0000000000..dab306f45e --- /dev/null +++ b/src/pt/org/README.md @@ -0,0 +1 @@ +# Project diff --git a/src/pt/org/feature_requests.md b/src/pt/org/feature_requests.md new file mode 100644 index 0000000000..5c57ea1f02 --- /dev/null +++ b/src/pt/org/feature_requests.md @@ -0,0 +1,9 @@ +# Feature Requests + +[Create new feature request](https://github.com/sanic-org/sanic/issues/new?assignees=&labels=feature+request&template=feature_request.md) + +To vote on a feature request, visit the [GitHub Issues](https://github.com/sanic-org/sanic/issues?q=is%3Aissue+is%3Aopen+label%3A%22feature+request%22%2CRFC+sort%3Areactions-%2B1-desc) and add a reaction + +--- + + diff --git a/src/pt/org/policies.md b/src/pt/org/policies.md new file mode 100644 index 0000000000..d21c22806b --- /dev/null +++ b/src/pt/org/policies.md @@ -0,0 +1,64 @@ +# Policies + +## Versioning + +Sanic uses [calendar versioning](https://calver.org/), aka "calver". To be more specific, the pattern follows: + +``` +YY.MM.MICRO +``` + +Generally, versions are referred to in their `YY.MM` form. The `MICRO` number indicates an incremental patch version, starting at `0`. + +## Release Schedule + +There are four (4) scheduled releases per year: March, June, September, and December. Therefore, there are four (4) released versions per year: `YY.3`, `YY.6`, `YY.9`, and `YY.12`. + +This release schedule provides: + +- a predictable release cadence, +- relatively short development windows allowing features to be regularly released, +- controlled [deprecations](#deprecation), and +- consistent stability with a yearly LTS. + +We also use the yearly release cycle in conjunction with our governance model, covered by the [S.C.O.P.E.](./scope.md) + +### Long term support v Interim releases + +Sanic releases a long term support release (aka "LTS") once a year in December. The LTS releases receive bug fixes and security updates for **24 months**. Interim releases throughout the year occur every three months, and are supported until the subsequent release. + +| Version | LTS | Supported | +| ------- | ------------- | ------------------------- | +| 22.12 | until 2024-12 | :white_check_mark: | +| 22.9 | | :x: | +| 22.6 | | :x: | +| 22.3 | | :x: | +| 21.12 | until 2023-12 | :ballot_box_with_check: | +| 21.9 | | :x: | +| 21.6 | | :x: | +| 21.3 | | :x: | +| 20.12 | | :x: | +| 20.9 | | :x: | +| 20.6 | | :x: | +| 20.3 | | :x: | +| 19.12 | | :x: | +| 19.9 | | :x: | +| 19.6 | | :x: | +| 19.3 | | :x: | +| 18.12 | | :x: | +| 0.8.3 | | :x: | +| 0.7.0 | | :x: | +| 0.6.0 | | :x: | +| 0.5.4 | | :x: | +| 0.4.1 | | :x: | +| 0.3.1 | | :x: | +| 0.2.0 | | :x: | +| 0.1.9 | | :x: | + +:ballot_box_with_check: = security fixes :white_check_mark: = full support + +## Deprecation + +Before a feature is deprecated, or breaking changes are introduced into the API, it shall be publicized and shall appear with deprecation warnings through two release cycles. No deprecations shall be made in an LTS release. + +Breaking changes or feature removal may happen outside of these guidelines when absolutely warranted. These circumstances should be rare. For example, it might happen when no alternative is available to curtail a major security issue. diff --git a/src/pt/org/scope.md b/src/pt/org/scope.md new file mode 100644 index 0000000000..1af529361a --- /dev/null +++ b/src/pt/org/scope.md @@ -0,0 +1,264 @@ +--- +title: S.C.O.P.E +--- + + +Sanic Community Organization Policy E-manual +============================================ + +December 2019, version 1 + +Goals +----- + +To create a sustainable, community-driven organization around the Sanic projects that promote: (1) stability and predictability, (2) quick iteration and enhancement cycles, (3) engagement from outside contributors, (4) overall reliable software, and (5) a safe, rewarding environment for the community members. + +Overview +-------- + +This Policy is the governance model for the Sanic Community Organization (“SCO”). The SCO is a meritocratic, consensus-based community organization responsible for all projects adopted by it. Anyone with an interest in one of the projects can join the community, contribute to the community or projects, and participate in the decision making process. This document describes how that participation takes place and how to set about earning merit within the project community. + +Structure +--------- + +The SCO has multiple **projects**. Each project is represented by a single GitHub repository under the Sanic community umbrella. These projects are used by **users**, developed by **contributors**, governed by **core developers**, released by **release managers**, and ultimately overseen by a **steering council**. If this sounds similar to the Python project and PEP 8016 that is because it is intentionally designed that way. + +Roles and responsibilities +-------------------------- + +### Users + +Users are community members who have a need for the projects. They are the developers and personnel that download and install the packages. Users are the **most important** members of the community and without them the projects would have no purpose. Anyone can be a user and the licenses adopted by the projects shall be appropriate open source licenses. + +_The SCO asks its users to participate in the project and community as much as possible._ + +User contributions enable the project team to ensure that they are satisfying the needs of those users. Common user contributions include (but are not limited to): + +* evangelizing about the project (e.g. a link on a website and word-of-mouth awareness raising) +* informing developers of strengths and weaknesses from a new user perspective +* providing moral support (a ‘thank you’ goes a long way) +* providing financial support (the software is open source, but its developers need to eat) + +Users who continue to engage with the SCO, its projects, and its community will often become more and more involved. Such users may find themselves becoming contributors, as described in the next section. + +### Contributors + +Contributors are community members who contribute in concrete ways to one or more of the projects. Anyone can become a contributor and contributions can take many forms. Contributions and requirements are governed by each project separately by a contribution policy. + +There is **no expectation** of commitment to the project, **no specific skill requirements** and **no selection process**. + +In addition to their actions as users, contributors may also find themselves doing one or more of the following: + +* supporting new users (existing users are often the best people to support new users) +* reporting bugs +* identifying requirements +* providing graphics and web design +* Programming +* example use cases +* assisting with project infrastructure +* writing documentation +* fixing bugs +* adding features +* providing constructive opinions and engaging in community discourse + +Contributors engage with the projects through GitHub and the Community Forums. They submit changes to the projects itself via pull requests, which will be considered for inclusion in the project by the community at large. The Community Forums are the most appropriate place to ask for help when making that first contribution. + +Indeed one of the most important roles of a contributor may be to **simply engage in the community conversation**. Most decisions about the direction of a project are made by consensus. This is discussed in more detail below. In general, however, it is helpful for the health and direction of the projects for the contributors to **speak freely** (within the confines of the code of conduct) and **express their opinions and experiences** to help drive the consensus building. + +As contributors gain experience and familiarity with a project, their profile within, and commitment to, the community will increase. At some stage, they may find themselves being nominated for a core developer team. + +### Core Developer + +Each project under the SCO umbrella has its own team of core developers. They are the people in charge of that project. + +_What is a core developer?_ + +Core developers are community members who have shown that they are committed to the continued development of the project through ongoing engagement with the community. Being a core developer allows contributors to more easily carry on with their project related activities by giving them direct access to the project’s resources. They can make changes directly to the project repository without having to submit changes via pull requests from a fork. + +This does not mean that a core developer is free to do what they want. In fact, core developers have no more direct authority over the final release of a package than do contributors. While this honor does indicate a valued member of the community who has demonstrated a healthy respect for the project’s aims and objectives, their work continues to be reviewed by the community before acceptance in an official release. + +_What can a core developer do on a project?_ + +Each project might define this role slightly differently. However, the general usage of this designation is that an individual has risen to a level of trust within the community such that they now are given some control. This comes in the form of push rights to non-protected branches, and the ability to have a voice in the approval of pull requests. + +The projects employ various communication mechanisms to ensure that all contributions are reviewed by the community as a whole. This includes tools provided by GitHub, as well as the Community Forums. By the time a contributor is invited to become a core developer, they should be familiar with the various tools and workflows as a user and then as a contributor. + +_How to become a core developer?_ + +Anyone can become a core developer; there are no special requirements, other than to have shown a willingness and ability to positively participate in the project as a team player. + +Typically, a potential core developer will need to show that they have an understanding of the project, its objectives and its strategy. They will also have provided valuable contributions to the project over a period of time. However, there is **no technical or other skill** requirement for eligibility. + +New core developers can be **nominated by any existing core developer** at any time. At least twice a year (April and October) there will be a ballot process run by the Steering Council. Voting should be done by secret ballot. Each existing core developer for that project receives a number of votes equivalent to the number of nominees on the ballot. For example, if there are four nominees, then each existing core developer has four votes. The core developer may cast those votes however they choose, but may not vote for a single nominee more than once. A nominee must receive two-thirds approval from the number of cast ballots (not the number of eligible ballots). Once accepted by the core developers, it is the responsibility of the Steering Council to approve and finalize the nomination. The Steering Council does not have the right to determine whether a nominee is meritorious enough to receive the core developer title. However, they do retain the right to override a vote in cases where the health of the community would so require. + +Once the vote has been held, the aggregated voting results are published on the Community Forums. The nominee is entitled to request an explanation of any override against them. A nominee that fails to be admitted as a core developer may be nominated again in the future. + +It is important to recognize that being a core developer is a privilege, not a right. That privilege must be earned and once earned it can be removed by the Steering Council (see next section) in extreme circumstances. However, under normal circumstances the core developer title exists for as long as the individual wishes to continue engaging with the project and community. + +A committer who shows an above-average level of contribution to the project, particularly with respect to its strategic direction and long-term health, may be nominated to become a member of the Steering Council, or a Release Manager. This role is described below. + +_What are the rights and responsibilities of core developers?_ + +As discussed, the majority of decisions to be made are by consensus building. In certain circumstances where an issue has become more contentious, or a major decision needs to be made, the Release Manager or Steering Council may decide (or be required) to implement the RFC process, which is outlined in more detail below. + +It is also incumbent upon core developers to have a voice in the governance of the community. All core developers for all of the projects have the ability to be nominated to be on the Steering Council and vote in their elections. + +This Policy (the “SCOPE”) may only be changed under the authority of two-thirds of active core developers, except that in the first six (6) months after adoption, the core developers reserve the right to make changes under the authority of a simple majority of active core developers. + +_What if a core developer becomes inactive?_ + +It is hoped that all core developers participate and remain active on a regular basis in their projects. However, it is also understood that such commitments may not be realistic or possible from time to time. + +Therefore, the Steering Council has the duty to encourage participation and the responsibility to place core developers into an inactive status if they are no longer willing or capable to participate. The main purpose of this is **not to punish** a person for behavior, but to help the development process to continue for those that do remain active. + +To this end, a core developer that becomes “inactive” shall not have commit rights to a repository, and shall not participate in any votes. To be eligible to vote in an election, a core developer **must have been active** at the time of the previous scheduled project release. + +Inactive members may ask the Steering Council to reinstate their status at any time, and upon such request the Steering Council shall make the core developer active again. + +Individuals that know they will be unable to maintain their active status for a period are asked to be in communication with the Steering Council and declare themselves inactive if necessary. + +An “active” core developer is an individual that has participated in a meaningful way during the previous six months. Any further definition is within the discretion of the Steering Council. + +### Release Manager + +Core developers shall have access only to make commits and merges on non-protected branches. The “master” branch and other protected branches are controlled by the release management team for that project. Release managers shall be elected from the core development team by the core development team, and shall serve for a full release cycle. + +Each core developer team may decide how many release managers to have for each release cycle. It is highly encouraged that there be at least two release managers for a release cycle to help divide the responsibilities and not force too much effort upon a single person. However, there also should not be so many managers that their efforts are impeded. + +The main responsibilities of the release management team include: + +* push the development cycle forward by monitoring and facilitating technical discussions +* establish a release calendar and perform actions required to release packages +* approve pull requests to the master branch and other protected branches +* merge pull requests to the master branch and other protected branches + +The release managers **do not have the authority to veto or withhold a merge** of a pull request that otherwise meets contribution criteria and has been accepted by the community. It is not their responsibility to decide what should be developed, but rather that the decisions of the community are carried out and that the project is being moved forward. + +From time to time, a decision may need to be made that cannot be achieved through consensus. In that case, the release managers have the authority to call upon the removal of the decision to the RFC process. This should not occur regularly (unless required as discussed below), and its use should be discouraged in favor of the more communal consensus building strategy. + +Since not all projects have the same requirements, the specifics governing release managers on a project shall be set forth in an Appendix to this Policy, or in the project’s contribution guidelines. + +If necessary, the Steering Council has the right to remove a release manager that is derelict in their duties, or for other good cause. + +### Steering Council + +The Steering Council is the governing body consisting of those individuals identified as the “project owner” and having control of the resources and assets of the SCO. Their ultimate goal is to ensure the smooth operation of the projects by removing impediments, and assisting the members as needed. It is expected that they will be regular voices in the community. + +_What can the Steering Council do?_ + +The members of the Steering Council **do not individually have any more authority than any other core developer**, and shall not have any additional rights to make decisions, commits, merges, or the like on a project. + +However, as a body, the Steering Council has the following capacity: + +* accept, remand, and reject all RFCs +* enforce the community code of conduct +* administer community assets such as repositories, servers, forums, integration services, and the like (or, to delegate such authority to someone else) +* place core developers into inactive status where appropriate take any other enforcement measures afforded to it in this Policy, including, in extreme cases, removing core developers +* adopt or remove projects from the community umbrella + +It is highly encouraged that the Steering Council delegate its authority as much as possible, and where appropriate, to other willing community members. + +The Steering Council **does not have the authority** to change this Policy. + +_How many members are on the Steering Council?_ + +Four. + +While it seems like a committee with four votes may potentially end in a deadlock with no way to break a majority vote, the Steering Council is discouraged from voting as much as possible. Instead, it should try to work by consensus, and requires three consenting votes when it is necessary to vote on a matter. + +_How long do members serve on the Steering Council?_ + +A single term shall be for two calendar years starting in January. Terms shall be staggered so that each year there are two members continuing from the previous year’s council. + +Therefore, the inaugural vote shall have two positions available for a two year term, and two positions available for a one year term. + +There are no limits to the number of terms that can be served, and it is possible for an individual to serve consecutive terms. + +_Who runs the Steering Council?_ + +After the Steering Council is elected, the group shall collectively decide upon one person to act as the Chair. The Chair does not have any additional rights or authority over any other member of the Steering Council. + +The role of the Chair is merely as a coordinator and facilitator. The Chair is expected to ensure that all governance processes are adhered to. The position is more administrative and clerical, and is expected that the Chair sets agendas and coordinates discussion of the group. + +_How are council members elected?_ + +Once a year, **all eligible core developers** for each of the projects shall have the right to elect members to the Steering Council. + +Nominations shall be open from September 1 and shall close on September 30. After that, voting shall begin on October 1 and shall close on October 31. Every core developer active on the date of the June release of the Sanic Framework for that year shall be eligible to receive one vote per vacant seat on the Steering Council. For the sake of clarity, to be eligible to vote, a core developer **does not** need to be a core developer on Sanic Framework, but rather just have been active within their respective project on that date. + +The top recipients of votes shall be declared the winners. If there is any tie, it is highly encouraged that the tied nominees themselves resolve the dispute before a decision is made at random. + +In regards to the inaugural vote of the Steering Council, the top two vote-recipients shall serve for two years, and the next two vote-recipients shall assume the one-year seats. + +To be an eligible candidate for the Steering Council, the individual must have been a core developer in active status on at least one project for the previous twelve months. + +_What if there is a vacancy?_ + +If a vacancy on the Steering Council exists during a term, then the next highest vote-recipient in the previous election shall be offered to complete the remainder of the term. If one cannot be found this way, the Steering Council may decide the most appropriate course of action to fill the seat (whether by appointment, vote, or other means). + +If a member of the Steering Council becomes inactive, then that individual shall be removed from the Steering Council immediately and the seat shall become vacant. + +In extreme cases, the body of all core developers has the right to bring a vote to remove a member of the Steering Council for cause by a two-thirds majority of all eligible voting core developers. + +_How shall the Steering Council conduct its business?_ + +As much as possible, the Steering Council shall conduct its business and discussions in the open. Any member of the community should be allowed to enter the conversation with them. However, at times it may be necessary or appropriate for discussions to be held privately. Selecting the proper venue for conversations is part of the administrative duties of the Chair. + +While the specifics of how to operate are beyond the scope of the Policy, it is encouraged that the Steering Council attempt to meet at least one time per quarter in a “real-time” discussion. This could be achieved via video conferencing, live chatting, or other appropriate means. + +Support +------- + +All participants in the community are encouraged to provide support for users within the project management infrastructure. This support is provided as a way of growing the community. Those seeking support should recognize that all support activity within the project is voluntary and is therefore provided as and when time allows. A user requiring guaranteed response times or results should therefore seek to purchase a support contract from a community member. However, for those willing to engage with the project on its own terms, and willing to help support other users, the community support channels are ideal. + +Decision making process +----------------------- + +Decisions about the future of the projects are made through discussion with all members of the community, from the newest user to the most experienced member. Everyone has a voice. + +All non-sensitive project management discussion takes place on the community forums, or other designated channels. Occasionally, sensitive discussions may occur in private. + +In order to ensure that the project is not bogged down by endless discussion and continual voting, the project operates a policy of **lazy consensus**. This allows the majority of decisions to be made without resorting to a formal vote. For any **major decision** (as defined below), there is a separate Request for Comment (RFC) process. + +### Technical decisions + +Pull requests and technical decisions should generally fall into the following categories. + +* **Routine**: Documentation fixes, code changes that are for cleanup or additional testing. No functionality changes. +* **Minor**: Changes to the code base that either fix a bug, or introduce a trivial feature. No breaking changes. +* **Major**: Any change to the code base that breaks or deprecates existing API, alters operation in a non-trivial manner, or adds a significant feature. + +It is generally the responsibility of the release managers to make sure that changes to the repositories receive the proper authorization before merge. + +The release managers retain the authority to individually review and accept routine decisions that meet standards for code quality without additional input. + +### Lazy consensus + +Decision making (whether by the community or Steering Council) typically involves the following steps: + +* proposal +* discussion +* vote (if consensus is not reached through discussion) +* decision + +Any community member can make a proposal for consideration by the community. In order to initiate a discussion about a new idea, they should post a message on the appropriate channel on the Community forums, or submit a pull request implementing the idea on GitHub. This will prompt a review and, if necessary, a discussion of the idea. + +The goal of this review and discussion is to gain approval for the contribution. Since most people in the project community have a shared vision, there is often little need for discussion in order to reach consensus. + +In general, as long as nobody explicitly opposes a proposal or patch, it is recognized as having the support of the community. This is called lazy consensus; that is, those who have not stated their opinion explicitly have implicitly agreed to the implementation of the proposal. + +Lazy consensus is a very important concept within the SCO. It is this process that allows a large group of people to efficiently reach consensus, as someone with no objections to a proposal need not spend time stating their position, and others need not spend time reading such messages. + +For lazy consensus to be effective, it is necessary to allow an appropriate amount of time before assuming that there are no objections to the proposal. This is somewhat dependent upon the circumstances, but it is generally assumed that 72 hours is reasonable. This requirement ensures that everyone is given enough time to read, digest and respond to the proposal. This time period is chosen so as to be as inclusive as possible of all participants, regardless of their location and time commitments. The facilitators of discussion (whether it be the Chair or the Release Managers, where applicable) shall be charged with determining the proper length of time for such consensus to be reached. + +As discussed above regarding so-called routine decisions, the release managers have the right to make decisions within a shorter period of time. In such cases, lazy consensus shall be implied. + +### Request for Comment (RFC) + +The Steering Council shall be in charge of overseeing the RFC process. It shall be a process that remains open to debate to all members of the community, and shall allow for ample time to consider a proposal and for members to respond and engage in meaningful discussion. + +The final decision is vested with the Steering Council. However, it is strongly discouraged that the Steering Council adopt a decision that is contrary to any consensus that may exist in the community. From time to time this may happen if there is a conflict between consensus and the overall project and community goals. + +An RFC shall be initiated by submission to the Steering Council in the public manner as set forth by the Steering Council. Debate shall continue and be facilitated by the Steering Council in general, and the Chair specifically. + +In circumstances that the Steering Council feels it is appropriate, the RFC process may be waived in favor of lazy consensus. diff --git a/src/pt/plugins/sanic-ext/configuration.md b/src/pt/plugins/sanic-ext/configuration.md new file mode 100644 index 0000000000..fe4600d412 --- /dev/null +++ b/src/pt/plugins/sanic-ext/configuration.md @@ -0,0 +1,243 @@ +# Configuration + +Sanic Extensions can be configured in all of the same ways that [you can configure Sanic](../../guide/deployment/configuration.md). That makes configuring Sanic Extensions very easy. + +```python +app = Sanic("MyApp") +app.config.OAS_URL_PREFIX = "/apidocs" +``` + +However, there are a few more configuration options that should be considered. + +## Manual `extend` + +---:1 Even though Sanic Extensions will automatically attach to your application, you can manually choose `extend`. When you do that, you can pass all of the configuration values as a keyword arguments (lowercase). :--: +```python +app = Sanic("MyApp") +app.extend(oas_url_prefix="/apidocs") +``` +:--- + +---:1 Or, alternatively they could be passed all at once as a single `dict`. :--: +```python +app = Sanic("MyApp") +app.extend(config={"oas_url_prefix": "/apidocs"}) +``` +:--- + +---:1 Both of these solutions suffers from the fact that the names of the configuration settings are not discoverable by an IDE. Therefore, there is also a type annotated object that you can use. This should help the development experience. :--: +```python +from sanic_ext import Config + +app = Sanic("MyApp") +app.extend(config=Config(oas_url_prefix="/apidocs")) +``` +:--- + +## Settings + +### `cors` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to enable CORS protection + +### `cors_allow_headers` + +- **Type**: `str` +- **Default**: `"*"` +- **Description**: Value of the header: `access-control-allow-headers` + +### `cors_always_send` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to always send the header: `access-control-allow-origin` + +### `cors_automatic_options` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to automatically generate `OPTIONS` endpoints for routes that do *not* already have one defined + +### `cors_expose_headers` + +- **Type**: `str` +- **Default**: `""` +- **Description**: Value of the header: `access-control-expose-headers` + +### `cors_max_age` + +- **Type**: `int` +- **Default**: `5` +- **Description**: Value of the header: `access-control-max-age` + +### `cors_methods` + +- **Type**: `str` +- **Default**: `""` +- **Description**: Value of the header: `access-control-access-control-allow-methods` + +### `cors_origins` + +- **Type**: `str` +- **Default**: `""` +- **Description**: Value of the header: `access-control-allow-origin` + +::: warning Be very careful if you place `*` here. Do not do this unless you know what you are doing as it can be a security issue. ::: + +### `cors_send_wildcard` + +- **Type**: `bool` +- **Default**: `False` +- **Description**: Whether to send a wildcard origin instead of the incoming request origin + +### `cors_supports_credentials` + +- **Type**: `bool` +- **Default**: `False` +- **Description**: Value of the header: `access-control-allow-credentials` + +### `cors_vary_header` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to add the `vary` header + +### `http_all_methods` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Adds the HTTP `CONNECT` and `TRACE` methods as allowable + +### `http_auto_head` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Automatically adds `HEAD` handlers to any `GET` routes + +### `http_auto_options` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Automatically adds `OPTIONS` handlers to any routes without + +### `http_auto_trace` + +- **Type**: `bool` +- **Default**: `False` +- **Description**: Automatically adds `TRACE` handlers to any routes without + +### `oas` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to enable OpenAPI specification generation + +### `oas_autodoc` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to automatically extract OpenAPI details from the docstring of a route function + +### `oas_ignore_head` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: WHen `True`, it will not add `HEAD` endpoints into the OpenAPI specification + +### `oas_ignore_options` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: WHen `True`, it will not add `OPTIONS` endpoints into the OpenAPI specification + +### `oas_path_to_redoc_html` + +- **Type**: `Optional[str]` +- **Default**: `None` +- **Description**: Path to HTML file to override the existing Redoc HTML + +### `oas_path_to_swagger_html` + +- **Type**: `Optional[str]` +- **Default**: `None` +- **Description**: Path to HTML file to override the existing Swagger HTML + +### `oas_ui_default` + +- **Type**: `Optional[str]` +- **Default**: `"redoc"` +- **Description**: Which OAS documentation to serve on the bare `oas_url_prefix` endpoint; when `None` there will be no documentation at that location + +### `oas_ui_redoc` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to enable the Redoc UI + +### `oas_ui_swagger` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to enable the Swagger UI + +### `oas_ui_swagger_version` + +- **Type**: `str` +- **Default**: `"4.1.0"` +- **Description**: Which Swagger version to use + +### `oas_uri_to_config` + +- **Type**: `str` +- **Default**: `"/swagger-config"` +- **Description**: Path to serve the Swagger configurtaion + +### `oas_uri_to_json` + +- **Type**: `str` +- **Default**: `"/openapi.json"` +- **Description**: Path to serve the OpenAPI JSON + +### `oas_uri_to_redoc` + +- **Type**: `str` +- **Default**: `"/redoc"` +- **Description**: Path to Redoc + +### `oas_uri_to_swagger` + +- **Type**: `str` +- **Default**: `"/swagger"` +- **Description**: Path to Swagger + +### `oas_url_prefix` + +- **Type**: `str` +- **Default**: `"/docs"` +- **Description**: URL prefix for the Blueprint that all of the OAS documentation witll attach to + +### `swagger_ui_configuration` + +- **Type**: `Dict[str, Any]` +- **Default**: `{"apisSorter": "alpha", "operationsSorter": "alpha", "docExpansion": "full"}` +- **Description**: The Swagger documentation to be served to the frontend + +### `templating_enable_async` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to set `enable_async` on the Jinja `Environment` + +### `templating_path_to_templates` + +- **Type**: `Union[str, os.PathLike, Sequence[Union[str, os.PathLike]]]` +- **Default**: `templates` +- **Description**: A single path, or multiple paths to where your template files are located + +### `trace_excluded_headers` + +- **Type**: `Sequence[str]` +- **Default**: `("authorization", "cookie")` +- **Description**: Which headers should be suppresed from responses to `TRACE` requests diff --git a/src/pt/plugins/sanic-ext/convenience.md b/src/pt/plugins/sanic-ext/convenience.md new file mode 100644 index 0000000000..b53fb06b5a --- /dev/null +++ b/src/pt/plugins/sanic-ext/convenience.md @@ -0,0 +1,86 @@ +# Convenience + +## Fixed serializer + +---:1 Often when developing an application, there will be certain routes that always return the same sort of response. When this is the case, you can predefine the return serializer and on the endpoint, and then all that needs to be returned is the content. :--:1 +```python +from sanic_ext import serializer + +@app.get("/") +@serializer(text) +async def hello_world(request, name: str): + if name.isnumeric(): + return "hello " * int(name) + return f"Hello, {name}" +``` +:--- + + +---:1 The `serializer` decorator also can add status codes. :--:1 +```python +from sanic_ext import serializer + +@app.post("/") +@serializer(text, status=202) +async def create_something(request): + ... +``` +:--- + +## Custom serializer + +---:1 Using the `@serializer` decorator, you can also pass your own custom functions as long as they also return a valid type (`HTTPResonse`). :--:1 +```python +def message(retval, request, action, status): + return json( + { + "request_id": str(request.id), + "action": action, + "message": retval, + }, + status=status, + ) + + +@app.post("/") +@serializer(message) +async def do_action(request, action: str): + return "This is a message" +``` +:--- + +---:1 Now, returning just a string should return a nice serialized output. :--:1 + +```python +$ curl localhost:8000/eat_cookies -X POST +{ + "request_id": "ef81c45b-235c-46dd-9dbd-b550f8fa77f9", + "action": "eat_cookies", + "message": "This is a message" +} + +``` +:--- + + +## Request counter + +---:1 Sanic Extensions comes with a subcleass of `Request` that can be setup to automatically keep track of the number of requests processed per worker process. To enable this, you should pass the `CountedRequest` class to your application contructor. :--:1 +```python +from sanic_ext import CountedRequest + +app = Sanic(..., request_class=CountedRequest) +``` +:--- + +---:1 You will now have access to the number of requests served during the lifetime of the worker process. :--:1 +```python +@app.get("/") +async def handler(request: CountedRequest): + return json({"count": request.count}) +``` +:--- + +If possible, the request count will also be added to the [worker state](../../guide/deployment/manager.md#worker-state). + +![](https://user-images.githubusercontent.com/166269/190922460-43bd2cfc-f81a-443b-b84f-07b6ce475cbf.png) diff --git a/src/pt/plugins/sanic-ext/custom.md b/src/pt/plugins/sanic-ext/custom.md new file mode 100644 index 0000000000..e374052a3d --- /dev/null +++ b/src/pt/plugins/sanic-ext/custom.md @@ -0,0 +1,84 @@ +# Custom extensions + +It is possible to create your own custom extensions. + +Version 22.9 added the `Extend.register` [method](#extension-preregistration). This makes it extremely easy to add custom expensions to an application. + +## Anatomy of an extension + +All extensions must subclass `Extension`. + +### Required + +- `name`: By convention, the name is an all-lowercase string +- `startup`: A method that runs when the extension is added + +### Optional + +- `label`: A method that returns additional information about the extension in the MOTD +- `included`: A method that returns a boolean whether the extension should be enabled or not (could be used for example to check config state) + +### Example + +```python +from sanic import Request, Sanic, json +from sanic_ext import Extend, Extension + +app = Sanic(__name__) +app.config.MONITOR = True + + +class AutoMonitor(Extension): + name = "automonitor" + + def startup(self, bootstrap) -> None: + if self.included(): + self.app.before_server_start(self.ensure_monitor_set) + self.app.on_request(self.monitor) + + @staticmethod + async def monitor(request: Request): + if request.route and request.route.ctx.monitor: + print("....") + + @staticmethod + async def ensure_monitor_set(app: Sanic): + for route in app.router.routes: + if not hasattr(route.ctx, "monitor"): + route.ctx.monitor = False + + def label(self): + has_monitor = [ + route + for route in self.app.router.routes + if getattr(route.ctx, "monitor", None) + ] + return f"{len(has_monitor)} endpoint(s)" + + def included(self): + return self.app.config.MONITOR + + +Extend.register(AutoMonitor) + + +@app.get("/", ctx_monitor=True) +async def handler(request: Request): + return json({"foo": "bar"}) +``` + + +## Extension preregistration + +---:1 `Extend.register` simplifies the addition of custom extensions. :--:1 +```python +from sanic_ext import Extend, Extension + +class MyCustomExtension(Extension): + ... + +Extend.register(MyCustomExtension()) +``` +:--- + +*Added in v22.9* diff --git a/src/pt/plugins/sanic-ext/getting-started.md b/src/pt/plugins/sanic-ext/getting-started.md new file mode 100644 index 0000000000..958f905fb1 --- /dev/null +++ b/src/pt/plugins/sanic-ext/getting-started.md @@ -0,0 +1,74 @@ +# Getting Started + +Sanic Extensions is an *officially supported* plugin developed, and maintained by the SCO. The primary goal of this project is to add additional features to help Web API and Web application development easier. + +## Features + +- CORS protection +- Template rendering with Jinja +- Dependency injection into route handlers +- OpenAPI documentation with Redoc and/or Swagger +- Predefined, endpoint-specific response serializers +- Request query arguments and body input validation +- Auto create `HEAD`, `OPTIONS`, and `TRACE` endpoints + +## Minimum requirements + +- **Python**: 3.8+ +- **Sanic**: 21.9+ + +## Install + +The best method is to just install Sanic Extensions along with Sanic itself: + +```bash +pip install sanic[ext] +``` + +You can of course also just install it by itself. + +```bash +pip install sanic-ext +``` + +## Extend your application + +Out of the box, Sanic Extensions will enable a bunch of features for you. + +---:1 To setup Sanic Extensions (v21.12+), you need to do: **nothing**. If it is installed in the environment, it is setup and ready to go. + +This code is the Hello, world app in the [Sanic Getting Started page](../../guide/getting-started.md) _without any changes_, but using Sanic Extensions with `sanic-ext` installed in the background. :--:1 +```python +from sanic import Sanic +from sanic.response import text + +app = Sanic("MyHelloWorldApp") + +@app.get("/") +async def hello_world(request): + return text("Hello, world.") +``` + +:--- + +---:1 **_OLD DEPRECATED SETUP_** + +In v21.9, the easiest way to get started is to instantiate it with `Extend`. + +If you look back at the Hello, world app in the [Sanic Getting Started page](../../guide/getting-started.md), you will see the only additions here are the two highlighted lines. :--:1 + +```python{3,6} +from sanic import Sanic +from sanic.response import text +from sanic_ext import Extend + +app = Sanic("MyHelloWorldApp") +Extend(app) + +@app.get("/") +async def hello_world(request): + return text("Hello, world.") +``` +:--- + +Regardless of how it is setup, you should now be able to view the OpenAPI documentation and see some of the functionality in action: [http://localhost:8000/docs](http://localhost:8000/docs). diff --git a/src/pt/plugins/sanic-ext/health-monitor.md b/src/pt/plugins/sanic-ext/health-monitor.md new file mode 100644 index 0000000000..b4e2abd370 --- /dev/null +++ b/src/pt/plugins/sanic-ext/health-monitor.md @@ -0,0 +1,62 @@ +# Health monitor + +The health monitor requires both `sanic>=22.9` and `sanic-ext>=22.9`. + +You can setup Sanic Extensions to monitor the health of your worker processes. This requires that you not be in [single process mode](../../guide/deployment/manager.md#single-process-mode). + +## Setup + +---:1 Out of the box, the health monitor is disabled. You will need to opt-in if you would like to use it. :--:1 +```python +app.config.HEALTH = True +``` +:--- + +## How does it work + +The monitor sets up a new background process that will periodically receive acknowledgements of liveliness from each worker process. If a worker process misses a report too many times, then the monitor will restart that one worker. + +## Diagnostics endpoint + +---:1 The health monitor will also enable a diagnostics endpoint that outputs the [worker state](../../guide/deployment/manager.md#worker-state). By default is id disabled. + +::: warning +The diagnostics endpoint is not secured. If you are deploying it in a production environment, you should take steps to protect it with a proxy server if you are using one. If not, you may want to consider disabling this feature in production since it will leak details about your server state. +::: +:--:1 +``` +$ curl http://localhost:8000/__health__ +{ + 'Sanic-Main': {'pid': 99997}, + 'Sanic-Server-0-0': { + 'server': True, + 'state': 'ACKED', + 'pid': 9999, + 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), + 'starts': 2, + 'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc) + }, + 'Sanic-Reloader-0': { + 'server': False, + 'state': 'STARTED', + 'pid': 99998, + 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), + 'starts': 1 + } +} +``` +:--- + + +## Configuration + +| Key | Type | Default | Description | +| -------------------------- | ------ | --------------- | --------------------------------------------------------------------------- | +| HEALTH | `bool` | `False` | Whether to enable this extension. | +| HEALTH_ENDPOINT | `bool` | `False` | Whether to enable the diagnostics endpoint. | +| HEALTH_MAX_MISSES | `int` | `3` | The number of consecutive misses before a worker process is restarted. | +| HEALTH_MISSED_THRESHHOLD | `int` | `10` | The number of seconds the monitor checks for worker process health. | +| HEALTH_MONITOR | `bool` | `True` | Whether to enable the health monitor. | +| HEALTH_REPORT_INTERVAL | `int` | `5` | The number of seconds between reporting each acknowledgement of liveliness. | +| HEALTH_URI_TO_INFO | `str` | `""` | The URI path of the diagnostics endpoint. | +| HEALTH_URL_PREFIX | `str` | `"/__health__"` | The URI prefix of the diagnostics blueprint. | diff --git a/src/pt/plugins/sanic-ext/http/cors.md b/src/pt/plugins/sanic-ext/http/cors.md new file mode 100644 index 0000000000..1149c04d0e --- /dev/null +++ b/src/pt/plugins/sanic-ext/http/cors.md @@ -0,0 +1,86 @@ +# CORS protection + +Cross-Origin Resource Sharing (aka CORS) is a *huge* topic by itself. The documentation here cannot go into enough detail about *what* it is. You are highly encouraged to do some research on your own to understand the security problem presented by it, and the theory behind the solutions. [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) are a great first step. + +In super brief terms, CORS protection is a framework that browsers use to facilitate how and when a web page can access information from another domain. It is extremely relevant to anyone building a single-page application. Often times your frontend might be on a domain like `https://portal.myapp.com`, but it needs to access the backend from `https://api.myapp.com`. + +The implementation here is heavily inspired by [`sanic-cors`](https://github.com/ashleysommer/sanic-cors), which is in turn based upon [`flask-cors`](https://github.com/corydolphin/flask-cors). It is therefore very likely that you can achieve a near drop-in replacement of `sanic-cors` with `sanic-ext`. + +## Basic implementation + +---:1 + +As shown in the example in the [auto-endpoints example](methods.md#options), Sanic Extensions will automatically enable CORS protection without further action. But, it does not offer too much out of the box. + +At a *bare minimum*, it is **highly** recommended that you set `config.CORS_ORIGINS` to the intended origin(s) that will be accessing the application. + +:--:1 +```python +from sanic import Sanic, text +from sanic_ext import Extend + +app = Sanic(__name__) +app.config.CORS_ORIGINS = "http://foobar.com,http://bar.com" +Extend(app) + +@app.get("/") +async def hello_world(request): + return text("Hello, world.") +``` + +``` +$ curl localhost:8000 -X OPTIONS -i +HTTP/1.1 204 No Content +allow: GET,HEAD,OPTIONS +access-control-allow-origin: http://foobar.com +connection: keep-alive +``` +:--- + +## Configuration + +The true power of CORS protection, however, comes into play once you start configuring it. Here is a table of all of the options. + +| Key | Type | Default | Description | +| --------------------------- | -------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `CORS_ALLOW_HEADERS` | `str` or `List[str]` | `"*"` | The list of headers that will appear in `access-control-allow-headers`. | +| `CORS_ALWAYS_SEND` | `bool` | `True` | When `True`, will always set a value for `access-control-allow-origin`. When `False`, will only set it if there is an `Origin` header. | +| `CORS_AUTOMATIC_OPTIONS` | `bool` | `True` | When the incoming preflight request is received, whether to automatically set values for `access-control-allow-headers`, `access-control-max-age`, and `access-control-allow-methods` headers. If `False` these values will only be set on routes that are decorated with the `@cors` decorator. | +| `CORS_EXPOSE_HEADERS` | `str` or `List[str]` | `""` | Specific list of headers to be set in `access-control-expose-headers` header. | +| `CORS_MAX_AGE` | `str`, `int`, `timedelta` | `0` | The maximum number of seconds the preflight response may be cached using the `access-control-max-age` header. A falsey value will cause the header to not be set. | +| `CORS_METHODS` | `str` or `List[str]` | `""` | The HTTP methods that the allowed origins can access, as set on the `access-control-allow-methods` header. | +| `CORS_ORIGINS` | `str`, `List[str]`, `re.Pattern` | `"*"` | The origins that are allowed to access the resource, as set on the `access-control-allow-origin` header. | +| `CORS_SEND_WILDCARD` | `bool` | `False` | If `True`, will send the wildcard `*` origin instead of the `origin` request header. | +| `CORS_SUPPORTS_CREDENTIALS` | `bool` | `False` | Whether to set the `access-control-allow-credentials` header. | +| `CORS_VARY_HEADER` | `bool` | `True` | Whether to add `vary` header, when appropriate. | + +*For the sake of brevity, where the above says `List[str]` any instance of a `list`, `set`, `frozenset`, or `tuple` will be acceptable. Alternatively, if the value is a `str`, it can be a comma delimited list.* + +## Route level overrides + +---:1 + +It may sometimes be necessary to override app-wide settings for a specific route. To allow for this, you can use the `@sanic_ext.cors()` decorator to set different route-specific values. + +The values that can be overridden with this decorator are: + +- `origins` +- `expose_headers` +- `allow_headers` +- `allow_methods` +- `supports_credentials` +- `max_age` + +:--:1 +```python +from sanic_ext import cors + +app.config.CORS_ORIGINS = "https://foo.com" + + +@app.get("/", host="bar.com") +@cors(origins="https://bar.com") +async def hello_world(request): + return text("Hello, world.") +``` +:--- diff --git a/src/pt/plugins/sanic-ext/http/methods.md b/src/pt/plugins/sanic-ext/http/methods.md new file mode 100644 index 0000000000..59cfdcd8d2 --- /dev/null +++ b/src/pt/plugins/sanic-ext/http/methods.md @@ -0,0 +1,125 @@ +# HTTP Methods + +## Auto-endpoints + +The default behavior is to automatically generate `HEAD` endpoints for all `GET` routes, and `OPTIONS` endpoints for all routes. Additionally, there is the option to automatically generate `TRACE` endpoints. However, these are not enabled by default. + +::::tabs + +:::tab HEAD + +- **Configuration**: `AUTO_HEAD` (default `True`) +- **MDN**: [Read more](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD) + +A `HEAD` request provides the headers and an otherwise identical response to what a `GET` request would provide. However, it does not actually return the body. + +```python +@app.get("/") +async def hello_world(request): + return text("Hello, world.") +``` + +Given the above route definition, Sanic Extensions will enable `HEAD` responses, as seen here. + +``` +$ curl localhost:8000 --head +HTTP/1.1 200 OK +access-control-allow-origin: * +content-length: 13 +connection: keep-alive +content-type: text/plain; charset=utf-8 +``` + +::: + +:::tab OPTIONS + +- **Configuration**: `AUTO_OPTIONS` (default `True`) +- **MDN**: [Read more](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS) + +`OPTIONS` requests provide the recipient with details about how the client is allowed to communicate with a given endpoint. + +```python +@app.get("/") +async def hello_world(request): + return text("Hello, world.") +``` + +Given the above route definition, Sanic Extensions will enable `OPTIONS` responses, as seen here. + +It is important to note that we also see `access-control-allow-origins` in this example. This is because the [CORS protection](cors.md) is enabled by default. + +``` +$ curl localhost:8000 -X OPTIONS -i +HTTP/1.1 204 No Content +allow: GET,HEAD,OPTIONS +access-control-allow-origin: * +connection: keep-alive +``` + +::: tip Even though Sanic Extensions will setup these routes for you automatically, if you decide to manually create an `@app.options` route, it will *not* be overridden. ::: + +:::tab TRACE + +- **Configuration**: `AUTO_TRACE` (default `False`) +- **MDN**: [Read more](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/TRACE) + +By default, `TRACE` endpoints will **not** be automatically created. However, Sanic Extensions **will allow** you to create them if you wanted. This is something that is not allowed in vanilla Sanic. + +```python +@app.route("/", methods=["trace"]) +async def handler(request): + ... +``` + +To enable auto-creation of these endpoints, you must first enable them when extending Sanic. + +```python +from sanic_ext import Extend, Config + +app.extend(config=Config(http_auto_trace=True)) +``` + +Now, assuming you have some endpoints setup, you can trace them as shown here: + +``` +$ curl localhost:8000 -X TRACE +TRACE / HTTP/1.1 +Host: localhost:9999 +User-Agent: curl/7.76.1 +Accept: */* +``` + +::: tip Setting up `AUTO_TRACE` can be super helpful, especially when your application is deployed behind a proxy since it will help you determine how the proxy is behaving. ::: + +:::: + +## Additional method support + +Vanilla Sanic allows you to build endpoints with the following HTTP methods: + +- [GET](/en/guide/basics/routing.html#get) +- [POST](/en/guide/basics/routing.html#post) +- [PUT](/en/guide/basics/routing.html#put) +- [HEAD](/en/guide/basics/routing.html#head) +- [OPTIONS](/en/guide/basics/routing.html#options) +- [PATCH](/en/guide/basics/routing.html#patch) +- [DELETE](/en/guide/basics/routing.html#delete) + +See [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) for more. + +---:1 + +There are, however, two more "standard" HTTP methods: `TRACE` and `CONNECT`. Sanic Extensions will allow you to build endpoints using these methods, which would otherwise not be allowed. + +It is worth pointing out that this will *NOT* enable convenience methods: `@app.trace` or `@app.connect`. You need to use `@app.route` as shown in the example here. + +:--:1 + +```python +@app.route("/", methods=["trace", "connect"]) +async def handler(_): + return empty() +``` + +:--- diff --git a/src/pt/plugins/sanic-ext/injection.md b/src/pt/plugins/sanic-ext/injection.md new file mode 100644 index 0000000000..0b3d073601 --- /dev/null +++ b/src/pt/plugins/sanic-ext/injection.md @@ -0,0 +1,329 @@ +# Dependency Injection + +Dependency injection is a method to add arguments to a route handler based upon the defined function signature. Specifically, it looks at the **type annotations** of the arguments in the handler. This can be useful in a number of cases like: + +- Fetching an object based upon request headers (like the current session user) +- Recasting certain objects into a specific type +- Using the request object to prefetch data +- Auto inject services + +The `Extend` instance has two basic methods on it used for dependency injection: a lower level `add_dependency`, and a higher level `dependency`. + +**Lower level**: `app.ext.add_dependency(...)` + +- `type: Type,`: some unique class that will be the type of the oject +- `constructor: Optional[Callable[..., Any]],` (OPTIONAL): a function that will return that type + +**Higher level**: `app.ext.dependency(...)` + +- `obj: Any`: any object that you would like injected +- `name: Optional[str]`: some name that could alternately be used as a reference + +Let's explore some use cases here. + +::: warning If you used dependency injection prior to v21.12, the lower level API method was called `injection`. It has since been renamed to `add_dependency` and starting in v21.12 `injection` is an alias for `add_dependency`. The `injection` method has been deprecated for removal in v22.6. ::: + +## Basic implementation + +The simplest use case would be simply to recast a value. + +---:1 This could be useful if you have a model that you want to generate based upon the matched path parameters. :--:1 +```python +@dataclass +class IceCream: + flavor: str + + def __str__(self) -> str: + return f"{self.flavor.title()} (Yum!)" + + +app.ext.add_dependency(IceCream) + + +@app.get("/") +async def ice_cream(request, flavor: IceCream): + return text(f"You chose: {flavor}") +``` + +``` +$ curl localhost:8000/chocolate +You chose Chocolate (Yum!) +``` +:--- + +---:1 This works by passing a keyword argument to the constructor of the `type` argument. The previous example is equivalent to this. :--:1 +```python +flavor = IceCream(flavor="chocolate") +``` +:--- + +## Additional constructors + +---:1 Sometimes you may need to also pass a constructor. This could be a function, or perhaps even a classmethod that acts as a constructor. In this example, we are creating an injection that will call `Person.create` first. + +Also important to note on this example, we are actually injecting **two (2)** objects! It of course does not need to be this way, but we will inject objects based upon the function signature. :--:1 +```python +@dataclass +class PersonID: + person_id: int + + +@dataclass +class Person: + person_id: PersonID + name: str + age: int + + @classmethod + async def create(cls, request: Request, person_id: int): + return cls(person_id=PersonID(person_id), name="noname", age=111) + + + +app.ext.add_dependency(Person, Person.create) +app.ext.add_dependency(PersonID) + +@app.get("/person/") +async def person_details( + request: Request, person_id: PersonID, person: Person +): + return text(f"{person_id}\n{person}") +``` + +``` +$ curl localhost:8000/person/123 +PersonID(person_id=123) +Person(person_id=PersonID(person_id=123), name='noname', age=111) +``` +:--- + +When a `constructor` is passed to `ext.add_dependency` (like in this example) that will be called. If not, then the object will be created by calling the `type`. A couple of important things to note about passing a `constructor`: + +1. A positional `request: Request` argument is *usually* expected. See the `Person.create` method above as an example using a `request` and [arbitrary constructors](#arbitrary-constructors) for how to use a callable that does not require a `request`. +1. All matched path parameters are injected as keyword arguments. +1. Dependencies can be chained and nested. Notice how in the previous example the `Person` dataclass has a `PersonID`? That means that `PersonID` will be called first, and that value is added to the keyword arguments when calling `Person.create`. + +## Arbitrary constructors + +---:1 Sometimes you may want to construct your injectable _without_ the `Request` object. This is useful if you have arbitrary classes or functions that create your objects. If the callable does have any required arguments, then they should themselves be injectable objects. + +This is very useful if you have services or other types of objects that should only exist for the lifetime of a single request. For example, you might use this pattern to pull a single connection from your database pool. :--:1 +```python +class Alpha: + ... + + +class Beta: + def __init__(self, alpha: Alpha) -> None: + self.alpha = alpha + +app.ext.add_dependency(Alpha) +app.ext.add_dependency(Beta) + +@app.get("/beta") +async def handler(request: Request, beta: Beta): + assert isinstance(beta.alpha, Alpha) +``` +:--- + +*Added in v22.9* + +## Objects from the `Request` + +---:1 Sometimes you may want to extract details from the request and preprocess them. You could, for example, cast the request JSON to a Python object, and then add some additional logic based upon DB queries. + +::: warning If you plan to use this method, you should note that the injection actually happens *before* Sanic has had a chance to read the request body. The headers should already have been consumed. So, if you do want access to the body, you will need to manually consume as seen in this example. + +```python +await request.receive_body() +``` +::: + +This could be used in cases where you otherwise might: + +- use middleware to preprocess and add something to the `request.ctx` +- use decorators to preprocess and inject arguments into the request handler + +In this example, we are using the `Request` object in the `compule_profile` constructor to run a fake DB query to generate and return a `UserProfile` object. :--:1 +```python +@dataclass +class User: + name: str + + +@dataclass +class UserProfile: + user: User + age: int = field(default=0) + email: str = field(default="") + + def __json__(self): + return ujson.dumps( + { + "name": self.user.name, + "age": self.age, + "email": self.email, + } + ) + + +async def fake_request_to_db(body): + today = date.today() + email = f'{body["name"]}@something.com'.lower() + difference = today - date.fromisoformat(body["birthday"]) + age = int(difference.days / 365) + return UserProfile( + User(body["name"]), + age=age, + email=email, + ) + + +async def compile_profile(request: Request): + await request.receive_body() + profile = await fake_request_to_db(request.json) + return profile + + +app.ext.add_dependency(UserProfile, compile_profile) + + +@app.patch("/profile") +async def update_profile(request, profile: UserProfile): + return json(profile) +``` + +``` +$ curl localhost:8000/profile -X PATCH -d '{"name": "Alice", "birthday": "2000-01-01"}' +{ + "name":"Alice", + "age":21, + "email":"alice@something.com" +} +``` +:--- + +## Injecting services + +It is a common pattern to create things like database connection pools and store them on the `app.ctx` object. This makes them available throughout your application, which is certainly a convenience. One downside, however, is that you no longer have a typed object to work with. You can use dependency injections to fix this. First we will show the concept using the lower level `add_dependency` like we have been using in the previous examples. But, there is a better way using the higher level `dependency` method. + +---:1 +### The lower level API using `add_dependency` + +This works very similar to the [last example](#objects-from-the-request) where the goal is the extract something from the `Request` object. In this example, a database object was created on the `app.ctx` instance, and is being returned in the dependency injection constructor. :--:1 +```python +class FakeConnection: + async def execute(self, query: str, **arguments): + return "result" + + +@app.before_server_start +async def setup_db(app, _): + app.ctx.db_conn = FakeConnection() + app.ext.add_dependency(FakeConnection, get_db) + + +def get_db(request: Request): + return request.app.ctx.db_conn + + + + +@app.get("/") +async def handler(request, conn: FakeConnection): + response = await conn.execute("...") + return text(response) +``` +``` +$ curl localhost:8000/ +result +``` +:--- + +---:1 +### The higher level API using `dependency` + +Since we have an actual *object* that is available when adding the dependency injection, we can use the higher level `dependency` method. This will make the pattern much easier to write. + +This method should always be used when you want to inject something that exists throughout the lifetime of the application instance and is not request specific. It is very useful for services, third party clients, and connection pools since they are not request specific. :--:1 +```python +class FakeConnection: + async def execute(self, query: str, **arguments): + return "result" + + +@app.before_server_start +async def setup_db(app, _): + db_conn = FakeConnection() + app.ext.dependency(db_conn) + + +@app.get("/") +async def handler(request, conn: FakeConnection): + response = await conn.execute("...") + return text(response) +``` +``` +$ curl localhost:8000/ +result +``` +:--- + +## Generic types + +Be carefule when using a [generic type](https://docs.python.org/3/library/typing.html#typing.Generic). The way that Sanic's dependency injection works is by matching the entire type definition. Therefore, `Foo` is not the same as `Foo[str]`. This can be particularly tricky when trying to use the [higher-level `dependency` method](#the-higher-level-api-using-dependency) since the type is inferred. + +---:1 For example, this will **NOT** work as expected since there is no definition for `Test[str]`. :--:1 +```python{12,16} +import typing +from sanic import Sanic, text + +T = typing.TypeVar("T") + + +class Test(typing.Generic[T]): + test: T + + +app = Sanic("testapp") +app.ext.dependency(Test()) + + +@app.get("/") +def test(request, test: Test[str]): + ... +``` +:--- + +---:1 To get this example to work, you will need to add an explicit definition for the type you intend to be injected. :--:1 +```python{13} +import typing +from sanic import Sanic, text + +T = typing.TypeVar("T") + + +class Test(typing.Generic[T]): + test: T + + +app = Sanic("testapp") +_singleton = Test() +app.ext.add_dependency(Test[str], lambda: _singleton) + + +@app.get("/") +def test(request, test: Test[str]): + ... +``` +:--- + +## Configuration + +---:1 By default, dependencies will be injected after the `http.routing.after` [signal](../../guide/advanced/signals.md#built-in-signals). Starting in v22.9, you can change this to the `http.handler.before` signal. :--:1 +```python +app.config.INJECTION_SIGNAL = "http.handler.before" +``` +:--- + +*Added in v22.9* diff --git a/src/pt/plugins/sanic-ext/logger.md b/src/pt/plugins/sanic-ext/logger.md new file mode 100644 index 0000000000..aa8b18c141 --- /dev/null +++ b/src/pt/plugins/sanic-ext/logger.md @@ -0,0 +1,26 @@ +# Background logger + +The background logger requires both `sanic>=22.9` and `sanic-ext>=22.9`. + +You can setup Sanic Extensions to log all of your messages from a background process. This requires that you not be in [single process mode](../../guide/deployment/manager.md#single-process-mode). + +Logging can sometimes be an expensive operation. By pushing all logging off to a background process, you can potentially gain some performance benefits. + +## Setup + +---:1 Out of the box, the background logger is disabled. You will need to opt-in if you would like to use it. :--:1 +```python +app.config.LOGGING = True +``` +:--- + +## How does it work + +When enabled, the extension will create a `multoprocessing.Queue`. It will remove all handlers on the [default Sanic loggers](../../guide/best-practices/logging.md) and replace them with a [`QueueHandler`](https://docs.python.org/3/library/logging.handlers.html#queuehandler). When a message is logged, it will be pushed into the queue by the handler, and read by the background process to the log handlers that were originally in place. This means you can still configure logging as normal and it should "just work." + +## Configuration + +| Key | Type | Default | Description | +| ------------------------ | ------ | ------- | ------------------------------------------------------- | +| LOGGING | `bool` | `False` | Whether to enable this extension. | +| LOGGING_QUEUE_MAX_SIZE | `int` | `4096` | The max size of the queue before messages are rejected. | diff --git a/src/pt/plugins/sanic-ext/openapi.md b/src/pt/plugins/sanic-ext/openapi.md new file mode 100644 index 0000000000..7e3314aa8c --- /dev/null +++ b/src/pt/plugins/sanic-ext/openapi.md @@ -0,0 +1,7 @@ +# Openapi + +- Adding documentation with decorators +- Documenting CBV +- Using autodoc +- Rendering docs with redoc/swagger +- Validation diff --git a/src/pt/plugins/sanic-ext/openapi/advanced.md b/src/pt/plugins/sanic-ext/openapi/advanced.md new file mode 100644 index 0000000000..46c3c45ec6 --- /dev/null +++ b/src/pt/plugins/sanic-ext/openapi/advanced.md @@ -0,0 +1,10 @@ +# Advanced + +_Documentation coming EOQ1 2023_ + +## CBV + +## Blueprints + + +## Components diff --git a/src/pt/plugins/sanic-ext/openapi/autodoc.md b/src/pt/plugins/sanic-ext/openapi/autodoc.md new file mode 100644 index 0000000000..a382e51329 --- /dev/null +++ b/src/pt/plugins/sanic-ext/openapi/autodoc.md @@ -0,0 +1,125 @@ +# Auto-documentation + +To make documenting endpoints easier, Sanic Extensions will use a function's docstring to populate your documentation. + +## Summary and description + +---:1 A function's docstring will be used to create the summary and description. As you can see from this example here, the docstring has been parsed to use the first line as the summary, and the remainder of the string as the description. :--:1 +```python +@app.get("/foo") +async def handler(request, something: str): + """This is a simple foo handler + + It is helpful to know that you could also use **markdown** inside your + docstrings. + + - one + - two + - three""" + return text(">>>") +``` +```json +"paths": { + "/foo": { + "get": { + "summary": "This is a simple foo handler", + "description": "It is helpful to know that you could also use **markdown** inside your
docstrings.

- one
- two
- three", + "responses": { + "default": { + "description": "OK" + } + }, + "operationId": "get_handler" + } + } +} +``` +:--- + +## Operation level YAML + +---:1 You can expand upon this by adding valid OpenAPI YAML to the docstring. Simply add a line that contains `openapi:`, followed by your YAML. + +The `---` shown in the example is *not* necessary. It is just there to help visually identify the YAML as a distinct section of the docstring. :--:1 +```python +@app.get("/foo") +async def handler(request, something: str): + """This is a simple foo handler + + Now we will add some more details + + openapi: + --- + operationId: fooDots + tags: + - one + - two + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: Just some dots + """ + return text("...") +``` +```json +"paths": { + "/foo": { + "get": { + "operationId": "fooDots", + "summary": "This is a simple foo handler", + "description": "Now we will add some more details", + "tags": [ + "one", + "two" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "How many items to return at one time (max 100)", + "required": false, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Just some dots" + } + } + } + } +} +``` + +:--- + +::: tip +When both YAML documentation and decorators are used, it is the content from the decorators that will take priority when generating the documentation. +::: + +## Excluding docstrings + +---:1 Sometimes a function may contain a docstring that is not meant to be consumed inside the documentation. + +**Option 1**: Globally turn off auto-documentation `app.config.OAS_AUTODOC = False` + +**Option 2**: Disable it for the single handler with the `@openapi.no_autodoc` decorator :--:1 +```python +@app.get("/foo") +@openapi.no_autodoc +async def handler(request, something: str): + """This is a docstring about internal info only. Do not parse it. + """ + return text("...") +``` +:--- diff --git a/src/pt/plugins/sanic-ext/openapi/basic.md b/src/pt/plugins/sanic-ext/openapi/basic.md new file mode 100644 index 0000000000..d820b8c7c7 --- /dev/null +++ b/src/pt/plugins/sanic-ext/openapi/basic.md @@ -0,0 +1,66 @@ +# Basics + +::: tip Side note The OpenAPI implementation in Sanic Extensions is based upon the OAS3 implementation from [`sanic-openapi`](https://github.com/sanic-org/sanic-openapi). In fact, Sanic Extensions is in a large way the successor to that project, which entered maintenance mode upon the release of Sanic Extensions. If you were previously using OAS3 with `sanic-openapi` you should have an easy path to upgrading to Sanic Extensions. Unfortunately, this project does *NOT* support the OAS2 specification. ::: + +---:1 + +Out of the box, Sanic Extensions provides automatically generated API documentation using the [v3.0 OpenAPI specification](https://swagger.io/specification/). There is nothing special that you need to do + +:--:1 + +```python +from sanic import Sanic + +app = Sanic("MyApp") + +# Add all of your views +``` + +:--- + +After doing this, you will now have beautiful documentation already generated for you based upon your existing application: + +- [http://localhost:8000/docs](http://localhost:8000/docs) +- [http://localhost:8000/docs/redoc](http://localhost:8000/docs/redoc) +- [http://localhost:8000/docs/swagger](http://localhost:8000/docs/swagger) + +Checkout the [section on configuration](../configuration.md) to learn about changing the routes for the docs. You can also turn off one of the two UIs, and customize which UI will be available on the `/docs` route. + +---:1 + +Using [Redoc](https://github.com/Redocly/redoc) + +![Redoc](~@assets/images/sanic-ext-redoc.png) + + +:--:1 + +or [Swagger UI](https://github.com/swagger-api/swagger-ui) + +![Swagger UI](~@assets/images/sanic-ext-swagger.png) + + +:--- + +## Changing specification metadata + +---:1 If you want to change any of the metada, you should use the `describe` method. + +In this example `dedent` is being used with the `description` argument to make multi-line strings a little cleaner. This is not necessary, you can pass any string value here. :--:1 +```python +from textwrap import dedent + +app.ext.openapi.describe( + "Testing API", + version="1.2.3", + description=dedent( + """ + # Info + This is a description. It is a good place to add some _extra_ doccumentation. + + **MARKDOWN** is supported. + """ + ), +) +``` +:--- diff --git a/src/pt/plugins/sanic-ext/openapi/decorators.md b/src/pt/plugins/sanic-ext/openapi/decorators.md new file mode 100644 index 0000000000..e4a04f15c2 --- /dev/null +++ b/src/pt/plugins/sanic-ext/openapi/decorators.md @@ -0,0 +1,479 @@ +# Decorators + +The primary mechanism for adding content to your schema is by decorating your endpoints. If you have used `sanic-openapi` in the past, this should be familiar to you. The decorators and their arguments match closely the [OAS v3.0 specification](https://swagger.io/specification/). + +---:1 + +All of the examples show will wrap around a route definition. When you are creating these, you should make sure that your Sanic route decorator (`@app.route`, `@app.get`, etc) is the outermost decorator. That is to say that you should put that first and then one or more of the below decorators after. + +:--:1 + +```python +from sanic_ext import openapi + + +@app.get("/path/to/") +@openapi.summary("This is a summary") +@openapi.description("This is a description") +async def handler(request, somethind: str): + ... +``` + +:--- + +---:1 + +You will also see a lot of the below examples reference a model object. For the sake of simplicity, the examples will use `UserProfile` that will look like this. The point is that it can be any well-typed class. You could easily imagine this being a `dataclass` or some other kind of model object. + +:--:1 + +```python +class UserProfile: + name: str + age: int + email: str +``` + +:--- + +## Definition decorator + +### `@openapi.definition` + +The `@openapi.definition` decorator allows you to define all parts of an operations on a path at once. It is an omnibums decorator in that it has the same capabilities to create operation definitions as the rest of the decorators. Using multiple field-specific decorators or a single decorator is a style choice for you the developer. + +The fields are purposely permissive in accepting multiple types to make it easiest for you to define your operation. + +**Arguments** + +| Field | Type | +| ------------- | ---------------------------------------------------- | +| `body` | ***dict, RequestBody, ***YourModel****** | +| `deprecated` | **bool** | +| `description` | **str** | +| `document` | **str, ExternalDocumentation** | +| `exclude` | **bool** | +| `operation` | **str** | +| `parameter` | **str, dict, Parameter, [str], [dict], [Parameter]** | +| `response` | **dict, Response, *YourModel*, [dict], [Response]** | +| `summary` | **str** | +| `tag` | **str, Tag, [str], [Tag]** | +| `secured` | **Dict[str, Any]** | + +**Examples** + +---:1 + +```python +@openapi.definition( + body=RequestBody(UserProfile, required=True), + summary="User profile update", + tag="one", + response=[Success, Response(Failure, status=400)], +) +``` + +:--:1 + +:--- + +*See below examples for more examples. Any of the values for the below decorators can be used in the corresponding keyword argument.* + +## Field-specific decorators + +All the following decorators are based on `@openapi` + +::::tabs + +:::tab body + +**Arguments** + +| Field | Type | +| ----------- | ---------------------------------- | +| **content** | ***YourModel*, dict, RequestBody** | + +**Examples** + +---:1 + +```python +@openapi.body(UserProfile) +``` + +```python +@openapi.body({"application/json": UserProfile}) +``` + +```python +@openapi.body(RequestBody({"application/json": UserProfile})) +``` + +:--:1 + +```python +@openapi.body({"content": UserProfile}) +``` + +```python +@openapi.body(RequestBody(UserProfile)) +``` + +```python +@openapi.body({"application/json": {"description": ...}}) +``` + +:--- + +::: + +:::tab deprecated + +**Arguments** + +*None* + +**Examples** + +---:1 + +```python +@openapi.deprecated() +``` + +:--:1 + +```python +@openapi.deprecated +``` + +:--- + +::: + +:::tab description + +**Arguments** + +| Field | Type | +| ------ | ------- | +| `text` | **str** | + +**Examples** + +---:1 + +```python +@openapi.description( + """This is a **description**. + +## You can use `markdown` + +- And +- make +- lists. +""" +) +``` + +:--:1 + +:--- + +::: + +:::tab document + +**Arguments** + +| Field | Type | +| ------------- | ------- | +| `url` | **str** | +| `description` | **str** | + +**Examples** + +---:1 + +```python +@openapi.document("http://example.com/docs") +``` + +:--:1 + +```python +@openapi.document(ExternalDocumentation("http://example.com/more")) +``` + +:--- + +::: + +:::tab exclude + +Can be used on route definitions like all of the other decorators, or can be called on a Blueprint + +**Arguments** + +| Field | Type | Default | +| ------ | ------------- | -------- | +| `flag` | **bool** | **True** | +| `bp` | **Blueprint** | | + +**Examples** + +---:1 + +```python +@openapi.exclude() +``` + +:--:1 + +```python +openapi.exclude(bp=some_blueprint) +``` + +:--- + +::: + +:::tab operation + +Sets the operation ID. + +**Arguments** + +| Field | Type | +| ------ | ------- | +| `name` | **str** | + +**Examples** + +---:1 + +```python +@openapi.operation("doNothing") +``` + +:--:1 + +:--- + +::: + +:::tab parameter + +**Arguments** + +| Field | Type | Default | +| ---------- | ----------------------------------------- | ----------- | +| `name` | **str** | | +| `schema` | ***type*** | **str** | +| `location` | **"query", "header", "path" or "cookie"** | **"query"** | + +**Examples** + +---:1 + +```python +@openapi.parameter("thing") +``` + +```python +@openapi.parameter(parameter=Parameter("foobar", deprecated=True)) +``` + +:--:1 + +```python +@openapi.parameter("Authorization", str, "header") +``` + +```python +@openapi.parameter("thing", required=True, allowEmptyValue=False) +``` + +:--- + +::: + +:::tab response + +**Arguments** + +If using a `Response` object, you should not pass any other arguments. + +| Field | Type | +| ------------- | ----------------------------- | +| `status` | **int** | +| `content` | ***type*, *YourModel*, dict** | +| `description` | **str** | +| `response` | **Response** | + +**Examples** + +---:1 + +```python +@openapi.response(200, str, "This is endpoint returns a string") +``` + +```python +@openapi.response(200, {"text/plain": str}, "...") +``` + +```python +@openapi.response(response=Response(UserProfile, description="...")) +``` + +```python +@openapi.response( + response=Response( + { + "application/json": UserProfile, + }, + description="...", + status=201, + ) +) +``` + +:--:1 + +```python +@openapi.response(200, UserProfile, "...") +``` + +```python +@openapi.response( + 200, + { + "application/json": UserProfile, + }, + "Description...", +) +``` + +:--- + +::: + +:::tab summary + +**Arguments** + +| Field | Type | +| ------ | ------- | +| `text` | **str** | + +**Examples** + +---:1 + +```python +@openapi.summary("This is an endpoint") +``` + +:--:1 + +:--- + +::: + +:::tab tag + +**Arguments** + +| Field | Type | +| ------- | ------------ | +| `*args` | **str, Tag** | + +**Examples** + +---:1 + +```python +@openapi.tag("foo") +``` + +:--:1 + +```python +@openapi.tag("foo", Tag("bar")) +``` + +:--- + +::: + +:::tab secured + +**Arguments** + +| Field | Type | +| ----------------- | ----------------------- | +| `*args, **kwargs` | **str, Dict[str, Any]** | + +**Examples** + +---:1 +```python +@openapi.secured() +``` +:--:1 :--- + +---:1 +```python +@openapi.secured("foo") +``` +:--:1 +```python +@openapi.secured("token1", "token2") +``` +:--- + +---:1 +```python +@openapi.secured({"my_api_key": []}) +``` +:--:1 +```python +@openapi.secured(my_api_key=[]) +``` +:--- + +Do not forget to use `add_security_scheme`. See [security](./security.md) for more details. + +::: + +:::: + +## Integration with Pydantic + +Pydantic models have the ability to [generate OpenAPI schema](https://pydantic-docs.helpmanual.io/usage/schema/). + +---:1 To take advantage of Pydantic model schema generation, pass the output in place of the schema. :--:1 +```python +from sanic import Sanic, json +from sanic_ext import validate, openapi +from pydantic import BaseModel, Field + +class Test(BaseModel): + foo: str = Field(description="Foo Description", example="FOOO") + bar: str = "test" + + +app = Sanic("test") + +@app.get("/") +@openapi.definition( + body={'application/json': Test.schema()}, +) +@validate(json=Test) +async def get(request): + return json({}) +``` +:--- + +*Added in v22.9* diff --git a/src/pt/plugins/sanic-ext/openapi/security.md b/src/pt/plugins/sanic-ext/openapi/security.md new file mode 100644 index 0000000000..b73b15207e --- /dev/null +++ b/src/pt/plugins/sanic-ext/openapi/security.md @@ -0,0 +1,86 @@ +# Security Schemes + +To document authentication schemes, there are two steps. + +_Security is only available starting in v21.12.2_ + +## Document the scheme + +---:1 The first thing that you need to do is define one or more security schemes. The basic pattern will be to define it as: + +```python +add_security_scheme("", "") +``` + +The `type` should correspond to one of the allowed security schemes: `"apiKey"`, `"http"`, `"oauth2"`, `"openIdConnect"`. You can then pass appropriate keyword arguments as allowed by the specification. + +You should consult the [OpenAPI Specification](https://swagger.io/specification/) for details on what values are appropriate. :--:1 +```python +app.ext.openapi.add_security_scheme("api_key", "apiKey") +app.ext.openapi.add_security_scheme( + "token", + "http", + scheme="bearer", + bearer_format="JWT", +) +app.ext.openapi.add_security_scheme("token2", "http") +app.ext.openapi.add_security_scheme( + "oldschool", + "http", + scheme="basic", +) +app.ext.openapi.add_security_scheme( + "oa2", + "oauth2", + flows={ + "implicit": { + "authorizationUrl": "http://example.com/auth", + "scopes": { + "on:two": "something", + "three:four": "something else", + "threefour": "something else...", + }, + } + }, +) +``` +:--- + +## Document the endpoints + +---:1 There are two options, document _all_ endpoints. + +:--:1 +```python +app.ext.openapi.secured() +app.ext.openapi.secured("token") +``` +:--- + +---:1 Or, document only specific routes. :--:1 +```python +@app.route("/one") +async def handler1(request): + """ + openapi: + --- + security: + - foo: [] + """ + + +@app.route("/two") +@openapi.secured("foo") +@openapi.secured({"bar": []}) +@openapi.secured(baz=[]) +async def handler2(request): + ... + + +@app.route("/three") +@openapi.definition(secured="foo") +@openapi.definition(secured={"bar": []}) +async def handler3(request): + ... +``` +:--- diff --git a/src/pt/plugins/sanic-ext/openapi/ui.md b/src/pt/plugins/sanic-ext/openapi/ui.md new file mode 100644 index 0000000000..81591fc1da --- /dev/null +++ b/src/pt/plugins/sanic-ext/openapi/ui.md @@ -0,0 +1,26 @@ +# UI + +Sanic Extensions comes with both Redoc and Swagger interfaces. You have a choice to use one, or both of them. Out of the box, the following endpoints are setup for you, with the bare `/docs` displaying Redoc. + +- `/docs` +- `/docs/openapi.json` +- `/docs/redoc` +- `/docs/swagger` +- `/docs/openapi-config` + +## Config options + +| **Key** | **Type** | **Default** | **Desctiption** | +| -------------------------- | --------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| `OAS_IGNORE_HEAD` | `bool` | `True` | Whether to display `HEAD` endpoints. | +| `OAS_IGNORE_OPTIONS` | `bool` | `True` | Whether to display `OPTIONS` endpoints. | +| `OAS_PATH_TO_REDOC_HTML` | `Optional[str]` | `None` | Path to HTML to override the default Redoc HTML | +| `OAS_PATH_TO_SWAGGER_HTML` | `Optional[str]` | `None` | Path to HTML to override the default Swagger HTML | +| `OAS_UI_DEFAULT` | `Optional[str]` | `"redoc"` | Can be set to `redoc` or `swagger`. Controls which UI to display on the base route. If set to `None`, then the base route will not be setup. | +| `OAS_UI_REDOC` | `bool` | `True` | Whether to enable Redoc UI. | +| `OAS_UI_SWAGGER` | `bool` | `True` | Whether to enable Swagger UI. | +| `OAS_URI_TO_CONFIG` | `str` | `"/openapi-config"` | URI path to the OpenAPI config used by Swagger | +| `OAS_URI_TO_JSON` | `str` | `"/openapi.json"` | URI path to the JSON document. | +| `OAS_URI_TO_REDOC` | `str` | `"/redoc"` | URI path to Redoc. | +| `OAS_URI_TO_SWAGGER` | `str` | `"/swagger"` | URI path to Swagger. | +| `OAS_URL_PREFIX` | `str` | `"/docs"` | URL prefix to use for the Blueprint for OpenAPI docs. | diff --git a/src/pt/plugins/sanic-ext/templating.md b/src/pt/plugins/sanic-ext/templating.md new file mode 100644 index 0000000000..91eea27c7b --- /dev/null +++ b/src/pt/plugins/sanic-ext/templating.md @@ -0,0 +1,132 @@ +# Templating + +Sanic Extensions can easily help you integrate templates into your route handlers. + + +## Dependencies + +**Currently, we only support [Jinja](https://github.com/pallets/jinja/).** + +[Read the Jinja docs first](https://jinja.palletsprojects.com/en/3.1.x/) if you are unfamiliar with how to create templates. + +Sanic Extensions will automatically setup and load Jinja for you if it is installed in your environment. Therefore, the only setup that you need to do is install Jinja: + +``` +pip install Jinja2 +``` + +## Rendering a template from a file + +There are three (3) ways for you: + +1. Using a decorator to pre-load the template file +1. Returning a rendered `HTTPResponse` object +1. Hybrid pattern that creates a `LazyResponse` + +Let's imagine you have a file called `./templates/foo.html`: + +```html + + + + + My Webpage + + + +

Hello, world!!!!

+
    + {% for item in seq %} +
  • {{ item }}
  • + {% endfor %} +
+ + + +``` + +Let's see how you could render it with Sanic + Jinja. + +### Option 1 - as a decorator + +---:1 The benefit of this approach is that the templates can be predefined at startup time. This will mean that less fetching needs to happen in the handler, and should therefore be the fastest option. :--:1 +```python +@app.get("/") +@app.ext.template("foo.html") +async def handler(request: Request): + return {"seq": ["one", "two"]} +``` +:--- + +### Option 2 - as a return object + +---:1 This is meant to mimic the `text`, `json`, `html`, `file`, etc pattern of core Sanic. It will allow the most customization to the response object since it has direct control of it. Just like in other `HTTPResponse` objects, you can control headers, cookies, etc. :--:1 +```python +from sanic_ext import render + +@app.get("/alt") +async def handler(request: Request): + return await render( + "foo.html", context={"seq": ["three", "four"]}, status=400 + ) +``` +:--- + +### Option 3 - hybrid/lazy + +---:1 In this approach, the template is defined up front and not inside the handler (for performance). Then, the `render` function returns a `LazyResponse` that can be used to build a proper `HTTPResponse` inside the decorator. :--:1 +```python +from sanic_ext import render + +@app.get("/") +@app.ext.template("foo.html") +async def handler(request: Request): + return await render(context={"seq": ["five", "six"]}, status=400) +``` +:--- + +## Rendering a template from a string + +---:1 Sometimes you may want to write (or generate) your template inside of Python code and _not_ read it from an HTML file. In this case, you can still use the `render` function we saw above. Just use `template_source`. :--:1 +```python +from sanic_ext import render +from textwrap import dedent + +@app.get("/") +async def handler(request): + template = dedent(""" + + + + + My Webpage + + + +

Hello, world!!!!

+
    + {% for item in seq %} +
  • {{ item }}
  • + {% endfor %} +
+ + + + """) + return await render( + template_source=template, + context={"seq": ["three", "four"]}, + app=app, + ) +``` +:--- + +::: tip In this example, we use `textwrap.dedent` to remove the whitespace in the beginning of each line of the multi-line string. It is not necessary, but just a nice touch to keep both the code and the generated source clean. ::: + +## Development and auto-reload + +If auto-reload is turned on, then changes to your template files should trigger a reload of the server. + +## Configuration + +See `templating_enable_async` and `templating_path_to_templates` in [settings](./configuration.md#settings). diff --git a/src/pt/plugins/sanic-ext/validation.md b/src/pt/plugins/sanic-ext/validation.md new file mode 100644 index 0000000000..0aa293cbaa --- /dev/null +++ b/src/pt/plugins/sanic-ext/validation.md @@ -0,0 +1,173 @@ +# Validation + +One of the most commonly implemented features of a web application is user-input validation. For obvious reasons, this is not only a security issue, but also just plain good practice. You want to make sure your data conforms to expectations, and throw a `400` response when it does not. + +## Implementation + +### Validation with Dataclasses + +With the introduction of [Data Classes](https://docs.python.org/3/library/dataclasses.html), Python made it super simple to create objects that meet a defined schema. However, the standard library only supports type checking validation, **not** runtime validation. Sanic Extensions adds the ability to do runtime validations on incoming requests using `dataclasses` out of the box. If you also have either `pydantic` or `attrs` installed, you can alternatively use one of those libraries. + +---:1 + +First, define a model. + +:--:1 + +```python +@dataclass +class SearchParams: + q: str +``` + +:--- + +---:1 + +Then, attach it to your route + +:--:1 + +```python +from sanic_ext import validate + +@app.route("/search") +@validate(query=SearchParams) +async def handler(request, query: SearchParams): + return json(asdict(query)) +``` + +:--- + +---:1 + +You should now have validation on the incoming request. + +:--:1 + +``` +$ curl localhost:8000/search +⚠️ 400 — Bad Request +==================== +Invalid request body: SearchParams. Error: missing a required argument: 'q' +``` +``` +$ curl localhost:8000/search\?q=python +{"q":"python"} +``` + +:--- + +### Validation with Pydantic + + +You can use Pydantic models also. + +---:1 + +First, define a model. + +:--:1 + +```python +class Person(BaseModel): + name: str + age: int +``` + +:--- + +---:1 + +Then, attach it to your route + +:--:1 + +```python +from sanic_ext import validate + +@app.post("/person") +@validate(json=Person) +async def handler(request, body: Person): + return json(body.dict()) +``` +:--- + +---:1 + +You should now have validation on the incoming request. + +:--:1 + +``` +$ curl localhost:8000/person -d '{"name": "Alice", "age": 21}' -X POST +{"name":"Alice","age":21} +``` + +:--- + +### Validation with Attrs + + +You can use Attrs also. + +---:1 + +First, define a model. + +:--:1 + +```python +@attrs.define +class Person: + name: str + age: int + +``` + +:--- + +---:1 + +Then, attach it to your route + +:--:1 + +```python +from sanic_ext import validate + +@app.post("/person") +@validate(json=Person) +async def handler(request, body: Person): + return json(attrs.asdict(body)) +``` +:--- + +---:1 + +You should now have validation on the incoming request. + +:--:1 + +``` +$ curl localhost:8000/person -d '{"name": "Alice", "age": 21}' -X POST +{"name":"Alice","age":21} +``` + +:--- + +## What can be validated? + +The `validate` decorator can be used to validate incoming user data from three places: JSON body data (`request.json`), form body data (`request.form`), and query parameters (`request.args`). + +---:1 As you might expect, you can attach your model using the keyword arguments of the decorator. + +:--:1 +```python +@validate( + json=ModelA, + query=ModelB, + form=ModelC, +) +``` +:--- diff --git a/src/pt/plugins/sanic-testing/clients.md b/src/pt/plugins/sanic-testing/clients.md new file mode 100644 index 0000000000..ee02576ecc --- /dev/null +++ b/src/pt/plugins/sanic-testing/clients.md @@ -0,0 +1,99 @@ +# Test Clients + +There are three different test clients available to you, each of them presents different capabilities. + +## Regular sync client: `SanicTestClient` + +The `SanicTestClient` runs an actual version of the Sanic Server on your local network to run its tests. Each time it calls an endpoint it will spin up a version of the application and bind it to a socket on the host OS. Then, it will use `httpx` to make calls directly to that application. + +This is the typical way that Sanic applications are tested. + +---:1 Once installing Sanic Testing, the regular `SanicTestClient` can be used without further setup. This is because Sanic does the leg work for you under the hood. :--: +```python +app.test_client.get("/path/to/endpoint") +``` +:--- + +---:1 However, you may find it desirable to instantiate the client yourself. :--: +```python +from sanic_testing.testing import SanicTestClient + +test_client = SanicTestClient(app) +test_client.get("/path/to/endpoint") +``` +:--- + +---:1 A third option for starting the test client is to use the `TestManager`. This is a convenience object that sets up both the `SanicTestClient` and the `SanicASGITestClient`. + +:--: +```python +from sanic_testing import TestManager + +mgr = TestManager(app) +app.test_client.get("/path/to/endpoint") +# or +mgr.test_client.get("/path/to/endpoint") +``` +:--- + +You can make a request by using one of the following methods + +- `SanicTestClient.get` +- `SanicTestClient.post` +- `SanicTestClient.put` +- `SanicTestClient.patch` +- `SanicTestClient.delete` +- `SanicTestClient.options` +- `SanicTestClient.head` +- `SanicTestClient.websocket` +- `SanicTestClient.request` + +You can use these methods *almost* identically as you would when using `httpx`. Any argument that you would pass to `httpx` will be accepted, **with one caveat**: If you are using `test_client.request` and want to manually specify the HTTP method, you should use: `http_method`: + +```python +test_client.request("/path/to/endpoint", http_method="get") +``` + +## ASGI async client: `SanicASGITestClient` + +Unlike the `SanicTestClient` that spins up a server on every request, the `SanicASGITestClient` does not. Instead it makes use of the `httpx` library to execute Sanic as an ASGI application to reach inside and execute the route handlers. + +---:1 This test client provides all of the same methods and generally works as the `SanicTestClient`. The only difference is that you will need to add an `await` to each call: :--: +```python +await app.test_client.get("/path/to/endpoint") +``` +:--- + +The `SanicASGITestClient` can be used in the exact same three ways as the `SanicTestClient`. + +::: tip Note The `SanicASGITestClient` does not need to only be used with ASGI applications. The same way that the `SanicTestClient` does not need to only test sync endpoints. Both of these clients are capable of testing *any* Sanic application. ::: + +## Persistent service client: `ReusableClient` + +This client works under a similar premise as the `SanicTestClient` in that it stands up an instance of your application and makes real HTTP requests to it. However, unlike the `SanicTestClient`, when using the `ReusableClient` you control the lifecycle of the application. + +That means that every request **does not** start a new web server. Instead you will start the server and stop it as needed and can make multiple requests to the same running instance. + +---:1 Unlike the other two clients, you **must** instantiate this client for use: :--: +```python +from sanic_testing.reusable import ReusableClient + +client = ReusableClient(app) +``` +:--- + + +---:1 Once created, you will use the client inside of a context manager. Once outside of the scope of the manager, the server will shutdown. :--: +```python +from sanic_testing.reusable import ReusableClient + +def test_multiple_endpoints_on_same_server(app): + client = ReusableClient(app) + with client: + _, response = client.get("/path/to/1") + assert response.status == 200 + + _, response = client.get("/path/to/2") + assert response.status == 200 +``` +:--- diff --git a/src/pt/plugins/sanic-testing/getting-started.md b/src/pt/plugins/sanic-testing/getting-started.md new file mode 100644 index 0000000000..e9f2c8c4e7 --- /dev/null +++ b/src/pt/plugins/sanic-testing/getting-started.md @@ -0,0 +1,83 @@ +# Getting Started + +Sanic Testing is the *official* testing client for Sanic. Its primary use is to power the tests of the Sanic project itself. However, it is also meant as an easy-to-use client for getting your API tests up and running quickly. + +## Minimum requirements + +- **Python**: 3.7+ +- **Sanic**: 21.3+ + +Versions of Sanic older than 21.3 have this module integrated into Sanic itself as `sanic.testing`. + +## Install + +Sanic Testing can be installed from PyPI: + +``` +pip install sanic-testing +``` + +## Basic Usage + +As long as the `sanic-testing` package is in the environment, there is nothing you need to do to start using it. + + +### Writing a sync test + +In order to use the test client, you just need to access the property `test_client` on your application instance: + +```python +import pytest +from sanic import Sanic, response + + +@pytest.fixture +def app(): + sanic_app = Sanic("TestSanic") + + @sanic_app.get("/") + def basic(request): + return response.text("foo") + + return sanic_app + +def test_basic_test_client(app): + request, response = app.test_client.get("/") + + assert request.method.lower() == "get" + assert response.body == b"foo" + assert response.status == 200 +``` + +### Writing an async test + +In order to use the async test client in `pytest`, you should install the `pytest-asyncio` plugin. + +``` +pip install pytest-asyncio +``` + +You can then create an async test and use the ASGI client: + +```python +import pytest +from sanic import Sanic, response + +@pytest.fixture +def app(): + sanic_app = Sanic(__name__) + + @sanic_app.get("/") + def basic(request): + return response.text("foo") + + return sanic_app + +@pytest.mark.asyncio +async def test_basic_asgi_client(app): + request, response = await app.asgi_client.get("/") + + assert request.method.lower() == "get" + assert response.body == b"foo" + assert response.status == 200 +``` diff --git a/src/ru/README.md b/src/ru/README.md new file mode 100644 index 0000000000..9445b40502 --- /dev/null +++ b/src/ru/README.md @@ -0,0 +1,30 @@ +--- +home: true +heroImage: https://raw.githubusercontent.com/huge-success/sanic-assets/master/png/sanic-framework-logo-400x97.png +heroText: Быстро создаётся. Быстро работает. +tagline: Веб-сервер/фреймворк Python следующего поколения +actionText: Начать → +actionLink: /ru/guide/ +features: + - + title: Простой и легковесный + details: Интуитивно понятный API с умными установками по умолчанию и без лишних наворотов позволяет вам быстро создать ваше приложение. + - + title: Ненавязчивый и гибкий + details: Разрабатывайте в той манере, в которой вы хотите, не позволяя вашему инструментарию вас ограничивать. + - + title: Производительный и масштабируемый + details: Создан с нуля с быстротой и масштабируемостью в качестве главной задачи. Готов к обслуживанию больших и маленьких веб-приложений. + - + title: Готов к работе в продакшене + details: Из коробки поставляется в комплекте с веб-сервером, готовым к обслуживанию ваших веб-приложений. + - + title: Доверие миллионов + details: Sanic является одним из самых популярных фреймворков на PyPI, а также самым популярным фреймворком с поддержкой асинхронной парадигмы + - + title: Поддерживается сообществом + details: Проект поддерживается и управляется сообществом для сообщества. +pageClass: landing-page +logo: false +--- + diff --git a/src/ru/guide/README.md b/src/ru/guide/README.md new file mode 100644 index 0000000000..cf9ee5545a --- /dev/null +++ b/src/ru/guide/README.md @@ -0,0 +1,99 @@ +--- +pageClass: intro +--- + +# Introduction + +Sanic is a Python 3.7+ web server and web framework that’s written to go fast. It allows the usage of the async/await syntax added in Python 3.5, which makes your code non-blocking and speedy. + +| | | +| ------- | ----------------------------------------------------------------------------------------------------------------------------- | +| Build | [![Build Status][1]][1] [![AppVeyor Build Status][3]][2] [![Codecov]][3] | +| Docs | [![Documentation]][4] | +| Package | [![PyPI][7]][5] [![PyPI version][9]][5] [![PyPI Wheel][11]][6] [![Supported implementations][13]][6] [![Code style black]][7] | +| Support | [![Forums][16]][8] [![Discord][18]][9] [![Awesome Sanic List]][10] | +| Stats | [![Downloads][21]][11] [![Downloads][23]][11] | + +## What is it? + +First things first, before you jump in the water, you should know that Sanic is different than other frameworks. + +Right there in that first sentence there is a huge mistake because Sanic is _both_ a **framework** and a **web server**. In the deployment section we will talk a little bit more about this. + +But, remember, out of the box Sanic comes with everything you need to write, deploy, and scale a production grade web application. :rocket: + +## Goal + +> To provide a simple way to get up and running a highly performant HTTP server that is easy to build, to expand, and ultimately to scale. +## Features + +---:1 + +### Core + +- Built in, **_fast_** web server +- Production ready +- Highly scalable +- ASGI compliant +- Simple and intuitive API design +- By the community, for the community + +:--:1 + +### Sanic Extensions [[learn more](../plugins/sanic-ext/getting-started.md)] + +- CORS protection +- Template rendering with Jinja +- Dependency injection into route handlers +- OpenAPI documentation with Redoc and/or Swagger +- Predefined, endpoint-specific response serializers +- Request query arguments and body input validation +- Auto create `HEAD`, `OPTIONS`, and `TRACE` endpoints + +:--- + + + +## Sponsor + +Check out [open collective](https://opencollective.com/sanic-org) to learn more about helping to fund Sanic. + + +## Join the Community + +The main channel for discussion is at the [community forums](https://community.sanicframework.org/). There also is a [Discord Server](https://discord.gg/FARQzAEMAA) for live discussion and chat. + +The Stackoverflow `[sanic]` tag is [actively monitored](https://stackoverflow.com/questions/tagged/sanic) by project maintainers. + +## Contribution + +We are always happy to have new contributions. We have [marked issues good for anyone looking to get started](https://github.com/sanic-org/sanic/issues?q=is%3Aopen+is%3Aissue+label%3Abeginner), and welcome [questions/answers/discussion on the forums](https://community.sanicframework.org/). Please take a look at our [Contribution guidelines](https://github.com/sanic-org/sanic/blob/master/CONTRIBUTING.rst). + +## Who we are + + + +[1]: https://travis-ci.com/sanic-org/sanic.svg?branch=master +[1]: https://travis-ci.com/sanic-org/sanic +[3]: https://ci.appveyor.com/api/projects/status/d8pt3ids0ynexi8c/branch/master?svg=true +[2]: https://ci.appveyor.com/project/sanic-org/sanic +[3]: https://codecov.io/gh/sanic-org/sanic +[4]: http://sanic.readthedocs.io/en/latest/?badge=latest +[7]: https://img.shields.io/pypi/v/sanic.svg +[5]: https://pypi.python.org/pypi/sanic/ +[9]: https://img.shields.io/pypi/pyversions/sanic.svg +[5]: https://pypi.python.org/pypi/sanic/ +[11]: https://img.shields.io/pypi/wheel/sanic.svg +[6]: https://pypi.python.org/pypi/sanic +[13]: https://img.shields.io/pypi/implementation/sanic.svg +[6]: https://pypi.python.org/pypi/sanic +[7]: https://github.com/ambv/black +[16]: https://img.shields.io/badge/forums-community-ff0068.svg +[8]: https://community.sanicframework.org/ +[18]: https://img.shields.io/discord/812221182594121728?logo=discord +[9]: https://discord.gg/FARQzAEMAA +[10]: https://github.com/mekicha/awesome-sanic +[21]: https://pepy.tech/badge/sanic/month +[11]: https://pepy.tech/project/sanic +[23]: https://pepy.tech/badge/sanic/week +[11]: https://pepy.tech/project/sanic diff --git a/src/ru/guide/advanced/README.md b/src/ru/guide/advanced/README.md new file mode 100644 index 0000000000..acde4032f0 --- /dev/null +++ b/src/ru/guide/advanced/README.md @@ -0,0 +1 @@ +# Продвинутый уровень diff --git a/src/ru/guide/advanced/class-based-views.md b/src/ru/guide/advanced/class-based-views.md new file mode 100644 index 0000000000..f85f22a6bf --- /dev/null +++ b/src/ru/guide/advanced/class-based-views.md @@ -0,0 +1,184 @@ +# Объекты представления на основе классов + +## Зачем их использовать? + +---:1 + +### Проблема + +Стандартным шаблоном проектирования API является наличие у одного и того же эндпоинта разной функциональности, которая зависит от HTTP метода. + +Несмотря на то, что оба этих варианта работают, они являются не самыми лучшими методами проектирования и возможно их будет трудно поддерживать в будущем по мере роста вашего проекта. :--:1 +```python +@app.get("/foo") +async def foo_get(request): + ... + +@app.post("/foo") +async def foo_post(request): + ... + +@app.put("/foo") +async def foo_put(request): + ... + +@app.route("/bar", methods=["GET", "POST", "PATCH"]) +async def bar(request): + if request.method == "GET": + ... + elif request.method == "POST": + ... + elif request.method == "PATCH": + ... +``` +:--- + +---:1 + +### Решение + +Объекты представления на основе классов - это просто классы, которые реализуют поведение ответов на запросы. Они обеспечивают способ разделения обработки различных типов HTTP-запросов в одном и том же эндпоинте. :--:1 +```python +from sanic.views import HTTPMethodView + +class FooBar(HTTPMethodView): + async def get(self, request): + ... + + async def post(self, request): + ... + + async def put(self, request): + ... + +app.add_route(FooBar.as_view(), "/foobar") +``` +:--- + +## Определение представления + +Объект представления на основе классов должен быть подклассом `HTTPMethodView`. Затем вы можете реализовать методы класса с названием соответствующего HTTP-метода. Если запрос содержит метод, который не был определен в представлении, будет сгенерирован ответ `405: Method not allowed`. + +---:1 + +Для регистрации объекта представления на основе классов в эндпоинте используется метод `app.add_route`. Первым аргументом должен быть заданный класс с методом `as_view`, а вторым - URL-путь к эндпоинту. + +Перечень доступных методов: + +- get +- post +- put +- patch +- delete +- head +- options :--:1 +```python +from sanic.views import HTTPMethodView +from sanic.response import text + +class SimpleView(HTTPMethodView): + + def get(self, request): + return text("I am get method") + + # Вы также можете использовать async + async def post(self, request): + return text("I am post method") + + def put(self, request): + return text("I am put method") + + def patch(self, request): + return text("I am patch method") + + def delete(self, request): + return text("I am delete method") + +app.add_route(SimpleView.as_view(), "/") +``` +:--- + +## Параметры пути + +---:1 + +Вы можете использовать параметры пути точно так же, как это обсуждалось в разделе [Маршрутизация](/guide/basics/routing.md). :--:1 +```python +class NameView(HTTPMethodView): + + def get(self, request, name): + return text("Hello {}".format(name)) + +app.add_route(NameView.as_view(), "/") +``` +:--- + +## Декораторы + +Как обсуждалось в разделе [Декораторы](/guide/best-practices/decorators.md), часто вам нужно добавить функционал в эндпоинты с использованием декораторов. С объектами представления на основе классов у вас есть два варианта: + +1. Применить ко _всем_ HTTP-методам в представлении +2. Индивидуально применить к конкретным HTTP-методам + +Давайте посмотрим, как это выглядит: + +---:1 + +### Применение ко всем методам + +Если вы хотите добавить какие-то декораторы к классу целиком, вы можете определить переменную класса `decorators`. Эти декораторы будут применены к классу, когда вызывается `as_view`. :--:1 +```python +class ViewWithDecorator(HTTPMethodView): + decorators = [список_объектов_декораторов] + + def get(self, request, name): + return text("Hello I have a decorator") + + def post(self, request, name): + return text("Hello I also have a decorator") + +app.add_route(ViewWithDecorator.as_view(), "/url") +``` +:--- + +---:1 + +### Применение к отдельным методам + +Но если вы просто хотите задекорировать не все, а отдельные методы, вы можете сделать это так, как показано ниже. :--:1 +```python +class ViewWithSomeDecorator(HTTPMethodView): + + @staticmethod + @some_decorator_here + def get(request, name): + return text("Hello I have a decorator") + + def post(self, request, name): + return text("Hello I do not have any decorators") + + @some_decorator_here + def patch(self, request, name): + return text("Hello I have a decorator") +``` +:--- + +## Создание URL +---:1 + +Это работает аналогично [созданию любого другого URL](/guide/basics/routing.md#generating-a-url) за исключением того, что имя класса является частью эндпоинта. :--:1 +```python +@app.route("/") +def index(request): + url = app.url_for("SpecialClassView") + return redirect(url) + + +class SpecialClassView(HTTPMethodView): + def get(self, request): + return text("Hello from the Special Class View!") + + +app.add_route(SpecialClassView.as_view(), "/special_class_view") +``` +:--- diff --git a/src/ru/guide/advanced/proxy-headers.md b/src/ru/guide/advanced/proxy-headers.md new file mode 100644 index 0000000000..405e943088 --- /dev/null +++ b/src/ru/guide/advanced/proxy-headers.md @@ -0,0 +1,435 @@ +# Конфигурация прокси + +При использовании обратного прокси-сервера (например, nginx), значение `request.ip` будет содержать прокси IP-адрес, как правило, `127.0.0.1`. Чаще всего вы хотите получить **не** это. + +Sanic может быть настроен на использование заголовков прокси для определения истинного IP-адреса клиента, доступного как `request.remote_addr`. Полный внешний URL также строится из полей заголовка, _если они имеются_. + +::: Подсказка Внимание +Без надлежащих предосторожностей злонамеренный клиент может использовать заголовки прокси для исправления собственного IP. Чтобы избежать подобных проблем, Sanic не использует ни один прокси-сервер, если он явно не включен. +::: + +---:1 + +Сервисы за обратными прокси должны настроить один или несколько из следующих [параметров конфигурации](/guide/deployment/configuration.md): + +- `FORWARDED_SECRET` +- `REAL_IP_HEADER` +- `PROXIES_COUNT` :--:1 +```python +app.config.FORWARDED_SECRET = "super-duper-secret" +app.config.REAL_IP_HEADER = "CF-Connecting-IP" +app.config.PROXIES_COUNT = 2 +``` +:--- + +## Заголовок Forwarded + +Чтобы использовать заголовок `Forwarded`, вы должны установить `app.config.FORWARDED_SECRET` на значение, известное доверенному прокси-серверу. Это секретное значение используется для надежной идентификации конкретного прокси-сервера. + +Sanic игнорирует любые элементы без секретного ключа, и даже не будет разбирать заголовок, если секретный ключ не установлен. + +Все остальные заголовки прокси-сервера игнорируются при обнаружении доверенного переадресационного элемента, так как он уже несет полную информацию о клиенте. + +Чтобы узнать больше о заголовке `Forwarded`, прочитайте связанные статьи [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded) и [Nginx](https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/). + +## Традиционные прокси-заголовки + +### Заголовки IP + +Когда ваш прокси перенаправляет вам IP-адрес в известном заголовке, с помощью конфигурации `REAL_IP_HEADER` вы можете указать Sanic, что это такое. + +### X-Forwarded-For + +Этот заголовок обычно содержит цепочку IP адресов через каждый слой прокси. Установка `PROXIES_COUNT` говорит Sanic как глубоко следует искать для получения фактического IP-адреса клиента. Это значение должно равняться _ожидаемому_ количеству IP-адресов в цепочке. + +### Другие X-заголовки + +Если IP-адрес клиента найден в одном из этих методов, Sanic использует следующие заголовки для частей URL: + +- x-forwarded-proto +- x-forwarded-host +- x-forwarded-port +- x-forwarded-path +- x-scheme + +## Примеры + +В следующих примерах все запросы предполагают, что эндпоинт выглядит следующим образом: +```python +@app.route("/fwd") +async def forwarded(request): + return json( + { + "remote_addr": request.remote_addr, + "scheme": request.scheme, + "server_name": request.server_name, + "server_port": request.server_port, + "forwarded": request.forwarded, + } + ) +``` +---:1 +--- + +##### Пример 1 +X-заголовки должны учитываться без настроенного FORWARDED_SECRET +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: for=1.1.1.1, for=injected;host=", for="[::2]";proto=https;host=me.tld;path="/app/";secret=mySecret,for=broken;;secret=b0rked, for=127.0.0.3;scheme=http;port=1234' \ + -H "X-Real-IP: 127.0.0.2" \ + -H "X-Forwarded-For: 127.0.1.1" \ + -H "X-Scheme: ws" \ + -H "Host: local.site" | jq +``` +:--:1 +```bash +# ответ curl +{ + "remote_addr": "127.0.0.2", + "scheme": "ws", + "server_name": "local.site", + "server_port": 80, + "forwarded": { + "for": "127.0.0.2", + "proto": "ws" + } +} +``` +:--- +--- +---:1 + +##### Пример 2 +FORWARDED_SECRET теперь настроен +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: for=1.1.1.1, for=injected;host=", for="[::2]";proto=https;host=me.tld;path="/app/";secret=mySecret,for=broken;;secret=b0rked, for=127.0.0.3;scheme=http;port=1234' \ + -H "X-Real-IP: 127.0.0.2" \ + -H "X-Forwarded-For: 127.0.1.1" \ + -H "X-Scheme: ws" \ + -H "Host: local.site" | jq +``` +:--:1 +```bash +# ответ curl +{ + "remote_addr": "[::2]", + "scheme": "https", + "server_name": "me.tld", + "server_port": 443, + "forwarded": { + "for": "[::2]", + "proto": "https", + "host": "me.tld", + "path": "/app/", + "secret": "mySecret" + } +} +``` +:--- +--- +---:1 + +##### Пример 3 +Пустой Forwarded header -> использование X-headers +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H "X-Real-IP: 127.0.0.2" \ + -H "X-Forwarded-For: 127.0.1.1" \ + -H "X-Scheme: ws" \ + -H "Host: local.site" | jq +``` +:--:1 +```bash +# ответ curl +{ + "remote_addr": "127.0.0.2", + "scheme": "ws", + "server_name": "local.site", + "server_port": 80, + "forwarded": { + "for": "127.0.0.2", + "proto": "ws" + } +} +``` +:--- +--- +---:1 + +##### Пример 4 +Заголовок присутствует, но ни с чем не совпадает +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H "Forwarded: nomatch" | jq +``` +:--:1 +```bash +# ответ curl +{ + "remote_addr": "", + "scheme": "http", + "server_name": "localhost", + "server_port": 8000, + "forwarded": {} +} + +``` +:--- +--- +---:1 + +##### Пример 5 +Заголовок Forwarded присутствует, но нет соответствующего секретного ключа -> использование X-заголовков +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H "Forwarded: for=1.1.1.1;secret=x, for=127.0.0.1" \ + -H "X-Real-IP: 127.0.0.2" | jq +``` +:--:1 +```bash +# ответ curl +{ + "remote_addr": "127.0.0.2", + "scheme": "http", + "server_name": "localhost", + "server_port": 8000, + "forwarded": { + "for": "127.0.0.2" + } +} +``` +:--- +--- +---:1 + +##### Пример 6 +Разное форматирование и попадание в обе стороны заголовка +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: Secret="mySecret";For=127.0.0.4;Port=1234' | jq +``` +:--:1 +```bash +# ответ curl +{ + "remote_addr": "127.0.0.4", + "scheme": "http", + "server_name": "localhost", + "server_port": 1234, + "forwarded": { + "secret": "mySecret", + "for": "127.0.0.4", + "port": 1234 + } +} +``` +:--- +--- +---:1 + +##### Пример 7 +Тест экранирования (измените это, если вы увидите, что кто-то заключает пары ключ-значение в кавычки) +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: for=test;quoted="\,x=x;y=\";secret=mySecret' | jq +``` +:--:1 +```bash +# ответ curl +{ + "remote_addr": "test", + "scheme": "http", + "server_name": "localhost", + "server_port": 8000, + "forwarded": { + "for": "test", + "quoted": "\\,x=x;y=\\", + "secret": "mySecret" + } +} +``` +:--- +--- +---:1 + +##### Пример 8 +Секретный ключ изолирован из-за неправильного формирования поля #1 +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: for=test;secret=mySecret;b0rked;proto=wss;' | jq +``` +:--:1 +```bash +# ответ curl +{ + "remote_addr": "test", + "scheme": "http", + "server_name": "localhost", + "server_port": 8000, + "forwarded": { + "for": "test", + "secret": "mySecret" + } +} +``` +:--- +--- +---:1 + +##### Пример 9 +Секретный ключ изолирован из-за неправильного формирования поля #2 +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: for=test;b0rked;secret=mySecret;proto=wss' | jq +``` +:--:1 +```bash +# ответ curl +{ + "remote_addr": "", + "scheme": "wss", + "server_name": "localhost", + "server_port": 8000, + "forwarded": { + "secret": "mySecret", + "proto": "wss" + } +} +``` +:--- +--- +---:1 + +##### Пример 10 +Непредвиденное завершение не должно выражаться в потере существующих приемлемых значений +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: b0rked;secret=mySecret;proto=wss' | jq +``` +:--:1 +```bash +# ответ curl +{ + "remote_addr": "", + "scheme": "wss", + "server_name": "localhost", + "server_port": 8000, + "forwarded": { + "secret": "mySecret", + "proto": "wss" + } +} +``` +:--- +--- +---:1 + +##### Пример 11 +Нормализация полей +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "mySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: PROTO=WSS;BY="CAFE::8000";FOR=unknown;PORT=X;HOST="A:2";PATH="/With%20Spaces%22Quoted%22/sanicApp?key=val";SECRET=mySecret' | jq +``` +:--:1 +```bash +# ответ curl +{ + "remote_addr": "", + "scheme": "wss", + "server_name": "a", + "server_port": 2, + "forwarded": { + "proto": "wss", + "by": "[cafe::8000]", + "host": "a:2", + "path": "/With Spaces\"Quoted\"/sanicApp?key=val", + "secret": "mySecret" + } +} +``` +:--- +--- +---:1 + +##### Пример 12 +Использование поля "by" как секрета +```python +app.config.PROXIES_COUNT = 1 +app.config.REAL_IP_HEADER = "x-real-ip" +app.config.FORWARDED_SECRET = "_proxySecret" +``` +```bash +$ curl localhost:8000/fwd \ + -H 'Forwarded: for=1.2.3.4; by=_proxySecret' | jq +``` +:--:1 +```bash +# ответ curl +{ + "remote_addr": "1.2.3.4", + "scheme": "http", + "server_name": "localhost", + "server_port": 8000, + "forwarded": { + "for": "1.2.3.4", + "by": "_proxySecret" + } +} + +``` +:--- diff --git a/src/ru/guide/advanced/signals.md b/src/ru/guide/advanced/signals.md new file mode 100644 index 0000000000..acf934ee91 --- /dev/null +++ b/src/ru/guide/advanced/signals.md @@ -0,0 +1,264 @@ +# Signals + +Signals provide a way for one part of your application to tell another part that something happened. + +```python +@app.signal("user.registration.created") +async def send_registration_email(**context): + await send_email(context["email"], template="registration") + +@app.post("/register") +async def handle_registration(request): + await do_registration(request) + await request.app.dispatch( + "user.registration.created", + context={"email": request.json.email} + }) +``` + +## Adding a signal + +---:1 The API for adding a signal is very similar to adding a route. :--:1 +```python +async def my_signal_handler(): + print("something happened") + +app.add_signal(my_signal_handler, "something.happened.ohmy") +``` +:--- + +---:1 But, perhaps a slightly more convenient method is to use the built-in decorators. :--:1 +```python +@app.signal("something.happened.ohmy") +async def my_signal_handler(): + print("something happened") +``` +:--- + +---:1 If the signal requires conditions, make sure to add them while adding the handler. :--:1 +```python +async def my_signal_handler1(): + print("something happened") + +app.add_signal( + my_signal_handler, + "something.happened.ohmy1", + conditions={"some_condition": "value"} +) + +@app.signal("something.happened.ohmy2", conditions={"some_condition": "value"}) +async def my_signal_handler2(): + print("something happened") +``` +:--- + +---:1 Signals can also be declared on blueprints :--:1 +```python +bp = Blueprint("foo") + +@bp.signal("something.happened.ohmy") +async def my_signal_handler(): + print("something happened") +``` +:--- + +## Built-in signals + +In addition to creating a new signal, there are a number of built-in signals that are dispatched from Sanic itself. These signals exist to provide developers with more opportunities to add functionality into the request and server lifecycles. + +*Added in v21.9* + +---:1 You can attach them just like any other signal to an application or blueprint instance. :--:1 +```python +@app.signal("http.lifecycle.complete") +async def my_signal_handler(conn_info): + print("Connection has been closed") +``` +:--- + +These signals are the signals that are available, along with the arguments that the handlers take, and the conditions that attach (if any). + + +| Event name | Arguments | Conditions | +| -------------------------- | ------------------------------- | --------------------------------------------------------- | +| `http.routing.before` | request | | +| `http.routing.after` | request, route, kwargs, handler | | +| `http.handler.before` | request | | +| `http.handler.after` | request | | +| `http.lifecycle.begin` | conn_info | | +| `http.lifecycle.read_head` | head | | +| `http.lifecycle.request` | request | | +| `http.lifecycle.handle` | request | | +| `http.lifecycle.read_body` | body | | +| `http.lifecycle.exception` | request, exception | | +| `http.lifecycle.response` | request, response | | +| `http.lifecycle.send` | data | | +| `http.lifecycle.complete` | conn_info | | +| `http.middleware.before` | request, response | `{"attach_to": "request"}` or `{"attach_to": "response"}` | +| `http.middleware.after` | request, response | `{"attach_to": "request"}` or `{"attach_to": "response"}` | +| `server.init.before` | app, loop | | +| `server.init.after` | app, loop | | +| `server.shutdown.before` | app, loop | | +| `server.shutdown.after` | app, loop | | + +Version 22.9 added `http.handler.before` and `http.handler.after`. + +---:1 To make using the built-in signals easier, there is an `Enum` object that contains all of the allowed built-ins. With a modern IDE this will help so that you do not need to remember the full list of event names as strings. + +*Added in v21.12* :--:1 +```python +from sanic.signals import Event + +@app.signal(Event.HTTP_LIFECYCLE_COMPLETE) +async def my_signal_handler(conn_info): + print("Connection has been closed") +``` +:--- + +## Events + +---:1 Signals are based off of an _event_. An event, is simply a string in the following pattern: :--:1 +``` +namespace.reference.action +``` +:--- + +::: tip Events must have three parts. If you do not know what to use, try these patterns: + +- `my_app.something.happened` +- `sanic.notice.hello` ::: + +### Event parameters + +---:1 An event can be "dynamic" and declared using the same syntax as [path parameters](../basics/routing.md#path-parameters). This allows matching based upon arbitrary values. :--:1 +```python +@app.signal("foo.bar.") +async def signal_handler(thing): + print(f"[signal_handler] {thing=}") + +@app.get("/") +async def trigger(request): + await app.dispatch("foo.bar.baz") + return response.text("Done.") +``` +:--- + +Checkout [path parameters](../basics/routing.md#path-parameters) for more information on allowed type definitions. + +::: warning Only the third part of an event (the "action") may be dynamic: + +- `foo.bar.` :ok: +- `foo..baz` :x: ::: + +### Waiting + +---:1 In addition to executing a signal handler, your application can wait for an event to be triggered. :--:1 +```python +await app.event("foo.bar.baz") +``` +:--- + +---:1 **IMPORTANT**: waiting is a blocking function. Therefore, you likely will want this to run in a [background task](../basics/tasks.md). :--:1 +```python +async def wait_for_event(app): + while True: + print("> waiting") + await app.event("foo.bar.baz") + print("> event found\n") + +@app.after_server_start +async def after_server_start(app, loop): + app.add_task(wait_for_event(app)) +``` +:--- + +---:1 If your event was defined with a dynamic path, you can use `*` to catch any action. :--:1 +```python +@app.signal("foo.bar.") + +... + +await app.event("foo.bar.*") +``` +:--- + +## Dispatching + +*In the future, Sanic will dispatch some events automatically to assist developers to hook into life cycle events.* + +---:1 Dispatching an event will do two things: + +1. execute any signal handlers defined on the event, and +2. resolve anything that is "waiting" for the event to complete. :--:1 +```python +@app.signal("foo.bar.") +async def foo_bar(thing): + print(f"{thing=}") + +await app.dispatch("foo.bar.baz") +``` +``` +thing=baz +``` +:--- + +### Context + +---:1 Sometimes you may find the need to pass extra information into the signal handler. In our first example above, we wanted our email registration process to have the email address for the user. :--:1 +```python +@app.signal("user.registration.created") +async def send_registration_email(**context): + print(context) + +await app.dispatch( + "user.registration.created", + context={"hello": "world"} +) +``` +``` +{'hello': 'world'} +``` +:--- + +::: tip FYI +Signals are dispatched in a background task. +::: + +### Blueprints + +Dispatching blueprint signals works similar in concept to [middleware](../basics/middleware.md). Anything that is done from the app level, will trickle down to the blueprints. However, dispatching on a blueprint, will only execute the signals that are defined on that blueprint. + +---:1 Perhaps an example is easier to explain: :--:1 +```python +bp = Blueprint("bp") + +app_counter = 0 +bp_counter = 0 + +@app.signal("foo.bar.baz") +def app_signal(): + nonlocal app_counter + app_counter += 1 + +@bp.signal("foo.bar.baz") +def bp_signal(): + nonlocal bp_counter + bp_counter += 1 +``` +:--- + +---:1 Running `app.dispatch("foo.bar.baz")` will execute both signals. :--:1 +```python +await app.dispatch("foo.bar.baz") +assert app_counter == 1 +assert bp_counter == 1 +``` +:--- + +---:1 Running `bp.dispatch("foo.bar.baz")` will execute only the blueprint signal. :--:1 +```python +await bp.dispatch("foo.bar.baz") +assert app_counter == 1 +assert bp_counter == 2 +``` +:--- diff --git a/src/ru/guide/advanced/streaming.md b/src/ru/guide/advanced/streaming.md new file mode 100644 index 0000000000..6b175a8105 --- /dev/null +++ b/src/ru/guide/advanced/streaming.md @@ -0,0 +1,134 @@ +# Потоковая передача + +## Потоковая передача в запросе + +Sanic позволяет получать поток данных, отправленных клиентом, чтобы начать их обработку по мере прибытия пакетов. + +---:1 + +Если это определено эндпоинтом, внутри него вы можете получать тело запроса при помощи `await request.stream.read()`. + +После завершения загрузки тела этот метод вернет `None`. :--:1 +```python +from sanic.views import stream + +class SimpleView(HTTPMethodView): + @stream + async def post(self, request): + result = "" + while True: + body = await request.stream.read() + if body is None: + break + result += body.decode("utf-8") + return text(result) +``` +:--- + +---:1 + +Также это можно включить, передав соответствующий параметр в декораторе... :--:1 +```python +@app.post("/stream", stream=True) +async def handler(request): + ... + body = await request.stream.read() + ... +``` +:--- + +---:1 + +... или в методе `add_route()`. :--:1 +```python +bp.add_route( + bp_handler, + "/bp_stream", + methods=["POST"], + stream=True, +) +``` +:--- + +::: Совет FYI +Только декораторы post, put и patch имеют аргумент stream. +::: + +## Потоковая передача в ответе + +---:1 Sanic позволяет отправлять клиенту контент в потоке. :--:1 + +```python +@app.route("/") +async def test(request): + response = await request.respond(content_type="text/csv") + await response.send("foo,") + await response.send("bar") + + # Опционально вы можете явно завершить потоковую передачу путём вызова: + await response.eof() +``` +:--- + +Это полезно в ситуациях, когда вы хотите передавать клиенту контент, который исходит внешних сервисов, например из базы данных. Например, вы можете транслировать записи базы данных клиенту с помощью асинхронного курсора `asyncpg`. + +```python +@app.route("/") +async def index(request): + response = await request.respond() + conn = await asyncpg.connect(database='test') + async with conn.transaction(): + async for record in conn.cursor('SELECT generate_series(0, 10)'): + await response.send(record[0]) +``` + + + +Вы можете явно завершить потоковую передачу, вызвав `await response.eof()`. Это удобный метод замены `await response.send("", True)`. Его следует вызвать **один раз** *после* того, как ваш обработчик определил, что у него ничего не осталось для отправки клиенту. Хотя это и *необязательно* для использования с сервером Sanic, если вы используете Sanic в режиме ASGI, тогда вы **должны** явно завершить потоковую передачу. + +*Calling `eof` became optional in v21.6* + +## Потоковая передача файлов + +---:1 + +Sanic предоставляет функцию `sanic.response.file_stream`, которая полезна при отправке большого файла. Она возвращает объект `StreamingHTTPResponse` и по умолчанию использует кодировку передачи в чанках. По этой причине Sanic не добавляет к ответу HTTP-заголовок `Content-Length`. + +Типичным примером использования может быть потоковая передача видео-файла. :--:1 +```python +@app.route("/mp4") +async def handler_file_stream(request): + return await response.file_stream( + "/path/to/sample.mp4", + chunk_size=1024, + mime_type="application/metalink4+xml", + headers={ + "Content-Disposition": 'Attachment; filename="nicer_name.meta4"', + "Content-Type": "application/metalink4+xml", + }, + ) +``` +:--- + +---:1 + +Если вы хотите использовать заголовок `Content-Length`, вы можете добавить его вручную, просто добавив заголовок `Content-Length` и отключить кодировку передачи в чанках. + +:--:1 +```python +from aiofiles import os as async_os +from sanic.response import file_stream + +@app.route("/") +async def index(request): + file_path = "/srv/www/whatever.png" + + file_stat = await async_os.stat(file_path) + headers = {"Content-Length": str(file_stat.st_size)} + + return await file_stream( + file_path, + headers=headers, + ) +``` +:--- diff --git a/src/ru/guide/advanced/versioning.md b/src/ru/guide/advanced/versioning.md new file mode 100644 index 0000000000..f7b7e08cb7 --- /dev/null +++ b/src/ru/guide/advanced/versioning.md @@ -0,0 +1,146 @@ +# Versioning + +It is standard practice in API building to add versions to your endpoints. This allows you to easily differentiate incompatible endpoints when you try and change your API down the road in a breaking manner. + +Adding a version will add a `/v{version}` url prefix to your endpoints. + +The version can be a `int`, `float`, or `str`. Acceptable values: + +- `1`, `2`, `3` +- `1.1`, `2.25`, `3.0` +- `"1"`, `"v1"`, `"v1.1"` + +## Per route + +---:1 + +You can pass a version number to the routes directly. :--:1 +```python +# /v1/text +@app.route("/text", version=1) +def handle_request(request): + return response.text("Hello world! Version 1") + +# /v2/text +@app.route("/text", version=2) +def handle_request(request): + return response.text("Hello world! Version 2") +``` +:--- + +## Per Blueprint + +---:1 + +You can also pass a version number to the blueprint, which will apply to all routes in that blueprint. :--:1 +```python +bp = Blueprint("test", url_prefix="/foo", version=1) + +# /v1/foo/html +@bp.route("/html") +def handle_request(request): + return response.html("

Hello world!

") +``` +:--- + +## Per Blueprint Group + +---:1 In order to simplify the management of the versioned blueprints, you can provide a version number in the blueprint group. The same will be inherited to all the blueprint grouped under it if the blueprints don't already override the same information with a value specified while creating a blueprint instance. + +When using blueprint groups for managing the versions, the following order is followed to apply the Version prefix to the routes being registered. + +1. Route Level configuration +2. Blueprint level configuration +3. Blueprint Group level configuration + +If we find a more pointed versioning specification, we will pick that over the more generic versioning specification provided under the Blueprint or Blueprint Group :--:1 +```python +from sanic.blueprints import Blueprint +from sanic.response import json + +bp1 = Blueprint( + name="blueprint-1", + url_prefix="/bp1", + version=1.25, +) +bp2 = Blueprint( + name="blueprint-2", + url_prefix="/bp2", +) + +group = Blueprint.group( + [bp1, bp2], + url_prefix="/bp-group", + version="v2", +) + +# GET /v1.25/bp-group/bp1/endpoint-1 +@bp1.get("/endpoint-1") +async def handle_endpoint_1_bp1(request): + return json({"Source": "blueprint-1/endpoint-1"}) + +# GET /v2/bp-group/bp2/endpoint-2 +@bp2.get("/endpoint-1") +async def handle_endpoint_1_bp2(request): + return json({"Source": "blueprint-2/endpoint-1"}) + +# GET /v1/bp-group/bp2/endpoint-2 +@bp2.get("/endpoint-2", version=1) +async def handle_endpoint_2_bp2(request): + return json({"Source": "blueprint-2/endpoint-2"}) +``` +:--- + +## Version prefix + +As seen above, the `version` that is applied to a route is **always** the first segment in the generated URI path. Therefore, to make it possible to add path segments before the version, every place that a `version` argument is passed, you can also pass `version_prefix`. + +The `version_prefix` argument can be defined in: + +- `app.route` and `bp.route` decorators (and all the convenience decorators also) +- `Blueprint` instantiation +- `Blueprint.group` constructor +- `BlueprintGroup` instantiation +- `app.blueprint` registration + +If there are definitions in multiple places, a more specific definition overrides a more general. This list provides that hierarchy. + +The default value of `version_prefix` is `/v`. + +---:1 An often requested feature is to be able to mount versioned routes on `/api`. This can easily be accomplished with `version_prefix`. :--:1 +```python +# /v1/my/path +app.route("/my/path", version=1, version_prefix="/api/v") +``` +:--- + +---:1 Perhaps a more compelling usage is to load all `/api` routes into a single `BlueprintGroup`. :--:1 +```python +# /v1/my/path +app = Sanic(__name__) +v2ip = Blueprint("v2ip", url_prefix="/ip", version=2) +api = Blueprint.group(v2ip, version_prefix="/api/version") + +# /api/version2/ip +@v2ip.get("/") +async def handler(request): + return text(request.ip) + +app.blueprint(api) +``` +:--- + +We can therefore learn that a route's URI is: + +``` +version_prefix + version + url_prefix + URI definition +``` + +::: tip Just like with `url_prefix`, it is possible to define path parameters inside a `version_prefix`. It is perfectly legitimate to do this. Just remember that every route will have that parameter injected into the handler. + +```python +version_prefix="//v" +``` +::: + +*Added in v21.6* diff --git a/src/ru/guide/advanced/websockets.md b/src/ru/guide/advanced/websockets.md new file mode 100644 index 0000000000..65ec9334b3 --- /dev/null +++ b/src/ru/guide/advanced/websockets.md @@ -0,0 +1,72 @@ +# Веб-сокеты + +Для [веб-сокетов](https://websockets.readthedocs.io/en/stable/) Sanic предоставляет легкую в использовании абстракцию. + + +## Маршрутизация + +---:1 + +Обработчики веб-сокетов могут быть подключены к маршрутизатору аналогичным для обычных хендлеров способом. :--:1 +```python +from sanic import Request, Websocket + +async def feed(request: Request, ws: Websocket): + pass + +app.add_websocket_route(feed, "/feed") +``` +```python +from sanic import Request, Websocket + +@app.websocket("/feed") +async def feed(request: Request, ws: Websocket): + pass +``` +:--- + +## Хендлер (обработчик) + + +---:1 Typically, a websocket handler will want to hold open a loop. + +Таким образом он может использовать методы `send()` и `recv()` для второго объекта, переданного в него. + +Этот пример представляет собой простой эндпоинт, который отправляет клиенту получаемые сообщения. :--:1 +```python +from sanic import Request, Websocket + +@app.websocket("/feed") +async def feed(request: Request, ws: Websocket): + while True: + data = "hello!" + print("Sending: " + data) + await ws.send(data) + data = await ws.recv() + print("Received: " + data) +``` +:--- + +---:1 You can simplify your loop by just iterating over the `Websocket` object in a for loop. + +*Added in v22.9* :--:1 +```python +from sanic import Request, Websocket + +@app.websocket("/feed") +async def feed(request: Request, ws: Websocket): + async for msg in ws: + await ws.send(msg) +``` +:--- + + +## Конфигурация + +See [configuration section](/guide/deployment/configuration.md) for more details, however the defaults are shown below. + +```python +app.config.WEBSOCKET_MAX_SIZE = 2 ** 20 +app.config.WEBSOCKET_PING_INTERVAL = 20 +app.config.WEBSOCKET_PING_TIMEOUT = 20 +``` diff --git a/src/ru/guide/basics/README.md b/src/ru/guide/basics/README.md new file mode 100644 index 0000000000..332ef37ae1 --- /dev/null +++ b/src/ru/guide/basics/README.md @@ -0,0 +1 @@ +# Основы diff --git a/src/ru/guide/basics/app.md b/src/ru/guide/basics/app.md new file mode 100644 index 0000000000..91bce16c74 --- /dev/null +++ b/src/ru/guide/basics/app.md @@ -0,0 +1,249 @@ +# Приложение Sanic + +## Экземпляр объекта + +---:1 Экземпляр объекта приложения `Sanic()` является самым основным кирпичиком. Это не обязательно, но обычно создание экземпляра приложения происходит в файле, называемом `server.py`. :--:1 +```python +# /path/to/server.py + +from sanic import Sanic + +app = Sanic("MyHelloWorldApp") +``` +:--- + +## Контекст приложения + +Большинство приложений будут иметь необходимость совместного или повторного использования данных или объектов в различных местах кода. Наиболее распространенным примером является соединение с базой данных. + +---:1 В версиях Sanic до v21.3 это делалось путем добавления атрибута к экземпляру приложения :--:1 +```python +# Выкидывает предупреждение об исключении подобного использования в версии 21.3 +app = Sanic("MyApp") +app.db = Database() +``` +:--- + +---:1 В связи с возможностью возникновения потенциальных проблем с конфликтами имён, v21.3 вводит объект контекста на уровне самого приложения для сохранения консистентности объектов [контекста запроса](./request.md#context). :--:1 +```python +# Правильный способ прикрепления объектов к приложению +app = Sanic("MyApp") +app.ctx.db = Database() +``` +:--- + +## Реестр приложений + +---:1 + +Когда вы создаете экземпляр Sanic, его можно будет позже позже из реестра приложения Sanic. Это может быть полезно, например, если вам нужен доступ к вашему экземпляру Sanic из места, где он недоступен. :--:1 +```python + +# ./path/to/server.py +from sanic import Sanic + +app = Sanic("my_awesome_server") + +# ./path/to/somewhere_else.py +from sanic import Sanic + +app = Sanic.get_app("my_awesome_server") +``` +:--- + +---:1 + +Если вы вызовете `Sanic.get_app("non-existing")` в приложении, которое не существует, то по умолчанию это вызовет `SanicException`. Однако, вместо этого вы можете принудительно вернуть новый экземпляр Sanic с таким именем. :--:1 +```python +app = Sanic.get_app( + "non-existing", + force_create=True, +) +``` +:--- + +---:1 Если **зарегистрирован только один** экземпляр Sanic, то вызов без аргументов `Sanic.get_app()` возвращает этот экземпляр :--:1 +```python +Sanic("My only app") + +app = Sanic.get_app() +``` +:--- + +## Настройки + +---:1 Настройки экземпляра приложения `Sanic` располагаются в атрибуте `config`. Конфигурация может быть изменена посредством **ЛИБО** точечной **ЛИБО** словарной нотации. :--:1 +```python +app = Sanic('myapp') + +app.config.DB_NAME = 'appdb' +app.config['DB_USER'] = 'appuser' + +db_settings = { + 'DB_HOST': 'localhost', + 'DB_NAME': 'appdb', + 'DB_USER': 'appuser' +} +app.config.update(db_settings) +``` +:--- + +::: Совет Конфигурационные ключи _должны_ быть указаны заглавными буквами. Но это общее следование конвенции. В нижнем регистре в целом тоже будет работать. +``` +app.config.GOOD = "yay!" +app.config.bad = "boo" +``` +::: + +Далее есть [более подробное описание настроек](/guide/deployment/configuration.md). + + +## Персональные настройки + +Экземпляр приложения Sanic может быть настроен различными способами в момент его создания с учетом потребностей вашего приложения. + +### Пользовательская конфигурация +---:1 + +Простейшей формой пользовательской конфигурации будет являться передача своего собственного объекта непосредственно в экземпляр приложения Sanic + +Если вы создаете пользовательский объект конфигурации, *настоятельно* рекомендуется наследовать его от объекта Sanic `Config`, чтобы перенять его поведение. Вы можете использовать эту опцию для добавления свойств или вашего собственного набора пользовательской логики. + +*Added in v21.6* :--:1 +```python +from sanic.config import Config + +class MyConfig(Config): + FOO = "bar" + +app = Sanic(..., config=MyConfig()) +``` +:--- + +---:1 Например, вы хотите использовать конфигурационный файл в форме, отличающейся от того, что изначально [ поддерживается](../deployment/configuration.md#using-sanic-update-config). :--:1 +```python +from sanic import Sanic, text +from sanic.config import Config + +class TomlConfig(Config): + def __init__(self, *args, path: str, **kwargs): + super().__init__(*args, **kwargs) + + with open(path, "r") as f: + self.apply(toml.load(f)) + + def apply(self, config): + self.update(self._to_uppercase(config)) + + def _to_uppercase(self, obj: Dict[str, Any]) -> Dict[str, Any]: + retval: Dict[str, Any] = {} + for key, value in obj.items(): + upper_key = key.upper() + if isinstance(value, list): + retval[upper_key] = [ + self._to_uppercase(item) for item in value + ] + elif isinstance(value, dict): + retval[upper_key] = self._to_uppercase(value) + else: + retval[upper_key] = value + return retval + +toml_config = TomlConfig(path="/path/to/config.toml") +app = Sanic(toml_config.APP_NAME, config=toml_config) +``` +:--- +### Переопределение контекста +---:1 By default, the application context is a [`SimpleNamespace()`](https://docs.python.org/3/library/types.html#types.SimpleNamespace) that allows you to set any properties you want on it. Тем не менее, у вас также есть возможность передать любой объект по вашему выбору. + +*Added in v21.6* :--:1 +```python +app = Sanic(..., ctx=1) +``` + +```python +app = Sanic(..., ctx={}) +``` + +```python +class MyContext: + ... + +app = Sanic(..., ctx=MyContext()) +``` +:--- +### Переопределение объекта запроса +---:1 Иногда может быть полезно иметь собственный класс `Request` и передать его Sanic для использования вместо стандартного. Например, вы хотите изменить поведение стандартного генератора `request.id`. + +::: Совет Важно + +Важно помнить, что вы передаёте объект *class*, а не экземпляр класса. + +::: :--:1 +```python +import time + +from sanic import Request, Sanic, text + + +class NanoSecondRequest(Request): + @classmethod + def generate_id(*_): + return time.time_ns() + + +app = Sanic(..., request_class=NanoSecondRequest) + + +@app.get("/") +async def handler(request): + return text(str(request.id)) +``` +:--- + +### Переопределение обработчика ошибок + +---:1 Более подробная информация в разделе [Обработка исключений](../best-practices/exceptions.md#custom-error-handling) :--:1 +```python +from sanic.handlers import ErrorHandler + +class CustomErrorHandler(ErrorHandler): + def default(self, request, exception): + ''' Обрабатывает ошибки, для которых нет иных назначенных обработчиков ''' + # Здесь ваша собственная логика обработки... + return super().default(request, exception) + +app = Sanic(..., error_handler=CustomErrorHandler()) +``` +:--- + +### Custom dumps function + +---:1 It may sometimes be necessary or desirable to provide a custom function that serializes an object to JSON data. :--:1 +```python +import ujson + +dumps = partial(ujson.dumps, escape_forward_slashes=False) +app = Sanic(__name__, dumps=dumps) +``` +:--- + +---:1 Or, perhaps use another library or create your own. :--:1 +```python +from orjson import dumps + +app = Sanic(__name__, dumps=dumps) +``` +:--- + +### Custom loads function + +---:1 Similar to `dumps`, you can also provide a custom function for deserializing data. + +*Added in v22.9* :--:1 +```python +from orjson import loads + +app = Sanic(__name__, loads=loads) +``` +:--- diff --git a/src/ru/guide/basics/cookies.md b/src/ru/guide/basics/cookies.md new file mode 100644 index 0000000000..7d2e88f1bd --- /dev/null +++ b/src/ru/guide/basics/cookies.md @@ -0,0 +1,73 @@ +# Cookies (Куки) + +## Чтение + +---:1 + +Cookies можно получить через словарь `cookies` в объекте `Request`. :--:1 +```python +@app.route("/cookie") +async def test(request): + test_cookie = request.cookies.get("test") + return text("Test cookie: {}".format(test_cookie)) +``` +:--- + + +## Запись + +---:1 + +При возврате ответа куки могут быть установлены в объекте `Response`: `response.cookies`. Этот объект является экземпляром `CookieJar`, представляющего собой особый вид словаря, который автоматически пишет для вас заголовки ответа. :--:1 +```python +@app.route("/cookie") +async def test(request): + response = text("There's a cookie up in this response") + response.cookies["test"] = "It worked!" + response.cookies["test"]["domain"] = ".yummy-yummy-cookie.com" + response.cookies["test"]["httponly"] = True + return response +``` +:--- + +В ответе cookie могут быть установлены в качестве значений словаря и иметь следующие параметры: + +- `expires: datetime` - Время истечения срока действия cookie в браузере клиента. +- `path: str` - Подмножество URL-адресов, к которым применяется этот файл cookie. По умолчанию `/`. +- `comment: str` - Комментарий (метаданные). +- `domain: str` - определяет домен, для которого cookie является допустимым. Явно указываемый домен должен всегда начинаться с точки. +- `max-age: int` - Количество секунд, в течение которых cookie должны жить. +- `secure: bool` - Указывает, будет ли cookie отправляться только через HTTPS. +- `httponly: bool` - Указывает, что cookie не могут быть прочитаны JavaScript. +- `samesite: str` - По умолчанию зависит от браузера, состояния, указанные в спецификации (Lax, Strict и None), являются допустимыми значениями. + +## Удаление + +---:1 + +Cookies могут быть удалены семантически или явно. :--:1 +```python +@app.route("/cookie") +async def test(request): + response = text("Time to eat some cookies muahaha") + + # This cookie will be set to expire in 0 seconds + del response.cookies["kill_me"] + + # This cookie will self destruct in 5 seconds + response.cookies["short_life"] = "Glad to be here" + response.cookies["short_life"]["max-age"] = 5 + del response.cookies["favorite_color"] + + # This cookie will remain unchanged + response.cookies["favorite_color"] = "blue" + response.cookies["favorite_color"] = "pink" + del response.cookies["favorite_color"] + + return response +``` +:--- + +## Питание + +Я люблю печеньки :cookie: *(Прим: дословный перевод слова "cookie" - печенье)* diff --git a/src/ru/guide/basics/handlers.md b/src/ru/guide/basics/handlers.md new file mode 100644 index 0000000000..3c1f16327f --- /dev/null +++ b/src/ru/guide/basics/handlers.md @@ -0,0 +1,113 @@ +# Хендлеры (обработчики) + +Следующий важный кирпичик — ваши _хендлеры (обработчики)_. Они также иногда называются "объекты представления". + +Обработчик в Sanic - это любой вызываемый объект, который принимает в качестве аргумента как минимум экземпляр объекта `Request`, и возвращает экземпляр `HTTPResponse` или корутину, которая делает то же самое. + + + +---:1 + +В смысле? :confused: + +Это **функция**; либо синхронная, либо асинхронная. + +Задача хендлера заключается в том, чтобы ответить эндпоинту и что-то сделать. Здесь будет находиться большая часть вашей бизнес-логики. :--:1 +```python +def i_am_a_handler(request): + return HTTPResponse() + +async def i_am_ALSO_a_handler(request): + return HTTPResponse() +``` +:--- + +::: Совет Внимание! Если вы хотите узнать больше о инкапсуляции вашей логики, изучите [Объекты представления на основе классов](/guide/advanced/class-based-views.md). ::: ---:1 Таким образом, всё, что вам нужно сделать, это присоединить его к эндпоинту. Подробнее мы скоро разберёмся с этим в разделе [Маршрутизация](./routing.md). + +Рассмотрим практический пример. + +- Мы используем в нашем приложении удобный декоратор: `@app.get()` +- И не менее удобный метод для генерации объекта ответа: `text()` + +Миссия выполнена :muscle: :--:1 +```python +from sanic.response import text + +@app.get("/foo") +async def foo_handler(request): + return text("I said foo!") +``` +:--- + +--- + +## Несколько слов об _асинхронной парадигме_... + +---:1 + +Разумеется, писать синхронные обработчики абсолютно допустимо. + +В этом примере мы используем _блокирующий_ `time.sleep()` для моделирования 100 мс времени обработки. Например, это представляет собой получение данных из БД или со стороннего веб-сайта. + +Используя четыре (4) рабочих процесса и общей механизм бенчмаркинга, получаем: + +- **956** запросов за 30.10 сек +- Или около **31.76** запросов в секунду :--:1 +```python +@app.get("/sync") +def sync_handler(request): + time.sleep(0.1) + return text("Done.") +``` +:--- + +---:1 + +Просто переписав код в асинхронный `asyncio.sleep()`, мы видим невероятное изменение производительности. :rocket: + +Используя те же четыре (4) рабочих процесса, получаем: + +- **115,590** запроса за 30.08 сек +- или около **3,843.17** запросов в секунду + +:flushed: + +Ладно... Это до смешного чересчур драматичный результат. И любой бенчмарк, который вы видите, по сути очень предвзятый. Этот пример предназначен для демонстрации общего преимущества `async/await` в веб-мире. Полученные результаты, безусловно, будут различными. Инструменты, такие как Sanic и другие асинхронные библиотеки Python, не являются волшебными пулями, делающими вещи быстрее. Они делают их _более эффективными_. + +В нашем примере асинхронная версия гораздо лучше, потому что пока один запрос спит, она способна начать выполнять другой, и другой, и другой, и другой... + +Но в этом-то всё дело! Sanic быстрый, потому что он берет все доступные ресурсы и выжимает из них производительность. Он может одновременно обрабатывать много запросов, что означает больше запросов в секунду. + +:--:1 +```python +@app.get("/async") +async def async_handler(request): + await asyncio.sleep(0.1) + return text("Done.") +``` +:--- + +::: Предупреждение Распространенная ошибка! + +Не делайте этого! Вам нужно пинговать веб-страницу. Что вы используете? `pip install ваша-любимая-request-библиотека` :see_no_evil: + +Вместо этого попробуйте использовать клиент, умеющий в `async/await`. Ваш сервер будет вам благодарен. Избегайте использования блокирующих инструментов, предпочитая те, которые хорошо работают в асинхронной экосистеме. Если вам нужны рекомендации, ознакомьтесь с [Awesome Sanic](https://github.com/mekicha/awesome-sanic). + +Sanic использует [httpx](https://www.python-httpx.org/) внутри пакета тестирования (sanic-testing) :wink:. + +::: + +--- + +## Полностью аннотированный обработчик + +Для тех, кто использует аннотации типов... + +```python +from sanic.response import HTTPResponse, text +from sanic.request import Request + +@app.get("/typed") +async def typed_handler(request: Request) -> HTTPResponse: + return text("Done.") +``` diff --git a/src/ru/guide/basics/headers.md b/src/ru/guide/basics/headers.md new file mode 100644 index 0000000000..55f552de90 --- /dev/null +++ b/src/ru/guide/basics/headers.md @@ -0,0 +1,230 @@ +# Заголовки + +Заголовки запросов и ответов доступны в объектах `Request` и `HTTPResponse`, соответственно. Они используют [пакет `multidict`](https://multidict.readthedocs.io/en/stable/multidict.html#cimultidict), который позволяет одному ключу иметь несколько значений. + +::: Совет К сведенью + +При обработке запроса ключи заголовков преобразуются в *в нижний регистр*. Написание имен заголовков с заглавной буквы не предполагается. + +::: + +## Запрос + +Sanic пытается сделать некоторую нормализацию заголовков запроса, прежде чем представить их разработчику, а также сделать некоторые потенциально значимые выборки наиболее часто используемых вариантов. + +---:1 + +#### Токены + +Токены авторизации из форм `Token ` или `Bearer ` извлекаются в объект запроса: `request.token`. + +:--:1 + +```python +@app.route("/") +async def handler(request): + return text(request.token) +``` + +```bash +$ curl localhost:8000 \ + -H "Authorization: Token ABCDEF12345679" +ABCDEF12345679 +``` + +```bash +$ curl localhost:8000 \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c +``` + +:--- + +---:1 + +#### Заголовки прокси + +У Sanic есть специальная обработка заголовков прокси. Смотрите раздел [Заголовки прокси](/guide/advanced/proxy-headers.md) для получения более подробной информации. + +#### Заголовок "host" и динамическое формирование URL + +*Принимающий хост* доступен через `request.host`. Он не обязательно совпадает со значением в заголовке host, так как он нацелен на прокси-перенаправленный хост и может быть принудительно изменен настройкой имени сервера. + +Веб-приложения должны использовать в основном это свойство, чтобы они могли работать одинаково независимо от способа развёртывания на сервере. При необходимости реальный заголовок host можно найти через `request.headers` + +Принимающий хост также используется в динамическом построении URL по запросу `request.url_for`, который использует запрос для определения внешнего адреса хендлера. + +::: Совет Будьте осторожны с вредоносными клиентами. Их URL могут быть подменены путём отправки подложных заголовков хостов. Если это имеет значение `app.url_for` следует использовать. ::: + +:--:1 + +```python +app.config.SERVER_NAME = "https://example.com" + +@app.route("/hosts", name="foo") +async def handler(request): + return json( + { + "effective host": request.host, + "host header": request.headers.get("host"), + "forwarded host": request.forwarded.get("host"), + "you are here": request.url_for("foo"), + } + ) +``` + +```bash +$ curl localhost:8000/hosts +{ + "effective host": "example.com", + "host header": "localhost:8000", + "forwarded host": null, + "you are here": "https://example.com/hosts" +} +``` + +:--- + +---:1 +#### Прочие заголовки + +Все заголовки запроса доступны в свойстве `request.headers`, и их можно получить в форме словарей. Написание имен заголовков с заглавной буквы не предполагается, в связи с этим используйте нижний или верхний регистр для доступка к ключам заголовков. + +:--:1 + +```python +@app.route("/") +async def handler(request): + return json( + { + "foo_weakref": request.headers["foo"], + "foo_get": request.headers.get("Foo"), + "foo_getone": request.headers.getone("FOO"), + "foo_getall": request.headers.getall("fOo"), + "all": list(request.headers.items()), + } + ) +``` + +```bash +$ curl localhost:9999/headers -H "Foo: one" -H "FOO: two"|jq +{ + "foo_weakref": "one", + "foo_get": "one", + "foo_getone": "one", + "foo_getall": [ + "one", + "two" + ], + "all": [ + [ + "host", + "localhost:9999" + ], + [ + "user-agent", + "curl/7.76.1" + ], + [ + "accept", + "*/*" + ], + [ + "foo", + "one" + ], + [ + "foo", + "two" + ] + ] +} +``` + +:--- + +::: Совет К сведенью Объект request.headers - один из нескольких типов, представляющих собой словарь, каждое значение которого является списком. Это связано с тем, что HTTP позволяет повторно использовать один ключ для отправки нескольких значений. + +Чаще всего вы хотите использовать метод .get() для доступа к первому элементу, а не списку. Если вы хотите получить список всех элементов, можно использовать .getlist(). ::: + +#### ID запроса + +---:1 + +Часто бывает удобно или необходимо отслеживать запрос через его заголовок `X-Request-ID`. Вы можете легко получить доступ к нему посредством: `request.id`. + +:--:1 + +```python +@app.route("/") +async def handler(request): + return text(request.id) +``` + +```bash +$ curl localhost:8000 \ + -H "X-Request-ID: ABCDEF12345679" +ABCDEF12345679 +``` + +:--- + +## Response + +Sanic автоматически установит для вас следующие заголовки ответов (при необходимости): + +- `content-length` +- `content-type` +- `connection` +- `transfer-encoding` + +В большинстве случаев вам не нужно беспокоиться об установке этих заголовков. + +---:1 + +Любой другой заголовок, который вы хотите установить, можно сделать либо в соответствующем хендлере, либо в middleware. + +:--:1 + +```python +@app.route("/") +async def handler(request): + return text("Done.", headers={"content-language": "en-US"}) + +@app.middleware("response") +async def add_csp(request, response): + response.headers["content-security-policy"] = "default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self';base-uri 'self';form-action 'self'" +``` + +:--- + +---:1 + +Скорее всего вы можете захотеть использовать [middleware](middleware.md), в котором к каждому ответу добавляется заголовок `X-Request-ID`. Как указано выше, `request.id` предоставит идентификатор входящего запроса. Но даже если идентификатор не был предоставлен в заголовках запроса, он будет предоставлен для вас автоматически. + +[Смотрите документацию API для более подробной информации](https://sanic.readthedocs.io/en/latest/sanic/api_reference.html#sanic.request.Request.id) + +:--:1 + +```python +@app.route("/") +async def handler(request): + return text(str(request.id)) + +@app.on_response +async def add_request_id_header(request, response): + response.headers["X-Request-ID"] = request.id +``` + +```bash +$ curl localhost:8000 -i +HTTP/1.1 200 OK +X-Request-ID: 805a958e-9906-4e7a-8fe0-cbe83590431b +content-length: 36 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +805a958e-9906-4e7a-8fe0-cbe83590431b +``` + +:--- diff --git a/src/ru/guide/basics/listeners.md b/src/ru/guide/basics/listeners.md new file mode 100644 index 0000000000..6fc5ea7ac7 --- /dev/null +++ b/src/ru/guide/basics/listeners.md @@ -0,0 +1,227 @@ +# Обработчики событий + +Sanic предоставляет восемь (8) вариантов включения операции в жизненный цикл вашего сервера приложений. Это не включает [сигналы](../advanced/signals.md), которые позволяют продолжить настройку инъекций. + +Два (2) из них выполняются **только** в вашем главном процессе Sanic (то есть, один раз за обращение к `sanic server.app`.) + +- `main_process_start` +- `main_process_stop` + +Ещё два (2) выполняются **только** в процессе перезагрузки, если включена автоматическая перезагрузка. + +- `reload_process_start` +- `reload_process_stop` + +*`reload_process_start` и `reload_process_stop` добавлены в v22.3* + +И наконец оставшиеся четыре (4) варианта, которые позволяют вам выполнить подготовительный/очищающий код при запуске или остановке сервера. + +- `before_server_start` +- `after_server_start` +- `before_server_stop` +- `after_server_stop` + +Жизненный цикл рабочего процесса выглядит так: + +```mermaid +sequenceDiagram +autonumber +participant Process +participant Worker +participant Listener +participant Handler +Note over Process: sanic server.app +loop + Process->>Listener: @app.main_process_start + Listener->>Handler: Invoke event handler +end +Process->>Worker: Run workers +loop Start each worker + loop + Worker->>Listener: @app.before_server_start + Listener->>Handler: Invoke event handler + end + Note over Worker: Server status: started + loop + Worker->>Listener: @app.after_server_start + Listener->>Handler: Invoke event handler + end + Note over Worker: Server status: ready +end +Process->>Worker: Graceful shutdown +loop Stop each worker + loop + Worker->>Listener: @app.before_server_stop + Listener->>Handler: Invoke event handler + end + Note over Worker: Server status: stopped + loop + Worker->>Listener: @app.after_server_stop + Listener->>Handler: Invoke event handler + end + Note over Worker: Server status: closed +end +loop + Process->>Listener: @app.main_process_stop + Listener->>Handler: Invoke event handler +end +Note over Process: exit +``` + +Процесс перезагрузчика существует не в этом процессе, а внутри процесса, который отвечает за запуск и остановку Sanic процессов. Рассмотрим следующий пример: + +```python +@app.reload_process_start +async def reload_start(*_): + print(">>>>>> reload_start <<<<<<") + + +@app.main_process_start +async def main_start(*_): + print(">>>>>> main_start <<<<<<") +``` + +Если это приложение было запущено с включенной автоматической перезагрузкой, функция `reload_start` будет вызвана один раз. Это контрастирует с `main_start`, который будет запускаться каждый раз при сохранении файла и перезапуске перезагрузчиком процесса приложения. + +## Подключение обработчика событий + +---:1 + +Процесс настройки функции в качестве обработчика событий похож на определение маршрута. + +В обработчик пробрасывается экземпляр текущего запущенного приложения `Sanic()`. :--:1 +```python +async def setup_db(app): + app.ctx.db = await db_setup() + +app.register_listener(setup_db, "before_server_start") +``` +:--- + +---:1 + +У экземпляра приложения `Sanic` также есть удобный декоратор для этого. :--:1 +```python +@app.listener("before_server_start") +async def setup_db(app): + app.ctx.db = await db_setup() +``` +:--- + +---:1 До v22.3 в функцию необходимо было пробрасывать и экземпляр приложения, и текущий цикл событий. Тем не менее, по умолчанию требуется только экземпляр приложения. Если сигнатура функции будет принимать и то, и другое, то оба этих параметра будут использованы, как показано ниже. :--:1 +```python +@app.listener("before_server_start") +async def setup_db(app, loop): + app.ctx.db = await db_setup() +``` +:--- + +---:1 + +Декоратор же можно сократить еще больше. Это полезно, если у вас есть IDE с автозавершением. + +:--:1 +```python +@app.before_server_start +async def setup_db(app): + app.ctx.db = await db_setup() +``` +:--- + +## Порядок выполнения + +Во время запуска обработчики событий выполняются в порядке, в котором они объявлены и в обратном порядке во время остановки сервера + +| | Этап | Порядок | +| --------------------- | --------------------------- | ----------------------------- | +| `main_process_start` | запуск главного процесса | обычный :smiley: | +| `before_server_start` | запуск воркера | обычный :smiley: | +| `after_server_start` | запуск воркера | обычный :smiley: | +| `before_server_stop` | остановка воркера | обратный :upside_down_face: | +| `after_server_stop` | остановка воркера | обратный :upside_down_face: | +| `main_process_stop` | остановка главного процесса | обратный :upside_down_face: | + +Если мы запустим два воркера с указанными ниже настройками, то в консоли мы можем ожидать следующий вывод. + +---:1 + +```python +@app.listener("before_server_start") +async def listener_1(app, loop): + print("listener_1") + +@app.before_server_start +async def listener_2(app, loop): + print("listener_2") + +@app.listener("after_server_start") +async def listener_3(app, loop): + print("listener_3") + +@app.after_server_start +async def listener_4(app, loop): + print("listener_4") + +@app.listener("before_server_stop") +async def listener_5(app, loop): + print("listener_5") + +@app.before_server_stop +async def listener_6(app, loop): + print("listener_6") + +@app.listener("after_server_stop") +async def listener_7(app, loop): + print("listener_7") + +@app.after_server_stop +async def listener_8(app, loop): + print("listener_8") +``` +:--:1 +```bash{3-7,13,19-22} +[pid: 1000000] [INFO] Goin' Fast @ http://127.0.0.1:9999 +[pid: 1000000] [INFO] listener_0 +[pid: 1111111] [INFO] listener_1 +[pid: 1111111] [INFO] listener_2 +[pid: 1111111] [INFO] listener_3 +[pid: 1111111] [INFO] listener_4 +[pid: 1111111] [INFO] Starting worker [1111111] +[pid: 1222222] [INFO] listener_1 +[pid: 1222222] [INFO] listener_2 +[pid: 1222222] [INFO] listener_3 +[pid: 1222222] [INFO] listener_4 +[pid: 1222222] [INFO] Starting worker [1222222] +[pid: 1111111] [INFO] Stopping worker [1111111] +[pid: 1222222] [INFO] Stopping worker [1222222] +[pid: 1222222] [INFO] listener_6 +[pid: 1222222] [INFO] listener_5 +[pid: 1222222] [INFO] listener_8 +[pid: 1222222] [INFO] listener_7 +[pid: 1111111] [INFO] listener_6 +[pid: 1111111] [INFO] listener_5 +[pid: 1111111] [INFO] listener_8 +[pid: 1111111] [INFO] listener_7 +[pid: 1000000] [INFO] listener_9 +[pid: 1000000] [INFO] Server Stopped +``` +В приведенном примере обратите внимание на то, как выполняются три процесса: + +- `pid: 1000000` - The *main* process +- `pid: 1111111` - Worker 1 +- `pid: 1222222` - Worker 2 + +*Здесь это выглядит так только потому, что в нашем примере все выводы каждого из воркеров сгруппированы. На самом же деле, поскольку они выполняются в отдельных процессах, такой порядок вывода не гарантируется. Но вы можете быть уверены, что любой отдельно взятый воркер **всегда** будет сохранять свой порядок выполнения.* :--- + + +::: Совет К сведенью Практический результат всего этого в том, что если первый обработчик в `before_server_start` устанавливает соединение с базой данных, то обработчики, которые зарегистрированы после этого события, могут использовать это установленное соединение и в момент их запуска, и в момент остановки. ::: +::: + +## Режим ASGI + +Если вы запускаете приложение с ASGI сервером, то обратите внимание на следующие изменения: + +- `reload_process_start` и `reload_process_stop` будут игнорироваться **** +- `main_process_start` и `main_process_stop` будут **игнорироваться** +- `before_server_start` будет запущен настолько рано, насколько это возможно, и в любом случае раньше `after_server_start`, но технически в этот момент сервер уже запущен +- `after_server_stop` будет запущен настолько поздно, насколько это возможно, и в любом случае позже `before_server_stop`, но технически в этот момент сервер все еще работает diff --git a/src/ru/guide/basics/middleware.md b/src/ru/guide/basics/middleware.md new file mode 100644 index 0000000000..3c7184f204 --- /dev/null +++ b/src/ru/guide/basics/middleware.md @@ -0,0 +1,210 @@ +# Middleware (промежуточное ПО) + +Если обработчики событий позволяют добавить некоторую функциональность к жизненному циклу воркера, то middleware позволяет добавить некоторую функциональность к жизненному циклу HTTP-потока. + +Middleware могут выполняться либо _до_ выполнения хендлера, либо _после_. + +```mermaid +sequenceDiagram +autonumber +participant Worker +participant Middleware +participant MiddlewareHandler +participant RouteHandler +Note over Worker: Incoming HTTP request +loop + Worker->>Middleware: @app.on_request + Middleware->>MiddlewareHandler: Invoke middleware handler + MiddlewareHandler-->>Worker: Return response (optional) +end +rect rgba(255, 13, 104, .1) +Worker->>RouteHandler: Invoke route handler +RouteHandler->>Worker: Return response +end +loop + Worker->>Middleware: @app.on_response + Middleware->>MiddlewareHandler: Invoke middleware handler + MiddlewareHandler-->>Worker: Return response (optional) +end +Note over Worker: Deliver response +``` +## Подключение middleware + +---:1 + +Пожалуй, это уже должно выглядеть знакомым. Все, что вам нужно сделать, это указать то событие, при котором middleware следует выполняться: `request` или `response`. :--:1 +```python +async def extract_user(request): + request.ctx.user = await extract_user_from_request(request) + +app.register_middleware(extract_user, "request") +``` +:--- + +---:1 + +Опять же, у экземпляра приложения `Sanic` есть для этого удобный декоратор. :--:1 +```python +@app.middleware("request") +async def extract_user(request): + request.ctx.user = await extract_user_from_request(request) +``` +:--- + +---:1 + +Middleware, выполняющееся на ответе, получает в качестве аргументов и `request`, и `response`. :--:1 +```python +@app.middleware('response') +async def prevent_xss(request, response): + response.headers["x-xss-protection"] = "1; mode=block" +``` +:--- + +---:1 + +Декоратор можно сократить еще больше. Это полезно, если у вас есть IDE с автозавершением. + +This is the preferred usage, and is what we will use going forward. + +:--:1 +```python +@app.on_request +async def extract_user(request): + ... + +@app.on_response +async def prevent_xss(request, response): + ... +``` +:--- + +## Изменения + +---:1 + +Middleware может изменять переданные в него параметры запроса или ответа, _так как оно их не возвращает_. + +#### Порядок выполнения + +1. Middleware запроса: `add_key` +2. Хендлер маршрута: `index` +3. Middleware ответа: `prevent_xss` +4. Middleware ответа: `custom_banner` :--:1 +```python +@app.on_request +async def add_key(request): + # Arbitrary data may be stored in request context: + request.ctx.foo = "bar" + + +@app.on_response +async def custom_banner(request, response): + response.headers["Server"] = "Fake-Server" + + +@app.on_response +async def prevent_xss(request, response): + response.headers["x-xss-protection"] = "1; mode=block" + + +@app.get("/") +async def index(request): + return text(request.ctx.foo) + +``` +:--- + + +---:1 Вы можете изменить объект `request.match_info`. Пример полезного использования middleware: преобразование `a-slug` в `a_slug`. :--:1 +```python +@app.on_request +def convert_slug_to_underscore(request: Request): + request.match_info["slug"] = request.match_info["slug"].replace("-", "_") + + +@app.get("/") +async def handler(request, slug): + return text(slug) +``` +``` +$ curl localhost:9999/foo-bar-baz +foo_bar_baz +``` +:--- +## Раннее реагирование + +---:1 + +Если middleware возвращает объект `HTTPResponse`, запрос перестанет обрабатываться, и будет возвращен ответ. Если это произойдет до того, как запрос достигнет хендлера маршрута, хендлер **не** будет вызван. Возврат ответа также предотвращает выполнение любых последующих middleware. + +::: Совет Вы можете вернуть значение `None` для остановки выполнения обработчика middleware для того, чтобы запрос мог дальше обрабатываться в нормальном режиме. Это может быть полезно, например, если вы используете ранний возврат в целях избежания дальнейшей обработки запроса внутри этого middleware. ::: :--:1 +```python +@app.on_request +async def halt_request(request): + return text("I halted the request") + +@app.on_response +async def halt_response(request, response): + return text("I halted the response") +``` +:--- + +## Порядок выполнения + +Middleware запросов выполняются в порядке их объявления. Middleware ответов выполняются в **обратном порядке**. + +Если мы запустим следующий код, то в консоли мы можем ожидать следующий вывод. + +---:1 +```python +@app.on_request +async def middleware_1(request): + print("middleware_1") + + +@app.on_request +async def middleware_2(request): + print("middleware_2") + + +@app.on_response +async def middleware_3(request, response): + print("middleware_3") + + +@app.on_response +async def middleware_4(request, response): + print("middleware_4") + +@app.get("/handler") +async def handler(request): + print("~ handler ~") + return text("Done.") +``` +:--:1 +```bash +middleware_1 +middleware_2 +~ handler ~ +middleware_4 +middleware_3 +[INFO][127.0.0.1:44788]: GET http://localhost:8000/handler 200 5 +``` +:--- + +### Middleware priority + +---:1 You can modify the order of execution of middleware by assigning it a higher priority. This happens inside of the middleware definition. The higher the value, the earlier it will execute relative to other middleware. The default priority for middleware is `0`. :--:1 +```python +@app.on_request +async def low_priority(request): + ... + +@app.on_request(priority=99) +async def high_priority(request): + ... +``` +:--- + +*Added in v22.9* diff --git a/src/ru/guide/basics/request.md b/src/ru/guide/basics/request.md new file mode 100644 index 0000000000..a5548777ea --- /dev/null +++ b/src/ru/guide/basics/request.md @@ -0,0 +1,232 @@ +# Объект запроса + +Объект `запроса` содержит **много** полезной информации о его параметрах. Обратитесь к [документации по API](https://sanic.readthedocs.io/) для получения полной информации. + +## Тело запроса + +:::: tabs +::: tab JSON + +**Параметр**: `request.json` +**Описание**: Десериализованный объект JSON + +```bash +$ curl localhost:8000 -d '{"foo": "bar"}' +``` + +```python +>>> print(request.json) +{'foo': 'bar'} +``` +::: + +::: tab Сырые данные + +**Параметр**: `request.body` +**Описание**: Сырые байты в теле запроса + +```bash +$ curl localhost:8000 -d '{"foo": "bar"}' +``` + +```python +>>> print(request.body) +b'{"foo": "bar"}' +``` +::: + +::: tab Форма + +**Параметр**: `request.form` +**Описание**: Данные формы + +```bash +$ curl localhost:8000 -d 'foo=bar' +``` + +```python +>>> print(request.body) +b'foo=bar' + +>>> print(request.form) +{'foo': ['bar']} + +>>> print(request.form.get("foo")) +bar + +>>> print(request.form.getlist("foo")) +['bar'] +``` + +::: Совет FYI :bulb: Объект `request.form` - один из нескольких типов, представляющих собой словарь, каждое значение которого является списком. Это связано с тем, что HTTP позволяет повторно использовать один ключ для отправки нескольких значений. + +Чаще всего вы хотите использовать метод `.get()` для доступа к первому элементу, а не списку. Если вы хотите получить список всех элементов, можно использовать `.getlist()`. ::: + +::: tab Загрузка данных + +**Параметр**: `request.files` +**Описание**: Файлы, загруженные на сервер + +```bash +$ curl -F 'my_file=@/path/to/TEST' http://localhost:8000 +``` + +```python +>>> print(request.body) +b'--------------------------cb566ad845ad02d3\r\nContent-Disposition: form-data; name="my_file"; filename="TEST"\r\nContent-Type: application/octet-stream\r\n\r\nhello\n\r\n--------------------------cb566ad845ad02d3--\r\n' + +>>> print(request.files) +{'my_file': [File(type='application/octet-stream', body=b'hello\n', name='TEST')]} + +>>> print(request.files.get("my_file")) +File(type='application/octet-stream', body=b'hello\n', name='TEST') + +>>> print(request.files.getlist("my_file")) +[File(type='application/octet-stream', body=b'hello\n', name='TEST')] +``` +::: Совет FYI :bulb: Объект `request.files` - один из нескольких типов, представляющих собой словарь, каждое значение которого является списком. Это связано с тем, что HTTP позволяет повторно использовать один ключ для отправки нескольких значений. + +Чаще всего вы хотите использовать метод `.get()` для доступа к первому элементу, а не списку. Если вы хотите получить список всех элементов, можно использовать `.getlist()`. ::: + +:::: + +## Контекст + +### Контекст запроса + +Объект `request.ctx` является местом, куда вы можете сохранить любую необходимую информацию о запросе. + +Он часто используется для хранения таких элементов, как аутентификационные данные пользователя. Позже мы окунёмся глубже в понятие [middleware](./middleware.md), а пока простой пример. + +```python +@app.on_request +async def run_before_handler(request): + request.ctx.user = await fetch_user_by_token(request.token) + +@app.route('/hi') +async def hi_my_name_is(request): + return text("Hi, my name is {}".format(request.ctx.user.name)) +``` + +Типичным вариантом использования является сохранение объекта user, полученного из базы данных в middleware, отвечающем за аутентификацию. Добавленные ключи доступны в течение всего выполнения запроса для всех последующих middleware, а также для хендлера запроса. + +Пользовательский контекст зарезервирован для приложений и расширений. Сам Sanic его не использует. + +### Контекст подключения + +---:1 + +Зачастую вашему API придется обрабатывать несколько одновременных (или последовательных) запросов к одному и тому же клиенту. Это очень часто происходит, например, с прогрессивными веб-приложениями, которые должны запрашивать несколько эндпоинтов для получения данных. + +Протокол HTTP требует уменьшения времени накладных расходов, вызванного соединением с использованием [keep alive headers](../deployment/configuration.md#keep-alive-timeout). + +Когда несколько запросов имеют единое соединение, Sanic предоставляет объект контекста, позволяющий этим запросам обмениваться состоянием. + +:--:1 +```python +@app.on_request +async def increment_foo(request): + if not hasattr(request.conn_info.ctx, "foo"): + request.conn_info.ctx.foo = 0 + request.conn_info.ctx.foo += 1 + +@app.get("/") +async def count_foo(request): + return text(f"request.conn_info.ctx.foo={request.conn_info.ctx.foo}") +``` + +```bash +$ curl localhost:8000 localhost:8000 localhost:8000 +request.conn_info.ctx.foo=1 +request.conn_info.ctx.foo=2 +request.conn_info.ctx.foo=3 +``` +:--- + +## Параметры + +---:1 Значения, извлекаемые из пути, указываются в сигнатуре хендлера в качестве позиционных или именованных аргументов. Подробнее об этом читайте в [разделе Маршрутизация](./routing.md). :--:1 +```python +@app.route('/tag/') +async def tag_handler(request, tag): + return text("Tag - {}".format(tag)) +``` +:--- + + +## Аргументы + +В объекте `request` есть два атрибута для получения параметров запроса: + +- `request.args` +- `request.query_args` + +```bash +$ curl http://localhost:8000\?key1\=val1\&key2\=val2\&key1\=val3 +``` + +```python +>>> print(request.args) +{'key1': ['val1', 'val3'], 'key2': ['val2']} + +>>> print(request.args.get("key1")) +val1 + +>>> print(request.args.getlist("key1")) +['val1', 'val3'] + +>>> print(request.query_args) +[('key1', 'val1'), ('key2', 'val2'), ('key1', 'val3')] + +>>> print(request.query_string) +key1=val1&key2=val2&key1=val3 + +``` + +::: Совет FYI :bulb: Объект `request.args` - один из нескольких типов, представляющих собой словарь, каждое значение которого является списком. Это связано с тем, что HTTP позволяет повторно использовать один ключ для отправки нескольких значений. + +Чаще всего вы хотите использовать метод `.get()` для доступа к первому элементу, а не списку. Если вы хотите получить список всех элементов, можно использовать `.getlist()`. ::: + +## Геттер текущего запроса + +Иногда вам может понадобиться доступ к текущему запросу в вашем приложении в том месте, где он недоступен. Типичный пример может быть в формате `логирования`. Вы можете использовать `Request.get_current()` для получения текущего запроса (если он присутствует). + +```python +import logging + +from sanic import Request, Sanic, json +from sanic.exceptions import SanicException +from sanic.log import LOGGING_CONFIG_DEFAULTS + +LOGGING_FORMAT = ( + "%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: " + "%(request_id)s %(request)s %(message)s %(status)d %(byte)d" +) + +old_factory = logging.getLogRecordFactory() + + +def record_factory(*args, **kwargs): + record = old_factory(*args, **kwargs) + record.request_id = "" + + try: + request = Request.get_current() + except SanicException: + ... + else: + record.request_id = str(request.id) + + return record + + +logging.setLogRecordFactory(record_factory) + +LOGGING_CONFIG_DEFAULTS["formatters"]["access"]["format"] = LOGGING_FORMAT + +app = Sanic("Example", log_config=LOGGING_CONFIG_DEFAULTS) +``` + +В этом примере мы добавляем `request.id` к каждому сообщению в access log. + +*Added in v22.6* diff --git a/src/ru/guide/basics/response.md b/src/ru/guide/basics/response.md new file mode 100644 index 0000000000..1470a95e47 --- /dev/null +++ b/src/ru/guide/basics/response.md @@ -0,0 +1,217 @@ +# Объект ответа + +All [handlers](./handlers.md)* **must** return a response object, and [middleware](./middleware.md) may optionally return a response object. + +To clarify that statement: +- unless the handler is a streaming endpoint, the return value must be an instance of `sanic.HTTPResponse` (to learn more about this exception see [streaming responses](../advanced/streaming.md#response-streaming)) +- if a middleware returns a response object, that will be used instead of whatever the handler would do (see [middleware](./middleware.md) to learn more) + +A most basic handler would look like the following. The `HTTPResponse` object will allow you to set the status, body, and headers to be returned to the client. + +```python +from sanic import HTTPResponse, Sanic + +app = Sanic("TestApp") + + +@app.route("") +def handler(_): + return HTTPResponse() +``` + +However, usually it is easier to use one of the convenience methods discussed below. + + +## Методы + +Самый простой способ сгенерировать объект ответа - использовать один из девяти (9) удобных методов. + +:::: tabs + +::: tab Текст + +**Default Content-Type**: `text/plain; charset=utf-8` +**Описание**: Возвращает обычный текст + +```python +from sanic.response import text + +@app.route("/") +async def handler(request): + return text("Hi 😎") +``` +::: +::: tab HTML + +**Default Content-Type**: `text/html; charset=utf-8` +**Описание**: Возвращает HTML документ + +```python +from sanic.response import html + +@app.route("/") +async def handler(request): + return html('
Hi 😎
') +``` +::: +::: tab JSON + +**Default Content-Type**: `application/json` +**Описание**: Возвращает JSON документ + +```python +from sanic.response import json + +@app.route("/") +async def handler(request): + return json({"foo": "bar"}) +``` + +По умолчанию Sanic поставляется с пакетом [`ujson`](https://github.com/ultrajson/ultrajson) в качестве выбранного кодировщика JSON. Если вы хотите это изменить - это супер просто. + +```python +from orjson import dumps + +json({"foo": "bar"}, dumps=dumps) +``` + +В случае, если `ujson` не установлен, будет использоваться `json` модуль из стандартной библиотеки. + +Вы можете дополнительно определить, какую реализацию использовать во всем вашем приложении при его инициализации: + +```python +from orjson import dumps + +app = Sanic(..., dumps=dumps) +``` +::: +::: tab File + +**Default Content-Type**: N/A +**Описание**: Возвращает файл + + +```python +from sanic.response import file + +@app.route("/") +async def handler(request): + return await file("/path/to/whatever.png") +``` + +Sanic исследует файл и попробует угадать его тип и использовать соответствующее значение для типа содержимого. Если хотите, вы можете указать это явно: + +```python +file("/path/to/whatever.png", mime_type="image/png") +``` + +Вы также можете переопределить имя файла: + +```python +file("/path/to/whatever.png", filename="super-awesome-incredible.png") +``` +::: +::: tab "Потоковая передача файла" + +**Default Content-Type**: N/A +**Описание**: Передаёт файл клиенту в потоке; полезно при пересылке больших файлов, например видео + +```python +from sanic.response import file_stream + +@app.route("/") +async def handler(request): + return await file_stream("/path/to/whatever.mp4") +``` + +Как и метод `file()`, `file_stream()` попытается определить тип содержимого файла. ::: +::: tab Сырые данные + +**Default Content-Type**: `application/octet-stream` +**Описание**: Отправляет сырые данные без кодирования тела ответа + +```python +from sanic.response import raw + +@app.route("/") +async def handler(request): + return raw(b"raw bytes") +``` +::: +::: tab Перенаправление + +**Default Content-Type**: `text/html; charset=utf-8` +**Описание**: Отправляет ответ с кодом `302` с последующим перенаправлением запроса клиента по другому пути + +```python +from sanic.response import redirect + +@app.route("/") +async def handler(request): + return redirect("/login") +``` + +::: +::: tab Пустой ответ + +**Default Content-Type**: N/A +**Описание**: Используется для отправки ответа с пустым сообщением, как описано в [RFC 2616](https://tools.ietf.org/search/rfc2616#section-7.2.1) + +```python +from sanic.response import empty + +@app.route("/") +async def handler(request): + return empty() +``` + +По умолчанию статус `204`. ::: +:::: + +## Код статуса по умолчанию + +По умолчанию HTTP-код статуса для ответа `200`. Если вам нужно его изменить, то это может быть сделано в методе ответа. + + +```python +@app.post("/") +async def create_new(request): + new_thing = await do_create(request) + return json({"created": True, "id": new_thing.thing_id}, status=201) +``` + +::: new NEW in v22.12 +## Returning JSON data + +Starting in v22.12, When you use the `sanic.json` convenience method, it will return a subclass of `HTTPResponse` called `JSONResponse`. This object will have several convenient methods available to modify common JSON body. + +```python +from sanic import json + +resp = json(...) +``` + +- `resp.set_body()` - Set the body of the JSON object to the value passed +- `resp.append()` - Append a value to the body like `list.append` (only works if the root JSON is an array) +- `resp.extend()` - Extend a value to the body like `list.extend` (only works if the root JSON is an array) +- `resp.update()` - Update the body with a value like `dict.update` (only works if the root JSON is an object) +- `resp.pop()` - Pop a value like `list.pop` or `dict.pop` (only works if the root JSON is an array or an object) + +::: warning The raw Python object is stored on the `JSONResponse` object as `raw_body`. While it is safe to overwrite this value with a new one, you should **not** attempt to mutate it. You should instead use the methods listed above. + +```python +resp = json({"foo": "bar"}) + +# This is OKAY +resp.raw_body = {"foo": "bar", "something": "else"} + +# This is better +resp.set_body({"foo": "bar", "something": "else"}) + +# This is also works well +resp.update({"something": "else"}) + +# This is NOT OKAY +resp.raw_body.update({"something": "else"}) +``` +::: diff --git a/src/ru/guide/basics/routing.md b/src/ru/guide/basics/routing.md new file mode 100644 index 0000000000..10e3b62d85 --- /dev/null +++ b/src/ru/guide/basics/routing.md @@ -0,0 +1,757 @@ +# Маршрутизация + +---:1 + +До сих пор мы часто встречали этот декоратор в различных формах. + +Но что это такое? И как мы это используем? :--:1 +```python +@app.route("/stairway") +... + +@app.get("/to") +... + +@app.post("/heaven") +... +``` +:--- + +## Добавление маршрута + +---:1 + +Самым простым способом привязать обработчик к эндпоинту является `app.add_route()`. + +Смотрите [API документацию](https://sanic.readthedocs.io/en/stable/sanic/api_reference.html#sanic.app.Sanic.url_for) для получения более подробной информации. :--:1 +```python +async def handler(request): + return text("OK") + +app.add_route(handler, "/test") +``` +:--- + +---:1 + +По умолчанию маршрут обрабатывает HTTP `GET` вызов. Вы можете настроить хендлер для ответа на один или несколько HTTP-методов. :--:1 +```python +app.add_route( + handler, + '/test', + methods=["POST", "PUT"], +) +``` +:--- + +---:1 + +Предыдущий пример соответствует следующему варианту с использованием синтаксиса декоратора. :--:1 +```python +@app.route('/test', methods=["POST", "PUT"]) +async def handler(request): + return text('OK') +``` +:--- + +## HTTP методы + +Каждый из стандартных методов HTTP имеет удобный декоратор. + +:::: tabs +::: tab GET + +```python +@app.get('/test') +async def handler(request): + return text('OK') +``` + +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET) ::: +::: tab POST + +```python +@app.post('/test') +async def handler(request): + return text('OK') +``` + +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) ::: +::: tab PUT + +```python +@app.put('/test') +async def handler(request): + return text('OK') +``` + +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT) ::: +::: tab PATCH + +```python +@app.patch('/test') +async def handler(request): + return text('OK') +``` + +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH) ::: +::: tab DELETE + +```python +@app.delete('/test') +async def handler(request): + return text('OK') +``` + +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE) ::: +::: tab HEAD + +```python +@app.head('/test') +async def handler(request): + return empty() +``` + +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD) ::: +::: tab OPTIONS + +```python +@app.options('/test') +async def handler(request): + return empty() +``` + +[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS) ::: +:::: + +::: Предупреждение По умолчанию, Sanic **будет ожидать** входящее тело запроса на небезопасных методах HTTP (`POST`, `PUT`, `PATCH`). Если вы хотите получать данные в HTTP-запросе любым другим методом, вам нужно выбрать один из следующих двух вариантов: + +**Вариант #1 - Сказать Sanic ожидать тело с помощью параметра `ignore_body`** +```python +@app.delete("/path", ignore_body=False) +async def handler(_): + ... +``` + +**Вариант #2 - Получать тело вручную внутри обработчика с помощью метода запроса `receive_body`** +```python +@app.delete("/path") +async def handler(request: Request): + await request.receive_body() +``` +::: + +## Параметры пути + +---:1 + +Sanic позволяет использовать встроенное сопоставление шаблонов и извлечение значений из URL-пути. Затем эти значения передаются в обработчик маршрута в качестве ключевых аргументов. :--:1 +```python +@app.get("/tag/") +async def tag_handler(request, tag): + return text("Tag - {}".format(tag)) +``` +:--- + +---:1 + +Вы можете указать тип параметра. Это будет применено при сопоставлении, а также приведёт переменную к соответствующему типу. :--:1 +```python +@app.get("/foo/") +async def uuid_handler(request, foo_id: UUID): + return text("UUID - {}".format(foo_id)) +``` +:--- + +### Поддерживаемые типы + +:::: tabs + +::: tab str + +```python +@app.route("/path/to/") +async def handler(request, foo: str): + ... +``` +**Применяемое регулярное выражение**: `r"[^/]+")` +**Тип приведения**: `str` +**Пример соответствия**: +- `/path/to/Bob` +- `/path/to/Python%203` + +Начиная с версии 22.3 `str` *не определяет * пустые строки. Для этого поведения используйте `strorempty`. + +::: +::: tab strorempty + +```python +@app.route("/path/to/") +async def handler(request, foo: str): + ... +``` +**Применяемое регулярное выражение**: `r"[^/]*")` +**Тип приведения**: `str` +**Пример соответствия**: +- `/path/to/Bob` +- `/path/to/Python%203` +- `/path/to/` + +В отличие от типа параметра `str`, `strorempty` также может определять пустой строковый сегмент пути. + +*Добавлено в v22.3* ::: +::: int + +```python +@app.route("/path/to/") +async def handler(request, foo: int): + ... +``` +**Применяемое регулярное выражение**: `r"-?\d+")` +**Тип приведения**: `int` +**Пример соответствия**: +- `/path/to/10` +- `/path/to/-10` + +_Не соответствует float, hex, octal, и т. д._ ::: +::: tab float + +```python +@app.route("/path/to/") +async def handler(request, foo: float): + ... +``` +**Применяемое регулярное выражение**: `r"-?(?:\d+(?:\.\d*)?|\.\d+)")` +**Тип приведения**: `float` +**Пример соответствия**: +- `/path/to/10` +- `/path/to/-10` +- `/path/to/1.5` + +::: +::: tab alpha + +```python +@app.route("/path/to/") +async def handler(request, foo: str): + ... +``` +**Применяемое регулярное выражение**: `r"[A-Za-z]+")` +**Тип приведения**: `str` +**Пример соответствия**: +- `/path/to/Bob` +- `/path/to/Python` + +_Не соответствует цифре, пробелу или другому специальному символу_ ::: +::: tab slug + +```python +@app.route("/path/to/") +async def handler(request, article: str): + ... +``` +**Применяемое регулярное выражение**: `r"[a-z0-9]+(?:-[a-z0-9]+)*")` +**Тип приведения**: `str` +**Пример соответствия**: +- `/path/to/some-news-story` +- `/path/to/or-has-digits-123` + +*Added in v21.6* ::: +::: tab path + +```python +@app.route("/path/to/") +async def handler(request, foo: str): + ... +``` +**Применяемое регулярное выражение**: `r"[^/].*?")` +**Тип приведения**: `str` +**Пример соответствия**: +- `/path/to/hello` +- `/path/to/hello.txt` +- `/path/to/hello/world.txt` + +::: Предупреждение Поскольку соспоставление проводится по `/`, вам следует очень тщательно протестировать шаблоны, которые используют `path`, чтобы они не перехватывали трафик, предназначенный для другого эндпоинта. ::: +::: tab ymd + +```python +@app.route("/path/to/") +async def handler(request, foo: datetime.date): + ... +``` +**Применяемое регулярное выражение**: `r"^([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))` +**Тип приведения**: `datetime.date` +**Пример соответствия**: +- `/path/to/2021-03-28` ::: + +::: tab uuid + +```python +@app.route("/path/to/") +async def handler(request, foo: UUID): + ... +``` +**Применяемое регулярное выражение**: `r"[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}"` +**Тип приведения**: `UUID` +**Пример соответствия**: +- `/path/to/123a123a-a12a-1a1a-a1a1-1a12a1a12345` + +::: + +::: tab ext + +```python +@app.route("/path/to/") +async def handler(request, foo: str, ext: str): + ... +``` +**Применяемое регулярное выражение**: n/a +**Тип приведения**: `вариативный` +**Пример соответствия**: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ определение + + пример + + имя файла + + расширение +
+ \ + + page.txt + + "page" + + "txt" +
+ \ + + cat.jpg + + "cat" + + "jpg" +
+ \ + + + png\ + + gif\ + + svg> | cat.jpg | "cat" | "jpg" +
+ + + 123.txt + + 123 + + "txt" +
+ + + + png\ + + gif\ + + svg> | 123.svg | 123 | "svg" +
+ + + 3.14.tar.gz + + 3.14 + + "tar.gz" +
+ +Используя специальный тип параметра `ext`, можно сопоставлять расширения файлов. Он использует специальный формат, который позволяет вам указывать другие типы для параметра имени файла, и одно или несколько конкретных расширений, как показано в приведенной выше таблице. + +Он *не* поддерживает параметры типа `path`. + +*Добавлено в v22.3* ::: + +::: tab regex + +```python +@app.route(r"/path/to/") +async def handler(request, foo: str): + ... +``` +**Применяемое регулярное выражение**: _любое по вашему выбору_ +**Тип приведения**: `str` +**Пример соответствия**: +- `/path/to/2021-01-01` + +Это дает вам свободу определять подходящие шаблоны для вашего варианта использования. + +В показанном примере мы ищем дату в формате `YYYY-MM-DD`. + +:::: + +### Сопоставление по регулярному выражению + + + +По сравнению со сложной маршрутизацией приведенный выше пример слишком прост, и чаще мы используем совершенно другой шаблон соответствия для маршрута. Поэтому здесь мы подробно опишем расширенное использование регулярных выражений. + +Иногда вы хотите сопоставить часть маршрута: + +```text +/image/123456789.jpg +``` + +Если вы хотите сопоставить паттерн файла, но только в его числовой части, вам придётся немного развлечься с регулярными выражениями 😄: + +```python +app.route(r"/image/\d+)\.jpg>") +``` + +Всё нижеуказанное должно подходить: + +```python +@app.get(r"/") # сопоставление полного паттерна +@app.get(r"/") # определение единственной группы выборки +@app.get(r"/[a-z]{3}).txt>") # определение единственной именнованной группы выборки +@app.get(r"/[a-z]{3}).(?:txt)>") # определение единственной именнованной группы выборки с одной или более несопоставляемыми группами +``` + +Также, если используется именованная группа выборки, то она должна совпадать с меткой сегмента. + +```python +@app.get(r"/\d+).jpg>") # OK +@app.get(r"/\d+).jpg>") # NOT OK +``` + +Для получения дополнительной информации о методах использования обратитесь к разделу [Операции регулярных выражений](https://docs.python.org/3/library/re.html) официальной библиотеки Python + +## Создание URL + +---:1 + +Sanic предоставляет метод генерации URL-маршрутов, основанных на имени обработчика: `app.url_for()`. Это полезно, если вы хотите избежать жесткого прописывания url-путей в вашем приложении; вместо этого вы можете просто сослаться на имя хендлера. :--:1 +```python +@app.route('/') +async def index(request): + # создаёт URL для эндпоинта `post_handler` + url = app.url_for('post_handler', post_id=5) + + # Перенаправление на `/posts/5` + return redirect(url) + +@app.route('/posts/') +async def post_handler(request, post_id): + ... +``` +:--- + +---:1 + +Вы можете передавать произвольное количество аргументов. Все, что _не_ является параметром запроса, будет трактоваться как часть запросной строки. :--:1 +```python +>>> app.url_for( + "post_handler", + post_id=5, + arg_one="one", + arg_two="two", +) +'/posts/5?arg_one=one&arg_two=two' +``` +:--- + +---:1 + +Также поддерживается передача нескольких значений для одного ключа запроса. :--:1 +```python +>>> app.url_for( + "post_handler", + post_id=5, + arg_one=["one", "two"], +) +'/posts/5?arg_one=one&arg_one=two' +``` +:--- + +### Специальные аргументы + +Смотрите [API документацию]() для получения более подробной информации. + +```python +>>> app.url_for("post_handler", post_id=5, arg_one="one", _anchor="anchor") +'/posts/5?arg_one=one#anchor' + +# Параметр _external требует передачи аргумента _server or установки переменной SERVER_NAME в app.config в случае, если ни один url не будет совпадать с пустым _external +>>> app.url_for("post_handler", post_id=5, arg_one="one", _external=True) +'//server/posts/5?arg_one=one' + +# при указании параметра _scheme, параметр _external должен быть True +>>> app.url_for("post_handler", post_id=5, arg_one="one", _scheme="http", _external=True) +'http://server/posts/5?arg_one=one' + +# вы можете передать все специальные аргументы одновременно +>>> app.url_for("post_handler", post_id=5, arg_one=["one", "two"], arg_two=2, _anchor="anchor", _scheme="http", _external=True, _server="another_server:8888") +'http://another_server:8888/posts/5?arg_one=one&arg_one=two&arg_two=2#anchor' +``` + +### Настройка имени маршрута + +---:1 + +Пользовательское имя маршрута может быть использовано при передаче аргумента `name` при регистрации маршрута. :--:1 +```python +@app.get("/get", name="get_handler") +def handler(request): + return text("OK") +``` +:--- + +---:1 + +Теперь используйте это имя для получения URL :--:1 +```python +>>> app.url_for("get_handler", foo="bar") +'/get?foo=bar' +``` +:--- + +## Маршруты веб-сокетов + +---:1 + +Маршрутизация веб-сокета работает аналогично методам HTTP. :--:1 +```python +async def handler(request, ws): + message = "Start" + while True: + await ws.send(message) + message = await ws.recv() + +app.add_websocket_route(handler, "/test") +``` +:--- + +---:1 + +У него также есть удобный декоратор. :--:1 +```python +@app.websocket("/test") +async def handler(request, ws): + message = "Start" + while True: + await ws.send(message) + message = await ws.recv() +``` +:--- + +Прочтите раздел [Веб-сокеты](/guide/advanced/websockets.md), чтобы узнать больше о том, как они работают. + +## Строгий слэш + + +---:1 + +Маршруты Sanic могут быть настроены на обязательное наличие слэша в конце: `/`. Это может быть указано на нескольких уровнях и в следующем порядке: + +1. Route +2. Blueprint +3. BlueprintGroup +4. Application + +:--:1 +```python +# указать значение strict_slashes по умолчанию для всех маршрутов += Sanic(__file__, strict_slashes=True) +``` + +```python +# переопределить значение strict_slashes для определенного маршрута +@app.get("/get", strict_slashes=False) +обработчик def (запрос): + return text("OK") +``` + +```python +# также работает для блюпринтов +bp = Blueprint(__file__, strict_slashes=True) + +@bp.get("/bp/get", strict_slashes=False) +def handler (request): + return text("OK") +``` + +```python +bp1 = Blueprint(name="bp1", url_prefix="/bp1") +bp2 = Blueprint( + name="bp2", + url_prefix="/bp2", + strict_slashes=False, +) + +# Это включит строгую проверку наличия слэша в маршрутах bp1 +# но будет её игнорировать в bp2 +# так как в его параметре strict_slashes явно указан False +group = Blueprint. roup([bp1, bp2], strict_slashes=True) +``` +:--- + +## Статичные файлы + +---:1 + +Для обработки статичных файлов используйте `app.static()`. + +Порядок аргументов имеет важное значение: + +1. Маршрут, в котором файлы будут обрабатываться +2. Путь к файлам на сервере + +Смотрите [API документацию]() для получения более подробной информации. :--:1 +```python +app.static("/static", "/path/to/directory") +``` +:--- + +---:1 + +Вы также можете обрабатывать отдельные файлы. :--:1 +```python +app.static("/", "/path/to/index.html") +``` +:--- + +---:1 + +Также иногда полезно придать вашему эндпоинту конкретное имя :--:1 +```python +app.static( + "/user/uploads", + "/path/to/uploads", + name="uploads", +) +``` +:--- + +---:1 + +Получение URL-адресов работает аналогично обработчикам. Но мы также можем добавить аргумент `filename`, когда нам нужен определенный файл внутри директории. :--:1 +```python +>>> app.url_for( + "static", + name="static", + filename="file.txt", +) +'/static/file.txt' +``` +```python +>>> app.url_for( + "static", + name="uploads", + filename="image.png", +) +'/user/uploads/image.png' + +``` +:--- + +::: Совет: Если вы собираетесь иметь несколько `static()` маршрутов, тогда *настоятельно* рекомендуется называть их вручную. Это почти наверняка облегчит поиск трудновыявляемых ошибки. + +```python +app.static("/user/uploads", "/path/to/uploads", name="uploads") +app.static("/user/profile", "/path/to/profile", name="profile_pics") +``` +::: + +## Контекст маршрута + +---:1 Когда маршрут определен, вы можете добавить в него любое количество аргументов с префиксом `ctx_`. Эти значения будут добавлены в объект маршрута `ctx`. :--:1 +```python +@app.get("/1", ctx_label="something") +async def handler1(request): + ... + +@app.get("/2", ctx_label="something") +async def handler2(request): + ... + +@app.get("/99") +async def handler99(request): + ... + +@app.on_request +async def do_something(request): + if request.route.ctx.label == "something": + ... +``` +:--- *Added in v21.12* diff --git a/src/ru/guide/basics/tasks.md b/src/ru/guide/basics/tasks.md new file mode 100644 index 0000000000..b4866e78c8 --- /dev/null +++ b/src/ru/guide/basics/tasks.md @@ -0,0 +1,100 @@ +# Фоновые задачи + +## Создание задач +Часто в асинхронном Python хочется использовать [задачи](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task), и это очень удобно. Sanic предоставляет удобный метод добавления задач к текущему **запущенному** циклу событий. Он чем-то похож на `asyncio.create_task`. См. следующий раздел для добавления задач до запуска цикла приложения. + +```python +async def notify_server_started_after_five_seconds(): + await asyncio.sleep(5) + print('Server successfully started!') + +app.add_task(notify_server_started_after_five_seconds()) +``` + +---:1 + +Sanic попытается автоматически внедрить приложение в задачу, передав его в качестве аргумента. :--:1 +```python +async def auto_inject(app): + await asyncio.sleep(5) + print(app.name) + +app.add_task(auto_inject) +``` +:--- + +---:1 + +Или вы можете передать аргумент `app` явно. :--:1 +```python +async def explicit_inject(app): + await asyncio.sleep(5) + print(app.name) + +app.add_task(explicit_inject(app)) +``` +:--- + +## Добавление задач до `app.run` + +Можно добавить фоновые задачи перед запуском приложения, т.е. до `app.run`. Чтобы добавить задачу перед запуском приложения, рекомендуется не передавать объект корутины (т. е. чтобы задача создавалась посредством вызова `асинхронного` вызываемого объекта). Вместо этого просто передавайте сам вызываемый объект, а Sanic создаст объект корутины на **каждый воркер**. Примечание: добавленные задачи выполняются как задачи `before_server_start` и таким образом запускаются в каждом воркере (и не в главном процессе). Это имеет определенные последствия. Пожалуйста, прочитайте [этот комментарий](https://github.com/sanic-org/sanic/issues/2139#issuecomment-868993668) по [этой проблеме](https://github.com/sanic-org/sanic/issues/2139) для получения дополнительной информации. + +Чтобы добавить задачу к основному процессу, попробуйте добавить её посредством [`@app.main_process_start`](./listeners.md). Примечание: воркеры не запустятся до тех пор, пока задача не завершится. + +---:1 + +Пример добавления задачи до `app.run` :---:1 +```python +async def slow_work(): + ... + +async def even_slower(num): + ... + +app = Sanic(...) +app.add_task(slow_work) # Примечание: мы передаем вызываемый объект, а не корутину... +app.add_task(even_slower(10)) # ... или мы можем вызвать функцию и передать корутину. +app.run(...) +``` + +## Именованные задачи + +_Поддерживается только в Python 3.8+_ + +---:1 При создании задачи вы можете попросить Sanic отслеживать её для вас, указав её `имя`. + +:--:1 +```python +app.add_task(slow_work, name="slow_task") +``` +:--- + +---:1 Теперь вы можете получить экземпляр этой задачи из любого места в вашем приложении, используя `get_task`. + +:--:1 +```python +task = app.get_task("slow_task") +``` +:--- + +---:1 Если эта задача должна быть отменена, вы можете это сделать при помощи `cancel_task`. Убедитесь, что вы указали на ней `await`. + +:--:1 +```python +await app.cancel_task("slow_task") +``` +:--- + +---:1 Все зарегистрированные задачи можно найти в свойстве `app.tasks`. Чтобы предотвратить выполнение отмененных задач, вы можете вызвать `app.purge_tasks`, который очистит все выполненные или отмененные задачи. + +:--:1 +```python +app.purge_tasks() +``` +:--- + +Этот паттерн может быть особенно полезен с `веб-сокетами`: + +```python async def receiver(ws): while True: message = await ws.recv() if not message: break print(f"Received: {message}") + +@app.websocket("/feed") async def feed(request, ws): task_name = f"receiver:{request.id}" request.app.add_task(receiver(ws), name=task_name) try: while True: await request.app.event("my.custom.event") await ws.send("A message") finally: # When the websocket closes, let's cleanup the task await request.app.cancel_task(task_name) request.app.purge_tasks() ::: *Added in v21.12* diff --git a/src/ru/guide/best-practices/README.md b/src/ru/guide/best-practices/README.md new file mode 100644 index 0000000000..f82c97602f --- /dev/null +++ b/src/ru/guide/best-practices/README.md @@ -0,0 +1 @@ +# Лучшие практики diff --git a/src/ru/guide/best-practices/blueprints.md b/src/ru/guide/best-practices/blueprints.md new file mode 100644 index 0000000000..6de1ceb47e --- /dev/null +++ b/src/ru/guide/best-practices/blueprints.md @@ -0,0 +1,369 @@ +# Blueprints + +## Overview + +Blueprints are objects that can be used for sub-routing within an application. Instead of adding routes to the application instance, blueprints define similar methods for adding routes, which are then registered with the application in a flexible and pluggable manner. + +Blueprints are especially useful for larger applications, where your application logic can be broken down into several groups or areas of responsibility. + +## Creating and registering + +---:1 + +First, you must create a blueprint. It has a very similar API as the `Sanic()` app instance with many of the same decorators. :--:1 +```python +# ./my_blueprint.py +from sanic.response import json +from sanic import Blueprint + +bp = Blueprint("my_blueprint") + +@bp.route("/") +async def bp_root(request): + return json({"my": "blueprint"}) +``` +:--- + + +---:1 + +Next, you register it with the app instance. :--:1 +```python +from sanic import Sanic +from my_blueprint import bp + +app = Sanic(__name__) +app.blueprint(bp) +``` +:--- + +Blueprints also have the same `websocket()` decorator and `add_websocket_route` method for implementing websockets. + +---:1 + +Beginning in v21.12, a Blueprint may be registered before or after adding objects to it. Previously, only objects attached to the Blueprint at the time of registration would be loaded into application instance. :--:1 +```python +app.blueprint(bp) + +@bp.route("/") +async def bp_root(request): + ... +``` +:--- +## Copying + +---:1 Blueprints along with everything that is attached to them can be copied to new instances using the `copy()` method. The only required argument is to pass it a new `name`. However, you could also use this to override any of the values from the old blueprint. :--:1 +```python +v1 = Blueprint("Version1", version=1) + +@v1.route("/something") +def something(request): + pass + +v2 = v1.copy("Version2", version=2) + +app.blueprint(v1) +app.blueprint(v2) +``` + +``` +Available routes: +/v1/something +/v2/something + +``` +:--- + +*Added in v21.9* + +## Blueprint groups + +Blueprints may also be registered as part of a list or tuple, where the registrar will recursively cycle through any sub-sequences of blueprints and register them accordingly. The Blueprint.group method is provided to simplify this process, allowing a ‘mock’ backend directory structure mimicking what’s seen from the front end. Consider this (quite contrived) example: + +```text +api/ +├──content/ +│ ├──authors.py +│ ├──static.py +│ └──__init__.py +├──info.py +└──__init__.py +app.py +``` + +---:1 + +#### First blueprint + +:--:1 +```python +# api/content/authors.py +from sanic import Blueprint + +authors = Blueprint("content_authors", url_prefix="/authors") +``` +:--- + +---:1 + +#### Second blueprint + +:--:1 +```python +# api/content/static.py +from sanic import Blueprint + +static = Blueprint("content_static", url_prefix="/static") +``` +:--- + +---:1 + +#### Blueprint group + +:--:1 +```python +# api/content/__init__.py +from sanic import Blueprint +from .static import static +from .authors import authors + +content = Blueprint.group(static, authors, url_prefix="/content") +``` +:--- + +---:1 + +#### Third blueprint + +:--:1 +```python +# api/info.py +from sanic import Blueprint + +info = Blueprint("info", url_prefix="/info") +``` +:--- + +---:1 + +#### Another blueprint group + +:--:1 +```python +# api/__init__.py +from sanic import Blueprint +from .content import content +from .info import info + +api = Blueprint.group(content, info, url_prefix="/api") +``` +:--- + +---:1 + +#### Main server + +All blueprints are now registered + +:--:1 +```python +# app.py +from sanic import Sanic +from .api import api + +app = Sanic(__name__) +app.blueprint(api) +``` +:--- + +## Middleware + +---:1 + +Blueprints can also have middleware that is specifically registered for its endpoints only. :--:1 +```python +@bp.middleware +async def print_on_request(request): + print("I am a spy") + +@bp.middleware("request") +async def halt_request(request): + return text("I halted the request") + +@bp.middleware("response") +async def halt_response(request, response): + return text("I halted the response") +``` +:--- + +---:1 + +Similarly, using blueprint groups, it is possible to apply middleware to an entire group of nested blueprints. :--:1 +```python +bp1 = Blueprint("bp1", url_prefix="/bp1") +bp2 = Blueprint("bp2", url_prefix="/bp2") + +@bp1.middleware("request") +async def bp1_only_middleware(request): + print("applied on Blueprint : bp1 Only") + +@bp1.route("/") +async def bp1_route(request): + return text("bp1") + +@bp2.route("/") +async def bp2_route(request, param): + return text(param) + +group = Blueprint.group(bp1, bp2) + +@group.middleware("request") +async def group_middleware(request): + print("common middleware applied for both bp1 and bp2") + +# Register Blueprint group under the app +app.blueprint(group) +``` +:--- + +## Exceptions + +---:1 + +Just like other [exception handling](./exceptions.md), you can define blueprint specific handlers. :--:1 +```python +@bp.exception(NotFound) +def ignore_404s(request, exception): + return text("Yep, I totally found the page: {}".format(request.url)) +``` +:--- + +## Static files + +---:1 + +Blueprints can also have their own static handlers :--:1 +```python +bp = Blueprint("bp", url_prefix="/bp") +bp.static("/web/path", "/folder/to/serve") +bp.static("/web/path", "/folder/to/server", name="uploads") +``` +:--- + +---:1 + +Which can then be retrieved using `url_for()`. See [routing](/guide/basics/routing.md) for more information. :--:1 +```python +>>> print(app.url_for("static", name="bp.uploads", filename="file.txt")) +'/bp/web/path/file.txt' +``` +:--- + +## Listeners + +---:1 + +Blueprints can also implement [listeners](/guide/basics/listeners.md). :--:1 +```python +@bp.listener("before_server_start") +async def before_server_start(app, loop): + ... + +@bp.listener("after_server_stop") +async def after_server_stop(app, loop): + ... +``` +:--- + +## Versioning + +As discussed in the [versioning section](/guide/advanced/versioning.md), blueprints can be used to implement different versions of a web API. + +---:1 + +The `version` will be prepended to the routes as `/v1` or `/v2`, etc. :--:1 +```python +auth1 = Blueprint("auth", url_prefix="/auth", version=1) +auth2 = Blueprint("auth", url_prefix="/auth", version=2) +``` +:--- + +---:1 + +When we register our blueprints on the app, the routes `/v1/auth` and `/v2/auth` will now point to the individual blueprints, which allows the creation of sub-sites for each API version. :--:1 +```python +from auth_blueprints import auth1, auth2 + +app = Sanic(__name__) +app.blueprint(auth1) +app.blueprint(auth2) +``` +:--- + +---:1 + +It is also possible to group the blueprints under a `BlueprintGroup` entity and version multiple of them together at the same time. :--:1 +```python +auth = Blueprint("auth", url_prefix="/auth") +metrics = Blueprint("metrics", url_prefix="/metrics") + +group = Blueprint.group(auth, metrics, version="v1") + +# This will provide APIs prefixed with the following URL path +# /v1/auth/ and /v1/metrics +``` +:--- + +## Composable + +A `Blueprint` may be registered to multiple groups, and each of `BlueprintGroup` itself could be registered and nested further. This creates a limitless possibility `Blueprint` composition. + +*Added in v21.6* ---:1 Take a look at this example and see how the two handlers are actually mounted as five (5) distinct routes. :--:1 +```python +app = Sanic(__name__) +blueprint_1 = Blueprint("blueprint_1", url_prefix="/bp1") +blueprint_2 = Blueprint("blueprint_2", url_prefix="/bp2") +group = Blueprint.group( + blueprint_1, + blueprint_2, + version=1, + version_prefix="/api/v", + url_prefix="/grouped", + strict_slashes=True, +) +primary = Blueprint.group(group, url_prefix="/primary") + + +@blueprint_1.route("/") +def blueprint_1_default_route(request): + return text("BP1_OK") + + +@blueprint_2.route("/") +def blueprint_2_default_route(request): + return text("BP2_OK") + + +app.blueprint(group) +app.blueprint(primary) +app.blueprint(blueprint_1) + +# The mounted paths: +# /api/v1/grouped/bp1/ +# /api/v1/grouped/bp2/ +# /api/v1/primary/grouped/bp1 +# /api/v1/primary/grouped/bp2 +# /bp1 + +``` +:--- + + +## Generating a URL + +When generating a url with `url_for()`, the endpoint name will be in the form: + +```text +{blueprint_name}.{handler_name} +``` diff --git a/src/ru/guide/best-practices/decorators.md b/src/ru/guide/best-practices/decorators.md new file mode 100644 index 0000000000..35a0772086 --- /dev/null +++ b/src/ru/guide/best-practices/decorators.md @@ -0,0 +1,182 @@ +# Decorators + +One of the best ways to create a consistent and DRY web API is to make use of decorators to remove functionality from the handlers, and make it repeatable across your views. + +---:1 + +Therefore, it is very common to see a Sanic view handler with several decorators on it. :--:1 +```python +@app.get("/orders") +@authorized("view_order") +@validate_list_params() +@inject_user() +async def get_order_details(request, params, user): + ... +``` +:--- + + +## Example + +Here is a starter template to help you create decorators. + +In this example, let’s say you want to check that a user is authorized to access a particular endpoint. You can create a decorator that wraps a handler function, checks a request if the client is authorized to access a resource, and sends the appropriate response. +```python +from functools import wraps +from sanic.response import json + +def authorized(): + def decorator(f): + @wraps(f) + async def decorated_function(request, *args, **kwargs): + # run some method that checks the request + # for the client's authorization status + is_authorized = await check_request_for_authorization_status(request) + + if is_authorized: + # the user is authorized. + # run the handler method and return the response + response = await f(request, *args, **kwargs) + return response + else: + # the user is not authorized. + return json({"status": "not_authorized"}, 403) + return decorated_function + return decorator + + +@app.route("/") +@authorized() +async def test(request): + return json({"status": "authorized"}) +``` + +## Templates + +Decorators are **fundamental** to building applications with Sanic. They increase the portability and maintainablity of your code. + +In paraphrasing the Zen of Python: "[decorators] are one honking great idea -- let's do more of those!" + +To make it easier to implement them, here are three examples of copy/pastable code to get you started. + +---:1 + +Don't forget to add these import statements. Although it is *not* necessary, using `@wraps` helps keep some of the metadata of your function intact. [See docs](https://docs.python.org/3/library/functools.html#functools.wraps). Also, we use the `isawaitable` pattern here to allow the route handlers to by regular or asynchronous functions. + +:--:1 + +```python +from inspect import isawaitable +from functools import wraps +``` + +:--- + +### With args + +---:1 + +Often, you will want a decorator that will *always* need arguments. Therefore, when it is implemented you will always be calling it. + +```python +@app.get("/") +@foobar(1, 2) +async def handler(request: Request): + return text("hi") +``` + +:--:1 + +```python +def foobar(arg1, arg2): + def decorator(f): + @wraps(f) + async def decorated_function(request, *args, **kwargs): + + response = f(request, *args, **kwargs) + if isawaitable(response): + response = await response + + return response + + return decorated_function + + return decorator +``` + +:--- + +### Without args + +---:1 + +Sometimes you want a decorator that will not take arguments. When this is the case, it is a nice convenience not to have to call it + +```python +@app.get("/") +@foobar +async def handler(request: Request): + return text("hi") +``` + +:--:1 + +```python +def foobar(func): + def decorator(f): + @wraps(f) + async def decorated_function(request, *args, **kwargs): + + response = f(request, *args, **kwargs) + if isawaitable(response): + response = await response + + return response + + return decorated_function + + return decorator(func) +``` + +:--- + +### With or Without args + +---:1 + +If you want a decorator with the ability to be called or not, you can follow this pattern. Using keyword only arguments is not necessary, but might make implementation simpler. + +```python +@app.get("/") +@foobar(arg1=1, arg2=2) +async def handler(request: Request): + return text("hi") +``` + +```python +@app.get("/") +@foobar +async def handler(request: Request): + return text("hi") +``` + +:--:1 + +```python +def foobar(maybe_func=None, *, arg1=None, arg2=None): + def decorator(f): + @wraps(f) + async def decorated_function(request, *args, **kwargs): + + response = f(request, *args, **kwargs) + if isawaitable(response): + response = await response + + return response + + return decorated_function + + return decorator(maybe_func) if maybe_func else decorator +``` + +:--- diff --git a/src/ru/guide/best-practices/exceptions.md b/src/ru/guide/best-practices/exceptions.md new file mode 100644 index 0000000000..42c2a44936 --- /dev/null +++ b/src/ru/guide/best-practices/exceptions.md @@ -0,0 +1,482 @@ +# Exceptions + +## Using Sanic exceptions + +Sometimes you just need to tell Sanic to halt execution of a handler and send back a status code response. You can raise a `SanicException` for this and Sanic will do the rest for you. + +You can pass an optional `status_code` argument. By default, a SanicException will return an internal server error 500 response. + +```python +from sanic.exceptions import SanicException + +@app.route("/youshallnotpass") +async def no_no(request): + raise SanicException("Something went wrong.", status_code=501) +``` + +Sanic provides a number of standard exceptions. They each automatically will raise the appropriate HTTP status code in your response. [Check the API reference](https://sanic.readthedocs.io/en/latest/sanic/api_reference.html#module-sanic.exceptions) for more details. + +---:1 + +The more common exceptions you _should_ implement yourself include: + +- `InvalidUsage` (400) +- `Unauthorized` (401) +- `Forbidden` (403) +- `NotFound` (404) +- `ServerError` (500) + +:--:1 + +```python +from sanic import exceptions + +@app.route("/login") +async def login(request): + user = await some_login_func(request) + if not user: + raise exceptions.NotFound( + f"Could not find user with username={request.json.username}" + ) + ... +``` + +:--- + +## Exception properties + +All exceptions in Sanic derive from `SanicException`. That class has a few properties on it that assist the developer in consistently reporting their exceptions across an application. + +- `message` +- `status_code` +- `quiet` +- `context` +- `extra` + +All of these properties can be passed to the exception when it is created, but the first three can also be used as class variables as we will see. + +---:1 +### `message` + +The `message` property obviously controls the message that will be displayed as with any other exception in Python. What is particularly useful is that you can set the `message` property on the class definition allowing for easy standardization of language across an application :--:1 +```python +class CustomError(SanicException): + message = "Something bad happened" + +raise CustomError +# or +raise CustomError("Override the default message with something else") +``` +:--- + +---:1 +### `status_code` + +This property is used to set the response code when the exception is raised. This can particularly be useful when creating custom 400 series exceptions that are usually in response to bad information coming from the client. :--:1 +```python +class TeapotError(SanicException): + status_code = 418 + message = "Sorry, I cannot brew coffee" + +raise TeapotError +# or +raise TeapotError(status_code=400) +``` +:--- + +---:1 +### `quiet` + +By default, exceptions will be output by Sanic to the `error_logger`. Sometimes this may not be desirable, especially if you are using exceptions to trigger events in exception handlers (see [the following section](./exceptions.md#handling)). You can suppress the log output using `quiet=True`. :--:1 +```python +class SilentError(SanicException): + message = "Something happened, but not shown in logs" + quiet = True + +raise SilentError +# or +raise InvalidUsage("blah blah", quiet=True) +``` +:--- + +---:1 Sometimes while debugging you may want to globally ignore the `quiet=True` property. You can force Sanic to log out all exceptions regardless of this property using `NOISY_EXCEPTIONS` + +*Added in v21.12* :--:1 +```python +app.config.NOISY_EXCEPTIONS = True +``` +:--- + +---:1 +### `extra` + +See [contextual exceptions](./exceptions.md#contextual-exceptions) + +*Added in v21.12* :--:1 +```python +raise SanicException(..., extra={"name": "Adam"}) +``` +:--- + +---:1 +### `context` + +See [contextual exceptions](./exceptions.md#contextual-exceptions) + +*Added in v21.12* :--:1 +```python +raise SanicException(..., context={"foo": "bar"}) +``` +:--- + + +## Handling + +Sanic handles exceptions automatically by rendering an error page, so in many cases you don't need to handle them yourself. However, if you would like more control on what to do when an exception is raised, you can implement a handler yourself. + +Sanic provides a decorator for this, which applies to not only the Sanic standard exceptions, but **any** exception that your application might throw. + +---:1 + +The easiest method to add a handler is to use `@app.exception()` and pass it one or more exceptions. + +:--:1 + +```python +from sanic.exceptions import NotFound + +@app.exception(NotFound, SomeCustomException) +async def ignore_404s(request, exception): + return text("Yep, I totally found the page: {}".format(request.url)) +``` + +:--- + +---:1 + +You can also create a catchall handler by catching `Exception`. + +:--:1 + +```python +@app.exception(Exception) +async def catch_anything(request, exception): + ... +``` + +:--- + +---:1 + +You can also use `app.error_handler.add()` to add error handlers. + +:--:1 + +```python +async def server_error_handler(request, exception): + return text("Oops, server error", status=500) + +app.error_handler.add(Exception, server_error_handler) +``` + +:--- + +## Built-in error handling + +Sanic ships with three formats for exceptions: HTML, JSON, and text. You can see examples of them below in the [Fallback handler](#fallback-handler) section. + +---:1 You can control _per route_ which format to use with the `error_format` keyword argument. + +*Added in v21.9* :--:1 +```python +@app.request("/", error_format="text") +async def handler(request): + ... +``` + +:--- + + +## Custom error handling + +In some cases, you might want to add some more error handling functionality to what is provided by default. In that case, you can subclass Sanic's default error handler as such: + +```python +from sanic.handlers import ErrorHandler + +class CustomErrorHandler(ErrorHandler): + def default(self, request, exception): + ''' handles errors that have no error handlers assigned ''' + # You custom error handling logic... + return super().default(request, exception) + +app.error_handler = CustomErrorHandler() +``` + +## Fallback handler + +Sanic comes with three fallback exception handlers: + +1. HTML (*default*) +2. Text +3. JSON + +These handlers present differing levels of detail depending upon whether your application is in [debug mode](/guide/deployment/development.md) or not. + +### HTML + +```python +app.config.FALLBACK_ERROR_FORMAT = "html" +``` + +---:1 + +```python +app.config.DEBUG = True +``` + +![Error](~@assets/images/error-html-debug.png) + +:--:1 + +```python +app.config.DEBUG = False +``` + +![Error](~@assets/images/error-html-no-debug.png) + +:--- + +### Text + +```python +app.config.FALLBACK_ERROR_FORMAT = "text" +``` + +---:1 + +```python +app.config.DEBUG = True +``` + +```bash +$ curl localhost:8000/exc -i +HTTP/1.1 500 Internal Server Error +content-length: 590 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +⚠️ 500 — Internal Server Error +============================== +That time when that thing broke that other thing? That happened. + +ServerError: That time when that thing broke that other thing? That happened. while handling path /exc +Traceback of __BASE__ (most recent call last): + + ServerError: That time when that thing broke that other thing? That happened. + File /path/to/sanic/app.py, line 986, in handle_request + response = await response + + File /path/to/server.py, line 222, in exc + raise ServerError( +``` + +:--:1 + +```python +app.config.DEBUG = False +``` + +```bash +$ curl localhost:8000/exc -i +HTTP/1.1 500 Internal Server Error +content-length: 134 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +⚠️ 500 — Internal Server Error +============================== +That time when that thing broke that other thing? That happened. +``` + +:--- + +### JSON + +```python +app.config.FALLBACK_ERROR_FORMAT = "json" +``` + +---:1 + +```python +app.config.DEBUG = True +``` + +```bash +$ curl localhost:8000/exc -i +HTTP/1.1 500 Internal Server Error +content-length: 129 +connection: keep-alive +content-type: application/json + +{ + "description": "Internal Server Error", + "status": 500, + "message": "That time when that thing broke that other thing? That happened.", + "path": "/exc", + "args": {}, + "exceptions": [ + { + "type": "ServerError", + "exception": "That time when that thing broke that other thing? That happened.", + "frames": [ + { + "file": "/path/to/sanic/app.py", + "line": 986, + "name": "handle_request", + "src": "response = await response" + }, + { + "file": "/path/to/server.py", + "line": 222, + "name": "exc", + "src": "raise ServerError(" + } + ] + } + ] +} + + +``` + +:--:1 + +```python +app.config.DEBUG = False +``` + +```bash +$ curl localhost:8000/exc -i +HTTP/1.1 500 Internal Server Error +content-length: 530 +connection: keep-alive +content-type: application/json + +{ + "description": "Internal Server Error", + "status": 500, + "message": "That time when that thing broke that other thing? That happened." +} + +``` + +:--- + +### Auto + +Sanic also provides an option for guessing which fallback option to use. This is still an **experimental feature**. + +```python +app.config.FALLBACK_ERROR_FORMAT = "auto" +``` +## Contextual Exceptions + +Default exception messages that simplify the ability to consistently raise exceptions throughout your application. + +```python +class TeapotError(SanicException): + status_code = 418 + message = "Sorry, I cannot brew coffee" + +raise TeapotError +``` + +But this lacks two things: + +1. A dynamic and predictable message format +2. The ability to add additional context to an error message (more on this in a moment) + +*Added in v21.12* + +### Dynamic and predictable message using `extra` + +Sanic exceptions can be raised using `extra` keyword arguments to provide additional information to a raised exception instance. + +```python +class TeapotError(SanicException): + status_code = 418 + + @property + def message(self): + return f"Sorry {self.extra['name']}, I cannot make you coffee" + +raise TeapotError(extra={"name": "Adam"}) +``` + +The new feature allows the passing of `extra` meta to the exception instance, which can be particularly useful as in the above example to pass dynamic data into the message text. This `extra` info object **will be suppressed** when in `PRODUCTION` mode, but displayed in `DEVELOPMENT` mode. + +---:1 **PRODUCTION** + +![image](https://user-images.githubusercontent.com/166269/139014161-cda67cd1-843f-4ad2-9fa1-acb94a59fc4d.png) :--:1 **DEVELOPMENT** + +![image](https://user-images.githubusercontent.com/166269/139014121-0596b084-b3c5-4adb-994e-31ba6eba6dad.png) :--- + +### Additional `context` to an error message + +Sanic exceptions can also be raised with a `context` argument to pass intended information along to the user about what happened. This is particularly useful when creating microservices or an API intended to pass error messages in JSON format. In this use case, we want to have some context around the exception beyond just a parseable error message to return details to the client. + +```python +raise TeapotError(context={"foo": "bar"}) +``` + +This is information **that we want** to always be passed in the error (when it is available). Here is what it should look like: + +---:1 **PRODUCTION** + +```json +{ + "description": "I'm a teapot", + "status": 418, + "message": "Sorry Adam, I cannot make you coffee", + "context": { + "foo": "bar" + } +} +``` +:--:1 **DEVELOPMENT** + +```json +{ + "description": "I'm a teapot", + "status": 418, + "message": "Sorry Adam, I cannot make you coffee", + "context": { + "foo": "bar" + }, + "path": "/", + "args": {}, + "exceptions": [ + { + "type": "TeapotError", + "exception": "Sorry Adam, I cannot make you coffee", + "frames": [ + { + "file": "handle_request", + "line": 83, + "name": "handle_request", + "src": "" + }, + { + "file": "/tmp/p.py", + "line": 17, + "name": "handler", + "src": "raise TeapotError(" + } + ] + } + ] +} +``` +:--- diff --git a/src/ru/guide/best-practices/logging.md b/src/ru/guide/best-practices/logging.md new file mode 100644 index 0000000000..427244df96 --- /dev/null +++ b/src/ru/guide/best-practices/logging.md @@ -0,0 +1,89 @@ +# Logging + +Sanic allows you to do different types of logging (access log, error log) on the requests based on the [Python logging API](https://docs.python.org/3/howto/logging.html). You should have some basic knowledge on Python logging if you want to create a new configuration. + +## Quick Start + +---:1 + +A simple example using default settings would be like this: :--:1 +```python +from sanic import Sanic +from sanic.log import logger +from sanic.response import text + +app = Sanic('logging_example') + +@app.route('/') +async def test(request): + logger.info('Here is your log') + return text('Hello World!') + +if __name__ == "__main__": + app.run(debug=True, access_log=True) +``` +:--- + +After the server is running, you should see logs like this. +```text +[2021-01-04 15:26:26 +0200] [1929659] [INFO] Goin' Fast @ http://127.0.0.1:8000 +[2021-01-04 15:26:26 +0200] [1929659] [INFO] Starting worker [1929659] +``` + +You can send a request to server and it will print the log messages. +```text +[2021-01-04 15:26:28 +0200] [1929659] [INFO] Here is your log +[2021-01-04 15:26:28 +0200] - (sanic.access)[INFO][127.0.0.1:44228]: GET http://localhost:8000/ 200 -1 +``` + +## Changing Sanic loggers + +To use your own logging config, simply use `logging.config.dictConfig`, or pass `log_config` when you initialize Sanic app. + +```python +app = Sanic('logging_example', log_config=LOGGING_CONFIG) + +if __name__ == "__main__": + app.run(access_log=False) +``` + +::: tip FYI Logging in Python is a relatively cheap operation. However, if you are serving a high number of requests and performance is a concern, all of that time logging out access logs adds up and becomes quite expensive. + +This is a good opportunity to place Sanic behind a proxy (like nginx) and to do your access logging there. You will see a *significant* increase in overall performance by disabling the `access_log`. + +For optimal production performance, it is advised to run Sanic with `debug` and `access_log` disabled: `app.run(debug=False, access_log=False)` ::: + +## Configuration + +Sanic's default logging configuration is: `sanic.log.LOGGING_CONFIG_DEFAULTS`. + +---:1 There are three loggers used in sanic, and must be defined if you want to create your own logging configuration: + +| **Logger Name** | **Use Case** | +| --------------- | ------------------------------ | +| `sanic.root` | Used to log internal messages. | +| `sanic.error` | Used to log error logs. | +| `sanic.access` | Used to log access logs. | + :--:1 + +:--- + +### Log format + +In addition to default parameters provided by Python (`asctime`, `levelname`, `message`), Sanic provides additional parameters for access logger with. + +| Log Context Parameter | Parameter Value | Datatype | +| --------------------- | ------------------------------------ | -------- | +| `host` | `request.ip` | `str` | +| `request` | `request.method + " " + request.url` | `str` | +| `status` | `response` | `int` | +| `byte` | `len(response.body)` | `int` | + + + + +The default access log format is: + +```text +%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: %(request)s %(message)s %(status)d %(byte)d +``` diff --git a/src/ru/guide/best-practices/testing.md b/src/ru/guide/best-practices/testing.md new file mode 100644 index 0000000000..9d19d5010d --- /dev/null +++ b/src/ru/guide/best-practices/testing.md @@ -0,0 +1,3 @@ +# Тестирование + +Смотри раздел [sanic-testing](../../plugins/sanic-testing/getting-started.md) diff --git a/src/ru/guide/deployment/README.md b/src/ru/guide/deployment/README.md new file mode 100644 index 0000000000..3b048754a1 --- /dev/null +++ b/src/ru/guide/deployment/README.md @@ -0,0 +1 @@ +# Развертывание diff --git a/src/ru/guide/deployment/app-loader.md b/src/ru/guide/deployment/app-loader.md new file mode 100644 index 0000000000..960138e3a9 --- /dev/null +++ b/src/ru/guide/deployment/app-loader.md @@ -0,0 +1,75 @@ +# Dynamic Applications + +Running Sanic has been optimized to work with the CLI. If you have not read it yet, you should read [Running Sanic](./running.md#sanic-server) to become familiar with the options. + +---:1 This includes running it as a global scope object... :--:1 +```python +# server.py +app = Sanic("TestApp") + +@app.get("/") +async def handler(request: Request): + return json({"foo": "bar"}) +``` +``` +sanic path.to.server:app +``` +:--- + + +---:1 ...or, a factory function that creates the `Sanic` application object. :--:1 +```python +# server.py +def create_app(): + app = Sanic("TestApp") + + @app.get("/") + async def handler(request: Request): + return json({"foo": "bar"}) + + return app +``` +``` +sanic path.to.server:create_app --factory +``` +:--- + + +**Sometimes, this is not enough ... :thinking:** + +Introduced in [v22.9](../release-notes/v22.9.md), Sanic has an `AppLoader` object that is responsible for creating an application in the various [worker processes](./manager.md#how-sanic-server-starts-processes). You can take advantage of this if you need to create a more dynamic startup experience for your application. + +---:1 An `AppLoader` can be passed a callable that returns a `Sanic` instance. That `AppLoader` could be used with the low-level application running API. :--:1 +```python +import sys +from functools import partial + +from sanic import Request, Sanic, json +from sanic.worker.loader import AppLoader + + +def attach_endpoints(app: Sanic): + @app.get("/") + async def handler(request: Request): + return json({"app_name": request.app.name}) + + +def create_app(app_name: str) -> Sanic: + app = Sanic(app_name) + attach_endpoints(app) + return app + + +if __name__ == "__main__": + app_name = sys.argv[-1] + loader = AppLoader(factory=partial(create_app, app_name)) + app = loader.load() + app.prepare(port=9999, dev=True) + Sanic.serve(primary=app, app_loader=loader) +``` +``` +$ python path/to/server.py MyTestAppName +``` +:--- + +In the above example, the `AppLoader` is created with a `factory` that can be used to create copies of the same application across processes. When doing this, you should explicitly use the `Sanic.serve` pattern shown above so that the `AppLoader` that you create is not replaced. diff --git a/src/ru/guide/deployment/configuration.md b/src/ru/guide/deployment/configuration.md new file mode 100644 index 0000000000..9b00ab3886 --- /dev/null +++ b/src/ru/guide/deployment/configuration.md @@ -0,0 +1,237 @@ +# Configuration + +## Basics + + +---:1 + +Sanic holds the configuration in the config attribute of the application object. The configuration object is merely an object that can be modified either using dot-notation or like a dictionary. :--:1 +```python +app = Sanic("myapp") +app.config.DB_NAME = "appdb" +app.config["DB_USER"] = "appuser" +``` +:--- + +---:1 + +You can also use the `update()` method like on regular dictionaries. :--:1 +```python +db_settings = { + 'DB_HOST': 'localhost', + 'DB_NAME': 'appdb', + 'DB_USER': 'appuser' +} +app.config.update(db_settings) +``` +:--- + +::: tip It is standard practice in Sanic to name your config values in **uppercase letters**. Indeed, you may experience weird behaviors if you start mixing uppercase and lowercase names. ::: + +## Loading + +### Environment variables + +---:1 + +Any environment variables defined with the `SANIC_` prefix will be applied to the Sanic config. For example, setting `SANIC_REQUEST_TIMEOUT` will be loaded by the application automatically and fed into the `REQUEST_TIMEOUT` config variable. :--:1 +```bash +$ export SANIC_REQUEST_TIMEOUT=10 +``` +```python +>>> print(app.config.REQUEST_TIMEOUT) +10 +``` +:--- + +---:1 + +You can change the prefix that Sanic is expecting at startup. :--:1 +```bash +$ export MYAPP_REQUEST_TIMEOUT=10 +``` +```python +>>> app = Sanic(__name__, env_prefix='MYAPP_') +>>> print(app.config.REQUEST_TIMEOUT) +10 +``` +:--- + +---:1 + +You can also disable environment variable loading completely. :--:1 +```python +app = Sanic(__name__, load_env=False) +``` +:--- + +### Using Sanic.update_config + +The `Sanic` instance has a _very_ versatile method for loading config: `app.update_config`. You can feed it a path to a file, a dictionary, a class, or just about any other sort of object. + +#### From a file + +---:1 + +Let's say you have `my_config.py` file that looks like this. :--:1 +```python +# my_config.py +A = 1 +B = 2 +``` +:--- + +---:1 + +You can load this as config values by passing its path to `app.update_config`. :--:1 +```python +>>> app.update_config("/path/to/my_config.py") +>>> print(app.config.A) +1 +``` +:--- + +---:1 + +This path also accepts bash style environment variables. :--:1 +```bash +$ export my_path="/path/to" +``` +```python +app.update_config("${my_path}/my_config.py") +``` +:--- + +::: tip Just remember that you have to provide environment variables in the format `${environment_variable}` and that `$environment_variable` is not expanded (is treated as "plain" text). ::: +#### From a dict + +---:1 + +The `app.update_config` method also works on plain dictionaries. :--:1 +```python +app.update_config({"A": 1, "B": 2}) +``` +:--- + +#### From a class or object + +---:1 + +You can define your own config class, and pass it to `app.update_config` :--:1 +```python +class MyConfig: + A = 1 + B = 2 + +app.update_config(MyConfig) +``` +:--- + +---:1 + +It even could be instantiated. :--:1 +```python +app.update_config(MyConfig()) +``` +:--- + +### Type casting + +When loading from environment variables, Sanic will attempt to cast the values to expected Python types. This particularly applies to: + +- `int` +- `float` +- `bool` + +In regards to `bool`, the following _case insensitive_ values are allowed: + +- **`True`**: `y`, `yes`, `yep`, `yup`, `t`, `true`, `on`, `enable`, `enabled`, `1` +- **`False`**: `n`, `no`, `f`, `false`, `off`, `disable`, `disabled`, `0` + +If a value cannot be cast, it will default to a `str`. + +---:1 Additionally, Sanic can be configured to cast additional types using additional type converters. This should be any callable that returns the value or raises a `ValueError`. + +*Added in v21.12* :--:1 +```python +app = Sanic(..., config=Config(converters=[UUID])) +``` +:--- + +## Builtin values + + +| **Variable** | **Default** | **Description** | +| --------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| ACCESS_LOG | True | Disable or enable access log | +| AUTO_EXTEND | True | Control whether [Sanic Extensions](../../plugins/sanic-ext/getting-started.md) will load if it is in the existing virtual environment | +| AUTO_RELOAD | True | Control whether the application will automatically reload when a file changes | +| EVENT_AUTOREGISTER | True | When `True` using the `app.event()` method on a non-existing signal will automatically create it and not raise an exception | +| FALLBACK_ERROR_FORMAT | html | Format of error response if an exception is not caught and handled | +| FORWARDED_FOR_HEADER | X-Forwarded-For | The name of "X-Forwarded-For" HTTP header that contains client and proxy ip | +| FORWARDED_SECRET | None | Used to securely identify a specific proxy server (see below) | +| GRACEFUL_SHUTDOWN_TIMEOUT | 15.0 | How long to wait to force close non-idle connection (sec) | +| INSPECTOR | False | Whether to enable the Inspector | +| INSPECTOR_HOST | localhost | The host for the Inspector | +| INSPECTOR_PORT | 6457 | The port for the Inspector | +| INSPECTOR_TLS_KEY | - | The TLS key for the Inspector | +| INSPECTOR_TLS_CERT | - | The TLS certificate for the Inspector | +| INSPECTOR_API_KEY | - | The API key for the Inspector | +| KEEP_ALIVE_TIMEOUT | 5 | How long to hold a TCP connection open (sec) | +| KEEP_ALIVE | True | Disables keep-alive when False | +| MOTD | True | Whether to display the MOTD (message of the day) at startup | +| MOTD_DISPLAY | {} | Key/value pairs to display additional, arbitrary data in the MOTD | +| NOISY_EXCEPTIONS | False | Force all `quiet` exceptions to be logged | +| PROXIES_COUNT | None | The number of proxy servers in front of the app (e.g. nginx; see below) | +| REAL_IP_HEADER | None | The name of "X-Real-IP" HTTP header that contains real client ip | +| REGISTER | True | Whether the app registry should be enabled | +| REQUEST_BUFFER_SIZE | 65536 | Request buffer size before request is paused, default is 64 Kib | +| REQUEST_ID_HEADER | X-Request-ID | The name of "X-Request-ID" HTTP header that contains request/correlation ID | +| REQUEST_MAX_SIZE | 100000000 | How big a request may be (bytes), default is 100 megabytes | +| REQUEST_TIMEOUT | 60 | How long a request can take to arrive (sec) | +| RESPONSE_TIMEOUT | 60 | How long a response can take to process (sec) | +| USE_UVLOOP | True | Whether to override the loop policy to use `uvloop`. Supported only with `app.run`. | +| WEBSOCKET_MAX_SIZE | 2^20 | Maximum size for incoming messages (bytes) | +| WEBSOCKET_PING_INTERVAL | 20 | A Ping frame is sent every ping_interval seconds. | +| WEBSOCKET_PING_TIMEOUT | 20 | Connection is closed when Pong is not received after ping_timeout seconds | + +::: tip FYI +- The `USE_UVLOOP` value will be ignored if running with Gunicorn. Defaults to `False` on non-supported platforms (Windows). +- The `WEBSOCKET_` values will be ignored if in ASGI mode. +- v21.12 added: `AUTO_EXTEND`, `MOTD`, `MOTD_DISPLAY`, `NOISY_EXCEPTIONS` +- v22.9 added: `INSPECTOR` +- v22.12 added: `INSPECTOR_HOST`, `INSPECTOR_PORT`, `INSPECTOR_TLS_KEY`, `INSPECTOR_TLS_CERT`, `INSPECTOR_API_KEY` ::: + +## Timeouts + +### REQUEST_TIMEOUT + +A request timeout measures the duration of time between the instant when a new open TCP connection is passed to the Sanic backend server, and the instant when the whole HTTP request is received. If the time taken exceeds the `REQUEST_TIMEOUT` value (in seconds), this is considered a Client Error so Sanic generates an `HTTP 408` response and sends that to the client. Set this parameter's value higher if your clients routinely pass very large request payloads or upload requests very slowly. + +### RESPONSE_TIMEOUT + +A response timeout measures the duration of time between the instant the Sanic server passes the HTTP request to the Sanic App, and the instant a HTTP response is sent to the client. If the time taken exceeds the `RESPONSE_TIMEOUT` value (in seconds), this is considered a Server Error so Sanic generates an `HTTP 503` response and sends that to the client. Set this parameter's value higher if your application is likely to have long-running process that delay the generation of a response. + +### KEEP_ALIVE_TIMEOUT + +#### What is Keep Alive? And what does the Keep Alive Timeout value do? + +`Keep-Alive` is a HTTP feature introduced in `HTTP 1.1`. When sending a HTTP request, the client (usually a web browser application) can set a `Keep-Alive` header to indicate the http server (Sanic) to not close the TCP connection after it has send the response. This allows the client to reuse the existing TCP connection to send subsequent HTTP requests, and ensures more efficient network traffic for both the client and the server. + +The `KEEP_ALIVE` config variable is set to `True` in Sanic by default. If you don't need this feature in your application, set it to `False` to cause all client connections to close immediately after a response is sent, regardless of the `Keep-Alive` header on the request. + +The amount of time the server holds the TCP connection open is decided by the server itself. In Sanic, that value is configured using the `KEEP_ALIVE_TIMEOUT` value. By default, it is set to 5 seconds. This is the same default setting as the Apache HTTP server and is a good balance between allowing enough time for the client to send a new request, and not holding open too many connections at once. Do not exceed 75 seconds unless you know your clients are using a browser which supports TCP connections held open for that long. + +For reference: + +* Apache httpd server default keepalive timeout = 5 seconds +* Nginx server default keepalive timeout = 75 seconds +* Nginx performance tuning guidelines uses keepalive = 15 seconds +* IE (5-9) client hard keepalive limit = 60 seconds +* Firefox client hard keepalive limit = 115 seconds +* Opera 11 client hard keepalive limit = 120 seconds +* Chrome 13+ client keepalive limit > 300+ seconds + +## Proxy configuration + +See [proxy configuration section](/guide/advanced/proxy-headers.md) diff --git a/src/ru/guide/deployment/development.md b/src/ru/guide/deployment/development.md new file mode 100644 index 0000000000..881a0be291 --- /dev/null +++ b/src/ru/guide/deployment/development.md @@ -0,0 +1,97 @@ +# Development + +The first thing that should be mentioned is that the webserver that is integrated into Sanic is **not** just a development server. + +It is production ready out-of-the-box, *unless you enable in debug mode*. + +## Debug mode + +By setting the debug mode, Sanic will be more verbose in its output and will disable several run-time optimizations. + +```python +from sanic import Sanic +from sanic.response import json + +app = Sanic(__name__) + +@app.route("/") +async def hello_world(request): + return json({"hello": "world"}) + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=1234, debug=True) +``` + +::: warning +Sanic's debug mode will slow down the server's performance and is therefore advised to enable it only in development environments. +::: +## Automatic Reloader + +---:1 + +Sanic offers a way to enable or disable the Automatic Reloader. The `auto_reload` argument will activate or deactivate the Automatic Reloader. Every time a Python file is changed, the reloader will restart your application automatically. This is very convenient while developing. :--:1 +```python +app.run(auto_reload=True) +``` +:--- + +---:1 If you have additional directories that you would like to automatically reload on file save (for example, a directory of HTML templates), you can add that at run time. :--:1 +```python +app.run(auto_reload=True, reload_dir="/path/to/templates") +# or multiple directories +app.run(auto_reload=True, reload_dir=["/path/to/one", "/path/to/two"]) +``` +:--- + +## Best of both worlds +---:1 If you would like to be in debug mode **and** have the Automatic Reloader running, you can pass `dev=True`. This is equivalent to **debug + auto reload**. + +*Added in v22.3* :--:1 +```python +app.run(dev=True) +``` +:--- + +## Automatic TLS certificate + +When running in `DEBUG` mode, you can ask Sanic to handle setting up localhost temporary TLS certificates. This is helpful if you want to access your local development environment with `https://`. + +This functionality is provided by either [mkcert](https://github.com/FiloSottile/mkcert) or [trustme](https://github.com/python-trio/trustme). Both are good choices, but there are some differences. `trustme` is a Python library and can be installed into your environment with `pip`. This makes for easy envrionment handling, but it is not compatible when running a HTTP/3 server. `mkcert` might be a more involved installation process, but can install a local CA and make it easier to use. + +---:1 You can choose which platform to use by setting `config.LOCAL_CERT_CREATOR`. When set to `"auto"`, it will select either option, preferring `mkcert` if possible. :--:1 +```python +app.config.LOCAL_CERT_CREATOR = "auto" +app.config.LOCAL_CERT_CREATOR = "mkcert" +app.config.LOCAL_CERT_CREATOR = "trustme" +``` +:--- + + +---:1 Automatic TLS can be enabled at Sanic server run time: :--:1 +``` +$ sanic path.to.server:app --auto-tls --debug +``` + +```python +app.run(debug=True, auto_tls=True) +``` +:--- + +::: warning + +Localhost TLS certificates (like those generated by both `mkcert` and `trustme`) are **NOT** suitable for production environments. If you are not familiar with how to obtain a *real* TLS certificate, checkout the [How to...](../how-to/tls.md) section. ::: + +*Added in v22.6* + +## CLI + +It should be noted that all of these have an equivalent in the Sanic CLI: + +``` +Development: + --debug Run the server in debug mode + -r, --reload, --auto-reload Watch source directory for file changes and reload on changes + -R PATH, --reload-dir PATH Extra directories to watch and reload on changes + -d, --dev debug + auto reload + --auto-tls Create a temporary TLS certificate for local development (requires mkcert or trustme) +``` diff --git a/src/ru/guide/deployment/docker.md b/src/ru/guide/deployment/docker.md new file mode 100644 index 0000000000..47f4966b27 --- /dev/null +++ b/src/ru/guide/deployment/docker.md @@ -0,0 +1,183 @@ +# Docker Deployment + +## Introduction + +For a long time, the environment has always been a difficult problem for deployment. If there are conflicting configurations in your project, you have to spend a lot of time resolving them. Fortunately, virtualization provides us with a good solution. Docker is one of them. If you don't know Docker, you can visit [Docker official website](https://www.docker.com/) to learn more. + +## Build Image + +Let's start with a simple project. We will use a Sanic project as an example. Assume the project path is `/path/to/SanicDocker`. + +---:1 + +The directory structure looks like this: + +:--:1 + +```text +# /path/to/SanicDocker +SanicDocker +├── requirements.txt +├── dockerfile +└── server.py +``` + +:--- + +---:1 + +And the `server.py` code looks like this: + +:--:1 + +```python +app = Sanic("MySanicApp") + +@app.get('/') +async def hello(request): + return text("OK!") + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8000) +``` + +:--- + +::: tip + +Please note that the host cannot be 127.0.0.1 . In docker container, 127.0.0.1 is the default network interface of the container, only the container can communicate with other containers. more information please visit [Docker network](https://docs.docker.com/engine/reference/commandline/network/) + +::: + +Code is ready, let's write the `Dockerfile`: + +```Dockerfile + +FROM sanicframework/sanic:3.8-latest + +WORKDIR /sanic + +COPY . . + +RUN pip install -r requirements.txt + +EXPOSE 8000 + +CMD ["python", "server.py"] +``` + +Run the following command to build the image: + +```shell +docker build -t my-sanic-image . +``` + +## Start Container + +---:1 + +After the image built, we can start the container use `my-sanic-image`: + +:--:1 + +```shell +docker run --name mysanic -p 8000:8000 -d my-sanic-image +``` + +:--- + +---:1 + +Now we can visit `http://localhost:8000` to see the result: + +:--:1 + +```text +OK! +``` + +:--- + +## Use docker-compose + +If your project consist of multiple services, you can use [docker-compose](https://docs.docker.com/compose/) to manage them. + +for example, we will deploy `my-sanic-image` and `nginx`, achieve through nginx access sanic server. + +---:1 + +First of all, we need prepare nginx configuration file. create a file named `mysanic.conf`: + +:--:1 + +```nginx +server { + listen 80; + listen [::]:80; + location / { + proxy_pass http://mysanic:8000/; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection upgrade; + proxy_set_header Accept-Encoding gzip; + } +} +``` + +:--- + +---:1 + +Then, we need to prepare `docker-compose.yml` file. The content follows: + +:--:1 + +```yml +version: "3" + +services: + mysanic: + image: my-sanic-image + ports: + - "8000:8000" + restart: always + + mynginx: + image: nginx:1.13.6-alpine + ports: + - "80:80" + depends_on: + - mysanic + volumes: + - ./mysanic.conf:/etc/nginx/conf.d/mysanic.conf + restart: always + +networks: + default: + driver: bridge +``` + +:--- + +---:1 + +After that, we can start them: + +:--:1 + +```shell +docker-compose up -d +``` + +:--- + +---:1 + +Now, we can visit `http://localhost:80` to see the result: + +:--:1 + +```text +OK! +``` + +:--- diff --git a/src/ru/guide/deployment/inspector.md b/src/ru/guide/deployment/inspector.md new file mode 100644 index 0000000000..81cacb3496 --- /dev/null +++ b/src/ru/guide/deployment/inspector.md @@ -0,0 +1,160 @@ +# Inspector + +The Sanic Inspector is a feature of Sanic Server. It is *only* available when running Sanic with the built-in [worker manager](./manager.md). + +It is an HTTP application that *optionally* runs in the background of your application to allow you to interact with the running instance of your application. + +::: tip INFO +The Inspector was introduced in limited capacity in v22.9, but the documentation on this page assumes you are using v22.12 or higher. +::: + +## Getting Started + +The inspector is disabled by default. To enable it, you have two options. + +---:1 Set a flag when creating your application instance. :--:1 +```python +app = Sanic("TestApp", inspector=True) +``` +:--- + +---:1 Or, set a configuration value. :--:1 +```python +app = Sanic("TestApp") +app.config.INSPECTOR = True +``` +:--- + +::: warning +If you are using the configuration value, it *must* be done early and before the main worker process starts. This means that it should either be an environment variable, or it should be set shortly after creating the application instance as shown above. +::: + +## Using the Inspector + +Once the inspector is running, you will have access to it via the CLI or by directly accessing its web API via HTTP. + +---:1 **Via CLI** +``` +$ sanic inspect +``` +:--:1 **Via HTTP** +``` +$ curl http://localhost:6457 +``` +:--- + +::: tip +Remember, the Inspector is not running on your Sanic application. It is a seperate process, with a seperate application, and exposed on a seperate socket. +::: + +## Built-in Commands + +The Inspector comes with the following built-in commands. + +| CLI Command | HTTP Action | Description | +| ------------------ | ---------------------------------------- | ------------------------------------------------------------------------ | +| `inspect` | `GET /` | Display basic details about the running application. | +| `inspect reload` | `POST /reload` | Trigger a reload of all server workers. | +| `inspect shutdown` | `POST /shutdown` | Trigger a shutdown of all processes. | +| `inspect scale N` | `POST /scale`
`{"replicas": N}` | Scale the number of workers. Where `N` is the target number of replicas. | + +## Custom Commands + +The Inspector is easily extendable to add custom commands (and endpoints). + +---:1 Subclass the `Inspector` class and create arbitrary methods. As long as the method name is not preceded by an underscore (`_`), then the name of the method will be a new subcommand on the inspector. :--:1 +```python +from sanic import json +from sanic.worker.inspector import Inspector + + +class MyInspector(Inspector): + async def something(self, *args, **kwargs): + print(args) + print(kwargs) + + +app = Sanic("TestApp", inspector_class=MyInspector, inspector=True) +``` +:--- + +This will expose custom methods in the general pattern: + +- CLI: `sanic inspect ` +- HTTP: `POST /` + +It is important to note that the arguments that the new method accepts are derived from how you intend to use the command. For example, the above `something` method accepts all positional and keyword based parameters. + +---:1 In the CLI, the positional and keyword parameters are passed as either positional or keyword arguments to your method. All values will be a `str` with the following exceptions: + +- A keyword parameter with no assigned value will be: `True` +- Unless the parameter is prefixed with `no-`, then it will be: `False` :--:1 +``` +$ sanic inspect something one two three --four --no-five --six=6 +``` +In your application log console, you will see: +``` +('one', 'two', 'three') +{'four': True, 'five': False, 'six': '6'} +``` +:--- + +---:1 The same can be achieved by hitting the API directly. You can pass arguments to the method by exposing them in a JSON payload. The only thing to note is that the positional arguments should be exposed as `{"args": [...]}`. :--:1 +``` +$ curl http://localhost:6457/something \ + --json '{"args":["one", "two", "three"], "four":true, "five":false, "six":6}' +``` +In your application log console, you will see: +``` +('one', 'two', 'three') +{'four': True, 'five': False, 'six': 6} +``` +:--- + + +## Using in production + +::: warning +Before exposing the Inspector on a product, please consider all of the options in this section carefully. +::: + +When running Inspector on a remote production instance, you can protect the endpoints by requiring TLS encryption, and requiring API key authentication. + +### TLS encryption + +---:1 To the Inspector HTTP instance over TLS, pass the paths to your certificate and key. :--:1 +```python +app.config.INSPECTOR_TLS_CERT = "/path/to/cert.pem" +app.config.INSPECTOR_TLS_KEY = "/path/to/key.pem" +``` +:--- + +---:1 This will require use of the `--secure` flag, or `https://`. :--:1 +``` +$ sanic insect --secure --host= +``` +``` +$ curl https://:6457 +``` +:--- + +### API Key Authentication + +---:1 You can secure the API with bearer token authentication. :--:1 +```python +app.config.INSPECTOR_API_KEY = "Super-Secret-200" +``` +:--- + +---:1 This will require the `--api-key` parameter, or bearer token authorization header. :--:1 +``` +$ sanic inspect --api-key=Super-Secret-200 +``` +``` +$ curl http://localhost:6457 -H "Authorization: Bearer Super-Secret-200" +``` +:--- + +## Configuration + +See [configuration](./configuration.md) diff --git a/src/ru/guide/deployment/kubernetes.md b/src/ru/guide/deployment/kubernetes.md new file mode 100644 index 0000000000..8d6340de1c --- /dev/null +++ b/src/ru/guide/deployment/kubernetes.md @@ -0,0 +1 @@ +# Kubernetes diff --git a/src/ru/guide/deployment/manager.md b/src/ru/guide/deployment/manager.md new file mode 100644 index 0000000000..4c4153b14d --- /dev/null +++ b/src/ru/guide/deployment/manager.md @@ -0,0 +1,290 @@ +# Worker Manager + +The worker manager and its functionality was introduced in version 22.9. + +*The details of this section are intended for more advanced usages and **not** necessary to get started.* + +The purpose of the manager is to create consistency and flexibility between development and production environments. Whether you intend to run a single worker, or multiple workers, whether with, or without auto-reload: the experience will be the same. + + +In general it looks like this: + +![](https://user-images.githubusercontent.com/166269/178677618-3b4089c3-6c6a-4ecc-8d7a-7eba2a7f29b0.png) + +When you run Sanic, the main process instantiates a `WorkerManager`. That manager is in charge of running one or more `WorkerProcess`. There generally are two kinds of processes: + +- server processes, and +- non-server processes. + +For the sake of ease, the User Guide generally will use the term "worker" or "worker process" to mean a server process, and "Manager" to mean the single worker manager running in your main process. + +## How Sanic Server starts processes + +Sanic will start processes using the [spawn](https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods) start method. This means that for every process/worker, the global scope of your application will be run on its own thread. The practical impact of this that *if* you do not run Sanic with the CLI, you will need to nest the execution code inside a block to make sure it only runs on `__main__`. + +```python +if __name__ == "__main__": + app.run() +``` + +If you do not, you are likely to see an error message like this: + +``` +sanic.exceptions.ServerError: Sanic server could not start: [Errno 98] Address already in use. + +This may have happened if you are running Sanic in the global scope and not inside of a `if __name__ == "__main__"` block. + +See more information: https://sanic.dev/en/guide/deployment/manager.html#how-sanic-server-starts-processes +``` + +The likely fix for this problem is nesting your Sanic run call inside of the `__name__ == "__main__"` block. If you continue to receive this message after nesting, or if you see this while using the CLI, then it means the port you are trying to use is not available on your machine and you must select another port. + +### Starting a worker + +All worker processes *must* send an acknowledgement when starting. This happens under the hood, and you as a developer do not need to do anything. However, the Manager will exit with a status code `1` if one or more workers do not send that `ack` message, or a worker process throws an exception while trying to start. If no exceptions are encountered, the Manager will wait for up to thirty (30) seconds for the acknowledgement. + +---:1 In the situation when you know that you will need more time to start, you can monkeypatch the Manager. The threshold does not include anything inside of a listener, and is limited to the execution time of everything in the global scope of your application. + +If you run into this issue, it may indicate a need to look deeper into what is causing the slow startup. :--:1 +```python +from sanic.worker.manager import WorkerManager + +WorkerManager.THRESHOLD = 100 # Value is in 0.1s +``` +:--- + +See [worker ack](#worker-ack) for more information. + +---:1 As stated above, Sanic will use [spawn](https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods) to start worker processes. If you would like to change this behavior and are aware of the implications of using different start methods, you can modify as shown here. :--:1 +```python +from sanic import Sanic + +Sanic.start_method = "fork" +``` +:--- + + +### Worker ack + +When all of your workers are running in a subprocess a potential problem is created: deadlock. This can occur when the child processes cease to function, but the main process is unaware that this happened. Therefore, Sanic servers will automatically send an `ack` message (short for acknowledge) to the main process after startup. + +In version 22.9, the `ack` timeout was short and limited to `5s`. In version 22.12, the timeout was lengthened to `30s`. If your application is shutting down after thirty seconds then it might be necessary to manually increase this threshhold. + +---:1 The value of `WorkerManager.THRESHOLD` is in `0.1s` increments. Therefore, to set it to one minute, you should set the value to `600`. + +This value should be set as early as possible in your application, and should ideally happen in the global scope. Setting it after the main process has started will not work. :--:1 +```python +from sanic.worker.manager import WorkerManager + +WorkerManager.THRESHOLD = 600 +``` +:--- + + + + +::: new NEW in v22.12 +### Zero downtime restarts + +By default, when restarting workers, Sanic will teardown the existing process first before starting a new one. + +If you are intending to use the restart functionality in production then you may be interested in having zero-downtime reloading. This can be accomplished by forcing the reloader to change the order to start a new process, wait for it to [ack](#worker-ack), and then teardown the old process. + +---:1 From the multiplexer, use the `zero_downtime` argument :--:1 +```python +app.m.restart(zero_downtime=True) +``` +:--- + +*Added in v22.12* +::: + +## Using shared context between worker processes + +Python provides a few methods for [exchanging objects](https://docs.python.org/3/library/multiprocessing.html#exchanging-objects-between-processes), [synchronizing](https://docs.python.org/3/library/multiprocessing.html#synchronization-between-processes), and [sharing state](https://docs.python.org/3/library/multiprocessing.html#sharing-state-between-processes) between processes. This usually involves objects from the `multiprocessing` and `ctypes` modules. + +If you are familiar with these objects and how to work with them, you will be happy to know that Sanic provides an API for sharing these objects between your worker processes. If you are not familiar, you are encouraged to read through the Python documentation linked above and try some of the examples before proceeding with implementing shared context. + +Similar to how [application context](../basics/app.md#application-context) allows an applicaiton to share state across the lifetime of the application with `app.ctx`, shared context provides the same for the special objects mentioned above. This context is available as `app.shared_ctx` and should **ONLY** be used to share objects intended for this purpose. + +The `shared_ctx` will: + +- *NOT* share regular objects like `int`, `dict`, or `list` +- *NOT* share state between Sanic instances running on different machines +- *NOT* share state to non-worker processes +- **only** share state between server workers managed by the same Manager + +Attaching an inappropriate object to `shared_ctx` will likely result in a warning, and not an error. You should be careful to not accidentally add an unsafe object to `shared_ctx` as it may not work as expected. If you are directed here because of one of those warnings, you might have accidentally used an unsafe object in `shared_ctx`. + +---:1 In order to create a shared object you **must** create it in the main process and attach it inside of the `main_process_start` listener. :--:1 +```python +from multiprocessing import Queue + +@app.main_process_start +async def main_process_start(app): + app.shared_ctx.queue = Queue() +``` +:--- + +Trying to attach to the `shared_ctx` object outside of this listener may result in a `RuntimeError`. + +---:1 After creating the objects in the `main_process_start` listener and attaching to the `shared_ctx`, they will be available in your workers wherever the application instance is available (example: listeners, middleware, request handlers). :--:1 +```python +from multiprocessing import Queue + +@app.get("") +async def handler(request): + request.app.shared_ctx.queue.put(1) + ... +``` +:--- + +## Access to the multiplexer + +The application instance has access to an object that provides access to interacting with the Manager and other worker processes. The object is attached as the `app.multiplexer` property, but it is more easily accessed by its alias: `app.m`. + +---:1 For example, you can get access to the current worker state. :--:1 +```python +@app.on_request +async def print_state(request: Request): + print(request.app.m.name) + print(request.app.m.pid) + print(request.app.m.state) +``` +``` +Sanic-Server-0-0 +99999 +{'server': True, 'state': 'ACKED', 'pid': 99999, 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), 'starts': 2, 'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc)} +``` +:--- + +---:1 The `multiplexer` also has access to terminate the Manager, or restart worker processes :--:1 +```python +# shutdown the entire application and all processes +app.m.name.terminate() + +# restart the current worker only +app.m.name.restart() + +# restart specific workers only (comma delimited) +app.m.name.restart("Sanic-Server-4-0,Sanic-Server-7-0") + +# restart ALL workers +app.m.name.restart(all_workers=True) # Available v22.12+ +``` +:--- + +## Worker state + +---:1 As shown above, the `multiplexer` has access to report upon the state of the current running worker. However, it also contains the state for ALL processes running. :--:1 +```python +@app.on_request +async def print_state(request: Request): + print(request.app.m.workers) +``` +``` +{ + 'Sanic-Main': {'pid': 99997}, + 'Sanic-Server-0-0': { + 'server': True, + 'state': 'ACKED', + 'pid': 9999, + 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), + 'starts': 2, + 'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc) + }, + 'Sanic-Reloader-0': { + 'server': False, + 'state': 'STARTED', + 'pid': 99998, + 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), + 'starts': 1 + } +} +``` +:--- + + +## Built-in non-server processes + +As mentioned, the Manager also has the ability to run non-server processes. Sanic comes with two built-in types of non-server processes, and allows for [creating custom processes](#running-custom-processes). + +The two built-in processes are + +- the [auto-reloader](./development.md#automatic-reloader), optionally enabled to watch the file system for changes and trigger a restart +- [inspector](#inspector), optionally enabled to provide external access to the state of the running instance + +## Inspector + +Sanic has the ability to expose the state and the functionality of the `multiplexer` to the CLI. Currently, this requires the CLI command to be run on the same machine as the running Sanic instance. By default the inspector is disabled. + +---:1 To enable it, set the config value to `True`. :--:1 +```python +app.config.INSPECTOR = True +``` +:--- + +You will now have access to execute any of these CLI commands: + +``` +sanic inspect reload Trigger a reload of the server workers +sanic inspect shutdown Shutdown the application and all processes +sanic inspect scale N Scale the number of workers to N +sanic inspect Run a custom command +``` + +![](https://user-images.githubusercontent.com/166269/190099384-2f2f3fae-22d5-4529-b279-8446f6b5f9bd.png) + +---:1 This works by exposing a small HTTP service on your machine. You can control the location using configuration values: :--:1 +```python +app.config.INSPECTOR_HOST = "localhost" +app.config.INSPECTOR_PORT = 6457 +``` +:--- + +[Learn more](./inspector.md) to find out what is possible with the Inspector. + +## Running custom processes + +To run a managed custom process on Sanic, you must create a callable. If that process is meant to be long-running, then it should handle a shutdown call by a `SIGINT` or `SIGTERM` signal. + +---:1 The simplest method for doing that in Python will be to just wrap your loop in `KeyboardInterrupt`. + +If you intend to run another application, like a bot, then it is likely that it already has capability to handle this signal and you likely do not need to do anything. :--:1 +```python +from time import sleep + +def my_process(foo): + try: + while True: + sleep(1) + except KeyboardInterrupt: + print("done") +``` +:--- + +---:1 That callable must be registered in the `main_process_ready` listener. It is important to note that is is **NOT** the same location that you should register [shared context](#using-shared-context-between-worker-processes) objects. :--:1 +```python +@app.main_process_ready +async def ready(app: Sanic, _): +# app.manager.manage(, , ) + app.manager.manage("MyProcess", my_process, {"foo": "bar"}) +``` +:--- + +## Single process mode + +---:1 If you would like to opt out of running multiple processes, you can run Sanic in a single process only. In this case, the Manager will not run. You will also not have access to any features that require processes (auto-reload, the inspector, etc). :--:1 +```python +if __name__ == "__main__": + app.run(single_process=True) +``` +```python +if __name__ == "__main__": + app.prepare(single_process=True) + Sanic.serve_single() +``` +``` +sanic path.to.server:app --single-process +``` +:--- diff --git a/src/ru/guide/deployment/nginx.md b/src/ru/guide/deployment/nginx.md new file mode 100644 index 0000000000..18fb68c4a3 --- /dev/null +++ b/src/ru/guide/deployment/nginx.md @@ -0,0 +1,186 @@ +# Nginx Deployment + +## Introduction + + +Although Sanic can be run directly on Internet, it may be useful to use a proxy server such as Nginx in front of it. This is particularly useful for running multiple virtual hosts on the same IP, serving NodeJS or other services beside a single Sanic app, and it also allows for efficient serving of static files. SSL and HTTP/2 are also easily implemented on such proxy. + +We are setting the Sanic app to serve only locally at `127.0.0.1:8000`, while the Nginx installation is responsible for providing the service to public Internet on domain `example.com`. Static files will be served from `/var/www/`. + + +## Proxied Sanic app + +The app needs to be setup with a secret key used to identify a trusted proxy, so that real client IP and other information can be identified. This protects against anyone on the Internet sending fake headers to spoof their IP addresses and other details. Choose any random string and configure it both on the app and in Nginx config. + +```python +from sanic import Sanic +from sanic.response import text + +app = Sanic("proxied_example") +app.config.FORWARDED_SECRET = "YOUR SECRET" + +@app.get("/") +def index(request): + # This should display external (public) addresses: + return text( + f"{request.remote_addr} connected to {request.url_for('index')}\n" + f"Forwarded: {request.forwarded}\n" + ) + +if __name__ == "__main__": + app.run(host="127.0.0.1", port=8000, workers=8, access_log=False) +``` + +Since this is going to be a system service, save your code to `/srv/sanicexample/sanicexample.py`. + +For testing, run your app in a terminal. + +## Nginx configuration + +Quite much configuration is required to allow fast transparent proxying, but for the most part these don't need to be modified, so bear with me. + +Upstream servers need to be configured in a separate `upstream` block to enable HTTP keep-alive, which can drastically improve performance, so we use this instead of directly providing an upstream address in `proxy_pass` directive. In this example, the upstream section is named by `server_name`, i.e. the public domain name, which then also gets passed to Sanic in the `Host` header. You may change the naming as you see fit. Multiple servers may also be provided for load balancing and failover. + +Change the two occurrences of `example.com` to your true domain name, and instead of `YOUR SECRET` use the secret you chose for your app. + +```nginx +upstream example.com { + keepalive 100; + server 127.0.0.1:8000; + #server unix:/tmp/sanic.sock; +} + +server { + server_name example.com; + listen 443 ssl http2 default_server; + listen [::]:443 ssl http2 default_server; + # Serve static files if found, otherwise proxy to Sanic + location / { + root /var/www; + try_files $uri @sanic; + } + location @sanic { + proxy_pass http://$server_name; + # Allow fast streaming HTTP/1.1 pipes (keep-alive, unbuffered) + proxy_http_version 1.1; + proxy_request_buffering off; + proxy_buffering off; + # Proxy forwarding (password configured in app.config.FORWARDED_SECRET) + proxy_set_header forwarded "$proxy_forwarded;secret=\"YOUR SECRET\""; + # Allow websockets and keep-alive (avoid connection: close) + proxy_set_header connection "upgrade"; + proxy_set_header upgrade $http_upgrade; + } +} +``` + +To avoid cookie visibility issues and inconsistent addresses on search engines, it is a good idea to redirect all visitors to one true domain, always using HTTPS: + +```nginx +# Redirect all HTTP to HTTPS with no-WWW +server { + listen 80 default_server; + listen [::]:80 default_server; + server_name ~^(?:www\.)?(.*)$; + return 301 https://$1$request_uri; +} + +# Redirect WWW to no-WWW +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name ~^www\.(.*)$; + return 301 $scheme://$1$request_uri; +} +``` + +The above config sections may be placed in `/etc/nginx/sites-available/default` or in other site configs (be sure to symlink them to `sites-enabled` if you create new ones). + +Make sure that your SSL certificates are configured in the main config, or add the `ssl_certificate` and `ssl_certificate_key` directives to each `server` section that listens on SSL. + +Additionally, copy&paste all of this into `nginx/conf.d/forwarded.conf`: + +```nginx +# RFC 7239 Forwarded header for Nginx proxy_pass + +# Add within your server or location block: +# proxy_set_header forwarded "$proxy_forwarded;secret=\"YOUR SECRET\""; + +# Configure your upstream web server to identify this proxy by that password +# because otherwise anyone on the Internet could spoof these headers and fake +# their real IP address and other information to your service. + + +# Provide the full proxy chain in $proxy_forwarded +map $proxy_add_forwarded $proxy_forwarded { + default "$proxy_add_forwarded;by=\"_$hostname\";proto=$scheme;host=\"$http_host\";path=\"$request_uri\""; +} + +# The following mappings are based on +# https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/ + +map $remote_addr $proxy_forwarded_elem { + # IPv4 addresses can be sent as-is + ~^[0-9.]+$ "for=$remote_addr"; + + # IPv6 addresses need to be bracketed and quoted + ~^[0-9A-Fa-f:.]+$ "for=\"[$remote_addr]\""; + + # Unix domain socket names cannot be represented in RFC 7239 syntax + default "for=unknown"; +} + +map $http_forwarded $proxy_add_forwarded { + # If the incoming Forwarded header is syntactically valid, append to it + "~^(,[ \\t]*)*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*([ \\t]*,([ \\t]*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*)?)*$" "$http_forwarded, $proxy_forwarded_elem"; + + # Otherwise, replace it + default "$proxy_forwarded_elem"; +} +``` + +::: tip Note For installs that don't use `conf.d` and `sites-available`, all of the above configs may also be placed inside the `http` section of the main `nginx.conf`. ::: + +Reload Nginx config after changes: + +```bash +sudo nginx -s reload +``` + +Now you should be able to connect your app on `https://example.com/`. Any 404 errors and such will be handled by Sanic's error pages, and whenever a static file is present at a given path, it will be served by Nginx. + +## SSL certificates + +If you haven't already configured valid certificates on your server, now is a good time to do so. Install `certbot` and `python3-certbot-nginx`, then run + +```bash +certbot --nginx -d example.com -d www.example.com +``` + +Reference: [Using Free Let’s Encrypt SSL/TLS Certificates with NGINX](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/) + +## Running as a service + +This part is for Linux distributions based on `systemd`. Create a unit file `/etc/systemd/system/sanicexample.service` + +```text +[Unit] +Description=Sanic Example + +[Service] +User=nobody +WorkingDirectory=/srv/sanicexample +ExecStart=/usr/bin/env python3 sanicexample.py +Restart=always + +[Install] +WantedBy=multi-user.target +``` + +Then reload service files, start your service and enable it on boot: + +```bash +sudo systemctl daemon-reload +sudo systemctl start sanicexample +sudo systemctl enable sanicexample +``` diff --git a/src/ru/guide/deployment/running.md b/src/ru/guide/deployment/running.md new file mode 100644 index 0000000000..d948035b44 --- /dev/null +++ b/src/ru/guide/deployment/running.md @@ -0,0 +1,386 @@ +# Running Sanic + +Sanic ships with its own internal web server. Under most circumstances, this is the preferred method for deployment. In addition, you can also deploy Sanic as an ASGI app bundled with an ASGI-able web server, or using gunicorn. + +## Sanic Server + +There are two main ways to run Sanic Server: + +1. Using `app.run` +1. Using the [CLI](#sanic-cli) + +When using `app.run` you will just call your Python file like any other script. + +---:1 `app.run` must be properly nested inside of a name-main block. :--:1 +```python +# server.py +app = Sanic("MyApp") + +if __name__ == "__main__": + app.run() +``` +:--- + + + +After defining an instance of `sanic.Sanic`, we can call the run method with the following keyword arguments: + +| Parameter | Default | Description | +|:--------------------:|:--------------:|:----------------------------------------------------------------------------------------- | +| **host** | `"127.0.0.1"` | Address to host the server on. | +| **port** | `8000` | Port to host the server on. | +| **unix** | `None` | Unix socket name to host the server on (instead of TCP). | +| **debug** | `False` | Enables debug output (slows server). | +| **ssl** | `None` | SSLContext for SSL encryption of worker(s). | +| **sock** | `None` | Socket for the server to accept connections from. | +| **workers** | `1` | Number of worker processes to spawn. Cannot be used with fast. | +| **loop** | `None` | An asyncio-compatible event loop. If none is specified, Sanic creates its own event loop. | +| **protocol** | `HttpProtocol` | Subclass of asyncio.protocol. | +| **access_log** | `True` | Enables log on handling requests (significantly slows server). | +| **reload_dir** | `None` | A path or list of paths to directories the auto-reloader should watch. | +| **noisy_exceptions** | `None` | Whether to set noisy exceptions globally. None means leave as default. | +| **motd** | `True` | Whether to display the startup message. | +| **motd_display** | `None` | A dict with extra key/value information to display in the startup message | +| **fast** | `False` | Whether to maximize worker processes. Cannot be used with workers. | +| **verbosity** | `0` | Level of logging detail. Max is 2. | +| **auto_tls** | `False` | Whether to auto-create a TLS certificate for local development. Not for production. | +| **single_process** | `False` | Whether to run Sanic in a single process. | + +---:1 In the above example, we decided to turn off the access log in order to increase performance. :--:1 +```python +# server.py +app = Sanic("MyApp") + +if __name__ == "__main__": + app.run(host='0.0.0.0', port=1337, access_log=False) +``` +:--- + +---:1 Now, just execute the python script that has `app.run(...)` :--:1 +```bash +python server.py +``` +:--- + +For a slightly more advanced implementation, it is good to know that `app.run` will call `app.prepare` and `Sanic.serve` under the hood. + +---:1 Therefore, these are equivalent: :--:1 +```python +if __name__ == "__main__": + app.run(host='0.0.0.0', port=1337, access_log=False) +``` +```python +if __name__ == "__main__": + app.prepare(host='0.0.0.0', port=1337, access_log=False) + Sanic.serve() +``` +:--- + +### Workers + +---:1 By default, Sanic runs a main process and a single worker process (see [worker manager](./manager.md) for more details). + +To crank up the juice, just specify the number of workers in the run arguments. :--:1 +```python +app.run(host='0.0.0.0', port=1337, workers=4) +``` +:--- + +Sanic will automatically spin up multiple processes and route traffic between them. We recommend as many workers as you have available processors. + +---:1 The easiest way to get the maximum CPU performance is to use the `fast` option. This will automatically run the maximum number of workers given the system constraints. + +*Added in v21.12* :--:1 +```python +app.run(host='0.0.0.0', port=1337, fast=True) +``` +```python +$ sanic server:app --host=0.0.0.0 --port=1337 --fast +``` +:--- + +In older versions of Sanic without the `fast` option, a common way to check this on Linux based operating systems: + +``` +$ nproc +``` + +Or, let Python do it: + +```python +import multiprocessing +workers = multiprocessing.cpu_count() +app.run(..., workers=workers) +``` + +In version 22.9, Sanic introduced a new worker manager to provide more consistency and flexibility between development and production servers. Read [about the manager](./manager.md) for more details about workers. + +---:1 If you only want to run Sanic with a single process, specify `single_process` in the run arguments. This means that auto-reload, and the worker manager will be unavailable. :--:1 +```python +app.run(host='0.0.0.0', port=1337, single_process=True) +``` +:--- + +### Running via command + +#### Sanic CLI + +---:1 Sanic also has a simple CLI to launch via command line. + +For example, if you initialized Sanic as app in a file named `server.py`, you could run the server like so: :--:1 +```bash +sanic server.app --host=0.0.0.0 --port=1337 --workers=4 +``` + +:--- + +Use `sanic --help` to see all the options. + +::: details Sanic CLI help output + +```text +$ sanic --help +usage: sanic [-h] [--version] + [--factory | -s | --inspect | --inspect-raw | --trigger-reload | --trigger-shutdown] + [--http {1,3}] [-1] [-3] [-H HOST] [-p PORT] [-u UNIX] + [--cert CERT] [--key KEY] [--tls DIR] [--tls-strict-host] + [-w WORKERS | --fast | --single-process] [--legacy] + [--access-logs | --no-access-logs] [--debug] [-r] [-R PATH] [-d] + [--auto-tls] [--coffee | --no-coffee] [--motd | --no-motd] [-v] + [--noisy-exceptions | --no-noisy-exceptions] + module + + ▄███ █████ ██ ▄█▄ ██ █ █ ▄██████████ + ██ █ █ █ ██ █ █ ██ + ▀███████ ███▄ ▀ █ █ ██ ▄ █ ██ + ██ █████████ █ ██ █ █ ▄▄ + ████ ████████▀ █ █ █ ██ █ ▀██ ███████ + + To start running a Sanic application, provide a path to the module, where + app is a Sanic() instance: + + $ sanic path.to.server:app + + Or, a path to a callable that returns a Sanic() instance: + + $ sanic path.to.factory:create_app --factory + + Or, a path to a directory to run as a simple HTTP server: + + $ sanic ./path/to/static --simple + +Required +======== + Positional: + module Path to your Sanic app. Example: path.to.server:app + If running a Simple Server, path to directory to serve. Example: ./ + +Optional +======== + General: + -h, --help show this help message and exit + --version show program's version number and exit + + Application: + --factory Treat app as an application factory, i.e. a () -> callable + -s, --simple Run Sanic as a Simple Server, and serve the contents of a directory + (module arg should be a path) + --inspect Inspect the state of a running instance, human readable + --inspect-raw Inspect the state of a running instance, JSON output + --trigger-reload Trigger worker processes to reload + --trigger-shutdown Trigger all processes to shutdown + + HTTP version: + --http {1,3} Which HTTP version to use: HTTP/1.1 or HTTP/3. Value should + be either 1, or 3. [default 1] + -1 Run Sanic server using HTTP/1.1 + -3 Run Sanic server using HTTP/3 + + Socket binding: + -H HOST, --host HOST + Host address [default 127.0.0.1] + -p PORT, --port PORT + Port to serve on [default 8000] + -u UNIX, --unix UNIX + location of unix socket + + TLS certificate: + --cert CERT Location of fullchain.pem, bundle.crt or equivalent + --key KEY Location of privkey.pem or equivalent .key file + --tls DIR TLS certificate folder with fullchain.pem and privkey.pem + May be specified multiple times to choose multiple certificates + --tls-strict-host Only allow clients that send an SNI matching server certs + + Worker: + -w WORKERS, --workers WORKERS + Number of worker processes [default 1] + --fast Set the number of workers to max allowed + --single-process Do not use multiprocessing, run server in a single process + --legacy Use the legacy server manager + --access-logs Display access logs + --no-access-logs No display access logs + + Development: + --debug Run the server in debug mode + -r, --reload, --auto-reload + Watch source directory for file changes and reload on changes + -R PATH, --reload-dir PATH + Extra directories to watch and reload on changes + -d, --dev debug + auto reload + --auto-tls Create a temporary TLS certificate for local development (requires mkcert or trustme) + + Output: + --coffee Uhm, coffee? + --no-coffee No uhm, coffee? + --motd Show the startup display + --no-motd No show the startup display + -v, --verbosity Control logging noise, eg. -vv or --verbosity=2 [default 0] + --noisy-exceptions Output stack traces for all exceptions + --no-noisy-exceptions + No output stack traces for all exceptions +``` +::: + +#### As a module + +---:1 It can also be called directly as a module. :--:1 +```bash +python -m sanic server.app --host=0.0.0.0 --port=1337 --workers=4 +``` +:--- + +::: tip FYI With either method (CLI or module), you shoud *not* invoke `app.run()` in your Python file. If you do, make sure you wrap it so that it only executes when directly run by the interpreter. + +```python +if __name__ == '__main__': + app.run(host='0.0.0.0', port=1337, workers=4) +``` +::: + + +### Sanic Simple Server + +---:1 Sometimes you just have a directory of static files that need to be served. This especially can be handy for quickly standing up a localhost server. Sanic ships with a Simple Server, where you only need to point it at a directory. :--:1 +```bash +sanic ./path/to/dir --simple +``` +:--- + +---:1 This could also be paired with auto-reloading. :--:1 +```bash +sanic ./path/to/dir --simple --reload --reload-dir=./path/to/dir +``` +:--- + +*Added in v21.6* + +### HTTP/3 + + +Sanic server offers HTTP/3 support using [aioquic](https://github.com/aiortc/aioquic). This **must** be installed to use HTTP/3: + +``` +pip install sanic aioquic +``` + +``` +pip install sanic[http3] +``` + +To start HTTP/3, you must explicitly request it when running your application. + +---:1 +``` +$ sanic path.to.server:app --http=3 +``` + +``` +$ sanic path.to.server:app -3 +``` +:--:1 + +```python +app.run(version=3) +``` +:--- + +To run both an HTTP/3 and HTTP/1.1 server simultaneously, you can use [application multi-serve](../release-notes/v22.3.html#application-multi-serve) introduced in v22.3. This will automatically add an [Alt-Svc](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Alt-Svc) header to your HTTP/1.1 requests to let the client know that it is also available as HTTP/3. + + +---:1 +``` +$ sanic path.to.server:app --http=3 --http=1 +``` + +``` +$ sanic path.to.server:app -3 -1 +``` +:--:1 + +```python +app.prepare(version=3) +app.prepare(version=1) +Sanic.serve() +``` +:--- + +Because HTTP/3 requires TLS, you cannot start a HTTP/3 server without a TLS certificate. You should [set it up yourself](../how-to/tls.html) or use `mkcert` if in `DEBUG` mode. Currently, automatic TLS setup for HTTP/3 is not compatible with `trustme`. See [development](./development.md) for more details. + +*Added in v22.6* + +## ASGI + +Sanic is also ASGI-compliant. This means you can use your preferred ASGI webserver to run Sanic. The three main implementations of ASGI are [Daphne](http://github.com/django/daphne), [Uvicorn](https://www.uvicorn.org/), and [Hypercorn](https://pgjones.gitlab.io/hypercorn/index.html). + +::: warning Daphne does not support the ASGI `lifespan` protocol, and therefore cannot be used to run Sanic. See [Issue #264](https://github.com/django/daphne/issues/264) for more details. ::: + +Follow their documentation for the proper way to run them, but it should look something like: + +``` +uvicorn myapp:app +hypercorn myapp:app +``` + +A couple things to note when using ASGI: + +1. When using the Sanic webserver, websockets will run using the `websockets` package. In ASGI mode, there is no need for this package since websockets are managed in the ASGI server. +2. The ASGI lifespan protocol , supports only two server events: startup and shutdown. Sanic has four: before startup, after startup, before shutdown, and after shutdown. Therefore, in ASGI mode, the startup and shutdown events will run consecutively and not actually around the server process beginning and ending (since that is now controlled by the ASGI server). Therefore, it is best to use `after_server_start` and `before_server_stop`. + +### Trio + +Sanic has experimental support for running on Trio with: + +``` +hypercorn -k trio myapp:app +``` + + +## Gunicorn + +[Gunicorn](http://gunicorn.org/) ("Green Unicorn") is a WSGI HTTP Server for UNIX based operating systems. It is a pre-fork worker model ported from Ruby’s Unicorn project. + +In order to run Sanic application with Gunicorn, you need to use it with the adapter from [uvicorn](https://www.uvicorn.org/). Make sure uvicorn is installed and run it with `uvicorn.workers.UvicornWorker` for Gunicorn worker-class argument: + +```bash +gunicorn myapp:app --bind 0.0.0.0:1337 --worker-class uvicorn.workers.UvicornWorker +``` + +See the [Gunicorn Docs](http://docs.gunicorn.org/en/latest/settings.html#max-requests) for more information. + +::: warning It is generally advised to not use `gunicorn` unless you need it. The Sanic Server is primed for running Sanic in production. Weigh your considerations carefully before making this choice. Gunicorn does provide a lot of configuration options, but it is not the best choice for getting Sanic to run at its fastest. ::: + +## Performance considerations + +---:1 When running in production, make sure you turn off `debug`. :--:1 +```python +app.run(..., debug=False) +``` +:--- + +---:1 Sanic will also perform fastest if you turn off `access_log`. + +If you still require access logs, but want to enjoy this performance boost, consider using [Nginx as a proxy](./nginx.md), and letting that handle your access logging. It will be much faster than anything Python can handle. :--:1 +```python +app.run(..., access_log=False) +``` +:--- diff --git a/src/ru/guide/deployment/server-choice.md b/src/ru/guide/deployment/server-choice.md new file mode 100644 index 0000000000..4830bf0951 --- /dev/null +++ b/src/ru/guide/deployment/server-choice.md @@ -0,0 +1 @@ +# Выбор сервера diff --git a/src/ru/guide/getting-started.md b/src/ru/guide/getting-started.md new file mode 100644 index 0000000000..80a5435b22 --- /dev/null +++ b/src/ru/guide/getting-started.md @@ -0,0 +1,86 @@ +# Getting Started + +Before we begin, make sure you are running Python 3.7 or higher. Currently, is known to work with Python versions 3.7, 3.8 and 3.9. + +## Install + +```bash +pip install sanic +``` + +## Hello, world application + +---:1 + +If you have ever used one of the many decorator based frameworks, this probably looks somewhat familiar to you. + +::: tip +If you are coming from Flask or another framework, there are a few important things to point out. Remember, Sanic aims for performance, flexibility, and ease of use. These guiding principles have tangible impact on the API and how it works. +::: + + + +:--:1 + +```python +from sanic import Sanic +from sanic.response import text + +app = Sanic("MyHelloWorldApp") + +@app.get("/") +async def hello_world(request): + return text("Hello, world.") +``` + +:--- + +### Important to note + +- Every request handler can either be sync (`def hello_world`) or async (`async def hello_world`). Unless you have a clear reason for it, always go with `async`. +- The `request` object is always the first argument of your handler. Other frameworks pass this around in a context variable to be imported. In the `async` world, this would not work so well and it is far easier (not to mention cleaner and more performant) to be explicit about it. +- You **must** use a response type. MANY other frameworks allow you to have a return value like this: `return "Hello, world."` or this: `return {"foo": "bar"}`. But, in order to do this implicit calling, somewhere in the chain needs to spend valuable time trying to determine what you meant. So, at the expense of this ease, Sanic has decided to require an explicit call. + +### Running + +---:1 Let's save the above file as `server.py`. And launch it. :--:1 +```bash +sanic server.app +``` +:--- + +::: tip This **another** important distinction. Other frameworks come with a built in development server and explicitly say that it is _only_ intended for development use. The opposite is true with Sanic. + +**The packaged server is production ready.** ::: + +## Sanic Extensions + +Sanic intentionally aims for a clean and unopinionated feature list. The project does not want to require you to build your application in a certain way, and tries to avoid prescribing specific development patterns. There are a number of third-party plugins that are built and maintained by the community to add additional features that do not otherwise meet the requirements of the core repository. + +However, in order **to help API developers**, the Sanic organization maintains an official plugin called [Sanic Extensions](../plugins/sanic-ext/getting-started.md) to provide all sorts of goodies, including: + +- **OpenAPI** documentation with Redoc and/or Swagger +- **CORS** protection +- **Dependency injection** into route handlers +- Request query arguments and body input **validation** +- Auto create `HEAD`, `OPTIONS`, and `TRACE` endpoints +- Predefined, endpoint-specific response serializers + +The preferred method to set it up is to install it along with Sanic, but you can also install the packages on their own. + +---:1 +``` +$ pip install sanic[ext] +``` +:--:1 +``` +$ pip install sanic sanic-ext +``` +:--- + +Starting in v21.12, Sanic will automatically setup Sanic Extensions if it is in the same environment. You will also have access to two additional application properties: + +- `app.extend()` - used to configure Sanic Extensions +- `app.ext` - the `Extend` instance attached to the application + +See [the plugin documentation](../plugins/sanic-ext/getting-started.md) for more information about how to use and work with the plugin diff --git a/src/ru/guide/how-to/README.md b/src/ru/guide/how-to/README.md new file mode 100644 index 0000000000..f4474ff5c8 --- /dev/null +++ b/src/ru/guide/how-to/README.md @@ -0,0 +1 @@ +# Как ... diff --git a/src/ru/guide/how-to/authentication.md b/src/ru/guide/how-to/authentication.md new file mode 100644 index 0000000000..9d80fd4c0b --- /dev/null +++ b/src/ru/guide/how-to/authentication.md @@ -0,0 +1,113 @@ +# Authentication + +> How do I control authentication and authorization? + +This is an _extremely_ complicated subject to cram into a few snippets. But, this should provide you with an idea on ways to tackle this problem. This example uses [JWTs](https://jwt.io/), but the concepts should be equally applicable to sessions or some other scheme. + +:::: tabs +::: tab server.py +```python +from sanic import Sanic, text + +from auth import protected +from login import login + +app = Sanic("AuthApp") +app.config.SECRET = "KEEP_IT_SECRET_KEEP_IT_SAFE" +app.blueprint(login) + + +@app.get("/secret") +@protected +async def secret(request): + return text("To go fast, you must be fast.") +``` +::: +::: tab login.py +```python +import jwt +from sanic import Blueprint, text + +login = Blueprint("login", url_prefix="/login") + + +@login.post("/") +async def do_login(request): + token = jwt.encode({}, request.app.config.SECRET) + return text(token) +``` +::: +::: tab auth.py +```python +from functools import wraps + +import jwt +from sanic import text + + +def check_token(request): + if not request.token: + return False + + try: + jwt.decode( + request.token, request.app.config.SECRET, algorithms=["HS256"] + ) + except jwt.exceptions.InvalidTokenError: + return False + else: + return True + + +def protected(wrapped): + def decorator(f): + @wraps(f) + async def decorated_function(request, *args, **kwargs): + is_authenticated = check_token(request) + + if is_authenticated: + response = await f(request, *args, **kwargs) + return response + else: + return text("You are unauthorized.", 401) + + return decorated_function + + return decorator(wrapped) +``` +This decorator pattern is taken from the [decorators page](/en/guide/best-practices/decorators.md). ::: +:::: + +```bash +$ curl localhost:9999/secret -i +HTTP/1.1 401 Unauthorized +content-length: 21 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +You are unauthorized. + +$ curl localhost:9999/login -X POST 7 ↵ +eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.rjxS7ztIGt5tpiRWS8BGLUqjQFca4QOetHcZTi061DE + +$ curl localhost:9999/secret -i -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.rjxS7ztIGt5tpiRWS8BGLUqjQFca4QOetHcZTi061DE" +HTTP/1.1 200 OK +content-length: 29 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +To go fast, you must be fast. + +$ curl localhost:9999/secret -i -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.BAD" +HTTP/1.1 401 Unauthorized +content-length: 21 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +You are unauthorized. +``` + +Also, checkout some resources from the community: + +- Awesome Sanic - [Authorization](https://github.com/mekicha/awesome-sanic/blob/master/README.md#authentication) & [Session](https://github.com/mekicha/awesome-sanic/blob/master/README.md#session) +- [EuroPython 2020 - Overcoming access control in web APIs](https://www.youtube.com/watch?v=Uqgoj43ky6A) diff --git a/src/ru/guide/how-to/autodiscovery.md b/src/ru/guide/how-to/autodiscovery.md new file mode 100644 index 0000000000..4cf319357b --- /dev/null +++ b/src/ru/guide/how-to/autodiscovery.md @@ -0,0 +1,161 @@ +--- +title: Autodiscovery +--- + + +# Autodiscovery of Blueprints, Middleware, and Listeners + +> How do I autodiscover the components I am using to build my application? + +One of the first problems someone faces when building an application, is *how* to structure the project. Sanic makes heavy use of decorators to register route handlers, middleware, and listeners. And, after creating blueprints, they need to be mounted to the application. + +A possible solution is a single file in which **everything** is imported and applied to the Sanic instance. Another is passing around the Sanic instance as a global variable. Both of these solutions have their drawbacks. + +An alternative is autodiscovery. You point your application at modules (already imported, or strings), and let it wire everything up. + +:::: tabs +::: tab server.py +```python +from sanic import Sanic +from sanic.response import empty + +import blueprints +from utility import autodiscover + +app = Sanic("auto", register=True) +autodiscover( + app, + blueprints, + "parent.child", + "listeners.something", + recursive=True, +) + +app.route("/")(lambda _: empty()) +``` +```bash +[2021-03-02 21:37:02 +0200] [880451] [INFO] Goin' Fast @ http://127.0.0.1:9999 +[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something +[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something @ nested +[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something @ level1 +[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something @ level3 +[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something inside __init__.py +[2021-03-02 21:37:02 +0200] [880451] [INFO] Starting worker [880451] +``` +::: +::: tab utility.py +```python + +from glob import glob +from importlib import import_module, util +from inspect import getmembers +from pathlib import Path +from types import ModuleType +from typing import Union + +from sanic.blueprints import Blueprint + + +def autodiscover( + app, *module_names: Union[str, ModuleType], recursive: bool = False +): + mod = app.__module__ + blueprints = set() + _imported = set() + + def _find_bps(module): + nonlocal blueprints + + for _, member in getmembers(module): + if isinstance(member, Blueprint): + blueprints.add(member) + + for module in module_names: + if isinstance(module, str): + module = import_module(module, mod) + _imported.add(module.__file__) + _find_bps(module) + + if recursive: + base = Path(module.__file__).parent + for path in glob(f"{base}/**/*.py", recursive=True): + if path not in _imported: + name = "module" + if "__init__" in path: + *_, name, __ = path.split("/") + spec = util.spec_from_file_location(name, path) + specmod = util.module_from_spec(spec) + _imported.add(path) + spec.loader.exec_module(specmod) + _find_bps(specmod) + + for bp in blueprints: + app.blueprint(bp) +``` +::: +::: tab blueprints/level1.py +```python +from sanic import Blueprint +from sanic.log import logger + +level1 = Blueprint("level1") + + +@level1.after_server_start +def print_something(app, loop): + logger.debug("something @ level1") +``` +::: +::: tab blueprints/one/two/level3.py +```python +from sanic import Blueprint +from sanic.log import logger + +level3 = Blueprint("level3") + + +@level3.after_server_start +def print_something(app, loop): + logger.debug("something @ level3") +``` +::: +::: tab listeners/something.py +```python +from sanic import Sanic +from sanic.log import logger + +app = Sanic.get_app("auto") + + +@app.after_server_start +def print_something(app, loop): + logger.debug("something") +``` +::: +::: tab parent/child/__init__.py +```python +from sanic import Blueprint +from sanic.log import logger + +bp = Blueprint("__init__") + + +@bp.after_server_start +def print_something(app, loop): + logger.debug("something inside __init__.py") +``` +::: +::: tab parent/child/nested.py +```python +from sanic import Blueprint +from sanic.log import logger + +nested = Blueprint("nested") + + +@nested.after_server_start +def print_something(app, loop): + logger.debug("something @ nested") +``` +::: +:::: diff --git a/src/ru/guide/how-to/cors.md b/src/ru/guide/how-to/cors.md new file mode 100644 index 0000000000..d1db877a9a --- /dev/null +++ b/src/ru/guide/how-to/cors.md @@ -0,0 +1,137 @@ +--- +title: CORS +--- + + +# Cross-origin resource sharing (CORS) + +> How do I configure my application for CORS? + +The best solution is to use [Sanic Extensions](../../plugins/sanic-ext/http/cors.md). However, if you would like to build your own version, you could use this limited example. + +:::: tabs +::: tab server.py +```python +from sanic import Sanic, text + +from cors import add_cors_headers +from options import setup_options + +app = Sanic("app") + + +@app.route("/", methods=["GET", "POST"]) +async def do_stuff(request): + return text("...") + + +# Add OPTIONS handlers to any route that is missing it +app.register_listener(setup_options, "before_server_start") + +# Fill in CORS headers +app.register_middleware(add_cors_headers, "response") +``` +::: +::: tab cors.py +```python +from typing import Iterable + + +def _add_cors_headers(response, methods: Iterable[str]) -> None: + allow_methods = list(set(methods)) + if "OPTIONS" not in allow_methods: + allow_methods.append("OPTIONS") + headers = { + "Access-Control-Allow-Methods": ",".join(allow_methods), + "Access-Control-Allow-Origin": "mydomain.com", + "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Headers": ( + "origin, content-type, accept, " + "authorization, x-xsrf-token, x-request-id" + ), + } + response.headers.extend(headers) + + +def add_cors_headers(request, response): + if request.method != "OPTIONS": + methods = [method for method in request.route.methods] + _add_cors_headers(response, methods) +``` +::: +::: tab options.py +```python +from collections import defaultdict +from typing import Dict, FrozenSet + +from sanic import Sanic, response +from sanic.router import Route + +from cors import _add_cors_headers + + +def _compile_routes_needing_options( + routes: Dict[str, Route] +) -> Dict[str, FrozenSet]: + needs_options = defaultdict(list) + # This is 21.12 and later. You will need to change this for older versions. + for route in routes.values(): + if "OPTIONS" not in route.methods: + needs_options[route.uri].extend(route.methods) + + return { + uri: frozenset(methods) for uri, methods in dict(needs_options).items() + } + + +def _options_wrapper(handler, methods): + def wrapped_handler(request, *args, **kwargs): + nonlocal methods + return handler(request, methods) + + return wrapped_handler + + +async def options_handler(request, methods) -> response.HTTPResponse: + resp = response.empty() + _add_cors_headers(resp, methods) + return resp + + +def setup_options(app: Sanic, _): + app.router.reset() + needs_options = _compile_routes_needing_options(app.router.routes_all) + for uri, methods in needs_options.items(): + app.add_route( + _options_wrapper(options_handler, methods), + uri, + methods=["OPTIONS"], + ) + app.router.finalize() +``` +::: +:::: +``` +$ curl localhost:9999/ -i +HTTP/1.1 200 OK +Access-Control-Allow-Methods: OPTIONS,POST,GET +Access-Control-Allow-Origin: mydomain.com +Access-Control-Allow-Credentials: true +Access-Control-Allow-Headers: origin, content-type, accept, authorization, x-xsrf-token, x-request-id +content-length: 3 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +... + +$ curl localhost:9999/ -i -X OPTIONS +HTTP/1.1 204 No Content +Access-Control-Allow-Methods: GET,POST,OPTIONS +Access-Control-Allow-Origin: mydomain.com +Access-Control-Allow-Credentials: true +Access-Control-Allow-Headers: origin, content-type, accept, authorization, x-xsrf-token, x-request-id +connection: keep-alive +``` +Also, checkout some resources from the community: + +- [Awesome Sanic](https://github.com/mekicha/awesome-sanic/blob/master/README.md#frontend) diff --git a/src/ru/guide/how-to/csrf.md b/src/ru/guide/how-to/csrf.md new file mode 100644 index 0000000000..01243b175e --- /dev/null +++ b/src/ru/guide/how-to/csrf.md @@ -0,0 +1 @@ +межсайтовая подделка запроса (csrf) diff --git a/src/ru/guide/how-to/db.md b/src/ru/guide/how-to/db.md new file mode 100644 index 0000000000..6b506767e8 --- /dev/null +++ b/src/ru/guide/how-to/db.md @@ -0,0 +1 @@ +подключение к источникам данных diff --git a/src/ru/guide/how-to/decorators.md b/src/ru/guide/how-to/decorators.md new file mode 100644 index 0000000000..9ef318134e --- /dev/null +++ b/src/ru/guide/how-to/decorators.md @@ -0,0 +1 @@ +decorators diff --git a/src/ru/guide/how-to/mounting.md b/src/ru/guide/how-to/mounting.md new file mode 100644 index 0000000000..918c422cdb --- /dev/null +++ b/src/ru/guide/how-to/mounting.md @@ -0,0 +1,52 @@ +# Application Mounting + +> How do I mount my application at some path above the root? + +```python +# server.py +from sanic import Sanic, text + +app = Sanic("app") +app.config.SERVER_NAME = "example.com/api" + + +@app.route("/foo") +def handler(request): + url = app.url_for("handler", _external=True) + return text(f"URL: {url}") +``` + +```yaml +# docker-compose.yml +version: "3.7" +services: + app: + image: nginx:alpine + ports: + - 80:80 + volumes: + - type: bind + source: ./conf + target: /etc/nginx/conf.d/default.conf +``` + +```nginx +# conf +server { + listen 80; + + # Computed data service + location /api/ { + proxy_pass http://:9999/; + proxy_set_header Host example.com; + } +} +``` +```bash +$ docker-compose up -d +$ sanic server.app --port=9999 --host=0.0.0.0 +``` +```bash +$ curl localhost/api/foo +URL: http://example.com/api/foo +``` diff --git a/src/ru/guide/how-to/orm.md b/src/ru/guide/how-to/orm.md new file mode 100644 index 0000000000..eb73022681 --- /dev/null +++ b/src/ru/guide/how-to/orm.md @@ -0,0 +1,370 @@ +# ORM + +> How do I use SQLAlchemy with Sanic ? + +All ORM tools can work with Sanic, but non-async ORM tool have a impact on Sanic performance. There are some orm packages who support + +At present, there are many ORMs that support Python's `async`/`await` keywords. Some possible choices include: + +- [Mayim](https://ahopkins.github.io/mayim/) +- [SQLAlchemy 1.4](https://docs.sqlalchemy.org/en/14/changelog/changelog_14.html) +- [tortoise-orm](https://github.com/tortoise/tortoise-orm) + +Integration in to your Sanic application is fairly simple: + +## Mayim + +Mayim ships with [an extension for Sanic Extensions](https://ahopkins.github.io/mayim/guide/extensions.html#sanic), which makes it super simple to get started with Sanic. It is certainly possible to run Mayim with Sanic without the extension, but it is recommended because it handles all of the [lifecycle events](https://sanic.dev/en/guide/basics/listeners.html) and [dependency injections](https://sanic.dev/en/plugins/sanic-ext/injection.html). + +---:1 +### Dependencies + +First, we need to install the required dependencies. See [Mayim docs](https://ahopkins.github.io/mayim/guide/install.html#postgres) for the installation needed for your DB driver. :--:1 +```shell +pip install sanic-ext +pip install mayim[postgres] +``` +:--- + +---:1 +### Define ORM Model + +Mayim allows you to use whatever you want for models. Whether it is [dataclasses](https://docs.python.org/3/library/dataclasses.html), [pydantic](https://pydantic-docs.helpmanual.io/), [attrs](https://www.attrs.org/en/stable/), or even just plain `dict` objects. Since it works very nicely [out of the box with Pydantic](https://ahopkins.github.io/mayim/guide/pydantic.html), that is what we will use here. :--:1 +```python +# ./models.py +from pydantic import BaseModel + + +class City(BaseModel): + id: int + name: str + district: str + population: int + + +class Country(BaseModel): + code: str + name: str + continent: str + region: str + capital: City +``` +:--- + +---:1 +### Define SQL + +If you are unfamiliar, Mayim is different from other ORMs in that it is one-way, SQL-first. This means you define your own queries either inline, or in a separate `.sql` file, which is what we will do here. :--:1 +```sql +-- ./queries/select_all_countries.sql +SELECT country.code, + country.name, + country.continent, + country.region, + ( + SELECT row_to_json(q) + FROM ( + SELECT city.id, + city.name, + city.district, + city.population + ) q + ) capital +FROM country + JOIN city ON country.capital = city.id +ORDER BY country.name ASC +LIMIT $limit OFFSET $offset; +``` +:--- + +---:1 +### Create Sanic App and Async Engine + +We need to create the app instance and attach the `SanicMayimExtension` with any executors. :--:1 +```python +# ./server.py +from sanic import Sanic, Request, json +from sanic_ext import Extend +from mayim.executor import PostgresExecutor +from mayim.extensions import SanicMayimExtension +from models import Country + + +class CountryExecutor(PostgresExecutor): + async def select_all_countries( + self, limit: int = 4, offset: int = 0 + ) -> list[Country]: + ... + + +app = Sanic("Test") +Extend.register( + SanicMayimExtension( + executors=[CountryExecutor], + dsn="postgres://...", + ) +) +``` +:--- + +---:1 +### Register Routes + +Because we are using Mayim's extension for Sanic, we have the automatic `CountryExecutor` injection into the route handler. It makes for an easy, type-annotated development experience. :--:1 +```python +@app.get("/") +async def handler(request: Request, executor: CountryExecutor): + countries = await executor.select_all_countries() + return json({"countries": [country.dict() for country in co +``` +:--- + +---:1 +### Send Requests +:--:1 +```sh +curl 'http://127.0.0.1:8000' +{"countries":[{"code":"AFG","name":"Afghanistan","continent":"Asia","region":"Southern and Central Asia","capital":{"id":1,"name":"Kabul","district":"Kabol","population":1780000}},{"code":"ALB","name":"Albania","continent":"Europe","region":"Southern Europe","capital":{"id":34,"name":"Tirana","district":"Tirana","population":270000}},{"code":"DZA","name":"Algeria","continent":"Africa","region":"Northern Africa","capital":{"id":35,"name":"Alger","district":"Alger","population":2168000}},{"code":"ASM","name":"American Samoa","continent":"Oceania","region":"Polynesia","capital":{"id":54,"name":"Fagatogo","district":"Tutuila","population":2323}}]} +``` +:--- + + +## SQLAlchemy + +Because [SQLAlchemy 1.4](https://docs.sqlalchemy.org/en/14/changelog/changelog_14.html) has added native support for `asyncio`, Sanic can finally work well with SQLAlchemy. Be aware that this functionality is still considered *beta* by the SQLAlchemy project. + + +---:1 +### Dependencies + +First, we need to install the required dependencies. In the past, the dependencies installed were `sqlalchemy` and `pymysql`, but now `sqlalchemy` and `aiomysql` are needed. :--:1 +```shell +pip install -U sqlalchemy +pip install -U aiomysql +``` +:--- + +---:1 +### Define ORM Model + +ORM model creation remains the same. :--:1 +```python +# ./models.py +from sqlalchemy import INTEGER, Column, ForeignKey, String +from sqlalchemy.orm import declarative_base, relationship + +Base = declarative_base() + + +class BaseModel(Base): + __abstract__ = True + id = Column(INTEGER(), primary_key=True) + + +class Person(BaseModel): + __tablename__ = "person" + name = Column(String()) + cars = relationship("Car") + + def to_dict(self): + return {"name": self.name, "cars": [{"brand": car.brand} for car in self.cars]} + + +class Car(BaseModel): + __tablename__ = "car" + + brand = Column(String()) + user_id = Column(ForeignKey("person.id")) + user = relationship("Person", back_populates="cars") +``` +:--- + +---:1 +### Create Sanic App and Async Engine + +Here we use mysql as the database, and you can also choose PostgreSQL/SQLite. Pay attention to changing the driver from `aiomysql` to `asyncpg`/`aiosqlite`. :--:1 +```python +# ./server.py +from sanic import Sanic +from sqlalchemy.ext.asyncio import create_async_engine + +app = Sanic("my_app") + +bind = create_async_engine("mysql+aiomysql://root:root@localhost/test", echo=True) +``` +:--- + +---:1 +### Register Middlewares + +The request middleware creates an usable `AsyncSession` object and set it to `request.ctx` and `_base_model_session_ctx`. + +Thread-safe variable `_base_model_session_ctx` helps you to use the session object instead of fetching it from `request.ctx`. :--:1 +```python +# ./server.py +from contextvars import ContextVar + +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import sessionmaker + +_sessionmaker = sessionmaker(bind, AsyncSession, expire_on_commit=False) + +_base_model_session_ctx = ContextVar("session") + +@app.middleware("request") +async def inject_session(request): + request.ctx.session = _sessionmaker() + request.ctx.session_ctx_token = _base_model_session_ctx.set(request.ctx.session) + + +@app.middleware("response") +async def close_session(request, response): + if hasattr(request.ctx, "session_ctx_token"): + _base_model_session_ctx.reset(request.ctx.session_ctx_token) + await request.ctx.session.close() +``` +:--- + +---:1 +### Register Routes + +According to sqlalchemy official docs, `session.query` will be legacy in 2.0, and the 2.0 way to query an ORM object is using `select`. :--:1 +```python +# ./server.py +from sqlalchemy import select +from sqlalchemy.orm import selectinload +from sanic.response import json + +from models import Car, Person + + +@app.post("/user") +async def create_user(request): + session = request.ctx.session + async with session.begin(): + car = Car(brand="Tesla") + person = Person(name="foo", cars=[car]) + session.add_all([person]) + return json(person.to_dict()) + + +@app.get("/user/") +async def get_user(request, pk): + session = request.ctx.session + async with session.begin(): + stmt = select(Person).where(Person.id == pk).options(selectinload(Person.cars)) + result = await session.execute(stmt) + person = result.scalar() + + if not person: + return json({}) + + return json(person.to_dict()) +``` +:--- + +---:1 +### Send Requests +:--:1 +```sh +curl --location --request POST 'http://127.0.0.1:8000/user' +{"name":"foo","cars":[{"brand":"Tesla"}]} +``` + +```sh +curl --location --request GET 'http://127.0.0.1:8000/user/1' +{"name":"foo","cars":[{"brand":"Tesla"}]} +``` +:--- + + +## Tortoise-ORM + +---:1 +### Dependencies + +tortoise-orm's dependency is very simple, you just need install tortoise-orm. :--:1 +```shell +pip install -U tortoise-orm +``` +:--- + +---:1 +### Define ORM Model + +If you are familiar with Django, you should find this part very familiar. :--:1 +```python +# ./models.py +from tortoise import Model, fields + + +class Users(Model): + id = fields.IntField(pk=True) + name = fields.CharField(50) + + def __str__(self): + return f"I am {self.name}" +``` +:--- + + +---:1 +### Create Sanic App and Async Engine + +Tortoise-orm provides a set of registration interface, which is convenient for users, and you can use it to create database connection easily. :--:1 +```python +# ./main.py + +from models import Users +from tortoise.contrib.sanic import register_tortoise + +app = Sanic(__name__) + + +register_tortoise( + app, db_url="mysql://root:root@localhost/test", modules={"models": ["models"]}, generate_schemas=True +) + +``` +:--- + +---:1 +### Register Routes +:--:1 +```python +# ./main.py + +from models import Users +from sanic import Sanic, response + + +@app.route("/user") +async def list_all(request): + users = await Users.all() + return response.json({"users": [str(user) for user in users]}) + + +@app.route("/user/") +async def get_user(request, pk): + user = await Users.query(pk=pk) + return response.json({"user": str(user)}) + +if __name__ == "__main__": + app.run(port=5000) +``` +:--- + +---:1 +### Send Requests +:--:1 +```sh +curl --location --request POST 'http://127.0.0.1:8000/user' +{"users":["I am foo", "I am bar"]} +``` + +```sh +curl --location --request GET 'http://127.0.0.1:8000/user/1' +{"user": "I am foo"} +``` +:--- + diff --git a/src/ru/guide/how-to/serialization.md b/src/ru/guide/how-to/serialization.md new file mode 100644 index 0000000000..c6041dd15f --- /dev/null +++ b/src/ru/guide/how-to/serialization.md @@ -0,0 +1 @@ +# Сериализация diff --git a/src/ru/guide/how-to/server-sent-events.md b/src/ru/guide/how-to/server-sent-events.md new file mode 100644 index 0000000000..80a936bf48 --- /dev/null +++ b/src/ru/guide/how-to/server-sent-events.md @@ -0,0 +1 @@ +События, посылаемые сервером (SSE - от англ. Server-Sent Events) diff --git a/src/ru/guide/how-to/static-redirects.md b/src/ru/guide/how-to/static-redirects.md new file mode 100644 index 0000000000..5566050c74 --- /dev/null +++ b/src/ru/guide/how-to/static-redirects.md @@ -0,0 +1,110 @@ +# "Static" Redirects + +> How do I configure static redirects? + +:::: tabs +::: tab app.py +```python +### SETUP ### +import typing +import sanic, sanic.response + +# Create the Sanic app +app = sanic.Sanic(__name__) + +# This dictionary represents your "static" +# redirects. For example, these values +# could be pulled from a configuration file. +REDIRECTS = { + '/':'/hello_world', # Redirect '/' to '/hello_world' + '/hello_world':'/hello_world.html' # Redirect '/hello_world' to 'hello_world.html' +} + +# This function will return another function +# that will return the configured value +# regardless of the arguments passed to it. +def get_static_function(value:typing.Any) -> typing.Callable[..., typing.Any]: + return lambda *_, **__: value + +### ROUTING ### +# Iterate through the redirects +for src, dest in REDIRECTS.items(): + # Create the redirect response object + response:sanic.HTTPResponse = sanic.response.redirect(dest) + + # Create the handler function. Typically, + # only a sanic.Request object is passed + # to the function. This object will be + # ignored. + handler = get_static_function(response) + + # Route the src path to the handler + app.route(src)(handler) + +# Route some file and client resources +app.static('/files/', 'files') +app.static('/', 'client') + +### RUN ### +if __name__ == '__main__': + app.run( + '127.0.0.1', + 10000 + ) +``` +::: + +::: tab client/hello_world.html +```html + + + + + + + Hello World + + + +
+ Hello world! +
+ + +``` +::: + +::: tab client/hello_world.css +```css +#hello_world { + width: 1000px; + margin-left: auto; + margin-right: auto; + margin-top: 100px; + + padding: 100px; + color: aqua; + text-align: center; + font-size: 100px; + font-family: monospace; + + background-color: rgba(0, 0, 0, 0.75); + + border-radius: 10px; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.75); +} + +body { + background-image: url("/files/lake.jpg"); + background-repeat: no-repeat; + background-size: cover; +} +``` +::: + +::: tab files/lake.jpg ![](./assets/images/lake.jpg) ::: +:::: + +Also, checkout some resources from the community: + +- [Static Routing Example](https://github.com/Perzan/sanic-static-routing-example) diff --git a/src/ru/guide/how-to/task-queue.md b/src/ru/guide/how-to/task-queue.md new file mode 100644 index 0000000000..1972fe5180 --- /dev/null +++ b/src/ru/guide/how-to/task-queue.md @@ -0,0 +1 @@ +очередь задач diff --git a/src/ru/guide/how-to/tls.md b/src/ru/guide/how-to/tls.md new file mode 100644 index 0000000000..5299134cb1 --- /dev/null +++ b/src/ru/guide/how-to/tls.md @@ -0,0 +1,158 @@ +# TLS/SSL/HTTPS + +> How do I run Sanic via HTTPS? + +If you do not have TLS certificates yet, [see the end of this page](./tls.md#get-certificates-for-your-domain-names). + +## Single domain and single certificate + +---:1 Let Sanic automatically load your certificate files, which need to be named `fullchain.pem` and `privkey.pem` in the given folder: + +:--:1 +```sh +sudo sanic myserver:app -H :: -p 443 \ + --tls /etc/letsencrypt/live/example.com/ +``` +```python +app.run("::", 443, ssl="/etc/letsencrypt/live/example.com/") +``` +:--- + +---:1 Or, you can pass cert and key filenames separately as a dictionary: + +Additionally, `password` may be added if the key is encrypted, all fields except for the password are passed to `request.conn_info.cert`. :--:1 +```python +ssl = { + "cert": "/path/to/fullchain.pem", + "key": "/path/to/privkey.pem", + "password": "for encrypted privkey file", # Optional +} +app.run(host="0.0.0.0", port=8443, ssl=ssl) +``` +:--- + +---:1 Alternatively, [`ssl.SSLContext`](https://docs.python.org/3/library/ssl.html) may be passed, if you need full control over details such as which crypto algorithms are permitted. By default Sanic only allows secure algorithms, which may restrict access from very old devices. :--:1 +```python +import ssl + +context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) +context.load_cert_chain("certs/fullchain.pem", "certs/privkey.pem") + +app.run(host="0.0.0.0", port=8443, ssl=context) +``` +:--- + + +## Multiple domains with separate certificates + +---:1 A list of multiple certificates may be provided, in which case Sanic chooses the one matching the hostname the user is connecting to. This occurs so early in the TLS handshake that Sanic has not sent any packets to the client yet. + +If the client sends no SNI (Server Name Indication), the first certificate on the list will be used even though on the client browser it will likely fail with a TLS error due to name mismatch. To prevent this fallback and to cause immediate disconnection of clients without a known hostname, add `None` as the first entry on the list. `--tls-strict-host` is the equivalent CLI option. :--:1 +```python +ssl = ["certs/example.com/", "certs/bigcorp.test/"] +app.run(host="0.0.0.0", port=8443, ssl=ssl) +``` +```sh +sanic myserver:app + --tls certs/example.com/ + --tls certs/bigcorp.test/ + --tls-strict-host +``` +:--- + +::: tip You may also use `None` in front of a single certificate if you do not wish to reveal your certificate, true hostname or site content to anyone connecting to the IP address instead of the proper DNS name. ::: + +---:1 Dictionaries can be used on the list. This allows also specifying which domains a certificate matches to, although the names present on the certificate itself cannot be controlled from here. If names are not specified, the names from the certificate itself are used. + +To only allow connections to the main domain **example.com** and only to subdomains of **bigcorp.test**: + +:--:1 +```python +ssl = [ + None, # No fallback if names do not match! + { + "cert": "certs/example.com/fullchain.pem", + "key": "certs/example.com/privkey.pem", + "names": ["example.com", "*.bigcorp.test"], + } +] +app.run(host="0.0.0.0", port=8443, ssl=ssl) +``` +:--- + +## Accessing TLS information in handlers via `request.conn_info` fields + +* `.ssl` - is the connection secure (bool) +* `.cert` - certificate info and dict fields of the currently active cert (dict) +* `.server_name` - the SNI sent by the client (str, may be empty) + +Do note that all `conn_info` fields are per connection, where there may be many requests over time. If a proxy is used in front of your server, these requests on the same pipe may even come from different users. + +## Redirect HTTP to HTTPS, with certificate requests still over HTTP + +In addition to your normal server(s) running HTTPS, run another server for redirection, `http_redir.py`: + +```python +from sanic import Sanic, exceptions, response + +app = Sanic("http_redir") + +# Serve ACME/certbot files without HTTPS, for certificate renewals +app.static("/.well-known", "/var/www/.well-known", resource_type="dir") + +@app.exception(exceptions.NotFound, exceptions.MethodNotSupported) +def redirect_everything_else(request, exception): + server, path = request.server_name, request.path + if server and path.startswith("/"): + return response.redirect(f"https://{server}{path}", status=308) + return response.text("Bad Request. Please use HTTPS!", status=400) +``` + +It is best to setup this as a systemd unit separate of your HTTPS servers. You may need to run HTTP while initially requesting your certificates, while you cannot run the HTTPS server yet. Start for IPv4 and IPv6: + +``` +sanic http_redir:app -H 0.0.0.0 -p 80 +sanic http_redir:app -H :: -p 80 +``` + +Alternatively, it is possible to run the HTTP redirect application from the main application: + +```python +# app == Your main application +# redirect == Your http_redir application +@app.before_server_start +async def start(app, _): + app.ctx.redirect = await redirect.create_server( + port=80, return_asyncio_server=True + ) + app.add_task(runner(redirect, app.ctx.redirect)) + + +@app.before_server_stop +async def stop(app, _): + await app.ctx.redirect.close() + + +async def runner(app, app_server): + app.is_running = True + try: + app.signalize() + app.finalize() + app.state.is_started = True + await app_server.serve_forever() + finally: + app.is_running = False + app.is_stopping = True +``` + +## Get certificates for your domain names + +You can get free certificates from [Let's Encrypt](https://letsencrypt.org/). Install [certbot](https://certbot.eff.org/) via your package manager, and request a certificate: + +```sh +sudo certbot certonly --key-type ecdsa --preferred-chain "ISRG Root X1" -d example.com -d www.example.com +``` + +Multiple domain names may be added by further `-d` arguments, all stored into a single certificate which gets saved to `/etc/letsencrypt/live/example.com/` as per **the first domain** that you list here. + +The key type and preferred chain options are necessary for getting a minimal size certificate file, essential for making your server run as *fast* as possible. The chain will still contain one RSA certificate until when Let's Encrypt gets their new EC chain trusted in all major browsers, possibly around 2023. diff --git a/src/ru/guide/how-to/toc.md b/src/ru/guide/how-to/toc.md new file mode 100644 index 0000000000..4afddd9afe --- /dev/null +++ b/src/ru/guide/how-to/toc.md @@ -0,0 +1,13 @@ +# Table of Contents + +We have compiled fully working examples to answer common questions and user cases. For the most part, the examples are as minimal as possible, but should be complete and runnable solutions. + +| Page | How do I ... | +|:------------------------------------------- |:------------------------------------------------------------------- | +| [Application mounting](./mounting.md) | ... mount my application at some path above the root? | +| [Authentication](./authentication.md) | ... control authentication and authorization? | +| [Autodiscovery](./autodiscovery.md) | ... autodiscover the components I am using to build my application? | +| [CORS](./cors.md) | ... configure my application for CORS? | +| [ORM](./orm) | ... use an ORM with Sanic? | +| ["Static" Redirects](./static-redirects.md) | ... configure static redirects | +| [TLS/SSL/HTTPS](./tls.md) | ... run Sanic via HTTPS?
... redirect HTTP to HTTPS? | diff --git a/src/ru/guide/how-to/validation.md b/src/ru/guide/how-to/validation.md new file mode 100644 index 0000000000..1d64b06c63 --- /dev/null +++ b/src/ru/guide/how-to/validation.md @@ -0,0 +1 @@ +валидация diff --git a/src/ru/guide/how-to/websocket-feed.md b/src/ru/guide/how-to/websocket-feed.md new file mode 100644 index 0000000000..7e4fda2a8e --- /dev/null +++ b/src/ru/guide/how-to/websocket-feed.md @@ -0,0 +1 @@ +Канал веб-сокета diff --git a/src/ru/guide/release-notes/v21.12.md b/src/ru/guide/release-notes/v21.12.md new file mode 100644 index 0000000000..d2a443b404 --- /dev/null +++ b/src/ru/guide/release-notes/v21.12.md @@ -0,0 +1,486 @@ +# Version 21.12 (LTS) + +[[toc]] + +## Introduction + +This is the final release of the version 21 [release cycle](../../org/policies.md#release-schedule). Version 21 will now enter long-term support and will be supported for two years until December 2023. + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + +### Strict application and blueprint names + +In [v21.6](./v21.6.md#stricter-application-and-blueprint-names-and-deprecation) application and blueprint names were required to conform to a new set of restrictions. That change is now being enforced at startup time. + +Names **must**: + +1. Only use alphanumeric characters (`a-zA-Z0-9`) +2. May contain a hyphen (`-`) or an underscore (`_`) +3. Must begin with a letter or underscore (`a-zA-Z_`) + +### Strict application and blueprint properties + +The old leniency to allow directly setting properties of a `Sanic` or `Blueprint` object was deprecated and no longer allowed. You must use the `ctx` object. + +```python +app = Sanic("MyApp") +app.ctx.db = Database() +``` + +### Removals + +The following deprecated features no longer exist: + +- `sanic.exceptions.abort` +- `sanic.views.CompositionView` +- `sanic.response.StreamingHTTPResponse` + +### Upgrade your streaming responses (if not already) + +The `sanic.response.stream` response method has been **deprecated** and will be removed in v22.6. If you are sill using an old school streaming response, please upgrade it. + +**OLD - Deprecated** + +```python +async def sample_streaming_fn(response): + await response.write("foo,") + await response.write("bar") + +@app.route("/") +async def test(request: Request): + return stream(sample_streaming_fn, content_type="text/csv") +``` + +**Current** + +```python +async def sample_streaming_fn(response): + await response.write("foo,") + await response.write("bar") + +@app.route("/") +async def test(request: Request): + response = await request.respond(content_type="text/csv") + await response.send("foo,") + await response.send("bar") +``` + +### CLI overhaul and MOTD (Message of the Day) + +The Sanic CLI has received a fairly extensive upgrade. It adds a bunch of new features to make it on par with `app.run()`. It also includes a new MOTD display to provide quick, at-a-glance highlights about your running environment. The MOTD is TTY-aware, and therefore will be less verbose in server logs. It is mainly intended as a convenience during application development. + +``` +$ sanic --help +usage: sanic [-h] [--version] [--factory] [-s] [-H HOST] [-p PORT] [-u UNIX] [--cert CERT] [--key KEY] [--tls DIR] [--tls-strict-host] + [-w WORKERS | --fast] [--access-logs | --no-access-logs] [--debug] [-d] [-r] [-R PATH] [--motd | --no-motd] [-v] + [--noisy-exceptions | --no-noisy-exceptions] + module + + ▄███ █████ ██ ▄█▄ ██ █ █ ▄██████████ + ██ █ █ █ ██ █ █ ██ + ▀███████ ███▄ ▀ █ █ ██ ▄ █ ██ + ██ █████████ █ ██ █ █ ▄▄ + ████ ████████▀ █ █ █ ██ █ ▀██ ███████ + + To start running a Sanic application, provide a path to the module, where + app is a Sanic() instance: + + $ sanic path.to.server:app + + Or, a path to a callable that returns a Sanic() instance: + + $ sanic path.to.factory:create_app --factory + + Or, a path to a directory to run as a simple HTTP server: + + $ sanic ./path/to/static --simple + +Required +======== + Positional: + module Path to your Sanic app. Example: path.to.server:app + If running a Simple Server, path to directory to serve. Example: ./ + +Optional +======== + General: + -h, --help show this help message and exit + --version show program's version number and exit + + Application: + --factory Treat app as an application factory, i.e. a () -> callable + -s, --simple Run Sanic as a Simple Server, and serve the contents of a directory + (module arg should be a path) + + Socket binding: + -H HOST, --host HOST Host address [default 127.0.0.1] + -p PORT, --port PORT Port to serve on [default 8000] + -u UNIX, --unix UNIX location of unix socket + + TLS certificate: + --cert CERT Location of fullchain.pem, bundle.crt or equivalent + --key KEY Location of privkey.pem or equivalent .key file + --tls DIR TLS certificate folder with fullchain.pem and privkey.pem + May be specified multiple times to choose multiple certificates + --tls-strict-host Only allow clients that send an SNI matching server certs + + Worker: + -w WORKERS, --workers WORKERS Number of worker processes [default 1] + --fast Set the number of workers to max allowed + --access-logs Display access logs + --no-access-logs No display access logs + + Development: + --debug Run the server in debug mode + -d, --dev Currently is an alias for --debug. But starting in v22.3, + --debug will no longer automatically trigger auto_restart. + However, --dev will continue, effectively making it the + same as debug + auto_reload. + -r, --reload, --auto-reload Watch source directory for file changes and reload on changes + -R PATH, --reload-dir PATH Extra directories to watch and reload on changes + + Output: + --motd Show the startup display + --no-motd No show the startup display + -v, --verbosity Control logging noise, eg. -vv or --verbosity=2 [default 0] + --noisy-exceptions Output stack traces for all exceptions + --no-noisy-exceptions No output stack traces for all exceptions +``` + +### Server running modes and changes coming to `debug` + +There are now two running modes: `DEV` and `PRODUCTION`. By default, Sanic server will run under `PRODUCTION` mode. This is intended for deployments. + +Currently, `DEV` mode will operate very similarly to how `debug=True` does in older Sanic versions. However, in v22.3. `debug=True` will **no longer** enable auto-reload. If you would like to have debugging and auto-reload, you should enable `DEV` mode. + +**DEVELOPMENT** + +``` +$ sanic server:app --dev +``` + +```python +app.run(debug=True, auto_reload=True) +``` + +**PRODUCTION** + +``` +$ sanic server:app +``` + +```python +app.run() +``` + +Beginning in v22.3, `PRODUCTION` mode will no longer enable access logs by default. + +A summary of the changes are as follows: + +| Flag | Mode | Tracebacks | Logging | Access logs | Reload | Max workers | +| ------- | ----- | ---------- | ------- | ----------- | ------ | ----------- | +| --debug | DEBUG | yes | DEBUG | yes | ^1 | | +| | PROD | no | INFO ^2 | ^3 | | | +| --dev | DEBUG | yes | DEBUG | yes | yes | | +| --fast | | | | | | yes | + + +- ^1 `--debug` to deprecate auto-reloading and remove in 22.3 +- ^2 After 22.3 this moves to WARNING +- ^3 After 22.3: no + +### Max allowed workers + +You can easily spin up the maximum number of allowed workers using `--fast`. + +``` +$ sanic server:app --fast +``` + +```python +app.run(fast=True) +``` + +### First-class Sanic Extensions support + +[Sanic Extensions](../../plugins/sanic-ext/getting-started.md) provides a number of additional features specifically intended for API developers. You can now easily implement all of the functionality it has to offer without additional setup as long as the package is in the environment. These features include: + +- Auto create `HEAD`, `OPTIONS`, and `TRACE` endpoints +- CORS protection +- Predefined, endpoint-specific response serializers +- Dependency injection into route handlers +- OpenAPI documentation with Redoc and/or Swagger +- Request query arguments and body input validation + +The preferred method is to install it along with Sanic, but you can also install the packages on their own. + +---:1 +``` +$ pip install sanic[ext] +``` + +:--:1 + +``` +$ pip install sanic sanic-ext +``` + +:--- + +After that, no additional configuration is required. Sanic Extensions will be attached to your application and provide all of the additional functionality with **no further configuration**. + +If you want to change how it works, or provide additional configuration, you can change Sanic extensions using `app.extend`. The following examples are equivalent. The `Config` object is to provide helpful type annotations for IDE development. + +---:1 +```python +# This is optional, not required +app = Sanic("MyApp") +app.extend(config={"oas_url_prefix": "/apidocs"}) +``` +:--: +```python +# This is optional, not required +app = Sanic("MyApp") +app.config.OAS_URL_PREFIX = "/apidocs" +``` +:--- + +---:1 +```python +# This is optional, not required +from sanic_ext import Config + +app = Sanic("MyApp") +app.extend(config=Config(oas_url_prefix="/apidocs")) +``` +:--: + +:--- + +### Contextual exceptions + +In [v21.9](./v21.9.md#default-exception-messages) we added default messages to exceptions that simplify the ability to consistently raise exceptions throughout your application. + +```python +class TeapotError(SanicException): + status_code = 418 + message = "Sorry, I cannot brew coffee" + +raise TeapotError +``` + +But this lacked two things: + +1. A dynamic and predictable message format +2. The ability to add additional context to an error message (more on this in a moment) + +The current release allows any Sanic exception to have additional information to when raised to provide context when writing an error message: + +```python +class TeapotError(SanicException): + status_code = 418 + + @property + def message(self): + return f"Sorry {self.extra['name']}, I cannot make you coffee" + +raise TeapotError(extra={"name": "Adam"}) +``` + +The new feature allows the passing of `extra` meta to the exception instance. This `extra` info object **will be suppressed** when in `PRODUCTION` mode, but displayed in `DEVELOPMENT` mode. + +---:1 **PRODUCTION** + +![image](https://user-images.githubusercontent.com/166269/139014161-cda67cd1-843f-4ad2-9fa1-acb94a59fc4d.png) :--:1 **DEVELOPMENT** + +![image](https://user-images.githubusercontent.com/166269/139014121-0596b084-b3c5-4adb-994e-31ba6eba6dad.png) :--- + +Getting back to item 2 from above: _The ability to add additional context to an error message_ + +This is particularly useful when creating microservices or an API that you intend to pass error messages back in JSON format. In this use case, we want to have some context around the exception beyond just a parseable error message to return details to the client. + + +```python +raise TeapotError(context={"foo": "bar"}) +``` + +This is information **that we want** to always be passed in the error (when it is available). Here is what it should look like: + +---:1 **PRODUCTION** + +```json +{ + "description": "I'm a teapot", + "status": 418, + "message": "Sorry Adam, I cannot make you coffee", + "context": { + "foo": "bar" + } +} +``` +:--:1 **DEVELOPMENT** + +```json +{ + "description": "I'm a teapot", + "status": 418, + "message": "Sorry Adam, I cannot make you coffee", + "context": { + "foo": "bar" + }, + "extra": { + "name": "Adam", + "more": "lines", + "complex": { + "one": "two" + } + }, + "path": "/", + "args": {}, + "exceptions": [ + { + "type": "TeapotError", + "exception": "Sorry Adam, I cannot make you coffee", + "frames": [ + { + "file": "handle_request", + "line": 83, + "name": "handle_request", + "src": "" + }, + { + "file": "/tmp/p.py", + "line": 17, + "name": "handler", + "src": "raise TeapotError(" + } + ] + } + ] +} +``` +:--- + +### Background task management + +When using the `app.add_task` method to create a background task, there now is the option to pass an optional `name` keyword argument that allows it to be fetched, or cancelled. + +```python +app.add_task(dummy, name="dummy_task") +task = app.get_task("dummy_task") + +app.cancel_task("dummy_task") +``` + +### Route context kwargs in definitions + +When a route is defined, you can add any number of keyword arguments with a `ctx_` prefix. These values will be injected into the route `ctx` object. + +```python +@app.get("/1", ctx_label="something") +async def handler1(request): + ... + +@app.get("/2", ctx_label="something") +async def handler2(request): + ... + +@app.get("/99") +async def handler99(request): + ... + +@app.on_request +async def do_something(request): + if request.route.ctx.label == "something": + ... +``` + +### Blueprints can be registered at any time + +In previous versions of Sanic, there was a strict ordering of when a Blueprint could be attached to an application. If you ran `app.blueprint(bp)` *before* attaching all objects to the Blueprint instance, they would be missed. + +Now, you can attach a Blueprint at anytime and everything attached to it will be included at startup. + +### Noisy exceptions (force all exceptions to logs) + +There is a new `NOISY_EXCEPTIONS` config value. When it is `False` (which is the default), Sanic will respect the `quiet` property of any `SanicException`. This means that an exception with `quiet=True` will not be displayed to the log output. + +However, when setting `NOISY_EXCEPTIONS=True`, all exceptions will be logged regardless of the `quiet` value. + +This can be helpful when debugging. + +```python +app.config.NOISY_EXCEPTIONS = True +``` + +### Signal events as `Enum` + +There is an `Enum` with all of the built-in signal values for convenience. + +```python +from sanic.signals import Event + +@app.signal(Event.HTTP_LIFECYCLE_BEGIN) +async def connection_opened(conn_info): + ... +``` + +### Custom type casting of environment variables + +By default, Sanic will convert an `int`, `float`, or a `bool` value when applying environment variables to the `config` instance. You can extend this with your own converter: + +```python +app = Sanic(..., config=Config(converters=[UUID])) +``` + +### Disable `uvloop` by configuration value + +The usage of `uvloop` can be controlled by configuration value: + + +```python +app.config.USE_UVLOOP = False +``` + +### Run Sanic server with multiple TLS certificates + +Sanic can be run with multiple TLS certificates: + +```python +app.run( + ssl=[ + "/etc/letsencrypt/live/example.com/", + "/etc/letsencrypt/live/mysite.example/", + ] +) +``` + +## News + +### Coming Soon: Python Web Development with Sanic + +A book about Sanic is coming soon by one of the core developers, [@ahopkins](https://github.com/ahopkins). + +Learn more at [sanicbook.com](https://sanicbook.com). + +> Get equipped with the practical knowledge of working with Sanic to increase the performance and scalability of your web applications. While doing that, we will level-up your development skills as you learn to customize your application to meet the changing business needs without having to significantly over-engineer the app. + +A portion of book proceeds goes into the Sanic Community Organization to help fund the development and operation of Sanic. So, buying the book is another way you can support Sanic. + +### Dark mode for the docs + +If you have not already noticed, this Sanic website is now available in a native dark mode. You can toggle the theme at the top right of the page. + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@adarsharegmi](https://github.com/adarsharegmi) [@ahopkins](https://github.com/ahopkins) [@ashleysommer](https://github.com/ashleysommer) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@cnicodeme](https://github.com/cnicodeme) [@kianmeng](https://github.com/kianmeng) [@meysam81](https://github.com/meysam81) [@nuxion](https://github.com/nuxion) [@prryplatypus](https://github.com/prryplatypus) [@realDragonium](https://github.com/realDragonium) [@SaidBySolo](https://github.com/SaidBySolo) [@sjsadowski](https://github.com/sjsadowski) [@Tronic](https://github.com/tronic) [@Varriount](https://github.com/Varriount) [@vltr](https://github.com/vltr) [@whos4n3](https://github.com/whos4n3) + +And, a special thank you to [@miss85246](https://github.com/miss85246) and [@ZinkLu](https://github.com/ZinkLu) for their tremendous work keeping the documentation synced and translated into Chinese. + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/ru/guide/release-notes/v21.3.md b/src/ru/guide/release-notes/v21.3.md new file mode 100644 index 0000000000..bca0729af6 --- /dev/null +++ b/src/ru/guide/release-notes/v21.3.md @@ -0,0 +1,262 @@ +# Version 21.3 + +[[toc]] + +## Introduction + +Sanic is now faster. + +Well, it already was fast. But with the first iteration of the v21 release, we incorporated a few major milestones that have made some tangible improvements. These encompass some ideas that have been in the works for years, and have finally made it into the released version. + +::: warning Breaking changes Version 21.3 introduces a lot of new features. But, it also includes some breaking changes. This is why these changes were introduced after the last LTS. If you rely upon something that has been removed, you should continue to use v20.12LTS until you are able to upgrade. + +```bash +pip install "sanic>=20.12,<20.13" +pip freeze > requirements.txt +``` + +For most typical installations, you should be able to upgrade without a problem. ::: + +## What to know + +Notable new or breaking features, and what to upgrade... + +### Python 3.7+ Only + +This version drops Python 3.6 support. Version 20.12LTS will continue to support Python 3.6 until its EOL in December, 2022, and version 19.12LTS will support it until its EOL in December, 2021. + +Read more about our [LTS policy](../project/policies.md#long-term-support-v-interim-releases). + +### Streaming as first class citizen + +The biggest speed improvement came from unifying the request/response cycle into a single flow. Previously, there was a difference between regular cycles, and streaming cycles. This has been simplified under the hood, even though the API is staying the same right now for compatibility. The net benefit is that **all** requests now should see a new benefit. + +Read more about [streaming changes](../advanced/streaming.md#response-streaming). + +### Router overhaul + +The old Sanic router was based upon regular expressions. In addition it suffered from a number of quirks that made it hard to modify at run time, and resulted in some performance issues. This change has been years in the making and now [converts the router to a compiled tree at startup](https://community.sanicframework.org/t/a-fast-new-router/649/41). Look for additional improvements throughout the year. + +The outward facing API has kept backwards compatibility. However, if you were accessing anything inside the router specifically, you many notice some changes. For example: + +1. `Router.get()` has a new return value +2. `Route` is now a proper class object and not a `namedtuple` +3. If building the router manually, you will need to call `Router.finalize()` before it is usable +4. There is a new `` pattern that can be matched in your routes +5. You cannot startup an application without at least one route defined + +The router is now located in its own repository: [sanic-org/sanic-router](https://github.com/sanic-org/sanic-router) and is also its own [standalone package on PyPI](https://pypi.org/project/sanic-routing/). + +### Signals API ⭐️ + +_BETA Feature: API to be finalized in v21.6_ + +A side benefit of the new router is that it can do double duty also powering the [new signals API](https://github.com/sanic-org/sanic/issues/1630). This feature is being released for public usage now, and likely the public API will not change in its final form. + +The core ideas of this feature are: + +1. to allow the developer greater control and access to plugging into the server and request lifecycles, +2. to provide new tools to synchronize and send messages through your application, and +3. to ultimately further increase performance. + +The API introduces three new methods: + +- `@app.signal(...)` - For defining a signal handler. It looks and operates very much like a route. Whenever that signal is dispatched, this handler will be executed. +- `app.event(...)` - An awaitable that can be used anywhere in your application to pause execution until the event is triggered. +- `app.dispatch(...)` - Trigger an event and cause the signal handlers to execute. + +```python +@app.signal("foo.bar.") +async def signal_handler(thing, **kwargs): + print(f"[signal_handler] {thing=}", kwargs) + +async def wait_for_event(app): + while True: + print("> waiting") + await app.event("foo.bar.*") + print("> event found\n") + +@app.after_server_start +async def after_server_start(app, loop): + app.add_task(wait_for_event(app)) + +@app.get("/") +async def trigger(request): + await app.dispatch("foo.bar.baz") + return response.text("Done.") +``` + +### Route naming + +Routes used to be referenced by both `route.name` and `route.endpoint`. While similar, they were slightly different. Now, all routes will be **consistently** namespaced and referenced. + +``` +.[optional:.] +``` + +This new "name" is assigned to the property `route.name`. We are deprecating `route.endpoint`, and will remove that property in v21.9. Until then, it will be an alias for `route.name`. + +In addition, naming prefixes that had been in use for things like static, websocket, and blueprint routes have been removed. + +### New decorators + +Several new convenience decorators to help IDEs with autocomplete. + +```python +# Alias to @app.listener("...") +@app.before_server_start +@app.after_server_stop +@app.before_server_start +@app.after_server_stop + +# Alias to @app.middleware("...") +@app.on_request +@app.on_response +``` + +### Unquote in route + +If you have a route that uses non-ascii characters, Sanic will no longer `unquote` the text for you. You will need to specifically tell the route definition that it should do so. + +```python +@app.route("/overload/", methods=["GET"], unquote=True) +async def handler2(request, param): + return text("OK2 " + param) + +request, response = app.test_client.get("/overload/您好") +assert response.text == "OK2 您好" +``` + +If you forget to do so, your text will remain encoded. + +### Alter `Request.match_info` + +The `match_info` has always provided the data for the matched path parameters. You now have access to modify that, for example in middleware. + +```python +@app.on_request +def convert_to_snake_case(request): + request.match_info = to_snake(request.match_info) +``` + +### Version types in routes + +The `version` argument in routes can now be: + +- `str` +- `int` +- `float` + +```python +@app.route("/foo", version="2.1.1") +@app.route("/foo", version=2) +@app.route("/foo", version=2.1) +``` +### Safe method handling with body + +Route handlers for `GET`, `HEAD`, `OPTIONS` and `DELETE` will not decode any HTTP body passed to it. You can override this: + +```python +@app.delete(..., ignore_body=False) +``` + +### Application, Blueprint and Blueprint Group parity + +The `Sanic` and `Blueprint` classes share a common base. Previously they duplicated a lot of functionality, that lead to slightly different implementations between them. Now that they both inherit the same base class, developers and plugins should have a more consistent API to work with. + +Also, Blueprint Groups now also support common URL extensions like the `version` and `strict_slashes` keyword arguments. + +### Dropped `httpx` from dependencies + +There is no longer a dependency on `httpx`. + +### Removed `testing` library + +Sanic internal testing client has been removed. It is now located in its own repository: [sanic-org/sanic-testing](https://github.com/sanic-org/sanic-testing) and is also its own [standalone package on PyPI](https://pypi.org/project/sanic-testing/). + +If you have `sanic-testing` installed, it will be available and usable on your `Sanic()` application instances as before. So, the **only** change you will need to make is to add `sanic-testing` to your test suite requirements. + +### Application and connection level context (`ctx`) objects + +Version 19.9 [added ](https://github.com/sanic-org/sanic/pull/1666/files) the `request.ctx` API. This helpful construct easily allows for attaching properties and data to a request object (for example, in middleware), and reusing the information elsewhere int he application. + +Similarly, this concept is being extended in two places: + +1. the application instance, and +2. a transport connection. + +#### Application context + +A common use case is to attach properties to the app instance. For the sake of consistency, and to avoid the issue of name collision with Sanic properties, the `ctx` object now exists on `Sanic` instances. + +```python +@app.before_server_startup +async def startup_db(app, _): + # WRONG + app.db = await connect_to_db() + + # CORRECT + app.ctx.db = await connect_to_db() +``` + +#### Connection context + +When a client sends a keep alive header, Sanic will attempt to keep the transport socket [open for a period of time](../deployment/configuration.md#keep-alive-timeout). That transport object now has a `ctx` object available on it. This effectively means that multiple requests from a single client (where the transport layer is being reused) may share state. + +```python +@app.on_request +async def increment_foo(request): + if not hasattr(request.conn_info.ctx, "foo"): + request.conn_info.ctx.foo = 0 + request.conn_info.ctx.foo += 1 + +@app.get("/") +async def count_foo(request): + return text(f"request.conn_info.ctx.foo={request.conn_info.ctx.foo}") +``` + +```bash +$ curl localhost:8000 localhost:8000 localhost:8000 +request.conn_info.ctx.foo=1 +request.conn_info.ctx.foo=2 +request.conn_info.ctx.foo=3 +``` + +::: warning +Connection level context is an experimental feature, and should be finalized in v21.6. +::: + +## News + + +### A NEW frontpage 🎉 + +We have split the documentation into two. The docstrings inside the codebase will still continue to build sphinx docs to ReadTheDocs. However, it will be limited to API documentation. The new frontpage will house the "Sanic User Guide". + +The new site runs on Vuepress. Contributions are welcome. We also invite help in translating the documents. + +As a part of this, we also freshened up the RTD documentation and changed it to API docs only. + +### Chat has moved to Discord + +The Gitter chatroom has taken one step closer to being phased out. In its place we opened a [Discord server](https://discord.gg/FARQzAEMAA). + +### Open Collective + +The Sanic Community Organization has [opened a page on Open Collective](https://opencollective.com/sanic-org) to enable anyone that would like to financially support the development of Sanic. + +### 2021 Release Managers + +Thank you to @sjsadowski and @yunstanford for acting as release managers for both 2019 and 2020. This year's release managers are @ahopkins and @vltr. + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@ahopkins](https://github.com/ahopkins) [@akshgpt7](https://github.com/akshgpt7) [@artcg](https://github.com/artcg) [@ashleysommer](https://github.com/ashleysommer) [@elis-k](https://github.com/elis-k) [@harshanarayana](https://github.com/harshanarayana) [@sjsadowski](https://github.com/sjsadowski) [@tronic](https://github.com/tronic) [@vltr](https://github.com/vltr), + +To [@ConnorZhang](https://github.com/miss85246) and [@ZinkLu](https://github.com/ZinkLu) for translating our documents into Chinese, + +--- + +Make sure to checkout the changelog to get links to all the PRs, etc. diff --git a/src/ru/guide/release-notes/v21.6.md b/src/ru/guide/release-notes/v21.6.md new file mode 100644 index 0000000000..388a97d211 --- /dev/null +++ b/src/ru/guide/release-notes/v21.6.md @@ -0,0 +1,324 @@ +# Version 21.6 + +[[toc]] + +## Introduction + +This is the second release of the version 21 [release cycle](../project/policies.md#release-schedule). There will be one more release in September before version 21 is "finalized" in the December long-term support version. One thing users may have noticed starting in 21.3, the router was moved to its own package: [`sanic-routing`](https://pypi.org/project/sanic-routing). This change is likely to stay for now. Starting with this release, the minimum required version is 0.7.0. + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + +### Deprecation of `StreamingHTTPResponse` + +The use of `StreamingHTTPResponse` has been deprecated and will be removed in the 21.12 release. This impacts both `sanic.response.stream` and `sanic.response.file_stream`, which both under the hood instantiate `StreamingHTTPResponse`. + +Although the exact migration path has yet to be determined, `sanic.response.stream` and `sanic.response.file_stream` will continue to exist in v21.12 in some form as convenience operators. Look for more details throughout this Summer as we hope to have this finalized by the September release. + +### Deprecation of `CompositionView` + +Usage of `CompositionView` has been deprecated and will be removed in 21.12. + +### Deprecation of path parameter types: `string` and `number` + +Going forward, you should use `str` and `float` for path param types instead of `string` and `number`. + +```python +@app.get("//") +async def handler(request, foo: str, bar: float): + ... +``` + +Existing `string` and `number` types are aliased and will continue to work, but will be removed in v21.12. + +### Version 0.7 router upgrades + +This includes a number of bug fixes and more gracefully handles a wider array of edge cases than v0.6. If you experience any patterns that are not supported, [please report them](https://github.com/sanic-org/sanic-routing/issues). You can see some of the issues resolved on the `sanic-routing` [release notes](https://github.com/sanic-org/sanic-routing/releases). + +### Inline streaming with `eof()` + +Version 21.3 included [big changes in how streaming is handled](https://sanic.dev/en/guide/release-notes/v21.3.html#what-to-know). The pattern introduced will become the default (see below). As a convenience, a new `response.eof()` method has been included. It should be called once the final data has been pushed to the client: + +```python +@app.route("/") +async def test(request): + response = await request.respond(content_type="text/csv") + await response.send("foo,") + await response.send("bar") + await response.eof() + return response +``` + +### New path parameter type: `slug` + +You can now specify a dynamic path segment as a `slug` with appropriate matching: + +```python +@app.get("/articles/") +async def article(request, article_slug: str): + ... +``` + +Slugs must consist of lowercase letters or digits. They may contain a hyphen (`-`), but it cannot be the first character. + +``` +this-is-a-slug +with-123-is-also-a-slug +111-at-start-is-a-slug +NOT-a-slug +-NOT-a-slug +``` + +### Stricter application and blueprint names, and deprecation + +Your application and `Blueprint` instances must conform to a stricter set of requirements: + +1. Only consisting of alphanumeric characters +2. May contain a hyphen (`-`) or an underscore (`_`) +3. Must begin with a letter (uppercase or lowercase) + +The naming convention is similar to Python variable naming conventions, with the addition of allowing hyphens (`-`). + +The looser standard has been deprecatated. Beginning in 21.12, non-conformance will be a startup time error. + +### A new access on `Route` object: `route.uri` + +The `Route` object in v21.3 no longer had a `uri` attribute. Instead, the closes you could get was `route.path`. However, because of how `sanic-routing` works, the `path` property does *not* have a leading `/`. This has been corrected so that now there is a `route.uri` with a leading slash: + +```python +route.uri == f"/{route.path}" +``` + +### A new accessor on `Request` object impacting IPs + +To access the IP address of the incoming request, Sanic has had a convenience accessor on the request object: `request.ip`. That is not new, and comes from an underlying object that provides details about the open HTTP connection: `request.conn_info`. + +The current version adds a new `client_ip` accessor to that `conn_info` object. For IPv4, you will not notice a difference. However, for IPv6 applications, the new accessor will provide an "unwrapped" version of the address. Consider the following example: + +```python +@app.get("/") +async def handler(request): + return json( + { + "request.ip": request.ip, + "request.conn_info.client": request.conn_info.client, + "request.conn_info.client_ip": request.conn_info.client_ip, + } + ) + + +app.run(sock=my_ipv6_sock) +``` + +```bash +$ curl http://\[::1\]:8000 +{ + "request.ip": "::1", + "request.conn_info.client": "[::1]", + "request.conn_info.client_ip": "::1" +} + +``` + +### Alternate `Config` and `Sanic.ctx` objects + +You can now pass your own config and context objects to your Sanic applications. A custom configuration *should* be a subclass of `sanic.config.Config`. The context object can be anything you want, with no restrictions whatsoever. + +```python +class CustomConfig(Config): + ... + +config = CustomConfig() +app = Sanic("custom", config=config) +assert isinstance(app.config, CustomConfig) +``` + +And... + +```python +class CustomContext: + ... + +ctx = CustomContext() +app = Sanic("custom", ctx=ctx) +assert isinstance(app.ctx, CustomContext) +``` + +### Sanic CLI improvements + +1. New flag for existing feature: `--auto-reload` +2. Some new shorthand flags for existing arguments +3. New feature: `--factory` +4. New feature: `--simple` +5. New feature: `--reload-dir` + +#### Factory applications + +For applications that follow the factory pattern (a function that returns a `sanic.Sanic` instance), you can now launch your application from the Sanic CLI using the `--factory` flag. + +```python +from sanic import Blueprint, Sanic, text + +bp = Blueprint(__file__) + +@bp.get("/") +async def handler(request): + return text("😎") + +def create_app() -> Sanic: + app = Sanic(__file__) + app.blueprint(bp) + return app +``` + +You can now run it: + +```bash +$ sanic path.to:create_app --factory +``` + +#### Sanic Simple Server + +Sanic CLI now includes a simple pattern to serve a directory as a web server. It will look for an `index.html` at the directory root. + +```bash +$ sanic ./path/to/dir --simple +``` + +::: warning This feature is still in early *beta* mode. It is likely to change in scope. ::: + +#### Additional reload directories + +When using either `debug` or `auto-reload`, you can include additional directories for Sanic to watch for new files. + +```bash +sanic ... --reload-dir=/path/to/foo --reload-dir=/path/to/bar +``` + +::: tip You do *NOT* need to include this on your application directory. Sanic will automatically reload when any Python file in your application changes. You should use the `reload-dir` argument when you want to listen and update your application when static files are updated. ::: + +### Version prefix + +When adding `version`, your route is prefixed with `/v`. This will always be at the beginning of the path. This is not new. + +```python +# /v1/my/path +app.route("/my/path", version=1) +``` + +Now, you can alter the prefix (and therefore add path segments *before* the version). + +```python +# /api/v1/my/path +app.route("/my/path", version=1, version_prefix="/api/v") +``` + +The `version_prefix` argument is can be defined in: + +- `app.route` and `bp.route` decorators (and all the convenience decorators also) +- `Blueprint` instantiation +- `Blueprint.group` constructor +- `BlueprintGroup` instantiation +- `app.blueprint` registration + +### Signal event auto-registration + +Setting `config.EVENT_AUTOREGISTER` to `True` will allow you to await any signal event even if it has not previously been defined with a signal handler. + +```python +@app.signal("do.something.start") +async def signal_handler(): + await do_something() + await app.dispatch("do.something.complete") + +# somethere else in your app: +await app.event("do.something.complete") +``` + +### Infinitely reusable and nestable `Blueprint` and `BlueprintGroup` + +A single `Blueprint` may not be assigned and reused to multiple groups. The groups themselves can also by infinitely nested into one or more other groups. This allows for an unlimited range of composition. + +### HTTP methods as `Enum` + +Sanic now has `sanic.HTTPMethod`, which is an `Enum`. It can be used interchangeably with strings: + +```python +from sanic import Sanic, HTTPMethod + +@app.route("/", methods=["post", "PUT", HTTPMethod.PATCH]) +async def handler(...): + ... +``` + +### Expansion of `HTTPMethodView` + +Class based views may be attached now in one of three ways: + +**Option 1 - Existing** +```python +class DummyView(HTTPMethodView): + ... + +app.add_route(DummyView.as_view(), "/dummy") +``` + +**Option 2 - From `attach` method** +```python +class DummyView(HTTPMethodView): + ... + +DummyView.attach(app, "/") +``` + +**Option 3 - From class definition at `__init_subclass__`** +```python +class DummyView(HTTPMethodView, attach=app, uri="/"): + ... +``` + +Options 2 and 3 are useful if your CBV is located in another file: + +```python +from sanic import Sanic, HTTPMethodView + +class DummyView(HTTPMethodView, attach=Sanic.get_app(), uri="/"): + ... +``` + +## News + +### Discord and support forums + +If you have not already joined our community, you can become a part by joining the [Discord server](https://discord.gg/FARQzAEMAA) and the [Community Forums](https://community.sanicframework.org/). Also, follow [@sanicframework](https://twitter.com/sanicframework) on Twitter. + +### SCO 2022 elections + +The Summer 🏝/Winter ❄️ (choose your Hemisphere) is upon us. That means we will be holding elections for the SCO. This year, we will have the following positions to fill: + +- Steering Council Member (2 year term) +- Steering Council Member (2 year term) +- Steering Council Member (1 year term) +- Release Manager v22 +- Release Manager v22 + +[@vltr](https://github.com/vltr) will be staying on to complete his second year on the Steering Council. + +If you are interested in learning more, you can read about the SCO [roles and responsibilities](../project/scope.md#roles-and-responsibilities), or Adam Hopkins on Discord. + +Nominations will begin September 1. More details will be available on the Forums as we get closer. + +### New project underway + +We have added a new project to the SCO umbrella: [`sanic-ext`](https://github.com/sanic-org/sanic-ext). It is not yet released, and in heavy active development. The goal for the project will ultimately be to replace [`sanic-openapi`](https://github.com/sanic-org/sanic-openapi) with something that provides more features for web application developers, including input validation, CORS handling, and HTTP auto-method handlers. If you are interested in helping out, let us know on Discord. Look for an initial release of this project sometime (hopefully) before the September release. + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@aaugustin](https://github.com/aaugustin) [@ahopkins](https://github.com/ahopkins) [@ajaygupta2790](https://github.com/ajaygupta2790) [@ashleysommer](https://github.com/ashleysommer) [@ENT8R](https://github.com/ent8r) [@fredlllll](https://github.com/fredlllll) [@graingert](https://github.com/graingert) [@harshanarayana](https://github.com/harshanarayana) [@jdraymon](https://github.com/jdraymon) [@Kyle-Verhoog](https://github.com/kyle-verhoog) [@sanjeevanahilan](https://github.com/sanjeevanahilan) [@sjsadowski](https://github.com/sjsadowski) [@Tronic](https://github.com/tronic) [@vltr](https://github.com/vltr) [@ZinkLu](https://github.com/zinklu) + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able, [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/ru/guide/release-notes/v21.9.md b/src/ru/guide/release-notes/v21.9.md new file mode 100644 index 0000000000..bc11399481 --- /dev/null +++ b/src/ru/guide/release-notes/v21.9.md @@ -0,0 +1,221 @@ +# Version 21.9 + +[[toc]] + +## Introduction + +This is the third release of the version 21 [release cycle](../../org/policies.md#release-schedule). Version 21 will be "finalized" in the December long-term support version release. + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + +### Removal of config values: `WEBSOCKET_READ_LIMIT`, `WEBSOCKET_WRITE_LIMIT` and `WEBSOCKET_MAX_QUEUE` + +With the complete overhaul of the websocket implementation, these configuration values were removed. There currently is not a plan to replace them. + +### Deprecation of default value of `FALLBACK_ERROR_FORMAT` + +When no error handler is attached, Sanic has used `html` as the fallback format-type. This has been deprecated and will change to `text` starting in v22.3. While the value of this has changed to `auto`, it will still continue to use HTML as the last resort thru v21.12LTS before changing. + +### `ErrorHandler.lookup` signature deprecation + +The `ErrorHandler.lookup` now **requires** two positional arguments: + +```python +def lookup(self, exception, route_name: Optional[str]): +``` + +A non-conforming method will cause Blueprint-specific exception handlers to not properly attach. + +### Reminder of upcoming removals + +As a reminder, the following items have already been deprecated, and will be removed in version 21.12LTS + +- `CompositionView` +- `load_env` (use `env_prefix` instead) +- Sanic objects (application instances, blueprints, and routes) must by alphanumeric conforming to: `^[a-zA-Z][a-zA-Z0-9_\-]*$` +- Arbitrary assignment of objects to application and blueprint instances (use `ctx` instead; removal of this has been bumped from 21.9 to 21.12) + +### Overhaul of websockets + +There has been a huge overhaul to the handling of websocket connections. Thanks to [@aaugustin](https://github.com/aaugustin) the [`websockets`](https://websockets.readthedocs.io/en/stable/index.html) now has a new implementation that allows Sanic to handle the I/O of websocket connections on its own. Therefore, Sanic has bumped the minimum version to `websockets>=10.0`. + +The change should mostly be unnoticeable to developers, except that some of the oddities around websocket handlers in Sanic have been corrected. For example, you now should be able to catch the `CancellError` yourself when someone disconnects: + +```python +@app.websocket("/") +async def handler(request, ws): + try: + while True: + await asyncio.sleep(0.25) + except asyncio.CancelledError: + print("User closed connection") +``` + +### Built-in signals + +Version [21.3](./v21.3.md) introduced [signals](../advanced/signals.md). Now, Sanic dispatches signal events **from within the codebase** itself. This means that developers now have the ability to hook into the request/response cycle at a much closer level than before. + +Previously, if you wanted to inject some logic you were limited to middleware. Think of integrated signals as _super_-middleware. The events that are dispatched now include: + +- `http.lifecycle.begin` +- `http.lifecycle.complete` +- `http.lifecycle.exception` +- `http.lifecycle.handle` +- `http.lifecycle.read_body` +- `http.lifecycle.read_head` +- `http.lifecycle.request` +- `http.lifecycle.response` +- `http.lifecycle.send` +- `http.middleware.after` +- `http.middleware.before` +- `http.routing.after` +- `http.routing.before` +- `server.init.after` +- `server.init.before` +- `server.shutdown.after` +- `server.shutdown.before` + +::: tip Note The `server` signals are the same as the four (4) main server listener events. In fact, those listeners themselves are now just convenience wrappers to signal implementations. ::: + +### Smarter `auto` exception formatting + +Sanic will now try to respond with an appropriate exception format based upon the endpoint and the client. For example, if your endpoint always returns a `sanic.response.json` object, then any exceptions will automatically be formatted in JSON. The same is true for `text` and `html` responses. + +Furthermore, you now can _explicitly_ control which formatter to use on a route-by-route basis using the route definition: + +```python +@app.route("/", error_format="json") +async def handler(request): + pass +``` + +### Blueprint copying + +Blueprints can be copied to new instances. This will carry forward everything attached to it, like routes, middleware, etc. + +```python +v1 = Blueprint("Version1", version=1) + +@v1.route("/something") +def something(request): + pass + +v2 = v1.copy("Version2", version=2) + +app.blueprint(v1) +app.blueprint(v2) +``` + +``` +/v1/something +/v2/something +``` +### Blueprint group convenience methods + +Blueprint groups should now have all of the same methods available to them as regular Blueprints. With this, along with Blueprint copying, Blueprints should now be very composable and flexible. + +### Accept header parsing + +Sanic `Request` objects can parse an `Accept` header to provide an ordered list of the client's content-type preference. You can simply access it as an accessor: + +```python +print(request.accept) +# ["*/*"] +``` + +It also is capable of handling wildcard matching. For example, assuming the incoming request included: + +``` +Accept: */* +``` + +Then, the following is `True`: + +```python +"text/plain" in request.accept +``` + +### Default exception messages + +Any exception that derives from `SanicException` can now define a default exception message. This makes it more convenient and maintainable to reuse the same exception in multiple places without running into DRY issues with the message that the exception provides. + +```python +class TeaError(SanicException): + message = "Tempest in a teapot" + + +raise TeaError +``` + +### Type annotation conveniences + +It is now possible to control the path parameter types using Python's type annotations. Instead of doing this: + +```python +@app.route("///") +def handler(request: Request, one: int, two: float, three: UUID): + ... +``` + +You can now simply do this: + +```python +@app.route("///") +def handler(request: Request, one: int, two: float, three: UUID): + ... +``` + +Both of these examples will result in the same routing principles to be applied. + +### Explicit static resource type + +You can now explicitly tell a `static` endpoint whether it is supposed to treat the resource as a file or a directory: + +```python +static("/", "/path/to/some/file", resource_type="file")) +``` + +## News + +### Release of `sanic-ext` and deprecation of `sanic-openapi` + +One of the core principles of Sanic is that it is meant to be a tool, not a dictator. As the frontpage of this website states: + +> Build the way you want to build without letting your tooling constrain you. + +This means that a lot of common features used (specifically by Web API developers) do not exist in the `sanic` repository. This is for good reason. Being unopinionated provides the developer freedom and flexibility. + +But, sometimes you do not want to have to build and rebuild the same things. Sanic has until now really relied upon the awesome support of the community to fill in the gaps with plugins. + +From the early days, there has been an official `sanic-openapi` package that offered the ability to create OpenAPI documentation based upon your application. But, that project has been plagued over the years and has not been given as much priority as the main project. + +Starting with the release of v21.9, the SCO is deprecating the `sanic-openapi` package and moving it to maintenance mode. This means that it will continue to get updates as needed to maintain it for the current future, but it will not receive any new feature enhancements. + +A new project called `sanic-ext` is taking its place. This package provides not only the ability to build OAS3 documentation, but fills in many of the gaps that API developers may want in their applications. For example, out of the box it will setup CORS, and auto enable `HEAD` and `OPTIONS` responses where needed. It also has the ability validate incoming data using either standard library Dataclasses or Pydantic models. + +The list of goodies includes: +- CORS protection +- incoming request validation +- auto OAS3 documentation using Redoc and/or Swagger UI +- auto `HEAD`, `OPTIONS`, and `TRACE` responses +- dependency injection +- response serialization + +This project is still in `alpha` mode for now and is subject to change. While it is considered to be production capable, there may be some need to change the API as we continue to add features. + +Checkout the [documentation](../../plugins/sanic-ext/getting-started.md) for more details. + + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@aaugustin](https://github.com/aaugustin) [@ahopkins](https://github.com/ahopkins) [@ashleysommer](https://github.com/ashleysommer) [@cansarigol3megawatt](https://github.com/cansarigol3megawatt) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@gluhar2006](https://github.com/gluhar2006) [@komar007](https://github.com/komar007) [@ombe1229](https://github.com/ombe1229) [@prryplatypus](https://github.com/prryplatypus) [@SaidBySolo](https://github.com/SaidBySolo) [@Tronic](https://github.com/tronic) [@vltr](https://github.com/vltr) + +And, a special thank you to [@miss85246](https://github.com/miss85246) and [@ZinkLu](https://github.com/ZinkLu) for their tremendous work keeping the documentation synced and translated into Chinese. + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able, [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/ru/guide/release-notes/v22.12.md b/src/ru/guide/release-notes/v22.12.md new file mode 100644 index 0000000000..50b3dd96ad --- /dev/null +++ b/src/ru/guide/release-notes/v22.12.md @@ -0,0 +1,176 @@ +# Version 22.12 + +[[toc]] + +## Introduction + +This is the final release of the version 22 [release cycle](../../org/policies.md#release-schedule). As such it is a **long-term support** release, and will be supported as stated in the [policies](../../org/policies.md#long-term-support-v-interim-releases). + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + +### 🚨 *BREAKING CHANGE* - Sanic Inspector is now an HTTP server + +Sanic v22.9 introduced the [Inspector](./v22.9.md#inspector) to allow live inspection of a running Sanic instance. This feature relied upon opening a TCP socket and communicating over a custom protocol. That basic TCP protocol has been dropped in favor of running a full HTTP service in its place. [Learn more about the Inspector](../deployment/inspector.md). + +The current release introduces a new HTTP server and a refreshed CLI experience. This enables several new features highlighted here. Perhaps the most significant change, however, is to move all of the Inspector's commands to a subparser on the CLI instance. + +``` +$ sanic inspect --help + + ▄███ █████ ██ ▄█▄ ██ █ █ ▄██████████ + ██ █ █ █ ██ █ █ ██ + ▀███████ ███▄ ▀ █ █ ██ ▄ █ ██ + ██ █████████ █ ██ █ █ ▄▄ + ████ ████████▀ █ █ █ ██ █ ▀██ ███████ + +Optional +======== + General: + -h, --help show this help message and exit + --host HOST, -H HOST Inspector host address [default 127.0.0.1] + --port PORT, -p PORT Inspector port [default 6457] + --secure, -s Whether to access the Inspector via TLS encryption + --api-key API_KEY, -k API_KEY Inspector authentication key + --raw Whether to output the raw response information + + Subcommands: + Run one or none of the below subcommands. Using inspect without a subcommand will fetch general information about the state of the application instance. + + Or, you can optionally follow inspect with a subcommand. If you have created a custom Inspector instance, then you can run custom commands. See https://sanic.dev/en/guide/deployment/inspector.html for more details. + + {reload,shutdown,scale,} + reload Trigger a reload of the server workers + shutdown Shutdown the application and all processes + scale Scale the number of workers + Run a custom command +``` + +#### CLI remote access now available + +The `host` and `port` of the Inspector are now explicitly exposed on the CLI as shown above. Previously in v22.9, they were inferred by reference to the application instance. Because of this change, it will be more possible to expose the Inspector on live production instances and access from a remote installation of the CLI. + +For example, you can check your running production deployment from your local development machine. + +``` +$ sanic inspect --host=1.2.3.4 +``` + +::: warning +For **production** instances, make sure you are _using TLS and authentication_ described below. +::: + +#### TLS encryption now available + +You can secure your remote Inspector access by providing a TLS certificate to encrypt the web traffic. + +```python +app.config.INSPECTOR_TLS_CERT = "/path/to/cert.pem" +app.config.INSPECTOR_TLS_KEY = "/path/to/key.pem" +``` + +To access an encrypted installation via the CLI, use the `--secure` flag. + +``` +$ sanic inspect --secure +``` + +#### Authentication now available + +To control access to the remote Inspector, you can protect the endpoints using an API key. + +```python +app.config.INSPECTOR_API_KEY = "Super-Secret-200" +``` + +To access a protected installation via the CLI, use the `--api-key` flag. + +``` +$ sanic inspect --api-key=Super-Secret-200 +``` + +This is equivalent to the header: `Authorization: Bearer `. + +``` +$ curl http://localhost:6457 -H "Authorization: Bearer Super-Secret-200" +``` + +### Scale number of running server workers + +The Inspector is now capable of scaling the number of worker processes. For example, to scale to 3 replicas, use the following command: + +``` +$ sanic inspect scale 3 +``` + +### Extend Inspector with custom commands + +The Inspector is now fully extendable to allow for adding custom commands to the CLI. For more information see [Custom Commands](../deployment/inspector.md#custom-commands). + +``` +$ sanic inspect foo --bar +``` + +### Early worker exit on failure + +The process manager shipped with v22.9 had a very short startup timeout. This was to protect against deadlock. This was increased to 30s, and a new mechanism has been added to fail early if there is a crash in a worker process on startup. + +### Introduce `JSONResponse` with convenience methods to update a JSON response body + +The `sanic.response.json` convenience method now returns a new subclass of `HTTPResponse` appropriately named: `JSONResponse`. This new type has some convenient methods for handling changes to a response body after its creation. + +```python +resp = json({"foo": "bar"}) +resp.update({"another": "value"}) +``` + +See [Returning JSON Data](../basics/response.md#returning-json-data) for more information. + +### Updates to downstream requirements: `uvloop` and `websockets` + +Minimum `uvloop` was set to `0.15.0`. Changes were added to make Sanic compliant with `websockets` version `11.0`. + +### Force exit on 2nd `ctrl+c` + +On supporting operating systems, the existing behavior is for Sanic server to try to perform a graceful shutdown when hitting `ctrl+c`. This new release will perform an immediate shutdown on subsequent `ctrl+c` after the initial shutdown has begun. + +### Deprecations and Removals + +1. *DEPRECATED* - The `--inspect*` commands introduced in v22.9 have been replaced with a new subcommand parser available as `inspect`. The flag versions will continue to operate until v23.3. You are encouraged to use the replacements. While this short deprecation period is a deviation from the standard two-cycles, we hope this change will be minimally disruptive. + ``` + OLD sanic ... --inspect + NEW sanic ... inspect + + OLD sanic ... --inspect-raw + NEW sanic ... inspect --raw + + OLD sanic ... --inspect-reload + NEW sanic ... inspect reload + + OLD sanic ... --inspect-shutdown + NEW sanic ... inspect shutdown + ``` + +## News + +The Sanic Community Organization will be headed by a new Steering Council for 2023. There are two returning and two new members. + +[@ahopkins](https://github.com/ahopkins) *returning* \ +[@prryplatypus](https://github.com/prryplatypus) *returning* \ +[@sjsadowski](https://github.com/sjsadowski) *NEW* \ +[@Tronic](https://github.com/Tronic) *NEW* + +The 2023 release managers are [@ahopkins](https://github.com/ahopkins) and [@sjsadowski](https://github.com/sjsadowski). + +If you are interested in getting more involved with Sanic, contact us on the [Discord server](https://discord.gg/FARQzAEMAA). + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@aaugustin](https://github.com/aaugustin) [@ahopkins](https://github.com/ahopkins) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@kijk2869](https://github.com/kijk2869) [@LiraNuna](https://github.com/LiraNuna) [@prryplatypus](https://github.com/prryplatypus) [@sjsadowski](https://github.com/sjsadowski) [@todddialpad](https://github.com/todddialpad) [@Tronic](https://github.com/Tronic) + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/ru/guide/release-notes/v22.3.md b/src/ru/guide/release-notes/v22.3.md new file mode 100644 index 0000000000..9222b48985 --- /dev/null +++ b/src/ru/guide/release-notes/v22.3.md @@ -0,0 +1,321 @@ +# Version 22.3 + +[[toc]] + +## Introduction + +This is the first release of the version 22 [release cycle](../../org/policies.md#release-schedule). All of the standard SCO libraries are now entering the same release cycle and will follow the same versioning pattern. Those packages are: + +- [`sanic-routing`](https://github.com/sanic-org/sanic-routing) +- [`sanic-testing`](https://github.com/sanic-org/sanic-testing) +- [`sanic-ext`](https://github.com/sanic-org/sanic-ext) + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + +### Application multi-serve + +The Sanic server now has an API to allow you to run multiple applications side-by-side in the same process. This is done by calling `app.prepare(...)` on one or more application instances, one or many times. Each time it should be bound to a unique host/port combination. Then, you begin serving the applications by calling `Sanic.serve()`. + +```python +app = Sanic("One") +app2 = Sanic("Two") + +app.prepare(port=9999) +app.prepare(port=9998) +app.prepare(port=9997) +app2.prepare(port=8888) +app2.prepare(port=8887) + +Sanic.serve() +``` + +In the above snippet, there are two applications that will be run concurrently and bound to multiple ports. This feature is *not* supported in the CLI. + +This pattern is meant to be an alternative to running `app.run(...)`. It should be noted that `app.run` is now just a shorthand for the above pattern and is still fully supported. + +### 👶 *BETA FEATURE* - New path parameter type: file extensions + +A very common pattern is to create a route that dynamically generates a file. The endpoint is meant to match on a file with an extension. There is a new path parameter to match files: ``. + +```python +@app.get("/path/to/") +async def handler(request, filename, ext): + ... +``` + +This will catch any pattern that ends with a file extension. You may, however want to expand this by specifying which extensions, and also by using other path parameter types for the file name. + +For example, if you want to catch a `.jpg` file that is only numbers: + +```python +@app.get("/path/to/") +async def handler(request, filename, ext): + ... +``` + +Some potential examples: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ definition + + example + + filename + + extension +
+ \ + + page.txt + + "page" + + "txt" +
+ \ + + cat.jpg + + "cat" + + "jpg" +
+ \ + + + png\ + + gif\ + + svg> | cat.jpg | "cat" | "jpg" +
+ + + 123.txt + + 123 + + "txt" +
+ + + + png\ + + gif\ + + svg> | 123.svg | 123 | "svg" +
+ + + 3.14.tar.gz + + 3.14 + + "tar.gz" +
+ +### 🚨 *BREAKING CHANGE* - Path parameter matching of non-empty strings + +A dynamic path parameter will only match on a non-empty string. + +Previously a route with a dynamic string parameter (`/` or `/`) would match on any string, including empty strings. It will now only match a non-empty string. To retain the old behavior, you should use the new parameter type: `/`. + +```python +@app.get("/path/to/") +async def handler(request, foo) + ... +``` + +### 🚨 *BREAKING CHANGE* - `sanic.worker.GunicornWorker` has been removed + +Departing from our normal deprecation policy, the `GunicornWorker` was removed as a part of the process of upgrading the Sanic server to include multi-serve. This decision was made largely in part because even while it existed it was not an optimal strategy for deploying Sanic. + +If you want to deploy Sanic using `gunicorn`, then you are advised to do it using [the strategy implemented by `uvicorn`](https://www.uvicorn.org/#running-with-gunicorn). This will effectively run Sanic as an ASGI application through `uvicorn`. You can upgrade to this pattern by installing `uvicorn`: + +``` +pip install uvicorn +``` + +Then, you should be able to run it with a pattern like this: + +``` +gunicorn path.to.sanic:app -k uvicorn.workers.UvicornWorker +``` + +### Authorization header parsing + +The `Authorization` header has been partially parseable for some time now. You have been able to use `request.token` to gain access to a header that was in one of the following two forms: + +``` +Authorization: Token +Authorization: Bearer +``` + +Sanic can now parse more credential types like `BASIC`: + +``` +Authorization: Basic Z2lsLWJhdGVzOnBhc3N3b3JkMTIz +``` + +This can be accessed now as `request.credentials`: + +```python +print(request.credentials) +# Credentials(auth_type='Basic', token='Z2lsLWJhdGVzOnBhc3N3b3JkMTIz', _username='gil-bates', _password='password123') +``` + +### CLI arguments optionally injected into application factory + +Sanic will now attempt to inject the parsed CLI arguments into your factory if you are using one. + +```python +def create_app(args): + app = Sanic("MyApp") + print(args) + return app +``` +``` +$sanic p:create_app --factory +Namespace(module='p:create_app', factory=True, simple=False, host='127.0.0.1', port=8000, unix='', cert=None, key=None, tls=None, tlshost=False, workers=1, fast=False, access_log=False, debug=False, auto_reload=False, path=None, dev=False, motd=True, verbosity=None, noisy_exceptions=False) +``` + +If you are running the CLI with `--factory`, you also have the option of passing arbitrary arguments to the command, which will be injected into the argument `Namespace`. + +``` +sanic p:create_app --factory --foo=bar +Namespace(module='p:create_app', factory=True, simple=False, host='127.0.0.1', port=8000, unix='', cert=None, key=None, tls=None, tlshost=False, workers=1, fast=False, access_log=False, debug=False, auto_reload=False, path=None, dev=False, motd=True, verbosity=None, noisy_exceptions=False, foo='bar') +``` + +### New reloader process listener events + +When running Sanic server with auto-reload, there are two new events that trigger a listener *only* on the reloader process: + +- `reload_process_start` +- `reload_process_stop` + +These are only triggered if the reloader is running. + +```python +@app.reload_process_start +async def reload_start(*_): + print(">>>>>> reload_start <<<<<<") + + +@app.reload_process_stop +async def reload_stop(*_): + print(">>>>>> reload_stop <<<<<<") +``` + +### The event loop is no longer a required argument of a listener + +You can leave out the `loop` argument of a listener. Both of these examples work as expected: + +```python +@app.before_server_start +async def without(app): + ... + +@app.before_server_start +async def with(app, loop): + ... +``` + +### Removal - Debug mode does not automatically start the reloader + +When running with `--debug` or `debug=True`, the Sanic server will not automatically start the auto-reloader. This feature of doing both on debug was deprecated in v21 and removed in this release. If you would like to have *both* debug mode and auto-reload, you can use `--dev` or `dev=True`. + +**dev = debug mode + auto reloader** + +### Deprecation - Loading of lower case environment variables + +Sanic loads prefixed environment variables as configuration values. It has not distinguished between uppercase and lowercase as long as the prefix matches. However, it has always been the convention that the keys should be uppercase. This is deprecated and you will receive a warning if the value is not uppercase. In v22.9 only uppercase and prefixed keys will be loaded. + +## News + +### Packt publishes new book on Sanic web development + +---:1 There is a new book on **Python Web Development with Sanic** by [@ahopkins](https://github.com/ahopkins). The book is endorsed by the SCO and part of the proceeds of all sales go directly to the SCO for further development of Sanic. + +You can learn more at [sanicbook.com](https://sanicbook.com/) :--:1 ![Python Web Development with Sanic](https://sanicbook.com/images/SanicCoverFinal.png) :--- + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@aericson](https://github.com/aericson) [@ahankinson](https://github.com/ahankinson) [@ahopkins](https://github.com/ahopkins) [@ariebovenberg](https://github.com/ariebovenberg) [@ashleysommer](https://github.com/ashleysommer) [@Bluenix2](https://github.com/Bluenix2) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@dotlambda](https://github.com/dotlambda) [@eric-spitler](https://github.com/eric-spitler) [@howzitcdf](https://github.com/howzitcdf) [@jonra1993](https://github.com/jonra1993) [@prryplatypus](https://github.com/prryplatypus) [@raphaelauv](https://github.com/raphaelauv) [@SaidBySolo](https://github.com/SaidBySolo) [@SerGeRybakov](https://github.com/SerGeRybakov) [@Tronic](https://github.com/Tronic) + + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/ru/guide/release-notes/v22.6.md b/src/ru/guide/release-notes/v22.6.md new file mode 100644 index 0000000000..f2779fcb19 --- /dev/null +++ b/src/ru/guide/release-notes/v22.6.md @@ -0,0 +1,152 @@ +# Version 22.6 + +[[toc]] + +## Introduction + +This is the second release of the version 22 [release cycle](../../org/policies.md#release-schedule). Version 22 will be "finalized" in the December long-term support version release. + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + + +### Automatic TLS setup in `DEBUG` mode + +The Sanic server can automatically setup a TLS certificate using either [mkcert](https://github.com/FiloSottile/mkcert) or [trustme](https://github.com/python-trio/trustme). This certificate will enable `https://localhost` (or another local address) for local development environments. You must install either `mkcert` or `trustme` on your own for this to work. + +---:1 +``` +$ sanic path.to.server:app --auto-tls --debug +``` +:--:1 + +```python +app.run(debug=True, auto_tls=True) +``` +:--- + +This feature is not available when running in `ASGI` mode, or in `PRODUCTION` mode. When running Sanic in production, you should be using a real TLS certificate either purchased through a legitimate vendor, or using [Let's Encrypt](https://letsencrypt.org/). + + +### HTTP/3 Server :rocket: + +In June 2022, the IETF finalized and published [RFC 9114](https://www.rfc-editor.org/rfc/rfc9114.html), the specification for HTTP/3. In short, HTTP/3 is a **very** different protocol than HTTP/1.1 and HTTP/2 because it implements HTTP over UDP instead of TCP. The new HTTP protocol promises faster webpage load times and solving some of the problems of the older standards. You are encouraged to [read more about](https://http3-explained.haxx.se/) this new web technology. You likely will need to install a [capable client](https://curl.se/docs/http3.html) as traditional tooling will not work. + +Sanic server offers HTTP/3 support using [aioquic](https://github.com/aiortc/aioquic). This **must** be installed: + +``` +pip install sanic aioquic +``` + +``` +pip install sanic[http3] +``` + +To start HTTP/3, you must explicitly request it when running your application. + +---:1 +``` +$ sanic path.to.server:app --http=3 +``` + +``` +$ sanic path.to.server:app -3 +``` +:--:1 + +```python +app.run(version=3) +``` +:--- + +To run both an HTTP/3 and HTTP/1.1 server simultaneously, you can use [application multi-serve](./v22.3.html#application-multi-serve) introduced in v22.3. + + +---:1 +``` +$ sanic path.to.server:app --http=3 --http=1 +``` + +``` +$ sanic path.to.server:app -3 -1 +``` +:--:1 + +```python +app.prepre(version=3) +app.prepre(version=1) +Sanic.serve() +``` +:--- + +Because HTTP/3 requires TLS, you cannot start a HTTP/3 server without a TLS certificate. You should [set it up yourself](../how-to/tls.html) or use `mkcert` if in `DEBUG` mode. Currently, automatic TLS setup for HTTP/3 is not compatible with `trustme`. + +**:baby: This feature is being released as an *EARLY RELEASE FEATURE*.** It is **not** yet fully compliant with the HTTP/3 specification, lacking some features like [websockets](https://websockets.spec.whatwg.org/), [webtransport](https://w3c.github.io/webtransport/), and [push responses](https://http3-explained.haxx.se/en/h3/h3-push). Instead the intent of this release is to bring the existing HTTP request/response cycle towards feature parity with HTTP/3. Over the next several releases, more HTTP/3 features will be added and the API for it finalized. + +### Consistent exception naming + +Some of the Sanic exceptions have been renamed to be more compliant with standard HTTP response names. + +- `InvalidUsage` >> `BadRequest` +- `MethodNotSupported` >> `MethodNotAllowed` +- `ContentRangeError` >> `RangeNotSatisfiable` + +All old names have been aliased and will remain backwards compatible. + +### Current request getter + +Similar to the API to access an application (`Sanic.get_app()`), there is a new method for retrieving the current request when outside of a request handler. + +```python +from sanic import Request + +Request.get_current() +``` + +### Improved API support for setting cache control headers + +The `file` response helper has some added parameters to make it easier to handle setting of the [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) header. + +```python +file( + ..., + last_modified=..., + max_age=..., + no_store=..., +) +``` + + +### Custom `loads` function + +Just like Sanic supports globally setting a custom `dumps`, you can now set a global custom `loads`. + +```python +from orjson import loads + +app = Sanic("Test", loads=loads) +``` + + +### Deprecations and Removals + +1. *REMOVED* - Applications may no longer opt-out of the application registry +1. *REMOVED* - Custom exception handlers will no longer run after some part of an exception has been sent +1. *REMOVED* - Fallback error formats cannot be set on the `ErrorHandler` and must **only** be set in the `Config` +1. *REMOVED* - Setting a custom `LOGO` for startup is no longer allowed +1. *REMOVED* - The old `stream` response convenience method has been removed +1. *REMOVED* - `AsyncServer.init` is removed and no longer an alias of `AsyncServer.app.state.is_started` + + + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@ahopkins](https://github.com/ahopkins) [@amitay87](https://github.com/amitay87) [@ashleysommer](https://github.com/ashleysommer) [@azimovMichael](https://github.com/azimovMichael) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@kijk2869](https://github.com/kijk2869) [@prryplatypus](https://github.com/prryplatypus) [@SaidBySolo](https://github.com/SaidBySolo) [@sjsadowski](https://github.com/sjsadowski) [@timmo001](https://github.com/timmo001) [@zozzz](https://github.com/zozzz) + + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/ru/guide/release-notes/v22.9.md b/src/ru/guide/release-notes/v22.9.md new file mode 100644 index 0000000000..33b265ea83 --- /dev/null +++ b/src/ru/guide/release-notes/v22.9.md @@ -0,0 +1,293 @@ +# Version 22.9 + +[[toc]] + +## Introduction + +This is the third release of the version 22 [release cycle](../../org/policies.md#release-schedule). Version 22 will be "finalized" in the December long-term support version release. + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + + +### :warning: *IMPORTANT* - New worker manager :rocket: + +Sanic server has been overhauled to provide more consistency and flexbility in how it operates. More details about the motivations are outlined in [PR #2499](https://github.com/sanic-org/sanic/pull/2499) and discussed in a live stream [discussion held on YouTube](https://youtu.be/m8HCO8NK7HE). + +This **does NOT apply** to Sanic in ASGI mode + +#### Overview of the changes + +- The worker servers will **always** run in a child process. + - Previously this could change depending upon one versus many workers, and the usage or not of the reloader. This should lead to much more predictable development environments that more closely match their production counterparts. +- Multi-workers is **now supported on Windows**. + - This was impossible because Sanic relied upon the `multiprocessing` module using `fork`, which is not available on Windows. + - Now, Sanic will always use `spawn`. This does have some noticeable differences, particularly if you are running Sanic in the global scope with `app.run` (see below re: upgrade issues). +- The application instance now has a new `multiplexer` object that can be used to restart one or many workers. This could, for example, be triggered by a request. +- There is a new Inspector that can provide details on the state of your server. +- Sanic worker manager can run arbitrary processes. + - This allows developers to add any process they want from within Sanic. + - Possible use cases: + - Health monitor, see [Sanic Extensions]() + - Logging queue, see [Sanic Extensions]() + - Background worker queue in a seperate process + - Running another application, like a bot +- There is a new listener called: `main_process_ready`. It really should only be used for adding arbitrary processes to Sanic. +- Passing shared objects between workers. + - Python does allow some types of objects to share state between processes, whether through shared memory, pipes, etc. + - Sanic will now allow these types of object to be shared on the `app.shared_ctx` object. + - Since this feature relies upon Pythons `multiprocessing` library, it obviously will only work to share state between Sanic worker instances that are instantiated from the same execution. This is *not* meant to provide an API for horizontal scaling across multiple machines for example. + +#### Adding a shared context object + +To share an object between worker processes, it *MUST* be assigned inside of the `main_process_start` listener. + +```python +from multiprocessing import Queue + +@app.main_process_start +async def main_process_start(app): + app.shared_ctx.queue = Queue() +``` + +All objects on `shared_ctx` will be available now within each worker process. + +```python +@app.before_server_starts +async def before_server_starts(app): + assert isinstance(app.shared_ctx.queue, Queue) + +@app.on_request +async def on_request(request): + assert isinstance(request.app.shared_ctx.queue, Queue) + +@app.get("/") +async def handler(request): + assert isinstance(request.app.shared_ctx.queue, Queue) +``` + +*NOTE: Sanic will not stop you from registering an unsafe object, but may warn you. Be careful not to just add a regular list object, for example, and expect it to work. You should have an understanding of how to share state between processes.* + +#### Running arbitrary processes + +Sanic can run any arbitrary process for you. It should be capable of being stopped by a `SIGINT` or `SIGTERM` OS signal. + +These processes should be registered inside of the `main_process_ready` listener. + +```python +@app.main_process_ready +async def ready(app: Sanic, _): + app.manager.manage("MyProcess", my_process, {"foo": "bar"}) +# app.manager.manage(, , ) +``` + +#### Inspector + +Sanic ships with an optional Inspector, which is a special process that allows for the CLI to inspect the running state of an application and issue commands. It currently will only work if the CLI is being run on the same machine as the Sanic instance. + +``` +sanic path.to:app --inspect +``` + +![Sanic inspector](https://user-images.githubusercontent.com/166269/190099384-2f2f3fae-22d5-4529-b279-8446f6b5f9bd.png) + +The new CLI commands are: + +``` + --inspect Inspect the state of a running instance, human readable + --inspect-raw Inspect the state of a running instance, JSON output + --trigger-reload Trigger worker processes to reload + --trigger-shutdown Trigger all processes to shutdown +``` + +This is not enabled by default. In order to have it available, you must opt in: + +```python +app.config.INSPECTOR = True +``` + +*Note: [Sanic Extensions]() provides a [custom request](../basics/app.md#custom-requests) class that will add a request counter to the server state. + +#### Application multiplexer + +Many of the same information and functionality is available on the application instance itself. There is a new `multiplexer` object on the application instance that has the ability to restart one or more workers, and fetch information about the current state. + +You can access it as `app.multiplexer`, or more likely by its short alias `app.m`. + +```python +@app.on_request +async def print_state(request: Request): + print(request.app.m.state) +``` + +#### Potential upgrade issues + +Because of the switch from `fork` to `spawn`, if you try running the server in the global scope you will receive an error. If you see something like this: + +``` +sanic.exceptions.ServerError: Sanic server could not start: [Errno 98] Address already in use. +This may have happened if you are running Sanic in the global scope and not inside of a `if __name__ == "__main__"` block. +``` + +... then the change is simple. Make sure `app.run` is inside a block. + +```python +if __name__ == "__main__": + app.run(port=9999, dev=True) +``` + +#### Opting out of the new functionality + +If you would like to run Sanic without the new process manager, you may easily use the legacy runners. Please note that support for them **will be removed** in the future. A date has not yet been set, but will likely be sometime in 2023. + +To opt out of the new server and use the legacy, choose the appropriate method depending upon how you run Sanic: + +---:1 If you use the CLI... :--:1 +``` +sanic path.to:app --legacy +``` +:--- + +---:1 If you use `app.run`... :--:1 +``` +app.run(..., legacy=True) +``` +:--- + +---:1 If you `app.prepare`... :--:1 +``` +app.prepare(...) +Sanic.serve_legacy() +``` +:--- + +Similarly, you can force Sanic to run in a single process. This however means there will not be any access to the auto-reloader. + +---:1 If you use the CLI... :--:1 +``` +sanic path.to:app --single-process +``` +:--- + +---:1 If you use `app.run`... :--:1 +``` +app.run(..., single_process=True) +``` +:--- + +---:1 If you `app.prepare`... :--:1 +``` +app.prepare(...) +Sanic.serve_single() +``` +:--- +### Middleware priority + +Middleware is executed in an order based upon when it was defined. Request middleware are executed in sequence and response middleware in reverse. This could have an unfortunate impact if your ordering is strictly based upon import ordering with global variables for example. + +A new addition is to break-out of the strict construct and allow a priority to be assigned to a middleware. The higher the number for a middleware definition, the earlier in the sequence it will be executed. This applies to **both** request and response middleware. + +```python +@app.on_request +async def low_priority(_): + ... + +@app.on_request(priority=10) +async def high_priority(_): + ... +``` + +In the above example, even though `low_priority` is defined first, `high_priority` will run first. + +### Custom `loads` function + + +Sanic has supported the ability to add a [custom `dumps` function](https://sanic.readthedocs.io/en/stable/sanic/api/app.html#sanic.app.Sanic) when instantiating an app. The same functionality has been extended to `loads`, which will be used when deserializing. + +```python +from json import loads + +Sanic("Test", loads=loads) +``` + +### Websocket objects are now iterable + + +Rather than calling `recv` in a loop on a `Websocket` object, you can iterate on it in a `for` loop. + + +```python +from sanic import Request, Websocket + +@app.websocket("/ws") +async def ws_echo_handler(request: Request, ws: Websocket): + async for msg in ws: + await ws.send(msg) +``` + +### Appropriately respond with 304 on static files + +When serving a static file, the Sanic server can respond appropriately to a request with `If-Modified-Since` using a `304` response instead of resending a file. + +### Two new signals to wrap handler execution + +Two new [signals](../advanced/signals.md) have been added that wrap the execution of a request handler. + +- `http.handler.before` - runs after request middleware but before the route handler +- `http.handler.after` - runs after the route handler + - In *most* circumstances, this also means that it will run before response middleware. However, if you call `request.respond` from inside of a route handler, then your middleware will come first + +### New Request properties for HTTP method information + +The HTTP specification defines which HTTP methods are: safe, idempotent, and cacheable. New properties have been added that will respond with a boolean flag to help identify the request property based upon the method. + +```python +request.is_safe +request.is_idempotent +request.is_cacheable +``` + +### 🚨 *BREAKING CHANGE* - Improved cancel request exception + +In prior version of Sanic, if a `CancelledError` was caught it could bubble up and cause the server to respond with a `503`. This is not always the desired outcome, and it prevented the usage of that error in other circumstances. As a result, Sanic will now use a subclass of `CancelledError` called: `RequestCancelled` for this functionality. It likely should have little impact unless you were explicitly relying upon the old behavior. + + +For more details on the specifics of these properties, checkout the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods). + +### New deprecation warning filter + +You can control the level of deprecation warnings from Sanic using [standard library warning filter values](https://docs.python.org/3/library/warnings.html#the-warnings-filter). Default is `"once"`. + +```python +app.config.DEPRECATION_FILTER = "ignore" +``` + +### Deprecations and Removals + +1. *DEPRECATED* - Duplicate route names have been deprecated and will be removed in v23.3 +1. *DEPRECATED* - Registering duplicate exception handlers has been deprecated and will be removed in v23.3 +1. *REMOVED* - `route.ctx` not set by Sanic, and is a blank object for users, therefore ... + - `route.ctx.ignore_body` >> `route.extra.ignore_body` + - `route.ctx.stream` >> `route.extra.stream` + - `route.ctx.hosts` >> `route.extra.hosts` + - `route.ctx.static` >> `route.extra.static` + - `route.ctx.error_format` >> `route.extra.error_format` + - `route.ctx.websocket` >> `route.extra.websocket` +1. *REMOVED* - `app.debug` is READ-ONLY +1. *REMOVED* - `app.is_running` removed +1. *REMOVED* - `app.is_stopping` removed +1. *REMOVED* - `Sanic._uvloop_setting` removed +1. *REMOVED* - Prefixed environment variables will be ignored if not uppercase + + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@ahopkins](https://github.com/ahopkins) [@azimovMichael](https://github.com/azimovMichael) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@huntzhan](https://github.com/huntzhan) [@monosans](https://github.com/monosans) [@prryplatypus](https://github.com/prryplatypus) [@SaidBySolo](https://github.com/SaidBySolo) [@seemethere](https://github.com/seemethere) [@sjsadowski](https://github.com/sjsadowski) [@timgates42](https://github.com/timgates42) [@Tronic](https://github.com/Tronic) + + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/ru/help.md b/src/ru/help.md new file mode 100644 index 0000000000..4fbdf1be99 --- /dev/null +++ b/src/ru/help.md @@ -0,0 +1,29 @@ +--- +layout: BlankLayout +--- + +# Нужна помощь? + +Будучи активным сообществом разработчиков, мы стараемся поддерживать друг друга. Если вам нужна помощь, попробуйте следующее: + +---:1 + +### Discord :speech_balloon: + +Лучшее место для быстрого получения ответов и общения в реальном времени + +`#sanic-support` канал на [Discord сервере](https://discord.gg/FARQzAEMAA) + +:--:1 + +### Форумы сообщества :busts_in_silhouette: + +Подходит для обмена фрагментами кода и более длинными запросами поддержки + +Разделы `Вопросы и Справка` на [форумах](https://community.sanicframework.org/c/questions-and-help/6) + +:--- + +--- + +Мы также активно отслеживаем тег `[sanic]` на [Stack Overflow](https://stackoverflow.com/questions/tagged/sanic). diff --git a/src/ru/org/README.md b/src/ru/org/README.md new file mode 100644 index 0000000000..d965d64c30 --- /dev/null +++ b/src/ru/org/README.md @@ -0,0 +1 @@ +# Проект diff --git a/src/ru/org/feature_requests.md b/src/ru/org/feature_requests.md new file mode 100644 index 0000000000..5c57ea1f02 --- /dev/null +++ b/src/ru/org/feature_requests.md @@ -0,0 +1,9 @@ +# Feature Requests + +[Create new feature request](https://github.com/sanic-org/sanic/issues/new?assignees=&labels=feature+request&template=feature_request.md) + +To vote on a feature request, visit the [GitHub Issues](https://github.com/sanic-org/sanic/issues?q=is%3Aissue+is%3Aopen+label%3A%22feature+request%22%2CRFC+sort%3Areactions-%2B1-desc) and add a reaction + +--- + + diff --git a/src/ru/org/policies.md b/src/ru/org/policies.md new file mode 100644 index 0000000000..d21c22806b --- /dev/null +++ b/src/ru/org/policies.md @@ -0,0 +1,64 @@ +# Policies + +## Versioning + +Sanic uses [calendar versioning](https://calver.org/), aka "calver". To be more specific, the pattern follows: + +``` +YY.MM.MICRO +``` + +Generally, versions are referred to in their `YY.MM` form. The `MICRO` number indicates an incremental patch version, starting at `0`. + +## Release Schedule + +There are four (4) scheduled releases per year: March, June, September, and December. Therefore, there are four (4) released versions per year: `YY.3`, `YY.6`, `YY.9`, and `YY.12`. + +This release schedule provides: + +- a predictable release cadence, +- relatively short development windows allowing features to be regularly released, +- controlled [deprecations](#deprecation), and +- consistent stability with a yearly LTS. + +We also use the yearly release cycle in conjunction with our governance model, covered by the [S.C.O.P.E.](./scope.md) + +### Long term support v Interim releases + +Sanic releases a long term support release (aka "LTS") once a year in December. The LTS releases receive bug fixes and security updates for **24 months**. Interim releases throughout the year occur every three months, and are supported until the subsequent release. + +| Version | LTS | Supported | +| ------- | ------------- | ------------------------- | +| 22.12 | until 2024-12 | :white_check_mark: | +| 22.9 | | :x: | +| 22.6 | | :x: | +| 22.3 | | :x: | +| 21.12 | until 2023-12 | :ballot_box_with_check: | +| 21.9 | | :x: | +| 21.6 | | :x: | +| 21.3 | | :x: | +| 20.12 | | :x: | +| 20.9 | | :x: | +| 20.6 | | :x: | +| 20.3 | | :x: | +| 19.12 | | :x: | +| 19.9 | | :x: | +| 19.6 | | :x: | +| 19.3 | | :x: | +| 18.12 | | :x: | +| 0.8.3 | | :x: | +| 0.7.0 | | :x: | +| 0.6.0 | | :x: | +| 0.5.4 | | :x: | +| 0.4.1 | | :x: | +| 0.3.1 | | :x: | +| 0.2.0 | | :x: | +| 0.1.9 | | :x: | + +:ballot_box_with_check: = security fixes :white_check_mark: = full support + +## Deprecation + +Before a feature is deprecated, or breaking changes are introduced into the API, it shall be publicized and shall appear with deprecation warnings through two release cycles. No deprecations shall be made in an LTS release. + +Breaking changes or feature removal may happen outside of these guidelines when absolutely warranted. These circumstances should be rare. For example, it might happen when no alternative is available to curtail a major security issue. diff --git a/src/ru/org/scope.md b/src/ru/org/scope.md new file mode 100644 index 0000000000..1af529361a --- /dev/null +++ b/src/ru/org/scope.md @@ -0,0 +1,264 @@ +--- +title: S.C.O.P.E +--- + + +Sanic Community Organization Policy E-manual +============================================ + +December 2019, version 1 + +Goals +----- + +To create a sustainable, community-driven organization around the Sanic projects that promote: (1) stability and predictability, (2) quick iteration and enhancement cycles, (3) engagement from outside contributors, (4) overall reliable software, and (5) a safe, rewarding environment for the community members. + +Overview +-------- + +This Policy is the governance model for the Sanic Community Organization (“SCO”). The SCO is a meritocratic, consensus-based community organization responsible for all projects adopted by it. Anyone with an interest in one of the projects can join the community, contribute to the community or projects, and participate in the decision making process. This document describes how that participation takes place and how to set about earning merit within the project community. + +Structure +--------- + +The SCO has multiple **projects**. Each project is represented by a single GitHub repository under the Sanic community umbrella. These projects are used by **users**, developed by **contributors**, governed by **core developers**, released by **release managers**, and ultimately overseen by a **steering council**. If this sounds similar to the Python project and PEP 8016 that is because it is intentionally designed that way. + +Roles and responsibilities +-------------------------- + +### Users + +Users are community members who have a need for the projects. They are the developers and personnel that download and install the packages. Users are the **most important** members of the community and without them the projects would have no purpose. Anyone can be a user and the licenses adopted by the projects shall be appropriate open source licenses. + +_The SCO asks its users to participate in the project and community as much as possible._ + +User contributions enable the project team to ensure that they are satisfying the needs of those users. Common user contributions include (but are not limited to): + +* evangelizing about the project (e.g. a link on a website and word-of-mouth awareness raising) +* informing developers of strengths and weaknesses from a new user perspective +* providing moral support (a ‘thank you’ goes a long way) +* providing financial support (the software is open source, but its developers need to eat) + +Users who continue to engage with the SCO, its projects, and its community will often become more and more involved. Such users may find themselves becoming contributors, as described in the next section. + +### Contributors + +Contributors are community members who contribute in concrete ways to one or more of the projects. Anyone can become a contributor and contributions can take many forms. Contributions and requirements are governed by each project separately by a contribution policy. + +There is **no expectation** of commitment to the project, **no specific skill requirements** and **no selection process**. + +In addition to their actions as users, contributors may also find themselves doing one or more of the following: + +* supporting new users (existing users are often the best people to support new users) +* reporting bugs +* identifying requirements +* providing graphics and web design +* Programming +* example use cases +* assisting with project infrastructure +* writing documentation +* fixing bugs +* adding features +* providing constructive opinions and engaging in community discourse + +Contributors engage with the projects through GitHub and the Community Forums. They submit changes to the projects itself via pull requests, which will be considered for inclusion in the project by the community at large. The Community Forums are the most appropriate place to ask for help when making that first contribution. + +Indeed one of the most important roles of a contributor may be to **simply engage in the community conversation**. Most decisions about the direction of a project are made by consensus. This is discussed in more detail below. In general, however, it is helpful for the health and direction of the projects for the contributors to **speak freely** (within the confines of the code of conduct) and **express their opinions and experiences** to help drive the consensus building. + +As contributors gain experience and familiarity with a project, their profile within, and commitment to, the community will increase. At some stage, they may find themselves being nominated for a core developer team. + +### Core Developer + +Each project under the SCO umbrella has its own team of core developers. They are the people in charge of that project. + +_What is a core developer?_ + +Core developers are community members who have shown that they are committed to the continued development of the project through ongoing engagement with the community. Being a core developer allows contributors to more easily carry on with their project related activities by giving them direct access to the project’s resources. They can make changes directly to the project repository without having to submit changes via pull requests from a fork. + +This does not mean that a core developer is free to do what they want. In fact, core developers have no more direct authority over the final release of a package than do contributors. While this honor does indicate a valued member of the community who has demonstrated a healthy respect for the project’s aims and objectives, their work continues to be reviewed by the community before acceptance in an official release. + +_What can a core developer do on a project?_ + +Each project might define this role slightly differently. However, the general usage of this designation is that an individual has risen to a level of trust within the community such that they now are given some control. This comes in the form of push rights to non-protected branches, and the ability to have a voice in the approval of pull requests. + +The projects employ various communication mechanisms to ensure that all contributions are reviewed by the community as a whole. This includes tools provided by GitHub, as well as the Community Forums. By the time a contributor is invited to become a core developer, they should be familiar with the various tools and workflows as a user and then as a contributor. + +_How to become a core developer?_ + +Anyone can become a core developer; there are no special requirements, other than to have shown a willingness and ability to positively participate in the project as a team player. + +Typically, a potential core developer will need to show that they have an understanding of the project, its objectives and its strategy. They will also have provided valuable contributions to the project over a period of time. However, there is **no technical or other skill** requirement for eligibility. + +New core developers can be **nominated by any existing core developer** at any time. At least twice a year (April and October) there will be a ballot process run by the Steering Council. Voting should be done by secret ballot. Each existing core developer for that project receives a number of votes equivalent to the number of nominees on the ballot. For example, if there are four nominees, then each existing core developer has four votes. The core developer may cast those votes however they choose, but may not vote for a single nominee more than once. A nominee must receive two-thirds approval from the number of cast ballots (not the number of eligible ballots). Once accepted by the core developers, it is the responsibility of the Steering Council to approve and finalize the nomination. The Steering Council does not have the right to determine whether a nominee is meritorious enough to receive the core developer title. However, they do retain the right to override a vote in cases where the health of the community would so require. + +Once the vote has been held, the aggregated voting results are published on the Community Forums. The nominee is entitled to request an explanation of any override against them. A nominee that fails to be admitted as a core developer may be nominated again in the future. + +It is important to recognize that being a core developer is a privilege, not a right. That privilege must be earned and once earned it can be removed by the Steering Council (see next section) in extreme circumstances. However, under normal circumstances the core developer title exists for as long as the individual wishes to continue engaging with the project and community. + +A committer who shows an above-average level of contribution to the project, particularly with respect to its strategic direction and long-term health, may be nominated to become a member of the Steering Council, or a Release Manager. This role is described below. + +_What are the rights and responsibilities of core developers?_ + +As discussed, the majority of decisions to be made are by consensus building. In certain circumstances where an issue has become more contentious, or a major decision needs to be made, the Release Manager or Steering Council may decide (or be required) to implement the RFC process, which is outlined in more detail below. + +It is also incumbent upon core developers to have a voice in the governance of the community. All core developers for all of the projects have the ability to be nominated to be on the Steering Council and vote in their elections. + +This Policy (the “SCOPE”) may only be changed under the authority of two-thirds of active core developers, except that in the first six (6) months after adoption, the core developers reserve the right to make changes under the authority of a simple majority of active core developers. + +_What if a core developer becomes inactive?_ + +It is hoped that all core developers participate and remain active on a regular basis in their projects. However, it is also understood that such commitments may not be realistic or possible from time to time. + +Therefore, the Steering Council has the duty to encourage participation and the responsibility to place core developers into an inactive status if they are no longer willing or capable to participate. The main purpose of this is **not to punish** a person for behavior, but to help the development process to continue for those that do remain active. + +To this end, a core developer that becomes “inactive” shall not have commit rights to a repository, and shall not participate in any votes. To be eligible to vote in an election, a core developer **must have been active** at the time of the previous scheduled project release. + +Inactive members may ask the Steering Council to reinstate their status at any time, and upon such request the Steering Council shall make the core developer active again. + +Individuals that know they will be unable to maintain their active status for a period are asked to be in communication with the Steering Council and declare themselves inactive if necessary. + +An “active” core developer is an individual that has participated in a meaningful way during the previous six months. Any further definition is within the discretion of the Steering Council. + +### Release Manager + +Core developers shall have access only to make commits and merges on non-protected branches. The “master” branch and other protected branches are controlled by the release management team for that project. Release managers shall be elected from the core development team by the core development team, and shall serve for a full release cycle. + +Each core developer team may decide how many release managers to have for each release cycle. It is highly encouraged that there be at least two release managers for a release cycle to help divide the responsibilities and not force too much effort upon a single person. However, there also should not be so many managers that their efforts are impeded. + +The main responsibilities of the release management team include: + +* push the development cycle forward by monitoring and facilitating technical discussions +* establish a release calendar and perform actions required to release packages +* approve pull requests to the master branch and other protected branches +* merge pull requests to the master branch and other protected branches + +The release managers **do not have the authority to veto or withhold a merge** of a pull request that otherwise meets contribution criteria and has been accepted by the community. It is not their responsibility to decide what should be developed, but rather that the decisions of the community are carried out and that the project is being moved forward. + +From time to time, a decision may need to be made that cannot be achieved through consensus. In that case, the release managers have the authority to call upon the removal of the decision to the RFC process. This should not occur regularly (unless required as discussed below), and its use should be discouraged in favor of the more communal consensus building strategy. + +Since not all projects have the same requirements, the specifics governing release managers on a project shall be set forth in an Appendix to this Policy, or in the project’s contribution guidelines. + +If necessary, the Steering Council has the right to remove a release manager that is derelict in their duties, or for other good cause. + +### Steering Council + +The Steering Council is the governing body consisting of those individuals identified as the “project owner” and having control of the resources and assets of the SCO. Their ultimate goal is to ensure the smooth operation of the projects by removing impediments, and assisting the members as needed. It is expected that they will be regular voices in the community. + +_What can the Steering Council do?_ + +The members of the Steering Council **do not individually have any more authority than any other core developer**, and shall not have any additional rights to make decisions, commits, merges, or the like on a project. + +However, as a body, the Steering Council has the following capacity: + +* accept, remand, and reject all RFCs +* enforce the community code of conduct +* administer community assets such as repositories, servers, forums, integration services, and the like (or, to delegate such authority to someone else) +* place core developers into inactive status where appropriate take any other enforcement measures afforded to it in this Policy, including, in extreme cases, removing core developers +* adopt or remove projects from the community umbrella + +It is highly encouraged that the Steering Council delegate its authority as much as possible, and where appropriate, to other willing community members. + +The Steering Council **does not have the authority** to change this Policy. + +_How many members are on the Steering Council?_ + +Four. + +While it seems like a committee with four votes may potentially end in a deadlock with no way to break a majority vote, the Steering Council is discouraged from voting as much as possible. Instead, it should try to work by consensus, and requires three consenting votes when it is necessary to vote on a matter. + +_How long do members serve on the Steering Council?_ + +A single term shall be for two calendar years starting in January. Terms shall be staggered so that each year there are two members continuing from the previous year’s council. + +Therefore, the inaugural vote shall have two positions available for a two year term, and two positions available for a one year term. + +There are no limits to the number of terms that can be served, and it is possible for an individual to serve consecutive terms. + +_Who runs the Steering Council?_ + +After the Steering Council is elected, the group shall collectively decide upon one person to act as the Chair. The Chair does not have any additional rights or authority over any other member of the Steering Council. + +The role of the Chair is merely as a coordinator and facilitator. The Chair is expected to ensure that all governance processes are adhered to. The position is more administrative and clerical, and is expected that the Chair sets agendas and coordinates discussion of the group. + +_How are council members elected?_ + +Once a year, **all eligible core developers** for each of the projects shall have the right to elect members to the Steering Council. + +Nominations shall be open from September 1 and shall close on September 30. After that, voting shall begin on October 1 and shall close on October 31. Every core developer active on the date of the June release of the Sanic Framework for that year shall be eligible to receive one vote per vacant seat on the Steering Council. For the sake of clarity, to be eligible to vote, a core developer **does not** need to be a core developer on Sanic Framework, but rather just have been active within their respective project on that date. + +The top recipients of votes shall be declared the winners. If there is any tie, it is highly encouraged that the tied nominees themselves resolve the dispute before a decision is made at random. + +In regards to the inaugural vote of the Steering Council, the top two vote-recipients shall serve for two years, and the next two vote-recipients shall assume the one-year seats. + +To be an eligible candidate for the Steering Council, the individual must have been a core developer in active status on at least one project for the previous twelve months. + +_What if there is a vacancy?_ + +If a vacancy on the Steering Council exists during a term, then the next highest vote-recipient in the previous election shall be offered to complete the remainder of the term. If one cannot be found this way, the Steering Council may decide the most appropriate course of action to fill the seat (whether by appointment, vote, or other means). + +If a member of the Steering Council becomes inactive, then that individual shall be removed from the Steering Council immediately and the seat shall become vacant. + +In extreme cases, the body of all core developers has the right to bring a vote to remove a member of the Steering Council for cause by a two-thirds majority of all eligible voting core developers. + +_How shall the Steering Council conduct its business?_ + +As much as possible, the Steering Council shall conduct its business and discussions in the open. Any member of the community should be allowed to enter the conversation with them. However, at times it may be necessary or appropriate for discussions to be held privately. Selecting the proper venue for conversations is part of the administrative duties of the Chair. + +While the specifics of how to operate are beyond the scope of the Policy, it is encouraged that the Steering Council attempt to meet at least one time per quarter in a “real-time” discussion. This could be achieved via video conferencing, live chatting, or other appropriate means. + +Support +------- + +All participants in the community are encouraged to provide support for users within the project management infrastructure. This support is provided as a way of growing the community. Those seeking support should recognize that all support activity within the project is voluntary and is therefore provided as and when time allows. A user requiring guaranteed response times or results should therefore seek to purchase a support contract from a community member. However, for those willing to engage with the project on its own terms, and willing to help support other users, the community support channels are ideal. + +Decision making process +----------------------- + +Decisions about the future of the projects are made through discussion with all members of the community, from the newest user to the most experienced member. Everyone has a voice. + +All non-sensitive project management discussion takes place on the community forums, or other designated channels. Occasionally, sensitive discussions may occur in private. + +In order to ensure that the project is not bogged down by endless discussion and continual voting, the project operates a policy of **lazy consensus**. This allows the majority of decisions to be made without resorting to a formal vote. For any **major decision** (as defined below), there is a separate Request for Comment (RFC) process. + +### Technical decisions + +Pull requests and technical decisions should generally fall into the following categories. + +* **Routine**: Documentation fixes, code changes that are for cleanup or additional testing. No functionality changes. +* **Minor**: Changes to the code base that either fix a bug, or introduce a trivial feature. No breaking changes. +* **Major**: Any change to the code base that breaks or deprecates existing API, alters operation in a non-trivial manner, or adds a significant feature. + +It is generally the responsibility of the release managers to make sure that changes to the repositories receive the proper authorization before merge. + +The release managers retain the authority to individually review and accept routine decisions that meet standards for code quality without additional input. + +### Lazy consensus + +Decision making (whether by the community or Steering Council) typically involves the following steps: + +* proposal +* discussion +* vote (if consensus is not reached through discussion) +* decision + +Any community member can make a proposal for consideration by the community. In order to initiate a discussion about a new idea, they should post a message on the appropriate channel on the Community forums, or submit a pull request implementing the idea on GitHub. This will prompt a review and, if necessary, a discussion of the idea. + +The goal of this review and discussion is to gain approval for the contribution. Since most people in the project community have a shared vision, there is often little need for discussion in order to reach consensus. + +In general, as long as nobody explicitly opposes a proposal or patch, it is recognized as having the support of the community. This is called lazy consensus; that is, those who have not stated their opinion explicitly have implicitly agreed to the implementation of the proposal. + +Lazy consensus is a very important concept within the SCO. It is this process that allows a large group of people to efficiently reach consensus, as someone with no objections to a proposal need not spend time stating their position, and others need not spend time reading such messages. + +For lazy consensus to be effective, it is necessary to allow an appropriate amount of time before assuming that there are no objections to the proposal. This is somewhat dependent upon the circumstances, but it is generally assumed that 72 hours is reasonable. This requirement ensures that everyone is given enough time to read, digest and respond to the proposal. This time period is chosen so as to be as inclusive as possible of all participants, regardless of their location and time commitments. The facilitators of discussion (whether it be the Chair or the Release Managers, where applicable) shall be charged with determining the proper length of time for such consensus to be reached. + +As discussed above regarding so-called routine decisions, the release managers have the right to make decisions within a shorter period of time. In such cases, lazy consensus shall be implied. + +### Request for Comment (RFC) + +The Steering Council shall be in charge of overseeing the RFC process. It shall be a process that remains open to debate to all members of the community, and shall allow for ample time to consider a proposal and for members to respond and engage in meaningful discussion. + +The final decision is vested with the Steering Council. However, it is strongly discouraged that the Steering Council adopt a decision that is contrary to any consensus that may exist in the community. From time to time this may happen if there is a conflict between consensus and the overall project and community goals. + +An RFC shall be initiated by submission to the Steering Council in the public manner as set forth by the Steering Council. Debate shall continue and be facilitated by the Steering Council in general, and the Chair specifically. + +In circumstances that the Steering Council feels it is appropriate, the RFC process may be waived in favor of lazy consensus. diff --git a/src/ru/plugins/sanic-ext/configuration.md b/src/ru/plugins/sanic-ext/configuration.md new file mode 100644 index 0000000000..fe4600d412 --- /dev/null +++ b/src/ru/plugins/sanic-ext/configuration.md @@ -0,0 +1,243 @@ +# Configuration + +Sanic Extensions can be configured in all of the same ways that [you can configure Sanic](../../guide/deployment/configuration.md). That makes configuring Sanic Extensions very easy. + +```python +app = Sanic("MyApp") +app.config.OAS_URL_PREFIX = "/apidocs" +``` + +However, there are a few more configuration options that should be considered. + +## Manual `extend` + +---:1 Even though Sanic Extensions will automatically attach to your application, you can manually choose `extend`. When you do that, you can pass all of the configuration values as a keyword arguments (lowercase). :--: +```python +app = Sanic("MyApp") +app.extend(oas_url_prefix="/apidocs") +``` +:--- + +---:1 Or, alternatively they could be passed all at once as a single `dict`. :--: +```python +app = Sanic("MyApp") +app.extend(config={"oas_url_prefix": "/apidocs"}) +``` +:--- + +---:1 Both of these solutions suffers from the fact that the names of the configuration settings are not discoverable by an IDE. Therefore, there is also a type annotated object that you can use. This should help the development experience. :--: +```python +from sanic_ext import Config + +app = Sanic("MyApp") +app.extend(config=Config(oas_url_prefix="/apidocs")) +``` +:--- + +## Settings + +### `cors` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to enable CORS protection + +### `cors_allow_headers` + +- **Type**: `str` +- **Default**: `"*"` +- **Description**: Value of the header: `access-control-allow-headers` + +### `cors_always_send` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to always send the header: `access-control-allow-origin` + +### `cors_automatic_options` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to automatically generate `OPTIONS` endpoints for routes that do *not* already have one defined + +### `cors_expose_headers` + +- **Type**: `str` +- **Default**: `""` +- **Description**: Value of the header: `access-control-expose-headers` + +### `cors_max_age` + +- **Type**: `int` +- **Default**: `5` +- **Description**: Value of the header: `access-control-max-age` + +### `cors_methods` + +- **Type**: `str` +- **Default**: `""` +- **Description**: Value of the header: `access-control-access-control-allow-methods` + +### `cors_origins` + +- **Type**: `str` +- **Default**: `""` +- **Description**: Value of the header: `access-control-allow-origin` + +::: warning Be very careful if you place `*` here. Do not do this unless you know what you are doing as it can be a security issue. ::: + +### `cors_send_wildcard` + +- **Type**: `bool` +- **Default**: `False` +- **Description**: Whether to send a wildcard origin instead of the incoming request origin + +### `cors_supports_credentials` + +- **Type**: `bool` +- **Default**: `False` +- **Description**: Value of the header: `access-control-allow-credentials` + +### `cors_vary_header` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to add the `vary` header + +### `http_all_methods` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Adds the HTTP `CONNECT` and `TRACE` methods as allowable + +### `http_auto_head` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Automatically adds `HEAD` handlers to any `GET` routes + +### `http_auto_options` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Automatically adds `OPTIONS` handlers to any routes without + +### `http_auto_trace` + +- **Type**: `bool` +- **Default**: `False` +- **Description**: Automatically adds `TRACE` handlers to any routes without + +### `oas` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to enable OpenAPI specification generation + +### `oas_autodoc` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to automatically extract OpenAPI details from the docstring of a route function + +### `oas_ignore_head` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: WHen `True`, it will not add `HEAD` endpoints into the OpenAPI specification + +### `oas_ignore_options` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: WHen `True`, it will not add `OPTIONS` endpoints into the OpenAPI specification + +### `oas_path_to_redoc_html` + +- **Type**: `Optional[str]` +- **Default**: `None` +- **Description**: Path to HTML file to override the existing Redoc HTML + +### `oas_path_to_swagger_html` + +- **Type**: `Optional[str]` +- **Default**: `None` +- **Description**: Path to HTML file to override the existing Swagger HTML + +### `oas_ui_default` + +- **Type**: `Optional[str]` +- **Default**: `"redoc"` +- **Description**: Which OAS documentation to serve on the bare `oas_url_prefix` endpoint; when `None` there will be no documentation at that location + +### `oas_ui_redoc` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to enable the Redoc UI + +### `oas_ui_swagger` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to enable the Swagger UI + +### `oas_ui_swagger_version` + +- **Type**: `str` +- **Default**: `"4.1.0"` +- **Description**: Which Swagger version to use + +### `oas_uri_to_config` + +- **Type**: `str` +- **Default**: `"/swagger-config"` +- **Description**: Path to serve the Swagger configurtaion + +### `oas_uri_to_json` + +- **Type**: `str` +- **Default**: `"/openapi.json"` +- **Description**: Path to serve the OpenAPI JSON + +### `oas_uri_to_redoc` + +- **Type**: `str` +- **Default**: `"/redoc"` +- **Description**: Path to Redoc + +### `oas_uri_to_swagger` + +- **Type**: `str` +- **Default**: `"/swagger"` +- **Description**: Path to Swagger + +### `oas_url_prefix` + +- **Type**: `str` +- **Default**: `"/docs"` +- **Description**: URL prefix for the Blueprint that all of the OAS documentation witll attach to + +### `swagger_ui_configuration` + +- **Type**: `Dict[str, Any]` +- **Default**: `{"apisSorter": "alpha", "operationsSorter": "alpha", "docExpansion": "full"}` +- **Description**: The Swagger documentation to be served to the frontend + +### `templating_enable_async` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to set `enable_async` on the Jinja `Environment` + +### `templating_path_to_templates` + +- **Type**: `Union[str, os.PathLike, Sequence[Union[str, os.PathLike]]]` +- **Default**: `templates` +- **Description**: A single path, or multiple paths to where your template files are located + +### `trace_excluded_headers` + +- **Type**: `Sequence[str]` +- **Default**: `("authorization", "cookie")` +- **Description**: Which headers should be suppresed from responses to `TRACE` requests diff --git a/src/ru/plugins/sanic-ext/convenience.md b/src/ru/plugins/sanic-ext/convenience.md new file mode 100644 index 0000000000..b53fb06b5a --- /dev/null +++ b/src/ru/plugins/sanic-ext/convenience.md @@ -0,0 +1,86 @@ +# Convenience + +## Fixed serializer + +---:1 Often when developing an application, there will be certain routes that always return the same sort of response. When this is the case, you can predefine the return serializer and on the endpoint, and then all that needs to be returned is the content. :--:1 +```python +from sanic_ext import serializer + +@app.get("/") +@serializer(text) +async def hello_world(request, name: str): + if name.isnumeric(): + return "hello " * int(name) + return f"Hello, {name}" +``` +:--- + + +---:1 The `serializer` decorator also can add status codes. :--:1 +```python +from sanic_ext import serializer + +@app.post("/") +@serializer(text, status=202) +async def create_something(request): + ... +``` +:--- + +## Custom serializer + +---:1 Using the `@serializer` decorator, you can also pass your own custom functions as long as they also return a valid type (`HTTPResonse`). :--:1 +```python +def message(retval, request, action, status): + return json( + { + "request_id": str(request.id), + "action": action, + "message": retval, + }, + status=status, + ) + + +@app.post("/") +@serializer(message) +async def do_action(request, action: str): + return "This is a message" +``` +:--- + +---:1 Now, returning just a string should return a nice serialized output. :--:1 + +```python +$ curl localhost:8000/eat_cookies -X POST +{ + "request_id": "ef81c45b-235c-46dd-9dbd-b550f8fa77f9", + "action": "eat_cookies", + "message": "This is a message" +} + +``` +:--- + + +## Request counter + +---:1 Sanic Extensions comes with a subcleass of `Request` that can be setup to automatically keep track of the number of requests processed per worker process. To enable this, you should pass the `CountedRequest` class to your application contructor. :--:1 +```python +from sanic_ext import CountedRequest + +app = Sanic(..., request_class=CountedRequest) +``` +:--- + +---:1 You will now have access to the number of requests served during the lifetime of the worker process. :--:1 +```python +@app.get("/") +async def handler(request: CountedRequest): + return json({"count": request.count}) +``` +:--- + +If possible, the request count will also be added to the [worker state](../../guide/deployment/manager.md#worker-state). + +![](https://user-images.githubusercontent.com/166269/190922460-43bd2cfc-f81a-443b-b84f-07b6ce475cbf.png) diff --git a/src/ru/plugins/sanic-ext/custom.md b/src/ru/plugins/sanic-ext/custom.md new file mode 100644 index 0000000000..e374052a3d --- /dev/null +++ b/src/ru/plugins/sanic-ext/custom.md @@ -0,0 +1,84 @@ +# Custom extensions + +It is possible to create your own custom extensions. + +Version 22.9 added the `Extend.register` [method](#extension-preregistration). This makes it extremely easy to add custom expensions to an application. + +## Anatomy of an extension + +All extensions must subclass `Extension`. + +### Required + +- `name`: By convention, the name is an all-lowercase string +- `startup`: A method that runs when the extension is added + +### Optional + +- `label`: A method that returns additional information about the extension in the MOTD +- `included`: A method that returns a boolean whether the extension should be enabled or not (could be used for example to check config state) + +### Example + +```python +from sanic import Request, Sanic, json +from sanic_ext import Extend, Extension + +app = Sanic(__name__) +app.config.MONITOR = True + + +class AutoMonitor(Extension): + name = "automonitor" + + def startup(self, bootstrap) -> None: + if self.included(): + self.app.before_server_start(self.ensure_monitor_set) + self.app.on_request(self.monitor) + + @staticmethod + async def monitor(request: Request): + if request.route and request.route.ctx.monitor: + print("....") + + @staticmethod + async def ensure_monitor_set(app: Sanic): + for route in app.router.routes: + if not hasattr(route.ctx, "monitor"): + route.ctx.monitor = False + + def label(self): + has_monitor = [ + route + for route in self.app.router.routes + if getattr(route.ctx, "monitor", None) + ] + return f"{len(has_monitor)} endpoint(s)" + + def included(self): + return self.app.config.MONITOR + + +Extend.register(AutoMonitor) + + +@app.get("/", ctx_monitor=True) +async def handler(request: Request): + return json({"foo": "bar"}) +``` + + +## Extension preregistration + +---:1 `Extend.register` simplifies the addition of custom extensions. :--:1 +```python +from sanic_ext import Extend, Extension + +class MyCustomExtension(Extension): + ... + +Extend.register(MyCustomExtension()) +``` +:--- + +*Added in v22.9* diff --git a/src/ru/plugins/sanic-ext/getting-started.md b/src/ru/plugins/sanic-ext/getting-started.md new file mode 100644 index 0000000000..958f905fb1 --- /dev/null +++ b/src/ru/plugins/sanic-ext/getting-started.md @@ -0,0 +1,74 @@ +# Getting Started + +Sanic Extensions is an *officially supported* plugin developed, and maintained by the SCO. The primary goal of this project is to add additional features to help Web API and Web application development easier. + +## Features + +- CORS protection +- Template rendering with Jinja +- Dependency injection into route handlers +- OpenAPI documentation with Redoc and/or Swagger +- Predefined, endpoint-specific response serializers +- Request query arguments and body input validation +- Auto create `HEAD`, `OPTIONS`, and `TRACE` endpoints + +## Minimum requirements + +- **Python**: 3.8+ +- **Sanic**: 21.9+ + +## Install + +The best method is to just install Sanic Extensions along with Sanic itself: + +```bash +pip install sanic[ext] +``` + +You can of course also just install it by itself. + +```bash +pip install sanic-ext +``` + +## Extend your application + +Out of the box, Sanic Extensions will enable a bunch of features for you. + +---:1 To setup Sanic Extensions (v21.12+), you need to do: **nothing**. If it is installed in the environment, it is setup and ready to go. + +This code is the Hello, world app in the [Sanic Getting Started page](../../guide/getting-started.md) _without any changes_, but using Sanic Extensions with `sanic-ext` installed in the background. :--:1 +```python +from sanic import Sanic +from sanic.response import text + +app = Sanic("MyHelloWorldApp") + +@app.get("/") +async def hello_world(request): + return text("Hello, world.") +``` + +:--- + +---:1 **_OLD DEPRECATED SETUP_** + +In v21.9, the easiest way to get started is to instantiate it with `Extend`. + +If you look back at the Hello, world app in the [Sanic Getting Started page](../../guide/getting-started.md), you will see the only additions here are the two highlighted lines. :--:1 + +```python{3,6} +from sanic import Sanic +from sanic.response import text +from sanic_ext import Extend + +app = Sanic("MyHelloWorldApp") +Extend(app) + +@app.get("/") +async def hello_world(request): + return text("Hello, world.") +``` +:--- + +Regardless of how it is setup, you should now be able to view the OpenAPI documentation and see some of the functionality in action: [http://localhost:8000/docs](http://localhost:8000/docs). diff --git a/src/ru/plugins/sanic-ext/health-monitor.md b/src/ru/plugins/sanic-ext/health-monitor.md new file mode 100644 index 0000000000..b4e2abd370 --- /dev/null +++ b/src/ru/plugins/sanic-ext/health-monitor.md @@ -0,0 +1,62 @@ +# Health monitor + +The health monitor requires both `sanic>=22.9` and `sanic-ext>=22.9`. + +You can setup Sanic Extensions to monitor the health of your worker processes. This requires that you not be in [single process mode](../../guide/deployment/manager.md#single-process-mode). + +## Setup + +---:1 Out of the box, the health monitor is disabled. You will need to opt-in if you would like to use it. :--:1 +```python +app.config.HEALTH = True +``` +:--- + +## How does it work + +The monitor sets up a new background process that will periodically receive acknowledgements of liveliness from each worker process. If a worker process misses a report too many times, then the monitor will restart that one worker. + +## Diagnostics endpoint + +---:1 The health monitor will also enable a diagnostics endpoint that outputs the [worker state](../../guide/deployment/manager.md#worker-state). By default is id disabled. + +::: warning +The diagnostics endpoint is not secured. If you are deploying it in a production environment, you should take steps to protect it with a proxy server if you are using one. If not, you may want to consider disabling this feature in production since it will leak details about your server state. +::: +:--:1 +``` +$ curl http://localhost:8000/__health__ +{ + 'Sanic-Main': {'pid': 99997}, + 'Sanic-Server-0-0': { + 'server': True, + 'state': 'ACKED', + 'pid': 9999, + 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), + 'starts': 2, + 'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc) + }, + 'Sanic-Reloader-0': { + 'server': False, + 'state': 'STARTED', + 'pid': 99998, + 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), + 'starts': 1 + } +} +``` +:--- + + +## Configuration + +| Key | Type | Default | Description | +| -------------------------- | ------ | --------------- | --------------------------------------------------------------------------- | +| HEALTH | `bool` | `False` | Whether to enable this extension. | +| HEALTH_ENDPOINT | `bool` | `False` | Whether to enable the diagnostics endpoint. | +| HEALTH_MAX_MISSES | `int` | `3` | The number of consecutive misses before a worker process is restarted. | +| HEALTH_MISSED_THRESHHOLD | `int` | `10` | The number of seconds the monitor checks for worker process health. | +| HEALTH_MONITOR | `bool` | `True` | Whether to enable the health monitor. | +| HEALTH_REPORT_INTERVAL | `int` | `5` | The number of seconds between reporting each acknowledgement of liveliness. | +| HEALTH_URI_TO_INFO | `str` | `""` | The URI path of the diagnostics endpoint. | +| HEALTH_URL_PREFIX | `str` | `"/__health__"` | The URI prefix of the diagnostics blueprint. | diff --git a/src/ru/plugins/sanic-ext/http/cors.md b/src/ru/plugins/sanic-ext/http/cors.md new file mode 100644 index 0000000000..1149c04d0e --- /dev/null +++ b/src/ru/plugins/sanic-ext/http/cors.md @@ -0,0 +1,86 @@ +# CORS protection + +Cross-Origin Resource Sharing (aka CORS) is a *huge* topic by itself. The documentation here cannot go into enough detail about *what* it is. You are highly encouraged to do some research on your own to understand the security problem presented by it, and the theory behind the solutions. [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) are a great first step. + +In super brief terms, CORS protection is a framework that browsers use to facilitate how and when a web page can access information from another domain. It is extremely relevant to anyone building a single-page application. Often times your frontend might be on a domain like `https://portal.myapp.com`, but it needs to access the backend from `https://api.myapp.com`. + +The implementation here is heavily inspired by [`sanic-cors`](https://github.com/ashleysommer/sanic-cors), which is in turn based upon [`flask-cors`](https://github.com/corydolphin/flask-cors). It is therefore very likely that you can achieve a near drop-in replacement of `sanic-cors` with `sanic-ext`. + +## Basic implementation + +---:1 + +As shown in the example in the [auto-endpoints example](methods.md#options), Sanic Extensions will automatically enable CORS protection without further action. But, it does not offer too much out of the box. + +At a *bare minimum*, it is **highly** recommended that you set `config.CORS_ORIGINS` to the intended origin(s) that will be accessing the application. + +:--:1 +```python +from sanic import Sanic, text +from sanic_ext import Extend + +app = Sanic(__name__) +app.config.CORS_ORIGINS = "http://foobar.com,http://bar.com" +Extend(app) + +@app.get("/") +async def hello_world(request): + return text("Hello, world.") +``` + +``` +$ curl localhost:8000 -X OPTIONS -i +HTTP/1.1 204 No Content +allow: GET,HEAD,OPTIONS +access-control-allow-origin: http://foobar.com +connection: keep-alive +``` +:--- + +## Configuration + +The true power of CORS protection, however, comes into play once you start configuring it. Here is a table of all of the options. + +| Key | Type | Default | Description | +| --------------------------- | -------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `CORS_ALLOW_HEADERS` | `str` or `List[str]` | `"*"` | The list of headers that will appear in `access-control-allow-headers`. | +| `CORS_ALWAYS_SEND` | `bool` | `True` | When `True`, will always set a value for `access-control-allow-origin`. When `False`, will only set it if there is an `Origin` header. | +| `CORS_AUTOMATIC_OPTIONS` | `bool` | `True` | When the incoming preflight request is received, whether to automatically set values for `access-control-allow-headers`, `access-control-max-age`, and `access-control-allow-methods` headers. If `False` these values will only be set on routes that are decorated with the `@cors` decorator. | +| `CORS_EXPOSE_HEADERS` | `str` or `List[str]` | `""` | Specific list of headers to be set in `access-control-expose-headers` header. | +| `CORS_MAX_AGE` | `str`, `int`, `timedelta` | `0` | The maximum number of seconds the preflight response may be cached using the `access-control-max-age` header. A falsey value will cause the header to not be set. | +| `CORS_METHODS` | `str` or `List[str]` | `""` | The HTTP methods that the allowed origins can access, as set on the `access-control-allow-methods` header. | +| `CORS_ORIGINS` | `str`, `List[str]`, `re.Pattern` | `"*"` | The origins that are allowed to access the resource, as set on the `access-control-allow-origin` header. | +| `CORS_SEND_WILDCARD` | `bool` | `False` | If `True`, will send the wildcard `*` origin instead of the `origin` request header. | +| `CORS_SUPPORTS_CREDENTIALS` | `bool` | `False` | Whether to set the `access-control-allow-credentials` header. | +| `CORS_VARY_HEADER` | `bool` | `True` | Whether to add `vary` header, when appropriate. | + +*For the sake of brevity, where the above says `List[str]` any instance of a `list`, `set`, `frozenset`, or `tuple` will be acceptable. Alternatively, if the value is a `str`, it can be a comma delimited list.* + +## Route level overrides + +---:1 + +It may sometimes be necessary to override app-wide settings for a specific route. To allow for this, you can use the `@sanic_ext.cors()` decorator to set different route-specific values. + +The values that can be overridden with this decorator are: + +- `origins` +- `expose_headers` +- `allow_headers` +- `allow_methods` +- `supports_credentials` +- `max_age` + +:--:1 +```python +from sanic_ext import cors + +app.config.CORS_ORIGINS = "https://foo.com" + + +@app.get("/", host="bar.com") +@cors(origins="https://bar.com") +async def hello_world(request): + return text("Hello, world.") +``` +:--- diff --git a/src/ru/plugins/sanic-ext/http/methods.md b/src/ru/plugins/sanic-ext/http/methods.md new file mode 100644 index 0000000000..59cfdcd8d2 --- /dev/null +++ b/src/ru/plugins/sanic-ext/http/methods.md @@ -0,0 +1,125 @@ +# HTTP Methods + +## Auto-endpoints + +The default behavior is to automatically generate `HEAD` endpoints for all `GET` routes, and `OPTIONS` endpoints for all routes. Additionally, there is the option to automatically generate `TRACE` endpoints. However, these are not enabled by default. + +::::tabs + +:::tab HEAD + +- **Configuration**: `AUTO_HEAD` (default `True`) +- **MDN**: [Read more](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD) + +A `HEAD` request provides the headers and an otherwise identical response to what a `GET` request would provide. However, it does not actually return the body. + +```python +@app.get("/") +async def hello_world(request): + return text("Hello, world.") +``` + +Given the above route definition, Sanic Extensions will enable `HEAD` responses, as seen here. + +``` +$ curl localhost:8000 --head +HTTP/1.1 200 OK +access-control-allow-origin: * +content-length: 13 +connection: keep-alive +content-type: text/plain; charset=utf-8 +``` + +::: + +:::tab OPTIONS + +- **Configuration**: `AUTO_OPTIONS` (default `True`) +- **MDN**: [Read more](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS) + +`OPTIONS` requests provide the recipient with details about how the client is allowed to communicate with a given endpoint. + +```python +@app.get("/") +async def hello_world(request): + return text("Hello, world.") +``` + +Given the above route definition, Sanic Extensions will enable `OPTIONS` responses, as seen here. + +It is important to note that we also see `access-control-allow-origins` in this example. This is because the [CORS protection](cors.md) is enabled by default. + +``` +$ curl localhost:8000 -X OPTIONS -i +HTTP/1.1 204 No Content +allow: GET,HEAD,OPTIONS +access-control-allow-origin: * +connection: keep-alive +``` + +::: tip Even though Sanic Extensions will setup these routes for you automatically, if you decide to manually create an `@app.options` route, it will *not* be overridden. ::: + +:::tab TRACE + +- **Configuration**: `AUTO_TRACE` (default `False`) +- **MDN**: [Read more](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/TRACE) + +By default, `TRACE` endpoints will **not** be automatically created. However, Sanic Extensions **will allow** you to create them if you wanted. This is something that is not allowed in vanilla Sanic. + +```python +@app.route("/", methods=["trace"]) +async def handler(request): + ... +``` + +To enable auto-creation of these endpoints, you must first enable them when extending Sanic. + +```python +from sanic_ext import Extend, Config + +app.extend(config=Config(http_auto_trace=True)) +``` + +Now, assuming you have some endpoints setup, you can trace them as shown here: + +``` +$ curl localhost:8000 -X TRACE +TRACE / HTTP/1.1 +Host: localhost:9999 +User-Agent: curl/7.76.1 +Accept: */* +``` + +::: tip Setting up `AUTO_TRACE` can be super helpful, especially when your application is deployed behind a proxy since it will help you determine how the proxy is behaving. ::: + +:::: + +## Additional method support + +Vanilla Sanic allows you to build endpoints with the following HTTP methods: + +- [GET](/en/guide/basics/routing.html#get) +- [POST](/en/guide/basics/routing.html#post) +- [PUT](/en/guide/basics/routing.html#put) +- [HEAD](/en/guide/basics/routing.html#head) +- [OPTIONS](/en/guide/basics/routing.html#options) +- [PATCH](/en/guide/basics/routing.html#patch) +- [DELETE](/en/guide/basics/routing.html#delete) + +See [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) for more. + +---:1 + +There are, however, two more "standard" HTTP methods: `TRACE` and `CONNECT`. Sanic Extensions will allow you to build endpoints using these methods, which would otherwise not be allowed. + +It is worth pointing out that this will *NOT* enable convenience methods: `@app.trace` or `@app.connect`. You need to use `@app.route` as shown in the example here. + +:--:1 + +```python +@app.route("/", methods=["trace", "connect"]) +async def handler(_): + return empty() +``` + +:--- diff --git a/src/ru/plugins/sanic-ext/injection.md b/src/ru/plugins/sanic-ext/injection.md new file mode 100644 index 0000000000..0b3d073601 --- /dev/null +++ b/src/ru/plugins/sanic-ext/injection.md @@ -0,0 +1,329 @@ +# Dependency Injection + +Dependency injection is a method to add arguments to a route handler based upon the defined function signature. Specifically, it looks at the **type annotations** of the arguments in the handler. This can be useful in a number of cases like: + +- Fetching an object based upon request headers (like the current session user) +- Recasting certain objects into a specific type +- Using the request object to prefetch data +- Auto inject services + +The `Extend` instance has two basic methods on it used for dependency injection: a lower level `add_dependency`, and a higher level `dependency`. + +**Lower level**: `app.ext.add_dependency(...)` + +- `type: Type,`: some unique class that will be the type of the oject +- `constructor: Optional[Callable[..., Any]],` (OPTIONAL): a function that will return that type + +**Higher level**: `app.ext.dependency(...)` + +- `obj: Any`: any object that you would like injected +- `name: Optional[str]`: some name that could alternately be used as a reference + +Let's explore some use cases here. + +::: warning If you used dependency injection prior to v21.12, the lower level API method was called `injection`. It has since been renamed to `add_dependency` and starting in v21.12 `injection` is an alias for `add_dependency`. The `injection` method has been deprecated for removal in v22.6. ::: + +## Basic implementation + +The simplest use case would be simply to recast a value. + +---:1 This could be useful if you have a model that you want to generate based upon the matched path parameters. :--:1 +```python +@dataclass +class IceCream: + flavor: str + + def __str__(self) -> str: + return f"{self.flavor.title()} (Yum!)" + + +app.ext.add_dependency(IceCream) + + +@app.get("/") +async def ice_cream(request, flavor: IceCream): + return text(f"You chose: {flavor}") +``` + +``` +$ curl localhost:8000/chocolate +You chose Chocolate (Yum!) +``` +:--- + +---:1 This works by passing a keyword argument to the constructor of the `type` argument. The previous example is equivalent to this. :--:1 +```python +flavor = IceCream(flavor="chocolate") +``` +:--- + +## Additional constructors + +---:1 Sometimes you may need to also pass a constructor. This could be a function, or perhaps even a classmethod that acts as a constructor. In this example, we are creating an injection that will call `Person.create` first. + +Also important to note on this example, we are actually injecting **two (2)** objects! It of course does not need to be this way, but we will inject objects based upon the function signature. :--:1 +```python +@dataclass +class PersonID: + person_id: int + + +@dataclass +class Person: + person_id: PersonID + name: str + age: int + + @classmethod + async def create(cls, request: Request, person_id: int): + return cls(person_id=PersonID(person_id), name="noname", age=111) + + + +app.ext.add_dependency(Person, Person.create) +app.ext.add_dependency(PersonID) + +@app.get("/person/") +async def person_details( + request: Request, person_id: PersonID, person: Person +): + return text(f"{person_id}\n{person}") +``` + +``` +$ curl localhost:8000/person/123 +PersonID(person_id=123) +Person(person_id=PersonID(person_id=123), name='noname', age=111) +``` +:--- + +When a `constructor` is passed to `ext.add_dependency` (like in this example) that will be called. If not, then the object will be created by calling the `type`. A couple of important things to note about passing a `constructor`: + +1. A positional `request: Request` argument is *usually* expected. See the `Person.create` method above as an example using a `request` and [arbitrary constructors](#arbitrary-constructors) for how to use a callable that does not require a `request`. +1. All matched path parameters are injected as keyword arguments. +1. Dependencies can be chained and nested. Notice how in the previous example the `Person` dataclass has a `PersonID`? That means that `PersonID` will be called first, and that value is added to the keyword arguments when calling `Person.create`. + +## Arbitrary constructors + +---:1 Sometimes you may want to construct your injectable _without_ the `Request` object. This is useful if you have arbitrary classes or functions that create your objects. If the callable does have any required arguments, then they should themselves be injectable objects. + +This is very useful if you have services or other types of objects that should only exist for the lifetime of a single request. For example, you might use this pattern to pull a single connection from your database pool. :--:1 +```python +class Alpha: + ... + + +class Beta: + def __init__(self, alpha: Alpha) -> None: + self.alpha = alpha + +app.ext.add_dependency(Alpha) +app.ext.add_dependency(Beta) + +@app.get("/beta") +async def handler(request: Request, beta: Beta): + assert isinstance(beta.alpha, Alpha) +``` +:--- + +*Added in v22.9* + +## Objects from the `Request` + +---:1 Sometimes you may want to extract details from the request and preprocess them. You could, for example, cast the request JSON to a Python object, and then add some additional logic based upon DB queries. + +::: warning If you plan to use this method, you should note that the injection actually happens *before* Sanic has had a chance to read the request body. The headers should already have been consumed. So, if you do want access to the body, you will need to manually consume as seen in this example. + +```python +await request.receive_body() +``` +::: + +This could be used in cases where you otherwise might: + +- use middleware to preprocess and add something to the `request.ctx` +- use decorators to preprocess and inject arguments into the request handler + +In this example, we are using the `Request` object in the `compule_profile` constructor to run a fake DB query to generate and return a `UserProfile` object. :--:1 +```python +@dataclass +class User: + name: str + + +@dataclass +class UserProfile: + user: User + age: int = field(default=0) + email: str = field(default="") + + def __json__(self): + return ujson.dumps( + { + "name": self.user.name, + "age": self.age, + "email": self.email, + } + ) + + +async def fake_request_to_db(body): + today = date.today() + email = f'{body["name"]}@something.com'.lower() + difference = today - date.fromisoformat(body["birthday"]) + age = int(difference.days / 365) + return UserProfile( + User(body["name"]), + age=age, + email=email, + ) + + +async def compile_profile(request: Request): + await request.receive_body() + profile = await fake_request_to_db(request.json) + return profile + + +app.ext.add_dependency(UserProfile, compile_profile) + + +@app.patch("/profile") +async def update_profile(request, profile: UserProfile): + return json(profile) +``` + +``` +$ curl localhost:8000/profile -X PATCH -d '{"name": "Alice", "birthday": "2000-01-01"}' +{ + "name":"Alice", + "age":21, + "email":"alice@something.com" +} +``` +:--- + +## Injecting services + +It is a common pattern to create things like database connection pools and store them on the `app.ctx` object. This makes them available throughout your application, which is certainly a convenience. One downside, however, is that you no longer have a typed object to work with. You can use dependency injections to fix this. First we will show the concept using the lower level `add_dependency` like we have been using in the previous examples. But, there is a better way using the higher level `dependency` method. + +---:1 +### The lower level API using `add_dependency` + +This works very similar to the [last example](#objects-from-the-request) where the goal is the extract something from the `Request` object. In this example, a database object was created on the `app.ctx` instance, and is being returned in the dependency injection constructor. :--:1 +```python +class FakeConnection: + async def execute(self, query: str, **arguments): + return "result" + + +@app.before_server_start +async def setup_db(app, _): + app.ctx.db_conn = FakeConnection() + app.ext.add_dependency(FakeConnection, get_db) + + +def get_db(request: Request): + return request.app.ctx.db_conn + + + + +@app.get("/") +async def handler(request, conn: FakeConnection): + response = await conn.execute("...") + return text(response) +``` +``` +$ curl localhost:8000/ +result +``` +:--- + +---:1 +### The higher level API using `dependency` + +Since we have an actual *object* that is available when adding the dependency injection, we can use the higher level `dependency` method. This will make the pattern much easier to write. + +This method should always be used when you want to inject something that exists throughout the lifetime of the application instance and is not request specific. It is very useful for services, third party clients, and connection pools since they are not request specific. :--:1 +```python +class FakeConnection: + async def execute(self, query: str, **arguments): + return "result" + + +@app.before_server_start +async def setup_db(app, _): + db_conn = FakeConnection() + app.ext.dependency(db_conn) + + +@app.get("/") +async def handler(request, conn: FakeConnection): + response = await conn.execute("...") + return text(response) +``` +``` +$ curl localhost:8000/ +result +``` +:--- + +## Generic types + +Be carefule when using a [generic type](https://docs.python.org/3/library/typing.html#typing.Generic). The way that Sanic's dependency injection works is by matching the entire type definition. Therefore, `Foo` is not the same as `Foo[str]`. This can be particularly tricky when trying to use the [higher-level `dependency` method](#the-higher-level-api-using-dependency) since the type is inferred. + +---:1 For example, this will **NOT** work as expected since there is no definition for `Test[str]`. :--:1 +```python{12,16} +import typing +from sanic import Sanic, text + +T = typing.TypeVar("T") + + +class Test(typing.Generic[T]): + test: T + + +app = Sanic("testapp") +app.ext.dependency(Test()) + + +@app.get("/") +def test(request, test: Test[str]): + ... +``` +:--- + +---:1 To get this example to work, you will need to add an explicit definition for the type you intend to be injected. :--:1 +```python{13} +import typing +from sanic import Sanic, text + +T = typing.TypeVar("T") + + +class Test(typing.Generic[T]): + test: T + + +app = Sanic("testapp") +_singleton = Test() +app.ext.add_dependency(Test[str], lambda: _singleton) + + +@app.get("/") +def test(request, test: Test[str]): + ... +``` +:--- + +## Configuration + +---:1 By default, dependencies will be injected after the `http.routing.after` [signal](../../guide/advanced/signals.md#built-in-signals). Starting in v22.9, you can change this to the `http.handler.before` signal. :--:1 +```python +app.config.INJECTION_SIGNAL = "http.handler.before" +``` +:--- + +*Added in v22.9* diff --git a/src/ru/plugins/sanic-ext/logger.md b/src/ru/plugins/sanic-ext/logger.md new file mode 100644 index 0000000000..aa8b18c141 --- /dev/null +++ b/src/ru/plugins/sanic-ext/logger.md @@ -0,0 +1,26 @@ +# Background logger + +The background logger requires both `sanic>=22.9` and `sanic-ext>=22.9`. + +You can setup Sanic Extensions to log all of your messages from a background process. This requires that you not be in [single process mode](../../guide/deployment/manager.md#single-process-mode). + +Logging can sometimes be an expensive operation. By pushing all logging off to a background process, you can potentially gain some performance benefits. + +## Setup + +---:1 Out of the box, the background logger is disabled. You will need to opt-in if you would like to use it. :--:1 +```python +app.config.LOGGING = True +``` +:--- + +## How does it work + +When enabled, the extension will create a `multoprocessing.Queue`. It will remove all handlers on the [default Sanic loggers](../../guide/best-practices/logging.md) and replace them with a [`QueueHandler`](https://docs.python.org/3/library/logging.handlers.html#queuehandler). When a message is logged, it will be pushed into the queue by the handler, and read by the background process to the log handlers that were originally in place. This means you can still configure logging as normal and it should "just work." + +## Configuration + +| Key | Type | Default | Description | +| ------------------------ | ------ | ------- | ------------------------------------------------------- | +| LOGGING | `bool` | `False` | Whether to enable this extension. | +| LOGGING_QUEUE_MAX_SIZE | `int` | `4096` | The max size of the queue before messages are rejected. | diff --git a/src/ru/plugins/sanic-ext/openapi.md b/src/ru/plugins/sanic-ext/openapi.md new file mode 100644 index 0000000000..866084fdea --- /dev/null +++ b/src/ru/plugins/sanic-ext/openapi.md @@ -0,0 +1,7 @@ +# Openapi + +- Добавление документации при помощи декораторов +- Документирование объектов представления на основе классов +- Использование autodoc +- Рендеринг документации при помощи redoc/swagger +- Валидация diff --git a/src/ru/plugins/sanic-ext/openapi/advanced.md b/src/ru/plugins/sanic-ext/openapi/advanced.md new file mode 100644 index 0000000000..072217d3bd --- /dev/null +++ b/src/ru/plugins/sanic-ext/openapi/advanced.md @@ -0,0 +1,10 @@ +# Продвинутый уровень + +_Documentation coming EOQ1 2023_ + +## Объекты представления на основе классов + +## Блюпринты + + +## Компоненты diff --git a/src/ru/plugins/sanic-ext/openapi/autodoc.md b/src/ru/plugins/sanic-ext/openapi/autodoc.md new file mode 100644 index 0000000000..a382e51329 --- /dev/null +++ b/src/ru/plugins/sanic-ext/openapi/autodoc.md @@ -0,0 +1,125 @@ +# Auto-documentation + +To make documenting endpoints easier, Sanic Extensions will use a function's docstring to populate your documentation. + +## Summary and description + +---:1 A function's docstring will be used to create the summary and description. As you can see from this example here, the docstring has been parsed to use the first line as the summary, and the remainder of the string as the description. :--:1 +```python +@app.get("/foo") +async def handler(request, something: str): + """This is a simple foo handler + + It is helpful to know that you could also use **markdown** inside your + docstrings. + + - one + - two + - three""" + return text(">>>") +``` +```json +"paths": { + "/foo": { + "get": { + "summary": "This is a simple foo handler", + "description": "It is helpful to know that you could also use **markdown** inside your
docstrings.

- one
- two
- three", + "responses": { + "default": { + "description": "OK" + } + }, + "operationId": "get_handler" + } + } +} +``` +:--- + +## Operation level YAML + +---:1 You can expand upon this by adding valid OpenAPI YAML to the docstring. Simply add a line that contains `openapi:`, followed by your YAML. + +The `---` shown in the example is *not* necessary. It is just there to help visually identify the YAML as a distinct section of the docstring. :--:1 +```python +@app.get("/foo") +async def handler(request, something: str): + """This is a simple foo handler + + Now we will add some more details + + openapi: + --- + operationId: fooDots + tags: + - one + - two + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: Just some dots + """ + return text("...") +``` +```json +"paths": { + "/foo": { + "get": { + "operationId": "fooDots", + "summary": "This is a simple foo handler", + "description": "Now we will add some more details", + "tags": [ + "one", + "two" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "How many items to return at one time (max 100)", + "required": false, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Just some dots" + } + } + } + } +} +``` + +:--- + +::: tip +When both YAML documentation and decorators are used, it is the content from the decorators that will take priority when generating the documentation. +::: + +## Excluding docstrings + +---:1 Sometimes a function may contain a docstring that is not meant to be consumed inside the documentation. + +**Option 1**: Globally turn off auto-documentation `app.config.OAS_AUTODOC = False` + +**Option 2**: Disable it for the single handler with the `@openapi.no_autodoc` decorator :--:1 +```python +@app.get("/foo") +@openapi.no_autodoc +async def handler(request, something: str): + """This is a docstring about internal info only. Do not parse it. + """ + return text("...") +``` +:--- diff --git a/src/ru/plugins/sanic-ext/openapi/basic.md b/src/ru/plugins/sanic-ext/openapi/basic.md new file mode 100644 index 0000000000..d820b8c7c7 --- /dev/null +++ b/src/ru/plugins/sanic-ext/openapi/basic.md @@ -0,0 +1,66 @@ +# Basics + +::: tip Side note The OpenAPI implementation in Sanic Extensions is based upon the OAS3 implementation from [`sanic-openapi`](https://github.com/sanic-org/sanic-openapi). In fact, Sanic Extensions is in a large way the successor to that project, which entered maintenance mode upon the release of Sanic Extensions. If you were previously using OAS3 with `sanic-openapi` you should have an easy path to upgrading to Sanic Extensions. Unfortunately, this project does *NOT* support the OAS2 specification. ::: + +---:1 + +Out of the box, Sanic Extensions provides automatically generated API documentation using the [v3.0 OpenAPI specification](https://swagger.io/specification/). There is nothing special that you need to do + +:--:1 + +```python +from sanic import Sanic + +app = Sanic("MyApp") + +# Add all of your views +``` + +:--- + +After doing this, you will now have beautiful documentation already generated for you based upon your existing application: + +- [http://localhost:8000/docs](http://localhost:8000/docs) +- [http://localhost:8000/docs/redoc](http://localhost:8000/docs/redoc) +- [http://localhost:8000/docs/swagger](http://localhost:8000/docs/swagger) + +Checkout the [section on configuration](../configuration.md) to learn about changing the routes for the docs. You can also turn off one of the two UIs, and customize which UI will be available on the `/docs` route. + +---:1 + +Using [Redoc](https://github.com/Redocly/redoc) + +![Redoc](~@assets/images/sanic-ext-redoc.png) + + +:--:1 + +or [Swagger UI](https://github.com/swagger-api/swagger-ui) + +![Swagger UI](~@assets/images/sanic-ext-swagger.png) + + +:--- + +## Changing specification metadata + +---:1 If you want to change any of the metada, you should use the `describe` method. + +In this example `dedent` is being used with the `description` argument to make multi-line strings a little cleaner. This is not necessary, you can pass any string value here. :--:1 +```python +from textwrap import dedent + +app.ext.openapi.describe( + "Testing API", + version="1.2.3", + description=dedent( + """ + # Info + This is a description. It is a good place to add some _extra_ doccumentation. + + **MARKDOWN** is supported. + """ + ), +) +``` +:--- diff --git a/src/ru/plugins/sanic-ext/openapi/decorators.md b/src/ru/plugins/sanic-ext/openapi/decorators.md new file mode 100644 index 0000000000..e4a04f15c2 --- /dev/null +++ b/src/ru/plugins/sanic-ext/openapi/decorators.md @@ -0,0 +1,479 @@ +# Decorators + +The primary mechanism for adding content to your schema is by decorating your endpoints. If you have used `sanic-openapi` in the past, this should be familiar to you. The decorators and their arguments match closely the [OAS v3.0 specification](https://swagger.io/specification/). + +---:1 + +All of the examples show will wrap around a route definition. When you are creating these, you should make sure that your Sanic route decorator (`@app.route`, `@app.get`, etc) is the outermost decorator. That is to say that you should put that first and then one or more of the below decorators after. + +:--:1 + +```python +from sanic_ext import openapi + + +@app.get("/path/to/") +@openapi.summary("This is a summary") +@openapi.description("This is a description") +async def handler(request, somethind: str): + ... +``` + +:--- + +---:1 + +You will also see a lot of the below examples reference a model object. For the sake of simplicity, the examples will use `UserProfile` that will look like this. The point is that it can be any well-typed class. You could easily imagine this being a `dataclass` or some other kind of model object. + +:--:1 + +```python +class UserProfile: + name: str + age: int + email: str +``` + +:--- + +## Definition decorator + +### `@openapi.definition` + +The `@openapi.definition` decorator allows you to define all parts of an operations on a path at once. It is an omnibums decorator in that it has the same capabilities to create operation definitions as the rest of the decorators. Using multiple field-specific decorators or a single decorator is a style choice for you the developer. + +The fields are purposely permissive in accepting multiple types to make it easiest for you to define your operation. + +**Arguments** + +| Field | Type | +| ------------- | ---------------------------------------------------- | +| `body` | ***dict, RequestBody, ***YourModel****** | +| `deprecated` | **bool** | +| `description` | **str** | +| `document` | **str, ExternalDocumentation** | +| `exclude` | **bool** | +| `operation` | **str** | +| `parameter` | **str, dict, Parameter, [str], [dict], [Parameter]** | +| `response` | **dict, Response, *YourModel*, [dict], [Response]** | +| `summary` | **str** | +| `tag` | **str, Tag, [str], [Tag]** | +| `secured` | **Dict[str, Any]** | + +**Examples** + +---:1 + +```python +@openapi.definition( + body=RequestBody(UserProfile, required=True), + summary="User profile update", + tag="one", + response=[Success, Response(Failure, status=400)], +) +``` + +:--:1 + +:--- + +*See below examples for more examples. Any of the values for the below decorators can be used in the corresponding keyword argument.* + +## Field-specific decorators + +All the following decorators are based on `@openapi` + +::::tabs + +:::tab body + +**Arguments** + +| Field | Type | +| ----------- | ---------------------------------- | +| **content** | ***YourModel*, dict, RequestBody** | + +**Examples** + +---:1 + +```python +@openapi.body(UserProfile) +``` + +```python +@openapi.body({"application/json": UserProfile}) +``` + +```python +@openapi.body(RequestBody({"application/json": UserProfile})) +``` + +:--:1 + +```python +@openapi.body({"content": UserProfile}) +``` + +```python +@openapi.body(RequestBody(UserProfile)) +``` + +```python +@openapi.body({"application/json": {"description": ...}}) +``` + +:--- + +::: + +:::tab deprecated + +**Arguments** + +*None* + +**Examples** + +---:1 + +```python +@openapi.deprecated() +``` + +:--:1 + +```python +@openapi.deprecated +``` + +:--- + +::: + +:::tab description + +**Arguments** + +| Field | Type | +| ------ | ------- | +| `text` | **str** | + +**Examples** + +---:1 + +```python +@openapi.description( + """This is a **description**. + +## You can use `markdown` + +- And +- make +- lists. +""" +) +``` + +:--:1 + +:--- + +::: + +:::tab document + +**Arguments** + +| Field | Type | +| ------------- | ------- | +| `url` | **str** | +| `description` | **str** | + +**Examples** + +---:1 + +```python +@openapi.document("http://example.com/docs") +``` + +:--:1 + +```python +@openapi.document(ExternalDocumentation("http://example.com/more")) +``` + +:--- + +::: + +:::tab exclude + +Can be used on route definitions like all of the other decorators, or can be called on a Blueprint + +**Arguments** + +| Field | Type | Default | +| ------ | ------------- | -------- | +| `flag` | **bool** | **True** | +| `bp` | **Blueprint** | | + +**Examples** + +---:1 + +```python +@openapi.exclude() +``` + +:--:1 + +```python +openapi.exclude(bp=some_blueprint) +``` + +:--- + +::: + +:::tab operation + +Sets the operation ID. + +**Arguments** + +| Field | Type | +| ------ | ------- | +| `name` | **str** | + +**Examples** + +---:1 + +```python +@openapi.operation("doNothing") +``` + +:--:1 + +:--- + +::: + +:::tab parameter + +**Arguments** + +| Field | Type | Default | +| ---------- | ----------------------------------------- | ----------- | +| `name` | **str** | | +| `schema` | ***type*** | **str** | +| `location` | **"query", "header", "path" or "cookie"** | **"query"** | + +**Examples** + +---:1 + +```python +@openapi.parameter("thing") +``` + +```python +@openapi.parameter(parameter=Parameter("foobar", deprecated=True)) +``` + +:--:1 + +```python +@openapi.parameter("Authorization", str, "header") +``` + +```python +@openapi.parameter("thing", required=True, allowEmptyValue=False) +``` + +:--- + +::: + +:::tab response + +**Arguments** + +If using a `Response` object, you should not pass any other arguments. + +| Field | Type | +| ------------- | ----------------------------- | +| `status` | **int** | +| `content` | ***type*, *YourModel*, dict** | +| `description` | **str** | +| `response` | **Response** | + +**Examples** + +---:1 + +```python +@openapi.response(200, str, "This is endpoint returns a string") +``` + +```python +@openapi.response(200, {"text/plain": str}, "...") +``` + +```python +@openapi.response(response=Response(UserProfile, description="...")) +``` + +```python +@openapi.response( + response=Response( + { + "application/json": UserProfile, + }, + description="...", + status=201, + ) +) +``` + +:--:1 + +```python +@openapi.response(200, UserProfile, "...") +``` + +```python +@openapi.response( + 200, + { + "application/json": UserProfile, + }, + "Description...", +) +``` + +:--- + +::: + +:::tab summary + +**Arguments** + +| Field | Type | +| ------ | ------- | +| `text` | **str** | + +**Examples** + +---:1 + +```python +@openapi.summary("This is an endpoint") +``` + +:--:1 + +:--- + +::: + +:::tab tag + +**Arguments** + +| Field | Type | +| ------- | ------------ | +| `*args` | **str, Tag** | + +**Examples** + +---:1 + +```python +@openapi.tag("foo") +``` + +:--:1 + +```python +@openapi.tag("foo", Tag("bar")) +``` + +:--- + +::: + +:::tab secured + +**Arguments** + +| Field | Type | +| ----------------- | ----------------------- | +| `*args, **kwargs` | **str, Dict[str, Any]** | + +**Examples** + +---:1 +```python +@openapi.secured() +``` +:--:1 :--- + +---:1 +```python +@openapi.secured("foo") +``` +:--:1 +```python +@openapi.secured("token1", "token2") +``` +:--- + +---:1 +```python +@openapi.secured({"my_api_key": []}) +``` +:--:1 +```python +@openapi.secured(my_api_key=[]) +``` +:--- + +Do not forget to use `add_security_scheme`. See [security](./security.md) for more details. + +::: + +:::: + +## Integration with Pydantic + +Pydantic models have the ability to [generate OpenAPI schema](https://pydantic-docs.helpmanual.io/usage/schema/). + +---:1 To take advantage of Pydantic model schema generation, pass the output in place of the schema. :--:1 +```python +from sanic import Sanic, json +from sanic_ext import validate, openapi +from pydantic import BaseModel, Field + +class Test(BaseModel): + foo: str = Field(description="Foo Description", example="FOOO") + bar: str = "test" + + +app = Sanic("test") + +@app.get("/") +@openapi.definition( + body={'application/json': Test.schema()}, +) +@validate(json=Test) +async def get(request): + return json({}) +``` +:--- + +*Added in v22.9* diff --git a/src/ru/plugins/sanic-ext/openapi/security.md b/src/ru/plugins/sanic-ext/openapi/security.md new file mode 100644 index 0000000000..b73b15207e --- /dev/null +++ b/src/ru/plugins/sanic-ext/openapi/security.md @@ -0,0 +1,86 @@ +# Security Schemes + +To document authentication schemes, there are two steps. + +_Security is only available starting in v21.12.2_ + +## Document the scheme + +---:1 The first thing that you need to do is define one or more security schemes. The basic pattern will be to define it as: + +```python +add_security_scheme("", "") +``` + +The `type` should correspond to one of the allowed security schemes: `"apiKey"`, `"http"`, `"oauth2"`, `"openIdConnect"`. You can then pass appropriate keyword arguments as allowed by the specification. + +You should consult the [OpenAPI Specification](https://swagger.io/specification/) for details on what values are appropriate. :--:1 +```python +app.ext.openapi.add_security_scheme("api_key", "apiKey") +app.ext.openapi.add_security_scheme( + "token", + "http", + scheme="bearer", + bearer_format="JWT", +) +app.ext.openapi.add_security_scheme("token2", "http") +app.ext.openapi.add_security_scheme( + "oldschool", + "http", + scheme="basic", +) +app.ext.openapi.add_security_scheme( + "oa2", + "oauth2", + flows={ + "implicit": { + "authorizationUrl": "http://example.com/auth", + "scopes": { + "on:two": "something", + "three:four": "something else", + "threefour": "something else...", + }, + } + }, +) +``` +:--- + +## Document the endpoints + +---:1 There are two options, document _all_ endpoints. + +:--:1 +```python +app.ext.openapi.secured() +app.ext.openapi.secured("token") +``` +:--- + +---:1 Or, document only specific routes. :--:1 +```python +@app.route("/one") +async def handler1(request): + """ + openapi: + --- + security: + - foo: [] + """ + + +@app.route("/two") +@openapi.secured("foo") +@openapi.secured({"bar": []}) +@openapi.secured(baz=[]) +async def handler2(request): + ... + + +@app.route("/three") +@openapi.definition(secured="foo") +@openapi.definition(secured={"bar": []}) +async def handler3(request): + ... +``` +:--- diff --git a/src/ru/plugins/sanic-ext/openapi/ui.md b/src/ru/plugins/sanic-ext/openapi/ui.md new file mode 100644 index 0000000000..81591fc1da --- /dev/null +++ b/src/ru/plugins/sanic-ext/openapi/ui.md @@ -0,0 +1,26 @@ +# UI + +Sanic Extensions comes with both Redoc and Swagger interfaces. You have a choice to use one, or both of them. Out of the box, the following endpoints are setup for you, with the bare `/docs` displaying Redoc. + +- `/docs` +- `/docs/openapi.json` +- `/docs/redoc` +- `/docs/swagger` +- `/docs/openapi-config` + +## Config options + +| **Key** | **Type** | **Default** | **Desctiption** | +| -------------------------- | --------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| `OAS_IGNORE_HEAD` | `bool` | `True` | Whether to display `HEAD` endpoints. | +| `OAS_IGNORE_OPTIONS` | `bool` | `True` | Whether to display `OPTIONS` endpoints. | +| `OAS_PATH_TO_REDOC_HTML` | `Optional[str]` | `None` | Path to HTML to override the default Redoc HTML | +| `OAS_PATH_TO_SWAGGER_HTML` | `Optional[str]` | `None` | Path to HTML to override the default Swagger HTML | +| `OAS_UI_DEFAULT` | `Optional[str]` | `"redoc"` | Can be set to `redoc` or `swagger`. Controls which UI to display on the base route. If set to `None`, then the base route will not be setup. | +| `OAS_UI_REDOC` | `bool` | `True` | Whether to enable Redoc UI. | +| `OAS_UI_SWAGGER` | `bool` | `True` | Whether to enable Swagger UI. | +| `OAS_URI_TO_CONFIG` | `str` | `"/openapi-config"` | URI path to the OpenAPI config used by Swagger | +| `OAS_URI_TO_JSON` | `str` | `"/openapi.json"` | URI path to the JSON document. | +| `OAS_URI_TO_REDOC` | `str` | `"/redoc"` | URI path to Redoc. | +| `OAS_URI_TO_SWAGGER` | `str` | `"/swagger"` | URI path to Swagger. | +| `OAS_URL_PREFIX` | `str` | `"/docs"` | URL prefix to use for the Blueprint for OpenAPI docs. | diff --git a/src/ru/plugins/sanic-ext/templating.md b/src/ru/plugins/sanic-ext/templating.md new file mode 100644 index 0000000000..91eea27c7b --- /dev/null +++ b/src/ru/plugins/sanic-ext/templating.md @@ -0,0 +1,132 @@ +# Templating + +Sanic Extensions can easily help you integrate templates into your route handlers. + + +## Dependencies + +**Currently, we only support [Jinja](https://github.com/pallets/jinja/).** + +[Read the Jinja docs first](https://jinja.palletsprojects.com/en/3.1.x/) if you are unfamiliar with how to create templates. + +Sanic Extensions will automatically setup and load Jinja for you if it is installed in your environment. Therefore, the only setup that you need to do is install Jinja: + +``` +pip install Jinja2 +``` + +## Rendering a template from a file + +There are three (3) ways for you: + +1. Using a decorator to pre-load the template file +1. Returning a rendered `HTTPResponse` object +1. Hybrid pattern that creates a `LazyResponse` + +Let's imagine you have a file called `./templates/foo.html`: + +```html + + + + + My Webpage + + + +

Hello, world!!!!

+
    + {% for item in seq %} +
  • {{ item }}
  • + {% endfor %} +
+ + + +``` + +Let's see how you could render it with Sanic + Jinja. + +### Option 1 - as a decorator + +---:1 The benefit of this approach is that the templates can be predefined at startup time. This will mean that less fetching needs to happen in the handler, and should therefore be the fastest option. :--:1 +```python +@app.get("/") +@app.ext.template("foo.html") +async def handler(request: Request): + return {"seq": ["one", "two"]} +``` +:--- + +### Option 2 - as a return object + +---:1 This is meant to mimic the `text`, `json`, `html`, `file`, etc pattern of core Sanic. It will allow the most customization to the response object since it has direct control of it. Just like in other `HTTPResponse` objects, you can control headers, cookies, etc. :--:1 +```python +from sanic_ext import render + +@app.get("/alt") +async def handler(request: Request): + return await render( + "foo.html", context={"seq": ["three", "four"]}, status=400 + ) +``` +:--- + +### Option 3 - hybrid/lazy + +---:1 In this approach, the template is defined up front and not inside the handler (for performance). Then, the `render` function returns a `LazyResponse` that can be used to build a proper `HTTPResponse` inside the decorator. :--:1 +```python +from sanic_ext import render + +@app.get("/") +@app.ext.template("foo.html") +async def handler(request: Request): + return await render(context={"seq": ["five", "six"]}, status=400) +``` +:--- + +## Rendering a template from a string + +---:1 Sometimes you may want to write (or generate) your template inside of Python code and _not_ read it from an HTML file. In this case, you can still use the `render` function we saw above. Just use `template_source`. :--:1 +```python +from sanic_ext import render +from textwrap import dedent + +@app.get("/") +async def handler(request): + template = dedent(""" + + + + + My Webpage + + + +

Hello, world!!!!

+
    + {% for item in seq %} +
  • {{ item }}
  • + {% endfor %} +
+ + + + """) + return await render( + template_source=template, + context={"seq": ["three", "four"]}, + app=app, + ) +``` +:--- + +::: tip In this example, we use `textwrap.dedent` to remove the whitespace in the beginning of each line of the multi-line string. It is not necessary, but just a nice touch to keep both the code and the generated source clean. ::: + +## Development and auto-reload + +If auto-reload is turned on, then changes to your template files should trigger a reload of the server. + +## Configuration + +See `templating_enable_async` and `templating_path_to_templates` in [settings](./configuration.md#settings). diff --git a/src/ru/plugins/sanic-ext/validation.md b/src/ru/plugins/sanic-ext/validation.md new file mode 100644 index 0000000000..0aa293cbaa --- /dev/null +++ b/src/ru/plugins/sanic-ext/validation.md @@ -0,0 +1,173 @@ +# Validation + +One of the most commonly implemented features of a web application is user-input validation. For obvious reasons, this is not only a security issue, but also just plain good practice. You want to make sure your data conforms to expectations, and throw a `400` response when it does not. + +## Implementation + +### Validation with Dataclasses + +With the introduction of [Data Classes](https://docs.python.org/3/library/dataclasses.html), Python made it super simple to create objects that meet a defined schema. However, the standard library only supports type checking validation, **not** runtime validation. Sanic Extensions adds the ability to do runtime validations on incoming requests using `dataclasses` out of the box. If you also have either `pydantic` or `attrs` installed, you can alternatively use one of those libraries. + +---:1 + +First, define a model. + +:--:1 + +```python +@dataclass +class SearchParams: + q: str +``` + +:--- + +---:1 + +Then, attach it to your route + +:--:1 + +```python +from sanic_ext import validate + +@app.route("/search") +@validate(query=SearchParams) +async def handler(request, query: SearchParams): + return json(asdict(query)) +``` + +:--- + +---:1 + +You should now have validation on the incoming request. + +:--:1 + +``` +$ curl localhost:8000/search +⚠️ 400 — Bad Request +==================== +Invalid request body: SearchParams. Error: missing a required argument: 'q' +``` +``` +$ curl localhost:8000/search\?q=python +{"q":"python"} +``` + +:--- + +### Validation with Pydantic + + +You can use Pydantic models also. + +---:1 + +First, define a model. + +:--:1 + +```python +class Person(BaseModel): + name: str + age: int +``` + +:--- + +---:1 + +Then, attach it to your route + +:--:1 + +```python +from sanic_ext import validate + +@app.post("/person") +@validate(json=Person) +async def handler(request, body: Person): + return json(body.dict()) +``` +:--- + +---:1 + +You should now have validation on the incoming request. + +:--:1 + +``` +$ curl localhost:8000/person -d '{"name": "Alice", "age": 21}' -X POST +{"name":"Alice","age":21} +``` + +:--- + +### Validation with Attrs + + +You can use Attrs also. + +---:1 + +First, define a model. + +:--:1 + +```python +@attrs.define +class Person: + name: str + age: int + +``` + +:--- + +---:1 + +Then, attach it to your route + +:--:1 + +```python +from sanic_ext import validate + +@app.post("/person") +@validate(json=Person) +async def handler(request, body: Person): + return json(attrs.asdict(body)) +``` +:--- + +---:1 + +You should now have validation on the incoming request. + +:--:1 + +``` +$ curl localhost:8000/person -d '{"name": "Alice", "age": 21}' -X POST +{"name":"Alice","age":21} +``` + +:--- + +## What can be validated? + +The `validate` decorator can be used to validate incoming user data from three places: JSON body data (`request.json`), form body data (`request.form`), and query parameters (`request.args`). + +---:1 As you might expect, you can attach your model using the keyword arguments of the decorator. + +:--:1 +```python +@validate( + json=ModelA, + query=ModelB, + form=ModelC, +) +``` +:--- diff --git a/src/ru/plugins/sanic-testing/clients.md b/src/ru/plugins/sanic-testing/clients.md new file mode 100644 index 0000000000..ee02576ecc --- /dev/null +++ b/src/ru/plugins/sanic-testing/clients.md @@ -0,0 +1,99 @@ +# Test Clients + +There are three different test clients available to you, each of them presents different capabilities. + +## Regular sync client: `SanicTestClient` + +The `SanicTestClient` runs an actual version of the Sanic Server on your local network to run its tests. Each time it calls an endpoint it will spin up a version of the application and bind it to a socket on the host OS. Then, it will use `httpx` to make calls directly to that application. + +This is the typical way that Sanic applications are tested. + +---:1 Once installing Sanic Testing, the regular `SanicTestClient` can be used without further setup. This is because Sanic does the leg work for you under the hood. :--: +```python +app.test_client.get("/path/to/endpoint") +``` +:--- + +---:1 However, you may find it desirable to instantiate the client yourself. :--: +```python +from sanic_testing.testing import SanicTestClient + +test_client = SanicTestClient(app) +test_client.get("/path/to/endpoint") +``` +:--- + +---:1 A third option for starting the test client is to use the `TestManager`. This is a convenience object that sets up both the `SanicTestClient` and the `SanicASGITestClient`. + +:--: +```python +from sanic_testing import TestManager + +mgr = TestManager(app) +app.test_client.get("/path/to/endpoint") +# or +mgr.test_client.get("/path/to/endpoint") +``` +:--- + +You can make a request by using one of the following methods + +- `SanicTestClient.get` +- `SanicTestClient.post` +- `SanicTestClient.put` +- `SanicTestClient.patch` +- `SanicTestClient.delete` +- `SanicTestClient.options` +- `SanicTestClient.head` +- `SanicTestClient.websocket` +- `SanicTestClient.request` + +You can use these methods *almost* identically as you would when using `httpx`. Any argument that you would pass to `httpx` will be accepted, **with one caveat**: If you are using `test_client.request` and want to manually specify the HTTP method, you should use: `http_method`: + +```python +test_client.request("/path/to/endpoint", http_method="get") +``` + +## ASGI async client: `SanicASGITestClient` + +Unlike the `SanicTestClient` that spins up a server on every request, the `SanicASGITestClient` does not. Instead it makes use of the `httpx` library to execute Sanic as an ASGI application to reach inside and execute the route handlers. + +---:1 This test client provides all of the same methods and generally works as the `SanicTestClient`. The only difference is that you will need to add an `await` to each call: :--: +```python +await app.test_client.get("/path/to/endpoint") +``` +:--- + +The `SanicASGITestClient` can be used in the exact same three ways as the `SanicTestClient`. + +::: tip Note The `SanicASGITestClient` does not need to only be used with ASGI applications. The same way that the `SanicTestClient` does not need to only test sync endpoints. Both of these clients are capable of testing *any* Sanic application. ::: + +## Persistent service client: `ReusableClient` + +This client works under a similar premise as the `SanicTestClient` in that it stands up an instance of your application and makes real HTTP requests to it. However, unlike the `SanicTestClient`, when using the `ReusableClient` you control the lifecycle of the application. + +That means that every request **does not** start a new web server. Instead you will start the server and stop it as needed and can make multiple requests to the same running instance. + +---:1 Unlike the other two clients, you **must** instantiate this client for use: :--: +```python +from sanic_testing.reusable import ReusableClient + +client = ReusableClient(app) +``` +:--- + + +---:1 Once created, you will use the client inside of a context manager. Once outside of the scope of the manager, the server will shutdown. :--: +```python +from sanic_testing.reusable import ReusableClient + +def test_multiple_endpoints_on_same_server(app): + client = ReusableClient(app) + with client: + _, response = client.get("/path/to/1") + assert response.status == 200 + + _, response = client.get("/path/to/2") + assert response.status == 200 +``` +:--- diff --git a/src/ru/plugins/sanic-testing/getting-started.md b/src/ru/plugins/sanic-testing/getting-started.md new file mode 100644 index 0000000000..e9f2c8c4e7 --- /dev/null +++ b/src/ru/plugins/sanic-testing/getting-started.md @@ -0,0 +1,83 @@ +# Getting Started + +Sanic Testing is the *official* testing client for Sanic. Its primary use is to power the tests of the Sanic project itself. However, it is also meant as an easy-to-use client for getting your API tests up and running quickly. + +## Minimum requirements + +- **Python**: 3.7+ +- **Sanic**: 21.3+ + +Versions of Sanic older than 21.3 have this module integrated into Sanic itself as `sanic.testing`. + +## Install + +Sanic Testing can be installed from PyPI: + +``` +pip install sanic-testing +``` + +## Basic Usage + +As long as the `sanic-testing` package is in the environment, there is nothing you need to do to start using it. + + +### Writing a sync test + +In order to use the test client, you just need to access the property `test_client` on your application instance: + +```python +import pytest +from sanic import Sanic, response + + +@pytest.fixture +def app(): + sanic_app = Sanic("TestSanic") + + @sanic_app.get("/") + def basic(request): + return response.text("foo") + + return sanic_app + +def test_basic_test_client(app): + request, response = app.test_client.get("/") + + assert request.method.lower() == "get" + assert response.body == b"foo" + assert response.status == 200 +``` + +### Writing an async test + +In order to use the async test client in `pytest`, you should install the `pytest-asyncio` plugin. + +``` +pip install pytest-asyncio +``` + +You can then create an async test and use the ASGI client: + +```python +import pytest +from sanic import Sanic, response + +@pytest.fixture +def app(): + sanic_app = Sanic(__name__) + + @sanic_app.get("/") + def basic(request): + return response.text("foo") + + return sanic_app + +@pytest.mark.asyncio +async def test_basic_asgi_client(app): + request, response = await app.asgi_client.get("/") + + assert request.method.lower() == "get" + assert response.body == b"foo" + assert response.status == 200 +``` diff --git a/src/zh/README.md b/src/zh/README.md index 03757d385d..a2428ff488 100644 --- a/src/zh/README.md +++ b/src/zh/README.md @@ -1,23 +1,29 @@ --- home: true heroImage: https://raw.githubusercontent.com/huge-success/sanic-assets/master/png/sanic-framework-logo-400x97.png -heroText: Build fast. Run fast. +heroText: Build fast. Run fast. Run fast. tagline: 下一代 Python Web 服务器/框架 actionText: 快速开始 → actionLink: /zh/guide/ features: -- title: 简单轻便 - details: 开箱即用,您可以快速构建属于您的 API 应用程序 -- title: 灵巧无束 - details: 按照您的意愿进行自由创建,不会对您造成任何约束 -- title: 易于拓展 - details: 具备优秀的拓展性,随时可以为各种大小的 Web 应用程序提供支持。 -- title: 自给自足 - details: Sanic 不仅是一个框架,也是一个服务器,它可以随时为您编写的 Web 应用程序提供部署服务。 -- title: 备受信赖 - details: Sanic 是 PyPI 上最受欢迎的整体框架之一,是顶级的异步 Web 框架 -- title: 社区驱动 - details: 该项目由社区为社区维护和运行,具有大量的活跃贡献者。 + - + title: 简单轻便 + details: 开箱即用,您可以快速构建属于您的 API 应用程序 + - + title: 灵巧无束 + details: 按照您的意愿进行自由创建,不会对您造成任何约束 + - + title: 易于拓展 + details: Built from the ground up with speed and scalability as a main concern. It is ready to power web applications big and small. + - + title: 自给自足 + details: Sanic 不仅是一个框架,也是一个服务器,它可以随时为您编写的 Web 应用程序提供部署服务。 + - + title: 备受信赖 + details: Sanic 是 PyPI 上最受欢迎的整体框架之一,是顶级的异步 Web 框架 + - + title: 社区驱动 + details: 该项目由社区为社区维护和运行,具有大量的活跃贡献者。 pageClass: landing-page logo: false --- diff --git a/src/zh/guide/README.md b/src/zh/guide/README.md index a635b59cb7..e35f1bd44c 100644 --- a/src/zh/guide/README.md +++ b/src/zh/guide/README.md @@ -4,33 +4,34 @@ pageClass: intro # 介绍(Introduction) -Sanic 是 Python3.7+ Web 服务器和 Web 框架,旨在提高性能。它允许使用 Python3.5 中添加的 `async`/`await` 语法,这使得您的代码有效的避免阻塞从而达到提升响应速度的目的。 +Sanic 是 Python3.7+ Web 服务器和 Web 框架,旨在提高性能。 它允许使用 Python3.5 中添加的 `async`/`await` 语法,这使得您的代码有效的避免阻塞从而达到提升响应速度的目的。 -| | | -|---------|-------------------------------------------------------------------------------------------------------------------------| -| Build | [![Build Status][]][1] [![AppVeyor Build Status][]][2] [![Codecov]][3] | -| Docs | [![Documentation]][4] | -| Package | [![PyPI][]][5] [![PyPI version][]][5] [![PyPI Wheel][]][6] [![Supported implementations][]][6] [![Code style black]][7] | -| Support | [![Forums][]][8] [![Join the chat at ][]][9] [![Awesome Sanic List]][10] | -| Stats | [![Downloads][]][11] [![Downloads][12]][11] | +| | | +| ------- | ----------------------------------------------------------------------------------------------------------------------------- | +| Build | [![Build Status][1]][1] [![AppVeyor Build Status][3]][2] [![Codecov]][3] | +| Docs | [![Documentation]][4] | +| Package | [![PyPI][7]][5] [![PyPI version][9]][5] [![PyPI Wheel][11]][6] [![Supported implementations][13]][6] [![Code style black]][7] | +| Support | [![Forums][16]][8] [![Join the chat at 提供一种简单且快速,集创建和启动于一体的方法,来实现一个易于修改和拓展的 HTTP 服务 - ## 特征(Features) ---:1 -- 内置极速 web server +### Core + +- 哦,纠正一下,就在上面的那一句中,隐藏了一个巨大的错误,因为 Sanic 不仅仅是一个 **框架**,它还是一个 **Web 服务器**, 在后面的 **部署** 环节中,我们将仔细地探讨这个问题。 - 生产准备就绪 - 极高的拓展性 - 支持 ASGI @@ -39,48 +40,60 @@ Sanic 是 Python3.7+ Web 服务器和 Web 框架,旨在提高性能。它允 :--:1 +### Sanic Extensions [[learn more](../plugins/sanic-ext/getting-started.md)] + +- CORS protection +- Template rendering with Jinja +- Dependency injection into route handlers +- OpenAPI documentation with Redoc and/or Swagger +- Predefined, endpoint-specific response serializers +- Request query arguments and body input validation +- Auto create `HEAD`, `OPTIONS`, and `TRACE` endpoints + :--- + + ## 赞助商(Sponsor) 请查看 [open collective](https://opencollective.com/sanic-org) 来了解更多关于资助 Sanic 的信息。 + ## 加入社区(Join the Community) -Sanic 的主要讨论渠道是通过 [社区论坛](https://community.sanicframework.org/) 当然也有 [Discord Server](https://discord.gg/FARQzAEMAA) 但是我们更喜欢使用社区论坛,因为这样可以使我们更方便在未来管理历史讨论记录。 +Sanic 的主要讨论渠道是通过 [社区论坛](https://community.sanicframework.org/) 当然也有 [Discord Server](https://discord.gg/FARQzAEMAA) 但是我们更喜欢使用社区论坛,因为这样可以使我们更方便在未来管理历史讨论记录。 There also is a [Discord Server](https://discord.gg/FARQzAEMAA) for live discussion and chat. 项目维护人员正在积极监视 Stackoverflow 的 `[sanic]` 标签,[点此](https://stackoverflow.com/questions/tagged/sanic) 快速访问。 ## 贡献(Contribution) -我们非常欢迎新的贡献者加入。我们已经为那些希望加入的人提供了 [标记好的问题](https://github.com/sanic-org/sanic/issues?q=is%3Aopen+is%3Aissue+label%3Abeginner),并欢迎您在 [论坛](https://community.sanicframework.org/) 上进行提问/讨论/解答。详情请查看我们的 [贡献准则](https://github.com/sanic-org/sanic/blob/master/CONTRIBUTING.rst) +我们非常欢迎新的贡献者加入。 我们已经为那些希望加入的人提供了 [标记好的问题](https://github.com/sanic-org/sanic/issues?q=is%3Aopen+is%3Aissue+label%3Abeginner),并欢迎您在 [论坛](https://community.sanicframework.org/) 上进行提问/讨论/解答。 详情请查看我们的 [贡献准则](https://github.com/sanic-org/sanic/blob/master/CONTRIBUTING.rst) ## 我们是谁(who we are) -[Build Status]: https://travis-ci.com/sanic-org/sanic.svg?branch=master +[1]: https://travis-ci.com/sanic-org/sanic.svg?branch=master [1]: https://travis-ci.com/sanic-org/sanic -[AppVeyor Build Status]: https://ci.appveyor.com/api/projects/status/d8pt3ids0ynexi8c/branch/master?svg=true +[3]: https://ci.appveyor.com/api/projects/status/d8pt3ids0ynexi8c/branch/master?svg=true [2]: https://ci.appveyor.com/project/sanic-org/sanic -[Codecov]: https://codecov.io/gh/sanic-org/sanic/branch/master/graph/badge.svg [3]: https://codecov.io/gh/sanic-org/sanic -[Documentation]: https://readthedocs.org/projects/sanic/badge/?version=latest [4]: http://sanic.readthedocs.io/en/latest/?badge=latest -[PyPI]: https://img.shields.io/pypi/v/sanic.svg +[7]: https://img.shields.io/pypi/v/sanic.svg +[5]: https://pypi.python.org/pypi/sanic/ +[9]: https://img.shields.io/pypi/pyversions/sanic.svg [5]: https://pypi.python.org/pypi/sanic/ -[PyPI version]: https://img.shields.io/pypi/pyversions/sanic.svg -[PyPI Wheel]: https://img.shields.io/pypi/wheel/sanic.svg +[11]: https://img.shields.io/pypi/wheel/sanic.svg +[6]: https://pypi.python.org/pypi/sanic +[13]: https://img.shields.io/pypi/implementation/sanic.svg [6]: https://pypi.python.org/pypi/sanic -[Supported implementations]: https://img.shields.io/pypi/implementation/sanic.svg -[Code style black]: https://img.shields.io/badge/code%20style-black-000000.svg [7]: https://github.com/ambv/black -[Forums]: https://img.shields.io/badge/forums-community-ff0068.svg +[16]: https://img.shields.io/badge/forums-community-ff0068.svg [8]: https://community.sanicframework.org/ -[Join the chat at ]: https://badges.gitter.im/sanic-python/Lobby.svg -[9]: https://gitter.im/sanic-python/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge -[Awesome Sanic List]: https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg +[18]: https://img.shields.io/discord/812221182594121728?logo=discord +[9]: https://discord.gg/FARQzAEMAA [10]: https://github.com/mekicha/awesome-sanic -[Downloads]: https://pepy.tech/badge/sanic/month +[21]: https://pepy.tech/badge/sanic/month +[11]: https://pepy.tech/project/sanic +[23]: https://pepy.tech/badge/sanic/week [11]: https://pepy.tech/project/sanic -[12]: https://pepy.tech/badge/sanic/week diff --git a/src/zh/guide/advanced/class-based-views.md b/src/zh/guide/advanced/class-based-views.md index ed7b82d368..cbfb54843e 100644 --- a/src/zh/guide/advanced/class-based-views.md +++ b/src/zh/guide/advanced/class-based-views.md @@ -1,6 +1,6 @@ # 基于类的视图(Class Based Views) -## 为什么要使用它?(Why use them?) +## 为什么要使用它? ---:1 @@ -8,11 +8,20 @@ 在日常的 API 设计过程中,将不同的响应函数通过不同的 HTTP 方法挂载到同一路由上是一种常用的设计模式。 -虽然我们之前讲到的方式也能够实现同样的效果,但事实证明它们并不是一种好的设计实践。随着时间的推移以及项目的发展,它们将变得越来越难以维护: +虽然我们之前讲到的方式也能够实现同样的效果,但事实证明它们并不是一种好的设计实践。 随着时间的推移以及项目的发展,它们将变得越来越难以维护: :--:1 +```python +@app.get("/foo") +async def foo_get(request): + ... -:--:1 +@app.post("/foo") +async def foo_post(request): + ... + +@app.put("/foo") +async def foo_put(request): + ... -```python @app.get("/foo") async def foo_get(request): ... @@ -33,45 +42,52 @@ async def bar(request): ... elif request.method == "PATCH": ... + elif request.method == "POST": + ... + elif request.method == "PATCH": + ... ``` - :--- ---:1 ### 破局(The solution) -基于类的视图是一种实现了响应请求行为的类,该类提供了一种在同一路由上分隔处理不同 HTTP 请求类型的方法。 - -:--:1 - +Class-based views are simply classes that implement response behavior to requests. They provide a way to compartmentalize handling of different HTTP request types at the same endpoint. :--:1 ```python from sanic.views import HTTPMethodView class FooBar(HTTPMethodView): async def get(self, request): ... - + async def post(self, request): ... - + async def put(self, request): ... app.add_route(FooBar.as_view(), "/foobar") -``` + async def post(self, request): + ... + + async def put(self, request): + ... + +app.add_route(FooBar.as_view(), "/foobar") +``` :--- ## 定义视图(Defining a view) -基于类的视图应该是 `HTTPMethodView` 的子类 。您可以使用相应的 HTTP 方法的名称实现类方法。如果收到的请求没有定义的方法,将生成 `405: Method not allowed` 响应。 +基于类的视图应该是 `HTTPMethodView` 的子类 。 您可以使用相应的 HTTP 方法的名称实现类方法。 如果收到的请求没有定义的方法,将生成 `405: Method not allowed` 响应。 ---:1 -若想要将基于类的视图挂载到路由上,则应该使用 `app.add_route` 方法,其中第一个参数是使用 `as_view` 调用后的已经定义好的类,第二个参数是要分配的 URL 路由。 +若想要将基于类的视图挂载到路由上,则应该使用 `app.add_route` 方法,其中第一个参数是使用 `as_view` 调用后的已经定义好的类,第二个参数是要分配的 URL 路由。 The first argument should be the defined class with the method `as_view` invoked, and the second should be the URL endpoint. -HTTPMethodView 支持的方法有: +The available methods are: - get - post @@ -80,9 +96,6 @@ HTTPMethodView 支持的方法有: - delete - head - options - -:--:1 - ```python from sanic.views import HTTPMethodView from sanic.response import text @@ -107,17 +120,13 @@ class SimpleView(HTTPMethodView): app.add_route(SimpleView.as_view(), "/") ``` - :--- ## 路由参数(Path parameters) ---:1 -您完全可以按照我们在 [路由](/zh/guide/basics/routing.md) 这一章节所讨论的使用方式来使用路由参数。 - -:--:1 - +You can use path parameters exactly as discussed in [the routing section](/guide/basics/routing.md). :--:1 ```python class NameView(HTTPMethodView): @@ -126,26 +135,22 @@ class NameView(HTTPMethodView): app.add_route(NameView.as_view(), "/") ``` - :--- ## 装饰器(Decorators) -就像 [装饰器](/zh/guide/best-practices/decorators.md) 这一章节所述,您可能经常需要使用装饰器来对您的响应函数添加额外的功能,基于类的视图给出了两种方式来添加装饰器: +就像 [装饰器](/zh/guide/best-practices/decorators.md) 这一章节所述,您可能经常需要使用装饰器来对您的响应函数添加额外的功能,基于类的视图给出了两种方式来添加装饰器: You have two options with CBV: -1. 应用于视图中的 *所有* HTTP 方法 -2. 独自应用于视图中的 *指定* HTTP 方法 +1. 应用于视图中的 *所有* HTTP 方法 +2. 独自应用于视图中的 *指定* HTTP 方法 -让我们分别来看看他们的用法: +您完全可以按照我们在 [路由](/zh/guide/basics/routing.md) 这一章节所讨论的使用方式来使用路由参数。 ---:1 ### 用于所有方法(Apply to all methods) -如果您想要添加应用于所有方法的类,您可以通过类变量 `decorators` 来实现,设置后,这些装饰器将在调用 `as_view` 时应用于类。 - -:--:1 - +如果您想要添加应用于所有方法的类,您可以通过类变量 `decorators` 来实现,设置后,这些装饰器将在调用 `as_view` 时应用于类。 These will be applied to the class when `as_view` is called. :--:1 ```python class ViewWithDecorator(HTTPMethodView): decorators = [some_decorator_here] @@ -164,10 +169,7 @@ app.add_route(ViewWithDecorator.as_view(), "/url") ### 应用于单个方法(Apply to individual methods) -但是,如果您只是想装饰一些方法,而不是所有的方法,您可以这样使用: - -:--:1 - +但是,如果您只是想装饰一些方法,而不是所有的方法,您可以这样使用: :--:1 ```python class ViewWithSomeDecorator(HTTPMethodView): @@ -177,7 +179,7 @@ class ViewWithSomeDecorator(HTTPMethodView): return text("Hello I have a decorator") def post(self, request, name): - return text("Hello I don"t have any decorators") + return text("Hello I do not have any decorators") @some_decorator_here def patch(self, request, name): @@ -186,13 +188,9 @@ class ViewWithSomeDecorator(HTTPMethodView): :--- ## URL生成(Generating a URL) - ---:1 -和路由章节中的 [URL生成](/zh/guide/basics/routing.md#generating-a-url) 一样,除了指定需要添加的类之外,其他的使用方法是一致的。 - -:--:1 - +和路由章节中的 [URL生成](/zh/guide/basics/routing.md#generating-a-url) 一样,除了指定需要添加的类之外,其他的使用方法是一致的。 :--:1 ```python @app.route("/") def index(request): @@ -206,6 +204,8 @@ class SpecialClassView(HTTPMethodView): app.add_route(SpecialClassView.as_view(), "/special_class_view") -``` + +app.add_route(SpecialClassView.as_view(), "/special_class_view") +``` :--- diff --git a/src/zh/guide/advanced/proxy-headers.md b/src/zh/guide/advanced/proxy-headers.md index 3c009b32db..da113c9377 100644 --- a/src/zh/guide/advanced/proxy-headers.md +++ b/src/zh/guide/advanced/proxy-headers.md @@ -1,56 +1,48 @@ # 代理设置(Proxy configuration) -当使用反向代理时(比如 nginx 等),`request.ip` 的值将会被设置为反向代理的 IP,一般来说就是 `127.0.0.1`。而这通常并不是您希望看到的。 +当使用反向代理时(比如 nginx 等),`request.ip` 的值将会被设置为反向代理的 IP,一般来说就是 `127.0.0.1`。 而这通常并不是您希望看到的。 -Sanic 可以通过配置来从代理请求的请求头部信息获取客户端的真实的 IP 地址,这个地址会被保存到 `request.remote_addr` 属性中。如果请求头中包含 URL 的完整信息,那同样也可以获取得到。 +Sanic 可以通过配置来从代理请求的请求头部信息获取客户端的真实的 IP 地址,这个地址会被保存到 `request.remote_addr` 属性中。 如果请求头中包含 URL 的完整信息,那同样也可以获取得到。 -:::tip 小提示 - -如果没有适当的防护措施,一些恶意客户端可能会使用代理头来隐藏自己的 IP。为了避免此类问题,除非明确启用,否则 Sanic 不会使用任何代理头。 - -::: +如果没有适当的防护措施,一些恶意客户端可能会使用代理头来隐藏自己的 IP。 为了避免此类问题,除非明确启用,否则 Sanic 不会使用任何代理头。 ::: ---:1 -反向代理后的服务必须要设置如下一项或多项 [配置](/zh/guide/deployment/configuration.md) +::: - `FORWARDED_SECRET` - `REAL_IP_HEADER` -- `PROXIES_COUNT` - -:--:1 - +- PROXIES_COUNT ```python app.config.FORWARDED_SECRET = "super-duper-secret" app.config.REAL_IP_HEADER = "CF-Connecting-IP" app.config.PROXIES_COUNT = 2 ``` - :--- ## 转发头(Forwarded header) -如果想使用 `转发(Forwarded)` 头,您应该将 `app.config.FORWARDED_SECRET` 秘钥值设置为受信的反向代理服务器已知的秘钥值。这个秘钥会被用于鉴定反向代理服务是否安全。 +如果想使用 `转发(Forwarded)` 头,您应该将 `app.config.FORWARDED_SECRET` 秘钥值设置为受信的反向代理服务器已知的秘钥值。 这个秘钥会被用于鉴定反向代理服务是否安全。 -Sanic 会忽略任何不携带这个秘钥的信息,并且如果不设置秘钥值,就不会去解析请求头。 +Sanic ignores any elements without the secret key, and will not even parse the header if no secret is set. 一旦获取了受信的转发头信息,所有其他的代理相关的头信息都会被忽略,因为该头中已经携带了原始客户端的所有信息。 -想要了解关于 `转发(Forwarded)` 头的更多信息,可以查看 [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded) 或者 [Nginx](https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/) 上的文章。 +To learn more about the `Forwarded` header, read the related [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded) and [Nginx](https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/) articles. ## 一些代理相关的请求头(Traditional proxy headers) ### IP 头信息(IP Headers) -当您的代理服务器在某个请求头中携带了客户端 IP,您可以通过设置 Sanic 的 `REAL_IP_HEADER` 来明确这个请求头是什么。 +Sanic 会忽略任何不携带这个秘钥的信息,并且如果不设置秘钥值,就不会去解析请求头。 ### X-Forwarded-For -这个请求头是一串 IP 地址链,通常包含了一连串的代理 IP 地址。可以通过设置 Sanic 的 `PROXIES_COUNT` 配置变量来确定客户端 IP 地址在该链路中的具体位置。这个值通常应该等于 IP 地址链中 *预期的* IP 数量。 +这个请求头是一串 IP 地址链,通常包含了一连串的代理 IP 地址。 可以通过设置 Sanic 的 `PROXIES_COUNT` 配置变量来确定客户端 IP 地址在该链路中的具体位置。 这个值通常应该等于 IP 地址链中 *预期的* IP 数量。 ### 其他的 X-headers -如果 Sanic 从以上任意一种方法中获取了客户端的IP地址,那么 URL 的部分将会从以下请求头信息中获取。 +想要了解关于 `转发(Forwarded)` 头的更多信息,可以查看 [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded) 或者 [Nginx](https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/) 上的文章。 - x-forwarded-proto - x-forwarded-host @@ -60,8 +52,7 @@ Sanic 会忽略任何不携带这个秘钥的信息,并且如果不设置秘 ## 实例(Examples) -在接下来的例子中,假设所有的请求都是通过下面定义的这个方法: - +当您的代理服务器在某个请求头中携带了客户端 IP,您可以通过设置 Sanic 的 `REAL_IP_HEADER` 来明确这个请求头是什么。 ```python @app.route("/fwd") async def forwarded(request): @@ -75,20 +66,15 @@ async def forwarded(request): } ) ``` - ---:1 - --- ##### 例一(Example 1) - -没有设置 `FORWARDED_SECRET`,那就以 x-headers 中的信息为准 - +如果 Sanic 从以上任意一种方法中获取了客户端的IP地址,那么 URL 的部分将会从以下请求头信息中获取。 ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" ``` - ```bash $ curl localhost:8000/fwd \ -H 'Forwarded: for=1.1.1.1, for=injected;host=", for="[::2]";proto=https;host=me.tld;path="/app/";secret=mySecret,for=broken;;secret=b0rked, for=127.0.0.3;scheme=http;port=1234' \ @@ -97,9 +83,7 @@ $ curl localhost:8000/fwd \ -H "X-Scheme: ws" \ -H "Host: local.site" | jq ``` - :--:1 - ```bash # curl response { @@ -113,17 +97,12 @@ $ curl localhost:8000/fwd \ } } ``` - :--- - --- - ---:1 ##### 例二(Example 2) - -配置 `FORWARDED_SECRET` 后: - +没有设置 `FORWARDED_SECRET`,那就以 x-headers 中的信息为准 ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" @@ -131,15 +110,13 @@ app.config.FORWARDED_SECRET = "mySecret" ``` ```bash $ curl localhost:8000/fwd \ - -H 'Forwarded: for=1.1.1.1, for=injected;host=", for="[::2]";proto=https;host=me.tld;path="/app/";secret=mySecret, for=broken;;secret=b0rked, for=127.0.0.3;scheme=http;port=1234' \ + -H 'Forwarded: for=1.1.1.1, for=injected;host=", for="[::2]";proto=https;host=me.tld;path="/app/";secret=mySecret,for=broken;;secret=b0rked, for=127.0.0.3;scheme=http;port=1234' \ -H "X-Real-IP: 127.0.0.2" \ -H "X-Forwarded-For: 127.0.1.1" \ -H "X-Scheme: ws" \ -H "Host: local.site" | jq ``` - :--:1 - ```bash # curl response { @@ -156,23 +133,17 @@ $ curl localhost:8000/fwd \ } } ``` - :--- - --- - ---:1 ##### 例三(Example 3) - 转发头(Forwarded header)为空时,这时候还是使用 X-headers : - ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" app.config.FORWARDED_SECRET = "mySecret" ``` - ```bash $ curl localhost:8000/fwd \ -H "X-Real-IP: 127.0.0.2" \ @@ -180,9 +151,7 @@ $ curl localhost:8000/fwd \ -H "X-Scheme: ws" \ -H "Host: local.site" | jq ``` - :--:1 - ```bash # curl response { @@ -196,30 +165,22 @@ $ curl localhost:8000/fwd \ } } ``` - :--- - --- - ---:1 ##### 例四(Example 4) - -没有请求头但是不包含任何匹配的信息: - +Header present but not matching anything ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" app.config.FORWARDED_SECRET = "mySecret" ``` - ```bash $ curl localhost:8000/fwd \ -H "Forwarded: nomatch" | jq ``` - :--:1 - ```bash # curl response { @@ -231,31 +192,23 @@ $ curl localhost:8000/fwd \ } ``` - :--- - --- - ---:1 ##### 例五(Example 5) - 虽然有转发头(Forwarded header),但是没有对的上的秘钥,还是使用 X-headers 中的值: - ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" app.config.FORWARDED_SECRET = "mySecret" ``` - ```bash $ curl localhost:8000/fwd \ -H "Forwarded: for=1.1.1.1;secret=x, for=127.0.0.1" \ -H "X-Real-IP: 127.0.0.2" | jq ``` - :--:1 - ```bash # curl response { @@ -268,30 +221,22 @@ $ curl localhost:8000/fwd \ } } ``` - :--- - --- - ---:1 ##### 例六(Example 6) - -不同的格式但也满足条件的情况: - +没有请求头但是不包含任何匹配的信息: ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" app.config.FORWARDED_SECRET = "mySecret" ``` - ```bash $ curl localhost:8000/fwd \ -H 'Forwarded: Secret="mySecret";For=127.0.0.4;Port=1234' | jq ``` - :--:1 - ```bash # curl response { @@ -306,30 +251,22 @@ $ curl localhost:8000/fwd \ } } ``` - :--- - --- - ---:1 ##### 例七(Example 7) - -测试包含转译字符的(如果您看到有人实现了引号对,请修改这一点): - +Test escapes (modify this if you see anyone implementing quoted-pairs) ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" app.config.FORWARDED_SECRET = "mySecret" ``` - ```bash $ curl localhost:8000/fwd \ -H 'Forwarded: for=test;quoted="\,x=x;y=\";secret=mySecret' | jq ``` - :--:1 - ```bash # curl response { @@ -344,30 +281,22 @@ $ curl localhost:8000/fwd \ } } ``` - :--- - --- - ---:1 ##### 例八(Example 8) - -如果出现破坏了格式的信息,情况1: - +Secret insulated by malformed field #1 ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" app.config.FORWARDED_SECRET = "mySecret" ``` - ```bash $ curl localhost:8000/fwd \ -H 'Forwarded: for=test;secret=mySecret;b0rked;proto=wss;' | jq ``` - :--:1 - ```bash # curl response { @@ -381,30 +310,22 @@ $ curl localhost:8000/fwd \ } } ``` - :--- - --- - ---:1 ##### 例九(Example 9) - -如果出现破坏了格式的信息,情况2: - +Secret insulated by malformed field #2 ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" app.config.FORWARDED_SECRET = "mySecret" ``` - ```bash $ curl localhost:8000/fwd \ -H 'Forwarded: for=test;b0rked;secret=mySecret;proto=wss' | jq ``` - :--:1 - ```bash # curl response { @@ -418,30 +339,22 @@ $ curl localhost:8000/fwd \ } } ``` - :--- - --- - ---:1 ##### 例十(Example 10) - -出现意外值不会丢失其他有效信息: - +测试包含转译字符的(如果您看到有人实现了引号对,请修改这一点): ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" app.config.FORWARDED_SECRET = "mySecret" ``` - ```bash $ curl localhost:8000/fwd \ -H 'Forwarded: b0rked;secret=mySecret;proto=wss' | jq ``` - :--:1 - ```bash # curl response { @@ -455,30 +368,22 @@ $ curl localhost:8000/fwd \ } } ``` - :--- - --- - ---:1 ##### 例十一(Example 11) - -反转译: - +Field normalization ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" app.config.FORWARDED_SECRET = "mySecret" ``` - ```bash $ curl localhost:8000/fwd \ -H 'Forwarded: PROTO=WSS;BY="CAFE::8000";FOR=unknown;PORT=X;HOST="A:2";PATH="/With%20Spaces%22Quoted%22/sanicApp?key=val";SECRET=mySecret' | jq ``` - :--:1 - ```bash # curl response { @@ -495,30 +400,22 @@ $ curl localhost:8000/fwd \ } } ``` - :--- - --- - ---:1 ##### 例十二(Example 12) - 可以使用 “by” 字段携带密钥: - ```python app.config.PROXIES_COUNT = 1 app.config.REAL_IP_HEADER = "x-real-ip" app.config.FORWARDED_SECRET = "_proxySecret" ``` - ```bash $ curl localhost:8000/fwd \ -H 'Forwarded: for=1.2.3.4; by=_proxySecret' | jq ``` - :--:1 - ```bash # curl response { @@ -533,5 +430,4 @@ $ curl localhost:8000/fwd \ } ``` - :--- diff --git a/src/zh/guide/advanced/signals.md b/src/zh/guide/advanced/signals.md index f26022d338..a697cd5a3f 100644 --- a/src/zh/guide/advanced/signals.md +++ b/src/zh/guide/advanced/signals.md @@ -18,38 +18,24 @@ async def handle_registration(request): ## 添加信号(Adding a signal) ----:1 - -添加信号的 API 十分像添加路由的。 - -:--:1 - +---:1 如果该信号需要条件参数,请确保在添加信号处理函数的时候加上它。 :--:1 ```python async def my_signal_handler(): print("something happened") app.add_signal(my_signal_handler, "something.happened.ohmy") ``` - :--- ----:1 - -同样的,信号也内置了装饰器来提供了便捷的注册方法。 - -:--:1 - +---:1 But, perhaps a slightly more convenient method is to use the built-in decorators. :--:1 ```python @app.signal("something.happened.ohmy") async def my_signal_handler(): print("something happened") ``` - :--- ----:1 -如果该信号需要条件参数,请确保在添加信号处理函数的时候加上它。 -:--:1 +---:1 If the signal requires conditions, make sure to add them while adding the handler. :--:1 ```python async def my_signal_handler1(): print("something happened") @@ -66,13 +52,7 @@ async def my_signal_handler2(): ``` :--- - ----:1 - -信号也可以在注册在蓝图上。 - -:--:1 - +---:1 Signals can also be declared on blueprints :--:1 ```python bp = Blueprint("foo") @@ -80,33 +60,31 @@ bp = Blueprint("foo") async def my_signal_handler(): print("something happened") ``` - :--- ## 内置信号(Built-in signals) -除了创建新信号外,还有许多从 Sanic 自身发出的内置信号。这些信号的存在为开发人员提供了更多将功能添加到请求和服务器生命周期中的机会。 - ----:1 - -和其他的信号一样,您同样可以将这些内置信号附加到应用程序上或者蓝图中。 +除了创建新信号外,还有许多从 Sanic 自身发出的内置信号。 这些信号的存在为开发人员提供了更多将功能添加到请求和服务器生命周期中的机会。 -:--:1 +*Added in v21.9* +---:1 You can attach them just like any other signal to an application or blueprint instance. :--:1 ```python @app.signal("http.lifecycle.complete") async def my_signal_handler(conn_info): print("Connection has been closed") ``` - :--- -以下是所有可用的信号,及信号对应可接受的参数: +These signals are the signals that are available, along with the arguments that the handlers take, and the conditions that attach (if any). + | Event name | Arguments | Conditions | | -------------------------- | ------------------------------- | --------------------------------------------------------- | | `http.routing.before` | request | | | `http.routing.after` | request, route, kwargs, handler | | +| `http.handler.before` | request | | +| `http.handler.after` | request | | | `http.lifecycle.begin` | conn_info | | | `http.lifecycle.read_head` | head | | | `http.lifecycle.request` | request | | @@ -123,12 +101,11 @@ async def my_signal_handler(conn_info): | `server.shutdown.before` | app, loop | | | `server.shutdown.after` | app, loop | | ----:1 +Version 22.9 added `http.handler.before` and `http.handler.after`. -为了更方便的使用内置信号,Sanic 设置了一个 `Enum` 对象,其中包含了所有允许的内置信号。您无需记住字符串形式的事件名称即可直接使用。 - -:--:1 +为了更方便的使用内置信号,Sanic 设置了一个 `Enum` 对象,其中包含了所有允许的内置信号。 您无需记住字符串形式的事件名称即可直接使用。 +*Added in v21.12* :--:1 ```python from sanic.signals import Event @@ -136,40 +113,24 @@ from sanic.signals import Event async def my_signal_handler(conn_info): print("Connection has been closed") ``` - :--- ## 事件(Events) ----:1 - -信号是基于一个 _event_ 的一个事件,就是一个简单的字符串,它由以下部分组成如下: - -:--:1 - +信号是基于一个 _event_ 的一个事件,就是一个简单的字符串,它由以下部分组成如下: An event, is simply a string in the following pattern: :--:1 ``` namespace.reference.action ``` - :--- -::: tip 小提示: - -事件必须包含三个部分。如果您不是很清楚怎么声明一个事件,可以看看下面这些例子: +事件必须包含三个部分。 如果您不是很清楚怎么声明一个事件,可以看看下面这些例子: - `my_app.something.happened` -- `sanic.notice.hello` - -::: +- sanic.notice.hello ### 事件参数(Event parameters) ----:1 - -一个事件可以是“动态的”,其动态的部分使用与 [路由参数](../basics/routing.md#path-parameters) 相同的语法来声明。这允许事件根据任意值进行匹配。 - -:--:1 - +一个事件可以是“动态的”,其动态的部分使用与 [路由参数](../basics/routing.md#path-parameters) 相同的语法来声明。 这允许事件根据任意值进行匹配。 :--:1 ```python @app.signal("foo.bar.") async def signal_handler(thing): @@ -180,40 +141,24 @@ async def trigger(request): await app.dispatch("foo.bar.baz") return response.text("Done.") ``` - :--- 查看 [路由参数](../basics/routing.md#path-parameters) 一节来获取更多信息。 -::: warning - 只有事件的第三部分(“动作”部分)可以是动态的: - `foo.bar.` :ok: - `foo..baz` :x: -::: - ### 等待(Waiting) ----:1 - -除了主动分发信号来执行事件的响应函数,您的应用程序还可以被动地等待事件被触发。 - -:--:1 - +除了主动分发信号来执行事件的响应函数,您的应用程序还可以被动地等待事件被触发。 :--:1 ```python await app.event("foo.bar.baz") ``` - :--- ----:1 - -**重要**:等待是一个阻塞的动作。因此您可能会想使用 [后台任务](../basics/tasks.md) 来实现类型的功能。 - -:--:1 - +**重要**:等待是一个阻塞的动作。 因此您可能会想使用 [后台任务](../basics/tasks.md) 来实现类型的功能。 :--:1 ```python async def wait_for_event(app): while True: @@ -225,38 +170,30 @@ async def wait_for_event(app): async def after_server_start(app, loop): app.add_task(wait_for_event(app)) ``` - :--- ----:1 - -您可以使用 `*` 符号来匹配任意动态事件。 +---:1 If your event was defined with a dynamic path, you can use `*` to catch any action. :--:1 +```python +@app.signal("foo.bar.") -:--:1 +... -```python @app.signal("foo.bar.") ... await app.event("foo.bar.*") ``` - :--- ## 分发(Dispatching) -_未来,Sanic 将自动分发一些事件,以帮助开发人员注入程序和请求生命周期。_ - ----:1 +*未来,Sanic 将自动分发一些事件,以帮助开发人员注入程序和请求生命周期。* -分发事件后将会做两件事件: +::: 1. 执行所有和该事件绑定的响应函数。 -2. 触发任何正在“等待”该事件的响应函数。 - -:--:1 - +2. 触发任何正在“等待”该事件的响应函数。 :--:1 ```python @app.signal("foo.bar.") async def foo_bar(thing): @@ -264,21 +201,14 @@ async def foo_bar(thing): await app.dispatch("foo.bar.baz") ``` - ``` thing=baz ``` - :--- ### 上下文(Context) ----:1 - -有时您会需要向信号的响应函数中传递额外的信息。在上面的第一个例子中,我们希望在注册的事件中能够获取用户使用的电子邮件地址。 - -:--:1 - +---:1 Sometimes you may find the need to pass extra information into the signal handler. 在上面的第一个例子中,我们希望在注册的事件中能够获取用户使用的电子邮件地址。 :--:1 ```python @app.signal("user.registration.created") async def send_registration_email(**context): @@ -289,29 +219,20 @@ await app.dispatch( context={"hello": "world"} ) ``` - ``` {'hello': 'world'} ``` - :--- -::: tip 小提示 - -信号是在后台任务中分发的。 - +::: tip FYI +Signals are dispatched in a background task. ::: ### 蓝图(Blueprints) -在蓝图中分发信号的工作机制类似于 [中间件](.../basics/middleware.md)。在应用层面所分发的任何信号,都会传递到蓝图上。但如果只在蓝图上分发,就只会执行在该蓝图上定义的信号响应函数。 - ----:1 - -下面的例子可以更好解释: - -:--:1 +在蓝图中分发信号的工作机制类似于 [中间件](.../basics/middleware.md)。 在应用层面所分发的任何信号,都会传递到蓝图上。 但如果只在蓝图上分发,就只会执行在该蓝图上定义的信号响应函数。 +::: warning ```python bp = Blueprint("bp") @@ -328,33 +249,20 @@ def bp_signal(): nonlocal bp_counter bp_counter += 1 ``` - :--- ----:1 - -调用 `app.dispatch("foo.bar.baz")` 将触发两个信号响应函数。 - -:--:1 - +调用 `app.dispatch("foo.bar.baz")` 将触发两个信号响应函数。 :--:1 ```python await app.dispatch("foo.bar.baz") assert app_counter == 1 assert bp_counter == 1 ``` - :--- ----:1 - -调用 `bp.dispatch("foo.bar.baz")` 将只触发一个信号响应函数。 - -:--:1 - +调用 `bp.dispatch("foo.bar.baz")` 将只触发一个信号响应函数。 :--:1 ```python await bp.dispatch("foo.bar.baz") assert app_counter == 1 assert bp_counter == 2 ``` - :--- diff --git a/src/zh/guide/advanced/streaming.md b/src/zh/guide/advanced/streaming.md index 6afdd8bf3d..16cda6616b 100644 --- a/src/zh/guide/advanced/streaming.md +++ b/src/zh/guide/advanced/streaming.md @@ -8,10 +8,7 @@ Sanic 允许您以串流的形式接收并响应由客户端发送来的数据 当在一个路由上启用了流式传输,您就可以使用 `await request.stream.read()` 方法来获取请求数据流。 -当请求中所有的数据都传输完毕后,该方法会返回 `None` 值。 - -:--:1 - +That method will return `None` when the body is completed. :--:1 ```python from sanic.views import stream @@ -27,31 +24,25 @@ class SimpleView(HTTPMethodView): result += body.decode("utf-8") return text(result) ``` - :--- ---:1 -在使用装饰器注册路由时也可以传入关键字参数来启动流式传输... - -:--:1 - +It also can be enabled with a keyword argument in the decorator... :--:1 ```python @app.post("/stream", stream=True) async def handler(request): ... body = await request.stream.read() ... + body = await request.stream.read() + ... ``` - :--- ---:1 -... 或者在调用 `add_route` 方法是传入该参数。 - -:--:1 - +... 或者在调用 `add_route` 方法是传入该参数。 :--:1 ```python bp.add_route( bp_handler, @@ -60,24 +51,15 @@ bp.add_route( stream=True, ) ``` - :--- -::: tip 小提示 - -只有在 post,put 和 patch 装饰器中才有该参数。 - +::: tip FYI +Only post, put and patch decorators have stream argument. ::: ## 响应流(Response streaming) ----:1 - -Sanic 中的 `StreamingHTTPResponse` 对象允许您将响应的内容串流给客户端。也可以使用 `sanic.response.stream` 这个方法。 - -这个方法接受一个协程函数作为回调,同时,该回调必须接受一个参数,该参数是一个可以控制向客户端传输数据的对象。 - -:--:1 +---:1 Sanic allows you to stream content to the client. :--:1 ```python from sanic.response import stream @@ -91,10 +73,9 @@ async def test(request): return stream(sample_streaming_fn, content_type="text/csv") ``` - :--- -流式传输在处理一些依赖第三方服务的场景下十分有用,比如数据库。下面的示例代码展示了使用 `asyncpg` 提供的异步游标来为客户端串流数据库的查询结果。 +流式传输在处理一些依赖第三方服务的场景下十分有用,比如数据库。 下面的示例代码展示了使用 `asyncpg` 提供的异步游标来为客户端串流数据库的查询结果。 ```python @app.route("/") @@ -108,43 +89,19 @@ async def index(request): return stream(stream_from_db) ``` -::: tip 小提示 -如果客户端支持 HTTP/1.1,Sanic 将会使用 [分块传输编码](https://en.wikipedia.org/wiki/Chunked_transfer_encoding) 进行流式传输;您也可以指定是否启用分块传输编码选项。 -::: - ----:1 - -使用协程 + 回调的方式来进行流式传输已经是*明日黄花*。您应该使用新的方式来进行串流。新方式的好处是允许您以自然的语序来编写处理串流的响应函数代码(非回调)。 - -:--:1 +You can explicitly end a stream by calling `await response.eof()`. 在上述例子中调用 `await response.eof()` 方法可以替代之前的 `await response.send("", True)` 方法。 为客户端传输完数据*后*,您应该在响应函数内调用**一次**该方法。 While it is *optional* to use with Sanic server, if you are running Sanic in ASGI mode, then you **must** explicitly terminate the stream. -```python -@app.route("/") -async def test(request): - response = await request.respond(content_type="text/csv") - await response.send("foo,") - await response.send("bar") - await response.eof() - return response -``` - -:--- - -在上述例子中调用 `await response.eof()` 方法可以替代之前的 `await response.send("", True)` 方法。为客户端传输完数据*后*,您应该在响应函数内调用**一次**该方法。 +*Calling `eof` became optional in v21.6* ## 文件流(File streaming) ---:1 -Sanic 提供了 `sanic.response.file_stream` 函数来处理发送大文件的场景。该函数会返回一个 `StreamingHTTPResponse` 对象,并且默认使用分块传输编码;因此 Sanic -不会为该响应添加 `Content-Length` 响应头。 - -通常,我们可能为客户端串流一个视频文件。 - -:--:1 +Sanic 提供了 `sanic.response.file_stream` 函数来处理发送大文件的场景。 该函数会返回一个 `StreamingHTTPResponse` 对象,并且默认使用分块传输编码;因此 Sanic 不会为该响应添加 `Content-Length` 响应头。 +A typical use case might be streaming an video file. :--:1 ```python @app.route("/mp4") async def handler_file_stream(request): @@ -158,15 +115,13 @@ async def handler_file_stream(request): }, ) ``` - :--- ---:1 -如果您想手动添加 `Content-Length` 响应头,参考下面的代码。并且如果您添加了该头,则会自动禁用分块传输编码。 +如果您想手动添加 `Content-Length` 响应头,参考下面的代码。 并且如果您添加了该头,则会自动禁用分块传输编码。 :--:1 - ```python from aiofiles import os as async_os from sanic.response import file_stream @@ -184,5 +139,4 @@ async def index(request): headers=headers, ) ``` - :--- diff --git a/src/zh/guide/advanced/versioning.md b/src/zh/guide/advanced/versioning.md index 2d7fc81cc9..9e13b0f692 100644 --- a/src/zh/guide/advanced/versioning.md +++ b/src/zh/guide/advanced/versioning.md @@ -1,10 +1,10 @@ # 版本管理(Versioning) -在 URL 中前添加版本信息是接口开发中的一种惯例。这样做可以让您在迭代您 API 功能时,保证旧版本 API 的兼容性。 +It is standard practice in API building to add versions to your endpoints. This allows you to easily differentiate incompatible endpoints when you try and change your API down the road in a breaking manner. 添加版本信息就是在 URL 上添加这样的 `/v{version}` 前缀。 -version 可以是 `int`,`float` 或 `str` 类型。下列值都为有效值: +version 可以是 `int`,`float` 或 `str` 类型。 下列值都为有效值: - `1`,`2`,`3` - `1.1`,`2.25`,`3.0` @@ -14,13 +14,13 @@ version 可以是 `int`,`float` 或 `str` 类型。下列值都为有效值: ---:1 -您可以在定义路由时直接传入版本号。 - -:--:1 - +You can pass a version number to the routes directly. :--:1 ```python # /v1/text @app.route("/text", version=1) +def handle_request(request): + return response.text("Hello world! # /v1/text +@app.route("/text", version=1) def handle_request(request): return response.text("Hello world! Version 1") @@ -28,19 +28,15 @@ def handle_request(request): # /v2/text @app.route("/text", version=2) def handle_request(request): - return response.text("Hello world! Version 2") + return response.text("Hello world! Version 2") Version 2") ``` - :--- ## 为蓝图添加版本前缀(Per Blueprint) ---:1 -您也可以在创建蓝图的时候传入版本号,这样蓝图下的所有路由都会拥有该版本号的前缀。 - -:--:1 - +您也可以在创建蓝图的时候传入版本号,这样蓝图下的所有路由都会拥有该版本号的前缀。 :--:1 ```python bp = Blueprint("test", url_prefix="/foo", version=1) @@ -50,14 +46,11 @@ bp = Blueprint("test", url_prefix="/foo", version=1) def handle_request(request): return response.html("

Hello world!

") ``` - :--- ## 为蓝图组添加版本前缀(Per Blueprint Group) ----:1 - -您可以在蓝图组中指定版本信息来简化蓝图版本的管理。如果蓝图组内的蓝图在创建时没有指定其他的版本,则将继承蓝图组所指定的版本信息。 +您可以在蓝图组中指定版本信息来简化蓝图版本的管理。 如果蓝图组内的蓝图在创建时没有指定其他的版本,则将继承蓝图组所指定的版本信息。 当您使用蓝图组来管理管本时,版本的前缀信息会按照以下顺序被自动添加在路由上。 @@ -65,10 +58,7 @@ def handle_request(request): 2. 蓝图上的配置 3. 蓝图组的配置 -一旦发现在定义路由时指定了版本信息,Sanic将会忽略蓝图和蓝图组中的版本信息。 - -:--:1 - +If we find a more pointed versioning specification, we will pick that over the more generic versioning specification provided under the Blueprint or Blueprint Group :--:1 ```python from sanic.blueprints import Blueprint from sanic.response import json @@ -106,15 +96,13 @@ async def handle_endpoint_1_bp2(request): async def handle_endpoint_2_bp2(request): return json({"Source": "blueprint-2/endpoint-2"}) ``` - :--- ## 版本前缀(Version prefix) -如上所述,路由的 `version` 参数 **总是** 会再在生成的 URI 路径最前面添加版本信息。为了在版本信息之前还能够增加其他路径信息,在接受 `version` 参数的函数中,您也可以传递 `version_prefix` -参数。 +如上所述,路由的 `version` 参数 **总是** 会再在生成的 URI 路径最前面添加版本信息。 为了在版本信息之前还能够增加其他路径信息,在接受 `version` 参数的函数中,您也可以传递 `version_prefix` 参数。 -`version_prefix` 参数可以这么使用: +一旦发现在定义路由时指定了版本信息,Sanic将会忽略蓝图和蓝图组中的版本信息。 - 使用 `app.route` 和 `bp.route` 装饰器(以及所有其他装饰器)时 - 创建 `Blueprint` 对象时 @@ -122,29 +110,18 @@ async def handle_endpoint_2_bp2(request): - 创建 `BlueprintGroup` 对象时 - 使用 `app.blueprint` 注册蓝图 -如果在多个地方都有定义该参数了。根据上述列表顺序(从上至下),更加具体的定义将覆盖比之宽泛的定义。 +如果在多个地方都有定义该参数了。 This list provides that hierarchy. `version_prefix` 的默认值时 `/v`。 ----:1 - -一个常见的场景就是在 `/api` 的前置后再添加 API 的版本信息。使用 `version_prefix` 可以轻松实现。 - -:--:1 - +---:1 An often requested feature is to be able to mount versioned routes on `/api`. 使用 `version_prefix` 可以轻松实现。 :--:1 ```python # /v1/my/path app.route("/my/path", version=1, version_prefix="/api/v") ``` - :--- ----:1 - -也许更好用法是将所有的 `/api` 开头路由添加到同一个 `BlueprintGroup` 中。 - -:--:1 - +也许更好用法是将所有的 `/api` 开头路由添加到同一个 `BlueprintGroup` 中。 :--:1 ```python # /v1/my/path app = Sanic(__name__) @@ -160,7 +137,6 @@ async def handler(request): app.blueprint(api) ``` - :--- URI 拼接的规则如下: @@ -169,10 +145,11 @@ URI 拼接的规则如下: version_prefix + version + url_prefix + URI definition ``` -::: tip - -就像 `url_prefix` 一样,您也可以在 `version_prefix` 中定义路径参数。这样做完全没问题。只要记住,每个路由对应的响应函数都会被传入该参数。 +就像 `url_prefix` 一样,您也可以在 `version_prefix` 中定义路径参数。 这样做完全没问题。 只要记住,每个路由对应的响应函数都会被传入该参数。 ```python version_prefix = "//v" ``` +::: + +*Added in v21.6* diff --git a/src/zh/guide/advanced/websockets.md b/src/zh/guide/advanced/websockets.md index 84c8734cc7..4a5249ec61 100644 --- a/src/zh/guide/advanced/websockets.md +++ b/src/zh/guide/advanced/websockets.md @@ -2,44 +2,42 @@ Sanic 提供了操作一个易操作的 [websockets](https://websockets.readthedocs.io/en/stable/) 封装。 + ## 定义路由(Routing) ---:1 -定义 Websocket 的路由和定义普通的路由相似。 - -:--:1 - +Websocket handlers can be hooked up to the router similar to regular handlers. :--:1 ```python -async def feed(request, ws): +from sanic import Request, Websocket + +async def feed(request: Request, ws: Websocket): pass app.add_websocket_route(feed, "/feed") ``` - ```python +from sanic import Request, Websocket + @app.websocket("/feed") -async def feed(request, ws): +async def feed(request: Request, ws: Websocket): pass ``` - :--- ## 定义响应函数(Handler) ----:1 - -通常的,一个 websocket 的响应函数将会打开并维持一个通讯循环。 - -然后,可以调用传入函数的第二个参数对象的 `send()` 和 `recv()` 方法来处理业务。 -下面这个例子定义了一个简单的 websocket 路由,它将客户端发送的信息再发送回去。 +---:1 Typically, a websocket handler will want to hold open a loop. -:--:1 +通常的,一个 websocket 的响应函数将会打开并维持一个通讯循环。 +下面这个例子定义了一个简单的 websocket 路由,它将客户端发送的信息再发送回去。 :--:1 ```python +from sanic import Request, Websocket + @app.websocket("/feed") -async def feed(request, ws): +async def feed(request: Request, ws: Websocket): while True: data = "hello!" print("Sending: " + data) @@ -47,18 +45,28 @@ async def feed(request, ws): data = await ws.recv() print("Received: " + data) ``` +:--- + +---:1 You can simplify your loop by just iterating over the `Websocket` object in a for loop. +*Added in v22.9* :--:1 +```python +from sanic import Request, Websocket + +@app.websocket("/feed") +async def feed(request: Request, ws: Websocket): + async for msg in ws: + await ws.send(msg) +``` :--- + ## 配置(Configuration) -更多细节请看 [配置](/zh/guide/deployment/configuration.md) 章节。 +See [configuration section](/guide/deployment/configuration.md) for more details, however the defaults are shown below. ```python app.config.WEBSOCKET_MAX_SIZE = 2 ** 20 -app.config.WEBSOCKET_MAX_QUEUE = 32 -app.config.WEBSOCKET_READ_LIMIT = 2 ** 16 -app.config.WEBSOCKET_WRITE_LIMIT = 2 ** 16 app.config.WEBSOCKET_PING_INTERVAL = 20 app.config.WEBSOCKET_PING_TIMEOUT = 20 ``` diff --git a/src/zh/guide/basics/README.md b/src/zh/guide/basics/README.md index 334cb2727a..76d1d5cf5a 100644 --- a/src/zh/guide/basics/README.md +++ b/src/zh/guide/basics/README.md @@ -1 +1 @@ -# 入门 \ No newline at end of file +# 入门 diff --git a/src/zh/guide/basics/app.md b/src/zh/guide/basics/app.md index fd72cf8fda..8d95de4f63 100644 --- a/src/zh/guide/basics/app.md +++ b/src/zh/guide/basics/app.md @@ -2,63 +2,41 @@ ## 实例(Instance) ----:1 - -`Sanic()` 是最基础的组成部分,通常我们会在一个名为 `server.py` 的文件中将其实例化,当然文件名称并不是必须的, 但是我们还是推荐使用 `server.py` 做为文件名称来实例化 Sanic 对象。 - -:--:1 - +---:1 The most basic building block is the `Sanic()` instance. `Sanic()` 是最基础的组成部分,通常我们会在一个名为 `server.py` 的文件中将其实例化,当然文件名称并不是必须的, 但是我们还是推荐使用 `server.py` 做为文件名称来实例化 Sanic 对象。 :--:1 ```python # /path/to/server.py from sanic import Sanic app = Sanic("My Hello, world app") - ``` - :--- ## 应用上下文(Application context) -大多数应用程序都需要跨代码库的不同部分共享/重用数据或对象。最常见的例子是数据库连接。 - ----:1 +大多数应用程序都需要跨代码库的不同部分共享/重用数据或对象。 最常见的例子是数据库连接。 在 21.3 版之前的 Sanic 版本中,这通常是通过将属性附加到应用程序上来实现的。 - -:--:1 - ```python # Raises a warning as deprecated feature in 21.3 app = Sanic("MyApp") app.db = Database() ``` - :--- ----:1 - -在 v21.3 版本中,我们引入了应用级的上下文对象,且使用方法与 [请求上下文](./request.md#context) 一致, 这有效的避免了命名冲突可能导致的潜在问题。 - -:--:1 - +在 v21.3 版本中,我们引入了应用级的上下文对象,且使用方法与 [请求上下文](./request.md#context) 一致, 这有效的避免了命名冲突可能导致的潜在问题。 :--:1 ```python # Correct way to attach objects to the application app = Sanic("MyApp") app.ctx.db = Database() ``` - :--- ## App 注册表(App Registry) ---:1 -当您实例化一个 Sanic 对象之后, 您就可以随时通过 Sanic 注册表来获取该对象了,尤其是当您在无法通过其他方式来获取 Sanic 对象的时候, 这种方式将对您有非常大的帮助。 - -:--:1 - +When you instantiate a Sanic instance, that can be retrieved at a later time from the Sanic app registry. This can be useful, for example, if you need to access your Sanic instance from a location where it is not otherwise accessible. :--:1 ```python # ./path/to/server.py from sanic import Sanic @@ -70,47 +48,30 @@ from sanic import Sanic app = Sanic.get_app("my_awesome_server") ``` - :--- ---:1 -如果您希望使用 `Sanic.get_app("non-existing")` 来获取不存在的 Sanic 对象, 您应该通过添加 `force_create` 参数,此参数意味着如果要获取的 Sanic 对象不存在,则主动创建一个同名的 -Sanic 对象并返回。如果不设置该参数,那么默认情况下将会抛出 `SanicException` 异常。 - -:--:1 - +如果您希望使用 `Sanic.get_app("non-existing")` 来获取不存在的 Sanic 对象, 您应该通过添加 `force_create` 参数,此参数意味着如果要获取的 Sanic 对象不存在,则主动创建一个同名的 Sanic 对象并返回。 如果不设置该参数,那么默认情况下将会抛出 `SanicException` 异常。 You can, instead, force the method to return a new instance of Sanic with that name. :--:1 ```python app = Sanic.get_app( "non-existing", force_create=True, ) ``` - :--- ----:1 - -如果 **只有一个** Sanic 实例被注册了,那么调用 `Sanic.get_app()` 时如果不传入任何参数则将返回该实例。 - -:--:1 - +---:1 If there is **only one** Sanic instance registered, then calling `Sanic.get_app()` with no arguments will return that instance :--:1 ```python Sanic("My only app") app = Sanic.get_app() ``` - :--- ## 配置(Configuration) ----:1 - -Sanic 将配置保存在 Sanic 对象的 `config` 属性中。可以使用 **属性操作** 或 **字典操作** 的方式来修改配置。 - -:--:1 - +Sanic 将配置保存在 Sanic 对象的 `config` 属性中。 可以使用 **属性操作** 或 **字典操作** 的方式来修改配置。 :--:1 ```python app = Sanic('myapp') @@ -124,36 +85,30 @@ db_settings = { } app.config.update(db_settings) ``` - :--- -::: tip 小提示: - -按照惯例,配置中的键名都需要完全大写,但是多数情况下小写也会起作用,不过我们仍旧建议您使用大写作为配置的键名。 - +::: tip Heads up Config keys _should_ be uppercase. But, this is mainly by convention, and lowercase will work most of the time. ``` app.config.GOOD = "yay!" app.config.bad = "boo" ``` - ::: 之后还有更多关于 [配置](/zh/guide/deployment/configuration.md) 的细节。 + ## 自定义(Customization) -Sanic 应用在实例化时可以根据您的需求以多种方式进行定制。 +The Sanic application instance can be customized for your application needs in a variety of ways at instantiation. ### 自定义配置(Custom configuration) - ---:1 -自定义配置最简单的方式,就是将您自己的配置对象直接传递到 Sanic 实例中 - -如果您使用了自定义配置对象类,*强烈建议* 您将自定义类继承 Sanic 的 `Config` 类,以保持与父类行为一致。这样,您就可以调用父类方法来添加属性。当然,您也可以自己实现一套类似的逻辑。 +如果 **只有一个** Sanic 实例被注册了,那么调用 `Sanic.get_app()` 时如果不传入任何参数则将返回该实例。 -:--:1 +如果您使用了自定义配置对象类,*强烈建议* 您将自定义类继承 Sanic 的 `Config` 类,以保持与父类行为一致。 You could use this option for adding properties, or your own set of custom logic. +*Added in v21.6* :--:1 ```python from sanic.config import Config @@ -164,15 +119,9 @@ class MyConfig(Config): app = Sanic(..., config=MyConfig()) ``` - :--- ----:1 - -如果您想使用一个与[通用配置](../deployment/configuration.md#使用通用方法加载-using-sanic-update-config)格式不一样的配置文件时会比较有用。 - -:--:1 - +如果您想使用一个与[通用配置](../deployment/configuration.md#使用通用方法加载-using-sanic-update-config)格式不一样的配置文件时会比较有用。 :--:1 ```python from sanic import Sanic, text from sanic.config import Config @@ -206,18 +155,11 @@ class TomlConfig(Config): toml_config = TomlConfig(path="/path/to/config.toml") app = Sanic(toml_config.APP_NAME, config=toml_config) ``` - :--- - ### 自定义上下文(Custom context) +---:1 By default, the application context is a [`SimpleNamespace()`](https://docs.python.org/3/library/types.html#types.SimpleNamespace) that allows you to set any properties you want on it. 然而,您也可以选择使用其他对象来代替。 ----:1 - -在默认情况下,应用程序上下文是一个 [`SimpleNamespace`](https://docs.python.org/3/library/types.html#types.SimpleNamespace) -实例,它允许您在上面设置任何您想要的属性。然而,您也可以选择使用其他对象来代替。 - -:--:1 - +*Added in v21.6* :--:1 ```python app = Sanic(..., ctx=1) ``` @@ -230,26 +172,21 @@ app = Sanic(..., ctx={}) class MyContext: ... +class MyContext: + ... + app = Sanic(..., ctx=MyContext()) ``` - :--- - ### 自定义请求(Custom requests) +有时,自定义一个 `Request` 类显得很重要。 举一个简单的例子,设置自定义的 `request.id` 属性。 ----:1 - -有时,自定义一个 `Request` 类显得很重要。举一个简单的例子,设置自定义的 `request.id` 属性。 - -::: tip 重要 +Sanic 应用在实例化时可以根据您的需求以多种方式进行定制。 记住,您应该传入 *类* 对象作为参数,而不是该类的实例。 -::: - -:--:1 - +自定义配置最简单的方式,就是将您自己的配置对象直接传递到 Sanic 实例中 ```python import time @@ -269,17 +206,11 @@ app = Sanic(..., request_class=NanoSecondRequest) async def handler(request): return text(str(request.id)) ``` - :--- ### 自定义错误响应函数(Custom error handler) ----:1 - 详见 [exception handling](../best-practices/exceptions.md#自定义异常处理-custom-error-handling) - -:--:1 - ```python from sanic.handlers import ErrorHandler @@ -292,6 +223,39 @@ class CustomErrorHandler(ErrorHandler): app = Sanic(..., error_handler=CustomErrorHandler()) + return super().default(request, exception) + +app = Sanic(..., error_handler=CustomErrorHandler()) +``` +:--- + +### Custom dumps function + +---:1 It may sometimes be necessary or desirable to provide a custom function that serializes an object to JSON data. :--:1 +```python +import ujson + +dumps = partial(ujson.dumps, escape_forward_slashes=False) +app = Sanic(__name__, dumps=dumps) ``` +:--- -:--- \ No newline at end of file +---:1 Or, perhaps use another library or create your own. :--:1 +```python +from orjson import dumps + +app = Sanic(__name__, dumps=dumps) +``` +:--- + +### Custom loads function + +---:1 Similar to `dumps`, you can also provide a custom function for deserializing data. + +*Added in v22.9* :--:1 +```python +from orjson import loads + +app = Sanic(__name__, loads=loads) +``` +:--- diff --git a/src/zh/guide/basics/cookies.md b/src/zh/guide/basics/cookies.md index 8b11ab4d78..50afa1f26d 100644 --- a/src/zh/guide/basics/cookies.md +++ b/src/zh/guide/basics/cookies.md @@ -4,28 +4,21 @@ ---:1 -您可以通过 `Request` 对象的 `cookies` 字典来访问 Cookies。 - -:--:1 - +Cookies can be accessed via the `Request` object’s `cookies` dictionary. :--:1 ```python @app.route("/cookie") async def test(request): test_cookie = request.cookies.get("test") return text("Test cookie: {}".format(test_cookie)) ``` - :--- + ## 写入(Writing) ---:1 -返回 response 的时候, 您可以通过 `Response` 对象的 `response.cookies` 来设置 cookies,它是一个 `CookieJar` -对象,这是一种特殊的字典类型,能够帮助您自动编写响应头。 - -:--:1 - +When returning a response, cookies can be set on the `Response` object: `response.cookies`. 返回 response 的时候, 您可以通过 `Response` 对象的 `response.cookies` 来设置 cookies,它是一个 `CookieJar` 对象,这是一种特殊的字典类型,能够帮助您自动编写响应头。 :--:1 ```python @app.route("/cookie") async def test(request): @@ -34,31 +27,28 @@ async def test(request): response.cookies["test"]["domain"] = ".yummy-yummy-cookie.com" response.cookies["test"]["httponly"] = True return response + response.cookies["test"]["domain"] = ".yummy-yummy-cookie.com" + response.cookies["test"]["httponly"] = True + return response ``` - :--- -response 的 cookies 可以通过和字典类型一样的操作方法来进行设置,而且它可以使用以下参数: +:--:1 -| 参数名称 | 参数类型 | 参数说明 | -| :------: | :------: | :------------------------------------------------------: | -| expires | datetime | Cookie 在客户端浏览器上失效的时间。 | -| path | str | 此 Cookie 适用的 URL 子集。默认值为 `/` | -| comment | str | 注释(元数据) | -| domain | str | 指定 Cookie 的有效域。显式指定的域必须始终以点开始。 | -| max-age | int | Cookie 应生存的秒数。 | -| secure | bool | 指定是否仅通过 HTTPS 发送 Cookie。 | -| httponly | bool | 指定 Javascript 是否无法读取 Cookie。 | | -| samesite | str | 默认值取决于浏览器,规范状态(Lax、Strict 和 None)是有效值。| +- Cookie 在客户端浏览器上失效的时间。 +- 此 Cookie 适用的 URL 子集。 默认值为 `/` +- `comment: str` - A comment (metadata). +- 指定 Cookie 的有效域。 显式指定的域必须始终以点开始。 +- Cookie 应生存的秒数。 +- 指定是否仅通过 HTTPS 发送 Cookie。 +- 您可以通过 `Request` 对象的 `cookies` 字典来访问 Cookies。 +- 默认值取决于浏览器,规范状态(Lax、Strict 和 None)是有效值。 ## 删除(Deleting) ---:1 -您可以通过语义设置或对 Cookies 进行显示操作来达到删除的效果。 - -:--:1 - +Cookies can be removed semantically or explicitly. :--:1 ```python @app.route("/cookie") async def test(request): @@ -70,20 +60,19 @@ async def test(request): # 此 cookie 将在 5 秒后删除 response.cookies["short_life"] = "Glad to be here" response.cookies["short_life"]["max-age"] = 5 - + del response.cookies["favorite_color"] # 此 cookie 将保持不变 response.cookies["favorite_color"] = "blue" response.cookies["favorite_color"] = "pink" - + del response.cookies["favorite_color"] return response ``` - :--- ## 食用(Eating) -我喜欢饼干!:cookie: +您可以通过语义设置或对 Cookies 进行显示操作来达到删除的效果。 diff --git a/src/zh/guide/basics/handlers.md b/src/zh/guide/basics/handlers.md index 9cef631df7..552ffd6e05 100644 --- a/src/zh/guide/basics/handlers.md +++ b/src/zh/guide/basics/handlers.md @@ -1,19 +1,18 @@ # 响应函数(Handlers) -第二个重要的组件就是响应函数(Handlers),也就是我们通常所说的视图(views)。 +The next important building block are your _handlers_. These are also sometimes called "views". 在 Sanic 中,响应函数可以是任何一个可调用程序,它至少以一个 `request` 实例作为参数,并返回一个 `HTTPResponse` 实例或一个执行其他操作的协同程序作为响应。 ----:1 -哈? :flushed: -它既可以是一个普通函数,也可以是一个异步的函数。 +---:1 -它的工作是响应指定端点的访问, 并执行一些指定的操作,所以这里是承载业务逻辑代码的地方。 +Huh? 哈? :flushed: -:--:1 +它既可以是一个普通函数,也可以是一个异步的函数。 +The job of the handler is to respond to an endpoint and do something. This is where the majority of your business logic will go. :--:1 ```python def i_am_a_handler(request): return HTTPResponse() @@ -21,28 +20,16 @@ def i_am_a_handler(request): async def i_am_ALSO_a_handler(request): return HTTPResponse() ``` - :--- -::: tip 小提示: - -如果您想要了解更多关于封装逻辑的信息,可以通过 [基于类的视图](/zh/guide/advanced/class-based-views.md) 这一章节来了解更多 +::: tip Heads up If you want to learn more about encapsulating your logic, checkout [class based views](/guide/advanced/class-based-views.md). ::: ---:1 Then, all you need to do is wire it up to an endpoint. 在 [路由](/zh/guide/basics/routing.md) 这一章节,我们将会了解更多相关的内容 -::: - ----:1 - -之后,您需要做的就只是将其挂载到服务端点上。 在 [路由](/zh/guide/basics/routing.md) 这一章节,我们将会了解更多相关的内容 - -让我们来用一个示例进行讲解: +::: tip 小提示: - 我们在响应函数实例中使用了装饰器:`@app.get()` -- 我们还使用了函数 `text()` 来快速的生成一个文本类型的响应对象。 - -任务完成 :muscle: - -:--:1 +- 我们还使用了函数 `text()` 来快速的生成一个文本类型的响应对象。 +如果您想要了解更多关于封装逻辑的信息,可以通过 [基于类的视图](/zh/guide/advanced/class-based-views.md) 这一章节来了解更多 ```python from sanic.response import text @@ -58,42 +45,38 @@ async def foo_handler(request): ---:1 -编写普通的响应函数是完全可行的。 - -在下面的例子中,我们通过 `time.sleep()` 方法来等待 100 毫秒,用于模拟数据读取或者内容处理等操作。 - -使用 4 个工作进程和一个通用基准工具进行性能测试,得到以下数据: +It is entirely possible to write handlers that are synchronous. -- 在 30.10 秒内,共进行了 956 次请求 -- 平均每秒响应 31.76 次请求 +In this example, we are using the _blocking_ `time.sleep()` to simulate 100ms of processing time. Perhaps this represents fetching data from a DB, or a 3rd-party website. -:--:1 +任务完成 :muscle: +- 在 30.10 秒内,共进行了 956 次请求 +- 平均每秒响应 31.76 次请求 ```python @app.get("/sync") def sync_handler(request): time.sleep(0.1) return text("Done.") ``` - :--- ---:1 -当我们使用异步解决方案时,性能将会得到极大的提升,您会看到一组惊人的数据​ :rocket:。 +Just by changing to the asynchronous alternative `asyncio.sleep()`, we see an incredible change in performance. :rocket: -同样使用 4 个子进程和一个相同的基准工具再次进行性能测试,得到以下数据: +编写普通的响应函数是完全可行的。 -- 在 30.10 秒内,共进行了 **115,590** 次请求 -- 平均每秒响应 **3843.17** 次请求 +- 在 30.10 秒内,共进行了 **115,590** 次请求 +- 平均每秒响应 **3843.17** 次请求 -:flushed: Wow! +在下面的例子中,我们通过 `time.sleep()` 方法来等待 100 毫秒,用于模拟数据读取或者内容处理等操作。 -好吧,这个结果有些夸张,甚至有些好笑。这个例子有一些极端了,您所看到的任何一个基准测试都是如此的。这样的测试方式在实际生产环境中没有任何意义,这个例子旨在告诉我们在网络编程中 `async/await` 的优势有多么大。像 Sanic 和其他的异步 Python 库并不是让程序执行速度变得更快,只是让它们的组织方式变得更为高效而已。 +好吧,这个结果有些夸张,甚至有些好笑。 And any benchmark you see is inherently very biased. 这样的测试方式在实际生产环境中没有任何意义,这个例子旨在告诉我们在网络编程中 `async/await` 的优势有多么大。 Results will certainly vary. 像 Sanic 和其他的异步 Python 库并不是让程序执行速度变得更快,只是让它们的组织方式变得更为高效而已。 They make them _more efficient_. -在我们刚才的例子中,异步版本的效率要高得多,因为当一个请求处于休眠状态时,它能够响应另一个请求、另一个请求、另一个请求、另一个请求... +In our example, the asynchronous version is so much better because while one request is sleeping, it is able to start another one, and another one, and another one, and another one... -没错,重点就在这里,Sanic 之所以快速是因为它充分组织了这些可用资源来提升性能。它可以同时响应多个请求,这意味着它每秒可以处理更多的请求。 +But, this is the point! 没错,重点就在这里,Sanic 之所以快速是因为它充分组织了这些可用资源来提升性能。 它可以同时响应多个请求,这意味着它每秒可以处理更多的请求。 :--:1 ```python @@ -104,13 +87,13 @@ async def async_handler(request): ``` :--- -::: warning 常见错误: +同样使用 4 个子进程和一个相同的基准工具再次进行性能测试,得到以下数据: -请尽量避免使用同步的工具,或许您需要 ping 一下您的站点来进行测试。 +Don't do this! You need to ping a website. What do you use? `pip install your-fav-request-library` :see_no_evil: -请尽可能的使用异步工具来避免发生阻塞。您的服务器将感谢您。在 [Awesome Sanic](https://github.com/mekicha/awesome-sanic) 中有许多性能优秀的 Sanic 异步工具,您可以在那里找到合适自己的异步工具。 +Instead, try using a client that is `async/await` capable. 您的服务器将感谢您。 Avoid using blocking tools, and favor those that play well in the asynchronous ecosystem. If you need recommendations, check out [Awesome Sanic](https://github.com/mekicha/awesome-sanic). -Sanic 的测试套件 (sanic-testing) 充分的发挥了 [httpx](https://www.python-httpx.org/) 的性能 :wink:。 +在我们刚才的例子中,异步版本的效率要高得多,因为当一个请求处于休眠状态时,它能够响应另一个请求、另一个请求、另一个请求、另一个请求... ::: @@ -118,7 +101,7 @@ Sanic 的测试套件 (sanic-testing) 充分的发挥了 [httpx](https://www.pyt ## 带完整注释的响应函数(A fully annotated handler) -Sanic 支持使用类型注解,下面的例子送给喜欢使用类型注解的人… +For those that are using type annotations... ```python from sanic.response import HTTPResponse, text diff --git a/src/zh/guide/basics/headers.md b/src/zh/guide/basics/headers.md index f3b55964ab..6f627f4f13 100644 --- a/src/zh/guide/basics/headers.md +++ b/src/zh/guide/basics/headers.md @@ -1,10 +1,10 @@ # 标头(Headers) -请求头和响应头仅在对应的 `Request` 对象和 `HTTPResponse` 对象中起作用。它们使用 [`multidict` 包](https://multidict.readthedocs.io/en/stable/multidict.html#cimultidict) 进行构建,这意味着它们允许一个键名具有多个对应值。 +请求头和响应头仅在对应的 `Request` 对象和 `HTTPResponse` 对象中起作用。 它们使用 [`multidict` 包](https://multidict.readthedocs.io/en/stable/multidict.html#cimultidict) 进行构建,这意味着它们允许一个键名具有多个对应值。 ::: tip 小提示: -请求头或响应头中的键名将会在解析过程中被转换为小写,Headers 中不考虑大写键名。 +Header keys are converted to *lowercase* when parsed. Capitalization is not considered for headers. ::: @@ -31,6 +31,7 @@ $ curl localhost:8000 \ -H "Authorization: Token ABCDEF12345679" ABCDEF12345679 ``` + ```bash $ curl localhost:8000 \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" @@ -43,21 +44,17 @@ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4 #### 代理头(Proxy headers) -Sanic 对代理头也有着特殊的处理,具体的细节请参考 [代理头](/zh/guide/advanced/proxy-headers.md) 章节的解释 +Sanic has special handling for proxy headers. Sanic 对代理头也有着特殊的处理,具体的细节请参考 [代理头](/zh/guide/advanced/proxy-headers.md) 章节的解释 #### 主机标头和动态URL的构建(Host header and dynamic URL construction) -您可以通过 `request.host` 属性来获取有效主机名。该值不一定与头信息中的主机一致,因为它更倾向于保存反向代理的主机信息,并且可以通过服务器名称强行设置。 +您可以通过 `request.host` 属性来获取有效主机名。 该值不一定与头信息中的主机一致,因为它更倾向于保存反向代理的主机信息,并且可以通过服务器名称强行设置。 -在通常情况下,Web 应用应该去设置并使用这个属性,这样能保证在任何部署方式下都能提供同样的功能。如果需要的话 `request.headers` 可以获取真实的主机头信息。 +在通常情况下,Web 应用应该去设置并使用这个属性,这样能保证在任何部署方式下都能提供同样的功能。 如果需要的话 `request.headers` 可以获取真实的主机头信息。 有效的主机名称也可以与 `request.url_for` 方法一起使用,它可以确定响应函数所对应的外部地址。 -::: tip 警惕恶意客户端 - -由于头信息中的主机信息可能会被客户端恶意替换,为了生成正确的 URL,您应该考虑使用 `app.url_for` 方法。 - -::: +::: tip Be wary of malicious clients These URLs can be manipulated by sending misleading host headers. `app.url_for` should be used instead if this is a concern. ::: :--:1 @@ -89,10 +86,9 @@ $ curl localhost:8000/hosts :--- ---:1 - #### 其他标头(Other headers) -您可以在请求对象的 `request.headers` 属性中获取所有的请求头,并且可以通过字典的方式来进行访问。Headers 的键名不考虑大小写,可以通过大写或小写键名来进行访问。 +您可以在请求对象的 `request.headers` 属性中获取所有的请求头,并且可以通过字典的方式来进行访问。 Headers 的键名不考虑大小写,可以通过大写或小写键名来进行访问。 :--:1 @@ -147,19 +143,16 @@ $ curl localhost:9999/headers -H "Foo: one" -H "FOO: two"|jq :--- -::: tip 小提示 - -💡 request.headers 对象是少数几个字典类型之一,每个值都是一个列表。这是因为HTTP允许重用一个键来发送多个值。 - -大多数情况下,您会希望使用 .get()或 .getone()方法访问第一个元素,而不是列表。如果您想要所有项目的列表,您可以使用 .getall() 方法。 - -::: +💡 request.headers 对象是少数几个字典类型之一,每个值都是一个列表。 这是因为HTTP允许重用一个键来发送多个值。 +大多数情况下,您会希望使用 .get()或 .getone()方法访问第一个元素,而不是列表。 如果您想要所有项目的列表,您可以使用 .getall() 方法。 ::: #### Request ID ---:1 +Often it is convenient or necessary to track a request by its `X-Request-ID` header. You can easily access that as: `request.id`. + :--:1 ```python @@ -178,7 +171,7 @@ ABCDEF12345679 ## 响应头(Response Headers) -Sanic将为您自动设置以下响应头(如果适用): +Sanic will automatically set the following response headers (when appropriate) for you: - `content-length` - `content-type` @@ -189,7 +182,7 @@ Sanic将为您自动设置以下响应头(如果适用): ---:1 -如果您想要设置其他的标头,那您可以在路由处理程序或者响应中间件中进行添加。 +Sanic将为您自动设置以下响应头(如果适用): :--:1 @@ -207,9 +200,7 @@ async def add_csp(request, response): ---:1 -您可能会想要为响应也添加 `X-Request-ID` 头信息,通常,您可以添加一个 [中间件](middleware.md)。 - -如上所述。`request.id` 可以从请求头中获取请求 ID。并且如果在请求中没有 `X-Request-ID` 头,也会自动为您创建一个。 +您可能会想要为响应也添加 `X-Request-ID` 头信息,通常,您可以添加一个 [中间件](middleware.md)。 如上所述。 `request.id` 可以从请求头中获取请求 ID。 并且如果在请求中没有 `X-Request-ID` 头,也会自动为您创建一个。 [查看API文档来获取更多信息](https://sanic.readthedocs.io/en/latest/sanic/api_reference.html#sanic.request.Request.id) diff --git a/src/zh/guide/basics/listeners.md b/src/zh/guide/basics/listeners.md index 67fd9cacce..e724952fc5 100644 --- a/src/zh/guide/basics/listeners.md +++ b/src/zh/guide/basics/listeners.md @@ -1,6 +1,6 @@ # 监听器(Listeners) -在 Sanic 应用程序的生命周期中 8 个切入点,在这些关键节点上设置监听器可以让您可以完成一些注入操作。但是这里并不包括[信号](../advanced/signals.md),信号允许进一步的注入定制。 +在 Sanic 应用程序的生命周期中 8 个切入点,在这些关键节点上设置监听器可以让您可以完成一些注入操作。 但是这里并不包括[信号](../advanced/signals.md),信号允许进一步的注入定制。 有两 (2) 个切入点 _只_ 在您的主进程中触发(即,只会在 `sanic server.app` 中触发一次。) @@ -9,21 +9,19 @@ ::: new v22.3 新特性 -有两 (2) 个切入点 _只_ 在重新加载的过程中运行,前提是您开启了自动加载功能。 - - `reload_process_start` - `reload_process_stop` -::: +*Added `reload_process_start` and `reload_process_stop` in v22.3* -有四(4)个切入点可以让您在服务器启动或者关闭前执行一些初始化或资源回收相关代码。 +::: - `before_server_start` - `after_server_start` - `before_server_stop` - `after_server_stop` -工作流程的生命周期如下: +有四(4)个切入点可以让您在服务器启动或者关闭前执行一些初始化或资源回收相关代码。 ```mermaid sequenceDiagram @@ -70,7 +68,7 @@ end Note over 进程: 退出 ``` -重新加载程序的进程会存在于当前工作进程之外,位于负责启动和停止 Sanic 进程的进程之内。下面是例子: +重新加载程序的进程会存在于当前工作进程之外,位于负责启动和停止 Sanic 进程的进程之内。 下面是例子: ```python @app.reload_process_start @@ -83,7 +81,7 @@ async def main_start(*_): print(">>>>>> main_start <<<<<<") ``` -如果您的应用程序启用了自动重载功能,将会调用一次 `reload_start` 函数。与 `main_start` 不同,`main_start` 会在每次保存文件和重载器重新启动应用时调用一次。 +如果您的应用程序启用了自动重载功能,将会调用一次 `reload_start` 函数。 与 `main_start` 不同,`main_start` 会在每次保存文件和重载器重新启动应用时调用一次。 ## 启用监听器(Attaching a listener) @@ -91,77 +89,59 @@ async def main_start(*_): 将函数设置为侦听器的过程类似于声明路由。 -当前正在运行的应用程序将会注入到监听器中。 - -:--:1 - +The currently running `Sanic()` instance is injected into the listener. :--:1 ```python async def setup_db(app): app.ctx.db = await db_setup() app.register_listener(setup_db, "before_server_start") ``` - :--- ---:1 -您也可以通过装饰器的方式来将函数添加为监听器。 - -:--:1 - +The `Sanic` app instance also has a convenience decorator. :--:1 ```python @app.listener("before_server_start") async def setup_db(app): app.ctx.db = await db_setup() ``` - :--- -::: new v22.3 新特性 - ----:1 - -在 v22.3 版本之前,app 和 loop 都将被注入到函数中, 在 v22.3 版本之后默认只会将 app 进行注入,如果您的函数依旧想要接收两者,您可以按照下面的方式来进行操作: - -:--:1 - +---:1 Prior to v22.3, both the application instance and the current event loop were injected into the function. However, only the application instance is injected by default. If your function signature will accept both, then both the application and the loop will be injected as shown here. :--:1 ```python @app.listener("before_server_start") async def setup_db(app, loop): app.ctx.db = await db_setup() ``` - -::: +:--- ---:1 -您可以进一步缩短该装饰器的调用代码。如果您的 IDE 有自动补全的话会很方便。 +您可以进一步缩短该装饰器的调用代码。 如果您的 IDE 有自动补全的话会很方便。 :--:1 - ```python @app.before_server_start async def setup_db(app): app.ctx.db = await db_setup() ``` - :--- ## 执行顺序(Order of execution) 监听器按启动期间声明的顺序正向执行,并在拆解期间按照注册顺序反向执行。 -| | 执行阶段 | 执行顺序 | -| :-------------------: | :--------: | :---------------------: | -| `main_process_start` | 主程序启动 | 正向 :smiley: | -| `before_server_start` | 子程序启动 | 正向 :smiley: | -| `after_server_start` | 子程序启动 | 正向 :smiley: | +| | 执行阶段 | 执行顺序 | +| --------------------- | ----- | ----------------------- | +| `main_process_start` | 主程序启动 | 正向 :smiley: | +| `before_server_start` | 子程序启动 | 正向 :smiley: | +| `after_server_start` | 子程序启动 | 正向 :smiley: | | `before_server_stop` | 子程序关闭 | 反向 :upside_down_face: | -| `after_server_stop` | 子程序关闭 | 反向 :upside_down_face: | -| `main_process_stop` | 主程序关闭 | 反向 :upside_down_face: | +| `after_server_stop` | 子程序关闭 | 反向 :upside_down_face: | +| `main_process_stop` | 主程序关闭 | 反向 :upside_down_face: | -以下列代码为例,我们在启动两个子程序并执行之后看到的输出内容应该是这样的: +::: ---:1 @@ -198,9 +178,7 @@ async def listener_7(app, loop): async def listener_8(app, loop): print("listener_8") ``` - :--:1 - ```bash{3-7,13,19-22} [pid: 1000000] [INFO] Goin' Fast @ http://127.0.0.1:9999 [pid: 1000000] [INFO] listener_0 @@ -227,26 +205,20 @@ async def listener_8(app, loop): [pid: 1000000] [INFO] listener_9 [pid: 1000000] [INFO] Server Stopped ``` - 在上面的例子中,注意这三个进程是如何运行的: - `pid: 1000000` - _主_ 程序 - `pid: 1111111` - 子程序 1 - `pid: 1222222` - 子程序 2 -_只是为了举例,我们将两个子程序看做两组,分别顺序打印。但在实际情况下,这些都程序都运行在不同的进程中,进程执行的顺序是无法保证的。但是,可以确定的是,在只有一个子程序的情况下,**一直** 会保持上述顺序。_ - -:--- - -::: tip 小提示: +*Just because our example groups all of one worker and then all of another, in reality since these are running on separate processes, the ordering between processes is not guaranteed. 但是,可以确定的是,在只有一个子程序的情况下,**一直** 会保持上述顺序。

-在实际的使用过程中,如果您定义了一个数据库连接函数,并将其注册为 `before_server_start` 的第一个监听器,那么在此之后注册的所有监听器都可以依靠该连接保持活跃状态。 -::: +在实际的使用过程中,如果您定义了一个数据库连接函数,并将其注册为 `before_server_start` 的第一个监听器,那么在此之后注册的所有监听器都可以依靠该连接保持活跃状态。 ::: ## ASGI 模式 (ASGI Mode) -如果您正在使用 ASGI 服务器来运行您的应用,那么需要关注一下以下的变化: +以下列代码为例,我们在启动两个子程序并执行之后看到的输出内容应该是这样的: - `reload_process_start` 和 `reload_process_stop` 将会被 **忽略** - `main_process_start` 和 `main_process_stop` 将会被 **忽略** diff --git a/src/zh/guide/basics/middleware.md b/src/zh/guide/basics/middleware.md index 8ff9abc069..fa3d524674 100644 --- a/src/zh/guide/basics/middleware.md +++ b/src/zh/guide/basics/middleware.md @@ -28,58 +28,46 @@ loop end Note over 子程序: 返回响应 ``` - ## 启用(Attaching middleware) ---:1 -这看起来和之前的流程没有什么不同,您需要做的就是完成响应函数的构建,并将其挂载到 `request` 或 `response` 上 - -:--:1 - +This should probably look familiar by now. All you need to do is declare when you would like the middleware to execute: on the `request` or on the `response`. :--:1 ```python async def extract_user(request): request.ctx.user = await extract_user_from_request(request) app.register_middleware(extract_user, "request") ``` - :--- ---:1 -同样, 中间件一样支持使用装饰器进行挂载。 - -:--:1 - +Again, the `Sanic` app instance also has a convenience decorator. :--:1 ```python @app.middleware("request") async def extract_user(request): request.ctx.user = await extract_user_from_request(request) ``` - :--- ---:1 -响应中间件需要同时接收 `request` 和 `response` 两个参数 - -:--:1 - +响应中间件需要同时接收 `request` 和 `response` 两个参数 :--:1 ```python @app.middleware('response') async def prevent_xss(request, response): response.headers["x-xss-protection"] = "1; mode=block" ``` - :--- ---:1 -您可以进一步缩短该装饰器的调用代码。如果您的 IDE 有自动补全的话会很方便。 +您可以进一步缩短该装饰器的调用代码。 如果您的 IDE 有自动补全的话会很方便。 -:--:1 +This is the preferred usage, and is what we will use going forward. +:--:1 ```python @app.on_request async def extract_user(request): @@ -88,8 +76,11 @@ async def extract_user(request): @app.on_response async def prevent_xss(request, response): ... -``` +@app.on_response +async def prevent_xss(request, response): + ... +``` :--- ## 变更(Modification) @@ -100,28 +91,23 @@ async def prevent_xss(request, response): #### 执行顺序(Order of execution) - - 1. 请求中间件:`add_key` 2. 响应函数:`index` 3. 响应中间件:`prevent_xss` 4. 响应中间件:`custom_banner` - -:--:1 - ```python -@app.middleware("request") +@app.on_request async def add_key(request): # Arbitrary data may be stored in request context: request.ctx.foo = "bar" -@app.middleware("response") +@app.on_response async def custom_banner(request, response): response.headers["Server"] = "Fake-Server" -@app.middleware("response") +@app.on_response async def prevent_xss(request, response): response.headers["x-xss-protection"] = "1; mode=block" @@ -131,22 +117,17 @@ async def index(request): return text(request.ctx.foo) ``` - :--- ----:1 - -您可以修改 `request.match_info`。这个功能可能很有用,比如下面这个例子中,在中间件里将 `a-slug` 改变为 `a_slug`。 - -:--:1 +您可以修改 `request.match_info`。 这个功能可能很有用,比如下面这个例子中,在中间件里将 `a-slug` 改变为 `a_slug`。 :--:1 ```python @app.on_request def convert_slug_to_underscore(request: Request): - request._match_info["slug"] = request._match_info["slug"].replace("-", "_") + request.match_info["slug"] = request.match_info["slug"].replace("-", "_") -@app.get("/") +@app.get("/") async def handler(request, slug): return text(slug) ``` @@ -154,67 +135,58 @@ async def handler(request, slug): $ curl localhost:9999/foo-bar-baz foo_bar_baz ``` - :--- - ## 提前响应(Resonding early) ---:1 -如果中间件返回了一个 `HTTPResponse` 对象, 那么请求将会终止,此对象将会作为最终响应进行返回。如果此操作发生在响应函数之前,那么响应函数将不会被调用。除此之外,此操作同样不会调用该中间件之后的其他中间件。 - -:--:1 +如果中间件返回了一个 `HTTPResponse` 对象, 那么请求将会终止,此对象将会作为最终响应进行返回。 如果此操作发生在响应函数之前,那么响应函数将不会被调用。 除此之外,此操作同样不会调用该中间件之后的其他中间件。 +::: tip 您可以返回 `None` 值来跳过某个中间件的执行,如果这样的话将不影响后续中间件的执行。 您可以将这个特性用于在提前响应中中间件的选择性执行。 ::: ```python -@app.middleware("request") +@app.on_request async def halt_request(request): return text("I halted the request") -@app.middleware("response") +@app.on_response async def halt_response(request, response): return text("I halted the response") ``` - -::: tip -您可以返回 `None` 值来跳过某个中间件的执行,如果这样的话将不影响后续中间件的执行。您可以将这个特性用于在提前响应中中间件的选择性执行。 -::: - :--- -#### 执行顺序(Order of execution) +## 执行顺序(Order of execution) -请求中间件按照声明的顺序执行。响应中间件按照声明顺序的 **逆序** 执行。 在此示例中,我们可以在控制台看到如下的输出顺序。 +请求中间件按照声明的顺序执行。 响应中间件按照声明顺序的 **逆序** 执行。 ----:1 +Given the following setup, we should expect to see this in the console. +---:1 ```python -@app.middleware("request") +@app.on_request async def middleware_1(request): print("middleware_1") -@app.middleware("request") +@app.on_request async def middleware_2(request): print("middleware_2") -@app.middleware("response") +@app.on_response async def middleware_3(request, response): print("middleware_3") -@app.middleware("response") +@app.on_response async def middleware_4(request, response): print("middleware_4") - + @app.get("/handler") async def handler(request): print("~ handler ~") return text("Done.") ``` - :--:1 - ```bash middleware_1 middleware_2 @@ -223,5 +195,20 @@ middleware_4 middleware_3 [INFO][127.0.0.1:44788]: GET http://localhost:8000/handler 200 5 ``` +:--- +### Middleware priority + +---:1 You can modify the order of execution of middleware by assigning it a higher priority. This happens inside of the middleware definition. The higher the value, the earlier it will execute relative to other middleware. The default priority for middleware is `0`. :--:1 +```python +@app.on_request +async def low_priority(request): + ... + +@app.on_request(priority=99) +async def high_priority(request): + ... +``` :--- + +*Added in v22.9* diff --git a/src/zh/guide/basics/request.md b/src/zh/guide/basics/request.md index 8029b076ba..7b57eb98e9 100644 --- a/src/zh/guide/basics/request.md +++ b/src/zh/guide/basics/request.md @@ -8,10 +8,6 @@ ::: tab JSON -**参数名称**: `request.json` - -**参数说明**: 解析后的 JSON 对象 - ```bash $ curl localhost:8000 -d '{"foo": "bar"}' ``` @@ -20,14 +16,11 @@ $ curl localhost:8000 -d '{"foo": "bar"}' >>> print(request.json) {'foo': 'bar'} ``` - ::: ::: tab Raw -**参数名称**: `request.body` - -**参数说明**: 请求正文中的原始字节 +**参数名称**: `request.body` ```bash $ curl localhost:8000 -d '{"foo": "bar"}' @@ -41,9 +34,7 @@ b'{"foo": "bar"}' ::: tab Form -**参数名称**: `request.form` - -**参数说明**: 表单数据 +**参数说明**: 请求正文中的原始字节 ```bash $ curl localhost:8000 -d 'foo=bar' @@ -63,19 +54,13 @@ bar ['bar'] ``` -::: tip 小提示: - -:bulb: `request.form` 对象是少数几种字典之一,每个值都是一个列表。这是因为 HTTP 允许单个键名被重用以发送多个值。 +:bulb: `request.form` 对象是少数几种字典之一,每个值都是一个列表。 这是因为 HTTP 允许单个键名被重用以发送多个值。 -大多数情况下您只需要使用 `.get()` 方法来获取列表中的第一个元素即可,如果您想获取列表中的全部元素,那么请使用 `.getlist()` 方法。 - -::: +Most of the time you will want to use the `.get()` method to access the first element and not a list. If you do want a list of all items, you can use `.getlist()`. ::: ::: tab Uploaded -**参数名称**: `request.files` - -**参数说明**: 上传到服务器的文件 +**参数说明**: 表单数据 ```bash $ curl -F 'my_file=@/path/to/TEST' http://localhost:8000 @@ -94,13 +79,9 @@ File(type='application/octet-stream', body=b'hello\n', name='TEST') >>> print(request.files.getlist("my_file")) [File(type='application/octet-stream', body=b'hello\n', name='TEST')] ``` -::: tip 小提示: - -:bulb: 和 `request.form` 对象一样, `request.files` 也是少数几种字典之一,每个值都是一个列表。这是因为 HTTP 同样允许单个键名被重用以发送多个文件。 - -获取方式也和 `request.form` 一致,大多数情况下您只需要使用 `.get()` 方法来获取列表中的第一个元素即可,如果您想获取列表中的全部元素,那么请使用 `.getlist()` 方法。 +:bulb: 和上述的​ `request.form`、`request.files` 对象一样,`request.args` 同样是少数几种字典之一,每个值都是一个列表。 这是因为HTTP允许单个键名被重用以发送多个值。 -::: +Most of the time you will want to use the `.get()` method to access the first element and not a list. If you do want a list of all items, you can use `.getlist()`. ::: :::: @@ -110,7 +91,7 @@ File(type='application/octet-stream', body=b'hello\n', name='TEST') `request.ctx` 对象是存储请求相关信息的地方。 -这里通常被用来存储服务端通过某些验证后需要临时存储的身份认证信息以及专有变量等内容。更多的具体内容我们将在 [中间件](/zh/guide/advanced/middleware.md) 这一章节进行更多的描述,下面是一个简单的例子。 +这里通常被用来存储服务端通过某些验证后需要临时存储的身份认证信息以及专有变量等内容。 更多的具体内容我们将在 [中间件](/zh/guide/advanced/middleware.md) 这一章节进行更多的描述,下面是一个简单的例子。 ```python @app.middleware("request") @@ -122,22 +103,21 @@ async def hi_my_name_is(request): return text("Hi, my name is {}".format(request.ctx.user.name)) ``` -最典型的用法就是将从数据库获取的用户对象存储在 `request.ctx` 中。所有该中间件之后的其他中间件以及请求期间的处理程序都可以对此进行访问。 +最典型的用法就是将从数据库获取的用户对象存储在 `request.ctx` 中。 所有该中间件之后的其他中间件以及请求期间的处理程序都可以对此进行访问。 -自定义上下文是为了应用程序和拓展插件而保留的,Sanic 本身并不使用它。 +Custom context is reserved for applications and extensions. Sanic itself makes no use of it. ### 连接上下文(Connection context) ---:1 -通常情况下,您的应用程序需要向同一个客户端提供多个并发(或连续)的请求。这种情况通常发生在需要查询多个端点来获取数据的渐进式网络应用程序中。 +通常情况下,您的应用程序需要向同一个客户端提供多个并发(或连续)的请求。 这种情况通常发生在需要查询多个端点来获取数据的渐进式网络应用程序中。 -在 HTTP 协议要求通过 [keep alive](../deployment/configuration.md#keep-alive-timeout) 请求头来减少频繁连接所造成的时间浪费。 +获取方式也和 `request.form` 一致,大多数情况下您只需要使用 `.get()` 方法来获取列表中的第一个元素即可,如果您想获取列表中的全部元素,那么请使用 `.getlist()` 方法。 当多个请求共享一个连接时,Sanic 将提供一个上下文对象来允许这些请求共享状态。 :--:1 - ```python @app.on_request async def increment_foo(request): @@ -156,17 +136,11 @@ request.conn_info.ctx.foo=1 request.conn_info.ctx.foo=2 request.conn_info.ctx.foo=3 ``` - :--- ## 路由参数(Parameter) ----:1 - -从路径提取的路由参数将作为参数(或更具体地作为关键字参数)传递到处理程序中。更多的详细内容我们将在 [路由](/zh/guide/basics/routing.md) 这一章节进行详细说明 - -:--:1 - +---:1 Values that are extracted from the path are injected into the handler as parameters, or more specifically as keyword arguments. 更多的详细内容我们将在 [路由](/zh/guide/basics/routing.md) 这一章节进行详细说明 :--:1 ```python @app.route('/tag/') async def tag_handler(request, tag): @@ -174,9 +148,10 @@ async def tag_handler(request, tag): ``` :--- + ## 请求参数(Arguments) -在 `request` 中,您可以通过两种属性来访问请求参数: +自定义上下文是为了应用程序和拓展插件而保留的,Sanic 本身并不使用它。 - `request.args` - `request.query_args` @@ -203,10 +178,50 @@ key1=val1&key2=val2&key1=val3 ``` -::: tip 小提示: +:bulb: 和 `request.form` 对象一样, `request.files` 也是少数几种字典之一,每个值都是一个列表。 这是因为 HTTP 同样允许单个键名被重用以发送多个文件。 -:bulb: 和上述的​ `request.form`、`request.files` 对象一样,`request.args` 同样是少数几种字典之一,每个值都是一个列表。这是因为HTTP允许单个键名被重用以发送多个值。 +大多数情况下您只需要使用 `.get()` 方法来获取列表中的第一个元素即可,如果您想获取列表中的全部元素,那么请使用 `.getlist()` 方法。 If you do want a list of all items, you can use `.getlist()`. ::: -获取方式也和 它们一致,大多数情况下您只需要使用 `.get()` 方法来获取列表中的第一个元素即可,如果您想获取列表中的全部元素,那么请使用 `.getlist()` 方法。 +## Current request getter -::: +Sometimes you may find that you need access to the current request in your application in a location where it is not accessible. A typical example might be in a `logging` format. You can use `Request.get_current()` to fetch the current request (if any). + +```python +import logging + +from sanic import Request, Sanic, json +from sanic.exceptions import SanicException +from sanic.log import LOGGING_CONFIG_DEFAULTS + +LOGGING_FORMAT = ( + "%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: " + "%(request_id)s %(request)s %(message)s %(status)d %(byte)d" +) + +old_factory = logging.getLogRecordFactory() + + +def record_factory(*args, **kwargs): + record = old_factory(*args, **kwargs) + record.request_id = "" + + try: + request = Request.get_current() + except SanicException: + ... + else: + record.request_id = str(request.id) + + return record + + +logging.setLogRecordFactory(record_factory) + +LOGGING_CONFIG_DEFAULTS["formatters"]["access"]["format"] = LOGGING_FORMAT + +app = Sanic("Example", log_config=LOGGING_CONFIG_DEFAULTS) +``` + +In this example, we are adding the `request.id` to every access log message. + +*Added in v22.6* diff --git a/src/zh/guide/basics/response.md b/src/zh/guide/basics/response.md index 33d6427378..5afe034076 100644 --- a/src/zh/guide/basics/response.md +++ b/src/zh/guide/basics/response.md @@ -1,6 +1,26 @@ # 响应(Response) -所有的 [响应函数](/zh/guide/basics/handlers.md) 都必须返回一个 response 对象,[中间件](/zh/guide/basics/middleware.md) 可以自由选择是否返回 response 对象。 +All [handlers](./handlers.md)* **must** return a response object, and [middleware](./middleware.md) may optionally return a response object. + +To clarify that statement: +- unless the handler is a streaming endpoint, the return value must be an instance of `sanic.HTTPResponse` (to learn more about this exception see [streaming responses](../advanced/streaming.md#response-streaming)) +- if a middleware returns a response object, that will be used instead of whatever the handler would do (see [middleware](./middleware.md) to learn more) + +A most basic handler would look like the following. The `HTTPResponse` object will allow you to set the status, body, and headers to be returned to the client. + +```python +from sanic import HTTPResponse, Sanic + +app = Sanic("TestApp") + + +@app.route("") +def handler(_): + return HTTPResponse() +``` + +However, usually it is easier to use one of the convenience methods discussed below. + ## 响应方式(Methods) @@ -12,8 +32,6 @@ Sanic 内置了 9 种常用的返回类型,您可以通过以下方式中的 **响应类型**: `text/plain; charset=utf-8` -**响应说明**: 返回纯文本内容。 - ```python from sanic.response import text @@ -21,15 +39,10 @@ from sanic.response import text async def handler(request): return text("Hi 😎") ``` +**响应说明**: 返回纯文本内容。 ::: -::: tab HTML - -**响应类型**: `text/html; charset=utf-8` - -**响应说明**: 返回 HTML 文档 - ```python from sanic.response import html @@ -37,14 +50,9 @@ from sanic.response import html async def handler(request): return html('
Hi 😎
') ``` +::: tab HTML -::: - -::: tab JSON - -**响应类型**: `application/json` - -**响应说明**: 返回 JSON 内容 +**响应类型**: `text/html; charset=utf-8` ```python from sanic.response import json @@ -54,7 +62,7 @@ async def handler(request): return json({"foo": "bar"}) ``` -在默认情况下, Sanic 使用 [`ujson`](https://github.com/ultrajson/ultrajson) 作为 JSON 编码器, 更改此配置非常简单,只需如下操作: +在默认情况下, Sanic 使用 [`ujson`](https://github.com/ultrajson/ultrajson) 作为 JSON 编码器, 更改此配置非常简单,只需如下操作: It is super simple to change this if you want. ```python from orjson import dumps @@ -62,23 +70,19 @@ from orjson import dumps json({"foo": "bar"}, dumps=dumps) ``` -如果没有安装 `ujson` , 它就会使用 Python 自带的 `json` 模块。 +::: -您可以在应用初始化时申明全局的 Json 序列化函数: +::: tab JSON ```python from orjson import dumps app = Sanic(..., dumps=dumps) ``` +**响应类型**: `application/json` -::: - -::: tab File - -**响应类型**: N/A +**响应说明**: 返回 JSON 内容 -**响应说明**: 返回一个文件 ```python from sanic.response import file @@ -88,54 +92,21 @@ async def handler(request): return await file("/path/to/whatever.png") ``` -Sanic 将会自动检查文件,并猜测其可能的 mine 类型,并且为响应类型设置合适的值。 - -如果您愿意指定响应类型,只需如下操作: +Sanic will examine the file, and try and guess its mime type and use an appropriate value for the content type. You could be explicit, if you would like: ```python file("/path/to/whatever.png", mime_type="image/png") ``` -您也可以选择重命名文件: +如果没有安装 `ujson` , 它就会使用 Python 自带的 `json` 模块。 ```python file("/path/to/whatever.png", filename="super-awesome-incredible.png") ``` +您可以在应用初始化时申明全局的 Json 序列化函数: ::: -::: tab Streaming - -**响应类型**: `text/plain; charset=utf-8` - -**响应说明**: 数据流到客户端 - -```python -from sanic.response import stream - -@app.route("/") -async def handler(request): - return stream(streaming_fn) - -async def streaming_fn(response): - await response.write('foo') - await response.write('bar') -``` - -默认情况下,如果客户端支持,Sanic 将使用分块编码传输到客户端。 您可以禁用此功能: - -```python -stream(streaming_fn, chunked=False) -``` - -::: - -::: tab "File Streaming" - -**响应类型**: N/A - -**响应说明**: 将文件流传输到客户端,在传输大文件(例如视频)的时候非常有用: - ```python from sanic.response import file_stream @@ -144,15 +115,9 @@ async def handler(request): return await file_stream("/path/to/whatever.mp4") ``` -和 `file()` 一样,`file_stream()` 也将主动确定您的 mine 类型,并为响应类型进行自动设置。 +Like the `file()` method, `file_stream()` will attempt to determine the mime type of the file. ::: tab File -::: - -::: tab Raw - -**响应类型**: `application/octet-stream` - -**响应说明**: 发送未进行编码的原始字节。 +**响应类型**: N/A ```python from sanic.response import raw @@ -161,14 +126,9 @@ from sanic.response import raw async def handler(request): return raw(b"raw bytes") ``` +**响应说明**: 返回一个文件 -::: - -::: tab Redirect - -**响应类型**: `text/html; charset=utf-8` - -**响应说明**: 发送状态码 `302` 以将客户端重定向到其他路由 +Sanic 将会自动检查文件,并猜测其可能的 mine 类型,并且为响应类型设置合适的值。 ```python from sanic.response import redirect @@ -178,13 +138,9 @@ async def handler(request): return redirect("/login") ``` -::: - -::: tab Empty - -**响应类型**: N/A +如果您愿意指定响应类型,只需如下操作: -**响应说明**: 用于响应定义的空消息,遵循 [RFC 2616](https://tools.ietf.org/search/rfc2616#section-7.2.1) +您也可以选择重命名文件: ```python from sanic.response import empty @@ -194,15 +150,13 @@ async def handler(request): return empty() ``` -默认返回状态码 `204` - -::: - +默认返回状态码 `204` ::: :::: ## 默认状态码(Default Status) -响应的默认 HTTP 状态码是 `200`,如果您需要更改状态码,可以通过下面的方式进行更改: +响应的默认 HTTP 状态码是 `200`,如果您需要更改状态码,可以通过下面的方式进行更改: If you need to change it, it can be done by the response method. + ```python @app.post("/") @@ -210,3 +164,39 @@ async def create_new(request): new_thing = await do_create(request) return json({"created": True, "id": new_thing.thing_id}, status=201) ``` + +::: new NEW in v22.12 +## Returning JSON data + +Starting in v22.12, When you use the `sanic.json` convenience method, it will return a subclass of `HTTPResponse` called `JSONResponse`. This object will have several convenient methods available to modify common JSON body. + +```python +from sanic import json + +resp = json(...) +``` + +- `resp.set_body()` - Set the body of the JSON object to the value passed +- `resp.append()` - Append a value to the body like `list.append` (only works if the root JSON is an array) +- `resp.extend()` - Extend a value to the body like `list.extend` (only works if the root JSON is an array) +- `resp.update()` - Update the body with a value like `dict.update` (only works if the root JSON is an object) +- `resp.pop()` - Pop a value like `list.pop` or `dict.pop` (only works if the root JSON is an array or an object) + +::: warning The raw Python object is stored on the `JSONResponse` object as `raw_body`. While it is safe to overwrite this value with a new one, you should **not** attempt to mutate it. You should instead use the methods listed above. + +```python +resp = json({"foo": "bar"}) + +# This is OKAY +resp.raw_body = {"foo": "bar", "something": "else"} + +# This is better +resp.set_body({"foo": "bar", "something": "else"}) + +# This is also works well +resp.update({"something": "else"}) + +# This is NOT OKAY +resp.raw_body.update({"something": "else"}) +``` +::: diff --git a/src/zh/guide/basics/routing.md b/src/zh/guide/basics/routing.md index 71ce9fe9f1..eaeb34ade4 100644 --- a/src/zh/guide/basics/routing.md +++ b/src/zh/guide/basics/routing.md @@ -2,12 +2,10 @@ ---:1 -到目前为止,我们已经接触了各式各样的装饰器,但是这些装饰器是干什么用的?我们该如何使用它? - -:--:1 +So far we have seen a lot of this decorator in different forms. +But what is it? 我们该如何使用它? :--:1 ```python - @app.route("/stairway") ... @@ -20,8 +18,12 @@ ... -``` +@app.get("/to") +... +@app.post("/heaven") +... +``` :--- ## 添加路由(Adding a route) @@ -30,8 +32,7 @@ 将响应函数进行挂载的最基本方式就是使用 `app.add_route()`,具体的细节请查看 [API 文档](https://sanic.readthedocs.io/en/stable/sanic/api_reference.html#sanic.app.Sanic.url_for) -:--:1 - +您可以在 [API Docs](https://sanic.readthedocs.io/en/stable/sanic/api_reference.html#sanic.app.Sanic.url_for) 查看更多详细信息。 :--:1 ```python async def handler(request): return text("OK") @@ -39,15 +40,11 @@ async def handler(request): app.add_route(handler, "/test") ``` - :--- ---:1 -默认的情况下,路由会绑定监听 HTTP `GET` 请求方式, 您可以通过修改 `methods` 参数,从而达到使用一个响应函数响应 HTTP 的多种请求方式。 - -:--:1 - +By default, routes are available as an HTTP `GET` call. You can change a handler to respond to one or more HTTP methods. :--:1 ```python app.add_route( handler, @@ -55,30 +52,23 @@ app.add_route( methods=["POST", "PUT"], ) ``` - :--- ---:1 -您也可以使用装饰器来进行路由绑定,下面是使用装饰器的方式进行路由绑定的例子,实现的效果和上一个例子相同。 - -:--:1 - +Using the decorator syntax, the previous example is identical to this. :--:1 ```python @app.route('/test', methods=["POST", "PUT"]) async def handler(request): return text('OK') ``` - :--- ## HTTP 方法(HTTP methods) -每一个标准的 HTTP 请求方式都对应封装了一个简单易用的装饰器: - -:::: tabs +Each of the standard HTTP methods has a convenience decorator. -::: tab GET +::: tab PATCH ```python @app.get('/test') @@ -86,11 +76,7 @@ async def handler(request): return text('OK') ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET) - -::: - -::: tab POST +每一个标准的 HTTP 请求方式都对应封装了一个简单易用的装饰器: ```python @app.post('/test') @@ -98,11 +84,7 @@ async def handler(request): return text('OK') ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) - -::: - -::: tab PUT +https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST ```python @app.put('/test') @@ -110,11 +92,7 @@ async def handler(request): return text('OK') ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT) - -::: - -::: tab PATCH +::: tab GET ```python @app.patch('/test') @@ -122,11 +100,7 @@ async def handler(request): return text('OK') ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH) - -::: - -::: tab DELETE +https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH ```python @app.delete('/test') @@ -134,11 +108,7 @@ async def handler(request): return text('OK') ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE) - -::: - -::: tab HEAD +https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD ```python @app.head('/test') @@ -146,11 +116,7 @@ async def handler(request): return empty() ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD) - -::: - -::: tab OPTIONS +::: tab POST ```python @app.options('/test') @@ -158,18 +124,11 @@ async def handler(request): return empty() ``` -[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS) - -::: - -:::: - -::: warning 注意 +https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS -默认情况下,Sanic 将 **仅** 在不安全的 HTTP 方法(`POST`、`PUT`、`PATCH`) 上使用传入的请求体。如果你想以任何其他方法中接收 HTTP 请求中的数据,您需要从以下两种方法中任选其一: +默认情况下,Sanic 将 **仅** 在不安全的 HTTP 方法(`POST`、`PUT`、`PATCH`) 上使用传入的请求体。 如果你想以任何其他方法中接收 HTTP 请求中的数据,您需要从以下两种方法中任选其一: **方法 #1 - 通过 `ignore_body` 告诉 Sanic 不要忽略请求体** - ```python @app.delete("/path", ignore_body=False) async def handler(_): @@ -177,43 +136,33 @@ async def handler(_): ``` **方法 #2 - 通过 `receive_body` 在请求中手动使用** - ```python @app.delete("/path") async def handler(request: Request): await request.receive_body() ``` - ::: ## 路由参数(Path parameters) ---:1 -Sanic 允许模式匹配,并从 URL 中提取值。然后将这些参数作为关键字参数传递到响应函数中。 - -:--:1 - +Sanic 允许模式匹配,并从 URL 中提取值。 These parameters are then injected as keyword arguments in the route handler. :--:1 ```python @app.get("/tag/") async def tag_handler(request, tag): return text("Tag - {}".format(tag)) ``` - :--- ---:1 -您可以为路由参数指定类型,它将在匹配时进行强制类型转换。 - -:--:1 - +You can declare a type for the parameter. This will be enforced when matching, and also will type cast the variable. :--:1 ```python @app.get("/foo/") async def uuid_handler(request, foo_id: UUID): return text("UUID - {}".format(foo_id)) ``` - :--- ### 匹配类型(Supported types) @@ -227,90 +176,49 @@ async def uuid_handler(request, foo_id: UUID): async def handler(request, foo: str): ... ``` - -**使用的正则表达式**: `r"[^/]+")` - -**转换类型**: `str` - -**匹配示例**: - +**使用的正则表达式**: `r"[^/].*?")` - `/path/to/Bob` - `/path/to/Python%203` -::: new v22.3 新特征 - -`str` 将不再匹配空字符串, 更多信息请参照 `strorempty` - -::: - -::: tab "strorempty 🌟" +Beginning in v22.3 `str` will *not* match on empty strings. See `strorempty` for this behavior. -::: new v22.3 新特征 +::: tab PUT ```python @app.route("/path/to/") async def handler(request, foo: str): ... ``` - -**使用正则表达式**: `r"[^/]*")` - -**转换类型**: `str` - -**匹配示例**: - +**使用的正则表达式**: _whatever you insert_ - `/path/to/Bob` - `/path/to/Python%203` - `/path/to/` -与 `str` 不同,`strorempty` 还能够匹配空字符串路径 +Unlike the `str` path parameter type, `strorempty` can also match on an empty string path segment. -::: - -::: tab int +::: warning 注意 ```python @app.route("/path/to/") async def handler(request, foo: int): ... ``` - -**使用的正则表达式**: `r"-?\d+")` - -**转换类型**: `int` - -**匹配示例**: - +**使用的正则表达式**: `r"[a-z0-9]+(?:-[a-z0-9]+)*")` - `/path/to/10` - - `/path/to/-10` - _无法匹配 float,hex,octal,etc 等数字类型。_ - -::: - -::: tab "float" +无法匹配 float,hex,octal,etc 等数字类型。 ```python @app.route("/path/to/") async def handler(request, foo: float): ... ``` - **使用的正则表达式**: `r"-?(?:\d+(?:\.\d*)?|\.\d+)")` - -**转换类型**: `float` - -**匹配示例**: - - `/path/to/10` - `/path/to/-10` - `/path/to/1.5` -在之前版本中,您应该这样写 ``。这种写法将在 v21.12 中被弃用 - -::: - ::: tab alpha ```python @@ -318,40 +226,22 @@ async def handler(request, foo: float): async def handler(request, foo: str): ... ``` - -**使用的正则表达式**: `r"[A-Za-z]+")` - -**转换类型**: `str` - -**匹配示例**: - +**使用的正则表达式**: `r"^([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))"` - `/path/to/Bob` - - `/path/to/Python` -_无法匹配数字,空格以及其他特殊字符。_ - -::: - -::: tab "slug" +该方法 _不_ 不支持 `path` 类型的参数。 ```python @app.route("/path/to/") async def handler(request, article: str): ... ``` - -**使用的正则表达式**: `r"[a-z0-9]+(?:-[a-z0-9]+)*")` - -**类型转换**: `str` - -**匹配示例**: - +**使用的正则表达式**: `r"[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}"` - `/path/to/some-news-story` - `/path/to/or-has-digits-123` -::: - +*Added in v21.6* ::: ::: tab path ```python @@ -359,40 +249,20 @@ async def handler(request, article: str): async def handler(request, foo: str): ... ``` - -**使用的正则表达式**: `r"[^/].*?")` - -**转换类型**: `str` - -**匹配示例**: - +**使用的正则表达式**: n/a - `/path/to/hello` - `/path/to/hello.txt` - `/path/to/hello/world.txt` -::: warning - -因为这将从 `/` 开始进行匹配,所以您应该小心使用,并测试您的正则表达式是否正确,以免匹配错误而调用了错误的响应函数。 - -::: - -::: tab ymd +::: warning Because this will match on `/`, you should be careful and thoroughly test your patterns that use `path` so they do not capture traffic intended for another endpoint. ::: tab "ext 🌟" ```python @app.route("/path/to/") async def handler(request, foo: datetime.date): ... ``` - -**使用的正则表达式**: `r"^([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))"` - -**转换类型**: `datetime.date` - -**匹配示例**: - -- `/path/to/2021-03-28` - -::: +**类型转换**: `str` +- /path/to/2021-03-28 ::: tab uuid @@ -401,73 +271,177 @@ async def handler(request, foo: datetime.date): async def handler(request, foo: UUID): ... ``` - -**使用的正则表达式**: `r"[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}"` - -**转换类型**: `UUID` - -**匹配示例**: - -`/path/to/123a123a-a12a-1a1a-a1a1-1a12a1a12345` +**转换类型**: _varies_ +- `/path/to/123a123a-a12a-1a1a-a1a1-1a12a1a12345` ::: -::: tab "ext 🌟" - -::: new v22.3 新特征 +**使用的正则表达式**: `r"[^/]+")` ```python @app.route("/path/to/") -async def handler(request, foo: UUID): +async def handler(request, foo: str, ext: str): ... ``` +**转换类型**: `str` -**使用的正则表达式**: n/a - -**转换类型**: _varies_ - -**匹配示例**: - -| 定义 | 示例 | 文件名 | 拓展名 | -| --------------------------------- | ----------- | -------- | ---------- | -| \ | page.txt | `"page"` | `"txt"` | -| \ | cat.jpg | `"cat"` | `"jpg"` | -| \ | cat.jpg | `"cat"` | `"jpg"` | -| | 123.txt | `123` | `"txt"` | -| | 123.svg | `123` | `"svg"` | -| | 3.14.tar.gz | `3.14` | `"tar.gz"` | - -可以使用特殊的 `ext` 参数类型匹配文件扩展名。它使用一种特殊的格式,允许您指定其他类型的参数类型作为文件名,以及一个或多个特定的扩展名,如上表所示。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 定义 + + 示例 + + 文件名 + + 拓展名 +
+ \ + + page.txt + + "page" + + "txt" +
+ \ + + cat.jpg + + "cat" + + "jpg" +
+ \ + + + png\ + + gif\ + + svg> | cat.jpg | "cat" | "jpg" +
+ + + 123.txt + + 123 + + "txt" +
+ + + + png\ + + gif\ + + svg> | 123.svg | 123 | "svg" +
+ + + 3.14.tar.gz + + 3.14 + + "tar.gz" +
+ +可以使用特殊的 `ext` 参数类型匹配文件扩展名。 它使用一种特殊的格式,允许您指定其他类型的参数类型作为文件名,以及一个或多个特定的扩展名,如上表所示。 -该方法 _不_ 不支持 `path` 类型的参数。 +::: new v22.3 新特征 -::: +`str` 将不再匹配空字符串, 更多信息请参照 `strorempty` ::: tab regex ```python -@app.route("/path/to/") +@app.route(r"/path/to/") async def handler(request, foo: str): ... ``` - -**使用的正则表达式**: _whatever you insert_ - -**转换类型**: `str` - -**匹配示例**: - +::: tab "strorempty 🌟" - `/path/to/2021-01-01` -该方法允许您使用自定义的匹配模式,在上面的示例中,我们通过指定的正则表达式,来匹配符合 `YYYY-MM-DD` 格式的路由参数。 +::: new v22.3 新特征 + +**使用正则表达式**: `r"[^/]*")` :::: ### 正则匹配(Regex Matching) -更多时候,相对于复杂的路由,以上示例还是过于简单了,由我们使用了和以前完全不同的路由匹配模式,所以在这里我们要详细的说明一下正则的进阶用法。 -有时,您希望匹配路由中的某一部分: + +**匹配示例**: + +与 `str` 不同,`strorempty` 还能够匹配空字符串路径 ```text /image/123456789.jpg @@ -479,7 +453,7 @@ async def handler(request, foo: str): app.route(r"/image/\d+)\.jpg>") ``` -更进一步,下面的这些匹配方式都是支持的: +::: tab int ```python @app.get(r"/") # 全模式匹配 @@ -488,24 +462,20 @@ app.route(r"/image/\d+)\.jpg>") @app.get(r"/[a-z]{3}).(?:txt)>") # 用一个或多个不匹配组定义单个命名匹配组 ``` -值得注意的是,如果您使用了命名的匹配组,它的名称必须与 `label` 相同 +**使用的正则表达式**: `r"-?\d+")` ```python @app.get(r"/\d+).jpg>") # 正确示例 @app.get(r"/\d+).jpg>") # 错误示例 ``` -更多的用方法请参考:[正则表达式操作](https://docs.python.org/zh-cn/3/library/re.html) +**转换类型**: `int` ## 动态访问(Generating a URL) ---:1 -Sanic 提供了一种基于处理程序方法名生成 url 的方法:`app.url_for()`,您只需要函数名称即可实现响应函数之间的处理权力的移交。在您不希望将 url -进行硬编码或希望响应函数之间具有层级关系的时候,这将非常有用。它的使用方法如下: - -:--:1 - +Sanic 提供了一种基于处理程序方法名生成 url 的方法:`app.url_for()`,您只需要函数名称即可实现响应函数之间的处理权力的移交。 This is useful if you want to avoid hardcoding url paths into your app; instead, you can just reference the handler name. :--:1 ```python @app.route('/') async def index(request): @@ -520,15 +490,11 @@ async def index(request): async def post_handler(request, post_id): ... ``` - :--- ---:1 -您可以传递任意数量的关键字参数,任何非路由参数的部分都会被是做为查询字符串的一部分 - -:--:1 - +You can pass any arbitrary number of keyword arguments. Anything that is _not_ a request parameter will be implemented as a part of the query string. :--:1 ```python >> > app.url_for( "post_handler", @@ -538,15 +504,11 @@ async def post_handler(request, post_id): ) '/posts/5?arg_one=one&arg_two=two' ``` - :--- ---:1 -该方法同样支持为一个键名传递多个值。 - -:--:1 - +该方法同样支持为一个键名传递多个值。 :--:1 ```python >> > app.url_for( "post_handler", @@ -555,12 +517,11 @@ async def post_handler(request, post_id): ) '/posts/5?arg_one=one&arg_one=two' ``` - :--- ### 特殊关键字参数(Special keyword arguments) -您可以在 [API Docs](https://sanic.readthedocs.io/en/stable/sanic/api_reference.html#sanic.app.Sanic.url_for) 查看更多详细信息。 +**使用的正则表达式**: `r"[A-Za-z]+")` ```python >> > app.url_for("post_handler", post_id=5, arg_one="one", _anchor="anchor") @@ -584,39 +545,28 @@ async def post_handler(request, post_id): ---:1 -在注册路由的时候,可以通过给定 `name` 参数来自定义路由名称 - -:--:1 - +在注册路由的时候,可以通过给定 `name` 参数来自定义路由名称 :--:1 ```python @app.get("/get", name="get_handler") def handler(request): return text("OK") ``` - :--- ---:1 -现在,您可以通过自定义的名称进行路由匹配。 - -:--:1 - +::: tab "slug" ```python >> > app.url_for("get_handler", foo="bar") '/get?foo=bar' ``` - :--- ## Websocket 路径(Websockets routes) ---:1 -Websocket 的工作方式和 HTTP 是类似的。 - -:--:1 - +Websocket 的工作方式和 HTTP 是类似的。 :--:1 ```python async def handler(request, ws): messgage = "Start" @@ -627,15 +577,11 @@ async def handler(request, ws): app.add_websocket_route(handler, "/test") ``` - :--- ---:1 -它也具备有一个独立的装饰器。 - -:--:1 - +它也具备有一个独立的装饰器。 :--:1 ```python @app.websocket("/test") async def handler(request, ws): @@ -644,16 +590,16 @@ async def handler(request, ws): await ws.send(message) message = ws.recv() ``` - :--- -具体的工作原理,我们会在之后的 [websocket](/zh/guide/advanced/websockets.md) 进行更多描述。 +**匹配示例**: ## 严格匹配分隔符(Strict slashes) + ---:1 -Sanic 可以按需开启或关闭路由的严格匹配模式,开启后路由将会严格按照 `/` 作为分隔来进行路由匹配,您可以在以下几种方法中进行匹配,它们的优先级遵循: +Sanic 可以按需开启或关闭路由的严格匹配模式,开启后路由将会严格按照 `/` 作为分隔来进行路由匹配,您可以在以下几种方法中进行匹配,它们的优先级遵循: This can be configured at a few levels and follows this order of precedence: 1. 路由(Route) 2. 蓝图(Blueprint) @@ -661,7 +607,6 @@ Sanic 可以按需开启或关闭路由的严格匹配模式,开启后路由 4. 应用(Application) :--:1 - ```python # 为应用程序下所有的路由都启用严格匹配模式 app = Sanic(__file__, strict_slashes=True) @@ -696,49 +641,36 @@ bp2 = Blueprint( # set the strict slashes check to false group = Blueprint.group([bp1, bp2], strict_slashes=True) ``` - :--- ## 静态文件(Static files) ---:1 -为了确保 Sanic 可以正确代理静态文件,请使用 `app.static()` 方法进行路由分配。 - -在这里,参数的顺序十分重要 - -第一个参数是静态文件所需要匹配的路由 - -第二个参数是渲染文件所在的文件(夹)路径 +**转换类型**: `datetime.date` -更多详细用法请参考 [API docs]() +**匹配示例**: -:--:1 +1. Route the files will be served from +2. Path to the files on the server +更多详细用法请参考 [API docs]() :--:1 ```python app.static("/static", "/path/to/directory") ``` - :--- ---:1 -您也可以提供单独的文件 - -:--:1 - +您也可以提供单独的文件 :--:1 ```python app.static("/", "/path/to/index.html") ``` - :--- ---:1 -它同样支持自定义名称,来帮助您实现快速访问 - -:--:1 - +It is also sometimes helpful to name your endpoint :--:1 ```python app.static( "/user/uploads", @@ -746,53 +678,41 @@ app.static( name="uploads", ) ``` - :--- ---:1 -检索 URL 的流程和响应函数类似,但是当您需要特定的文件的时候,可以通过添加 `filename` 参数来达到效果。 - -:--:1 - -````python ->> > app.url_for( +Retrieving the URLs works similar to handlers. But, we can also add the `filename` argument when we need a specific file inside a directory. :--:1 +```python +>>> app.url_for( "static", name="static", filename="file.txt", ) '/static/file.txt' - -​```python ->> > app.url_for( +``` +```python +>>> app.url_for( "static", name="uploads", filename="image.png", ) '/user/uploads/image.png' -```` - +``` :--- -::: tip - -如果您想要设置多个静态文件路由,我们*强烈建议*您手动为 `static()` 加上 `name` 参数。可以确定的是,这样做可以减少一些潜在且隐蔽的 bug。 +如果您想要设置多个静态文件路由,我们*强烈建议*您手动为 `static()` 加上 `name` 参数。 可以确定的是,这样做可以减少一些潜在且隐蔽的 bug。 ```python app.static("/user/uploads", "/path/to/uploads", name="uploads") app.static("/user/profile", "/path/to/profile", name="profile_pics") - ``` +::: ## 路由上下文(Route context) ----:1 - -定义路由时,您可以添加任意数量的带有 `ctx_` 前缀的关键字参数。这些值将被注入到路由的 `ctx` 对象中。 - -:--:1 - +定义路由时,您可以添加任意数量的带有 `ctx_` 前缀的关键字参数。 这些值将被注入到路由的 `ctx` 对象中。 :--:1 ```python @app.get("/1", ctx_label="something") async def handler1(request): @@ -810,6 +730,18 @@ async def handler99(request): async def do_something(request): if request.route.ctx.label == "something": ... -``` -:--- +@app.get("/2", ctx_label="something") +async def handler2(request): + ... + +@app.get("/99") +async def handler99(request): + ... + +@app.on_request +async def do_something(request): + if request.route.ctx.label == "something": + ... +``` +:--- *Added in v21.12* diff --git a/src/zh/guide/basics/tasks.md b/src/zh/guide/basics/tasks.md index 9459dc1a06..be7684f946 100644 --- a/src/zh/guide/basics/tasks.md +++ b/src/zh/guide/basics/tasks.md @@ -1,23 +1,34 @@ # 后台任务(Background tasks) ## 创建任务(Creating Tasks) - -在异步 Python 里使用 [任务](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task) 是非常简单方便的,Sanic 提供了一种简单的方法来将任务添加到**当前运行**的 loop 当中, 它有点类似于 `asyncio.create_task`。关于在 "应用程序" loop 运行之前添加任务,请参见下一节。 +在异步 Python 里使用 [任务](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task) 是非常简单方便的,Sanic 提供了一种简单的方法来将任务添加到**当前运行**的 loop 当中, 它有点类似于 `asyncio.create_task`。 Sanic provides a convenient method to add tasks to the currently **running** loop. It is somewhat similar to `asyncio.create_task`. 关于在 "应用程序" loop 运行之前添加任务,请参见下一节。 ```python -async def notify_server_started_after_five_seconds(): - await asyncio.sleep(5) - print('Server successfully started!') +async def receiver(ws): + while True: + message = await ws.recv() + if not message: + break + print(f"Received: {message}") -app.add_task(notify_server_started_after_five_seconds()) +@app.websocket("/feed") +async def feed(request, ws): + task_name = f"receiver:{request.id}" + request.app.add_task(receiver(ws), name=task_name) + try: + while True: + await request.app.event("my.custom.event") + await ws.send("A message") + finally: + # 当 websocket 关闭连接的时候,我们可以清除所有的任务。 + +await request.app.cancel_task(task_name) + request.app.purge_tasks() ``` ---:1 -Sanic 将尝试自动注入该应用程序,并将应用程序作为任务的参数进行传递。 - -:--:1 - +Sanic 将尝试自动注入该应用程序,并将应用程序作为任务的参数进行传递。 :--:1 ```python async def auto_inject(app): await asyncio.sleep(5) @@ -25,15 +36,11 @@ async def auto_inject(app): app.add_task(auto_inject) ``` - :--- ---:1 -或者您可以选择显式的将 `app` 作为传递参数。 - -:--:1 - +或者您可以选择显式的将 `app` 作为传递参数。 :--:1 ```python async def explicit_inject(app): await asyncio.sleep(5) @@ -41,113 +48,73 @@ async def explicit_inject(app): app.add_task(explicit_inject(app)) ``` - :--- ## 在 `app.run` 之前添加任务(Adding tasks before `app.run`) -在 app 运行前是可以添加后台任务的。为了完成这个目的,您应该为 `Sanic.add_task` 方法传入一个可调用的对象,即使用 `async` 定义的函数本身,而不是一个协程对象(调用 `async` 函数后得到的对象),当传入可调用对象之后,Sanic 将为 **每一个** 子程序都创建该任务。 - -注意:这样添加的任务是在 `before_server_start` 阶段工作的,因此在每个子进程都会有该任务(而不是在主进程中)。 - -这将会产生一些后果:请查看[这个问题](https://github.com/sanic-org/sanic/issues/2139)的[这条评论](https://github.com/sanic-org/sanic/issues/2139#issuecomment-868993668)来获取更多信息。 +It is possible to add background tasks before the App is run ie. before `app.run`. 在 app 运行前是可以添加后台任务的。 为了完成这个目的,您应该为 `Sanic.add_task` 方法传入一个可调用的对象,即使用 `async` 定义的函数本身,而不是一个协程对象(调用 `async` 函数后得到的对象),当传入可调用对象之后,Sanic 将为 **每一个** 子程序都创建该任务。 Note: the tasks that are added such are run as `before_server_start` jobs and thus run on every worker (and not in the main process). 这将会产生一些后果:请查看[这个问题](https://github.com/sanic-org/sanic/issues/2139)的[这条评论](https://github.com/sanic-org/sanic/issues/2139#issuecomment-868993668)来获取更多信息。 -要想只为主进程添加任务,您应该考虑使用 [`@app.main_process_start`](./listeners.md)添加任务。注意:任务没完成子进程将无法启动。 +要想只为主进程添加任务,您应该考虑使用 [`@app.main_process_start`](./listeners.md)添加任务。 注意:任务没完成子进程将无法启动。 ---:1 -示例代码: +注意:这样添加的任务是在 `before_server_start` 阶段工作的,因此在每个子进程都会有该任务(而不是在主进程中)。 +```python +async def slow_work(): + ... -:---:1 +async def even_slower(num): + ... -```python +app = Sanic(...) async def slow_work(...): ... app = Sanic(...) app.add_task(slow_work) # Note: we are passing the callable and not coroutine object `slow_work(...)` app.run(...) - +app.add_task(even_slower(10)) # ... or we can call the function and pass the coroutine. +app.run(...) ``` -::: tip - -如果您想要为 `slow_work` 绑定一些参数,可以使用 `functools.partial`。 - -::: - ## 命名任务(Named tasks) _这只适用于 Python3.8 及以上版本_ ----:1 - 当您创建任务的时候,您可以为您的任务指定一个名称,方便您后续进行任务追踪。 :--:1 - ```python app.add_task(slow_work, name="slow_task") ``` - :--- ----:1 - -之后,您可以使用 `get_task` 方法从应用程序的任何地方查看您的任务。 +:---:1 :--:1 - ```python task = app.get_task("slow_task") ``` - :--- ----:1 - -如果您想要取消任务,您可以通过 `cacle_task` 来进行操作,当然,该方法也是异步的,请确保使用时添加了 `await`。 +如果您想要取消任务,您可以通过 `cacle_task` 来进行操作,当然,该方法也是异步的,请确保使用时添加了 `await`。 Make sure that you `await` it. :--:1 - ```python await app.cancel_task("slow_task") ``` - :--- ----:1 - -所有注册的任务都可以在 `app.tasks` 属性中找到。为了防止已取消的任务填满,您可能需要运行 `app.purge_tasks` 来清除所有已完成或已取消的任务。 +所有注册的任务都可以在 `app.tasks` 属性中找到。 为了防止已取消的任务填满,您可能需要运行 `app.purge_tasks` 来清除所有已完成或已取消的任务。 :--:1 - ```python app.purge_tasks() ``` - :--- 这种模式对于 websocket 来说非常有用: -```python -async def receiver(ws): - while True: - message = await ws.recv() - if not message: - break - print(f"Received: {message}") +之后,您可以使用 `get_task` 方法从应用程序的任何地方查看您的任务。 -@app.websocket("/feed") -async def feed(request, ws): - task_name = f"receiver:{request.id}" - request.app.add_task(receiver(ws), name=task_name) - try: - while True: - await request.app.event("my.custom.event") - await ws.send("A message") - finally: - # 当 websocket 关闭连接的时候,我们可以清除所有的任务。 - await request.app.cancel_task(task_name) - request.app.purge_tasks() -``` +@app.websocket("/feed") async def feed(request, ws): task_name = f"receiver:{request.id}" request.app.add_task(receiver(ws), name=task_name) try: while True: await request.app.event("my.custom.event") await ws.send("A message") finally: # When the websocket closes, let's cleanup the task await request.app.cancel_task(task_name) request.app.purge_tasks() ::: *Added in v21.12* diff --git a/src/zh/guide/best-practices/blueprints.md b/src/zh/guide/best-practices/blueprints.md index 5d31e03de8..30f86c74aa 100644 --- a/src/zh/guide/best-practices/blueprints.md +++ b/src/zh/guide/best-practices/blueprints.md @@ -2,18 +2,15 @@ ## 概述(Overview) -蓝图是应用中可以作为子路由的对象。蓝图定义了同样的添加路由的方式,您可以将一系列路由注册到蓝图上而不是直接注册到应用上,然后再以可插拔的方式将蓝图注册到到应用程序。 +蓝图是应用中可以作为子路由的对象。 蓝图定义了同样的添加路由的方式,您可以将一系列路由注册到蓝图上而不是直接注册到应用上,然后再以可插拔的方式将蓝图注册到到应用程序。 -蓝图对于大型应用特别有用。在大型应用中,您可以将应用代码根据不同的业务分解成多个蓝图。 +Blueprints are especially useful for larger applications, where your application logic can be broken down into several groups or areas of responsibility. ## 创建和注册蓝图(Creating and registering) ---:1 -首先,您必须先创建一个蓝图。蓝图对象有着和 `Sanic` 对象十分相似的方法,它也提供了相同的装饰器来注册路由。 - -:--:1 - +首先,您必须先创建一个蓝图。 It has a very similar API as the `Sanic()` app instance with many of the same decorators. :--:1 ```python # ./my_blueprint.py from sanic.response import json @@ -26,15 +23,12 @@ bp = Blueprint("my_blueprint") async def bp_root(request): return json({"my": "blueprint"}) ``` - :--- ----:1 - -接下来,您可以将蓝图注册到 Sanic 应用上。 -:--:1 +---:1 +Next, you register it with the app instance. :--:1 ```python from sanic import Sanic from my_blueprint import bp @@ -42,17 +36,13 @@ from my_blueprint import bp app = Sanic(__name__) app.blueprint(bp) ``` - :--- 蓝图也提供了 `websocket()` 装饰器和 `add_websocket_route` 方法来实现 websocket 通讯。 ---:1 -从 v21.12 开始,您可以在向蓝图中添加响应函数之前或之后注册蓝图。以前,只有在注册时附加到蓝图的响应函数才会被加载到应用程序实例中。 - -:--:1 - +从 v21.12 开始,您可以在向蓝图中添加响应函数之前或之后注册蓝图。 Previously, only objects attached to the Blueprint at the time of registration would be loaded into application instance. :--:1 ```python app.blueprint(bp) @@ -60,17 +50,10 @@ app.blueprint(bp) async def bp_root(request): ... ``` - :--- - ## 复制蓝图(Copying) ----:1 - -使用 `copy()` 方法可以将蓝图以及附加到其上的所有内容复制到新实例中。唯一需要的参数是给它传递一个新的 `name`。当然,您也可以使用它来覆盖旧蓝图中的任何值。 - -:--:1 - +---:1 Blueprints along with everything that is attached to them can be copied to new instances using the `copy()` method. 唯一需要的参数是给它传递一个新的 `name`。 当然,您也可以使用它来覆盖旧蓝图中的任何值。 :--:1 ```python v1 = Blueprint("Version1", version=1) @@ -90,12 +73,13 @@ Available routes: /v2/something ``` - :--- +*Added in v21.9* + ## 蓝图组(Blueprint groups) -蓝图也可以以列表或者元组的形式进行注册,在这种情况下,注册时会递归地遍历当前序列,在序列中或者在子序列中的所有蓝图对象都会被注册到应用上。`Blueprint.group` 方法允许模拟一个后端目录结构来简化上述问题。请看这个例子: +蓝图也可以以列表或者元组的形式进行注册,在这种情况下,注册时会递归地遍历当前序列,在序列中或者在子序列中的所有蓝图对象都会被注册到应用上。 `Blueprint.group` 方法允许模拟一个后端目录结构来简化上述问题。 请看这个例子: ```text api/ @@ -113,14 +97,12 @@ app.py #### 第一个蓝图(First blueprint) :--:1 - ```python # api/content/authors.py from sanic import Blueprint authors = Blueprint("content_authors", url_prefix="/authors") ``` - :--- ---:1 @@ -128,14 +110,12 @@ authors = Blueprint("content_authors", url_prefix="/authors") #### 第二个蓝图(Second blueprint) :--:1 - ```python # api/content/static.py from sanic import Blueprint static = Blueprint("content_static", url_prefix="/static") ``` - :--- ---:1 @@ -143,7 +123,6 @@ static = Blueprint("content_static", url_prefix="/static") #### 蓝图组(Blueprint group) :--:1 - ```python # api/content/__init__.py from sanic import Blueprint @@ -152,7 +131,6 @@ from .authors import authors content = Blueprint.group(static, authors, url_prefix="/content") ``` - :--- ---:1 @@ -160,14 +138,12 @@ content = Blueprint.group(static, authors, url_prefix="/content") #### 第三个蓝图(Third blueprint) :--:1 - ```python # api/info.py from sanic import Blueprint info = Blueprint("info", url_prefix="/info") ``` - :--- ---:1 @@ -175,7 +151,6 @@ info = Blueprint("info", url_prefix="/info") #### 另一个蓝图组(Another blueprint group) :--:1 - ```python # api/__init__.py from sanic import Blueprint @@ -184,7 +159,6 @@ from .info import info api = Blueprint.group(content, info, url_prefix="/api") ``` - :--- ---:1 @@ -194,7 +168,6 @@ api = Blueprint.group(content, info, url_prefix="/api") 所有的蓝图都会被注册。 :--:1 - ```python # app.py from sanic import Sanic @@ -203,17 +176,13 @@ from .api import api app = Sanic(__name__) app.blueprint(api) ``` - :--- ## 中间件(Middleware) ---:1 -蓝图也可以有自己的中间件,这些中间件只会影响到注册到该蓝图上的路由。 - -:--:1 - +Blueprints can also have middleware that is specifically registered for its endpoints only. :--:1 ```python @bp.middleware async def print_on_request(request): @@ -229,15 +198,11 @@ async def halt_request(request): async def halt_response(request, response): return text("I halted the response") ``` - :--- ---:1 -同样的,使用蓝图组能够将中间件应用给同组中的所用蓝图。 - -:--:1 - +同样的,使用蓝图组能够将中间件应用给同组中的所用蓝图。 :--:1 ```python bp1 = Blueprint("bp1", url_prefix="/bp1") bp2 = Blueprint("bp2", url_prefix="/bp2") @@ -269,23 +234,18 @@ async def group_middleware(request): # Register Blueprint group under the app app.blueprint(group) ``` - :--- ## 异常(Exceptions) ---:1 -正如 [异常处理](./exceptions.md) 一章所述,您可以定义蓝图特定的异常响应函数。 - -:--:1 - +正如 [异常处理](./exceptions.md) 一章所述,您可以定义蓝图特定的异常响应函数。 :--:1 ```python @bp.exception(NotFound) def ignore_404s(request, exception): return text("Yep, I totally found the page: {}".format(request.url)) ``` - :--- ## 静态文件(Static files) @@ -293,38 +253,27 @@ def ignore_404s(request, exception): ---:1 蓝图也可以单独指定需要代理的静态文件。 - -:--:1 - ```python bp = Blueprint("bp", url_prefix="/bp") bp.static("/web/path", "/folder/to/serve") bp.static("/web/path", "/folder/to/server", name="uploads") ``` - :--- ---:1 -然后文件可以使用 `url_for()` 函数来获取。详见 [路由](/zh/guide/basics/routing.md) 章节。 - -:--:1 - +然后文件可以使用 `url_for()` 函数来获取。 详见 [路由](/zh/guide/basics/routing.md) 章节。 :--:1 ```python >>> print(app.url_for("static", name="bp.uploads", filename="file.txt")) '/bp/web/path/file.txt' ``` - :--- ## 监听器(Listeners) ---:1 -蓝图也可以实现 [监听器](/zh/guide/basics/listeners.md)。 - -:--:1 - +蓝图也可以实现 [监听器](/zh/guide/basics/listeners.md)。 :--:1 ```python @bp.listener("before_server_start") async def before_server_start(app, loop): @@ -334,8 +283,11 @@ async def before_server_start(app, loop): @bp.listener("after_server_stop") async def after_server_stop(app, loop): ... -``` +@bp.listener("after_server_stop") +async def after_server_stop(app, loop): + ... +``` :--- ## 版本管理(Versioning) @@ -345,22 +297,15 @@ async def after_server_stop(app, loop): ---:1 `version` 参数会被作为前缀添加到路由上,如 `/v1`,`/v2` 等等。 - -:--:1 - ```python auth1 = Blueprint("auth", url_prefix="/auth", version=1) auth2 = Blueprint("auth", url_prefix="/auth", version=2) ``` - :--- ---:1 -当我们将蓝图注册到APP上时,`/v1/auth` 和 `/v2/auth` 路由将指向两个不同的蓝图,这就允许您为每个 API 版本创建子路由。 - -:--:1 - +当我们将蓝图注册到APP上时,`/v1/auth` 和 `/v2/auth` 路由将指向两个不同的蓝图,这就允许您为每个 API 版本创建子路由。 :--:1 ```python from auth_blueprints import auth1, auth2 @@ -368,15 +313,11 @@ app = Sanic(__name__) app.blueprint(auth1) app.blueprint(auth2) ``` - :--- ---:1 -您也可以将多个蓝图放在一个蓝图组下然后同时为他们添加上版本信息。 - -:--:1 - +It is also possible to group the blueprints under a `BlueprintGroup` entity and version multiple of them together at the same time. :--:1 ```python auth = Blueprint("auth", url_prefix="/auth") metrics = Blueprint("metrics", url_prefix="/metrics") @@ -386,19 +327,13 @@ group = Blueprint.group(auth, metrics, version="v1") # This will provide APIS prefixed with the following URL path # /v1/auth/ and /v1/metrics ``` - :--- ## 组合(Composable) -一个蓝图对象可以被多个蓝图组注册,且蓝图组之间可以进行嵌套注册。这样就消除了蓝图之间组合的限制。 - ----:1 - -请看下面的例子,看看两个响应函数是如何被注册到不同的五个路由上的。 - -:--:1 +A `Blueprint` may be registered to multiple groups, and each of `BlueprintGroup` itself could be registered and nested further. This creates a limitless possibility `Blueprint` composition. +*Added in v21.6* ---:1 Take a look at this example and see how the two handlers are actually mounted as five (5) distinct routes. :--:1 ```python app = Sanic(__name__) blueprint_1 = Blueprint("blueprint_1", url_prefix="/bp1") @@ -436,9 +371,9 @@ app.blueprint(blueprint_1) # /bp1 ``` - :--- + ## URL 生成(Generating a URL) 当使用 `url_for()` 来生成 URL 时,端点的名称将以以下格式来组织: diff --git a/src/zh/guide/best-practices/decorators.md b/src/zh/guide/best-practices/decorators.md index 6d7ad6bf41..ecb2954946 100644 --- a/src/zh/guide/best-practices/decorators.md +++ b/src/zh/guide/best-practices/decorators.md @@ -4,10 +4,7 @@ ---:1 -因此,在 Sanic 的视图函数上使用多个装饰器是十分常见的。 - -:--:1 - +因此,在 Sanic 的视图函数上使用多个装饰器是十分常见的。 :--:1 ```python @app.get("/orders") @authorized("view_order") @@ -16,15 +13,79 @@ async def get_order_details(request, params, user): ... ``` - :--- -### 例子(Example) +## 例子(Example) 这里有一个入门模板来帮助您创建装饰器。 -在下面的例子中,假设您想去检查某个用户是否对特定的路由有访问的权限。您可以创建一个装饰器来装饰一个响应函数,检查发送请求的客户端是否有权限来访问该资源,并返回正确的响应。 +在下面的例子中,假设您想去检查某个用户是否对特定的路由有访问的权限。 您可以创建一个装饰器来装饰一个响应函数,检查发送请求的客户端是否有权限来访问该资源,并返回正确的响应。 +```python +from functools import wraps +from sanic.response import json + +def authorized(): + def decorator(f): + @wraps(f) + async def decorated_function(request, *args, **kwargs): + # run some method that checks the request + # for the client's authorization status + is_authorized = await check_request_for_authorization_status(request) + + if is_authorized: + # the user is authorized. + # run the handler method and return the response + response = await f(request, *args, **kwargs) + return response + else: + # the user is not authorized. + return json({"status": "not_authorized"}, 403) + return decorated_function + return decorator + + +@app.route("/") +@authorized() +async def test(request): + return json({"status": "authorized"}) +``` + +## Templates + +Decorators are **fundamental** to building applications with Sanic. They increase the portability and maintainablity of your code. + +In paraphrasing the Zen of Python: "[decorators] are one honking great idea -- let's do more of those!" + +To make it easier to implement them, here are three examples of copy/pastable code to get you started. + +---:1 + +Don't forget to add these import statements. Although it is *not* necessary, using `@wraps` helps keep some of the metadata of your function intact. [See docs](https://docs.python.org/3/library/functools.html#functools.wraps). Also, we use the `isawaitable` pattern here to allow the route handlers to by regular or asynchronous functions. + +:--:1 + +```python +from inspect import isawaitable +from functools import wraps +``` + +:--- + +### With args + +---:1 + +Often, you will want a decorator that will *always* need arguments. Therefore, when it is implemented you will always be calling it. + +```python +@app.get("/") +@foobar(1, 2) +async def handler(request: Request): + return text("hi") +``` + +:--:1 ```python from functools import wraps @@ -55,3 +116,80 @@ def authorized(): async def test(request): return json({"status": "authorized"}) ``` + +:--- + +### Without args + +---:1 + +Sometimes you want a decorator that will not take arguments. When this is the case, it is a nice convenience not to have to call it + +```python +@app.get("/") +@foobar +async def handler(request: Request): + return text("hi") +``` + +:--:1 + +```python +def foobar(func): + def decorator(f): + @wraps(f) + async def decorated_function(request, *args, **kwargs): + + response = f(request, *args, **kwargs) + if isawaitable(response): + response = await response + + return response + + return decorated_function + + return decorator(func) +``` + +:--- + +### With or Without args + +---:1 + +If you want a decorator with the ability to be called or not, you can follow this pattern. Using keyword only arguments is not necessary, but might make implementation simpler. + +```python +@app.get("/") +@foobar(arg1=1, arg2=2) +async def handler(request: Request): + return text("hi") +``` + +```python +@app.get("/") +@foobar +async def handler(request: Request): + return text("hi") +``` + +:--:1 + +```python +def foobar(maybe_func=None, *, arg1=None, arg2=None): + def decorator(f): + @wraps(f) + async def decorated_function(request, *args, **kwargs): + + response = f(request, *args, **kwargs) + if isawaitable(response): + response = await response + + return response + + return decorated_function + + return decorator(maybe_func) if maybe_func else decorator +``` + +:--- diff --git a/src/zh/guide/best-practices/exceptions.md b/src/zh/guide/best-practices/exceptions.md index 3c6cabd4ed..90a87ec4c0 100644 --- a/src/zh/guide/best-practices/exceptions.md +++ b/src/zh/guide/best-practices/exceptions.md @@ -2,9 +2,9 @@ ## 使用 Sanic 内置异常(Using Sanic exceptions) -有时,您只需要告诉 Sanic 终止执行响应函数,并返回一个状态码。此时,您可以抛出 `SanicException` 异常,之后,Sanic 将为您自动完成剩下的工作。 +有时,您只需要告诉 Sanic 终止执行响应函数,并返回一个状态码。 此时,您可以抛出 `SanicException` 异常,之后,Sanic 将为您自动完成剩下的工作。 -您可以选择传递一个参数 `status_code`。默认情况下,如果您不传递该参数,SanicException 将会返回一个 HTTP 500 内部服务错误的响应 +您可以选择传递一个参数 `status_code`。 默认情况下,如果您不传递该参数,SanicException 将会返回一个 HTTP 500 内部服务错误的响应 ```python from sanic.exceptions import SanicException @@ -14,7 +14,7 @@ async def no_no(request): raise SanicException("Something went wrong.", status_code=501) ``` -Sanic 预置了许多标准异常。它们每个都会在您的响应中自动触发适当的 HTTP 状态代码。查看 [接口文档](https://sanic.readthedocs.io/en/latest/sanic/api_reference.html#module-sanic.exceptions) 了解更多详细信息。 +Sanic 预置了许多标准异常。 它们每个都会在您的响应中自动触发适当的 HTTP 状态代码。 查看 [接口文档](https://sanic.readthedocs.io/en/latest/sanic/api_reference.html#module-sanic.exceptions) 了解更多详细信息。 ---:1 @@ -45,7 +45,7 @@ async def login(request): ## 异常属性(Exception properties) -在 Sanic 中,所有异常都继承自 `SanicException`。该类有一些属性,可以帮助开发人员在整个应用程序中一致地报告异常。 +在 Sanic 中,所有异常都继承自 `SanicException`。 该类有一些属性,可以帮助开发人员在整个应用程序中一致地报告异常。 - `message` - `status_code` @@ -56,13 +56,9 @@ async def login(request): 所有这些属性都可以在异常创建时传递给它,但是前三个将会作为可见的信息被返回出来。 ---:1 +### `message` -### 信息属性(`message`) - -`message` 属性控制着 Python 中任何其他异常显示的消息。您可以在类定义上设置 `message` 属性,以便在整个应用程序中轻松实现语言的标准化。 - -:--:1 - +`message` 属性控制着 Python 中任何其他异常显示的消息。 您可以在类定义上设置 `message` 属性,以便在整个应用程序中轻松实现语言的标准化。 ```python class CustomError(SanicException): message = "Something bad happened" @@ -71,17 +67,12 @@ raise CustomError # or raise CustomError("Override the default message with something else") ``` - :--- ---:1 +### `status_code` -### 状态码属性(`status_code`) - -该属性用于设置异常时的响应码。这在创建自定义 400 系列异常时特别有用,这些异常通常是为了响应来自客户端的错误信息。 - -:--:1 - +该属性用于设置异常时的响应码。 这在创建自定义 400 系列异常时特别有用,这些异常通常是为了响应来自客户端的错误信息。 :--:1 ```python class TeapotError(SanicException): status_code = 418 @@ -91,17 +82,12 @@ raise TeapotError # or raise TeapotError(status_code=400) ``` - :--- ---:1 +### `quiet` -### 静默输出(`quiet`) - -默认情况下,Sanic 会将异常输出到 `error_logger`。有时这可能并不理想,尤其是当您使用异常来触发异常处理程序中的事件时(参见[下一节](./exceptions.md#handling))。您可以使用`quiet=True` 来禁止日志输出。 - -:--:1 - +默认情况下,Sanic 会将异常输出到 `error_logger`。 有时这可能并不理想,尤其是当您使用异常来触发异常处理程序中的事件时(参见[下一节](./exceptions.md#handling))。 您可以使用`quiet=True` 来禁止日志输出。 :--:1 ```python class SilentError(SanicException): message = "Something happened, but not shown in logs" @@ -111,52 +97,42 @@ raise SilentError # or raise InvalidUsage("blah blah", quiet=True) ``` - :--- ----:1 - -有时在调试时,您可能希望全局忽略 `quiet=True` 属性。您可以使用 `NOISY_EXCEPTIONS` 来忽略 `quiet` 的设置,强制 Sanic 注销所有异常。 - -:--:1 +---:1 Sometimes while debugging you may want to globally ignore the `quiet=True` property. 您可以使用 `NOISY_EXCEPTIONS` 来忽略 `quiet` 的设置,强制 Sanic 注销所有异常。 +*Added in v21.12* :--:1 ```python app.config.NOISY_EXCEPTIONS = True ``` - :--- ---:1 - -### 额外属性(`extra`) +### `extra` 请参考 [上下文异常](./exceptions.md#contextual-exceptions) -:--:1 - +*Added in v21.12* :--:1 ```python raise SanicException(..., extra={"name": "Adam"}) ``` - :--- ---:1 - -### 上下文(`context`) +### `context` 请参考 [上下文异常](./exceptions.md#contextual-exceptions) -:--:1 - +*Added in v21.12* :--:1 ```python raise SanicException(..., context={"foo": "bar"}) ``` - :--- + ## 处理(Handling) -Sanic 通过呈现错误页面来自动处理异常,因此在许多情况下,您不需要自己处理它们。但是,如果您希望在引发异常时更多地控制该做什么,您同样可以自己实现一个处理程序。 +Sanic 通过呈现错误页面来自动处理异常,因此在许多情况下,您不需要自己处理它们。 但是,如果您希望在引发异常时更多地控制该做什么,您同样可以自己实现一个处理程序。 Sanic 为此提供了一个装饰器,它不仅适用于 Sanic 标准异常,还适用于您的应用程序可能抛出的**任何**异常。 @@ -207,14 +183,11 @@ app.error_handler.add(Exception, server_error_handler) ## 内置异常处理(Built-in error handling) -Sanic 支持三种不同的异常格式: HTML、JSON 和 TEXT。您可以在下面的 [异常格式](#fallback-handler) 一节中看到它们的示例。 +Sanic 支持三种不同的异常格式: HTML、JSON 和 TEXT。 您可以在下面的 [异常格式](#fallback-handler) 一节中看到它们的示例。 ----:1 - -您可以通过设置 `error_format` 关键字参数来控制每一个路由所使用的异常格式。 - -:--:1 +---:1 You can control _per route_ which format to use with the `error_format` keyword argument. +*Added in v21.9* :--:1 ```python @app.request("/", error_format="text") async def handler(request): @@ -223,9 +196,10 @@ async def handler(request): :--- + ## 自定义异常处理(Custom error handling) -在某些情况下,您可能希望在默认设置的基础上增加更多的错误处理功能。在这种情况下,您可以将 Sanic 的默认错误处理程序子类化,例如: +在某些情况下,您可能希望在默认设置的基础上增加更多的错误处理功能。 在这种情况下,您可以将 Sanic 的默认错误处理程序子类化,例如: ```python from sanic.handlers import ErrorHandler @@ -236,13 +210,15 @@ class CustomErrorHandler(ErrorHandler): # You custom error handling logic... return super().default(request, exception) +app.error_handler = CustomErrorHandler() + return super().default(request, exception) + app.error_handler = CustomErrorHandler() ``` ## 异常格式(Fallback handler) -Sanic comes with three fallback exception handlers: -Sanic 自带了三种异常格式。 +Sanic comes with three fallback exception handlers: Sanic 自带了三种异常格式。 1. HTML (_default_) 2. Text @@ -297,6 +273,20 @@ content-type: text/plain; charset=utf-8 ============================== That time when that thing broke that other thing? That happened. +ServerError: That time when that thing broke that other thing? That happened. while handling path /exc +Traceback of __BASE__ (most recent call last): + + ServerError: That time when that thing broke that other thing? That happened. + $ curl localhost:8000/exc -i +HTTP/1.1 500 Internal Server Error +content-length: 590 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +⚠️ 500 — Internal Server Error +============================== +That time when that thing broke that other thing? That happened. + ServerError: That time when that thing broke that other thing? That happened. while handling path /exc Traceback of __BASE__ (most recent call last): @@ -323,7 +313,7 @@ content-type: text/plain; charset=utf-8 ⚠️ 500 — Internal Server Error ============================== -That time when that thing broke that other thing? That happened. +That time when that thing broke that other thing? That happened. That happened. ``` :--- @@ -395,6 +385,7 @@ content-type: application/json "description": "Internal Server Error", "status": 500, "message": "That time when that thing broke that other thing? That happened." +} That happened." } ``` @@ -403,12 +394,11 @@ content-type: application/json ### Auto -Sanic 还提供了一个选项,用于猜测使用哪种异常格式。该功能依旧处于 **试验阶段**。 +Sanic 还提供了一个选项,用于猜测使用哪种异常格式。 该功能依旧处于 **试验阶段**。 ```python app.config.FALLBACK_ERROR_FORMAT = "auto" ``` - ## 上下文异常(Contextual Exceptions) 默认异常消息简化了在整个应用程序中一致引发异常的能力。 @@ -421,14 +411,16 @@ class TeapotError(SanicException): raise TeapotError ``` -但这样会忽视两个非常重要的问题: +But this lacks two things: 1. 如何设置动态且可预测的消息格式 2. 如何向错误消息中添加额外上下文内容(稍后将详细介绍) +*Added in v21.12* + ### 使用 `extra` 来设置动态且可预测的消息(Dynamic and predictable message using `extra`) -Sanic 异常可以使用 `extra` 属性来获取传入的额外信息,例如: +Sanic exceptions can be raised using `extra` keyword arguments to provide additional information to a raised exception instance. ```python class TeapotError(SanicException): @@ -441,35 +433,25 @@ class TeapotError(SanicException): raise TeapotError(extra={"name": "Adam"}) ``` -这个新特性允许将 `extra` 元传递给异常实例,这在上面的例子中将动态数据传递给消息文本时非常有用。在生产模式下,`extra` 信息对象 **将被隐藏**,但在开发模式下将s显示。 - ----:1 - -**生产模式** - -![image](https://user-images.githubusercontent.com/166269/139014161-cda67cd1-843f-4ad2-9fa1-acb94a59fc4d.png) +这个新特性允许将 `extra` 元传递给异常实例,这在上面的例子中将动态数据传递给消息文本时非常有用。 在生产模式下,`extra` 信息对象 **将被隐藏**,但在开发模式下将s显示。 -:--:1 - -**开发模式** +但这样会忽视两个非常重要的问题: -![image](https://user-images.githubusercontent.com/166269/139014121-0596b084-b3c5-4adb-994e-31ba6eba6dad.png) +![image](https://user-images.githubusercontent.com/166269/139014161-cda67cd1-843f-4ad2-9fa1-acb94a59fc4d.png) Sanic 异常可以使用 `extra` 属性来获取传入的额外信息,例如: -:--- +![image](https://user-images.githubusercontent.com/166269/139014121-0596b084-b3c5-4adb-994e-31ba6eba6dad.png) :--- ### 在异常信息中附加上下文内容(Additional `context` to an error message) -Sanic 异常也可以通过 `context` 参数来引发,方便告诉开发者发生了什么。这对微服务或旨在以 JSON 格式传递错误消息的 API 特别有用。在这个用例中,我们希望有一些关于异常的上下文,而不仅仅是一个可解析的错误消息,以便向客户端返回详细信息。 +Sanic 异常也可以通过 `context` 参数来引发,方便告诉开发者发生了什么。 这对微服务或旨在以 JSON 格式传递错误消息的 API 特别有用。 在这个用例中,我们希望有一些关于异常的上下文,而不仅仅是一个可解析的错误消息,以便向客户端返回详细信息。 ```python raise TeapotError(context={"foo": "bar"}) ``` -`context` 将始终被传递到异常信息中,这正是我们想要看到的,它看起来就像这样: +This is information **that we want** to always be passed in the error (when it is available). Here is what it should look like: ----:1 - -**生产模式** +---:1 **PRODUCTION** ```json { @@ -481,9 +463,7 @@ raise TeapotError(context={"foo": "bar"}) } } ``` -:--:1 - -**开发模式** +:--:1 **DEVELOPMENT** ```json { diff --git a/src/zh/guide/best-practices/logging.md b/src/zh/guide/best-practices/logging.md index 1c13349c9c..87880f9f9a 100644 --- a/src/zh/guide/best-practices/logging.md +++ b/src/zh/guide/best-practices/logging.md @@ -1,15 +1,12 @@ # 日志(Logging) -Sanic 允许您根据请求进行不同类型的记录(访问日志、错误日志)[Python 日志 API](https://docs.python.org/3/howto/logging.html)。如果您想创建一个新的配置,您应该有一些关于 Python logging 的基础知识。 +Sanic 允许您根据请求进行不同类型的记录(访问日志、错误日志)[Python 日志 API](https://docs.python.org/3/howto/logging.html)。 如果您想创建一个新的配置,您应该有一些关于 Python logging 的基础知识。 ## 快速开始(Quick Start) ---:1 使用默认配置的简单示例如下: - -:--:1 - ```python from sanic import Sanic from sanic.log import logger @@ -24,19 +21,19 @@ async def test(request): if __name__ == "__main__": app.run(debug=True, access_log=True) -``` +if __name__ == "__main__": + app.run(debug=True, access_log=True) +``` :--- -在服务器运行后,您应该看到以下的日志信息: - +After the server is running, you should see logs like this. ```text [2021-01-04 15:26:26 +0200] [1929659] [INFO] Goin' Fast @ http://127.0.0.1:8000 [2021-01-04 15:26:26 +0200] [1929659] [INFO] Starting worker [1929659] ``` -您可以尝试向服务器发送请求,之后,您会看到输出如下的日志信息: - +在服务器运行后,您应该看到以下的日志信息: ```text [2021-01-04 15:26:28 +0200] [1929659] [INFO] Here is your log [2021-01-04 15:26:28 +0200] - (sanic.access)[INFO][127.0.0.1:44228]: GET http://localhost:8000/ 200 -1 @@ -44,7 +41,7 @@ if __name__ == "__main__": ## 自定义日志(Changing Sanic loggers) -如果要使用自己的日志配置,只需使用 `logging.config.dictConfig`,或者在初始化 Sanic app 时传递 `log_config` 即可。 +您可以尝试向服务器发送请求,之后,您会看到输出如下的日志信息: ```python app = Sanic('logging_example', log_config=LOGGING_CONFIG) @@ -53,44 +50,37 @@ if __name__ == "__main__": app.run(access_log=False) ``` -::: tip FYI 小提示 +在 Python 中处理日志是一个比较轻松的操作,但是如果您需要处理大量的请求,那么性能可能回成为一个瓶颈。 添加访问日志的耗时将会增加,这将会增大您的系统开销。 -在 Python 中处理日志是一个比较轻松的操作,但是如果您需要处理大量的请求,那么性能可能回成为一个瓶颈。添加访问日志的耗时将会增加,这将会增大您的系统开销。 +This is a good opportunity to place Sanic behind a proxy (like nginx) and to do your access logging there. You will see a *significant* increase in overall performance by disabling the `access_log`. -使用 Nginx 记录访问日志是一个减轻系统开销的好办法,将 Sanic 部署在 Nginx 代理之后,并禁用 Sanic 的 `access_log`,您将能够看到性能的显著提升 - -为了在生产环境下获得最佳性能,建议在禁用 `debug` 和 `access_log` 的情况下运行Sanic:`app.run(debug=False, access_log=False)` - -::: +如果要使用自己的日志配置,只需使用 `logging.config.dictConfig`,或者在初始化 Sanic app 时传递 `log_config` 即可。 ## 配置(Configuration) -Sanic 的默认认知配置为:`sanic.log.LOGGING_CONFIG_DEFAULTS`。 - ----:1 +使用 Nginx 记录访问日志是一个减轻系统开销的好办法,将 Sanic 部署在 Nginx 代理之后,并禁用 Sanic 的 `access_log`,您将能够看到性能的显著提升 -Sanic 使用了三个日志器,如果您想要创建并使用自己的日志配置,则您需要自定义该配置: +为了在生产环境下获得最佳性能,建议在禁用 `debug` 和 `access_log` 的情况下运行Sanic:`app.run(debug=False, access_log=False)` | **Logger Name** | **Use Case** | -|-----------------|-------------------------------| +| --------------- | ----------------------------- | | `sanic.root` | Used to log inernal messages. | | `sanic.error` | Used to log error logs. | | `sanic.access` | Used to log access logs. | - -:--:1 + :--:1 :--- ### 日志格式(Log format) -除了 Python 提供的默认参数(`asctime`、`levelname`、`message`)之外,Sanic 还为日志器提供了附加参数: +Sanic 的默认认知配置为:`sanic.log.LOGGING_CONFIG_DEFAULTS`。 -| 参数名称 | 参数值 | 参数类型 | -|-----------------------|---------------------------------------|----------| -| `host` | `request.ip` | `str` | -| `request` | `request.method + " " + request.url` | `str` | -| `status` | `response` | `int` | -| `byte` | `len(response.body)` | `int` | +| 参数名称 | 参数值 | 参数类型 | +| --------- | ------------------------------------ | ----- | +| `host` | `request.ip` | `str` | +| `request` | `request.method + " " + request.url` | `str` | +| `status` | `response` | `int` | +| `byte` | `len(response.body)` | `int` | diff --git a/src/zh/guide/deployment/app-loader.md b/src/zh/guide/deployment/app-loader.md new file mode 100644 index 0000000000..960138e3a9 --- /dev/null +++ b/src/zh/guide/deployment/app-loader.md @@ -0,0 +1,75 @@ +# Dynamic Applications + +Running Sanic has been optimized to work with the CLI. If you have not read it yet, you should read [Running Sanic](./running.md#sanic-server) to become familiar with the options. + +---:1 This includes running it as a global scope object... :--:1 +```python +# server.py +app = Sanic("TestApp") + +@app.get("/") +async def handler(request: Request): + return json({"foo": "bar"}) +``` +``` +sanic path.to.server:app +``` +:--- + + +---:1 ...or, a factory function that creates the `Sanic` application object. :--:1 +```python +# server.py +def create_app(): + app = Sanic("TestApp") + + @app.get("/") + async def handler(request: Request): + return json({"foo": "bar"}) + + return app +``` +``` +sanic path.to.server:create_app --factory +``` +:--- + + +**Sometimes, this is not enough ... :thinking:** + +Introduced in [v22.9](../release-notes/v22.9.md), Sanic has an `AppLoader` object that is responsible for creating an application in the various [worker processes](./manager.md#how-sanic-server-starts-processes). You can take advantage of this if you need to create a more dynamic startup experience for your application. + +---:1 An `AppLoader` can be passed a callable that returns a `Sanic` instance. That `AppLoader` could be used with the low-level application running API. :--:1 +```python +import sys +from functools import partial + +from sanic import Request, Sanic, json +from sanic.worker.loader import AppLoader + + +def attach_endpoints(app: Sanic): + @app.get("/") + async def handler(request: Request): + return json({"app_name": request.app.name}) + + +def create_app(app_name: str) -> Sanic: + app = Sanic(app_name) + attach_endpoints(app) + return app + + +if __name__ == "__main__": + app_name = sys.argv[-1] + loader = AppLoader(factory=partial(create_app, app_name)) + app = loader.load() + app.prepare(port=9999, dev=True) + Sanic.serve(primary=app, app_loader=loader) +``` +``` +$ python path/to/server.py MyTestAppName +``` +:--- + +In the above example, the `AppLoader` is created with a `factory` that can be used to create copies of the same application across processes. When doing this, you should explicitly use the `Sanic.serve` pattern shown above so that the `AppLoader` that you create is not replaced. diff --git a/src/zh/guide/deployment/configuration.md b/src/zh/guide/deployment/configuration.md index e3f3470dd0..4c1c597b53 100644 --- a/src/zh/guide/deployment/configuration.md +++ b/src/zh/guide/deployment/configuration.md @@ -2,26 +2,20 @@ ## 基础(Basics) ----:1 - -Sanic 会将配置保存在应用程序对象的 Config 属性中,它是一个可以通过字典的形式或者属性的形式进行操作的对象。 -:--:1 +---:1 +Sanic 会将配置保存在应用程序对象的 Config 属性中,它是一个可以通过字典的形式或者属性的形式进行操作的对象。 The configuration object is merely an object that can be modified either using dot-notation or like a dictionary. :--:1 ```python app = Sanic("myapp") app.config.DB_NAME = "appdb" app.config["DB_USER"] = "appuser" ``` - :--- ---:1 -因此,您也可以使用 `update()` 方法来更新配置。 - -:--:1 - +因此,您也可以使用 `update()` 方法来更新配置。 :--:1 ```python db_settings = { 'DB_HOST': 'localhost', @@ -30,14 +24,9 @@ db_settings = { } app.config.update(db_settings) ``` - :--- -::: tip 小提示 - -在 Sanic 中, 标准做法是使用 **大写字母** 来命名您的配置名称,如果您将大写名称和小写名称混合使用,可能会导致某些配置无法正常读取,遇到无法解释的状况。 - -::: +在 Sanic 中, 标准做法是使用 **大写字母** 来命名您的配置名称,如果您将大写名称和小写名称混合使用,可能会导致某些配置无法正常读取,遇到无法解释的状况。 Indeed, you may experience weird behaviors if you start mixing uppercase and lowercase names. ::: ## 配置加载(Loading) @@ -45,119 +34,83 @@ app.config.update(db_settings) ---:1 -任何使用 `SANIC_` 作为前缀的环境变量都会被加载并应用于 Sanic 配置。例如:在环境变量中设置 `SANIC_REQUEST_TIMEOUT` 环境变量后,将会被应用程序自动加载,并传递到 `REQUEST_TIMEOUT` 配置变量中。 - -:--:1 - +任何使用 `SANIC_` 作为前缀的环境变量都会被加载并应用于 Sanic 配置。 例如:在环境变量中设置 `SANIC_REQUEST_TIMEOUT` 环境变量后,将会被应用程序自动加载,并传递到 `REQUEST_TIMEOUT` 配置变量中。 :--:1 ```bash $ export SANIC_REQUEST_TIMEOUT=10 ``` - ```python >>> print(app.config.REQUEST_TIMEOUT) 10 ``` - :--- ---:1 -您可以自动选择启动时应用程序要读取的变量前缀。 - -:--:1 - +You can change the prefix that Sanic is expecting at startup. :--:1 ```bash $ export MYAPP_REQUEST_TIMEOUT=10 ``` - ```python >>> app = Sanic(__name__, load_env='MYAPP_') >>> print(app.config.REQUEST_TIMEOUT) 10 ``` - :--- ---:1 -同样,您可以完全禁用环境变量的加载。 - -:--:1 - +同样,您可以完全禁用环境变量的加载。 :--:1 ```python app = Sanic(__name__, load_env=False) ``` - :--- ### 使用通用方法加载(Using Sanic.update_config) -`Sanic` 中有一种通用的方法用于加载配置:`app.update_config` 。您可以通过向它提供文件路径、字典、类或者几乎任何其他种类的对象的路径来更新配置。 +`Sanic` 中有一种通用的方法用于加载配置:`app.update_config` 。 您可以通过向它提供文件路径、字典、类或者几乎任何其他种类的对象的路径来更新配置。 #### 通过文件加载(From a file) ---:1 -假设您有一个名为 `my_config.py` 的文件,它的内容如下: - -:--:1 - +假设您有一个名为 `my_config.py` 的文件,它的内容如下: :--:1 ```python # my_config.py A = 1 B = 2 ``` - :--- ---:1 -您可以通过将文件路径传递给 `app.update_config` 进行配置加载。 - -:--:1 - +您可以通过将文件路径传递给 `app.update_config` 进行配置加载。 :--:1 ```python >>> app.update_config("/path/to/my_config.py") >>> print(app.config.A) 1 ``` - :--- ---:1 -它同样接受 bash 风格的环境变量。 - -:--:1 - +它同样接受 bash 风格的环境变量。 :--:1 ```bash $ export my_path="/path/to" ``` - ```python app.update_config("${my_path}/my_config.py") ``` - :--- -::: tip 小提示 - -请记住,您必须以 `$environment_variable` 的格式来提供环境变量。而且 `${environment_variable}` 被视作纯文本(没有使用字符串格式化) - -::: - +请记住,您必须以 `$environment_variable` 的格式来提供环境变量。 ::: #### 通过字典加载(From a dict) ---:1 -`app.update_config` 的方法同样适用于字典 - -:--:1 - +The `app.update_config` method also works on plain dictionaries. :--:1 ```python app.update_config({"A": 1, "B": 2}) ``` - :--- #### 通过类加载(From a class or object) @@ -165,9 +118,6 @@ app.update_config({"A": 1, "B": 2}) ---:1 您可以自定义配置类,并将该类传递给 `app.update_config` - -:--:1 - ```python class MyConfig: A = 1 @@ -175,24 +125,19 @@ class MyConfig: app.update_config(MyConfig) ``` - :--- ---:1 -甚至您可以向它传递一个实例化好的对象 - -:--:1 - +It even could be instantiated. :--:1 ```python app.update_config(MyConfig()) ``` - :--- ### 类型转换(Type casting) -从环境变量加载时,Sanic 将尝试将值转换为预期的 Python 类型。这尤其适用于: +从环境变量加载时,Sanic 将尝试将值转换为预期的 Python 类型。 这尤其适用于: - `int` - `float` @@ -203,84 +148,89 @@ app.update_config(MyConfig()) - **`True`**: `y`, `yes`, `yep`, `yup`, `t`, `true`, `on`, `enable`, `enabled`, `1` - **`False`**: `n`, `no`, `f`, `false`, `off`, `disable`, `disabled`, `0` ----:1 - -此外,Sanic 可以通过配置类型转换器来进行类型转换。这应该是一个能够返回任何值且能触发 `ValueError` 的可调用函数。 +If a value cannot be cast, it will default to a `str`. -:--:1 +此外,Sanic 可以通过配置类型转换器来进行类型转换。 这应该是一个能够返回任何值且能触发 `ValueError` 的可调用函数。 +*Added in v21.12* :--:1 ```python app = Sanic(..., config=Config(converters=[UUID])) ``` - :--- ## 内置配置(Builtin values) -| 变量名称 | 默认值 | 说明 | -| :------------------------ | --------------- | -------------------------------------------------- | -| ACCESS_LOG | True | 访问日志开关 | -| AUTO_EXTEND^ | True | Sanic 拓展启用开关 | -| AUTO_RELOAD | True | 自动重载开关 | -| EVENT_AUTOREGISTER | True | 自动注册信号开关(开启后不存在的事件将会自动注册) | -| FALLBACK_ERROR_FORMAT | html | 异常返回格式 | -| FORWARDED_FOR_HEADER | X-Forwarded-For | 客户端 IP 和代理 IP:X-Forwarded-For | -| FORWARDED_SECRET | None | 用于安全地识别特定的代理服务器(见下文) | -| GRACEFUL_SHUTDOWN_TIMEOUT | 15.0 | 强制关闭非空闲连接的等待时间(秒) | -| KEEP_ALIVE | True | 是否启用长连接 | -| KEEP_ALIVE_TIMEOUT | 5 | 长连接超时时间 | -| MOTD^ | True | 是否在启动时展示 MOTD 信息 | -| MOTD_DISPLAY | {} | 键/值对显示 MOTD 中的附加任意数据 | -| NOISY_EXCEPTIONS ^ | False | 强制禁止异常输出 | -| PROXIES_COUNT | None | 应用程序钱代理服务器的数量(见下文) | -| REAL_IP_HEADER | None | 客户端真实 IP: X-Real-IP | -| REGISTER | True | 是否启用应用程序注册表 | -| REQUEST_BUFFER_QUEUE_SIZE | 100 | 请求流缓冲区队列大小 | -| REQUEST_ID_HEADER | X-Request-ID | 请求头中的请求 ID 名称:X-Request-ID | -| REQUEST_MAX_SIZE | 100000000 | Request 的最大字节数 | -| REQUEST_TIMEOUT | 60 | 请求超时时间 | -| RESPONSE_TIMEOUT | 60 | 响应超时时间 | -| WEBSOCKET_MAX_SIZE | 2^20 | websocket 传入消息最大字节数 | -| WEBSOCKET_PING_INTERVAL | 20 | websocket ping 帧 发送间隔 | -| WEBSOCKET_PING_TIMEOUT | 20 | websocket pong 帧 响应超时时间 | - -::: tip - -如果您使用 Gunicorn 运行,那么 `USE_UVLOOP` 将会被忽略。在不支持的平台(Windows)上该值默认为 False。 - -如果您处于 ASGI 模式, 那么 `WEBSOCKET_` 的值将会被忽略 - -::: + +| **变量名称** | **默认值** | **说明** | +| --------------------------- | --------------- | ----------------------------------------------------------------------------------- | +| ACCESS_LOG | True | 访问日志开关 | +| AUTO_EXTEND | True | Sanic 拓展启用开关 | +| AUTO_RELOAD | True | 自动重载开关 | +| EVENT_AUTOREGISTER | True | 自动注册信号开关(开启后不存在的事件将会自动注册) | +| FALLBACK_ERROR_FORMAT | html | 异常返回格式 | +| FORWARDED_FOR_HEADER | X-Forwarded-For | 客户端 IP 和代理 IP:X-Forwarded-For | +| FORWARDED_SECRET | None | 用于安全地识别特定的代理服务器(见下文) | +| GRACEFUL_SHUTDOWN_TIMEOUT | 15.0 | 强制关闭非空闲连接的等待时间(秒) | +| INSPECTOR | False | Whether to enable the Inspector | +| INSPECTOR_HOST | localhost | The host for the Inspector | +| INSPECTOR_PORT | 6457 | The port for the Inspector | +| INSPECTOR_TLS_KEY | - | The TLS key for the Inspector | +| INSPECTOR_TLS_CERT | - | The TLS certificate for the Inspector | +| INSPECTOR_API_KEY | - | The API key for the Inspector | +| KEEP_ALIVE_TIMEOUT | 5 | 长连接超时时间 | +| KEEP_ALIVE | True | 是否启用长连接 | +| MOTD | True | 是否在启动时展示 MOTD 信息 | +| MOTD_DISPLAY | {} | 键/值对显示 MOTD 中的附加任意数据 | +| NOISY_EXCEPTIONS | False | 强制禁止异常输出 | +| PROXIES_COUNT | None | 应用程序钱代理服务器的数量(见下文) | +| REAL_IP_HEADER | None | 客户端真实 IP: X-Real-IP | +| REGISTER | True | 是否启用应用程序注册表 | +| REQUEST_BUFFER_QUEUE_SIZE | 100 | 请求流缓冲区队列大小 | +| REQUEST_ID_HEADER | X-Request-ID | 请求头中的请求 ID 名称:X-Request-ID | +| REQUEST_MAX_SIZE | 100000000 | Request 的最大字节数 | +| REQUEST_TIMEOUT | 60 | 请求超时时间 | +| RESPONSE_TIMEOUT | 60 | 响应超时时间 | +| USE_UVLOOP | True | Whether to override the loop policy to use `uvloop`. Supported only with `app.run`. | +| WEBSOCKET_MAX_SIZE | 2^20 | websocket ping 帧 发送间隔 | +| WEBSOCKET_PING_INTERVAL | 20 | websocket pong 帧 响应超时时间 | +| WEBSOCKET_PING_TIMEOUT | 20 | Connection is closed when Pong is not received after ping_timeout seconds | + +`app.update_config` 的方法同样适用于字典 +- 如果您使用 Gunicorn 运行,那么 `USE_UVLOOP` 将会被忽略。 在不支持的平台(Windows)上该值默认为 False。 +- 如果您处于 ASGI 模式, 那么 `WEBSOCKET_` 的值将会被忽略 +- v21.12 added: `AUTO_EXTEND`, `MOTD`, `MOTD_DISPLAY`, `NOISY_EXCEPTIONS` +- v22.9 added: `INSPECTOR` +- v22.12 added: `INSPECTOR_HOST`, `INSPECTOR_PORT`, `INSPECTOR_TLS_KEY`, `INSPECTOR_TLS_CERT`, `INSPECTOR_API_KEY` ::: ## 超时(Timeouts) -### 请求超时(REQUEST_TIMEOUT) +### REQUEST_TIMEOUT -请求时间用于衡量从建立 TCP 连接到整个 HTTP 请求接收完成所花费的时间。如果请求时间超过了设定的 `REQUEST_TIMEOUT` ,Sanic 会将其视为客户端错误并将 HTTP 408 作为响应发送给客户端。如果您的客户端需要频繁传递大量的数据, 请您将此参数调至更高或减少传输数据。 +请求时间用于衡量从建立 TCP 连接到整个 HTTP 请求接收完成所花费的时间。 如果请求时间超过了设定的 `REQUEST_TIMEOUT` ,Sanic 会将其视为客户端错误并将 HTTP 408 作为响应发送给客户端。 如果您的客户端需要频繁传递大量的数据, 请您将此参数调至更高或减少传输数据。 -### 响应超时(RESPONSE_TIMEOUT) +### RESPONSE_TIMEOUT -响应时间用于衡量从整个 HTTP 请求接收完成到 Sanic 将响应完整发送至客户端所花费的时间。如果响应时间超过了设定的 `RESONSE_TIMEOUT` ,Sanic 会将其视为服务端错误并将 HTTP 503 作为响应发送给客户端。如果您的应用程序需要消耗大量的时间来进行响应,请尝试将此参数调至更高或优化响应效率。 +响应时间用于衡量从整个 HTTP 请求接收完成到 Sanic 将响应完整发送至客户端所花费的时间。 如果响应时间超过了设定的 `RESONSE_TIMEOUT` ,Sanic 会将其视为服务端错误并将 HTTP 503 作为响应发送给客户端。 如果您的应用程序需要消耗大量的时间来进行响应,请尝试将此参数调至更高或优化响应效率。 -### 长连接超时(KEEP_ALIVE_TIMEOUT) +### KEEP_ALIVE_TIMEOUT -#### 什么是长连接?长连接超时有什么作用? +#### What is Keep Alive? And what does the Keep Alive Timeout value do? -`Keep-Alive` 中文叫做长连接,它是 HTTP1.1 中引入的 HTTP 功能。当发送 HTTP 请求时,客户端(通常是浏览器)可以通过设置 `Keep-Alive` 标头来指示 http 服务器(Sanic)在发送响应之后不关闭 TCP 连接。这将允许客户端重用现有的 TCP 连接来发送后续的 HTTP 请求,以提高客户端和服务端之间的通讯效率。 +`Keep-Alive` 中文叫做长连接,它是 HTTP1.1 中引入的 HTTP 功能。 当发送 HTTP 请求时,客户端(通常是浏览器)可以通过设置 `Keep-Alive` 标头来指示 http 服务器(Sanic)在发送响应之后不关闭 TCP 连接。 这将允许客户端重用现有的 TCP 连接来发送后续的 HTTP 请求,以提高客户端和服务端之间的通讯效率。 -在默认情况下,Sanic 中的 `Keep-Alive` 的值为 `True` 。如果您的应用程序不需要此功能,可以将其设置为 False。不过此举将导致 Sanic 无视 `Keep_Alive` 标头,且所有的客户端连接在响应发送完成之后被立即关闭。 +在默认情况下,Sanic 中的 `Keep-Alive` 的值为 `True` 。 如果您的应用程序不需要此功能,可以将其设置为 False。 不过此举将导致 Sanic 无视 `Keep_Alive` 标头,且所有的客户端连接在响应发送完成之后被立即关闭。 -TCP 连接打开的时长本质上由服务器自身决定,在 Sanic 中,使用 `KEEP_ALIVE_TIMEOUT` 作为该值。默认情况下它设置为 5 秒。这与 Apache 的默认值相同。该值足够客户端发送一个新的请求。如非必要请勿更改此项。如需更改,请勿超过 75 秒,除非您确认客户端支持 TCP 连接保持足够久。 +The amount of time the server holds the TCP connection open is decided by the server itself. TCP 连接打开的时长本质上由服务器自身决定,在 Sanic 中,使用 `KEEP_ALIVE_TIMEOUT` 作为该值。 默认情况下它设置为 5 秒。 This is the same default setting as the Apache HTTP server and is a good balance between allowing enough time for the client to send a new request, and not holding open too many connections at once. 如需更改,请勿超过 75 秒,除非您确认客户端支持 TCP 连接保持足够久。 小提示: -- Apache httpd 服务器默认 KEEP_ALIVE_TIMEOUT = 5 秒 -- Nginx 服务器默认 KEEP_ALIVE_TIMEOUT = 75 秒 -- Nginx 性能调整准则使用 KEEP_ALIVE_TIMEOUT = 15 秒 -- IE(5-9)客户端 KEEP_ALIVE_LIMIT = 60 秒 -- Firefox 客户端 KEEP_ALIVE_LIMIT = 115 秒 -- Opera 11 客户端 KEEP_ALIVE_LIMIT = 120 秒 -- Chrome 13+ 客户端 KEEP_ALIVE_LIMIT > 300+秒 +* Apache httpd 服务器默认 KEEP_ALIVE_TIMEOUT = 5 秒 +* Nginx 服务器默认 KEEP_ALIVE_TIMEOUT = 75 秒 +* Nginx 性能调整准则使用 KEEP_ALIVE_TIMEOUT = 15 秒 +* IE(5-9)客户端 KEEP_ALIVE_LIMIT = 60 秒 +* Firefox 客户端 KEEP_ALIVE_LIMIT = 115 秒 +* Opera 11 客户端 KEEP_ALIVE_LIMIT = 120 秒 +* Chrome 13+ 客户端 KEEP_ALIVE_LIMIT > 300+秒 ## 代理配置(Proxy configuration) diff --git a/src/zh/guide/deployment/development.md b/src/zh/guide/deployment/development.md index 42d7cf6cfc..878c2eaf19 100644 --- a/src/zh/guide/deployment/development.md +++ b/src/zh/guide/deployment/development.md @@ -2,7 +2,7 @@ 首先要明确的是,集成到 Sanic 中的 Web 服务器不只是一个开发服务器。 -只要您没有处于调试模式,它就可以投入生产。 +It is production ready out-of-the-box, *unless you enable in debug mode*. ## 调试模式(Debug mode) @@ -22,67 +22,73 @@ if __name__ == "__main__": app.run(host="0.0.0.0", port=1234, debug=True) ``` -::: warning 警告 - -Sanic 的调试模式会降低服务器的性能,因此建议只在开发环境中启用它。 - -::: - +Sanic 的调试模式会降低服务器的性能,因此建议只在开发环境中启用它。 ::: ## 自动重载(Automatic Reloader) ---:1 -在调试模式之外,Sanic 还提供了一种手动开关自动重载的方法。`auto_reload` 参数将开启或关闭自动重载功能。 - -:--:1 - +在调试模式之外,Sanic 还提供了一种手动开关自动重载的方法。 `auto_reload` 参数将开启或关闭自动重载功能。 Every time a Python file is changed, the reloader will restart your application automatically. This is very convenient while developing. :--:1 ```python app.run(auto_reload=True) ``` - :--- ----:1 - -如果您有额外的目录想要在文件保存时自动重新加载(例如,HTML 模板的目录),您可以在运行时添加它。 - -:--:1 - +如果您有额外的目录想要在文件保存时自动重新加载(例如,HTML 模板的目录),您可以在运行时添加它。 :--:1 ```python app.run(auto_reload=True, reload_dir="/path/to/templates") # or multiple directories app.run(auto_reload=True, reload_dir=["/path/to/one", "/path/to/two"]) ``` - :--- ## 两全其美(Best of both worlds) +如果您想在调试模式中使用自动重载功能,您可以设置 `dev=True`,这相当于您开启了 **调试模式 + 自动重载** This is equivalent to **debug + auto reload**. + +*Added in v22.3* :--:1 +```python +app.run(dev=True) +``` +:--- -::: new v22.3 新特征 +## 客户端(CLI) ----:1 +When running in `DEBUG` mode, you can ask Sanic to handle setting up localhost temporary TLS certificates. This is helpful if you want to access your local development environment with `https://`. + +This functionality is provided by either [mkcert](https://github.com/FiloSottile/mkcert) or [trustme](https://github.com/python-trio/trustme). Both are good choices, but there are some differences. `trustme` is a Python library and can be installed into your environment with `pip`. This makes for easy envrionment handling, but it is not compatible when running a HTTP/3 server. `mkcert` might be a more involved installation process, but can install a local CA and make it easier to use. + +---:1 You can choose which platform to use by setting `config.LOCAL_CERT_CREATOR`. When set to `"auto"`, it will select either option, preferring `mkcert` if possible. :--:1 +```python +app.config.LOCAL_CERT_CREATOR = "auto" +app.config.LOCAL_CERT_CREATOR = "mkcert" +app.config.LOCAL_CERT_CREATOR = "trustme" +``` +:--- -如果您想在调试模式中使用自动重载功能,您可以设置 `dev=True`,这相当于您开启了 **调试模式 + 自动重载** :--:1 +``` +$ sanic path.to.server:app --auto-tls --debug +``` ```python -app.run(dev=True) +app.run(debug=True, auto_tls=True) ``` - :--- -::: +::: warning 警告 -## 客户端(CLI) +Localhost TLS certificates (like those generated by both `mkcert` and `trustme`) are **NOT** suitable for production environments. If you are not familiar with how to obtain a *real* TLS certificate, checkout the [How to...](../how-to/tls.md) section. ::: + +*Added in v22.6* + +## CLI 值得注意的是,所有这些在 Sanic CLI 中都有对应的内容: ``` - Development: - --debug 在调试模式下运行服务器。它包括调试日志、关于异常的附加上下文, - 以及对生产不安全但有助于调试问题的其他设置。 - -r, --reload, --auto-reload 监听文件的修改,并在修改时重新加载 +Development: + --debug 在调试模式下运行服务器。 它包括调试日志、关于异常的附加上下文, + 以及对生产不安全但有助于调试问题的其他设置。 -r, --reload, --auto-reload 监听文件的修改,并在修改时重新加载 -R PATH, --reload-dir PATH 更改时需要监听和重新加载的额外目录 -d, --dev 调试模式 + 自动重载 ``` diff --git a/src/zh/guide/deployment/docker.md b/src/zh/guide/deployment/docker.md index 0b39dd6a51..f934013184 100644 --- a/src/zh/guide/deployment/docker.md +++ b/src/zh/guide/deployment/docker.md @@ -2,11 +2,11 @@ ## 介绍(Introduction) -长期以来,环境一直是部署的难题。如果您的项目中有冲突的配置,您将不得不花费大量时间来解决它们。幸运的是,虚拟化为我们提供了一个很好的解决思路。Docker 就是其中之一。如果您不了解 Docker,可以访问 [Docker 官网](https://www.docker.com/) 了解更多。 +长期以来,环境一直是部署的难题。 如果您的项目中有冲突的配置,您将不得不花费大量时间来解决它们。 幸运的是,虚拟化为我们提供了一个很好的解决思路。 Docker 就是其中之一。 如果您不了解 Docker,可以访问 [Docker 官网](https://www.docker.com/) 了解更多。 ## 构建镜像(Build Image) -我们以一个最简单的 Sanic 工程作为例子。假设项目`SanicDocker` 的路径是`/path/to/SanicDocker`。 +Let's start with a simple project. 我们以一个最简单的 Sanic 工程作为例子。 假设项目`SanicDocker` 的路径是`/path/to/SanicDocker`。 ---:1 @@ -37,6 +37,9 @@ app = Sanic("MySanicApp") async def hello(request): return text("OK!") +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8000) + if __name__ == '__main__': app.run(host='0.0.0.0', port=8000) ``` @@ -45,7 +48,7 @@ if __name__ == '__main__': ::: tip 小提示 -请注意这里的 host 不能为 127.0.0.1 。在 docker 容器中,127.0.0.1 只表示本机本容器中的一个虚拟网卡,只接受本容器中的应用相互通讯。更多信息请参考 [Docker 网络](https://docs.docker.com/engine/reference/commandline/network/) +请注意这里的 host 不能为 127.0.0.1 。 在 docker 容器中,127.0.0.1 只表示本机本容器中的一个虚拟网卡,只接受本容器中的应用相互通讯。 更多信息请参考 [Docker 网络](https://docs.docker.com/engine/reference/commandline/network/) ::: @@ -59,6 +62,12 @@ WORKDIR /sanic COPY . . +FROM sanicframework/sanic:3.8-latest + +WORKDIR /sanic + +COPY . . + RUN pip install -r requirements.txt EXPOSE 8000 @@ -100,11 +109,13 @@ OK! ## 使用 docker-compose 编排(Use docker-compose) -当您的项目中涵盖了多个不同的组件或服务时,您可以使用 [docker-compose](https://docs.docker.com/compose/) 来编排容器。以刚才打包好的 `my-sanic-image` 和 `nginx` 为例,我们来实现通过 `nginx` 代理 `Sanic` 服务器。 +当您的项目中涵盖了多个不同的组件或服务时,您可以使用 [docker-compose](https://docs.docker.com/compose/) 来编排容器。 + +首先我们需要准备 nginx 的配置文件,将其命名为 `mysanic.conf` 并写入内容: ---:1 -首先我们需要准备 nginx 的配置文件,将其命名为 `mysanic.conf` 并写入内容: +First of all, we need prepare nginx configuration file. create a file named `mysanic.conf`: :--:1 @@ -125,7 +136,7 @@ server { ---:1 -然后我们来准备 `docker-compose.yml` 文件,其内容为: +然后我们来准备 `docker-compose.yml` 文件,其内容为: The content follows: :--:1 diff --git a/src/zh/guide/deployment/inspector.md b/src/zh/guide/deployment/inspector.md new file mode 100644 index 0000000000..81cacb3496 --- /dev/null +++ b/src/zh/guide/deployment/inspector.md @@ -0,0 +1,160 @@ +# Inspector + +The Sanic Inspector is a feature of Sanic Server. It is *only* available when running Sanic with the built-in [worker manager](./manager.md). + +It is an HTTP application that *optionally* runs in the background of your application to allow you to interact with the running instance of your application. + +::: tip INFO +The Inspector was introduced in limited capacity in v22.9, but the documentation on this page assumes you are using v22.12 or higher. +::: + +## Getting Started + +The inspector is disabled by default. To enable it, you have two options. + +---:1 Set a flag when creating your application instance. :--:1 +```python +app = Sanic("TestApp", inspector=True) +``` +:--- + +---:1 Or, set a configuration value. :--:1 +```python +app = Sanic("TestApp") +app.config.INSPECTOR = True +``` +:--- + +::: warning +If you are using the configuration value, it *must* be done early and before the main worker process starts. This means that it should either be an environment variable, or it should be set shortly after creating the application instance as shown above. +::: + +## Using the Inspector + +Once the inspector is running, you will have access to it via the CLI or by directly accessing its web API via HTTP. + +---:1 **Via CLI** +``` +$ sanic inspect +``` +:--:1 **Via HTTP** +``` +$ curl http://localhost:6457 +``` +:--- + +::: tip +Remember, the Inspector is not running on your Sanic application. It is a seperate process, with a seperate application, and exposed on a seperate socket. +::: + +## Built-in Commands + +The Inspector comes with the following built-in commands. + +| CLI Command | HTTP Action | Description | +| ------------------ | ---------------------------------------- | ------------------------------------------------------------------------ | +| `inspect` | `GET /` | Display basic details about the running application. | +| `inspect reload` | `POST /reload` | Trigger a reload of all server workers. | +| `inspect shutdown` | `POST /shutdown` | Trigger a shutdown of all processes. | +| `inspect scale N` | `POST /scale`
`{"replicas": N}` | Scale the number of workers. Where `N` is the target number of replicas. | + +## Custom Commands + +The Inspector is easily extendable to add custom commands (and endpoints). + +---:1 Subclass the `Inspector` class and create arbitrary methods. As long as the method name is not preceded by an underscore (`_`), then the name of the method will be a new subcommand on the inspector. :--:1 +```python +from sanic import json +from sanic.worker.inspector import Inspector + + +class MyInspector(Inspector): + async def something(self, *args, **kwargs): + print(args) + print(kwargs) + + +app = Sanic("TestApp", inspector_class=MyInspector, inspector=True) +``` +:--- + +This will expose custom methods in the general pattern: + +- CLI: `sanic inspect ` +- HTTP: `POST /` + +It is important to note that the arguments that the new method accepts are derived from how you intend to use the command. For example, the above `something` method accepts all positional and keyword based parameters. + +---:1 In the CLI, the positional and keyword parameters are passed as either positional or keyword arguments to your method. All values will be a `str` with the following exceptions: + +- A keyword parameter with no assigned value will be: `True` +- Unless the parameter is prefixed with `no-`, then it will be: `False` :--:1 +``` +$ sanic inspect something one two three --four --no-five --six=6 +``` +In your application log console, you will see: +``` +('one', 'two', 'three') +{'four': True, 'five': False, 'six': '6'} +``` +:--- + +---:1 The same can be achieved by hitting the API directly. You can pass arguments to the method by exposing them in a JSON payload. The only thing to note is that the positional arguments should be exposed as `{"args": [...]}`. :--:1 +``` +$ curl http://localhost:6457/something \ + --json '{"args":["one", "two", "three"], "four":true, "five":false, "six":6}' +``` +In your application log console, you will see: +``` +('one', 'two', 'three') +{'four': True, 'five': False, 'six': 6} +``` +:--- + + +## Using in production + +::: warning +Before exposing the Inspector on a product, please consider all of the options in this section carefully. +::: + +When running Inspector on a remote production instance, you can protect the endpoints by requiring TLS encryption, and requiring API key authentication. + +### TLS encryption + +---:1 To the Inspector HTTP instance over TLS, pass the paths to your certificate and key. :--:1 +```python +app.config.INSPECTOR_TLS_CERT = "/path/to/cert.pem" +app.config.INSPECTOR_TLS_KEY = "/path/to/key.pem" +``` +:--- + +---:1 This will require use of the `--secure` flag, or `https://`. :--:1 +``` +$ sanic insect --secure --host= +``` +``` +$ curl https://:6457 +``` +:--- + +### API Key Authentication + +---:1 You can secure the API with bearer token authentication. :--:1 +```python +app.config.INSPECTOR_API_KEY = "Super-Secret-200" +``` +:--- + +---:1 This will require the `--api-key` parameter, or bearer token authorization header. :--:1 +``` +$ sanic inspect --api-key=Super-Secret-200 +``` +``` +$ curl http://localhost:6457 -H "Authorization: Bearer Super-Secret-200" +``` +:--- + +## Configuration + +See [configuration](./configuration.md) diff --git a/src/zh/guide/deployment/manager.md b/src/zh/guide/deployment/manager.md new file mode 100644 index 0000000000..4c4153b14d --- /dev/null +++ b/src/zh/guide/deployment/manager.md @@ -0,0 +1,290 @@ +# Worker Manager + +The worker manager and its functionality was introduced in version 22.9. + +*The details of this section are intended for more advanced usages and **not** necessary to get started.* + +The purpose of the manager is to create consistency and flexibility between development and production environments. Whether you intend to run a single worker, or multiple workers, whether with, or without auto-reload: the experience will be the same. + + +In general it looks like this: + +![](https://user-images.githubusercontent.com/166269/178677618-3b4089c3-6c6a-4ecc-8d7a-7eba2a7f29b0.png) + +When you run Sanic, the main process instantiates a `WorkerManager`. That manager is in charge of running one or more `WorkerProcess`. There generally are two kinds of processes: + +- server processes, and +- non-server processes. + +For the sake of ease, the User Guide generally will use the term "worker" or "worker process" to mean a server process, and "Manager" to mean the single worker manager running in your main process. + +## How Sanic Server starts processes + +Sanic will start processes using the [spawn](https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods) start method. This means that for every process/worker, the global scope of your application will be run on its own thread. The practical impact of this that *if* you do not run Sanic with the CLI, you will need to nest the execution code inside a block to make sure it only runs on `__main__`. + +```python +if __name__ == "__main__": + app.run() +``` + +If you do not, you are likely to see an error message like this: + +``` +sanic.exceptions.ServerError: Sanic server could not start: [Errno 98] Address already in use. + +This may have happened if you are running Sanic in the global scope and not inside of a `if __name__ == "__main__"` block. + +See more information: https://sanic.dev/en/guide/deployment/manager.html#how-sanic-server-starts-processes +``` + +The likely fix for this problem is nesting your Sanic run call inside of the `__name__ == "__main__"` block. If you continue to receive this message after nesting, or if you see this while using the CLI, then it means the port you are trying to use is not available on your machine and you must select another port. + +### Starting a worker + +All worker processes *must* send an acknowledgement when starting. This happens under the hood, and you as a developer do not need to do anything. However, the Manager will exit with a status code `1` if one or more workers do not send that `ack` message, or a worker process throws an exception while trying to start. If no exceptions are encountered, the Manager will wait for up to thirty (30) seconds for the acknowledgement. + +---:1 In the situation when you know that you will need more time to start, you can monkeypatch the Manager. The threshold does not include anything inside of a listener, and is limited to the execution time of everything in the global scope of your application. + +If you run into this issue, it may indicate a need to look deeper into what is causing the slow startup. :--:1 +```python +from sanic.worker.manager import WorkerManager + +WorkerManager.THRESHOLD = 100 # Value is in 0.1s +``` +:--- + +See [worker ack](#worker-ack) for more information. + +---:1 As stated above, Sanic will use [spawn](https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods) to start worker processes. If you would like to change this behavior and are aware of the implications of using different start methods, you can modify as shown here. :--:1 +```python +from sanic import Sanic + +Sanic.start_method = "fork" +``` +:--- + + +### Worker ack + +When all of your workers are running in a subprocess a potential problem is created: deadlock. This can occur when the child processes cease to function, but the main process is unaware that this happened. Therefore, Sanic servers will automatically send an `ack` message (short for acknowledge) to the main process after startup. + +In version 22.9, the `ack` timeout was short and limited to `5s`. In version 22.12, the timeout was lengthened to `30s`. If your application is shutting down after thirty seconds then it might be necessary to manually increase this threshhold. + +---:1 The value of `WorkerManager.THRESHOLD` is in `0.1s` increments. Therefore, to set it to one minute, you should set the value to `600`. + +This value should be set as early as possible in your application, and should ideally happen in the global scope. Setting it after the main process has started will not work. :--:1 +```python +from sanic.worker.manager import WorkerManager + +WorkerManager.THRESHOLD = 600 +``` +:--- + + + + +::: new NEW in v22.12 +### Zero downtime restarts + +By default, when restarting workers, Sanic will teardown the existing process first before starting a new one. + +If you are intending to use the restart functionality in production then you may be interested in having zero-downtime reloading. This can be accomplished by forcing the reloader to change the order to start a new process, wait for it to [ack](#worker-ack), and then teardown the old process. + +---:1 From the multiplexer, use the `zero_downtime` argument :--:1 +```python +app.m.restart(zero_downtime=True) +``` +:--- + +*Added in v22.12* +::: + +## Using shared context between worker processes + +Python provides a few methods for [exchanging objects](https://docs.python.org/3/library/multiprocessing.html#exchanging-objects-between-processes), [synchronizing](https://docs.python.org/3/library/multiprocessing.html#synchronization-between-processes), and [sharing state](https://docs.python.org/3/library/multiprocessing.html#sharing-state-between-processes) between processes. This usually involves objects from the `multiprocessing` and `ctypes` modules. + +If you are familiar with these objects and how to work with them, you will be happy to know that Sanic provides an API for sharing these objects between your worker processes. If you are not familiar, you are encouraged to read through the Python documentation linked above and try some of the examples before proceeding with implementing shared context. + +Similar to how [application context](../basics/app.md#application-context) allows an applicaiton to share state across the lifetime of the application with `app.ctx`, shared context provides the same for the special objects mentioned above. This context is available as `app.shared_ctx` and should **ONLY** be used to share objects intended for this purpose. + +The `shared_ctx` will: + +- *NOT* share regular objects like `int`, `dict`, or `list` +- *NOT* share state between Sanic instances running on different machines +- *NOT* share state to non-worker processes +- **only** share state between server workers managed by the same Manager + +Attaching an inappropriate object to `shared_ctx` will likely result in a warning, and not an error. You should be careful to not accidentally add an unsafe object to `shared_ctx` as it may not work as expected. If you are directed here because of one of those warnings, you might have accidentally used an unsafe object in `shared_ctx`. + +---:1 In order to create a shared object you **must** create it in the main process and attach it inside of the `main_process_start` listener. :--:1 +```python +from multiprocessing import Queue + +@app.main_process_start +async def main_process_start(app): + app.shared_ctx.queue = Queue() +``` +:--- + +Trying to attach to the `shared_ctx` object outside of this listener may result in a `RuntimeError`. + +---:1 After creating the objects in the `main_process_start` listener and attaching to the `shared_ctx`, they will be available in your workers wherever the application instance is available (example: listeners, middleware, request handlers). :--:1 +```python +from multiprocessing import Queue + +@app.get("") +async def handler(request): + request.app.shared_ctx.queue.put(1) + ... +``` +:--- + +## Access to the multiplexer + +The application instance has access to an object that provides access to interacting with the Manager and other worker processes. The object is attached as the `app.multiplexer` property, but it is more easily accessed by its alias: `app.m`. + +---:1 For example, you can get access to the current worker state. :--:1 +```python +@app.on_request +async def print_state(request: Request): + print(request.app.m.name) + print(request.app.m.pid) + print(request.app.m.state) +``` +``` +Sanic-Server-0-0 +99999 +{'server': True, 'state': 'ACKED', 'pid': 99999, 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), 'starts': 2, 'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc)} +``` +:--- + +---:1 The `multiplexer` also has access to terminate the Manager, or restart worker processes :--:1 +```python +# shutdown the entire application and all processes +app.m.name.terminate() + +# restart the current worker only +app.m.name.restart() + +# restart specific workers only (comma delimited) +app.m.name.restart("Sanic-Server-4-0,Sanic-Server-7-0") + +# restart ALL workers +app.m.name.restart(all_workers=True) # Available v22.12+ +``` +:--- + +## Worker state + +---:1 As shown above, the `multiplexer` has access to report upon the state of the current running worker. However, it also contains the state for ALL processes running. :--:1 +```python +@app.on_request +async def print_state(request: Request): + print(request.app.m.workers) +``` +``` +{ + 'Sanic-Main': {'pid': 99997}, + 'Sanic-Server-0-0': { + 'server': True, + 'state': 'ACKED', + 'pid': 9999, + 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), + 'starts': 2, + 'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc) + }, + 'Sanic-Reloader-0': { + 'server': False, + 'state': 'STARTED', + 'pid': 99998, + 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), + 'starts': 1 + } +} +``` +:--- + + +## Built-in non-server processes + +As mentioned, the Manager also has the ability to run non-server processes. Sanic comes with two built-in types of non-server processes, and allows for [creating custom processes](#running-custom-processes). + +The two built-in processes are + +- the [auto-reloader](./development.md#automatic-reloader), optionally enabled to watch the file system for changes and trigger a restart +- [inspector](#inspector), optionally enabled to provide external access to the state of the running instance + +## Inspector + +Sanic has the ability to expose the state and the functionality of the `multiplexer` to the CLI. Currently, this requires the CLI command to be run on the same machine as the running Sanic instance. By default the inspector is disabled. + +---:1 To enable it, set the config value to `True`. :--:1 +```python +app.config.INSPECTOR = True +``` +:--- + +You will now have access to execute any of these CLI commands: + +``` +sanic inspect reload Trigger a reload of the server workers +sanic inspect shutdown Shutdown the application and all processes +sanic inspect scale N Scale the number of workers to N +sanic inspect Run a custom command +``` + +![](https://user-images.githubusercontent.com/166269/190099384-2f2f3fae-22d5-4529-b279-8446f6b5f9bd.png) + +---:1 This works by exposing a small HTTP service on your machine. You can control the location using configuration values: :--:1 +```python +app.config.INSPECTOR_HOST = "localhost" +app.config.INSPECTOR_PORT = 6457 +``` +:--- + +[Learn more](./inspector.md) to find out what is possible with the Inspector. + +## Running custom processes + +To run a managed custom process on Sanic, you must create a callable. If that process is meant to be long-running, then it should handle a shutdown call by a `SIGINT` or `SIGTERM` signal. + +---:1 The simplest method for doing that in Python will be to just wrap your loop in `KeyboardInterrupt`. + +If you intend to run another application, like a bot, then it is likely that it already has capability to handle this signal and you likely do not need to do anything. :--:1 +```python +from time import sleep + +def my_process(foo): + try: + while True: + sleep(1) + except KeyboardInterrupt: + print("done") +``` +:--- + +---:1 That callable must be registered in the `main_process_ready` listener. It is important to note that is is **NOT** the same location that you should register [shared context](#using-shared-context-between-worker-processes) objects. :--:1 +```python +@app.main_process_ready +async def ready(app: Sanic, _): +# app.manager.manage(, , ) + app.manager.manage("MyProcess", my_process, {"foo": "bar"}) +``` +:--- + +## Single process mode + +---:1 If you would like to opt out of running multiple processes, you can run Sanic in a single process only. In this case, the Manager will not run. You will also not have access to any features that require processes (auto-reload, the inspector, etc). :--:1 +```python +if __name__ == "__main__": + app.run(single_process=True) +``` +```python +if __name__ == "__main__": + app.prepare(single_process=True) + Sanic.serve_single() +``` +``` +sanic path.to.server:app --single-process +``` +:--- diff --git a/src/zh/guide/deployment/nginx.md b/src/zh/guide/deployment/nginx.md index 4ccaf8e3bb..acce9e5c88 100644 --- a/src/zh/guide/deployment/nginx.md +++ b/src/zh/guide/deployment/nginx.md @@ -2,19 +2,15 @@ ## 介绍(Introduction) -尽管 Sanic 可以直接运行在 Internet 中,但是使用代理服务器可能会更好。 -例如在 Sanic 服务器之前添加 Nginx 代理服务器。这将有助于在同一台机器上同时提供多个不同的服务。 -这样做还可以简单快捷的提供静态文件。包括 SSL 和 HTTP2 等协议也可以在此类代理上轻松实现。 -我们将 Sanic 应用部署在本地,监听 `127.0.0.1`, -然后使用 Nginx 代理 `/var/www` 下的静态文件, -最后使用 Nginx 绑定域名 `example.com` 向公网提供服务 +尽管 Sanic 可以直接运行在 Internet 中,但是使用代理服务器可能会更好。 例如在 Sanic 服务器之前添加 Nginx 代理服务器。 This is particularly useful for running multiple virtual hosts on the same IP, serving NodeJS or other services beside a single Sanic app, and it also allows for efficient serving of static files. 包括 SSL 和 HTTP2 等协议也可以在此类代理上轻松实现。 + +我们将 Sanic 应用部署在本地,监听 `127.0.0.1`, 然后使用 Nginx 代理 `/var/www` 下的静态文件, 最后使用 Nginx 绑定域名 `example.com` 向公网提供服务 Static files will be served from `/var/www/`. + ## 代理 Sanic(Proxied Sanic app) -被代理的应用应该设置 `FORWARDED_SECRET`(受信任代理的密钥)用于识别真实的客户端 IP 以及其他信息。 -这可以有效的防止网络中发送的伪造标头来隐藏其 IP 地址的请求。 -您可以设置任意随机字符串,同时,您需要在 Nginx 中进行相同的配置。 +被代理的应用应该设置 `FORWARDED_SECRET`(受信任代理的密钥)用于识别真实的客户端 IP 以及其他信息。 这可以有效的防止网络中发送的伪造标头来隐藏其 IP 地址的请求。 您可以设置任意随机字符串,同时,您需要在 Nginx 中进行相同的配置。 ```python from sanic import Sanic @@ -43,11 +39,9 @@ if __name__ == "__main__": 允许快速透明代理需要相当多的配置,但是在大多数情况下,这些并不需要修改。 -在单独的 `upstream` 模块中配置 `keepalive` 来启用长连接,这可以极大的提高性能,而不是直接在 `server` 中 配置 `proxy_pass`。 -在此示例中,`upstream` 命名为 `server_name` 及域名,该名称将通过 Host 标头传递给 Sanic, 您可以按需修改该名称,也可以提供多个服务器以达到负载均衡和故障转移。 +在单独的 `upstream` 模块中配置 `keepalive` 来启用长连接,这可以极大的提高性能,而不是直接在 `server` 中 配置 `proxy_pass`。 在此示例中,`upstream` 命名为 `server_name` 及域名,该名称将通过 Host 标头传递给 Sanic, 您可以按需修改该名称,也可以提供多个服务器以达到负载均衡和故障转移。 You may change the naming as you see fit. Multiple servers may also be provided for load balancing and failover. -将两次出现的 `example.com` 更改为您的域名,然后 -将 `YOUR SECRET` 替换为您应用中配置的 `FORWARDED_SECRET` +将两次出现的 `example.com` 更改为您的域名,然后 将 `YOUR SECRET` 替换为您应用中配置的 `FORWARDED_SECRET` ```nginx upstream example.com { @@ -80,9 +74,7 @@ server { } ``` -为避免 Cookie 可见性问题和搜索引擎上的地址不一致的问题, -您可以使用以下方法将所有的访问都重定向到真实的域名上。 -以确保始终为 HTTPS 访问: +为避免 Cookie 可见性问题和搜索引擎上的地址不一致的问题, 您可以使用以下方法将所有的访问都重定向到真实的域名上。 以确保始终为 HTTPS 访问: ```nginx # Redirect all HTTP to HTTPS with no-WWW @@ -119,6 +111,16 @@ server { # their real IP address and other information to your service. +# RFC 7239 Forwarded header for Nginx proxy_pass + +# Add within your server or location block: +# proxy_set_header forwarded "$proxy_forwarded;secret=\"YOUR SECRET\""; + +# Configure your upstream web server to identify this proxy by that password +# because otherwise anyone on the Internet could spoof these headers and fake +# their real IP address and other information to your service. + + # Provide the full proxy chain in $proxy_forwarded map $proxy_add_forwarded $proxy_forwarded { default "$proxy_add_forwarded;by=\"_$hostname\";proto=$scheme;host=\"$http_host\";path=\"$request_uri\""; @@ -147,31 +149,25 @@ map $http_forwarded $proxy_add_forwarded { } ``` -::: tip 小提示 +::: tip Note For installs that don't use `conf.d` and `sites-available`, all of the above configs may also be placed inside the `http` section of the main `nginx.conf`. ::: 如果您的 Nginx 中不使用 `conf.d` 和 `sites-available`,以上配置也可以放在 `nginx.conf` 的 `http` 中。 -::: - -保存修改之后,重新启动 Nginx 服务: - ```bash sudo nginx -s reload ``` -现在,您应该可以在 `https://example.com/` 上访问您的应用了。 -任何的 404 以及类似的错误都将交由 Sanic 进行处理。 -静态文件存储在指定的目录下,将由 Nginx 提供访问。 +现在,您应该可以在 `https://example.com/` 上访问您的应用了。 任何的 404 以及类似的错误都将交由 Sanic 进行处理。 静态文件存储在指定的目录下,将由 Nginx 提供访问。 ## SSL 证书(SSL certificates) -如果您尚未在服务器上配置有效证书,您可以安装 `certbot` 和 `python3-certbot-nginx` 以使用免费的 SSL/TLS 证书,然后运行: +If you haven't already configured valid certificates on your server, now is a good time to do so. Install `certbot` and `python3-certbot-nginx`, then run ```bash certbot --nginx -d example.com -d www.example.com ``` -相关资料请参考:[使用免费的 SSL/TLS 证书](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/) +Reference: [Using Free Let’s Encrypt SSL/TLS Certificates with NGINX](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/) ## 作为服务运行(Running as a service) @@ -191,7 +187,7 @@ Restart=always WantedBy=multi-user.target ``` -之后重新加载服务文件,启动服务并允许开机启动: +相关资料请参考:[使用免费的 SSL/TLS 证书](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/) ```bash sudo systemctl daemon-reload diff --git a/src/zh/guide/deployment/running.md b/src/zh/guide/deployment/running.md index 0962a1e4b7..1631491a54 100644 --- a/src/zh/guide/deployment/running.md +++ b/src/zh/guide/deployment/running.md @@ -1,80 +1,102 @@ # 运行 Sanic(Running Sanic) -Sanic 自带了一个 Web 服务器。在大多数情况下,推荐使用该服务器来部署您的 Sanic 应用。除此之外,您还可以使用支持 ASGI 应用的服务器来部署 Sanic,或者使用 Gunicorn。 +Sanic 自带了一个 Web 服务器。 在大多数情况下,推荐使用该服务器来部署您的 Sanic 应用。 除此之外,您还可以使用支持 ASGI 应用的服务器来部署 Sanic,或者使用 Gunicorn。 ## Sanic 服务器(Sanic Server) -当定义了 `sanic.Sanic` 实例后,我们可以调用其 `run` 方法,该方法支持以下几个关键字参数: - -| 参数名称 | 默认值 | 参数说明 | -| :------------: | :------------: | :---------------------------------------------------------------------- | -| **host** | `"127.0.0.1"` | 服务器监听的地址。 | -| **port** | `8000` | 服务器监听的端口。 | -| **unix** | `None` | Unix 套接字文件(不是 TCP)。 | -| **debug** | `False` | 开启 DEBUG 输出 (降低服务器性能)。 | -| **ssl** | `None` | SSLContext,子进程用于 SSL 加密。 | -| **sock** | `None` | 服务器接受连接的套接字。 | -| **workers** | `1` | 要生成的子进程数量。 | -| **loop** | `None` | 一个兼容 asyncio 的事件循环。如果没有指定,Sanic 会创建自己的事件循环。 | -| **protocol** | `HttpProtocol` | asyncio.protocol 子类。 | -| **access_log** | `True` | 启用请求访问日志(显著降低服务器速度)。 | - ----:1 +There are two main ways to run Sanic Server: -在该样例中,我们关闭输出访问日志来提升性能。 +1. Using `app.run` +1. Using the [CLI](#sanic-cli) -:--:1 +When using `app.run` you will just call your Python file like any other script. +---:1 `app.run` must be properly nested inside of a name-main block. :--:1 ```python # server.py -app = Sanic("My App") -app.run(host='0.0.0.0', port=1337, access_log=False) -``` +app = Sanic("MyApp") +if __name__ == "__main__": + app.run() +``` :--- ----:1 -现在,执行包含 `app.run(...)` 代码的 Python 脚本。 -:--:1 +当定义了 `sanic.Sanic` 实例后,我们可以调用其 `run` 方法,该方法支持以下几个关键字参数: + +| 参数名称 | 默认值 | 参数说明 | +|:--------------------:|:--------------:|:----------------------------------------------------------------------------------- | +| **host** | `"127.0.0.1"` | 服务器监听的地址。 | +| **port** | `8000` | 服务器监听的端口。 | +| **unix** | `None` | Unix 套接字文件(不是 TCP)。 | +| **debug** | `False` | 开启 DEBUG 输出 (降低服务器性能)。 | +| **ssl** | `None` | SSLContext,子进程用于 SSL 加密。 | +| **sock** | `None` | 服务器接受连接的套接字。 | +| **workers** | `1` | 要生成的子进程数量。 Cannot be used with fast. | +| **loop** | `None` | 一个兼容 asyncio 的事件循环。 如果没有指定,Sanic 会创建自己的事件循环。 | +| **protocol** | `HttpProtocol` | asyncio.protocol 子类。 | +| **access_log** | `True` | 启用请求访问日志(显著降低服务器速度)。 | +| **reload_dir** | `None` | A path or list of paths to directories the auto-reloader should watch. | +| **noisy_exceptions** | `None` | Whether to set noisy exceptions globally. None means leave as default. | +| **motd** | `True` | Whether to display the startup message. | +| **motd_display** | `None` | A dict with extra key/value information to display in the startup message | +| **fast** | `False` | Whether to maximize worker processes. Cannot be used with workers. | +| **verbosity** | `0` | Level of logging detail. Max is 2. | +| **auto_tls** | `False` | Whether to auto-create a TLS certificate for local development. Not for production. | +| **single_process** | `False` | Whether to run Sanic in a single process. | + +---:1 In the above example, we decided to turn off the access log in order to increase performance. :--:1 +```python +# server.py +app = Sanic("MyApp") + +if __name__ == "__main__": + app.run(host='0.0.0.0', port=1337, access_log=False) +``` +:--- +---:1 Now, just execute the python script that has `app.run(...)` :--:1 ```bash python server.py ``` - :--- -### 子进程(Workers) +For a slightly more advanced implementation, it is good to know that `app.run` will call `app.prepare` and `Sanic.serve` under the hood. ----:1 +---:1 Therefore, these are equivalent: :--:1 +```python +if __name__ == "__main__": + app.run(host='0.0.0.0', port=1337, access_log=False) +``` +```python +if __name__ == "__main__": + app.prepare(host='0.0.0.0', port=1337, access_log=False) + Sanic.serve() +``` +:--- -在默认情况下,Sanic 在主进程中只占用一个 CPU 核心进行服务的监听。要想增加并发,只需在运行参数中指定 workers 的数量即可。 +### 子进程(Workers) -:--:1 +---:1 By default, Sanic runs a main process and a single worker process (see [worker manager](./manager.md) for more details). +要想增加并发,只需在运行参数中指定 workers 的数量即可。 :--:1 ```python app.run(host='0.0.0.0', port=1337, workers=4) ``` - :--- -Sanic 会自动管理多个进程,并在它们之间进行负载均衡。我们建议将子进程数量设置的和您机器的 CPU 核心数量一样。 +Sanic 会自动管理多个进程,并在它们之间进行负载均衡。 我们建议将子进程数量设置的和您机器的 CPU 核心数量一样。 ----:1 - -获得最大 CPU 性能的最简单方法是使用 `fast` 参数。这将自动以系统最大的核心数量来创建工作线程。 - -:--:1 +获得最大 CPU 性能的最简单方法是使用 `fast` 参数。 This will automatically run the maximum number of workers given the system constraints. +*Added in v21.12* :--:1 ```python app.run(host='0.0.0.0', port=1337, fast=True) ``` - ```python $ sanic server:app --host=0.0.0.0 --port=1337 --fast ``` - :--- 在没有 `fast` 选项的旧版 Sanic 中, 在基于 Linux 的操作系统上,有一个通用的方式来检查 CPU 核心数量: @@ -83,7 +105,7 @@ $ sanic server:app --host=0.0.0.0 --port=1337 --fast $ nproc ``` -或者,我们可以使用 Python 来获取该值: +Or, let Python do it: ```python import multiprocessing @@ -91,30 +113,40 @@ workers = multiprocessing.cpu_count() app.run(..., workers=workers) ``` +In version 22.9, Sanic introduced a new worker manager to provide more consistency and flexibility between development and production servers. Read [about the manager](./manager.md) for more details about workers. + +---:1 If you only want to run Sanic with a single process, specify `single_process` in the run arguments. This means that auto-reload, and the worker manager will be unavailable. :--:1 +```python +app.run(host='0.0.0.0', port=1337, single_process=True) +``` +:--- + ### 通过命令行运行(Running via command) #### Sanic 命令行界面(Sanic CLI) ----:1 - Sanic 也提供一个简单的命令行界面,来帮助您通过命令行启动。 比如,如果您在 `server.py` 文件中初始化了一个 Sanic 应用,您可以使用右侧命令运行程序: - -:--:1 - ```bash sanic server.app --host=0.0.0.0 --port=1337 --workers=4 ``` :--- -您还可以使用 `sanic --help` 来查看所有选项。 +或者,我们可以使用 Python 来获取该值: + +::: details Sanic CLI help output ```text $ sanic --help -usage: sanic [-h] [--version] [--factory] [-s] [-H HOST] [-p PORT] [-u UNIX] [--cert CERT] [--key KEY] [--tls DIR] [--tls-strict-host] - [-w WORKERS | --fast] [--access-logs | --no-access-logs] [--debug] [-d] [-r] [-R PATH] [--motd | --no-motd] [-v] +usage: sanic [-h] [--version] + [--factory | -s | --inspect | --inspect-raw | --trigger-reload | --trigger-shutdown] + [--http {1,3}] [-1] [-3] [-H HOST] [-p PORT] [-u UNIX] + [--cert CERT] [--key KEY] [--tls DIR] [--tls-strict-host] + [-w WORKERS | --fast | --single-process] [--legacy] + [--access-logs | --no-access-logs] [--debug] [-r] [-R PATH] [-d] + [--auto-tls] [--coffee | --no-coffee] [--motd | --no-motd] [-v] [--noisy-exceptions | --no-noisy-exceptions] module @@ -140,129 +172,179 @@ usage: sanic [-h] [--version] [--factory] [-s] [-H HOST] [-p PORT] [-u UNIX] [-- Required ======== Positional: - module Path to your Sanic app. Example: path.to.server:app - If running a Simple Server, path to directory to serve. Example: ./ + module Path to your Sanic app. Example: path.to.server:app + If running a Simple Server, path to directory to serve. Example: ./ Optional ======== General: - -h, --help show this help message and exit - --version show program's version number and exit + -h, --help show this help message and exit + --version show program's version number and exit Application: - --factory Treat app as an application factory, i.e. a () -> callable - -s, --simple Run Sanic as a Simple Server, and serve the contents of a directory - (module arg should be a path) + --factory Treat app as an application factory, i.e. a () -> callable + -s, --simple Run Sanic as a Simple Server, and serve the contents of a directory + (module arg should be a path) + --inspect Inspect the state of a running instance, human readable + --inspect-raw Inspect the state of a running instance, JSON output + --trigger-reload Trigger worker processes to reload + --trigger-shutdown Trigger all processes to shutdown + + HTTP version: + --http {1,3} Which HTTP version to use: HTTP/1.1 or HTTP/3. Value should + be either 1, or 3. [default 1] + -1 Run Sanic server using HTTP/1.1 + -3 Run Sanic server using HTTP/3 Socket binding: - -H HOST, --host HOST Host address [default 127.0.0.1] - -p PORT, --port PORT Port to serve on [default 8000] - -u UNIX, --unix UNIX location of unix socket + -H HOST, --host HOST + Host address [default 127.0.0.1] + -p PORT, --port PORT + Port to serve on [default 8000] + -u UNIX, --unix UNIX + location of unix socket TLS certificate: - --cert CERT Location of fullchain.pem, bundle.crt or equivalent - --key KEY Location of privkey.pem or equivalent .key file - --tls DIR TLS certificate folder with fullchain.pem and privkey.pem - May be specified multiple times to choose multiple certificates - --tls-strict-host Only allow clients that send an SNI matching server certs + --cert CERT Location of fullchain.pem, bundle.crt or equivalent + --key KEY Location of privkey.pem or equivalent .key file + --tls DIR TLS certificate folder with fullchain.pem and privkey.pem + May be specified multiple times to choose multiple certificates + --tls-strict-host Only allow clients that send an SNI matching server certs Worker: - -w WORKERS, --workers WORKERS Number of worker processes [default 1] - --fast Set the number of workers to max allowed - --access-logs Display access logs - --no-access-logs No display access logs + -w WORKERS, --workers WORKERS + Number of worker processes [default 1] + --fast Set the number of workers to max allowed + --single-process Do not use multiprocessing, run server in a single process + --legacy Use the legacy server manager + --access-logs Display access logs + --no-access-logs No display access logs Development: - --debug Run the server in debug mode - -d, --dev Currently is an alias for --debug. But starting in v22.3, - --debug will no longer automatically trigger auto_restart. - However, --dev will continue, effectively making it the - same as debug + auto_reload. - -r, --reload, --auto-reload Watch source directory for file changes and reload on changes - -R PATH, --reload-dir PATH Extra directories to watch and reload on changes + --debug Run the server in debug mode + -r, --reload, --auto-reload + Watch source directory for file changes and reload on changes + -R PATH, --reload-dir PATH + Extra directories to watch and reload on changes + -d, --dev debug + auto reload + --auto-tls Create a temporary TLS certificate for local development (requires mkcert or trustme) Output: - --motd Show the startup display - --no-motd No show the startup display - -v, --verbosity Control logging noise, eg. -vv or --verbosity=2 [default 0] - --noisy-exceptions Output stack traces for all exceptions - --no-noisy-exceptions No output stack traces for all exceptions + --coffee Uhm, coffee? + --no-coffee No uhm, coffee? + --motd Show the startup display + --no-motd No show the startup display + -v, --verbosity Control logging noise, eg. -vv or --verbosity=2 [default 0] + --noisy-exceptions Output stack traces for all exceptions + --no-noisy-exceptions + No output stack traces for all exceptions ``` +::: #### 作为模块运行 (As a module) ----:1 - -Sanic 也可以被当做模板直接调用。 - -:--:1 - +---:1 It can also be called directly as a module. :--:1 ```bash python -m sanic server.app --host=0.0.0.0 --port=1337 --workers=4 ``` - :--- -::: tip 小提示 - -无论使用哪种方法(CLI 或模块),都再不应该在 Python 文件中调用 `app.run()`。如果您想调用该方法,请确认将其包装起来,使它只有在使用解释器运行文件时才会被执行。 +无论使用哪种方法(CLI 或模块),都再不应该在 Python 文件中调用 `app.run()`。 如果您想调用该方法,请确认将其包装起来,使它只有在使用解释器运行文件时才会被执行。 ```python if __name__ == '__main__': app.run(host='0.0.0.0', port=1337, workers=4) ``` - ::: -#### Sanic 简易服务器 - ----:1 -有时,您为了快速搭建一个本地环境,只需要代理一些静态文件。现在,只要指定一个特定的目录,Sanic 就能为您搭建一个简易的静态文件服务器。 - -:--:1 +### Sanic 简易服务器 +---:1 Sometimes you just have a directory of static files that need to be served. This especially can be handy for quickly standing up a localhost server. 现在,只要指定一个特定的目录,Sanic 就能为您搭建一个简易的静态文件服务器。 :--:1 ```bash sanic ./path/to/dir --simple ``` +:--- +---:1 This could also be paired with auto-reloading. :--:1 +```bash +sanic ./path/to/dir --simple --reload --reload-dir=./path/to/dir +``` :--- ----:1 +*Added in v21.6* + +### HTTP/3 + + +Sanic server offers HTTP/3 support using [aioquic](https://github.com/aiortc/aioquic). This **must** be installed to use HTTP/3: + +``` +pip install sanic aioquic +``` + +``` +pip install sanic[http3] +``` -这也可以和自动重启功能一起使用。 +::: tip 小提示 +---:1 +``` +$ sanic path.to.server:app --http=3 +``` + +``` +$ sanic path.to.server:app -3 +``` :--:1 -```bash -sanic ./path/to/dir --simple --reload --reload-dir=./path/to/dir +```python +app.run(version=3) ``` +:--- +To run both an HTTP/3 and HTTP/1.1 server simultaneously, you can use [application multi-serve](../release-notes/v22.3.html#application-multi-serve) introduced in v22.3. This will automatically add an [Alt-Svc](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Alt-Svc) header to your HTTP/1.1 requests to let the client know that it is also available as HTTP/3. + + +---:1 +``` +$ sanic path.to.server:app --http=3 --http=1 +``` + +``` +$ sanic path.to.server:app -3 -1 +``` +:--:1 + +```python +app.prepare(version=3) +app.prepare(version=1) +Sanic.serve() +``` :--- -## ASGI +Because HTTP/3 requires TLS, you cannot start a HTTP/3 server without a TLS certificate. You should [set it up yourself](../how-to/tls.html) or use `mkcert` if in `DEBUG` mode. Currently, automatic TLS setup for HTTP/3 is not compatible with `trustme`. See [development](./development.md) for more details. -Sanic 同样兼容 ASGI。这意味着您可以使用您喜爱的 ASGI 服务器来运行 Sanic。现在有三大主流的 ASGI 服务器, [Daphne](http://github.com/django/daphne),[Uvicorn](https://www.uvicorn.org/),和 [Hypercorn](https://pgjones.gitlab.io/hypercorn/index.html) 。 +*Added in v22.6* -::: warning 警告 +## ASGI -Daphne 不支持 ASGI 中的 `lifespan` 协议,因此并不能用于 Sanic。更多详细信息请参考 -[Issue #264](https://github.com/django/daphne/issues/264)。 +Sanic 同样兼容 ASGI。 这意味着您可以使用您喜爱的 ASGI 服务器来运行 Sanic。 现在有三大主流的 ASGI 服务器, [Daphne](http://github.com/django/daphne),[Uvicorn](https://www.uvicorn.org/),和 [Hypercorn](https://pgjones.gitlab.io/hypercorn/index.html) 。 -::: +Daphne 不支持 ASGI 中的 `lifespan` 协议,因此并不能用于 Sanic。 更多详细信息请参考 [Issue #264](https://github.com/django/daphne/issues/264)。 ::: -您需要参考他们的文档来找到运行 ASGI 应用的正确方式,这些启动命令大概看起来像这样: +::: warning 警告 ``` uvicorn myapp:app hypercorn myapp:app ``` -当使用 ASGI 时,您需要关注以下几件事情: +A couple things to note when using ASGI: -1. 当使用 Sanic 服务器,websocket 功能将使用 `websockets` 包来实现。在 ASGI 模式中,将不会使用该第三方包,因为 ASGI 服务器将会管理 websocket 链接。 - -2. [ASGI 生命周期协议](https://asgi.readthedocs.io/en/latest/specs/lifespan.html) 中规定 ASGI 只支持两种服务器事件:启动和关闭。而 Sanic 则有四个事件:启动前、启动后、关闭前和关闭后。因此,在 ASGI 模式下,启动和关闭事件将连续运行,并不是根据服务器进程的实际状态来运行(因为此时是由 ASGI 服务器控制状态)。因此,最好使用 `after_server_start` 和 `before_server_stop` 。 +1. 当使用 Sanic 服务器,websocket 功能将使用 `websockets` 包来实现。 在 ASGI 模式中,将不会使用该第三方包,因为 ASGI 服务器将会管理 websocket 链接。 +2. [ASGI 生命周期协议](https://asgi.readthedocs.io/en/latest/specs/lifespan.html) 中规定 ASGI 只支持两种服务器事件:启动和关闭。 而 Sanic 则有四个事件:启动前、启动后、关闭前和关闭后。 因此,在 ASGI 模式下,启动和关闭事件将连续运行,并不是根据服务器进程的实际状态来运行(因为此时是由 ASGI 服务器控制状态)。 因此,最好使用 `after_server_start` 和 `before_server_stop` 。 ### Trio @@ -272,50 +354,33 @@ Sanic 对使用 Trio 运行有着实验性的支持: hypercorn -k trio myapp:app ``` + ## Gunicorn -[Gunicorn](http://gunicorn.org/) ("Green Unicorn") 是一个基于 UNIX 操作系统的 WSGI HTTP 服务器。它是从 Ruby 的 Unicorn 项目中移植而来,采用的是 pre-fork worker 模型。 +[Gunicorn](http://gunicorn.org/) ("Green Unicorn") 是一个基于 UNIX 操作系统的 WSGI HTTP 服务器。 它是从 Ruby 的 Unicorn 项目中移植而来,采用的是 pre-fork worker 模型。 -为了使用 Gunicorn 来运行 Sanic 应用程序,您需要使用 Sanic 提供的 `sanic.worker.GunicornWorker` 类作为 Gunicorn worker-class 参数。 +In order to run Sanic application with Gunicorn, you need to use it with the adapter from [uvicorn](https://www.uvicorn.org/). Make sure uvicorn is installed and run it with `uvicorn.workers.UvicornWorker` for Gunicorn worker-class argument: ```bash -gunicorn myapp:app --bind 0.0.0.0:1337 --worker-class sanic.worker.GunicornWorker +gunicorn myapp:app --bind 0.0.0.0:1337 --worker-class uvicorn.workers.UvicornWorker ``` -如果您的应用有内存泄漏的困扰,您可以通过配置 Gunicorn 使子进程在处理了一定数量的请求后平滑重启。这种方法可以很方便得减少内存泄漏带来的影响。 - 查看 [Gunicorn 文档](http://docs.gunicorn.org/en/latest/settings.html#max-requests) 来获取更多信息。 -::: warning - -当通过 `gunicorn` 运行 Sanic 时,您将失去 `async/await` 带来的诸多性能优势。对于该种部署方式,请三思而后行。的确,Gunicorn 提供了很多配置选项,但它不是让 Sanic 全速运行的最佳坏境。 - -::: +::: warning It is generally advised to not use `gunicorn` unless you need it. The Sanic Server is primed for running Sanic in production. Weigh your considerations carefully before making this choice. 的确,Gunicorn 提供了很多配置选项,但它不是让 Sanic 全速运行的最佳坏境。 ::: ## 性能方面的考虑 (Performance considerations) ----:1 - -当部署在生产环境时,请确保 `debug` 模式处于关闭状态。 - -:--:1 - +当部署在生产环境时,请确保 `debug` 模式处于关闭状态。 :--:1 ```python app.run(..., debug=False) ``` - :--- ----:1 - -如果您选择关闭了 `access_log` ,Sanic 将会全速运行。 - -如果您的确需要请求访问日志,又想获得更好的性能,可以考虑使用 [Nginx](./nginx.md) 作为代理,让 Nginx 来处理您的访问日志。这种方式要比用 Python 处理快得多得多。 - -:--:1 +::: warning +如果您的确需要请求访问日志,又想获得更好的性能,可以考虑使用 [Nginx](./nginx.md) 作为代理,让 Nginx 来处理您的访问日志。 这种方式要比用 Python 处理快得多得多。 :--:1 ```python app.run(..., access_log=False) ``` - :--- diff --git a/src/zh/guide/getting-started.md b/src/zh/guide/getting-started.md index b0de667300..4ebdbdd216 100644 --- a/src/zh/guide/getting-started.md +++ b/src/zh/guide/getting-started.md @@ -1,6 +1,6 @@ # 快速开始(Getting Started) -在我们开始之前,请确保您使用的是 Python3.7 或更高版本。目前已知可以使用的 Python 版本包括:3.7,3.8 和 3.9。 +在我们开始之前,请确保您使用的是 Python3.7 或更高版本。 目前已知可以使用的 Python 版本包括:3.7,3.8 和 3.9。 ## 安装(Install) @@ -14,11 +14,9 @@ pip install sanic 如果您熟悉其他任意一款基于装饰器的框架,那么您可能对此感觉有些亲切。 -::: tip 小提示 +如果您来自 Flask 或其他框架,则需要指出一些重要的事情。 请记住,Sanic 旨在提高性能、灵活性和易用性。 这些指导原则对 API 及其工作方式产生了重要影响。 ::: -如果您来自 Flask 或其他框架,则需要指出一些重要的事情。 请记住,Sanic 旨在提高性能、灵活性和易用性。 这些指导原则对 API 及其工作方式产生了重要影响。 -::: :--:1 @@ -37,39 +35,27 @@ async def hello_world(request): ### 注意(Important to note) -- 每一个请求响应函数都可以使用同步方式(`def hello_world`)和异步方式(`async def hello_world`)进行声明。除非您有一个明确的需求和完善的使用方法,否则的话,请尽量使用 `async` 来声明响应函数。 - -- `request` 对象始终是响应函数的第一个参数。 其他框架在需要导入的上下文变量中进行传递。 在 `async` 的世界里,如果使用隐式传递,那么它将无法完美的运行,更何况还要兼顾简洁且高效的表现形式。所以我们在这里进行显式传递。 - -- 您 **必须** 使用 `Response` 或继承自 `Response` 的类作为响应类型。在许多其他框架中,它们允许您使用诸如 `return "Hello World"` 或者 `return {"foo":"bar"}` 的方式来进行返回,但是为了执行这类隐式调用,需要在响应流程中的某个位置花费大量的时间来确定您到底想要表达什么意思。因此,我们以轻松调用为代价,来提升服务的响应速度,Sanic 会要求您对您的响应进行显式调用。 +- 每一个请求响应函数都可以使用同步方式(`def hello_world`)和异步方式(`async def hello_world`)进行声明。 除非您有一个明确的需求和完善的使用方法,否则的话,请尽量使用 `async` 来声明响应函数。 +- `request` 对象始终是响应函数的第一个参数。 其他框架在需要导入的上下文变量中进行传递。 在 `async` 的世界里,如果使用隐式传递,那么它将无法完美的运行,更何况还要兼顾简洁且高效的表现形式。 +- 您 **必须** 使用 `Response` 或继承自 `Response` 的类作为响应类型。 在许多其他框架中,它们允许您使用诸如 `return "Hello World"` 或者 `return {"foo":"bar"}` 的方式来进行返回,但是为了执行这类隐式调用,需要在响应流程中的某个位置花费大量的时间来确定您到底想要表达什么意思。 But, in order to do this implicit calling, somewhere in the chain needs to spend valuable time trying to determine what you meant. So, at the expense of this ease, Sanic has decided to require an explicit call. ### 运行(Running) ----:1 - -让我们将上面写好的文件保存为 `server.py`, 然后运行它。 - -:--:1 - +---:1 Let's save the above file as `server.py`. And launch it. :--:1 ```bash sanic server.app ``` - :--- -::: tip 小提示 +这是 **另一个** 重要的区别。 其他框架带有一个内置的开发服务器,并明确表示它只用于开发。 而 Sanic 的情况恰好相反。 -这是 **另一个** 重要的区别。其他框架带有一个内置的开发服务器,并明确表示它只用于开发。而 Sanic 的情况恰好相反。 - -**可以用于生产环境的服务器已经准备就绪** - -::: +让我们将上面写好的文件保存为 `server.py`, 然后运行它。 ## Sanic 拓展(Sanic Extensions) -Sanic 致力于构建一个简洁且没有任何偏见的特征表。该项目不想要求您以某种方式构建应用程序,并试图避免指定特定的开发模式。有许多由社区构建和维护的第三方插件,用于添加不符合核心库要求的附加功能。 +Sanic 致力于构建一个简洁且没有任何偏见的特征表。 该项目不想要求您以某种方式构建应用程序,并试图避免指定特定的开发模式。 有许多由社区构建和维护的第三方插件,用于添加不符合核心库要求的附加功能。 -但是,为了 **帮助 API 开发者** ,Sanic 组织维护了一个名为 [Sanic Extensions](../plugins/sanic-ext/getting-started.md) 的项目来提供各种易用的功能,包括: +请查看 [插件文档](../plugins/sanic-ext/getting-started.md) 来了解如何使用拓展插件。 - **OpenAPI** 使用 Redoc 和/或 Swagger 的文档 - **CORS** 保护 @@ -78,25 +64,21 @@ Sanic 致力于构建一个简洁且没有任何偏见的特征表。该项目 - 自动创建 `HEAD`, `OPTIONS`, 和 `TRACE` 响应函数 - 响应序列化 -安装它的首选方法是与 Sanic 一起安装,当然您也可以单独安装。 +::: tip 小提示 ---:1 - ``` $ pip install sanic[ext] ``` - :--:1 - ``` $ pip install sanic sanic-ext ``` - :--- -从 v21.12 开始,如果在相同的环境中,Sanic 将自动设置 Sanic 扩展。您可以通过以下的两种方式来进行访问拓展功能: +从 v21.12 开始,如果在相同的环境中,Sanic 将自动设置 Sanic 扩展。 您可以通过以下的两种方式来进行访问拓展功能: - `app.extend()` - 用于配置 Sanic 拓展 - `app.ext` - 注入到应用程序的扩展实例 -请查看 [插件文档](../plugins/sanic-ext/getting-started.md) 来了解如何使用拓展插件。 +See [the plugin documentation](../plugins/sanic-ext/getting-started.md) for more information about how to use and work with the plugin diff --git a/src/zh/guide/how-to/authentication.md b/src/zh/guide/how-to/authentication.md index 5ffc401c56..ac346a8315 100644 --- a/src/zh/guide/how-to/authentication.md +++ b/src/zh/guide/how-to/authentication.md @@ -2,14 +2,9 @@ > 我该如何控制认证和权限? -这是一个 *十分* 复杂的话题,很难用几行代码阐述清楚。尽管如此,本章节也许能够为您提供一些解决问题的思路。 +这是一个 *十分* 复杂的话题,很难用几行代码阐述清楚。 But, this should provide you with an idea on ways to tackle this problem. This example uses [JWTs](https://jwt.io/), but the concepts should be equally applicable to sessions or some other scheme. 下面的例子使用了 [JWTs](https://jwt.io/) 来实现,如果您想使用 session 或者是其他的方式,那做法应该是类似的。 - -:::: tabs - -::: tab server.py - ```python from sanic import Sanic, text @@ -26,10 +21,7 @@ app.blueprint(login) async def secret(request): return text("To go fast, you must be fast.") ``` -::: - -::: tab login.py - +:::: tabs ```python import jwt from sanic import Blueprint, text @@ -42,10 +34,7 @@ async def do_login(request): token = jwt.encode({}, request.app.config.SECRET) return text(token) ``` -::: - -::: tab auth.py - +::: tab server.py ```python from functools import wraps @@ -83,11 +72,7 @@ def protected(wrapped): return decorator(wrapped) ``` - -这一段装饰器的代码来自于 [装饰器](/zh/guide/best-practices/decorators.md) 一节。 - -::: - +这一段装饰器的代码来自于 [装饰器](/zh/guide/best-practices/decorators.md) 一节。 ::: :::: ```bash @@ -99,6 +84,14 @@ content-type: text/plain; charset=utf-8 You are unauthorized. +$ curl localhost:9999/secret -i +HTTP/1.1 401 Unauthorized +content-length: 21 +connection: keep-alive +content-type: text/plain; charset=utf-8 + +You are unauthorized. + $ curl localhost:9999/login -X POST 7 ↵ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.rjxS7ztIGt5tpiRWS8BGLUqjQFca4QOetHcZTi061DE @@ -116,10 +109,18 @@ content-length: 21 connection: keep-alive content-type: text/plain; charset=utf-8 +You are unauthorized. + +$ curl localhost:9999/secret -i -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.BAD" +HTTP/1.1 401 Unauthorized +content-length: 21 +connection: keep-alive +content-type: text/plain; charset=utf-8 + You are unauthorized. ``` -同时,您可以查看一下社区内提供的资源: - +::: tab login.py + - Awesome Sanic - [认证(Authentication)](https://github.com/mekicha/awesome-sanic/blob/master/README.md#authentication) & [会话(Session)](https://github.com/mekicha/awesome-sanic/blob/master/README.md#session) - [EuroPython 2020 - Overcoming access control in web APIs](https://www.youtube.com/watch?v=Uqgoj43ky6A) diff --git a/src/zh/guide/how-to/autodiscovery.md b/src/zh/guide/how-to/autodiscovery.md index 050b5bf492..ad2215060b 100644 --- a/src/zh/guide/how-to/autodiscovery.md +++ b/src/zh/guide/how-to/autodiscovery.md @@ -7,16 +7,13 @@ title: 自动发现(Autodiscovery) > 我该如何自动发现我正在使用的组件来构建我的应用程序? -当创建一个应用时,您可能碰到的第一个问题就是 *如何* 去组织一个项目。Sanic 十分依赖装饰器来注册路由、中间件和监听器。并且,在创建蓝图之后,也需要被挂载到应用上。 +当创建一个应用时,您可能碰到的第一个问题就是 *如何* 去组织一个项目。 Sanic 十分依赖装饰器来注册路由、中间件和监听器。 并且,在创建蓝图之后,也需要被挂载到应用上。 -一种解决方案是创建一个文件,在这个文件中导入 *所有* 的东西并且被应用于 Sanic 应用实例。另一种解决方式是将 Sanic 应用实例作为全局变量在不同文件中传递并使用。这两种方式都会有他的缺点。 +一种解决方案是创建一个文件,在这个文件中导入 *所有* 的东西并且被应用于 Sanic 应用实例。 另一种解决方式是将 Sanic 应用实例作为全局变量在不同文件中传递并使用。 这两种方式都会有他的缺点。 -而自动发现函数作为另一种补充。您可以把应用程序与模块(已经导入的对象或字符串)传入自动发现的函数中,该工具函数就会自动加载一切。 +而自动发现函数作为另一种补充。 您可以把应用程序与模块(已经导入的对象或字符串)传入自动发现的函数中,该工具函数就会自动加载一切。 :::: tabs - -::: tab server.py - ```python from sanic import Sanic from sanic.response import empty @@ -35,7 +32,6 @@ autodiscover( app.route("/")(lambda _: empty()) ``` - ```bash [2021-03-02 21:37:02 +0200] [880451] [INFO] Goin' Fast @ http://127.0.0.1:9999 [2021-03-02 21:37:02 +0200] [880451] [DEBUG] something @@ -45,11 +41,7 @@ app.route("/")(lambda _: empty()) [2021-03-02 21:37:02 +0200] [880451] [DEBUG] something inside __init__.py [2021-03-02 21:37:02 +0200] [880451] [INFO] Starting worker [880451] ``` - -::: - -::: tab utility.py - +::: tab server.py ```python from glob import glob @@ -98,11 +90,7 @@ def autodiscover( for bp in blueprints: app.blueprint(bp) ``` - ::: - -::: tab blueprints/level1.py - ```python from sanic import Blueprint from sanic.log import logger @@ -114,11 +102,7 @@ level1 = Blueprint("level1") def print_something(app, loop): logger.debug("something @ level1") ``` - -::: - -::: tab blueprints/one/two/level3.py - +::: tab utility.py ```python from sanic import Blueprint from sanic.log import logger @@ -130,11 +114,7 @@ level3 = Blueprint("level3") def print_something(app, loop): logger.debug("something @ level3") ``` - ::: - -::: tab listeners/something.py - ```python from sanic import Sanic from sanic.log import logger @@ -146,11 +126,7 @@ app = Sanic.get_app("auto") def print_something(app, loop): logger.debug("something") ``` - -::: - -::: tab parent/child/__init__.py - +::: tab blueprints/level1.py ```python from sanic import Blueprint from sanic.log import logger @@ -162,11 +138,7 @@ bp = Blueprint("__init__") def print_something(app, loop): logger.debug("something inside __init__.py") ``` - ::: - -::: tab parent/child/nested.py - ```python from sanic import Blueprint from sanic.log import logger @@ -178,7 +150,4 @@ nested = Blueprint("nested") def print_something(app, loop): logger.debug("something @ nested") ``` - -::: - -:::: +::: tab blueprints/one/two/level3.py diff --git a/src/zh/guide/how-to/cors.md b/src/zh/guide/how-to/cors.md index 6fa13f5973..1e5b89b03b 100644 --- a/src/zh/guide/how-to/cors.md +++ b/src/zh/guide/how-to/cors.md @@ -7,10 +7,9 @@ title: 跨域资源共享(CORS) > 我该如何配置跨域资源共享? -:::: tabs - -::: tab server.py +The best solution is to use [Sanic Extensions](../../plugins/sanic-ext/http/cors.md). However, if you would like to build your own version, you could use this limited example. +:::: tabs ```python from sanic import Sanic, text @@ -30,12 +29,15 @@ app.register_listener(setup_options, "before_server_start") # Fill in CORS headers app.register_middleware(add_cors_headers, "response") -``` -::: -::: tab cors.py +# Add OPTIONS handlers to any route that is missing it +app.register_listener(setup_options, "before_server_start") +# Fill in CORS headers +app.register_middleware(add_cors_headers, "response") +``` +::: tab server.py ```python from typing import Iterable @@ -65,11 +67,7 @@ def add_cors_headers(request, response): ] _add_cors_headers(response, methods) ``` - ::: - -::: tab options.py - ```python from collections import defaultdict from typing import Dict, FrozenSet @@ -117,13 +115,42 @@ def setup_options(app: Sanic, _): uri, methods=["OPTIONS"], ) - app.router.finalize() -``` + app.router.finalize() You will need to change this for older versions. + for route in routes.values(): + if "OPTIONS" not in route.methods: + needs_options[route.uri].extend(route.methods) -::: + return { + uri: frozenset(methods) for uri, methods in dict(needs_options).items() + } -:::: +def _options_wrapper(handler, methods): + def wrapped_handler(request, *args, **kwargs): + nonlocal methods + return handler(request, methods) + + return wrapped_handler + + +async def options_handler(request, methods) -> response.HTTPResponse: + resp = response.empty() + _add_cors_headers(resp, methods) + return resp + + +def setup_options(app: Sanic, _): + app.router.reset() + needs_options = _compile_routes_needing_options(app.router.routes_all) + for uri, methods in needs_options.items(): + app.add_route( + _options_wrapper(options_handler, methods), + uri, + methods=["OPTIONS"], + ) + app.router.finalize() +``` +::: tab cors.py ``` $ curl localhost:9999/ -i HTTP/1.1 200 OK @@ -144,8 +171,15 @@ Access-Control-Allow-Origin: mydomain.com Access-Control-Allow-Credentials: true Access-Control-Allow-Headers: origin, content-type, accept, authorization, x-xsrf-token, x-request-id connection: keep-alive -``` -同时,您可以查看一下社区内提供的资源: +$ curl localhost:9999/ -i -X OPTIONS +HTTP/1.1 204 No Content +Access-Control-Allow-Methods: GET,POST,OPTIONS +Access-Control-Allow-Origin: mydomain.com +Access-Control-Allow-Credentials: true +Access-Control-Allow-Headers: origin, content-type, accept, authorization, x-xsrf-token, x-request-id +connection: keep-alive +``` +::: - [Awesome Sanic](https://github.com/mekicha/awesome-sanic/blob/master/README.md#frontend) diff --git a/src/zh/guide/how-to/mounting.md b/src/zh/guide/how-to/mounting.md index 53ef3e6215..5faf22df45 100644 --- a/src/zh/guide/how-to/mounting.md +++ b/src/zh/guide/how-to/mounting.md @@ -42,12 +42,10 @@ server { } } ``` - ```bash $ docker-compose up -d $ sanic server.app --port=9999 --host=0.0.0.0 ``` - ```bash $ curl localhost/api/foo URL: http://example.com/api/foo diff --git a/src/zh/guide/how-to/orm.md b/src/zh/guide/how-to/orm.md index d5e2383134..e013fefbd0 100644 --- a/src/zh/guide/how-to/orm.md +++ b/src/zh/guide/how-to/orm.md @@ -4,43 +4,149 @@ Sanic 可以与所有的 ORM 工具一起使用,但是非异步的 ORM 框架将会拖累 Sanic 的性能。 目前已经支持异步的 orm 有很多, 比较好用的有: -[SQLAlchemy 1.4](https://docs.sqlalchemy.org/en/14/changelog/changelog_14.html) [tortoise-orm](https://github.com/tortoise/tortoise-orm) +At present, there are many ORMs that support Python's `async`/`await` keywords. Some possible choices include: -只知道有这些工具但不会用?不用担心,接下来我们以 mysql 为例, 手把手教您使用两种 orm +- [Mayim](https://ahopkins.github.io/mayim/) +- [SQLAlchemy 1.4](https://docs.sqlalchemy.org/en/14/changelog/changelog_14.html) +- [pip install tortoise-orm](https://github.com/tortoise/tortoise-orm) -## SQLAlchemy +Integration in to your Sanic application is fairly simple: -是的,您没有听错,在 [SQLAlchemy 1.4](https://docs.sqlalchemy.org/en/14/changelog/changelog_14.html) 版本中,添加了对 asyncio -的原生支持,至此,Sanic 终于可以和 ORM 界的老前辈愉快的玩耍了。 +## Mayim ----:1 +Mayim ships with [an extension for Sanic Extensions](https://ahopkins.github.io/mayim/guide/extensions.html#sanic), which makes it super simple to get started with Sanic. It is certainly possible to run Mayim with Sanic without the extension, but it is recommended because it handles all of the [lifecycle events](https://sanic.dev/en/guide/basics/listeners.html) and [dependency injections](https://sanic.dev/en/plugins/sanic-ext/injection.html). +---:1 ### 安装依赖 -首先,我们需要安装依赖,在以前的时候,我们安装的依赖是 `sqlalchemy` 和 `pymysql` 但是现在我们需要的是 `sqlalchemy` 和 `aiomysql` - -:--:1 - +First, we need to install the required dependencies. See [Mayim docs](https://ahopkins.github.io/mayim/guide/install.html#postgres) for the installation needed for your DB driver. :--:1 ```shell -pip install sqlalchemy, aiomysql +pip install sanic-ext +pip install mayim[postgres] ``` +:--- + +---:1 +### 定义 ORM 模型 + +Mayim allows you to use whatever you want for models. Whether it is [dataclasses](https://docs.python.org/3/library/dataclasses.html), [pydantic](https://pydantic-docs.helpmanual.io/), [attrs](https://www.attrs.org/en/stable/), or even just plain `dict` objects. Since it works very nicely [out of the box with Pydantic](https://ahopkins.github.io/mayim/guide/pydantic.html), that is what we will use here. :--:1 +```python +# ./models.py +from pydantic import BaseModel -或者 -```shell -pip install sqlalchemy, asyncpg +class City(BaseModel): + id: int + name: str + district: str + population: int + + +class Country(BaseModel): + code: str + name: str + continent: str + region: str + capital: City ``` +:--- +---:1 +### Define SQL + +If you are unfamiliar, Mayim is different from other ORMs in that it is one-way, SQL-first. This means you define your own queries either inline, or in a separate `.sql` file, which is what we will do here. :--:1 +```sql +-- ./queries/select_all_countries.sql +SELECT country.code, + country.name, + country.continent, + country.region, + ( + SELECT row_to_json(q) + FROM ( + SELECT city.id, + city.name, + city.district, + city.population + ) q + ) capital +FROM country + JOIN city ON country.capital = city.id +ORDER BY country.name ASC +LIMIT $limit OFFSET $offset; +``` :--- ---:1 +### 创建 Sanic app 与异步数据库引擎。 -### 定义 ORM 模型 +We need to create the app instance and attach the `SanicMayimExtension` with any executors. :--:1 +```python +# ./server.py +from sanic import Sanic, Request, json +from sanic_ext import Extend +from mayim.executor import PostgresExecutor +from mayim.extensions import SanicMayimExtension +from models import Country + + +class CountryExecutor(PostgresExecutor): + async def select_all_countries( + self, limit: int = 4, offset: int = 0 + ) -> list[Country]: + ... + + +app = Sanic("Test") +Extend.register( + SanicMayimExtension( + executors=[CountryExecutor], + dsn="postgres://...", + ) +) +``` +:--- -您依旧可以按照以前的方式来创建 ORM 模型 +---:1 +### 注册路由 +Because we are using Mayim's extension for Sanic, we have the automatic `CountryExecutor` injection into the route handler. It makes for an easy, type-annotated development experience. :--:1 +```python +@app.get("/") +async def handler(request: Request, executor: CountryExecutor): + countries = await executor.select_all_countries() + return json({"countries": [country.dict() for country in co +``` +:--- + +---:1 +### 发送请求 :--:1 +```sh +curl 'http://127.0.0.1:8000' +{"countries":[{"code":"AFG","name":"Afghanistan","continent":"Asia","region":"Southern and Central Asia","capital":{"id":1,"name":"Kabul","district":"Kabol","population":1780000}},{"code":"ALB","name":"Albania","continent":"Europe","region":"Southern Europe","capital":{"id":34,"name":"Tirana","district":"Tirana","population":270000}},{"code":"DZA","name":"Algeria","continent":"Africa","region":"Northern Africa","capital":{"id":35,"name":"Alger","district":"Alger","population":2168000}},{"code":"ASM","name":"American Samoa","continent":"Oceania","region":"Polynesia","capital":{"id":54,"name":"Fagatogo","district":"Tutuila","population":2323}}]} +``` +:--- + + +## SQLAlchemy + +是的,您没有听错,在 [SQLAlchemy 1.4](https://docs.sqlalchemy.org/en/14/changelog/changelog_14.html) 版本中,添加了对 asyncio 的原生支持,至此,Sanic 终于可以和 ORM 界的老前辈愉快的玩耍了。 Be aware that this functionality is still considered *beta* by the SQLAlchemy project. + + +---:1 +### 安装依赖 + +First, we need to install the required dependencies. 首先,我们需要安装依赖,在以前的时候,我们安装的依赖是 `sqlalchemy` 和 `pymysql` 但是现在我们需要的是 `sqlalchemy` 和 `aiomysql` :--:1 +```shell +pip install sqlalchemy, aiomysql +``` +:--- + +---:1 +### 定义 ORM 模型 +您依旧可以按照以前的方式来创建 ORM 模型 :--:1 ```python # ./models.py from sqlalchemy import INTEGER, Column, ForeignKey, String @@ -70,17 +176,12 @@ class Car(BaseModel): user_id = Column(ForeignKey("person.id")) user = relationship("Person", back_populates="cars") ``` - :--- ---:1 +### 创建 Sanic app 与异步数据库引擎 -### 创建 Sanic app 与异步数据库引擎。 - -这里我们使用 mysql 作为数据库,您也可以选择 PostgreSQL / SQLite,注意要将驱动从 `aiomysql` 换为 `asyncpg` / `aiosqlite` - -:--:1 - +这里我们使用 mysql 作为数据库,您也可以选择 PostgreSQL / SQLite,注意要将驱动从 `aiomysql` 换为 `asyncpg` / `aiosqlite` Pay attention to changing the driver from `aiomysql` to `asyncpg`/`aiosqlite`. :--:1 ```python # ./server.py from sanic import Sanic @@ -90,20 +191,14 @@ app = Sanic("my_app") bind = create_async_engine("mysql+aiomysql://root:root@localhost/test", echo=True) ``` - :--- ---:1 - ### 注册中间件 -在这里,请求中间件为我们创建了一个可用的 `AsyncSession` 对象并且将其绑定至 `request.ctx` 中,而 `_base_model_session_ctx` 也会在这是被赋予可用的值,如果您需要在其他地方使用 -session 对象(而非从 `request.ctx` 中取值),该全局变量或许能帮助您(它是线程安全的)。 - -响应中间件会将创建的 `AsyncSession` 关闭,并重置 `_base_model_session_ctx` 的值,进而释放资源。 - -:--:1 +在这里,请求中间件为我们创建了一个可用的 `AsyncSession` 对象并且将其绑定至 `request.ctx` 中,而 `_base_model_session_ctx` 也会在这是被赋予可用的值,如果您需要在其他地方使用 session 对象(而非从 `request.ctx` 中取值),该全局变量或许能帮助您(它是线程安全的)。 +响应中间件会将创建的 `AsyncSession` 关闭,并重置 `_base_model_session_ctx` 的值,进而释放资源。 :--:1 ```python # ./server.py from contextvars import ContextVar @@ -111,12 +206,13 @@ from contextvars import ContextVar from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import sessionmaker -_base_model_session_ctx = ContextVar("session") +_sessionmaker = sessionmaker(bind, AsyncSession, expire_on_commit=False) +_base_model_session_ctx = ContextVar("session") @app.middleware("request") async def inject_session(request): - request.ctx.session = sessionmaker(bind, AsyncSession, expire_on_commit=False)() + request.ctx.session = _sessionmaker() request.ctx.session_ctx_token = _base_model_session_ctx.set(request.ctx.session) @@ -126,17 +222,12 @@ async def close_session(request, response): _base_model_session_ctx.reset(request.ctx.session_ctx_token) await request.ctx.session.close() ``` - :--- ---:1 - ### 注册路由 -根据 SQLAlchemy 的官方文档,`session.query` 将在 2.0 版本中被淘汰,取而代之的是使用 `select` 查询 ORM 对象。 - -:--:1 - +根据 SQLAlchemy 的官方文档,`session.query` 将在 2.0 版本中被淘汰,取而代之的是使用 `select` 查询 ORM 对象。 :--:1 ```python # ./server.py from sqlalchemy import select @@ -169,15 +260,11 @@ async def get_user(request, pk): return json(person.to_dict()) ``` - :--- ---:1 - -### 发送请求 - +### 启动服务并发送请求: :--:1 - ```sh curl --location --request POST 'http://127.0.0.1:8000/user' {"name":"foo","cars":[{"brand":"Tesla"}]} @@ -187,33 +274,24 @@ curl --location --request POST 'http://127.0.0.1:8000/user' curl --location --request GET 'http://127.0.0.1:8000/user/1' {"name":"foo","cars":[{"brand":"Tesla"}]} ``` - :--- + ## Tortoise-ORM ---:1 +### Dependencies -### 安装依赖 - -tortoise-orm 的依赖非常简单,您只需要安装它即可。 - -:--:1 - +tortoise-orm 的依赖非常简单,您只需要安装它即可。 :--:1 ```shell -pip install tortoise-orm +pip install sqlalchemy, asyncpg ``` - :--- ---:1 +### Define ORM Model -### 定义 ORM 模型 - -如果您熟悉 Django 那您应该会觉得这一部分非常熟悉,是的,它就是仿照 Django 来的。 - -:--:1 - +如果您熟悉 Django 那您应该会觉得这一部分非常熟悉,是的,它就是仿照 Django 来的。 :--:1 ```python # ./models.py from tortoise import Model, fields @@ -226,17 +304,13 @@ class Users(Model): def __str__(self): return f"I am {self.name}" ``` - :--- ----:1 - -### 创建 Sanic app 与异步数据库引擎 - -tortoise-orm 专门提供了一套注册接口,方便用户的使用,您可以使用它轻松地创建数据库连接。 -:--:1 +---:1 +### Create Sanic App and Async Engine +tortoise-orm 专门提供了一套注册接口,方便用户的使用,您可以使用它轻松地创建数据库连接。 :--:1 ```python # ./main.py @@ -245,54 +319,34 @@ from tortoise.contrib.sanic import register_tortoise app = Sanic(__name__) + register_tortoise( app, db_url="mysql://root:root@localhost/test", modules={"models": ["models"]}, generate_schemas=True ) ``` - :--- ---:1 - -### 注册路由 - -直接按照 Django ORM 的操作方式进行操作就可以了 - +### Register Routes :--:1 - ```python - # ./main.py from models import Users -from sanic import Sanic, response - - -@app.route("/user") -async def list_all(request): - users = await Users.all() - return response.json({"users": [str(user) for user in users]}) - - -@app.route("/user/") -async def get_user(request, pk): - user = await Users.query(pk=pk) - return response.json({"user": str(user)}) +from tortoise.contrib.sanic import register_tortoise +app = Sanic(__name__) -if __name__ == "__main__": - app.run(port=5000) +register_tortoise( + app, db_url="mysql://root:root@localhost/test", modules={"models": ["models"]}, generate_schemas=True +) ``` - :--- ---:1 - -### 启动服务并发送请求: - +### Send Requests :--:1 - ```sh curl --location --request POST 'http://127.0.0.1:8000/user' {"users":["I am foo", "I am bar"]} @@ -302,5 +356,5 @@ curl --location --request POST 'http://127.0.0.1:8000/user' curl --location --request GET 'http://127.0.0.1:8000/user/1' {"user": "I am foo"} ``` +:--- -:--- \ No newline at end of file diff --git a/src/zh/guide/how-to/static-redirects.md b/src/zh/guide/how-to/static-redirects.md new file mode 100644 index 0000000000..5566050c74 --- /dev/null +++ b/src/zh/guide/how-to/static-redirects.md @@ -0,0 +1,110 @@ +# "Static" Redirects + +> How do I configure static redirects? + +:::: tabs +::: tab app.py +```python +### SETUP ### +import typing +import sanic, sanic.response + +# Create the Sanic app +app = sanic.Sanic(__name__) + +# This dictionary represents your "static" +# redirects. For example, these values +# could be pulled from a configuration file. +REDIRECTS = { + '/':'/hello_world', # Redirect '/' to '/hello_world' + '/hello_world':'/hello_world.html' # Redirect '/hello_world' to 'hello_world.html' +} + +# This function will return another function +# that will return the configured value +# regardless of the arguments passed to it. +def get_static_function(value:typing.Any) -> typing.Callable[..., typing.Any]: + return lambda *_, **__: value + +### ROUTING ### +# Iterate through the redirects +for src, dest in REDIRECTS.items(): + # Create the redirect response object + response:sanic.HTTPResponse = sanic.response.redirect(dest) + + # Create the handler function. Typically, + # only a sanic.Request object is passed + # to the function. This object will be + # ignored. + handler = get_static_function(response) + + # Route the src path to the handler + app.route(src)(handler) + +# Route some file and client resources +app.static('/files/', 'files') +app.static('/', 'client') + +### RUN ### +if __name__ == '__main__': + app.run( + '127.0.0.1', + 10000 + ) +``` +::: + +::: tab client/hello_world.html +```html + + + + + + + Hello World + + + +
+ Hello world! +
+ + +``` +::: + +::: tab client/hello_world.css +```css +#hello_world { + width: 1000px; + margin-left: auto; + margin-right: auto; + margin-top: 100px; + + padding: 100px; + color: aqua; + text-align: center; + font-size: 100px; + font-family: monospace; + + background-color: rgba(0, 0, 0, 0.75); + + border-radius: 10px; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.75); +} + +body { + background-image: url("/files/lake.jpg"); + background-repeat: no-repeat; + background-size: cover; +} +``` +::: + +::: tab files/lake.jpg ![](./assets/images/lake.jpg) ::: +:::: + +Also, checkout some resources from the community: + +- [Static Routing Example](https://github.com/Perzan/sanic-static-routing-example) diff --git a/src/zh/guide/how-to/tls.md b/src/zh/guide/how-to/tls.md index d5bce7611e..7164ece319 100644 --- a/src/zh/guide/how-to/tls.md +++ b/src/zh/guide/how-to/tls.md @@ -8,29 +8,19 @@ ---:1 -如果您想让 Sanic 自动加载您的证书文件,您需要将证书文件在指定的文件夹中命名为 `fullchain.pem` 和 `privkey.pem`: - :--:1 - ```sh sudo sanic myserver:app -H :: -p 443 \ --tls /etc/letsencrypt/live/example.com/ ``` - ```python app.run("::", 443, ssl="/etc/letsencrypt/live/example.com/") ``` - :--- ----:1 - 或者您可以将证书文件路径和秘钥文件路径作为字典的值传入到运行配置中: -除此之外,如果您的秘钥文件被加密,您还可以选择传入 `password`,除了 `password` 之外的所有参数都将被传递给 `request.conn_info.cert`。 - -:--:1 - +除此之外,如果您的秘钥文件被加密,您还可以选择传入 `password`,除了 `password` 之外的所有参数都将被传递给 `request.conn_info.cert`。 :--:1 ```python ssl = { "cert": "/path/to/fullchain.pem", @@ -39,15 +29,9 @@ ssl = { } app.run(host="0.0.0.0", port=8443, ssl=ssl) ``` - :--- ----:1 - -或者,如果您需要完全控制诸如允许哪些加密算法之类的细节,可以通过[`ssl` 模块](https://docs.python.org/3/library/ssl.html)。默认情况下,Sanic 只允许安全算法,这可能会限制来自非常旧的设备的访问。 - -:--:1 - +或者,如果您需要完全控制诸如允许哪些加密算法之类的细节,可以通过[`ssl` 模块](https://docs.python.org/3/library/ssl.html)。 默认情况下,Sanic 只允许安全算法,这可能会限制来自非常旧的设备的访问。 :--:1 ```python import ssl @@ -60,53 +44,37 @@ context.load_cert_chain( app.run(host="0.0.0.0", port=8443, ssl=context) ``` - :--- -## 具有多个证书的域名(Multiple domains with separate certificates) - ----:1 -有些时候,一个域名可能提供了多种证书,在这种情况下,Sanic 会主动选择和客户端相匹配的证书。该阶段处于 TLS 握手阶段,在该阶段中 Sanic 不会向客户端发送任何数据包。 - -如果客户端没有发送 SNI(服务器名称指示),将使用证书列表中的第一个证书,即使在客户端浏览器上它可能会由于名称不匹配而失败,并出现 TLS 错误。为了防止这种回退并使没有已知主机名的客户端立即断开连接,请在列表中添加 `None` 作为第一个条目。 +## 具有多个证书的域名(Multiple domains with separate certificates) -在 CLI 参数中,`-tls-strict-host` 是等效的选项。 - -:--:1 +有些时候,一个域名可能提供了多种证书,在这种情况下,Sanic 会主动选择和客户端相匹配的证书。 该阶段处于 TLS 握手阶段,在该阶段中 Sanic 不会向客户端发送任何数据包。 +如果客户端没有发送 SNI(服务器名称指示),将使用证书列表中的第一个证书,即使在客户端浏览器上它可能会由于名称不匹配而失败,并出现 TLS 错误。 为了防止这种回退并使没有已知主机名的客户端立即断开连接,请在列表中添加 `None` 作为第一个条目。 在 CLI 参数中,`-tls-strict-host` 是等效的选项。 :--:1 ```python ssl = ["certs/example.com/", "certs/bigcorp.test/"] app.run(host="0.0.0.0", port=8443, ssl=ssl) ``` - ```sh sanic myserver:app --tls certs/example.com/ --tls certs/bigcorp.test/ --tls-strict-host ``` - :--- -::: tip 小提示 - -如果您不希望将您的证书、真实主机名或网站内容透露给任何连接到该 IP 地址的人,而不是正确的 DNS 名称,您也可以在单个证书前面使用 `None`。 +如果您不希望将您的证书、真实主机名或网站内容透露给任何连接到该 IP 地址的人,而不是正确的 DNS 名称,您也可以在单个证书前面使用 `None`。 ::: -::: +单子上可以用字典。 这也允许指定证书匹配的域,尽管不能从这里控制证书上的名称。 如果未指定名称,则使用证书本身的名称。 ---:1 -单子上可以用字典。这也允许指定证书匹配的域,尽管不能从这里控制证书上的名称。如果未指定名称,则使用证书本身的名称。 - -字典也可以的用于证书列表中,您可以通过 `names` 来指定证书需要使用的域。这并不能控制证书上对应的域,只是做了一层额外的控制。如果您未指定证书要使用的域,则将使用证书本身对应的域。 - -只允许连接到主域 **example.com** 和 **bigcorp.test** 的子域示例: - :--:1 - ```python ssl = [ + None, # No fallback if names do not match! + ssl = [ None, # No fallback if names do not match! { "cert": "certs/example.com/fullchain.pem", @@ -116,20 +84,19 @@ ssl = [ ] app.run(host="0.0.0.0", port=8443, ssl=ssl) ``` - :--- ## 通过 `request.conn_info` 访问 TLS 信息(Accessing TLS information in handlers via `request.conn_info` fields) -- `.ssl` - 连接是否安全(布尔类型) -- `.cert` - 当前使用的证书以及证书相关信息(字典类型) -- `.server_name` - 客户端发送的 SNI 类型信息(字符类型,可能为空) +* `.ssl` - 连接是否安全(布尔类型) +* `.cert` - 当前使用的证书以及证书相关信息(字典类型) +* `.server_name` - 客户端发送的 SNI 类型信息(字符类型,可能为空) -请注意,所有 `conn_info` 字段都是针对每个连接的,其中可能会有许多请求。如果您在服务器前面使用了代理,那么这些请求虽然信息相同,但很有可能来自不同的用户。 +请注意,所有 `conn_info` 字段都是针对每个连接的,其中可能会有许多请求。 如果您在服务器前面使用了代理,那么这些请求虽然信息相同,但很有可能来自不同的用户。 ## 将证书请求之外的 HTTP 重定向到 HTTPS(Redirect HTTP to HTTPS, with certificate requests still over HTTP) -除了需要运行一个以 HTTPS 启动的应用程序之外,您还需要额外的启动一个额外的应用程序用于重定向: +In addition to your normal server(s) running HTTPS, run another server for redirection, `http_redir.py`: ```python from sanic import Sanic, exceptions, response @@ -144,17 +111,17 @@ def redirect_everything_else(request, exception): server, path = request.server_name, request.path if server and path.startswith("/"): return response.redirect(f"https://{server}{path}", status=308) - return response.text("Bad Request. Please use HTTPS!", status=400) + return response.text("Bad Request. Please use HTTPS!", status=400) Please use HTTPS!", status=400) ``` -最好通过 systemd 将您的转发应用程序注册为独立于 HTTPS 应用程序的服务。在请求证书时,会将请求发送到到您的 HTTP 服务上,此时 HTTPS 服务并未工作。IPv4 和 IPv6 的启动方式如下: +最好通过 systemd 将您的转发应用程序注册为独立于 HTTPS 应用程序的服务。 在请求证书时,会将请求发送到到您的 HTTP 服务上,此时 HTTPS 服务并未工作。 IPv4 和 IPv6 的启动方式如下: ``` sanic http_redir:app -H 0.0.0.0 -p 80 sanic http_redir:app -H :: -p 80 ``` -当然,您也可以在 HTTPS 应用程序上运行 HTTP 重定向服务。 +::: tip 小提示 ```python # app == Your main application @@ -186,15 +153,12 @@ async def runner(app, app_server): ## 为您的域名获取证书(Get certificates for your domain names) -您可以通过 [Let's Encrypt](https://letsencrypt.org/) 来获取免费的证书。您可以通过包管理器来安装 [certbot](https://certbot.eff.org/) 来请求创建一个证书: +您可以通过 [Let's Encrypt](https://letsencrypt.org/) 来获取免费的证书。 您可以通过包管理器来安装 [certbot](https://certbot.eff.org/) 来请求创建一个证书: ```sh sudo certbot certonly --key-type ecdsa --preferred-chain "ISRG Root X1" -d example.com -d www.example.com ``` -您可以通过 `-d` 参数来指定多个域名,这些域名都将共用一个证书。证书的保存位置在 `/etc/letsencrypt/live/example.com/` 下,与您列出的第一个域名名称相匹配。 -The key type and preferred chain options are necessary for getting a minimal size certificate file, essential for making your server run as *fast* as possible. The chain will still contain one RSA certificate until when Let's Encrypt gets their new EC chain trusted in all major browsers, possibly around 2023. - -如果你想获取体积最小的证书文件,那么 `秘钥类型` 以及 `首选链` 这两个选项是很有必要的。这有助于让您的服务器更快地进行认证。该链中包含了一个 RSA 证书,直到所有的主流服务器都信任 Let's Encrypt 的新 EC 链,可能在 2023 年左右。 +::: -密钥类型和首选链选项对于获取最小大小的证书文件是必需的,对于使您的服务器尽可能*快*地运行是必不可少的。该链仍将包含一个RSA证书,直到Let's Encrypt在所有主流浏览器中信任他们的新EC链,可能在2023年左右。 \ No newline at end of file +密钥类型和首选链选项对于获取最小大小的证书文件是必需的,对于使您的服务器尽可能*快*地运行是必不可少的。 该链仍将包含一个RSA证书,直到Let's Encrypt在所有主流浏览器中信任他们的新EC链,可能在2023年左右。 diff --git a/src/zh/guide/how-to/toc.md b/src/zh/guide/how-to/toc.md index 29961b9604..d25834c8f1 100644 --- a/src/zh/guide/how-to/toc.md +++ b/src/zh/guide/how-to/toc.md @@ -1,21 +1,13 @@ # 目录(Table of Contents) -虽然我们已经编写了各种用户场景下的代码样例来回答常见的问题。但是在大多数情况下,这些例子都是尽可能的简单,所以同时应该有完整的可运行方案。 +虽然我们已经编写了各种用户场景下的代码样例来回答常见的问题。 但是在大多数情况下,这些例子都是尽可能的简单,所以同时应该有完整的可运行方案。 -| 内容 | 我该如何 ... | -|:--------------------------------------|:-------------------------------------------------------| -| [应用挂载](./mounting.md) | ... 将我的应用程序挂载在根目录下的某个路径? | -| [认证](./authentication.md) | ... 控制认证和权限? | -| [自动发现](./autodiscovery.md) | ... 自动发现我正在使用的组件来构建我的应用程序? | -| [CORS](./cors.md) | ... 如何配置跨域资源共享 | -| CSRF | *Coming soon* | -| Databases | *Coming soon* | -| IPv6 | *Coming soon* | -| Request ID Logging | *Coming soon* | -| Request validation | *Coming soon* | -| Serialization | *Coming soon* | -| Server Sent Events | *Coming soon* | -| [ORM](./orm.md) | 在 Sanic 中使用 ORM ? | -| Task queues | *Coming soon* | -| [TLS/SSL/HTTPS](./tls.md) | ... 在 HTTPS 下运行 Sanic ?... 将 HTTP 重定向到 HTTPS ? | -| Websocket feeds | *Coming soon* | +| 内容 | 我该如何 ... | +|:------------------------------------------- |:-------------------------------------------------------------- | +| [应用挂载](./mounting.md) | ... 将我的应用程序挂载在根目录下的某个路径? | +| [认证](./authentication.md) | ... 控制认证和权限? | +| [自动发现](./autodiscovery.md) | ... 自动发现我正在使用的组件来构建我的应用程序? | +| [CORS](./cors.md) | ... 如何配置跨域资源共享 | +| [ORM](./orm) | 在 Sanic 中使用 ORM ? | +| ["Static" Redirects](./static-redirects.md) | ... configure static redirects | +| [TLS/SSL/HTTPS](./tls.md) | ... run Sanic via HTTPS?
... redirect HTTP to HTTPS? | diff --git a/src/zh/guide/release-notes/v21.12.md b/src/zh/guide/release-notes/v21.12.md index af5fd79513..ed509f9db8 100644 --- a/src/zh/guide/release-notes/v21.12.md +++ b/src/zh/guide/release-notes/v21.12.md @@ -4,15 +4,15 @@ ## 介绍(Introduction) -该版本为 21 版本 [发布周期](../../org/policies.md#release-schedule) 中的最后一个发行版本,目前 21 版本将进入长期维护状态,直至 2023 年 12 月。 +该版本为 21 版本 [发布周期](../../org/policies.md#release-schedule) 中的最后一个发行版本,目前 21 版本将进入长期维护状态,直至 2023 年 12 月。 Version 21 will now enter long-term support and will be supported for two years until December 2023. ## 更新内容(What to know) -更多详情请见 [更新记录](https://sanic.readthedocs.io/en/stable/sanic/changelog.html)。值得注意的新功能或突破性功能,以及要升级的内容... +更多详情请见 [更新记录](https://sanic.readthedocs.io/en/stable/sanic/changelog.html)。 值得注意的新功能或突破性功能,以及要升级的内容... ### 更严格的应用程序和蓝图命名检查(Strict application and blueprint names) -在 [v21.6](./v21.6.md#stricter-application-and-blueprint-names-and-deprecation) 中,应用程序或蓝图的名称需要符合新的命名规范,现在该规范将在应用启动时进行强制检查。 +在 [v21.6](./v21.6.md#stricter-application-and-blueprint-names-and-deprecation) 中,应用程序或蓝图的名称需要符合新的命名规范,现在该规范将在应用启动时进行强制检查。 That change is now being enforced at startup time. 名称 **必须** 符合以下规则: @@ -22,7 +22,7 @@ ### 更严格的应用程序和蓝图属性检查(Strict application and blueprint properties) -在以前的版本中,您可以直接将属性挂载到 `Sanic` 或者 `Blueprint` 上,现在您必须使用 `ctx` 来进行属性设置。 +The old leniency to allow directly setting properties of a `Sanic` or `Blueprint` object was deprecated and no longer allowed. You must use the `ctx` object. ```python app = Sanic("MyApp") @@ -39,7 +39,7 @@ app.ctx.db = Database() ### 升级您的流式响应 (如果您尚未准备好弃用) -`sanic.response.stream` 响应方法已被 **弃用** 并将在 v22.6 版本中删除。如果您仍在使用旧的流式响应,请将其升级。 +`sanic.response.stream` 响应方法已被 **弃用** 并将在 v22.6 版本中删除。 如果您仍在使用旧的流式响应,请将其升级。 **旧用法 - 已弃用** @@ -69,7 +69,7 @@ async def test(request: Request): ### CLI 样式更新及每日消息(MOTD) -Sanic CLI 获得了升级,它增加了非常多的新功能,使其能够与 `app.run` 一较高下。它还包含了一个全新的 MOTD 显示功能。让您快速的对当前环境配置有一个了解。MOTD 和 TTY 是互相感知的,因此它在您的日志中并不会有更详细的记录,主要目的是为了方便应用程序的开发。 +Sanic CLI 获得了升级,它增加了非常多的新功能,使其能够与 `app.run` 一较高下。 It adds a bunch of new features to make it on par with `app.run()`. 它还包含了一个全新的 MOTD 显示功能。 让您快速的对当前环境配置有一个了解。 The MOTD is TTY-aware, and therefore will be less verbose in server logs. It is mainly intended as a convenience during application development. ``` $ sanic --help @@ -141,6 +141,52 @@ Optional -r, --reload, --auto-reload Watch source directory for file changes and reload on changes -R PATH, --reload-dir PATH Extra directories to watch and reload on changes + Output: + --motd Show the startup display + --no-motd No show the startup display + -v, --verbosity Control logging noise, eg. -vv or --verbosity=2 [default 0] + --noisy-exceptions Output stack traces for all exceptions + --no-noisy-exceptions No output stack traces for all exceptions Example: path.to.server:app + If running a Simple Server, path to directory to serve. Example: ./ + +Optional +======== + General: + -h, --help show this help message and exit + --version show program's version number and exit + + Application: + --factory Treat app as an application factory, i.e. a () -> callable + -s, --simple Run Sanic as a Simple Server, and serve the contents of a directory + (module arg should be a path) + + Socket binding: + -H HOST, --host HOST Host address [default 127.0.0.1] + -p PORT, --port PORT Port to serve on [default 8000] + -u UNIX, --unix UNIX location of unix socket + + TLS certificate: + --cert CERT Location of fullchain.pem, bundle.crt or equivalent + --key KEY Location of privkey.pem or equivalent .key file + --tls DIR TLS certificate folder with fullchain.pem and privkey.pem + May be specified multiple times to choose multiple certificates + --tls-strict-host Only allow clients that send an SNI matching server certs + + Worker: + -w WORKERS, --workers WORKERS Number of worker processes [default 1] + --fast Set the number of workers to max allowed + --access-logs Display access logs + --no-access-logs No display access logs + + Development: + --debug Run the server in debug mode + -d, --dev Currently is an alias for --debug. But starting in v22.3, + --debug will no longer automatically trigger auto_restart. + However, --dev will continue, effectively making it the + same as debug + auto_reload. + -r, --reload, --auto-reload Watch source directory for file changes and reload on changes + -R PATH, --reload-dir PATH Extra directories to watch and reload on changes + Output: --motd Show the startup display --no-motd No show the startup display @@ -151,9 +197,9 @@ Optional ### 服务运行模式和 `debug` 的更改 -目前拥有的两个运行模式是:开发(`DEV`) 和 生产(`PRODUCTION`) Sanic 服务在生产模式下运行,这是为部署而设计的。 +There are now two running modes: `DEV` and `PRODUCTION`. By default, Sanic server will run under `PRODUCTION` mode. This is intended for deployments. -目前,`DEV` 模式的运行方式与旧版本 Sanic 中的 `debug=True` 非常相似。但是,在 v22.3 中,`debug=True` 将 **不再** 启用自动重载功能。如果您想要调试和自动重载,您应该启用 开发(`dev`) 模式。 +目前,`DEV` 模式的运行方式与旧版本 Sanic 中的 `debug=True` 非常相似。 However, in v22.3. 但是,在 v22.3 中,`debug=True` 将 **不再** 启用自动重载功能。 如果您想要调试和自动重载,您应该启用 开发(`dev`) 模式。 **开发模式** @@ -179,12 +225,13 @@ app.run() 变化摘要如下: -| 标志 | 模式 | 错误追溯 | 日志 | 访问日志 | 自动重载 | 最大 works | -| ------- | ----- | -------- | ------- | -------- | -------- | ---------- | -| --debug | DEBUG | yes | DEBUG | yes | ^1 | | -| | PROD | no | INFO ^2 | ^3 | | | -| --dev | DEBUG | yes | DEBUG | yes | yes | | -| --fast | | | | | | yes | +| 标志 | 模式 | 错误追溯 | 日志 | 访问日志 | 自动重载 | 最大 works | +| ------- | ----- | ---- | ------- | ---- | ---- | -------- | +| --debug | DEBUG | yes | DEBUG | yes | ^1 | | +| | PROD | no | INFO ^2 | ^3 | | | +| --dev | DEBUG | yes | DEBUG | yes | yes | | +| --fast | | | | | | yes | + - ^1 `--debug` 参数将在 v22.3 版本中移除自动重载功能 - ^2 在 v22.3 版本后将日志级别变更为 WARNING @@ -204,7 +251,7 @@ app.run(fast=True) ### 一流的 Sanic 拓展支持(First-class Sanic Extensions support) -[Sanic 拓展](../../plugins/sanic-ext/getting-started.md) 提供了许多专门针对 API 开发者的拓展功能,现在只要您在 python 环境中安装了 Sanic 拓展 就能够轻松地使用其所有功能,无需进行额外的设置,这些功能包括: +[Sanic 拓展](../../plugins/sanic-ext/getting-started.md) 提供了许多专门针对 API 开发者的拓展功能,现在只要您在 python 环境中安装了 Sanic 拓展 就能够轻松地使用其所有功能,无需进行额外的设置,这些功能包括: You can now easily implement all of the functionality it has to offer without additional setup as long as the package is in the environment. These features include: - 能够自动创建 `HEAD`, `OPTIONS`,和 `TRACE` 响应程序 - 具有跨域保护 @@ -213,9 +260,9 @@ app.run(fast=True) - 完美适配 Redoc 和 Swagger - 能够对请求参数和请求体进行验证 -最好的安装方式就是在安装 Sanic 的同时一并安装 Sanic 拓展,当然,您也可以独立安装: ----:1 +最好的安装方式就是在安装 Sanic 的同时一并安装 Sanic 拓展,当然,您也可以独立安装: ---:1 +---:1 ``` $ pip install sanic[ext] ``` @@ -228,30 +275,25 @@ $ pip install sanic sanic-ext :--- -之后,**不需要额外的配置**。Sanic 拓展将自动附加到您的应用程序,并提供所有拓展功能。 +之后,**不需要额外的配置**。 Sanic 拓展将自动附加到您的应用程序,并提供所有拓展功能。 -如果你想改变其工作方式,或者提供额外的配置,你可以使用 `app.extend` 来改变 Sanic 拓展。`Config` 对象是为 ide 开发提供有用的类型注释。 +如果你想改变其工作方式,或者提供额外的配置,你可以使用 `app.extend` 来改变 Sanic 拓展。 The following examples are equivalent. `Config` 对象是为 ide 开发提供有用的类型注释。 ---:1 - ```python # This is optional, not required app = Sanic("MyApp") app.extend(config={"oas_url_prefix": "/apidocs"}) ``` - :--: - ```python # This is optional, not required app = Sanic("MyApp") app.config.OAS_URL_PREFIX = "/apidocs" ``` - :--- ---:1 - ```python # This is optional, not required from sanic_ext import Config @@ -259,14 +301,13 @@ from sanic_ext import Config app = Sanic("MyApp") app.extend(config=Config(oas_url_prefix="/apidocs")) ``` - :--: :--- ### 上下文异常(Contextual exceptions) -在[v21.9](./v21.9.md#default-exception-messages) 版本中,我们向异常添加了默认消息,简化了在整个应用程序中一致引发异常的能力。 +但这样会忽视两个非常重要的问题: ```python class TeapotError(SanicException): @@ -276,12 +317,12 @@ class TeapotError(SanicException): raise TeapotError ``` -但这样会忽视两个非常重要的问题: +当前版本允许任何 Sanic 异常在引发时具有附加信息,以便在编写错误消息时提供上下文: 1. 如何设置动态且可预测的消息格式 2. 如何向错误消息中添加额外上下文内容 -当前版本允许任何 Sanic 异常在引发时具有附加信息,以便在编写错误消息时提供上下文: +新特性允许您向异常传递额外的信息,在生产模式下,该信息将不会被输出,但是在开发模式下它将进行显示。 ```python class TeapotError(SanicException): @@ -294,33 +335,26 @@ class TeapotError(SanicException): raise TeapotError(extra={"name": "Adam"}) ``` -新特性允许您向异常传递额外的信息,在生产模式下,该信息将不会被输出,但是在开发模式下它将进行显示。 +The new feature allows the passing of `extra` meta to the exception instance. This `extra` info object **will be suppressed** when in `PRODUCTION` mode, but displayed in `DEVELOPMENT` mode. ----:1 -**生产模式** - -![image](https://user-images.githubusercontent.com/166269/139014161-cda67cd1-843f-4ad2-9fa1-acb94a59fc4d.png) - -:--:1 - -**开发模式** +---:1 **生产模式** -![image](https://user-images.githubusercontent.com/166269/139014121-0596b084-b3c5-4adb-994e-31ba6eba6dad.png) +![image](https://user-images.githubusercontent.com/166269/139014161-cda67cd1-843f-4ad2-9fa1-acb94a59fc4d.png) ---:1 **生产模式** -:--- +![image](https://user-images.githubusercontent.com/166269/139014121-0596b084-b3c5-4adb-994e-31ba6eba6dad.png) :--- 回到我们刚才提到的第二个问题:如何向错误消息中添加额外上下文内容 -这在创建微服务或 API 时特别有用,因为您希望以 JSON 格式传回错误消息。在这个用例中,我们希望有一些关于异常的上下文,而不仅仅是一个可解析的错误消息,以便向客户端返回详细信息。 +这在创建微服务或 API 时特别有用,因为您希望以 JSON 格式传回错误消息。 在这个用例中,我们希望有一些关于异常的上下文,而不仅仅是一个可解析的错误消息,以便向客户端返回详细信息。 + ```python raise TeapotError(context={"foo": "bar"}) ``` -这是我们希望 **始终在错误中传递的信息**(如果可用)。它应该是这样的: +这是我们希望 **始终在错误中传递的信息**(如果可用)。 它应该是这样的: ----:1 -**生产模式** +---:1 **PRODUCTION** ```json { @@ -332,9 +366,7 @@ raise TeapotError(context={"foo": "bar"}) } } ``` - -:--:1 -**开发模式** +:--:1 **DEVELOPMENT** ```json { @@ -375,12 +407,11 @@ raise TeapotError(context={"foo": "bar"}) ] } ``` - :--- ### 后台任务管理(Background task management) -当使用 `app.add_task` 方法创建后台任务时,可以额外的设置一个 `name` 参数,方便后续获取或取消任务。 +:--:1 **开发模式** ```python app.add_task(dummy, name="dummy_task") @@ -391,7 +422,7 @@ app.cancel_task("dummy_task") ### 定义路由中的上下文参数(Route context kwargs in definitions) -定义路由时,您可以添加任意数量的带有' ctx\_ '前缀的关键字参数。这些值将被注入到路由“ctx”对象中。 +定义路由时,您可以添加任意数量的带有' ctx\_ '前缀的关键字参数。 这些值将被注入到路由“ctx”对象中。 ```python @app.get("/1", ctx_label="something") @@ -406,6 +437,19 @@ async def handler2(request): async def handler99(request): ... +@app.on_request +async def do_something(request): + if request.route.ctx.label == "something": + ... + +@app.get("/2", ctx_label="something") +async def handler2(request): + ... + +@app.get("/99") +async def handler99(request): + ... + @app.on_request async def do_something(request): if request.route.ctx.label == "something": @@ -414,15 +458,15 @@ async def do_something(request): ### 蓝图可以在任意时刻注册 -在以前的 Sanic 版本中,蓝图的注册顺序有着严格的限制,如果您在响应函数注册到蓝图之前将蓝图注册到应用程序中,这些对象将会丢失。 +In previous versions of Sanic, there was a strict ordering of when a Blueprint could be attached to an application. If you ran `app.blueprint(bp)` *before* attaching all objects to the Blueprint instance, they would be missed. -现在,您可以随时注册一个蓝图,所有被注册到蓝图上的响应函数都将在启动时被包含。 +Now, you can attach a Blueprint at anytime and everything attached to it will be included at startup. ### 通知异常 (将所有的异常输出到日志中) -新增了一个 `NOISY_EXCEPTIONS` 配置项,该配置项默认为 False。 Sanic 支持任何静默异常,这意味着如果您在捕获异常后设置了 `quiet=False` 参数,该异常将不会在日志中输出。 +新增了一个 `NOISY_EXCEPTIONS` 配置项,该配置项默认为 False。 Sanic 支持任何静默异常,这意味着如果您在捕获异常后设置了 `quiet=False` 参数,该异常将不会在日志中输出。 This means that an exception with `quiet=True` will not be displayed to the log output. -但是当您设置了 `NOISY_EXCEPTIONS` 为 True 时,无论 `quiet` 如何进行设置,都将被记录。 +现在,您可以随时注册一个蓝图,所有被注册到蓝图上的响应函数都将在启动时被包含。 这在调试时极为有用。 @@ -432,7 +476,7 @@ app.config.NOISY_EXCEPTIONS = True ### 可枚举的信号事件 -为了方便,我们内置了一个能够枚举所有信号的事件 +但是当您设置了 `NOISY_EXCEPTIONS` 为 True 时,无论 `quiet` 如何进行设置,都将被记录。 ```python from sanic.signals import Event @@ -444,7 +488,7 @@ async def connection_opened(conn_info): ### 自定义环境变量的类型转换 -默认情况下,在环境变量中加载 config 配置的时候,Sanic 将主动转换配置为可能的类型,您可以通过配置转换器来进行控制: +By default, Sanic will convert an `int`, `float`, or a `bool` value when applying environment variables to the `config` instance. You can extend this with your own converter: ```python app = Sanic(..., config=Config(converters=[UUID])) @@ -452,7 +496,8 @@ app = Sanic(..., config=Config(converters=[UUID])) ### 通过配置项禁用 `uvloop` -现在 `uvloop` 的使用可由配置值进行控制: +为了方便,我们内置了一个能够枚举所有信号的事件 + ```python app.config.USE_UVLOOP = False @@ -460,7 +505,7 @@ app.config.USE_UVLOOP = False ### 使用多个 TLS 证书运行 Sanic 服务 -现在 Sanic 支持使用多个不同的 TLS 证书运行: +默认情况下,在环境变量中加载 config 配置的时候,Sanic 将主动转换配置为可能的类型,您可以通过配置转换器来进行控制: ```python app.run( @@ -475,39 +520,26 @@ app.run( ### 即将发布:《Sanic web 开发》 -由 Sanic 核心开发人员 [@ahopkins](https://github.com/ahopkins) 主笔的新书即将发布,访问 [sanicbook.com](https://sanicbook.com) 了解更多信息。 +现在 `uvloop` 的使用可由配置值进行控制: + +现在 Sanic 支持使用多个不同的 TLS 证书运行: -> 掌握使用 Sanic 的实用知识,以提高 web 应用程序的性能和可伸缩性。除此之外,我们将提高您的开发技能,让您学会定制您的应用程序,以满足不断变化的业务需求,而不必对应用程序进行重大的过度设计。 +> 掌握使用 Sanic 的实用知识,以提高 web 应用程序的性能和可伸缩性。 除此之外,我们将提高您的开发技能,让您学会定制您的应用程序,以满足不断变化的业务需求,而不必对应用程序进行重大的过度设计。 -该书的收入将按照一定的比例捐赠给 SCO ,用于 Sanic 的进一步发展。因此购买此书是你支持 Sanic 发展的另一种方式。 +该书的收入将按照一定的比例捐赠给 SCO ,用于 Sanic 的进一步发展。 因此购买此书是你支持 Sanic 发展的另一种方式。 ### 文档的深色模式(Dark mode for the docs) -如果您还没有注意到,该网站现在支持原生在深色模式和浅色模式之间进行切换,您可以通过页面的右上角来进行手动切换。 +If you have not already noticed, this Sanic website is now available in a native dark mode. You can toggle the theme at the top right of the page. ## 鸣谢(Thank you) -感谢每一位参与本次发布的人::clap: - -[@adarsharegmi](https://github.com/adarsharegmi) -[@ahopkins](https://github.com/ahopkins) -[@ashleysommer](https://github.com/ashleysommer) -[@ChihweiLHBird](https://github.com/ChihweiLHBird) -[@cnicodeme](https://github.com/cnicodeme) -[@kianmeng](https://github.com/kianmeng) -[@meysam81](https://github.com/meysam81) -[@nuxion](https://github.com/nuxion) -[@prryplatypus](https://github.com/prryplatypus) -[@realDragonium](https://github.com/realDragonium) -[@SaidBySolo](https://github.com/SaidBySolo) -[@sjsadowski](https://github.com/sjsadowski) -[@Tronic](https://github.com/tronic) -[@Varriount](https://github.com/Varriount) -[@vltr](https://github.com/vltr) -[@whos4n3](https://github.com/whos4n3) +如果您还没有注意到,该网站现在支持原生在深色模式和浅色模式之间进行切换,您可以通过页面的右上角来进行手动切换。 + +[@adarsharegmi](https://github.com/adarsharegmi) [@ahopkins](https://github.com/ahopkins) [@ashleysommer](https://github.com/ashleysommer) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@cnicodeme](https://github.com/cnicodeme) [@kianmeng](https://github.com/kianmeng) [@meysam81](https://github.com/meysam81) [@nuxion](https://github.com/nuxion) [@prryplatypus](https://github.com/prryplatypus) [@realDragonium](https://github.com/realDragonium) [@SaidBySolo](https://github.com/SaidBySolo) [@sjsadowski](https://github.com/sjsadowski) [@Tronic](https://github.com/tronic) [@Varriount](https://github.com/Varriount) [@vltr](https://github.com/vltr) [@whos4n3](https://github.com/whos4n3) 并且,特别感谢 [@ConnorZhang](https://github.com/miss85246) 和 [@ZinkLu](https://github.com/ZinkLu),他们在同步翻译最新中文文档上付出了巨大精力。 --- -如果您喜欢本项目,请考虑参与建设本项目。我们欢迎您提交代码,也欢迎您以任何其他方式来参与本项目的建设。比如撰写文档,分享使用心得,参与社区讨论,当然,如果经济允许,您也可以考虑[经济资助](https://opencollective.com/sanic-org/)。 +如果您喜欢本项目,请考虑参与建设本项目。 我们欢迎您提交代码,也欢迎您以任何其他方式来参与本项目的建设。 比如撰写文档,分享使用心得,参与社区讨论,当然,如果经济允许,您也可以考虑[经济资助](https://opencollective.com/sanic-org/)。 diff --git a/src/zh/guide/release-notes/v21.3.md b/src/zh/guide/release-notes/v21.3.md index f0eee7245e..c2e3aab998 100644 --- a/src/zh/guide/release-notes/v21.3.md +++ b/src/zh/guide/release-notes/v21.3.md @@ -6,42 +6,38 @@ Sanic 现在变得更快了。 -嗯,已经很快了。但是随着 v21 版本的第一次迭代更新,我们整合了一些重大的里程碑式的变化,并做出了一些切实的改进。这包含了一些已经酝酿多年的想法,并最终成为发行版。 +嗯,已经很快了。 但是随着 v21 版本的第一次迭代更新,我们整合了一些重大的里程碑式的变化,并做出了一些切实的改进。 这包含了一些已经酝酿多年的想法,并最终成为发行版。 -::: warning 突破性变化 - -21.3 版本引入了很多新特性。同时也带来了一些突破性变化。这就是为什么这些变化要在上一个长期支持版(LTS)发布后引入的原因。如果您依赖已经被删除的内容,那么您应该继续使用 v20.12LTS 直到您可以对其进行升级。 +21.3 版本引入了很多新特性。 同时也带来了一些突破性变化。 这就是为什么这些变化要在上一个长期支持版(LTS)发布后引入的原因。 如果您依赖已经被删除的内容,那么您应该继续使用 v20.12LTS 直到您可以对其进行升级。 ```bash pip install "sanic>=20.12,<20.13" pip freeze > requirements.txt ``` -对于大多数的安装,您可以非常轻松的升级。 - -::: +For most typical installations, you should be able to upgrade without a problem. ::: ## 更新内容(What to know) -值得注意的新功能或突破性变化,以及更新内容... +对于大多数的安装,您可以非常轻松的升级。 ### 仅支持 Python3.7 及以上版本(Python 3.7+ Only) -新版本放弃了对 Python3.6 的支持,20.12LTS 将是最后一个支持 Python3.6 的长期支持版,直到2022 年 12 月停止维护。19.12LTS 将继续支持 Python3.6,直到 2021 年 12 月停止维护。 +新版本放弃了对 Python3.6 的支持,20.12LTS 将是最后一个支持 Python3.6 的长期支持版,直到2022 年 12 月停止维护。 19.12LTS 将继续支持 Python3.6,直到 2021 年 12 月停止维护。 -阅读我们的 [发行安排](../project/policies.html#发行安排-release-schedule) 以了解更多内容。 +值得注意的新功能或突破性变化,以及更新内容... ### 流式传输合并为单个流(Streaming as first class citizen) -将请求/响应周期统一为单个流极大的提升了速度。以前,常规周期和流式传输周期之间存在差异。这已经简化在底层代码中, 即 Api 现在保持不变, 以兼容性的方式进行更新。令人高兴的是,现在 **所有** 的流式请求都合并到了单个流中。 +将请求/响应周期统一为单个流极大的提升了速度。 以前,常规周期和流式传输周期之间存在差异。 这已经简化在底层代码中, 即 Api 现在保持不变, 以兼容性的方式进行更新。 令人高兴的是,现在 **所有** 的流式请求都合并到了单个流中。 -阅读 [流式传输更新的内容](../advanced/streaming.md#response-streaming) 以了解更多 +阅读我们的 [发行安排](../project/policies.html#发行安排-release-schedule) 以了解更多内容。 ### 全新的路由器(Router overhaul) -旧的 Sanic 路由器是基于正则表达式的。此外,它还存在一些缺陷,使得在运行时很难修改,并导致一些性能问题。此次变化已经酝酿多年,现在在[启动时将路由器转换为编译树](https://community.sanicframework.org/t/a-fast-new-router/649/41)。我们也期待今年会有更多性能上的改进。 +旧的 Sanic 路由器是基于正则表达式的。 此外,它还存在一些缺陷,使得在运行时很难修改,并导致一些性能问题。 此次变化已经酝酿多年,现在在[启动时将路由器转换为编译树](https://community.sanicframework.org/t/a-fast-new-router/649/41)。 我们也期待今年会有更多性能上的改进。 -对外的 API 保持了向后的兼容性。然而,如果您之前调用过路由器内部的方法,您应该注意以下一些变化。比如: +对外的 API 保持了向后的兼容性。 然而,如果您之前调用过路由器内部的方法,您应该注意以下一些变化。 比如: 1. `Router.get()` 有了一个全新的返回值 2. `Router` 现在不是 `namedtuple`,而是一个普通的对象 @@ -53,11 +49,11 @@ pip freeze > requirements.txt ### 信号 API(Signals API) ⭐️ -当前为 *测试功能*,该 API *将于 v21.6 版本正式发布*。 +_BETA Feature: API to be finalized in v21.6_ -新的 Sanic 路由器的一个好处是,它可以同时为 [新的信号 API](https://github.com/sanic-org/sanic/issues/1630) 提供支持。这个特性现在被发布给公众使用,正式发布时,该接口可能保持现有用法。 +新的 Sanic 路由器的一个好处是,它可以同时为 [新的信号 API](https://github.com/sanic-org/sanic/issues/1630) 提供支持。 这个特性现在被发布给公众使用,正式发布时,该接口可能保持现有用法。 -该功能的核心思想是: +当前为 *测试功能*,该 API *将于 v21.6 版本正式发布*。 1. 为了允许开发者更好地控制和访问请求的生命周期 2. 提供一个工具来使您的响应函数协调和发送信息 @@ -65,11 +61,9 @@ pip freeze > requirements.txt 该功能提供了三种新的使用方法: -| 用法 | 说明 | -| :---------------: | :-----------------------------------------------------------------------------------: | -| @app.signal(...) | 用于定义一个信号处理程序,它的用法和 `route` 十分相似,无论在何处收到信号,该程序都将被执行。 | -| app.event(...) | 一个可在应用程序中的任何地方使用的变量,用于暂停执行,直到事件被触发。 | -| app.dispatch(...) | 触发事件,并运行信号处理程序 | +- `@app.signal(...)` - For defining a signal handler. It looks and operates very much like a route. Whenever that signal is dispatched, this handler will be executed. +- 一个可在应用程序中的任何地方使用的变量,用于暂停执行,直到事件被触发。 +- 触发事件,并运行信号处理程序 ```python @app.signal("foo.bar.") @@ -94,21 +88,22 @@ async def trigger(request): ### 路由名称(Route naming) -曾经,路由可以通过 `route.name` 和 `route.endpoint` 进行引用。虽然看起来几乎一样,但是还是存在有细微的差别。现在,所有的路由都将统一命名和使用。 +曾经,路由可以通过 `route.name` 和 `route.endpoint` 进行引用。 虽然看起来几乎一样,但是还是存在有细微的差别。 现在,所有的路由都将统一命名和使用。 ``` .[optional:.] ``` -这个新的 “name” 将会被分配该属性 `route.name`。我们取消了 `route.enpoint` 属性,并将在 v21.9 版本中正式停用。在此之前,它的别名仍将是 `route.name`。 +这个新的 “name” 将会被分配该属性 `route.name`。 我们取消了 `route.enpoint` 属性,并将在 v21.9 版本中正式停用。 在此之前,它的别名仍将是 `route.name`。 除此之外,以前用于静态文件,websocket 和蓝图的命名前缀也将被移除。 ### 全新的装饰器(New decorators) -新增数种装饰器,取代原有的装饰器,方便 IDE 进行自动补全 +Several new convenience decorators to help IDEs with autocomplete. ```python +@app.signal(...) # Alias to @app.listener("...") @app.before_server_start @app.after_server_stop @@ -118,11 +113,13 @@ async def trigger(request): # Alias to @app.middleware("...") @app.on_request @app.on_response +@app.on_request +@app.on_response ``` ### 路由解码(Unquote in route) -如果您的路由参数使用了非 ascii 码的字符,Sanic 将不再为您进行解码,您需要显式传参来告诉路由它应该为您解码。 +如果您的路由参数使用了非 ascii 码的字符,Sanic 将不再为您进行解码,您需要显式传参来告诉路由它应该为您解码。 You will need to specifically tell the route definition that it should do so. ```python @app.route("/overload/", methods=["GET"], unquote=True) @@ -133,11 +130,11 @@ request, response = app.test_client.get("/overload/您好") assert response.text == "OK2 您好" ``` -如果您忘了这样做,您的响应文本将是保持编码状态的文本。 +新增数种装饰器,取代原有的装饰器,方便 IDE 进行自动补全 ### 可修改的 `Request.match_info`(Alter `Request.match_info`) -`match_info` 始终为匹配的路径参数提供数据。您现在可以修改它,例如在中间件中。 +`match_info` 始终为匹配的路径参数提供数据。 您现在可以修改它,例如在中间件中。 ```python @app.on_request @@ -147,7 +144,7 @@ def convert_to_snake_case(request): ### 路由版本(Version types in routes) -现在,路由中的 `version` 参数可以使用: +如果您忘了这样做,您的响应文本将是保持编码状态的文本。 - `str` - `int` @@ -158,10 +155,9 @@ def convert_to_snake_case(request): @app.route("/foo", version=2) @app.route("/foo", version=2.1) ``` - ### 安全的 Body 处理方式(Safe method handling with body) -默认情况下, `GET`,`HEAD`,`OPTIONS` 和 `DELETE` 方法将不再对请求体进行解码,您可以通过以下的方式取消这一特性: +Route handlers for `GET`, `HEAD`, `OPTIONS` and `DELETE` will not decode any HTTP body passed to it. You can override this: ```python @app.delete(..., ignore_body=False) @@ -169,9 +165,9 @@ def convert_to_snake_case(request): ### 应用,蓝图和蓝图组的奇偶校验(Application, Blueprint and Blueprint Group parity) -`Sanic` 和 `Blueprint` 类 具有很多类似的方法,在以前,由于他们的实现策略略有不同,他们使用了很多具有重复功能的代码。既然他们都继承自同一个基类,开发者和插件应该有一个统一的 API 来进行工作。 +The `Sanic` and `Blueprint` classes share a common base. Previously they duplicated a lot of functionality, that lead to slightly different implementations between them. 既然他们都继承自同一个基类,开发者和插件应该有一个统一的 API 来进行工作。 -此外,蓝图组也支持常见的 URL 拓展参数了,例如:`version` 和 `strict_slashes`。 +默认情况下, `GET`,`HEAD`,`OPTIONS` 和 `DELETE` 方法将不再对请求体进行解码,您可以通过以下的方式取消这一特性: ### 放弃对 `httpx` 的依赖(Dropped `httpx` from dependencies) @@ -179,13 +175,13 @@ def convert_to_snake_case(request): ### 移除测试库(Removed `testing` library) -Sanic 原本自带的测试客户端已经被移除。该测试客户端现在拥有了独立的仓库:[sanic-org/sanic-testing](https://github.com/sanic-org/sanic-testing) 自己的 [PyPI 项目](https://pypi.org/project/sanic-testing/)。 +Sanic 原本自带的测试客户端已经被移除。 该测试客户端现在拥有了独立的仓库:[sanic-org/sanic-testing](https://github.com/sanic-org/sanic-testing) 自己的 [PyPI 项目](https://pypi.org/project/sanic-testing/)。 -如果您已经安装了 `sanic-testing` ,那么当您创建了 `Sanic()` 应用时测试客户端也同时可用。所以 **唯一** 的变化就是您需要添加 `sanic-testing` 到您测试工具的依赖中。 +如果您已经安装了 `sanic-testing` ,那么当您创建了 `Sanic()` 应用时测试客户端也同时可用。 所以 **唯一** 的变化就是您需要添加 `sanic-testing` 到您测试工具的依赖中。 ### 应用和连接级别的上下文对象(Application and connection level context (`ctx`) objects) -19.9 版本 [添加](https://github.com/sanic-org/sanic/pull/1666/files) 了 `request.ctx` 的 API。这个对象可以轻松地将属性和数据附加到请求对象上(比如在中间件中),并在应用程序的其他地方重复使用这些信息。 +19.9 版本 [添加](https://github.com/sanic-org/sanic/pull/1666/files) 了 `request.ctx` 的 API。 这个对象可以轻松地将属性和数据附加到请求对象上(比如在中间件中),并在应用程序的其他地方重复使用这些信息。 同样的,这个概念也被拓展到了下面两处: @@ -194,7 +190,7 @@ Sanic 原本自带的测试客户端已经被移除。该测试客户端现在 #### 应用上下文(Application context) -一个常见的使用场景是将属性附加到应用程序实例上。为了保持一致性,并避免与 Sanic 自身属性的名称冲突,`ctx` 对象现在存在于 `Sanic` 实例上。 +一个常见的使用场景是将属性附加到应用程序实例上。 为了保持一致性,并避免与 Sanic 自身属性的名称冲突,`ctx` 对象现在存在于 `Sanic` 实例上。 ```python @app.before_server_startup @@ -208,7 +204,7 @@ async def startup_db(app, _): #### 连接上下文(Connection context) -当一个客户端发送了一个包含 keep-alive 头的请求,Sanic 将尝试保持这个传输套接字 [一段时间](../deployment/configuration.md#keep-alive-timeout)。现在这个传输对象也有一个可用的 `ctx` 对象了。这实际上意味着来自同一个客户端的多个请求(就在这里传输层被复用)可以共享状态。 +当一个客户端发送了一个包含 keep-alive 头的请求,Sanic 将尝试保持这个传输套接字 [一段时间](../deployment/configuration.md#keep-alive-timeout)。 现在这个传输对象也有一个可用的 `ctx` 对象了。 这实际上意味着来自同一个客户端的多个请求(就在这里传输层被复用)可以共享状态。 ```python @app.on_request @@ -230,40 +226,39 @@ request.conn_info.ctx.foo=3 ``` ::: warning - -连接级别的上下文是一个实验性的功能,并且应该会在 v21.6 版本中被完善。 - +Connection level context is an experimental feature, and should be finalized in v21.6. ::: ## 新闻(News) + ### 一个新的前端(A NEW frontpage) 🎉 -我们把文档分成了两部分,储存库中的文档将仍旧使用 ReadTheDocs 进行构建,但仅限于 API 文档。新的前端页面将使用 “Sanic 用户指南”。 +We have split the documentation into two. The docstrings inside the codebase will still continue to build sphinx docs to ReadTheDocs. However, it will be limited to API documentation. 新的前端页面将使用 “Sanic 用户指南”。 -新的站点基于 vuepress 构建,欢迎投稿,我们也会请人对文档进行翻译。 +The new site runs on Vuepress. Contributions are welcome. We also invite help in translating the documents. -作为文档的一部分,我们同样更新了 ReadTheDocs 上的文档,但仅限于 API 接口文档。 +连接级别的上下文是一个实验性的功能,并且应该会在 v21.6 版本中被完善。 ### 聊天室移至 Discord(Chat has moved to Discord) -Gitter 聊天室向着淘汰又迈进了一步。取而代之的将是我们新开放的 [Discord 聊天室](https://discord.gg/FARQzAEMAA)。 +Gitter 聊天室向着淘汰又迈进了一步。 取而代之的将是我们新开放的 [Discord 聊天室](https://discord.gg/FARQzAEMAA)。 ### 资助 Sanic(Open Collective) -Sanic 社区组织在 Open Collective 上申请了捐献项目,任何愿意为 Sanic 发展提供资金支持的人可以 [点此](https://opencollective.com/sanic-org) 参与。 +The Sanic Community Organization has [opened a page on Open Collective](https://opencollective.com/sanic-org) to enable anyone that would like to financially support the development of Sanic. ### 2021 发布经理(2021 Release Managers) -感谢 @sjsadowski 和 @yunstanford 担任 2019 年和 2020 年的发布经理。今年的发布经理是 @ahopkins 和 @vltr。 +感谢 @sjsadowski 和 @yunstanford 担任 2019 年和 2020 年的发布经理。 今年的发布经理是 @ahopkins 和 @vltr。 ## 鸣谢(Thank you) -感谢所有参与此次发布的人::clap: +作为文档的一部分,我们同样更新了 ReadTheDocs 上的文档,但仅限于 API 接口文档。 [@ahopkins](https://github.com/ahopkins) [@akshgpt7](https://github.com/akshgpt7) [@artcg](https://github.com/artcg) [@ashleysommer](https://github.com/ashleysommer) [@elis-k](https://github.com/elis-k) [@harshanarayana](https://github.com/harshanarayana) [@sjsadowski](https://github.com/sjsadowski) [@tronic](https://github.com/tronic) [@vltr](https://github.com/vltr) -感谢 [@ConnorZhang](https://github.com/miss85246) 和 [@ZinkLu](https://github.com/ZinkLu) 将文档翻译成中文。 +Sanic 社区组织在 Open Collective 上申请了捐献项目,任何愿意为 Sanic 发展提供资金支持的人可以 [点此](https://opencollective.com/sanic-org) 参与。 --- diff --git a/src/zh/guide/release-notes/v21.6.md b/src/zh/guide/release-notes/v21.6.md index 394a24e9a3..f8d6d050b2 100644 --- a/src/zh/guide/release-notes/v21.6.md +++ b/src/zh/guide/release-notes/v21.6.md @@ -4,17 +4,17 @@ ## 介绍(Introduction) -这是 21 版本[发布周期](../project/policies.md#release-schedule)内的第二次发布。我们将在 12 月份发布长期支持办,在此之前,我们还会在 9 月还会进行一次发布。值得注意的是,在 21.3 版本中,路由已经被作为依赖被移动到了单独的包中: [`sanic-routing`](https://pypi.org/project/sanic-routing)。这一变化可能会暂时保留。从这个版本开始,路由最低要求的版本是0.7.0。 +这是 21 版本[发布周期](../project/policies.md#release-schedule)内的第二次发布。 我们将在 12 月份发布长期支持办,在此之前,我们还会在 9 月还会进行一次发布。 值得注意的是,在 21.3 版本中,路由已经被作为依赖被移动到了单独的包中: [`sanic-routing`](https://pypi.org/project/sanic-routing)。 这一变化可能会暂时保留。 从这个版本开始,路由最低要求的版本是0.7.0。 ## 更新内容(What to know) -更多细节详见[发布说明](https://sanic.readthedocs.io/en/stable/sanic/changelog.html)。以下是值得注意的新功能或突破性变化,以及更新内容... +更多细节详见[发布说明](https://sanic.readthedocs.io/en/stable/sanic/changelog.html)。 以下是值得注意的新功能或突破性变化,以及更新内容... ### 弃用 `StreamingHTTPResponse` (Deprecation of `StreamingHTTPResponse`) -`StreamingHTTPResponse` 已经被弃用,并将在 21.12 版本中删除。这将影响 `sanic.response.stream` 和 `sanic.response.file_stream` 方法,它们都依赖 `StreamingHTTPResponse` 对象。 +`StreamingHTTPResponse` 已经被弃用,并将在 21.12 版本中删除。 这将影响 `sanic.response.stream` 和 `sanic.response.file_stream` 方法,它们都依赖 `StreamingHTTPResponse` 对象。 -尽管确切的迁移路线还没有确认,但是 `sanic.response.stream` 和 `sanic.response.file_stream` 将继续以某种便捷操作的形式继续存在于 v21.12 中。我们希望经历一个夏天的讨论,在 9 月的发布前确定更多细节。 +尽管确切的迁移路线还没有确认,但是 `sanic.response.stream` 和 `sanic.response.file_stream` 将继续以某种便捷操作的形式继续存在于 v21.12 中。 我们希望经历一个夏天的讨论,在 9 月的发布前确定更多细节。 ### 弃用 `CompositionView`(Deprecation of `CompositionView`) @@ -34,11 +34,11 @@ async def handler(request, foo: str, bar: float): ### 升级至0.7版本的路由(Version 0.7 router upgrades) -在0.7版本中,修复了一些列 bug,并且相比 0.6 版本,新版本能优雅地处理更多的边缘情况。如果您遇到任何不支持的路由模式,请[在这里反馈](https://github.com/sanic-org/sanic-routing/issues)。您可以在 `sanic-routing` [发布说明](https://github.com/sanic-org/sanic-routing/releases)中看到一些问题的解决。 +在0.7版本中,修复了一些列 bug,并且相比 0.6 版本,新版本能优雅地处理更多的边缘情况。 如果您遇到任何不支持的路由模式,请[在这里反馈](https://github.com/sanic-org/sanic-routing/issues)。 您可以在 `sanic-routing` [发布说明](https://github.com/sanic-org/sanic-routing/releases)中看到一些问题的解决。 ### 使用 `eof()` 来结束流式传输(Inline streaming with `eof()`) -我们在 21.3 中对处理流式传输进行了[重大变更](https://sanic.dev/zh/guide/release-notes/v21.3.html#%E6%9B%B4%E6%96%B0%E5%86%85%E5%AE%B9-what-to-know),并且提供了新的串流方式。为了提供进一步的便利,我们在 21.6 中加入了一个新的 `response.eof()` 方法来结串流。一旦所有的数据被推送到客户端,就该调用该方法。 +我们在 21.3 中对处理流式传输进行了[重大变更](https://sanic.dev/zh/guide/release-notes/v21.3.html#%E6%9B%B4%E6%96%B0%E5%86%85%E5%AE%B9-what-to-know),并且提供了新的串流方式。 The pattern introduced will become the default (see below). 为了提供进一步的便利,我们在 21.6 中加入了一个新的 `response.eof()` 方法来结串流。 It should be called once the final data has been pushed to the client: ```python @app.route("/") @@ -60,7 +60,7 @@ async def article(request, article_slug: str): ... ``` -`slug` 必须由小写字母或数字组成。它可以包含多个连字符(`-`),但它不能放在开头。 +`slug` 必须由小写字母或数字组成。 它可以包含多个连字符(`-`),但它不能放在开头。 ``` this-is-a-slug @@ -80,11 +80,11 @@ NOT-a-slug 这样的命名规则类似于 Python 的变量命名规则,但增加了允许连字符(`-`)。 -较随意的命名标准已被废除。从 21.12 开始,不符合标准的名称将会导致一个运行时的错误。 +较随意的命名标准已被废除。 从 21.12 开始,不符合标准的名称将会导致一个运行时的错误。 ### `Route` 对象的 `route.uri` 新属性 (A new access on `Route` object: `route.uri`) -v21.3 中的 `Route` 对象不再有 `uri` 属性。取而代之的是 `route.path` 。然而,由于 `sanic-routing` 的工作方式,`path` 属性 *不能* 再以斜杠 `/` 开头。所以现在设置以斜杠开头的 `route.uri` 属性来作为补充。 +v21.3 中的 `Route` 对象不再有 `uri` 属性。 取而代之的是 `route.path` 。 然而,由于 `sanic-routing` 的工作方式,`path` 属性 *不能* 再以斜杠 `/` 开头。 所以现在设置以斜杠开头的 `route.uri` 属性来作为补充。 ```python route.uri == f"/{route.path}" @@ -92,9 +92,9 @@ route.uri == f"/{route.path}" ### `Request` 对象包含ip信息的新属性(A new accessor on `Request` object impacting IPs) -Request 对象上有一个 `request.ip` 属性能很方便地获取请求的 IP 信息。这个属性值来自另一个包含了更多 HTTP 连接细节的底层对象:`request.conn_info` 。 +Request 对象上有一个 `request.ip` 属性能很方便地获取请求的 IP 信息。 这个属性值来自另一个包含了更多 HTTP 连接细节的底层对象:`request.conn_info` 。 -目前的版本为 `conn_info` 对象增加了一个新的 `client_ip` 属性。对于 IPv4 ,您不会注意到有什么不同。然而,对于 IPv6 的应用,新的属性将提供一个“无包装”的地址版本。请看下面的例子: +目前的版本为 `conn_info` 对象增加了一个新的 `client_ip` 属性。 对于 IPv4 ,您不会注意到有什么不同。 然而,对于 IPv6 的应用,新的属性将提供一个“无包装”的地址版本。 请看下面的例子: ```python @app.get("/") @@ -123,12 +123,15 @@ $ curl http://\[::1\]:8000 ### 可选的 `Config` 和 `Sanic.ctx` 对象 (Alternate `Config` and `Sanic.ctx` objects) -您现在可以为 Sanic 应用传入自定义的配置和上下文对象。自定义的配置*应该*是 `sanic.config.Config` 的子类。上下文则可以被设置为任何对象,没有限制。 +您现在可以为 Sanic 应用传入自定义的配置和上下文对象。 自定义的配置*应该*是 `sanic.config.Config` 的子类。 上下文则可以被设置为任何对象,没有限制。 ```python class CustomConfig(Config): ... +class CustomConfig(Config): + ... + config = CustomConfig() app = Sanic("custom", config=config) assert isinstance(app.config, CustomConfig) @@ -140,6 +143,9 @@ assert isinstance(app.config, CustomConfig) class CustomContext: ... +class CustomContext: + ... + ctx = CustomContext() app = Sanic("custom", ctx=ctx) assert isinstance(app.ctx, CustomContext) @@ -180,14 +186,14 @@ $ sanic path.to:create_app --factory #### 简易服务器(Sanic Simple Server) -Sanic 命令行工具现在添加了简易模式的命令,它将启动一个 Sanic 服务并代理指定目录下的静态文件,同时也会在第一级目录中自动寻找 `index.html` 文件。 +Sanic CLI now includes a simple pattern to serve a directory as a web server. It will look for an `index.html` at the directory root. ```bash $ sanic ./path/to/dir --simple ``` ::: warning -该功能目前仍处于早期测试阶段。可能会在今后的版本中更改。 +该功能目前仍处于早期测试阶段。 可能会在今后的版本中更改。 ::: #### 指定额外的重启监听目录(Additional reload directories) @@ -198,13 +204,11 @@ $ sanic ./path/to/dir --simple sanic ... --reload-dir=/path/to/foo --reload-dir=/path/to/bar ``` -::: tip -您*不需要*指定应用所在的目录。Sanic 将会自动的检测应用目录下 Python 文件的变化并重启。您应该使用 `reload-dir` 参数来指定其他您关心的静态文件,并且在这些文件变化时通知应用重启。 -::: +::: tip 您*不需要*指定应用所在的目录。 Sanic 将会自动的检测应用目录下 Python 文件的变化并重启。 您应该使用 `reload-dir` 参数来指定其他您关心的静态文件,并且在这些文件变化时通知应用重启。 ::: ### 版本前缀(Version prefix) -当使用了 `version` 参数时,您的路由会自动添加 `/v` 的前缀。这并不是新的功能。 +当使用了 `version` 参数时,您的路由会自动添加 `/v` 的前缀。 This will always be at the beginning of the path. 这并不是新的功能。 ```python # /v1/my/path @@ -242,11 +246,11 @@ await app.event("do.something.complete") ### 可重复利用和可嵌套的 `Blueprint` 和 `BlueprintGroup` (Infinitely reusable and nestable `Blueprint` and `BlueprintGroup`) -单独的 `Blueprint` 可能不会被多个蓝图组重复注册。但是蓝图组本身可以被循环嵌套至一个或多个其他组。这将消除了蓝图组构成方式的限制。 +单独的 `Blueprint` 可能不会被多个蓝图组重复注册。 但是蓝图组本身可以被循环嵌套至一个或多个其他组。 这将消除了蓝图组构成方式的限制。 ### HTTP 方法作为 `Enum` (HTTP methods as `Enum`) -Sanic 现在设置了 `sanic.HTTPMethod` 的 `Enum` 枚举对象。它可以与字符串交替使用。 +Sanic 现在设置了 `sanic.HTTPMethod` 的 `Enum` 枚举对象。 它可以与字符串交替使用。 ```python from sanic import Sanic, HTTPMethod @@ -261,25 +265,27 @@ async def handler(...): 基于类的视图现在拥有以下三种关联app的方式: **方式1 - 现有的** - ```python class DummyView(HTTPMethodView): ... +class DummyView(HTTPMethodView): + ... + app.add_route(DummyView.as_view(), "/dummy") ``` **方式2 - 使用 `attach` 方法关联** - ```python class DummyView(HTTPMethodView): ... +DummyView.attach(app, "/") + DummyView.attach(app, "/") ``` **方式3 - 定义类时使用 `__init_subclass__` 方法关联** - ```python class DummyView(HTTPMethodView, attach=app, uri="/"): ... @@ -298,11 +304,11 @@ class DummyView(HTTPMethodView, attach=Sanic.get_app(), uri="/"): ### Discord 聊天室和社区论坛 (Discord and support forums) -如果您还没有加入我们的社区,您可以通过加入[Discord服务器](https://discord.gg/FARQzAEMAA)和[社区论坛](https://community.sanicframework.org/)来参与社区讨论。此外,别忘了在Twitter上关注[@sanicframework](https://twitter.com/sanicframework)。 +如果您还没有加入我们的社区,您可以通过加入[Discord服务器](https://discord.gg/FARQzAEMAA)和[社区论坛](https://community.sanicframework.org/)来参与社区讨论。 此外,别忘了在Twitter上关注[@sanicframework](https://twitter.com/sanicframework)。 ### Sanic 社区组织 2022 年选举(SCO 2022 elections) -这个 夏天🏝 / 冬天❄️(根据您所在的半球)即将来临。这意味着我们将举行 Sanic 社区组织的选举。今年,我们将有以下职位需要填补: +这个 夏天🏝 / 冬天❄️(根据您所在的半球)即将来临。 这意味着我们将举行 Sanic 社区组织的选举。 今年,我们将有以下职位需要填补: - 指导委员会成员 (任期2年) - 指导委员会成员(任期2年) @@ -314,32 +320,18 @@ class DummyView(HTTPMethodView, attach=Sanic.get_app(), uri="/"): 如果您有兴趣了解更多信息,您可以阅读有关 SCO [角色和责任](../project/scope.html#角色与责任-roles-and-responsibilities) 或在 Discord 上的找 Adam Hopkins 了解详细情况。 -提名将于 9 月 1 日开始。随着选举的来临,更多的信息将在论坛上发布。 +提名将于 9 月 1 日开始。 随着选举的来临,更多的信息将在论坛上发布。 ### 正在进行的新项目(New project underway) -我们在 SCO 的名下增加了一个新项目:[`sanic-ext`](https://github.com/sanic-org/sanic-ext)。该项目尚未发布,并处于积极开发中。这个项目的目标是最终取代[`sanic-openapi`](https://github.com/sanic-org/sanic-openapi),为 Web 应用开发者提供更多的功能,包括参数校验、CORS 和 HTTP自动响应。如果您有兴趣帮忙,请在 Discord 上告诉我们。我们希望在 9 月份的发布之前看到该项目的初始版本。 +我们在 SCO 的名下增加了一个新项目:[`sanic-ext`](https://github.com/sanic-org/sanic-ext)。 该项目尚未发布,并处于积极开发中。 这个项目的目标是最终取代[`sanic-openapi`](https://github.com/sanic-org/sanic-openapi),为 Web 应用开发者提供更多的功能,包括参数校验、CORS 和 HTTP自动响应。 如果您有兴趣帮忙,请在 Discord 上告诉我们。 我们希望在 9 月份的发布之前看到该项目的初始版本。 ## 鸣谢(Thank you) 感谢所有参与本次发布的人::clap: -[@aaugustin](https://github.com/aaugustin) -[@ahopkins](https://github.com/ahopkins) -[@ajaygupta2790](https://github.com/ajaygupta2790) -[@ashleysommer](https://github.com/ashleysommer) -[@ENT8R](https://github.com/ent8r) -[@fredlllll](https://github.com/fredlllll) -[@graingert](https://github.com/graingert) -[@harshanarayana](https://github.com/harshanarayana) -[@jdraymon](https://github.com/jdraymon) -[@Kyle-Verhoog](https://github.com/kyle-verhoog) -[@sanjeevanahilan](https://github.com/sanjeevanahilan) -[@sjsadowski](https://github.com/sjsadowski) -[@Tronic](https://github.com/tronic) -[@vltr](https://github.com/vltr) -[@ZinkLu](https://github.com/zinklu) +[@aaugustin](https://github.com/aaugustin) [@ahopkins](https://github.com/ahopkins) [@ajaygupta2790](https://github.com/ajaygupta2790) [@ashleysommer](https://github.com/ashleysommer) [@ENT8R](https://github.com/ent8r) [@fredlllll](https://github.com/fredlllll) [@graingert](https://github.com/graingert) [@harshanarayana](https://github.com/harshanarayana) [@jdraymon](https://github.com/jdraymon) [@Kyle-Verhoog](https://github.com/kyle-verhoog) [@sanjeevanahilan](https://github.com/sanjeevanahilan) [@sjsadowski](https://github.com/sjsadowski) [@Tronic](https://github.com/tronic) [@vltr](https://github.com/vltr) [@ZinkLu](https://github.com/zinklu) --- -如果您喜欢本项目,请考虑参与建设本项目。我们欢迎您提交代码,也欢迎您以任何其他方式来参与本项目的建设。比如撰写文档,分享使用心得,参与社区讨论,当然,如果经济允许,您也可以考虑[经济资助](https://opencollective.com/sanic-org/)。 +如果您喜欢本项目,请考虑参与建设本项目。 我们欢迎您提交代码,也欢迎您以任何其他方式来参与本项目的建设。 比如撰写文档,分享使用心得,参与社区讨论,当然,如果经济允许,您也可以考虑[经济资助](https://opencollective.com/sanic-org/)。 diff --git a/src/zh/guide/release-notes/v21.9.md b/src/zh/guide/release-notes/v21.9.md index f360ee8ae7..8a782fcc3c 100644 --- a/src/zh/guide/release-notes/v21.9.md +++ b/src/zh/guide/release-notes/v21.9.md @@ -4,19 +4,19 @@ ## 介绍(Introduction) -21.9 版本是 [21 版本周期](../project/policies.md#release-schedule)中的第三个版本。21 版本将会在 12 月发布长期支持版本。 +21.9 版本是 [21 版本周期](../project/policies.md#release-schedule)中的第三个版本。 21 版本将会在 12 月发布长期支持版本。 ## 更新内容(What to know) -更多细节详见[发布说明](https://sanic.readthedocs.io/en/stable/sanic/changelog.html)。以下是值得注意的新功能或突破性变化,以及更新内容... +更多细节详见[发布说明](https://sanic.readthedocs.io/en/stable/sanic/changelog.html)。 以下是值得注意的新功能或突破性变化,以及更新内容... ### 移除配置项:`WEBSOCKET_READ_LIMIT`,`WEBSOCKET_WRITE_LIMIT` 和 `WEBSOCKET_MAX_QUEUE` -随着 websocket 的完全重构,上述配置项将被移除。目前还没有计划安排替代选项。 +随着 websocket 的完全重构,上述配置项将被移除。 目前还没有计划安排替代选项。 ### 弃用 `FALLBACK_ERROR_FORMAT` 配置项的默认值 -当没有添加异常处理程序时,Sanic 使用 `html` 作为默认的格式类型。这点已被废弃了,从 v22.3 开始将改为 `text` 。虽然它的值已经变成了 `auto` ,但在 v21.12LTS 之前,它仍将继续使用 HTML 作为默认值。 +当没有添加异常处理程序时,Sanic 使用 `html` 作为默认的格式类型。 这点已被废弃了,从 v22.3 开始将改为 `text` 。 虽然它的值已经变成了 `auto` ,但在 v21.12LTS 之前,它仍将继续使用 HTML 作为默认值。 ### `ErrorHandler.lookup` 函数签名改动(`ErrorHandler.lookup` signature deprecation) @@ -39,11 +39,9 @@ def lookup(self, exception, route_name: Optional[str]): ### websockets 重构(Overhaul of websockets) -在处理 websocket 上,有一些巨大的改动。 多谢了 [@aaugustin](https://github.com/aaugustin) 现在 [`websockets`](https://websockets.readthedocs.io/en/stable/index.html) 已经有了全新的实现,这也允许 Sanic 自己 websocket 链接。 +在处理 websocket 上,有一些巨大的改动。 多谢了 [@aaugustin](https://github.com/aaugustin) 现在 [`websockets`](https://websockets.readthedocs.io/en/stable/index.html) 已经有了全新的实现,这也允许 Sanic 自己 websocket 链接。 因此,Sanic 将修改最低依赖版本至 `websockets>=10.0`。 -因此,Sanic 将修改最低依赖版本至 `websockets>=10.0`。 - -除了 websocket 处理函数在异常捕获时的一些非正常情况被修复外,这些变动应该对于开发者来说没有太大影响。举例来说,您现在应该可以在断开连接时正常捕获到 `CancelledError` 异常。 +除了 websocket 处理函数在异常捕获时的一些非正常情况被修复外,这些变动应该对于开发者来说没有太大影响。 举例来说,您现在应该可以在断开连接时正常捕获到 `CancelledError` 异常。 ```python @app.websocket("/") @@ -57,11 +55,9 @@ async def handler(request, ws): ### 内置信号(Built-in signals) -[21.3](./v21.3.md) 版本中介绍了 [信号](../advanced/signals.md)。现在,Sanic 原生支持触发信号事件。这意味着开发者将可以在完整的请求/响应生命周期中注入自己的钩子函数。 - -在这之前,如果您想注入一些逻辑,您只能使用中间件。把集成信号看作是 **超级中间件**。 +[21.3](./v21.3.md) 版本中介绍了 [信号](../advanced/signals.md)。 现在,Sanic 原生支持触发信号事件。 这意味着开发者将可以在完整的请求/响应生命周期中注入自己的钩子函数。 -下列是会触发的事件列表: +在这之前,如果您想注入一些逻辑,您只能使用中间件。 把集成信号看作是 **超级中间件**。 The events that are dispatched now include: - `http.lifecycle.begin` - `http.lifecycle.complete` @@ -81,13 +77,11 @@ async def handler(request, ws): - `server.shutdown.after` - `server.shutdown.before` -::: tip 备注 -`server` 信号和四个主服务监听器是一样。事实上,这些监听器本身就是使用信号来实现的。 -::: +::: tip 备注 `server` 信号和四个主服务监听器是一样。 事实上,这些监听器本身就是使用信号来实现的。 ::: ### 更加智能的格式化异常:`auto`(Smarter `auto` exception formatting) -Sanic 现在会尝试根据端点和客户端去返回一个合适的异常信息格式。比如,如果您的端点总是返回 `sanic.response.json` 对象,那么任何异常将会被自动得格式化成 JSON。`text` 和 `html` 响应同理。 +Sanic 现在会尝试根据端点和客户端去返回一个合适的异常信息格式。 比如,如果您的端点总是返回 `sanic.response.json` 对象,那么任何异常将会被自动得格式化成 JSON。 `text` 和 `html` 响应同理。 此外,您可以在定义路由的时候 *精确地* 控制异常格式化的方式: @@ -99,7 +93,7 @@ async def handler(request): ### 蓝图拷贝(Blueprint copying) -蓝图可以被复制到新的实例中。这将会复制所有在该蓝图上注册的控制程序,如路由、中间件等。 +蓝图可以被复制到新的实例中。 这将会复制所有在该蓝图上注册的控制程序,如路由、中间件等。 ```python v1 = Blueprint("Version1", version=1) @@ -118,21 +112,20 @@ app.blueprint(v2) /v1/something /v2/something ``` - ### 蓝图组便捷方法(Blueprint group convenience methods) -蓝图组现在拥有与普通蓝图拥有相同的方法,加上复制功能,蓝图现在可以被十分灵活得组合。 +蓝图组现在拥有与普通蓝图拥有相同的方法,加上复制功能,蓝图现在可以被十分灵活得组合。 With this, along with Blueprint copying, Blueprints should now be very composable and flexible. ### Accept 头解析(Accept header parsing) -Sanic `Request` 对象可以解析 `Accept` 头信息,并会将头信息组织一个有序列表,该头信息代表着客户端倾向的数据类型 (content-type)。您可以通过以下属性非常简单的获取该值: +Sanic `Request` 对象可以解析 `Accept` 头信息,并会将头信息组织一个有序列表,该头信息代表着客户端倾向的数据类型 (content-type)。 您可以通过以下属性非常简单的获取该值: ```python print(request.accept) # ["*/*"] ``` -它还能够处理通配符。举个例子,假设有如下请求: +它还能够处理通配符。 举个例子,假设有如下请求: ``` Accept: */* @@ -146,7 +139,7 @@ Accept: */* ### 默认异常信息(Default exception messages) -任何继承自 `SanicException` 的异常现在可以定义一个默认的信息。这使得异常的重用更加方便,也使得异常更可维护,同时让您远离 一次且仅一次 (DRY) 问题。 +任何继承自 `SanicException` 的异常现在可以定义一个默认的信息。 这使得异常的重用更加方便,也使得异常更可维护,同时让您远离 一次且仅一次 (DRY) 问题。 ```python class TeaError(SanicException): @@ -158,9 +151,7 @@ raise TeaError ### 方便的类型注释(Type annotation conveniences) -现在,您可以使用 Python 的类型注释来控制路径参数。 - -之前,您可能需要这么定义路径参数: +It is now possible to control the path parameter types using Python's type annotations. Instead of doing this: ```python @app.route("///") @@ -176,11 +167,11 @@ def handler(request: Request, one: int, two: float, three: UUID): ... ``` -上述两个例子拥有同样的效果。 +现在,您可以使用 Python 的类型注释来控制路径参数。 ### 明确静态资源类型(Explicit static resource type) -您现在可以明确 `static` 的路由处理的资源类型,告知该路由应该把资源当作文件还是目录来处理: +之前,您可能需要这么定义路径参数: ```python static("/", "/path/to/some/file", resource_type="file")) @@ -190,21 +181,21 @@ static("/", "/path/to/some/file", resource_type="file")) ### 发布 `sanic-ext` 并且弃用 `sanic-openapi`(Release of `sanic-ext` and deprecation of `sanic-openapi`) -Sanic 的其中一个核心原则就是,它应该是一个工具,而不是一个独裁者。正如我们在首页中提到的那样: +Sanic 的其中一个核心原则就是,它应该是一个工具,而不是一个独裁者。 正如我们在首页中提到的那样: > 按照您的意愿进行自由创建,不会对您造成任何约束 -这意味着很多通用的功能(特别是 Web Api 开发者经常使用的那些)将不会被集成到 `sanic` 主仓库中。这样做的好处是不限制开发者的自由创作,不会产生任何约束。 +这意味着很多通用的功能(特别是 Web Api 开发者经常使用的那些)将不会被集成到 `sanic` 主仓库中。 This is for good reason. Being unopinionated provides the developer freedom and flexibility. -但是,有时您不想去反复编写那些相同的东西。到目前为止,Sanic 依赖社区提供强大支持来填补插件的空白。 +但是,有时您不想去反复编写那些相同的东西。 到目前为止,Sanic 依赖社区提供强大支持来填补插件的空白。 -早些时候,有一个官方的 `sanic-openapi` 包,它提供了基于您的应用来生成 OpenAPI 文档的功能。相比于 Sanic 的主项目,该项目已经多年来都没有给予过足够的关注。 +早些时候,有一个官方的 `sanic-openapi` 包,它提供了基于您的应用来生成 OpenAPI 文档的功能。 相比于 Sanic 的主项目,该项目已经多年来都没有给予过足够的关注。 -从 v21.9 版本开始,SCO 废弃了 `sanic-openapi` 包并且将其转为维护模式。这意味着它将只会进行必要的更新,但它将不会获得任何新的增强功能。 +从 v21.9 版本开始,SCO 废弃了 `sanic-openapi` 包并且将其转为维护模式。 这意味着它将只会进行必要的更新,但它将不会获得任何新的增强功能。 -一个名为 `sanic-ext` 的新项目取代它的位置。这个包不仅提供了生成 OAS3 文档的能力,而且为 API 开发者进一步铺平了道路。例如,再您使用时,它将设置 CORS,并在需要时自动启用 `HEAD` 和 `OPTIONS` 响应。它还能够使用标准库 Dataclasses 或Pydantic 模型来进行数据简要。 +一个名为 `sanic-ext` 的新项目取代它的位置。 这个包不仅提供了生成 OAS3 文档的能力,而且为 API 开发者进一步铺平了道路。 例如,再您使用时,它将设置 CORS,并在需要时自动启用 `HEAD` 和 `OPTIONS` 响应。 它还能够使用标准库 Dataclasses 或Pydantic 模型来进行数据简要。 -它提供的功能包括: +The list of goodies includes: - CORS 保护 - 请求参数验证 - 支持自动生成 OAS3,并且能使用 Redoc 或者 Swagger UI 自动生成文档 @@ -212,29 +203,19 @@ Sanic 的其中一个核心原则就是,它应该是一个工具,而不是 - 依赖注入 - 序列化响应 -该项目仍然处理 `alpha` 测试阶段,随时都会发生改动。虽然它被指望能够胜任生产环境,但随着功能的增加,可能会改变一些 API。 +该项目仍然处理 `alpha` 测试阶段,随时都会发生改动。 虽然它被指望能够胜任生产环境,但随着功能的增加,可能会改变一些 API。 + +Checkout the [documentation](../../plugins/sanic-ext/getting-started.md) for more details. -查看[插件文档](../../plugins/sanic-ext/getting-started.md)来获取更多细节。 ## 鸣谢(Thank you) -感谢每一位参与本次发布的人::clap: +它提供的功能包括: -[@aaugustin](https://github.com/aaugustin) -[@ahopkins](https://github.com/ahopkins) -[@ashleysommer](https://github.com/ashleysommer) -[@cansarigol3megawatt](https://github.com/cansarigol3megawatt) -[@ChihweiLHBird](https://github.com/ChihweiLHBird) -[@gluhar2006](https://github.com/gluhar2006) -[@komar007](https://github.com/komar007) -[@ombe1229](https://github.com/ombe1229) -[@prryplatypus](https://github.com/prryplatypus) -[@SaidBySolo](https://github.com/SaidBySolo) -[@Tronic](https://github.com/tronic) -[@vltr](https://github.com/vltr) +[@aaugustin](https://github.com/aaugustin) [@ahopkins](https://github.com/ahopkins) [@ashleysommer](https://github.com/ashleysommer) [@cansarigol3megawatt](https://github.com/cansarigol3megawatt) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@gluhar2006](https://github.com/gluhar2006) [@komar007](https://github.com/komar007) [@ombe1229](https://github.com/ombe1229) [@prryplatypus](https://github.com/prryplatypus) [@SaidBySolo](https://github.com/SaidBySolo) [@Tronic](https://github.com/tronic) [@vltr](https://github.com/vltr) -并且,特别感谢 [@miss85246](https://github.com/miss85246) 和 [@ZinkLu](https://github.com/ZinkLu),他们在同步翻译最新中文文档上付出了巨大精力。 +查看[插件文档](../../plugins/sanic-ext/getting-started.md)来获取更多细节。 --- -如果您喜欢本项目,请考虑参与建设本项目。我们欢迎您提交代码,也欢迎您以任何其他方式来参与本项目的建设。比如撰写文档,分享使用心得,参与社区讨论,当然,如果经济允许,您也可以考虑[经济资助](https://opencollective.com/sanic-org/)。 \ No newline at end of file +如果您喜欢本项目,请考虑参与建设本项目。 我们欢迎您提交代码,也欢迎您以任何其他方式来参与本项目的建设。 比如撰写文档,分享使用心得,参与社区讨论,当然,如果经济允许,您也可以考虑[经济资助](https://opencollective.com/sanic-org/)。 diff --git a/src/zh/guide/release-notes/v22.12.md b/src/zh/guide/release-notes/v22.12.md new file mode 100644 index 0000000000..50b3dd96ad --- /dev/null +++ b/src/zh/guide/release-notes/v22.12.md @@ -0,0 +1,176 @@ +# Version 22.12 + +[[toc]] + +## Introduction + +This is the final release of the version 22 [release cycle](../../org/policies.md#release-schedule). As such it is a **long-term support** release, and will be supported as stated in the [policies](../../org/policies.md#long-term-support-v-interim-releases). + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + +### 🚨 *BREAKING CHANGE* - Sanic Inspector is now an HTTP server + +Sanic v22.9 introduced the [Inspector](./v22.9.md#inspector) to allow live inspection of a running Sanic instance. This feature relied upon opening a TCP socket and communicating over a custom protocol. That basic TCP protocol has been dropped in favor of running a full HTTP service in its place. [Learn more about the Inspector](../deployment/inspector.md). + +The current release introduces a new HTTP server and a refreshed CLI experience. This enables several new features highlighted here. Perhaps the most significant change, however, is to move all of the Inspector's commands to a subparser on the CLI instance. + +``` +$ sanic inspect --help + + ▄███ █████ ██ ▄█▄ ██ █ █ ▄██████████ + ██ █ █ █ ██ █ █ ██ + ▀███████ ███▄ ▀ █ █ ██ ▄ █ ██ + ██ █████████ █ ██ █ █ ▄▄ + ████ ████████▀ █ █ █ ██ █ ▀██ ███████ + +Optional +======== + General: + -h, --help show this help message and exit + --host HOST, -H HOST Inspector host address [default 127.0.0.1] + --port PORT, -p PORT Inspector port [default 6457] + --secure, -s Whether to access the Inspector via TLS encryption + --api-key API_KEY, -k API_KEY Inspector authentication key + --raw Whether to output the raw response information + + Subcommands: + Run one or none of the below subcommands. Using inspect without a subcommand will fetch general information about the state of the application instance. + + Or, you can optionally follow inspect with a subcommand. If you have created a custom Inspector instance, then you can run custom commands. See https://sanic.dev/en/guide/deployment/inspector.html for more details. + + {reload,shutdown,scale,} + reload Trigger a reload of the server workers + shutdown Shutdown the application and all processes + scale Scale the number of workers + Run a custom command +``` + +#### CLI remote access now available + +The `host` and `port` of the Inspector are now explicitly exposed on the CLI as shown above. Previously in v22.9, they were inferred by reference to the application instance. Because of this change, it will be more possible to expose the Inspector on live production instances and access from a remote installation of the CLI. + +For example, you can check your running production deployment from your local development machine. + +``` +$ sanic inspect --host=1.2.3.4 +``` + +::: warning +For **production** instances, make sure you are _using TLS and authentication_ described below. +::: + +#### TLS encryption now available + +You can secure your remote Inspector access by providing a TLS certificate to encrypt the web traffic. + +```python +app.config.INSPECTOR_TLS_CERT = "/path/to/cert.pem" +app.config.INSPECTOR_TLS_KEY = "/path/to/key.pem" +``` + +To access an encrypted installation via the CLI, use the `--secure` flag. + +``` +$ sanic inspect --secure +``` + +#### Authentication now available + +To control access to the remote Inspector, you can protect the endpoints using an API key. + +```python +app.config.INSPECTOR_API_KEY = "Super-Secret-200" +``` + +To access a protected installation via the CLI, use the `--api-key` flag. + +``` +$ sanic inspect --api-key=Super-Secret-200 +``` + +This is equivalent to the header: `Authorization: Bearer `. + +``` +$ curl http://localhost:6457 -H "Authorization: Bearer Super-Secret-200" +``` + +### Scale number of running server workers + +The Inspector is now capable of scaling the number of worker processes. For example, to scale to 3 replicas, use the following command: + +``` +$ sanic inspect scale 3 +``` + +### Extend Inspector with custom commands + +The Inspector is now fully extendable to allow for adding custom commands to the CLI. For more information see [Custom Commands](../deployment/inspector.md#custom-commands). + +``` +$ sanic inspect foo --bar +``` + +### Early worker exit on failure + +The process manager shipped with v22.9 had a very short startup timeout. This was to protect against deadlock. This was increased to 30s, and a new mechanism has been added to fail early if there is a crash in a worker process on startup. + +### Introduce `JSONResponse` with convenience methods to update a JSON response body + +The `sanic.response.json` convenience method now returns a new subclass of `HTTPResponse` appropriately named: `JSONResponse`. This new type has some convenient methods for handling changes to a response body after its creation. + +```python +resp = json({"foo": "bar"}) +resp.update({"another": "value"}) +``` + +See [Returning JSON Data](../basics/response.md#returning-json-data) for more information. + +### Updates to downstream requirements: `uvloop` and `websockets` + +Minimum `uvloop` was set to `0.15.0`. Changes were added to make Sanic compliant with `websockets` version `11.0`. + +### Force exit on 2nd `ctrl+c` + +On supporting operating systems, the existing behavior is for Sanic server to try to perform a graceful shutdown when hitting `ctrl+c`. This new release will perform an immediate shutdown on subsequent `ctrl+c` after the initial shutdown has begun. + +### Deprecations and Removals + +1. *DEPRECATED* - The `--inspect*` commands introduced in v22.9 have been replaced with a new subcommand parser available as `inspect`. The flag versions will continue to operate until v23.3. You are encouraged to use the replacements. While this short deprecation period is a deviation from the standard two-cycles, we hope this change will be minimally disruptive. + ``` + OLD sanic ... --inspect + NEW sanic ... inspect + + OLD sanic ... --inspect-raw + NEW sanic ... inspect --raw + + OLD sanic ... --inspect-reload + NEW sanic ... inspect reload + + OLD sanic ... --inspect-shutdown + NEW sanic ... inspect shutdown + ``` + +## News + +The Sanic Community Organization will be headed by a new Steering Council for 2023. There are two returning and two new members. + +[@ahopkins](https://github.com/ahopkins) *returning* \ +[@prryplatypus](https://github.com/prryplatypus) *returning* \ +[@sjsadowski](https://github.com/sjsadowski) *NEW* \ +[@Tronic](https://github.com/Tronic) *NEW* + +The 2023 release managers are [@ahopkins](https://github.com/ahopkins) and [@sjsadowski](https://github.com/sjsadowski). + +If you are interested in getting more involved with Sanic, contact us on the [Discord server](https://discord.gg/FARQzAEMAA). + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@aaugustin](https://github.com/aaugustin) [@ahopkins](https://github.com/ahopkins) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@kijk2869](https://github.com/kijk2869) [@LiraNuna](https://github.com/LiraNuna) [@prryplatypus](https://github.com/prryplatypus) [@sjsadowski](https://github.com/sjsadowski) [@todddialpad](https://github.com/todddialpad) [@Tronic](https://github.com/Tronic) + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/zh/guide/release-notes/v22.3.md b/src/zh/guide/release-notes/v22.3.md index c728745dad..d073dec717 100644 --- a/src/zh/guide/release-notes/v22.3.md +++ b/src/zh/guide/release-notes/v22.3.md @@ -4,7 +4,7 @@ ## 介绍(Introduction) -22.3 版本是 22 [版本周期](../project/policies.md#release-schedule)中的第一个版本。所有的标准 SCO 库现在都进入了相同的发布周期,并将遵循相同的版本控制模式。包括: +22.3 版本是 22 [版本周期](../project/policies.md#release-schedule)中的第一个版本。 所有的标准 SCO 库现在都进入了相同的发布周期,并将遵循相同的版本控制模式。 包括: - [`sanic-routing`](https://github.com/sanic-org/sanic-routing) - [`sanic-testing`](https://github.com/sanic-org/sanic-testing) @@ -12,11 +12,11 @@ ## 更新内容(What to know) -更多细节详见[发布说明](https://sanic.readthedocs.io/en/stable/sanic/changelog.html)。以下是值得注意的新功能或突破性变化,以及更新内容... +更多细节详见[发布说明](https://sanic.readthedocs.io/en/stable/sanic/changelog.html)。 以下是值得注意的新功能或突破性变化,以及更新内容... ### 多应用服务(Application multi-serve) -Sanic 新增了一个 API,允许您在同一个进程上并行运行多个应用。通过 `app.prepare(...)` 将不同的应用一次或多次绑定到唯一的主机端口上。最后再通过 `Sanic.server()` 来运行应用程序。 +Sanic 新增了一个 API,允许您在同一个进程上并行运行多个应用。 通过 `app.prepare(...)` 将不同的应用一次或多次绑定到唯一的主机端口上。 Each time it should be bound to a unique host/port combination. 最后再通过 `Sanic.server()` 来运行应用程序。 ```python app = Sanic("One") @@ -31,13 +31,13 @@ app2.prepare(port=8887) Sanic.serve() ``` -在上面的代码片段中,两个应用程序将会在多个不同的端口上并发运行。CIL 客户端当前暂不支持该功能。 +在上面的代码片段中,两个应用程序将会在多个不同的端口上并发运行。 CIL 客户端当前暂不支持该功能。 -这种模式依旧依赖了 `app.run`,不过 `app.run` 是上述模式的简写,`app.run` 方法不会被弃用,依旧会得到完整的维护。 +这种模式依旧依赖了 `app.run`,不过 `app.run` 是上述模式的简写,`app.run` 方法不会被弃用,依旧会得到完整的维护。 It should be noted that `app.run` is now just a shorthand for the above pattern and is still fully supported. ### 👶 _BETA 功能_ - 新的路由参数类型: 文件名和拓展名 -一种常见的开发模式是创建一个动态生成文件的路由。端点用于匹配带有扩展名的文件。有一个新的路径参数来匹配文件: ``。 +一种常见的开发模式是创建一个动态生成文件的路由。 端点用于匹配带有扩展名的文件。 有一个新的路径参数来匹配文件: ``。 ```python @app.get("/path/to/") @@ -45,7 +45,7 @@ async def handler(request, filename, ext): ... ``` -这将捕获任何以文件扩展名结尾的模式。但是,您可能希望通过指定扩展名以及对文件名使用其他路径参数类型来扩展它。 +这将捕获任何以文件扩展名结尾的模式。 但是,您可能希望通过指定扩展名以及对文件名使用其他路径参数类型来扩展它。 例如您只想捕获文件名为纯数字的 `.jpg` 文件: @@ -57,20 +57,139 @@ async def handler(request, filename, ext): 下面是一些常用的匹配例子: -| 路由定义 | 示例 | 文件名 | 拓展名 | -| --------------------------------- | ----------- | -------- | ---------- | -| \ | page.txt | `"page"` | `"txt"` | -| \ | cat.jpg | `"cat"` | `"jpg"` | -| \ | cat.jpg | `"cat"` | `"jpg"` | -| | 123.txt | `123` | `"txt"` | -| | 123.svg | `123` | `"svg"` | -| | 3.14.tar.gz | `3.14` | `"tar.gz"` | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 路由定义 + + 示例 + + 文件名 + + 拓展名 +
+ \ + + page.txt + + "page" + + "txt" +
+ \ + + cat.jpg + + "cat" + + "jpg" +
+ \ + + + png\ + + gif\ + + svg> | cat.jpg | "cat" | "jpg" +
+ + + 123.txt + + 123 + + "txt" +
+ + + + png\ + + gif\ + + svg> | 123.svg | 123 | "svg" +
+ + + 3.14.tar.gz + + 3.14 + + "tar.gz" +
### 🚨 _突破性变化_ - 非空字符串的路径参数匹配 动态路径参数只能匹配非空字符串。 -以前,带有动态字符串参数 (`/` 或 `/`) 的路由可以匹配任何字符串,包括空字符串。它现在将只匹配一个非空字符串。要保留旧的行为,应该使用新的参数类型: `/< foo:stroempty >`。 +以前,带有动态字符串参数 (`/` 或 `/`) 的路由可以匹配任何字符串,包括空字符串。 它现在将只匹配一个非空字符串。 要保留旧的行为,应该使用新的参数类型: `/< foo:stroempty >`。 ```python @app.get("/path/to/") @@ -80,9 +199,9 @@ async def handler(request, foo) ### 🚨 _突破性变化_ - `sanic.worker.GunicornWorker` 现已被移除 -不同于正常流程下的弃用策略,为了支持使 Sanic 支持多服务,我们在升级过程中删除了 `GunicornWorker` 类,因为即便它存在,也不是部署 Sanic 的最佳策略。 +不同于正常流程下的弃用策略,为了支持使 Sanic 支持多服务,我们在升级过程中删除了 `GunicornWorker` 类,因为即便它存在,也不是部署 Sanic 的最佳策略。 This decision was made largely in part because even while it existed it was not an optimal strategy for deploying Sanic. -如果您想使用 `gunicorn` 部署 Sanic,那么建议您使用 [`uvicorn`](https://www.uvicorn.org/#running-with-gunicorn)。它可以有效的将 `unicorn` 作为 ASGI 应用程序来运行。您可以通过安装 `uvicorn` 升级到此模式: +如果您想使用 `gunicorn` 部署 Sanic,那么建议您使用 [`uvicorn`](https://www.uvicorn.org/#running-with-gunicorn)。 它可以有效的将 `unicorn` 作为 ASGI 应用程序来运行。 您可以通过安装 `uvicorn` 升级到此模式: ``` pip install uvicorn @@ -96,7 +215,7 @@ gunicorn path.to.sanic:app -k uvicorn.workers.UvicornWorker ### 认证头解析(Authorization header parsing) -目前, `Authorization` 标头已经可以进行解析。您已经能够使用 `request.token` 来访问以下两种形式之一的报文: +目前, `Authorization` 标头已经可以进行解析。 您已经能够使用 `request.token` 来访问以下两种形式之一的报文: ``` Authorization: Token @@ -126,7 +245,6 @@ def create_app(args): print(args) return app ``` - ``` $sanic p:create_app --factory Namespace(module='p:create_app', factory=True, simple=False, host='127.0.0.1', port=8000, unix='', cert=None, key=None, tls=None, tlshost=False, workers=1, fast=False, access_log=False, debug=False, auto_reload=False, path=None, dev=False, motd=True, verbosity=None, noisy_exceptions=False) @@ -161,13 +279,17 @@ async def reload_stop(*_): ### 监听器不再需要 `loop` 参数 -现在,您可以在您的监听器函数中抛弃 `loop` 参数了,按照下面的两种方式来声明监听器函数都是可行的方法: +You can leave out the `loop` argument of a listener. Both of these examples work as expected: ```python @app.before_server_start async def without(app): ... +@app.before_server_start +async def without(app): + ... + @app.before_server_start async def with(app, loop): ... @@ -175,49 +297,29 @@ async def with(app, loop): ### 功能迁移 - 调试模式不再自动开启重载功能 -使用 `-debug` 或 `debug=True` 运行时,Sanic 服务器不会自动启动自动重装程序。这种在调试时同时执行这两项操作的功能在 v21 中被弃用,并在此版本中被删除。如果你想同时拥有调试模式和自动重载,你可以使用 `-dev` 或者 `dev=True`。 +使用 `-debug` 或 `debug=True` 运行时,Sanic 服务器不会自动启动自动重装程序。 这种在调试时同时执行这两项操作的功能在 v21 中被弃用,并在此版本中被删除。 如果你想同时拥有调试模式和自动重载,你可以使用 `-dev` 或者 `dev=True`。 **开发模式 = 调试模式 + 自动重载** ### 功能弃用 - 加载小写的环境变量 -在以前的版本中,Sanic 加载环境变量时,只要能够完全匹配就不会区分大小写。但是一般情况下环境变量应该是完全大写的,所以该功能已被提上弃用日程,如果环境变量不是完全大写的,您将会收到一条警告。加载非大写的环境变量功能将在 v22.9 版本中彻底移除。 +Sanic loads prefixed environment variables as configuration values. It has not distinguished between uppercase and lowercase as long as the prefix matches. However, it has always been the convention that the keys should be uppercase. This is deprecated and you will receive a warning if the value is not uppercase. In v22.9 only uppercase and prefixed keys will be loaded. ## 新闻(News) ### Packt 出版了 《Sanic web 开发》 的新书 ----:1 - -由 [@ahopkins](https://github.com/ahopkins) 主笔, SCO 认证的新书 《Sanic Web 开发》现已发布。该书的收入将按照一定的比例捐赠给 SCO ,用于 Sanic 的进一步发展。 +由 [@ahopkins](https://github.com/ahopkins) 主笔, SCO 认证的新书 《Sanic Web 开发》现已发布。 该书的收入将按照一定的比例捐赠给 SCO ,用于 Sanic 的进一步发展。 -您可以通过 [sanicbook.com](https://sanicbook.com/) 来了解更多详细信息。 - -:--:1 - -![Python Web Development with Sanic](https://sanicbook.com/images/SanicCoverFinal.png) - -:--- +Python Web Development with Sanic ## 鸣谢(Thank you) -感谢每一位参与本次发布的人::clap: +您可以通过 [sanicbook.com](https://sanicbook.com/) 来了解更多详细信息。 -[@aaugustin](https://github.com/aaugustin) -[@ahopkins](https://github.com/ahopkins) -[@ashleysommer](https://github.com/ashleysommer) -[@cansarigol3megawatt](https://github.com/cansarigol3megawatt) -[@ChihweiLHBird](https://github.com/ChihweiLHBird) -[@gluhar2006](https://github.com/gluhar2006) -[@komar007](https://github.com/komar007) -[@ombe1229](https://github.com/ombe1229) -[@prryplatypus](https://github.com/prryplatypus) -[@SaidBySolo](https://github.com/SaidBySolo) -[@Tronic](https://github.com/tronic) -[@vltr](https://github.com/vltr) +:--:1 -并且,特别感谢 [@ConnorZhang](https://github.com/miss85246) 和 [@ZinkLu](https://github.com/ZinkLu),他们在同步翻译最新中文文档上付出了巨大精力。 --- -如果您喜欢本项目,请考虑参与建设本项目。我们欢迎您提交代码,也欢迎您以任何其他方式来参与本项目的建设。比如撰写文档,分享使用心得,参与社区讨论,当然,如果经济允许,您也可以考虑[经济资助](https://opencollective.com/sanic-org/)。 +如果您喜欢本项目,请考虑参与建设本项目。 我们欢迎您提交代码,也欢迎您以任何其他方式来参与本项目的建设。 比如撰写文档,分享使用心得,参与社区讨论,当然,如果经济允许,您也可以考虑[经济资助](https://opencollective.com/sanic-org/)。 diff --git a/src/zh/guide/release-notes/v22.6.md b/src/zh/guide/release-notes/v22.6.md new file mode 100644 index 0000000000..f2779fcb19 --- /dev/null +++ b/src/zh/guide/release-notes/v22.6.md @@ -0,0 +1,152 @@ +# Version 22.6 + +[[toc]] + +## Introduction + +This is the second release of the version 22 [release cycle](../../org/policies.md#release-schedule). Version 22 will be "finalized" in the December long-term support version release. + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + + +### Automatic TLS setup in `DEBUG` mode + +The Sanic server can automatically setup a TLS certificate using either [mkcert](https://github.com/FiloSottile/mkcert) or [trustme](https://github.com/python-trio/trustme). This certificate will enable `https://localhost` (or another local address) for local development environments. You must install either `mkcert` or `trustme` on your own for this to work. + +---:1 +``` +$ sanic path.to.server:app --auto-tls --debug +``` +:--:1 + +```python +app.run(debug=True, auto_tls=True) +``` +:--- + +This feature is not available when running in `ASGI` mode, or in `PRODUCTION` mode. When running Sanic in production, you should be using a real TLS certificate either purchased through a legitimate vendor, or using [Let's Encrypt](https://letsencrypt.org/). + + +### HTTP/3 Server :rocket: + +In June 2022, the IETF finalized and published [RFC 9114](https://www.rfc-editor.org/rfc/rfc9114.html), the specification for HTTP/3. In short, HTTP/3 is a **very** different protocol than HTTP/1.1 and HTTP/2 because it implements HTTP over UDP instead of TCP. The new HTTP protocol promises faster webpage load times and solving some of the problems of the older standards. You are encouraged to [read more about](https://http3-explained.haxx.se/) this new web technology. You likely will need to install a [capable client](https://curl.se/docs/http3.html) as traditional tooling will not work. + +Sanic server offers HTTP/3 support using [aioquic](https://github.com/aiortc/aioquic). This **must** be installed: + +``` +pip install sanic aioquic +``` + +``` +pip install sanic[http3] +``` + +To start HTTP/3, you must explicitly request it when running your application. + +---:1 +``` +$ sanic path.to.server:app --http=3 +``` + +``` +$ sanic path.to.server:app -3 +``` +:--:1 + +```python +app.run(version=3) +``` +:--- + +To run both an HTTP/3 and HTTP/1.1 server simultaneously, you can use [application multi-serve](./v22.3.html#application-multi-serve) introduced in v22.3. + + +---:1 +``` +$ sanic path.to.server:app --http=3 --http=1 +``` + +``` +$ sanic path.to.server:app -3 -1 +``` +:--:1 + +```python +app.prepre(version=3) +app.prepre(version=1) +Sanic.serve() +``` +:--- + +Because HTTP/3 requires TLS, you cannot start a HTTP/3 server without a TLS certificate. You should [set it up yourself](../how-to/tls.html) or use `mkcert` if in `DEBUG` mode. Currently, automatic TLS setup for HTTP/3 is not compatible with `trustme`. + +**:baby: This feature is being released as an *EARLY RELEASE FEATURE*.** It is **not** yet fully compliant with the HTTP/3 specification, lacking some features like [websockets](https://websockets.spec.whatwg.org/), [webtransport](https://w3c.github.io/webtransport/), and [push responses](https://http3-explained.haxx.se/en/h3/h3-push). Instead the intent of this release is to bring the existing HTTP request/response cycle towards feature parity with HTTP/3. Over the next several releases, more HTTP/3 features will be added and the API for it finalized. + +### Consistent exception naming + +Some of the Sanic exceptions have been renamed to be more compliant with standard HTTP response names. + +- `InvalidUsage` >> `BadRequest` +- `MethodNotSupported` >> `MethodNotAllowed` +- `ContentRangeError` >> `RangeNotSatisfiable` + +All old names have been aliased and will remain backwards compatible. + +### Current request getter + +Similar to the API to access an application (`Sanic.get_app()`), there is a new method for retrieving the current request when outside of a request handler. + +```python +from sanic import Request + +Request.get_current() +``` + +### Improved API support for setting cache control headers + +The `file` response helper has some added parameters to make it easier to handle setting of the [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) header. + +```python +file( + ..., + last_modified=..., + max_age=..., + no_store=..., +) +``` + + +### Custom `loads` function + +Just like Sanic supports globally setting a custom `dumps`, you can now set a global custom `loads`. + +```python +from orjson import loads + +app = Sanic("Test", loads=loads) +``` + + +### Deprecations and Removals + +1. *REMOVED* - Applications may no longer opt-out of the application registry +1. *REMOVED* - Custom exception handlers will no longer run after some part of an exception has been sent +1. *REMOVED* - Fallback error formats cannot be set on the `ErrorHandler` and must **only** be set in the `Config` +1. *REMOVED* - Setting a custom `LOGO` for startup is no longer allowed +1. *REMOVED* - The old `stream` response convenience method has been removed +1. *REMOVED* - `AsyncServer.init` is removed and no longer an alias of `AsyncServer.app.state.is_started` + + + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@ahopkins](https://github.com/ahopkins) [@amitay87](https://github.com/amitay87) [@ashleysommer](https://github.com/ashleysommer) [@azimovMichael](https://github.com/azimovMichael) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@kijk2869](https://github.com/kijk2869) [@prryplatypus](https://github.com/prryplatypus) [@SaidBySolo](https://github.com/SaidBySolo) [@sjsadowski](https://github.com/sjsadowski) [@timmo001](https://github.com/timmo001) [@zozzz](https://github.com/zozzz) + + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/zh/guide/release-notes/v22.9.md b/src/zh/guide/release-notes/v22.9.md new file mode 100644 index 0000000000..33b265ea83 --- /dev/null +++ b/src/zh/guide/release-notes/v22.9.md @@ -0,0 +1,293 @@ +# Version 22.9 + +[[toc]] + +## Introduction + +This is the third release of the version 22 [release cycle](../../org/policies.md#release-schedule). Version 22 will be "finalized" in the December long-term support version release. + +## What to know + +More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade... + + +### :warning: *IMPORTANT* - New worker manager :rocket: + +Sanic server has been overhauled to provide more consistency and flexbility in how it operates. More details about the motivations are outlined in [PR #2499](https://github.com/sanic-org/sanic/pull/2499) and discussed in a live stream [discussion held on YouTube](https://youtu.be/m8HCO8NK7HE). + +This **does NOT apply** to Sanic in ASGI mode + +#### Overview of the changes + +- The worker servers will **always** run in a child process. + - Previously this could change depending upon one versus many workers, and the usage or not of the reloader. This should lead to much more predictable development environments that more closely match their production counterparts. +- Multi-workers is **now supported on Windows**. + - This was impossible because Sanic relied upon the `multiprocessing` module using `fork`, which is not available on Windows. + - Now, Sanic will always use `spawn`. This does have some noticeable differences, particularly if you are running Sanic in the global scope with `app.run` (see below re: upgrade issues). +- The application instance now has a new `multiplexer` object that can be used to restart one or many workers. This could, for example, be triggered by a request. +- There is a new Inspector that can provide details on the state of your server. +- Sanic worker manager can run arbitrary processes. + - This allows developers to add any process they want from within Sanic. + - Possible use cases: + - Health monitor, see [Sanic Extensions]() + - Logging queue, see [Sanic Extensions]() + - Background worker queue in a seperate process + - Running another application, like a bot +- There is a new listener called: `main_process_ready`. It really should only be used for adding arbitrary processes to Sanic. +- Passing shared objects between workers. + - Python does allow some types of objects to share state between processes, whether through shared memory, pipes, etc. + - Sanic will now allow these types of object to be shared on the `app.shared_ctx` object. + - Since this feature relies upon Pythons `multiprocessing` library, it obviously will only work to share state between Sanic worker instances that are instantiated from the same execution. This is *not* meant to provide an API for horizontal scaling across multiple machines for example. + +#### Adding a shared context object + +To share an object between worker processes, it *MUST* be assigned inside of the `main_process_start` listener. + +```python +from multiprocessing import Queue + +@app.main_process_start +async def main_process_start(app): + app.shared_ctx.queue = Queue() +``` + +All objects on `shared_ctx` will be available now within each worker process. + +```python +@app.before_server_starts +async def before_server_starts(app): + assert isinstance(app.shared_ctx.queue, Queue) + +@app.on_request +async def on_request(request): + assert isinstance(request.app.shared_ctx.queue, Queue) + +@app.get("/") +async def handler(request): + assert isinstance(request.app.shared_ctx.queue, Queue) +``` + +*NOTE: Sanic will not stop you from registering an unsafe object, but may warn you. Be careful not to just add a regular list object, for example, and expect it to work. You should have an understanding of how to share state between processes.* + +#### Running arbitrary processes + +Sanic can run any arbitrary process for you. It should be capable of being stopped by a `SIGINT` or `SIGTERM` OS signal. + +These processes should be registered inside of the `main_process_ready` listener. + +```python +@app.main_process_ready +async def ready(app: Sanic, _): + app.manager.manage("MyProcess", my_process, {"foo": "bar"}) +# app.manager.manage(, , ) +``` + +#### Inspector + +Sanic ships with an optional Inspector, which is a special process that allows for the CLI to inspect the running state of an application and issue commands. It currently will only work if the CLI is being run on the same machine as the Sanic instance. + +``` +sanic path.to:app --inspect +``` + +![Sanic inspector](https://user-images.githubusercontent.com/166269/190099384-2f2f3fae-22d5-4529-b279-8446f6b5f9bd.png) + +The new CLI commands are: + +``` + --inspect Inspect the state of a running instance, human readable + --inspect-raw Inspect the state of a running instance, JSON output + --trigger-reload Trigger worker processes to reload + --trigger-shutdown Trigger all processes to shutdown +``` + +This is not enabled by default. In order to have it available, you must opt in: + +```python +app.config.INSPECTOR = True +``` + +*Note: [Sanic Extensions]() provides a [custom request](../basics/app.md#custom-requests) class that will add a request counter to the server state. + +#### Application multiplexer + +Many of the same information and functionality is available on the application instance itself. There is a new `multiplexer` object on the application instance that has the ability to restart one or more workers, and fetch information about the current state. + +You can access it as `app.multiplexer`, or more likely by its short alias `app.m`. + +```python +@app.on_request +async def print_state(request: Request): + print(request.app.m.state) +``` + +#### Potential upgrade issues + +Because of the switch from `fork` to `spawn`, if you try running the server in the global scope you will receive an error. If you see something like this: + +``` +sanic.exceptions.ServerError: Sanic server could not start: [Errno 98] Address already in use. +This may have happened if you are running Sanic in the global scope and not inside of a `if __name__ == "__main__"` block. +``` + +... then the change is simple. Make sure `app.run` is inside a block. + +```python +if __name__ == "__main__": + app.run(port=9999, dev=True) +``` + +#### Opting out of the new functionality + +If you would like to run Sanic without the new process manager, you may easily use the legacy runners. Please note that support for them **will be removed** in the future. A date has not yet been set, but will likely be sometime in 2023. + +To opt out of the new server and use the legacy, choose the appropriate method depending upon how you run Sanic: + +---:1 If you use the CLI... :--:1 +``` +sanic path.to:app --legacy +``` +:--- + +---:1 If you use `app.run`... :--:1 +``` +app.run(..., legacy=True) +``` +:--- + +---:1 If you `app.prepare`... :--:1 +``` +app.prepare(...) +Sanic.serve_legacy() +``` +:--- + +Similarly, you can force Sanic to run in a single process. This however means there will not be any access to the auto-reloader. + +---:1 If you use the CLI... :--:1 +``` +sanic path.to:app --single-process +``` +:--- + +---:1 If you use `app.run`... :--:1 +``` +app.run(..., single_process=True) +``` +:--- + +---:1 If you `app.prepare`... :--:1 +``` +app.prepare(...) +Sanic.serve_single() +``` +:--- +### Middleware priority + +Middleware is executed in an order based upon when it was defined. Request middleware are executed in sequence and response middleware in reverse. This could have an unfortunate impact if your ordering is strictly based upon import ordering with global variables for example. + +A new addition is to break-out of the strict construct and allow a priority to be assigned to a middleware. The higher the number for a middleware definition, the earlier in the sequence it will be executed. This applies to **both** request and response middleware. + +```python +@app.on_request +async def low_priority(_): + ... + +@app.on_request(priority=10) +async def high_priority(_): + ... +``` + +In the above example, even though `low_priority` is defined first, `high_priority` will run first. + +### Custom `loads` function + + +Sanic has supported the ability to add a [custom `dumps` function](https://sanic.readthedocs.io/en/stable/sanic/api/app.html#sanic.app.Sanic) when instantiating an app. The same functionality has been extended to `loads`, which will be used when deserializing. + +```python +from json import loads + +Sanic("Test", loads=loads) +``` + +### Websocket objects are now iterable + + +Rather than calling `recv` in a loop on a `Websocket` object, you can iterate on it in a `for` loop. + + +```python +from sanic import Request, Websocket + +@app.websocket("/ws") +async def ws_echo_handler(request: Request, ws: Websocket): + async for msg in ws: + await ws.send(msg) +``` + +### Appropriately respond with 304 on static files + +When serving a static file, the Sanic server can respond appropriately to a request with `If-Modified-Since` using a `304` response instead of resending a file. + +### Two new signals to wrap handler execution + +Two new [signals](../advanced/signals.md) have been added that wrap the execution of a request handler. + +- `http.handler.before` - runs after request middleware but before the route handler +- `http.handler.after` - runs after the route handler + - In *most* circumstances, this also means that it will run before response middleware. However, if you call `request.respond` from inside of a route handler, then your middleware will come first + +### New Request properties for HTTP method information + +The HTTP specification defines which HTTP methods are: safe, idempotent, and cacheable. New properties have been added that will respond with a boolean flag to help identify the request property based upon the method. + +```python +request.is_safe +request.is_idempotent +request.is_cacheable +``` + +### 🚨 *BREAKING CHANGE* - Improved cancel request exception + +In prior version of Sanic, if a `CancelledError` was caught it could bubble up and cause the server to respond with a `503`. This is not always the desired outcome, and it prevented the usage of that error in other circumstances. As a result, Sanic will now use a subclass of `CancelledError` called: `RequestCancelled` for this functionality. It likely should have little impact unless you were explicitly relying upon the old behavior. + + +For more details on the specifics of these properties, checkout the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods). + +### New deprecation warning filter + +You can control the level of deprecation warnings from Sanic using [standard library warning filter values](https://docs.python.org/3/library/warnings.html#the-warnings-filter). Default is `"once"`. + +```python +app.config.DEPRECATION_FILTER = "ignore" +``` + +### Deprecations and Removals + +1. *DEPRECATED* - Duplicate route names have been deprecated and will be removed in v23.3 +1. *DEPRECATED* - Registering duplicate exception handlers has been deprecated and will be removed in v23.3 +1. *REMOVED* - `route.ctx` not set by Sanic, and is a blank object for users, therefore ... + - `route.ctx.ignore_body` >> `route.extra.ignore_body` + - `route.ctx.stream` >> `route.extra.stream` + - `route.ctx.hosts` >> `route.extra.hosts` + - `route.ctx.static` >> `route.extra.static` + - `route.ctx.error_format` >> `route.extra.error_format` + - `route.ctx.websocket` >> `route.extra.websocket` +1. *REMOVED* - `app.debug` is READ-ONLY +1. *REMOVED* - `app.is_running` removed +1. *REMOVED* - `app.is_stopping` removed +1. *REMOVED* - `Sanic._uvloop_setting` removed +1. *REMOVED* - Prefixed environment variables will be ignored if not uppercase + + +## Thank you + +Thank you to everyone that participated in this release: :clap: + +[@ahopkins](https://github.com/ahopkins) [@azimovMichael](https://github.com/azimovMichael) [@ChihweiLHBird](https://github.com/ChihweiLHBird) [@huntzhan](https://github.com/huntzhan) [@monosans](https://github.com/monosans) [@prryplatypus](https://github.com/prryplatypus) [@SaidBySolo](https://github.com/SaidBySolo) [@seemethere](https://github.com/seemethere) [@sjsadowski](https://github.com/sjsadowski) [@timgates42](https://github.com/timgates42) [@Tronic](https://github.com/Tronic) + + +--- + +If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/). diff --git a/src/zh/help.md b/src/zh/help.md index 3c4f2cf078..bd280bea3e 100644 --- a/src/zh/help.md +++ b/src/zh/help.md @@ -4,7 +4,7 @@ layout: BlankLayout # Need some help? -As an active community of developers, we try to support each other. If you need some help, try one of the following: +As an active community of developers, we try to support each other. If you need some help, try one of the following: If you need some help, try one of the following: ---:1 diff --git a/src/zh/org/feature_requests.md b/src/zh/org/feature_requests.md new file mode 100644 index 0000000000..5c57ea1f02 --- /dev/null +++ b/src/zh/org/feature_requests.md @@ -0,0 +1,9 @@ +# Feature Requests + +[Create new feature request](https://github.com/sanic-org/sanic/issues/new?assignees=&labels=feature+request&template=feature_request.md) + +To vote on a feature request, visit the [GitHub Issues](https://github.com/sanic-org/sanic/issues?q=is%3Aissue+is%3Aopen+label%3A%22feature+request%22%2CRFC+sort%3Areactions-%2B1-desc) and add a reaction + +--- + + diff --git a/src/zh/org/policies.md b/src/zh/org/policies.md index c8d9123f8e..b03fe82ab4 100644 --- a/src/zh/org/policies.md +++ b/src/zh/org/policies.md @@ -2,18 +2,17 @@ ## 版本号(Versioning) -Sanic 使用 [calendar versioning](https://calver.org/),别名 "calver"。具体使用如下格式: +Sanic 使用 [calendar versioning](https://calver.org/),别名 "calver"。 具体使用如下格式: ``` YY.MM.MICRO ``` -通常来说,版本号是以 `YY.MM` 的格式来进行编排的,小版本号表示从 `0` 开始的增量补丁。 - +通常来说,版本号是以 `YY.MM` 的格式来进行编排的,小版本号表示从 `0` 开始的增量补丁。 The `MICRO` number indicates an incremental patch version, starting at `0`. ## 发行安排(Release Schedule) -每年有 4 次预定发布:3月,6月,9月,12月。因此,每年发布 4 个版本:`YY.3`, `YY.6`, `YY.9` 和 `YY.12`。 +每年有 4 次预定发布:3月,6月,9月,12月。 因此,每年发布 4 个版本:`YY.3`, `YY.6`, `YY.9` 和 `YY.12`。 该发行安排提供: @@ -26,37 +25,40 @@ YY.MM.MICRO ### 长期支持版 v 临时版本(Long term support v Interim releases) -Sanic 将在每年的 12 月发布一次长期支持版本(TLS 版本)TLS 版本会在 24个月内得到错误修复与安全更新。每年的 3 个临时发行版本每 3 个月发行一次,并且在后续版本发布之前一直受支持。 - -| Version | LTS | Supported | -| ------- | ------------- | ----------------------- | -| 21.9 | | :white_check_mark: | -| 21.6 | | :x: | -| 21.3 | | :x: | -| 20.12 | 2022-12 为止 | :white_check_mark: | -| 20.9 | | :x: | -| 20.6 | | :x: | -| 20.3 | | :x: | -| 19.12 | 2021-12 为止 | :ballot_box_with_check: | -| 19.9 | | :x: | -| 19.6 | | :x: | -| 19.3 | | :x: | -| 18.12 | | :x: | -| 0.8.3 | | :x: | -| 0.7.0 | | :x: | -| 0.6.0 | | :x: | -| 0.5.4 | | :x: | -| 0.4.1 | | :x: | -| 0.3.1 | | :x: | -| 0.2.0 | | :x: | -| 0.1.9 | | :x: | - -:ballot_box_with_check: = 安全/错误 修复 -:white_check_mark: = 长期支持 +Sanic 将在每年的 12 月发布一次长期支持版本(TLS 版本)TLS 版本会在 24个月内得到错误修复与安全更新。 The LTS releases receive bug fixes and security updates for **24 months**. 每年的 3 个临时发行版本每 3 个月发行一次,并且在后续版本发布之前一直受支持。 + +| Version | LTS | Supported | +| ---------- | ------------- | ------------------------- | +| 22.12 | until 2024-12 | :white_check_mark: | +| 22.9 | | :x: | +| 22.6 | | :x: | +| 22.3 | | :x: | +| 2021-12 为止 | 2022-12 为止 | :ballot_box_with_check: | +| 21.9 | | :x: | +| 21.6 | | :x: | +| 21.3 | | :x: | +| 20.12 | | :x: | +| 20.9 | | :x: | +| 20.6 | | :x: | +| 20.3 | | :x: | +| 19.12 | | :x: | +| 19.9 | | :x: | +| 19.6 | | :x: | +| 19.3 | | :x: | +| 18.12 | | :x: | +| 0.8.3 | | :x: | +| 0.7.0 | | :x: | +| 0.6.0 | | :x: | +| 0.5.4 | | :x: | +| 0.4.1 | | :x: | +| 0.3.1 | | :x: | +| 0.2.0 | | :x: | +| 0.1.9 | | :x: | + +:ballot_box_with_check: = 安全/错误 修复 :white_check_mark: = 长期支持 ## 弃用(Deprecation) -在一个特性被否决之前,或者在接口中引入突破性的变化之前,它应该被公开,并在两个发布周期中出现弃用警告。在LTS版本中不得有任何弃用行为。 - -绝对必要时,可能会在这些准则之外发生中断更新或功能删除的状况。这些情况应该很少见。例如,当没有替代方案来解决重大安全问题时,就可能发生这种情况。 +在一个特性被否决之前,或者在接口中引入突破性的变化之前,它应该被公开,并在两个发布周期中出现弃用警告。 在LTS版本中不得有任何弃用行为。 +绝对必要时,可能会在这些准则之外发生中断更新或功能删除的状况。 这些情况应该很少见。 例如,当没有替代方案来解决重大安全问题时,就可能发生这种情况。 diff --git a/src/zh/org/scope.md b/src/zh/org/scope.md index 0037284116..41580570f1 100644 --- a/src/zh/org/scope.md +++ b/src/zh/org/scope.md @@ -2,162 +2,159 @@ title: S.C.O.P.E --- -# Sanic 社区公约白皮书 + +Sanic 社区公约白皮书 +============================================ 2019 年 12 月 第一版 -## 目标(Goals) +目标(Goals) +----- 围绕 Sanic 项目创建一个可持续的、社区驱动的组织,以促进: -(1)增强 Sanic 的稳定性和可预测性。 - -(2)对 Sanic 进行快速迭代并增强周期支持。 - -(3)外部贡献者参与其中。 - -(4)创造一个整体可靠的软件。 - -(5)为社区成员提供一个安全有益的环境。 - -## 概览(Overview) +概览(Overview) +-------- -本公约是 Sanic 社区组织(Sanic Community Organization 简称 SCO)的管理概述。SCO 是一个精英主义、以共识为基础的社区组织。负责其下所有的项目。任何对组织下任一项目感兴趣的人都可以加入社区。为社区或项目做贡献,并参与决策过程。本公约用于描述如何参与其中以及如何从中获益。 +本公约是 Sanic 社区组织(Sanic Community Organization 简称 SCO)的管理概述。 SCO 是一个精英主义、以共识为基础的社区组织。 负责其下所有的项目。 任何对组织下任一项目感兴趣的人都可以加入社区。 为社区或项目做贡献,并参与决策过程。 This document describes how that participation takes place and how to set about earning merit within the project community. -## 架构(Structure) +架构(Structure) +--------- -SCO 有多个 **项目**。每一个项目都由单独的 Github 仓库进行存储。这些项目由 **用户** 使用,由 **贡献者** 开发, **核心开发者** 管理, **发布经理** 发布 最终由 **指导委员会** 监督。这看起来与 Python 项目和 PEP8016 类似, 因为那是有意设计的。 +SCO 有多个 **项目**。 每一个项目都由单独的 Github 仓库进行存储。 这些项目由 **用户** 使用,由 **贡献者** 开发, **核心开发者** 管理, **发布经理** 发布 最终由 **指导委员会** 监督。 这看起来与 Python 项目和 PEP8016 类似, 因为那是有意设计的。 -## 角色与责任(Roles and responsibilities) +角色与责任(Roles and responsibilities) +-------------------------- ### 用户(Users) -用户是对项目有需求的社区成员。他们是下载和安装软件包的开发者和使用者。他们是社区中 **最重要** 的成员,没有他们,项目就没有意义。任何人都可以是用户,项目采用的许可证应该是适当的开源许可证。 +用户是对项目有需求的社区成员。 他们是下载和安装软件包的开发者和使用者。 他们是社区中 **最重要** 的成员,没有他们,项目就没有意义。 任何人都可以是用户,项目采用的许可证应该是适当的开源许可证。 -SCO 认为 _用户应该尽可能多的_ 参与项目与社区活动 +_长期参与 SCO 的项目和社区的用户,随着时间的推移,他们可能会发现自己成为贡献者,就如下一章节所述。_ -用户贡献使项目团队能够确保他们满足这些用户的需求。常见的用户贡献包括(但不限于): +用户贡献使项目团队能够确保他们满足这些用户的需求。 常见的用户贡献包括(但不限于): -* 宣传项目(例如:网站链接或进行宣传)。 -* 从新用户的角度告知开发者优势与劣势。 -* 提供精神支持(您的一句谢谢会让您受益匪浅)。 -* 提供资金支持(该软件开源,但是开发者也需要糊口)。 +* 宣传项目(例如:网站链接或进行宣传)。 +* 从新用户的角度告知开发者优势与劣势。 +* 提供精神支持(您的一句谢谢会让您受益匪浅)。 +* 提供资金支持(该软件开源,但是开发者也需要糊口)。 -长期参与 SCO 的项目和社区的用户,随着时间的推移,他们可能会发现自己成为贡献者,就如下一章节所述。 +Users who continue to engage with the SCO, its projects, and its community will often become more and more involved. Such users may find themselves becoming contributors, as described in the next section. ### 贡献者(Contributors) -贡献者是以具体的方式为一个或多个项目做出贡献的社区成员。 -任何人都可以成为贡献者,贡献者可以有很多种形式。捐款和需求由每个项目通过捐献政策分别管理。 +贡献者是以具体的方式为一个或多个项目做出贡献的社区成员。 任何人都可以成为贡献者,贡献者可以有很多种形式。 捐款和需求由每个项目通过捐献政策分别管理。 在贡献过程中,**不需要对项目进行负责**,也 **没有具体技能要求**,同样 **不需要对人员进行选拔**。 -除了作为用户的行为之外,贡献者可能还会发现自己在做以下一项或多项事情: +SCO 认为 _用户应该尽可能多的_ 参与项目与社区活动 -* 为新用户提供技术支持(现有用户通常是为新用户提供技术支持的最佳人选) -* 报告错误 -* 提交需求 -* 提供图像设计或网站设计 -* 编程 -* 提供使用示例 -* 协助项目进行改进 -* 编写文档 -* 修复 BUG -* 添加功能 -* 提供建设性意见和参与社区讨论 +* 为新用户提供技术支持(现有用户通常是为新用户提供技术支持的最佳人选) +* 报告错误 +* 提交需求 +* 提供图像设计或网站设计 +* 编程 +* 提供使用示例 +* 协助项目进行改进 +* 编写文档 +* 修复 BUG +* 添加功能 +* 提供建设性意见和参与社区讨论 -贡献者通过 GitHub 和社区论坛参与项目。他们通过提交请求的方式向项目提交变更,这将被整个社区考虑纳入项目中。当做出第一次贡献时,社区论坛是寻求帮助最合适的地方。 +贡献者通过 GitHub 和社区论坛参与项目。 他们通过提交请求的方式向项目提交变更,这将被整个社区考虑纳入项目中。 当做出第一次贡献时,社区论坛是寻求帮助最合适的地方。 -事实上, 贡献者承担最多的任务可能是 **参与社区对话**。因为大多数关于项目方向的决定都是通过协商做出的。这将在下面详细讨论。但是,一般来说,有助于项目发展的事情,贡献者都可以 **畅所欲言** (在行为准则的范围内)并 **表达他们的意见和经验** 以帮助达大家成共识。 +事实上, 贡献者承担最多的任务可能是 **参与社区对话**。 因为大多数关于项目方向的决定都是通过协商做出的。 这将在下面详细讨论。 但是,一般来说,有助于项目发展的事情,贡献者都可以 **畅所欲言** (在行为准则的范围内)并 **表达他们的意见和经验** 以帮助达大家成共识。 -随着贡献者获得开发经验和对项目的熟悉度逐渐提升,他们在社区中的形象和在社区中承担的责任会也会逐步提升。直到某个阶段,他们可能会发现自己已经成为了核心开发者。 +随着贡献者获得开发经验和对项目的熟悉度逐渐提升,他们在社区中的形象和在社区中承担的责任会也会逐步提升。 直到某个阶段,他们可能会发现自己已经成为了核心开发者。 ### 核心开发者(Core Developer) -SCO 下的每一个项目都有自己的核心开发团队。他们是项目的负责人。 +SCO 下的每一个项目都有自己的核心开发团队。 他们是项目的负责人。 _什么是核心开发者?_ -核心开发者是社区成员,他们通过长期参与社区活动,表明他们致力于项目的持续发展。作为一名核心开发人员,通过让贡献者直接访问项目资源,使得他们可以更好的完成项目相关的内容。他们可以直接对项目库进行更改,不必通过 frok 项目的方式。 +核心开发者是社区成员,他们通过长期参与社区活动,表明他们致力于项目的持续发展。 作为一名核心开发人员,通过让贡献者直接访问项目资源,使得他们可以更好的完成项目相关的内容。 他们可以直接对项目库进行更改,不必通过 frok 项目的方式。 -这并不意味着核心开发者可以自由地做他们想做的事情。核心开发者并不比普通贡献者拥有更大的权力。他们并不能直接控制项目的最终发布。虽然这一荣誉确实表明他们作为社区中的重要成员对项目做出了具有积极意义的贡献,但他们的工作在正式发布前仍会受到社区的审查。 +这并不意味着核心开发者可以自由地做他们想做的事情。 In fact, core developers have no more direct authority over the final release of a package than do contributors. 虽然这一荣誉确实表明他们作为社区中的重要成员对项目做出了具有积极意义的贡献,但他们的工作在正式发布前仍会受到社区的审查。 _核心开发者能够在项目中做什么?_ -每个项目对核心开发者的定义可能略有不同。当然,通常来说,拥有这种称呼的人在社区中已经上升到一个信任的水平,因此他们现在被给予一些权限。这表现为对非受保护分支的推送权限,以及在批准请求时拥有发言权。 +每个项目对核心开发者的定义可能略有不同。 当然,通常来说,拥有这种称呼的人在社区中已经上升到一个信任的水平,因此他们现在被给予一些权限。 这表现为对非受保护分支的推送权限,以及在批准请求时拥有发言权。 + +The projects employ various communication mechanisms to ensure that all contributions are reviewed by the community as a whole. This includes tools provided by GitHub, as well as the Community Forums. By the time a contributor is invited to become a core developer, they should be familiar with the various tools and workflows as a user and then as a contributor. _如何成为核心开发者?_ -任何人都可以成为核心开发者。除了表现出作为团队成员积极参与项目的意愿和能力之外,没有其他特殊要求。 +任何人都可以成为核心开发者。 除了表现出作为团队成员积极参与项目的意愿和能力之外,没有其他特殊要求。 -通常,一个潜在的核心开发者需要展现出他对项目目标与理念的深度理解。他们还将在一段时间内为该项目做出宝贵贡献。但是,对于资格没有 **技术** 或 **其他技能** 的要求 +通常,一个潜在的核心开发者需要展现出他对项目目标与理念的深度理解。 他们还将在一段时间内为该项目做出宝贵贡献。 但是,对于资格没有 **技术** 或 **其他技能** 的要求 -新的核心开发者可以由现有的任何核心开发者 **随时** 提名。指导委员会每年至少举行两次投票(4 月 和 10 月)。投票应以无记名投票方式进行。该项目的每一个现有的核心开发者都将获得相当于选票上被提名者人数的票数。例如,如果有四个提名者,那么每个现有的核心开发者有四票。核心开发者可以投出他们的选票,但是不能将选票重复投给同一个候选人。被提名的候选人必须获得三分之二的选票数才会被批准。一旦被核心开发团队接受,指导委员会有责任批准并最终确定提名。指导委员会无权决定被提名人是否有足够的资格获得核心开发者称号。当然,在必要的情况下,他们保留否定投票结果的权利。 +新的核心开发者可以由现有的任何核心开发者 **随时** 提名。 指导委员会每年至少举行两次投票(4 月 和 10 月)。 投票应以无记名投票方式进行。 该项目的每一个现有的核心开发者都将获得相当于选票上被提名者人数的票数。 例如,如果有四个提名者,那么每个现有的核心开发者有四票。 核心开发者可以投出他们的选票,但是不能将选票重复投给同一个候选人。 被提名的候选人必须获得三分之二的选票数才会被批准。 一旦被核心开发团队接受,指导委员会有责任批准并最终确定提名。 指导委员会无权决定被提名人是否有足够的资格获得核心开发者称号。 当然,在必要的情况下,他们保留否定投票结果的权利。 -投票结束后,投票结果将在社区论坛上进行公布。被提名人有权要求对任何针对他们的否决进行解释。未被录取为核心开发者的被提名人,可以在之后的选举中被再次提名。 +投票结束后,投票结果将在社区论坛上进行公布。 被提名人有权要求对任何针对他们的否决进行解释。 未被录取为核心开发者的被提名人,可以在之后的选举中被再次提名。 -最重要的是要明白成为核心开发者是一种荣誉,而不是一种权利。只要成为核心开发者就必然获得这种荣誉。当然,指导委员会可以在极端情况下取消核心开发者称号(见下一节)。在正常情况下,只要个人希望继续参与项目和社区,核心开发者的头衔就一直存在。 +最重要的是要明白成为核心开发者是一种荣誉,而不是一种权利。 That privilege must be earned and once earned it can be removed by the Steering Council (see next section) in extreme circumstances. 在正常情况下,只要个人希望继续参与项目和社区,核心开发者的头衔就一直存在。 -对项目做出高于平均水平的贡献,特别是在战略方向和长期发展方面的贡献,可以被提名为指导委员会成员或发布经理。 +A committer who shows an above-average level of contribution to the project, particularly with respect to its strategic direction and long-term health, may be nominated to become a member of the Steering Council, or a Release Manager. This role is described below. -_核心开发者的权力和责任是什么?_ +_如果核心开发者变得不活跃怎么办?_ -如前所述,大多数决策都是通过协商并达成共识做出的。在某些情况下,当某个问题变得具有争议,或者需要做出重大决策时,发布经理或指导委员会可能会决定(或被要求)实施 RFC 流程,这将在之后进行详细介绍。 +如前所述,大多数决策都是通过协商并达成共识做出的。 在某些情况下,当某个问题变得具有争议,或者需要做出重大决策时,发布经理或指导委员会可能会决定(或被要求)实施 RFC 流程,这将在之后进行详细介绍。 -核心开发者在社区治理中同样拥有发言权。所有项目的所有核心开发者都有被提名为指导委员会委员和在选举中投票的权力。 +核心开发者在社区治理中同样拥有发言权。 所有项目的所有核心开发者都有被提名为指导委员会委员和在选举中投票的权力。 -本社区公约只能在三分之二活跃核心开发者的授权下进行更改,除非在采用后的前六个月内,多数核心开发者否定了多数活跃核心开发者的授权。 +对项目做出高于平均水平的贡献,特别是在战略方向和长期发展方面的贡献,可以被提名为指导委员会成员或发布经理。 -_如果核心开发者变得不活跃怎么办?_ +_核心开发者的权力和责任是什么?_ -虽然我们希望所有核心开发者能够定期参与并保持活跃。但是大家都明白,这种承诺有时并不现实。 +虽然我们希望所有核心开发者能够定期参与并保持活跃。 但是大家都明白,这种承诺有时并不现实。 -因此,指导委员会有责任鼓励参与,并有责任将不再愿意或没有能力参与的核心开发者置于不活跃状态。这样做的主要目的 **不是为了惩罚** 一个人的行为,而是为了帮助那些确实保持活跃的人继续发展。 +因此,指导委员会有责任鼓励参与,并有责任将不再愿意或没有能力参与的核心开发者置于不活跃状态。 这样做的主要目的 **不是为了惩罚** 一个人的行为,而是为了帮助那些确实保持活跃的人继续发展。 -一个变得“不活跃”的核心开发者不应该拥有存储库的提交权,也不应该参与任何投票。为了有资格在选举中投票,核心开发人员必须在先前计划的项目发布时处于 **活跃状态**。 +一个变得“不活跃”的核心开发者不应该拥有存储库的提交权,也不应该参与任何投票。 为了有资格在选举中投票,核心开发人员必须在先前计划的项目发布时处于 **活跃状态**。 不活跃的成员可以随时要求指导委员会恢复他们的状态,根据这种请求,指导委员会应使核心开发者再次归为活跃状态。 -如果个人知道他们将在一段时间内无法保持其活跃状态,则会被要求与指导委员会保持联系,并在必要时宣布自己处于不活跃状态。 +Individuals that know they will be unable to maintain their active status for a period are asked to be in communication with the Steering Council and declare themselves inactive if necessary. -“活跃的”核心开发者是指在过去六个月中以有意义的方式参与的核心开发者。最终解释权归指导委员会所有。 +“活跃的”核心开发者是指在过去六个月中以有意义的方式参与的核心开发者。 最终解释权归指导委员会所有。 ### 发布经理(Release Manager) -核心开发者只能在不受保护的分支上进行提交和合并。主分支和其他受保护的分支由该项目的发布管理团队控制。发布经理应由核心开发团队从核心开发团队中选出,并应在整个发布周期为大家服务。 +核心开发者只能在不受保护的分支上进行提交和合并。 主分支和其他受保护的分支由该项目的发布管理团队控制。 发布经理应由核心开发团队从核心开发团队中选出,并应在整个发布周期为大家服务。 -每个核心开发团队可以决定每个发布周期有多少个发布经理。建每一个发布周期至少有两个发布经理,以分担职责。不能让一个人承担所有的压力。但是,也不应该有太多的发布经理,这样项目维护起来会变得十分繁琐。 +每个核心开发团队可以决定每个发布周期有多少个发布经理。 建每一个发布周期至少有两个发布经理,以分担职责。 不能让一个人承担所有的压力。 However, there also should not be so many managers that their efforts are impeded. -发布管理团队的主要职责: +如果个人知道他们将在一段时间内无法保持其活跃状态,则会被要求与指导委员会保持联系,并在必要时宣布自己处于不活跃状态。 -* 通过监听和促进技术讨论来推进开发周期 -* 制定发布日历并执行发布所需的操作 -* 批准对主分支和其他受保护分支的请求 -* 将请求合并到主分支和其他受保护的分支 +* 通过监听和促进技术讨论来推进开发周期 +* 制定发布日历并执行发布所需的操作 +* 批准对主分支和其他受保护分支的请求 +* 将请求合并到主分支和其他受保护的分支 -发布经理 **无权否决或拒绝** 那些 **符合贡献标准并已被社区接受** 的合并请求。他们无权决定开发什么。他们的责任是执行社区的决定,推进项目的进度。 +发布经理 **无权否决或拒绝** 那些 **符合贡献标准并已被社区接受** 的合并请求。 他们无权决定开发什么。 他们的责任是执行社区的决定,推进项目的进度。 -有时,可能需要做出无法通过协商达成共识的决定。在这种情况下,发布经理有权要求 RFC 流程删除决策。这种情况不应经常发生(除非有以下讨论的要求)。不应该鼓励使用这种方法,而是采用更有利于建立社区共识的方法。 +有时,可能需要做出无法通过协商达成共识的决定。 在这种情况下,发布经理有权要求 RFC 流程删除决策。 This should not occur regularly (unless required as discussed below), and its use should be discouraged in favor of the more communal consensus building strategy. -由于并非所有的项目都有相同的要求,所以管理项目发布经理的细节应在本公约的附录或项目贡献指南中规定。 +Since not all projects have the same requirements, the specifics governing release managers on a project shall be set forth in an Appendix to this Policy, or in the project’s contribution guidelines. -如有必要或出于其他正当理由,指导委员会有权撤换失职的发布经理。 +发布管理团队的主要职责: ### 指导委员会(Steering Council) -指导委员会是由被确定为 “项目所有人” 并控制 SCO 资源和资产的个人组成的管理组织。他们的最终目标是通过消除障碍,并根据需要帮助成员,以确保项目的顺利进行。预计他们将成为社区的常客。 +指导委员会是由被确定为 “项目所有人” 并控制 SCO 资源和资产的个人组成的管理组织。 他们的最终目标是通过消除障碍,并根据需要帮助成员,以确保项目的顺利进行。 预计他们将成为社区的常客。 _指导委员会能做什么?_ -指导委员会的成员 **没有** 比其他任何核心开发者更多的权力,也没有任何额外的权利对项目进行决策、提交、合并等。 +由于并非所有的项目都有相同的要求,所以管理项目发布经理的细节应在本公约的附录或项目贡献指南中规定。 -但是,指导委员会具有以下职能: +如有必要或出于其他正当理由,指导委员会有权撤换失职的发布经理。 -* 同意、搁置和拒绝所有的 RFC 建议 -* 强制执行社区行为准则 -* 管理社区资产,如存储库、服务器、论坛、集成服务等(或者将这些权限委托给其他人) -* 在适当的情况下,将核心开发者置于不活跃状态,并采取本政策中规定的任何其他强制措施。包括在极端情况下,移除核心开发者 -* 从社区保护伞下采纳或移除项目 +* 同意、搁置和拒绝所有的 RFC 建议 +* 强制执行社区行为准则 +* 管理社区资产,如存储库、服务器、论坛、集成服务等(或者将这些权限委托给其他人) +* 在适当的情况下,将核心开发者置于不活跃状态,并采取本政策中规定的任何其他强制措施。 +* 从社区保护伞下采纳或移除项目 我们强烈建议指导委员会尽可能将其权力授予其他有意愿的社区成员。 @@ -165,31 +162,39 @@ _指导委员会能做什么?_ _指导委员会有多少成员?_ +但是,指导委员会具有以下职能: + +虽然只拥有 4 票的委员会可能会陷入僵局,无法打破多数票,但指导委员应该尽可能少地投票。 相反,它应该努力以协商一致的方式工作。 在有必要就一个问题进行表决时,需要三票同意。 + +_指导委员是如何选出的?_ + +单个任期应为两年,从 1 月开始。 任期应错开,以便于每年有两名成员从上一年的理事会延续下来。 + 4 个。 -虽然只拥有 4 票的委员会可能会陷入僵局,无法打破多数票,但指导委员应该尽可能少地投票。相反,它应该努力以协商一致的方式工作。在有必要就一个问题进行表决时,需要三票同意。 +There are no limits to the number of terms that can be served, and it is possible for an individual to serve consecutive terms. _指导委员的任期是多久?_ -单个任期应为两年,从 1 月开始。任期应错开,以便于每年有两名成员从上一年的理事会延续下来。 - -因此,选举投票将有两个任期为两年的职位,两个任期为一年的职位。 +指导委员会选出后,小组应集体决策由一人担任主席。 主席的权力和其他委员完全相同,没有任何额外的权力。 -可以任职的任期没有限制,个人有可能连续任职。 +主席的作用仅仅是作为协调者和推动者。 主席应确保遵守所有治理规则。 该职位更多的是行政和文书工作,预计主席将制定议程并协调小组讨论。 _指导委员会如何运作?_ -指导委员会选出后,小组应集体决策由一人担任主席。主席的权力和其他委员完全相同,没有任何额外的权力。 +指导委员会的成员 **没有** 比其他任何核心开发者更多的权力,也没有任何额外的权利对项目进行决策、提交、合并等。 -主席的作用仅仅是作为协调者和推动者。主席应确保遵守所有治理规则。该职位更多的是行政和文书工作,预计主席将制定议程并协调小组讨论。 +选举提名从 9 月 1 日开始,9 月 30 日截止。 投票于 10 月 1 日开始,10 月 31 日结束。 每个在 6 月份发布该年度版本时活跃的核心开发者都能够参与选举和投票,每一位核心开发者都能获得相当于空缺的指导委员数量的选票。 为了公平起见,核心开发者 **不需要** 是 Sanic Framework 的核心开发者,而是在各自的项目中处于活跃状态的核心开发者。 -_指导委员是如何选出的?_ +获得票数最多的核心开发者将被宣布成为指导委员会委员。 如果有任何平票,强烈建议平票的被提名者自己解决争议,然后再随机做出决定。 + +因此,选举投票将有两个任期为两年的职位,两个任期为一年的职位。 每年一次,**所有核心开发者** 都有权选举指导委员会委员。 -选举提名从 9 月 1 日开始,9 月 30 日截止。投票于 10 月 1 日开始,10 月 31 日结束。每个在 6 月份发布该年度版本时活跃的核心开发者都能够参与选举和投票,每一位核心开发者都能获得相当于空缺的指导委员数量的选票。为了公平起见,核心开发者 **不需要** 是 Sanic Framework 的核心开发者,而是在各自的项目中处于活跃状态的核心开发者。 +_What if there is a vacancy?_ -获得票数最多的核心开发者将被宣布成为指导委员会委员。如果有任何平票,强烈建议平票的被提名者自己解决争议,然后再随机做出决定。 +如果指导委员会在某个任期内存在空缺,则应提供上一次选举中得票次高的候选人来完成剩余任期。 如果找不到合适的人选,指导委员会可以决定最合适的人选(通过任命、投票或其他方式)。 关于指导委员会的创始投票,得票最多的两位当选者任期两年,之后两位当选者将获得一年的任期。 @@ -197,39 +202,31 @@ _指导委员是如何选出的?_ _如果空缺怎么办?_ -如果指导委员会在某个任期内存在空缺,则应提供上一次选举中得票次高的候选人来完成剩余任期。如果找不到合适的人选,指导委员会可以决定最合适的人选(通过任命、投票或其他方式)。 - -如果指导委员会的一名成员处于不活跃状态,则该成员应立即从指导委员会中除名,该席位将空缺。 +指导委员会应尽可能公开开展工作和讨论。 社区的任何成员都可以和他们进行对话。 当然,有时私下讨论可能是必要的或适当的。 选择合适的谈话地点是主席管理职责的一部分。 -在极端情况下,所有的核心开发者团队有权以投票的方式表决罢免指导委员会委员(需要超过三分之二的核心开发者赞同) +虽然如何操作的细节超出了本公约的范围,但我们鼓励指导委员会尝试每季度至少召开一次“实时”讨论会议。 这可以通过视频会议、实时聊天或其他适当的方式来实现。 -_指导委员会应如何展开工作?_ +支持(Support) +------- -指导委员会应尽可能公开开展工作和讨论。社区的任何成员都可以和他们进行对话。当然,有时私下讨论可能是必要的或适当的。选择合适的谈话地点是主席管理职责的一部分。 +鼓励社区中的所有参与者在项目管理基础架构中为用户提供技术支持。 这种支持是作为社区发展的一种方式提供的。 寻求支持的人应该意识到,项目中的所有技术支持都是自愿的,是在时间允许的情况下提供的。 因此,要求保证响应时间或结果的用户应该寻求从社区成员处购买支持。 当然,对于那些愿意以自己的方式参与项目,并愿意帮助支持其他用户的人来说,社区支持渠道是最理想的。 -虽然如何操作的细节超出了本公约的范围,但我们鼓励指导委员会尝试每季度至少召开一次“实时”讨论会议。这可以通过视频会议、实时聊天或其他适当的方式来实现。 +决策过程(Decision making process) +----------------------- -## 支持(Support) +从最新用户到最有经验的成员,通过与社区所有成员的讨论来决定项目的未来。 每个人都具有发言权。 -鼓励社区中的所有参与者在项目管理基础架构中为用户提供技术支持。这种支持是作为社区发展的一种方式提供的。寻求支持的人应该意识到,项目中的所有技术支持都是自愿的,是在时间允许的情况下提供的。因此,要求保证响应时间或结果的用户应该寻求从社区成员处购买支持。当然,对于那些愿意以自己的方式参与项目,并愿意帮助支持其他用户的人来说,社区支持渠道是最理想的。 +所有非敏感项目管理讨论都在社区论坛或其他指定渠道进行。 偶尔,敏感的讨论可能会私下进行。 -## 决策过程(Decision making process) - -从最新用户到最有经验的成员,通过与社区所有成员的讨论来决定项目的未来。每个人都具有发言权。 - -所有非敏感项目管理讨论都在社区论坛或其他指定渠道进行。偶尔,敏感的讨论可能会私下进行。 - -为了确保项目不会因无休止的讨论和持续的投票导致停滞不前,该项目采取了 **惰性共识** 政策。这使得大多数决策无需正式投票即可做出。对于任何 **重大决策** (定义如下),都有单独的征求意见(RFC)流程。 +为了确保项目不会因无休止的讨论和持续的投票导致停滞不前,该项目采取了 **惰性共识** 政策。 这使得大多数决策无需正式投票即可做出。 对于任何 **重大决策** (定义如下),都有单独的征求意见(RFC)流程。 ### 技术决策(Technical decisions) 技术决策通常应分为以下几类: -* **常规决策**:文档修复,用于清理或附加测试代码的更改。功能没有变化。 - -* **次要决策**:对代码库的修改,修复一个错误或引入一个无关紧要的特性。没有突破性的变化。 - -* **重要决策**: 对代码库的任何更改都会中断或弃用现有的应用接口,以一种非同寻常的方式改变操作,或者增加一个重要的特性。 +* **常规决策**:文档修复,用于清理或附加测试代码的更改。 功能没有变化。 +* **次要决策**:对代码库的修改,修复一个错误或引入一个无关紧要的特性。 没有突破性的变化。 +* **重要决策**: 对代码库的任何更改都会中断或弃用现有的应用接口,以一种非同寻常的方式改变操作,或者增加一个重要的特性。 发布经理通常有责任确保对存储库的更改在合并之前得到适当的授权。 @@ -239,29 +236,29 @@ _指导委员会应如何展开工作?_ 决策(无论是由社区还是指导委员会)通常包括以下步骤: -* 提议 -* 讨论 -* 投票(如果讨论未达成共识) -* 决策 +* 提议 +* 讨论 +* 投票(如果讨论未达成共识) +* 决策 -任何社区成员都可以提出建议供社区考虑。为了发起关于一个新想法的讨论,他们应该在社区论坛的适当渠道上发布一条消息,或者在 GitHub 上提交一个实现该想法的请求。这将促进对这一想法进行审查,并在必要时进行讨论。 +任何社区成员都可以提出建议供社区考虑。 为了发起关于一个新想法的讨论,他们应该在社区论坛的适当渠道上发布一条消息,或者在 GitHub 上提交一个实现该想法的请求。 这将促进对这一想法进行审查,并在必要时进行讨论。 -本次评审和讨论的目的是获得对贡献的认可。由于项目社区中的大多数人都有一个共同的愿景,因此通常很少需要讨论来达成共识。 +本次评审和讨论的目的是获得对贡献的认可。 由于项目社区中的大多数人都有一个共同的愿景,因此通常很少需要讨论来达成共识。 -一般来说,只要没有人明确反对一个提案或补丁,它就被认为得到了社区的支持。这叫惰性共识;也就是说,那些没有明确表示意见的人已经含蓄地同意了执行这项建议。 +一般来说,只要没有人明确反对一个提案或补丁,它就被认为得到了社区的支持。 这叫惰性共识;也就是说,那些没有明确表示意见的人已经含蓄地同意了执行这项建议。 -惰性共识是 SCO 内一个非常重要的概念。正是这一过程使一大群人能够有效地达成共识,因为对一项提议没有异议的人不需要花时间阐述他们的立场,其他人也不需要花时间阅读这些信息。 +惰性共识是 SCO 内一个非常重要的概念。 正是这一过程使一大群人能够有效地达成共识,因为对一项提议没有异议的人不需要花时间阐述他们的立场,其他人也不需要花时间阅读这些信息。 -为了使惰性共识有效,有必要在假设没有人反对该提案之前留出适当的时间。这在一定程度上取决于具体情况,但一般认为 72 小时是合理的。这一要求确保每个人都有足够的时间阅读、消化和回应提案。选择该时间段是为了尽可能包容所有参与者,无论他们的位置和时间如何。讨论主持人(无论是主席还是发布经理)应负责确定达成此类共识的适当时间长度。 +为了使惰性共识有效,有必要在假设没有人反对该提案之前留出适当的时间。 这在一定程度上取决于具体情况,但一般认为 72 小时是合理的。 这一要求确保每个人都有足够的时间阅读、消化和回应提案。 选择该时间段是为了尽可能包容所有参与者,无论他们的位置和时间如何。 讨论主持人(无论是主席还是发布经理)应负责确定达成此类共识的适当时间长度。 -如上所述,关于所谓的常规决策,发布经理有权在较短的时间内做出决策。在这种情况下,应暗示惰性共识。 +如上所述,关于所谓的常规决策,发布经理有权在较短的时间内做出决策。 在这种情况下,应暗示惰性共识。 ### 意见征求(Request for Comment)(RFC) -指导委员会应负责监督意见征求的过程。这将是一个向社区所有成员开放的辩论过程,并应留出充足的时间来审议一项提案,让成员做出回应并参与有意义的讨论。 +指导委员会应负责监督意见征求的过程。 这将是一个向社区所有成员开放的辩论过程,并应留出充足的时间来审议一项提案,让成员做出回应并参与有意义的讨论。 -最终决定权属于指导委员会。但是,强烈建议指导委员会不要通过与社区中可能存在的任何共识相悖的决定。有时可能会发生共识与整体项目和社区目标之间存在冲突的情况。 +最终决定权属于指导委员会。 但是,强烈建议指导委员会不要通过与社区中可能存在的任何共识相悖的决定。 有时可能会发生共识与整体项目和社区目标之间存在冲突的情况。 -应该按照指导委员会的规定,以公开的方式向指导委员会提交建议书,从而启动征求建议书。辩论应继续进行,并由指导委员会,特别是主席提供便利。 +应该按照指导委员会的规定,以公开的方式向指导委员会提交建议书,从而启动征求建议书。 辩论应继续进行,并由指导委员会,特别是主席提供便利。 在指导委员会认为合适的情况下,可以放弃征求建议书程序,达成惰性共识。 diff --git a/src/zh/plugins/sanic-ext/configuration.md b/src/zh/plugins/sanic-ext/configuration.md index a025a48b1b..fe4600d412 100644 --- a/src/zh/plugins/sanic-ext/configuration.md +++ b/src/zh/plugins/sanic-ext/configuration.md @@ -1 +1,243 @@ # Configuration + +Sanic Extensions can be configured in all of the same ways that [you can configure Sanic](../../guide/deployment/configuration.md). That makes configuring Sanic Extensions very easy. + +```python +app = Sanic("MyApp") +app.config.OAS_URL_PREFIX = "/apidocs" +``` + +However, there are a few more configuration options that should be considered. + +## Manual `extend` + +---:1 Even though Sanic Extensions will automatically attach to your application, you can manually choose `extend`. When you do that, you can pass all of the configuration values as a keyword arguments (lowercase). :--: +```python +app = Sanic("MyApp") +app.extend(oas_url_prefix="/apidocs") +``` +:--- + +---:1 Or, alternatively they could be passed all at once as a single `dict`. :--: +```python +app = Sanic("MyApp") +app.extend(config={"oas_url_prefix": "/apidocs"}) +``` +:--- + +---:1 Both of these solutions suffers from the fact that the names of the configuration settings are not discoverable by an IDE. Therefore, there is also a type annotated object that you can use. This should help the development experience. :--: +```python +from sanic_ext import Config + +app = Sanic("MyApp") +app.extend(config=Config(oas_url_prefix="/apidocs")) +``` +:--- + +## Settings + +### `cors` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to enable CORS protection + +### `cors_allow_headers` + +- **Type**: `str` +- **Default**: `"*"` +- **Description**: Value of the header: `access-control-allow-headers` + +### `cors_always_send` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to always send the header: `access-control-allow-origin` + +### `cors_automatic_options` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to automatically generate `OPTIONS` endpoints for routes that do *not* already have one defined + +### `cors_expose_headers` + +- **Type**: `str` +- **Default**: `""` +- **Description**: Value of the header: `access-control-expose-headers` + +### `cors_max_age` + +- **Type**: `int` +- **Default**: `5` +- **Description**: Value of the header: `access-control-max-age` + +### `cors_methods` + +- **Type**: `str` +- **Default**: `""` +- **Description**: Value of the header: `access-control-access-control-allow-methods` + +### `cors_origins` + +- **Type**: `str` +- **Default**: `""` +- **Description**: Value of the header: `access-control-allow-origin` + +::: warning Be very careful if you place `*` here. Do not do this unless you know what you are doing as it can be a security issue. ::: + +### `cors_send_wildcard` + +- **Type**: `bool` +- **Default**: `False` +- **Description**: Whether to send a wildcard origin instead of the incoming request origin + +### `cors_supports_credentials` + +- **Type**: `bool` +- **Default**: `False` +- **Description**: Value of the header: `access-control-allow-credentials` + +### `cors_vary_header` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to add the `vary` header + +### `http_all_methods` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Adds the HTTP `CONNECT` and `TRACE` methods as allowable + +### `http_auto_head` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Automatically adds `HEAD` handlers to any `GET` routes + +### `http_auto_options` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Automatically adds `OPTIONS` handlers to any routes without + +### `http_auto_trace` + +- **Type**: `bool` +- **Default**: `False` +- **Description**: Automatically adds `TRACE` handlers to any routes without + +### `oas` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to enable OpenAPI specification generation + +### `oas_autodoc` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to automatically extract OpenAPI details from the docstring of a route function + +### `oas_ignore_head` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: WHen `True`, it will not add `HEAD` endpoints into the OpenAPI specification + +### `oas_ignore_options` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: WHen `True`, it will not add `OPTIONS` endpoints into the OpenAPI specification + +### `oas_path_to_redoc_html` + +- **Type**: `Optional[str]` +- **Default**: `None` +- **Description**: Path to HTML file to override the existing Redoc HTML + +### `oas_path_to_swagger_html` + +- **Type**: `Optional[str]` +- **Default**: `None` +- **Description**: Path to HTML file to override the existing Swagger HTML + +### `oas_ui_default` + +- **Type**: `Optional[str]` +- **Default**: `"redoc"` +- **Description**: Which OAS documentation to serve on the bare `oas_url_prefix` endpoint; when `None` there will be no documentation at that location + +### `oas_ui_redoc` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to enable the Redoc UI + +### `oas_ui_swagger` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to enable the Swagger UI + +### `oas_ui_swagger_version` + +- **Type**: `str` +- **Default**: `"4.1.0"` +- **Description**: Which Swagger version to use + +### `oas_uri_to_config` + +- **Type**: `str` +- **Default**: `"/swagger-config"` +- **Description**: Path to serve the Swagger configurtaion + +### `oas_uri_to_json` + +- **Type**: `str` +- **Default**: `"/openapi.json"` +- **Description**: Path to serve the OpenAPI JSON + +### `oas_uri_to_redoc` + +- **Type**: `str` +- **Default**: `"/redoc"` +- **Description**: Path to Redoc + +### `oas_uri_to_swagger` + +- **Type**: `str` +- **Default**: `"/swagger"` +- **Description**: Path to Swagger + +### `oas_url_prefix` + +- **Type**: `str` +- **Default**: `"/docs"` +- **Description**: URL prefix for the Blueprint that all of the OAS documentation witll attach to + +### `swagger_ui_configuration` + +- **Type**: `Dict[str, Any]` +- **Default**: `{"apisSorter": "alpha", "operationsSorter": "alpha", "docExpansion": "full"}` +- **Description**: The Swagger documentation to be served to the frontend + +### `templating_enable_async` + +- **Type**: `bool` +- **Default**: `True` +- **Description**: Whether to set `enable_async` on the Jinja `Environment` + +### `templating_path_to_templates` + +- **Type**: `Union[str, os.PathLike, Sequence[Union[str, os.PathLike]]]` +- **Default**: `templates` +- **Description**: A single path, or multiple paths to where your template files are located + +### `trace_excluded_headers` + +- **Type**: `Sequence[str]` +- **Default**: `("authorization", "cookie")` +- **Description**: Which headers should be suppresed from responses to `TRACE` requests diff --git a/src/zh/plugins/sanic-ext/convenience.md b/src/zh/plugins/sanic-ext/convenience.md index 4025b5ed9c..7885a0b880 100644 --- a/src/zh/plugins/sanic-ext/convenience.md +++ b/src/zh/plugins/sanic-ext/convenience.md @@ -2,12 +2,7 @@ ## 固定序列化器(Fixed serializer) ----:1 - -通常在开发应用程序的时候,总会有一些路由返回相同类型的响应。在这种情况下,您可以在响应程序上定义一个返回序列化程序,之后您只需要返回响应内容即可。 - -:--:1 - +---:1 Often when developing an application, there will be certain routes that always return the same sort of response. 在这种情况下,您可以在响应程序上定义一个返回序列化程序,之后您只需要返回响应内容即可。 :--:1 ```python from sanic_ext import serializer @@ -19,15 +14,10 @@ async def hello_world(request, name: str): return "hello " * int(name) return f"Hello, {name}" ``` - :--- ----:1 - -`serializer` 装饰器同样支持设置 HTTP 状态码 - -:--:1 +---:1 The `serializer` decorator also can add status codes. :--:1 ```python from sanic_ext import serializer @@ -37,17 +27,11 @@ from sanic_ext import serializer async def create_something(request): ... ``` - :--- ## 自定义序列化器(Custom serializer) ----:1 - -使用 `@serializer` 装饰器,您也可以传递自定义函数,只要它们返回的是一个有效地响应类型(`HTTPResonse`)。 - -:--:1 - +---:1 Using the `@serializer` decorator, you can also pass your own custom functions as long as they also return a valid type (`HTTPResonse`). :--:1 ```python def message(retval, request, action, status): return json( @@ -65,14 +49,9 @@ def message(retval, request, action, status): async def do_action(request, action: str): return "This is a message" ``` - :--- ----:1 - -现在,返回一个字符串应该会得到一个不错的序列化输出。 - -:--:1 +---:1 Now, returning just a string should return a nice serialized output. :--:1 ```python $ curl @@ -85,5 +64,27 @@ POST } ``` +:--- + + +## Request counter + +---:1 Sanic Extensions comes with a subcleass of `Request` that can be setup to automatically keep track of the number of requests processed per worker process. To enable this, you should pass the `CountedRequest` class to your application contructor. :--:1 +```python +from sanic_ext import CountedRequest + +app = Sanic(..., request_class=CountedRequest) +``` +:--- +---:1 You will now have access to the number of requests served during the lifetime of the worker process. :--:1 +```python +@app.get("/") +async def handler(request: CountedRequest): + return json({"count": request.count}) +``` :--- + +If possible, the request count will also be added to the [worker state](../../guide/deployment/manager.md#worker-state). + +![](https://user-images.githubusercontent.com/166269/190922460-43bd2cfc-f81a-443b-b84f-07b6ce475cbf.png) diff --git a/src/zh/plugins/sanic-ext/custom.md b/src/zh/plugins/sanic-ext/custom.md new file mode 100644 index 0000000000..e374052a3d --- /dev/null +++ b/src/zh/plugins/sanic-ext/custom.md @@ -0,0 +1,84 @@ +# Custom extensions + +It is possible to create your own custom extensions. + +Version 22.9 added the `Extend.register` [method](#extension-preregistration). This makes it extremely easy to add custom expensions to an application. + +## Anatomy of an extension + +All extensions must subclass `Extension`. + +### Required + +- `name`: By convention, the name is an all-lowercase string +- `startup`: A method that runs when the extension is added + +### Optional + +- `label`: A method that returns additional information about the extension in the MOTD +- `included`: A method that returns a boolean whether the extension should be enabled or not (could be used for example to check config state) + +### Example + +```python +from sanic import Request, Sanic, json +from sanic_ext import Extend, Extension + +app = Sanic(__name__) +app.config.MONITOR = True + + +class AutoMonitor(Extension): + name = "automonitor" + + def startup(self, bootstrap) -> None: + if self.included(): + self.app.before_server_start(self.ensure_monitor_set) + self.app.on_request(self.monitor) + + @staticmethod + async def monitor(request: Request): + if request.route and request.route.ctx.monitor: + print("....") + + @staticmethod + async def ensure_monitor_set(app: Sanic): + for route in app.router.routes: + if not hasattr(route.ctx, "monitor"): + route.ctx.monitor = False + + def label(self): + has_monitor = [ + route + for route in self.app.router.routes + if getattr(route.ctx, "monitor", None) + ] + return f"{len(has_monitor)} endpoint(s)" + + def included(self): + return self.app.config.MONITOR + + +Extend.register(AutoMonitor) + + +@app.get("/", ctx_monitor=True) +async def handler(request: Request): + return json({"foo": "bar"}) +``` + + +## Extension preregistration + +---:1 `Extend.register` simplifies the addition of custom extensions. :--:1 +```python +from sanic_ext import Extend, Extension + +class MyCustomExtension(Extension): + ... + +Extend.register(MyCustomExtension()) +``` +:--- + +*Added in v22.9* diff --git a/src/zh/plugins/sanic-ext/getting-started.md b/src/zh/plugins/sanic-ext/getting-started.md index f4e3fc77c9..feaf9dd527 100644 --- a/src/zh/plugins/sanic-ext/getting-started.md +++ b/src/zh/plugins/sanic-ext/getting-started.md @@ -1,6 +1,6 @@ # 快速开始(Getting Started) -Sanic Extensions 是一个由 SCO _官方开发和维护的_ 插件。这个项目的主要目标是为 Sanic 添加额外的功能,以帮助开发人员更轻松地进行网络应用程序开发。 +Sanic Extensions 是一个由 SCO _官方开发和维护的_ 插件。 这个项目的主要目标是为 Sanic 添加额外的功能,以帮助开发人员更轻松地进行网络应用程序开发。 ## 特征(Features) @@ -10,6 +10,7 @@ Sanic Extensions 是一个由 SCO _官方开发和维护的_ 插件。这个项 - 允许进行路由参数的注入 - 完美适配 Redoc 和 Swagger - 能够对请求参数和请求体进行验证 +- Auto create `HEAD`, `OPTIONS`, and `TRACE` endpoints ## 最低要求(Minimum requirements) @@ -34,16 +35,9 @@ pip install sanic-ext ---:1 -开箱即用,Sanic 扩展将为您启用一系列功能。 - -::: new v21.12 新特性 - -如果您安装了 Sanic 拓展(v21.12+),您不需要做任何初始化设置,它已经是配置好的状态,随时都可使用。 - -下面这段代码和 [Sanic 快速开始页面](../../guide/getting-started.md) 中的 Hello,World 示例完全一致,没有做任何改动。 - -:--:1 +---:1 To setup Sanic Extensions (v21.12+), you need to do: **nothing**. If it is installed in the environment, it is setup and ready to go. +This code is the Hello, world app in the [Sanic Getting Started page](../../guide/getting-started.md) _without any changes_, but using Sanic Extensions with `sanic-ext` installed in the background. :--:1 ```python from sanic import Sanic from sanic.response import text @@ -57,15 +51,12 @@ async def hello_world(request): :--- ----:1 -**_已弃用的旧配置_** - -在 v21.9 中,最简单的使用方法就是通过 `Extend` 来对其进行一个实例化。 - -如果您回看 [Sanic 快速开始](../../guide/getting-started.md) 页面 中的 Hello,world 示例代码,您将看它们之间唯一的区别是多了两行高亮部分的代码。 +下面这段代码和 [Sanic 快速开始页面](../../guide/getting-started.md) 中的 Hello,World 示例完全一致,没有做任何改动。 :--:1 +如果您回看 [Sanic 快速开始](../../guide/getting-started.md) 页面 中的 Hello,world 示例代码,您将看它们之间唯一的区别是多了两行高亮部分的代码。 :--:1 + ```python{3,6} from sanic import Sanic from sanic.response import text @@ -78,7 +69,6 @@ Extend(app) async def hello_world(request): return text("Hello, world.") ``` - :--- -不管它是如何设置的,您现在应该能够通过 [http://localhost:8000/docs](http://localhost:8000/docs) 看到 OpenAPI 文档,并能够看到一些正在运行的功能。 +在 v21.9 中,最简单的使用方法就是通过 `Extend` 来对其进行一个实例化。 diff --git a/src/zh/plugins/sanic-ext/health-monitor.md b/src/zh/plugins/sanic-ext/health-monitor.md new file mode 100644 index 0000000000..b4e2abd370 --- /dev/null +++ b/src/zh/plugins/sanic-ext/health-monitor.md @@ -0,0 +1,62 @@ +# Health monitor + +The health monitor requires both `sanic>=22.9` and `sanic-ext>=22.9`. + +You can setup Sanic Extensions to monitor the health of your worker processes. This requires that you not be in [single process mode](../../guide/deployment/manager.md#single-process-mode). + +## Setup + +---:1 Out of the box, the health monitor is disabled. You will need to opt-in if you would like to use it. :--:1 +```python +app.config.HEALTH = True +``` +:--- + +## How does it work + +The monitor sets up a new background process that will periodically receive acknowledgements of liveliness from each worker process. If a worker process misses a report too many times, then the monitor will restart that one worker. + +## Diagnostics endpoint + +---:1 The health monitor will also enable a diagnostics endpoint that outputs the [worker state](../../guide/deployment/manager.md#worker-state). By default is id disabled. + +::: warning +The diagnostics endpoint is not secured. If you are deploying it in a production environment, you should take steps to protect it with a proxy server if you are using one. If not, you may want to consider disabling this feature in production since it will leak details about your server state. +::: +:--:1 +``` +$ curl http://localhost:8000/__health__ +{ + 'Sanic-Main': {'pid': 99997}, + 'Sanic-Server-0-0': { + 'server': True, + 'state': 'ACKED', + 'pid': 9999, + 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), + 'starts': 2, + 'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc) + }, + 'Sanic-Reloader-0': { + 'server': False, + 'state': 'STARTED', + 'pid': 99998, + 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), + 'starts': 1 + } +} +``` +:--- + + +## Configuration + +| Key | Type | Default | Description | +| -------------------------- | ------ | --------------- | --------------------------------------------------------------------------- | +| HEALTH | `bool` | `False` | Whether to enable this extension. | +| HEALTH_ENDPOINT | `bool` | `False` | Whether to enable the diagnostics endpoint. | +| HEALTH_MAX_MISSES | `int` | `3` | The number of consecutive misses before a worker process is restarted. | +| HEALTH_MISSED_THRESHHOLD | `int` | `10` | The number of seconds the monitor checks for worker process health. | +| HEALTH_MONITOR | `bool` | `True` | Whether to enable the health monitor. | +| HEALTH_REPORT_INTERVAL | `int` | `5` | The number of seconds between reporting each acknowledgement of liveliness. | +| HEALTH_URI_TO_INFO | `str` | `""` | The URI path of the diagnostics endpoint. | +| HEALTH_URL_PREFIX | `str` | `"/__health__"` | The URI prefix of the diagnostics blueprint. | diff --git a/src/zh/plugins/sanic-ext/http/cors.md b/src/zh/plugins/sanic-ext/http/cors.md index b0662f92ee..3d5affdf19 100644 --- a/src/zh/plugins/sanic-ext/http/cors.md +++ b/src/zh/plugins/sanic-ext/http/cors.md @@ -1,24 +1,20 @@ # 跨域保护(CORS protection) -跨域资源共享(aka CORS)本身就是一个 *巨大* 的话题,这里的文档再多也不为过,至于它究竟是什么,建议您自行进行一些研究,以了解其存在的安全问题以及背后的理论解决方案。 -[MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) 是很好的入门文章。 +跨域资源共享(aka CORS)本身就是一个 *巨大* 的话题,这里的文档再多也不为过,至于它究竟是什么,建议您自行进行一些研究,以了解其存在的安全问题以及背后的理论解决方案。 The documentation here cannot go into enough detail about *what* it is. You are highly encouraged to do some research on your own to understand the security problem presented by it, and the theory behind the solutions. [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) 是很好的入门文章。 -简而言之,CORS保护是一个框架,浏览器使用它来访问来自其他域的信息的时候能够有效地提升访问效率和缩短访问时间。它与任何构建单页应用程序的人都息息相关。 尤其是在您的前端页面位于类似 `https://portal.myapp.com` -但它需要访问位于 `https://api.myapp.com` 的后端的时候。 +简而言之,CORS保护是一个框架,浏览器使用它来访问来自其他域的信息的时候能够有效地提升访问效率和缩短访问时间。 它与任何构建单页应用程序的人都息息相关。 尤其是在您的前端页面位于类似 `https://portal.myapp.com` 但它需要访问位于 `https://api.myapp.com` 的后端的时候。 -这一部分功能的实现深受 [sanic-cors](https://github.com/ashleysommer/sanic-cors) -的启发,它基于 [flask-cors](https://github.com/corydolphin/flask-cors) 因此,您几乎可以直接使用 `sanic-ext` 替换 `sanic-cors`。 +这一部分功能的实现深受 [sanic-cors](https://github.com/ashleysommer/sanic-cors) 的启发,它基于 [flask-cors](https://github.com/corydolphin/flask-cors) 因此,您几乎可以直接使用 `sanic-ext` 替换 `sanic-cors`。 It is therefore very likely that you can achieve a near drop-in replacement of `sanic-cors` with `sanic-ext`. ## 基本实现(Basic implementation) ---:1 -就如 [自动创建响应程序示例](methods.md#options) 中所示例的那样,Sanic 拓展将会自动地启用跨域保护而不需要太多的操作,但是它也没有提供太多开箱即用的方法。 +就如 [自动创建响应程序示例](methods.md#options) 中所示例的那样,Sanic 拓展将会自动地启用跨域保护而不需要太多的操作,但是它也没有提供太多开箱即用的方法。 But, it does not offer too much out of the box. 强烈建议您设置 `config.CORS_ORIGINS` 至少将需要访问的应用程序的域名添加到其中。 :--:1 - ```python from sanic import Sanic, text from sanic_ext import Extend @@ -33,40 +29,39 @@ async def hello_world(request): return text("Hello, world.") ``` -```text +``` $ curl localhost:8000 -X OPTIONS -i HTTP/1.1 204 No Content allow: GET,HEAD,OPTIONS access-control-allow-origin: http://foobar.com connection: keep-alive ``` - :--- ## 配置(Configuration) -只有您合理地配置跨域保护,它才能发挥出真正的力量。下面是一张配置选项清单: +The true power of CORS protection, however, comes into play once you start configuring it. Here is a table of all of the options. -| 配置名称 | 数据类型 | 默认值 | 说明 -|:----------------------------|:---------------------------------|:-------|:------------------------------------------------------------- -| `CORS_ALLOW_HEADERS` | `str`, `List[str]` | `"*"` | 被添加的值会在 `access-control-allow-headers` 中的请求头列表中出现 -| `CORS_ALWAYS_SEND` | `bool` | `True` | 当 `True` 时,将总是为 `access-control-allow-headers` 设置一个值。当 `False` 时,仅当有 `Origin` 请求头时才会设置值。 -| `CORS_AUTOMATIC_OPTIONS` | `bool` | `True` | 当收到传入的预检请求时,是否自动设置 `access-control-allow-headers`、`access-control-max-age` 和 `access-control-allow-methods` 请求头的值。如果 `False` 这些值将只应用在使用了 `@cors` 装饰器装饰的路由上。 -| `CORS_EXPOSE_HEADERS` | `str`, `List[str]` | `""` | 在`access-control-expose-headers` 请求头中设置的特定请求头列表。 -| `CORS_MAX_AGE` | `str`, `int`, `timedelta` | `0` | 使用 `access-control-max-age` 请求头可以缓存预检响应的最大秒数。 一个错误值将导致不设置请求头。 -| `CORS_METHODS` | `str`, `List[str]` | `""` | 允许的来源可以使用的 HTTP 访问方法,将设置在 `access-control-allow-methods` 请求头中。 -| `CORS_ORIGINS` | `str`, `List[str]`, `re.Pattern` | `"*"` | 允许访问资源的来源,将设置在 `access-control-allow-origin` 请求头中。 -| `CORS_SEND_WILDCARD` | `bool` | `False`| 如果为 `True`,将发送通配符 `*` 源而不是 `Origin` 请求头。 -| `CORS_SUPPORTS_CREDENTIALS` | `bool` | `False`| 是否设置 `access-control-allow-credentials` 请求头。 -| `CORS_VARY_HEADER` | `bool` | `True` | 是否在适当的时候添加 `vary` 请求头。 +| 配置名称 | 数据类型 | 默认值 | 说明 | +| --------------------------- | -------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `CORS_ALLOW_HEADERS` | `str`, `List[str]` | `"*"` | 被添加的值会在 `access-control-allow-headers` 中的请求头列表中出现 | +| `CORS_ALWAYS_SEND` | `bool` | `True` | 当 `True` 时,将总是为 `access-control-allow-headers` 设置一个值。 当 `False` 时,仅当有 `Origin` 请求头时才会设置值。 | +| `CORS_AUTOMATIC_OPTIONS` | `bool` | `True` | 当收到传入的预检请求时,是否自动设置 `access-control-allow-headers`、`access-control-max-age` 和 `access-control-allow-methods` 请求头的值。 如果 `False` 这些值将只应用在使用了 `@cors` 装饰器装饰的路由上。 | +| `CORS_EXPOSE_HEADERS` | `str`, `List[str]` | `""` | 在`access-control-expose-headers` 请求头中设置的特定请求头列表。 | +| `CORS_MAX_AGE` | `str`, `int`, `timedelta` | `0` | 使用 `access-control-max-age` 请求头可以缓存预检响应的最大秒数。 一个错误值将导致不设置请求头。 | +| `CORS_METHODS` | `str`, `List[str]` | `""` | 允许的来源可以使用的 HTTP 访问方法,将设置在 `access-control-allow-methods` 请求头中。 | +| `CORS_ORIGINS` | `str`, `List[str]`, `re.Pattern` | `"*"` | 允许访问资源的来源,将设置在 `access-control-allow-origin` 请求头中。 | +| `CORS_SEND_WILDCARD` | `bool` | `False` | 如果为 `True`,将发送通配符 `*` 源而不是 `Origin` 请求头。 | +| `CORS_SUPPORTS_CREDENTIALS` | `bool` | `False` | 是否设置 `access-control-allow-credentials` 请求头。 | +| `CORS_VARY_HEADER` | `bool` | `True` | 是否在适当的时候添加 `vary` 请求头。 | -*为了提高易用性,上面提到的 `List[str]` 任何一个 `list`、`set`、`frozenset` 或 `tuple` 的实例都将可用。或者,如果值是一个 `str`,它可以是一个逗号分隔的列表。* +*为了提高易用性,上面提到的 `List[str]` 任何一个 `list`、`set`、`frozenset` 或 `tuple` 的实例都将可用。 或者,如果值是一个 `str`,它可以是一个逗号分隔的列表。* ## 路由级别覆盖(Route level overrides) ---:1 -有些时候我们需要对特定的路由进行跨域保护设置,为此,您可以使用 `@sanic_ext.cors()` 装饰器来为不同的路由设置不同的跨域保护配置。 +It may sometimes be necessary to override app-wide settings for a specific route. 有些时候我们需要对特定的路由进行跨域保护设置,为此,您可以使用 `@sanic_ext.cors()` 装饰器来为不同的路由设置不同的跨域保护配置。 可以被此装饰器设置的值包括: @@ -78,7 +73,6 @@ connection: keep-alive - `max_age` :--:1 - ```python from sanic_ext import cors @@ -90,5 +84,4 @@ app.config.CORS_ORIGINS = "https://foo.com" async def hello_world(request): return text("Hello, world.") ``` - :--- diff --git a/src/zh/plugins/sanic-ext/http/methods.md b/src/zh/plugins/sanic-ext/http/methods.md index 8249f88c05..ac1eea326c 100644 --- a/src/zh/plugins/sanic-ext/http/methods.md +++ b/src/zh/plugins/sanic-ext/http/methods.md @@ -21,7 +21,7 @@ async def hello_world(request): 给定上述路由定义,Sanic 拓展将启用 `HEAD` 响应,如下所示。 -```text +``` $ curl localhost:8000 --head HTTP/1.1 200 OK access-control-allow-origin: * @@ -47,9 +47,9 @@ async def hello_world(request): 给定上述路由定义,Sanic 拓展将启用 `OPTIONS` 响应,如下所示。 -另外,值得注意的是,在这个例子中,我们还看到了 `access-control-allow-origins` 请求头。这是因为默认情况下,[CORS保护](cors.md) 是启用状态的。 +另外,值得注意的是,在这个例子中,我们还看到了 `access-control-allow-origins` 请求头。 这是因为默认情况下,[CORS保护](cors.md) 是启用状态的。 -```text +``` $ curl localhost:8000 -X OPTIONS -i HTTP/1.1 204 No Content allow: GET,HEAD,OPTIONS @@ -57,18 +57,14 @@ access-control-allow-origin: * connection: keep-alive ``` -::: tip 小提示 +::: tip Even though Sanic Extensions will setup these routes for you automatically, if you decide to manually create an `@app.options` route, it will *not* be overridden. ::: Sanic 拓展将会自动为您创建 `OPTIONS` 路由, 即便您选择手动创建一个 `@app.options` 的路由,它也不会被覆盖掉。 -::: - -::: tab TRACE - - **配置参数**: `AUTO_TRACE` (默认值 `False`) - **MDN**: [Read more](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/TRACE) -默认情况下,`TRACE` 响应函数是 **不会** 被自动创建的,当然,如果您需要,Sanic 拓展允许为您自动创建,这在普通的 Sanic 应用程序中是禁止的。 +By default, `TRACE` endpoints will **not** be automatically created. However, Sanic Extensions **will allow** you to create them if you wanted. This is something that is not allowed in vanilla Sanic. ```python @app.route("/", methods=["trace"]) @@ -76,7 +72,7 @@ async def handler(request): ... ``` -想要允许 Sanic 拓展为您自动创建 TRACE 响应函数,您必须在拓展 Sanic 的时候启用它们: +::: tab TRACE ```python from sanic_ext import Extend, Config @@ -84,9 +80,9 @@ from sanic_ext import Extend, Config Extend(app, config=Config(auto_trace=True)) ``` -现在,假设您有一些响应函数设置,您可以跟踪它们,如下所示: +默认情况下,`TRACE` 响应函数是 **不会** 被自动创建的,当然,如果您需要,Sanic 拓展允许为您自动创建,这在普通的 Sanic 应用程序中是禁止的。 -```text +``` $ curl localhost:8000 -X TRACE TRACE / HTTP/1.1 Host: localhost:9999 @@ -94,33 +90,29 @@ User-Agent: curl/7.76.1 Accept: */* ``` -::: tip 小提示 - -设置 `AUTO_TRACE` 是非常有帮助的,尤其是当您的应用程序部署在代理后面时,因为它会帮助您确定代理的行为。 - -::: +::: tip Setting up `AUTO_TRACE` can be super helpful, especially when your application is deployed behind a proxy since it will help you determine how the proxy is behaving. ::: :::: ## 额外方法的支持(Additional method support) -普通的 Sanic 应用程序允许您使用以下 HTTP 方法构建响应程序: +::: tip 小提示 -- [GET](/zh/guide/basics/routing.html#get) -- [POST](/zh/guide/basics/routing.html#post) -- [PUT](/zh/guide/basics/routing.html#put) -- [HEAD](/zh/guide/basics/routing.html#head) -- [OPTIONS](/zh/guide/basics/routing.html#options) -- [PATCH](/zh/guide/basics/routing.html#patch) -- [DELETE](/zh/guide/basics/routing.html#delete) +- [GET](/en/guide/basics/routing.html#get) +- [POST](/en/guide/basics/routing.html#post) +- [PUT](/en/guide/basics/routing.html#put) +- [HEAD](/en/guide/basics/routing.html#head) +- [OPTIONS](/en/guide/basics/routing.html#options) +- [PATCH](/en/guide/basics/routing.html#patch) +- [DELETE](/en/guide/basics/routing.html#delete) -查看 [MDN Web Docs](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods) 以了解更多。 +设置 `AUTO_TRACE` 是非常有帮助的,尤其是当您的应用程序部署在代理后面时,因为它会帮助您确定代理的行为。 ---:1 -然而,还有两种 “标准” HTTP方法:`TRACE` 和 `CONNECT`。Sanic 扩展将允许您构建使用这些方法的响应函数,这在普通的 Sanic 应用程序上是禁止的。 +然而,还有两种 “标准” HTTP方法:`TRACE` 和 `CONNECT`。 Sanic 扩展将允许您构建使用这些方法的响应函数,这在普通的 Sanic 应用程序上是禁止的。 -需要指出的是,Sanic 拓展并 **不能** 允许您直接使用 `@app.trace` 或 `@app.connect` 来创建使用对应 HTTP 方法的响应程序,您需要使用 `@app.route` 来进行创建,如 👉 所示: +It is worth pointing out that this will *NOT* enable convenience methods: `@app.trace` or `@app.connect`. You need to use `@app.route` as shown in the example here. :--:1 diff --git a/src/zh/plugins/sanic-ext/injection.md b/src/zh/plugins/sanic-ext/injection.md index a516173ca4..0f676cadbd 100644 --- a/src/zh/plugins/sanic-ext/injection.md +++ b/src/zh/plugins/sanic-ext/injection.md @@ -1,29 +1,33 @@ # 注入(Injection) -依赖注入是一种根据定义的函数签名向响应程序中添加参数的方法。 这在许多情况下很有用,例如: +依赖注入是一种根据定义的函数签名向响应程序中添加参数的方法。 Specifically, it looks at the **type annotations** of the arguments in the handler. 这在许多情况下很有用,例如: - 基于请求头获取对象(如当前 Session 用户) - 将某些对象重构为指定的格式 - 通过 request 对象对数据进行预处理 - 自动注入服务 -当您 `扩展` 应用程序时,响应程序上会挂载一个 `注入` 方法。该方法接受以下参数: +The `Extend` instance has two basic methods on it used for dependency injection: a lower level `add_dependency`, and a higher level `dependency`. + +让我们通过一些例子来实际体会一下: - *类*: 一些独特的类,将成为对象的类型 - *构造函数*(可选): 将返回该类型的函数 -让我们通过一些例子来实际体会一下: +最简单的例子就是用它来重写一个值。 -## 基本实现(Basic implementation) +- 使用中间件对 `request.ctx` 进行预处理并添加内容 +- 使用装饰器对请求处理程序进行预处理并注入参数 -最简单的例子就是用它来重写一个值。 +Let's explore some use cases here. ----:1 +::: warning If you used dependency injection prior to v21.12, the lower level API method was called `injection`. It has since been renamed to `add_dependency` and starting in v21.12 `injection` is an alias for `add_dependency`. The `injection` method has been deprecated for removal in v22.6. ::: -如果您想要基于匹配的路径参数生成一个模型,这可能会很有用。 +## 基本实现(Basic implementation) -:--:1 +The simplest use case would be simply to recast a value. +---:1 This could be useful if you have a model that you want to generate based upon the matched path parameters. :--:1 ```python @dataclass class IceCream: @@ -36,6 +40,14 @@ class IceCream: ext.injection(IceCream) +@app.get("/") +async def ice_cream(request, flavor: IceCream): + return text(f"You chose: {flavor}") + + +app.ext.add_dependency(IceCream) + + @app.get("/") async def ice_cream(request, flavor: IceCream): return text(f"You chose: {flavor}") @@ -45,21 +57,9 @@ async def ice_cream(request, flavor: IceCream): $ curl localhost:8000/chocolate You chose Chocolate (Yum!) ``` - :--- -## 附加构造函数(Additional constructors) - ----:1 - -有时您可能还需要传递一个构造函数。这可能是一个函数,甚至可能是一个充当构造函数的类方法。在这个例子中,我们正在创建一个名为 `Person.create` 的注入。 - -同样需要注意的是,在这个例子中,我们实际上是在注入 **两个对象** !当然并不需要这样,但是我们将基于函数签名注入对象。 - -当构造函数没有传递给 `ext.injection` 时,将通过调用该类型来创建对象。 - -:--:1 - +---:1 This works by passing a keyword argument to the constructor of the `type` argument. The previous example is equivalent to this. :--:1 ```python @dataclass class PersonID: @@ -87,38 +87,13 @@ async def person_details( ): return text(f"{person_id}\n{person}") ``` - -``` -$ curl localhost:8000/person/123 -PersonID(person_id=123) -Person(person_id=PersonID(person_id=123), name='noname', age=111) -``` - :--- -## 来自 `Request` 的对象(Objects from the `Request`) - ----:1 - -有时,您可能希望从请求中提取细节并对它们进行预处理。例如,您可以将请求 JSON 转换为 Python 对象,然后基于数据库查询添加一些额外的逻辑。 - -::: warning 注意 - -如果您计划使用这种方法,您应该注意到注入操作实际上是在 Sanic 读取请求体之前发生的。请求头应该已经被处理。因此,如果您确实想要访问请求体,您将需要手动消费,如本例所示。 - -```python -await request.receive_body() -``` - -::: - -这可以用于以下情况: - -- 使用中间件对 `request.ctx` 进行预处理并添加内容 -- 使用装饰器对请求处理程序进行预处理并注入参数 +## 附加构造函数(Additional constructors) -:--:1 +---:1 Sometimes you may need to also pass a constructor. 这可能是一个函数,甚至可能是一个充当构造函数的类方法。 在这个例子中,我们正在创建一个名为 `Person.create` 的注入。 +同样需要注意的是,在这个例子中,我们实际上是在注入 **两个对象** ! 当然并不需要这样,但是我们将基于函数签名注入对象。 :--:1 ```python @dataclass class UserProfile: @@ -162,6 +137,85 @@ async def update_profile(request, profile: UserProfile): return json(profile) ``` +``` +$ curl localhost:8000/person/123 +PersonID(person_id=123) +Person(person_id=PersonID(person_id=123), name='noname', age=111) +``` +:--- + +When a `constructor` is passed to `ext.add_dependency` (like in this example) that will be called. 当构造函数没有传递给 `ext.injection` 时,将通过调用该类型来创建对象。 A couple of important things to note about passing a `constructor`: + +1. A positional `request: Request` argument is *usually* expected. See the `Person.create` method above as an example using a `request` and [arbitrary constructors](#arbitrary-constructors) for how to use a callable that does not require a `request`. +1. All matched path parameters are injected as keyword arguments. +1. Dependencies can be chained and nested. Notice how in the previous example the `Person` dataclass has a `PersonID`? That means that `PersonID` will be called first, and that value is added to the keyword arguments when calling `Person.create`. + +## Arbitrary constructors + +---:1 Sometimes you may want to construct your injectable _without_ the `Request` object. This is useful if you have arbitrary classes or functions that create your objects. If the callable does have any required arguments, then they should themselves be injectable objects. + +This is very useful if you have services or other types of objects that should only exist for the lifetime of a single request. For example, you might use this pattern to pull a single connection from your database pool. :--:1 +```python +class Alpha: + ... + + +class Beta: + def __init__(self, alpha: Alpha) -> None: + self.alpha = alpha + +app.ext.add_dependency(Alpha) +app.ext.add_dependency(Beta) + +@app.get("/beta") +async def handler(request: Request, beta: Beta): + assert isinstance(beta.alpha, Alpha) +``` +:--- + +*Added in v22.9* + +## 来自 `Request` 的对象(Objects from the `Request`) + +---:1 Sometimes you may want to extract details from the request and preprocess them. 例如,您可以将请求 JSON 转换为 Python 对象,然后基于数据库查询添加一些额外的逻辑。 + +如果您计划使用这种方法,您应该注意到注入操作实际上是在 Sanic 读取请求体之前发生的。 请求头应该已经被处理。 因此,如果您确实想要访问请求体,您将需要手动消费,如本例所示。 + +```python +await request.receive_body() +``` +::: + +这可以用于以下情况: + +- use middleware to preprocess and add something to the `request.ctx` +- use decorators to preprocess and inject arguments into the request handler + +In this example, we are using the `Request` object in the `compule_profile` constructor to run a fake DB query to generate and return a `UserProfile` object. :--:1 +```python +class FakeConnection: + async def execute(self, query: str, **arguments): + return "result" + + +@app.before_server_start +async def setup_db(app, _): + app.ctx.db_conn = FakeConnection() + + +def get_db(request: Request): + return request.app.ctx.db_conn + + +ext.injection(FakeConnection, get_db) + + +@app.get("/") +async def handler(request, conn: FakeConnection): + response = await conn.execute("...") + return text(response) +``` + ``` $ curl localhost:8000/profile -X PATCH -d '{"name": "Alice", "birthday": "2000-01-01"}' { @@ -170,19 +224,16 @@ $ curl localhost:8000/profile -X PATCH -d '{"name": "Alice", "birthday": "2000-0 "email":"alice@something.com" } ``` - :--- ## 注入服务(Injecting services) ----:1 - -创建数据库连接池之类的对象并将它们存储在 `app.ctx` 对象上是一种常见的模式。这使得它们可以在整个应用程序中使用,这是一种非常方便的做法。 - -但是这样做的缺点是您将不再拥有一个类型化的对象可以使用。您可以通过注入来解决这个问题。 +创建数据库连接池之类的对象并将它们存储在 `app.ctx` 对象上是一种常见的模式。 这使得它们可以在整个应用程序中使用,这是一种非常方便的做法。 但是这样做的缺点是您将不再拥有一个类型化的对象可以使用。 You can use dependency injections to fix this. First we will show the concept using the lower level `add_dependency` like we have been using in the previous examples. But, there is a better way using the higher level `dependency` method. -:--:1 +---:1 +### The lower level API using `add_dependency` +This works very similar to the [last example](#objects-from-the-request) where the goal is the extract something from the `Request` object. In this example, a database object was created on the `app.ctx` instance, and is being returned in the dependency injection constructor. :--:1 ```python class FakeConnection: async def execute(self, query: str, **arguments): @@ -192,13 +243,13 @@ class FakeConnection: @app.before_server_start async def setup_db(app, _): app.ctx.db_conn = FakeConnection() + app.ext.add_dependency(FakeConnection, get_db) def get_db(request: Request): return request.app.ctx.db_conn -ext.injection(FakeConnection, get_db) @app.get("/") @@ -206,10 +257,96 @@ async def handler(request, conn: FakeConnection): response = await conn.execute("...") return text(response) ``` +``` +$ curl localhost:8000/ +result +``` +:--- + +---:1 +### The higher level API using `dependency` + +Since we have an actual *object* that is available when adding the dependency injection, we can use the higher level `dependency` method. This will make the pattern much easier to write. + +This method should always be used when you want to inject something that exists throughout the lifetime of the application instance and is not request specific. It is very useful for services, third party clients, and connection pools since they are not request specific. :--:1 +```python +class FakeConnection: + async def execute(self, query: str, **arguments): + return "result" + + +@app.before_server_start +async def setup_db(app, _): + db_conn = FakeConnection() + app.ext.dependency(db_conn) + +@app.get("/") +async def handler(request, conn: FakeConnection): + response = await conn.execute("...") + return text(response) +``` ``` $ curl localhost:8000/ result ``` +:--- + +## Generic types + +Be carefule when using a [generic type](https://docs.python.org/3/library/typing.html#typing.Generic). The way that Sanic's dependency injection works is by matching the entire type definition. Therefore, `Foo` is not the same as `Foo[str]`. This can be particularly tricky when trying to use the [higher-level `dependency` method](#the-higher-level-api-using-dependency) since the type is inferred. + +---:1 For example, this will **NOT** work as expected since there is no definition for `Test[str]`. :--:1 +```python{12,16} +import typing +from sanic import Sanic, text + +T = typing.TypeVar("T") + + +class Test(typing.Generic[T]): + test: T + + +app = Sanic("testapp") +app.ext.dependency(Test()) + +@app.get("/") +def test(request, test: Test[str]): + ... +``` +:--- + +---:1 To get this example to work, you will need to add an explicit definition for the type you intend to be injected. :--:1 +```python{13} +import typing +from sanic import Sanic, text + +T = typing.TypeVar("T") + + +class Test(typing.Generic[T]): + test: T + + +app = Sanic("testapp") +_singleton = Test() +app.ext.add_dependency(Test[str], lambda: _singleton) + + +@app.get("/") +def test(request, test: Test[str]): + ... +``` +:--- + +## Configuration + +---:1 By default, dependencies will be injected after the `http.routing.after` [signal](../../guide/advanced/signals.md#built-in-signals). Starting in v22.9, you can change this to the `http.handler.before` signal. :--:1 +```python +app.config.INJECTION_SIGNAL = "http.handler.before" +``` :--- + +*Added in v22.9* diff --git a/src/zh/plugins/sanic-ext/logger.md b/src/zh/plugins/sanic-ext/logger.md new file mode 100644 index 0000000000..aa8b18c141 --- /dev/null +++ b/src/zh/plugins/sanic-ext/logger.md @@ -0,0 +1,26 @@ +# Background logger + +The background logger requires both `sanic>=22.9` and `sanic-ext>=22.9`. + +You can setup Sanic Extensions to log all of your messages from a background process. This requires that you not be in [single process mode](../../guide/deployment/manager.md#single-process-mode). + +Logging can sometimes be an expensive operation. By pushing all logging off to a background process, you can potentially gain some performance benefits. + +## Setup + +---:1 Out of the box, the background logger is disabled. You will need to opt-in if you would like to use it. :--:1 +```python +app.config.LOGGING = True +``` +:--- + +## How does it work + +When enabled, the extension will create a `multoprocessing.Queue`. It will remove all handlers on the [default Sanic loggers](../../guide/best-practices/logging.md) and replace them with a [`QueueHandler`](https://docs.python.org/3/library/logging.handlers.html#queuehandler). When a message is logged, it will be pushed into the queue by the handler, and read by the background process to the log handlers that were originally in place. This means you can still configure logging as normal and it should "just work." + +## Configuration + +| Key | Type | Default | Description | +| ------------------------ | ------ | ------- | ------------------------------------------------------- | +| LOGGING | `bool` | `False` | Whether to enable this extension. | +| LOGGING_QUEUE_MAX_SIZE | `int` | `4096` | The max size of the queue before messages are rejected. | diff --git a/src/zh/plugins/sanic-ext/openapi/advanced.md b/src/zh/plugins/sanic-ext/openapi/advanced.md index fb51e72941..46c3c45ec6 100644 --- a/src/zh/plugins/sanic-ext/openapi/advanced.md +++ b/src/zh/plugins/sanic-ext/openapi/advanced.md @@ -1,5 +1,7 @@ # Advanced +_Documentation coming EOQ1 2023_ + ## CBV ## Blueprints diff --git a/src/zh/plugins/sanic-ext/openapi/autodoc.md b/src/zh/plugins/sanic-ext/openapi/autodoc.md index 40d56cbae7..afc40930b8 100644 --- a/src/zh/plugins/sanic-ext/openapi/autodoc.md +++ b/src/zh/plugins/sanic-ext/openapi/autodoc.md @@ -4,12 +4,7 @@ ## 总结和描述(Summary and description) ----:1 - -函数的文档字符串将用于创建摘要和描述。从这个例子中可以看出,文档字符串已经被解析,使用第一行作为摘要,字符串的剩余部分作为描述。 - -:--:1 - +函数的文档字符串将用于创建摘要和描述。 从这个例子中可以看出,文档字符串已经被解析,使用第一行作为摘要,字符串的剩余部分作为描述。 :--:1 ```python @app.get("/foo") async def handler(request, something: str): @@ -22,8 +17,12 @@ async def handler(request, something: str): - two - three""" return text(">>>") -``` + - one + - two + - three""" + return text(">>>") +``` ```json { "paths": { @@ -42,19 +41,13 @@ async def handler(request, something: str): } } ``` - :--- ## YAML 级操作(Operation level YAML) ----:1 - -您可以通过在文档字符串中添加有效的 OpenAPI YAML 来对此进行扩展。只需添加一行包含 `openapi:` 的内容,后跟您的 YAML。 - -示例中显示的 `---` 不是必需的。它只是在那里帮助视觉识别 YAML 作为文档字符串的一个独特部分。 - -:--:1 +您可以通过在文档字符串中添加有效的 OpenAPI YAML 来对此进行扩展。 只需添加一行包含 `openapi:` 的内容,后跟您的 YAML。 +示例中显示的 `---` 不是必需的。 它只是在那里帮助视觉识别 YAML 作为文档字符串的一个独特部分。 :--:1 ```python @app.get("/foo") async def handler(request, something: str): @@ -82,7 +75,6 @@ async def handler(request, something: str): """ return text("...") ``` - ```json { "paths": { @@ -120,31 +112,23 @@ async def handler(request, something: str): :--- -::: tip 小提示 - -当 YAML 文档和装饰器都被使用时,在生成文档时将优先使用装饰器中的内容。 - -::: +当 YAML 文档和装饰器都被使用时,在生成文档时将优先使用装饰器中的内容。 ::: ## 排除文档字符串(Excluding docstrings) ----:1 - 有时,函数的文档字符串中可能包含并不打算展示在文档中的字符串。 -**选项 1**: 在配置文件中设置 `app.config.OAS_AUTODOC = False` 禁用整个项目的文档自动生成功能 - -**选项 2**: 使用 `@openapi.no_autodoc` 装饰器为某个指定的响应程序禁用自动文档生成功能。 - :--:1 +**选项 2**: 使用 `@openapi.no_autodoc` 装饰器为某个指定的响应程序禁用自动文档生成功能。 ```python @app.get("/foo") @openapi.no_autodoc async def handler(request, something: str): """This is a docstring about internal info only. Do not parse it. """ + return text("...") Do not parse it. + """ return text("...") ``` - :--- diff --git a/src/zh/plugins/sanic-ext/openapi/basic.md b/src/zh/plugins/sanic-ext/openapi/basic.md index b1ec9c6272..6c14266ed7 100644 --- a/src/zh/plugins/sanic-ext/openapi/basic.md +++ b/src/zh/plugins/sanic-ext/openapi/basic.md @@ -1,17 +1,10 @@ # 入门(Basics) -::: tip 边注 - -Sanic 拓展中的 OpenAPI 基于 [sanic-openapi](https://github.com/sanic-org/sanic-openapi) 的 OAS3 规范实现。 事实上,Sanic -拓展在很大程度上是该项目的继承者,该项目将在 Sanic 拓展发布后进入维护模式。 如果您之前使用 `sanic-openapi` 的 OAS3 规范,您应该可以轻松升级到 Sanic 扩展。 遗憾的是,该项目 *不* 支持 OAS2 -规范。 - -::: +Sanic 拓展中的 OpenAPI 基于 [sanic-openapi](https://github.com/sanic-org/sanic-openapi) 的 OAS3 规范实现。 事实上,Sanic 拓展在很大程度上是该项目的继承者,该项目将在 Sanic 拓展发布后进入维护模式。 如果您之前使用 `sanic-openapi` 的 OAS3 规范,您应该可以轻松升级到 Sanic 扩展。 遗憾的是,该项目 *不* 支持 OAS2 规范。 ::: ---:1 -开箱即用,Sanic 拓展使用 [v3.0 OpenAPI 规范](https://swag.io/specification/) 提供自动生成的 API 文档。您唯一需要做地就是将 Sanic 扩展实例化来 `扩展` -您的应用程序。 +开箱即用,Sanic 拓展使用 [v3.0 OpenAPI 规范](https://swag.io/specification/) 提供自动生成的 API 文档。 There is nothing special that you need to do :--:1 @@ -27,18 +20,47 @@ Extend(app) 完成此操作后,您将得到一份基于现有应用程序生成的精美文档: -[http://localhost:8000/docs](http://localhost:8000/docs) +- [http://localhost:8000/docs](http://localhost:8000/docs) +- [使用 [Redoc](https://github.com/Redocly/redoc)](http://localhost:8000/docs/redoc) +- [或者 [Swagger UI](https://github.com/swagger-api/swagger-ui)](http://localhost:8000/docs/swagger) + +Checkout the [section on configuration](../configuration.md) to learn about changing the routes for the docs. You can also turn off one of the two UIs, and customize which UI will be available on the `/docs` route. ---:1 -使用 [Redoc](https://github.com/Redocly/redoc) +Using [Redoc](https://github.com/Redocly/redoc) ![Redoc](~@assets/images/sanic-ext-redoc.png) + :--:1 -或者 [Swagger UI](https://github.com/swagger-api/swagger-ui) +or [Swagger UI](https://github.com/swagger-api/swagger-ui) ![Swagger UI](~@assets/images/sanic-ext-swagger.png) + +:--- + +## Changing specification metadata + +---:1 If you want to change any of the metada, you should use the `describe` method. + +In this example `dedent` is being used with the `description` argument to make multi-line strings a little cleaner. This is not necessary, you can pass any string value here. :--:1 +```python +from textwrap import dedent + +app.ext.openapi.describe( + "Testing API", + version="1.2.3", + description=dedent( + """ + # Info + This is a description. It is a good place to add some _extra_ doccumentation. + + **MARKDOWN** is supported. + """ + ), +) +``` :--- diff --git a/src/zh/plugins/sanic-ext/openapi/decorators.md b/src/zh/plugins/sanic-ext/openapi/decorators.md index 01aeb739f5..9151c58bbb 100644 --- a/src/zh/plugins/sanic-ext/openapi/decorators.md +++ b/src/zh/plugins/sanic-ext/openapi/decorators.md @@ -23,7 +23,7 @@ async def handler(request, somethind: str): ---:1 -您还将看到以下许多示例都引用了模型对象。 为简单起见,示例将使用如下所示的 `UserProfile`。 这是一个非常简单的模型类。您可以轻易的将其联想到 `数据类` 或其他的任何类型的模型对象。 +您还将看到以下许多示例都引用了模型对象。 为简单起见,示例将使用如下所示的 `UserProfile`。 这是一个非常简单的模型类。 您可以轻易的将其联想到 `数据类` 或其他的任何类型的模型对象。 :--:1 @@ -44,22 +44,23 @@ class UserProfile: 这些字段特意允许接受多种类型,使您可以最轻松的定义您的文档。 -**参数:** +**Arguments** -| 参数名称 | 参数类型 | -|:--------------|:-------------------------------------------------------------------:| -| `body` | **dict, RequestBody, *YourModel*** | -| `deprecated` | **bool** | -| `description` | **str** | -| `document` | **str, ExternalDocumentation** | -| `exclude` | **bool** | -| `operation` | **str** | -| `parameter` |**dict, Parameter, *YourModel*, [dict], [Parameter], [*YourModel*]** | -| `response` |**dict, Response, *YourModel*, [dict], [Response], [*YourModel*]** | -| `summary` | **str** | -| `tag` | **str, Tag, [str], [Tag]** | +| 参数名称 | 参数类型 | +| ------------- | -------------------------------------------------------------------- | +| `body` | ***dict, RequestBody, ***YourModel****** | +| `deprecated` | **bool** | +| `description` | **str** | +| `document` | **str, ExternalDocumentation** | +| `exclude` | **bool** | +| `operation` | **str** | +| `parameter` | **dict, Parameter, *YourModel*, [dict], [Parameter], [*YourModel*]** | +| `response` | **dict, Response, *YourModel*, [dict], [Response], [*YourModel*]** | +| `summary` | **str** | +| `tag` | **str, Tag, [str], [Tag]** | +| `secured` | **Dict[str, Any]** | -**示例:** +**Examples** ---:1 @@ -86,13 +87,13 @@ class UserProfile: ::: tab body -**参数:** +**Arguments** -| 参数名称 | 参数类型 | -|-------------|------------------------------------| +| 参数名称 | 参数类型 | +| ----------- | ---------------------------------- | | **content** | ***YourModel*, dict, RequestBody** | -**示例:** +**Examples** ---:1 @@ -118,17 +119,21 @@ class UserProfile: @openapi.body(RequestBody(UserProfile)) ``` +```python +@openapi.body({"application/json": {"description": ...}}) +``` + :--- ::: ::: tab deprecated -**参数:** +**Arguments** *None* -**示例:** +**Examples** ---:1 @@ -150,8 +155,8 @@ class UserProfile: **Arguments** -| 参数名称 | 参数名称 | -|--------|---------| +| 参数名称 | 参数名称 | +| ------ | ------- | | `text` | **str** | **Examples** @@ -164,6 +169,14 @@ class UserProfile: ## You can use `markdown` +- And +- make +- lists. +""" +) + +## You can use `markdown` + - And - make - lists. @@ -179,14 +192,14 @@ class UserProfile: ::: tab document -**参数:** +**Arguments** -| 参数名称 | 参数类型 | -|---------------|---------| -| `url` | **str** | +| 参数名称 | 参数类型 | +| ------------- | ------- | +| `url` | **str** | | `description` | **str** | -**示例:** +**Examples** ---:1 @@ -208,14 +221,14 @@ class UserProfile: 可以像其他所有装饰器一样用在响应函数上,也可以作用在蓝图上 -**参数:** +**Arguments** -| 参数名称 | 参数类型 | 默认值 | -|--------|---------------|----------| -| `flag` | **bool** | **True** | -| `bp` | **Blueprint** | | +| 参数名称 | 参数类型 | 默认值 | +| ------ | ------------- | -------- | +| `flag` | **bool** | **True** | +| `bp` | **Blueprint** | | -**示例:** +**Examples** ---:1 @@ -237,13 +250,13 @@ openapi.exclude(bp=some_blueprint) 设置操作 ID。 -**参数:** +**Arguments** -| 参数名称 | 参数类型 | -|--------|---------| +| 参数名称 | 参数类型 | +| ------ | ------- | | `name` | **str** | -**示例:** +**Examples** ---:1 @@ -259,15 +272,15 @@ openapi.exclude(bp=some_blueprint) ::: tab parameter -**参数:** +**Arguments** -| 参数名称 | 参数类型 | 默认值 -|------------|-------------------------------------------|--------------| -| `name` | **str** | | -| `schema` | ***type*** | **str** | -| `location` | **"query", "header", "path" or "cookie"** | **"query"** | +| 参数名称 | 参数类型 | 默认值 | +| ---------- | ----------------------------------------- | ----------- | +| `name` | **str** | | +| `schema` | ***type*** | **str** | +| `location` | **"query", "header", "path" or "cookie"** | **"query"** | -**示例:** +**Examples** ---:1 @@ -295,18 +308,18 @@ openapi.exclude(bp=some_blueprint) ::: tab response -**参数:** +**Arguments** 如果使用 `Response` 对象,则不应传递任何其他参数。 -| 参数名称 | 参数类型 | -|---------------|-------------------------------| -| `status` | **int** | -| `content` | ***type*, *YourModel*, dict** | -| `description` | **str** | -| `response` | **Response** | +| 参数名称 | 参数类型 | +| ------------- | ----------------------------- | +| `status` | **int** | +| `content` | ***type*, *YourModel*, dict** | +| `description` | **str** | +| `response` | **Response** | -**示例:** +**Examples** ---:1 @@ -356,13 +369,13 @@ openapi.exclude(bp=some_blueprint) ::: tab summary -**参数名称:** +**Arguments** -| 参数名称 | 参数类型 | -|---------|---------| -| `text` | **str** | +| 参数名称 | 参数类型 | +| ------ | ------- | +| `text` | **str** | -**示例:** +**Examples** ---:1 @@ -378,13 +391,13 @@ openapi.exclude(bp=some_blueprint) ::: tab tag -**参数:** +**Arguments** -| 参数名称 | 参数类型 | -|---------|--------------| +| 参数名称 | 参数类型 | +| ------- | ------------ | | `*args` | **str, Tag** | -**示例:** +**Examples** ---:1 @@ -402,4 +415,73 @@ openapi.exclude(bp=some_blueprint) ::: -:::: \ No newline at end of file +:::tab secured + +**Arguments** + +| Field | Type | +| ----------------- | ----------------------- | +| `*args, **kwargs` | **str, Dict[str, Any]** | + +**Examples** + +---:1 +```python +@openapi.secured() +``` +:--:1 :--- + +---:1 +```python +@openapi.secured("foo") +``` +:--:1 +```python +@openapi.secured("token1", "token2") +``` +:--- + +---:1 +```python +@openapi.secured({"my_api_key": []}) +``` +:--:1 +```python +@openapi.secured(my_api_key=[]) +``` +:--- + +Do not forget to use `add_security_scheme`. See [security](./security.md) for more details. + +::: + +:::: + +## Integration with Pydantic + +Pydantic models have the ability to [generate OpenAPI schema](https://pydantic-docs.helpmanual.io/usage/schema/). + +---:1 To take advantage of Pydantic model schema generation, pass the output in place of the schema. :--:1 +```python +from sanic import Sanic, json +from sanic_ext import validate, openapi +from pydantic import BaseModel, Field + +class Test(BaseModel): + foo: str = Field(description="Foo Description", example="FOOO") + bar: str = "test" + + +app = Sanic("test") + +@app.get("/") +@openapi.definition( + body={'application/json': Test.schema()}, +) +@validate(json=Test) +async def get(request): + return json({}) +``` +:--- + +*Added in v22.9* diff --git a/src/zh/plugins/sanic-ext/openapi/security.md b/src/zh/plugins/sanic-ext/openapi/security.md new file mode 100644 index 0000000000..b73b15207e --- /dev/null +++ b/src/zh/plugins/sanic-ext/openapi/security.md @@ -0,0 +1,86 @@ +# Security Schemes + +To document authentication schemes, there are two steps. + +_Security is only available starting in v21.12.2_ + +## Document the scheme + +---:1 The first thing that you need to do is define one or more security schemes. The basic pattern will be to define it as: + +```python +add_security_scheme("", "") +``` + +The `type` should correspond to one of the allowed security schemes: `"apiKey"`, `"http"`, `"oauth2"`, `"openIdConnect"`. You can then pass appropriate keyword arguments as allowed by the specification. + +You should consult the [OpenAPI Specification](https://swagger.io/specification/) for details on what values are appropriate. :--:1 +```python +app.ext.openapi.add_security_scheme("api_key", "apiKey") +app.ext.openapi.add_security_scheme( + "token", + "http", + scheme="bearer", + bearer_format="JWT", +) +app.ext.openapi.add_security_scheme("token2", "http") +app.ext.openapi.add_security_scheme( + "oldschool", + "http", + scheme="basic", +) +app.ext.openapi.add_security_scheme( + "oa2", + "oauth2", + flows={ + "implicit": { + "authorizationUrl": "http://example.com/auth", + "scopes": { + "on:two": "something", + "three:four": "something else", + "threefour": "something else...", + }, + } + }, +) +``` +:--- + +## Document the endpoints + +---:1 There are two options, document _all_ endpoints. + +:--:1 +```python +app.ext.openapi.secured() +app.ext.openapi.secured("token") +``` +:--- + +---:1 Or, document only specific routes. :--:1 +```python +@app.route("/one") +async def handler1(request): + """ + openapi: + --- + security: + - foo: [] + """ + + +@app.route("/two") +@openapi.secured("foo") +@openapi.secured({"bar": []}) +@openapi.secured(baz=[]) +async def handler2(request): + ... + + +@app.route("/three") +@openapi.definition(secured="foo") +@openapi.definition(secured={"bar": []}) +async def handler3(request): + ... +``` +:--- diff --git a/src/zh/plugins/sanic-ext/openapi/ui.md b/src/zh/plugins/sanic-ext/openapi/ui.md index bdd769f572..5cc3619732 100644 --- a/src/zh/plugins/sanic-ext/openapi/ui.md +++ b/src/zh/plugins/sanic-ext/openapi/ui.md @@ -10,17 +10,17 @@ Sanic Extensions 带有 Redoc 和 Swagger 接口。 您可以选择使用其中 ## 配置选项(Config options) -| **配置名称** | **类型** | **默认值** | **说明** | -| -------------------------- | --------------- | ------------------- | -------------------------------------------------------------- | -| `OAS_IGNORE_HEAD` | `bool` | `True` | 是否显示 `HEAD` 响应函数 | -| `OAS_IGNORE_OPTIONS` | `bool` | `True` | 是否显示 `OPTIONS` 响应函数 | -| `OAS_PATH_TO_REDOC_HTML` | `Optional[str]` | `None` | 用于覆盖默认 Redoc HTML 的 HTML 路径 | -| `OAS_PATH_TO_SWAGGER_HTML` | `Optional[str]` | `None` | 用于覆盖默认 Swagger HTML 的 HTML 路径 | +| **配置名称** | **类型** | **默认值** | **说明** | +| -------------------------- | --------------- | ------------------- | ------------------------------------------------------------------ | +| `OAS_IGNORE_HEAD` | `bool` | `True` | 是否显示 `HEAD` 响应函数 | +| `OAS_IGNORE_OPTIONS` | `bool` | `True` | 是否显示 `OPTIONS` 响应函数 | +| `OAS_PATH_TO_REDOC_HTML` | `Optional[str]` | `None` | 用于覆盖默认 Redoc HTML 的 HTML 路径 | +| `OAS_PATH_TO_SWAGGER_HTML` | `Optional[str]` | `None` | 用于覆盖默认 Swagger HTML 的 HTML 路径 | | `OAS_UI_DEFAULT` | `Optional[str]` | `"redoc"` | 可以设置为 `redoc` 或 `swagger`。 控制要在基本路由上显示的 UI。 如果设置为 `None`,则不会设置文档路由 | -| `OAS_UI_REDOC` | `bool` | `True` | 是否启用 Redoc UI | -| `OAS_UI_SWAGGER` | `bool` | `True` | 是否启用 Swagger UI | -| `OAS_URI_TO_CONFIG` | `str` | `"/openapi-config"` | Swagger 使用的 OpenAPI 配置的 URI 路径 | -| `OAS_URI_TO_JSON` | `str` | `"/openapi.json"` | JSON 文档的 URI 路径 | -| `OAS_URI_TO_REDOC` | `str` | `"/redoc"` | Redoc 的 URI 路径 | -| `OAS_URI_TO_SWAGGER` | `str` | `"/swagger"` | Swagger 的 URI 路径 | -| `OAS_URL_PREFIX` | `str` | `"/docs"` | 用于 OpenAPI 文档蓝图的 URL 前缀 | \ No newline at end of file +| `OAS_UI_REDOC` | `bool` | `True` | 是否启用 Redoc UI | +| `OAS_UI_SWAGGER` | `bool` | `True` | 是否启用 Swagger UI | +| `OAS_URI_TO_CONFIG` | `str` | `"/openapi-config"` | Swagger 使用的 OpenAPI 配置的 URI 路径 | +| `OAS_URI_TO_JSON` | `str` | `"/openapi.json"` | JSON 文档的 URI 路径 | +| `OAS_URI_TO_REDOC` | `str` | `"/redoc"` | Redoc 的 URI 路径 | +| `OAS_URI_TO_SWAGGER` | `str` | `"/swagger"` | Swagger 的 URI 路径 | +| `OAS_URL_PREFIX` | `str` | `"/docs"` | 用于 OpenAPI 文档蓝图的 URL 前缀 | diff --git a/src/zh/plugins/sanic-ext/templating.md b/src/zh/plugins/sanic-ext/templating.md new file mode 100644 index 0000000000..91eea27c7b --- /dev/null +++ b/src/zh/plugins/sanic-ext/templating.md @@ -0,0 +1,132 @@ +# Templating + +Sanic Extensions can easily help you integrate templates into your route handlers. + + +## Dependencies + +**Currently, we only support [Jinja](https://github.com/pallets/jinja/).** + +[Read the Jinja docs first](https://jinja.palletsprojects.com/en/3.1.x/) if you are unfamiliar with how to create templates. + +Sanic Extensions will automatically setup and load Jinja for you if it is installed in your environment. Therefore, the only setup that you need to do is install Jinja: + +``` +pip install Jinja2 +``` + +## Rendering a template from a file + +There are three (3) ways for you: + +1. Using a decorator to pre-load the template file +1. Returning a rendered `HTTPResponse` object +1. Hybrid pattern that creates a `LazyResponse` + +Let's imagine you have a file called `./templates/foo.html`: + +```html + + + + + My Webpage + + + +

Hello, world!!!!

+
    + {% for item in seq %} +
  • {{ item }}
  • + {% endfor %} +
+ + + +``` + +Let's see how you could render it with Sanic + Jinja. + +### Option 1 - as a decorator + +---:1 The benefit of this approach is that the templates can be predefined at startup time. This will mean that less fetching needs to happen in the handler, and should therefore be the fastest option. :--:1 +```python +@app.get("/") +@app.ext.template("foo.html") +async def handler(request: Request): + return {"seq": ["one", "two"]} +``` +:--- + +### Option 2 - as a return object + +---:1 This is meant to mimic the `text`, `json`, `html`, `file`, etc pattern of core Sanic. It will allow the most customization to the response object since it has direct control of it. Just like in other `HTTPResponse` objects, you can control headers, cookies, etc. :--:1 +```python +from sanic_ext import render + +@app.get("/alt") +async def handler(request: Request): + return await render( + "foo.html", context={"seq": ["three", "four"]}, status=400 + ) +``` +:--- + +### Option 3 - hybrid/lazy + +---:1 In this approach, the template is defined up front and not inside the handler (for performance). Then, the `render` function returns a `LazyResponse` that can be used to build a proper `HTTPResponse` inside the decorator. :--:1 +```python +from sanic_ext import render + +@app.get("/") +@app.ext.template("foo.html") +async def handler(request: Request): + return await render(context={"seq": ["five", "six"]}, status=400) +``` +:--- + +## Rendering a template from a string + +---:1 Sometimes you may want to write (or generate) your template inside of Python code and _not_ read it from an HTML file. In this case, you can still use the `render` function we saw above. Just use `template_source`. :--:1 +```python +from sanic_ext import render +from textwrap import dedent + +@app.get("/") +async def handler(request): + template = dedent(""" + + + + + My Webpage + + + +

Hello, world!!!!

+
    + {% for item in seq %} +
  • {{ item }}
  • + {% endfor %} +
+ + + + """) + return await render( + template_source=template, + context={"seq": ["three", "four"]}, + app=app, + ) +``` +:--- + +::: tip In this example, we use `textwrap.dedent` to remove the whitespace in the beginning of each line of the multi-line string. It is not necessary, but just a nice touch to keep both the code and the generated source clean. ::: + +## Development and auto-reload + +If auto-reload is turned on, then changes to your template files should trigger a reload of the server. + +## Configuration + +See `templating_enable_async` and `templating_path_to_templates` in [settings](./configuration.md#settings). diff --git a/src/zh/plugins/sanic-ext/validation.md b/src/zh/plugins/sanic-ext/validation.md new file mode 100644 index 0000000000..0aa293cbaa --- /dev/null +++ b/src/zh/plugins/sanic-ext/validation.md @@ -0,0 +1,173 @@ +# Validation + +One of the most commonly implemented features of a web application is user-input validation. For obvious reasons, this is not only a security issue, but also just plain good practice. You want to make sure your data conforms to expectations, and throw a `400` response when it does not. + +## Implementation + +### Validation with Dataclasses + +With the introduction of [Data Classes](https://docs.python.org/3/library/dataclasses.html), Python made it super simple to create objects that meet a defined schema. However, the standard library only supports type checking validation, **not** runtime validation. Sanic Extensions adds the ability to do runtime validations on incoming requests using `dataclasses` out of the box. If you also have either `pydantic` or `attrs` installed, you can alternatively use one of those libraries. + +---:1 + +First, define a model. + +:--:1 + +```python +@dataclass +class SearchParams: + q: str +``` + +:--- + +---:1 + +Then, attach it to your route + +:--:1 + +```python +from sanic_ext import validate + +@app.route("/search") +@validate(query=SearchParams) +async def handler(request, query: SearchParams): + return json(asdict(query)) +``` + +:--- + +---:1 + +You should now have validation on the incoming request. + +:--:1 + +``` +$ curl localhost:8000/search +⚠️ 400 — Bad Request +==================== +Invalid request body: SearchParams. Error: missing a required argument: 'q' +``` +``` +$ curl localhost:8000/search\?q=python +{"q":"python"} +``` + +:--- + +### Validation with Pydantic + + +You can use Pydantic models also. + +---:1 + +First, define a model. + +:--:1 + +```python +class Person(BaseModel): + name: str + age: int +``` + +:--- + +---:1 + +Then, attach it to your route + +:--:1 + +```python +from sanic_ext import validate + +@app.post("/person") +@validate(json=Person) +async def handler(request, body: Person): + return json(body.dict()) +``` +:--- + +---:1 + +You should now have validation on the incoming request. + +:--:1 + +``` +$ curl localhost:8000/person -d '{"name": "Alice", "age": 21}' -X POST +{"name":"Alice","age":21} +``` + +:--- + +### Validation with Attrs + + +You can use Attrs also. + +---:1 + +First, define a model. + +:--:1 + +```python +@attrs.define +class Person: + name: str + age: int + +``` + +:--- + +---:1 + +Then, attach it to your route + +:--:1 + +```python +from sanic_ext import validate + +@app.post("/person") +@validate(json=Person) +async def handler(request, body: Person): + return json(attrs.asdict(body)) +``` +:--- + +---:1 + +You should now have validation on the incoming request. + +:--:1 + +``` +$ curl localhost:8000/person -d '{"name": "Alice", "age": 21}' -X POST +{"name":"Alice","age":21} +``` + +:--- + +## What can be validated? + +The `validate` decorator can be used to validate incoming user data from three places: JSON body data (`request.json`), form body data (`request.form`), and query parameters (`request.args`). + +---:1 As you might expect, you can attach your model using the keyword arguments of the decorator. + +:--:1 +```python +@validate( + json=ModelA, + query=ModelB, + form=ModelC, +) +``` +:--- diff --git a/src/zh/plugins/sanic-testing/clients.md b/src/zh/plugins/sanic-testing/clients.md new file mode 100644 index 0000000000..ee02576ecc --- /dev/null +++ b/src/zh/plugins/sanic-testing/clients.md @@ -0,0 +1,99 @@ +# Test Clients + +There are three different test clients available to you, each of them presents different capabilities. + +## Regular sync client: `SanicTestClient` + +The `SanicTestClient` runs an actual version of the Sanic Server on your local network to run its tests. Each time it calls an endpoint it will spin up a version of the application and bind it to a socket on the host OS. Then, it will use `httpx` to make calls directly to that application. + +This is the typical way that Sanic applications are tested. + +---:1 Once installing Sanic Testing, the regular `SanicTestClient` can be used without further setup. This is because Sanic does the leg work for you under the hood. :--: +```python +app.test_client.get("/path/to/endpoint") +``` +:--- + +---:1 However, you may find it desirable to instantiate the client yourself. :--: +```python +from sanic_testing.testing import SanicTestClient + +test_client = SanicTestClient(app) +test_client.get("/path/to/endpoint") +``` +:--- + +---:1 A third option for starting the test client is to use the `TestManager`. This is a convenience object that sets up both the `SanicTestClient` and the `SanicASGITestClient`. + +:--: +```python +from sanic_testing import TestManager + +mgr = TestManager(app) +app.test_client.get("/path/to/endpoint") +# or +mgr.test_client.get("/path/to/endpoint") +``` +:--- + +You can make a request by using one of the following methods + +- `SanicTestClient.get` +- `SanicTestClient.post` +- `SanicTestClient.put` +- `SanicTestClient.patch` +- `SanicTestClient.delete` +- `SanicTestClient.options` +- `SanicTestClient.head` +- `SanicTestClient.websocket` +- `SanicTestClient.request` + +You can use these methods *almost* identically as you would when using `httpx`. Any argument that you would pass to `httpx` will be accepted, **with one caveat**: If you are using `test_client.request` and want to manually specify the HTTP method, you should use: `http_method`: + +```python +test_client.request("/path/to/endpoint", http_method="get") +``` + +## ASGI async client: `SanicASGITestClient` + +Unlike the `SanicTestClient` that spins up a server on every request, the `SanicASGITestClient` does not. Instead it makes use of the `httpx` library to execute Sanic as an ASGI application to reach inside and execute the route handlers. + +---:1 This test client provides all of the same methods and generally works as the `SanicTestClient`. The only difference is that you will need to add an `await` to each call: :--: +```python +await app.test_client.get("/path/to/endpoint") +``` +:--- + +The `SanicASGITestClient` can be used in the exact same three ways as the `SanicTestClient`. + +::: tip Note The `SanicASGITestClient` does not need to only be used with ASGI applications. The same way that the `SanicTestClient` does not need to only test sync endpoints. Both of these clients are capable of testing *any* Sanic application. ::: + +## Persistent service client: `ReusableClient` + +This client works under a similar premise as the `SanicTestClient` in that it stands up an instance of your application and makes real HTTP requests to it. However, unlike the `SanicTestClient`, when using the `ReusableClient` you control the lifecycle of the application. + +That means that every request **does not** start a new web server. Instead you will start the server and stop it as needed and can make multiple requests to the same running instance. + +---:1 Unlike the other two clients, you **must** instantiate this client for use: :--: +```python +from sanic_testing.reusable import ReusableClient + +client = ReusableClient(app) +``` +:--- + + +---:1 Once created, you will use the client inside of a context manager. Once outside of the scope of the manager, the server will shutdown. :--: +```python +from sanic_testing.reusable import ReusableClient + +def test_multiple_endpoints_on_same_server(app): + client = ReusableClient(app) + with client: + _, response = client.get("/path/to/1") + assert response.status == 200 + + _, response = client.get("/path/to/2") + assert response.status == 200 +``` +:--- diff --git a/src/zh/plugins/sanic-testing/getting-started.md b/src/zh/plugins/sanic-testing/getting-started.md new file mode 100644 index 0000000000..e9f2c8c4e7 --- /dev/null +++ b/src/zh/plugins/sanic-testing/getting-started.md @@ -0,0 +1,83 @@ +# Getting Started + +Sanic Testing is the *official* testing client for Sanic. Its primary use is to power the tests of the Sanic project itself. However, it is also meant as an easy-to-use client for getting your API tests up and running quickly. + +## Minimum requirements + +- **Python**: 3.7+ +- **Sanic**: 21.3+ + +Versions of Sanic older than 21.3 have this module integrated into Sanic itself as `sanic.testing`. + +## Install + +Sanic Testing can be installed from PyPI: + +``` +pip install sanic-testing +``` + +## Basic Usage + +As long as the `sanic-testing` package is in the environment, there is nothing you need to do to start using it. + + +### Writing a sync test + +In order to use the test client, you just need to access the property `test_client` on your application instance: + +```python +import pytest +from sanic import Sanic, response + + +@pytest.fixture +def app(): + sanic_app = Sanic("TestSanic") + + @sanic_app.get("/") + def basic(request): + return response.text("foo") + + return sanic_app + +def test_basic_test_client(app): + request, response = app.test_client.get("/") + + assert request.method.lower() == "get" + assert response.body == b"foo" + assert response.status == 200 +``` + +### Writing an async test + +In order to use the async test client in `pytest`, you should install the `pytest-asyncio` plugin. + +``` +pip install pytest-asyncio +``` + +You can then create an async test and use the ASGI client: + +```python +import pytest +from sanic import Sanic, response + +@pytest.fixture +def app(): + sanic_app = Sanic(__name__) + + @sanic_app.get("/") + def basic(request): + return response.text("foo") + + return sanic_app + +@pytest.mark.asyncio +async def test_basic_asgi_client(app): + request, response = await app.asgi_client.get("/") + + assert request.method.lower() == "get" + assert response.body == b"foo" + assert response.status == 200 +```