Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dc71a0b725 | |||
| 969c0b408d | |||
| 6cd596b2b9 | |||
| 5cfbe13ddf | |||
| 9d31549fe8 | |||
| 7ae8062fc5 | |||
| 03ee860ceb | |||
| 9dd9792204 | |||
| 60b4516894 | |||
| 8442a0af85 | |||
| 42d5aea168 | |||
| feb473e952 | |||
| 7dccbc7e19 | |||
| 6e5ce5b42a | |||
| 1d3ec74e09 | |||
| b3fe28f522 | |||
| bfd9f875b7 | |||
| d00d5ec391 | |||
| b68d349afe | |||
| abdf9d7b86 | |||
| a8f9bc2bc1 | |||
| fb4cec76d8 | |||
| 0aee74f03a | |||
| 0b0b366804 | |||
| 6ef0a4cb91 | |||
| e70b638271 | |||
| f99530d723 | |||
| f28cd7001a | |||
| 5c5d0bbd74 | |||
| 4c00c90c49 | |||
| 093819b249 | |||
| 1193bdcb6c | |||
| cc6f10f9bd | |||
| 4ad2f00c1c | |||
| a491f9a3fe |
@@ -0,0 +1,4 @@
|
||||
# Sphinx build info version 1
|
||||
# This file records the configuration used when building these files. When it is not found, a full rebuild will be done.
|
||||
config: 5ce83980cbbb1ff8c0566b9128c91144
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
@@ -1,42 +0,0 @@
|
||||
Release a new version of responder to PyPI and GitHub.
|
||||
|
||||
Usage: /release <version> (e.g. /release 3.6.0)
|
||||
|
||||
If no version is provided, ask the user what version to release.
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Verify clean state**: Run `git status` and ensure the working tree is clean. If not, stop and ask the user.
|
||||
|
||||
2. **Run tests**: Run `uv run pytest -x --no-header -q`. If any fail, stop and report.
|
||||
|
||||
3. **Bump version**: Update `responder/__version__.py` to the new version.
|
||||
|
||||
4. **Update changelog**:
|
||||
- Run `git log --oneline $(git describe --tags --abbrev=0)..HEAD` to get commits since last release.
|
||||
- Add a new section in `CHANGELOG.md` under `## [Unreleased]` with the date, categorized into Added/Changed/Fixed/Removed.
|
||||
- Update the compare links at the bottom of the file.
|
||||
|
||||
5. **Lock deps**: Run `uv lock`.
|
||||
|
||||
6. **Commit**: Stage `responder/__version__.py`, `CHANGELOG.md`, and `uv.lock`. Commit with message `Bump version to X.Y.Z and update changelog`.
|
||||
|
||||
7. **Push and tag**:
|
||||
```
|
||||
git push
|
||||
git tag vX.Y.Z
|
||||
git push origin vX.Y.Z
|
||||
```
|
||||
|
||||
8. **GitHub release**: Create a release with `gh release create` including highlights and a link to the full changelog.
|
||||
|
||||
9. **Build and publish**:
|
||||
```
|
||||
uv build
|
||||
uvx twine upload dist/responder-X.Y.Z*
|
||||
```
|
||||
Note: This requires a PyPI token. If twine fails due to auth, tell the user to set `TWINE_USERNAME=__token__` and `TWINE_PASSWORD` and re-run, or run `! uvx twine upload dist/responder-X.Y.Z*` interactively.
|
||||
|
||||
10. **Update GitHub release**: Edit the release to add a link to the PyPI page: `https://pypi.org/project/responder/X.Y.Z/`
|
||||
|
||||
11. **Report**: Print a summary with links to the GitHub release and PyPI page.
|
||||
@@ -1,3 +0,0 @@
|
||||
github: kennethreitz
|
||||
thanks_dev: kennethreitz
|
||||
custom: https://cash.app/$KennethReitz
|
||||
@@ -1,16 +0,0 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
@@ -1,53 +0,0 @@
|
||||
name: "Documentation"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request: ~
|
||||
workflow_dispatch:
|
||||
|
||||
# Cancel redundant in-progress jobs.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
|
||||
documentation:
|
||||
name: "Documentation"
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
UV_SYSTEM_PYTHON: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
version: "latest"
|
||||
enable-cache: true
|
||||
cache-dependency-glob: |
|
||||
pyproject.toml
|
||||
|
||||
- name: Install package and documentation dependencies
|
||||
run: uv pip install '.[docs]'
|
||||
|
||||
- name: Build static HTML documentation
|
||||
run: sphinx-build -W --keep-going docs/source docs/build
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./docs/build
|
||||
cname: responder.kennethreitz.org
|
||||
@@ -1,58 +0,0 @@
|
||||
name: "Tests"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request: ~
|
||||
workflow_dispatch:
|
||||
|
||||
# Cancel redundant in-progress jobs.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
test:
|
||||
name: "Python ${{ matrix.python-version }}"
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: [
|
||||
"3.10",
|
||||
"3.11",
|
||||
"3.12",
|
||||
"3.13",
|
||||
"3.14",
|
||||
"3.14t",
|
||||
"pypy3.11",
|
||||
]
|
||||
env:
|
||||
UV_SYSTEM_PYTHON: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
version: "latest"
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ matrix.python-version }}
|
||||
cache-dependency-glob: |
|
||||
pyproject.toml
|
||||
|
||||
- name: Install package
|
||||
run: uv pip install '.[develop,test]'
|
||||
|
||||
- name: Run tests
|
||||
run: pytest
|
||||
@@ -1,20 +0,0 @@
|
||||
.venv*
|
||||
.vscode/
|
||||
.cache
|
||||
.idea
|
||||
.python-version
|
||||
.coverage
|
||||
.pytest_cache
|
||||
.DS_Store
|
||||
coverage.xml
|
||||
.coverage*
|
||||
*.lock
|
||||
|
||||
__pycache__
|
||||
tests/__pycache__
|
||||
|
||||
build
|
||||
responder.egg-info/
|
||||
dist/
|
||||
app.py
|
||||
app2.py
|
||||
@@ -1,44 +0,0 @@
|
||||
# Responder
|
||||
|
||||
A familiar HTTP Service Framework for Python, by Kenneth Reitz.
|
||||
|
||||
## Commands
|
||||
|
||||
- **Tests**: `uv run pytest` (runs full suite with coverage)
|
||||
- **Single test**: `uv run pytest tests/test_responder.py::test_name -xvs`
|
||||
- **Lint**: `uv run ruff check .`
|
||||
- **Type check**: `uv run mypy`
|
||||
- **Build docs**: `cd docs && uv run make html`
|
||||
- **Build package**: `uv build`
|
||||
- **Lock deps**: `uv lock`
|
||||
|
||||
## Architecture
|
||||
|
||||
- `responder/api.py` — Main `API` class, the entry point for all apps
|
||||
- `responder/routes.py` — `Router`, `Route`, `WebSocketRoute` dispatch
|
||||
- `responder/models.py` — `Request` and `Response` wrappers around Starlette
|
||||
- `responder/ext/` — Extensions: CLI, GraphQL, OpenAPI, rate limiting
|
||||
- `responder/background.py` — Background task queue
|
||||
- `responder/formats.py` — Content negotiation (JSON, YAML, msgpack)
|
||||
- `responder/__version__.py` — Single source of truth for version string
|
||||
|
||||
## Conventions
|
||||
|
||||
- Python 3.10+ only. Use `from __future__ import annotations` where present.
|
||||
- Use `inspect.iscoroutinefunction` (not `asyncio.iscoroutinefunction`).
|
||||
- Tests use `api.requests` (Starlette TestClient) with `allowed_hosts=[";"]` or `["localhost"]`.
|
||||
- Werkzeug 3.1.7+ rejects invalid Host headers — use `localhost` when mounting WSGI apps in tests.
|
||||
- Version is in `responder/__version__.py`, bump it there.
|
||||
- Changelog follows [Keep a Changelog](https://keepachangelog.com/) format in `CHANGELOG.md`.
|
||||
- Compare links at the bottom of CHANGELOG.md must be updated when adding a release.
|
||||
- All deps managed via `uv`. Lock file (`uv.lock`) is not committed.
|
||||
|
||||
## Release Process
|
||||
|
||||
1. Bump version in `responder/__version__.py`
|
||||
2. Add changelog entry in `CHANGELOG.md` (update compare links too)
|
||||
3. `uv lock` to refresh the lock file
|
||||
4. Commit: `Bump version to X.Y.Z and update changelog`
|
||||
5. `git tag vX.Y.Z && git push && git push origin vX.Y.Z`
|
||||
6. `gh release create vX.Y.Z --title "vX.Y.Z" --notes "..."`
|
||||
7. `uv build && uvx twine upload dist/responder-X.Y.Z*`
|
||||
@@ -1,178 +0,0 @@
|
||||
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -1,109 +0,0 @@
|
||||
# Responder
|
||||
|
||||
A familiar HTTP Service Framework for Python, powered by [Starlette](https://www.starlette.io/).
|
||||
|
||||
```python
|
||||
import responder
|
||||
|
||||
api = responder.API()
|
||||
|
||||
@api.route("/{greeting}")
|
||||
async def greet_world(req, resp, *, greeting):
|
||||
resp.text = f"{greeting}, world!"
|
||||
|
||||
if __name__ == "__main__":
|
||||
api.run()
|
||||
```
|
||||
|
||||
$ pip install responder
|
||||
|
||||
That's it. Supports Python 3.10+.
|
||||
|
||||
## The Basics
|
||||
|
||||
- `resp.text` sends back text. `resp.html` sends back HTML. `resp.content` sends back bytes.
|
||||
- `resp.media` sends back JSON (or YAML, with content negotiation).
|
||||
- `resp.file("path.pdf")` serves a file with automatic content-type detection.
|
||||
- `req.headers` is case-insensitive. `req.params` gives you query parameters.
|
||||
- Both sync and async views work — the `async` is optional.
|
||||
|
||||
## Highlights
|
||||
|
||||
```python
|
||||
# Type-safe route parameters
|
||||
@api.route("/users/{user_id:int}")
|
||||
async def get_user(req, resp, *, user_id):
|
||||
resp.media = {"id": user_id}
|
||||
|
||||
# HTTP method filtering
|
||||
@api.route("/items", methods=["POST"])
|
||||
async def create_item(req, resp):
|
||||
data = await req.media()
|
||||
resp.media = {"created": data}
|
||||
|
||||
# Class-based views
|
||||
@api.route("/things/{id}")
|
||||
class ThingResource:
|
||||
def on_get(self, req, resp, *, id):
|
||||
resp.media = {"id": id}
|
||||
def on_post(self, req, resp, *, id):
|
||||
resp.text = "created"
|
||||
|
||||
# Before-request hooks (auth, rate limiting, etc.)
|
||||
@api.route(before_request=True)
|
||||
def check_auth(req, resp):
|
||||
if not req.headers.get("Authorization"):
|
||||
resp.status_code = 401
|
||||
resp.media = {"error": "unauthorized"}
|
||||
|
||||
# Custom error handling
|
||||
@api.exception_handler(ValueError)
|
||||
async def handle_error(req, resp, exc):
|
||||
resp.status_code = 400
|
||||
resp.media = {"error": str(exc)}
|
||||
|
||||
# Lifespan events
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app):
|
||||
print("starting up")
|
||||
yield
|
||||
print("shutting down")
|
||||
|
||||
api = responder.API(lifespan=lifespan)
|
||||
|
||||
# GraphQL
|
||||
import graphene
|
||||
api.graphql("/graphql", schema=graphene.Schema(query=Query))
|
||||
|
||||
# WebSockets
|
||||
@api.route("/ws", websocket=True)
|
||||
async def websocket(ws):
|
||||
await ws.accept()
|
||||
while True:
|
||||
name = await ws.receive_text()
|
||||
await ws.send_text(f"Hello {name}!")
|
||||
|
||||
# Mount WSGI/ASGI apps
|
||||
from flask import Flask
|
||||
flask_app = Flask(__name__)
|
||||
api.mount("/flask", flask_app)
|
||||
|
||||
# Background tasks
|
||||
@api.route("/work")
|
||||
def do_work(req, resp):
|
||||
@api.background.task
|
||||
def process():
|
||||
import time; time.sleep(10)
|
||||
process()
|
||||
resp.media = {"status": "processing"}
|
||||
```
|
||||
|
||||
Built-in OpenAPI docs, cookie-based sessions, gzip compression, static file serving, Jinja2 templates, and a production uvicorn server.
|
||||
|
||||
Route convertors: `str`, `int`, `float`, `uuid`, `path`.
|
||||
|
||||
## Documentation
|
||||
|
||||
https://responder.kennethreitz.org
|
||||
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" data-content_root="../">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Overview: module code — responder 3.6.2 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="../_static/pygments.css?v=5ecbeea2" />
|
||||
<link rel="stylesheet" type="text/css" href="../_static/basic.css?v=b08954a9" />
|
||||
<link rel="stylesheet" type="text/css" href="../_static/alabaster.css?v=27fed22d" />
|
||||
<link rel="stylesheet" type="text/css" href="../_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="../_static/design-elements.e5416f61bae5d36adc6d722a2b6f8cff.css?v=452a8e97" />
|
||||
<script src="../_static/documentation_options.js?v=c0c9fa11"></script>
|
||||
<script src="../_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="../_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="../_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="../_static/copybutton.js?v=fd10adb8"></script>
|
||||
<script>
|
||||
</script>
|
||||
<script src="../_static/design-elements.bbdccc18c4abea9397628f9fea3d48c2.js?v=03c7770e"></script>
|
||||
<link rel="index" title="Index" href="../genindex.html" />
|
||||
<link rel="search" title="Search" href="../search.html" />
|
||||
|
||||
<link rel="stylesheet" href="../_static/custom.css" type="text/css" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</head><body>
|
||||
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
|
||||
|
||||
<div class="body" role="main">
|
||||
|
||||
<h1>All modules for which code is available</h1>
|
||||
<ul><li><a href="responder/api.html">responder.api</a></li>
|
||||
<li><a href="responder/background.html">responder.background</a></li>
|
||||
<li><a href="responder/ext/ratelimit.html">responder.ext.ratelimit</a></li>
|
||||
<li><a href="responder/models.html">responder.models</a></li>
|
||||
<li><a href="responder/status_codes.html">responder.status_codes</a></li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="sphinxsidebar" role="navigation" aria-label="Main">
|
||||
<div class="sphinxsidebarwrapper"><p class="logo">
|
||||
<a href="../index.html">
|
||||
<img class="logo" src="../_static/responder.png" />
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Responder</strong> — a familiar HTTP service framework for Python.
|
||||
<br />
|
||||
<small>v3.6.2</small>
|
||||
</p>
|
||||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
|
||||
<li><a href="https://pypi.org/project/responder/">Responder @ PyPI</a></li>
|
||||
<li><a href="https://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
|
||||
</ul>
|
||||
<search id="searchbox" style="display: none" role="search">
|
||||
<h3 id="searchlabel">Quick search</h3>
|
||||
<div class="searchformwrapper">
|
||||
<form class="search" action="../search.html" method="get">
|
||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
||||
<input type="submit" value="Go" />
|
||||
</form>
|
||||
</div>
|
||||
</search>
|
||||
<script>document.getElementById('searchbox').style.display = "block"</script>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
©2018-2026, Kenneth Reitz.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,777 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" data-content_root="../../">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>responder.api — responder 3.6.2 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css?v=5ecbeea2" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/basic.css?v=b08954a9" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/alabaster.css?v=27fed22d" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/design-elements.e5416f61bae5d36adc6d722a2b6f8cff.css?v=452a8e97" />
|
||||
<script src="../../_static/documentation_options.js?v=c0c9fa11"></script>
|
||||
<script src="../../_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="../../_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="../../_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="../../_static/copybutton.js?v=fd10adb8"></script>
|
||||
<script>
|
||||
</script>
|
||||
<script src="../../_static/design-elements.bbdccc18c4abea9397628f9fea3d48c2.js?v=03c7770e"></script>
|
||||
<link rel="index" title="Index" href="../../genindex.html" />
|
||||
<link rel="search" title="Search" href="../../search.html" />
|
||||
|
||||
<link rel="stylesheet" href="../../_static/custom.css" type="text/css" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</head><body>
|
||||
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
|
||||
|
||||
<div class="body" role="main">
|
||||
|
||||
<h1>Source code for responder.api</h1><div class="highlight"><pre>
|
||||
<span></span><span class="kn">import</span><span class="w"> </span><span class="nn">asyncio</span>
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">inspect</span>
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">os</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">pathlib</span><span class="w"> </span><span class="kn">import</span> <span class="n">Path</span>
|
||||
|
||||
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"API"</span><span class="p">]</span>
|
||||
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">uvicorn</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">starlette.middleware.cors</span><span class="w"> </span><span class="kn">import</span> <span class="n">CORSMiddleware</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">starlette.middleware.errors</span><span class="w"> </span><span class="kn">import</span> <span class="n">ServerErrorMiddleware</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">starlette.middleware.exceptions</span><span class="w"> </span><span class="kn">import</span> <span class="n">ExceptionMiddleware</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">starlette.middleware.gzip</span><span class="w"> </span><span class="kn">import</span> <span class="n">GZipMiddleware</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">starlette.middleware.httpsredirect</span><span class="w"> </span><span class="kn">import</span> <span class="n">HTTPSRedirectMiddleware</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">starlette.middleware.sessions</span><span class="w"> </span><span class="kn">import</span> <span class="n">SessionMiddleware</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">starlette.middleware.trustedhost</span><span class="w"> </span><span class="kn">import</span> <span class="n">TrustedHostMiddleware</span>
|
||||
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">.</span><span class="w"> </span><span class="kn">import</span> <span class="n">status_codes</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">.background</span><span class="w"> </span><span class="kn">import</span> <span class="n">BackgroundQueue</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">.formats</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_formats</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">.models</span><span class="w"> </span><span class="kn">import</span> <span class="n">Request</span><span class="p">,</span> <span class="n">Response</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">.routes</span><span class="w"> </span><span class="kn">import</span> <span class="n">Router</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">.staticfiles</span><span class="w"> </span><span class="kn">import</span> <span class="n">StaticFiles</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">.statics</span><span class="w"> </span><span class="kn">import</span> <span class="n">DEFAULT_CORS_PARAMS</span><span class="p">,</span> <span class="n">DEFAULT_OPENAPI_THEME</span><span class="p">,</span> <span class="n">DEFAULT_SECRET_KEY</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">.templates</span><span class="w"> </span><span class="kn">import</span> <span class="n">Templates</span>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="API">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API">[docs]</a>
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">API</span><span class="p">:</span>
|
||||
<span class="w"> </span><span class="sd">"""The primary web-service class.</span>
|
||||
|
||||
<span class="sd"> :param static_dir: The directory to use for static files. Will be created for you if it doesn't already exist.</span>
|
||||
<span class="sd"> :param templates_dir: The directory to use for templates. Will be created for you if it doesn't already exist.</span>
|
||||
<span class="sd"> :param auto_escape: If ``True``, HTML and XML templates will automatically be escaped.</span>
|
||||
<span class="sd"> :param enable_hsts: If ``True``, send all responses to HTTPS URLs.</span>
|
||||
<span class="sd"> :param gzip: If ``True`` (the default), compress responses with GZip.</span>
|
||||
<span class="sd"> :param openapi_theme: OpenAPI documentation theme, must be one of ``elements``, ``rapidoc``, ``redoc``, ``swagger_ui``</span>
|
||||
<span class="sd"> """</span> <span class="c1"># noqa: E501</span>
|
||||
|
||||
<span class="n">status_codes</span> <span class="o">=</span> <span class="n">status_codes</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="p">,</span>
|
||||
<span class="o">*</span><span class="p">,</span>
|
||||
<span class="n">debug</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
|
||||
<span class="n">title</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">version</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">description</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">terms_of_service</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">contact</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">license</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="c1"># noqa: A002</span>
|
||||
<span class="n">openapi</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">openapi_route</span><span class="o">=</span><span class="s2">"/schema.yml"</span><span class="p">,</span>
|
||||
<span class="n">static_dir</span><span class="o">=</span><span class="s2">"static"</span><span class="p">,</span>
|
||||
<span class="n">static_route</span><span class="o">=</span><span class="s2">"/static"</span><span class="p">,</span>
|
||||
<span class="n">templates_dir</span><span class="o">=</span><span class="s2">"templates"</span><span class="p">,</span>
|
||||
<span class="n">auto_escape</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
|
||||
<span class="n">secret_key</span><span class="o">=</span><span class="n">DEFAULT_SECRET_KEY</span><span class="p">,</span>
|
||||
<span class="n">enable_hsts</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
|
||||
<span class="n">docs_route</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">cors</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
|
||||
<span class="n">cors_params</span><span class="o">=</span><span class="n">DEFAULT_CORS_PARAMS</span><span class="p">,</span>
|
||||
<span class="n">allowed_hosts</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">openapi_theme</span><span class="o">=</span><span class="n">DEFAULT_OPENAPI_THEME</span><span class="p">,</span>
|
||||
<span class="n">lifespan</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">gzip</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
|
||||
<span class="n">request_id</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
|
||||
<span class="n">enable_logging</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
|
||||
<span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Create a new Responder API instance.</span>
|
||||
|
||||
<span class="sd"> :param debug: If ``True``, enable debug mode with verbose error pages.</span>
|
||||
<span class="sd"> :param title: The title of the API, used in OpenAPI documentation.</span>
|
||||
<span class="sd"> :param version: The version string for the API (e.g. ``"1.0"``).</span>
|
||||
<span class="sd"> :param description: A longer description of the API for OpenAPI docs.</span>
|
||||
<span class="sd"> :param terms_of_service: URL to the API's terms of service.</span>
|
||||
<span class="sd"> :param contact: Contact information dict for the API (``name``, ``url``, ``email``).</span>
|
||||
<span class="sd"> :param license: License information dict (``name``, ``url``).</span>
|
||||
<span class="sd"> :param openapi: The OpenAPI version string (e.g. ``"3.0.2"``). Enables OpenAPI schema generation.</span>
|
||||
<span class="sd"> :param openapi_route: The URL path for the OpenAPI schema (default ``"/schema.yml"``).</span>
|
||||
<span class="sd"> :param static_dir: Directory for static files. Set to ``None`` to disable. Created automatically if missing.</span>
|
||||
<span class="sd"> :param static_route: URL prefix for serving static files (default ``"/static"``).</span>
|
||||
<span class="sd"> :param templates_dir: Directory for Jinja2 templates (default ``"templates"``).</span>
|
||||
<span class="sd"> :param auto_escape: If ``True``, auto-escape HTML/XML in templates.</span>
|
||||
<span class="sd"> :param secret_key: Secret key for signing cookie-based sessions. **Always set this in production.**</span>
|
||||
<span class="sd"> :param enable_hsts: If ``True``, redirect all HTTP requests to HTTPS.</span>
|
||||
<span class="sd"> :param docs_route: URL path for interactive API docs (e.g. ``"/docs"``). Enables OpenAPI if not already set.</span>
|
||||
<span class="sd"> :param cors: If ``True``, enable CORS middleware.</span>
|
||||
<span class="sd"> :param cors_params: Dict of CORS configuration (``allow_origins``, ``allow_methods``, etc.).</span>
|
||||
<span class="sd"> :param allowed_hosts: List of allowed hostnames (e.g. ``["example.com"]``). Defaults to ``["*"]``.</span>
|
||||
<span class="sd"> :param openapi_theme: Documentation UI theme: ``"swagger_ui"``, ``"redoc"``, ``"rapidoc"``, or ``"elements"``.</span>
|
||||
<span class="sd"> :param lifespan: An async context manager for startup/shutdown logic.</span>
|
||||
<span class="sd"> :param gzip: If ``True`` (the default), compress responses with GZip.</span>
|
||||
<span class="sd"> :param request_id: If ``True``, add ``X-Request-ID`` headers to all responses.</span>
|
||||
<span class="sd"> :param enable_logging: If ``True``, enable structured logging with per-request context (request ID, method, path, client IP).</span>
|
||||
<span class="sd"> """</span> <span class="c1"># noqa: E501</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">background</span> <span class="o">=</span> <span class="n">BackgroundQueue</span><span class="p">()</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">secret_key</span> <span class="o">=</span> <span class="n">secret_key</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">router</span> <span class="o">=</span> <span class="n">Router</span><span class="p">(</span><span class="n">lifespan</span><span class="o">=</span><span class="n">lifespan</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">static_dir</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">static_route</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">static_route</span> <span class="o">=</span> <span class="s2">""</span>
|
||||
<span class="n">static_dir</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">static_dir</span><span class="p">)</span><span class="o">.</span><span class="n">resolve</span><span class="p">()</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">static_dir</span> <span class="o">=</span> <span class="n">static_dir</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">static_route</span> <span class="o">=</span> <span class="n">static_route</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">hsts_enabled</span> <span class="o">=</span> <span class="n">enable_hsts</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">cors</span> <span class="o">=</span> <span class="n">cors</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">cors_params</span> <span class="o">=</span> <span class="n">cors_params</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">debug</span> <span class="o">=</span> <span class="n">debug</span>
|
||||
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">allowed_hosts</span><span class="p">:</span>
|
||||
<span class="n">allowed_hosts</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"*"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">allowed_hosts</span> <span class="o">=</span> <span class="n">allowed_hosts</span>
|
||||
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">static_dir</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">static_dir</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">parents</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">exist_ok</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">mount</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">static_route</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">static_app</span><span class="p">)</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">formats</span> <span class="o">=</span> <span class="n">get_formats</span><span class="p">()</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_session</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">default_endpoint</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">app</span> <span class="o">=</span> <span class="n">ExceptionMiddleware</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">router</span><span class="p">,</span> <span class="n">debug</span><span class="o">=</span><span class="n">debug</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">gzip</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">add_middleware</span><span class="p">(</span><span class="n">GZipMiddleware</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">hsts_enabled</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">add_middleware</span><span class="p">(</span><span class="n">HTTPSRedirectMiddleware</span><span class="p">)</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">add_middleware</span><span class="p">(</span><span class="n">TrustedHostMiddleware</span><span class="p">,</span> <span class="n">allowed_hosts</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">allowed_hosts</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">cors</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">add_middleware</span><span class="p">(</span><span class="n">CORSMiddleware</span><span class="p">,</span> <span class="o">**</span><span class="bp">self</span><span class="o">.</span><span class="n">cors_params</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">add_middleware</span><span class="p">(</span><span class="n">ServerErrorMiddleware</span><span class="p">,</span> <span class="n">debug</span><span class="o">=</span><span class="n">debug</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">add_middleware</span><span class="p">(</span><span class="n">SessionMiddleware</span><span class="p">,</span> <span class="n">secret_key</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">secret_key</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">openapi</span> <span class="ow">or</span> <span class="n">docs_route</span><span class="p">:</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">.ext.openapi</span><span class="w"> </span><span class="kn">import</span> <span class="n">OpenAPISchema</span>
|
||||
<span class="k">except</span> <span class="ne">ImportError</span> <span class="k">as</span> <span class="n">ex</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">ImportError</span><span class="p">(</span>
|
||||
<span class="s2">"The dependencies for the OpenAPI extension are not installed. "</span>
|
||||
<span class="s2">"Install them using: pip install responder"</span>
|
||||
<span class="p">)</span> <span class="kn">from</span><span class="w"> </span><span class="nn">ex</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">openapi</span> <span class="o">=</span> <span class="n">OpenAPISchema</span><span class="p">(</span>
|
||||
<span class="n">app</span><span class="o">=</span><span class="bp">self</span><span class="p">,</span>
|
||||
<span class="n">title</span><span class="o">=</span><span class="n">title</span><span class="p">,</span>
|
||||
<span class="n">version</span><span class="o">=</span><span class="n">version</span><span class="p">,</span>
|
||||
<span class="n">openapi</span><span class="o">=</span><span class="n">openapi</span><span class="p">,</span>
|
||||
<span class="n">docs_route</span><span class="o">=</span><span class="n">docs_route</span><span class="p">,</span>
|
||||
<span class="n">description</span><span class="o">=</span><span class="n">description</span><span class="p">,</span>
|
||||
<span class="n">terms_of_service</span><span class="o">=</span><span class="n">terms_of_service</span><span class="p">,</span>
|
||||
<span class="n">contact</span><span class="o">=</span><span class="n">contact</span><span class="p">,</span>
|
||||
<span class="n">license</span><span class="o">=</span><span class="n">license</span><span class="p">,</span>
|
||||
<span class="n">openapi_route</span><span class="o">=</span><span class="n">openapi_route</span><span class="p">,</span>
|
||||
<span class="n">static_route</span><span class="o">=</span><span class="n">static_route</span><span class="p">,</span>
|
||||
<span class="n">openapi_theme</span><span class="o">=</span><span class="n">openapi_theme</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">templates</span> <span class="o">=</span> <span class="n">Templates</span><span class="p">(</span><span class="n">directory</span><span class="o">=</span><span class="n">templates_dir</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">request_id</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">enable_logging</span><span class="p">:</span>
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">uuid</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="nn">_uuid</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">_add_request_id</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="n">rid</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="n">headers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"X-Request-ID"</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">_uuid</span><span class="o">.</span><span class="n">uuid4</span><span class="p">()))</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"X-Request-ID"</span><span class="p">]</span> <span class="o">=</span> <span class="n">rid</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">router</span><span class="o">.</span><span class="n">after_request</span><span class="p">(</span><span class="n">_add_request_id</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">enable_logging</span><span class="p">:</span>
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">logging</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="nn">_logging</span>
|
||||
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">.ext.logging</span><span class="w"> </span><span class="kn">import</span> <span class="n">LoggingMiddleware</span><span class="p">,</span> <span class="n">get_logger</span><span class="p">,</span> <span class="n">setup_logging</span>
|
||||
|
||||
<span class="n">log_level</span> <span class="o">=</span> <span class="n">_logging</span><span class="o">.</span><span class="n">DEBUG</span> <span class="k">if</span> <span class="n">debug</span> <span class="k">else</span> <span class="n">_logging</span><span class="o">.</span><span class="n">INFO</span>
|
||||
<span class="n">setup_logging</span><span class="p">(</span><span class="n">level</span><span class="o">=</span><span class="n">log_level</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">add_middleware</span><span class="p">(</span><span class="n">LoggingMiddleware</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">log</span> <span class="o">=</span> <span class="n">get_logger</span><span class="p">(</span><span class="s2">"responder.app"</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">logging</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="nn">_logging</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">log</span> <span class="o">=</span> <span class="n">_logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s2">"responder.app"</span><span class="p">)</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">requests</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""A test client connected to the ASGI app. Lazily initialized."""</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">session</span><span class="p">()</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">static_app</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""The Starlette ``StaticFiles`` application for serving static assets."""</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="nb">hasattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="s2">"_static_app"</span><span class="p">):</span>
|
||||
<span class="k">assert</span> <span class="bp">self</span><span class="o">.</span><span class="n">static_dir</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_static_app</span> <span class="o">=</span> <span class="n">StaticFiles</span><span class="p">(</span><span class="n">directory</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">static_dir</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_static_app</span>
|
||||
|
||||
<div class="viewcode-block" id="API.before_request">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API.before_request">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">before_request</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">websocket</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Register a function to run before every request.</span>
|
||||
|
||||
<span class="sd"> If the hook sets ``resp.status_code``, the route handler is skipped</span>
|
||||
<span class="sd"> and the response is sent immediately (short-circuiting).</span>
|
||||
|
||||
<span class="sd"> :param websocket: If ``True``, register as a WebSocket before-request hook instead of HTTP.</span>
|
||||
|
||||
<span class="sd"> Usage::</span>
|
||||
|
||||
<span class="sd"> @api.before_request()</span>
|
||||
<span class="sd"> def check_auth(req, resp):</span>
|
||||
<span class="sd"> if "Authorization" not in req.headers:</span>
|
||||
<span class="sd"> resp.status_code = 401</span>
|
||||
<span class="sd"> resp.media = {"error": "unauthorized"}</span>
|
||||
|
||||
<span class="sd"> """</span> <span class="c1"># noqa: E501</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">decorator</span><span class="p">(</span><span class="n">f</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">router</span><span class="o">.</span><span class="n">before_request</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="n">websocket</span><span class="o">=</span><span class="n">websocket</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="n">f</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">decorator</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="API.after_request">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API.after_request">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">after_request</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Register a function to run after every request.</span>
|
||||
|
||||
<span class="sd"> Usage::</span>
|
||||
|
||||
<span class="sd"> @api.after_request()</span>
|
||||
<span class="sd"> def add_request_id(req, resp):</span>
|
||||
<span class="sd"> resp.headers["X-Request-ID"] = str(uuid.uuid4())</span>
|
||||
|
||||
<span class="sd"> """</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">decorator</span><span class="p">(</span><span class="n">f</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">router</span><span class="o">.</span><span class="n">after_request</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="n">f</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">decorator</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="API.add_middleware">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API.add_middleware">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">add_middleware</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">middleware_cls</span><span class="p">,</span> <span class="o">**</span><span class="n">middleware_config</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Add ASGI middleware to the application.</span>
|
||||
|
||||
<span class="sd"> Middleware wraps the entire application and can inspect or modify</span>
|
||||
<span class="sd"> every request and response. Middleware is applied in reverse order —</span>
|
||||
<span class="sd"> the last middleware added runs first.</span>
|
||||
|
||||
<span class="sd"> :param middleware_cls: A Starlette-compatible middleware class.</span>
|
||||
<span class="sd"> :param middleware_config: Keyword arguments passed to the middleware constructor.</span>
|
||||
|
||||
<span class="sd"> Usage::</span>
|
||||
|
||||
<span class="sd"> from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware</span>
|
||||
<span class="sd"> api.add_middleware(HTTPSRedirectMiddleware)</span>
|
||||
|
||||
<span class="sd"> """</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">app</span> <span class="o">=</span> <span class="n">middleware_cls</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">app</span><span class="p">,</span> <span class="o">**</span><span class="n">middleware_config</span><span class="p">)</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="API.exception_handler">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API.exception_handler">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">exception_handler</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">exception_cls</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Register a handler for a specific exception type.</span>
|
||||
|
||||
<span class="sd"> Usage::</span>
|
||||
|
||||
<span class="sd"> @api.exception_handler(ValueError)</span>
|
||||
<span class="sd"> async def handle_value_error(req, resp, exc):</span>
|
||||
<span class="sd"> resp.status_code = 400</span>
|
||||
<span class="sd"> resp.media = {"error": str(exc)}</span>
|
||||
|
||||
<span class="sd"> """</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">decorator</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">_handler</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">exc</span><span class="p">):</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">starlette.responses</span><span class="w"> </span><span class="kn">import</span> <span class="n">Response</span> <span class="k">as</span> <span class="n">StarletteResp</span>
|
||||
|
||||
<span class="n">req</span> <span class="o">=</span> <span class="n">Request</span><span class="p">(</span><span class="n">request</span><span class="o">.</span><span class="n">scope</span><span class="p">,</span> <span class="n">request</span><span class="o">.</span><span class="n">receive</span><span class="p">,</span> <span class="n">formats</span><span class="o">=</span><span class="n">get_formats</span><span class="p">())</span>
|
||||
<span class="n">resp</span> <span class="o">=</span> <span class="n">Response</span><span class="p">(</span><span class="n">req</span><span class="o">=</span><span class="n">req</span><span class="p">,</span> <span class="n">formats</span><span class="o">=</span><span class="n">get_formats</span><span class="p">())</span>
|
||||
<span class="k">if</span> <span class="n">inspect</span><span class="o">.</span><span class="n">iscoroutinefunction</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
|
||||
<span class="k">await</span> <span class="n">func</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="n">exc</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">func</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="n">exc</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mi">500</span>
|
||||
<span class="n">body</span><span class="p">,</span> <span class="n">headers</span> <span class="o">=</span> <span class="k">await</span> <span class="n">resp</span><span class="o">.</span><span class="n">body</span>
|
||||
<span class="k">return</span> <span class="n">StarletteResp</span><span class="p">(</span>
|
||||
<span class="n">content</span><span class="o">=</span><span class="n">body</span><span class="p">,</span> <span class="n">status_code</span><span class="o">=</span><span class="n">resp</span><span class="o">.</span><span class="n">status_code</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># Register with the ExceptionMiddleware</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">router</span><span class="o">.</span><span class="n">_exception_handlers</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">router</span><span class="p">,</span> <span class="s2">"_exception_handlers"</span><span class="p">,</span> <span class="p">{}</span>
|
||||
<span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">router</span><span class="o">.</span><span class="n">_exception_handlers</span><span class="p">[</span><span class="n">exception_cls</span><span class="p">]</span> <span class="o">=</span> <span class="n">_handler</span>
|
||||
<span class="c1"># Also register on the ASGI app chain</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">starlette.middleware.exceptions</span><span class="w"> </span><span class="kn">import</span> <span class="n">ExceptionMiddleware</span> <span class="k">as</span> <span class="n">EM</span>
|
||||
|
||||
<span class="n">app</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">app</span>
|
||||
<span class="k">while</span> <span class="n">app</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">EM</span><span class="p">):</span>
|
||||
<span class="n">app</span><span class="o">.</span><span class="n">add_exception_handler</span><span class="p">(</span><span class="n">exception_cls</span><span class="p">,</span> <span class="n">_handler</span><span class="p">)</span>
|
||||
<span class="k">break</span>
|
||||
<span class="n">app</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="s2">"app"</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="n">func</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">decorator</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="API.schema">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API.schema">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">schema</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="o">**</span><span class="n">options</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""</span>
|
||||
<span class="sd"> Decorator for creating new routes around function and class definitions.</span>
|
||||
|
||||
<span class="sd"> Usage::</span>
|
||||
|
||||
<span class="sd"> from marshmallow import Schema, fields</span>
|
||||
<span class="sd"> @api.schema("Pet")</span>
|
||||
<span class="sd"> class PetSchema(Schema):</span>
|
||||
<span class="sd"> name = fields.Str()</span>
|
||||
<span class="sd"> """</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">decorator</span><span class="p">(</span><span class="n">f</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">openapi</span><span class="o">.</span><span class="n">add_schema</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="n">name</span><span class="p">,</span> <span class="n">schema</span><span class="o">=</span><span class="n">f</span><span class="p">,</span> <span class="o">**</span><span class="n">options</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="n">f</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">decorator</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="API.path_matches_route">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API.path_matches_route">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">path_matches_route</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Given a path portion of a URL, tests that it matches against any registered route.</span>
|
||||
|
||||
<span class="sd"> :param path: The path portion of a URL, to test all known routes against.</span>
|
||||
<span class="sd"> """</span> <span class="c1"># noqa: E501 (Line too long)</span>
|
||||
<span class="k">for</span> <span class="n">route</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">router</span><span class="o">.</span><span class="n">routes</span><span class="p">:</span>
|
||||
<span class="n">match</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="n">route</span><span class="o">.</span><span class="n">matches</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">match</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="n">route</span>
|
||||
<span class="k">return</span> <span class="kc">None</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="API.add_route">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API.add_route">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">add_route</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="p">,</span>
|
||||
<span class="n">route</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">endpoint</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="o">*</span><span class="p">,</span>
|
||||
<span class="n">default</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
|
||||
<span class="n">static</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
|
||||
<span class="n">check_existing</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
|
||||
<span class="n">websocket</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
|
||||
<span class="n">before_request</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
|
||||
<span class="n">methods</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Adds a route to the API.</span>
|
||||
|
||||
<span class="sd"> :param route: A string representation of the route.</span>
|
||||
<span class="sd"> :param endpoint: The endpoint for the route -- can be a callable, or a class.</span>
|
||||
<span class="sd"> :param default: If ``True``, all unknown requests will route to this view.</span>
|
||||
<span class="sd"> :param static: If ``True``, and no endpoint was passed, render "static/index.html".</span>
|
||||
<span class="sd"> Also, it will become a default route.</span>
|
||||
<span class="sd"> :param methods: Optional list of HTTP methods (e.g. ``["GET", "POST"]``).</span>
|
||||
<span class="sd"> """</span> <span class="c1"># noqa: E501</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">static</span><span class="p">:</span>
|
||||
<span class="k">assert</span> <span class="bp">self</span><span class="o">.</span><span class="n">static_dir</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">endpoint</span><span class="p">:</span>
|
||||
<span class="n">endpoint</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_static_response</span>
|
||||
<span class="n">default</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">router</span><span class="o">.</span><span class="n">add_route</span><span class="p">(</span>
|
||||
<span class="n">route</span><span class="p">,</span>
|
||||
<span class="n">endpoint</span><span class="p">,</span>
|
||||
<span class="n">default</span><span class="o">=</span><span class="n">default</span><span class="p">,</span>
|
||||
<span class="n">websocket</span><span class="o">=</span><span class="n">websocket</span><span class="p">,</span>
|
||||
<span class="n">before_request</span><span class="o">=</span><span class="n">before_request</span><span class="p">,</span>
|
||||
<span class="n">check_existing</span><span class="o">=</span><span class="n">check_existing</span><span class="p">,</span>
|
||||
<span class="n">methods</span><span class="o">=</span><span class="n">methods</span><span class="p">,</span>
|
||||
<span class="p">)</span></div>
|
||||
|
||||
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">_static_response</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="k">assert</span> <span class="bp">self</span><span class="o">.</span><span class="n">static_dir</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span>
|
||||
|
||||
<span class="n">index</span> <span class="o">=</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">static_dir</span> <span class="o">/</span> <span class="s2">"index.html"</span><span class="p">)</span><span class="o">.</span><span class="n">resolve</span><span class="p">()</span>
|
||||
<span class="k">if</span> <span class="n">index</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">html</span> <span class="o">=</span> <span class="n">index</span><span class="o">.</span><span class="n">read_text</span><span class="p">()</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="n">status_codes</span><span class="o">.</span><span class="n">HTTP_404</span> <span class="c1"># type: ignore[attr-defined]</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="s2">"Not found."</span>
|
||||
|
||||
<div class="viewcode-block" id="API.redirect">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API.redirect">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">redirect</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="p">,</span>
|
||||
<span class="n">resp</span><span class="p">,</span>
|
||||
<span class="n">location</span><span class="p">,</span>
|
||||
<span class="o">*</span><span class="p">,</span>
|
||||
<span class="n">set_text</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
|
||||
<span class="n">status_code</span><span class="o">=</span><span class="n">status_codes</span><span class="o">.</span><span class="n">HTTP_301</span><span class="p">,</span> <span class="c1"># type: ignore[attr-defined]</span>
|
||||
<span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""</span>
|
||||
<span class="sd"> Redirects a given response to a given location.</span>
|
||||
|
||||
<span class="sd"> :param resp: The Response to mutate.</span>
|
||||
<span class="sd"> :param location: The location of the redirect.</span>
|
||||
<span class="sd"> :param set_text: If ``True``, sets the Redirect body content automatically.</span>
|
||||
<span class="sd"> :param status_code: an `API.status_codes` attribute, or an integer,</span>
|
||||
<span class="sd"> representing the HTTP status code of the redirect.</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">redirect</span><span class="p">(</span><span class="n">location</span><span class="p">,</span> <span class="n">set_text</span><span class="o">=</span><span class="n">set_text</span><span class="p">,</span> <span class="n">status_code</span><span class="o">=</span><span class="n">status_code</span><span class="p">)</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="API.on_event">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API.on_event">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">on_event</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">event_type</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="o">**</span><span class="n">args</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Decorator for registering functions or coroutines to run at certain events</span>
|
||||
<span class="sd"> Supported events: startup, shutdown</span>
|
||||
|
||||
<span class="sd"> Usage::</span>
|
||||
|
||||
<span class="sd"> @api.on_event('startup')</span>
|
||||
<span class="sd"> async def open_database_connection_pool():</span>
|
||||
<span class="sd"> ...</span>
|
||||
|
||||
<span class="sd"> @api.on_event('shutdown')</span>
|
||||
<span class="sd"> async def close_database_connection_pool():</span>
|
||||
<span class="sd"> ...</span>
|
||||
|
||||
<span class="sd"> """</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">decorator</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">add_event_handler</span><span class="p">(</span><span class="n">event_type</span><span class="p">,</span> <span class="n">func</span><span class="p">,</span> <span class="o">**</span><span class="n">args</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="n">func</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">decorator</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="API.add_event_handler">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API.add_event_handler">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">add_event_handler</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">event_type</span><span class="p">,</span> <span class="n">handler</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Adds an event handler to the API.</span>
|
||||
|
||||
<span class="sd"> :param event_type: A string in ("startup", "shutdown")</span>
|
||||
<span class="sd"> :param handler: The function to run. Can be either a function or a coroutine.</span>
|
||||
<span class="sd"> """</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">router</span><span class="o">.</span><span class="n">add_event_handler</span><span class="p">(</span><span class="n">event_type</span><span class="p">,</span> <span class="n">handler</span><span class="p">)</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="API.route">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API.route">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">route</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">route</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">request_model</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">response_model</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="o">**</span><span class="n">options</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Decorator for creating new routes around function and class definitions.</span>
|
||||
|
||||
<span class="sd"> Usage::</span>
|
||||
|
||||
<span class="sd"> @api.route("/hello")</span>
|
||||
<span class="sd"> def hello(req, resp):</span>
|
||||
<span class="sd"> resp.text = "hello, world!"</span>
|
||||
|
||||
<span class="sd"> With Pydantic models for OpenAPI documentation::</span>
|
||||
|
||||
<span class="sd"> from pydantic import BaseModel</span>
|
||||
|
||||
<span class="sd"> class ItemIn(BaseModel):</span>
|
||||
<span class="sd"> name: str</span>
|
||||
<span class="sd"> price: float</span>
|
||||
|
||||
<span class="sd"> class ItemOut(BaseModel):</span>
|
||||
<span class="sd"> id: int</span>
|
||||
<span class="sd"> name: str</span>
|
||||
<span class="sd"> price: float</span>
|
||||
|
||||
<span class="sd"> @api.route("/items", methods=["POST"],</span>
|
||||
<span class="sd"> request_model=ItemIn, response_model=ItemOut)</span>
|
||||
<span class="sd"> async def create_item(req, resp):</span>
|
||||
<span class="sd"> data = await req.media()</span>
|
||||
<span class="sd"> resp.media = {"id": 1, **data}</span>
|
||||
|
||||
<span class="sd"> """</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">decorator</span><span class="p">(</span><span class="n">f</span><span class="p">):</span>
|
||||
<span class="k">if</span> <span class="n">request_model</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">f</span><span class="o">.</span><span class="n">_request_model</span> <span class="o">=</span> <span class="n">request_model</span>
|
||||
<span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="s2">"openapi"</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">openapi</span><span class="o">.</span><span class="n">add_schema</span><span class="p">(</span>
|
||||
<span class="n">request_model</span><span class="o">.</span><span class="vm">__name__</span><span class="p">,</span> <span class="n">request_model</span><span class="p">,</span> <span class="n">check_existing</span><span class="o">=</span><span class="kc">False</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">response_model</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">f</span><span class="o">.</span><span class="n">_response_model</span> <span class="o">=</span> <span class="n">response_model</span>
|
||||
<span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="s2">"openapi"</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">openapi</span><span class="o">.</span><span class="n">add_schema</span><span class="p">(</span>
|
||||
<span class="n">response_model</span><span class="o">.</span><span class="vm">__name__</span><span class="p">,</span> <span class="n">response_model</span><span class="p">,</span> <span class="n">check_existing</span><span class="o">=</span><span class="kc">False</span>
|
||||
<span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">add_route</span><span class="p">(</span><span class="n">route</span><span class="p">,</span> <span class="n">f</span><span class="p">,</span> <span class="o">**</span><span class="n">options</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="n">f</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">decorator</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="API.graphql">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API.graphql">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">graphql</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">route</span><span class="o">=</span><span class="s2">"/graphql"</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">schema</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Mount a GraphQL API at the given route.</span>
|
||||
|
||||
<span class="sd"> Usage::</span>
|
||||
|
||||
<span class="sd"> import graphene</span>
|
||||
|
||||
<span class="sd"> class Query(graphene.ObjectType):</span>
|
||||
<span class="sd"> hello = graphene.String(name=graphene.String(default_value="stranger"))</span>
|
||||
<span class="sd"> def resolve_hello(self, info, name):</span>
|
||||
<span class="sd"> return f"Hello {name}"</span>
|
||||
|
||||
<span class="sd"> api.graphql("/graphql", schema=graphene.Schema(query=Query))</span>
|
||||
|
||||
<span class="sd"> :param route: The URL path for the GraphQL endpoint.</span>
|
||||
<span class="sd"> :param schema: A Graphene schema instance.</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">.ext.graphql</span><span class="w"> </span><span class="kn">import</span> <span class="n">GraphQLView</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">add_route</span><span class="p">(</span><span class="n">route</span><span class="p">,</span> <span class="n">GraphQLView</span><span class="p">(</span><span class="n">api</span><span class="o">=</span><span class="bp">self</span><span class="p">,</span> <span class="n">schema</span><span class="o">=</span><span class="n">schema</span><span class="p">))</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="API.mount">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API.mount">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">mount</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">route</span><span class="p">,</span> <span class="n">app</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Mounts an WSGI / ASGI application at a given route.</span>
|
||||
|
||||
<span class="sd"> :param route: String representation of the route to be used</span>
|
||||
<span class="sd"> (shouldn't be parameterized).</span>
|
||||
<span class="sd"> :param app: The other WSGI / ASGI app.</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">router</span><span class="o">.</span><span class="n">apps</span><span class="o">.</span><span class="n">update</span><span class="p">({</span><span class="n">route</span><span class="p">:</span> <span class="n">app</span><span class="p">})</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="API.session">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API.session">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">session</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">base_url</span><span class="o">=</span><span class="s2">"http://;"</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Testing HTTP client. Returns a Starlette TestClient instance,</span>
|
||||
<span class="sd"> able to send HTTP requests to the Responder application.</span>
|
||||
|
||||
<span class="sd"> :param base_url: The base URL for the test client.</span>
|
||||
<span class="sd"> """</span>
|
||||
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_session</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">starlette.testclient</span><span class="w"> </span><span class="kn">import</span> <span class="n">TestClient</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_session</span> <span class="o">=</span> <span class="n">TestClient</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">base_url</span><span class="o">=</span><span class="n">base_url</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_session</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="API.url_for">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API.url_for">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">url_for</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">endpoint</span><span class="p">,</span> <span class="o">**</span><span class="n">params</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Given an endpoint, returns a rendered URL for its route.</span>
|
||||
|
||||
<span class="sd"> :param endpoint: The route endpoint you're searching for.</span>
|
||||
<span class="sd"> :param params: Data to pass into the URL generator (for parameterized URLs).</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">router</span><span class="o">.</span><span class="n">url_for</span><span class="p">(</span><span class="n">endpoint</span><span class="p">,</span> <span class="o">**</span><span class="n">params</span><span class="p">)</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="API.template">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API.template">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">template</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">filename</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sa">r</span><span class="sd">"""Render a Jinja2 template file with the provided values.</span>
|
||||
|
||||
<span class="sd"> :param filename: The filename of the jinja2 template, in ``templates_dir``.</span>
|
||||
<span class="sd"> :param \*args: Data to pass into the template.</span>
|
||||
<span class="sd"> :param \*\*kwargs: Data to pass into the template.</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">templates</span><span class="o">.</span><span class="n">render</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="API.template_string">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API.template_string">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">template_string</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">source</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sa">r</span><span class="sd">"""Render a Jinja2 template string with the provided values.</span>
|
||||
|
||||
<span class="sd"> :param source: The template to use, a Jinja2 template string.</span>
|
||||
<span class="sd"> :param \*args: Data to pass into the template.</span>
|
||||
<span class="sd"> :param \*\*kwargs: Data to pass into the template.</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">templates</span><span class="o">.</span><span class="n">render_string</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="API.serve">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API.serve">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">serve</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">address</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">debug</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="o">**</span><span class="n">options</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""</span>
|
||||
<span class="sd"> Run the application with uvicorn.</span>
|
||||
|
||||
<span class="sd"> If the ``PORT`` environment variable is set, requests will be served on that port</span>
|
||||
<span class="sd"> automatically to all known hosts.</span>
|
||||
|
||||
<span class="sd"> :param address: The address to bind to.</span>
|
||||
<span class="sd"> :param port: The port to bind to. If none is provided, one will be selected at random.</span>
|
||||
<span class="sd"> :param debug: Whether to run application in debug mode.</span>
|
||||
<span class="sd"> :param options: Additional keyword arguments to send to ``uvicorn.run()``.</span>
|
||||
<span class="sd"> """</span> <span class="c1"># noqa: E501</span>
|
||||
|
||||
<span class="k">if</span> <span class="s2">"PORT"</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">address</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">address</span> <span class="o">=</span> <span class="s2">"0.0.0.0"</span> <span class="c1"># noqa: S104</span>
|
||||
<span class="n">port</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">"PORT"</span><span class="p">])</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">address</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">address</span> <span class="o">=</span> <span class="s2">"127.0.0.1"</span>
|
||||
<span class="k">if</span> <span class="n">port</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">port</span> <span class="o">=</span> <span class="mi">5042</span>
|
||||
<span class="k">if</span> <span class="n">debug</span><span class="p">:</span>
|
||||
<span class="n">options</span><span class="p">[</span><span class="s2">"log_level"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"debug"</span>
|
||||
|
||||
<span class="n">uvicorn</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">host</span><span class="o">=</span><span class="n">address</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="n">port</span><span class="p">,</span> <span class="o">**</span><span class="n">options</span><span class="p">)</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="API.run">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API.run">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Run the application. Shorthand for :meth:`serve` that inherits the ``debug`` setting.</span>
|
||||
|
||||
<span class="sd"> :param kwargs: Keyword arguments passed through to :meth:`serve`.</span>
|
||||
<span class="sd"> """</span> <span class="c1"># noqa: E501</span>
|
||||
<span class="k">if</span> <span class="s2">"debug"</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">kwargs</span><span class="p">:</span>
|
||||
<span class="n">kwargs</span><span class="o">.</span><span class="n">update</span><span class="p">({</span><span class="s2">"debug"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">debug</span><span class="p">})</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">serve</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="API.group">
|
||||
<a class="viewcode-back" href="../../api.html#responder.API.group">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">group</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">prefix</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Create a route group with a shared URL prefix.</span>
|
||||
|
||||
<span class="sd"> Usage::</span>
|
||||
|
||||
<span class="sd"> v1 = api.group("/v1")</span>
|
||||
|
||||
<span class="sd"> @v1.route("/users")</span>
|
||||
<span class="sd"> def list_users(req, resp):</span>
|
||||
<span class="sd"> resp.media = []</span>
|
||||
|
||||
<span class="sd"> @v1.route("/users/{id:int}")</span>
|
||||
<span class="sd"> def get_user(req, resp, *, id):</span>
|
||||
<span class="sd"> resp.media = {"id": id}</span>
|
||||
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">return</span> <span class="n">RouteGroup</span><span class="p">(</span><span class="n">api</span><span class="o">=</span><span class="bp">self</span><span class="p">,</span> <span class="n">prefix</span><span class="o">=</span><span class="n">prefix</span><span class="p">)</span></div>
|
||||
|
||||
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="fm">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">scope</span><span class="p">,</span> <span class="n">receive</span><span class="p">,</span> <span class="n">send</span><span class="p">):</span>
|
||||
<span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">app</span><span class="p">(</span><span class="n">scope</span><span class="p">,</span> <span class="n">receive</span><span class="p">,</span> <span class="n">send</span><span class="p">)</span></div>
|
||||
|
||||
|
||||
|
||||
<div class="viewcode-block" id="RouteGroup">
|
||||
<a class="viewcode-back" href="../../api.html#responder.RouteGroup">[docs]</a>
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">RouteGroup</span><span class="p">:</span>
|
||||
<span class="w"> </span><span class="sd">"""A group of routes with a shared URL prefix."""</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">api</span><span class="p">,</span> <span class="n">prefix</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">api</span> <span class="o">=</span> <span class="n">api</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">prefix</span> <span class="o">=</span> <span class="n">prefix</span><span class="o">.</span><span class="n">rstrip</span><span class="p">(</span><span class="s2">"/"</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">route</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">route</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="o">**</span><span class="n">options</span><span class="p">):</span>
|
||||
<span class="n">full_route</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">prefix</span><span class="si">}{</span><span class="n">route</span><span class="si">}</span><span class="s2">"</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="n">full_route</span><span class="p">,</span> <span class="o">**</span><span class="n">options</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">before_request</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">api</span><span class="o">.</span><span class="n">before_request</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span></div>
|
||||
|
||||
</pre></div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="sphinxsidebar" role="navigation" aria-label="Main">
|
||||
<div class="sphinxsidebarwrapper"><p class="logo">
|
||||
<a href="../../index.html">
|
||||
<img class="logo" src="../../_static/responder.png" />
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Responder</strong> — a familiar HTTP service framework for Python.
|
||||
<br />
|
||||
<small>v3.6.2</small>
|
||||
</p>
|
||||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
|
||||
<li><a href="https://pypi.org/project/responder/">Responder @ PyPI</a></li>
|
||||
<li><a href="https://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
|
||||
</ul>
|
||||
<search id="searchbox" style="display: none" role="search">
|
||||
<h3 id="searchlabel">Quick search</h3>
|
||||
<div class="searchformwrapper">
|
||||
<form class="search" action="../../search.html" method="get">
|
||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
||||
<input type="submit" value="Go" />
|
||||
</form>
|
||||
</div>
|
||||
</search>
|
||||
<script>document.getElementById('searchbox').style.display = "block"</script>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
©2018-2026, Kenneth Reitz.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,178 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" data-content_root="../../">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>responder.background — responder 3.6.2 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css?v=5ecbeea2" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/basic.css?v=b08954a9" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/alabaster.css?v=27fed22d" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/design-elements.e5416f61bae5d36adc6d722a2b6f8cff.css?v=452a8e97" />
|
||||
<script src="../../_static/documentation_options.js?v=c0c9fa11"></script>
|
||||
<script src="../../_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="../../_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="../../_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="../../_static/copybutton.js?v=fd10adb8"></script>
|
||||
<script>
|
||||
</script>
|
||||
<script src="../../_static/design-elements.bbdccc18c4abea9397628f9fea3d48c2.js?v=03c7770e"></script>
|
||||
<link rel="index" title="Index" href="../../genindex.html" />
|
||||
<link rel="search" title="Search" href="../../search.html" />
|
||||
|
||||
<link rel="stylesheet" href="../../_static/custom.css" type="text/css" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</head><body>
|
||||
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
|
||||
|
||||
<div class="body" role="main">
|
||||
|
||||
<h1>Source code for responder.background</h1><div class="highlight"><pre>
|
||||
<span></span><span class="kn">import</span><span class="w"> </span><span class="nn">asyncio</span>
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">concurrent.futures</span>
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">inspect</span>
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">multiprocessing</span>
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">traceback</span>
|
||||
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">starlette.concurrency</span><span class="w"> </span><span class="kn">import</span> <span class="n">run_in_threadpool</span>
|
||||
|
||||
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"BackgroundQueue"</span><span class="p">]</span>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="BackgroundQueue">
|
||||
<a class="viewcode-back" href="../../api.html#responder.background.BackgroundQueue">[docs]</a>
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">BackgroundQueue</span><span class="p">:</span>
|
||||
<span class="w"> </span><span class="sd">"""A queue for running tasks in background threads.</span>
|
||||
|
||||
<span class="sd"> Uses a ``ThreadPoolExecutor`` sized to the number of CPUs. Access it</span>
|
||||
<span class="sd"> via ``api.background``.</span>
|
||||
|
||||
<span class="sd"> Usage::</span>
|
||||
|
||||
<span class="sd"> # As a decorator — fire and forget</span>
|
||||
<span class="sd"> @api.background.task</span>
|
||||
<span class="sd"> def send_email(to, subject):</span>
|
||||
<span class="sd"> ...</span>
|
||||
|
||||
<span class="sd"> send_email("user@example.com", "Hello")</span>
|
||||
|
||||
<span class="sd"> # Direct submission</span>
|
||||
<span class="sd"> future = api.background.run(send_email, "user@example.com", "Hello")</span>
|
||||
|
||||
<span class="sd"> # As a callable (supports async functions)</span>
|
||||
<span class="sd"> await api.background(send_email, "user@example.com", "Hello")</span>
|
||||
|
||||
<span class="sd"> """</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">n</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Create a new background queue.</span>
|
||||
|
||||
<span class="sd"> :param n: Number of worker threads. Defaults to CPU count.</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">if</span> <span class="n">n</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">n</span> <span class="o">=</span> <span class="n">multiprocessing</span><span class="o">.</span><span class="n">cpu_count</span><span class="p">()</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">n</span> <span class="o">=</span> <span class="n">n</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">pool</span> <span class="o">=</span> <span class="n">concurrent</span><span class="o">.</span><span class="n">futures</span><span class="o">.</span><span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">n</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
|
||||
<div class="viewcode-block" id="BackgroundQueue.run">
|
||||
<a class="viewcode-back" href="../../api.html#responder.background.BackgroundQueue.run">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">f</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Submit a function to run in a background thread.</span>
|
||||
|
||||
<span class="sd"> :param f: The function to run.</span>
|
||||
<span class="sd"> :returns: A ``concurrent.futures.Future`` for the result.</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="n">f</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">pool</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="n">f</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="BackgroundQueue.task">
|
||||
<a class="viewcode-back" href="../../api.html#responder.background.BackgroundQueue.task">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">task</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">f</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Decorator that wraps a function to run in the background thread pool.</span>
|
||||
|
||||
<span class="sd"> The decorated function returns a ``Future`` instead of blocking.</span>
|
||||
<span class="sd"> Exceptions are printed to stderr via traceback.</span>
|
||||
|
||||
<span class="sd"> :param f: The function to wrap.</span>
|
||||
<span class="sd"> """</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">on_future_done</span><span class="p">(</span><span class="n">fs</span><span class="p">):</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">fs</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
|
||||
<span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
|
||||
<span class="n">traceback</span><span class="o">.</span><span class="n">print_exc</span><span class="p">()</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">do_task</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||||
<span class="n">result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
|
||||
<span class="n">result</span><span class="o">.</span><span class="n">add_done_callback</span><span class="p">(</span><span class="n">on_future_done</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="n">result</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">do_task</span></div>
|
||||
|
||||
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="fm">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">func</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">inspect</span><span class="o">.</span><span class="n">iscoroutinefunction</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">))</span>
|
||||
<span class="k">return</span> <span class="k">await</span> <span class="n">run_in_threadpool</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span></div>
|
||||
|
||||
</pre></div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="sphinxsidebar" role="navigation" aria-label="Main">
|
||||
<div class="sphinxsidebarwrapper"><p class="logo">
|
||||
<a href="../../index.html">
|
||||
<img class="logo" src="../../_static/responder.png" />
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Responder</strong> — a familiar HTTP service framework for Python.
|
||||
<br />
|
||||
<small>v3.6.2</small>
|
||||
</p>
|
||||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
|
||||
<li><a href="https://pypi.org/project/responder/">Responder @ PyPI</a></li>
|
||||
<li><a href="https://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
|
||||
</ul>
|
||||
<search id="searchbox" style="display: none" role="search">
|
||||
<h3 id="searchlabel">Quick search</h3>
|
||||
<div class="searchformwrapper">
|
||||
<form class="search" action="../../search.html" method="get">
|
||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
||||
<input type="submit" value="Go" />
|
||||
</form>
|
||||
</div>
|
||||
</search>
|
||||
<script>document.getElementById('searchbox').style.display = "block"</script>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
©2018-2026, Kenneth Reitz.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,168 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" data-content_root="../../../">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>responder.ext.ratelimit — responder 3.6.2 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="../../../_static/pygments.css?v=5ecbeea2" />
|
||||
<link rel="stylesheet" type="text/css" href="../../../_static/basic.css?v=b08954a9" />
|
||||
<link rel="stylesheet" type="text/css" href="../../../_static/alabaster.css?v=27fed22d" />
|
||||
<link rel="stylesheet" type="text/css" href="../../../_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="../../../_static/design-elements.e5416f61bae5d36adc6d722a2b6f8cff.css?v=452a8e97" />
|
||||
<script src="../../../_static/documentation_options.js?v=c0c9fa11"></script>
|
||||
<script src="../../../_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="../../../_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="../../../_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="../../../_static/copybutton.js?v=fd10adb8"></script>
|
||||
<script>
|
||||
</script>
|
||||
<script src="../../../_static/design-elements.bbdccc18c4abea9397628f9fea3d48c2.js?v=03c7770e"></script>
|
||||
<link rel="index" title="Index" href="../../../genindex.html" />
|
||||
<link rel="search" title="Search" href="../../../search.html" />
|
||||
|
||||
<link rel="stylesheet" href="../../../_static/custom.css" type="text/css" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</head><body>
|
||||
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
|
||||
|
||||
<div class="body" role="main">
|
||||
|
||||
<h1>Source code for responder.ext.ratelimit</h1><div class="highlight"><pre>
|
||||
<span></span><span class="sd">"""Simple in-memory rate limiter for Responder."""</span>
|
||||
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">threading</span>
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">time</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">collections</span><span class="w"> </span><span class="kn">import</span> <span class="n">defaultdict</span>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="RateLimiter">
|
||||
<a class="viewcode-back" href="../../../api.html#responder.ext.ratelimit.RateLimiter">[docs]</a>
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">RateLimiter</span><span class="p">:</span>
|
||||
<span class="w"> </span><span class="sd">"""Token bucket rate limiter.</span>
|
||||
|
||||
<span class="sd"> Usage::</span>
|
||||
|
||||
<span class="sd"> from responder.ext.ratelimit import RateLimiter</span>
|
||||
|
||||
<span class="sd"> limiter = RateLimiter(requests=100, period=60) # 100 req/min</span>
|
||||
|
||||
<span class="sd"> @api.route(before_request=True)</span>
|
||||
<span class="sd"> def rate_limit(req, resp):</span>
|
||||
<span class="sd"> limiter.check(req, resp)</span>
|
||||
|
||||
<span class="sd"> Or use the shorthand::</span>
|
||||
|
||||
<span class="sd"> limiter = RateLimiter(requests=100, period=60)</span>
|
||||
<span class="sd"> limiter.install(api)</span>
|
||||
|
||||
<span class="sd"> """</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">requests</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span> <span class="n">period</span><span class="o">=</span><span class="mi">60</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">max_requests</span> <span class="o">=</span> <span class="n">requests</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">period</span> <span class="o">=</span> <span class="n">period</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_buckets</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">list</span><span class="p">[</span><span class="nb">float</span><span class="p">]]</span> <span class="o">=</span> <span class="n">defaultdict</span><span class="p">(</span><span class="nb">list</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_lock</span> <span class="o">=</span> <span class="n">threading</span><span class="o">.</span><span class="n">Lock</span><span class="p">()</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">_client_key</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">req</span><span class="p">):</span>
|
||||
<span class="n">client</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="n">client</span>
|
||||
<span class="k">if</span> <span class="n">client</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="n">client</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
|
||||
<span class="k">return</span> <span class="n">req</span><span class="o">.</span><span class="n">headers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"X-Forwarded-For"</span><span class="p">,</span> <span class="s2">"unknown"</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">_cleanup</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">):</span>
|
||||
<span class="n">now</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
|
||||
<span class="n">cutoff</span> <span class="o">=</span> <span class="n">now</span> <span class="o">-</span> <span class="bp">self</span><span class="o">.</span><span class="n">period</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_buckets</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="n">t</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_buckets</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="k">if</span> <span class="n">t</span> <span class="o">></span> <span class="n">cutoff</span><span class="p">]</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_buckets</span><span class="p">[</span><span class="n">key</span><span class="p">]:</span>
|
||||
<span class="k">del</span> <span class="bp">self</span><span class="o">.</span><span class="n">_buckets</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
|
||||
|
||||
<div class="viewcode-block" id="RateLimiter.check">
|
||||
<a class="viewcode-back" href="../../../api.html#responder.ext.ratelimit.RateLimiter.check">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Check rate limit. Sets 429 status if exceeded."""</span>
|
||||
<span class="n">key</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_client_key</span><span class="p">(</span><span class="n">req</span><span class="p">)</span>
|
||||
|
||||
<span class="k">with</span> <span class="bp">self</span><span class="o">.</span><span class="n">_lock</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_cleanup</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_buckets</span><span class="p">[</span><span class="n">key</span><span class="p">])</span> <span class="o">>=</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_requests</span><span class="p">:</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mi">429</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"error"</span><span class="p">:</span> <span class="s2">"rate limit exceeded"</span><span class="p">}</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Retry-After"</span><span class="p">]</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">period</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="kc">False</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_buckets</span><span class="p">[</span><span class="n">key</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">())</span>
|
||||
<span class="n">remaining</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_requests</span> <span class="o">-</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_buckets</span><span class="p">[</span><span class="n">key</span><span class="p">])</span>
|
||||
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"X-RateLimit-Limit"</span><span class="p">]</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">max_requests</span><span class="p">)</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"X-RateLimit-Remaining"</span><span class="p">]</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">remaining</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="kc">True</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="RateLimiter.install">
|
||||
<a class="viewcode-back" href="../../../api.html#responder.ext.ratelimit.RateLimiter.install">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">install</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">api</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Install as a before_request hook on the API."""</span>
|
||||
|
||||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="n">before_request</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">_rate_limit</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">check</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">)</span></div>
|
||||
</div>
|
||||
|
||||
</pre></div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="sphinxsidebar" role="navigation" aria-label="Main">
|
||||
<div class="sphinxsidebarwrapper"><p class="logo">
|
||||
<a href="../../../index.html">
|
||||
<img class="logo" src="../../../_static/responder.png" />
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Responder</strong> — a familiar HTTP service framework for Python.
|
||||
<br />
|
||||
<small>v3.6.2</small>
|
||||
</p>
|
||||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
|
||||
<li><a href="https://pypi.org/project/responder/">Responder @ PyPI</a></li>
|
||||
<li><a href="https://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
|
||||
</ul>
|
||||
<search id="searchbox" style="display: none" role="search">
|
||||
<h3 id="searchlabel">Quick search</h3>
|
||||
<div class="searchformwrapper">
|
||||
<form class="search" action="../../../search.html" method="get">
|
||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
||||
<input type="submit" value="Go" />
|
||||
</form>
|
||||
</div>
|
||||
</search>
|
||||
<script>document.getElementById('searchbox').style.display = "block"</script>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
©2018-2026, Kenneth Reitz.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,755 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" data-content_root="../../">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>responder.models — responder 3.6.2 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css?v=5ecbeea2" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/basic.css?v=b08954a9" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/alabaster.css?v=27fed22d" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/design-elements.e5416f61bae5d36adc6d722a2b6f8cff.css?v=452a8e97" />
|
||||
<script src="../../_static/documentation_options.js?v=c0c9fa11"></script>
|
||||
<script src="../../_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="../../_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="../../_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="../../_static/copybutton.js?v=fd10adb8"></script>
|
||||
<script>
|
||||
</script>
|
||||
<script src="../../_static/design-elements.bbdccc18c4abea9397628f9fea3d48c2.js?v=03c7770e"></script>
|
||||
<link rel="index" title="Index" href="../../genindex.html" />
|
||||
<link rel="search" title="Search" href="../../search.html" />
|
||||
|
||||
<link rel="stylesheet" href="../../_static/custom.css" type="text/css" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</head><body>
|
||||
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
|
||||
|
||||
<div class="body" role="main">
|
||||
|
||||
<h1>Source code for responder.models</h1><div class="highlight"><pre>
|
||||
<span></span><span class="kn">from</span><span class="w"> </span><span class="nn">__future__</span><span class="w"> </span><span class="kn">import</span> <span class="n">annotations</span>
|
||||
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">functools</span>
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">inspect</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">collections.abc</span><span class="w"> </span><span class="kn">import</span> <span class="n">Callable</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">http.cookies</span><span class="w"> </span><span class="kn">import</span> <span class="n">SimpleCookie</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">urllib.parse</span><span class="w"> </span><span class="kn">import</span> <span class="n">parse_qs</span><span class="p">,</span> <span class="n">urlparse</span>
|
||||
|
||||
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"Request"</span><span class="p">,</span> <span class="s2">"Response"</span><span class="p">,</span> <span class="s2">"QueryDict"</span><span class="p">]</span>
|
||||
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">chardet</span>
|
||||
<span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
|
||||
<span class="n">chardet</span> <span class="o">=</span> <span class="kc">None</span> <span class="c1"># type: ignore[assignment]</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">starlette.requests</span><span class="w"> </span><span class="kn">import</span> <span class="n">Request</span> <span class="k">as</span> <span class="n">StarletteRequest</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">starlette.requests</span><span class="w"> </span><span class="kn">import</span> <span class="n">State</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">starlette.responses</span><span class="w"> </span><span class="kn">import</span> <span class="p">(</span>
|
||||
<span class="n">Response</span> <span class="k">as</span> <span class="n">StarletteResponse</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">starlette.responses</span><span class="w"> </span><span class="kn">import</span> <span class="p">(</span>
|
||||
<span class="n">StreamingResponse</span> <span class="k">as</span> <span class="n">StarletteStreamingResponse</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">.statics</span><span class="w"> </span><span class="kn">import</span> <span class="n">DEFAULT_ENCODING</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">.status_codes</span><span class="w"> </span><span class="kn">import</span> <span class="n">HTTP_301</span> <span class="c1"># type: ignore[attr-defined]</span>
|
||||
|
||||
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">CaseInsensitiveDict</span><span class="p">(</span><span class="nb">dict</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""A case-insensitive dict for HTTP headers."""</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__setitem__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
|
||||
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__setitem__</span><span class="p">(</span><span class="n">key</span><span class="o">.</span><span class="n">lower</span><span class="p">(),</span> <span class="n">value</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__getitem__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__getitem__</span><span class="p">(</span><span class="n">key</span><span class="o">.</span><span class="n">lower</span><span class="p">())</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__delitem__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">):</span>
|
||||
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__delitem__</span><span class="p">(</span><span class="n">key</span><span class="o">.</span><span class="n">lower</span><span class="p">())</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__contains__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__contains__</span><span class="p">(</span><span class="n">key</span><span class="o">.</span><span class="n">lower</span><span class="p">())</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">key</span><span class="o">.</span><span class="n">lower</span><span class="p">(),</span> <span class="n">default</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">pop</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">pop</span><span class="p">(</span><span class="n">key</span><span class="o">.</span><span class="n">lower</span><span class="p">(),</span> <span class="o">*</span><span class="n">args</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">setdefault</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">setdefault</span><span class="p">(</span><span class="n">key</span><span class="o">.</span><span class="n">lower</span><span class="p">(),</span> <span class="n">default</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">update</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||||
<span class="k">if</span> <span class="n">other</span><span class="p">:</span>
|
||||
<span class="k">for</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">other</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
|
||||
<span class="bp">self</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
|
||||
<span class="k">for</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
|
||||
<span class="bp">self</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="QueryDict">
|
||||
<a class="viewcode-back" href="../../api.html#responder.QueryDict">[docs]</a>
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">QueryDict</span><span class="p">(</span><span class="nb">dict</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""A dictionary for query string parameters that handles multi-value keys.</span>
|
||||
|
||||
<span class="sd"> Single-value access returns the last value for a key. Use :meth:`get_list`</span>
|
||||
<span class="sd"> to retrieve all values for a multi-value parameter.</span>
|
||||
<span class="sd"> """</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">query_string</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">parse_qs</span><span class="p">(</span><span class="n">query_string</span><span class="p">))</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__getitem__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""</span>
|
||||
<span class="sd"> Return the last data value for this key, or [] if it's an empty list;</span>
|
||||
<span class="sd"> raise KeyError if not found.</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="n">list_</span> <span class="o">=</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__getitem__</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="n">list_</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
|
||||
<span class="k">except</span> <span class="ne">IndexError</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="p">[]</span>
|
||||
|
||||
<div class="viewcode-block" id="QueryDict.get">
|
||||
<a class="viewcode-back" href="../../api.html#responder.QueryDict.get">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""</span>
|
||||
<span class="sd"> Return the last data value for the passed key. If key doesn't exist</span>
|
||||
<span class="sd"> or value is an empty list, return `default`.</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">val</span> <span class="o">=</span> <span class="bp">self</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
|
||||
<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="n">default</span>
|
||||
<span class="k">if</span> <span class="n">val</span> <span class="o">==</span> <span class="p">[]:</span>
|
||||
<span class="k">return</span> <span class="n">default</span>
|
||||
<span class="k">return</span> <span class="n">val</span></div>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">_get_list</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">force_list</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""</span>
|
||||
<span class="sd"> Return a list of values for the key.</span>
|
||||
|
||||
<span class="sd"> Used internally to manipulate values list. If force_list is True,</span>
|
||||
<span class="sd"> return a new copy of values.</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">values</span> <span class="o">=</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__getitem__</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
|
||||
<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">default</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="p">[]</span>
|
||||
<span class="k">return</span> <span class="n">default</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">force_list</span><span class="p">:</span>
|
||||
<span class="n">values</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">values</span><span class="p">)</span> <span class="k">if</span> <span class="n">values</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="k">else</span> <span class="kc">None</span>
|
||||
<span class="k">return</span> <span class="n">values</span>
|
||||
|
||||
<div class="viewcode-block" id="QueryDict.get_list">
|
||||
<a class="viewcode-back" href="../../api.html#responder.QueryDict.get_list">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">get_list</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""</span>
|
||||
<span class="sd"> Return the list of values for the key. If key doesn't exist, return a</span>
|
||||
<span class="sd"> default value.</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_list</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">default</span><span class="p">,</span> <span class="n">force_list</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="QueryDict.items">
|
||||
<a class="viewcode-back" href="../../api.html#responder.QueryDict.items">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">items</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""</span>
|
||||
<span class="sd"> Yield (key, value) pairs, where value is the last item in the list</span>
|
||||
<span class="sd"> associated with the key.</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">for</span> <span class="n">key</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">:</span>
|
||||
<span class="k">yield</span> <span class="n">key</span><span class="p">,</span> <span class="bp">self</span><span class="p">[</span><span class="n">key</span><span class="p">]</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="QueryDict.items_list">
|
||||
<a class="viewcode-back" href="../../api.html#responder.QueryDict.items_list">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">items_list</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""</span>
|
||||
<span class="sd"> Yield (key, value) pairs, where value is the the list.</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">yield from</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">items</span><span class="p">()</span></div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="viewcode-block" id="Request">
|
||||
<a class="viewcode-back" href="../../api.html#responder.Request">[docs]</a>
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">Request</span><span class="p">:</span>
|
||||
<span class="w"> </span><span class="sd">"""An HTTP request, passed to each view as the first argument.</span>
|
||||
|
||||
<span class="sd"> Provides access to headers, cookies, query parameters, the request body,</span>
|
||||
<span class="sd"> session data, and more. Most properties are synchronous; reading the body</span>
|
||||
<span class="sd"> (via :attr:`content`, :attr:`text`, or :meth:`media`) requires ``await``.</span>
|
||||
<span class="sd"> """</span>
|
||||
|
||||
<span class="vm">__slots__</span> <span class="o">=</span> <span class="p">[</span>
|
||||
<span class="s2">"_starlette"</span><span class="p">,</span>
|
||||
<span class="s2">"formats"</span><span class="p">,</span>
|
||||
<span class="s2">"_headers"</span><span class="p">,</span>
|
||||
<span class="s2">"_encoding"</span><span class="p">,</span>
|
||||
<span class="s2">"api"</span><span class="p">,</span>
|
||||
<span class="s2">"_content"</span><span class="p">,</span>
|
||||
<span class="s2">"_cookies"</span><span class="p">,</span>
|
||||
<span class="p">]</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">scope</span><span class="p">,</span> <span class="n">receive</span><span class="p">,</span> <span class="n">api</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">formats</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_starlette</span> <span class="o">=</span> <span class="n">StarletteRequest</span><span class="p">(</span><span class="n">scope</span><span class="p">,</span> <span class="n">receive</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">formats</span> <span class="o">=</span> <span class="n">formats</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_encoding</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">api</span> <span class="o">=</span> <span class="n">api</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_content</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="n">headers</span><span class="p">:</span> <span class="n">CaseInsensitiveDict</span> <span class="o">=</span> <span class="n">CaseInsensitiveDict</span><span class="p">()</span>
|
||||
<span class="k">for</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_starlette</span><span class="o">.</span><span class="n">headers</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
|
||||
<span class="n">headers</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_headers</span> <span class="o">=</span> <span class="n">headers</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_cookies</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">session</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""The session data, in dict form, from the Request."""</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_starlette</span><span class="o">.</span><span class="n">session</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">headers</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""A case-insensitive dictionary, containing all headers sent in the Request."""</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_headers</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">mimetype</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""The MIME type of the request body, from the ``Content-Type`` header."""</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">headers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"Content-Type"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">is_json</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Returns ``True`` if the request content type is JSON."""</span>
|
||||
<span class="k">return</span> <span class="s2">"json"</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">mimetype</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">method</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""The incoming HTTP method used for the request, lower-cased."""</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_starlette</span><span class="o">.</span><span class="n">method</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">full_url</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""The full URL of the Request, query parameters and all."""</span>
|
||||
<span class="k">return</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_starlette</span><span class="o">.</span><span class="n">url</span><span class="p">)</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">url</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""The parsed URL of the Request."""</span>
|
||||
<span class="k">return</span> <span class="n">urlparse</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">full_url</span><span class="p">)</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">cookies</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""The cookies sent in the Request, as a dictionary."""</span>
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_cookies</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">cookies</span> <span class="o">=</span> <span class="p">{}</span>
|
||||
<span class="n">cookie_header</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">headers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"Cookie"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span>
|
||||
|
||||
<span class="n">bc</span><span class="p">:</span> <span class="n">SimpleCookie</span> <span class="o">=</span> <span class="n">SimpleCookie</span><span class="p">(</span><span class="n">cookie_header</span><span class="p">)</span>
|
||||
<span class="k">for</span> <span class="n">key</span><span class="p">,</span> <span class="n">morsel</span> <span class="ow">in</span> <span class="n">bc</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
|
||||
<span class="n">cookies</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">morsel</span><span class="o">.</span><span class="n">value</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_cookies</span> <span class="o">=</span> <span class="n">cookies</span>
|
||||
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_cookies</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">params</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""A dictionary of the parsed query parameters used for the Request."""</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="n">QueryDict</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">url</span><span class="o">.</span><span class="n">query</span><span class="p">)</span>
|
||||
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="n">QueryDict</span><span class="p">({})</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">path_params</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="nb">dict</span><span class="p">:</span>
|
||||
<span class="w"> </span><span class="sd">"""The path parameters extracted from the URL route."""</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_starlette</span><span class="o">.</span><span class="n">path_params</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">client</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""The client's address as a (host, port) named tuple, or None."""</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_starlette</span><span class="o">.</span><span class="n">client</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">state</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="n">State</span><span class="p">:</span>
|
||||
<span class="w"> </span><span class="sd">"""</span>
|
||||
<span class="sd"> Use the state to store additional information.</span>
|
||||
|
||||
<span class="sd"> This can be a very helpful feature, if you want to hand over</span>
|
||||
<span class="sd"> information from a middelware or a route decorator to the</span>
|
||||
<span class="sd"> actual route handler.</span>
|
||||
|
||||
<span class="sd"> Usage: ``request.state.time_started = time.time()``</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_starlette</span><span class="o">.</span><span class="n">state</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">encoding</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""The encoding of the Request's body. Can be set, manually. Must be awaited."""</span>
|
||||
<span class="c1"># Use the user-set encoding first.</span>
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_encoding</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_encoding</span>
|
||||
|
||||
<span class="k">return</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">apparent_encoding</span>
|
||||
|
||||
<span class="nd">@encoding</span><span class="o">.</span><span class="n">setter</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">encoding</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_encoding</span> <span class="o">=</span> <span class="n">value</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">content</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""The Request body, as bytes. Must be awaited."""</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_content</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_content</span> <span class="o">=</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">_starlette</span><span class="o">.</span><span class="n">body</span><span class="p">()</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_content</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">text</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""The Request body, as unicode. Must be awaited."""</span>
|
||||
<span class="k">return</span> <span class="p">(</span><span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">content</span><span class="p">)</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">encoding</span><span class="p">)</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">declared_encoding</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="k">if</span> <span class="s2">"Encoding"</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">headers</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Encoding"</span><span class="p">]</span>
|
||||
<span class="k">return</span> <span class="kc">None</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">apparent_encoding</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""The apparent encoding, detected automatically. Must be awaited.</span>
|
||||
|
||||
<span class="sd"> Uses chardet for detection if installed, otherwise falls back to UTF-8.</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="n">declared_encoding</span> <span class="o">=</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">declared_encoding</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">declared_encoding</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="n">declared_encoding</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">chardet</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="n">chardet</span><span class="o">.</span><span class="n">detect</span><span class="p">(</span><span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">content</span><span class="p">)[</span><span class="s2">"encoding"</span><span class="p">]</span> <span class="ow">or</span> <span class="n">DEFAULT_ENCODING</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">DEFAULT_ENCODING</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">is_secure</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""``True`` if the request was made over HTTPS."""</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">url</span><span class="o">.</span><span class="n">scheme</span> <span class="o">==</span> <span class="s2">"https"</span>
|
||||
|
||||
<div class="viewcode-block" id="Request.accepts">
|
||||
<a class="viewcode-back" href="../../api.html#responder.Request.accepts">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">accepts</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content_type</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Returns ``True`` if the incoming Request accepts the given ``content_type``."""</span>
|
||||
<span class="k">return</span> <span class="n">content_type</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">headers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"Accept"</span><span class="p">,</span> <span class="p">[])</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="Request.media">
|
||||
<a class="viewcode-back" href="../../api.html#responder.Request.media">[docs]</a>
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">media</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">format</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="n">Callable</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span> <span class="c1"># noqa: A002</span>
|
||||
<span class="w"> </span><span class="sd">"""Renders incoming json/yaml/form data as Python objects. Must be awaited.</span>
|
||||
|
||||
<span class="sd"> :param format: The name of the format being used.</span>
|
||||
<span class="sd"> Alternatively, accepts a custom callable for the format type.</span>
|
||||
<span class="sd"> """</span>
|
||||
|
||||
<span class="k">if</span> <span class="nb">format</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="nb">format</span> <span class="o">=</span> <span class="s2">"yaml"</span> <span class="k">if</span> <span class="s2">"yaml"</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">mimetype</span> <span class="k">else</span> <span class="s2">"json"</span> <span class="c1"># noqa: A001</span>
|
||||
<span class="nb">format</span> <span class="o">=</span> <span class="s2">"form"</span> <span class="k">if</span> <span class="s2">"form"</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">mimetype</span> <span class="k">else</span> <span class="nb">format</span> <span class="c1"># noqa: A001</span>
|
||||
|
||||
<span class="n">formatter</span><span class="p">:</span> <span class="n">Callable</span>
|
||||
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="nb">format</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">formats</span><span class="p">[</span><span class="nb">format</span><span class="p">]</span>
|
||||
<span class="k">except</span> <span class="ne">KeyError</span> <span class="k">as</span> <span class="n">ex</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Unable to process data in '</span><span class="si">{</span><span class="nb">format</span><span class="si">}</span><span class="s2">' format"</span><span class="p">)</span> <span class="kn">from</span><span class="w"> </span><span class="nn">ex</span>
|
||||
|
||||
<span class="k">elif</span> <span class="nb">callable</span><span class="p">(</span><span class="nb">format</span><span class="p">):</span>
|
||||
<span class="n">formatter</span> <span class="o">=</span> <span class="nb">format</span>
|
||||
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Invalid 'format' argument: </span><span class="si">{</span><span class="nb">format</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
|
||||
<span class="k">return</span> <span class="k">await</span> <span class="n">formatter</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span></div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">content_setter</span><span class="p">(</span><span class="n">mimetype</span><span class="p">):</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">getter</span><span class="p">(</span><span class="n">instance</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="n">instance</span><span class="o">.</span><span class="n">content</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">setter</span><span class="p">(</span><span class="n">instance</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
|
||||
<span class="n">instance</span><span class="o">.</span><span class="n">content</span> <span class="o">=</span> <span class="n">value</span>
|
||||
<span class="n">instance</span><span class="o">.</span><span class="n">mimetype</span> <span class="o">=</span> <span class="n">mimetype</span>
|
||||
|
||||
<span class="k">return</span> <span class="nb">property</span><span class="p">(</span><span class="n">fget</span><span class="o">=</span><span class="n">getter</span><span class="p">,</span> <span class="n">fset</span><span class="o">=</span><span class="n">setter</span><span class="p">)</span>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="Response">
|
||||
<a class="viewcode-back" href="../../api.html#responder.Response">[docs]</a>
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">Response</span><span class="p">:</span>
|
||||
<span class="w"> </span><span class="sd">"""An HTTP response, passed to each view as the second argument.</span>
|
||||
|
||||
<span class="sd"> Mutate this object to control what gets sent back to the client. Set</span>
|
||||
<span class="sd"> :attr:`text`, :attr:`html`, :attr:`media`, or :attr:`content` to define</span>
|
||||
<span class="sd"> the body. Use :attr:`headers` and :meth:`set_cookie` to control metadata.</span>
|
||||
|
||||
<span class="sd"> :var text: Set the response body as plain text (sets ``Content-Type: text/plain``).</span>
|
||||
<span class="sd"> :var html: Set the response body as HTML (sets ``Content-Type: text/html``).</span>
|
||||
<span class="sd"> :var media: Set a Python object (dict, list) to be serialized as JSON (or negotiated format).</span>
|
||||
<span class="sd"> :var content: Set the raw response body as bytes.</span>
|
||||
<span class="sd"> :var status_code: The HTTP status code (e.g. ``200``, ``404``). Defaults to ``200`` if not set.</span>
|
||||
<span class="sd"> :var headers: A dict of response headers.</span>
|
||||
<span class="sd"> :var cookies: A ``SimpleCookie`` holding cookies to set on the response.</span>
|
||||
<span class="sd"> :var session: A dict of session data. Changes are persisted in a signed cookie.</span>
|
||||
<span class="sd"> """</span> <span class="c1"># noqa: E501</span>
|
||||
|
||||
<span class="vm">__slots__</span> <span class="o">=</span> <span class="p">[</span>
|
||||
<span class="s2">"req"</span><span class="p">,</span>
|
||||
<span class="s2">"status_code"</span><span class="p">,</span>
|
||||
<span class="s2">"content"</span><span class="p">,</span>
|
||||
<span class="s2">"encoding"</span><span class="p">,</span>
|
||||
<span class="s2">"media"</span><span class="p">,</span>
|
||||
<span class="s2">"headers"</span><span class="p">,</span>
|
||||
<span class="s2">"formats"</span><span class="p">,</span>
|
||||
<span class="s2">"cookies"</span><span class="p">,</span>
|
||||
<span class="s2">"session"</span><span class="p">,</span>
|
||||
<span class="s2">"mimetype"</span><span class="p">,</span>
|
||||
<span class="s2">"_stream"</span><span class="p">,</span>
|
||||
<span class="p">]</span>
|
||||
|
||||
<span class="n">text</span> <span class="o">=</span> <span class="n">content_setter</span><span class="p">(</span><span class="s2">"text/plain"</span><span class="p">)</span>
|
||||
<span class="n">html</span> <span class="o">=</span> <span class="n">content_setter</span><span class="p">(</span><span class="s2">"text/html"</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">req</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">formats</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">req</span> <span class="o">=</span> <span class="n">req</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">status_code</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">content</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">mimetype</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">encoding</span> <span class="o">=</span> <span class="n">DEFAULT_ENCODING</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_stream</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">headers</span> <span class="o">=</span> <span class="p">{}</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">formats</span> <span class="o">=</span> <span class="n">formats</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">cookies</span><span class="p">:</span> <span class="n">SimpleCookie</span> <span class="o">=</span> <span class="n">SimpleCookie</span><span class="p">()</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">session</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="n">session</span>
|
||||
|
||||
<div class="viewcode-block" id="Response.stream">
|
||||
<a class="viewcode-back" href="../../api.html#responder.Response.stream">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">stream</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">func</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Set up a streaming response from an async generator function.</span>
|
||||
|
||||
<span class="sd"> The generator yields chunks of bytes that are sent to the client</span>
|
||||
<span class="sd"> as they are produced, without buffering the full response in memory.</span>
|
||||
|
||||
<span class="sd"> Usage::</span>
|
||||
|
||||
<span class="sd"> @api.route("/stream")</span>
|
||||
<span class="sd"> async def stream_data(req, resp):</span>
|
||||
<span class="sd"> @resp.stream</span>
|
||||
<span class="sd"> async def body():</span>
|
||||
<span class="sd"> for i in range(10):</span>
|
||||
<span class="sd"> yield f"chunk {i}\\n".encode()</span>
|
||||
|
||||
<span class="sd"> :param func: An async generator function that yields response chunks.</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">assert</span> <span class="n">inspect</span><span class="o">.</span><span class="n">isasyncgenfunction</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_stream</span> <span class="o">=</span> <span class="n">functools</span><span class="o">.</span><span class="n">partial</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">func</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="Response.sse">
|
||||
<a class="viewcode-back" href="../../api.html#responder.Response.sse">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">sse</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">func</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Set up Server-Sent Events streaming.</span>
|
||||
|
||||
<span class="sd"> Usage::</span>
|
||||
|
||||
<span class="sd"> @api.route("/events")</span>
|
||||
<span class="sd"> async def events(req, resp):</span>
|
||||
<span class="sd"> @resp.sse</span>
|
||||
<span class="sd"> async def stream():</span>
|
||||
<span class="sd"> for i in range(10):</span>
|
||||
<span class="sd"> yield {"data": f"message {i}"}</span>
|
||||
|
||||
<span class="sd"> Each yielded dict can have: data, event, id, retry.</span>
|
||||
<span class="sd"> Yielding a string is treated as data.</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">assert</span> <span class="n">inspect</span><span class="o">.</span><span class="n">isasyncgenfunction</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
|
||||
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">sse_generator</span><span class="p">():</span>
|
||||
<span class="k">async</span> <span class="k">for</span> <span class="n">event</span> <span class="ow">in</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||||
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
|
||||
<span class="k">yield</span> <span class="sa">f</span><span class="s2">"data: </span><span class="si">{</span><span class="n">event</span><span class="si">}</span><span class="se">\n\n</span><span class="s2">"</span><span class="o">.</span><span class="n">encode</span><span class="p">()</span>
|
||||
<span class="k">elif</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="nb">dict</span><span class="p">):</span>
|
||||
<span class="n">parts</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
<span class="k">if</span> <span class="s2">"event"</span> <span class="ow">in</span> <span class="n">event</span><span class="p">:</span>
|
||||
<span class="n">parts</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">"event: </span><span class="si">{</span><span class="n">event</span><span class="p">[</span><span class="s1">'event'</span><span class="p">]</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="s2">"id"</span> <span class="ow">in</span> <span class="n">event</span><span class="p">:</span>
|
||||
<span class="n">parts</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">"id: </span><span class="si">{</span><span class="n">event</span><span class="p">[</span><span class="s1">'id'</span><span class="p">]</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="s2">"retry"</span> <span class="ow">in</span> <span class="n">event</span><span class="p">:</span>
|
||||
<span class="n">parts</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">"retry: </span><span class="si">{</span><span class="n">event</span><span class="p">[</span><span class="s1">'retry'</span><span class="p">]</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="n">data</span> <span class="o">=</span> <span class="n">event</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"data"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span>
|
||||
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">str</span><span class="p">(</span><span class="n">data</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">):</span>
|
||||
<span class="n">parts</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">"data: </span><span class="si">{</span><span class="n">line</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="n">parts</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span>
|
||||
<span class="n">parts</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span>
|
||||
<span class="k">yield</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">parts</span><span class="p">)</span><span class="o">.</span><span class="n">encode</span><span class="p">()</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">yield</span> <span class="sa">f</span><span class="s2">"data: </span><span class="si">{</span><span class="n">event</span><span class="si">}</span><span class="se">\n\n</span><span class="s2">"</span><span class="o">.</span><span class="n">encode</span><span class="p">()</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_stream</span> <span class="o">=</span> <span class="n">sse_generator</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">mimetype</span> <span class="o">=</span> <span class="s2">"text/event-stream"</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Cache-Control"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"no-cache"</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Connection"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"keep-alive"</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">func</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="Response.stream_file">
|
||||
<a class="viewcode-back" href="../../api.html#responder.Response.stream_file">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">stream_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">content_type</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">chunk_size</span><span class="o">=</span><span class="mi">8192</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Stream a file without loading it entirely into memory.</span>
|
||||
|
||||
<span class="sd"> :param path: Path to the file.</span>
|
||||
<span class="sd"> :param content_type: Optional MIME type override.</span>
|
||||
<span class="sd"> :param chunk_size: Size of chunks to read (default 8192 bytes).</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">pathlib</span><span class="w"> </span><span class="kn">import</span> <span class="n">Path</span> <span class="k">as</span> <span class="n">PathType</span>
|
||||
|
||||
<span class="n">path</span> <span class="o">=</span> <span class="n">PathType</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">content_type</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">mimetype</span> <span class="o">=</span> <span class="n">content_type</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">mimetypes</span>
|
||||
|
||||
<span class="n">guessed</span> <span class="o">=</span> <span class="n">mimetypes</span><span class="o">.</span><span class="n">guess_type</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">))[</span><span class="mi">0</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">mimetype</span> <span class="o">=</span> <span class="n">guessed</span> <span class="ow">or</span> <span class="s2">"application/octet-stream"</span>
|
||||
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">file_generator</span><span class="p">():</span>
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">anyio</span>
|
||||
|
||||
<span class="k">async</span> <span class="k">with</span> <span class="k">await</span> <span class="n">anyio</span><span class="o">.</span><span class="n">open_file</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s2">"rb"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
|
||||
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
|
||||
<span class="n">chunk</span> <span class="o">=</span> <span class="k">await</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">chunk_size</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">chunk</span><span class="p">:</span>
|
||||
<span class="k">break</span>
|
||||
<span class="k">yield</span> <span class="n">chunk</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_stream</span> <span class="o">=</span> <span class="n">file_generator</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="Response.file">
|
||||
<a class="viewcode-back" href="../../api.html#responder.Response.file">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">content_type</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Serve a file from disk as the response.</span>
|
||||
|
||||
<span class="sd"> :param path: Path to the file to serve.</span>
|
||||
<span class="sd"> :param content_type: Optional MIME type override.</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">pathlib</span><span class="w"> </span><span class="kn">import</span> <span class="n">Path</span>
|
||||
|
||||
<span class="n">path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">content</span> <span class="o">=</span> <span class="n">path</span><span class="o">.</span><span class="n">read_bytes</span><span class="p">()</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">content_type</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">mimetype</span> <span class="o">=</span> <span class="n">content_type</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">mimetypes</span>
|
||||
|
||||
<span class="n">guessed</span> <span class="o">=</span> <span class="n">mimetypes</span><span class="o">.</span><span class="n">guess_type</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">))[</span><span class="mi">0</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">mimetype</span> <span class="o">=</span> <span class="n">guessed</span> <span class="ow">or</span> <span class="s2">"application/octet-stream"</span></div>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="Response.redirect">
|
||||
<a class="viewcode-back" href="../../api.html#responder.Response.redirect">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">redirect</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">location</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">set_text</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">status_code</span><span class="o">=</span><span class="n">HTTP_301</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Redirect the client to a different URL.</span>
|
||||
|
||||
<span class="sd"> :param location: The URL to redirect to.</span>
|
||||
<span class="sd"> :param set_text: If ``True``, set a default redirect message as the body.</span>
|
||||
<span class="sd"> :param status_code: The HTTP status code (default ``301``).</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="n">status_code</span>
|
||||
<span class="k">if</span> <span class="n">set_text</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"Redirecting to: </span><span class="si">{</span><span class="n">location</span><span class="si">}</span><span class="s2">"</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">headers</span><span class="o">.</span><span class="n">update</span><span class="p">({</span><span class="s2">"Location"</span><span class="p">:</span> <span class="n">location</span><span class="p">})</span></div>
|
||||
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">body</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_stream</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">headers</span> <span class="o">=</span> <span class="p">{}</span>
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">mimetype</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">headers</span><span class="p">[</span><span class="s2">"Content-Type"</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">mimetype</span>
|
||||
<span class="k">return</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_stream</span><span class="p">(),</span> <span class="n">headers</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">content</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">headers</span> <span class="o">=</span> <span class="p">{}</span>
|
||||
<span class="n">content</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">content</span>
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">mimetype</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">headers</span><span class="p">[</span><span class="s2">"Content-Type"</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">mimetype</span>
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">mimetype</span> <span class="o">==</span> <span class="s2">"text/plain"</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">encoding</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">headers</span><span class="p">[</span><span class="s2">"Encoding"</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">encoding</span>
|
||||
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
|
||||
<span class="n">content</span> <span class="o">=</span> <span class="n">content</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">encoding</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="n">headers</span><span class="p">)</span>
|
||||
|
||||
<span class="k">for</span> <span class="n">format_</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">formats</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">req</span><span class="o">.</span><span class="n">accepts</span><span class="p">(</span><span class="n">format_</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="p">(</span><span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">formats</span><span class="p">[</span><span class="n">format_</span><span class="p">](</span><span class="bp">self</span><span class="p">,</span> <span class="n">encode</span><span class="o">=</span><span class="kc">True</span><span class="p">)),</span> <span class="p">{}</span>
|
||||
|
||||
<span class="c1"># Default to JSON anyway.</span>
|
||||
<span class="k">return</span> <span class="p">(</span>
|
||||
<span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">formats</span><span class="p">[</span><span class="s2">"json"</span><span class="p">](</span><span class="bp">self</span><span class="p">,</span> <span class="n">encode</span><span class="o">=</span><span class="kc">True</span><span class="p">),</span>
|
||||
<span class="p">{</span><span class="s2">"Content-Type"</span><span class="p">:</span> <span class="s2">"application/json"</span><span class="p">},</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<div class="viewcode-block" id="Response.set_cookie">
|
||||
<a class="viewcode-back" href="../../api.html#responder.Response.set_cookie">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">set_cookie</span><span class="p">(</span>
|
||||
<span class="bp">self</span><span class="p">,</span>
|
||||
<span class="n">key</span><span class="p">,</span>
|
||||
<span class="n">value</span><span class="o">=</span><span class="s2">""</span><span class="p">,</span>
|
||||
<span class="n">expires</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">path</span><span class="o">=</span><span class="s2">"/"</span><span class="p">,</span>
|
||||
<span class="n">domain</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">max_age</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">secure</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
|
||||
<span class="n">httponly</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
|
||||
<span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""Set a cookie on the response with full control over directives.</span>
|
||||
|
||||
<span class="sd"> :param key: The cookie name.</span>
|
||||
<span class="sd"> :param value: The cookie value.</span>
|
||||
<span class="sd"> :param expires: Expiration date string (e.g. ``"Thu, 01 Jan 2026 00:00:00 GMT"``).</span>
|
||||
<span class="sd"> :param path: URL path the cookie applies to (default ``"/"``).</span>
|
||||
<span class="sd"> :param domain: Domain the cookie is valid for.</span>
|
||||
<span class="sd"> :param max_age: Maximum age in seconds before the cookie expires.</span>
|
||||
<span class="sd"> :param secure: If ``True``, cookie is only sent over HTTPS.</span>
|
||||
<span class="sd"> :param httponly: If ``True`` (default), cookie is inaccessible to JavaScript.</span>
|
||||
|
||||
<span class="sd"> Usage::</span>
|
||||
|
||||
<span class="sd"> resp.set_cookie(</span>
|
||||
<span class="sd"> "token", value="abc123",</span>
|
||||
<span class="sd"> max_age=3600, secure=True, httponly=True,</span>
|
||||
<span class="sd"> )</span>
|
||||
|
||||
<span class="sd"> """</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">cookies</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
|
||||
<span class="n">morsel</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">cookies</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
|
||||
<span class="k">if</span> <span class="n">expires</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">morsel</span><span class="p">[</span><span class="s2">"expires"</span><span class="p">]</span> <span class="o">=</span> <span class="n">expires</span>
|
||||
<span class="k">if</span> <span class="n">path</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">morsel</span><span class="p">[</span><span class="s2">"path"</span><span class="p">]</span> <span class="o">=</span> <span class="n">path</span>
|
||||
<span class="k">if</span> <span class="n">domain</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">morsel</span><span class="p">[</span><span class="s2">"domain"</span><span class="p">]</span> <span class="o">=</span> <span class="n">domain</span>
|
||||
<span class="k">if</span> <span class="n">max_age</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">morsel</span><span class="p">[</span><span class="s2">"max-age"</span><span class="p">]</span> <span class="o">=</span> <span class="n">max_age</span>
|
||||
<span class="n">morsel</span><span class="p">[</span><span class="s2">"secure"</span><span class="p">]</span> <span class="o">=</span> <span class="n">secure</span>
|
||||
<span class="n">morsel</span><span class="p">[</span><span class="s2">"httponly"</span><span class="p">]</span> <span class="o">=</span> <span class="n">httponly</span></div>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">_prepare_cookies</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">starlette_response</span><span class="p">):</span>
|
||||
<span class="n">cookie_header</span> <span class="o">=</span> <span class="p">(</span>
|
||||
<span class="p">(</span><span class="sa">b</span><span class="s2">"set-cookie"</span><span class="p">,</span> <span class="n">morsel</span><span class="o">.</span><span class="n">output</span><span class="p">(</span><span class="n">header</span><span class="o">=</span><span class="s2">""</span><span class="p">)</span><span class="o">.</span><span class="n">lstrip</span><span class="p">()</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s2">"latin-1"</span><span class="p">))</span>
|
||||
<span class="k">for</span> <span class="n">morsel</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">cookies</span><span class="o">.</span><span class="n">values</span><span class="p">()</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">starlette_response</span><span class="o">.</span><span class="n">raw_headers</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">cookie_header</span><span class="p">)</span>
|
||||
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="fm">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">scope</span><span class="p">,</span> <span class="n">receive</span><span class="p">,</span> <span class="n">send</span><span class="p">):</span>
|
||||
<span class="n">body</span><span class="p">,</span> <span class="n">headers</span> <span class="o">=</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">body</span>
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">headers</span><span class="p">:</span>
|
||||
<span class="n">headers</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">headers</span><span class="p">)</span>
|
||||
|
||||
<span class="n">response_cls</span><span class="p">:</span> <span class="nb">type</span><span class="p">[</span><span class="n">StarletteResponse</span><span class="p">]</span> <span class="o">|</span> <span class="nb">type</span><span class="p">[</span><span class="n">StarletteStreamingResponse</span><span class="p">]</span>
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_stream</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">response_cls</span> <span class="o">=</span> <span class="n">StarletteStreamingResponse</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">response_cls</span> <span class="o">=</span> <span class="n">StarletteResponse</span>
|
||||
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">response_cls</span><span class="p">(</span><span class="n">body</span><span class="p">,</span> <span class="n">status_code</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">status_code_safe</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">)</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_prepare_cookies</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>
|
||||
|
||||
<span class="k">await</span> <span class="n">response</span><span class="p">(</span><span class="n">scope</span><span class="p">,</span> <span class="n">receive</span><span class="p">,</span> <span class="n">send</span><span class="p">)</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">ok</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""``True`` if the status code is in the 2xx range (success)."""</span>
|
||||
<span class="k">return</span> <span class="mi">200</span> <span class="o"><=</span> <span class="bp">self</span><span class="o">.</span><span class="n">status_code_safe</span> <span class="o"><</span> <span class="mi">300</span>
|
||||
|
||||
<span class="nd">@property</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">status_code_safe</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span>
|
||||
<span class="w"> </span><span class="sd">"""Return the status code, raising ``RuntimeError`` if it hasn't been set."""</span>
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">status_code</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">RuntimeError</span><span class="p">(</span><span class="s2">"HTTP status code has not been defined"</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">status_code</span></div>
|
||||
|
||||
</pre></div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="sphinxsidebar" role="navigation" aria-label="Main">
|
||||
<div class="sphinxsidebarwrapper"><p class="logo">
|
||||
<a href="../../index.html">
|
||||
<img class="logo" src="../../_static/responder.png" />
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Responder</strong> — a familiar HTTP service framework for Python.
|
||||
<br />
|
||||
<small>v3.6.2</small>
|
||||
</p>
|
||||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
|
||||
<li><a href="https://pypi.org/project/responder/">Responder @ PyPI</a></li>
|
||||
<li><a href="https://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
|
||||
</ul>
|
||||
<search id="searchbox" style="display: none" role="search">
|
||||
<h3 id="searchlabel">Quick search</h3>
|
||||
<div class="searchformwrapper">
|
||||
<form class="search" action="../../search.html" method="get">
|
||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
||||
<input type="submit" value="Go" />
|
||||
</form>
|
||||
</div>
|
||||
</search>
|
||||
<script>document.getElementById('searchbox').style.display = "block"</script>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
©2018-2026, Kenneth Reitz.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,210 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" data-content_root="../../">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>responder.status_codes — responder 3.6.2 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css?v=5ecbeea2" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/basic.css?v=b08954a9" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/alabaster.css?v=27fed22d" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/design-elements.e5416f61bae5d36adc6d722a2b6f8cff.css?v=452a8e97" />
|
||||
<script src="../../_static/documentation_options.js?v=c0c9fa11"></script>
|
||||
<script src="../../_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="../../_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="../../_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="../../_static/copybutton.js?v=fd10adb8"></script>
|
||||
<script>
|
||||
</script>
|
||||
<script src="../../_static/design-elements.bbdccc18c4abea9397628f9fea3d48c2.js?v=03c7770e"></script>
|
||||
<link rel="index" title="Index" href="../../genindex.html" />
|
||||
<link rel="search" title="Search" href="../../search.html" />
|
||||
|
||||
<link rel="stylesheet" href="../../_static/custom.css" type="text/css" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</head><body>
|
||||
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
|
||||
|
||||
<div class="body" role="main">
|
||||
|
||||
<h1>Source code for responder.status_codes</h1><div class="highlight"><pre>
|
||||
<span></span><span class="n">codes</span> <span class="o">=</span> <span class="p">{</span>
|
||||
<span class="c1"># Informational.</span>
|
||||
<span class="mi">100</span><span class="p">:</span> <span class="p">(</span><span class="s2">"continue"</span><span class="p">,),</span>
|
||||
<span class="mi">101</span><span class="p">:</span> <span class="p">(</span><span class="s2">"switching_protocols"</span><span class="p">,),</span>
|
||||
<span class="mi">102</span><span class="p">:</span> <span class="p">(</span><span class="s2">"processing"</span><span class="p">,),</span>
|
||||
<span class="mi">103</span><span class="p">:</span> <span class="p">(</span><span class="s2">"checkpoint"</span><span class="p">,),</span>
|
||||
<span class="mi">122</span><span class="p">:</span> <span class="p">(</span><span class="s2">"uri_too_long"</span><span class="p">,</span> <span class="s2">"request_uri_too_long"</span><span class="p">),</span>
|
||||
<span class="mi">200</span><span class="p">:</span> <span class="p">(</span><span class="s2">"ok"</span><span class="p">,</span> <span class="s2">"okay"</span><span class="p">,</span> <span class="s2">"all_ok"</span><span class="p">,</span> <span class="s2">"all_okay"</span><span class="p">,</span> <span class="s2">"all_good"</span><span class="p">,</span> <span class="s2">"</span><span class="se">\\</span><span class="s2">o/"</span><span class="p">,</span> <span class="s2">"✓"</span><span class="p">),</span>
|
||||
<span class="mi">201</span><span class="p">:</span> <span class="p">(</span><span class="s2">"created"</span><span class="p">,),</span>
|
||||
<span class="mi">202</span><span class="p">:</span> <span class="p">(</span><span class="s2">"accepted"</span><span class="p">,),</span>
|
||||
<span class="mi">203</span><span class="p">:</span> <span class="p">(</span><span class="s2">"non_authoritative_info"</span><span class="p">,</span> <span class="s2">"non_authoritative_information"</span><span class="p">),</span>
|
||||
<span class="mi">204</span><span class="p">:</span> <span class="p">(</span><span class="s2">"no_content"</span><span class="p">,),</span>
|
||||
<span class="mi">205</span><span class="p">:</span> <span class="p">(</span><span class="s2">"reset_content"</span><span class="p">,</span> <span class="s2">"reset"</span><span class="p">),</span>
|
||||
<span class="mi">206</span><span class="p">:</span> <span class="p">(</span><span class="s2">"partial_content"</span><span class="p">,</span> <span class="s2">"partial"</span><span class="p">),</span>
|
||||
<span class="mi">207</span><span class="p">:</span> <span class="p">(</span><span class="s2">"multi_status"</span><span class="p">,</span> <span class="s2">"multiple_status"</span><span class="p">,</span> <span class="s2">"multi_stati"</span><span class="p">,</span> <span class="s2">"multiple_stati"</span><span class="p">),</span>
|
||||
<span class="mi">208</span><span class="p">:</span> <span class="p">(</span><span class="s2">"already_reported"</span><span class="p">,),</span>
|
||||
<span class="mi">226</span><span class="p">:</span> <span class="p">(</span><span class="s2">"im_used"</span><span class="p">,),</span>
|
||||
<span class="c1"># Redirection.</span>
|
||||
<span class="mi">300</span><span class="p">:</span> <span class="p">(</span><span class="s2">"multiple_choices"</span><span class="p">,),</span>
|
||||
<span class="mi">301</span><span class="p">:</span> <span class="p">(</span><span class="s2">"moved_permanently"</span><span class="p">,</span> <span class="s2">"moved"</span><span class="p">,</span> <span class="s2">"</span><span class="se">\\</span><span class="s2">o-"</span><span class="p">),</span>
|
||||
<span class="mi">302</span><span class="p">:</span> <span class="p">(</span><span class="s2">"found"</span><span class="p">,),</span>
|
||||
<span class="mi">303</span><span class="p">:</span> <span class="p">(</span><span class="s2">"see_other"</span><span class="p">,</span> <span class="s2">"other"</span><span class="p">),</span>
|
||||
<span class="mi">304</span><span class="p">:</span> <span class="p">(</span><span class="s2">"not_modified"</span><span class="p">,),</span>
|
||||
<span class="mi">305</span><span class="p">:</span> <span class="p">(</span><span class="s2">"use_proxy"</span><span class="p">,),</span>
|
||||
<span class="mi">306</span><span class="p">:</span> <span class="p">(</span><span class="s2">"switch_proxy"</span><span class="p">,),</span>
|
||||
<span class="mi">307</span><span class="p">:</span> <span class="p">(</span><span class="s2">"temporary_redirect"</span><span class="p">,</span> <span class="s2">"temporary_moved"</span><span class="p">,</span> <span class="s2">"temporary"</span><span class="p">),</span>
|
||||
<span class="mi">308</span><span class="p">:</span> <span class="p">(</span><span class="s2">"permanent_redirect"</span><span class="p">,),</span>
|
||||
<span class="c1"># Client Error.</span>
|
||||
<span class="mi">400</span><span class="p">:</span> <span class="p">(</span><span class="s2">"bad_request"</span><span class="p">,</span> <span class="s2">"bad"</span><span class="p">),</span>
|
||||
<span class="mi">401</span><span class="p">:</span> <span class="p">(</span><span class="s2">"unauthorized"</span><span class="p">,),</span>
|
||||
<span class="mi">402</span><span class="p">:</span> <span class="p">(</span><span class="s2">"payment_required"</span><span class="p">,</span> <span class="s2">"payment"</span><span class="p">),</span>
|
||||
<span class="mi">403</span><span class="p">:</span> <span class="p">(</span><span class="s2">"forbidden"</span><span class="p">,),</span>
|
||||
<span class="mi">404</span><span class="p">:</span> <span class="p">(</span><span class="s2">"not_found"</span><span class="p">,</span> <span class="s2">"-o-"</span><span class="p">),</span>
|
||||
<span class="mi">405</span><span class="p">:</span> <span class="p">(</span><span class="s2">"method_not_allowed"</span><span class="p">,</span> <span class="s2">"not_allowed"</span><span class="p">),</span>
|
||||
<span class="mi">406</span><span class="p">:</span> <span class="p">(</span><span class="s2">"not_acceptable"</span><span class="p">,),</span>
|
||||
<span class="mi">407</span><span class="p">:</span> <span class="p">(</span><span class="s2">"proxy_authentication_required"</span><span class="p">,</span> <span class="s2">"proxy_auth"</span><span class="p">,</span> <span class="s2">"proxy_authentication"</span><span class="p">),</span>
|
||||
<span class="mi">408</span><span class="p">:</span> <span class="p">(</span><span class="s2">"request_timeout"</span><span class="p">,</span> <span class="s2">"timeout"</span><span class="p">),</span>
|
||||
<span class="mi">409</span><span class="p">:</span> <span class="p">(</span><span class="s2">"conflict"</span><span class="p">,),</span>
|
||||
<span class="mi">410</span><span class="p">:</span> <span class="p">(</span><span class="s2">"gone"</span><span class="p">,),</span>
|
||||
<span class="mi">411</span><span class="p">:</span> <span class="p">(</span><span class="s2">"length_required"</span><span class="p">,),</span>
|
||||
<span class="mi">412</span><span class="p">:</span> <span class="p">(</span><span class="s2">"precondition_failed"</span><span class="p">,</span> <span class="s2">"precondition"</span><span class="p">),</span>
|
||||
<span class="mi">413</span><span class="p">:</span> <span class="p">(</span><span class="s2">"request_entity_too_large"</span><span class="p">,),</span>
|
||||
<span class="mi">414</span><span class="p">:</span> <span class="p">(</span><span class="s2">"request_uri_too_large"</span><span class="p">,),</span>
|
||||
<span class="mi">415</span><span class="p">:</span> <span class="p">(</span><span class="s2">"unsupported_media_type"</span><span class="p">,</span> <span class="s2">"unsupported_media"</span><span class="p">,</span> <span class="s2">"media_type"</span><span class="p">),</span>
|
||||
<span class="mi">416</span><span class="p">:</span> <span class="p">(</span>
|
||||
<span class="s2">"requested_range_not_satisfiable"</span><span class="p">,</span>
|
||||
<span class="s2">"requested_range"</span><span class="p">,</span>
|
||||
<span class="s2">"range_not_satisfiable"</span><span class="p">,</span>
|
||||
<span class="p">),</span>
|
||||
<span class="mi">417</span><span class="p">:</span> <span class="p">(</span><span class="s2">"expectation_failed"</span><span class="p">,),</span>
|
||||
<span class="mi">418</span><span class="p">:</span> <span class="p">(</span><span class="s2">"im_a_teapot"</span><span class="p">,</span> <span class="s2">"teapot"</span><span class="p">,</span> <span class="s2">"i_am_a_teapot"</span><span class="p">),</span>
|
||||
<span class="mi">421</span><span class="p">:</span> <span class="p">(</span><span class="s2">"misdirected_request"</span><span class="p">,),</span>
|
||||
<span class="mi">422</span><span class="p">:</span> <span class="p">(</span><span class="s2">"unprocessable_entity"</span><span class="p">,</span> <span class="s2">"unprocessable"</span><span class="p">),</span>
|
||||
<span class="mi">423</span><span class="p">:</span> <span class="p">(</span><span class="s2">"locked"</span><span class="p">,),</span>
|
||||
<span class="mi">424</span><span class="p">:</span> <span class="p">(</span><span class="s2">"failed_dependency"</span><span class="p">,</span> <span class="s2">"dependency"</span><span class="p">),</span>
|
||||
<span class="mi">425</span><span class="p">:</span> <span class="p">(</span><span class="s2">"unordered_collection"</span><span class="p">,</span> <span class="s2">"unordered"</span><span class="p">),</span>
|
||||
<span class="mi">426</span><span class="p">:</span> <span class="p">(</span><span class="s2">"upgrade_required"</span><span class="p">,</span> <span class="s2">"upgrade"</span><span class="p">),</span>
|
||||
<span class="mi">428</span><span class="p">:</span> <span class="p">(</span><span class="s2">"precondition_required"</span><span class="p">,</span> <span class="s2">"precondition"</span><span class="p">),</span>
|
||||
<span class="mi">429</span><span class="p">:</span> <span class="p">(</span><span class="s2">"too_many_requests"</span><span class="p">,</span> <span class="s2">"too_many"</span><span class="p">),</span>
|
||||
<span class="mi">431</span><span class="p">:</span> <span class="p">(</span><span class="s2">"header_fields_too_large"</span><span class="p">,</span> <span class="s2">"fields_too_large"</span><span class="p">),</span>
|
||||
<span class="mi">444</span><span class="p">:</span> <span class="p">(</span><span class="s2">"no_response"</span><span class="p">,</span> <span class="s2">"none"</span><span class="p">),</span>
|
||||
<span class="mi">449</span><span class="p">:</span> <span class="p">(</span><span class="s2">"retry_with"</span><span class="p">,</span> <span class="s2">"retry"</span><span class="p">),</span>
|
||||
<span class="mi">450</span><span class="p">:</span> <span class="p">(</span><span class="s2">"blocked_by_windows_parental_controls"</span><span class="p">,</span> <span class="s2">"parental_controls"</span><span class="p">),</span>
|
||||
<span class="mi">451</span><span class="p">:</span> <span class="p">(</span><span class="s2">"unavailable_for_legal_reasons"</span><span class="p">,</span> <span class="s2">"legal_reasons"</span><span class="p">),</span>
|
||||
<span class="mi">499</span><span class="p">:</span> <span class="p">(</span><span class="s2">"client_closed_request"</span><span class="p">,),</span>
|
||||
<span class="c1"># Server Error.</span>
|
||||
<span class="mi">500</span><span class="p">:</span> <span class="p">(</span><span class="s2">"internal_server_error"</span><span class="p">,</span> <span class="s2">"server_error"</span><span class="p">,</span> <span class="s2">"/o</span><span class="se">\\</span><span class="s2">"</span><span class="p">,</span> <span class="s2">"✗"</span><span class="p">),</span>
|
||||
<span class="mi">501</span><span class="p">:</span> <span class="p">(</span><span class="s2">"not_implemented"</span><span class="p">,),</span>
|
||||
<span class="mi">502</span><span class="p">:</span> <span class="p">(</span><span class="s2">"bad_gateway"</span><span class="p">,),</span>
|
||||
<span class="mi">503</span><span class="p">:</span> <span class="p">(</span><span class="s2">"service_unavailable"</span><span class="p">,</span> <span class="s2">"unavailable"</span><span class="p">),</span>
|
||||
<span class="mi">504</span><span class="p">:</span> <span class="p">(</span><span class="s2">"gateway_timeout"</span><span class="p">,),</span>
|
||||
<span class="mi">505</span><span class="p">:</span> <span class="p">(</span><span class="s2">"http_version_not_supported"</span><span class="p">,</span> <span class="s2">"http_version"</span><span class="p">),</span>
|
||||
<span class="mi">506</span><span class="p">:</span> <span class="p">(</span><span class="s2">"variant_also_negotiates"</span><span class="p">,),</span>
|
||||
<span class="mi">507</span><span class="p">:</span> <span class="p">(</span><span class="s2">"insufficient_storage"</span><span class="p">,),</span>
|
||||
<span class="mi">509</span><span class="p">:</span> <span class="p">(</span><span class="s2">"bandwidth_limit_exceeded"</span><span class="p">,</span> <span class="s2">"bandwidth"</span><span class="p">),</span>
|
||||
<span class="mi">510</span><span class="p">:</span> <span class="p">(</span><span class="s2">"not_extended"</span><span class="p">,),</span>
|
||||
<span class="mi">511</span><span class="p">:</span> <span class="p">(</span><span class="s2">"network_authentication_required"</span><span class="p">,</span> <span class="s2">"network_auth"</span><span class="p">,</span> <span class="s2">"network_authentication"</span><span class="p">),</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="k">for</span> <span class="n">number</span> <span class="ow">in</span> <span class="n">codes</span><span class="p">:</span>
|
||||
<span class="nb">locals</span><span class="p">()[</span><span class="sa">f</span><span class="s2">"HTTP_</span><span class="si">{</span><span class="n">number</span><span class="si">}</span><span class="s2">"</span><span class="p">]</span> <span class="o">=</span> <span class="n">number</span>
|
||||
|
||||
<span class="k">for</span> <span class="n">label</span> <span class="ow">in</span> <span class="n">codes</span><span class="p">[</span><span class="n">number</span><span class="p">]:</span>
|
||||
<span class="nb">locals</span><span class="p">()[</span><span class="n">label</span><span class="p">]</span> <span class="o">=</span> <span class="n">number</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">_is_category</span><span class="p">(</span><span class="n">category</span><span class="p">,</span> <span class="n">status_code</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="n">category</span> <span class="o"><=</span> <span class="n">status_code</span> <span class="o"><</span> <span class="n">category</span> <span class="o">+</span> <span class="mi">100</span>
|
||||
|
||||
|
||||
<div class="viewcode-block" id="is_100">
|
||||
<a class="viewcode-back" href="../../api.html#responder.status_codes.is_100">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">is_100</span><span class="p">(</span><span class="n">status_code</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="n">_is_category</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="n">status_code</span><span class="p">)</span></div>
|
||||
|
||||
|
||||
|
||||
<div class="viewcode-block" id="is_200">
|
||||
<a class="viewcode-back" href="../../api.html#responder.status_codes.is_200">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">is_200</span><span class="p">(</span><span class="n">status_code</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="n">_is_category</span><span class="p">(</span><span class="mi">200</span><span class="p">,</span> <span class="n">status_code</span><span class="p">)</span></div>
|
||||
|
||||
|
||||
|
||||
<div class="viewcode-block" id="is_300">
|
||||
<a class="viewcode-back" href="../../api.html#responder.status_codes.is_300">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">is_300</span><span class="p">(</span><span class="n">status_code</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="n">_is_category</span><span class="p">(</span><span class="mi">300</span><span class="p">,</span> <span class="n">status_code</span><span class="p">)</span></div>
|
||||
|
||||
|
||||
|
||||
<div class="viewcode-block" id="is_400">
|
||||
<a class="viewcode-back" href="../../api.html#responder.status_codes.is_400">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">is_400</span><span class="p">(</span><span class="n">status_code</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="n">_is_category</span><span class="p">(</span><span class="mi">400</span><span class="p">,</span> <span class="n">status_code</span><span class="p">)</span></div>
|
||||
|
||||
|
||||
|
||||
<div class="viewcode-block" id="is_500">
|
||||
<a class="viewcode-back" href="../../api.html#responder.status_codes.is_500">[docs]</a>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">is_500</span><span class="p">(</span><span class="n">status_code</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="n">_is_category</span><span class="p">(</span><span class="mi">500</span><span class="p">,</span> <span class="n">status_code</span><span class="p">)</span></div>
|
||||
|
||||
</pre></div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="sphinxsidebar" role="navigation" aria-label="Main">
|
||||
<div class="sphinxsidebarwrapper"><p class="logo">
|
||||
<a href="../../index.html">
|
||||
<img class="logo" src="../../_static/responder.png" />
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Responder</strong> — a familiar HTTP service framework for Python.
|
||||
<br />
|
||||
<small>v3.6.2</small>
|
||||
</p>
|
||||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
|
||||
<li><a href="https://pypi.org/project/responder/">Responder @ PyPI</a></li>
|
||||
<li><a href="https://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
|
||||
</ul>
|
||||
<search id="searchbox" style="display: none" role="search">
|
||||
<h3 id="searchlabel">Quick search</h3>
|
||||
<div class="searchformwrapper">
|
||||
<form class="search" action="../../search.html" method="get">
|
||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
||||
<input type="submit" value="Go" />
|
||||
</form>
|
||||
</div>
|
||||
</search>
|
||||
<script>document.getElementById('searchbox').style.display = "block"</script>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
©2018-2026, Kenneth Reitz.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,6 +5,40 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and
|
||||
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [v3.6.2] - 2026-04-12
|
||||
|
||||
### Fixed
|
||||
|
||||
- GraphQL error responses now correctly return 400 status instead of always 200
|
||||
- OpenAPI docs UI now respects custom `openapi_route` instead of hardcoding `/schema.yml`
|
||||
- `before_requests` default type mismatch that could crash routes called outside the router
|
||||
- Blocking synchronous file I/O in `Response.stream_file()` — now uses async I/O via anyio
|
||||
- Memory leak in rate limiter (empty bucket keys never cleaned up)
|
||||
- Race condition in rate limiter `check()` — added thread-safe locking
|
||||
- WSGI fallback catching all `TypeError`s instead of just call-signature mismatches
|
||||
- Pydantic request/response model validation crashing on non-dict bodies
|
||||
- Test assertions that could never fail (`or True`, `< 500` patterns)
|
||||
- `CaseInsensitiveDict` missing `__delitem__`, `pop`, and `setdefault` overrides
|
||||
- `assert` used for input validation in OpenAPI extension (stripped by `python -O`)
|
||||
- Potential XSS in GraphiQL template endpoint injection
|
||||
- Dead `or ""` in media format detection logic
|
||||
|
||||
### Changed
|
||||
|
||||
- `DELETE` requests now participate in Pydantic request body validation
|
||||
- Simplified status code category check to use chained comparison
|
||||
|
||||
### Removed
|
||||
|
||||
- Unused `method` parameter from `load_target()`
|
||||
- Unused Node.js setup step from CI test workflow
|
||||
|
||||
## [v3.6.1] - 2026-04-12
|
||||
|
||||
### Added
|
||||
|
||||
- Configurable GZip compression via `gzip` parameter on `API()` (defaults to `True`)
|
||||
|
||||
## [v3.6.0] - 2026-03-24
|
||||
|
||||
### Added
|
||||
@@ -491,6 +525,8 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
|
||||
- Conception!
|
||||
|
||||
[v3.6.2]: https://github.com/kennethreitz/responder/compare/v3.6.1..v3.6.2
|
||||
[v3.6.1]: https://github.com/kennethreitz/responder/compare/v3.6.0..v3.6.1
|
||||
[v3.6.0]: https://github.com/kennethreitz/responder/compare/v3.5.0..v3.6.0
|
||||
[v3.5.0]: https://github.com/kennethreitz/responder/compare/v3.4.0..v3.5.0
|
||||
[v3.4.0]: https://github.com/kennethreitz/responder/compare/v3.3.0..v3.4.0
|
||||
@@ -0,0 +1,26 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
console.info("Initializing sphinx-design-elements");
|
||||
setup_dropdown_group();
|
||||
});
|
||||
|
||||
function setup_dropdown_group() {
|
||||
|
||||
// Select all relevant detail elements nested within container elements using the `dropdown-group` class.
|
||||
const dropdown_details = document.querySelectorAll(".dropdown-group details");
|
||||
|
||||
// Add event listener for special toggling.
|
||||
dropdown_details.forEach((details) => {
|
||||
details.addEventListener("toggle", toggleOpenGroup);
|
||||
});
|
||||
|
||||
// When toggling elements, exclusively open one element, and close all others.
|
||||
function toggleOpenGroup(e) {
|
||||
if (this.open) {
|
||||
dropdown_details.forEach((details) => {
|
||||
if (details !== this && details.open) {
|
||||
details.open = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Appearance shortcuts.
|
||||
*/
|
||||
|
||||
|
||||
/* General */
|
||||
|
||||
/* Display paragraphs inline (side by side) */
|
||||
.inline p {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* No borders */
|
||||
.no-margin {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
hr.docutils {
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
/* Very small text size */
|
||||
.text-small {
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
/* Smaller text size */
|
||||
.text-smaller {
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
/* Medium text size */
|
||||
.text-medium {
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
/* Large text size */
|
||||
.text-large {
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
/* Very large text size */
|
||||
.text-larger {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
|
||||
/* Grid table: Light-weight table-row outline styling. */
|
||||
.top-border {
|
||||
border-top-width: thin;
|
||||
border-top-style: solid;
|
||||
border-top-color: lightgray;
|
||||
}
|
||||
.bottom-border {
|
||||
border-bottom-width: thin;
|
||||
border-bottom-style: solid;
|
||||
border-top-color: lightgray;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appearance bugfixes.
|
||||
*/
|
||||
|
||||
/* Grid table: Fix margins of child elements inside table items. */
|
||||
.sd-col > .highlight-markdown {
|
||||
margin: unset;
|
||||
}
|
||||
.sd-col > .highlight-restructuredtext {
|
||||
margin: unset;
|
||||
}
|
||||
|
||||
/* Shield: Inside a grid table, code blocks in neighbouring cells did not have any margins to each other. */
|
||||
.col-compact * {
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
.sd-col .highlight {
|
||||
margin-bottom: 0.3em !important;
|
||||
}
|
||||
|
||||
/* Info card: Fix grid item formatting. */
|
||||
.sd-col > div.line-block {
|
||||
margin-top: unset;
|
||||
margin-bottom: unset;
|
||||
}
|
||||
|
||||
.bottom-margin-generous {
|
||||
margin-bottom: 2em !important;
|
||||
}
|
||||
@@ -0,0 +1,663 @@
|
||||
/* -- page layout ----------------------------------------------------------- */
|
||||
|
||||
body {
|
||||
font-family: Georgia, serif;
|
||||
font-size: 17px;
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
div.document {
|
||||
width: 940px;
|
||||
margin: 30px auto 0 auto;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0 0 0 220px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
width: 220px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #B1B4B6;
|
||||
}
|
||||
|
||||
div.body {
|
||||
background-color: #fff;
|
||||
color: #3E4349;
|
||||
padding: 0 30px 0 30px;
|
||||
}
|
||||
|
||||
div.body > .section {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
width: 940px;
|
||||
margin: 20px auto 30px auto;
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
div.footer a {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
p.caption {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
|
||||
div.relations {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
div.sphinxsidebar {
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #999;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a:hover {
|
||||
border-bottom: 1px solid #999;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper {
|
||||
padding: 18px 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper p.logo {
|
||||
padding: 0;
|
||||
margin: -10px 0 0 0px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper h1.logo {
|
||||
margin-top: -10px;
|
||||
text-align: center;
|
||||
margin-bottom: 5px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper h1.logo-name {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper p.blurb {
|
||||
margin-top: 0;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3,
|
||||
div.sphinxsidebar h4 {
|
||||
font-family: Georgia, serif;
|
||||
color: #444;
|
||||
font-size: 24px;
|
||||
font-weight: normal;
|
||||
margin: 0 0 5px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h4 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3 a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.logo a,
|
||||
div.sphinxsidebar h3 a,
|
||||
div.sphinxsidebar p.logo a:hover,
|
||||
div.sphinxsidebar h3 a:hover {
|
||||
border: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p {
|
||||
color: #555;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
margin: 10px 0;
|
||||
padding: 0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul li.toctree-l1 > a {
|
||||
font-size: 120%;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul li.toctree-l2 > a {
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #CCC;
|
||||
font-family: Georgia, serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
div.sphinxsidebar #searchbox {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar .search > div {
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
div.sphinxsidebar hr {
|
||||
border: none;
|
||||
height: 1px;
|
||||
color: #AAA;
|
||||
background: #AAA;
|
||||
|
||||
text-align: left;
|
||||
margin-left: 0;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
div.sphinxsidebar .badge {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar .badge:hover {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* To address an issue with donation coming after search */
|
||||
div.sphinxsidebar h3.donation {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* -- body styles ----------------------------------------------------------- */
|
||||
|
||||
a {
|
||||
color: #004B6B;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #6D4100;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.body h1,
|
||||
div.body h2,
|
||||
div.body h3,
|
||||
div.body h4,
|
||||
div.body h5,
|
||||
div.body h6 {
|
||||
font-family: Georgia, serif;
|
||||
font-weight: normal;
|
||||
margin: 30px 0px 10px 0px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
|
||||
div.body h2 { font-size: 180%; }
|
||||
div.body h3 { font-size: 150%; }
|
||||
div.body h4 { font-size: 130%; }
|
||||
div.body h5 { font-size: 100%; }
|
||||
div.body h6 { font-size: 100%; }
|
||||
|
||||
a.headerlink {
|
||||
color: #DDD;
|
||||
padding: 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
color: #444;
|
||||
background: #EAEAEA;
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li {
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
div.admonition {
|
||||
margin: 20px 0px;
|
||||
padding: 10px 30px;
|
||||
background-color: #EEE;
|
||||
border: 1px solid #CCC;
|
||||
}
|
||||
|
||||
div.admonition tt.xref, div.admonition code.xref, div.admonition a tt {
|
||||
background-color: #FBFBFB;
|
||||
border-bottom: 1px solid #fafafa;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title {
|
||||
font-family: Georgia, serif;
|
||||
font-weight: normal;
|
||||
font-size: 24px;
|
||||
margin: 0 0 10px 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
div.admonition p.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt:target, .highlight {
|
||||
background: #FAF3E8;
|
||||
}
|
||||
|
||||
div.warning {
|
||||
background-color: #FCC;
|
||||
border: 1px solid #FAA;
|
||||
}
|
||||
|
||||
div.danger {
|
||||
background-color: #FCC;
|
||||
border: 1px solid #FAA;
|
||||
-moz-box-shadow: 2px 2px 4px #D52C2C;
|
||||
-webkit-box-shadow: 2px 2px 4px #D52C2C;
|
||||
box-shadow: 2px 2px 4px #D52C2C;
|
||||
}
|
||||
|
||||
div.error {
|
||||
background-color: #FCC;
|
||||
border: 1px solid #FAA;
|
||||
-moz-box-shadow: 2px 2px 4px #D52C2C;
|
||||
-webkit-box-shadow: 2px 2px 4px #D52C2C;
|
||||
box-shadow: 2px 2px 4px #D52C2C;
|
||||
}
|
||||
|
||||
div.caution {
|
||||
background-color: #FCC;
|
||||
border: 1px solid #FAA;
|
||||
}
|
||||
|
||||
div.attention {
|
||||
background-color: #FCC;
|
||||
border: 1px solid #FAA;
|
||||
}
|
||||
|
||||
div.important {
|
||||
background-color: #EEE;
|
||||
border: 1px solid #CCC;
|
||||
}
|
||||
|
||||
div.note {
|
||||
background-color: #EEE;
|
||||
border: 1px solid #CCC;
|
||||
}
|
||||
|
||||
div.tip {
|
||||
background-color: #EEE;
|
||||
border: 1px solid #CCC;
|
||||
}
|
||||
|
||||
div.hint {
|
||||
background-color: #EEE;
|
||||
border: 1px solid #CCC;
|
||||
}
|
||||
|
||||
div.seealso {
|
||||
background-color: #EEE;
|
||||
border: 1px solid #CCC;
|
||||
}
|
||||
|
||||
div.topic {
|
||||
background-color: #EEE;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p.admonition-title:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
pre, tt, code {
|
||||
font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.hll {
|
||||
background-color: #FFC;
|
||||
margin: 0 -12px;
|
||||
padding: 0 12px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
}
|
||||
|
||||
tt.descname, tt.descclassname, code.descname, code.descclassname {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
tt.descname, code.descname {
|
||||
padding-right: 0.08em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
-moz-box-shadow: 2px 2px 4px #EEE;
|
||||
-webkit-box-shadow: 2px 2px 4px #EEE;
|
||||
box-shadow: 2px 2px 4px #EEE;
|
||||
}
|
||||
|
||||
table.docutils {
|
||||
border: 1px solid #888;
|
||||
-moz-box-shadow: 2px 2px 4px #EEE;
|
||||
-webkit-box-shadow: 2px 2px 4px #EEE;
|
||||
box-shadow: 2px 2px 4px #EEE;
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
border: 1px solid #888;
|
||||
padding: 0.25em 0.7em;
|
||||
}
|
||||
|
||||
table.field-list, table.footnote {
|
||||
border: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
table.footnote {
|
||||
margin: 15px 0;
|
||||
width: 100%;
|
||||
border: 1px solid #EEE;
|
||||
background: #FDFDFD;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
table.footnote + table.footnote {
|
||||
margin-top: -15px;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
table.field-list th {
|
||||
padding: 0 0.8em 0 0;
|
||||
}
|
||||
|
||||
table.field-list td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.field-list p {
|
||||
margin-bottom: 0.8em;
|
||||
}
|
||||
|
||||
/* Cloned from
|
||||
* https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68
|
||||
*/
|
||||
.field-name {
|
||||
-moz-hyphens: manual;
|
||||
-ms-hyphens: manual;
|
||||
-webkit-hyphens: manual;
|
||||
hyphens: manual;
|
||||
}
|
||||
|
||||
table.footnote td.label {
|
||||
width: .1px;
|
||||
padding: 0.3em 0 0.3em 0.5em;
|
||||
}
|
||||
|
||||
table.footnote td {
|
||||
padding: 0.3em 0.5em;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
margin-top: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 0 30px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
/* Matches the 30px from the narrow-screen "li > ul" selector below */
|
||||
margin: 10px 0 10px 30px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: unset;
|
||||
padding: 7px 30px;
|
||||
margin: 15px 0px;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
div.viewcode-block:target {
|
||||
background: #ffd;
|
||||
}
|
||||
|
||||
dl pre, blockquote pre, li pre {
|
||||
margin-left: 0;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
tt, code {
|
||||
background-color: #ecf0f3;
|
||||
color: #222;
|
||||
/* padding: 1px 2px; */
|
||||
}
|
||||
|
||||
tt.xref, code.xref, a tt {
|
||||
background-color: #FBFBFB;
|
||||
border-bottom: 1px solid #fff;
|
||||
}
|
||||
|
||||
a.reference {
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #004B6B;
|
||||
}
|
||||
|
||||
a.reference:hover {
|
||||
border-bottom: 1px solid #6D4100;
|
||||
}
|
||||
|
||||
/* Don't put an underline on images */
|
||||
a.image-reference, a.image-reference:hover {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
a.footnote-reference {
|
||||
text-decoration: none;
|
||||
font-size: 0.7em;
|
||||
vertical-align: top;
|
||||
border-bottom: 1px dotted #004B6B;
|
||||
}
|
||||
|
||||
a.footnote-reference:hover {
|
||||
border-bottom: 1px solid #6D4100;
|
||||
}
|
||||
|
||||
a:hover tt, a:hover code {
|
||||
background: #EEE;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 940px) {
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px 30px;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: none;
|
||||
background: #fff;
|
||||
margin-left: 0;
|
||||
margin-top: 0;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
display: block;
|
||||
float: none;
|
||||
width: unset;
|
||||
margin: 50px -30px -20px -30px;
|
||||
padding: 10px 20px;
|
||||
background: #333;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
|
||||
div.sphinxsidebar h3 a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #AAA;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.document {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.body {
|
||||
min-height: 0;
|
||||
min-width: auto; /* fixes width on small screens, breaks .hll */
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.hll {
|
||||
/* "fixes" the breakage */
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.rtd_doc_footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.document {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.github {
|
||||
display: none;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
li > ul {
|
||||
/* Matches the 30px from the "ul, ol" selector above */
|
||||
margin-left: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* misc. */
|
||||
|
||||
.revsys-inline {
|
||||
display: none!important;
|
||||
}
|
||||
|
||||
/* Hide ugly table cell borders in ..bibliography:: directive output */
|
||||
table.docutils.citation, table.docutils.citation td, table.docutils.citation th {
|
||||
border: none;
|
||||
/* Below needed in some edge cases; if not applied, bottom shadows appear */
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
/* relbar */
|
||||
|
||||
.related {
|
||||
line-height: 30px;
|
||||
width: 100%;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.related.top {
|
||||
border-bottom: 1px solid #EEE;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.related.bottom {
|
||||
border-top: 1px solid #EEE;
|
||||
}
|
||||
|
||||
.related ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.related li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
nav#rellinks {
|
||||
float: right;
|
||||
}
|
||||
|
||||
nav#rellinks li+li:before {
|
||||
content: "|";
|
||||
}
|
||||
|
||||
nav#breadcrumbs li+li:before {
|
||||
content: "\00BB";
|
||||
}
|
||||
|
||||
/* Hide certain items when printing */
|
||||
@media print {
|
||||
div.related {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
img.github {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
border: 0;
|
||||
right: 0;
|
||||
}
|
||||
@@ -0,0 +1,906 @@
|
||||
/*
|
||||
* Sphinx stylesheet -- basic theme.
|
||||
*/
|
||||
|
||||
/* -- main layout ----------------------------------------------------------- */
|
||||
|
||||
div.clearer {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
div.section::after {
|
||||
display: block;
|
||||
content: '';
|
||||
clear: left;
|
||||
}
|
||||
|
||||
/* -- relbar ---------------------------------------------------------------- */
|
||||
|
||||
div.related {
|
||||
width: 100%;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
div.related h3 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.related ul {
|
||||
margin: 0;
|
||||
padding: 0 0 0 10px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
div.related li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
div.related li.right {
|
||||
float: right;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/* -- sidebar --------------------------------------------------------------- */
|
||||
|
||||
div.sphinxsidebarwrapper {
|
||||
padding: 10px 5px 0 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
float: left;
|
||||
width: 230px;
|
||||
margin-left: -100%;
|
||||
font-size: 90%;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap : break-word;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul ul,
|
||||
div.sphinxsidebar ul.want-points {
|
||||
margin-left: 20px;
|
||||
list-style: square;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar form {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #98dbcc;
|
||||
font-family: sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
div.sphinxsidebar #searchbox form.search {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
div.sphinxsidebar #searchbox input[type="text"] {
|
||||
float: left;
|
||||
width: 80%;
|
||||
padding: 0.25em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div.sphinxsidebar #searchbox input[type="submit"] {
|
||||
float: left;
|
||||
width: 20%;
|
||||
border-left: none;
|
||||
padding: 0.25em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* -- search page ----------------------------------------------------------- */
|
||||
|
||||
ul.search {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
ul.search li {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
ul.search li a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ul.search li p.context {
|
||||
color: #888;
|
||||
margin: 2px 0 0 30px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
ul.keywordmatches li.goodmatch a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* -- index page ------------------------------------------------------------ */
|
||||
|
||||
table.contentstable {
|
||||
width: 90%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
table.contentstable p.biglink {
|
||||
line-height: 150%;
|
||||
}
|
||||
|
||||
a.biglink {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
span.linkdescr {
|
||||
font-style: italic;
|
||||
padding-top: 5px;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
/* -- general index --------------------------------------------------------- */
|
||||
|
||||
table.indextable {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.indextable td {
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
table.indextable ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
table.indextable > tbody > tr > td > ul {
|
||||
padding-left: 0em;
|
||||
}
|
||||
|
||||
table.indextable tr.pcap {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
table.indextable tr.cap {
|
||||
margin-top: 10px;
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
img.toggler {
|
||||
margin-right: 3px;
|
||||
margin-top: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.modindex-jumpbox {
|
||||
border-top: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin: 1em 0 1em 0;
|
||||
padding: 0.4em;
|
||||
}
|
||||
|
||||
div.genindex-jumpbox {
|
||||
border-top: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin: 1em 0 1em 0;
|
||||
padding: 0.4em;
|
||||
}
|
||||
|
||||
/* -- domain module index --------------------------------------------------- */
|
||||
|
||||
table.modindextable td {
|
||||
padding: 2px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
/* -- general body styles --------------------------------------------------- */
|
||||
|
||||
div.body {
|
||||
min-width: inherit;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li, div.body blockquote {
|
||||
-moz-hyphens: auto;
|
||||
-ms-hyphens: auto;
|
||||
-webkit-hyphens: auto;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
a.headerlink {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #551A8B;
|
||||
}
|
||||
|
||||
h1:hover > a.headerlink,
|
||||
h2:hover > a.headerlink,
|
||||
h3:hover > a.headerlink,
|
||||
h4:hover > a.headerlink,
|
||||
h5:hover > a.headerlink,
|
||||
h6:hover > a.headerlink,
|
||||
dt:hover > a.headerlink,
|
||||
caption:hover > a.headerlink,
|
||||
p.caption:hover > a.headerlink,
|
||||
div.code-block-caption:hover > a.headerlink {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
div.body p.caption {
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
div.body td {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.first {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
p.rubric {
|
||||
margin-top: 30px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
img.align-left, figure.align-left, .figure.align-left, object.align-left {
|
||||
clear: left;
|
||||
float: left;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
img.align-right, figure.align-right, .figure.align-right, object.align-right {
|
||||
clear: right;
|
||||
float: right;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
img.align-center, figure.align-center, .figure.align-center, object.align-center {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
img.align-default, figure.align-default, .figure.align-default {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.align-default {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* -- sidebars -------------------------------------------------------------- */
|
||||
|
||||
div.sidebar,
|
||||
aside.sidebar {
|
||||
margin: 0 0 0.5em 1em;
|
||||
border: 1px solid #ddb;
|
||||
padding: 7px;
|
||||
background-color: #ffe;
|
||||
width: 40%;
|
||||
float: right;
|
||||
clear: right;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
p.sidebar-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
nav.contents,
|
||||
aside.topic,
|
||||
div.admonition, div.topic, blockquote {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
/* -- topics ---------------------------------------------------------------- */
|
||||
|
||||
nav.contents,
|
||||
aside.topic,
|
||||
div.topic {
|
||||
border: 1px solid #ccc;
|
||||
padding: 7px;
|
||||
margin: 10px 0 10px 0;
|
||||
}
|
||||
|
||||
p.topic-title {
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* -- admonitions ----------------------------------------------------------- */
|
||||
|
||||
div.admonition {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
div.admonition dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
margin: 0px 10px 5px 0px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.body p.centered {
|
||||
text-align: center;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
/* -- content of sidebars/topics/admonitions -------------------------------- */
|
||||
|
||||
div.sidebar > :last-child,
|
||||
aside.sidebar > :last-child,
|
||||
nav.contents > :last-child,
|
||||
aside.topic > :last-child,
|
||||
div.topic > :last-child,
|
||||
div.admonition > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.sidebar::after,
|
||||
aside.sidebar::after,
|
||||
nav.contents::after,
|
||||
aside.topic::after,
|
||||
div.topic::after,
|
||||
div.admonition::after,
|
||||
blockquote::after {
|
||||
display: block;
|
||||
content: '';
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* -- tables ---------------------------------------------------------------- */
|
||||
|
||||
table.docutils {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
border: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table.align-center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
table.align-default {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
table caption span.caption-number {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
table caption span.caption-text {
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
padding: 1px 8px 1px 5px;
|
||||
border-top: 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
table.citation {
|
||||
border-left: solid 1px gray;
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
table.citation td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
th > :first-child,
|
||||
td > :first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
th > :last-child,
|
||||
td > :last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
/* -- figures --------------------------------------------------------------- */
|
||||
|
||||
div.figure, figure {
|
||||
margin: 0.5em;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
div.figure p.caption, figcaption {
|
||||
padding: 0.3em;
|
||||
}
|
||||
|
||||
div.figure p.caption span.caption-number,
|
||||
figcaption span.caption-number {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
div.figure p.caption span.caption-text,
|
||||
figcaption span.caption-text {
|
||||
}
|
||||
|
||||
/* -- field list styles ----------------------------------------------------- */
|
||||
|
||||
table.field-list td, table.field-list th {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
.field-list ul {
|
||||
margin: 0;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.field-list p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.field-name {
|
||||
-moz-hyphens: manual;
|
||||
-ms-hyphens: manual;
|
||||
-webkit-hyphens: manual;
|
||||
hyphens: manual;
|
||||
}
|
||||
|
||||
/* -- hlist styles ---------------------------------------------------------- */
|
||||
|
||||
table.hlist {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
table.hlist td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/* -- object description styles --------------------------------------------- */
|
||||
|
||||
.sig {
|
||||
font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
||||
}
|
||||
|
||||
.sig-name, code.descname {
|
||||
background-color: transparent;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sig-name {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
code.descname {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.sig-prename, code.descclassname {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.optional {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.sig-paren {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.sig-param.n {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* C++ specific styling */
|
||||
|
||||
.sig-inline.c-texpr,
|
||||
.sig-inline.cpp-texpr {
|
||||
font-family: unset;
|
||||
}
|
||||
|
||||
.sig.c .k, .sig.c .kt,
|
||||
.sig.cpp .k, .sig.cpp .kt {
|
||||
color: #0033B3;
|
||||
}
|
||||
|
||||
.sig.c .m,
|
||||
.sig.cpp .m {
|
||||
color: #1750EB;
|
||||
}
|
||||
|
||||
.sig.c .s, .sig.c .sc,
|
||||
.sig.cpp .s, .sig.cpp .sc {
|
||||
color: #067D17;
|
||||
}
|
||||
|
||||
|
||||
/* -- other body styles ----------------------------------------------------- */
|
||||
|
||||
ol.arabic {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
ol.loweralpha {
|
||||
list-style: lower-alpha;
|
||||
}
|
||||
|
||||
ol.upperalpha {
|
||||
list-style: upper-alpha;
|
||||
}
|
||||
|
||||
ol.lowerroman {
|
||||
list-style: lower-roman;
|
||||
}
|
||||
|
||||
ol.upperroman {
|
||||
list-style: upper-roman;
|
||||
}
|
||||
|
||||
:not(li) > ol > li:first-child > :first-child,
|
||||
:not(li) > ul > li:first-child > :first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
:not(li) > ol > li:last-child > :last-child,
|
||||
:not(li) > ul > li:last-child > :last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
ol.simple ol p,
|
||||
ol.simple ul p,
|
||||
ul.simple ol p,
|
||||
ul.simple ul p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
ol.simple > li:not(:first-child) > p,
|
||||
ul.simple > li:not(:first-child) > p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
ol.simple p,
|
||||
ul.simple p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
aside.footnote > span,
|
||||
div.citation > span {
|
||||
float: left;
|
||||
}
|
||||
aside.footnote > span:last-of-type,
|
||||
div.citation > span:last-of-type {
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
aside.footnote > p {
|
||||
margin-left: 2em;
|
||||
}
|
||||
div.citation > p {
|
||||
margin-left: 4em;
|
||||
}
|
||||
aside.footnote > p:last-of-type,
|
||||
div.citation > p:last-of-type {
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
aside.footnote > p:last-of-type:after,
|
||||
div.citation > p:last-of-type:after {
|
||||
content: "";
|
||||
clear: both;
|
||||
}
|
||||
|
||||
dl.field-list {
|
||||
display: grid;
|
||||
grid-template-columns: fit-content(30%) auto;
|
||||
}
|
||||
|
||||
dl.field-list > dt {
|
||||
font-weight: bold;
|
||||
word-break: break-word;
|
||||
padding-left: 0.5em;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
dl.field-list > dd {
|
||||
padding-left: 0.5em;
|
||||
margin-top: 0em;
|
||||
margin-left: 0em;
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
dd > :first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
dd ul, dd table {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-top: 3px;
|
||||
margin-bottom: 10px;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
.sig dd {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.sig dl {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
dl > dd:last-child,
|
||||
dl > dd:last-child > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt:target, span.highlighted {
|
||||
background-color: #fbe54e;
|
||||
}
|
||||
|
||||
rect.highlighted {
|
||||
fill: #fbe54e;
|
||||
}
|
||||
|
||||
dl.glossary dt {
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.versionmodified {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.system-message {
|
||||
background-color: #fda;
|
||||
padding: 5px;
|
||||
border: 3px solid red;
|
||||
}
|
||||
|
||||
.footnote:target {
|
||||
background-color: #ffa;
|
||||
}
|
||||
|
||||
.line-block {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.line-block .line-block {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: 1.5em;
|
||||
}
|
||||
|
||||
.guilabel, .menuselection {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.accelerator {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.classifier {
|
||||
font-style: oblique;
|
||||
}
|
||||
|
||||
.classifier:before {
|
||||
font-style: normal;
|
||||
margin: 0 0.5em;
|
||||
content: ":";
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
abbr, acronym {
|
||||
border-bottom: dotted 1px;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
/* -- code displays --------------------------------------------------------- */
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
overflow-y: hidden; /* fixes display issues on Chrome browsers */
|
||||
}
|
||||
|
||||
pre, div[class*="highlight-"] {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
span.pre {
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
-webkit-hyphens: none;
|
||||
hyphens: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
div[class*="highlight-"] {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
td.linenos pre {
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
table.highlighttable {
|
||||
display: block;
|
||||
}
|
||||
|
||||
table.highlighttable tbody {
|
||||
display: block;
|
||||
}
|
||||
|
||||
table.highlighttable tr {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
table.highlighttable td {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.highlighttable td.linenos {
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
|
||||
table.highlighttable td.code {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.highlight .hll {
|
||||
display: block;
|
||||
}
|
||||
|
||||
div.highlight pre,
|
||||
table.highlighttable pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.code-block-caption + div {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
div.code-block-caption {
|
||||
margin-top: 1em;
|
||||
padding: 2px 5px;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
div.code-block-caption code {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
table.highlighttable td.linenos,
|
||||
span.linenos,
|
||||
div.highlight span.gp { /* gp: Generic.Prompt */
|
||||
user-select: none;
|
||||
-webkit-user-select: text; /* Safari fallback only */
|
||||
-webkit-user-select: none; /* Chrome/Safari */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* IE10+ */
|
||||
}
|
||||
|
||||
div.code-block-caption span.caption-number {
|
||||
padding: 0.1em 0.3em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
div.code-block-caption span.caption-text {
|
||||
}
|
||||
|
||||
div.literal-block-wrapper {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
code.xref, a code {
|
||||
background-color: transparent;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.viewcode-link {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.viewcode-back {
|
||||
float: right;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
div.viewcode-block:target {
|
||||
margin: -1px -10px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
/* -- math display ---------------------------------------------------------- */
|
||||
|
||||
img.math {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
div.body div.math p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
span.eqno {
|
||||
float: right;
|
||||
}
|
||||
|
||||
span.eqno a.headerlink {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
div.math:hover a.headerlink {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* -- printout stylesheet --------------------------------------------------- */
|
||||
|
||||
@media print {
|
||||
div.document,
|
||||
div.documentwrapper,
|
||||
div.bodywrapper {
|
||||
margin: 0 !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.sphinxsidebar,
|
||||
div.related,
|
||||
div.footer,
|
||||
#top-link {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-check" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#22863a" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M5 12l5 5l10 -10" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 313 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-copy" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="#000000" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<rect x="8" y="8" width="12" height="12" rx="2" />
|
||||
<path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 411 B |
@@ -0,0 +1,94 @@
|
||||
/* Copy buttons */
|
||||
button.copybtn {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
top: .3em;
|
||||
right: .3em;
|
||||
width: 1.7em;
|
||||
height: 1.7em;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s, border .3s, background-color .3s;
|
||||
user-select: none;
|
||||
padding: 0;
|
||||
border: none;
|
||||
outline: none;
|
||||
border-radius: 0.4em;
|
||||
/* The colors that GitHub uses */
|
||||
border: #1b1f2426 1px solid;
|
||||
background-color: #f6f8fa;
|
||||
color: #57606a;
|
||||
}
|
||||
|
||||
button.copybtn.success {
|
||||
border-color: #22863a;
|
||||
color: #22863a;
|
||||
}
|
||||
|
||||
button.copybtn svg {
|
||||
stroke: currentColor;
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
padding: 0.1em;
|
||||
}
|
||||
|
||||
div.highlight {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Show the copybutton */
|
||||
.highlight:hover button.copybtn, button.copybtn.success {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.highlight button.copybtn:hover {
|
||||
background-color: rgb(235, 235, 235);
|
||||
}
|
||||
|
||||
.highlight button.copybtn:active {
|
||||
background-color: rgb(187, 187, 187);
|
||||
}
|
||||
|
||||
/**
|
||||
* A minimal CSS-only tooltip copied from:
|
||||
* https://codepen.io/mildrenben/pen/rVBrpK
|
||||
*
|
||||
* To use, write HTML like the following:
|
||||
*
|
||||
* <p class="o-tooltip--left" data-tooltip="Hey">Short</p>
|
||||
*/
|
||||
.o-tooltip--left {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.o-tooltip--left:after {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
content: attr(data-tooltip);
|
||||
padding: .2em;
|
||||
font-size: .8em;
|
||||
left: -.2em;
|
||||
background: grey;
|
||||
color: white;
|
||||
white-space: nowrap;
|
||||
z-index: 2;
|
||||
border-radius: 2px;
|
||||
transform: translateX(-102%) translateY(0);
|
||||
transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1);
|
||||
}
|
||||
|
||||
.o-tooltip--left:hover:after {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateX(-100%) translateY(0);
|
||||
transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1);
|
||||
transition-delay: .5s;
|
||||
}
|
||||
|
||||
/* By default the copy button shouldn't show up when printing a page */
|
||||
@media print {
|
||||
button.copybtn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
// Localization support
|
||||
const messages = {
|
||||
'en': {
|
||||
'copy': 'Copy',
|
||||
'copy_to_clipboard': 'Copy to clipboard',
|
||||
'copy_success': 'Copied!',
|
||||
'copy_failure': 'Failed to copy',
|
||||
},
|
||||
'es' : {
|
||||
'copy': 'Copiar',
|
||||
'copy_to_clipboard': 'Copiar al portapapeles',
|
||||
'copy_success': '¡Copiado!',
|
||||
'copy_failure': 'Error al copiar',
|
||||
},
|
||||
'de' : {
|
||||
'copy': 'Kopieren',
|
||||
'copy_to_clipboard': 'In die Zwischenablage kopieren',
|
||||
'copy_success': 'Kopiert!',
|
||||
'copy_failure': 'Fehler beim Kopieren',
|
||||
},
|
||||
'fr' : {
|
||||
'copy': 'Copier',
|
||||
'copy_to_clipboard': 'Copier dans le presse-papier',
|
||||
'copy_success': 'Copié !',
|
||||
'copy_failure': 'Échec de la copie',
|
||||
},
|
||||
'ru': {
|
||||
'copy': 'Скопировать',
|
||||
'copy_to_clipboard': 'Скопировать в буфер',
|
||||
'copy_success': 'Скопировано!',
|
||||
'copy_failure': 'Не удалось скопировать',
|
||||
},
|
||||
'zh-CN': {
|
||||
'copy': '复制',
|
||||
'copy_to_clipboard': '复制到剪贴板',
|
||||
'copy_success': '复制成功!',
|
||||
'copy_failure': '复制失败',
|
||||
},
|
||||
'it' : {
|
||||
'copy': 'Copiare',
|
||||
'copy_to_clipboard': 'Copiato negli appunti',
|
||||
'copy_success': 'Copiato!',
|
||||
'copy_failure': 'Errore durante la copia',
|
||||
}
|
||||
}
|
||||
|
||||
let locale = 'en'
|
||||
if( document.documentElement.lang !== undefined
|
||||
&& messages[document.documentElement.lang] !== undefined ) {
|
||||
locale = document.documentElement.lang
|
||||
}
|
||||
|
||||
let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT;
|
||||
if (doc_url_root == '#') {
|
||||
doc_url_root = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* SVG files for our copy buttons
|
||||
*/
|
||||
let iconCheck = `<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-check" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#22863a" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<title>${messages[locale]['copy_success']}</title>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M5 12l5 5l10 -10" />
|
||||
</svg>`
|
||||
|
||||
// If the user specified their own SVG use that, otherwise use the default
|
||||
let iconCopy = ``;
|
||||
if (!iconCopy) {
|
||||
iconCopy = `<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-copy" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="#000000" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<title>${messages[locale]['copy_to_clipboard']}</title>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<rect x="8" y="8" width="12" height="12" rx="2" />
|
||||
<path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2" />
|
||||
</svg>`
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up copy/paste for code blocks
|
||||
*/
|
||||
|
||||
const runWhenDOMLoaded = cb => {
|
||||
if (document.readyState != 'loading') {
|
||||
cb()
|
||||
} else if (document.addEventListener) {
|
||||
document.addEventListener('DOMContentLoaded', cb)
|
||||
} else {
|
||||
document.attachEvent('onreadystatechange', function() {
|
||||
if (document.readyState == 'complete') cb()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const codeCellId = index => `codecell${index}`
|
||||
|
||||
// Clears selected text since ClipboardJS will select the text when copying
|
||||
const clearSelection = () => {
|
||||
if (window.getSelection) {
|
||||
window.getSelection().removeAllRanges()
|
||||
} else if (document.selection) {
|
||||
document.selection.empty()
|
||||
}
|
||||
}
|
||||
|
||||
// Changes tooltip text for a moment, then changes it back
|
||||
// We want the timeout of our `success` class to be a bit shorter than the
|
||||
// tooltip and icon change, so that we can hide the icon before changing back.
|
||||
var timeoutIcon = 2000;
|
||||
var timeoutSuccessClass = 1500;
|
||||
|
||||
const temporarilyChangeTooltip = (el, oldText, newText) => {
|
||||
el.setAttribute('data-tooltip', newText)
|
||||
el.classList.add('success')
|
||||
// Remove success a little bit sooner than we change the tooltip
|
||||
// So that we can use CSS to hide the copybutton first
|
||||
setTimeout(() => el.classList.remove('success'), timeoutSuccessClass)
|
||||
setTimeout(() => el.setAttribute('data-tooltip', oldText), timeoutIcon)
|
||||
}
|
||||
|
||||
// Changes the copy button icon for two seconds, then changes it back
|
||||
const temporarilyChangeIcon = (el) => {
|
||||
el.innerHTML = iconCheck;
|
||||
setTimeout(() => {el.innerHTML = iconCopy}, timeoutIcon)
|
||||
}
|
||||
|
||||
const addCopyButtonToCodeCells = () => {
|
||||
// If ClipboardJS hasn't loaded, wait a bit and try again. This
|
||||
// happens because we load ClipboardJS asynchronously.
|
||||
if (window.ClipboardJS === undefined) {
|
||||
setTimeout(addCopyButtonToCodeCells, 250)
|
||||
return
|
||||
}
|
||||
|
||||
// Add copybuttons to all of our code cells
|
||||
const COPYBUTTON_SELECTOR = 'div.highlight pre';
|
||||
const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR)
|
||||
codeCells.forEach((codeCell, index) => {
|
||||
const id = codeCellId(index)
|
||||
codeCell.setAttribute('id', id)
|
||||
|
||||
const clipboardButton = id =>
|
||||
`<button class="copybtn o-tooltip--left" data-tooltip="${messages[locale]['copy']}" data-clipboard-target="#${id}">
|
||||
${iconCopy}
|
||||
</button>`
|
||||
codeCell.insertAdjacentHTML('afterend', clipboardButton(id))
|
||||
})
|
||||
|
||||
function escapeRegExp(string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes excluded text from a Node.
|
||||
*
|
||||
* @param {Node} target Node to filter.
|
||||
* @param {string} exclude CSS selector of nodes to exclude.
|
||||
* @returns {DOMString} Text from `target` with text removed.
|
||||
*/
|
||||
function filterText(target, exclude) {
|
||||
const clone = target.cloneNode(true); // clone as to not modify the live DOM
|
||||
if (exclude) {
|
||||
// remove excluded nodes
|
||||
clone.querySelectorAll(exclude).forEach(node => node.remove());
|
||||
}
|
||||
return clone.innerText;
|
||||
}
|
||||
|
||||
// Callback when a copy button is clicked. Will be passed the node that was clicked
|
||||
// should then grab the text and replace pieces of text that shouldn't be used in output
|
||||
function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") {
|
||||
var regexp;
|
||||
var match;
|
||||
|
||||
// Do we check for line continuation characters and "HERE-documents"?
|
||||
var useLineCont = !!lineContinuationChar
|
||||
var useHereDoc = !!hereDocDelim
|
||||
|
||||
// create regexp to capture prompt and remaining line
|
||||
if (isRegexp) {
|
||||
regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)')
|
||||
} else {
|
||||
regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)')
|
||||
}
|
||||
|
||||
const outputLines = [];
|
||||
var promptFound = false;
|
||||
var gotLineCont = false;
|
||||
var gotHereDoc = false;
|
||||
const lineGotPrompt = [];
|
||||
for (const line of textContent.split('\n')) {
|
||||
match = line.match(regexp)
|
||||
if (match || gotLineCont || gotHereDoc) {
|
||||
promptFound = regexp.test(line)
|
||||
lineGotPrompt.push(promptFound)
|
||||
if (removePrompts && promptFound) {
|
||||
outputLines.push(match[2])
|
||||
} else {
|
||||
outputLines.push(line)
|
||||
}
|
||||
gotLineCont = line.endsWith(lineContinuationChar) & useLineCont
|
||||
if (line.includes(hereDocDelim) & useHereDoc)
|
||||
gotHereDoc = !gotHereDoc
|
||||
} else if (!onlyCopyPromptLines) {
|
||||
outputLines.push(line)
|
||||
} else if (copyEmptyLines && line.trim() === '') {
|
||||
outputLines.push(line)
|
||||
}
|
||||
}
|
||||
|
||||
// If no lines with the prompt were found then just use original lines
|
||||
if (lineGotPrompt.some(v => v === true)) {
|
||||
textContent = outputLines.join('\n');
|
||||
}
|
||||
|
||||
// Remove a trailing newline to avoid auto-running when pasting
|
||||
if (textContent.endsWith("\n")) {
|
||||
textContent = textContent.slice(0, -1)
|
||||
}
|
||||
return textContent
|
||||
}
|
||||
|
||||
|
||||
var copyTargetText = (trigger) => {
|
||||
var target = document.querySelector(trigger.attributes['data-clipboard-target'].value);
|
||||
|
||||
// get filtered text
|
||||
let exclude = '.linenos';
|
||||
|
||||
let text = filterText(target, exclude);
|
||||
return formatCopyText(text, '>>> |\\.\\.\\. |\\$ ', true, true, true, true, '', '')
|
||||
}
|
||||
|
||||
// Initialize with a callback so we can modify the text before copy
|
||||
const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText})
|
||||
|
||||
// Update UI with error/success messages
|
||||
clipboard.on('success', event => {
|
||||
clearSelection()
|
||||
temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success'])
|
||||
temporarilyChangeIcon(event.trigger)
|
||||
})
|
||||
|
||||
clipboard.on('error', event => {
|
||||
temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure'])
|
||||
})
|
||||
}
|
||||
|
||||
runWhenDOMLoaded(addCopyButtonToCodeCells)
|
||||
@@ -0,0 +1,73 @@
|
||||
function escapeRegExp(string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes excluded text from a Node.
|
||||
*
|
||||
* @param {Node} target Node to filter.
|
||||
* @param {string} exclude CSS selector of nodes to exclude.
|
||||
* @returns {DOMString} Text from `target` with text removed.
|
||||
*/
|
||||
export function filterText(target, exclude) {
|
||||
const clone = target.cloneNode(true); // clone as to not modify the live DOM
|
||||
if (exclude) {
|
||||
// remove excluded nodes
|
||||
clone.querySelectorAll(exclude).forEach(node => node.remove());
|
||||
}
|
||||
return clone.innerText;
|
||||
}
|
||||
|
||||
// Callback when a copy button is clicked. Will be passed the node that was clicked
|
||||
// should then grab the text and replace pieces of text that shouldn't be used in output
|
||||
export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") {
|
||||
var regexp;
|
||||
var match;
|
||||
|
||||
// Do we check for line continuation characters and "HERE-documents"?
|
||||
var useLineCont = !!lineContinuationChar
|
||||
var useHereDoc = !!hereDocDelim
|
||||
|
||||
// create regexp to capture prompt and remaining line
|
||||
if (isRegexp) {
|
||||
regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)')
|
||||
} else {
|
||||
regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)')
|
||||
}
|
||||
|
||||
const outputLines = [];
|
||||
var promptFound = false;
|
||||
var gotLineCont = false;
|
||||
var gotHereDoc = false;
|
||||
const lineGotPrompt = [];
|
||||
for (const line of textContent.split('\n')) {
|
||||
match = line.match(regexp)
|
||||
if (match || gotLineCont || gotHereDoc) {
|
||||
promptFound = regexp.test(line)
|
||||
lineGotPrompt.push(promptFound)
|
||||
if (removePrompts && promptFound) {
|
||||
outputLines.push(match[2])
|
||||
} else {
|
||||
outputLines.push(line)
|
||||
}
|
||||
gotLineCont = line.endsWith(lineContinuationChar) & useLineCont
|
||||
if (line.includes(hereDocDelim) & useHereDoc)
|
||||
gotHereDoc = !gotHereDoc
|
||||
} else if (!onlyCopyPromptLines) {
|
||||
outputLines.push(line)
|
||||
} else if (copyEmptyLines && line.trim() === '') {
|
||||
outputLines.push(line)
|
||||
}
|
||||
}
|
||||
|
||||
// If no lines with the prompt were found then just use original lines
|
||||
if (lineGotPrompt.some(v => v === true)) {
|
||||
textContent = outputLines.join('\n');
|
||||
}
|
||||
|
||||
// Remove a trailing newline to avoid auto-running when pasting
|
||||
if (textContent.endsWith("\n")) {
|
||||
textContent = textContent.slice(0, -1)
|
||||
}
|
||||
return textContent
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/* This file intentionally left blank. */
|
||||
@@ -0,0 +1,26 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
console.info("Initializing sphinx-design-elements");
|
||||
setup_dropdown_group();
|
||||
});
|
||||
|
||||
function setup_dropdown_group() {
|
||||
|
||||
// Select all relevant detail elements nested within container elements using the `dropdown-group` class.
|
||||
const dropdown_details = document.querySelectorAll(".dropdown-group details");
|
||||
|
||||
// Add event listener for special toggling.
|
||||
dropdown_details.forEach((details) => {
|
||||
details.addEventListener("toggle", toggleOpenGroup);
|
||||
});
|
||||
|
||||
// When toggling elements, exclusively open one element, and close all others.
|
||||
function toggleOpenGroup(e) {
|
||||
if (this.open) {
|
||||
dropdown_details.forEach((details) => {
|
||||
if (details !== this && details.open) {
|
||||
details.open = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Appearance shortcuts.
|
||||
*/
|
||||
|
||||
|
||||
/* General */
|
||||
|
||||
/* Display paragraphs inline (side by side) */
|
||||
.inline p {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* No borders */
|
||||
.no-margin {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
hr.docutils {
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
/* Very small text size */
|
||||
.text-small {
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
/* Smaller text size */
|
||||
.text-smaller {
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
/* Medium text size */
|
||||
.text-medium {
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
/* Large text size */
|
||||
.text-large {
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
/* Very large text size */
|
||||
.text-larger {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
|
||||
/* Grid table: Light-weight table-row outline styling. */
|
||||
.top-border {
|
||||
border-top-width: thin;
|
||||
border-top-style: solid;
|
||||
border-top-color: lightgray;
|
||||
}
|
||||
.bottom-border {
|
||||
border-bottom-width: thin;
|
||||
border-bottom-style: solid;
|
||||
border-top-color: lightgray;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appearance bugfixes.
|
||||
*/
|
||||
|
||||
/* Grid table: Fix margins of child elements inside table items. */
|
||||
.sd-col > .highlight-markdown {
|
||||
margin: unset;
|
||||
}
|
||||
.sd-col > .highlight-restructuredtext {
|
||||
margin: unset;
|
||||
}
|
||||
|
||||
/* Shield: Inside a grid table, code blocks in neighbouring cells did not have any margins to each other. */
|
||||
.col-compact * {
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
.sd-col .highlight {
|
||||
margin-bottom: 0.3em !important;
|
||||
}
|
||||
|
||||
/* Info card: Fix grid item formatting. */
|
||||
.sd-col > div.line-block {
|
||||
margin-top: unset;
|
||||
margin-bottom: unset;
|
||||
}
|
||||
|
||||
.bottom-margin-generous {
|
||||
margin-bottom: 2em !important;
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Base JavaScript utilities for all Sphinx HTML documentation.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([
|
||||
"TEXTAREA",
|
||||
"INPUT",
|
||||
"SELECT",
|
||||
"BUTTON",
|
||||
]);
|
||||
|
||||
const _ready = (callback) => {
|
||||
if (document.readyState !== "loading") {
|
||||
callback();
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", callback);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Small JavaScript module for the documentation.
|
||||
*/
|
||||
const Documentation = {
|
||||
init: () => {
|
||||
Documentation.initDomainIndexTable();
|
||||
Documentation.initOnKeyListeners();
|
||||
},
|
||||
|
||||
/**
|
||||
* i18n support
|
||||
*/
|
||||
TRANSLATIONS: {},
|
||||
PLURAL_EXPR: (n) => (n === 1 ? 0 : 1),
|
||||
LOCALE: "unknown",
|
||||
|
||||
// gettext and ngettext don't access this so that the functions
|
||||
// can safely bound to a different name (_ = Documentation.gettext)
|
||||
gettext: (string) => {
|
||||
const translated = Documentation.TRANSLATIONS[string];
|
||||
switch (typeof translated) {
|
||||
case "undefined":
|
||||
return string; // no translation
|
||||
case "string":
|
||||
return translated; // translation exists
|
||||
default:
|
||||
return translated[0]; // (singular, plural) translation tuple exists
|
||||
}
|
||||
},
|
||||
|
||||
ngettext: (singular, plural, n) => {
|
||||
const translated = Documentation.TRANSLATIONS[singular];
|
||||
if (typeof translated !== "undefined")
|
||||
return translated[Documentation.PLURAL_EXPR(n)];
|
||||
return n === 1 ? singular : plural;
|
||||
},
|
||||
|
||||
addTranslations: (catalog) => {
|
||||
Object.assign(Documentation.TRANSLATIONS, catalog.messages);
|
||||
Documentation.PLURAL_EXPR = new Function(
|
||||
"n",
|
||||
`return (${catalog.plural_expr})`
|
||||
);
|
||||
Documentation.LOCALE = catalog.locale;
|
||||
},
|
||||
|
||||
/**
|
||||
* helper function to focus on search bar
|
||||
*/
|
||||
focusSearchBar: () => {
|
||||
document.querySelectorAll("input[name=q]")[0]?.focus();
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialise the domain index toggle buttons
|
||||
*/
|
||||
initDomainIndexTable: () => {
|
||||
const toggler = (el) => {
|
||||
const idNumber = el.id.substr(7);
|
||||
const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`);
|
||||
if (el.src.substr(-9) === "minus.png") {
|
||||
el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`;
|
||||
toggledRows.forEach((el) => (el.style.display = "none"));
|
||||
} else {
|
||||
el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`;
|
||||
toggledRows.forEach((el) => (el.style.display = ""));
|
||||
}
|
||||
};
|
||||
|
||||
const togglerElements = document.querySelectorAll("img.toggler");
|
||||
togglerElements.forEach((el) =>
|
||||
el.addEventListener("click", (event) => toggler(event.currentTarget))
|
||||
);
|
||||
togglerElements.forEach((el) => (el.style.display = ""));
|
||||
if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler);
|
||||
},
|
||||
|
||||
initOnKeyListeners: () => {
|
||||
// only install a listener if it is really needed
|
||||
if (
|
||||
!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS &&
|
||||
!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS
|
||||
)
|
||||
return;
|
||||
|
||||
document.addEventListener("keydown", (event) => {
|
||||
// bail for input elements
|
||||
if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
|
||||
// bail with special keys
|
||||
if (event.altKey || event.ctrlKey || event.metaKey) return;
|
||||
|
||||
if (!event.shiftKey) {
|
||||
switch (event.key) {
|
||||
case "ArrowLeft":
|
||||
if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
|
||||
|
||||
const prevLink = document.querySelector('link[rel="prev"]');
|
||||
if (prevLink && prevLink.href) {
|
||||
window.location.href = prevLink.href;
|
||||
event.preventDefault();
|
||||
}
|
||||
break;
|
||||
case "ArrowRight":
|
||||
if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
|
||||
|
||||
const nextLink = document.querySelector('link[rel="next"]');
|
||||
if (nextLink && nextLink.href) {
|
||||
window.location.href = nextLink.href;
|
||||
event.preventDefault();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// some keyboard layouts may need Shift to get /
|
||||
switch (event.key) {
|
||||
case "/":
|
||||
if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break;
|
||||
Documentation.focusSearchBar();
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// quick alias for translations
|
||||
const _ = Documentation.gettext;
|
||||
|
||||
_ready(Documentation.init);
|
||||
@@ -0,0 +1,13 @@
|
||||
const DOCUMENTATION_OPTIONS = {
|
||||
VERSION: '3.6.2',
|
||||
LANGUAGE: 'en',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
FILE_SUFFIX: '.html',
|
||||
LINK_SUFFIX: '.html',
|
||||
HAS_SOURCE: true,
|
||||
SOURCELINK_SUFFIX: '.txt',
|
||||
NAVIGATION_WITH_KEYS: false,
|
||||
SHOW_SEARCH_SUMMARY: true,
|
||||
ENABLE_SEARCH_SHORTCUTS: true,
|
||||
};
|
||||
|
After Width: | Height: | Size: 286 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 250 250" fill="#fff">
|
||||
<path d="M0 0l115 115h15l12 27 108 108V0z" fill="#151513"/>
|
||||
<path d="M128 109c-15-9-9-19-9-19 3-7 2-11 2-11-1-7 3-2 3-2 4 5 2 11 2 11-3 10 5 15 9 16"/>
|
||||
<path d="M115 115s4 2 5 0l14-14c3-2 6-3 8-3-8-11-15-24 2-41 5-5 10-7 16-7 1-2 3-7 12-11 0 0 5 3 7 16 4 2 8 5 12 9s7 8 9 12c14 3 17 7 17 7-4 8-9 11-11 11 0 6-2 11-7 16-16 16-30 10-41 2 0 3-1 7-5 11l-12 11c-1 1 1 5 1 5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 490 B |
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* This script contains the language-specific data used by searchtools.js,
|
||||
* namely the list of stopwords, stemmer, scorer and splitter.
|
||||
*/
|
||||
|
||||
var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"];
|
||||
|
||||
|
||||
/* Non-minified version is copied as a separate JS file, if available */
|
||||
|
||||
/**
|
||||
* Porter Stemmer
|
||||
*/
|
||||
var Stemmer = function() {
|
||||
|
||||
var step2list = {
|
||||
ational: 'ate',
|
||||
tional: 'tion',
|
||||
enci: 'ence',
|
||||
anci: 'ance',
|
||||
izer: 'ize',
|
||||
bli: 'ble',
|
||||
alli: 'al',
|
||||
entli: 'ent',
|
||||
eli: 'e',
|
||||
ousli: 'ous',
|
||||
ization: 'ize',
|
||||
ation: 'ate',
|
||||
ator: 'ate',
|
||||
alism: 'al',
|
||||
iveness: 'ive',
|
||||
fulness: 'ful',
|
||||
ousness: 'ous',
|
||||
aliti: 'al',
|
||||
iviti: 'ive',
|
||||
biliti: 'ble',
|
||||
logi: 'log'
|
||||
};
|
||||
|
||||
var step3list = {
|
||||
icate: 'ic',
|
||||
ative: '',
|
||||
alize: 'al',
|
||||
iciti: 'ic',
|
||||
ical: 'ic',
|
||||
ful: '',
|
||||
ness: ''
|
||||
};
|
||||
|
||||
var c = "[^aeiou]"; // consonant
|
||||
var v = "[aeiouy]"; // vowel
|
||||
var C = c + "[^aeiouy]*"; // consonant sequence
|
||||
var V = v + "[aeiou]*"; // vowel sequence
|
||||
|
||||
var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0
|
||||
var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1
|
||||
var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1
|
||||
var s_v = "^(" + C + ")?" + v; // vowel in stem
|
||||
|
||||
this.stemWord = function (w) {
|
||||
var stem;
|
||||
var suffix;
|
||||
var firstch;
|
||||
var origword = w;
|
||||
|
||||
if (w.length < 3)
|
||||
return w;
|
||||
|
||||
var re;
|
||||
var re2;
|
||||
var re3;
|
||||
var re4;
|
||||
|
||||
firstch = w.substr(0,1);
|
||||
if (firstch == "y")
|
||||
w = firstch.toUpperCase() + w.substr(1);
|
||||
|
||||
// Step 1a
|
||||
re = /^(.+?)(ss|i)es$/;
|
||||
re2 = /^(.+?)([^s])s$/;
|
||||
|
||||
if (re.test(w))
|
||||
w = w.replace(re,"$1$2");
|
||||
else if (re2.test(w))
|
||||
w = w.replace(re2,"$1$2");
|
||||
|
||||
// Step 1b
|
||||
re = /^(.+?)eed$/;
|
||||
re2 = /^(.+?)(ed|ing)$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
re = new RegExp(mgr0);
|
||||
if (re.test(fp[1])) {
|
||||
re = /.$/;
|
||||
w = w.replace(re,"");
|
||||
}
|
||||
}
|
||||
else if (re2.test(w)) {
|
||||
var fp = re2.exec(w);
|
||||
stem = fp[1];
|
||||
re2 = new RegExp(s_v);
|
||||
if (re2.test(stem)) {
|
||||
w = stem;
|
||||
re2 = /(at|bl|iz)$/;
|
||||
re3 = new RegExp("([^aeiouylsz])\\1$");
|
||||
re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
|
||||
if (re2.test(w))
|
||||
w = w + "e";
|
||||
else if (re3.test(w)) {
|
||||
re = /.$/;
|
||||
w = w.replace(re,"");
|
||||
}
|
||||
else if (re4.test(w))
|
||||
w = w + "e";
|
||||
}
|
||||
}
|
||||
|
||||
// Step 1c
|
||||
re = /^(.+?)y$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
stem = fp[1];
|
||||
re = new RegExp(s_v);
|
||||
if (re.test(stem))
|
||||
w = stem + "i";
|
||||
}
|
||||
|
||||
// Step 2
|
||||
re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
stem = fp[1];
|
||||
suffix = fp[2];
|
||||
re = new RegExp(mgr0);
|
||||
if (re.test(stem))
|
||||
w = stem + step2list[suffix];
|
||||
}
|
||||
|
||||
// Step 3
|
||||
re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
stem = fp[1];
|
||||
suffix = fp[2];
|
||||
re = new RegExp(mgr0);
|
||||
if (re.test(stem))
|
||||
w = stem + step3list[suffix];
|
||||
}
|
||||
|
||||
// Step 4
|
||||
re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
|
||||
re2 = /^(.+?)(s|t)(ion)$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
stem = fp[1];
|
||||
re = new RegExp(mgr1);
|
||||
if (re.test(stem))
|
||||
w = stem;
|
||||
}
|
||||
else if (re2.test(w)) {
|
||||
var fp = re2.exec(w);
|
||||
stem = fp[1] + fp[2];
|
||||
re2 = new RegExp(mgr1);
|
||||
if (re2.test(stem))
|
||||
w = stem;
|
||||
}
|
||||
|
||||
// Step 5
|
||||
re = /^(.+?)e$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
stem = fp[1];
|
||||
re = new RegExp(mgr1);
|
||||
re2 = new RegExp(meq1);
|
||||
re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
|
||||
if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
|
||||
w = stem;
|
||||
}
|
||||
re = /ll$/;
|
||||
re2 = new RegExp(mgr1);
|
||||
if (re.test(w) && re2.test(w)) {
|
||||
re = /.$/;
|
||||
w = w.replace(re,"");
|
||||
}
|
||||
|
||||
// and turn initial Y back to y
|
||||
if (firstch == "y")
|
||||
w = firstch.toLowerCase() + w.substr(1);
|
||||
return w;
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 90 B |
|
After Width: | Height: | Size: 90 B |
@@ -0,0 +1,84 @@
|
||||
pre { line-height: 125%; }
|
||||
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
|
||||
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
|
||||
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
|
||||
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
|
||||
.highlight .hll { background-color: #ffffcc }
|
||||
.highlight { background: #f8f8f8; }
|
||||
.highlight .c { color: #8F5902; font-style: italic } /* Comment */
|
||||
.highlight .err { color: #A40000; border: 1px solid #EF2929 } /* Error */
|
||||
.highlight .g { color: #000 } /* Generic */
|
||||
.highlight .k { color: #004461; font-weight: bold } /* Keyword */
|
||||
.highlight .l { color: #000 } /* Literal */
|
||||
.highlight .n { color: #000 } /* Name */
|
||||
.highlight .o { color: #582800 } /* Operator */
|
||||
.highlight .x { color: #000 } /* Other */
|
||||
.highlight .p { color: #000; font-weight: bold } /* Punctuation */
|
||||
.highlight .ch { color: #8F5902; font-style: italic } /* Comment.Hashbang */
|
||||
.highlight .cm { color: #8F5902; font-style: italic } /* Comment.Multiline */
|
||||
.highlight .cp { color: #8F5902 } /* Comment.Preproc */
|
||||
.highlight .cpf { color: #8F5902; font-style: italic } /* Comment.PreprocFile */
|
||||
.highlight .c1 { color: #8F5902; font-style: italic } /* Comment.Single */
|
||||
.highlight .cs { color: #8F5902; font-style: italic } /* Comment.Special */
|
||||
.highlight .gd { color: #A40000 } /* Generic.Deleted */
|
||||
.highlight .ge { color: #000; font-style: italic } /* Generic.Emph */
|
||||
.highlight .ges { color: #000 } /* Generic.EmphStrong */
|
||||
.highlight .gr { color: #EF2929 } /* Generic.Error */
|
||||
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
||||
.highlight .gi { color: #00A000 } /* Generic.Inserted */
|
||||
.highlight .go { color: #888 } /* Generic.Output */
|
||||
.highlight .gp { color: #745334 } /* Generic.Prompt */
|
||||
.highlight .gs { color: #000; font-weight: bold } /* Generic.Strong */
|
||||
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
|
||||
.highlight .gt { color: #A40000; font-weight: bold } /* Generic.Traceback */
|
||||
.highlight .kc { color: #004461; font-weight: bold } /* Keyword.Constant */
|
||||
.highlight .kd { color: #004461; font-weight: bold } /* Keyword.Declaration */
|
||||
.highlight .kn { color: #004461; font-weight: bold } /* Keyword.Namespace */
|
||||
.highlight .kp { color: #004461; font-weight: bold } /* Keyword.Pseudo */
|
||||
.highlight .kr { color: #004461; font-weight: bold } /* Keyword.Reserved */
|
||||
.highlight .kt { color: #004461; font-weight: bold } /* Keyword.Type */
|
||||
.highlight .ld { color: #000 } /* Literal.Date */
|
||||
.highlight .m { color: #900 } /* Literal.Number */
|
||||
.highlight .s { color: #4E9A06 } /* Literal.String */
|
||||
.highlight .na { color: #C4A000 } /* Name.Attribute */
|
||||
.highlight .nb { color: #004461 } /* Name.Builtin */
|
||||
.highlight .nc { color: #000 } /* Name.Class */
|
||||
.highlight .no { color: #000 } /* Name.Constant */
|
||||
.highlight .nd { color: #888 } /* Name.Decorator */
|
||||
.highlight .ni { color: #CE5C00 } /* Name.Entity */
|
||||
.highlight .ne { color: #C00; font-weight: bold } /* Name.Exception */
|
||||
.highlight .nf { color: #000 } /* Name.Function */
|
||||
.highlight .nl { color: #F57900 } /* Name.Label */
|
||||
.highlight .nn { color: #000 } /* Name.Namespace */
|
||||
.highlight .nx { color: #000 } /* Name.Other */
|
||||
.highlight .py { color: #000 } /* Name.Property */
|
||||
.highlight .nt { color: #004461; font-weight: bold } /* Name.Tag */
|
||||
.highlight .nv { color: #000 } /* Name.Variable */
|
||||
.highlight .ow { color: #004461; font-weight: bold } /* Operator.Word */
|
||||
.highlight .pm { color: #000; font-weight: bold } /* Punctuation.Marker */
|
||||
.highlight .w { color: #F8F8F8 } /* Text.Whitespace */
|
||||
.highlight .mb { color: #900 } /* Literal.Number.Bin */
|
||||
.highlight .mf { color: #900 } /* Literal.Number.Float */
|
||||
.highlight .mh { color: #900 } /* Literal.Number.Hex */
|
||||
.highlight .mi { color: #900 } /* Literal.Number.Integer */
|
||||
.highlight .mo { color: #900 } /* Literal.Number.Oct */
|
||||
.highlight .sa { color: #4E9A06 } /* Literal.String.Affix */
|
||||
.highlight .sb { color: #4E9A06 } /* Literal.String.Backtick */
|
||||
.highlight .sc { color: #4E9A06 } /* Literal.String.Char */
|
||||
.highlight .dl { color: #4E9A06 } /* Literal.String.Delimiter */
|
||||
.highlight .sd { color: #8F5902; font-style: italic } /* Literal.String.Doc */
|
||||
.highlight .s2 { color: #4E9A06 } /* Literal.String.Double */
|
||||
.highlight .se { color: #4E9A06 } /* Literal.String.Escape */
|
||||
.highlight .sh { color: #4E9A06 } /* Literal.String.Heredoc */
|
||||
.highlight .si { color: #4E9A06 } /* Literal.String.Interpol */
|
||||
.highlight .sx { color: #4E9A06 } /* Literal.String.Other */
|
||||
.highlight .sr { color: #4E9A06 } /* Literal.String.Regex */
|
||||
.highlight .s1 { color: #4E9A06 } /* Literal.String.Single */
|
||||
.highlight .ss { color: #4E9A06 } /* Literal.String.Symbol */
|
||||
.highlight .bp { color: #3465A4 } /* Name.Builtin.Pseudo */
|
||||
.highlight .fm { color: #000 } /* Name.Function.Magic */
|
||||
.highlight .vc { color: #000 } /* Name.Variable.Class */
|
||||
.highlight .vg { color: #000 } /* Name.Variable.Global */
|
||||
.highlight .vi { color: #000 } /* Name.Variable.Instance */
|
||||
.highlight .vm { color: #000 } /* Name.Variable.Magic */
|
||||
.highlight .il { color: #900 } /* Literal.Number.Integer.Long */
|
||||
|
Before Width: | Height: | Size: 762 KiB After Width: | Height: | Size: 762 KiB |
@@ -0,0 +1,635 @@
|
||||
/*
|
||||
* Sphinx JavaScript utilities for the full-text search.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Simple result scoring code.
|
||||
*/
|
||||
if (typeof Scorer === "undefined") {
|
||||
var Scorer = {
|
||||
// Implement the following function to further tweak the score for each result
|
||||
// The function takes a result array [docname, title, anchor, descr, score, filename]
|
||||
// and returns the new score.
|
||||
/*
|
||||
score: result => {
|
||||
const [docname, title, anchor, descr, score, filename, kind] = result
|
||||
return score
|
||||
},
|
||||
*/
|
||||
|
||||
// query matches the full name of an object
|
||||
objNameMatch: 11,
|
||||
// or matches in the last dotted part of the object name
|
||||
objPartialMatch: 6,
|
||||
// Additive scores depending on the priority of the object
|
||||
objPrio: {
|
||||
0: 15, // used to be importantResults
|
||||
1: 5, // used to be objectResults
|
||||
2: -5, // used to be unimportantResults
|
||||
},
|
||||
// Used when the priority is not in the mapping.
|
||||
objPrioDefault: 0,
|
||||
|
||||
// query found in title
|
||||
title: 15,
|
||||
partialTitle: 7,
|
||||
// query found in terms
|
||||
term: 5,
|
||||
partialTerm: 2,
|
||||
};
|
||||
}
|
||||
|
||||
// Global search result kind enum, used by themes to style search results.
|
||||
class SearchResultKind {
|
||||
static get index() { return "index"; }
|
||||
static get object() { return "object"; }
|
||||
static get text() { return "text"; }
|
||||
static get title() { return "title"; }
|
||||
}
|
||||
|
||||
const _removeChildren = (element) => {
|
||||
while (element && element.lastChild) element.removeChild(element.lastChild);
|
||||
};
|
||||
|
||||
/**
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
|
||||
*/
|
||||
const _escapeRegExp = (string) =>
|
||||
string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
|
||||
|
||||
const _displayItem = (item, searchTerms, highlightTerms) => {
|
||||
const docBuilder = DOCUMENTATION_OPTIONS.BUILDER;
|
||||
const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX;
|
||||
const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX;
|
||||
const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY;
|
||||
const contentRoot = document.documentElement.dataset.content_root;
|
||||
|
||||
const [docName, title, anchor, descr, score, _filename, kind] = item;
|
||||
|
||||
let listItem = document.createElement("li");
|
||||
// Add a class representing the item's type:
|
||||
// can be used by a theme's CSS selector for styling
|
||||
// See SearchResultKind for the class names.
|
||||
listItem.classList.add(`kind-${kind}`);
|
||||
let requestUrl;
|
||||
let linkUrl;
|
||||
if (docBuilder === "dirhtml") {
|
||||
// dirhtml builder
|
||||
let dirname = docName + "/";
|
||||
if (dirname.match(/\/index\/$/))
|
||||
dirname = dirname.substring(0, dirname.length - 6);
|
||||
else if (dirname === "index/") dirname = "";
|
||||
requestUrl = contentRoot + dirname;
|
||||
linkUrl = requestUrl;
|
||||
} else {
|
||||
// normal html builders
|
||||
requestUrl = contentRoot + docName + docFileSuffix;
|
||||
linkUrl = docName + docLinkSuffix;
|
||||
}
|
||||
let linkEl = listItem.appendChild(document.createElement("a"));
|
||||
linkEl.href = linkUrl + anchor;
|
||||
linkEl.dataset.score = score;
|
||||
linkEl.innerHTML = title;
|
||||
if (descr) {
|
||||
listItem.appendChild(document.createElement("span")).innerHTML =
|
||||
" (" + descr + ")";
|
||||
// highlight search terms in the description
|
||||
if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js
|
||||
highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted"));
|
||||
}
|
||||
else if (showSearchSummary)
|
||||
fetch(requestUrl)
|
||||
.then((responseData) => responseData.text())
|
||||
.then((data) => {
|
||||
if (data)
|
||||
listItem.appendChild(
|
||||
Search.makeSearchSummary(data, searchTerms, anchor)
|
||||
);
|
||||
// highlight search terms in the summary
|
||||
if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js
|
||||
highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted"));
|
||||
});
|
||||
Search.output.appendChild(listItem);
|
||||
};
|
||||
const _finishSearch = (resultCount) => {
|
||||
Search.stopPulse();
|
||||
Search.title.innerText = _("Search Results");
|
||||
if (!resultCount)
|
||||
Search.status.innerText = Documentation.gettext(
|
||||
"Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories."
|
||||
);
|
||||
else
|
||||
Search.status.innerText = Documentation.ngettext(
|
||||
"Search finished, found one page matching the search query.",
|
||||
"Search finished, found ${resultCount} pages matching the search query.",
|
||||
resultCount,
|
||||
).replace('${resultCount}', resultCount);
|
||||
};
|
||||
const _displayNextItem = (
|
||||
results,
|
||||
resultCount,
|
||||
searchTerms,
|
||||
highlightTerms,
|
||||
) => {
|
||||
// results left, load the summary and display it
|
||||
// this is intended to be dynamic (don't sub resultsCount)
|
||||
if (results.length) {
|
||||
_displayItem(results.pop(), searchTerms, highlightTerms);
|
||||
setTimeout(
|
||||
() => _displayNextItem(results, resultCount, searchTerms, highlightTerms),
|
||||
5
|
||||
);
|
||||
}
|
||||
// search finished, update title and status message
|
||||
else _finishSearch(resultCount);
|
||||
};
|
||||
// Helper function used by query() to order search results.
|
||||
// Each input is an array of [docname, title, anchor, descr, score, filename, kind].
|
||||
// Order the results by score (in opposite order of appearance, since the
|
||||
// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically.
|
||||
const _orderResultsByScoreThenName = (a, b) => {
|
||||
const leftScore = a[4];
|
||||
const rightScore = b[4];
|
||||
if (leftScore === rightScore) {
|
||||
// same score: sort alphabetically
|
||||
const leftTitle = a[1].toLowerCase();
|
||||
const rightTitle = b[1].toLowerCase();
|
||||
if (leftTitle === rightTitle) return 0;
|
||||
return leftTitle > rightTitle ? -1 : 1; // inverted is intentional
|
||||
}
|
||||
return leftScore > rightScore ? 1 : -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Default splitQuery function. Can be overridden in ``sphinx.search`` with a
|
||||
* custom function per language.
|
||||
*
|
||||
* The regular expression works by splitting the string on consecutive characters
|
||||
* that are not Unicode letters, numbers, underscores, or emoji characters.
|
||||
* This is the same as ``\W+`` in Python, preserving the surrogate pair area.
|
||||
*/
|
||||
if (typeof splitQuery === "undefined") {
|
||||
var splitQuery = (query) => query
|
||||
.split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu)
|
||||
.filter(term => term) // remove remaining empty strings
|
||||
}
|
||||
|
||||
/**
|
||||
* Search Module
|
||||
*/
|
||||
const Search = {
|
||||
_index: null,
|
||||
_queued_query: null,
|
||||
_pulse_status: -1,
|
||||
|
||||
htmlToText: (htmlString, anchor) => {
|
||||
const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html');
|
||||
for (const removalQuery of [".headerlink", "script", "style"]) {
|
||||
htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() });
|
||||
}
|
||||
if (anchor) {
|
||||
const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`);
|
||||
if (anchorContent) return anchorContent.textContent;
|
||||
|
||||
console.warn(
|
||||
`Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.`
|
||||
);
|
||||
}
|
||||
|
||||
// if anchor not specified or not found, fall back to main content
|
||||
const docContent = htmlElement.querySelector('[role="main"]');
|
||||
if (docContent) return docContent.textContent;
|
||||
|
||||
console.warn(
|
||||
"Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template."
|
||||
);
|
||||
return "";
|
||||
},
|
||||
|
||||
init: () => {
|
||||
const query = new URLSearchParams(window.location.search).get("q");
|
||||
document
|
||||
.querySelectorAll('input[name="q"]')
|
||||
.forEach((el) => (el.value = query));
|
||||
if (query) Search.performSearch(query);
|
||||
},
|
||||
|
||||
loadIndex: (url) =>
|
||||
(document.body.appendChild(document.createElement("script")).src = url),
|
||||
|
||||
setIndex: (index) => {
|
||||
Search._index = index;
|
||||
if (Search._queued_query !== null) {
|
||||
const query = Search._queued_query;
|
||||
Search._queued_query = null;
|
||||
Search.query(query);
|
||||
}
|
||||
},
|
||||
|
||||
hasIndex: () => Search._index !== null,
|
||||
|
||||
deferQuery: (query) => (Search._queued_query = query),
|
||||
|
||||
stopPulse: () => (Search._pulse_status = -1),
|
||||
|
||||
startPulse: () => {
|
||||
if (Search._pulse_status >= 0) return;
|
||||
|
||||
const pulse = () => {
|
||||
Search._pulse_status = (Search._pulse_status + 1) % 4;
|
||||
Search.dots.innerText = ".".repeat(Search._pulse_status);
|
||||
if (Search._pulse_status >= 0) window.setTimeout(pulse, 500);
|
||||
};
|
||||
pulse();
|
||||
},
|
||||
|
||||
/**
|
||||
* perform a search for something (or wait until index is loaded)
|
||||
*/
|
||||
performSearch: (query) => {
|
||||
// create the required interface elements
|
||||
const searchText = document.createElement("h2");
|
||||
searchText.textContent = _("Searching");
|
||||
const searchSummary = document.createElement("p");
|
||||
searchSummary.classList.add("search-summary");
|
||||
searchSummary.innerText = "";
|
||||
const searchList = document.createElement("ul");
|
||||
searchList.setAttribute("role", "list");
|
||||
searchList.classList.add("search");
|
||||
|
||||
const out = document.getElementById("search-results");
|
||||
Search.title = out.appendChild(searchText);
|
||||
Search.dots = Search.title.appendChild(document.createElement("span"));
|
||||
Search.status = out.appendChild(searchSummary);
|
||||
Search.output = out.appendChild(searchList);
|
||||
|
||||
const searchProgress = document.getElementById("search-progress");
|
||||
// Some themes don't use the search progress node
|
||||
if (searchProgress) {
|
||||
searchProgress.innerText = _("Preparing search...");
|
||||
}
|
||||
Search.startPulse();
|
||||
|
||||
// index already loaded, the browser was quick!
|
||||
if (Search.hasIndex()) Search.query(query);
|
||||
else Search.deferQuery(query);
|
||||
},
|
||||
|
||||
_parseQuery: (query) => {
|
||||
// stem the search terms and add them to the correct list
|
||||
const stemmer = new Stemmer();
|
||||
const searchTerms = new Set();
|
||||
const excludedTerms = new Set();
|
||||
const highlightTerms = new Set();
|
||||
const objectTerms = new Set(splitQuery(query.toLowerCase().trim()));
|
||||
splitQuery(query.trim()).forEach((queryTerm) => {
|
||||
const queryTermLower = queryTerm.toLowerCase();
|
||||
|
||||
// maybe skip this "word"
|
||||
// stopwords array is from language_data.js
|
||||
if (
|
||||
stopwords.indexOf(queryTermLower) !== -1 ||
|
||||
queryTerm.match(/^\d+$/)
|
||||
)
|
||||
return;
|
||||
|
||||
// stem the word
|
||||
let word = stemmer.stemWord(queryTermLower);
|
||||
// select the correct list
|
||||
if (word[0] === "-") excludedTerms.add(word.substr(1));
|
||||
else {
|
||||
searchTerms.add(word);
|
||||
highlightTerms.add(queryTermLower);
|
||||
}
|
||||
});
|
||||
|
||||
if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js
|
||||
localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" "))
|
||||
}
|
||||
|
||||
// console.debug("SEARCH: searching for:");
|
||||
// console.info("required: ", [...searchTerms]);
|
||||
// console.info("excluded: ", [...excludedTerms]);
|
||||
|
||||
return [query, searchTerms, excludedTerms, highlightTerms, objectTerms];
|
||||
},
|
||||
|
||||
/**
|
||||
* execute search (requires search index to be loaded)
|
||||
*/
|
||||
_performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => {
|
||||
const filenames = Search._index.filenames;
|
||||
const docNames = Search._index.docnames;
|
||||
const titles = Search._index.titles;
|
||||
const allTitles = Search._index.alltitles;
|
||||
const indexEntries = Search._index.indexentries;
|
||||
|
||||
// Collect multiple result groups to be sorted separately and then ordered.
|
||||
// Each is an array of [docname, title, anchor, descr, score, filename, kind].
|
||||
const normalResults = [];
|
||||
const nonMainIndexResults = [];
|
||||
|
||||
_removeChildren(document.getElementById("search-progress"));
|
||||
|
||||
const queryLower = query.toLowerCase().trim();
|
||||
for (const [title, foundTitles] of Object.entries(allTitles)) {
|
||||
if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) {
|
||||
for (const [file, id] of foundTitles) {
|
||||
const score = Math.round(Scorer.title * queryLower.length / title.length);
|
||||
const boost = titles[file] === title ? 1 : 0; // add a boost for document titles
|
||||
normalResults.push([
|
||||
docNames[file],
|
||||
titles[file] !== title ? `${titles[file]} > ${title}` : title,
|
||||
id !== null ? "#" + id : "",
|
||||
null,
|
||||
score + boost,
|
||||
filenames[file],
|
||||
SearchResultKind.title,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// search for explicit entries in index directives
|
||||
for (const [entry, foundEntries] of Object.entries(indexEntries)) {
|
||||
if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) {
|
||||
for (const [file, id, isMain] of foundEntries) {
|
||||
const score = Math.round(100 * queryLower.length / entry.length);
|
||||
const result = [
|
||||
docNames[file],
|
||||
titles[file],
|
||||
id ? "#" + id : "",
|
||||
null,
|
||||
score,
|
||||
filenames[file],
|
||||
SearchResultKind.index,
|
||||
];
|
||||
if (isMain) {
|
||||
normalResults.push(result);
|
||||
} else {
|
||||
nonMainIndexResults.push(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lookup as object
|
||||
objectTerms.forEach((term) =>
|
||||
normalResults.push(...Search.performObjectSearch(term, objectTerms))
|
||||
);
|
||||
|
||||
// lookup as search terms in fulltext
|
||||
normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms));
|
||||
|
||||
// let the scorer override scores with a custom scoring function
|
||||
if (Scorer.score) {
|
||||
normalResults.forEach((item) => (item[4] = Scorer.score(item)));
|
||||
nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item)));
|
||||
}
|
||||
|
||||
// Sort each group of results by score and then alphabetically by name.
|
||||
normalResults.sort(_orderResultsByScoreThenName);
|
||||
nonMainIndexResults.sort(_orderResultsByScoreThenName);
|
||||
|
||||
// Combine the result groups in (reverse) order.
|
||||
// Non-main index entries are typically arbitrary cross-references,
|
||||
// so display them after other results.
|
||||
let results = [...nonMainIndexResults, ...normalResults];
|
||||
|
||||
// remove duplicate search results
|
||||
// note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept
|
||||
let seen = new Set();
|
||||
results = results.reverse().reduce((acc, result) => {
|
||||
let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(',');
|
||||
if (!seen.has(resultStr)) {
|
||||
acc.push(result);
|
||||
seen.add(resultStr);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return results.reverse();
|
||||
},
|
||||
|
||||
query: (query) => {
|
||||
const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query);
|
||||
const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms);
|
||||
|
||||
// for debugging
|
||||
//Search.lastresults = results.slice(); // a copy
|
||||
// console.info("search results:", Search.lastresults);
|
||||
|
||||
// print the results
|
||||
_displayNextItem(results, results.length, searchTerms, highlightTerms);
|
||||
},
|
||||
|
||||
/**
|
||||
* search for object names
|
||||
*/
|
||||
performObjectSearch: (object, objectTerms) => {
|
||||
const filenames = Search._index.filenames;
|
||||
const docNames = Search._index.docnames;
|
||||
const objects = Search._index.objects;
|
||||
const objNames = Search._index.objnames;
|
||||
const titles = Search._index.titles;
|
||||
|
||||
const results = [];
|
||||
|
||||
const objectSearchCallback = (prefix, match) => {
|
||||
const name = match[4]
|
||||
const fullname = (prefix ? prefix + "." : "") + name;
|
||||
const fullnameLower = fullname.toLowerCase();
|
||||
if (fullnameLower.indexOf(object) < 0) return;
|
||||
|
||||
let score = 0;
|
||||
const parts = fullnameLower.split(".");
|
||||
|
||||
// check for different match types: exact matches of full name or
|
||||
// "last name" (i.e. last dotted part)
|
||||
if (fullnameLower === object || parts.slice(-1)[0] === object)
|
||||
score += Scorer.objNameMatch;
|
||||
else if (parts.slice(-1)[0].indexOf(object) > -1)
|
||||
score += Scorer.objPartialMatch; // matches in last name
|
||||
|
||||
const objName = objNames[match[1]][2];
|
||||
const title = titles[match[0]];
|
||||
|
||||
// If more than one term searched for, we require other words to be
|
||||
// found in the name/title/description
|
||||
const otherTerms = new Set(objectTerms);
|
||||
otherTerms.delete(object);
|
||||
if (otherTerms.size > 0) {
|
||||
const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase();
|
||||
if (
|
||||
[...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0)
|
||||
)
|
||||
return;
|
||||
}
|
||||
|
||||
let anchor = match[3];
|
||||
if (anchor === "") anchor = fullname;
|
||||
else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname;
|
||||
|
||||
const descr = objName + _(", in ") + title;
|
||||
|
||||
// add custom score for some objects according to scorer
|
||||
if (Scorer.objPrio.hasOwnProperty(match[2]))
|
||||
score += Scorer.objPrio[match[2]];
|
||||
else score += Scorer.objPrioDefault;
|
||||
|
||||
results.push([
|
||||
docNames[match[0]],
|
||||
fullname,
|
||||
"#" + anchor,
|
||||
descr,
|
||||
score,
|
||||
filenames[match[0]],
|
||||
SearchResultKind.object,
|
||||
]);
|
||||
};
|
||||
Object.keys(objects).forEach((prefix) =>
|
||||
objects[prefix].forEach((array) =>
|
||||
objectSearchCallback(prefix, array)
|
||||
)
|
||||
);
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* search for full-text terms in the index
|
||||
*/
|
||||
performTermsSearch: (searchTerms, excludedTerms) => {
|
||||
// prepare search
|
||||
const terms = Search._index.terms;
|
||||
const titleTerms = Search._index.titleterms;
|
||||
const filenames = Search._index.filenames;
|
||||
const docNames = Search._index.docnames;
|
||||
const titles = Search._index.titles;
|
||||
|
||||
const scoreMap = new Map();
|
||||
const fileMap = new Map();
|
||||
|
||||
// perform the search on the required terms
|
||||
searchTerms.forEach((word) => {
|
||||
const files = [];
|
||||
// find documents, if any, containing the query word in their text/title term indices
|
||||
// use Object.hasOwnProperty to avoid mismatching against prototype properties
|
||||
const arr = [
|
||||
{ files: terms.hasOwnProperty(word) ? terms[word] : undefined, score: Scorer.term },
|
||||
{ files: titleTerms.hasOwnProperty(word) ? titleTerms[word] : undefined, score: Scorer.title },
|
||||
];
|
||||
// add support for partial matches
|
||||
if (word.length > 2) {
|
||||
const escapedWord = _escapeRegExp(word);
|
||||
if (!terms.hasOwnProperty(word)) {
|
||||
Object.keys(terms).forEach((term) => {
|
||||
if (term.match(escapedWord))
|
||||
arr.push({ files: terms[term], score: Scorer.partialTerm });
|
||||
});
|
||||
}
|
||||
if (!titleTerms.hasOwnProperty(word)) {
|
||||
Object.keys(titleTerms).forEach((term) => {
|
||||
if (term.match(escapedWord))
|
||||
arr.push({ files: titleTerms[term], score: Scorer.partialTitle });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// no match but word was a required one
|
||||
if (arr.every((record) => record.files === undefined)) return;
|
||||
|
||||
// found search word in contents
|
||||
arr.forEach((record) => {
|
||||
if (record.files === undefined) return;
|
||||
|
||||
let recordFiles = record.files;
|
||||
if (recordFiles.length === undefined) recordFiles = [recordFiles];
|
||||
files.push(...recordFiles);
|
||||
|
||||
// set score for the word in each file
|
||||
recordFiles.forEach((file) => {
|
||||
if (!scoreMap.has(file)) scoreMap.set(file, new Map());
|
||||
const fileScores = scoreMap.get(file);
|
||||
fileScores.set(word, record.score);
|
||||
});
|
||||
});
|
||||
|
||||
// create the mapping
|
||||
files.forEach((file) => {
|
||||
if (!fileMap.has(file)) fileMap.set(file, [word]);
|
||||
else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word);
|
||||
});
|
||||
});
|
||||
|
||||
// now check if the files don't contain excluded terms
|
||||
const results = [];
|
||||
for (const [file, wordList] of fileMap) {
|
||||
// check if all requirements are matched
|
||||
|
||||
// as search terms with length < 3 are discarded
|
||||
const filteredTermCount = [...searchTerms].filter(
|
||||
(term) => term.length > 2
|
||||
).length;
|
||||
if (
|
||||
wordList.length !== searchTerms.size &&
|
||||
wordList.length !== filteredTermCount
|
||||
)
|
||||
continue;
|
||||
|
||||
// ensure that none of the excluded terms is in the search result
|
||||
if (
|
||||
[...excludedTerms].some(
|
||||
(term) =>
|
||||
terms[term] === file ||
|
||||
titleTerms[term] === file ||
|
||||
(terms[term] || []).includes(file) ||
|
||||
(titleTerms[term] || []).includes(file)
|
||||
)
|
||||
)
|
||||
break;
|
||||
|
||||
// select one (max) score for the file.
|
||||
const score = Math.max(...wordList.map((w) => scoreMap.get(file).get(w)));
|
||||
// add result to the result list
|
||||
results.push([
|
||||
docNames[file],
|
||||
titles[file],
|
||||
"",
|
||||
null,
|
||||
score,
|
||||
filenames[file],
|
||||
SearchResultKind.text,
|
||||
]);
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* helper function to return a node containing the
|
||||
* search summary for a given text. keywords is a list
|
||||
* of stemmed words.
|
||||
*/
|
||||
makeSearchSummary: (htmlText, keywords, anchor) => {
|
||||
const text = Search.htmlToText(htmlText, anchor);
|
||||
if (text === "") return null;
|
||||
|
||||
const textLower = text.toLowerCase();
|
||||
const actualStartPosition = [...keywords]
|
||||
.map((k) => textLower.indexOf(k.toLowerCase()))
|
||||
.filter((i) => i > -1)
|
||||
.slice(-1)[0];
|
||||
const startWithContext = Math.max(actualStartPosition - 120, 0);
|
||||
|
||||
const top = startWithContext === 0 ? "" : "...";
|
||||
const tail = startWithContext + 240 < text.length ? "..." : "";
|
||||
|
||||
let summary = document.createElement("p");
|
||||
summary.classList.add("context");
|
||||
summary.textContent = top + text.substr(startWithContext, 240).trim() + tail;
|
||||
|
||||
return summary;
|
||||
},
|
||||
};
|
||||
|
||||
_ready(Search.init);
|
||||
@@ -0,0 +1,154 @@
|
||||
/* Highlighting utilities for Sphinx HTML documentation. */
|
||||
"use strict";
|
||||
|
||||
const SPHINX_HIGHLIGHT_ENABLED = true
|
||||
|
||||
/**
|
||||
* highlight a given string on a node by wrapping it in
|
||||
* span elements with the given class name.
|
||||
*/
|
||||
const _highlight = (node, addItems, text, className) => {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
const val = node.nodeValue;
|
||||
const parent = node.parentNode;
|
||||
const pos = val.toLowerCase().indexOf(text);
|
||||
if (
|
||||
pos >= 0 &&
|
||||
!parent.classList.contains(className) &&
|
||||
!parent.classList.contains("nohighlight")
|
||||
) {
|
||||
let span;
|
||||
|
||||
const closestNode = parent.closest("body, svg, foreignObject");
|
||||
const isInSVG = closestNode && closestNode.matches("svg");
|
||||
if (isInSVG) {
|
||||
span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
|
||||
} else {
|
||||
span = document.createElement("span");
|
||||
span.classList.add(className);
|
||||
}
|
||||
|
||||
span.appendChild(document.createTextNode(val.substr(pos, text.length)));
|
||||
const rest = document.createTextNode(val.substr(pos + text.length));
|
||||
parent.insertBefore(
|
||||
span,
|
||||
parent.insertBefore(
|
||||
rest,
|
||||
node.nextSibling
|
||||
)
|
||||
);
|
||||
node.nodeValue = val.substr(0, pos);
|
||||
/* There may be more occurrences of search term in this node. So call this
|
||||
* function recursively on the remaining fragment.
|
||||
*/
|
||||
_highlight(rest, addItems, text, className);
|
||||
|
||||
if (isInSVG) {
|
||||
const rect = document.createElementNS(
|
||||
"http://www.w3.org/2000/svg",
|
||||
"rect"
|
||||
);
|
||||
const bbox = parent.getBBox();
|
||||
rect.x.baseVal.value = bbox.x;
|
||||
rect.y.baseVal.value = bbox.y;
|
||||
rect.width.baseVal.value = bbox.width;
|
||||
rect.height.baseVal.value = bbox.height;
|
||||
rect.setAttribute("class", className);
|
||||
addItems.push({ parent: parent, target: rect });
|
||||
}
|
||||
}
|
||||
} else if (node.matches && !node.matches("button, select, textarea")) {
|
||||
node.childNodes.forEach((el) => _highlight(el, addItems, text, className));
|
||||
}
|
||||
};
|
||||
const _highlightText = (thisNode, text, className) => {
|
||||
let addItems = [];
|
||||
_highlight(thisNode, addItems, text, className);
|
||||
addItems.forEach((obj) =>
|
||||
obj.parent.insertAdjacentElement("beforebegin", obj.target)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Small JavaScript module for the documentation.
|
||||
*/
|
||||
const SphinxHighlight = {
|
||||
|
||||
/**
|
||||
* highlight the search words provided in localstorage in the text
|
||||
*/
|
||||
highlightSearchWords: () => {
|
||||
if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight
|
||||
|
||||
// get and clear terms from localstorage
|
||||
const url = new URL(window.location);
|
||||
const highlight =
|
||||
localStorage.getItem("sphinx_highlight_terms")
|
||||
|| url.searchParams.get("highlight")
|
||||
|| "";
|
||||
localStorage.removeItem("sphinx_highlight_terms")
|
||||
url.searchParams.delete("highlight");
|
||||
window.history.replaceState({}, "", url);
|
||||
|
||||
// get individual terms from highlight string
|
||||
const terms = highlight.toLowerCase().split(/\s+/).filter(x => x);
|
||||
if (terms.length === 0) return; // nothing to do
|
||||
|
||||
// There should never be more than one element matching "div.body"
|
||||
const divBody = document.querySelectorAll("div.body");
|
||||
const body = divBody.length ? divBody[0] : document.querySelector("body");
|
||||
window.setTimeout(() => {
|
||||
terms.forEach((term) => _highlightText(body, term, "highlighted"));
|
||||
}, 10);
|
||||
|
||||
const searchBox = document.getElementById("searchbox");
|
||||
if (searchBox === null) return;
|
||||
searchBox.appendChild(
|
||||
document
|
||||
.createRange()
|
||||
.createContextualFragment(
|
||||
'<p class="highlight-link">' +
|
||||
'<a href="javascript:SphinxHighlight.hideSearchWords()">' +
|
||||
_("Hide Search Matches") +
|
||||
"</a></p>"
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* helper function to hide the search marks again
|
||||
*/
|
||||
hideSearchWords: () => {
|
||||
document
|
||||
.querySelectorAll("#searchbox .highlight-link")
|
||||
.forEach((el) => el.remove());
|
||||
document
|
||||
.querySelectorAll("span.highlighted")
|
||||
.forEach((el) => el.classList.remove("highlighted"));
|
||||
localStorage.removeItem("sphinx_highlight_terms")
|
||||
},
|
||||
|
||||
initEscapeListener: () => {
|
||||
// only install a listener if it is really needed
|
||||
if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return;
|
||||
|
||||
document.addEventListener("keydown", (event) => {
|
||||
// bail for input elements
|
||||
if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
|
||||
// bail with special keys
|
||||
if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return;
|
||||
if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) {
|
||||
SphinxHighlight.hideSearchWords();
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
_ready(() => {
|
||||
/* Do not call highlightSearchWords() when we are on the search page.
|
||||
* It will highlight words from the *previous* search query.
|
||||
*/
|
||||
if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords();
|
||||
SphinxHighlight.initEscapeListener();
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" data-content_root="./">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<title>Backlog — responder 3.6.2 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=5ecbeea2" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/basic.css?v=b08954a9" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css?v=27fed22d" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/design-elements.e5416f61bae5d36adc6d722a2b6f8cff.css?v=452a8e97" />
|
||||
<script src="_static/documentation_options.js?v=c0c9fa11"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="_static/copybutton.js?v=fd10adb8"></script>
|
||||
<script>
|
||||
</script>
|
||||
<script src="_static/design-elements.bbdccc18c4abea9397628f9fea3d48c2.js?v=03c7770e"></script>
|
||||
<link rel="index" title="Index" href="genindex.html" />
|
||||
<link rel="search" title="Search" href="search.html" />
|
||||
<link rel="prev" title="Development Sandbox" href="sandbox.html" />
|
||||
|
||||
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</head><body>
|
||||
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
|
||||
|
||||
<div class="body" role="main">
|
||||
|
||||
<section id="backlog">
|
||||
<h1>Backlog<a class="headerlink" href="#backlog" title="Link to this heading">¶</a></h1>
|
||||
<section id="future-ideas">
|
||||
<h2>Future Ideas<a class="headerlink" href="#future-ideas" title="Link to this heading">¶</a></h2>
|
||||
<ul class="simple">
|
||||
<li><p>WebSocket before_request short-circuit support (reject before accept)</p></li>
|
||||
<li><p>Per-route rate limiting (different limits for different endpoints)</p></li>
|
||||
<li><p>Built-in structured logging with request context</p></li>
|
||||
<li><p>OpenAPI 3.1 support</p></li>
|
||||
<li><p>Dependency injection for route handlers</p></li>
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="sphinxsidebar" role="navigation" aria-label="Main">
|
||||
<div class="sphinxsidebarwrapper"><p class="logo">
|
||||
<a href="index.html">
|
||||
<img class="logo" src="_static/responder.png" />
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Responder</strong> — a familiar HTTP service framework for Python.
|
||||
<br />
|
||||
<small>v3.6.2</small>
|
||||
</p>
|
||||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
|
||||
<li><a href="https://pypi.org/project/responder/">Responder @ PyPI</a></li>
|
||||
<li><a href="https://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<h3><a href="index.html">Table of Contents</a></h3>
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Backlog</a><ul>
|
||||
<li><a class="reference internal" href="#future-ideas">Future Ideas</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
<search id="searchbox" style="display: none" role="search">
|
||||
<h3 id="searchlabel">Quick search</h3>
|
||||
<div class="searchformwrapper">
|
||||
<form class="search" action="search.html" method="get">
|
||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
||||
<input type="submit" value="Go" />
|
||||
</form>
|
||||
</div>
|
||||
</search>
|
||||
<script>document.getElementById('searchbox').style.display = "block"</script>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
©2018-2026, Kenneth Reitz.
|
||||
|
||||
|
|
||||
<a href="_sources/backlog.md.txt"
|
||||
rel="nofollow">Page source</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,198 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" data-content_root="./">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<title>Command Line Interface — responder 3.6.2 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=5ecbeea2" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/basic.css?v=b08954a9" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css?v=27fed22d" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/design-elements.e5416f61bae5d36adc6d722a2b6f8cff.css?v=452a8e97" />
|
||||
<script src="_static/documentation_options.js?v=c0c9fa11"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="_static/copybutton.js?v=fd10adb8"></script>
|
||||
<script>
|
||||
</script>
|
||||
<script src="_static/design-elements.bbdccc18c4abea9397628f9fea3d48c2.js?v=03c7770e"></script>
|
||||
<link rel="index" title="Index" href="genindex.html" />
|
||||
<link rel="search" title="Search" href="search.html" />
|
||||
<link rel="next" title="Building a REST API" href="tutorial-rest.html" />
|
||||
<link rel="prev" title="API Reference" href="api.html" />
|
||||
|
||||
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</head><body>
|
||||
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
|
||||
|
||||
<div class="body" role="main">
|
||||
|
||||
<section id="command-line-interface">
|
||||
<h1>Command Line Interface<a class="headerlink" href="#command-line-interface" title="Link to this heading">¶</a></h1>
|
||||
<p>Responder installs a <code class="docutils literal notranslate"><span class="pre">responder</span></code> command that lets you launch
|
||||
applications from the terminal. You can point it at a Python module,
|
||||
a local file, or even a URL — and it will find your <code class="docutils literal notranslate"><span class="pre">API</span></code> instance
|
||||
and start serving.</p>
|
||||
<section id="launching-from-a-module">
|
||||
<h2>Launching from a Module<a class="headerlink" href="#launching-from-a-module" title="Link to this heading">¶</a></h2>
|
||||
<p>The most common way to run a Responder application in production. Use
|
||||
Python’s standard dotted module path:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ responder run acme.app
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This imports <code class="docutils literal notranslate"><span class="pre">acme.app</span></code> and looks for an attribute called <code class="docutils literal notranslate"><span class="pre">api</span></code>
|
||||
(a <code class="docutils literal notranslate"><span class="pre">responder.API</span></code> instance). It’s the same import system Python
|
||||
uses everywhere — your <code class="docutils literal notranslate"><span class="pre">PYTHONPATH</span></code> and virtual environment are
|
||||
respected.</p>
|
||||
</section>
|
||||
<section id="launching-from-a-file">
|
||||
<h2>Launching from a File<a class="headerlink" href="#launching-from-a-file" title="Link to this heading">¶</a></h2>
|
||||
<p>During development, you often have a single file you want to run:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ responder run helloworld.py
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This loads the file directly and starts the server. Quick and easy for
|
||||
prototyping and single-file applications.</p>
|
||||
<p>You can test it with a simple HTTP request:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ curl http://127.0.0.1:5042/hello
|
||||
hello, world!
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="launching-from-a-url">
|
||||
<h2>Launching from a URL<a class="headerlink" href="#launching-from-a-url" title="Link to this heading">¶</a></h2>
|
||||
<p>Responder can fetch and run a Python file from any URL — great for
|
||||
demos, sharing examples, and running code from GitHub:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ responder run https://github.com/kennethreitz/responder/raw/refs/heads/main/examples/helloworld.py
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This also works with <code class="docutils literal notranslate"><span class="pre">github://</span></code> URLs and any filesystem protocol
|
||||
supported by <a class="reference external" href="https://filesystem-spec.readthedocs.io/">fsspec</a>:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ responder run github://kennethreitz:responder@/examples/helloworld.py
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Cloud storage is supported too — Azure Blob Storage, Google Cloud
|
||||
Storage, S3, HDFS, SFTP, and more. Install <code class="docutils literal notranslate"><span class="pre">fsspec[full]</span></code> for all
|
||||
protocols:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ uv pip install 'fsspec[full]'
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="custom-instance-names">
|
||||
<h2>Custom Instance Names<a class="headerlink" href="#custom-instance-names" title="Link to this heading">¶</a></h2>
|
||||
<p>By default, Responder looks for an attribute called <code class="docutils literal notranslate"><span class="pre">api</span></code>. If your
|
||||
application uses a different name, specify it with a colon:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ responder run acme.app:service
|
||||
$ responder run myapp.py:application
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>For URLs, use a fragment:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ responder run https://example.com/app.py#service
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="environment-variables">
|
||||
<h2>Environment Variables<a class="headerlink" href="#environment-variables" title="Link to this heading">¶</a></h2>
|
||||
<p>Responder automatically reads the <code class="docutils literal notranslate"><span class="pre">PORT</span></code> environment variable at
|
||||
runtime:</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">PORT</span></code> — bind to <code class="docutils literal notranslate"><span class="pre">0.0.0.0</span></code> on this port (cloud platform convention)</p></li>
|
||||
</ul>
|
||||
<p>When <code class="docutils literal notranslate"><span class="pre">PORT</span></code> is set, the server binds to all interfaces automatically.
|
||||
This is how cloud platforms like Fly.io, Railway, and Heroku inject the
|
||||
listen port.</p>
|
||||
<p>For other settings like <code class="docutils literal notranslate"><span class="pre">SECRET_KEY</span></code>, read them in your application
|
||||
code and pass them to <code class="docutils literal notranslate"><span class="pre">responder.API()</span></code>:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">os</span>
|
||||
<span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">secret_key</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">"SECRET_KEY"</span><span class="p">])</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="building-frontend-assets">
|
||||
<h2>Building Frontend Assets<a class="headerlink" href="#building-frontend-assets" title="Link to this heading">¶</a></h2>
|
||||
<p>If your project includes a JavaScript frontend with a <code class="docutils literal notranslate"><span class="pre">package.json</span></code>,
|
||||
the <code class="docutils literal notranslate"><span class="pre">build</span></code> subcommand runs <code class="docutils literal notranslate"><span class="pre">npm</span> <span class="pre">run</span> <span class="pre">build</span></code>:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ responder build
|
||||
$ responder build /path/to/frontend
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="sphinxsidebar" role="navigation" aria-label="Main">
|
||||
<div class="sphinxsidebarwrapper"><p class="logo">
|
||||
<a href="index.html">
|
||||
<img class="logo" src="_static/responder.png" />
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Responder</strong> — a familiar HTTP service framework for Python.
|
||||
<br />
|
||||
<small>v3.6.2</small>
|
||||
</p>
|
||||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
|
||||
<li><a href="https://pypi.org/project/responder/">Responder @ PyPI</a></li>
|
||||
<li><a href="https://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<h3><a href="index.html">Table of Contents</a></h3>
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Command Line Interface</a><ul>
|
||||
<li><a class="reference internal" href="#launching-from-a-module">Launching from a Module</a></li>
|
||||
<li><a class="reference internal" href="#launching-from-a-file">Launching from a File</a></li>
|
||||
<li><a class="reference internal" href="#launching-from-a-url">Launching from a URL</a></li>
|
||||
<li><a class="reference internal" href="#custom-instance-names">Custom Instance Names</a></li>
|
||||
<li><a class="reference internal" href="#environment-variables">Environment Variables</a></li>
|
||||
<li><a class="reference internal" href="#building-frontend-assets">Building Frontend Assets</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
<search id="searchbox" style="display: none" role="search">
|
||||
<h3 id="searchlabel">Quick search</h3>
|
||||
<div class="searchformwrapper">
|
||||
<form class="search" action="search.html" method="get">
|
||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
||||
<input type="submit" value="Go" />
|
||||
</form>
|
||||
</div>
|
||||
</search>
|
||||
<script>document.getElementById('searchbox').style.display = "block"</script>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
©2018-2026, Kenneth Reitz.
|
||||
|
||||
|
|
||||
<a href="_sources/cli.rst.txt"
|
||||
rel="nofollow">Page source</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,278 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" data-content_root="./">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<title>Deployment — responder 3.6.2 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=5ecbeea2" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/basic.css?v=b08954a9" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css?v=27fed22d" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/design-elements.e5416f61bae5d36adc6d722a2b6f8cff.css?v=452a8e97" />
|
||||
<script src="_static/documentation_options.js?v=c0c9fa11"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="_static/copybutton.js?v=fd10adb8"></script>
|
||||
<script>
|
||||
</script>
|
||||
<script src="_static/design-elements.bbdccc18c4abea9397628f9fea3d48c2.js?v=03c7770e"></script>
|
||||
<link rel="index" title="Index" href="genindex.html" />
|
||||
<link rel="search" title="Search" href="search.html" />
|
||||
<link rel="next" title="Testing" href="testing.html" />
|
||||
<link rel="prev" title="Feature Tour" href="tour.html" />
|
||||
|
||||
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</head><body>
|
||||
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
|
||||
|
||||
<div class="body" role="main">
|
||||
|
||||
<section id="deployment">
|
||||
<h1>Deployment<a class="headerlink" href="#deployment" title="Link to this heading">¶</a></h1>
|
||||
<p>Responder applications are standard <a class="reference external" href="https://asgi.readthedocs.io/">ASGI</a>
|
||||
apps. ASGI (Asynchronous Server Gateway Interface) is the modern successor
|
||||
to WSGI — it supports async, WebSockets, and HTTP/2. This means you can
|
||||
deploy a Responder app anywhere that runs Python, using any ASGI server.</p>
|
||||
<section id="running-locally">
|
||||
<h2>Running Locally<a class="headerlink" href="#running-locally" title="Link to this heading">¶</a></h2>
|
||||
<p>During development, <code class="docutils literal notranslate"><span class="pre">api.run()</span></code> is all you need:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span>
|
||||
<span class="n">api</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This starts a <a class="reference external" href="https://www.uvicorn.org/">uvicorn</a> server on
|
||||
<code class="docutils literal notranslate"><span class="pre">127.0.0.1:5042</span></code>. Uvicorn is a lightning-fast ASGI server built on
|
||||
<a class="reference external" href="https://uvloop.readthedocs.io/">uvloop</a> — it handles thousands of
|
||||
concurrent connections efficiently and protects against slowloris attacks,
|
||||
making a reverse proxy like nginx optional for many deployments.</p>
|
||||
</section>
|
||||
<section id="docker">
|
||||
<h2>Docker<a class="headerlink" href="#docker" title="Link to this heading">¶</a></h2>
|
||||
<p>Docker is the most common way to package and deploy web applications.
|
||||
Here’s a minimal Dockerfile:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">FROM</span> <span class="n">python</span><span class="p">:</span><span class="mf">3.13</span><span class="o">-</span><span class="n">slim</span>
|
||||
<span class="n">WORKDIR</span> <span class="o">/</span><span class="n">app</span>
|
||||
<span class="n">COPY</span> <span class="o">--</span><span class="n">from</span><span class="o">=</span><span class="n">ghcr</span><span class="o">.</span><span class="n">io</span><span class="o">/</span><span class="n">astral</span><span class="o">-</span><span class="n">sh</span><span class="o">/</span><span class="n">uv</span><span class="p">:</span><span class="n">latest</span> <span class="o">/</span><span class="n">uv</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="nb">bin</span><span class="o">/</span><span class="n">uv</span>
|
||||
<span class="n">COPY</span> <span class="o">.</span> <span class="o">.</span>
|
||||
<span class="n">RUN</span> <span class="n">uv</span> <span class="n">pip</span> <span class="n">install</span> <span class="o">--</span><span class="n">system</span> <span class="n">responder</span>
|
||||
<span class="n">ENV</span> <span class="n">PORT</span><span class="o">=</span><span class="mi">80</span>
|
||||
<span class="n">EXPOSE</span> <span class="mi">80</span>
|
||||
<span class="n">CMD</span> <span class="p">[</span><span class="s2">"python"</span><span class="p">,</span> <span class="s2">"api.py"</span><span class="p">]</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Build and run:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ docker build -t myapi .
|
||||
$ docker run -p 8000:80 myapi
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">python:3.13-slim</span></code> image is about 150MB — small enough for fast
|
||||
deploys but includes everything you need. Using <code class="docutils literal notranslate"><span class="pre">uv</span></code> for installs
|
||||
is significantly faster than pip. For even smaller images, you can use
|
||||
<code class="docutils literal notranslate"><span class="pre">python:3.13-alpine</span></code>, though some packages may need extra build
|
||||
dependencies.</p>
|
||||
</section>
|
||||
<section id="cloud-platforms">
|
||||
<h2>Cloud Platforms<a class="headerlink" href="#cloud-platforms" title="Link to this heading">¶</a></h2>
|
||||
<p>Responder automatically honors the <code class="docutils literal notranslate"><span class="pre">PORT</span></code> environment variable. When
|
||||
<code class="docutils literal notranslate"><span class="pre">PORT</span></code> is set, the server binds to <code class="docutils literal notranslate"><span class="pre">0.0.0.0</span></code> on that port — this is
|
||||
the convention that virtually every cloud platform uses.</p>
|
||||
<p>This means zero configuration on:</p>
|
||||
<ul class="simple">
|
||||
<li><p><strong>Fly.io</strong> — <code class="docutils literal notranslate"><span class="pre">fly</span> <span class="pre">launch</span></code> and you’re done</p></li>
|
||||
<li><p><strong>Railway</strong> — push your code, Railway sets <code class="docutils literal notranslate"><span class="pre">PORT</span></code></p></li>
|
||||
<li><p><strong>Render</strong> — set start command to <code class="docutils literal notranslate"><span class="pre">python</span> <span class="pre">api.py</span></code></p></li>
|
||||
<li><p><strong>Google Cloud Run</strong> — containerize and deploy</p></li>
|
||||
<li><p><strong>Azure Container Apps</strong> — same pattern</p></li>
|
||||
<li><p><strong>AWS App Runner</strong> — and here too</p></li>
|
||||
</ul>
|
||||
<p>The pattern is always the same: deploy your code, set the start command
|
||||
to <code class="docutils literal notranslate"><span class="pre">python</span> <span class="pre">api.py</span></code>, and the platform handles the rest.</p>
|
||||
</section>
|
||||
<section id="health-check-endpoint">
|
||||
<h2>Health Check Endpoint<a class="headerlink" href="#health-check-endpoint" title="Link to this heading">¶</a></h2>
|
||||
<p>Every production deployment needs a health check — a lightweight endpoint
|
||||
that monitoring tools, load balancers, and orchestrators can poll to verify
|
||||
your service is running:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/health"</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">health</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"status"</span><span class="p">:</span> <span class="s2">"healthy"</span><span class="p">}</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Keep it simple. Don’t query the database or do expensive work — the health
|
||||
check should return instantly. Cloud platforms, Docker, and Kubernetes all
|
||||
look for an HTTP 200 to confirm your service is alive.</p>
|
||||
<p>For Docker, add a <code class="docutils literal notranslate"><span class="pre">HEALTHCHECK</span></code> instruction:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">HEALTHCHECK</span> <span class="o">--</span><span class="n">interval</span><span class="o">=</span><span class="mi">30</span><span class="n">s</span> <span class="o">--</span><span class="n">timeout</span><span class="o">=</span><span class="mi">3</span><span class="n">s</span> \
|
||||
<span class="n">CMD</span> <span class="n">curl</span> <span class="o">-</span><span class="n">f</span> <span class="n">http</span><span class="p">:</span><span class="o">//</span><span class="n">localhost</span><span class="o">/</span><span class="n">health</span> <span class="o">||</span> <span class="n">exit</span> <span class="mi">1</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="uvicorn-directly">
|
||||
<h2>Uvicorn Directly<a class="headerlink" href="#uvicorn-directly" title="Link to this heading">¶</a></h2>
|
||||
<p>For production deployments where you want more control, bypass
|
||||
<code class="docutils literal notranslate"><span class="pre">api.run()</span></code> and use uvicorn directly:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ uvicorn api:api --host 0.0.0.0 --port 8000 --workers 4
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">--workers</span></code> flag spawns multiple processes, each handling requests
|
||||
independently. A good starting point is 2-4 workers per CPU core.</p>
|
||||
<p>Uvicorn supports many options — SSL certificates, access logging, graceful
|
||||
shutdown timeouts, and more. See the
|
||||
<a class="reference external" href="https://www.uvicorn.org/deployment/">uvicorn documentation</a> for details.</p>
|
||||
<p>For platforms like Heroku or Railway that use a <code class="docutils literal notranslate"><span class="pre">Procfile</span></code>:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>web: uvicorn api:api --host 0.0.0.0 --port $PORT --workers 4
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="docker-compose">
|
||||
<h2>Docker Compose<a class="headerlink" href="#docker-compose" title="Link to this heading">¶</a></h2>
|
||||
<p>For local development with databases and other services, Docker Compose
|
||||
ties everything together:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># docker-compose.yml</span>
|
||||
<span class="n">services</span><span class="p">:</span>
|
||||
<span class="n">api</span><span class="p">:</span>
|
||||
<span class="n">build</span><span class="p">:</span> <span class="o">.</span>
|
||||
<span class="n">ports</span><span class="p">:</span>
|
||||
<span class="o">-</span> <span class="s2">"5042:80"</span>
|
||||
<span class="n">environment</span><span class="p">:</span>
|
||||
<span class="o">-</span> <span class="n">PORT</span><span class="o">=</span><span class="mi">80</span>
|
||||
<span class="o">-</span> <span class="n">DATABASE_URL</span><span class="o">=</span><span class="n">postgresql</span><span class="o">+</span><span class="n">asyncpg</span><span class="p">:</span><span class="o">//</span><span class="n">user</span><span class="p">:</span><span class="k">pass</span><span class="nd">@db</span><span class="o">/</span><span class="n">myapp</span>
|
||||
<span class="o">-</span> <span class="n">SECRET_KEY</span><span class="o">=</span><span class="n">dev</span><span class="o">-</span><span class="n">secret</span>
|
||||
<span class="n">depends_on</span><span class="p">:</span>
|
||||
<span class="o">-</span> <span class="n">db</span>
|
||||
|
||||
<span class="n">db</span><span class="p">:</span>
|
||||
<span class="n">image</span><span class="p">:</span> <span class="n">docker</span><span class="o">.</span><span class="n">io</span><span class="o">/</span><span class="n">postgres</span><span class="p">:</span><span class="mi">16</span><span class="o">-</span><span class="n">alpine</span>
|
||||
<span class="n">environment</span><span class="p">:</span>
|
||||
<span class="n">POSTGRES_USER</span><span class="p">:</span> <span class="n">user</span>
|
||||
<span class="n">POSTGRES_PASSWORD</span><span class="p">:</span> <span class="k">pass</span>
|
||||
<span class="n">POSTGRES_DB</span><span class="p">:</span> <span class="n">myapp</span>
|
||||
<span class="n">volumes</span><span class="p">:</span>
|
||||
<span class="o">-</span> <span class="n">pgdata</span><span class="p">:</span><span class="o">/</span><span class="n">var</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">postgresql</span><span class="o">/</span><span class="n">data</span>
|
||||
|
||||
<span class="n">volumes</span><span class="p">:</span>
|
||||
<span class="n">pgdata</span><span class="p">:</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Run with <code class="docutils literal notranslate"><span class="pre">docker</span> <span class="pre">compose</span> <span class="pre">up</span></code>. The API waits for <code class="docutils literal notranslate"><span class="pre">db</span></code> to start, then
|
||||
connects using the <code class="docutils literal notranslate"><span class="pre">DATABASE_URL</span></code> environment variable.</p>
|
||||
</section>
|
||||
<section id="reverse-proxy">
|
||||
<h2>Reverse Proxy<a class="headerlink" href="#reverse-proxy" title="Link to this heading">¶</a></h2>
|
||||
<p>For high-traffic production deployments, you may want a reverse proxy like
|
||||
<a class="reference external" href="https://nginx.org/">nginx</a> or <a class="reference external" href="https://caddyserver.com/">Caddy</a> in
|
||||
front of your application for:</p>
|
||||
<ul class="simple">
|
||||
<li><p><strong>SSL/TLS termination</strong> — let the proxy handle HTTPS certificates</p></li>
|
||||
<li><p><strong>Load balancing</strong> — distribute traffic across multiple app instances</p></li>
|
||||
<li><p><strong>Static asset serving</strong> — offload static files to the proxy</p></li>
|
||||
<li><p><strong>Rate limiting</strong> — at the infrastructure level</p></li>
|
||||
</ul>
|
||||
<p>A minimal Caddy config that handles HTTPS automatically:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Caddyfile</span>
|
||||
<span class="n">example</span><span class="o">.</span><span class="n">com</span> <span class="p">{</span>
|
||||
<span class="n">reverse_proxy</span> <span class="n">localhost</span><span class="p">:</span><span class="mi">5042</span>
|
||||
<span class="p">}</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Responder’s <code class="docutils literal notranslate"><span class="pre">TrustedHostMiddleware</span></code> and <code class="docutils literal notranslate"><span class="pre">HTTPSRedirectMiddleware</span></code> work
|
||||
correctly behind proxies that set standard forwarding headers
|
||||
(<code class="docutils literal notranslate"><span class="pre">X-Forwarded-For</span></code>, <code class="docutils literal notranslate"><span class="pre">X-Forwarded-Proto</span></code>).</p>
|
||||
<p>That said, uvicorn is production-ready on its own. Many applications run
|
||||
uvicorn directly without a reverse proxy and do just fine.</p>
|
||||
</section>
|
||||
<section id="production-checklist">
|
||||
<h2>Production Checklist<a class="headerlink" href="#production-checklist" title="Link to this heading">¶</a></h2>
|
||||
<p>Before going live:</p>
|
||||
<ul class="simple">
|
||||
<li><p><strong>Set a secret key</strong> — <code class="docutils literal notranslate"><span class="pre">SECRET_KEY</span></code> env var, never the default</p></li>
|
||||
<li><p><strong>Disable debug mode</strong> — <code class="docutils literal notranslate"><span class="pre">DEBUG=false</span></code> or omit it entirely</p></li>
|
||||
<li><p><strong>Set allowed hosts</strong> — restrict to your actual domain names</p></li>
|
||||
<li><p><strong>Use multiple workers</strong> — <code class="docutils literal notranslate"><span class="pre">--workers</span> <span class="pre">4</span></code> or more, depending on CPU cores</p></li>
|
||||
<li><p><strong>Add a health check</strong> — <code class="docutils literal notranslate"><span class="pre">/health</span></code> endpoint for monitoring</p></li>
|
||||
<li><p><strong>Enable HTTPS</strong> — via your proxy, cloud platform, or uvicorn’s <code class="docutils literal notranslate"><span class="pre">--ssl-*</span></code> flags</p></li>
|
||||
<li><p><strong>Set up logging</strong> — uvicorn logs requests by default; pipe them to your log aggregator</p></li>
|
||||
<li><p><strong>Pin your dependencies</strong> — use a lock file or pinned requirements for reproducible deploys</p></li>
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="sphinxsidebar" role="navigation" aria-label="Main">
|
||||
<div class="sphinxsidebarwrapper"><p class="logo">
|
||||
<a href="index.html">
|
||||
<img class="logo" src="_static/responder.png" />
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Responder</strong> — a familiar HTTP service framework for Python.
|
||||
<br />
|
||||
<small>v3.6.2</small>
|
||||
</p>
|
||||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
|
||||
<li><a href="https://pypi.org/project/responder/">Responder @ PyPI</a></li>
|
||||
<li><a href="https://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<h3><a href="index.html">Table of Contents</a></h3>
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Deployment</a><ul>
|
||||
<li><a class="reference internal" href="#running-locally">Running Locally</a></li>
|
||||
<li><a class="reference internal" href="#docker">Docker</a></li>
|
||||
<li><a class="reference internal" href="#cloud-platforms">Cloud Platforms</a></li>
|
||||
<li><a class="reference internal" href="#health-check-endpoint">Health Check Endpoint</a></li>
|
||||
<li><a class="reference internal" href="#uvicorn-directly">Uvicorn Directly</a></li>
|
||||
<li><a class="reference internal" href="#docker-compose">Docker Compose</a></li>
|
||||
<li><a class="reference internal" href="#reverse-proxy">Reverse Proxy</a></li>
|
||||
<li><a class="reference internal" href="#production-checklist">Production Checklist</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
<search id="searchbox" style="display: none" role="search">
|
||||
<h3 id="searchlabel">Quick search</h3>
|
||||
<div class="searchformwrapper">
|
||||
<form class="search" action="search.html" method="get">
|
||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
||||
<input type="submit" value="Go" />
|
||||
</form>
|
||||
</div>
|
||||
</search>
|
||||
<script>document.getElementById('searchbox').style.display = "block"</script>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
©2018-2026, Kenneth Reitz.
|
||||
|
||||
|
|
||||
<a href="_sources/deployment.rst.txt"
|
||||
rel="nofollow">Page source</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,19 +0,0 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
SOURCEDIR = source
|
||||
BUILDDIR = build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
@@ -1,35 +0,0 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=source
|
||||
set BUILDDIR=build
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
|
||||
:end
|
||||
popd
|
||||
@@ -1,21 +0,0 @@
|
||||
<style type="text/css">
|
||||
/* Make the document a little wider. */
|
||||
div.document {
|
||||
width: 1008px;
|
||||
}
|
||||
|
||||
/* Better spacing around code blocks. */
|
||||
div.highlight pre {
|
||||
padding: 11px 14px;
|
||||
}
|
||||
|
||||
/* Responsive layout. */
|
||||
@media screen and (max-width: 1008px) {
|
||||
div.sphinxsidebar {
|
||||
display: none;
|
||||
}
|
||||
div.document {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,16 +0,0 @@
|
||||
<p class="logo">
|
||||
<a href="{{ pathto(master_doc) }}">
|
||||
<img class="logo" src="{{ pathto('_static/responder.png', 1) }}" />
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Responder</strong> — a familiar HTTP service framework for Python.
|
||||
<br />
|
||||
<small>v{{ version }}</small>
|
||||
</p>
|
||||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
|
||||
<li><a href="https://pypi.org/project/responder/">Responder @ PyPI</a></li>
|
||||
<li><a href="https://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
|
||||
</ul>
|
||||
@@ -1 +0,0 @@
|
||||
../../CHANGELOG.md
|
||||
@@ -1,52 +0,0 @@
|
||||
# Sphinx configuration for Responder documentation.
|
||||
|
||||
import os
|
||||
|
||||
project = "responder"
|
||||
copyright = "2018-2026, Kenneth Reitz"
|
||||
author = "Kenneth Reitz"
|
||||
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
about = {}
|
||||
with open(os.path.join(here, "..", "..", "responder", "__version__.py")) as f:
|
||||
exec(f.read(), about)
|
||||
|
||||
version = about["__version__"]
|
||||
release = about["__version__"]
|
||||
|
||||
extensions = [
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.viewcode",
|
||||
"myst_parser",
|
||||
"sphinx_copybutton",
|
||||
"sphinx_design_elements",
|
||||
]
|
||||
|
||||
templates_path = ["_templates"]
|
||||
source_suffix = {".rst": "restructuredtext"}
|
||||
master_doc = "index"
|
||||
language = "en"
|
||||
exclude_patterns = []
|
||||
|
||||
# Theme
|
||||
html_theme = "alabaster"
|
||||
html_theme_options = {
|
||||
"show_powered_by": False,
|
||||
"github_user": "kennethreitz",
|
||||
"github_repo": "responder",
|
||||
"github_banner": False,
|
||||
"show_related": False,
|
||||
}
|
||||
html_static_path = ["_static"]
|
||||
html_sidebars = {
|
||||
"index": ["sidebarintro.html", "searchbox.html"],
|
||||
"**": ["sidebarintro.html", "localtoc.html", "searchbox.html"],
|
||||
}
|
||||
|
||||
# MyST
|
||||
myst_heading_anchors = 3
|
||||
|
||||
# Copybutton
|
||||
copybutton_remove_prompts = True
|
||||
copybutton_prompt_text = r">>> |\.\.\. |\$ "
|
||||
copybutton_prompt_is_regexp = True
|
||||
@@ -1,19 +0,0 @@
|
||||
# Example HTTP service definition, using Responder.
|
||||
# https://pypi.org/project/responder/
|
||||
import responder
|
||||
|
||||
api = responder.API()
|
||||
|
||||
|
||||
@api.route("/")
|
||||
async def index(req, resp):
|
||||
resp.text = "hello, world!"
|
||||
|
||||
|
||||
@api.route("/{greeting}")
|
||||
async def greet_world(req, resp, *, greeting):
|
||||
resp.text = f"{greeting}, world!"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
api.run()
|
||||
@@ -1,26 +0,0 @@
|
||||
# Example showing the lifespan context manager pattern.
|
||||
# https://pypi.org/project/responder/
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
import responder
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app):
|
||||
# Startup: initialize resources
|
||||
print("Starting up...")
|
||||
yield
|
||||
# Shutdown: clean up resources
|
||||
print("Shutting down...")
|
||||
|
||||
|
||||
api = responder.API(lifespan=lifespan)
|
||||
|
||||
|
||||
@api.route("/{greeting}")
|
||||
async def greet_world(req, resp, *, greeting):
|
||||
resp.text = f"{greeting}, world!"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
api.run()
|
||||
@@ -1,31 +0,0 @@
|
||||
"""Mount marimo notebooks inside a Responder API.
|
||||
|
||||
Requirements:
|
||||
pip install responder marimo
|
||||
|
||||
Usage:
|
||||
python examples/marimo_mount.py
|
||||
|
||||
Then visit:
|
||||
http://127.0.0.1:5042/hello → Responder JSON endpoint
|
||||
http://127.0.0.1:5042/notebooks/ → Interactive marimo notebook
|
||||
"""
|
||||
|
||||
import marimo
|
||||
|
||||
import responder
|
||||
|
||||
api = responder.API()
|
||||
|
||||
|
||||
@api.route("/hello")
|
||||
def hello(req, resp):
|
||||
resp.media = {"message": "Hello from Responder!"}
|
||||
|
||||
|
||||
# Mount marimo notebooks at /notebooks
|
||||
server = marimo.create_asgi_app().with_app(path="", root="notebooks/hello.py")
|
||||
api.mount("/notebooks", server.build())
|
||||
|
||||
if __name__ == "__main__":
|
||||
api.run()
|
||||
@@ -1,76 +0,0 @@
|
||||
# Complete REST API example with Pydantic validation.
|
||||
# https://responder.kennethreitz.org/tutorial-rest.html
|
||||
from pydantic import BaseModel
|
||||
|
||||
import responder
|
||||
|
||||
|
||||
class BookIn(BaseModel):
|
||||
title: str
|
||||
author: str
|
||||
year: int
|
||||
isbn: str | None = None
|
||||
|
||||
|
||||
class BookOut(BaseModel):
|
||||
id: int
|
||||
title: str
|
||||
author: str
|
||||
year: int
|
||||
isbn: str | None = None
|
||||
|
||||
|
||||
api = responder.API(
|
||||
title="Book Catalog",
|
||||
version="1.0",
|
||||
openapi="3.0.2",
|
||||
docs_route="/docs",
|
||||
)
|
||||
|
||||
books_db: dict[int, dict] = {}
|
||||
next_id = 1
|
||||
|
||||
|
||||
@api.route("/books", methods=["GET"])
|
||||
def list_books(req, resp):
|
||||
resp.media = list(books_db.values())
|
||||
|
||||
|
||||
@api.route(
|
||||
"/books",
|
||||
methods=["POST"],
|
||||
check_existing=False,
|
||||
request_model=BookIn,
|
||||
response_model=BookOut,
|
||||
)
|
||||
async def create_book(req, resp):
|
||||
global next_id
|
||||
data = await req.media()
|
||||
book = {"id": next_id, **data}
|
||||
books_db[next_id] = book
|
||||
next_id += 1
|
||||
resp.media = book
|
||||
resp.status_code = 201
|
||||
|
||||
|
||||
@api.route("/books/{book_id:int}", methods=["GET"])
|
||||
def get_book(req, resp, *, book_id):
|
||||
if book_id not in books_db:
|
||||
resp.status_code = 404
|
||||
resp.media = {"error": f"Book {book_id} not found"}
|
||||
return
|
||||
resp.media = books_db[book_id]
|
||||
|
||||
|
||||
@api.route("/books/{book_id:int}", methods=["DELETE"], check_existing=False)
|
||||
def delete_book(req, resp, *, book_id):
|
||||
if book_id not in books_db:
|
||||
resp.status_code = 404
|
||||
resp.media = {"error": f"Book {book_id} not found"}
|
||||
return
|
||||
del books_db[book_id]
|
||||
resp.status_code = 204
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
api.run()
|
||||
@@ -1,42 +0,0 @@
|
||||
# Server-Sent Events streaming example.
|
||||
# https://responder.kennethreitz.org/tour.html#server-sent-events-sse
|
||||
import asyncio
|
||||
|
||||
import responder
|
||||
|
||||
api = responder.API()
|
||||
|
||||
|
||||
@api.route("/")
|
||||
def index(req, resp):
|
||||
resp.html = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>SSE Stream</h1>
|
||||
<div id="events"></div>
|
||||
<script>
|
||||
const source = new EventSource("/stream");
|
||||
const events = document.getElementById("events");
|
||||
source.onmessage = (e) => {
|
||||
const p = document.createElement("p");
|
||||
p.textContent = e.data;
|
||||
events.appendChild(p);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
@api.route("/stream")
|
||||
async def stream(req, resp):
|
||||
@resp.sse
|
||||
async def events():
|
||||
for i in range(20):
|
||||
yield {"data": f"Event #{i}"}
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
api.run()
|
||||
@@ -1,25 +0,0 @@
|
||||
# Example HTTP service definition, using Responder.
|
||||
# https://pypi.org/project/responder/
|
||||
import responder
|
||||
|
||||
api = responder.API()
|
||||
|
||||
|
||||
@api.route("/")
|
||||
async def index(req, resp):
|
||||
resp.text = "Welcome"
|
||||
|
||||
|
||||
@api.route("/user")
|
||||
async def user_create(req, resp):
|
||||
data = await req.media()
|
||||
resp.text = f"Hello, {data['username']}"
|
||||
|
||||
|
||||
@api.route("/user/{identifier}")
|
||||
async def user_get(req, resp, *, identifier):
|
||||
resp.text = f"Hello, user {identifier}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
api.run()
|
||||
@@ -1,59 +0,0 @@
|
||||
# WebSocket chat room example.
|
||||
# https://responder.kennethreitz.org/tutorial-websockets.html
|
||||
from starlette.websockets import WebSocketDisconnect
|
||||
|
||||
import responder
|
||||
|
||||
api = responder.API()
|
||||
|
||||
connected = set()
|
||||
|
||||
|
||||
@api.route("/")
|
||||
def index(req, resp):
|
||||
resp.html = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Chat Room</h1>
|
||||
<div id="messages" style="height:300px;overflow-y:scroll;border:1px solid #ccc;padding:10px;"></div>
|
||||
<input id="input" placeholder="Type a message..." style="width:300px;" />
|
||||
<script>
|
||||
const ws = new WebSocket(`ws://${location.host}/chat`);
|
||||
const messages = document.getElementById("messages");
|
||||
const input = document.getElementById("input");
|
||||
ws.onmessage = (e) => {
|
||||
const p = document.createElement("p");
|
||||
p.textContent = e.data;
|
||||
messages.appendChild(p);
|
||||
messages.scrollTop = messages.scrollHeight;
|
||||
};
|
||||
input.addEventListener("keypress", (e) => {
|
||||
if (e.key === "Enter" && input.value) {
|
||||
ws.send(input.value);
|
||||
input.value = "";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
""" # noqa: E501
|
||||
|
||||
|
||||
@api.route("/chat", websocket=True)
|
||||
async def chat(ws):
|
||||
await ws.accept()
|
||||
connected.add(ws)
|
||||
try:
|
||||
while True:
|
||||
message = await ws.receive_text()
|
||||
for client in connected:
|
||||
await client.send_text(message)
|
||||
except WebSocketDisconnect:
|
||||
pass
|
||||
finally:
|
||||
connected.discard(ws)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
api.run()
|
||||
|
Before Width: | Height: | Size: 338 KiB |
|
Before Width: | Height: | Size: 349 KiB |
|
Before Width: | Height: | Size: 837 KiB |
|
Before Width: | Height: | Size: 19 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve"><polygon points="32.625,51 21.836,51 28.536,13 39.325,13 "></polygon><polygon points="49.107,51 38.319,51 45.019,13 55.808,13 "></polygon><rect x="9" y="18" width="12" height="12"></rect><rect x="9" y="33" width="12" height="12"></rect></svg>
|
||||
|
Before Width: | Height: | Size: 430 B |