Compare commits

..

1107 Commits

Author SHA1 Message Date
kennethreitz 24958bff51 v3.2.0: Request ID, rate limiting, MessagePack, docs update
New features:
- Request ID: api = responder.API(request_id=True)
- Rate limiting: RateLimiter(requests=100, period=60).install(api)
- MessagePack format: await req.media("msgpack")
- All new features documented in tour

176 tests, 95% coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 12:45:50 -04:00
kennethreitz 364f6b67f7 Add auto-validation, SSE, stream_file, after_request, route groups
Five new features:

1. **Pydantic auto-validation** — if request_model is set, request
   bodies are validated automatically and 422 returned on failure.
   If response_model is set, resp.media is serialized through the
   model (extra fields stripped, types enforced).

2. **Server-Sent Events** — resp.sse for real-time streaming:

       @resp.sse
       async def stream():
           yield {"event": "update", "data": "hello"}

3. **resp.stream_file()** — stream large files without loading
   into memory, with automatic content-type detection.

4. **after_request hooks** — run code after every request:

       @api.after_request()
       def add_request_id(req, resp):
           resp.headers["X-Request-ID"] = str(uuid.uuid4())

5. **Route groups** — organize routes with shared prefixes:

       v1 = api.group("/v1")

       @v1.route("/users")
       def list_users(req, resp): ...

Also fix streaming responses not sending Content-Type headers.

172 tests, 95% coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 12:42:48 -04:00
kennethreitz 2cab7b5af7 Document Pydantic OpenAPI support in feature tour
Three approaches: Pydantic models (recommended), YAML docstrings,
and marshmallow schemas — all work together.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 12:36:02 -04:00
kennethreitz 1bfd85b003 Add Pydantic support for OpenAPI schema generation
Define your API schemas with Pydantic models instead of (or alongside)
YAML docstrings and marshmallow:

    from pydantic import BaseModel

    class PetIn(BaseModel):
        name: str
        age: int = 0

    class PetOut(BaseModel):
        id: int
        name: str
        age: int

    @api.route("/pets", methods=["POST"],
               request_model=PetIn, response_model=PetOut)
    async def create_pet(req, resp):
        data = await req.media()
        resp.media = {"id": 1, **data}

Also works with @api.schema("Name") decorator for registering
standalone schema components.

Pydantic models, marshmallow schemas, and YAML docstrings can all
be used together in the same API.

Also: rewrite docs with more prose, restore sidebar logo and links,
add FastAPI acknowledgment, update homepage copy.

161 tests, 95% coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 12:35:07 -04:00
kennethreitz 33ebc77f10 Rewrite docs from scratch, remove 14MB of bundled fonts
Complete documentation rewrite:
- Clean, code-first index page (no badges, no testimonials)
- Rewritten quickstart: request/response reference, templates, background tasks
- Rewritten feature tour: method filtering, lifespan, file serving,
  error handling, hooks, WebSockets, GraphQL, OpenAPI, CORS, sessions
- Simplified testing and deployment guides
- Stripped conf.py to essential extensions only

Removed cruft:
- 14MB of paid font files (Mercury, Operator Mono)
- Google Analytics (deprecated Universal Analytics)
- UserVoice widget
- Konami code easter egg
- Algolia DocSearch (not configured)
- Twitter widgets
- Unused Sphinx extensions (mathjax, ifconfig, coverage, doctest, opengraph)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 12:17:22 -04:00
kennethreitz 30801557a3 Bump version to 3.1.0
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 07:57:32 -04:00
kennethreitz 73d46e9b03 CI: Remove linkcheck from docs workflow
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 07:54:59 -04:00
kennethreitz 3d65d88ea9 Rewrite README from scratch
Clean, code-first README. No badges, no testimonials — just
show what the framework does with real examples.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 07:53:25 -04:00
kennethreitz 8f979719a0 Remove poethepoet, use direct commands in CI
Replace all poe task runner usage with direct pytest/sphinx/ruff
commands. Remove poethepoet dependency and [tool.poe.tasks] config.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 07:50:32 -04:00
kennethreitz 0cbcaf9c4f Code quality improvements and test fixes (#592)
## Summary
Comprehensive post-v3.0.0 modernization: new features, bug fixes,
dependency cleanup, docs, and test coverage.

**New features:**
- **HTTP method filtering** — `@api.route("/data", methods=["GET"])`
- **Lifespan context manager** — modern async startup/shutdown
- **`api.exception_handler()`** — custom error handling per exception
type
- **`api.graphql()`** — one-liner GraphQL setup
- **`resp.file()`** — serve files from disk with auto content-type
- **before_request short-circuit** — set status code to skip route
handler
- **`req.path_params`** / **`req.client`** / **`req.is_json`** — new
request properties
- **`uuid`** and **`path`** route convertors
- **PEP 561 `py.typed`** marker

**Bug fixes:**
- Fix multipart parser losing headers
- Fix `url_for()` with typed params (`{id:int}`)
- Fix `resp.body` encoding crash on bytes content
- Fix Python 3.9 type syntax (`from __future__ import annotations`)
- Fix broken session test and no-op file upload test
- Fix helloworld example 404 on root path

**Dependencies:**
- Flattened — `pip install responder` gets everything
- Core: just starlette + uvicorn (down from 10 deps)

**Docs & README:**
- All new features documented in tour
- Modernized README features list
- Deployment guide: Docker, cloud, uvicorn
- Removed Pipenv, extras, stale references throughout

**Tests & quality:**
- 117 tests (up from 92), 91% coverage, 0 warnings
- CaseInsensitiveDict, GraphQL edge cases, staticfiles tests
- Ruff clean, all `tmpdir` → `tmp_path`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 07:44:11 -04:00
kennethreitz 3fa6f11ffa Clean up stale comments, dead test code, and flaky npm assertions
- Remove commented-out tests (route overlap, form data, file uploads)
- Remove stale TODO/FIXME comments from routes.py and api.py
- Make CLI npm error assertions case-insensitive and more flexible
  to handle different npm versions across CI environments

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 05:51:47 -04:00
kennethreitz 8b88b148bf Remove requests/requests-toolbelt from test code
Replace with stdlib urllib.request. No third-party HTTP client
needed for test probing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 05:51:47 -04:00
kennethreitz 1aecafa82a Update CHANGELOG for v3.0.0 release
Reflect all changes made in this branch: dependency reduction,
GraphQL modernization, pyproject.toml migration, etc. Also fix
compare links to point to kennethreitz/responder.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 05:51:47 -04:00
kennethreitz 8c763aa97e Migrate from setup.py to declarative pyproject.toml
All package metadata now lives in pyproject.toml. Removes setup.py
and MANIFEST.in. Also exports __version__ from the package root.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 05:51:47 -04:00
kennethreitz 91aa242a5a Fix python-multipart import deprecation warning
Use `python_multipart` import name instead of deprecated `multipart`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 05:51:47 -04:00
kennethreitz 084d057a99 Move apispec and marshmallow to openapi extra
These are only used by the OpenAPI extension, not core responder.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 05:51:47 -04:00
kennethreitz d3acf2c1c1 Drop rfc3986 dep, clean up internals
- Replace rfc3986 with stdlib urllib.parse
- Remove deprecated status code aliases (resume_incomplete/resume)
  that were marked for removal in 3.0
- Remove private ThreadPoolExecutor API usage in BackgroundQueue
- Clean up stale comments (old Starlette PR refs, requests attribution)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 05:51:47 -04:00
kennethreitz 80715a12ac Drop requests dependency, modernize GraphQL, clean up setup.py
- Remove `requests` as a core dependency — replaced CaseInsensitiveDict
  and RequestsCookieJar with lightweight stdlib implementations
- Remove `requests-toolbelt` — replaced multipart decoder with
  python-multipart (already a starlette transitive dep)
- Upgrade GraphQL to graphene 3 + graphql-core 3, drop graphql-server-core
- Update GraphiQL template from 0.12.0 (2018) to 3.0.6 with React 18
- Clean up setup.py: remove dead DebCommand, UploadCommand, publish hack
- Remove linting from `poe check` (tests only)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 05:51:47 -04:00
kennethreitz 66fc7afbe4 CI: Simplify test matrix to Ubuntu-only
No need to test on all three OSes — this is a pure Python web
framework with no platform-specific code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 05:51:47 -04:00
kennethreitz e7776eb9e8 v3.0.0: Modernize for latest Starlette, drop EOL Pythons
- Bump version to 3.0.0
- Replace deprecated starlette.middleware.wsgi with a2wsgi
- Pin starlette[full]>=0.40, add a2wsgi dependency
- Bump minimum Python to 3.9, drop 3.7/3.8 support
- Remove whitenoise conditional (Python <3.8 no longer supported)
- Clean up CI matrix: drop old Python versions and OS exclusions
- Fix deprecated httpx TestClient usage in tests (data= -> content=, per-request cookies)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 05:51:47 -04:00
Andreas Motl 944d47da45 CI: Remove pip caching. Has woes. 2025-02-03 00:43:10 +01:00
Andreas Motl a3a12cff77 Chore: Add another basic example 2025-02-03 00:43:10 +01:00
Andreas Motl 7b2839086d Thanks, JetBrains. 2025-01-21 23:19:09 +01:00
Andreas Motl 351ff8d95e Documentation: Copy editing, this and that 2025-01-19 05:21:46 +01:00
Andreas Motl 2278beba18 SFA: Update to pueblo[sfa] 0.0.11 2025-01-19 05:02:47 +01:00
Andreas Motl 3cfc7ec2b6 Chore: Remove support for EOL Python 3.6 2025-01-19 05:02:47 +01:00
Andreas Motl 0de22eeed2 SFA: Use application loader from pueblo.sfa 2025-01-19 05:02:47 +01:00
Andreas Motl b0cc37861b SFA: Unlock loading application from remote location, using fsspec 2025-01-19 05:02:47 +01:00
Andreas Motl 7d4532acc9 CI: Use GHA recipe astral-sh/setup-uv, and more 2025-01-18 22:56:50 +01:00
Andreas Motl 1b63d2943a Chore: A few updates from code review etc. 2025-01-18 22:22:36 +01:00
Andreas Motl b5723303c8 CI: The macOS-12 environment is deprecated 2025-01-18 22:22:36 +01:00
Andreas Motl 5730be4b31 Chore: Format code using most recent ruff and pyproject-fmt 2025-01-18 22:22:36 +01:00
Andreas Motl 6f9c11645a CLI: Load from file or module. Add software tests and documentation.
Also, refactor to `responder.ext.cli`.
2025-01-18 22:22:36 +01:00
kennethreitz 827cc64988 CLI: Re-add command line interface (2024)
Install: pip install 'responder[cli]'

The CLI is an optional subsystem from now on.
2025-01-18 22:22:36 +01:00
Andreas Motl 7b5db5bc33 uvicorn: Fix uvicorn.run invocation re. debug argument
The `debug` argument no longer exists. Let's adjust the `log_level` to
`debug` instead.
2025-01-18 22:22:36 +01:00
kennethreitz b9a03c7088 Create FUNDING.yml
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2024-11-17 06:28:31 -05:00
Andreas Motl 4cbf55508e Documentation: Update change log for upcoming version 3.0.0 2024-10-31 09:51:29 +01:00
Andreas Motl 83d0fcf1ae Documentation: Update developer sandbox documentation 2024-10-31 09:51:29 +01:00
Taoufik a698eaaab3 GraphQL: Re-add extension and dependencies (2024) 2024-10-31 06:36:13 +01:00
Andreas Motl 3aa21eed08 OpenAPI: Refactor module to responder.ext.openapi
It has been `responder.ext.schema` before.
2024-10-30 23:45:55 +01:00
Andreas Motl 2741c74b90 OpenAPI: Make extension optional
Install with: pip install 'responder[openapi]'
2024-10-30 23:45:55 +01:00
Andreas Motl aba96525ad Dependencies: Migrate from WhiteNoise to ServeStatic 2024-10-30 23:21:23 +01:00
Andreas Motl a5b6d36991 Sandbox: Enable mypy type checker 2024-10-30 23:12:11 +01:00
Andreas Motl e4cff76fa6 Documentation: Unlock writing in Markdown, using Sphinx/MyST 2024-10-30 21:23:12 +01:00
Andreas Motl f11ad7136d Documentation: Add Sphinx extensions "copybutton" and "opengraph" 2024-10-30 21:23:12 +01:00
Andreas Motl c32e8c7468 Documentation: Refactor Sphinx dependencies into setup.py 2024-10-30 20:42:10 +01:00
Andreas Motl d93e3cd12c Documentation: Update Read the Docs (RTD) configuration 2024-10-30 20:42:10 +01:00
Andreas Motl 040f1a57e4 Dependencies: Remove aiofiles
Apparently, it is not used.
2024-10-28 16:36:46 +01:00
dependabot[bot] 307313744f Update alabaster requirement from <0.8 to <1.1
Updates the requirements on [alabaster](https://github.com/sphinx-doc/alabaster) to permit the latest version.
- [Release notes](https://github.com/sphinx-doc/alabaster/releases)
- [Changelog](https://github.com/sphinx-doc/alabaster/blob/master/docs/changelog.rst)
- [Commits](https://github.com/sphinx-doc/alabaster/compare/0.1.0...1.0.0)

---
updated-dependencies:
- dependency-name: alabaster
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 10:47:51 +01:00
Andreas Motl 98ca45003b Documentation: Badges, linking, wording, inline comments. This and that.
A few of the adjustments here have been required to mitigate Sphinx
warnings, which would converge to errors on CI, thus failing the build.

A few other changes, both wording and syntax/formatting fixes, are
coming from regular copyediting and documentation maintenance.
2024-10-27 18:13:13 +01:00
Andreas Motl ab76594297 CI: Run link checker and build documentation as GHA workflow 2024-10-27 18:13:13 +01:00
Andreas Motl 7fba0f6362 Fix dispatching static_route=None on Windows 2024-10-26 05:20:57 -04:00
Andreas Motl 4ff73e9d0c Sandbox: Bring back python setup.py publish subcommand
It has been removed too early.
2024-10-26 05:16:12 -04:00
Andreas Motl 68bbea0a55 CI: Validate on Python 3.6+
You never know how you possibly save someone's life with that.
2024-10-26 00:42:07 +02:00
Andreas Motl 106e5e9073 CI: Validate on Windows operating system 2024-10-26 00:27:44 +02:00
Andreas Motl 3426aa71da Documentation: Fix broken links in README 2024-10-25 12:13:08 -04:00
Andreas Motl 413028b636 Tasks: Define sandbox tasks in pyproject.toml, using poethepoet
The fundamental commands to mostly use are:

- poe format
- poe check
2024-10-25 07:39:54 -04:00
Andreas Motl 3edf979a8c Dependencies: Dissolve requirements-dev.txt 2024-10-25 07:39:54 -04:00
Andreas Motl cd75deeb4e Python: Verify support for Python 3.13 2024-10-24 18:36:05 +02:00
Andreas Motl b71bb5ddb9 apistar: Rename variables api_theme -> openapi_theme, etc. 2024-10-24 18:19:03 +02:00
Tabot Kevin 27a9459f22 apistar: Replace use of apistar package with local API theme files
This has already been submitted by @tabotkevin with GH-480, but got lost
for whatever reason.
2024-10-24 18:19:03 +02:00
dependabot[bot] b39c539d57 Update readme-renderer requirement from <23 to <45 (#540)
[//]: # (dependabot-start)
⚠️  **Dependabot is rebasing this PR** ⚠️ 

Rebasing might not happen immediately, so don't worry if this takes some
time.

Note: if you make any changes to this PR yourself, they will take
precedence over the rebase.

---

[//]: # (dependabot-end)

Updates the requirements on
[readme-renderer](https://github.com/pypa/readme_renderer) to permit the
latest version.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pypa/readme_renderer/releases">readme-renderer's
releases</a>.</em></p>
<blockquote>
<h2>44.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Support newer docutils versions by <a
href="https://github.com/kurtmckee"><code>@​kurtmckee</code></a> in <a
href="https://redirect.github.com/pypa/readme_renderer/pull/315">pypa/readme_renderer#315</a></li>
<li>Resolve Node 16 deprecation warnings in CI by <a
href="https://github.com/kurtmckee"><code>@​kurtmckee</code></a> in <a
href="https://redirect.github.com/pypa/readme_renderer/pull/309">pypa/readme_renderer#309</a></li>
<li>Lint specific directories by <a
href="https://github.com/kurtmckee"><code>@​kurtmckee</code></a> in <a
href="https://redirect.github.com/pypa/readme_renderer/pull/312">pypa/readme_renderer#312</a></li>
<li>Build a wheel once, for all test environments by <a
href="https://github.com/kurtmckee"><code>@​kurtmckee</code></a> in <a
href="https://redirect.github.com/pypa/readme_renderer/pull/308">pypa/readme_renderer#308</a></li>
<li>Update .gitpod.yml to replace deprecated extension by <a
href="https://github.com/shenxianpeng"><code>@​shenxianpeng</code></a>
in <a
href="https://redirect.github.com/pypa/readme_renderer/pull/306">pypa/readme_renderer#306</a></li>
<li>Exclude .gitpod.yml by default with check-manifest by <a
href="https://github.com/shenxianpeng"><code>@​shenxianpeng</code></a>
in <a
href="https://redirect.github.com/pypa/readme_renderer/pull/307">pypa/readme_renderer#307</a></li>
<li>Lazy open output files, and always close them by <a
href="https://github.com/kurtmckee"><code>@​kurtmckee</code></a> in <a
href="https://redirect.github.com/pypa/readme_renderer/pull/314">pypa/readme_renderer#314</a></li>
<li>Release 44 by <a
href="https://github.com/kurtmckee"><code>@​kurtmckee</code></a> in <a
href="https://redirect.github.com/pypa/readme_renderer/pull/316">pypa/readme_renderer#316</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/kurtmckee"><code>@​kurtmckee</code></a>
made their first contribution in <a
href="https://redirect.github.com/pypa/readme_renderer/pull/315">pypa/readme_renderer#315</a></li>
<li><a
href="https://github.com/shenxianpeng"><code>@​shenxianpeng</code></a>
made their first contribution in <a
href="https://redirect.github.com/pypa/readme_renderer/pull/306">pypa/readme_renderer#306</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/pypa/readme_renderer/compare/43.0...44.0">https://github.com/pypa/readme_renderer/compare/43.0...44.0</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/pypa/readme_renderer/blob/main/CHANGES.rst">readme-renderer's
changelog</a>.</em></p>
<blockquote>
<h2>44.0 (2024-07-08)</h2>
<ul>
<li>Drop support for Python 3.8 (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/315">#315</a>)</li>
<li>Require docutils 0.21.2 and higher (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/315">#315</a>)</li>
<li>Remove HTML5 <code>&lt;s&gt;</code> tag from the list of allowed
HTML tags (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/315">#315</a>)</li>
<li>Test all supported CPython and PyPy versions in CI (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/315">#315</a>)</li>
<li>Resolve Node 16 deprecation warnings in CI (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/309">#309</a>)</li>
<li>Lint specific directories (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/312">#312</a>)</li>
<li>Build a wheel once for all tox test environments (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/308">#308</a>)</li>
<li>Lazy open output files, and always close them (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/314">#314</a>)</li>
<li>Gitpod: Migrate to the Even Better TOML extension (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/306">#306</a>)</li>
<li>check-manifest: Remove a now-default <code>.gitpod.yml</code>
exclusion (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/307">#307</a>)</li>
</ul>
<h2>43.0 (2024-02-26)</h2>
<ul>
<li>Allow HTML5 <code>picture</code> tag through cleaner (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/299">#299</a>)</li>
<li>Test against Python 3.12 (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/300">#300</a>)</li>
</ul>
<h2>42.0 (2023-09-07)</h2>
<ul>
<li>Migrate from <code>bleach</code> to <code>nh3</code> (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/295">#295</a>)</li>
<li>Migrate from <code>setup.py</code> to
<code>pyproject.toml</code></li>
</ul>
<h2>41.0 (2023-08-18)</h2>
<ul>
<li>Allow HTML5 <code>figcaption</code> tag through cleaner (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/291">#291</a>)</li>
<li>Test <code>README.rst</code> from this project (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/288">#288</a>)</li>
</ul>
<h2>40.0 (2023-06-16)</h2>
<ul>
<li>Add CLI option to render package README. (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/271">#271</a>)</li>
<li>Adapt tests to pygments 2.14.0 (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/272">#272</a>)</li>
<li>Update release process to use Trusted Publishing (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/276">#276</a>)</li>
<li>Replace usage of deprecated <code>pkg_resources</code> with
<code>importlib.metadata</code> (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/281">#281</a>)</li>
<li>Drop support for Python 3.7 (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/282">#282</a>),
Test against Python 3.11 (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/280">#280</a>)</li>
</ul>
<h2>37.3 (2022-10-31)</h2>
<ul>
<li>Allow HTML5 <code>figure</code> tag through cleaner (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/265">#265</a>)</li>
</ul>
<h2>37.2 (2022-09-24)</h2>
<ul>
<li>Allow HTML5 <code>s</code> tag through cleaner (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/261">#261</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pypa/readme_renderer/commit/1d0497c37a6033d791c74e800590dcd0d34f6e08"><code>1d0497c</code></a>
Release 44 (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/316">#316</a>)</li>
<li><a
href="https://github.com/pypa/readme_renderer/commit/09620a64219f80238e396b25ab18016ca495cf3c"><code>09620a6</code></a>
Lazy open output files, and always close them (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/314">#314</a>)</li>
<li><a
href="https://github.com/pypa/readme_renderer/commit/6061b3ebbcdecc33f369c0d48fe0641b34858294"><code>6061b3e</code></a>
Exclude .gitpod.yml by default with check-manifest (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/307">#307</a>)</li>
<li><a
href="https://github.com/pypa/readme_renderer/commit/749204b0eaa5a72fceb29c707d5321687ac447a3"><code>749204b</code></a>
Update .gitpod.yml to replace deprecated extension (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/306">#306</a>)</li>
<li><a
href="https://github.com/pypa/readme_renderer/commit/e84ded18e61e33ae117f20d0eefb1f92edc88ed0"><code>e84ded1</code></a>
Build a wheel once, for all test environments (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/308">#308</a>)</li>
<li><a
href="https://github.com/pypa/readme_renderer/commit/b447d5d7ba60ee71dff19f753d7b6c33312411b8"><code>b447d5d</code></a>
Lint specific directories (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/312">#312</a>)</li>
<li><a
href="https://github.com/pypa/readme_renderer/commit/08172046a88d019989c2de36f9cc0c88695cf2b2"><code>0817204</code></a>
Resolve Node 16 deprecation warnings in CI (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/309">#309</a>)</li>
<li><a
href="https://github.com/pypa/readme_renderer/commit/fefd2859fb3253744a21f327b2079cdd14240bfe"><code>fefd285</code></a>
Support newer docutils versions (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/315">#315</a>)</li>
<li><a
href="https://github.com/pypa/readme_renderer/commit/175c65ad514acad7fccf54c3aab9fe701bdd9f06"><code>175c65a</code></a>
Release 43.0 (<a
href="https://redirect.github.com/pypa/readme_renderer/issues/303">#303</a>)</li>
<li><a
href="https://github.com/pypa/readme_renderer/commit/78ccf3f50f58467e2b3886412d3ba8c7a3a398d4"><code>78ccf3f</code></a>
adds testing for 3.12, fixes <a
href="https://redirect.github.com/pypa/readme_renderer/issues/290">#290</a>
(<a
href="https://redirect.github.com/pypa/readme_renderer/issues/300">#300</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/pypa/readme_renderer/compare/0.1.0...44.0">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-24 07:39:51 -04:00
dependabot[bot] 718b53cce2 Update markupsafe requirement from <2 to <4 (#539)
[//]: # (dependabot-start)
⚠️  **Dependabot is rebasing this PR** ⚠️ 

Rebasing might not happen immediately, so don't worry if this takes some
time.

Note: if you make any changes to this PR yourself, they will take
precedence over the rebase.

---

[//]: # (dependabot-end)

Updates the requirements on
[markupsafe](https://github.com/pallets/markupsafe) to permit the latest
version.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pallets/markupsafe/releases">markupsafe's
releases</a>.</em></p>
<blockquote>
<h2>3.0.2</h2>
<p>This is the MarkupSafe 3.0.2 fix release, which fixes bugs but does
not otherwise change behavior and should not result in breaking
changes.</p>
<p>PyPI: <a
href="https://pypi.org/project/MarkupSafe/3.0.2/">https://pypi.org/project/MarkupSafe/3.0.2/</a>
Changes: <a
href="https://markupsafe.palletsprojects.com/en/stable/changes/#version-3-0-2">https://markupsafe.palletsprojects.com/en/stable/changes/#version-3-0-2</a>
Milestone: <a
href="https://github.com/pallets/markupsafe/milestone/14?closed=1">https://github.com/pallets/markupsafe/milestone/14?closed=1</a></p>
<ul>
<li>Fix compatibility when <code>__str__</code> returns a
<code>str</code> subclass. <a
href="https://redirect.github.com/pallets/markupsafe/issues/472">#472</a></li>
<li>Build requires setuptools &gt;= 70.1. <a
href="https://redirect.github.com/pallets/markupsafe/issues/475">#475</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/pallets/markupsafe/blob/main/CHANGES.rst">markupsafe's
changelog</a>.</em></p>
<blockquote>
<h2>Version 3.0.2</h2>
<p>Released 2024-10-18</p>
<ul>
<li>Fix compatibility when <code>__str__</code> returns a
<code>str</code> subclass. :issue:<code>472</code></li>
<li>Build requires setuptools &gt;= 70.1. :issue:<code>475</code></li>
</ul>
<h2>Version 3.0.1</h2>
<p>Released 2024-10-08</p>
<ul>
<li>Address compiler warnings that became errors in GCC 14.
:issue:<code>466</code></li>
<li>Fix compatibility with proxy objects. :issue:<code>467</code></li>
</ul>
<h2>Version 3.0.0</h2>
<p>Released 2024-10-07</p>
<ul>
<li>Support Python 3.13 and its experimental free-threaded build.
:pr:<code>461</code></li>
<li>Drop support for Python 3.7 and 3.8.</li>
<li>Use modern packaging metadata with <code>pyproject.toml</code>
instead of <code>setup.cfg</code>.
:pr:<code>348</code></li>
<li>Change <code>distutils</code> imports to <code>setuptools</code>.
:pr:<code>399</code></li>
<li>Use deferred evaluation of annotations. :pr:<code>400</code></li>
<li>Update signatures for <code>Markup</code> methods to match
<code>str</code> signatures. Use
positional-only arguments. :pr:<code>400</code></li>
<li>Some <code>str</code> methods on <code>Markup</code> no longer
escape their argument:
<code>strip</code>, <code>lstrip</code>, <code>rstrip</code>,
<code>removeprefix</code>, <code>removesuffix</code>,
<code>partition</code>, and <code>rpartition</code>;
<code>replace</code> only escapes its <code>new</code>
argument. These methods are conceptually linked to search methods such
as
<code>in</code>, <code>find</code>, and <code>index</code>, which
already do not escape their argument.
:issue:<code>401</code></li>
<li>The <code>__version__</code> attribute is deprecated. Use feature
detection, or
<code>importlib.metadata.version(&quot;markupsafe&quot;)</code>,
instead. :pr:<code>402</code></li>
<li>Speed up escaping plain strings by 40%. :pr:<code>434</code></li>
<li>Simplify speedups implementation. :pr:<code>437</code></li>
</ul>
<h2>Version 2.1.5</h2>
<p>Released 2024-02-02</p>
<ul>
<li>Fix <code>striptags</code> not collapsing spaces.
:issue:<code>417</code></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pallets/markupsafe/commit/28ace20b140d15c083e1cbc163ee6b7778ba098c"><code>28ace20</code></a>
release version 3.0.2</li>
<li><a
href="https://github.com/pallets/markupsafe/commit/6b51fd8f7386983b7038ad973557367cbd48579a"><code>6b51fd8</code></a>
build requires at least setuptools 70.1 (<a
href="https://redirect.github.com/pallets/markupsafe/issues/478">#478</a>)</li>
<li><a
href="https://github.com/pallets/markupsafe/commit/99dda9fd708432bd07d02327b2668661aa3cdaa0"><code>99dda9f</code></a>
build requires at least setuptools 70.1</li>
<li><a
href="https://github.com/pallets/markupsafe/commit/3d8fd8cc006124a49ce2f4268b4d1739e301583e"><code>3d8fd8c</code></a>
fix version</li>
<li><a
href="https://github.com/pallets/markupsafe/commit/1933c4be9c2c88613f7660840cde27a1bb7567e0"><code>1933c4b</code></a>
fix version</li>
<li><a
href="https://github.com/pallets/markupsafe/commit/e85aff4d878aa458d5c1e879bf475d8483647f71"><code>e85aff4</code></a>
relax speedups str check (<a
href="https://redirect.github.com/pallets/markupsafe/issues/477">#477</a>)</li>
<li><a
href="https://github.com/pallets/markupsafe/commit/8cb1691ca038ca39942e088b956f5b94d8f636bf"><code>8cb1691</code></a>
relax speedups str check</li>
<li><a
href="https://github.com/pallets/markupsafe/commit/4dafb7c36f1f654f1edd85228d346252b0065d45"><code>4dafb7c</code></a>
start version 3.1.0</li>
<li><a
href="https://github.com/pallets/markupsafe/commit/9c44ecf45141f691d373a66ce664c43b5a6cc761"><code>9c44ecf</code></a>
update docs build</li>
<li><a
href="https://github.com/pallets/markupsafe/commit/275c76905617c3f0e34de14e8794fcf4dfb0f937"><code>275c769</code></a>
Merge branch '2.1.x' into 3.0.x</li>
<li>Additional commits viewable in <a
href="https://github.com/pallets/markupsafe/compare/0.9...3.0.2">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-24 07:39:11 -04:00
dependabot[bot] 2e0b4975f7 Update sphinxcontrib-websupport requirement from <1.2 to <2.1 (#538)
[//]: # (dependabot-start)
⚠️  **Dependabot is rebasing this PR** ⚠️ 

Rebasing might not happen immediately, so don't worry if this takes some
time.

Note: if you make any changes to this PR yourself, they will take
precedence over the rebase.

---

[//]: # (dependabot-end)

Updates the requirements on
[sphinxcontrib-websupport](https://github.com/sphinx-doc/sphinxcontrib-websupport)
to permit the latest version.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/sphinx-doc/sphinxcontrib-websupport/releases">sphinxcontrib-websupport's
releases</a>.</em></p>
<blockquote>
<h2>sphinxcontrib-websupport 2.0.0</h2>
<p>Changelog: <a
href="https://github.com/sphinx-doc/sphinxcontrib-websupport/blob/master/CHANGES.rst">https://github.com/sphinx-doc/sphinxcontrib-websupport/blob/master/CHANGES.rst</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/sphinx-doc/sphinxcontrib-websupport/blob/master/CHANGES.rst">sphinxcontrib-websupport's
changelog</a>.</em></p>
<blockquote>
<h1>Release 2.0.0 (2024-07-28)</h1>
<ul>
<li>Adopt Ruff</li>
<li>Tighten MyPy settings</li>
<li>Update GitHub actions versions</li>
</ul>
<h1>Release 1.2.7 (2024-01-13)</h1>
<ul>
<li>Fix tests for sqlalchemy 2.</li>
<li>Publish a <code>whoosh</code> extra.</li>
</ul>
<h1>Release 1.2.6 (2023-08-09)</h1>
<ul>
<li>Fix tests for Sphinx 7.1 and below</li>
</ul>
<h1>Release 1.2.5 (2023-08-07)</h1>
<ul>
<li>Drop support for Python 3.5, 3.6, 3.7, and 3.8</li>
<li>Raise minimum required Sphinx version to 5.0</li>
</ul>
<h1>Release 1.2.4 (2020-08-09)</h1>
<ul>
<li>Import PickleHTMLBuilder from sphinxcontrib-serializinghtml
package</li>
</ul>
<h1>Release 1.2.3 (2020-06-27)</h1>
<ul>
<li><a
href="https://redirect.github.com/sphinx-doc/sphinxcontrib-websupport/issues/43">#43</a>:
doctreedir argument has been ignored on initialize app</li>
</ul>
<h1>Release 1.2.2 (2020-04-29)</h1>
<ul>
<li>Stop to use sphinx.util.pycompat:htmlescape</li>
</ul>
<h1>Release 1.2.1 (2020-03-21)</h1>
<ul>
<li><a
href="https://redirect.github.com/sphinx-doc/sphinxcontrib-websupport/issues/41">#41</a>:
templates/searchresults.html is missing in the source tarball</li>
</ul>
<h1>Release 1.2.0 (2020-02-07)</h1>
<ul>
<li>Drop python2.7 and 3.4 support</li>
</ul>
<p>Release 1.1.2 (2019-05-19)</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/sphinx-doc/sphinxcontrib-websupport/commit/142b41e404e0197c8b48147284302cb6aa8b4207"><code>142b41e</code></a>
Bump to 2.0.0</li>
<li><a
href="https://github.com/sphinx-doc/sphinxcontrib-websupport/commit/6a625bd314a7338c3a91ebdf846743421387092d"><code>6a625bd</code></a>
Update CHANGES links</li>
<li><a
href="https://github.com/sphinx-doc/sphinxcontrib-websupport/commit/b6906da79bfff09120d43a0aa551b89d774ea3af"><code>b6906da</code></a>
Rename LICENSE to LICENCE.rst</li>
<li><a
href="https://github.com/sphinx-doc/sphinxcontrib-websupport/commit/a21e38577951121bd92f5a5606a012dc75a0a32b"><code>a21e385</code></a>
Rename CHANGES to CHANGES.rst</li>
<li><a
href="https://github.com/sphinx-doc/sphinxcontrib-websupport/commit/992d6fd2fd4ed1185d424596517a0c81be6c039b"><code>992d6fd</code></a>
Run CI with Python 3.12 releases</li>
<li><a
href="https://github.com/sphinx-doc/sphinxcontrib-websupport/commit/6c0277eb35aa2866b18d9bdc111fc074719309f0"><code>6c0277e</code></a>
Run mypy without command-line options</li>
<li><a
href="https://github.com/sphinx-doc/sphinxcontrib-websupport/commit/83f178dcc1e446956d8a24de06afe8222dc48e1b"><code>83f178d</code></a>
Use the latest GitHub actions versions</li>
<li><a
href="https://github.com/sphinx-doc/sphinxcontrib-websupport/commit/155ae9ca9f26c022d79f93058da75b7385e1adf1"><code>155ae9c</code></a>
Enable GitHub's dependabot package update service</li>
<li><a
href="https://github.com/sphinx-doc/sphinxcontrib-websupport/commit/7f05400e51a9bae54009f90cc60dbee83341c087"><code>7f05400</code></a>
Adopt Ruff and use stricter MyPy settings</li>
<li><a
href="https://github.com/sphinx-doc/sphinxcontrib-websupport/commit/be777a7ed355ac85234646505c6ce402966d7543"><code>be777a7</code></a>
Update .gitignore</li>
<li>Additional commits viewable in <a
href="https://github.com/sphinx-doc/sphinxcontrib-websupport/compare/1.0.0...2.0.0">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Kenneth Reitz <me@kennethreitz.org>
2024-10-24 07:38:58 -04:00
dependabot[bot] a118a5dc4b Bump actions/setup-python from 4 to 5 (#537)
[//]: # (dependabot-start)
⚠️  **Dependabot is rebasing this PR** ⚠️ 

Rebasing might not happen immediately, so don't worry if this takes some
time.

Note: if you make any changes to this PR yourself, they will take
precedence over the rebase.

---

[//]: # (dependabot-end)

Bumps [actions/setup-python](https://github.com/actions/setup-python)
from 4 to 5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/setup-python/releases">actions/setup-python's
releases</a>.</em></p>
<blockquote>
<h2>v5.0.0</h2>
<h2>What's Changed</h2>
<p>In scope of this release, we update node version runtime from node16
to node20 (<a
href="https://redirect.github.com/actions/setup-python/pull/772">actions/setup-python#772</a>).
Besides, we update dependencies to the latest versions.</p>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-python/compare/v4.8.0...v5.0.0">https://github.com/actions/setup-python/compare/v4.8.0...v5.0.0</a></p>
<h2>v4.8.0</h2>
<h2>What's Changed</h2>
<p>In scope of this release we added support for GraalPy (<a
href="https://redirect.github.com/actions/setup-python/pull/694">actions/setup-python#694</a>).
You can use this snippet to set up GraalPy:</p>
<pre lang="yaml"><code>steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4 
  with:
    python-version: 'graalpy-22.3' 
- run: python my_script.py
</code></pre>
<p>Besides, the release contains such changes as:</p>
<ul>
<li>Trim python version when reading from file by <a
href="https://github.com/FerranPares"><code>@​FerranPares</code></a> in
<a
href="https://redirect.github.com/actions/setup-python/pull/628">actions/setup-python#628</a></li>
<li>Use non-deprecated versions in examples by <a
href="https://github.com/jeffwidman"><code>@​jeffwidman</code></a> in <a
href="https://redirect.github.com/actions/setup-python/pull/724">actions/setup-python#724</a></li>
<li>Change deprecation comment to past tense by <a
href="https://github.com/jeffwidman"><code>@​jeffwidman</code></a> in <a
href="https://redirect.github.com/actions/setup-python/pull/723">actions/setup-python#723</a></li>
<li>Bump <code>@​babel/traverse</code> from 7.9.0 to 7.23.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/setup-python/pull/743">actions/setup-python#743</a></li>
<li>advanced-usage.md: Encourage the use actions/checkout@v4 by <a
href="https://github.com/cclauss"><code>@​cclauss</code></a> in <a
href="https://redirect.github.com/actions/setup-python/pull/729">actions/setup-python#729</a></li>
<li>Examples now use checkout@v4 by <a
href="https://github.com/simonw"><code>@​simonw</code></a> in <a
href="https://redirect.github.com/actions/setup-python/pull/738">actions/setup-python#738</a></li>
<li>Update actions/checkout to v4 by <a
href="https://github.com/dmitry-shibanov"><code>@​dmitry-shibanov</code></a>
in <a
href="https://redirect.github.com/actions/setup-python/pull/761">actions/setup-python#761</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/FerranPares"><code>@​FerranPares</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-python/pull/628">actions/setup-python#628</a></li>
<li><a href="https://github.com/timfel"><code>@​timfel</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/setup-python/pull/694">actions/setup-python#694</a></li>
<li><a
href="https://github.com/jeffwidman"><code>@​jeffwidman</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/setup-python/pull/724">actions/setup-python#724</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-python/compare/v4...v4.8.0">https://github.com/actions/setup-python/compare/v4...v4.8.0</a></p>
<h2>v4.7.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Bump word-wrap from 1.2.3 to 1.2.4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/setup-python/pull/702">actions/setup-python#702</a></li>
<li>Add range validation for toml files by <a
href="https://github.com/dmitry-shibanov"><code>@​dmitry-shibanov</code></a>
in <a
href="https://redirect.github.com/actions/setup-python/pull/726">actions/setup-python#726</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-python/compare/v4...v4.7.1">https://github.com/actions/setup-python/compare/v4...v4.7.1</a></p>
<h2>v4.7.0</h2>
<p>In scope of this release, the support for reading python version from
pyproject.toml was added (<a
href="https://redirect.github.com/actions/setup-python/pull/669">actions/setup-python#669</a>).</p>
<pre lang="yaml"><code>      - name: Setup Python
        uses: actions/setup-python@v4
&lt;/tr&gt;&lt;/table&gt; 
</code></pre>
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/setup-python/commit/f677139bbe7f9c59b41e40162b753c062f5d49a3"><code>f677139</code></a>
Bump pyinstaller from 3.6 to 5.13.1 in /<strong>tests</strong>/data (<a
href="https://redirect.github.com/actions/setup-python/issues/923">#923</a>)</li>
<li><a
href="https://github.com/actions/setup-python/commit/2bd53f9a4d1dd1cd21eaffcc01a7b91a8e73ea4c"><code>2bd53f9</code></a>
Documentation update for caching poetry dependencies (<a
href="https://redirect.github.com/actions/setup-python/issues/908">#908</a>)</li>
<li><a
href="https://github.com/actions/setup-python/commit/80b49d3ed89312896dbdcbefc2ddb159c7f8ca43"><code>80b49d3</code></a>
fix: add arch to cache key (<a
href="https://redirect.github.com/actions/setup-python/issues/896">#896</a>)</li>
<li><a
href="https://github.com/actions/setup-python/commit/036a5236741fd24c89eea80d1b76179e8e5f9214"><code>036a523</code></a>
Fix: Add <code>.zip</code> extension to Windows package downloads for
<code>Expand-Archive</code> C...</li>
<li><a
href="https://github.com/actions/setup-python/commit/04c1311429f7be71707d8ab66c7af8a14e54b938"><code>04c1311</code></a>
Fix display of emojis in contributors doc (<a
href="https://redirect.github.com/actions/setup-python/issues/899">#899</a>)</li>
<li><a
href="https://github.com/actions/setup-python/commit/cb6845644151e35f879e10f2f0896c3c8bee372c"><code>cb68456</code></a>
Updated <code>@​iarna/toml</code> version to 3.0.0 (<a
href="https://redirect.github.com/actions/setup-python/issues/912">#912</a>)</li>
<li><a
href="https://github.com/actions/setup-python/commit/39cd14951b08e74b54015e9e001cdefcf80e669f"><code>39cd149</code></a>
Documentation update for cache (<a
href="https://redirect.github.com/actions/setup-python/issues/873">#873</a>)</li>
<li><a
href="https://github.com/actions/setup-python/commit/a0d74c0c423f896bc4e7be91d5cb1e2d54438db3"><code>a0d74c0</code></a>
fix(ci): update all failing workflows (<a
href="https://redirect.github.com/actions/setup-python/issues/863">#863</a>)</li>
<li><a
href="https://github.com/actions/setup-python/commit/4eb7dbcb9561cb76a85079ffa9d89b983166e00c"><code>4eb7dbc</code></a>
Bump braces from 3.0.2 to 3.0.3 (<a
href="https://redirect.github.com/actions/setup-python/issues/893">#893</a>)</li>
<li><a
href="https://github.com/actions/setup-python/commit/82c7e631bb3cdc910f68e0081d67478d79c6982d"><code>82c7e63</code></a>
Documentation changes for avoiding rate limit issues on GHES (<a
href="https://redirect.github.com/actions/setup-python/issues/835">#835</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/setup-python/compare/v4...v5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-python&package-manager=github_actions&previous-version=4&new-version=5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Kenneth Reitz <me@kennethreitz.org>
2024-10-24 07:38:37 -04:00
dependabot[bot] 69c1d7f185 Bump actions/checkout from 3 to 4 (#536)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to
4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/releases">actions/checkout's
releases</a>.</em></p>
<blockquote>
<h2>v4.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update default runtime to node20 by <a
href="https://github.com/takost"><code>@​takost</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1436">actions/checkout#1436</a></li>
<li>Support fetching without the --progress option by <a
href="https://github.com/simonbaird"><code>@​simonbaird</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1067">actions/checkout#1067</a></li>
<li>Release 4.0.0 by <a
href="https://github.com/takost"><code>@​takost</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1447">actions/checkout#1447</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/takost"><code>@​takost</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1436">actions/checkout#1436</a></li>
<li><a
href="https://github.com/simonbaird"><code>@​simonbaird</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1067">actions/checkout#1067</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v3...v4.0.0">https://github.com/actions/checkout/compare/v3...v4.0.0</a></p>
<h2>v3.6.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Mark test scripts with Bash'isms to be run via Bash by <a
href="https://github.com/dscho"><code>@​dscho</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1377">actions/checkout#1377</a></li>
<li>Add option to fetch tags even if fetch-depth &gt; 0 by <a
href="https://github.com/RobertWieczoreck"><code>@​RobertWieczoreck</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/579">actions/checkout#579</a></li>
<li>Release 3.6.0 by <a
href="https://github.com/luketomlinson"><code>@​luketomlinson</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1437">actions/checkout#1437</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/RobertWieczoreck"><code>@​RobertWieczoreck</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/579">actions/checkout#579</a></li>
<li><a
href="https://github.com/luketomlinson"><code>@​luketomlinson</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1437">actions/checkout#1437</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v3.5.3...v3.6.0">https://github.com/actions/checkout/compare/v3.5.3...v3.6.0</a></p>
<h2>v3.5.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix: Checkout Issue in self hosted runner due to faulty submodule
check-ins by <a
href="https://github.com/megamanics"><code>@​megamanics</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1196">actions/checkout#1196</a></li>
<li>Fix typos found by codespell by <a
href="https://github.com/DimitriPapadopoulos"><code>@​DimitriPapadopoulos</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1287">actions/checkout#1287</a></li>
<li>Add support for sparse checkouts by <a
href="https://github.com/dscho"><code>@​dscho</code></a> and <a
href="https://github.com/dfdez"><code>@​dfdez</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1369">actions/checkout#1369</a></li>
<li>Release v3.5.3 by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1376">actions/checkout#1376</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/megamanics"><code>@​megamanics</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1196">actions/checkout#1196</a></li>
<li><a
href="https://github.com/DimitriPapadopoulos"><code>@​DimitriPapadopoulos</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1287">actions/checkout#1287</a></li>
<li><a href="https://github.com/dfdez"><code>@​dfdez</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1369">actions/checkout#1369</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v3...v3.5.3">https://github.com/actions/checkout/compare/v3...v3.5.3</a></p>
<h2>v3.5.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix: Use correct API url / endpoint in GHES by <a
href="https://github.com/fhammerl"><code>@​fhammerl</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1289">actions/checkout#1289</a>
based on <a
href="https://redirect.github.com/actions/checkout/issues/1286">#1286</a>
by <a href="https://github.com/1newsr"><code>@​1newsr</code></a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v3.5.1...v3.5.2">https://github.com/actions/checkout/compare/v3.5.1...v3.5.2</a></p>
<h2>v3.5.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Improve checkout performance on Windows runners by upgrading
<code>@​actions/github</code> dependency by <a
href="https://github.com/BrettDong"><code>@​BrettDong</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1246">actions/checkout#1246</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/BrettDong"><code>@​BrettDong</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1246">actions/checkout#1246</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>v4.2.2</h2>
<ul>
<li><code>url-helper.ts</code> now leverages well-known environment
variables by <a href="https://github.com/jww3"><code>@​jww3</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li>
<li>Expand unit test coverage for <code>isGhes</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li>
</ul>
<h2>v4.2.1</h2>
<ul>
<li>Check out other refs/* by commit if provided, fall back to ref by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li>
</ul>
<h2>v4.2.0</h2>
<ul>
<li>Add Ref and Commit outputs by <a
href="https://github.com/lucacome"><code>@​lucacome</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1180">actions/checkout#1180</a></li>
<li>Dependency updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>- <a
href="https://redirect.github.com/actions/checkout/pull/1777">actions/checkout#1777</a>,
<a
href="https://redirect.github.com/actions/checkout/pull/1872">actions/checkout#1872</a></li>
</ul>
<h2>v4.1.7</h2>
<ul>
<li>Bump the minor-npm-dependencies group across 1 directory with 4
updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1739">actions/checkout#1739</a></li>
<li>Bump actions/checkout from 3 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1697">actions/checkout#1697</a></li>
<li>Check out other refs/* by commit by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1774">actions/checkout#1774</a></li>
<li>Pin actions/checkout's own workflows to a known, good, stable
version. by <a href="https://github.com/jww3"><code>@​jww3</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1776">actions/checkout#1776</a></li>
</ul>
<h2>v4.1.6</h2>
<ul>
<li>Check platform to set archive extension appropriately by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1732">actions/checkout#1732</a></li>
</ul>
<h2>v4.1.5</h2>
<ul>
<li>Update NPM dependencies by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1703">actions/checkout#1703</a></li>
<li>Bump github/codeql-action from 2 to 3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1694">actions/checkout#1694</a></li>
<li>Bump actions/setup-node from 1 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1696">actions/checkout#1696</a></li>
<li>Bump actions/upload-artifact from 2 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1695">actions/checkout#1695</a></li>
<li>README: Suggest <code>user.email</code> to be
<code>41898282+github-actions[bot]@users.noreply.github.com</code> by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1707">actions/checkout#1707</a></li>
</ul>
<h2>v4.1.4</h2>
<ul>
<li>Disable <code>extensions.worktreeConfig</code> when disabling
<code>sparse-checkout</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1692">actions/checkout#1692</a></li>
<li>Add dependabot config by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1688">actions/checkout#1688</a></li>
<li>Bump the minor-actions-dependencies group with 2 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1693">actions/checkout#1693</a></li>
<li>Bump word-wrap from 1.2.3 to 1.2.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1643">actions/checkout#1643</a></li>
</ul>
<h2>v4.1.3</h2>
<ul>
<li>Check git version before attempting to disable
<code>sparse-checkout</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1656">actions/checkout#1656</a></li>
<li>Add SSH user parameter by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1685">actions/checkout#1685</a></li>
<li>Update <code>actions/checkout</code> version in
<code>update-main-version.yml</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1650">actions/checkout#1650</a></li>
</ul>
<h2>v4.1.2</h2>
<ul>
<li>Fix: Disable sparse checkout whenever <code>sparse-checkout</code>
option is not present <a
href="https://github.com/dscho"><code>@​dscho</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1598">actions/checkout#1598</a></li>
</ul>
<h2>v4.1.1</h2>
<ul>
<li>Correct link to GitHub Docs by <a
href="https://github.com/peterbe"><code>@​peterbe</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1511">actions/checkout#1511</a></li>
<li>Link to release page from what's new section by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1514">actions/checkout#1514</a></li>
</ul>
<h2>v4.1.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1396">Add
support for partial checkout filters</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/checkout/commit/11bd71901bbe5b1630ceea73d27597364c9af683"><code>11bd719</code></a>
Prepare 4.2.2 Release (<a
href="https://redirect.github.com/actions/checkout/issues/1953">#1953</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/e3d2460bbb42d7710191569f88069044cfb9d8cf"><code>e3d2460</code></a>
Expand unit test coverage (<a
href="https://redirect.github.com/actions/checkout/issues/1946">#1946</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/163217dfcd28294438ea1c1c149cfaf66eec283e"><code>163217d</code></a>
<code>url-helper.ts</code> now leverages well-known environment
variables. (<a
href="https://redirect.github.com/actions/checkout/issues/1941">#1941</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871"><code>eef6144</code></a>
Prepare 4.2.1 release (<a
href="https://redirect.github.com/actions/checkout/issues/1925">#1925</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/6b42224f41ee5dfe5395e27c8b2746f1f9955030"><code>6b42224</code></a>
Add workflow file for publishing releases to immutable action package
(<a
href="https://redirect.github.com/actions/checkout/issues/1919">#1919</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/de5a000abf73b6f4965bd1bcdf8f8d94a56ea815"><code>de5a000</code></a>
Check out other refs/* by commit if provided, fall back to ref (<a
href="https://redirect.github.com/actions/checkout/issues/1924">#1924</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/d632683dd7b4114ad314bca15554477dd762a938"><code>d632683</code></a>
Prepare 4.2.0 release (<a
href="https://redirect.github.com/actions/checkout/issues/1878">#1878</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/6d193bf28034eafb982f37bd894289fe649468fc"><code>6d193bf</code></a>
Bump braces from 3.0.2 to 3.0.3 (<a
href="https://redirect.github.com/actions/checkout/issues/1777">#1777</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/db0cee9a514becbbd4a101a5fbbbf47865ee316c"><code>db0cee9</code></a>
Bump the minor-npm-dependencies group across 1 directory with 4 updates
(<a
href="https://redirect.github.com/actions/checkout/issues/1872">#1872</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/b6849436894e144dbce29d7d7fda2ae3bf9d8365"><code>b684943</code></a>
Add Ref and Commit outputs (<a
href="https://redirect.github.com/actions/checkout/issues/1180">#1180</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/checkout/compare/v3...v4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=3&new-version=4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-24 07:38:03 -04:00
dependabot[bot] fba2f135a3 Update sphinx requirement from <6,>=5 to >=5,<9 (#542)
Updates the requirements on
[sphinx](https://github.com/sphinx-doc/sphinx) to permit the latest
version.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/sphinx-doc/sphinx/releases">sphinx's
releases</a>.</em></p>
<blockquote>
<h2>Sphinx 8.1.3</h2>
<p>Changelog: <a
href="https://www.sphinx-doc.org/en/master/changes/8.1.html">https://www.sphinx-doc.org/en/master/changes/8.1.html</a></p>
<h2>Bugs fixed</h2>
<ul>
<li><a
href="https://redirect.github.com/sphinx-doc/sphinx/issues/13013">#13013</a>:
Restore support for <code>cut_lines()</code> with no object type. Patch
by Adam Turner.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/sphinx-doc/sphinx/blob/v8.1.3/CHANGES.rst">sphinx's
changelog</a>.</em></p>
<blockquote>
<h1>Release 8.1.3 (released Oct 13, 2024)</h1>
<h2>Bugs fixed</h2>
<ul>
<li><a
href="https://redirect.github.com/sphinx-doc/sphinx/issues/13013">#13013</a>:
Restore support for :func:<code>!cut_lines</code> with no object type.
Patch by Adam Turner.</li>
</ul>
<h1>Release 8.1.2 (released Oct 12, 2024)</h1>
<h2>Bugs fixed</h2>
<ul>
<li><a
href="https://redirect.github.com/sphinx-doc/sphinx/issues/13012">#13012</a>:
Expose :exc:<code>sphinx.errors.ExtensionError</code> in
<code>sphinx.util</code>
for backwards compatibility.
This will be removed in Sphinx 9, as exposing the exception
in <code>sphinx.util</code> was never intentional.
:exc:<code>!ExtensionError</code> has been part of
<code>sphinx.errors</code> since Sphinx 0.9.
Patch by Adam Turner.</li>
</ul>
<h1>Release 8.1.1 (released Oct 11, 2024)</h1>
<h2>Bugs fixed</h2>
<ul>
<li><a
href="https://redirect.github.com/sphinx-doc/sphinx/issues/13006">#13006</a>:
Use the preferred <a
href="https://www.cve.org/">https://www.cve.org/</a> URL for
the :rst:role:<code>:cve: &lt;cve&gt;</code> role.
Patch by Hugo van Kemenade.</li>
<li><a
href="https://redirect.github.com/sphinx-doc/sphinx/issues/13007">#13007</a>:
LaTeX: Improve resiliency when the required
<code>fontawesome</code> or <code>fontawesome5</code> packages are not
installed.
Patch by Jean-François B.</li>
</ul>
<h1>Release 8.1.0 (released Oct 10, 2024)</h1>
<h2>Dependencies</h2>
<ul>
<li><a
href="https://redirect.github.com/sphinx-doc/sphinx/issues/12756">#12756</a>:
Add lower-bounds to the <code>sphinxcontrib-*</code> dependencies.
Patch by Adam Turner.</li>
<li><a
href="https://redirect.github.com/sphinx-doc/sphinx/issues/12833">#12833</a>:
Update the LaTeX <code>parskip</code> package from 2001 to 2018.
Patch by Jean-François B.</li>
</ul>
<h2>Incompatible changes</h2>
<ul>
<li><a
href="https://redirect.github.com/sphinx-doc/sphinx/issues/12763">#12763</a>:
Remove unused internal class <code>sphinx.util.Tee</code>.</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/sphinx-doc/sphinx/commit/a1510de4777eaa2e569435f95b05f6f3293d7035"><code>a1510de</code></a>
Bump to 8.1.3 final</li>
<li><a
href="https://github.com/sphinx-doc/sphinx/commit/62e9606d63c8bbb4964213fd6b427d1483847662"><code>62e9606</code></a>
Restore support for <code>cut_lines()</code> with no object type (<a
href="https://redirect.github.com/sphinx-doc/sphinx/issues/13015">#13015</a>)</li>
<li><a
href="https://github.com/sphinx-doc/sphinx/commit/5ae32ce9bfe4a17a7f00e1e8d39a80449423c726"><code>5ae32ce</code></a>
Bump version</li>
<li><a
href="https://github.com/sphinx-doc/sphinx/commit/a72b47bb408923cb7809eb9f96885545184e3773"><code>a72b47b</code></a>
Bump to 8.1.2 final</li>
<li><a
href="https://github.com/sphinx-doc/sphinx/commit/39a45ad4073a4d8c3b7dfd64d22e8a88870dcc7c"><code>39a45ad</code></a>
Expose <code>ExtensionError</code> in <code>sphinx.util</code> for
backwards compatibility.</li>
<li><a
href="https://github.com/sphinx-doc/sphinx/commit/5a4859a2e489c66b38804e95bf77fd0baf4320dc"><code>5a4859a</code></a>
Add docs about sphinx-autobuild (<a
href="https://redirect.github.com/sphinx-doc/sphinx/issues/13011">#13011</a>)</li>
<li><a
href="https://github.com/sphinx-doc/sphinx/commit/05679efe7b34f8b2fb87605438c40248ac8cae83"><code>05679ef</code></a>
Type-check the 'autodoc_intenum' example (<a
href="https://redirect.github.com/sphinx-doc/sphinx/issues/12827">#12827</a>)</li>
<li><a
href="https://github.com/sphinx-doc/sphinx/commit/86d1d31fb370f031739079de7d827be0074e7661"><code>86d1d31</code></a>
Prune CHANGES of unneeded sections</li>
<li><a
href="https://github.com/sphinx-doc/sphinx/commit/b6269d3790bb3bdd652ce67fecb59e6afddc8014"><code>b6269d3</code></a>
Improve documentation for the Builder API (<a
href="https://redirect.github.com/sphinx-doc/sphinx/issues/13008">#13008</a>)</li>
<li><a
href="https://github.com/sphinx-doc/sphinx/commit/c46abc47210088a6c4fee9dac23badfcebc441d7"><code>c46abc4</code></a>
Improve clarity for <code>master_doc</code> and
<code>root_doc</code></li>
<li>Additional commits viewable in <a
href="https://github.com/sphinx-doc/sphinx/compare/v5.0.0...v8.1.3">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-24 07:37:38 -04:00
Andreas Motl 4006de72cd Dependencies: Add Dependabot configuration (#534)
What the title says.
2024-10-24 07:30:41 -04:00
Andreas Motl b3c7252197 Chore: Format code using Ruff, and fix linter errors (#531)
## About
- Add Ruff configuration to `pyproject.toml`, apply its formatter, and
satisfy its linter.
- Migrate pytest configuration to `pyproject.toml`.
2024-10-24 07:30:18 -04:00
Andreas Motl 398ac3343e This and that: 20241024-02 (#530)
## About
After GH-529, another round of improvements submitted as a bundle.

## References
- GH-529
2024-10-23 21:04:03 -04:00
Andreas Motl 8b197ba361 CI: Improve test matrix configuration. Add macOS, both Intel and ARM. 2024-10-24 02:27:32 +02:00
Andreas Motl e700aa2937 Chore: Update LICENSE file
GitHub wasn't able to discover the license (badge) from the LICENSE
file. Let's use a vanilla variant for Apache 2.0.
2024-10-24 02:27:32 +02:00
Andreas Motl 3894550642 Tests: Enable pytest options, increasing verbosity
That is to display test case names, not just dots that don't convey
much.
2024-10-24 02:27:32 +02:00
Andreas Motl 43fd041138 CI: Add PyPy to Python test matrix 2024-10-24 02:27:32 +02:00
Andreas Motl 363af5338d CI: Properly verify package on Python 3.10, 3.11, and 3.12 2024-10-24 02:27:32 +02:00
Andreas Motl 55430a4366 Dependencies: Separate runtime vs. test vs. development definitions 2024-10-24 02:27:32 +02:00
kennethreitz f7c6a3ae97 Docs: Update to jinja2<3.2, Dependabot admonitions versions <3.1.4 (#528)
## About
What the title says.

## Details
It's only about building static docs, so there is no danger for this
project. It's just a chore fix to properly dismiss the security warning
signalled by Dependabot.

## References
- https://github.com/kennethreitz/responder/security/dependabot/61
2024-10-23 19:38:02 -04:00
Andreas Motl dcadba1425 Docs: Update to jinja2<3.2, Dependabot admonitions versions <3.1.4 2024-10-24 01:36:07 +02:00
kennethreitz de08b15ae8 Docs: Minimally modernize Sphinx configuration. Fix building on Python 3.11. (#526)
## About
Just a little maintenance patch for the Sphinx docs, to minimally
modernize dependencies, and to fix the build [^1].

[^1]: [...] and to probe if any commit styles of mine (commit messages,
wording, whatever) need to be adjusted to comply with any policies
employed here.
2024-10-23 19:32:44 -04:00
kennethreitz 0cfca6d906 Merge branch 'main' into docs-dependencies 2024-10-23 19:30:17 -04:00
kennethreitz a73e413a66 CI: Slightly update GHA configuration, now targeting branch main (#527)
## Problem
CI did not start on GH-526.

## Details
Also, add a configuration snippet to cancel redundant in-progress jobs,
in order to save resources. That means running jobs are terminated when
subsequently pushing to the same branch / updating the same PR,
DWIM-like.
2024-10-23 19:30:01 -04:00
Andreas Motl 87931a25d0 CI: Slightly update GHA configuration, now targeting branch main
Also, add a configuration snippet to cancel redundant in-progress jobs.
That means running jobs are terminated when subsequently pushing to the
same branch, in order to save resources.
2024-10-24 01:24:57 +02:00
Andreas Motl 1fd9a682dd Docs: Fix broken links 2024-10-24 01:17:54 +02:00
Andreas Motl 5d3e650901 Docs: Update dependencies, fixing the build on Python 3.11 2024-10-24 01:13:22 +02:00
Andreas Motl 48d082e6a5 Docs: Use relaxed upper-bound dependency pinning for Sphinx dependencies 2024-10-24 01:04:38 +02:00
Andreas Motl 87e22481e8 Docs: Clean up docs/requirements.txt. It just needs Sphinx and friends. 2024-10-24 01:04:08 +02:00
Andreas Motl e48ce6c301 Chore: Update .gitignore to ignore all virtualenvs 2024-10-24 01:02:25 +02:00
kennethreitz e9613500da Delete Pipfile (#516) 2024-03-31 10:56:22 -04:00
kennethreitz c2943accd0 Delete Pipfile 2024-03-31 10:54:49 -04:00
kennethreitz 649a255657 remove files 2024-03-30 20:43:28 -04:00
kennethreitz 7eaaaaafe1 Add Flask to requirements.txt 2024-03-30 20:37:52 -04:00
kennethreitz ae09b88978 Add typesystem==0.2.5 to requirements.txt 2024-03-30 20:37:05 -04:00
kennethreitz e3e307fd68 Update uv pip install command to use --system flag 2024-03-30 20:36:10 -04:00
kennethreitz 89f0724029 Update test.yaml workflow 2024-03-30 20:31:20 -04:00
kennethreitz bebe62adaf Add apistar to requirements.txt 2024-03-30 20:28:08 -04:00
kennethreitz eb9cddc8c2 Update dependency installation command 2024-03-30 20:27:30 -04:00
kennethreitz 7c19eca78a Remove unused code and dependencies 2024-03-30 20:26:43 -04:00
kennethreitz ed28b11d21 remove schema_doc.py 2024-03-30 20:21:38 -04:00
kennethreitz 46cdd4a245 Update GraphQL dependencies 2024-03-30 20:18:42 -04:00
kennethreitz ac91b172e6 Add graphql_server to requirements.txt 2024-03-30 20:15:50 -04:00
kennethreitz ed0da6d462 test 2024-03-30 20:14:26 -04:00
kennethreitz 555e9bff65 Add helloworld.py and update serve method in api.py 2024-03-30 20:11:42 -04:00
kennethreitz bf43d9f202 Add graphene to required packages 2024-03-30 20:08:25 -04:00
kennethreitz e239cc304d Update Python versions in test.yaml 2024-03-30 20:06:05 -04:00
kennethreitz 3285bd57c7 Update python_requires to >=3.11 2024-03-30 20:05:53 -04:00
kennethreitz 3090fb9e68 Update branch name in GitHub Actions workflow 2024-03-30 20:03:52 -04:00
kennethreitz e90bd24ebe Update test.yaml, add Pipfile, and delete httpbin.py 2024-03-30 20:02:39 -04:00
kennethreitz a0acc03a97 delete lint 2024-03-30 19:56:14 -04:00
kennethreitz 8a668e6efe Update GitHub Actions workflow for Python testing 2024-03-30 19:55:56 -04:00
kennethreitz 4c75742e4d Update Python versions and operating systems in test.yaml 2024-03-30 19:54:18 -04:00
kennethreitz 796fdc2ddf Fix commented out code in test_responder.py 2024-03-30 19:48:57 -04:00
kennethreitz a8caa3054b Update API requests from GET to POST 2024-03-30 19:44:45 -04:00
kennethreitz 2ef9e133ad Remove unused dependencies and update setup.py 2024-03-30 19:37:46 -04:00
kennethreitz 2ec570ad61 Refactor code by removing unused imports and properties 2024-03-30 19:35:12 -04:00
taoufik07 02aa338970 v2.0.7 2021-01-08 09:27:53 +01:00
Taoufik 882250bd86 Merge pull request #450 from taoufik07/uvicron_extra_standard
Add uvicorn[extra]
2021-01-08 09:26:31 +01:00
taoufik07 3809eda2f2 Use uvicorn[standard] 2021-01-07 21:39:17 +01:00
Taoufik b32eda70d2 Merge pull request #448 from taoufik07/taoufik07-patch-1
Add test for 3.9
2021-01-06 09:05:23 +01:00
taoufik07 f1b2f46a10 v2.0.6 2021-01-06 09:02:42 +01:00
Taoufik cf82dac4ad Add test for 3.9 2021-01-06 08:58:58 +01:00
Taoufik a0913e3f63 Merge pull request #447 from timgates42/bugfix_typo_marshmallow
docs: fix simple typo, mashmallow -> marshmallow
2020-12-24 08:44:43 +01:00
Tim Gates f90955a9b9 docs: fix simple typo, mashmallow -> marshmallow
There is a small typo in responder/ext/schema/__init__.py.

Should read `marshmallow` rather than `mashmallow`.
2020-12-24 13:27:01 +11:00
Taoufik 3736c9229d Merge pull request #429 from taoufik07/dependabot/pip/docs/bleach-3.1.4
Bump bleach from 3.1.1 to 3.1.4 in /docs
2020-12-02 09:44:55 +01:00
dependabot[bot] a802853367 Bump bleach from 3.1.1 to 3.1.4 in /docs
Bumps [bleach](https://github.com/mozilla/bleach) from 3.1.1 to 3.1.4.
- [Release notes](https://github.com/mozilla/bleach/releases)
- [Changelog](https://github.com/mozilla/bleach/blob/master/CHANGES)
- [Commits](https://github.com/mozilla/bleach/compare/v3.1.1...v3.1.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-01 23:11:49 +00:00
Taoufik 96ca88fe88 Merge pull request #446 from taoufik07/github_actions
Switch to github actions
2020-12-02 00:11:05 +01:00
taoufik07 a57570210a Add prettier to pre-commit 2020-12-01 23:36:42 +01:00
taoufik07 7682e94b35 Disable tests in CI for windows 2020-12-01 23:10:24 +01:00
taoufik07 8bbebe113c Bump black 2020-12-01 23:10:24 +01:00
taoufik07 7c921f827b Switch to github actions 2020-12-01 23:10:24 +01:00
taoufik07 4cc055f93a Add pre-commit 2020-12-01 23:10:21 +01:00
Taoufik e596a8b457 Merge pull request #444 from majiang/patch-1
Call user-provided `default_response`
2020-12-01 21:42:13 +01:00
Taoufik fd2da55880 Merge pull request #445 from majiang/patch-2
test_redirects: access '/2' and redirect to '/1'
2020-12-01 21:37:28 +01:00
majiang 975e9b5643 test_redirects: access '/2' and redirect to '/1' 2020-12-01 16:18:56 +09:00
majiang c0036e0474 Call user-provided default_response
I'm not 100% sure, but it seems that user-provided `default_response`, stored as `Router.default_endpoint`, should be called when no match was found.
2020-12-01 16:15:57 +09:00
Taoufik 103816e27a Merge pull request #439 from ryuuji/master
bump uvicorn 0.11.* to 0.11.7
2020-08-12 11:01:13 +02:00
Ryuuji Yoshimoto b7c1684ab4 bump 0.11.7 2020-08-11 18:57:35 +09:00
Ryuuji Yoshimoto 16bd6ca266 bump uvicorn 2020-08-11 18:53:59 +09:00
Taoufik 20bae4712b Merge pull request #430 from ucpr/fix-lock
Fix hash in pygment from piwheel to pypi.
2020-04-17 05:40:29 +02:00
ucpr a7aa80c690 Fix hash in pygment from piwheel to pypi. 2020-04-15 00:30:34 +09:00
Taoufik df89d1d58b Merge pull request #424 from taoufik07/starlette_0_13
Fixes and bump dependencies
2020-03-09 05:32:30 +01:00
taoufik07 477cddd29c travisci add python 3.8 2020-03-09 05:26:48 +01:00
taoufik07 9b8cf3a1b1 Bump uvicorn version to 0.11 2020-03-09 05:16:34 +01:00
taoufik07 2871a3c07f starlette 0.13 and fix lifespan 2020-03-09 05:13:12 +01:00
Taoufik 13763296dd Merge pull request #421 from taoufik07/dependabot/pip/docs/bleach-3.1.1
Bump bleach from 3.0.2 to 3.1.1 in /docs
2020-02-25 02:24:11 +01:00
dependabot[bot] 783b22ab1c Bump bleach from 3.0.2 to 3.1.1 in /docs
Bumps [bleach](https://github.com/mozilla/bleach) from 3.0.2 to 3.1.1.
- [Release notes](https://github.com/mozilla/bleach/releases)
- [Changelog](https://github.com/mozilla/bleach/blob/master/CHANGES)
- [Commits](https://github.com/mozilla/bleach/compare/v3.0.2...v3.1.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-02-24 18:31:05 +00:00
Taoufik 109937adf4 Merge pull request #414 from taoufik07/v2.0.5
v2.0.5
2019-12-15 16:38:02 +01:00
taoufik 63ea9cc4e0 v2.0.5 2019-12-15 16:32:47 +01:00
Taoufik ec40a0c4c3 Merge pull request #413 from taoufik07/support_python3.8
Update requirements to support python3.8
2019-12-15 16:28:17 +01:00
taoufik 0855d1a378 Update requirements to support python3.8 2019-12-15 16:22:26 +01:00
Taoufik 77fe17d350 Merge pull request #411 from StevenAvelino24/feature/use-openapi-params
Use OpenAPI info params on init of API
2019-12-04 20:07:46 +01:00
Steven Avelino 0b8a031ccb Use openapi info params on init of API 2019-12-04 14:50:30 +01:00
taoufik 0678daa880 Bump to v2.0.4 2019-11-19 17:35:16 +01:00
Taoufik 6761e3bdd8 Merge pull request #406 from daphil19/master
fix issues related to using `static=true` in `api.add_route()` and `static_route` in `responder.API()`
2019-11-19 17:30:22 +01:00
David Phillips ead213a506 responder now checks routes added via add_route before mounted routes
routes added by `add_route` (including @api.route()) are now checked
before routes that were added by api.mount()

this especially helps cases for situations where the static route
defined by the API class is '/' but the default route is '/' as well
2019-11-02 13:57:39 -04:00
David Phillips 75b5782eee fix static_response endpoint to return index.html contents 2019-11-02 13:55:34 -04:00
Taoufik a80df809e4 Merge pull request #399 from vbarbaresi/fix_render_async
Async templates require enable_async in jinja2 Environment
2019-10-25 22:19:04 +01:00
taoufik 7f3177f662 v2.0.3 2019-10-22 10:41:34 +01:00
taoufik 906cd2fbbf Fix templates conflicts 2019-10-20 12:48:02 +01:00
taoufik 9d0129da56 Fix templates conflicts 2019-10-20 12:39:32 +01:00
taoufik aedcf12d99 v2.0.1 2019-10-20 12:20:54 +01:00
Vincent Barbaresi 86361523e2 fix async templates rendering requiring enable_async in jinja2
When trying to test the render_async() feature I realized that it wasn't working
The template instance needs to be created with enable_async=True
2019-10-19 14:49:50 +02:00
Taoufik a7110ef441 Merge pull request #395 from vbarbaresi/master
add a few tests for API and remove some dead code
2019-10-19 13:06:01 +01:00
Vincent Barbaresi d3e4968546 add a few tests for API and remove some dead code
Increasing the coverage of api.py to 86%

- use pytest.parametrize to test cors and hsts parameters usage
- use pytest.parametrize to factorize test_staticfiles_custom_route
- add tests for path_matches_route and Route() with no endpoint
- test Jinja template rendering from a file

- remove   _notfound_wsgi_app, before_ws_requests, before_http_requests

they didn't seem used anywhere and the before_* method were broken, referring to
self.before_requests that doesn't exist anymore
2019-10-19 13:47:39 +02:00
Taoufik 03e34d56ab Merge pull request #398 from taoufik07/v2
v2.0.0
2019-10-19 12:22:32 +01:00
taoufik b470d10416 v2.0.0 2019-10-19 12:21:06 +01:00
Taoufik b8aea89039 Merge pull request #397 from taoufik07/update_pipfile
Bump versions
2019-10-19 12:13:06 +01:00
taoufik 4a1e89af1b Bump versions 2019-10-19 12:07:31 +01:00
Taoufik 81fbc94d36 Merge pull request #396 from vbarbaresi/fix_3.8_tests
upgrade pluggy to 0.13.0 to support importlib.metadata on python 3.8
2019-10-03 17:24:53 +01:00
Vincent Barbaresi 6487671559 upgrade pluggy to 0.13.0 to support stb lib importlib.metadata on python 3.8 2019-10-03 18:15:09 +02:00
Taoufik 838c7f29b5 Update docs url 2019-09-26 12:07:59 +01:00
Taoufik df85a4c214 Merge pull request #388 from taoufik07/new-router
New Router and refactor
2019-09-03 20:13:46 +02:00
taoufik 1bdbea238e Add a api.static_app and extend starlette's StaticFiles app 2019-08-31 01:38:00 +02:00
Taoufik 97dbef92d9 Bump werkzug version to 0.15.5 in docs 2019-08-21 23:05:27 +02:00
taoufik 85c1c0036c [WIP] Update docs 2019-08-21 22:50:58 +02:00
taoufik 0653ee2c6b Add Templates 2019-08-21 13:46:31 +02:00
taoufik d1db913c7d Bump dependencies 2019-08-17 22:15:43 +02:00
taoufik d24b921cdc Move open api schema to ext/schema 2019-08-17 18:25:19 +02:00
taoufik b31b742787 Implement a new Router and other changes
- Router
- Use starlette's Session middlewre
- Add Exception Middleware
- ...
2019-08-17 14:41:11 +02:00
Taoufik d820f0277f Merge pull request #387 from taoufik07/v-1.3.2
Bump version and update CHANGELOG
2019-08-15 22:57:21 +02:00
taoufik 7219856177 Bump version and update CHANGELOG 2019-08-15 22:51:28 +02:00
Taoufik e6d302aabb Merge pull request #385 from FirstKlaas/state-for-request
Added a state property to the request object.
2019-08-07 19:28:13 +02:00
FirstKlaas d73243ab60 Linting with black 2019-08-07 18:44:43 +02:00
FirstKlaas 8101e7d7b0 Added a state property to the request opbject. 2019-08-07 18:29:03 +02:00
Taoufik 784c7e72ae Merge pull request #381 from taoufik07/ci-py-3.8-dev
Travis-ci add python 3.8-dev
2019-08-05 20:08:44 +02:00
taoufik 93156fd2f1 Travis-ci add python 3.8-dev 2019-08-05 20:00:14 +02:00
Taoufik e4f6898498 Merge pull request #380 from taoufik07/refactor-changelog
Refactor CHANGELOG following the keepachangelog format
2019-08-05 19:59:00 +02:00
taoufik ef330135f9 Refactor CHANGELOG following the keepachangelog format
- Format following https://keepachangelog.com/en/1.0.0/
- Refactor CHANGELOGS
- Add missing changes
2019-08-05 19:56:01 +02:00
Taoufik 54cbbdface Merge pull request #379 from taoufik07/asgi3
ASGI 3 support
2019-08-05 19:38:46 +02:00
taoufik f3c9320837 ASGI 3 support 2019-08-05 15:33:56 +02:00
Taoufik 0529629ac8 Merge pull request #378 from ucpr/add_formats_test
add test for #367
2019-07-29 23:00:50 +02:00
Taoufik 22af42ead6 Merge pull request #377 from ucpr/fix_readme
fix links
2019-07-29 22:59:40 +02:00
ucpr bd2efb68e1 test: add test for #367 2019-07-30 00:26:01 +09:00
ucpr 37ba3d2efc fix: fix links 2019-07-29 23:24:57 +09:00
kennethreitz ed8afeaa87 Merge pull request #367 from ucpr/fix_format_form
format data when mimetype is form-data
2019-07-16 11:05:57 -04:00
kennethreitz ee6efe5aa4 Merge pull request #372 from mlschneid/master
Encouraging use of stable
2019-07-16 11:05:07 -04:00
Mike Schneider 3a8113d8b0 Found more --pre flags 2019-07-16 07:43:55 -07:00
Mike Schneider 7afce42943 Encouraging use of stable 2019-07-16 07:34:35 -07:00
Frost Ming 67a6c25256 Merge pull request #370 from waghanza/remove_pipenv_requires
Specify constraint for python version in pipenv
2019-07-09 17:15:53 +08:00
Marwan Rabbâa 38dea8311c refactor(pip): Remove pipenv restriction on python version 2019-07-09 11:09:29 +02:00
Marwan Rabbâa 555e1f7924 refactor(pip): Specify constraint for python version in pipenv 2019-07-08 18:35:42 +02:00
Taoufik 8b87f63609 Merge pull request #369 from waghanza/py_37
Add python3.7 on CI
2019-07-08 13:56:46 +02:00
Marwan Rabbâa f01b1d493f feat(ci): Test under python 3.7 2019-07-08 09:26:34 +02:00
ucpr a802245bf0 format data when mimetype is form-data 2019-07-01 05:58:43 +09:00
kennethreitz 6dbbad158a Merge pull request #357 from amikrop/master
Remove extra `static_url` function in `API.docs`
2019-06-25 09:17:14 -07:00
kennethreitz 877fe144b4 Merge pull request #365 from tedder/ted/add_links_2
add some links to the main page
2019-06-25 09:16:47 -07:00
ted 70e6bc0466 add some links to the main page
Basically linked things I had to google/look up on my own. There are a million things that _could_ be linked, but using myself as a median Python coder, it seems about right. Removed italics because I can't figure out how to use them with a link.
2019-06-25 08:21:07 -07:00
Taoufik 63f2e833eb Merge pull request #362 from mtcronin99/patch-1
Update tour.rst to add quotes around hostnames
2019-06-04 17:29:35 +02:00
mtcronin99 fb71abe534 Update tour.rst to add quotes around hostnames
The API call in the Trusted Hosts section needs quotes around the hostnames in the example.
2019-06-04 11:21:27 -04:00
Taoufik 8ccb39560e Merge pull request #359 from EdwardBetts/patch-1
Correct a spelling mistake
2019-05-11 22:24:16 +02:00
Edward Betts e6b880be62 Correct a spelling mistake 2019-05-11 20:17:10 +01:00
kennethreitz d0016ac7c9 Update README.md 2019-05-06 07:42:31 -04:00
Aristotelis Mikropoulos 05035e0171 Remove extra static_url function in API.docs 2019-05-02 17:09:34 +03:00
Taoufik 78b5bef879 Merge pull request #354 from kennethreitz/starlette-new-lifespan
Starlette 0.11.* and cleanup
2019-04-29 23:14:27 +02:00
taoufik a6955b5db5 Fix lifespan and bump starlette to 0.11.* 2019-04-29 23:09:48 +02:00
taoufik a1a0a1b71e Remove duplicated code and rename _dispatch_request 2019-04-29 22:44:44 +02:00
Taoufik 0bdde6d5fe Merge pull request #353 from kennethreitz/taoufik07-patch-1
Typo schema docs
2019-04-28 16:02:29 +02:00
Taoufik cf5447d5bd Typo schema docs 2019-04-28 15:59:15 +02:00
taoufik b2dd2c205d Fix tests 2019-04-28 15:48:36 +02:00
kennethreitz e52c9277c8 fix lockfile 2019-04-28 09:43:29 -04:00
kennethreitz 712ec2410d changes 2019-04-28 09:39:12 -04:00
kennethreitz dea2ca41d2 Merge pull request #351 from kennethreitz/route-params-docs
Add docs for route params convertors
2019-04-28 09:19:42 -04:00
Taoufik ca0f32c02b Black check before tests 2019-04-27 20:44:02 +02:00
Taoufik f21b296fba Add docs for route params convertors 2019-04-27 20:10:31 +02:00
Taoufik 3224479b99 Merge pull request #350 from kennethreitz/cleanup
Cleanup
2019-04-27 13:52:48 +02:00
Taoufik f95950eedc Cleanup 2019-04-27 13:49:54 +02:00
kennethreitz 4467376d0a Merge pull request #333 from taoufik07/custom_specifiers
Route params convertors
2019-04-27 07:32:13 -04:00
kennethreitz ac65dc5361 Merge pull request #347 from s-pace/doc/add_search
[doc website] Add a nice search experience
2019-04-27 07:25:38 -04:00
s-pace 4957793c80 feat: add search to every documentation pages 2019-04-22 22:23:07 +02:00
s-pace ff7f4b502d feat: add search to the main introduction page 2019-04-22 22:22:46 +02:00
Timo Furrer 816cb7188b Merge pull request #339 from jonbeebe/master
Removed asgiref dependency
2019-03-29 18:34:14 +01:00
Jonathan Beebe 6456d435eb Revert Pipfile.lock (with asgiref removed) 2019-03-28 20:36:11 -07:00
Jonathan Beebe 63e338ed6f Removed asgiref dependency
Replaced WsgiToAsgi (asgiref.wsgi) usage with WSGIMiddleware (starlette.middleware.wsgi) to fix breaking changes with asgiref 3.0.0.
2019-03-28 20:12:22 -07:00
Timo Furrer 00211c8f03 Merge pull request #335 from kobayashi/master
add sample of uploading a file
2019-03-21 21:30:14 +01:00
kobayashi ebed9fe3aa fix typos 2019-03-17 19:05:14 -04:00
kobayashi 734b5e7303 add sample of uploading a file 2019-03-17 15:21:27 -04:00
Taoufik 1696d501e2 Merge pull request #334 from MerleLiuKun/fix_test_responder_variable
fix variable error at test responder
2019-03-14 08:19:53 +00:00
ikronskun e65d2f8c50 fix variable error at test responder 2019-03-14 14:25:45 +08:00
taoufik07 9ea705b2ea Specifiers test 2019-03-12 17:39:53 +01:00
taoufik07 5a5a811dca Add routes specifiers 2019-03-12 16:58:36 +01:00
kennethreitz df7b9419c2 Merge pull request #332 from jlewis91/patch-1
Update tour.rst to be openapi compliant
2019-03-10 10:55:38 -04:00
Jeremiah 37318f1106 Update tour.rst 2019-03-10 14:53:44 +01:00
kennethreitz 19e9f6ac5d Merge pull request #320 from taoufik07/static_serve
Fix #303
2019-03-05 08:33:39 -05:00
kennethreitz 658b51a449 Merge pull request #321 from taoufik07/revert_before_requests
Revert before_requests
2019-03-05 08:33:25 -05:00
Parth Shandilya 485303c0f2 Merge pull request #314 from vlcinsky/fix_uvloop_env_marker
Fix #313 incomplete environment marker for uvloop
2019-03-03 23:22:53 +05:30
taoufik07 885d902b7d Revert 2019-02-26 23:46:07 +01:00
taoufik07 a35f02fb64 Add tests 2019-02-26 22:23:43 +01:00
taoufik07 28d1f16ad5 Disable serving when static_dir is None and handle templates_dir 2019-02-26 22:10:13 +01:00
Taoufik a04d7c3a9a Merge pull request #319 from taoufik07/refactor_before_ws
Refactor before_requests and websockets
2019-02-26 19:27:26 +00:00
taoufik07 b876f8484c Update docs 2019-02-26 17:01:13 +01:00
taoufik07 854c6d3d65 Add @before_request 2019-02-26 16:44:12 +01:00
taoufik07 f9a850a8fe Add before_requets for ws and refactor 2019-02-26 15:50:55 +01:00
taoufik07 e808662fe7 Refactor websockets 2019-02-26 15:27:26 +01:00
Taoufik 7bbb02126e Merge pull request #316 from tkamenoko/patch-4
fix typo
2019-02-23 15:15:31 +00:00
Taoufik aa101059a7 Merge pull request #315 from taoufik07/websockets_tests
Websockets tests
2019-02-23 15:11:23 +00:00
T.Kameyama d1f7fe02e4 fix typo 2019-02-24 00:10:47 +09:00
taoufik07 3e26dc1373 Add websockets tests 2019-02-23 16:00:22 +01:00
Jan Vlčinský 0a9d819555 Merge branch 'master' into fix_uvloop_env_marker 2019-02-22 20:47:25 +01:00
Jan Vlcinsky b31dfeefb7 Fix #313 incomplete environment marker for uvloop 2019-02-22 20:44:32 +01:00
Taoufik fc640ec331 Merge pull request #312 from iancleary/bug/242_docs_consistency
fixed flow between OpenAPI and Interactive Docs sections
2019-02-22 13:33:37 +00:00
iancleary 3382723457 fixed flow between OpenAPI and Interactive Docs sections 2019-02-22 06:16:45 -07:00
Taoufik 1fc0722ad6 Merge pull request #310 from taoufik07/fix/req_text
DEFAULT_ENCODING if none is detected
2019-02-22 11:57:11 +00:00
taoufik07 b21e308357 Add tests 2019-02-22 12:44:32 +01:00
taoufik07 738105314b Return DEFAULT_ENCODING if none and remove redundant code 2019-02-22 12:34:22 +01:00
Timo Furrer f3cdc99b29 release: 1.3.0 2019-02-22 10:36:57 +00:00
Taoufik eb70376438 v1.3.0 changelog 2019-02-22 10:34:19 +00:00
Timo Furrer dae1a4fa35 Add template for 1.3.0 CHANGELOG 2019-02-22 10:16:17 +00:00
Taoufik 2ad351197e Merge pull request #304 from taoufik07/content_type
Add resp.html property and make resp.text a property
2019-02-22 10:12:46 +00:00
Taoufik 3d9235c4bc Merge pull request #293 from taoufik07/multiple_cookies_and_directives
Multiple cookies and directives
2019-02-22 10:12:18 +00:00
taoufik07 2cd5596def lint 2019-02-22 10:40:27 +01:00
Taoufik d4191030d9 Merge branch 'master' into multiple_cookies_and_directives 2019-02-22 10:33:53 +01:00
Taoufik 447630a051 Merge branch 'master' into content_type 2019-02-22 10:30:38 +01:00
Timo Furrer f7b53a4895 Merge pull request #308 from taoufik07/feature/stream
Support stream response
2019-02-22 10:15:42 +01:00
taoufik07 21896aa171 Support stream response 2019-02-22 03:58:09 +01:00
Taoufik e8a15697d2 Merge pull request #306 from iancleary/242_modify_swagger_strings
implemented rest of OpenAPI Info Object
2019-02-22 02:05:23 +00:00
icleary 0030993631 api OpenAPI params match /docs display order, updated tour docs and docs test 2019-02-21 18:35:19 -07:00
taoufik07 13ba2f72f5 Update docs 2019-02-22 02:00:12 +01:00
taoufik07 9f39917895 Update docs and README 2019-02-22 01:12:25 +01:00
taoufik07 1b0859fdbb Only encode text 2019-02-22 00:59:17 +01:00
taoufik07 acd1561b1b Add tests 2019-02-22 00:01:49 +01:00
icleary 9f2182949d snake case for terms_of_service, is not None for if statements 2019-02-21 08:35:53 -07:00
icleary 6e5b3a4bf9 ran black on changed file 2019-02-20 22:38:57 -07:00
icleary d2ec323888 edited docstring to remove ->type 2019-02-20 22:29:49 -07:00
icleary 8b9645cf2d implemented rest of OpenAPI Info Object 2019-02-20 22:04:58 -07:00
Taoufik 4ecfef0ddf Merge branch 'master' into content_type 2019-02-21 02:57:06 +01:00
taoufik07 84fb7bd622 resp.html 2019-02-21 02:55:20 +01:00
taoufik07 0b261252e1 Add resp.html property and make resp.text a property 2019-02-21 02:49:47 +01:00
Taoufik d60b5ee39e Merge pull request #297 from taoufik07/whitnoise_notfound 2019-02-21 00:19:02 +00:00
taoufik07 e2f887ec5f Add tests 2019-02-21 00:51:31 +01:00
taoufik07 97da6a6694 Format static_route 2019-02-21 00:46:27 +01:00
taoufik07 c0e9a6778d Set static_response status if not found 2019-02-20 23:20:37 +01:00
Taoufik 5c327a2e0b Merge pull request #302 from taoufik07/lock_update
Pin starlette to 0.10.* and update the lock file
2019-02-20 17:52:00 +00:00
taoufik07 5ed45634cb Pin starlette to 0.10.* and update the lock file 2019-02-20 18:45:53 +01:00
Taoufik a50a373e84 Merge pull request #301 from taoufik07/starlette-0.10.5 2019-02-19 21:59:27 +00:00
taoufik07 86705d0c2f Pin starlette to 0.10.5 2019-02-19 22:35:23 +01:00
taoufik07 b9581444f9 Return 404 when static file is not found 2019-02-19 14:47:31 +01:00
Taoufik 2a60b094b8 Merge branch 'master' into multiple_cookies_and_directives 2019-02-19 13:49:21 +01:00
taoufik07 1ec567cabf Cleanup and black 2019-02-19 13:47:59 +01:00
Taoufik 4fd898b239 Merge pull request #296 from taoufik07/travis_black_check 2019-02-19 12:43:01 +00:00
taoufik07 03d6b72a00 Add linting checks to travis 2019-02-19 13:34:02 +01:00
taoufik07 4d0382d580 Lint 2019-02-19 13:33:17 +01:00
taoufik07 a0dd7481ec Add tests 2019-02-17 19:39:36 +01:00
taoufik07 1c91480b0c Multiple cookies and directives 2019-02-17 19:35:34 +01:00
Taoufik 85e5ec0a9a Merge pull request #288 from taoufik07/starlette>=0.10.2
Update Pipfile.lock and starlette==0.10.*
2019-02-14 13:49:57 +00:00
Taoufik 4ac04b0abc Merge pull request #290 from josegonzalez/patch-1 2019-02-14 13:49:07 +00:00
Taoufik d7e64a6e39 Merge pull request #289 from taoufik07/patch-20 2019-02-14 12:39:40 +00:00
Taoufik 17d526632e Merge pull request #285 from taoufik07/patch-19
Await for background task
2019-02-14 12:34:21 +00:00
Jose Diaz-Gonzalez 43da481df7 fix: always respect the configured session_cookie
The `session_cookie` was refactored to be set via a hardcoded `DEFAULT_SESSION_COOKIE` static variable, and this change will allow future cookie changes to trickle to the Request object.
2019-02-13 10:54:07 -05:00
Taoufik 5f5402833b Typos 2019-02-13 15:09:48 +00:00
taoufik07 d59c4333f2 starlette 0.10.* 2019-02-13 16:03:20 +01:00
taoufik07 49114f36ce Update starlette>=0.10.2 2019-02-13 12:14:13 +01:00
taoufik07 b2039d99f3 Update Pipfile.lock 2019-02-13 12:13:16 +01:00
Taoufik 94fd86fee0 Await for background task 2019-02-11 08:45:48 +00:00
kennethreitz d70fdd3301 todo 2019-02-09 06:45:25 -06:00
kennethreitz 05b75efb43 version 2019-01-12 07:07:50 -05:00
kennethreitz be56e92d65 Merge pull request #277 from amikrop/master
Make `_route_for` shorter
2019-01-12 07:06:31 -05:00
kennethreitz 69eb843604 fixes 2019-01-12 06:57:28 -05:00
Aristotelis Mikropoulos 84a7f0e90b Make _route_for shorter 2019-01-11 03:46:35 +02:00
kennethreitz d1e105a29a Merge pull request #276 from tomchristie/resolve-startup-and-shutdown-events
Resolve startup/shutdown events
2019-01-09 17:49:49 -05:00
Tom Christie 9f0a568fa3 Resolve startup/shutdwown events 2019-01-09 12:42:15 +00:00
kennethreitz 05b46cbe34 Merge pull request #269 from erm/lifespan-handler
Return lifespan handler in dispatch method
2018-12-29 07:05:41 -05:00
kennethreitz c045586997 Merge pull request #267 from valtyr/graphql-context
Add Request and Response to GraphQL context
2018-12-29 07:05:26 -05:00
kennethreitz 8f0707f697 Merge pull request #262 from carlodri/carlodri-patch-1
include LICENSE il sdist
2018-12-29 07:05:12 -05:00
kennethreitz 36929b265c Merge pull request #263 from tkamenoko/patch-3
fix docstring of `API.url_for`
2018-12-29 07:05:05 -05:00
kennethreitz 734ba64965 Merge pull request #264 from sangheestyle/master
fix typo
2018-12-29 07:04:53 -05:00
kennethreitz 148e6742df Merge pull request #268 from taoufik07/patch-18
Websocket docs
2018-12-29 07:04:45 -05:00
Jordan bcb7e8f4f3 Update lock file 2018-12-28 18:10:27 +11:00
Jordan f678112099 Return lifespan handler in dispatch method 2018-12-28 17:46:27 +11:00
Taoufik 60b0c5f256 Update 2018-12-25 01:32:38 +00:00
Taoufik c8627939de Update 2018-12-25 01:18:44 +00:00
Taoufik 9144f0158a Websocket docs 2018-12-25 01:15:54 +00:00
Valtýr Örn Kjartansson d541aca80f Mention the GraphQL context object in docs 2018-12-23 13:28:50 +00:00
Valtýr Örn Kjartansson c73b2b8d34 Add request and response to GraphQL context
This allows the GraphQL resolvers access to things like session, headers etc.
Also enables creation of powerful GraphQL middleware.
2018-12-23 13:14:15 +00:00
Kim, Sanghee e2493b489d fix typo 2018-12-20 17:54:15 +02:00
T.Kameyama 8dee28ac7c fix docstring of API.url_for 2018-12-17 19:23:33 +09:00
Carlo cdd3885a0c include LICENSE il sdist 2018-12-12 22:35:41 +01:00
kennethreitz 1a28d528d0 Merge pull request #233 from taoufik07/websocket-x.x
WebSocket returns
2018-12-12 03:59:24 -05:00
kennethreitz 3ba12b8cee Merge pull request #230 from tkamenoko/file-form
formats: format_file returns filename, data, and content-type [Breaking]
2018-12-12 03:59:10 -05:00
kennethreitz 5a29ab6917 Merge pull request #257 from ibnesayeed/lifespan
Import lifespan as a middleware as per the change in starlette package
2018-12-12 03:58:53 -05:00
kennethreitz 694144a0c8 Merge pull request #244 from barrust/route-isclass-fix
fix for route.is_class_based
2018-12-12 03:58:41 -05:00
kennethreitz 8bed8e8741 Merge pull request #249 from abstiles/fix-broken-test-500
Fix broken exception handling in test_500
2018-12-12 03:58:06 -05:00
kennethreitz a81a348bce Merge pull request #245 from cdfuller/add-monospace-default
Add monospace fallback for preformatted text in docs
2018-12-12 03:57:55 -05:00
kennethreitz fd9e8c5cbc Merge pull request #240 from barrust/persist-debug
persist debug from API to run
2018-12-12 03:57:19 -05:00
kennethreitz 8030b1919d Merge pull request #253 from TomFaulkner/patch-1
Grammatical fix
2018-12-12 03:56:52 -05:00
Sawood Alam 72c789fdd7 Update Pipfile.lock to reflect version changes 2018-11-30 12:30:28 -05:00
Sawood Alam 1113a9aa0d Import lifespan as a middleware as per the change in starlette package 2018-11-30 11:52:43 -05:00
Tom Faulkner a5532614a2 Grammatical fix
Grammar: `into to` to `into`
2018-11-26 19:30:20 -06:00
Andrew Stiles 122023fb70 Restore the removal of ExceptionMiddleware
Back out the changes made to API and fix the test with a non-raising
instance of TestClient.
2018-11-21 13:05:51 -06:00
Andrew Stiles b8fa923ec9 Fix broken exception handling in test_500 2018-11-21 11:30:31 -06:00
Tyler Barrus ae06b3e01a default cont to False 2018-11-20 10:38:15 -05:00
Cody Fuller 5599ec2809 Add monospace fallback for preformatted text 2018-11-20 09:37:30 -06:00
Tyler Barrus e795cbddb6 fix for route.is_class_based 2018-11-20 10:26:29 -05:00
Tyler Barrus 0cb087c37b persist debug from API to run 2018-11-19 23:37:04 -05:00
taoufik07 983cbcc711 cleanup 2018-11-17 21:40:17 +01:00
taoufik07 6d154b0c78 WIP websocket 2018-11-17 20:49:26 +01:00
Tessei Kameyama f3f36e28c4 [formats] format-files detects file or simple-data
if `part` has `content-type`, `format_files` returns
`{"name" : filedata}` . `filedata` is `dict`, the keys are
`filename, content, content-type` .

Otherwise, `format_files` just returns `{"name" : b"data"}` .
2018-11-16 23:50:00 +09:00
Tessei Kameyama fdf4797726 [test] update file-upload [wip]
if form-data don't have content-type,
`req.media("files")` returns `{"name" : b"data"}` , else
`{"name" :
{"filename" : "foo.txt",
"content" : b"data",
"content-type" : "some/type"} }` .

not implemented yet.
2018-11-16 22:13:05 +09:00
Tessei Kameyama 67d8a3be98 [fix] typo 2018-11-16 20:23:09 +09:00
kennethreitz 4001a60f6c Merge pull request #229 from tomchristie/run-in-threadpool
Use Starlette's run_in_threadpool
2018-11-15 06:24:34 -05:00
kennethreitz d94db41271 Merge branch 'master' of https://github.com/kennethreitz/responder 2018-11-15 06:25:42 -05:00
kennethreitz 8abb78bb58 fix for #215 2018-11-15 06:22:04 -05:00
kennethreitz a80db99aa3 Merge pull request #228 from mmanhertz/patch-2
Fix issue in OpenAPI Schema Support example
2018-11-15 05:49:07 -05:00
Tom Christie 69a300f21a Use Starlette's run_in_threadpool 2018-11-15 10:38:05 +00:00
Matthias Manhertz 1b024b8092 Fix issue in OpenAPI Schema Support example
`PetSchema().dump()` returns a dict. Calling `.data` on it causes an AttributeError.
2018-11-15 11:19:11 +01:00
kennethreitz a622689597 Merge pull request #225 from peerster/patch-1
Update README
2018-11-14 06:18:13 -05:00
Pär 9943e66c49 Updates all of the python-responder.org links 2018-11-14 11:10:29 +01:00
Pär 7233c08281 Update README
Update link to python-responder.org to use https
2018-11-14 11:07:18 +01:00
kennethreitz 0845d92fda Merge pull request #223 from jkermes/master
Fix static response
2018-11-13 09:33:34 -05:00
Julien Kermes 1cc02e5a83 Fix static response 2018-11-13 15:18:30 +01:00
kennethreitz aa4cd7a144 Merge pull request #222 from MRSharff/debian-manifest-typofix
Fixed a typo in setup.py: mainfest -> manifest
2018-11-13 07:10:34 -05:00
Mathew Sharff b42ae0dfd7 Fixed a typo in setup.py: mainfest -> manifest 2018-11-12 21:33:12 -08:00
kennethreitz a6bd179726 version bump 2018-11-11 16:58:07 -05:00
kennethreitz aac7b5117b Merge pull request #218 from tomchristie/patch-1
Bring testominials in line with README
2018-11-10 17:48:29 -05:00
Tom Christie 8e8e99ed2e Bring testominials in line with README 2018-11-09 18:25:53 +00:00
kennethreitz 078ac23b20 Merge pull request #213 from mmanhertz/fix-212
fix for #212
2018-11-07 17:25:14 -05:00
kennethreitz 8e61df6b6a Merge pull request #207 from taoufik07/await_backgroundtask_raise
Await for background task and raise
2018-11-07 17:25:02 -05:00
mmanhertz cf7fb56653 fix for #212 2018-11-07 17:05:18 +01:00
taoufik07 da20d13c49 Await for background task and raise 2018-11-06 17:27:04 +01:00
kennethreitz 7a250aa8fc Merge branch 'master' of github.com:kennethreitz/responder 2018-11-06 05:46:25 -05:00
kennethreitz af28ecb82d fix for #200 2018-11-06 05:46:17 -05:00
kennethreitz 39a0e52a2a Merge pull request #201 from mmanhertz/patch-1
Fix OpenAPI version in examples
2018-11-06 05:12:51 -05:00
Matthias Manhertz a4f5a111c7 Fix OpenAPI version in examples
The example for "OpenAPI Schema Support" and "Interactive Documentation had `openapi="3.0"` which would cause the following error when swagger UI tried to render the schema:
```
Unable to render this definition
The provided definition does not specify a valid version field.

Please indicate a valid Swagger or OpenAPI version field. Supported version fields are swagger: "2.0" and those that match  openapi: 3.0.n (for example, openapi: 3.0.0).
```
2018-11-06 10:46:30 +01:00
kennethreitz c65e585493 Merge pull request #192 from vlcinsky/fix_doc_typo
Fixed missing `.data` on Marshmallow dump
2018-11-05 17:40:06 -05:00
kennethreitz c9e94561aa Merge pull request #196 from xyb/patch-1
fix Dockerfile
2018-11-05 14:09:13 -05:00
Xie Yanbo 1daa4c202b fix Dockerfile 2018-11-06 00:47:09 +08:00
kennethreitz 83ff361672 Merge pull request #195 from taoufik07/fix/routes_conflict
Fix/routes conflict
2018-11-05 09:01:15 -05:00
taoufik07 2c686f107d Tests 2018-11-05 14:52:08 +01:00
taoufik07 ac8ec3d5ef cleanup 2018-11-05 14:51:56 +01:00
taoufik07 21e70ef913 Fix tests 2018-11-05 14:48:34 +01:00
taoufik07 c4f5b0e7c2 Fix routes conflict 2018-11-05 14:47:46 +01:00
Jan Vlcinsky 45d6c1389d Fixed missing .data on Marshmallow dump 2018-11-04 22:06:54 +01:00
kennethreitz 0c9bc5a3af fix 2018-11-04 07:22:03 -05:00
kennethreitz 5b67d5a04a Merge branch 'master' of github.com:kennethreitz/responder 2018-11-04 07:20:41 -05:00
kennethreitz f3396a5573 fix for #188 2018-11-04 07:20:30 -05:00
kennethreitz e9f48788a3 Merge pull request #184 from rparrapy/change-route-cache
Replace memoize decorator by functools.lru_cache
2018-11-02 07:09:08 -04:00
Rodrigo Parra 6993a1ea46 Replace memoize decorator by functools.lru_cache 2018-11-02 06:32:01 -03:00
kennethreitz 8bcfb4585b cleanup testimonials 2018-11-01 05:38:22 -04:00
kennethreitz db45251f7f Merge pull request #182 from rparrapy/patch-1
Fix typo
2018-10-31 17:39:02 -04:00
kennethreitz 5582667b4f deployment 2018-10-31 17:37:13 -04:00
Rodrigo Parra 2c898aaf23 Fix typo 2018-10-31 14:36:40 -03:00
kennethreitz e0999ffcdd Merge branch 'master' of github.com:kennethreitz/responder 2018-10-31 06:10:49 -04:00
kennethreitz 03811768bb depoloyment 2018-10-31 06:10:40 -04:00
kennethreitz fbef577c9f Merge pull request #179 from taoufik07/patch-16
Typo :3
2018-10-30 21:56:18 -04:00
Taoufik 9434510ce9 Typo :3 2018-10-31 01:04:25 +01:00
kennethreitz 354130c151 .serve 2018-10-30 19:08:57 -04:00
kennethreitz 3e3cba016a Merge pull request #177 from tomchristie/patch-1
Update api.py
2018-10-30 10:37:38 -04:00
Tom Christie f75e120bef Update api.py
Refs https://github.com/kennethreitz/responder/issues/174

You don't need both to use both DebugMiddleware and ExceptionMiddleware, just one or the other.

Currently errors will cause a traceback 500 page, then re-raise, then attempt to generate another traceback 500 page, resulting in the behavior commented on in #174
2018-10-30 14:34:10 +00:00
kennethreitz 1d0294e430 merge 2018-10-30 05:01:25 -04:00
kennethreitz f786dd8254 apispec 2018-10-30 04:59:24 -04:00
kennethreitz cd9d09fd53 Merge pull request #175 from taoufik07/patch-15
Set debug to False in tests
2018-10-29 19:04:32 -04:00
kennethreitz 7471bbcd4e Merge pull request #173 from taoufik07/patch-14
Trusted hosts docs
2018-10-29 19:04:19 -04:00
Taoufik 43b04eccbd Set debug to False in tests 2018-10-29 22:53:56 +01:00
Taoufik 6a5d0b5e9f Trusted hosts docs 2018-10-29 21:56:54 +01:00
kennethreitz 359d366de4 Merge pull request #172 from taoufik07/trustedhost_starlette
starlette's TrustedhostMiddleware
2018-10-29 16:33:35 -04:00
taoufik07 556d9f3a7b Update starlette 2018-10-29 21:08:38 +01:00
taoufik07 2cab2dcec0 Tests 2018-10-29 20:54:38 +01:00
taoufik07 99d4e78dc9 Use starlette TrustedHostMiddleware 2018-10-29 20:48:51 +01:00
kennethreitz 9aa99869ae next version 2018-10-29 08:00:44 -04:00
kennethreitz 08e0d87347 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-29 07:57:19 -04:00
kennethreitz 3f9e4057d3 run sync tasks in threadpoolexecutor 2018-10-29 07:54:59 -04:00
kennethreitz a29e40353c Merge pull request #170 from taoufik07/trusted_hosts
Trusted hosts support
2018-10-28 14:47:28 -04:00
taoufik07 778cb2dd0f Add Tests 2018-10-28 18:26:42 +00:00
taoufik07 f7d5514b94 Fix test base_url 2018-10-28 18:12:21 +00:00
taoufik07 954637f7b3 Pass base_url to the TestClient 2018-10-28 18:09:52 +00:00
taoufik07 1ab46104c8 Allow all hosts by default 2018-10-28 14:51:24 +00:00
kennethreitz 815776d473 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-28 05:25:56 -04:00
kennethreitz 8db1a7be90 Merge pull request #169 from taoufik07/patch-13
typo
2018-10-28 05:23:56 -04:00
taoufik07 7b11fa24dd Silence for now 2018-10-28 01:38:17 +01:00
taoufik07 1f0f2318d5 cleanup 2018-10-28 01:34:26 +01:00
taoufik07 029b3e2a52 Tests 2018-10-28 00:46:50 +01:00
taoufik07 4fff823def Trusted host 2018-10-28 00:46:39 +01:00
Taoufik cab78275f4 typo 2018-10-27 22:25:11 +01:00
kennethreitz 5f60e4fedb before_request 2018-10-27 09:22:17 -04:00
kennethreitz 96971a33a7 tour 2018-10-27 09:20:18 -04:00
kennethreitz 9a7409f521 test for before_request 2018-10-27 09:18:07 -04:00
kennethreitz 80aa7e305b before_request=True 2018-10-27 09:15:52 -04:00
kennethreitz 27d513cb01 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-27 09:04:29 -04:00
kennethreitz 9bf5cc8c03 before_request, v1 2018-10-27 09:04:19 -04:00
kennethreitz 7994b210cd Merge pull request #166 from repodevs/fix-deployment-example
DOC: change dockerfile instruction `from` to use UPPERCASE
2018-10-27 07:33:47 -04:00
Edi Santoso 46555bbe3f DOC: change dockerfile instruction from to use UPPERCASE 2018-10-27 18:29:43 +07:00
kennethreitz 4d15dbc465 fix for sessions 2018-10-27 07:10:05 -04:00
kennethreitz 855d3c4320 cookies 2018-10-27 06:21:19 -04:00
kennethreitz 4564862acc xfail 2018-10-27 06:11:47 -04:00
kennethreitz 176dd70073 fix 301 redirects 2018-10-27 06:07:50 -04:00
kennethreitz a5e6f0c196 version 2018-10-27 05:39:17 -04:00
kennethreitz 083bb5a96c Merge branch 'master' of github.com:kennethreitz/responder 2018-10-27 05:13:39 -04:00
kennethreitz 04522281be don't do whitenoise index file 2018-10-27 05:13:30 -04:00
kennethreitz 0e8bb49b59 Merge pull request #164 from taoufik07/patch-12
Fix typo
2018-10-27 04:51:34 -04:00
Taoufik 9abf6eea16 typo 2018-10-26 23:57:16 +01:00
Taoufik 1d7a04ce7b Fix typo 2018-10-26 23:15:13 +01:00
kennethreitz 49fb5792c3 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-26 17:04:22 -04:00
kennethreitz 5eebba09c5 test redirects 2018-10-26 17:04:14 -04:00
kennethreitz b86974688e Merge pull request #163 from Hwesta/patch-1
Fix typo in tour.rst
2018-10-26 15:24:06 -04:00
Holly Becker 74afe2ed13 Fix typo in tour.rst 2018-10-26 12:19:33 -07:00
kennethreitz ed53a0b624 Merge pull request #161 from repodevs/fix-doc-quickstart
DOC: Fix quickstart response headers
2018-10-26 14:17:31 -04:00
kennethreitz 23e15d6459 Merge pull request #162 from sheb/patch-3
fix CI build failing since secret key has changed
2018-10-26 14:17:20 -04:00
Sébastien Geffroy 71ea19d1c1 fix CI build failing since secret key has changed 2018-10-26 20:15:05 +02:00
Edi Santoso fa621d076d DOC: Fix quickstart response headers 2018-10-27 00:53:11 +07:00
kennethreitz 4902f1328a Merge pull request #160 from frostming/graphql-doc
Fix doc about graphql usage.
2018-10-26 11:57:15 -04:00
kennethreitz 2ee8ff484d better 2018-10-26 11:09:24 -04:00
kennethreitz c872fe3c78 image 2018-10-26 11:08:37 -04:00
kennethreitz a08b275463 fix 2018-10-26 10:51:48 -04:00
kennethreitz 9717208dd4 v1.0.1 2018-10-26 10:51:35 -04:00
kennethreitz c9a233f5e5 api cleanup 2018-10-26 10:50:58 -04:00
kennethreitz 7389350ff9 fail 2018-10-26 10:48:12 -04:00
Frost Ming f46ac08cff Fix doc about graphql usage. 2018-10-26 22:03:43 +08:00
kennethreitz 296d5e7974 pipfile.lock 2018-10-26 09:19:31 -04:00
kennethreitz fe0bea686c simplify 2018-10-26 08:22:04 -04:00
kennethreitz 838d172512 v1.0.0 2018-10-26 08:10:49 -04:00
kennethreitz 2c02c51c37 fix docs 2018-10-26 08:09:56 -04:00
kennethreitz 67a4cbca2c move graphql to extension 2018-10-26 08:07:24 -04:00
kennethreitz a2f97e727f powered by starlette 2018-10-26 07:39:02 -04:00
kennethreitz 462506113e cleanup 2018-10-26 07:36:41 -04:00
kennethreitz 5f2a72203f cleanup things 2018-10-26 07:29:46 -04:00
kennethreitz d6febe2d02 test for startup 2018-10-26 07:01:28 -04:00
kennethreitz c2bd1e989a add_event_handler update 2018-10-26 06:55:33 -04:00
kennethreitz f886c2c050 cleanup 2018-10-26 06:54:15 -04:00
kennethreitz ae770e603a Merge branch 'master' into master 2018-10-26 06:48:42 -04:00
kennethreitz 7b79472d65 v0.3.3 2018-10-25 18:15:22 -04:00
kennethreitz 090a3a571b Merge pull request #156 from taoufik07/patch-11
Fix link formatting
2018-10-25 18:12:36 -04:00
kennethreitz f9d55fc425 Merge pull request #157 from steinnes/re-raise-exception-after-response
Improve exception handling
2018-10-25 18:12:24 -04:00
Steinn Eldjárn Sigurðarson 4f57e8a5d1 Improve exception handling
Re-raise exceptions caught in _dispatch_request.  Added starlette
ExceptionMiddleware to be able to test this gracefully.
2018-10-25 22:08:10 +00:00
Taoufik 1e6c9d935a Fix link formatting 2018-10-25 22:45:29 +01:00
kennethreitz 00cfde169b remove future ideas 2018-10-25 17:41:58 -04:00
kennethreitz 02733ac718 Merge pull request #155 from taoufik07/features/CORS
Custom CORS params and docs
2018-10-25 17:36:40 -04:00
Taoufik 55b55e62da Improvements 2018-10-25 20:58:01 +01:00
Taoufik 5fccedd4c4 Improvement 2018-10-25 20:55:14 +01:00
kennethreitz b9ad78ec79 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-25 15:44:07 -04:00
taoufik07 64ac6bcd1f Add CORS docs 2018-10-25 20:44:04 +01:00
kennethreitz 45e4d80c4d --pre 2018-10-25 15:44:01 -04:00
taoufik07 a5b1652d15 Custom CORS params via cors_params 2018-10-25 20:43:50 +01:00
kennethreitz f954eb7d88 Merge pull request #154 from taoufik07/features/CORS
CORS
2018-10-25 15:30:19 -04:00
taoufik07 53216813e5 Add CORSMiddleware 2018-10-25 20:12:21 +01:00
kennethreitz 1618203930 Merge pull request #153 from metakermit/address_doc
document how to customise the address
2018-10-25 13:40:58 -04:00
Dražen Lučanin 237a2ed426 document how to customise the address 2018-10-25 16:43:19 +02:00
kennethreitz d33289503a remove is_routed 2018-10-25 08:24:45 -04:00
kennethreitz f5ff4c9725 clean up 2018-10-25 08:23:22 -04:00
kennethreitz 62f932dcfc default secret key 2018-10-25 08:22:14 -04:00
kennethreitz b66112d0ca improvements 2018-10-25 08:20:06 -04:00
kennethreitz b98354e63a simplify readme 2018-10-25 07:31:34 -04:00
kennethreitz 94b3625718 ideas 2018-10-25 07:29:05 -04:00
kennethreitz f7ee720281 subtle improvements 2018-10-25 07:26:05 -04:00
kennethreitz 4ab523bf01 fix static 2018-10-25 07:25:35 -04:00
kennethreitz 2d4f1bfd02 test static 2018-10-25 07:10:51 -04:00
kennethreitz 38426c9143 for tests 2018-10-25 07:10:42 -04:00
kennethreitz bdf151e0a7 test responder docs 2018-10-25 07:08:45 -04:00
kennethreitz 9768b7888d Merge branch 'master' of github.com:kennethreitz/responder 2018-10-25 06:59:05 -04:00
kennethreitz 740a48566f important stuff 2018-10-25 06:58:57 -04:00
kennethreitz 475cd1a106 removed 2018-10-25 06:47:58 -04:00
kennethreitz 38e7c39d69 Merge pull request #151 from tkamenoko/patch-2
fix: license mismatch in setup.py
2018-10-25 06:38:45 -04:00
T.Kameyama 774db6bead fix: license mismatch in setup.py 2018-10-25 18:52:00 +09:00
kennethreitz a1a3e0412a Merge pull request #148 from taoufik07/patch-10
Quick fix
2018-10-24 19:09:59 -04:00
Taoufik 0b39c89e60 Quick fix 2018-10-24 19:17:53 +01:00
kennethreitz 53be4d8954 index.rst 2018-10-24 10:32:28 -04:00
kennethreitz 03812cc7eb v0.3.1 2018-10-24 08:34:46 -04:00
kennethreitz aa12b24293 whitenoise 2018-10-24 08:34:06 -04:00
kennethreitz daf43009ba changelog 2018-10-24 08:21:08 -04:00
kennethreitz 955d777ca5 Merge pull request #146 from JayjeetAtGithub/master
Improve readability
2018-10-24 08:19:00 -04:00
kennethreitz cc9472aa2f Merge pull request #147 from taoufik07/fix/default_endpoint_and_route_found
Do not return default endpoint if the route is found
2018-10-24 08:18:38 -04:00
kennethreitz e527f3cb1f interactive documentation 2018-10-24 08:19:28 -04:00
kennethreitz 3a375a8975 documentation 2018-10-24 08:17:26 -04:00
kennethreitz 2698496592 documentation endpoint 2018-10-24 08:16:55 -04:00
kennethreitz 0155d854e3 v0.3.0 2018-10-24 08:16:22 -04:00
kennethreitz c74cc8586f improve imports 2018-10-24 08:15:21 -04:00
kennethreitz 8eb89da9a0 improve imports 2018-10-24 08:14:24 -04:00
kennethreitz dee6ee3cef whitenoise, apistar 2018-10-24 08:13:15 -04:00
kennethreitz beab89df09 apistar 2018-10-24 08:12:45 -04:00
kennethreitz 5164d4ec32 apistar 2018-10-24 08:12:39 -04:00
taoufik07 878db851af Return default endpoint if the route is not found 2018-10-24 12:47:47 +01:00
Jayjeet Chakraborty 686ff72ae0 Improve readability 2018-10-24 16:37:02 +05:30
kennethreitz 2710d7098f v0.2.3 2018-10-24 07:02:44 -04:00
kennethreitz 7f41ff4035 Merge pull request #138 from taoufik07/fix/cbv
Fix CBV
2018-10-24 06:59:51 -04:00
kennethreitz ed8d51014c Merge branch 'master' into fix/cbv 2018-10-24 06:57:28 -04:00
kennethreitz d09a51f47d Merge pull request #140 from taoufik07/patch-9
Typo
2018-10-24 06:56:53 -04:00
kennethreitz 59bae90454 Merge pull request #142 from taoufik07/fix/static_response
Fix static response
2018-10-24 06:56:42 -04:00
kennethreitz 13ee0ca94e Merge pull request #136 from taoufik07/fix/Route.is_function
Fix Route.is_function
2018-10-24 06:56:24 -04:00
kennethreitz 5abc095050 Merge pull request #139 from JayjeetAtGithub/master
Fix Typo in api.py
2018-10-24 06:56:02 -04:00
kennethreitz 7eb68c8388 Merge pull request #143 from frostming/patch-1
Typo in tour.rst
2018-10-24 06:55:50 -04:00
Frost Ming f69b644a77 Typo in tour.rst 2018-10-24 12:28:11 +08:00
Peder Bergebakken Sundt 6b93125ff2 Add support for "tick" in api.on_event 2018-10-24 06:26:58 +02:00
Peder Bergebakken Sundt 43faef4569 Add api.on_event decorator supporting startup and cleanup 2018-10-24 06:25:44 +02:00
taoufik07 fe41d4c863 Fix static response 2018-10-24 01:17:02 +01:00
Taoufik 29830455ed Typo 2018-10-24 00:11:27 +01:00
Jayjeet Chakraborty e50828093d Clean print statement 2018-10-24 02:56:43 +05:30
Jayjeet Chakraborty 880d29c5a9 Fix Typo in api.py 2018-10-24 02:46:33 +05:30
taoufik07 77b2e9ba7a tests 2018-10-23 21:20:09 +01:00
taoufik07 586fad7646 Fix CBV 2018-10-23 21:19:57 +01:00
kennethreitz fb636028fb improvements 2018-10-23 14:58:02 -04:00
taoufik07 a8c3f8fc46 Fix Route.is_function 2018-10-23 19:52:43 +01:00
kennethreitz 72f4227c5a remove redundancy in tour 2018-10-23 08:46:20 -04:00
kennethreitz 8ccace8ef9 testing 2018-10-23 08:36:20 -04:00
kennethreitz 6d40c6dfe5 assert 2018-10-23 08:34:32 -04:00
kennethreitz 0b5562cdec fix 2018-10-23 08:33:51 -04:00
kennethreitz eeff0816f3 doc updates 2018-10-23 08:29:02 -04:00
kennethreitz f1f16dea3f best practices for secret key 2018-10-23 08:14:00 -04:00
kennethreitz bfc6ef2049 test client docs 2018-10-23 08:12:40 -04:00
kennethreitz 5212de79d3 v0.2.2 2018-10-23 08:05:07 -04:00
kennethreitz b61c02e5df Merge pull request #132 from vuonghv/show-exception-background-task
Show traceback info when background tasks raise exceptions
2018-10-23 08:02:59 -04:00
kennethreitz f982954e8f versions 2018-10-23 08:03:28 -04:00
kennethreitz 3ba20e69ba requests session 2018-10-23 08:02:30 -04:00
kennethreitz aea01fd893 Revert "idk what's happening"
This reverts commit e34cb539d2.
2018-10-23 08:00:56 -04:00
kennethreitz 950be14eca Revert "Merge branch 'master' of github.com:kennethreitz/responder"
This reverts commit 446deffc17, reversing
changes made to e0863115ee.
2018-10-23 08:00:30 -04:00
kennethreitz 446deffc17 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-23 07:59:07 -04:00
kennethreitz e0863115ee use api.requests 2018-10-23 07:59:00 -04:00
kennethreitz e34cb539d2 idk what's happening 2018-10-23 07:58:48 -04:00
kennethreitz d8ade8638a merge 2018-10-23 07:57:23 -04:00
Vuong Hoang 3067080474 Show traceback when background tasks raise exceptions 2018-10-23 18:23:22 +07:00
kennethreitz 886cc0f214 Merge pull request #131 from daikeren/master
Fix Route.is_function
2018-10-23 07:01:40 -04:00
Andy Dai 071d34b016 Fix Route.is_function 2018-10-23 17:07:42 +08:00
kennethreitz a1564ca003 Merge pull request #123 from taoufik07/patch-7
Quick fix
2018-10-22 17:25:15 -04:00
Taoufik 60f0e765c2 Quick fix 2018-10-22 22:14:18 +01:00
kennethreitz 3f0ecea4bf Merge pull request #120 from Pentusha/master
Depend on marshmallow>=3.0.0b7
2018-10-22 17:06:00 -04:00
kennethreitz 2c9e6572c5 Update tour.rst 2018-10-22 17:05:50 -04:00
Ivan Larin 371a83f20f Depend on marshmallow>=3.0.0b7 kennethreitz/responder#119 2018-10-22 19:46:55 +03:00
kennethreitz b8cff1655a websockets 2018-10-22 10:18:37 -04:00
kennethreitz 232856ca3a Merge branch 'master' of github.com:kennethreitz/responder 2018-10-22 10:07:38 -04:00
kennethreitz 3f168ac6fd slots 2018-10-22 10:06:55 -04:00
kennethreitz c59cb1d0d3 websocket 2018-10-22 10:06:03 -04:00
kennethreitz ec13df75d0 kinda test websocket support 2018-10-22 10:05:20 -04:00
kennethreitz 6fc02964ba cleanup 2018-10-22 09:59:38 -04:00
kennethreitz ed79e45680 Merge pull request #116 from tkamenoko/patch-1
doc: fix Class-based views
2018-10-22 09:30:14 -04:00
kennethreitz 1be983bf89 cleanup 2018-10-22 09:28:14 -04:00
T.Kameyama b09d6a9d04 doc: fix Class-based views
In Class-based views, each method needs `self` as 1st argument.
2018-10-22 14:37:55 +09:00
taoufik07 db143d845d cleanup 2018-10-21 18:17:56 +01:00
taoufik07 2e23501f9d Fix check_existing 2018-10-21 18:15:13 +01:00
taoufik07 bd6addcd3a Add websocket support 2018-10-21 18:00:25 +01:00
taoufik07 631e1fb604 Add WebSocket 2018-10-21 17:36:21 +01:00
kennethreitz 30ee6726a8 Merge pull request #113 from metakermit/extend-static-docs
Extend static=True docs
2018-10-21 06:25:56 -04:00
kennethreitz 1c397db9d8 cleanup 2018-10-21 06:23:02 -04:00
kennethreitz cc23ca80f4 Merge pull request #112 from Nitish18/feat/server_debug_mode
feat: added debug mode for uvicorn server
2018-10-21 06:20:16 -04:00
Dražen Lučanin 449379a0ed extend static=True docs 2018-10-21 11:58:54 +02:00
Nitish Chauhan b3208b1c5b feat: added debug mode for uvicorn server 2018-10-21 15:20:08 +05:30
kennethreitz 4df60b55a6 Merge pull request #110 from sheb/patch-2
fix an AttributeError when route does not exist
2018-10-20 13:54:58 -07:00
Sébastien Geffroy 379553a1a5 fix an AttributeError when route does not exist 2018-10-20 21:55:43 +02:00
kennethreitz a2eaa5c7b5 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-20 14:46:21 -04:00
kennethreitz 175c46e68c __version__ 2018-10-20 14:46:10 -04:00
kennethreitz a58cc11079 500 support 2018-10-20 14:45:52 -04:00
kennethreitz 218a375c27 test 500s 2018-10-20 14:45:33 -04:00
kennethreitz 567b1577c6 Merge pull request #108 from shyamjos/patch-1
Fixed minor spelling mistakes in changelog
2018-10-20 11:21:11 -07:00
kennethreitz 3c3687d11f Merge pull request #109 from taoufik07/patch-6
clean up
2018-10-20 11:21:00 -07:00
Taoufik 19dfac8340 clean up 2018-10-20 18:37:04 +01:00
kennethreitz b61feafe5a 500 on errrors 2018-10-20 13:36:06 -04:00
Shyam Jos 0c342c8b3e Corrected Spelling
Corrected Spelling
2018-10-20 23:01:57 +05:30
Shyam Jos dbcba8fad7 Fixed minor spelling mistakes in changelog
Fixed minor spelling mistakes in changelog
2018-10-20 22:24:44 +05:30
kennethreitz b8053e20f2 fix 2018-10-20 12:10:59 -04:00
kennethreitz 1896901aa8 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-20 12:10:19 -04:00
kennethreitz 383c9132ed improvement 2018-10-20 12:10:09 -04:00
kennethreitz 57b144c3e7 Merge pull request #107 from taoufik07/patch-5
Refactor Route._weight and f-strings everywhere
2018-10-20 07:08:51 -07:00
kennethreitz eed5365fe0 file upload support 2018-10-20 09:56:35 -04:00
kennethreitz f5905568c4 files support 2018-10-20 09:54:53 -04:00
kennethreitz 096099470e yay tests pass 2018-10-20 08:50:36 -04:00
kennethreitz e7ed7aca3c tests still pass 2018-10-20 08:23:10 -04:00
kennethreitz 6725b275b8 cleanup 2018-10-20 07:59:39 -04:00
kennethreitz 3447a7ef41 v0.1.5 2018-10-20 07:59:12 -04:00
kennethreitz 99f35fbea4 use querydict for form parsing 2018-10-20 07:57:27 -04:00
kennethreitz 5c9a3912a9 cached _content 2018-10-20 07:38:53 -04:00
Taoufik 5d43c0418c f-string 2018-10-19 23:13:21 +01:00
Taoufik 87c0076e12 use f-string
Every time I scroll through the README, it hurts me
2018-10-19 23:10:39 +01:00
Taoufik 95252ac697 Refactor 2018-10-19 23:06:55 +01:00
kennethreitz 5bb9f96701 cleanup 2018-10-19 05:11:07 -07:00
kennethreitz 750e9dfaa7 cleanup 2018-10-19 04:54:49 -07:00
kennethreitz f34f3c1661 v0.1.4 2018-10-19 04:17:06 -07:00
kennethreitz d4f83c978c improvements 2018-10-19 04:16:19 -07:00
kennethreitz 212f280c19 models 2018-10-19 03:10:39 -07:00
kennethreitz f3e2450636 models 2018-10-19 03:09:53 -07:00
kennethreitz d6d496018d fix 2018-10-19 03:08:15 -07:00
kennethreitz 78be7fc772 api 2018-10-19 03:00:41 -07:00
kennethreitz 6ebadd8469 new files 2018-10-19 02:19:38 -07:00
kennethreitz 557750c8d4 customizable cookie 2018-10-18 17:02:10 -07:00
kennethreitz e85ef27e6c Merge pull request #98 from pbsds/master
Store Jinja enviroment in between template render calls
2018-10-18 16:46:31 -07:00
kennethreitz 4ca961a1b4 Merge pull request #104 from metakermit/cli-build
CLI build command
2018-10-18 16:42:17 -07:00
kennethreitz 6a9110e9c1 Merge branch 'master' into cli-build 2018-10-18 16:40:20 -07:00
Dražen Lučanin 51ffce09ae fix cli setup 2018-10-18 23:48:09 +02:00
Peder Bergebakken Sundt 1c4e96b365 Add api.jinja_values_base:dict
This allows the user to add functions and values for use in all
templates, without needing to pass them on each render call.

As a side effect: The reference to `api` is still passed into the template view,
but this now yield to the values passed into api.template(), like one
would normally expect.
2018-10-18 20:47:59 +02:00
Peder Bergebakken Sundt 0db70e8edd Store jinja enviroment in between the template render calls
This allows the user to modify the jinja
enviroment, adding custom filters and such
2018-10-18 20:47:34 +02:00
Peder Bergebakken Sundt e46b3a5e19 Rename s to s_ in api.template_string()
Issue described in #76 applied here as well, however less propable.
Same fix as in a8fc78fcda
2018-10-18 20:47:14 +02:00
kennethreitz fdd3d4d85a sessions 2018-10-18 10:25:19 -07:00
kennethreitz 37c9cba42e version 2018-10-18 10:20:06 -07:00
kennethreitz c1544f66bb tour 2018-10-18 10:14:20 -07:00
kennethreitz d37f41f6a5 docstrings 2018-10-18 10:08:57 -07:00
kennethreitz b245dd2d51 Merge pull request #96 from kennethreitz/sessions
sessions
2018-10-18 10:05:16 -07:00
kennethreitz a1fcf11399 Merge pull request #95 from taoufik07/patch-4
Use HTTPSRedirectMiddleware
2018-10-18 10:04:41 -07:00
kennethreitz 8f876da245 sessions 2018-10-18 10:03:56 -07:00
Taoufik 23b8e5a2b3 Use HTTPSRedirectMiddleware 2018-10-18 18:01:41 +01:00
kennethreitz 3b7e7c7192 Merge pull request #94 from mathiasose/graphql-variables-and-operation-name
Find GraphQL variables, operation name from JSON
2018-10-18 07:49:34 -07:00
Mathias Ose b7ecf6e2e0 Find GraphQL variables, operation name from JSON
Make `_resolve_graphql_query` return *three* things from the JSON query: query (as before), variables and operation names. These values are all passed on to `schema.execute`.

TODO:
- Get variables and operation names from other requests types than JSON.
- Write tests.
- _Possibly_ refactor `_resolve_graphql_query` to return something a bit more structured than a 3-tuple.
2018-10-18 16:09:49 +02:00
kennethreitz 2ec6aaff03 docstrings 2018-10-18 06:42:26 -07:00
kennethreitz 19f8553f2d fix 2018-10-18 04:31:38 -07:00
kennethreitz 05a64ff095 cookies 2018-10-18 04:31:22 -07:00
kennethreitz a8fc78fcda fixes #76 2018-10-18 04:26:25 -07:00
kennethreitz e0e8b40fa2 Merge pull request #91 from kennethreitz/cookies
Cookies
2018-10-18 04:23:16 -07:00
kennethreitz 00165cd6ca tests for cookies 2018-10-18 04:16:44 -07:00
kennethreitz cd799ddfcd cookies 2018-10-18 04:07:13 -07:00
kennethreitz fffd6b7c86 Merge pull request #83 from kennethreitz/bnm_tests
added more tests to routes, and changed some bits in routes regarding regex
2018-10-18 02:51:55 -07:00
kennethreitz 439b008a34 Merge pull request #85 from condemil/patch-1
Add .python-version to .gitignore
2018-10-18 02:51:42 -07:00
kennethreitz f38e538892 Merge pull request #89 from pyasi/tests_for_status_codes
Add basic tests for the status codes file
2018-10-18 02:50:57 -07:00
Peter Yasi 6aa87a073f Add basic tests for the status codes file 2018-10-17 21:25:28 -04:00
Dmitry c38198ccba Add .python-version to .gitignore
.python-version allows to specify separate pyenv virtual environment
2018-10-17 22:58:16 +02:00
Luna 3be88c8cbf removed redundant import in routes.py 2018-10-17 21:17:14 +01:00
Luna 558ced1afb recommented pytest.ini addopts 2018-10-17 21:07:39 +01:00
Luna 0149e6935d added more tests to routes, and changed some bits in routes regarding regex 2018-10-17 21:05:38 +01:00
kennethreitz d97fdfd7c4 Merge pull request #75 from tomchristie/asgi-middleware
Support ASGI middleware
2018-10-17 12:03:15 -07:00
kennethreitz 8b85d8c6fb Merge pull request #80 from taoufik07/fix-CBV-missing-prams
Fix CBV missing params
2018-10-17 12:02:23 -07:00
kennethreitz 673779490c Merge pull request #82 from squiddy/patch-1
Fix docker image typo in deployment documentation
2018-10-17 12:01:43 -07:00
Reiner Gerecke 48154e7e2d Fix docker image typo in deployment documentation 2018-10-17 19:59:40 +02:00
taoufik07 20f72b3f63 Add tests 2018-10-17 18:43:24 +01:00
taoufik07 e82c958af2 Add missing params to on_method 2018-10-17 18:20:44 +01:00
taoufik07 60c311ab9f Add missing params to on_request 2018-10-17 18:20:16 +01:00
Tom Christie fbac81c245 Drop commented out gzip code 2018-10-17 15:13:09 +01:00
Tom Christie 9ca67d9228 Support ASGI middleware 2018-10-17 15:11:16 +01:00
kennethreitz 5ffa18221f an 2018-10-17 06:20:06 -07:00
kennethreitz aceb1f0f61 Must be awaited. 2018-10-17 06:17:21 -07:00
kennethreitz cee5ca8873 v0.1.1 2018-10-17 06:01:41 -07:00
kennethreitz d961d4ab43 default routes 2018-10-17 06:01:27 -07:00
kennethreitz 5205150a89 default route 2018-10-17 05:53:23 -07:00
kennethreitz 48e58cde5d docker 2018-10-17 05:19:22 -07:00
kennethreitz 033e91f8df name 2018-10-17 05:15:25 -07:00
kennethreitz aab3705897 myapi 2018-10-17 05:14:19 -07:00
kennethreitz d02efa81f2 deployment 2018-10-17 05:12:11 -07:00
kennethreitz 95a8240da7 single-page webapps 2018-10-17 04:58:11 -07:00
kennethreitz dd0ddab610 single page 2018-10-17 04:52:02 -07:00
kennethreitz d23ac10f90 version 2018-10-17 04:49:00 -07:00
kennethreitz ec18290b8a changelog 2018-10-17 04:48:38 -07:00
kennethreitz 2c4cd39dc9 static application support 2018-10-17 04:48:33 -07:00
kennethreitz 830bad0b85 docs for #53 2018-10-17 04:47:39 -07:00
kennethreitz f14ef6fa15 #53 2018-10-17 04:45:12 -07:00
kennethreitz 7400b1c83d static support #53 2018-10-17 04:38:51 -07:00
kennethreitz e7caf39fba static_route 2018-10-17 04:25:09 -07:00
kennethreitz 09fd0fb0ca version 2018-10-17 04:19:38 -07:00
kennethreitz 72adb13c0f version 2018-10-17 04:16:22 -07:00
kennethreitz ea0e382f82 test for #71 2018-10-17 04:15:36 -07:00
kennethreitz e70cba5143 Fix for #71 2018-10-17 04:12:13 -07:00
kennethreitz 8aec244c31 openapi 2018-10-17 04:12:03 -07:00
kennethreitz 60e163164f v0.0.8 2018-10-17 03:58:23 -07:00
kennethreitz 86b9b5f3fa graphiql 2018-10-17 03:57:39 -07:00
kennethreitz 401a208767 changelog 2018-10-17 03:27:26 -07:00
kennethreitz a1bfbda05b Merge branch 'master' into cli 2018-10-17 03:15:49 -07:00
kennethreitz 7d1f991ce4 changelog 2018-10-17 02:52:22 -07:00
kennethreitz 1b10378f58 merge 2018-10-17 02:33:49 -07:00
kennethreitz 2bbb379994 Merge pull request #70 from rpost/patch-1
Fix typo
2018-10-17 05:27:59 -04:00
kennethreitz a835f119e1 Merge pull request #67 from goodbadwolf/patch-1
Fix typo in quickstart example
2018-10-17 05:27:47 -04:00
kennethreitz 91d8bac680 Merge pull request #65 from taoufik07/routes_matching
Routes matching for humans
2018-10-17 05:27:32 -04:00
kennethreitz 3db10a4ce8 Merge pull request #63 from pesap/fix-typo
Fix typo in docs
2018-10-17 05:26:19 -04:00
kennethreitz 590640645b Merge pull request #62 from ybv/master
Add status code test for class based view
2018-10-17 05:26:03 -04:00
kennethreitz 7f02bfdf0c Merge pull request #61 from ewjoachim/patch-1
Fix typo
2018-10-17 05:25:49 -04:00
Radek Postołowicz e5cef0d9c0 Fix typo 2018-10-17 10:08:59 +02:00
ArtemGordinsky 85f9c33b2b Integrate GraphiQL 2018-10-17 08:00:03 +02:00
Manish P Mathai 148a430da4 Fix typo in quickstart example 2018-10-16 22:36:54 -07:00
Taoufik f7657679ac A verbose name 2018-10-17 05:07:29 +01:00
taoufik07 f0479019c3 Order the routes based on the weight 2018-10-17 04:38:08 +01:00
taoufik07 a9a4ceaa78 Add weight to Route 2018-10-17 04:37:31 +01:00
pesap c55c905621 Fix typo 2018-10-16 17:23:47 -07:00
ybv 4db2289b7e Add status code rest for class based view 2018-10-16 22:39:09 +05:30
Joachim Jablon 93172ea1d0 Fix typo 2018-10-16 14:41:30 +02:00
kennethreitz 2d935542e1 v0.0.7, immutable response object 2018-10-16 05:24:20 -07:00
kennethreitz f309ad7746 cli 2018-10-16 05:14:48 -07:00
kennethreitz a7ec1364f4 features 2018-10-16 04:42:44 -07:00
kennethreitz eb71ced092 graphql 2018-10-16 04:42:25 -07:00
kennethreitz 712ad0a73b mount a wsgi app 2018-10-16 04:35:30 -07:00
kennethreitz 48c0b137d5 version 2018-10-16 04:30:03 -07:00
kennethreitz dfccfcc3e5 wsgi apps 2018-10-16 04:29:44 -07:00
kennethreitz 6abe667efb Merge branch 'master' of github.com:kennethreitz/responder 2018-10-16 04:27:50 -07:00
kennethreitz c2472215ab features 2018-10-16 04:27:40 -07:00
kennethreitz ac3c1e149c Update README.md 2018-10-16 04:22:29 -07:00
kennethreitz cdf989427a mount flaks apps 2018-10-16 04:20:29 -07:00
kennethreitz ebf129edd3 /cc @tomchristie 2018-10-16 03:59:43 -07:00
kennethreitz 08c30f4baf Merge pull request #55 from sloria/upgrade-apispec
Depend on apispec>=1.0.0b1
2018-10-16 06:14:25 -04:00
kennethreitz cf6bdc20ef Merge pull request #57 from frostming/fix-docstring
Remove wsgi mentions in docstring
2018-10-16 06:14:15 -04:00
frostming 3ece644af8 Remove wsgi mentions in docstring 2018-10-16 12:54:28 +08:00
Steven Loria 3991c82c91 Depend on apispec>=1.0.0b1
The current code depends on the `apispec.yaml_utils`
module, which only exists in apispec 1.0.0b.

Close #52
2018-10-15 19:46:46 -04:00
kennethreitz 9b635253f0 Update index.rst 2018-10-15 12:29:50 -04:00
kennethreitz b62f41336e Merge pull request #54 from tomchristie/testimonial
Testimonial
2018-10-15 12:28:28 -04:00
Tom Christie f7b777c79e Testimonial 2018-10-15 16:47:12 +01:00
kennethreitz d18fa8e42a v0.0.6 2018-10-15 08:56:32 -04:00
kennethreitz 525c62ad26 content-type 2018-10-15 08:56:02 -04:00
kennethreitz 4000a6a48c fix 2018-10-15 08:45:22 -04:00
kennethreitz 5b173ed4c4 tour 2018-10-15 08:44:45 -04:00
kennethreitz f56ad73565 tour 2018-10-15 08:42:57 -04:00
kennethreitz 003991c8c6 fix 2018-10-15 08:41:39 -04:00
kennethreitz e2a32afb80 next version 2018-10-15 08:28:10 -04:00
kennethreitz f305a69bb3 tour 2018-10-15 08:26:13 -04:00
kennethreitz 84e8babd9e cleanups 2018-10-15 08:23:38 -04:00
kennethreitz aeb46d9b54 open_api spec 2018-10-15 08:21:40 -04:00
kennethreitz fafe0bd8e4 changelog 2018-10-15 07:19:01 -04:00
kennethreitz 9a2ab45957 safe dump 2018-10-15 07:18:19 -04:00
kennethreitz 66978a8cdc test yaml and form too 2018-10-15 07:14:14 -04:00
kennethreitz 1636012700 json uploads 2018-10-15 07:11:57 -04:00
kennethreitz 09206ae1e4 .pre 2018-10-15 07:05:59 -04:00
kennethreitz 9188475746 remove contributors file 2018-10-15 07:04:30 -04:00
kennethreitz 34d158a632 cleanup homepage 2018-10-15 07:02:53 -04:00
kennethreitz c06e6aa5ca changelog 2018-10-15 07:01:49 -04:00
kennethreitz f4f670f048 fix pipfile and pipfile.lock 2018-10-15 06:56:27 -04:00
kennethreitz 778d742b6e new lockfile 2018-10-15 06:55:31 -04:00
kennethreitz c8392b65b6 remove cli, next version 2018-10-15 06:53:41 -04:00
kennethreitz c0ace9c2e5 fix all the format things 2018-10-15 06:52:10 -04:00
kennethreitz dfcab7dcbf fix #47 2018-10-15 06:41:47 -04:00
kennethreitz eb0870deb1 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-14 20:40:45 -04:00
kennethreitz 5b7ef34523 fix #45 2018-10-14 20:40:31 -04:00
kennethreitz 6ec728e466 Merge pull request #43 from sheb/fix-typo-route
fix typo in doc string
2018-10-14 17:22:15 -04:00
kennethreitz f12a562a08 Merge pull request #44 from kennethreitz/bnm_tests
added tests for models.QueryDict
2018-10-14 17:21:55 -04:00
Luna 17c4c95593 added tests for models.QueryDict 2018-10-14 18:20:50 +01:00
kennethreitz 9b72c90944 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-14 11:57:22 -04:00
kennethreitz ec34da60a1 more tests 2018-10-14 11:57:15 -04:00
Sébastien Geffroy daa4b6368a fix typo in doc string 2018-10-14 17:51:21 +02:00
kennethreitz 931a7a1a6c Merge pull request #39 from rcatajar/patch-1
Fix typo in quickstart documentation
2018-10-14 11:47:25 -04:00
kennethreitz 69d5790078 Merge pull request #42 from kennethreitz/bnm_tests
Cleaned up redundant test function
2018-10-14 11:46:08 -04:00
kennethreitz 7571c18a55 Merge pull request #40 from tselepakis/fix-memoize
(fix) memoization functionality
2018-10-14 11:45:04 -04:00
Luna ff7ce9bdd0 linter error 2018-10-14 16:43:16 +01:00
Luna e5fc801899 added boilerplate cli 2018-10-14 16:40:57 +01:00
Konstantinos Tselepakis b362aa6813 (fix) memoization functionality
* Keep an object level cache
* Use func name as part of the dictionary key
2018-10-14 18:09:43 +03:00
Luna 652b961ac8 fixed conflict 2018-10-14 15:41:16 +01:00
Luna 652713aec4 clean up 2018-10-14 15:40:29 +01:00
kennethreitz 387b2f166b cleanup 2018-10-14 09:11:47 -04:00
kennethreitz 164b4a056a no memoize routes 2018-10-14 09:10:39 -04:00
Romain Catajar 29e514fea6 Fix typo in quickstart documentation 2018-10-14 15:10:29 +02:00
kennethreitz 310fff78c6 no benchmarks 2018-10-14 08:55:01 -04:00
kennethreitz f2efdc007c no benchmarks 2018-10-14 08:54:37 -04:00
kennethreitz b3be767923 upgrades 2018-10-14 08:46:33 -04:00
kennethreitz e86f2f3873 media() 2018-10-14 08:17:17 -04:00
kennethreitz 13d84f73d4 fix template rendering 2018-10-14 07:59:18 -04:00
kennethreitz e31342d3ba fix 2018-10-14 07:53:47 -04:00
kennethreitz daf0538bf3 fix 2018-10-14 07:52:52 -04:00
kennethreitz 451ce8b0c7 fix 2018-10-14 07:48:22 -04:00
kennethreitz b8cce14705 fix 2018-10-14 07:48:07 -04:00
kennethreitz bf1c9c650e fix 2018-10-14 07:47:45 -04:00
kennethreitz 8f6387536c fix 2018-10-14 07:46:15 -04:00
kennethreitz 56535ece11 fix 2018-10-14 07:44:13 -04:00
kennethreitz f1767719cb paragraphs 2018-10-14 07:43:43 -04:00
kennethreitz c925b06114 margin-top 2018-10-14 07:43:01 -04:00
kennethreitz 402426884d try this 2018-10-14 07:40:22 -04:00
kennethreitz df6c8a5a75 quotes 2018-10-14 07:39:51 -04:00
kennethreitz 99f5ae7125 another fix 2018-10-14 07:39:20 -04:00
kennethreitz d50a1b7d07 fix 2018-10-14 07:38:30 -04:00
kennethreitz fab3bb76f7 let's see if this works 2018-10-14 07:38:19 -04:00
kennethreitz 5025c66bb2 less testimonial 2018-10-14 07:37:22 -04:00
kennethreitz 800c153e96 Merge pull request #38 from kennethreitz/bnm_tests
Tests clean up
2018-10-14 07:29:07 -04:00
Luna 71bbda0fb7 clean up 2018-10-14 12:25:15 +01:00
kennethreitz 6e6bac429a order 2018-10-14 07:24:48 -04:00
kennethreitz 1ce091a4d9 feature tour 2018-10-14 07:24:14 -04:00
Luna a8f889be74 restructured tests 2018-10-14 12:23:33 +01:00
kennethreitz 5f33c6bfee rendering a template 2018-10-14 07:23:10 -04:00
Luna 6a290c49d8 Merge remote-tracking branch 'origin' into bnm_tests 2018-10-14 12:21:23 +01:00
kennethreitz b304d5d784 real fix 2018-10-14 07:12:45 -04:00
kennethreitz cfe83b97d9 fix 2018-10-14 07:12:27 -04:00
kennethreitz 2fec2bf560 response headers 2018-10-14 07:11:14 -04:00
kennethreitz 73dc1a7839 ! 2018-10-14 07:02:22 -04:00
kennethreitz 66fe951831 python 2018-10-14 07:01:13 -04:00
kennethreitz 7991bcbf1a note 2018-10-14 06:59:26 -04:00
kennethreitz de9516563a await 2018-10-14 06:58:56 -04:00
kennethreitz 27fefb821c quickstart 2018-10-14 06:58:14 -04:00
kennethreitz c195894db9 yaml 2018-10-14 06:57:39 -04:00
kennethreitz 6777b4d370 data 2018-10-14 06:55:54 -04:00
kennethreitz 09269c22a2 fix 2018-10-14 06:46:21 -04:00
kennethreitz 2e24a2f079 cleanup 2018-10-14 06:45:23 -04:00
kennethreitz 5d9932dd61 more quickstart 2018-10-14 06:44:18 -04:00
kennethreitz 062064213a improvements to docs 2018-10-14 06:34:04 -04:00
kennethreitz a2ae3ffb2b comments 2018-10-14 06:27:13 -04:00
kennethreitz 6cb4a0a3eb fix for encodings 2018-10-14 06:26:20 -04:00
kennethreitz f17c49091f cleanup 2018-10-14 06:23:23 -04:00
kennethreitz c16afc07df raises 2018-10-14 06:23:09 -04:00
kennethreitz 1616a96b2c fix 2018-10-14 06:21:59 -04:00
kennethreitz 261601230a better tests 2018-10-14 06:21:41 -04:00
kennethreitz 453a38df54 docstrings 2018-10-14 06:08:50 -04:00
kennethreitz 5b004a849f really fix encodings 2018-10-14 06:05:41 -04:00
kennethreitz 29d811d3fd conflict 2018-10-14 06:04:44 -04:00
kennethreitz 36c5739318 fix encoding 2018-10-14 06:03:57 -04:00
kennethreitz b3f9c67d34 cli 2018-10-14 05:47:04 -04:00
kennethreitz bc8eb802f7 remove heroku example 2018-10-14 05:46:36 -04:00
kennethreitz a138eead74 Merge pull request #35 from frostming/fix-req-content
Fix request.content
2018-10-14 05:00:13 -04:00
Frost Ming a700a0e1b1 Fix request.content 2018-10-14 10:31:03 +08:00
kennethreitz 205a33a241 Merge pull request #29 from ArtemGordinsky/ignore_missing_accept_header
Don't break when "Accept" header is missing
2018-10-13 22:08:10 -04:00
kennethreitz c88fd94c8b Merge pull request #33 from javad94/master
fixed typos
2018-10-13 22:07:40 -04:00
kennethreitz a2b4e2e87c Merge pull request #30 from gdamjan/master
fix docstring to remove mention of Waitress
2018-10-13 22:07:20 -04:00
Javad 4a8f1e95ba fixed typos 2018-10-14 01:18:14 +03:30
Javad 3a847d921e fixed typo 2018-10-14 01:15:11 +03:30
Javad 806fdb9541 fixed typos 2018-10-14 01:05:29 +03:30
Luna cf1adbdb01 Merge branch 'master' into bnm_tests 2018-10-13 21:57:29 +01:00
Luna 349d08e799 added conftest 2018-10-13 21:56:52 +01:00
Damjan Georgievski d680c7ed83 fix docstring to remove mention of Waitress 2018-10-13 19:45:31 +02:00
Artem Gordinsky d4cb7a711b Don't break when "Accept" header is missing 2018-10-13 17:30:52 +02:00
kennethreitz bb6e19e7cd requirements.txt 2018-10-13 09:54:53 -04:00
kennethreitz 1c3ea53e63 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-13 09:50:19 -04:00
kennethreitz 88e17029c5 fixes 2018-10-13 09:50:13 -04:00
kennethreitz 588e91b19f fix 2018-10-13 09:46:34 -04:00
kennethreitz 8cc2e7b6f1 fix 2018-10-13 09:46:29 -04:00
kennethreitz 222353b532 version 2018-10-13 09:46:14 -04:00
kennethreitz b88b266fd5 profile 2018-10-13 09:40:33 -04:00
kennethreitz 60e6fb99af fix 2018-10-13 09:27:11 -04:00
kennethreitz 65b60e57b2 Merge pull request #27 from kennethreitz/pytest
added addopts in pytest
2018-10-13 09:26:40 -04:00
kennethreitz 16a8402bf4 api.static_url 2018-10-13 09:23:05 -04:00
Luna 5896411136 added addopts in pytest 2018-10-13 14:14:59 +01:00
kennethreitz 0bb74a7885 pytest.ini 2018-10-13 09:13:33 -04:00
kennethreitz 86dfb9231f oops 2018-10-13 09:10:51 -04:00
kennethreitz 7198ce3eb0 fomatting 2018-10-13 09:09:08 -04:00
kennethreitz 08fecf1eb2 Merge branch 'bnm_tests' 2018-10-13 09:08:16 -04:00
kennethreitz 3eda26ca94 Merge branch 'master' into bnm_tests 2018-10-13 09:07:44 -04:00
kennethreitz d907914c7c Merge pull request #25 from 0xflotus/patch-1
fixed Characteristics
2018-10-13 09:02:54 -04:00
kennethreitz 266ab48fed better tests 2018-10-13 09:03:20 -04:00
Luna 3325cffa91 added entry to gitignore 2018-10-13 14:02:17 +01:00
Luna 43469ac62a Merge branch 'master' of https://github.com/kennethreitz/responder into bnm_tests 2018-10-13 14:00:59 +01:00
0xflotus a5c953fdb6 fixed Characteristics 2018-10-13 15:00:38 +02:00
Luna 627c46e458 added test cases for routes.py 2018-10-13 13:59:46 +01:00
kennethreitz 205eb34adc cleanup 2018-10-13 08:51:01 -04:00
kennethreitz 125e14d377 responder 2018-10-13 08:50:23 -04:00
kennethreitz a51c8a700b fix 2018-10-13 08:49:15 -04:00
kennethreitz 94e0400ea1 v0.0.2 2018-10-13 08:42:20 -04:00
kennethreitz 47c5b84093 form parsing working 2018-10-13 08:41:28 -04:00
kennethreitz 8b1fbfd16d better 2018-10-13 08:24:08 -04:00
kennethreitz cceb698899 installing 2018-10-13 08:23:28 -04:00
kennethreitz 01741df10d the cake is a lie 2018-10-13 08:20:38 -04:00
kennethreitz f91ebf8baa Merge branch 'master' of github.com:kennethreitz/responder 2018-10-13 08:19:21 -04:00
kennethreitz 4dde076030 move installation up 2018-10-13 08:19:12 -04:00
kennethreitz 3491001b7f Update README.md 2018-10-13 08:15:00 -04:00
kennethreitz 2acec68649 __slots__ 2018-10-13 08:13:32 -04:00
kennethreitz 51dab27374 index.rst 2018-10-13 08:08:00 -04:00
kennethreitz 145f5041bf options 2018-10-13 08:02:17 -04:00
kennethreitz 6034505380 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-13 08:01:18 -04:00
kennethreitz 8533d74906 5042 2018-10-13 08:01:10 -04:00
kennethreitz b2ae57b982 port 2018-10-13 07:59:16 -04:00
kennethreitz 49ffe9bec9 Merge pull request #20 from aitoehigie/master
Fleshed out the benchmarks section of the README.md file
2018-10-13 07:57:16 -04:00
kennethreitz fe5d92674e google analytics 2018-10-13 07:53:13 -04:00
kennethreitz 197d28f5c7 index 2018-10-13 07:50:29 -04:00
kennethreitz cd48bb0789 update readme 2018-10-13 07:50:09 -04:00
kennethreitz 90fc411e9a : 2018-10-13 07:46:36 -04:00
kennethreitz c22b6a84aa an 2018-10-13 07:45:42 -04:00
kennethreitz 9b65642f05 async 2018-10-13 07:43:57 -04:00
kennethreitz 83547dce9c continuation? 2018-10-13 07:43:09 -04:00
kennethreitz efeecceb54 fix? 2018-10-13 07:41:35 -04:00
kennethreitz ba9b5a40d2 simplify 2018-10-13 07:37:16 -04:00
kennethreitz 47b5bda277 fix 2018-10-13 07:36:43 -04:00
kennethreitz a343b6b1b6 fix 2018-10-13 07:36:30 -04:00
kennethreitz 0fe48d3003 index 2018-10-13 07:33:19 -04:00
kennethreitz 23e3760b08 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-13 07:31:50 -04:00
kennethreitz 3d31905562 improve formats for json 2018-10-13 07:31:45 -04:00
kennethreitz 9638c5266b index.rst 2018-10-13 07:30:56 -04:00
kennethreitz ad7ce9f55a Merge pull request #23 from aaqaishtyaq/docs-badge
add documentation badge in Readme
2018-10-13 07:26:55 -04:00
kennethreitz b0baf3b85a readme 2018-10-13 07:26:18 -04:00
kennethreitz d4d3687882 readme 2018-10-13 07:24:19 -04:00
kennethreitz faf55ca191 readme 2018-10-13 07:21:52 -04:00
kennethreitz d5096a23fb async def 2018-10-13 07:06:55 -04:00
kennethreitz ed5841d201 test async function 2018-10-13 07:06:14 -04:00
kennethreitz bbfc095a00 gitignore 2018-10-13 07:00:53 -04:00
kennethreitz 0fcb68a13d bunk 2018-10-13 07:00:42 -04:00
kennethreitz f97744c098 async defenitions work 2018-10-13 06:59:23 -04:00
kennethreitz d1cfa8d27a safe_load 2018-10-13 05:55:46 -04:00
Aaqa Ishtyaq 218dcf25c1 add documentation badge in Readme 2018-10-13 12:31:18 +05:30
aitoehigie 06e06973a4 format the readme file 2018-10-13 03:24:57 +01:00
aitoehigie 6f73cfc5f2 format the readme file 2018-10-13 03:23:47 +01:00
aitoehigie 6db5bbeaee format the readme file 2018-10-13 03:22:55 +01:00
aitoehigie 6ef5077164 format the readme file 2018-10-13 03:21:53 +01:00
aitoehigie 45e1ed7022 format the readme file 2018-10-13 03:20:36 +01:00
aitoehigie c14b4535a6 format the readme file 2018-10-13 03:19:21 +01:00
aitoehigie 411631d2f8 format the readme file 2018-10-13 03:17:42 +01:00
aitoehigie f4c3690bd8 format the readme file 2018-10-13 03:15:49 +01:00
aitoehigie 56fdea6b5d update the benchmarks section 2018-10-13 03:05:12 +01:00
aitoehigie 8a5c053d39 Add a concise benchmarks section to the README.md file 2018-10-13 02:34:58 +01:00
kennethreitz 42870cfa23 memoize template rendering 2018-10-12 16:56:21 -04:00
kennethreitz 6cf256cc05 memoize routes 2018-10-12 16:48:41 -04:00
kennethreitz 9fec915f62 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-12 16:42:21 -04:00
kennethreitz f1d5ab73cd readme 2018-10-12 16:42:13 -04:00
kennethreitz cd62972945 Merge pull request #14 from taoufik07/feature/query_dict
Add a QueryDict to manage query params
2018-10-12 15:26:05 -04:00
kennethreitz 998d09170c Update README.md 2018-10-12 14:36:03 -04:00
kennethreitz 4ba57181ec Update README.md 2018-10-12 14:31:27 -04:00
kennethreitz 8b9d8bdc62 Update README.md 2018-10-12 14:30:44 -04:00
taoufik07 4291d42dc0 Merge branch 'master' into feature/query_dict 2018-10-12 18:55:17 +01:00
kennethreitz 79fcc1ce40 uvloop 2018-10-12 13:28:07 -04:00
kennethreitz bfc6778dca readme 2018-10-12 13:27:02 -04:00
kennethreitz 701e57c264 fix all the things 2018-10-12 13:25:38 -04:00
kennethreitz 163d025c0d fix tests 2018-10-12 13:15:53 -04:00
kennethreitz d9befc6d8c test 2018-10-12 13:13:13 -04:00
kennethreitz 9e50a4c241 background 2018-10-12 12:28:14 -04:00
kennethreitz 9b0cae3794 background 2018-10-12 12:26:04 -04:00
taoufik07 6160dfb2f7 Add a simple test 2018-10-12 17:01:23 +01:00
taoufik07 cd013cdb06 Add QueryDict 2018-10-12 17:01:06 +01:00
kennethreitz 26cc7c90e9 Merge branch 'asgi' of https://github.com/tomchristie/responder into asgi 2018-10-12 10:29:13 -04:00
kennethreitz f28ac3cf22 Merge pull request #12 from taoufik07/patch-1
Typo : nicities -> niceties
2018-10-12 10:27:37 -04:00
kennethreitz 58fec4b082 Merge pull request #13 from taoufik07/patch-2
Fix resolve_hello indentation
2018-10-12 10:27:26 -04:00
Taoufik b91805a5df Fix graphql test 2018-10-12 15:27:04 +01:00
Tom Christie 0fa0df1bdf Add Pipfile 2018-10-12 15:18:13 +01:00
Tom Christie 3f7cacee3e Updated Pipfile.lock with latest pipenv 2018-10-12 15:12:44 +01:00
Taoufik 72637fd650 FIx resolve_hello indentation and use f-string 2018-10-12 15:12:17 +01:00
Taoufik aba1284f8e Typo : nicities -> niceties 2018-10-12 15:03:49 +01:00
Tom Christie 179e1dc9e5 Update Pipfile.lock 2018-10-12 15:02:08 +01:00
kennethreitz 75879a494e Merge pull request #7 from CianciuStyles/fix-familar-typo
Change "familar" into "familiar"
2018-10-12 09:54:46 -04:00
kennethreitz 73b1ea4713 Merge pull request #10 from OdinTech3/patch-1
Fixed a mispelt word
2018-10-12 09:54:30 -04:00
kennethreitz 55dc991c13 Merge pull request #11 from MichaelPereira/patch-1
Small typo : cooke -> cookie
2018-10-12 09:54:20 -04:00
Tom Christie c30316588a Form parsing requires Starlette 102 2018-10-12 14:53:55 +01:00
Tom Christie db5d6e7481 Lowercase method for on_{method} 2018-10-12 14:47:20 +01:00
Tom Christie f8d52f58d4 Drop form parsing support until Starlette PR #102 is in 2018-10-12 14:36:44 +01:00
Tom Christie 227ee499e4 request.params for API compatability with existing codebase/tests 2018-10-12 14:35:31 +01:00
Michael Pereira dcdaf6a674 Small typo : cooke -> cookie 2018-10-12 09:32:38 -04:00
Tom Christie d524ba3a37 Merge remote-tracking branch 'upstream/master' into asgi 2018-10-12 14:28:33 +01:00
Nick Spirit da5e288476 Fixed a mispelt word 2018-10-12 09:27:52 -04:00
kennethreitz baad7cd60d fix rst 2018-10-12 09:15:03 -04:00
kennethreitz e9d6fc33fd rst 2018-10-12 09:13:53 -04:00
kennethreitz c2fa0899e9 responder 2018-10-12 09:12:34 -04:00
kennethreitz 2dc09ec1f2 build status 2018-10-12 09:09:42 -04:00
kennethreitz fba640976f gzip 2018-10-12 09:04:33 -04:00
kennethreitz 8e7df61a73 fixes 2018-10-12 09:00:52 -04:00
Tom Christie 41776cf2df Initial pass at ASGI support 2018-10-12 13:57:22 +01:00
kennethreitz 23983f0b75 hacktoberfest 2018-10-12 08:55:27 -04:00
kennethreitz 84b457ede5 fix 2018-10-12 08:31:46 -04:00
kennethreitz a906e0bf0c Merge branch 'master' of github.com:kennethreitz/responder 2018-10-12 08:26:28 -04:00
kennethreitz 3db1aad96a hacks 2018-10-12 08:26:20 -04:00
kennethreitz 9c909e7a2c Update README.md 2018-10-12 08:19:38 -04:00
kennethreitz ad2ef7cb33 readme improvements 2018-10-12 08:17:56 -04:00
kennethreitz c851510ca9 fix travis 2018-10-12 08:13:35 -04:00
kennethreitz 71a21c2059 license 2018-10-12 08:12:54 -04:00
kennethreitz d90537eb8d README 2018-10-12 08:12:02 -04:00
kennethreitz 8266a15df5 travis 2018-10-12 08:08:05 -04:00
kennethreitz 9fce286f92 working again 2018-10-12 08:06:32 -04:00
kennethreitz fc244922cc endpoint 2018-10-12 08:03:36 -04:00
kennethreitz fce87fe20c better docs 2018-10-12 08:03:15 -04:00
kennethreitz d420358248 fix 2018-10-12 08:01:23 -04:00
kennethreitz be829ff0ae requirements 2018-10-12 07:59:31 -04:00
kennethreitz 0c9e224d45 fix 2018-10-12 07:58:14 -04:00
kennethreitz 58158c4d2b 3.6 2018-10-12 07:47:41 -04:00
kennethreitz b1e4222c93 requirements for builds 2018-10-12 07:46:12 -04:00
kennethreitz 70e5a016a6 v0.0.1 2018-10-12 07:41:56 -04:00
kennethreitz 6d7e7809a4 models 2018-10-12 07:39:30 -04:00
kennethreitz 4afc1ced93 python 3.7 2018-10-12 07:37:52 -04:00
kennethreitz 3c1807f04f docstrings 2018-10-12 07:35:36 -04:00
kennethreitz 6e9adac871 changes on the horizon 2018-10-12 07:19:29 -04:00
kennethreitz 2c3a3b2e17 docstrings 2018-10-12 07:02:35 -04:00
kennethreitz c070cc3f1a web service 2018-10-12 06:58:00 -04:00
kennethreitz d47caebc97 space for methods 2018-10-12 06:56:43 -04:00
kennethreitz 224c2bbb2b yellow 2018-10-12 06:55:35 -04:00
kennethreitz 48f1a0545e frontend 2018-10-12 06:53:38 -04:00
kennethreitz ac5146dbce fixes 2018-10-12 06:52:27 -04:00
kennethreitz 4fa82f4d7a index 2018-10-12 06:51:43 -04:00
kennethreitz 4bdd3f9138 lightgrey 2018-10-12 06:50:41 -04:00
kennethreitz 86bffbf62d italics 2018-10-12 06:49:53 -04:00
kennethreitz f7b0fb3f66 grey 2018-10-12 06:47:50 -04:00
kennethreitz 971be488d5 hacks 2018-10-12 06:47:16 -04:00
kennethreitz f415e9814c important 2018-10-12 06:44:47 -04:00
kennethreitz 01575a0b8d more colors 2018-10-12 06:43:31 -04:00
kennethreitz a77492dae1 changes 2018-10-12 06:42:39 -04:00
kennethreitz 080d6d30da color 2018-10-12 06:41:53 -04:00
kennethreitz ec9b20f87c class in yellow 2018-10-12 06:39:48 -04:00
kennethreitz 7aa405c87d lightgrey 2018-10-12 06:38:52 -04:00
kennethreitz af6257d364 cleanup 2018-10-12 06:37:06 -04:00
kennethreitz f930cbb2c9 grey 2018-10-12 06:35:52 -04:00
kennethreitz 6a5a22f035 hacks 2018-10-12 06:34:10 -04:00
kennethreitz 53f87e5def hacks 2018-10-12 06:33:28 -04:00
kennethreitz a00687cc0f hacks 2018-10-12 06:32:49 -04:00
kennethreitz d1d66c0e78 hacks 2018-10-12 06:31:53 -04:00
kennethreitz ee51f50809 fixes 2018-10-12 06:30:32 -04:00
kennethreitz b067da2a1c hacks 2018-10-12 06:27:43 -04:00
kennethreitz 3db2c00cd8 read the docs 2018-10-12 06:25:24 -04:00
kennethreitz 2f52ccbe4e responder 2018-10-12 06:22:33 -04:00
Stefano Cianciulli 25e9888438 Change "familar" into "familiar" 2018-10-12 11:00:59 +01:00
kennethreitz fabe7b9427 css 2018-10-12 05:32:22 -04:00
kennethreitz 9e464c394d Merge branch 'master' of github.com:kennethreitz/responder 2018-10-12 05:25:08 -04:00
kennethreitz dcaf9b61d4 cleanup 2018-10-12 05:24:44 -04:00
kennethreitz ecf0b2e57b Merge pull request #6 from 0x49D1/master
Update README.md
2018-10-12 05:22:20 -04:00
Dmitry Pursanov df32660754 Update README.md
Just a several small spelling fixes
2018-10-12 12:48:27 +04:00
kennethreitz 8bf795c8e4 typo 2018-10-11 22:45:10 -04:00
kennethreitz 0fc765a1fd .css 2018-10-11 22:22:41 -04:00
kennethreitz 6e7d97e5c0 hacks 2018-10-11 22:20:54 -04:00
kennethreitz a33ac8ed5f fonts 2018-10-11 22:15:08 -04:00
kennethreitz 7ca264fabd hr 2018-10-11 22:11:36 -04:00
kennethreitz e75f195f7c hacks 2018-10-11 22:10:06 -04:00
kennethreitz d3efa8b80d font haqcks 2018-10-11 22:08:56 -04:00
kennethreitz 82b78b6022 fonts 2018-10-11 22:00:12 -04:00
kennethreitz d8d1787e6f not all the time 2018-10-11 21:08:42 -04:00
kennethreitz d40cad2064 fix 2018-10-11 20:58:41 -04:00
kennethreitz cf323db503 i got it dude 2018-10-11 19:51:17 -04:00
kennethreitz 39d9b05455 cleaned up format 2018-10-11 19:43:50 -04:00
kennethreitz 475c7a9571 new media api 2018-10-11 19:40:21 -04:00
kennethreitz 7c4b6cf4f7 media 2018-10-11 19:22:54 -04:00
kennethreitz 697807c2d7 more tests 2018-10-11 19:11:54 -04:00
kennethreitz 85d900727b Merge branch 'master' of github.com:kennethreitz/responder 2018-10-11 18:54:07 -04:00
kennethreitz fc0d811740 fixes 2018-10-11 18:53:51 -04:00
kennethreitz 513867d242 Update README.md 2018-10-11 18:22:39 -04:00
kennethreitz 07ba75d9d5 changes 2018-10-11 16:44:56 -04:00
kennethreitz 6fa5f9af0c typography 2018-10-11 16:41:46 -04:00
kennethreitz 3c796b95fd models 2018-10-11 15:26:34 -04:00
kennethreitz 6de212d4bc fix sidebars 2018-10-11 14:08:47 -04:00
kennethreitz bb539c4d28 fix 2018-10-11 14:06:44 -04:00
kennethreitz f5d491667e index 2018-10-11 14:06:21 -04:00
kennethreitz 042d9ebc6c tagline 2018-10-11 14:05:46 -04:00
kennethreitz ac69ccae5e index 2018-10-11 14:04:28 -04:00
kennethreitz 940ab9d762 better 2018-10-11 13:57:55 -04:00
kennethreitz 00bfdf0e3e less is more 2018-10-11 13:23:17 -04:00
kennethreitz f3bc57a566 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-11 13:21:51 -04:00
kennethreitz 50c9bc60f9 fixes 2018-10-11 13:21:42 -04:00
kennethreitz ba384bb12a Update README.md 2018-10-11 13:18:58 -04:00
kennethreitz fe9184048c Merge branch 'master' of github.com:kennethreitz/responder 2018-10-11 13:18:48 -04:00
kennethreitz 90083f945e cleanup 2018-10-11 13:18:40 -04:00
kennethreitz eee17ca20b Update README.md 2018-10-11 13:14:28 -04:00
kennethreitz b2d47abacd Update README.md 2018-10-11 13:13:56 -04:00
kennethreitz 548fb229af Update README.md 2018-10-11 13:13:22 -04:00
kennethreitz c00b259c43 Update README.md 2018-10-11 13:11:50 -04:00
kennethreitz 2dbd6ac451 small 2018-10-11 13:11:34 -04:00
kennethreitz aab82baad0 Update README.md 2018-10-11 13:11:20 -04:00
kennethreitz ea2c5c3025 docs 2018-10-11 12:59:27 -04:00
kennethreitz b391b5622f basics 2018-10-11 12:59:20 -04:00
kennethreitz ea3cb8aa7b artboards 2018-10-11 12:51:58 -04:00
kennethreitz 6e125f5f47 logos 2018-10-11 12:48:59 -04:00
kennethreitz 554500a314 assets 2018-10-11 12:26:57 -04:00
kennethreitz f1c9de7ace readme 2018-10-11 12:24:01 -04:00
kennethreitz 3bcfe89f2a updates to redirects 2018-10-11 12:22:33 -04:00
kennethreitz 151c7bd342 fix 2018-10-11 12:14:20 -04:00
kennethreitz b8d569129e more complex example 2018-10-11 12:13:53 -04:00
kennethreitz b421e2e925 cleanup 2018-10-11 12:09:12 -04:00
kennethreitz 79b09a5ae5 oh yeah 2018-10-11 12:07:35 -04:00
kennethreitz b0d3c2de09 readme 2018-10-11 11:58:11 -04:00
kennethreitz 3201a46003 switch order 2018-10-11 11:56:00 -04:00
kennethreitz 538c72f5bd dict 2018-10-11 11:55:12 -04:00
kennethreitz 6efe28bd54 fix 2018-10-11 11:54:02 -04:00
kennethreitz 7eaa95b7ee cleanup 2018-10-11 11:52:32 -04:00
kennethreitz d2f8b41e25 fix 2018-10-11 11:51:29 -04:00
kennethreitz 034aa19564 contributors 2018-10-11 11:50:47 -04:00
kennethreitz bd049d6263 Merge pull request #2 from serhii73/patch-1
Update test_responder.py
2018-10-11 11:48:07 -04:00
kennethreitz ffb7468b44 Merge pull request #3 from nicoddemus/patch-1
Fix typo in README
2018-10-11 11:47:53 -04:00
kennethreitz 6dfbde27ca fix 2018-10-11 11:47:48 -04:00
kennethreitz c6c0197d86 Merge branch 'master' into patch-1 2018-10-11 11:47:47 -04:00
kennethreitz 678596ace4 readme 2018-10-11 11:41:22 -04:00
kennethreitz 9295525b92 practice what you preach 2018-10-11 11:39:25 -04:00
kennethreitz fde2d5415f more usage 2018-10-11 11:38:26 -04:00
kennethreitz fac80383d9 grip 2018-10-11 11:37:05 -04:00
kennethreitz f4cfe5639a blend 2018-10-11 11:36:07 -04:00
kennethreitz 87749e4288 sentence 2018-10-11 11:35:21 -04:00
kennethreitz 787e056b7f python highlighting 2018-10-11 11:33:40 -04:00
kennethreitz 62bd3d905b days ago 2018-10-11 11:31:57 -04:00
kennethreitz 2122f1ef1c code example 2018-10-11 11:31:17 -04:00
Bruno Oliveira 0bed48e756 Fix typo in README 2018-10-11 12:24:58 -03:00
kennethreitz f2b2128675 fix server 2018-10-11 11:12:22 -04:00
kennethreitz c0c5770a89 tests 2018-10-11 10:52:13 -04:00
kennethreitz e7dc55edf5 more tests 2018-10-11 10:30:46 -04:00
kennethreitz 83fa6d6897 parameterized routes working 2018-10-11 10:29:15 -04:00
serhii73 5d636ee9c4 Update test_responder.py
Fix order import
2018-10-11 17:01:35 +03:00
kennethreitz 1fdda366dd tests 2018-10-11 06:37:02 -04:00
kennethreitz 671499bb43 working order 2018-10-10 11:07:11 -04:00
kennethreitz 749a7efdef templates working 2018-10-10 07:48:00 -04:00
kennethreitz 90eecdaa84 mount other wsgi apps 2018-10-10 07:14:29 -04:00
kennethreitz 39f9cbfdbb note 2018-10-10 06:42:57 -04:00
kennethreitz 5794ba890c README 2018-10-09 08:06:42 -04:00
kennethreitz 4a3bf3a1aa fixes 2018-10-09 08:06:22 -04:00
kennethreitz 1427ca0a35 pipfile cleanup 2018-10-09 07:39:05 -04:00
kennethreitz 5b7b0fcb8e cleanups 2018-10-09 07:33:38 -04:00
kennethreitz 6ac48de44c I 2018-10-09 07:30:35 -04:00
kennethreitz 674efa3052 readme 2018-10-09 07:30:19 -04:00
kennethreitz 152a7153d0 cargo culting 2018-10-09 07:28:36 -04:00
kennethreitz 9d1f7f8086 test client 2018-10-09 07:19:33 -04:00
kennethreitz f4c252e2d9 readme 2018-10-09 07:15:17 -04:00
kennethreitz abb8153b64 files 2018-10-09 06:45:02 -04:00
kennethreitz e84f8510c8 responses 2018-10-08 07:31:56 -04:00
160 changed files with 57300 additions and 17207 deletions
-4
View File
@@ -1,4 +0,0 @@
# 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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
github: kennethreitz
thanks_dev: kennethreitz
custom: https://cash.app/$KennethReitz
+16
View File
@@ -0,0 +1,16 @@
# 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"
+42
View File
@@ -0,0 +1,42 @@
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
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
+56
View File
@@ -0,0 +1,56 @@
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.9",
"3.10",
"3.11",
"3.12",
"3.13",
]
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
+19
View File
@@ -0,0 +1,19 @@
.venv*
.vscode/
.cache
.idea
.python-version
.coverage
.pytest_cache
.DS_Store
coverage.xml
.coverage*
__pycache__
tests/__pycache__
build
responder.egg-info/
dist/
app.py
app2.py
+33
View File
@@ -0,0 +1,33 @@
# .readthedocs.yml
# Read the Docs configuration file
# Details
# - https://docs.readthedocs.io/en/stable/config-file/v2.html
# Required
version: 2
build:
os: "ubuntu-24.04"
tools:
python: "3.12"
python:
install:
- method: pip
path: .
extra_requirements:
- docs
sphinx:
configuration: docs/source/conf.py
# Use standard HTML builder.
builder: html
# Fail on all warnings to avoid broken references.
fail_on_warning: true
# Optionally build your docs in additional formats such as PDF
#formats:
# - pdf
+2 -160
View File
@@ -5,159 +5,7 @@ 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
- Built-in structured logging with per-request context (`enable_logging=True`)
- `api.log` — always-available logger, enriched with request context when logging is enabled
- Automatic access logging with timing: `GET /path → 200 (1.2ms)`
- Request ID generation/forwarding via `X-Request-ID` header
- `contextvars`-based request context (ID, method, path, client IP) on every log record
- `responder.ext.logging` module: `get_logger()`, `RequestContext`, `RequestContextFilter`
- CLAUDE.md project guide and `/release` command
- Version number in docs sidebar
### Changed
- Comprehensive documentation improvements across all pages
- Deployment: health checks, Docker Compose, Caddy, Procfile, production checklist
- API reference: usage examples for every class
- Feature tour: Pydantic validation, content negotiation, structured logging sections
- Tutorials: modernized SQLAlchemy to `mapped_column()`, fixed deprecated `datetime.utcnow()`,
WebSocket `WebSocketDisconnect` handling, role-based auth, auth strategy guide
- Testing: rate limiting and WSGI mount examples
- Middleware: pure ASGI middleware example
- Quickstart: links to all tutorials
- Sandbox: full rewrite with project layout
- Docker example uses `uv` instead of pip
- Backlog updated: removed implemented features, replaced HTTP/2 server push with dependency injection
### Removed
- `uv.lock` — this is a library, not an application
## [v3.5.0] - 2026-03-24
### Added
- CI validation for Python 3.14, 3.14 free-threaded, and PyPy 3.11
- Marimo notebook mounting docs and example
- Type annotations for `routes.py`
### Changed
- Replaced deprecated `asyncio.iscoroutinefunction` with `inspect.iscoroutinefunction` ahead of Python 3.16 removal
- Narrowed broad `except Exception` to specific exceptions in response model serialization and websocket chat example
- Improved GraphQL API interface with expanded test coverage
- Code formatting cleanup via pyproject-fmt and ruff
- Dropped Python 3.9 from CI
### Fixed
- WSGI mount returning 400 when requesting the exact mount root path
- Werkzeug 3.1.7 compatibility for trusted host validation in tests
- `future.result` bare property access in background task test (now properly calls `future.result()`)
- OpenAPI template packaging and static file serving
- RST title underline warning breaking docs CI
### Removed
- Read the Docs configuration (docs hosted on GitHub Pages)
## [v3.4.0] - 2026-03-22
### Changed
- Upgraded to Starlette 1.0
- Added comprehensive docstrings across the codebase
- Expanded API reference documentation
## [v3.3.0] - 2026-03-22
### Added
- Full documentation rewrite: tutorials for REST APIs, SQLAlchemy, Flask migration
- Auth, WebSocket, middleware, and configuration guides
- Testing docs with prose, examples, and tips
- GitHub Pages deployment for docs
### Changed
- Reworked homepage prose
- Rewrote CLI and API reference docs
## [v3.2.0] - 2026-03-22
### Added
- Pydantic auto-validation: `request_model` validates input, returns 422 on failure
- Pydantic auto-serialization: `response_model` strips extra fields from responses
- Server-Sent Events: `@resp.sse` for real-time streaming
- `resp.stream_file()` for streaming large files without loading into memory
- `@api.after_request()` hooks
- `api.group("/prefix")` for route groups and API versioning
- `api.graphql("/path", schema=schema)` one-liner GraphQL setup
- `api = responder.API(request_id=True)` for automatic request ID generation
- Built-in rate limiter: `RateLimiter(requests=100, period=60).install(api)`
- MessagePack format support: `await req.media("msgpack")`
- `req.is_json`, `req.path_params`, `req.client` properties
- `api.exception_handler()` decorator for custom error handling
- Lifespan context manager support
- `uuid` and `path` route convertors
- PEP 561 `py.typed` marker
- Pydantic support for OpenAPI schema generation
### Changed
- Dependencies flattened: `pip install responder` gets everything
- Core deps reduced to starlette + uvicorn
- TestClient lazy-loaded (no httpx import in production)
- Before-request hooks can short-circuit by setting status code
- Removed poethepoet task runner
### Fixed
- Multipart parser losing headers when parts have multiple headers
- `url_for()` with typed route params (`{id:int}`)
- `resp.body` encoding crash on bytes content
- GraphQL text query missing `await`
- Streaming responses not sending Content-Type headers
- Python 3.9 compatibility for union type syntax
## [Unreleased]
## [v3.0.0] - 2026-03-22
@@ -525,13 +373,7 @@ 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
[v3.3.0]: https://github.com/kennethreitz/responder/compare/v3.2.0..v3.3.0
[v3.2.0]: https://github.com/kennethreitz/responder/compare/v3.0.0..v3.2.0
[unreleased]: https://github.com/kennethreitz/responder/compare/v3.0.0..HEAD
[v3.0.0]: https://github.com/kennethreitz/responder/compare/v2.0.5..v3.0.0
[v2.0.5]: https://github.com/kennethreitz/responder/compare/v2.0.4..v2.0.5
[v2.0.4]: https://github.com/kennethreitz/responder/compare/v2.0.3..v2.0.4
-1
View File
@@ -1 +0,0 @@
responder.kennethreitz.org
+178
View File
@@ -0,0 +1,178 @@
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
+109
View File
@@ -0,0 +1,109 @@
# 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.9+.
## 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
-92
View File
@@ -1,92 +0,0 @@
<!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 &#8212; 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">
&#169;2018-2026, Kenneth Reitz.
</div>
</body>
</html>
-777
View File
@@ -1,777 +0,0 @@
<!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 &#8212; 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">&quot;API&quot;</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">&quot;&quot;&quot;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&#39;t already exist.</span>
<span class="sd"> :param templates_dir: The directory to use for templates. Will be created for you if it doesn&#39;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"> &quot;&quot;&quot;</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">&quot;/schema.yml&quot;</span><span class="p">,</span>
<span class="n">static_dir</span><span class="o">=</span><span class="s2">&quot;static&quot;</span><span class="p">,</span>
<span class="n">static_route</span><span class="o">=</span><span class="s2">&quot;/static&quot;</span><span class="p">,</span>
<span class="n">templates_dir</span><span class="o">=</span><span class="s2">&quot;templates&quot;</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">&quot;&quot;&quot;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. ``&quot;1.0&quot;``).</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&#39;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. ``&quot;3.0.2&quot;``). Enables OpenAPI schema generation.</span>
<span class="sd"> :param openapi_route: The URL path for the OpenAPI schema (default ``&quot;/schema.yml&quot;``).</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 ``&quot;/static&quot;``).</span>
<span class="sd"> :param templates_dir: Directory for Jinja2 templates (default ``&quot;templates&quot;``).</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. ``&quot;/docs&quot;``). 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. ``[&quot;example.com&quot;]``). Defaults to ``[&quot;*&quot;]``.</span>
<span class="sd"> :param openapi_theme: Documentation UI theme: ``&quot;swagger_ui&quot;``, ``&quot;redoc&quot;``, ``&quot;rapidoc&quot;``, or ``&quot;elements&quot;``.</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"> &quot;&quot;&quot;</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">&quot;&quot;</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">&quot;*&quot;</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">&quot;The dependencies for the OpenAPI extension are not installed. &quot;</span>
<span class="s2">&quot;Install them using: pip install responder&quot;</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">&quot;X-Request-ID&quot;</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">&quot;X-Request-ID&quot;</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">&quot;responder.app&quot;</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">&quot;responder.app&quot;</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">&quot;&quot;&quot;A test client connected to the ASGI app. Lazily initialized.&quot;&quot;&quot;</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">&quot;&quot;&quot;The Starlette ``StaticFiles`` application for serving static assets.&quot;&quot;&quot;</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">&quot;_static_app&quot;</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">&quot;&quot;&quot;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 &quot;Authorization&quot; not in req.headers:</span>
<span class="sd"> resp.status_code = 401</span>
<span class="sd"> resp.media = {&quot;error&quot;: &quot;unauthorized&quot;}</span>
<span class="sd"> &quot;&quot;&quot;</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">&quot;&quot;&quot;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[&quot;X-Request-ID&quot;] = str(uuid.uuid4())</span>
<span class="sd"> &quot;&quot;&quot;</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">&quot;&quot;&quot;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"> &quot;&quot;&quot;</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">&quot;&quot;&quot;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 = {&quot;error&quot;: str(exc)}</span>
<span class="sd"> &quot;&quot;&quot;</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">&quot;_exception_handlers&quot;</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">&quot;app&quot;</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">&quot;&quot;&quot;</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(&quot;Pet&quot;)</span>
<span class="sd"> class PetSchema(Schema):</span>
<span class="sd"> name = fields.Str()</span>
<span class="sd"> &quot;&quot;&quot;</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">&quot;&quot;&quot;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"> &quot;&quot;&quot;</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">&quot;&quot;&quot;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 &quot;static/index.html&quot;.</span>
<span class="sd"> Also, it will become a default route.</span>
<span class="sd"> :param methods: Optional list of HTTP methods (e.g. ``[&quot;GET&quot;, &quot;POST&quot;]``).</span>
<span class="sd"> &quot;&quot;&quot;</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">&quot;index.html&quot;</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">&quot;Not found.&quot;</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">&quot;&quot;&quot;</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"> &quot;&quot;&quot;</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">&quot;&quot;&quot;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(&#39;startup&#39;)</span>
<span class="sd"> async def open_database_connection_pool():</span>
<span class="sd"> ...</span>
<span class="sd"> @api.on_event(&#39;shutdown&#39;)</span>
<span class="sd"> async def close_database_connection_pool():</span>
<span class="sd"> ...</span>
<span class="sd"> &quot;&quot;&quot;</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">&quot;&quot;&quot;Adds an event handler to the API.</span>
<span class="sd"> :param event_type: A string in (&quot;startup&quot;, &quot;shutdown&quot;)</span>
<span class="sd"> :param handler: The function to run. Can be either a function or a coroutine.</span>
<span class="sd"> &quot;&quot;&quot;</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">&quot;&quot;&quot;Decorator for creating new routes around function and class definitions.</span>
<span class="sd"> Usage::</span>
<span class="sd"> @api.route(&quot;/hello&quot;)</span>
<span class="sd"> def hello(req, resp):</span>
<span class="sd"> resp.text = &quot;hello, world!&quot;</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(&quot;/items&quot;, methods=[&quot;POST&quot;],</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 = {&quot;id&quot;: 1, **data}</span>
<span class="sd"> &quot;&quot;&quot;</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">&quot;openapi&quot;</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">&quot;openapi&quot;</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">&quot;/graphql&quot;</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">&quot;&quot;&quot;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=&quot;stranger&quot;))</span>
<span class="sd"> def resolve_hello(self, info, name):</span>
<span class="sd"> return f&quot;Hello {name}&quot;</span>
<span class="sd"> api.graphql(&quot;/graphql&quot;, 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"> &quot;&quot;&quot;</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">&quot;&quot;&quot;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&#39;t be parameterized).</span>
<span class="sd"> :param app: The other WSGI / ASGI app.</span>
<span class="sd"> &quot;&quot;&quot;</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">&quot;http://;&quot;</span><span class="p">):</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;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"> &quot;&quot;&quot;</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">&quot;&quot;&quot;Given an endpoint, returns a rendered URL for its route.</span>
<span class="sd"> :param endpoint: The route endpoint you&#39;re searching for.</span>
<span class="sd"> :param params: Data to pass into the URL generator (for parameterized URLs).</span>
<span class="sd"> &quot;&quot;&quot;</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">&quot;&quot;&quot;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"> &quot;&quot;&quot;</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">&quot;&quot;&quot;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"> &quot;&quot;&quot;</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">&quot;&quot;&quot;</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"> &quot;&quot;&quot;</span> <span class="c1"># noqa: E501</span>
<span class="k">if</span> <span class="s2">&quot;PORT&quot;</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">&quot;0.0.0.0&quot;</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">&quot;PORT&quot;</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">&quot;127.0.0.1&quot;</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">&quot;log_level&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;debug&quot;</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">&quot;&quot;&quot;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"> &quot;&quot;&quot;</span> <span class="c1"># noqa: E501</span>
<span class="k">if</span> <span class="s2">&quot;debug&quot;</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">&quot;debug&quot;</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">&quot;&quot;&quot;Create a route group with a shared URL prefix.</span>
<span class="sd"> Usage::</span>
<span class="sd"> v1 = api.group(&quot;/v1&quot;)</span>
<span class="sd"> @v1.route(&quot;/users&quot;)</span>
<span class="sd"> def list_users(req, resp):</span>
<span class="sd"> resp.media = []</span>
<span class="sd"> @v1.route(&quot;/users/{id:int}&quot;)</span>
<span class="sd"> def get_user(req, resp, *, id):</span>
<span class="sd"> resp.media = {&quot;id&quot;: id}</span>
<span class="sd"> &quot;&quot;&quot;</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">&quot;&quot;&quot;A group of routes with a shared URL prefix.&quot;&quot;&quot;</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">&quot;/&quot;</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">&quot;</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">&quot;</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">
&#169;2018-2026, Kenneth Reitz.
</div>
</body>
</html>
-178
View File
@@ -1,178 +0,0 @@
<!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 &#8212; 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">&quot;BackgroundQueue&quot;</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">&quot;&quot;&quot;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(&quot;user@example.com&quot;, &quot;Hello&quot;)</span>
<span class="sd"> # Direct submission</span>
<span class="sd"> future = api.background.run(send_email, &quot;user@example.com&quot;, &quot;Hello&quot;)</span>
<span class="sd"> # As a callable (supports async functions)</span>
<span class="sd"> await api.background(send_email, &quot;user@example.com&quot;, &quot;Hello&quot;)</span>
<span class="sd"> &quot;&quot;&quot;</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">&quot;&quot;&quot;Create a new background queue.</span>
<span class="sd"> :param n: Number of worker threads. Defaults to CPU count.</span>
<span class="sd"> &quot;&quot;&quot;</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">&quot;&quot;&quot;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"> &quot;&quot;&quot;</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">&quot;&quot;&quot;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"> &quot;&quot;&quot;</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">-&gt;</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">
&#169;2018-2026, Kenneth Reitz.
</div>
</body>
</html>
-168
View File
@@ -1,168 +0,0 @@
<!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 &#8212; 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">&quot;&quot;&quot;Simple in-memory rate limiter for Responder.&quot;&quot;&quot;</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">&quot;&quot;&quot;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"> &quot;&quot;&quot;</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">&quot;X-Forwarded-For&quot;</span><span class="p">,</span> <span class="s2">&quot;unknown&quot;</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">&gt;</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">&quot;&quot;&quot;Check rate limit. Sets 429 status if exceeded.&quot;&quot;&quot;</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">&gt;=</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">&quot;error&quot;</span><span class="p">:</span> <span class="s2">&quot;rate limit exceeded&quot;</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">&quot;Retry-After&quot;</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">&quot;X-RateLimit-Limit&quot;</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">&quot;X-RateLimit-Remaining&quot;</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">&quot;&quot;&quot;Install as a before_request hook on the API.&quot;&quot;&quot;</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">
&#169;2018-2026, Kenneth Reitz.
</div>
</body>
</html>
-755
View File
@@ -1,755 +0,0 @@
<!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 &#8212; 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">&quot;Request&quot;</span><span class="p">,</span> <span class="s2">&quot;Response&quot;</span><span class="p">,</span> <span class="s2">&quot;QueryDict&quot;</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">&quot;&quot;&quot;A case-insensitive dict for HTTP headers.&quot;&quot;&quot;</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">&quot;&quot;&quot;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"> &quot;&quot;&quot;</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">&quot;&quot;&quot;</span>
<span class="sd"> Return the last data value for this key, or [] if it&#39;s an empty list;</span>
<span class="sd"> raise KeyError if not found.</span>
<span class="sd"> &quot;&quot;&quot;</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">&quot;&quot;&quot;</span>
<span class="sd"> Return the last data value for the passed key. If key doesn&#39;t exist</span>
<span class="sd"> or value is an empty list, return `default`.</span>
<span class="sd"> &quot;&quot;&quot;</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">&quot;&quot;&quot;</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"> &quot;&quot;&quot;</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">&quot;&quot;&quot;</span>
<span class="sd"> Return the list of values for the key. If key doesn&#39;t exist, return a</span>
<span class="sd"> default value.</span>
<span class="sd"> &quot;&quot;&quot;</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">&quot;&quot;&quot;</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"> &quot;&quot;&quot;</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">&quot;&quot;&quot;</span>
<span class="sd"> Yield (key, value) pairs, where value is the the list.</span>
<span class="sd"> &quot;&quot;&quot;</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">&quot;&quot;&quot;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"> &quot;&quot;&quot;</span>
<span class="vm">__slots__</span> <span class="o">=</span> <span class="p">[</span>
<span class="s2">&quot;_starlette&quot;</span><span class="p">,</span>
<span class="s2">&quot;formats&quot;</span><span class="p">,</span>
<span class="s2">&quot;_headers&quot;</span><span class="p">,</span>
<span class="s2">&quot;_encoding&quot;</span><span class="p">,</span>
<span class="s2">&quot;api&quot;</span><span class="p">,</span>
<span class="s2">&quot;_content&quot;</span><span class="p">,</span>
<span class="s2">&quot;_cookies&quot;</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">&quot;&quot;&quot;The session data, in dict form, from the Request.&quot;&quot;&quot;</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">&quot;&quot;&quot;A case-insensitive dictionary, containing all headers sent in the Request.&quot;&quot;&quot;</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">&quot;&quot;&quot;The MIME type of the request body, from the ``Content-Type`` header.&quot;&quot;&quot;</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">&quot;Content-Type&quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</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">&quot;&quot;&quot;Returns ``True`` if the request content type is JSON.&quot;&quot;&quot;</span>
<span class="k">return</span> <span class="s2">&quot;json&quot;</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">&quot;&quot;&quot;The incoming HTTP method used for the request, lower-cased.&quot;&quot;&quot;</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">&quot;&quot;&quot;The full URL of the Request, query parameters and all.&quot;&quot;&quot;</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">&quot;&quot;&quot;The parsed URL of the Request.&quot;&quot;&quot;</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">&quot;&quot;&quot;The cookies sent in the Request, as a dictionary.&quot;&quot;&quot;</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">&quot;Cookie&quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</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">&quot;&quot;&quot;A dictionary of the parsed query parameters used for the Request.&quot;&quot;&quot;</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">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;The path parameters extracted from the URL route.&quot;&quot;&quot;</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">&quot;&quot;&quot;The client&#39;s address as a (host, port) named tuple, or None.&quot;&quot;&quot;</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">-&gt;</span> <span class="n">State</span><span class="p">:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</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"> &quot;&quot;&quot;</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">&quot;&quot;&quot;The encoding of the Request&#39;s body. Can be set, manually. Must be awaited.&quot;&quot;&quot;</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">&quot;&quot;&quot;The Request body, as bytes. Must be awaited.&quot;&quot;&quot;</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">&quot;&quot;&quot;The Request body, as unicode. Must be awaited.&quot;&quot;&quot;</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">&quot;Encoding&quot;</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">&quot;Encoding&quot;</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">&quot;&quot;&quot;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"> &quot;&quot;&quot;</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">&quot;encoding&quot;</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">&quot;&quot;&quot;``True`` if the request was made over HTTPS.&quot;&quot;&quot;</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">&quot;https&quot;</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">&quot;&quot;&quot;Returns ``True`` if the incoming Request accepts the given ``content_type``.&quot;&quot;&quot;</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">&quot;Accept&quot;</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">&quot;&quot;&quot;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"> &quot;&quot;&quot;</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">&quot;yaml&quot;</span> <span class="k">if</span> <span class="s2">&quot;yaml&quot;</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">&quot;json&quot;</span> <span class="c1"># noqa: A001</span>
<span class="nb">format</span> <span class="o">=</span> <span class="s2">&quot;form&quot;</span> <span class="k">if</span> <span class="s2">&quot;form&quot;</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">&quot;Unable to process data in &#39;</span><span class="si">{</span><span class="nb">format</span><span class="si">}</span><span class="s2">&#39; format&quot;</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">&quot;Invalid &#39;format&#39; argument: </span><span class="si">{</span><span class="nb">format</span><span class="si">}</span><span class="s2">&quot;</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">&quot;&quot;&quot;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"> &quot;&quot;&quot;</span> <span class="c1"># noqa: E501</span>
<span class="vm">__slots__</span> <span class="o">=</span> <span class="p">[</span>
<span class="s2">&quot;req&quot;</span><span class="p">,</span>
<span class="s2">&quot;status_code&quot;</span><span class="p">,</span>
<span class="s2">&quot;content&quot;</span><span class="p">,</span>
<span class="s2">&quot;encoding&quot;</span><span class="p">,</span>
<span class="s2">&quot;media&quot;</span><span class="p">,</span>
<span class="s2">&quot;headers&quot;</span><span class="p">,</span>
<span class="s2">&quot;formats&quot;</span><span class="p">,</span>
<span class="s2">&quot;cookies&quot;</span><span class="p">,</span>
<span class="s2">&quot;session&quot;</span><span class="p">,</span>
<span class="s2">&quot;mimetype&quot;</span><span class="p">,</span>
<span class="s2">&quot;_stream&quot;</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">&quot;text/plain&quot;</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">&quot;text/html&quot;</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">&quot;&quot;&quot;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(&quot;/stream&quot;)</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&quot;chunk {i}\\n&quot;.encode()</span>
<span class="sd"> :param func: An async generator function that yields response chunks.</span>
<span class="sd"> &quot;&quot;&quot;</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">&quot;&quot;&quot;Set up Server-Sent Events streaming.</span>
<span class="sd"> Usage::</span>
<span class="sd"> @api.route(&quot;/events&quot;)</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 {&quot;data&quot;: f&quot;message {i}&quot;}</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"> &quot;&quot;&quot;</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">&quot;data: </span><span class="si">{</span><span class="n">event</span><span class="si">}</span><span class="se">\n\n</span><span class="s2">&quot;</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">&quot;event&quot;</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">&quot;event: </span><span class="si">{</span><span class="n">event</span><span class="p">[</span><span class="s1">&#39;event&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="k">if</span> <span class="s2">&quot;id&quot;</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">&quot;id: </span><span class="si">{</span><span class="n">event</span><span class="p">[</span><span class="s1">&#39;id&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="k">if</span> <span class="s2">&quot;retry&quot;</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">&quot;retry: </span><span class="si">{</span><span class="n">event</span><span class="p">[</span><span class="s1">&#39;retry&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&quot;</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">&quot;data&quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</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">&quot;</span><span class="se">\n</span><span class="s2">&quot;</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">&quot;data: </span><span class="si">{</span><span class="n">line</span><span class="si">}</span><span class="s2">&quot;</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">&quot;&quot;</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">&quot;&quot;</span><span class="p">)</span>
<span class="k">yield</span> <span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</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">&quot;data: </span><span class="si">{</span><span class="n">event</span><span class="si">}</span><span class="se">\n\n</span><span class="s2">&quot;</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">&quot;text/event-stream&quot;</span>
<span class="bp">self</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">&quot;Cache-Control&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;no-cache&quot;</span>
<span class="bp">self</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">&quot;Connection&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;keep-alive&quot;</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">&quot;&quot;&quot;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"> &quot;&quot;&quot;</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">&quot;application/octet-stream&quot;</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">&quot;rb&quot;</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">&quot;&quot;&quot;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"> &quot;&quot;&quot;</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">&quot;application/octet-stream&quot;</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">&quot;&quot;&quot;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"> &quot;&quot;&quot;</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">&quot;Redirecting to: </span><span class="si">{</span><span class="n">location</span><span class="si">}</span><span class="s2">&quot;</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">&quot;Location&quot;</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">&quot;Content-Type&quot;</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">&quot;Content-Type&quot;</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">&quot;text/plain&quot;</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">&quot;Encoding&quot;</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">&quot;json&quot;</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">&quot;Content-Type&quot;</span><span class="p">:</span> <span class="s2">&quot;application/json&quot;</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">&quot;&quot;</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">&quot;/&quot;</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">&quot;&quot;&quot;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. ``&quot;Thu, 01 Jan 2026 00:00:00 GMT&quot;``).</span>
<span class="sd"> :param path: URL path the cookie applies to (default ``&quot;/&quot;``).</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"> &quot;token&quot;, value=&quot;abc123&quot;,</span>
<span class="sd"> max_age=3600, secure=True, httponly=True,</span>
<span class="sd"> )</span>
<span class="sd"> &quot;&quot;&quot;</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">&quot;expires&quot;</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">&quot;path&quot;</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">&quot;domain&quot;</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">&quot;max-age&quot;</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">&quot;secure&quot;</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">&quot;httponly&quot;</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">&quot;set-cookie&quot;</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">&quot;&quot;</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">&quot;latin-1&quot;</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">&quot;&quot;&quot;``True`` if the status code is in the 2xx range (success).&quot;&quot;&quot;</span>
<span class="k">return</span> <span class="mi">200</span> <span class="o">&lt;=</span> <span class="bp">self</span><span class="o">.</span><span class="n">status_code_safe</span> <span class="o">&lt;</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">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;Return the status code, raising ``RuntimeError`` if it hasn&#39;t been set.&quot;&quot;&quot;</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">&quot;HTTP status code has not been defined&quot;</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">
&#169;2018-2026, Kenneth Reitz.
</div>
</body>
</html>
-210
View File
@@ -1,210 +0,0 @@
<!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 &#8212; 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">&quot;continue&quot;</span><span class="p">,),</span>
<span class="mi">101</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;switching_protocols&quot;</span><span class="p">,),</span>
<span class="mi">102</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;processing&quot;</span><span class="p">,),</span>
<span class="mi">103</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;checkpoint&quot;</span><span class="p">,),</span>
<span class="mi">122</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;uri_too_long&quot;</span><span class="p">,</span> <span class="s2">&quot;request_uri_too_long&quot;</span><span class="p">),</span>
<span class="mi">200</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;ok&quot;</span><span class="p">,</span> <span class="s2">&quot;okay&quot;</span><span class="p">,</span> <span class="s2">&quot;all_ok&quot;</span><span class="p">,</span> <span class="s2">&quot;all_okay&quot;</span><span class="p">,</span> <span class="s2">&quot;all_good&quot;</span><span class="p">,</span> <span class="s2">&quot;</span><span class="se">\\</span><span class="s2">o/&quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">),</span>
<span class="mi">201</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;created&quot;</span><span class="p">,),</span>
<span class="mi">202</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;accepted&quot;</span><span class="p">,),</span>
<span class="mi">203</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;non_authoritative_info&quot;</span><span class="p">,</span> <span class="s2">&quot;non_authoritative_information&quot;</span><span class="p">),</span>
<span class="mi">204</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;no_content&quot;</span><span class="p">,),</span>
<span class="mi">205</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;reset_content&quot;</span><span class="p">,</span> <span class="s2">&quot;reset&quot;</span><span class="p">),</span>
<span class="mi">206</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;partial_content&quot;</span><span class="p">,</span> <span class="s2">&quot;partial&quot;</span><span class="p">),</span>
<span class="mi">207</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;multi_status&quot;</span><span class="p">,</span> <span class="s2">&quot;multiple_status&quot;</span><span class="p">,</span> <span class="s2">&quot;multi_stati&quot;</span><span class="p">,</span> <span class="s2">&quot;multiple_stati&quot;</span><span class="p">),</span>
<span class="mi">208</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;already_reported&quot;</span><span class="p">,),</span>
<span class="mi">226</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;im_used&quot;</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">&quot;multiple_choices&quot;</span><span class="p">,),</span>
<span class="mi">301</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;moved_permanently&quot;</span><span class="p">,</span> <span class="s2">&quot;moved&quot;</span><span class="p">,</span> <span class="s2">&quot;</span><span class="se">\\</span><span class="s2">o-&quot;</span><span class="p">),</span>
<span class="mi">302</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;found&quot;</span><span class="p">,),</span>
<span class="mi">303</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;see_other&quot;</span><span class="p">,</span> <span class="s2">&quot;other&quot;</span><span class="p">),</span>
<span class="mi">304</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;not_modified&quot;</span><span class="p">,),</span>
<span class="mi">305</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;use_proxy&quot;</span><span class="p">,),</span>
<span class="mi">306</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;switch_proxy&quot;</span><span class="p">,),</span>
<span class="mi">307</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;temporary_redirect&quot;</span><span class="p">,</span> <span class="s2">&quot;temporary_moved&quot;</span><span class="p">,</span> <span class="s2">&quot;temporary&quot;</span><span class="p">),</span>
<span class="mi">308</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;permanent_redirect&quot;</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">&quot;bad_request&quot;</span><span class="p">,</span> <span class="s2">&quot;bad&quot;</span><span class="p">),</span>
<span class="mi">401</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;unauthorized&quot;</span><span class="p">,),</span>
<span class="mi">402</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;payment_required&quot;</span><span class="p">,</span> <span class="s2">&quot;payment&quot;</span><span class="p">),</span>
<span class="mi">403</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;forbidden&quot;</span><span class="p">,),</span>
<span class="mi">404</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;not_found&quot;</span><span class="p">,</span> <span class="s2">&quot;-o-&quot;</span><span class="p">),</span>
<span class="mi">405</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;method_not_allowed&quot;</span><span class="p">,</span> <span class="s2">&quot;not_allowed&quot;</span><span class="p">),</span>
<span class="mi">406</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;not_acceptable&quot;</span><span class="p">,),</span>
<span class="mi">407</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;proxy_authentication_required&quot;</span><span class="p">,</span> <span class="s2">&quot;proxy_auth&quot;</span><span class="p">,</span> <span class="s2">&quot;proxy_authentication&quot;</span><span class="p">),</span>
<span class="mi">408</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;request_timeout&quot;</span><span class="p">,</span> <span class="s2">&quot;timeout&quot;</span><span class="p">),</span>
<span class="mi">409</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;conflict&quot;</span><span class="p">,),</span>
<span class="mi">410</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;gone&quot;</span><span class="p">,),</span>
<span class="mi">411</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;length_required&quot;</span><span class="p">,),</span>
<span class="mi">412</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;precondition_failed&quot;</span><span class="p">,</span> <span class="s2">&quot;precondition&quot;</span><span class="p">),</span>
<span class="mi">413</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;request_entity_too_large&quot;</span><span class="p">,),</span>
<span class="mi">414</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;request_uri_too_large&quot;</span><span class="p">,),</span>
<span class="mi">415</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;unsupported_media_type&quot;</span><span class="p">,</span> <span class="s2">&quot;unsupported_media&quot;</span><span class="p">,</span> <span class="s2">&quot;media_type&quot;</span><span class="p">),</span>
<span class="mi">416</span><span class="p">:</span> <span class="p">(</span>
<span class="s2">&quot;requested_range_not_satisfiable&quot;</span><span class="p">,</span>
<span class="s2">&quot;requested_range&quot;</span><span class="p">,</span>
<span class="s2">&quot;range_not_satisfiable&quot;</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">&quot;expectation_failed&quot;</span><span class="p">,),</span>
<span class="mi">418</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;im_a_teapot&quot;</span><span class="p">,</span> <span class="s2">&quot;teapot&quot;</span><span class="p">,</span> <span class="s2">&quot;i_am_a_teapot&quot;</span><span class="p">),</span>
<span class="mi">421</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;misdirected_request&quot;</span><span class="p">,),</span>
<span class="mi">422</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;unprocessable_entity&quot;</span><span class="p">,</span> <span class="s2">&quot;unprocessable&quot;</span><span class="p">),</span>
<span class="mi">423</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;locked&quot;</span><span class="p">,),</span>
<span class="mi">424</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;failed_dependency&quot;</span><span class="p">,</span> <span class="s2">&quot;dependency&quot;</span><span class="p">),</span>
<span class="mi">425</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;unordered_collection&quot;</span><span class="p">,</span> <span class="s2">&quot;unordered&quot;</span><span class="p">),</span>
<span class="mi">426</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;upgrade_required&quot;</span><span class="p">,</span> <span class="s2">&quot;upgrade&quot;</span><span class="p">),</span>
<span class="mi">428</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;precondition_required&quot;</span><span class="p">,</span> <span class="s2">&quot;precondition&quot;</span><span class="p">),</span>
<span class="mi">429</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;too_many_requests&quot;</span><span class="p">,</span> <span class="s2">&quot;too_many&quot;</span><span class="p">),</span>
<span class="mi">431</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;header_fields_too_large&quot;</span><span class="p">,</span> <span class="s2">&quot;fields_too_large&quot;</span><span class="p">),</span>
<span class="mi">444</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;no_response&quot;</span><span class="p">,</span> <span class="s2">&quot;none&quot;</span><span class="p">),</span>
<span class="mi">449</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;retry_with&quot;</span><span class="p">,</span> <span class="s2">&quot;retry&quot;</span><span class="p">),</span>
<span class="mi">450</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;blocked_by_windows_parental_controls&quot;</span><span class="p">,</span> <span class="s2">&quot;parental_controls&quot;</span><span class="p">),</span>
<span class="mi">451</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;unavailable_for_legal_reasons&quot;</span><span class="p">,</span> <span class="s2">&quot;legal_reasons&quot;</span><span class="p">),</span>
<span class="mi">499</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;client_closed_request&quot;</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">&quot;internal_server_error&quot;</span><span class="p">,</span> <span class="s2">&quot;server_error&quot;</span><span class="p">,</span> <span class="s2">&quot;/o</span><span class="se">\\</span><span class="s2">&quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">),</span>
<span class="mi">501</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;not_implemented&quot;</span><span class="p">,),</span>
<span class="mi">502</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;bad_gateway&quot;</span><span class="p">,),</span>
<span class="mi">503</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;service_unavailable&quot;</span><span class="p">,</span> <span class="s2">&quot;unavailable&quot;</span><span class="p">),</span>
<span class="mi">504</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;gateway_timeout&quot;</span><span class="p">,),</span>
<span class="mi">505</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;http_version_not_supported&quot;</span><span class="p">,</span> <span class="s2">&quot;http_version&quot;</span><span class="p">),</span>
<span class="mi">506</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;variant_also_negotiates&quot;</span><span class="p">,),</span>
<span class="mi">507</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;insufficient_storage&quot;</span><span class="p">,),</span>
<span class="mi">509</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;bandwidth_limit_exceeded&quot;</span><span class="p">,</span> <span class="s2">&quot;bandwidth&quot;</span><span class="p">),</span>
<span class="mi">510</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;not_extended&quot;</span><span class="p">,),</span>
<span class="mi">511</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;network_authentication_required&quot;</span><span class="p">,</span> <span class="s2">&quot;network_auth&quot;</span><span class="p">,</span> <span class="s2">&quot;network_authentication&quot;</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">&quot;HTTP_</span><span class="si">{</span><span class="n">number</span><span class="si">}</span><span class="s2">&quot;</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">&lt;=</span> <span class="n">status_code</span> <span class="o">&lt;</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">
&#169;2018-2026, Kenneth Reitz.
</div>
</body>
</html>
-182
View File
@@ -1,182 +0,0 @@
API Reference
=============
This page documents Responder's public Python API. For usage examples
and explanations, see the :doc:`quickstart` and :doc:`tour`.
The API Class
-------------
The central object of every Responder application. It holds your routes,
middleware, templates, and configuration. Create one at the top of your
module and use it to define your entire web service.
Quick example::
import responder
api = responder.API(
title="My Service", # OpenAPI title
version="1.0", # OpenAPI version
openapi="3.0.2", # enable OpenAPI
docs_route="/docs", # Swagger UI at /docs
cors=True, # enable CORS
secret_key="change-me", # session signing key
allowed_hosts=["example.com"],
)
.. module:: responder
.. autoclass:: API
:inherited-members:
Request
-------
The request object is passed into every view as the first argument. It
gives you access to everything the client sent — headers, query
parameters, the request body, cookies, and more.
Most properties are synchronous, but reading the body requires ``await``
because it involves I/O.
Common patterns::
# Headers (case-insensitive)
token = req.headers.get("Authorization")
# Query parameters: /search?q=python&page=2
query = req.params["q"]
# JSON body
data = await req.media()
# Form data
form = await req.media("form")
# File uploads
files = await req.media("files")
# Client info
ip, port = req.client
is_https = req.is_secure
.. autoclass:: Request
:inherited-members:
Response
--------
The response object is passed into every view as the second argument.
Mutate it to control what gets sent back to the client — the body,
status code, headers, and cookies.
Common patterns::
resp.text = "plain text" # text/plain
resp.html = "<h1>Hello</h1>" # text/html
resp.media = {"key": "value"} # application/json
resp.content = b"raw bytes" # application/octet-stream
resp.file("path/to/file.pdf") # auto content-type
resp.stream_file("large/export.csv") # streamed
resp.status_code = 201
resp.headers["X-Custom"] = "value"
resp.cookies["session"] = "abc123"
.. autoclass:: Response
:inherited-members:
Route Groups
------------
Group related routes under a shared URL prefix — useful for API versioning
and organizing large applications::
v1 = api.group("/v1")
@v1.route("/users")
def list_users(req, resp):
resp.media = []
.. autoclass:: responder.api.RouteGroup
:members:
Background Queue
----------------
Run tasks in background threads without blocking the response. Available
as ``api.background``::
@api.route("/submit")
async def submit(req, resp):
data = await req.media()
@api.background.task
def process(data):
# runs in a thread pool
...
process(data)
resp.media = {"status": "accepted"}
.. autoclass:: responder.background.BackgroundQueue
:members:
Query Dict
----------
A dictionary subclass for query string parameters with multi-value support.
Behaves like a normal dict for single values, but supports ``getlist()``
for parameters that appear multiple times (e.g. ``?tag=a&tag=b``).
.. autoclass:: responder.models.QueryDict
:members:
Rate Limiter
------------
In-memory token bucket rate limiter. Limits requests per client IP address
and returns ``429 Too Many Requests`` when exceeded::
from responder.ext.ratelimit import RateLimiter
limiter = RateLimiter(requests=100, period=60) # 100 req/min
limiter.install(api)
Response headers: ``X-RateLimit-Limit``, ``X-RateLimit-Remaining``,
and ``Retry-After`` (when limited).
.. autoclass:: responder.ext.ratelimit.RateLimiter
:members:
Status Code Helpers
-------------------
Convenience functions for checking which category a status code falls
into. Useful in middleware and after-request hooks::
from responder.status_codes import is_200, is_400, is_500
@api.after_request()
def log_errors(req, resp):
if is_400(resp.status_code) or is_500(resp.status_code):
print(f"Error: {req.method} {req.url.path} -> {resp.status_code}")
.. autofunction:: responder.status_codes.is_100
.. autofunction:: responder.status_codes.is_200
.. autofunction:: responder.status_codes.is_300
.. autofunction:: responder.status_codes.is_400
.. autofunction:: responder.status_codes.is_500
-8
View File
@@ -1,8 +0,0 @@
# Backlog
## Future Ideas
- WebSocket before_request short-circuit support (reject before accept)
- Per-route rate limiting (different limits for different endpoints)
- Built-in structured logging with request context
- OpenAPI 3.1 support
- Dependency injection for route handlers
-100
View File
@@ -1,100 +0,0 @@
Command Line Interface
======================
Responder installs a ``responder`` 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 ``API`` instance
and start serving.
Launching from a Module
-----------------------
The most common way to run a Responder application in production. Use
Python's standard dotted module path::
$ responder run acme.app
This imports ``acme.app`` and looks for an attribute called ``api``
(a ``responder.API`` instance). It's the same import system Python
uses everywhere — your ``PYTHONPATH`` and virtual environment are
respected.
Launching from a File
---------------------
During development, you often have a single file you want to run::
$ responder run helloworld.py
This loads the file directly and starts the server. Quick and easy for
prototyping and single-file applications.
You can test it with a simple HTTP request::
$ curl http://127.0.0.1:5042/hello
hello, world!
Launching from a URL
--------------------
Responder can fetch and run a Python file from any URL — great for
demos, sharing examples, and running code from GitHub::
$ responder run https://github.com/kennethreitz/responder/raw/refs/heads/main/examples/helloworld.py
This also works with ``github://`` URLs and any filesystem protocol
supported by `fsspec <https://filesystem-spec.readthedocs.io/>`_::
$ responder run github://kennethreitz:responder@/examples/helloworld.py
Cloud storage is supported too — Azure Blob Storage, Google Cloud
Storage, S3, HDFS, SFTP, and more. Install ``fsspec[full]`` for all
protocols::
$ uv pip install 'fsspec[full]'
Custom Instance Names
---------------------
By default, Responder looks for an attribute called ``api``. If your
application uses a different name, specify it with a colon::
$ responder run acme.app:service
$ responder run myapp.py:application
For URLs, use a fragment::
$ responder run https://example.com/app.py#service
Environment Variables
---------------------
Responder automatically reads the ``PORT`` environment variable at
runtime:
- ``PORT`` — bind to ``0.0.0.0`` on this port (cloud platform convention)
When ``PORT`` is set, the server binds to all interfaces automatically.
This is how cloud platforms like Fly.io, Railway, and Heroku inject the
listen port.
For other settings like ``SECRET_KEY``, read them in your application
code and pass them to ``responder.API()``::
import os
api = responder.API(secret_key=os.environ["SECRET_KEY"])
Building Frontend Assets
-------------------------
If your project includes a JavaScript frontend with a ``package.json``,
the ``build`` subcommand runs ``npm run build``::
$ responder build
$ responder build /path/to/frontend
-187
View File
@@ -1,187 +0,0 @@
Deployment
==========
Responder applications are standard `ASGI <https://asgi.readthedocs.io/>`_
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.
Running Locally
---------------
During development, ``api.run()`` is all you need::
if __name__ == "__main__":
api.run()
This starts a `uvicorn <https://www.uvicorn.org/>`_ server on
``127.0.0.1:5042``. Uvicorn is a lightning-fast ASGI server built on
`uvloop <https://uvloop.readthedocs.io/>`_ — it handles thousands of
concurrent connections efficiently and protects against slowloris attacks,
making a reverse proxy like nginx optional for many deployments.
Docker
------
Docker is the most common way to package and deploy web applications.
Here's a minimal Dockerfile::
FROM python:3.13-slim
WORKDIR /app
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
COPY . .
RUN uv pip install --system responder
ENV PORT=80
EXPOSE 80
CMD ["python", "api.py"]
Build and run::
$ docker build -t myapi .
$ docker run -p 8000:80 myapi
The ``python:3.13-slim`` image is about 150MB — small enough for fast
deploys but includes everything you need. Using ``uv`` for installs
is significantly faster than pip. For even smaller images, you can use
``python:3.13-alpine``, though some packages may need extra build
dependencies.
Cloud Platforms
---------------
Responder automatically honors the ``PORT`` environment variable. When
``PORT`` is set, the server binds to ``0.0.0.0`` on that port — this is
the convention that virtually every cloud platform uses.
This means zero configuration on:
- **Fly.io** — ``fly launch`` and you're done
- **Railway** — push your code, Railway sets ``PORT``
- **Render** — set start command to ``python api.py``
- **Google Cloud Run** — containerize and deploy
- **Azure Container Apps** — same pattern
- **AWS App Runner** — and here too
The pattern is always the same: deploy your code, set the start command
to ``python api.py``, and the platform handles the rest.
Health Check Endpoint
---------------------
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::
@api.route("/health")
def health(req, resp):
resp.media = {"status": "healthy"}
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.
For Docker, add a ``HEALTHCHECK`` instruction::
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost/health || exit 1
Uvicorn Directly
----------------
For production deployments where you want more control, bypass
``api.run()`` and use uvicorn directly::
$ uvicorn api:api --host 0.0.0.0 --port 8000 --workers 4
The ``--workers`` flag spawns multiple processes, each handling requests
independently. A good starting point is 2-4 workers per CPU core.
Uvicorn supports many options — SSL certificates, access logging, graceful
shutdown timeouts, and more. See the
`uvicorn documentation <https://www.uvicorn.org/deployment/>`_ for details.
For platforms like Heroku or Railway that use a ``Procfile``::
web: uvicorn api:api --host 0.0.0.0 --port $PORT --workers 4
Docker Compose
--------------
For local development with databases and other services, Docker Compose
ties everything together::
# docker-compose.yml
services:
api:
build: .
ports:
- "5042:80"
environment:
- PORT=80
- DATABASE_URL=postgresql+asyncpg://user:pass@db/myapp
- SECRET_KEY=dev-secret
depends_on:
- db
db:
image: docker.io/postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Run with ``docker compose up``. The API waits for ``db`` to start, then
connects using the ``DATABASE_URL`` environment variable.
Reverse Proxy
-------------
For high-traffic production deployments, you may want a reverse proxy like
`nginx <https://nginx.org/>`_ or `Caddy <https://caddyserver.com/>`_ in
front of your application for:
- **SSL/TLS termination** — let the proxy handle HTTPS certificates
- **Load balancing** — distribute traffic across multiple app instances
- **Static asset serving** — offload static files to the proxy
- **Rate limiting** — at the infrastructure level
A minimal Caddy config that handles HTTPS automatically::
# Caddyfile
example.com {
reverse_proxy localhost:5042
}
Responder's ``TrustedHostMiddleware`` and ``HTTPSRedirectMiddleware`` work
correctly behind proxies that set standard forwarding headers
(``X-Forwarded-For``, ``X-Forwarded-Proto``).
That said, uvicorn is production-ready on its own. Many applications run
uvicorn directly without a reverse proxy and do just fine.
Production Checklist
--------------------
Before going live:
- **Set a secret key** — ``SECRET_KEY`` env var, never the default
- **Disable debug mode** — ``DEBUG=false`` or omit it entirely
- **Set allowed hosts** — restrict to your actual domain names
- **Use multiple workers** — ``--workers 4`` or more, depending on CPU cores
- **Add a health check** — ``/health`` endpoint for monitoring
- **Enable HTTPS** — via your proxy, cloud platform, or uvicorn's ``--ssl-*`` flags
- **Set up logging** — uvicorn logs requests by default; pipe them to your log aggregator
- **Pin your dependencies** — use a lock file or pinned requirements for reproducible deploys
-172
View File
@@ -1,172 +0,0 @@
Configuration
=============
Every application needs different settings for different environments —
debug mode in development, real secrets in production, different database
URLs for testing. This guide covers how to manage configuration cleanly.
Environment Variables
---------------------
The simplest and most universal approach. Environment variables work
everywhere — locally, in Docker, on cloud platforms — and keep secrets
out of your source code::
import os
import responder
api = responder.API(
debug=os.getenv("DEBUG", "false").lower() == "true",
secret_key=os.environ["SECRET_KEY"],
cors=os.getenv("CORS_ENABLED", "false").lower() == "true",
)
Some variables Responder handles automatically:
- ``PORT`` — when set, the server binds to ``0.0.0.0`` on this port
Set variables in your shell::
$ export SECRET_KEY="your-secret-here"
$ export DEBUG=true
$ python app.py
Or in a ``.env`` file (don't commit this to git)::
SECRET_KEY=your-secret-here
DEBUG=true
Using .env Files
----------------
For local development, a ``.env`` file is convenient. Install
``python-dotenv`` and load it at the top of your app::
$ uv pip install python-dotenv
::
from dotenv import load_dotenv
load_dotenv()
import os
import responder
api = responder.API(
secret_key=os.environ["SECRET_KEY"],
)
Add ``.env`` to your ``.gitignore`` — never commit secrets.
Configuration Class Pattern
----------------------------
For larger applications, a configuration class keeps things organized::
import os
class Config:
SECRET_KEY = os.environ.get("SECRET_KEY", "dev-secret")
DEBUG = os.environ.get("DEBUG", "false").lower() == "true"
DATABASE_URL = os.environ.get("DATABASE_URL", "sqlite:///dev.db")
CORS_ORIGINS = os.environ.get("CORS_ORIGINS", "").split(",")
config = Config()
api = responder.API(
debug=config.DEBUG,
secret_key=config.SECRET_KEY,
cors=bool(config.CORS_ORIGINS[0]),
cors_params={"allow_origins": config.CORS_ORIGINS},
)
This makes it easy to see all your settings in one place.
Secret Key
----------
The ``secret_key`` is used to sign session cookies. If someone knows your
secret key, they can forge session data and impersonate any user.
Rules:
- **Never use the default** in production
- **Generate a random key**: ``python -c "import secrets; print(secrets.token_hex(32))"``
- **Store it in an environment variable**, not in code
- **Rotate it** if it's ever compromised (this invalidates all sessions)
::
api = responder.API(secret_key=os.environ["SECRET_KEY"])
Debug Mode
----------
Debug mode controls error page behavior:
- **On** (``debug=True``): detailed error pages with tracebacks. Never
use this in production — it exposes your source code.
- **Off** (``debug=False``): generic error pages. This is the default.
::
api = responder.API(debug=True) # development only
A common pattern is to read it from the environment::
api = responder.API(debug=os.getenv("DEBUG") == "true")
Allowed Hosts
-------------
In production, always set ``allowed_hosts`` to prevent Host header
attacks. This should match the domain names your application serves::
api = responder.API(
allowed_hosts=["example.com", "www.example.com"],
)
In development, you can use ``["*"]`` (the default) or specific local
addresses::
api = responder.API(allowed_hosts=["localhost", "127.0.0.1"])
Putting It All Together
-----------------------
A production-ready configuration setup::
import os
from dotenv import load_dotenv
load_dotenv()
import responder
api = responder.API(
debug=os.getenv("DEBUG", "false") == "true",
secret_key=os.environ["SECRET_KEY"],
allowed_hosts=os.getenv("ALLOWED_HOSTS", "*").split(","),
cors=bool(os.getenv("CORS_ORIGINS")),
cors_params={
"allow_origins": os.getenv("CORS_ORIGINS", "").split(","),
"allow_methods": ["GET", "POST", "PUT", "DELETE"],
},
)
With a ``.env`` file for local development::
SECRET_KEY=dev-secret-do-not-use-in-prod
DEBUG=true
ALLOWED_HOSTS=localhost,127.0.0.1
CORS_ORIGINS=http://localhost:3000
And environment variables set properly in production (via your cloud
platform's dashboard, Docker secrets, or a secrets manager).
-135
View File
@@ -1,135 +0,0 @@
Responder
=========
A familiar HTTP Service Framework for Python.
.. code:: 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()
Powered by `Starlette`_, `uvicorn`_, and good intentions. The ``async`` is optional.
The Idea
--------
If you've ever used `Flask`_, the routing will look familiar. If you've
used `Falcon`_, the request/response pattern will click immediately. And
if you've used `Requests`_ — well, you'll feel right at home.
Responder takes these ideas and brings them together. Every view receives
a request and a response. You read from one and write to the other. No
return values, no special response classes, no boilerplate.
- ``resp.text`` sends text. ``resp.html`` sends HTML. ``resp.media`` sends JSON.
- ``resp.file("path")`` serves a file. ``resp.content`` sends raw bytes.
- ``req.headers`` is case-insensitive. ``req.params`` holds query parameters.
- ``resp.status_code``, ``req.method``, ``req.url`` — the familiar ones.
Set ``resp.media`` to a dict and the right thing happens. If the client
asks for YAML, it gets YAML. Content negotiation is automatic.
Responder and `FastAPI`_ are siblings — both built on Starlette, both
born around the same time, both part of the push that made ASGI the
future of Python web services. FastAPI went deep on type annotations
and automatic validation. Responder went for simplicity and a mutable
request/response pattern. Both projects are better for the other
existing. Use whichever feels right.
This is a passion project. It exists because building a web framework
from scratch is one of the best ways to understand how the web works.
It's a great fit for personal projects, prototyping, teaching, research,
and anyone who values a clean API over a sprawling ecosystem. If you
need battle-tested infrastructure at scale, FastAPI and Django will
serve you well. If you want something small, expressive, and fun to
work with — welcome.
What You Get
------------
One ``pip install``, batteries included:
- Pydantic request validation and response serialization.
- Mount Flask, Django, or any WSGI/ASGI app at a subroute.
- Gzip compression, HSTS, CORS, and trusted host validation.
- Before-request and after-request hooks for auth and logging.
- A test client for fast, in-process testing with pytest.
- Route parameters with f-string syntax and type convertors.
- Lifespan context managers for startup and shutdown logic.
- Custom exception handlers for clean error responses.
- `GraphQL`_ with Graphene and a built-in GraphiQL IDE.
- Server-Sent Events for real-time streaming.
- File serving with automatic content-type detection.
- Sync and async views — ``async`` is always optional.
- Class-based views with ``on_get``, ``on_post``, ``on_request``.
- Built-in rate limiting with ``X-RateLimit`` headers.
- Structured logging with per-request context.
- Content negotiation: JSON, YAML, and MessagePack.
- A pleasant API with a single import statement.
- OpenAPI schema generation with Swagger UI.
- A production `uvicorn`_ server, ready to deploy.
- Route groups for API versioning.
- Signed cookie-based sessions.
- Background tasks in a thread pool.
- WebSocket support.
Installation
------------
.. code-block:: shell
$ uv pip install responder
Python 3.10 and above. That's it.
.. toctree::
:maxdepth: 2
:caption: User Guide
quickstart
tour
deployment
testing
api
cli
.. toctree::
:maxdepth: 2
:caption: Tutorials
tutorial-rest
tutorial-sqlalchemy
tutorial-auth
tutorial-websockets
tutorial-middleware
tutorial-flask
guide-config
.. toctree::
:maxdepth: 1
:caption: Project
changes
Sandbox <sandbox>
backlog
.. _Starlette: https://www.starlette.io/
.. _uvicorn: https://www.uvicorn.org/
.. _Flask: https://flask.palletsprojects.com/
.. _Falcon: https://falconframework.org/
.. _FastAPI: https://fastapi.tiangolo.com/
.. _GraphQL: https://graphql.org/
.. _Requests: https://requests.readthedocs.io/
-384
View File
@@ -1,384 +0,0 @@
Quick Start
===========
This guide will walk you through the basics of building a web service with
Responder. By the end, you'll understand how HTTP requests and responses
work, how to define routes, read data from clients, send data back, render
HTML templates, and process work in the background.
Create a Web Service
--------------------
Every web application starts with a single object — the application
instance. In Responder, this is the ``API`` class. It holds your routes,
middleware, templates, and configuration. Think of it as the central
nervous system of your web service::
import responder
api = responder.API()
That's it. One import, one line. You now have a fully functional ASGI
application with gzip compression, static file serving, session support,
and a production-ready server — all wired up and ready to go.
Hello World
-----------
A web service isn't very useful until it can respond to requests. In HTTP,
a *route* maps a URL path to a function that handles it. When a client
(like a browser or ``curl``) sends a request to that path, your function
runs and produces a response.
Here's the simplest possible route::
@api.route("/")
def hello_world(req, resp):
resp.text = "hello, world!"
Two things to notice:
1. Every view function receives two arguments: ``req`` (the incoming
request) and ``resp`` (the outgoing response).
2. You don't return anything. Instead, you *mutate* the response object
directly. This is a deliberate design choice — it keeps the API
consistent whether you're setting text, JSON, headers, cookies, or
status codes.
Run the Server
--------------
Start your web service with a single call::
api.run()
This spins up a production-grade `uvicorn <https://www.uvicorn.org/>`_
server on port ``5042``, ready for incoming HTTP requests. Open
``http://localhost:5042`` in your browser and you'll see your hello world
response.
You can customize the port with ``api.run(port=8000)``. The ``PORT``
environment variable is also honored automatically — when set, Responder
binds to ``0.0.0.0`` on that port, which is what cloud platforms expect.
.. note::
Both sync and async views are supported. The ``async`` keyword is always
optional — use it when you need to ``await`` something, like reading a
request body or querying a database.
Route Parameters
----------------
Static URLs like ``/about`` are useful, but most applications need dynamic
routes — URLs that contain variable data, like a user ID or a product slug.
In Responder, you declare route parameters using Python's f-string syntax::
@api.route("/hello/{who}")
def hello_to(req, resp, *, who):
resp.text = f"hello, {who}!"
A ``GET`` request to ``/hello/world`` will respond with ``hello, world!``.
A request to ``/hello/guido`` will respond with ``hello, guido!``.
Route parameters are passed as *keyword-only* arguments (after the ``*``
in the function signature). This is a Python feature that makes the
interface explicit — you always know which arguments come from the URL.
Type Convertors
^^^^^^^^^^^^^^^
By default, route parameters are strings. But often you want them as
integers, UUIDs, or other types. Responder can convert them automatically
using type annotations in the route pattern::
@api.route("/add/{a:int}/{b:int}")
async def add(req, resp, *, a, b):
resp.text = f"{a} + {b} = {a + b}"
Here, ``a`` and ``b`` will arrive as Python ``int`` objects, not strings.
If someone requests ``/add/3/hello``, they'll get a 404 — the route won't
match because ``hello`` isn't a valid integer.
Supported types:
- ``str`` — matches any string without slashes (this is the default)
- ``int`` — matches digits and converts to ``int``
- ``float`` — matches decimal numbers and converts to ``float``
- ``uuid`` — matches UUID strings like ``550e8400-e29b-41d4-a716-446655440000``
- ``path`` — matches any string *including* slashes, useful for file paths
like ``/files/{filepath:path}``
Sending Responses
-----------------
When an HTTP server receives a request, it must send back a response. Every
HTTP response has three parts: a status code (like ``200 OK`` or ``404 Not
Found``), headers (metadata like ``Content-Type``), and a body (the actual
data).
Responder lets you set all three by mutating the response object.
**Text and HTML** — the simplest response types. ``resp.text`` sets the
``Content-Type`` to ``text/plain``, while ``resp.html`` sets it to
``text/html``::
resp.text = "plain text response"
resp.html = "<h1>HTML response</h1>"
**JSON** — the lingua franca of web APIs. Set ``resp.media`` to any
JSON-serializable Python object — a dict, a list, whatever — and Responder
will serialize it to JSON and set the right headers::
@api.route("/hello/{who}/json")
def hello_json(req, resp, *, who):
resp.media = {"hello": who}
If the client sends an ``Accept: application/x-yaml`` header, the same data
will be returned as YAML instead. This is called *content negotiation* —
the server and client agree on a format. It happens automatically.
**Files** — serve a file from disk. Responder uses Python's ``mimetypes``
module to figure out the ``Content-Type`` from the file extension::
resp.file("reports/annual.pdf")
**Raw bytes** — for binary data like images or protocol buffers::
resp.content = b"\x89PNG\r\n..."
**Status codes** — HTTP status codes tell the client what happened. ``200``
means success, ``201`` means something was created, ``404`` means not found,
``500`` means the server broke. Set it directly::
resp.status_code = 201
**Headers** — HTTP headers carry metadata. Common ones include
``Content-Type``, ``Cache-Control``, ``Authorization``, and custom
application headers::
resp.headers["X-Custom"] = "value"
**Redirects** — tell the client to go somewhere else::
api.redirect(resp, location="/new-url")
This sends a ``301 Moved Permanently`` response by default. The client's
browser will automatically follow the redirect.
Reading Requests
----------------
The other half of HTTP is the request — the data the client sends to your
server. This includes the HTTP method (GET, POST, PUT, DELETE), the URL,
headers, query parameters, cookies, and optionally a body.
Responder wraps all of this in the ``req`` object.
**Method and URL** — every HTTP request has a method (what the client wants
to do) and a URL (what resource it's about)::
req.method # "get", "post", etc. (lowercase)
req.full_url # "http://example.com/path?q=1"
req.url # parsed URL object
**Headers** — HTTP headers carry metadata from the client, like what
content types it accepts, authentication tokens, and more. Responder's
headers dict is case-insensitive, because the HTTP spec says header names
are case-insensitive::
req.headers["Content-Type"]
req.headers["content-type"] # same thing
**Query parameters** — the part of the URL after the ``?``. These are
commonly used for search, filtering, and pagination::
# GET /search?q=python&page=2
req.params["q"] # "python"
req.params["page"] # "2"
Note that query parameters are always strings. If you need an integer,
you'll need to convert it yourself: ``int(req.params["page"])``.
**Path parameters** — the dynamic parts of the URL that matched your route
pattern. These are also available on the request object, which is useful
in before-request hooks where they aren't passed as function arguments::
req.path_params["user_id"] # same as the keyword argument
**Request body** — for POST, PUT, and PATCH requests, the client sends
data in the body. Since reading the body is an I/O operation, you need to
``await`` it::
# JSON body (the most common format for APIs)
data = await req.media()
# Form data (from HTML forms)
data = await req.media("form")
# File uploads (multipart)
files = await req.media("files")
# Raw bytes
body = await req.content
# Raw text
text = await req.text
**Other useful properties**::
req.is_json # True if the content type is JSON
req.cookies # dict of cookies sent by the client
req.session # session data (a signed, server-side dict)
req.client # (host, port) tuple — the client's IP address
req.is_secure # True if the request came over HTTPS
Rendering Templates
-------------------
While APIs typically return JSON, many web applications need to render
HTML pages. Responder includes built-in support for
`Jinja2 <https://jinja.palletsprojects.com/>`_, one of the most popular
templating engines in the Python ecosystem.
Templates let you write HTML with placeholders that get filled in with
dynamic data. This keeps your presentation logic (HTML) separate from
your application logic (Python) — a pattern called
*separation of concerns*.
The simplest way to render a template is ``api.template()``. Templates
are loaded from the ``templates/`` directory by default::
@api.route("/hello/{name}/html")
def hello_html(req, resp, *, name):
resp.html = api.template("hello.html", name=name)
The template file ``templates/hello.html`` might look like::
<h1>Hello, {{ name }}!</h1>
The ``{{ name }}`` part is a Jinja2 expression — it gets replaced with
the value you passed in.
You can also use the ``Templates`` class directly for more control over
the template directory and configuration::
from responder.templates import Templates
templates = Templates(directory="my_templates")
@api.route("/page")
def page(req, resp):
resp.html = templates.render("page.html", title="Hello")
For applications that need non-blocking template rendering (rare, but
useful under extreme load), async rendering is supported::
templates = Templates(directory="templates", enable_async=True)
resp.html = await templates.render_async("page.html", title="Hello")
And for quick one-off templates, you can render a string directly without
a file::
resp.html = api.template_string("Hello, {{ name }}!", name="world")
Background Tasks
----------------
Sometimes you want to accept a request, respond immediately, and do the
actual processing later. This is a common pattern for operations that take
a long time — sending emails, processing images, updating caches, or
calling slow external APIs.
Responder makes this easy with background tasks. Decorate any function
with ``@api.background.task`` and it will run in a thread pool, separate
from the request/response cycle::
@api.route("/incoming")
async def receive_incoming(req, resp):
data = await req.media()
@api.background.task
def process_data(data):
"""This runs in a background thread."""
import time
time.sleep(10) # simulate heavy work
process_data(data)
# This response is sent immediately, while process_data
# continues running in the background.
resp.media = {"status": "accepted"}
The client gets an instant response — the heavy lifting happens after.
This is the same pattern used by task queues like Celery, but much simpler
for lightweight use cases where you don't need a full message broker.
.. note::
Background tasks run in threads, not processes. They share memory with
your application, which makes them fast to start but means CPU-intensive
work will block the event loop. For heavy computation, consider a proper
task queue.
Putting It All Together
-----------------------
Here's a complete, working Responder application that combines everything
from this guide::
import responder
api = responder.API()
@api.route("/")
def index(req, resp):
resp.text = "Welcome to the API"
@api.route("/hello/{name}")
def greet(req, resp, *, name):
resp.media = {"message": f"hello, {name}!"}
@api.route("/add/{a:int}/{b:int}")
def add(req, resp, *, a, b):
resp.media = {"result": a + b}
@api.route("/echo", methods=["POST"])
async def echo(req, resp):
data = await req.media()
resp.media = {"received": data}
if __name__ == "__main__":
api.run()
Save this as ``app.py``, run it with ``python app.py``, and try::
$ curl http://localhost:5042/
$ curl http://localhost:5042/hello/world
$ curl http://localhost:5042/add/3/4
$ curl -X POST http://localhost:5042/echo \
-H "Content-Type: application/json" -d '{"key": "value"}'
From here, explore the :doc:`tour` for the full range of features, or
jump into the tutorials:
- :doc:`tutorial-rest` — build a full CRUD API with validation
- :doc:`tutorial-sqlalchemy` — connect to a database
- :doc:`tutorial-auth` — add authentication
- :doc:`tutorial-websockets` — real-time communication
- :doc:`tutorial-middleware` — hooks and middleware
- :doc:`tutorial-flask` — migrating from Flask
- :doc:`guide-config` — environment variables and secrets
- :doc:`deployment` — Docker, cloud platforms, and production
- :doc:`testing` — writing tests with pytest
-62
View File
@@ -1,62 +0,0 @@
(sandbox)=
# Development Sandbox
## Setup
Clone the repo and install all dependencies:
```shell
git clone https://github.com/kennethreitz/responder.git
cd responder
uv venv && source .venv/bin/activate
uv pip install --upgrade --editable '.[develop,docs,release,test]'
```
## Running Tests
```shell
pytest # full suite with coverage
pytest tests/test_responder.py -xvs # single file, stop on first failure
pytest -k "test_mount" # run tests matching a pattern
```
## Code Formatting
```shell
ruff format . # auto-format
ruff check --fix . # lint and auto-fix
```
## Type Checking
```shell
mypy
```
## Documentation
Live-reloading doc server (opens in browser):
```shell
cd docs
sphinx-autobuild --open-browser --watch source source build
```
Or build once:
```shell
cd docs
make html
# open build/html/index.html
```
## Project Layout
```
responder/
├── responder/ # main package
│ ├── api.py # API class — the entry point
│ ├── routes.py # Router, Route, WebSocketRoute
│ ├── models.py # Request and Response wrappers
│ ├── ext/ # extensions (CLI, GraphQL, OpenAPI, rate limiting)
│ ├── background.py # background task queue
│ └── formats.py # content negotiation (JSON, YAML, msgpack)
├── tests/ # pytest test suite
├── examples/ # runnable example apps
├── docs/source/ # Sphinx documentation
└── pyproject.toml # project metadata and tool config
```
-340
View File
@@ -1,340 +0,0 @@
Testing
=======
Responder includes a built-in test client powered by Starlette's
``TestClient``. You don't need to start a server — tests run in-process,
making them fast and reliable. There's no separate test server to manage,
no ports to allocate, and no race conditions to worry about. Just import
your app and start making requests.
Getting Started
---------------
Given a simple application in ``api.py``::
import responder
api = responder.API()
@api.route("/")
def hello(req, resp):
resp.text = "hello, world!"
if __name__ == "__main__":
api.run()
You can test it with pytest. Every Responder ``API`` instance has a
``requests`` property that gives you a test client — use it exactly like
you'd use ``requests`` or ``httpx``::
# test_api.py
import api as service
def test_hello():
r = service.api.requests.get("/")
assert r.text == "hello, world!"
Run your tests::
$ pytest
That's really all there is to it. No configuration, no test server setup.
Using Fixtures
--------------
For larger test suites, pytest fixtures keep things organized. Create a
fixture that returns your API instance, and every test gets a fresh
reference to it::
import pytest
import api as service
@pytest.fixture
def api():
return service.api
def test_hello(api):
r = api.requests.get("/")
assert r.text == "hello, world!"
def test_json(api):
@api.route("/data")
def data(req, resp):
resp.media = {"key": "value"}
r = api.requests.get(api.url_for(data))
assert r.json() == {"key": "value"}
The ``api.url_for()`` method generates a URL for a given route endpoint,
so you don't have to hard-code paths in your tests. If you rename a route
later, your tests won't break.
Testing JSON APIs
-----------------
Most APIs send and receive JSON. The test client makes this natural — pass
``json=`` to send a JSON body, and call ``.json()`` on the response to
parse it::
def test_create_item(api):
@api.route("/items")
async def create(req, resp):
data = await req.media()
resp.media = {"created": data}
resp.status_code = 201
r = api.requests.post(api.url_for(create), json={"name": "widget"})
assert r.status_code == 201
assert r.json() == {"created": {"name": "widget"}}
You can also test content negotiation by setting the ``Accept`` header::
r = api.requests.get("/data", headers={"Accept": "application/x-yaml"})
assert "key: value" in r.text
Testing Request Validation
--------------------------
If you're using Pydantic models for request validation, you can test
that invalid inputs are properly rejected::
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: float
def test_validation(api):
@api.route("/items", methods=["POST"], request_model=Item)
async def create(req, resp):
data = await req.media()
resp.media = data
# Valid request
r = api.requests.post("/items", json={"name": "thing", "price": 9.99})
assert r.status_code == 200
# Missing required field
r = api.requests.post("/items", json={"name": "thing"})
assert r.status_code == 422
assert "errors" in r.json()
Testing File Uploads
--------------------
File uploads use the ``files`` parameter, just like the ``requests``
library. Each file is a tuple of ``(filename, content, content_type)``::
def test_upload(api):
@api.route("/upload")
async def upload(req, resp):
files = await req.media("files")
resp.media = {"received": list(files.keys())}
files = {"doc": ("report.pdf", b"content", "application/pdf")}
r = api.requests.post(api.url_for(upload), files=files)
assert r.json() == {"received": ["doc"]}
Testing Headers and Cookies
----------------------------
Check response headers and cookies just like you would with any HTTP
client::
def test_headers(api):
@api.route("/")
def view(req, resp):
resp.headers["X-Custom"] = "hello"
resp.cookies["session"] = "abc123"
r = api.requests.get("/")
assert r.headers["X-Custom"] == "hello"
assert "session" in r.cookies
Testing WebSockets
------------------
WebSocket tests use Starlette's ``TestClient`` directly, since WebSocket
connections require a different protocol. The ``websocket_connect`` context
manager gives you a connection you can send and receive on::
from starlette.testclient import TestClient
def test_websocket(api):
@api.route("/ws", websocket=True)
async def ws(ws):
await ws.accept()
name = await ws.receive_text()
await ws.send_text(f"hello, {name}!")
await ws.close()
client = TestClient(api)
with client.websocket_connect("/ws") as ws:
ws.send_text("world")
assert ws.receive_text() == "hello, world!"
Testing Error Handling
----------------------
By default, the test client raises exceptions from your route handlers,
which is usually what you want — it makes bugs obvious. But when you're
testing error handling specifically, you want to see the error response
instead. Disable exception propagation with ``raise_server_exceptions``::
from starlette.testclient import TestClient
def test_500(api):
@api.route("/fail")
def fail(req, resp):
raise ValueError("something broke")
client = TestClient(api, raise_server_exceptions=False)
r = client.get(api.url_for(fail))
assert r.status_code == 500
If you've registered a custom exception handler, you can test that too::
def test_custom_error(api):
@api.exception_handler(ValueError)
async def handle(req, resp, exc):
resp.status_code = 400
resp.media = {"error": str(exc)}
@api.route("/fail")
def fail(req, resp):
raise ValueError("bad input")
client = TestClient(api, raise_server_exceptions=False)
r = client.get(api.url_for(fail))
assert r.status_code == 400
assert r.json() == {"error": "bad input"}
Testing Lifespan Events
-----------------------
If your app uses startup and shutdown events (for database connections,
caches, etc.), you need the test client to trigger them. Wrap the client
in a ``with`` block — startup runs on enter, shutdown runs on exit::
def test_with_lifespan(api):
started = {"value": False}
@api.on_event("startup")
async def on_startup():
started["value"] = True
@api.route("/")
def check(req, resp):
resp.media = {"started": started["value"]}
with api.requests as session:
r = session.get("http://;/")
assert r.json() == {"started": True}
Without the ``with`` block, lifespan events won't fire, which can lead to
confusing test failures if your routes depend on startup initialization.
Testing Before and After Hooks
------------------------------
Before-request and after-request hooks run automatically during tests,
just like in production. You can verify their effects on the response::
def test_hooks(api):
@api.route(before_request=True)
def add_version(req, resp):
resp.headers["X-Version"] = "3.2"
@api.after_request()
def add_timing(req, resp):
resp.headers["X-Served-By"] = "responder"
@api.route("/")
def view(req, resp):
resp.text = "ok"
r = api.requests.get("/")
assert r.headers["X-Version"] == "3.2"
assert r.headers["X-Served-By"] == "responder"
Testing Rate Limiting
---------------------
Rate limiters are just hooks — they run automatically during tests.
Verify the headers and the 429 response::
from responder.ext.ratelimit import RateLimiter
def test_rate_limiting():
api = responder.API(allowed_hosts=["localhost"])
limiter = RateLimiter(requests=2, period=60)
limiter.install(api)
@api.route("/")
def view(req, resp):
resp.text = "ok"
# First two requests succeed
for _ in range(2):
r = api.requests.get("http://localhost/")
assert r.status_code == 200
assert "X-RateLimit-Remaining" in r.headers
# Third request is rate limited
r = api.requests.get("http://localhost/")
assert r.status_code == 429
Testing Mounted Apps
--------------------
When testing WSGI apps mounted at a subroute, use ``localhost`` as the
host to avoid Werkzeug's trusted host validation::
from flask import Flask
def test_flask_mount():
api = responder.API(allowed_hosts=["localhost"])
flask_app = Flask(__name__)
@flask_app.route("/")
def hello():
return "Hello from Flask!"
api.mount("/flask", flask_app)
r = api.requests.get("http://localhost/flask")
assert r.status_code == 200
assert "Hello from Flask" in r.text
Tips
----
- **Keep tests fast.** The in-process test client is already fast — no
network overhead. Avoid ``time.sleep()`` in tests.
- **One API per test** when testing configuration. If you need a specific
``API()`` configuration (like ``cors=True``), create a new instance
in the test rather than sharing the fixture.
- Use ``api.url_for()`` instead of hard-coded paths. It's a small
thing, but it makes refactoring painless.
- **Test the contract, not the implementation.** Assert on status codes,
response bodies, and headers — not on internal state.
- **Use ``localhost`` for mounted WSGI apps.** Werkzeug 3.1.7+ validates
the ``Host`` header, so avoid synthetic hosts like ``;`` in tests.
-738
View File
@@ -1,738 +0,0 @@
Feature Tour
============
This section walks through Responder's features in depth. Each section
explains the concept, shows working code, and explains the design choices
behind it. If you're new to web development, this is a good place to learn
how modern web frameworks work under the hood.
Method Filtering
----------------
HTTP defines several *methods* (also called verbs) that describe what a
client wants to do with a resource. The most common are:
- ``GET`` — retrieve data
- ``POST`` — create something new
- ``PUT`` — replace something entirely
- ``PATCH`` — update part of something
- ``DELETE`` — remove something
By default, a Responder route matches all methods. This is fine for simple
endpoints, but REST APIs typically map different methods to different
operations. Use the ``methods`` parameter to restrict a route::
@api.route("/items", methods=["GET"])
def list_items(req, resp):
resp.media = {"items": []}
@api.route("/items", methods=["POST"], check_existing=False)
async def create_item(req, resp):
data = await req.media()
resp.media = {"created": data}
Note the ``check_existing=False`` — Responder normally prevents you from
registering two routes with the same path (to catch typos). When you
intentionally want multiple handlers for the same path with different
methods, you need to opt in.
Class-Based Views
-----------------
Function-based views are great for simple endpoints, but sometimes you want
to group related HTTP methods together into a single resource. This is
where class-based views come in — a pattern popularized by
`Falcon <https://falconframework.org/>`_.
Responder dispatches to the appropriate method handler based on the HTTP
method::
@api.route("/{greeting}")
class GreetingResource:
def on_get(self, req, resp, *, greeting):
resp.text = f"{greeting}, world!"
def on_post(self, req, resp, *, greeting):
resp.media = {"received": greeting}
def on_request(self, req, resp, *, greeting):
"""Called on EVERY request, before the method-specific handler."""
resp.headers["X-Greeting"] = greeting
The ``on_request`` method is called for all HTTP methods, much like
middleware scoped to a single route. Method-specific handlers (``on_get``,
``on_post``, ``on_put``, ``on_delete``, etc.) are called after.
No inheritance required — just define a class with the right method names.
This is simpler than Django's ``View`` classes and more Pythonic than
framework-specific base classes.
Lifespan Events
---------------
Real applications need to set up resources when they start (database
connection pools, ML models, caches) and tear them down when they stop.
This is called the application *lifespan*.
The modern approach is the *context manager* pattern, where startup and
shutdown are two halves of the same block::
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app):
# Startup — runs before the first request
print("connecting to database...")
yield
# Shutdown — runs after the server stops
print("closing connections...")
api = responder.API(lifespan=lifespan)
Everything before ``yield`` runs at startup. Everything after runs at
shutdown. If startup fails, the server won't start. If shutdown raises,
it's logged but the server still exits.
The traditional event decorator style also works::
@api.on_event("startup")
async def startup():
print("starting up")
@api.on_event("shutdown")
async def shutdown():
print("shutting down")
The context manager is preferred for new code — it keeps related startup
and shutdown logic together and makes resource cleanup more explicit.
Serving Files
-------------
Web applications often need to serve files — downloads, reports, images.
Responder makes this simple with ``resp.file()``, which reads a file from
disk and sets the ``Content-Type`` header automatically using Python's
``mimetypes`` module::
@api.route("/download")
def download(req, resp):
resp.file("reports/annual.pdf")
You can override the content type if the automatic detection isn't right::
@api.route("/image")
def image(req, resp):
resp.file("photos/cat.jpg", content_type="image/jpeg")
For large files, use ``resp.stream_file()`` to avoid loading the entire
file into memory. This streams the file in chunks::
@api.route("/export")
def export(req, resp):
resp.stream_file("data/export.csv")
Custom Error Handling
---------------------
In production, you don't want your users to see raw Python tracebacks.
Responder lets you register custom handlers for specific exception types,
so you can return clean, structured error responses::
@api.exception_handler(ValueError)
async def handle_value_error(req, resp, exc):
resp.status_code = 400
resp.media = {"error": str(exc)}
Now, any route that raises a ``ValueError`` will return a clean JSON
response with a 400 status code instead of a generic 500 error page.
This is a common pattern in API development — you define your own exception
classes for different error conditions, register handlers for each, and
your API always returns consistent, machine-readable error responses.
Before-Request Hooks
--------------------
Sometimes you need to run the same code before every request —
authentication checks, request logging, adding common headers, or setting
up per-request state. Before-request hooks let you do this without
duplicating code in every route::
@api.route(before_request=True)
def add_headers(req, resp):
resp.headers["X-API-Version"] = "3.2"
**Short-circuiting** is the really powerful part. If your hook sets
``resp.status_code``, the route handler is skipped entirely and the
response is sent immediately. This is the pattern for authentication::
@api.route(before_request=True)
def auth_check(req, resp):
if "Authorization" not in req.headers:
resp.status_code = 401
resp.media = {"error": "unauthorized"}
If the ``Authorization`` header is missing, the client gets a 401 response
and the actual route handler never runs. This is cleaner than adding
auth checks to every individual route.
After-Request Hooks
-------------------
The complement to before-request hooks. After-request hooks run after the
route handler completes but before the response is sent. They're useful
for logging, adding response headers, or any post-processing::
@api.after_request()
def log_response(req, resp):
print(f"{req.method} {req.full_url} -> {resp.status_code}")
@api.after_request()
async def add_timing(req, resp):
resp.headers["X-Served-By"] = "responder"
WebSocket Support
-----------------
HTTP is a request-response protocol — the client asks, the server answers.
But some applications need real-time, bidirectional communication: chat
apps, live dashboards, multiplayer games, collaborative editors.
`WebSockets <https://en.wikipedia.org/wiki/WebSocket>`_ solve this by
upgrading an HTTP connection into a persistent, full-duplex channel where
both sides can send messages at any time::
@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}!")
await ws.close()
You can send and receive in multiple formats:
- ``send_text`` / ``receive_text`` — plain text strings
- ``send_json`` / ``receive_json`` — JSON objects (auto-serialized)
- ``send_bytes`` / ``receive_bytes`` — raw binary data
WebSocket routes are marked with ``websocket=True`` in the route decorator.
They receive a ``ws`` object instead of ``req`` and ``resp``.
Server-Sent Events (SSE)
-------------------------
SSE is a simpler alternative to WebSockets for *one-way* real-time
communication — the server pushes events to the client, but the client
can't send messages back. This is perfect for live feeds, progress bars,
notification streams, and AI response streaming.
Unlike WebSockets, SSE works over plain HTTP, is automatically reconnected
by the browser, and doesn't require any special client-side libraries::
@api.route("/events")
async def events(req, resp):
@resp.sse
async def stream():
for i in range(10):
yield {"data": f"message {i}"}
On the client side, you consume SSE events with JavaScript's built-in
``EventSource`` API::
const source = new EventSource("/events");
source.onmessage = (event) => {
console.log(event.data);
};
Each yielded value can be a string (treated as data) or a dict with the
standard SSE fields::
yield {"event": "update", "data": "hello", "id": "1", "retry": "5000"}
yield "simple string message"
GraphQL
-------
`GraphQL <https://graphql.org/>`_ is a query language for APIs that lets
clients request exactly the data they need — no more, no less. Instead of
multiple REST endpoints, you define a schema and let clients query it.
Responder includes built-in GraphQL support via
`Graphene <https://graphene-python.org/>`_. Set up a full GraphQL endpoint
with a single method call::
import graphene
class Query(graphene.ObjectType):
hello = graphene.String(name=graphene.String(default_value="stranger"))
def resolve_hello(self, info, name):
return f"Hello {name}"
api.graphql("/graphql", schema=graphene.Schema(query=Query))
Visiting ``/graphql`` in a browser renders the
`GraphiQL <https://github.com/graphql/graphiql>`_ interactive IDE, where
you can explore your schema, write queries, and see results in real-time.
Programmatic clients can POST JSON queries to the same endpoint.
You can access the Responder request and response objects in your resolvers
through ``info.context["request"]`` and ``info.context["response"]``.
OpenAPI Documentation
---------------------
`OpenAPI <https://www.openapis.org/>`_ (formerly Swagger) is the industry
standard for describing REST APIs. An OpenAPI specification lets you
auto-generate interactive documentation, client libraries, and validation
logic.
Responder generates OpenAPI specs from your code::
api = responder.API(
title="Pet Store",
version="1.0",
openapi="3.0.2",
docs_route="/docs",
)
This gives you:
- An OpenAPI schema at ``/schema.yml``
- Interactive Swagger UI documentation at ``/docs``
There are three ways to document your endpoints.
**Pydantic models** — the recommended approach. Use ``request_model`` and
``response_model`` to annotate your routes, and Responder generates the
schema automatically. When ``request_model`` is set, request bodies are
also validated automatically — invalid inputs get a ``422`` response with
detailed error messages::
from pydantic import BaseModel
class PetIn(BaseModel):
name: str
age: int = 0
class PetOut(BaseModel):
id: int
name: str
age: int
@api.route("/pets", methods=["POST"],
request_model=PetIn, response_model=PetOut)
async def create_pet(req, resp):
data = await req.media()
resp.media = {"id": 1, **data}
When ``response_model`` is set, the response is serialized through the
model — extra fields are stripped and types are enforced.
**YAML docstrings** — for full control, embed OpenAPI YAML in the
docstring::
@api.route("/pets")
def list_pets(req, resp):
"""A list of pets.
---
get:
description: Get all pets
responses:
200:
description: A list of pets
"""
resp.media = [{"name": "Fido"}]
**Marshmallow schemas** — if you're already using marshmallow::
from marshmallow import Schema, fields
@api.schema("Pet")
class PetSchema(Schema):
name = fields.Str()
All three approaches can be mixed in the same API. You can choose from
multiple documentation themes: ``swagger_ui`` (default), ``redoc``,
``rapidoc``, or ``elements``.
Route Groups
------------
As your application grows, you'll want to organize routes logically.
Route groups let you share a URL prefix across related endpoints — a
common pattern for API versioning::
v1 = api.group("/v1")
@v1.route("/users")
def list_users(req, resp):
resp.media = []
@v1.route("/users/{user_id:int}")
def get_user(req, resp, *, user_id):
resp.media = {"id": user_id}
v2 = api.group("/v2")
@v2.route("/users")
def list_users_v2(req, resp):
resp.media = {"users": [], "total": 0}
This keeps your code organized without affecting the routing logic.
Mounting Other Apps
-------------------
Responder can mount any WSGI or ASGI application at a subroute. This is
incredibly useful for gradual migrations — you can run Flask and Responder
side by side, moving routes over one at a time::
from flask import Flask
flask_app = Flask(__name__)
@flask_app.route("/")
def hello():
return "Hello from Flask!"
api.mount("/flask", flask_app)
Requests to ``/flask/`` will be handled by Flask. Everything else goes
through Responder. Both WSGI and ASGI apps are supported — Responder
wraps WSGI apps in an ASGI adapter automatically.
You can also mount `marimo <https://marimo.io/>`_ notebooks as
interactive dashboards within your API::
import marimo
server = (
marimo.create_asgi_app()
.with_app(path="", root="./notebooks/dashboard.py")
.with_app(path="/analysis", root="./notebooks/analysis.py")
)
api.mount("/notebooks", server.build())
Notebooks are served at ``/notebooks/`` and ``/notebooks/analysis``,
with full interactivity — reactive cells, widgets, plots, and all.
Cookies
-------
`Cookies <https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies>`_ are
small pieces of data that the server asks the browser to store and send
back with every subsequent request. They're the foundation of sessions,
authentication tokens, and user preferences on the web.
Reading and writing cookies is straightforward::
# Read cookies from the request
session_id = req.cookies.get("session_id")
# Set a cookie on the response
resp.cookies["hello"] = "world"
For production use, you'll want to set security directives. The
``httponly`` flag prevents JavaScript from reading the cookie (defending
against XSS attacks), and ``secure`` ensures it's only sent over HTTPS::
resp.set_cookie(
"token",
value="abc123",
max_age=3600, # expires in 1 hour
secure=True, # HTTPS only
httponly=True, # no JavaScript access
path="/",
)
Cookie-Based Sessions
---------------------
Sessions let you store per-user data across multiple requests. Responder's
built-in sessions are cookie-based — the session data is serialized, signed
with your secret key, and stored in a cookie. The signature prevents
tampering: if someone modifies the cookie, the signature won't match and
the data will be rejected::
@api.route("/login")
def login(req, resp):
resp.session["username"] = "alice"
@api.route("/profile")
def profile(req, resp):
resp.media = {"user": req.session.get("username")}
.. warning::
Always set a secret key in production. The default key is not secret::
api = responder.API(secret_key="your-secret-key-here")
Static Files
------------
Most web applications serve static assets — CSS stylesheets, JavaScript
files, images, fonts. Responder serves these from the ``static/`` directory
by default::
api = responder.API(static_dir="static", static_route="/static")
Place your assets in the ``static/`` directory and they'll be served
automatically at ``/static/style.css``, ``/static/app.js``, etc.
For single-page applications (React, Vue, Angular), you can serve
``index.html`` as the default response for all unmatched routes::
api.add_route("/", static=True)
CORS
----
`CORS <https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS>`_ (Cross-
Origin Resource Sharing) is a security mechanism that controls which
websites can make requests to your API. Browsers enforce this — if your
API is at ``api.example.com`` and your frontend is at ``app.example.com``,
the browser will block requests unless your API explicitly allows it.
Enable CORS and configure which origins are allowed::
api = responder.API(cors=True, cors_params={
"allow_origins": ["https://app.example.com"],
"allow_methods": ["GET", "POST"],
"allow_headers": ["*"],
"allow_credentials": True,
"max_age": 600,
})
The default policy is restrictive — you must explicitly allow each origin.
Using ``["*"]`` for allow_origins permits any website to call your API,
which is fine for public APIs but not for private ones.
HSTS
----
`HSTS <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security>`_
(HTTP Strict Transport Security) tells browsers to always use HTTPS when
communicating with your server. Once a browser sees the HSTS header, it
will refuse to connect over plain HTTP, even if the user types ``http://``
in the address bar::
api = responder.API(enable_hsts=True)
Trusted Hosts
-------------
The ``Host`` header in an HTTP request tells the server which domain name
the client used. Attackers can forge this header to trick your application
into generating URLs to malicious domains (a class of attack called *Host
header injection*).
Restrict which hostnames your application accepts::
api = responder.API(allowed_hosts=["example.com", "*.example.com"])
Requests with unrecognized hosts get a ``400 Bad Request``. Wildcard
patterns are supported. By default, all hostnames are allowed.
Request ID
----------
In distributed systems, tracing a single request across multiple services
is essential for debugging. Request IDs are unique identifiers attached to
each request — if something goes wrong, you can search your logs for that
ID and find every related event.
Responder can auto-generate request IDs. If the client sends an
``X-Request-ID`` header (common in microservice architectures), it's
forwarded. Otherwise, a new UUID is generated::
api = responder.API(request_id=True)
The ID appears in the ``X-Request-ID`` response header.
Rate Limiting
-------------
Rate limiting prevents individual clients from overwhelming your API with
too many requests. It's essential for public APIs, and good practice even
for internal ones.
Responder includes a built-in token bucket rate limiter::
from responder.ext.ratelimit import RateLimiter
limiter = RateLimiter(requests=100, period=60) # 100 req/min
limiter.install(api)
When the limit is exceeded, clients receive a ``429 Too Many Requests``
response with a ``Retry-After`` header. Every response includes
``X-RateLimit-Limit`` and ``X-RateLimit-Remaining`` headers so clients
can pace themselves.
The rate limiter is per-client, keyed by IP address.
Structured Logging
------------------
Production applications need structured, searchable logs. Responder
includes built-in logging that automatically attaches request context
— request ID, HTTP method, path, and client IP — to every log message
emitted during request handling::
api = responder.API(enable_logging=True)
This gives you:
- **Access logging** with timing for every request::
2026-03-24 12:00:00 [INFO] responder.access — GET /users → 200 (1.2ms)
- **A logger on the API instance** — use ``api.log`` anywhere in
your routes. Request context (ID, method, path, client IP) is
attached automatically::
@api.route("/users/{user_id:int}")
def get_user(req, resp, *, user_id):
api.log.info("fetching user %d", user_id)
# => [INFO] responder.app -- fetching user 42 [GET /users/42] [req:a1b2c3] [client:10.0.0.1]
resp.media = {"id": user_id}
- **Request IDs** generated automatically (or forwarded from the
``X-Request-ID`` header) and included in responses.
The logging uses Python's standard ``logging`` module, so it works with
any handler — files, syslog, JSON formatters, Datadog, Sentry, whatever
you already use.
For additional loggers (e.g. in helper modules), use ``get_logger``::
from responder.ext.logging import get_logger
logger = get_logger("myapp.db")
You can also access the current request context directly::
from responder.ext.logging import RequestContext
@api.route("/debug")
def debug(req, resp):
resp.media = {
"request_id": RequestContext.get_request_id(),
"client_ip": RequestContext.get_client_ip(),
}
When ``enable_logging=True`` is set, it supersedes ``request_id=True``
— the logging middleware handles request IDs itself, so you don't get
duplicate headers.
Pydantic Validation
-------------------
`Pydantic <https://docs.pydantic.dev/>`_ models integrate directly with
Responder's routing. Set ``request_model`` to validate incoming data and
``response_model`` to control the shape of outgoing data::
from pydantic import BaseModel
class ItemIn(BaseModel):
name: str
price: float
class ItemOut(BaseModel):
id: int
name: str
price: float
@api.route("/items", methods=["POST"],
request_model=ItemIn, response_model=ItemOut)
async def create_item(req, resp):
data = await req.media()
resp.media = {"id": 1, **data}
When ``request_model`` is set:
- Valid requests are parsed and the data is available via ``await req.media()``
- Invalid requests get an automatic ``422 Unprocessable Entity`` response
with detailed error messages — you don't write any validation code
When ``response_model`` is set:
- The response is serialized through the model before being sent
- Extra fields are stripped automatically
- Type coercion happens at the boundary
This is the recommended way to build validated REST APIs with Responder.
See the :doc:`tutorial-rest` for a complete walkthrough.
Content Negotiation
-------------------
Responder automatically negotiates the response format based on the
client's ``Accept`` header. Set ``resp.media`` to a Python object and
the right thing happens:
- ``Accept: application/json`` (default) → JSON
- ``Accept: application/x-yaml`` → YAML
- ``Accept: application/x-msgpack`` → MessagePack
This means a single endpoint serves multiple formats without any
conditional logic in your code::
@api.route("/data")
def data(req, resp):
resp.media = {"key": "value"}
Clients get the format they ask for::
$ curl http://localhost:5042/data
{"key": "value"}
$ curl -H "Accept: application/x-yaml" http://localhost:5042/data
key: value
MessagePack
-----------
`MessagePack <https://msgpack.org/>`_ is a binary serialization format
that's more compact and faster to parse than JSON. It's useful for
high-throughput APIs, IoT devices, and anywhere bandwidth matters.
Responder supports MessagePack alongside JSON and YAML::
# Decode a MessagePack request body
data = await req.media("msgpack")
# Respond with MessagePack
resp.media = {"result": [1, 2, 3]}
Content negotiation works automatically — clients can send
``Accept: application/x-msgpack`` to receive MessagePack responses
instead of JSON. You can also explicitly decode MessagePack request
bodies by passing ``"msgpack"`` to ``req.media()``.
-244
View File
@@ -1,244 +0,0 @@
Authentication
==============
Every API that handles user data needs authentication — a way to verify
who is making a request. This guide covers the most common patterns:
API keys, JWT tokens, and how to build reusable auth guards with
Responder's before-request hooks.
API Key Authentication
----------------------
The simplest approach. The client sends a secret key in a header, and
your server checks it against a known value. This is common for
server-to-server communication and simple APIs::
API_KEYS = {"sk-abc123", "sk-def456"}
@api.route(before_request=True)
def check_api_key(req, resp):
key = req.headers.get("X-API-Key")
if key not in API_KEYS:
resp.status_code = 401
resp.media = {"error": "Invalid or missing API key"}
Because the before-request hook sets ``resp.status_code``, the route
handler is skipped entirely for unauthorized requests. The client never
reaches your endpoint — the guard catches them first.
The client sends the key like this::
$ curl -H "X-API-Key: sk-abc123" http://localhost:5042/protected
Bearer Token Authentication
----------------------------
Bearer tokens are the standard for modern APIs. The client sends a token
in the ``Authorization`` header, and the server validates it. The most
common format is `JWT <https://jwt.io/>`_ (JSON Web Tokens).
Install PyJWT::
$ uv pip install pyjwt
Create a helper to encode and decode tokens::
import jwt
from datetime import datetime, timedelta, timezone
SECRET = "your-secret-key"
def create_token(user_id: int) -> str:
payload = {
"sub": user_id,
"exp": datetime.now(timezone.utc) + timedelta(hours=24),
}
return jwt.encode(payload, SECRET, algorithm="HS256")
def verify_token(token: str) -> dict | None:
try:
return jwt.decode(token, SECRET, algorithms=["HS256"])
except jwt.InvalidTokenError:
return None
Add a login endpoint that issues tokens, and a before-request hook that
verifies them::
@api.route("/login", methods=["POST"])
async def login(req, resp):
data = await req.media()
# In a real app, check credentials against a database
if data.get("username") == "admin" and data.get("password") == "secret":
token = create_token(user_id=1)
resp.media = {"token": token}
else:
resp.status_code = 401
resp.media = {"error": "Invalid credentials"}
@api.route(before_request=True)
def auth_guard(req, resp):
# Skip auth for the login endpoint itself
if req.url.path == "/login":
return
auth = req.headers.get("Authorization", "")
if not auth.startswith("Bearer "):
resp.status_code = 401
resp.media = {"error": "Missing bearer token"}
return
token = auth[7:] # Strip "Bearer "
payload = verify_token(token)
if payload is None:
resp.status_code = 401
resp.media = {"error": "Invalid or expired token"}
return
# Store the authenticated user on the request state
req.state.user_id = payload["sub"]
Now any route can access the authenticated user::
@api.route("/me")
def get_me(req, resp):
resp.media = {"user_id": req.state.user_id}
The client flow:
1. ``POST /login`` with credentials → receive a token
2. Include ``Authorization: Bearer <token>`` on every subsequent request
3. The token expires after 24 hours — the client must log in again
Skipping Auth for Public Routes
--------------------------------
The example above skips auth for ``/login`` by checking the path. For
more control, you can use a set of public paths::
PUBLIC_PATHS = {"/login", "/signup", "/health", "/docs", "/schema.yml"}
@api.route(before_request=True)
def auth_guard(req, resp):
if req.url.path in PUBLIC_PATHS:
return
# ... check token
Custom Exception for Auth Errors
---------------------------------
For cleaner code, define a custom exception and register a handler::
class AuthError(Exception):
def __init__(self, message="Unauthorized", status_code=401):
self.message = message
self.status_code = status_code
@api.exception_handler(AuthError)
async def handle_auth_error(req, resp, exc):
resp.status_code = exc.status_code
resp.media = {"error": exc.message}
Now your auth guard can simply raise::
@api.route(before_request=True)
def auth_guard(req, resp):
if req.url.path in PUBLIC_PATHS:
return
if "Authorization" not in req.headers:
raise AuthError("Missing authorization header")
Using Sessions for Web Apps
----------------------------
For traditional web applications (with HTML pages and forms), cookie-based
sessions are simpler than tokens. The browser handles cookies automatically
— no client-side token management needed::
@api.route("/login", methods=["POST"])
async def login(req, resp):
data = await req.media("form")
if data["username"] == "admin" and data["password"] == "secret":
resp.session["user"] = data["username"]
api.redirect(resp, location="/dashboard")
else:
resp.status_code = 401
resp.html = "<p>Invalid credentials</p>"
@api.route("/dashboard")
def dashboard(req, resp):
user = req.session.get("user")
if not user:
api.redirect(resp, location="/login")
return
resp.html = f"<h1>Welcome, {user}!</h1>"
@api.route("/logout")
def logout(req, resp):
resp.session.clear()
api.redirect(resp, location="/login")
Remember to set a proper secret key::
api = responder.API(secret_key="your-production-secret-key")
The session data is signed (not encrypted) — users can read it but
can't tamper with it. Don't store sensitive data like passwords in
sessions.
Role-Based Access Control
--------------------------
For APIs where different users have different permissions, embed the
role in the token and check it in route-specific guards::
def create_token(user_id: int, role: str = "user") -> str:
payload = {
"sub": user_id,
"role": role,
"exp": datetime.now(timezone.utc) + timedelta(hours=24),
}
return jwt.encode(payload, SECRET, algorithm="HS256")
Create a helper that checks for a specific role::
def require_role(*roles):
"""Before-request hook factory that restricts by role."""
def check(req, resp):
user_role = getattr(req.state, "role", None)
if user_role not in roles:
resp.status_code = 403
resp.media = {"error": "Insufficient permissions"}
return check
Use it on specific routes::
@api.route("/admin/users", before_request=require_role("admin"))
def list_all_users(req, resp):
resp.media = {"users": [...]}
And store the role during token verification::
# In your auth_guard:
req.state.user_id = payload["sub"]
req.state.role = payload.get("role", "user")
Choosing an Auth Strategy
--------------------------
- **API keys** — simplest. Good for server-to-server, CLI tools, and
internal services. No expiration unless you build it.
- **JWT tokens** — standard for SPAs and mobile apps. Stateless, so
they scale well. Downside: you can't revoke them without a blocklist.
- **Sessions** — best for traditional web apps with HTML forms. The
browser manages cookies automatically. Stateful — the server controls
the session lifecycle.
Start with API keys for internal tools, JWT for public APIs, and
sessions for web apps with login pages.
-192
View File
@@ -1,192 +0,0 @@
Migrating from Flask
====================
If you're coming from Flask, you'll find Responder familiar but different
in a few key ways. This guide maps Flask concepts to their Responder
equivalents and shows you how to translate common patterns.
The Big Differences
-------------------
**No return values.** In Flask, you return a response. In Responder, you
mutate it. This is the single biggest difference:
Flask::
@app.route("/")
def hello():
return "hello, world!"
Responder::
@api.route("/")
def hello(req, resp):
resp.text = "hello, world!"
**Explicit request and response.** Flask uses a global ``request`` object
(via thread-local magic). Responder passes ``req`` and ``resp`` explicitly.
No magic, no import needed — they're right there in the function signature.
**ASGI, not WSGI.** Flask runs on WSGI, which is synchronous. Responder
runs on ASGI, which supports async natively. You can still write sync
views — Responder runs them in a thread pool automatically.
Quick Reference
---------------
.. list-table::
:header-rows: 1
:widths: 40 60
* - Flask
- Responder
* - ``Flask(__name__)``
- ``responder.API()``
* - ``return "text"``
- ``resp.text = "text"``
* - ``return jsonify(data)``
- ``resp.media = data``
* - ``return render_template("t.html", x=1)``
- ``resp.html = api.template("t.html", x=1)``
* - ``request.args["q"]``
- ``req.params["q"]``
* - ``request.json``
- ``await req.media()``
* - ``request.form``
- ``await req.media("form")``
* - ``request.headers["X"]``
- ``req.headers["X"]``
* - ``request.method``
- ``req.method``
* - ``request.cookies["x"]``
- ``req.cookies["x"]``
* - ``session["x"] = 1``
- ``resp.session["x"] = 1``
* - ``abort(404)``
- ``resp.status_code = 404``
* - ``redirect("/new")``
- ``api.redirect(resp, location="/new")``
* - ``@app.before_request``
- ``@api.route(before_request=True)``
* - ``@app.errorhandler(404)``
- ``@api.exception_handler(ValueError)``
* - ``app.run(debug=True)``
- ``api.run(debug=True)``
Route Parameters
----------------
Flask uses ``<angle_brackets>``. Responder uses ``{curly_braces}``
with the same type convertor idea:
Flask::
@app.route("/users/<int:user_id>")
def get_user(user_id):
return jsonify({"id": user_id})
Responder::
@api.route("/users/{user_id:int}")
def get_user(req, resp, *, user_id):
resp.media = {"id": user_id}
Note the ``*`` — route parameters are keyword-only arguments in
Responder. This makes the interface explicit about which arguments
come from the URL.
JSON APIs
---------
Flask::
@app.route("/api/items", methods=["POST"])
def create_item():
data = request.json
# ... create item
return jsonify(item), 201
Responder::
@api.route("/api/items", methods=["POST"])
async def create_item(req, resp):
data = await req.media()
# ... create item
resp.media = item
resp.status_code = 201
The ``await`` is needed because reading the request body is an async
I/O operation. This is more explicit than Flask's approach, and it
means the event loop isn't blocked while waiting for the body to arrive.
Templates
---------
Both use Jinja2. The syntax is nearly identical:
Flask::
@app.route("/hello/<name>")
def hello(name):
return render_template("hello.html", name=name)
Responder::
@api.route("/hello/{name}")
def hello(req, resp, *, name):
resp.html = api.template("hello.html", name=name)
Blueprints → Route Groups
--------------------------
Flask uses Blueprints to organize routes. Responder has route groups:
Flask::
bp = Blueprint("api", __name__, url_prefix="/api")
@bp.route("/users")
def list_users():
return jsonify([])
app.register_blueprint(bp)
Responder::
api_v1 = api.group("/api")
@api_v1.route("/users")
def list_users(req, resp):
resp.media = []
Gradual Migration
-----------------
You don't have to migrate all at once. Responder can mount your existing
Flask app at a subroute, so you can move endpoints over one at a time::
from flask import Flask
flask_app = Flask(__name__)
# Your existing Flask routes stay here
@flask_app.route("/legacy")
def legacy():
return "old endpoint"
# Mount Flask under /old, new routes go on Responder
api.mount("/old", flask_app)
@api.route("/new")
def new_endpoint(req, resp):
resp.media = {"modern": True}
Requests to ``/old/legacy`` go to Flask. Everything else goes to
Responder. When you've moved everything over, remove the mount.
-170
View File
@@ -1,170 +0,0 @@
Writing Middleware
==================
Middleware sits between the server and your route handlers, processing
every request and response that flows through your application. It's the
right tool for cross-cutting concerns — things that apply to *all*
requests, not just specific routes.
Common middleware use cases:
- Request logging and timing
- Authentication and authorization
- Adding security headers
- Request ID generation
- Rate limiting
- Response compression (built-in)
Hooks vs. Middleware
--------------------
Responder gives you two levels of request processing:
**Hooks** (``before_request`` / ``after_request``) run inside Responder's
routing layer. They receive Responder's ``req`` and ``resp`` objects and
are the simplest way to add behavior::
@api.route(before_request=True)
def add_header(req, resp):
resp.headers["X-Powered-By"] = "Responder"
@api.after_request()
def log_request(req, resp):
print(f"{req.method} {req.url.path} -> {resp.status_code}")
**Middleware** runs at the ASGI level, wrapping the entire application.
It's more powerful but more complex — you work with raw ASGI scopes
instead of Responder objects. Use middleware when you need to process
requests *before* they reach Responder's routing, or when you need to
integrate with Starlette middleware.
Using Starlette Middleware
--------------------------
Responder is built on Starlette, so any Starlette middleware works
out of the box::
from starlette.middleware.base import BaseHTTPMiddleware
class TimingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
import time
start = time.time()
response = await call_next(request)
duration = time.time() - start
response.headers["X-Response-Time"] = f"{duration:.3f}s"
return response
api.add_middleware(TimingMiddleware)
The ``dispatch`` method receives a Starlette ``Request`` and a
``call_next`` function. Call ``call_next(request)`` to pass the request
to the next middleware (or to your route handler). The return value is
a Starlette ``Response`` that you can modify before it's sent.
Built-in Middleware
-------------------
Responder configures several middleware components automatically:
- **GZipMiddleware** — compresses responses larger than 500 bytes
- **TrustedHostMiddleware** — validates the ``Host`` header
- **ServerErrorMiddleware** — catches unhandled exceptions
- **ExceptionMiddleware** — routes exceptions to your handlers
- **SessionMiddleware** — manages signed cookie sessions
Optional middleware you can enable:
- **CORSMiddleware** — ``api = responder.API(cors=True)``
- **HTTPSRedirectMiddleware** — ``api = responder.API(enable_hsts=True)``
Adding Third-Party Middleware
-----------------------------
Any ASGI middleware can be added with ``api.add_middleware()``::
from some_package import SomeMiddleware
api.add_middleware(SomeMiddleware, option1="value", option2=True)
Keyword arguments are passed to the middleware's constructor.
Middleware Order
----------------
Middleware wraps your application like layers of an onion. The *last*
middleware added is the *outermost* layer — it sees the request first
and the response last.
Responder's built-in middleware stack (from outermost to innermost):
1. SessionMiddleware
2. ServerErrorMiddleware
3. CORSMiddleware (if enabled)
4. TrustedHostMiddleware
5. HTTPSRedirectMiddleware (if enabled)
6. GZipMiddleware
7. ExceptionMiddleware
8. Your routes
When you call ``api.add_middleware()``, your middleware is added *outside*
the existing stack. Keep this in mind for ordering dependencies — if
middleware A depends on middleware B having run first, add B before A.
Writing Pure ASGI Middleware
----------------------------
For maximum performance and control, you can write middleware as a plain
ASGI application. This bypasses Starlette's ``BaseHTTPMiddleware``
abstraction — it's faster and gives you direct access to the ASGI
protocol::
class SecurityHeadersMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
if scope["type"] != "http":
await self.app(scope, receive, send)
return
async def send_with_headers(message):
if message["type"] == "http.response.start":
extra = [
(b"x-content-type-options", b"nosniff"),
(b"x-frame-options", b"DENY"),
(b"referrer-policy", b"strict-origin-when-cross-origin"),
]
message["headers"] = list(message["headers"]) + extra
await send(message)
await self.app(scope, receive, send_with_headers)
api.add_middleware(SecurityHeadersMiddleware)
This is the same pattern used internally by Starlette and uvicorn. The
middleware receives the ASGI ``scope``, ``receive``, and ``send`` callables,
and wraps ``send`` to inject headers into the response.
For most cases, ``BaseHTTPMiddleware`` is simpler and perfectly fine.
Use the pure ASGI approach when you need to handle WebSocket connections,
streaming responses, or want to avoid the overhead of request/response
object creation.
When to Use What
-----------------
- **Simple header additions, logging, auth checks** → use hooks
- **Response transformation, timing, third-party integrations** → use middleware
- **Rate limiting** → use the built-in ``RateLimiter`` (it uses hooks internally)
- **Request ID** → use ``api = responder.API(request_id=True)``
Start with hooks. They're simpler and cover most cases. Graduate to
middleware when hooks aren't enough.
-219
View File
@@ -1,219 +0,0 @@
Building a REST API
===================
This tutorial walks you through building a complete REST API from scratch.
By the end, you'll have a working API with CRUD operations, request
validation, error handling, and interactive documentation.
We'll build a simple book catalog — a service that lets you create, read,
update, and delete books.
Project Setup
-------------
Create a new file called ``app.py``::
import responder
api = responder.API(
title="Book Catalog",
version="1.0",
openapi="3.0.2",
docs_route="/docs",
)
We're enabling OpenAPI documentation from the start. Visit ``/docs`` at
any point to see interactive Swagger UI for your API.
Define Your Models
------------------
We'll use `Pydantic <https://docs.pydantic.dev/>`_ to define our data
models. Pydantic models serve double duty — they validate incoming data
*and* generate OpenAPI schemas automatically::
from pydantic import BaseModel
class BookIn(BaseModel):
"""What the client sends when creating a book."""
title: str
author: str
year: int
isbn: str | None = None
class Book(BaseModel):
"""What the API returns."""
id: int
title: str
author: str
year: int
isbn: str | None = None
``BookIn`` is the *input* model — it doesn't have an ``id`` because the
server assigns that. ``Book`` is the *output* model — it includes
everything. This input/output separation is a common REST API pattern.
In-Memory Storage
-----------------
For this tutorial, we'll store books in a simple dict. In a real
application, you'd use a database (see :doc:`tutorial-sqlalchemy`)::
books_db: dict[int, dict] = {}
next_id = 1
List All Books
--------------
The first endpoint — list all books. This is a ``GET`` request to
``/books``::
@api.route("/books", methods=["GET"], response_model=list)
def list_books(req, resp):
resp.media = list(books_db.values())
In REST API design, ``GET`` requests should never modify data. They're
*safe* and *idempotent* — calling them multiple times has the same effect
as calling them once.
Create a Book
-------------
To create a book, the client sends a ``POST`` request with a JSON body.
We use ``request_model=BookIn`` to validate the input automatically — if
the client sends bad data, they get a ``422`` response with error details::
@api.route("/books", methods=["POST"], check_existing=False,
request_model=BookIn, response_model=Book)
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
Note ``resp.status_code = 201`` — the HTTP ``201 Created`` status code
tells the client that a new resource was successfully created. This is
more informative than a generic ``200 OK``.
Get a Single Book
-----------------
Retrieve a specific book by its ID. The ``{book_id:int}`` route parameter
ensures only integer IDs match — requests like ``/books/abc`` will 404::
@api.route("/books/{book_id:int}", methods=["GET"], response_model=Book)
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]
Update a Book
-------------
``PUT`` replaces a resource entirely. The client must send all fields::
@api.route("/books/{book_id:int}", methods=["PUT"], check_existing=False,
request_model=BookIn, response_model=Book)
async def update_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
data = await req.media()
book = {"id": book_id, **data}
books_db[book_id] = book
resp.media = book
Delete a Book
-------------
``DELETE`` removes a resource. The convention is to return ``204 No Content``
with an empty body on success::
@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
Error Handling
--------------
Let's add a custom error handler so any ``ValueError`` in our code returns
a clean JSON response instead of a 500 error::
@api.exception_handler(ValueError)
async def handle_value_error(req, resp, exc):
resp.status_code = 400
resp.media = {"error": str(exc)}
Run It
------
Add the standard entry point at the bottom of your file::
if __name__ == "__main__":
api.run()
Start the server::
$ python app.py
Visit ``http://localhost:5042/docs`` to see your interactive API
documentation. You can test every endpoint directly from the browser.
Try It Out
----------
Using ``curl``::
# Create a book
$ curl -X POST http://localhost:5042/books \
-H "Content-Type: application/json" \
-d '{"title": "Dune", "author": "Frank Herbert", "year": 1965}'
# List all books
$ curl http://localhost:5042/books
# Get a specific book
$ curl http://localhost:5042/books/1
# Update a book
$ curl -X PUT http://localhost:5042/books/1 \
-H "Content-Type: application/json" \
-d '{"title": "Dune", "author": "Frank Herbert", "year": 1965, "isbn": "978-0441172719"}'
# Delete a book
$ curl -X DELETE http://localhost:5042/books/1
What's Next
-----------
This tutorial used in-memory storage. For a real application, you'll want
a database. See :doc:`tutorial-sqlalchemy` for how to integrate SQLAlchemy
with Responder using the lifespan pattern.
-255
View File
@@ -1,255 +0,0 @@
Using SQLAlchemy
================
Most real web applications need a database. This guide shows how to
integrate `SQLAlchemy <https://www.sqlalchemy.org/>`_ with Responder,
using async support and the lifespan pattern for connection management.
SQLAlchemy is the most popular Python database toolkit. It gives you an
ORM (Object-Relational Mapper) for working with databases using Python
classes instead of raw SQL, plus a powerful query builder for when you
need fine-grained control.
Installation
------------
Install SQLAlchemy with async support and an async database driver.
We'll use SQLite for simplicity, but the pattern works with PostgreSQL,
MySQL, and any other database SQLAlchemy supports::
$ uv pip install 'sqlalchemy[asyncio]' aiosqlite
Define Your Models
------------------
SQLAlchemy models map Python classes to database tables. Each attribute
becomes a column::
# models.py
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class Book(Base):
__tablename__ = "books"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
title: Mapped[str] = mapped_column(String, nullable=False)
author: Mapped[str] = mapped_column(String, nullable=False)
year: Mapped[int] = mapped_column(nullable=False)
isbn: Mapped[str | None] = mapped_column(String, nullable=True)
This uses SQLAlchemy 2.0's ``Mapped`` type annotations and
``mapped_column()``, which give you type checker support and cleaner
syntax than the older ``Column()`` style. Each model class corresponds
to a table, and each ``mapped_column()`` corresponds to a column.
Database Setup
--------------
Create the async engine and session factory. The *engine* manages
the connection pool. The *session* is your unit of work — you use it to
query and modify data within a transaction::
# database.py
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
DATABASE_URL = "sqlite+aiosqlite:///./books.db"
engine = create_async_engine(DATABASE_URL, echo=True)
async_session = async_sessionmaker(engine, expire_on_commit=False)
The ``echo=True`` flag prints all SQL queries to the console — very
helpful during development, but you'll want to disable it in production.
The ``expire_on_commit=False`` flag keeps model attributes accessible
after a commit, which is convenient for returning created objects in
API responses.
Lifespan for Startup and Shutdown
----------------------------------
Use Responder's lifespan context manager to create the database tables
on startup and dispose of connections on shutdown::
# app.py
from contextlib import asynccontextmanager
import responder
from database import engine
from models import Base
@asynccontextmanager
async def lifespan(app):
# Startup: create tables
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
yield
# Shutdown: close all connections
await engine.dispose()
api = responder.API(lifespan=lifespan)
This is the proper way to manage database connections in an async
application. The lifespan context manager ensures that:
1. Tables are created before the first request
2. The connection pool is properly closed when the server shuts down
3. If table creation fails, the server won't start
CRUD Endpoints
--------------
Now let's build the API endpoints. Each one opens a database session,
does its work, and commits or rolls back::
from pydantic import BaseModel
from sqlalchemy import select
from database import async_session
from models import Book
# Pydantic models for request/response validation
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
class Config:
from_attributes = True
The ``from_attributes = True`` config tells Pydantic to read data from
SQLAlchemy model attributes (not just dicts). This lets you pass a
SQLAlchemy ``Book`` object directly to ``BookOut``.
**List all books**::
@api.route("/books", methods=["GET"])
async def list_books(req, resp):
async with async_session() as session:
result = await session.execute(select(Book))
books = result.scalars().all()
resp.media = [BookOut.model_validate(b).model_dump() for b in books]
**Create a book**::
@api.route("/books", methods=["POST"], check_existing=False,
request_model=BookIn, response_model=BookOut)
async def create_book(req, resp):
data = await req.media()
async with async_session() as session:
book = Book(**data)
session.add(book)
await session.commit()
await session.refresh(book)
resp.media = BookOut.model_validate(book).model_dump()
resp.status_code = 201
**Get a single book**::
@api.route("/books/{book_id:int}", methods=["GET"])
async def get_book(req, resp, *, book_id):
async with async_session() as session:
book = await session.get(Book, book_id)
if book is None:
resp.status_code = 404
resp.media = {"error": "Book not found"}
return
resp.media = BookOut.model_validate(book).model_dump()
**Update a book**::
@api.route("/books/{book_id:int}", methods=["PUT"], check_existing=False,
request_model=BookIn)
async def update_book(req, resp, *, book_id):
data = await req.media()
async with async_session() as session:
book = await session.get(Book, book_id)
if book is None:
resp.status_code = 404
resp.media = {"error": "Book not found"}
return
for key, value in data.items():
setattr(book, key, value)
await session.commit()
await session.refresh(book)
resp.media = BookOut.model_validate(book).model_dump()
**Delete a book**::
@api.route("/books/{book_id:int}", methods=["DELETE"], check_existing=False)
async def delete_book(req, resp, *, book_id):
async with async_session() as session:
book = await session.get(Book, book_id)
if book is None:
resp.status_code = 404
resp.media = {"error": "Book not found"}
return
await session.delete(book)
await session.commit()
resp.status_code = 204
Run It
------
::
if __name__ == "__main__":
api.run()
Start the server and you'll see SQLAlchemy's SQL echo in the console.
The SQLite database file ``books.db`` is created automatically on first
startup.
Using PostgreSQL
----------------
To switch to PostgreSQL, just change the connection URL and driver::
$ uv pip install asyncpg
::
DATABASE_URL = "postgresql+asyncpg://user:pass@localhost/mydb"
Everything else stays the same. SQLAlchemy abstracts the database
differences so your application code doesn't need to change.
Tips
----
- Use ``async with async_session() as session`` for every request.
Don't share sessions across requests — each request should get its
own session and transaction.
- For complex queries, use SQLAlchemy's ``select()`` with ``.where()``,
``.order_by()``, ``.limit()``, and ``.offset()`` — it composes
naturally.
- In production, use connection pooling (SQLAlchemy does this by
default) and set pool size limits appropriate for your database.
- Consider `Alembic <https://alembic.sqlalchemy.org/>`_ for database
migrations — it tracks schema changes over time so you can evolve
your database without losing data.
-219
View File
@@ -1,219 +0,0 @@
WebSocket Tutorial
==================
HTTP is request-response — the client asks, the server answers, and the
connection closes. WebSockets upgrade that into a persistent, bidirectional
channel where both sides can send messages at any time. This is what powers
chat apps, live dashboards, multiplayer games, and collaborative editors.
This tutorial builds a simple chat room to show how WebSockets work in
Responder.
How WebSockets Work
-------------------
1. The client sends a normal HTTP request with an ``Upgrade: websocket``
header.
2. The server accepts the upgrade and the connection switches protocols.
3. Both sides can now send messages freely — no more request/response.
4. Either side can close the connection at any time.
In Responder, WebSocket routes receive a ``ws`` object instead of
``req`` and ``resp``. The ``ws`` object has methods for accepting the
connection, sending and receiving data, and closing.
Echo Server
-----------
The simplest WebSocket — echoes everything back::
@api.route("/ws", websocket=True)
async def echo(ws):
await ws.accept()
while True:
data = await ws.receive_text()
await ws.send_text(f"Echo: {data}")
The ``await ws.accept()`` call completes the WebSocket handshake. After
that, you're in a loop — receive a message, send a response.
Test it with a WebSocket client::
$ pip install websocket-client
$ python -c "
import websocket
ws = websocket.create_connection('ws://localhost:5042/ws')
ws.send('hello')
print(ws.recv()) # Echo: hello
ws.close()
"
Chat Room
---------
A chat room needs to broadcast messages to all connected clients. We keep
a set of active connections and iterate through them when someone sends
a message::
from starlette.websockets import WebSocketDisconnect
connected = set()
@api.route("/chat", websocket=True)
async def chat(ws):
await ws.accept()
connected.add(ws)
try:
while True:
message = await ws.receive_text()
# Broadcast to all connected clients
for client in connected:
await client.send_text(message)
except WebSocketDisconnect:
pass
finally:
connected.discard(ws)
The ``try/finally`` block ensures we remove disconnected clients from
the set, even if the connection drops unexpectedly. Catching
``WebSocketDisconnect`` specifically (rather than bare ``Exception``)
makes the intent clear and avoids swallowing real bugs.
Data Formats
------------
WebSockets support three data formats:
**Text** — plain strings::
await ws.send_text("hello")
message = await ws.receive_text()
**JSON** — auto-serialized Python objects::
await ws.send_json({"type": "update", "data": [1, 2, 3]})
message = await ws.receive_json()
**Binary** — raw bytes, useful for images, audio, or custom protocols::
await ws.send_bytes(b"\x00\x01\x02")
data = await ws.receive_bytes()
HTML Client
-----------
Here's a minimal HTML page that connects to the chat room. The browser's
built-in ``WebSocket`` API handles everything — no libraries needed:
.. code-block:: html
<!DOCTYPE html>
<html>
<body>
<div id="messages"></div>
<input id="input" placeholder="Type a message..." />
<script>
const ws = new WebSocket("ws://localhost:5042/chat");
const messages = document.getElementById("messages");
const input = document.getElementById("input");
ws.onmessage = (event) => {
const p = document.createElement("p");
p.textContent = event.data;
messages.appendChild(p);
};
input.addEventListener("keypress", (e) => {
if (e.key === "Enter") {
ws.send(input.value);
input.value = "";
}
});
</script>
</body>
</html>
Save this as ``static/index.html`` and serve it with Responder's
built-in static file support.
Before-Request Hooks for WebSockets
------------------------------------
You can run code before a WebSocket connection is established, just like
HTTP before-request hooks. This is useful for authentication::
@api.before_request(websocket=True)
async def ws_auth(ws):
# Check for a token in the query string
# (WebSocket headers are limited in browsers)
await ws.accept()
WebSocket before-request hooks receive the ``ws`` object and must call
``await ws.accept()`` if they want the connection to proceed.
Connection Lifecycle
--------------------
WebSocket connections go through several states:
1. **Connecting** — the client sends an upgrade request
2. **Open** — after ``await ws.accept()``, both sides can send messages
3. **Closing** — either side initiates a close handshake
4. **Closed** — the connection is fully terminated
When a client disconnects (closes the tab, loses network), the next
``await ws.receive_text()`` raises ``WebSocketDisconnect``. Always
handle this — otherwise your server accumulates dead connections::
from starlette.websockets import WebSocketDisconnect
@api.route("/ws", websocket=True)
async def handler(ws):
await ws.accept()
try:
while True:
data = await ws.receive_text()
await ws.send_text(f"Got: {data}")
except WebSocketDisconnect:
print("Client disconnected")
You can also close connections from the server side::
await ws.close(code=1000) # 1000 = normal closure
Common close codes: ``1000`` (normal), ``1001`` (going away),
``1008`` (policy violation), ``1011`` (server error).
Testing WebSockets
------------------
Use Starlette's ``TestClient`` for WebSocket tests::
from starlette.testclient import TestClient
def test_echo():
client = TestClient(api)
with client.websocket_connect("/ws") as ws:
ws.send_text("hello")
assert ws.receive_text() == "Echo: hello"
The ``websocket_connect`` context manager handles the connection
lifecycle — it connects on enter and disconnects on exit.
You can also test that connections are properly rejected::
from starlette.websockets import WebSocketDisconnect
def test_websocket_404():
client = TestClient(api)
with pytest.raises(WebSocketDisconnect):
with client.websocket_connect("/nonexistent"):
pass
@@ -1,26 +0,0 @@
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;
}
});
}
}
}
@@ -1,90 +0,0 @@
/**
* 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;
}
-663
View File
@@ -1,663 +0,0 @@
/* -- 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;
}
-906
View File
@@ -1,906 +0,0 @@
/*
* 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;
}
}
-4
View File
@@ -1,4 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 313 B

-7
View File
File diff suppressed because one or more lines are too long
-5
View File
@@ -1,5 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 411 B

-94
View File
@@ -1,94 +0,0 @@
/* 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;
}
}
-248
View File
@@ -1,248 +0,0 @@
// 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)
-73
View File
@@ -1,73 +0,0 @@
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
}
-1
View File
@@ -1 +0,0 @@
/* This file intentionally left blank. */
@@ -1,26 +0,0 @@
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;
}
});
}
}
}
@@ -1,90 +0,0 @@
/**
* 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;
}
-149
View File
@@ -1,149 +0,0 @@
/*
* 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);
-13
View File
@@ -1,13 +0,0 @@
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,
};
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 B

-5
View File
@@ -1,5 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 490 B

-192
View File
@@ -1,192 +0,0 @@
/*
* 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;
}
}
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 B

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 B

-84
View File
@@ -1,84 +0,0 @@
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 */
-635
View File
@@ -1,635 +0,0 @@
/*
* 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);
-154
View File
@@ -1,154 +0,0 @@
/* 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();
});
-1131
View File
File diff suppressed because one or more lines are too long
-114
View File
@@ -1,114 +0,0 @@
<!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 &#8212; 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">
&#169;2018-2026, Kenneth Reitz.
|
<a href="_sources/backlog.md.txt"
rel="nofollow">Page source</a>
</div>
</body>
</html>
-1076
View File
File diff suppressed because it is too large Load Diff
-198
View File
@@ -1,198 +0,0 @@
<!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 &#8212; 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
Pythons 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). Its 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 &#39;fsspec[full]&#39;
</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">&quot;SECRET_KEY&quot;</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">
&#169;2018-2026, Kenneth Reitz.
|
<a href="_sources/cli.rst.txt"
rel="nofollow">Page source</a>
</div>
</body>
</html>
-278
View File
@@ -1,278 +0,0 @@
<!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 &#8212; 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">&quot;__main__&quot;</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.
Heres 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">&quot;python&quot;</span><span class="p">,</span> <span class="s2">&quot;api.py&quot;</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 youre 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">&quot;/health&quot;</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">&quot;status&quot;</span><span class="p">:</span> <span class="s2">&quot;healthy&quot;</span><span class="p">}</span>
</pre></div>
</div>
<p>Keep it simple. Dont 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">&quot;5042:80&quot;</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>Responders <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 uvicorns <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">
&#169;2018-2026, Kenneth Reitz.
|
<a href="_sources/deployment.rst.txt"
rel="nofollow">Page source</a>
</div>
</body>
</html>
+19
View File
@@ -0,0 +1,19 @@
# 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)
+35
View File
@@ -0,0 +1,35 @@
@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

Before

Width:  |  Height:  |  Size: 762 KiB

After

Width:  |  Height:  |  Size: 762 KiB

+21
View File
@@ -0,0 +1,21 @@
<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>
+14
View File
@@ -0,0 +1,14 @@
<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.
</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>
+35
View File
@@ -0,0 +1,35 @@
API Documentation
=================
Web Service (API) Class
-----------------------
.. module:: responder
.. autoclass:: API
:inherited-members:
Requests & Responses
--------------------
.. autoclass:: Request
:inherited-members:
.. autoclass:: Response
:inherited-members:
Utility Functions
-----------------
.. autofunction:: responder.API.status_codes.is_100
.. autofunction:: responder.API.status_codes.is_200
.. autofunction:: responder.API.status_codes.is_300
.. autofunction:: responder.API.status_codes.is_400
.. autofunction:: responder.API.status_codes.is_500
+7
View File
@@ -0,0 +1,7 @@
# Backlog
## Future Ideas
- Consider adding `after_request` hooks (complement to `before_request`)
- Explore WebSocket before_request short-circuit support
- Add rate limiting middleware
- Consider async template rendering by default
+1
View File
@@ -0,0 +1 @@
../../CHANGELOG.md
+174
View File
@@ -0,0 +1,174 @@
Responder CLI
=============
Responder installs a command line program ``responder``. Use it to launch
a Responder application from a file or module, either located on a local
or remote filesystem, or object store.
Launch Module Entrypoint
------------------------
For loading a Responder application from a Python module, you will refer to
its ``API()`` instance using a `Python entry point object reference`_ that
points to a Python object. It is either in the form ``importable.module``,
or ``importable.module:object.attr``.
A basic invocation command to launch a Responder application:
.. code-block:: shell
responder run acme.app
The command above assumes a Python package ``acme`` including an ``app``
module ``acme/app.py`` that includes an attribute ``api`` that refers
to a ``responder.API`` instance, reflecting the typical layout of
a standard Responder application.
Loading a Responder application using an entrypoint specification will
inherit the capacities of `Python's import system`_, as implemented by
`importlib`_.
Launch Local File
-----------------
Acquire a minimal example single-file application, ``helloworld.py`` [1]_,
to your local filesystem, giving you the chance to edit it, and launch the
Responder HTTP service.
.. code-block:: shell
wget https://github.com/kennethreitz/responder/raw/refs/heads/main/examples/helloworld.py
responder run helloworld.py
.. note::
To validate the example application, invoke a HTTP request, for example using
`curl`_, `HTTPie`_, or your favourite browser at hand.
.. code-block:: shell
http http://127.0.0.1:5042/Hello
The response is no surprise.
::
HTTP/1.1 200 OK
content-length: 13
content-type: text/plain
date: Sat, 26 Oct 2024 13:16:55 GMT
encoding: utf-8
server: uvicorn
Hello, world!
.. [1] The Responder application `helloworld.py`_ implements a basic echo handler.
Launch Remote File
------------------
You can also launch a single-file application where its Python file is stored
on a remote location.
Responder supports all filesystem adapters compatible with `fsspec`_, and
installs the adapters for Azure Blob Storage (az), Google Cloud Storage (gs),
GitHub, HTTP, and AWS S3 by default.
.. code-block:: shell
# Works 1:1.
responder run https://github.com/kennethreitz/responder/raw/refs/heads/main/examples/helloworld.py
responder run github://kennethreitz:responder@/examples/helloworld.py
If you need access other kinds of remote targets, see the `list of
fsspec-supported filesystems and protocols`_. The next section enumerates
a few synthetic examples. The corresponding storage buckets do not even
exist, so don't expect those commands to work.
.. code-block:: shell
# Azure Blob Storage, Google Cloud Storage, and AWS S3.
responder run az://kennethreitz-assets/responder/examples/helloworld.py
responder run gs://kennethreitz-assets/responder/examples/helloworld.py
responder run s3://kennethreitz-assets/responder/examples/helloworld.py
# Hadoop Distributed File System (hdfs), SSH File Transfer Protocol (sftp),
# Common Internet File System (smb), Web-based Distributed Authoring and
# Versioning (webdav).
responder run hdfs://kennethreitz-assets/responder/examples/helloworld.py
responder run sftp://user@host/kennethreitz/responder/examples/helloworld.py
responder run smb://workgroup;user:password@server:port/responder/examples/helloworld.py
responder run webdav+https://user:password@server:port/responder/examples/helloworld.py
.. tip::
In order to install support for all filesystem types supported by fsspec, run:
.. code-block:: shell
uv pip install 'fsspec[full]'
When using ``uv``, this concludes within an acceptable time of approx.
25 seconds. If you need to be more selectively instead of using ``full``,
choose from one or multiple of the available `fsspec extras`_, which are:
abfs, arrow, dask, dropbox, fuse, gcs, git, github, hdfs, http, oci, s3,
sftp, smb, ssh.
Launch with Non-Standard Instance Name
--------------------------------------
By default, Responder will acquire an ``responder.API`` instance using the
symbol name ``api`` from the specified Python module.
If your main application file uses a different name than ``api``, please
append the designated symbol name to the launch target address.
It works like this for module entrypoints and local files:
.. code-block:: shell
responder run acme.app:service
responder run /path/to/acme/app.py:service
It works like this for URLs:
.. code-block:: shell
responder run http://app.server.local/path/to/acme/app.py#service
Within your ``app.py``, the instance would have been defined to use
the ``service`` symbol name instead of ``api``, like this:
.. code-block:: python
service = responder.API()
Build JavaScript Application
----------------------------
The ``build`` subcommand invokes ``npm run build``, optionally accepting
a target directory. By default, it uses the current working directory,
where it expects a regular NPM ``package.json`` file.
.. code-block:: shell
responder build
When specifying a target directory, Responder will change to that
directory beforehand.
.. code-block:: shell
responder build /path/to/project
.. _curl: https://curl.se/
.. _fsspec: https://filesystem-spec.readthedocs.io/en/latest/
.. _fsspec extras: https://github.com/fsspec/filesystem_spec/blob/2024.12.0/pyproject.toml#L27-L69
.. _helloworld.py: https://github.com/kennethreitz/responder/blob/main/examples/helloworld.py
.. _HTTPie: https://httpie.io/docs/cli
.. _importlib: https://docs.python.org/3/library/importlib.html
.. _list of fsspec-supported filesystems and protocols: https://github.com/fsspec/universal_pathlib#currently-supported-filesystems-and-protocols
.. _Python entry point object reference: https://packaging.python.org/en/latest/specifications/entry-points/
.. _Python's import system: https://docs.python.org/3/reference/import.html
+52
View File
@@ -0,0 +1,52 @@
# 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
+86
View File
@@ -0,0 +1,86 @@
Deployment
==========
Responder applications are standard ASGI apps. You can deploy them anywhere
you'd deploy a Python web service.
Running Locally
---------------
The simplest way to run your application::
# api.py
import responder
api = responder.API()
@api.route("/")
def hello(req, resp):
resp.text = "hello, world!"
if __name__ == "__main__":
api.run()
This starts a production uvicorn server on ``127.0.0.1:5042``.
Docker
------
A minimal Dockerfile for deploying a Responder application::
FROM python:3.13-slim
WORKDIR /app
COPY . .
RUN pip install responder
ENV PORT=80
EXPOSE 80
CMD ["python", "api.py"]
Build and run::
$ docker build -t myapi .
$ docker run -p 8000:80 myapi
Cloud Platforms
---------------
Responder automatically honors the ``PORT`` environment variable, which is
set by most cloud platforms. When ``PORT`` is set, Responder binds to
``0.0.0.0`` on that port automatically.
This works out of the box with:
- **Fly.io**
- **Railway**
- **Render**
- **Google Cloud Run**
- **Azure Container Apps**
- **AWS App Runner**
Just deploy your code and set the start command to ``python api.py``.
Uvicorn Directly
----------------
For more control over the production server, you can bypass ``api.run()``
and use uvicorn directly::
$ uvicorn api:api --host 0.0.0.0 --port 8000 --workers 4
This gives you access to all of uvicorn's options: worker count, SSL
certificates, access logging, and more. See the
`uvicorn documentation <https://www.uvicorn.org/>`_ for details.
Reverse Proxy
-------------
In production, you may want to place Responder behind a reverse proxy like
nginx or Caddy for SSL termination, load balancing, or serving static assets.
Responder's ``TrustedHostMiddleware`` and ``HTTPSRedirectMiddleware`` work
correctly behind proxies that set standard forwarding headers.
+111
View File
@@ -0,0 +1,111 @@
Responder
=========
A familiar HTTP Service Framework for Python.
.. code:: 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()
Powered by `Starlette`_ and `uvicorn`_. The ``async`` is optional.
The Idea
--------
Responder takes the best ideas from `Flask`_ and `Falcon`_ and brings them
together into one clean framework.
The request and response objects are passed into every view and mutated
directly — no return values, no boilerplate. If you've used Requests,
you'll feel right at home. If you've used Flask, the routing will look
familiar. If you've used Falcon, the ``req`` / ``resp`` pattern will
click immediately.
- ``resp.text`` sends back text. ``resp.html`` sends back HTML.
- ``resp.media`` sends back JSON — or YAML, if the client asks for it.
- ``resp.file("path")`` serves a file. ``resp.content`` sends raw bytes.
- ``req.headers`` is case-insensitive. ``req.params`` holds query parameters.
- ``resp.status_code``, ``req.method``, ``req.url`` — the usual suspects.
Content negotiation happens automatically. Set ``resp.media`` to a dict
and Responder figures out the rest.
Responder and `FastAPI`_ share DNA — both are built on Starlette, both
appeared around the same time, and both pushed Python's ASGI ecosystem
forward. FastAPI went deep on type annotations and automatic validation.
Responder went for a mutable request/response pattern and a simpler,
more familiar API. Both projects are better for the other existing, and
you should use whichever feels right for what you're building.
What You Get
------------
One ``pip install``, batteries included:
- Mount Flask, Django, or any WSGI/ASGI app at a subroute.
- Gzip compression, HSTS, CORS, and trusted host validation.
- Before-request hooks that can short-circuit for auth guards.
- A test client for fast, in-process testing with pytest.
- Route parameters with f-string syntax and type convertors.
- Lifespan context managers for startup and shutdown logic.
- Custom exception handlers for clean error responses.
- `GraphQL`_ with Graphene and a built-in GraphiQL IDE.
- File serving with automatic content-type detection.
- Sync and async views — ``async`` is always optional.
- Class-based views with ``on_get``, ``on_post``, ``on_request``.
- A pleasant API with a single import statement.
- OpenAPI schema generation with Swagger UI.
- A production `uvicorn`_ server, ready to deploy.
- HTTP method filtering for REST APIs.
- Signed cookie-based sessions.
- Background tasks in a thread pool.
- WebSocket support.
Installation
------------
.. code-block:: shell
$ uv pip install responder
Python 3.9 and above. That's it.
.. toctree::
:maxdepth: 2
:caption: User Guide
quickstart
tour
deployment
testing
api
cli
.. toctree::
:maxdepth: 1
:caption: Project
changes
Sandbox <sandbox>
backlog
.. _Starlette: https://www.starlette.io/
.. _uvicorn: https://www.uvicorn.org/
.. _Flask: https://flask.palletsprojects.com/
.. _Falcon: https://falconframework.org/
.. _FastAPI: https://fastapi.tiangolo.com/
.. _GraphQL: https://graphql.org/
+234
View File
@@ -0,0 +1,234 @@
Quick Start
===========
This guide will walk you through the basics of building a web service with
Responder. By the end, you'll know how to declare routes, handle requests,
send responses, render templates, and process background tasks.
Create a Web Service
--------------------
The first thing you need to do is declare a web service. This is the central
object that holds all your routes, middleware, and configuration::
import responder
api = responder.API()
Hello World
-----------
Next, add a route. Here, we'll make the root URL say "hello, world!"::
@api.route("/")
def hello_world(req, resp):
resp.text = "hello, world!"
Every view receives a ``req`` (request) and ``resp`` (response) object. You
don't need to return anything — just mutate the response directly.
Run the Server
--------------
Start your web service with ``api.run()``::
api.run()
This spins up a production-grade uvicorn server on port ``5042``, ready for
incoming HTTP requests.
You can customize the port with ``api.run(port=8000)``. The ``PORT``
environment variable is also honored automatically — when set, Responder
binds to ``0.0.0.0`` on that port, which is what cloud platforms like
Fly.io, Railway, and Google Cloud Run expect.
.. note::
Both sync and async views are supported. The ``async`` keyword is always
optional — use it when you need to ``await`` something.
Route Parameters
----------------
If you want dynamic URLs, use Python's familiar f-string syntax to declare
variables in your routes::
@api.route("/hello/{who}")
def hello_to(req, resp, *, who):
resp.text = f"hello, {who}!"
A ``GET`` request to ``/hello/world`` will respond with ``hello, world!``.
Route parameters are passed as keyword-only arguments (after the ``*``).
Type Convertors
^^^^^^^^^^^^^^^
You can constrain route parameters to specific types. The parameter will be
automatically converted before it reaches your view::
@api.route("/add/{a:int}/{b:int}")
async def add(req, resp, *, a, b):
resp.text = f"{a} + {b} = {a + b}"
Supported types:
- ``str`` — matches any string without slashes (default)
- ``int`` — matches digits, converts to ``int``
- ``float`` — matches decimal numbers, converts to ``float``
- ``uuid`` — matches UUID strings like ``550e8400-e29b-41d4-a716-446655440000``
- ``path`` — matches any string *including* slashes, useful for file paths
Sending Responses
-----------------
Responder gives you several ways to send data back to the client. Just set
the appropriate property on the response object.
**Text and HTML**::
resp.text = "plain text response"
resp.html = "<h1>HTML response</h1>"
**JSON** — the most common pattern for APIs. Set ``resp.media`` to any
JSON-serializable Python object::
@api.route("/hello/{who}/json")
def hello_json(req, resp, *, who):
resp.media = {"hello": who}
If the client sends an ``Accept: application/x-yaml`` header, the same data
will be returned as YAML instead. Content negotiation is automatic.
**Files** — serve a file from disk with automatic content-type detection::
resp.file("reports/annual.pdf")
**Raw bytes**::
resp.content = b"\x89PNG\r\n..."
**Status codes and headers**::
resp.status_code = 201
resp.headers["X-Custom"] = "value"
**Redirects**::
api.redirect(resp, location="/new-url")
Reading Requests
----------------
The request object gives you access to everything the client sent.
**Method and URL**::
req.method # "get", "post", etc. (lowercase)
req.full_url # "http://example.com/path?q=1"
req.url # parsed URL object
**Headers** — case-insensitive, just like you'd expect::
req.headers["Content-Type"]
req.headers["content-type"] # same thing
**Query parameters**::
# GET /search?q=python&page=2
req.params["q"] # "python"
req.params["page"] # "2"
**Path parameters** — also available on the request object::
req.path_params["user_id"] # same as the keyword argument
**Request body** — for POST/PUT/PATCH requests, you need to ``await`` the
body content::
# JSON body
data = await req.media()
# Form data
data = await req.media("form")
# File uploads
files = await req.media("files")
# Raw bytes
body = await req.content
# Raw text
text = await req.text
**Other useful properties**::
req.is_json # True if content type is JSON
req.cookies # dict of cookies
req.session # session data (dict)
req.client # (host, port) tuple
req.is_secure # True if HTTPS
Rendering Templates
-------------------
Responder includes built-in `Jinja2 <https://jinja.palletsprojects.com/>`_
support. Templates are loaded from the ``templates/`` directory by default.
The simplest way is to use ``api.template()``::
@api.route("/hello/{name}/html")
def hello_html(req, resp, *, name):
resp.html = api.template("hello.html", name=name)
You can also use the ``Templates`` class directly for more control::
from responder.templates import Templates
templates = Templates(directory="templates")
@api.route("/page")
def page(req, resp):
resp.html = templates.render("page.html", title="Hello")
Async rendering is supported too::
templates = Templates(directory="templates", enable_async=True)
resp.html = await templates.render_async("page.html", title="Hello")
You can render template strings without a file::
resp.html = api.template_string("Hello, {{ name }}!", name="world")
Background Tasks
----------------
Sometimes you want to accept a request, respond immediately, and do the
actual processing later. Responder makes this easy with background tasks::
@api.route("/incoming")
async def receive_incoming(req, resp):
data = await req.media()
@api.background.task
def process_data(data):
"""This runs in a background thread."""
import time
time.sleep(10) # simulate heavy work
process_data(data)
# Respond immediately — processing continues in the background
resp.media = {"status": "accepted"}
The ``@api.background.task`` decorator wraps any function to run in a thread
pool. The client gets an immediate response while the work continues.
+36
View File
@@ -0,0 +1,36 @@
(sandbox)=
# Development Sandbox
## Setup
Set up a development sandbox.
Acquire sources and create virtualenv.
```shell
git clone https://github.com/kennethreitz/responder.git
cd responder
uv venv
```
Install project in editable mode, including
all development tools.
```shell
uv pip install --upgrade --editable '.[develop,docs,release,test]'
```
## Operations
Run tests.
```shell
source .venv/bin/activate
pytest
```
Format code.
```shell
ruff format .
ruff check --fix .
```
Documentation authoring.
```shell
sphinx-autobuild --open-browser --watch docs/source docs/source docs/build
```
+157
View File
@@ -0,0 +1,157 @@
Testing
=======
Responder includes a built-in test client powered by Starlette's
``TestClient``. You don't need to start a server — tests run in-process,
making them fast and reliable.
Getting Started
---------------
Given a simple application in ``api.py``::
import responder
api = responder.API()
@api.route("/")
def hello(req, resp):
resp.text = "hello, world!"
if __name__ == "__main__":
api.run()
You can test it with pytest::
# test_api.py
import api as service
def test_hello():
r = service.api.requests.get("/")
assert r.text == "hello, world!"
Run your tests::
$ pytest
Using Fixtures
--------------
For larger test suites, use pytest fixtures to share the API instance
across tests::
import pytest
import api as service
@pytest.fixture
def api():
return service.api
def test_hello(api):
r = api.requests.get("/")
assert r.text == "hello, world!"
def test_json(api):
@api.route("/data")
def data(req, resp):
resp.media = {"key": "value"}
r = api.requests.get(api.url_for(data))
assert r.json() == {"key": "value"}
The ``api.url_for()`` method generates a URL for a given route endpoint,
so you don't have to hard-code paths in your tests.
Testing JSON APIs
-----------------
Send JSON data and check the response::
def test_create_item(api):
@api.route("/items")
async def create(req, resp):
data = await req.media()
resp.media = {"created": data}
resp.status_code = 201
r = api.requests.post(api.url_for(create), json={"name": "widget"})
assert r.status_code == 201
assert r.json() == {"created": {"name": "widget"}}
Testing File Uploads
--------------------
Send files using the ``files`` parameter::
def test_upload(api):
@api.route("/upload")
async def upload(req, resp):
files = await req.media("files")
resp.media = {"received": list(files.keys())}
files = {"doc": ("report.pdf", b"content", "application/pdf")}
r = api.requests.post(api.url_for(upload), files=files)
assert r.json() == {"received": ["doc"]}
Testing WebSockets
------------------
Use Starlette's ``TestClient`` directly for WebSocket connections::
from starlette.testclient import TestClient
def test_websocket(api):
@api.route("/ws", websocket=True)
async def ws(ws):
await ws.accept()
await ws.send_text("hello")
await ws.close()
client = TestClient(api)
with client.websocket_connect("/ws") as ws:
assert ws.receive_text() == "hello"
Testing Error Handling
----------------------
To test error responses without pytest raising the exception, disable
server exception propagation::
from starlette.testclient import TestClient
def test_500(api):
@api.route("/fail")
def fail(req, resp):
raise ValueError("something broke")
client = TestClient(api, raise_server_exceptions=False)
r = client.get(api.url_for(fail))
assert r.status_code == 500
Testing Lifespan Events
-----------------------
The test client supports lifespan events. Use ``with`` to ensure startup
and shutdown hooks run::
def test_with_lifespan(api):
started = {"value": False}
@api.on_event("startup")
async def on_startup():
started["value"] = True
@api.route("/")
def check(req, resp):
resp.media = {"started": started["value"]}
with api.requests as session:
r = session.get("http://;/")
assert r.json() == {"started": True}
+508
View File
@@ -0,0 +1,508 @@
Feature Tour
============
This section walks through Responder's features in detail. Each section
includes working code examples you can copy into your application.
Method Filtering
----------------
By default, a route matches all HTTP methods. If you want to restrict a
route to specific methods, pass the ``methods`` parameter::
@api.route("/items", methods=["GET"])
def list_items(req, resp):
resp.media = {"items": []}
@api.route("/items", methods=["POST"], check_existing=False)
async def create_item(req, resp):
data = await req.media()
resp.media = {"created": data}
Note the ``check_existing=False`` — this allows you to register multiple
handlers for the same path with different methods.
Class-Based Views
-----------------
For more complex resources, you can use class-based views. Responder will
dispatch to the appropriate method handler based on the HTTP method::
@api.route("/{greeting}")
class GreetingResource:
def on_get(self, req, resp, *, greeting):
resp.text = f"{greeting}, world!"
def on_post(self, req, resp, *, greeting):
resp.media = {"received": greeting}
def on_request(self, req, resp, *, greeting):
"""Called on EVERY request, before the method-specific handler."""
resp.headers["X-Greeting"] = greeting
The ``on_request`` method is called for all HTTP methods, much like
middleware scoped to a single route. Method-specific handlers (``on_get``,
``on_post``, ``on_put``, ``on_delete``, etc.) are called after.
No inheritance required — just define a class with the right method names.
Lifespan Events
---------------
Modern applications often need to set up resources on startup (database
connections, caches, ML models) and tear them down on shutdown. Responder
supports the lifespan context manager pattern::
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app):
# Startup — runs before the first request
print("connecting to database...")
yield
# Shutdown — runs after the server stops
print("closing connections...")
api = responder.API(lifespan=lifespan)
You can also use the traditional event decorator style::
@api.on_event("startup")
async def startup():
print("starting up")
@api.on_event("shutdown")
async def shutdown():
print("shutting down")
The context manager approach is preferred for new code — it makes the
startup/shutdown relationship explicit and keeps related code together.
Serving Files
-------------
Serve files from disk with automatic content-type detection. Responder
uses Python's ``mimetypes`` module to figure out the right ``Content-Type``
header for you::
@api.route("/download")
def download(req, resp):
resp.file("reports/annual.pdf")
You can override the content type if needed::
@api.route("/image")
def image(req, resp):
resp.file("photos/cat.jpg", content_type="image/jpeg")
Custom Error Handling
---------------------
By default, unhandled exceptions result in a 500 Internal Server Error.
You can register custom handlers for specific exception types to return
structured error responses::
@api.exception_handler(ValueError)
async def handle_value_error(req, resp, exc):
resp.status_code = 400
resp.media = {"error": str(exc)}
Now, any route that raises a ``ValueError`` will return a clean 400 response
with a JSON error message instead of a generic 500 page.
Before-Request Hooks
--------------------
Run code before every request. This is useful for logging, adding common
headers, or setting up per-request state::
@api.route(before_request=True)
def add_headers(req, resp):
resp.headers["X-API-Version"] = "3.1"
**Short-circuiting:** If your hook sets ``resp.status_code``, the route
handler will be skipped entirely and the response will be sent immediately.
This is the pattern for authentication guards::
@api.route(before_request=True)
def auth_check(req, resp):
if "Authorization" not in req.headers:
resp.status_code = 401
resp.media = {"error": "unauthorized"}
If the ``Authorization`` header is missing, the client gets a 401 response
and the actual route handler never runs.
WebSocket hooks work the same way::
@api.before_request(websocket=True)
async def ws_auth(ws):
await ws.accept()
WebSocket Support
-----------------
Responder supports WebSockets for real-time, bidirectional communication::
@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}!")
await ws.close()
You can send and receive in multiple formats:
- ``send_text`` / ``receive_text`` — plain text
- ``send_json`` / ``receive_json`` — JSON objects
- ``send_bytes`` / ``receive_bytes`` — raw binary data
GraphQL
-------
Responder includes built-in GraphQL support via
`Graphene <https://graphene-python.org/>`_. Set up a full GraphQL endpoint
with a single method call::
import graphene
class Query(graphene.ObjectType):
hello = graphene.String(name=graphene.String(default_value="stranger"))
def resolve_hello(self, info, name):
return f"Hello {name}"
api.graphql("/graphql", schema=graphene.Schema(query=Query))
Visiting ``/graphql`` in a browser renders the GraphiQL interactive IDE,
where you can explore your schema and test queries. Programmatic clients
can POST JSON queries to the same endpoint.
You can access the Responder request and response objects in your resolvers
through ``info.context["request"]`` and ``info.context["response"]``.
OpenAPI Documentation
---------------------
Responder can generate an OpenAPI schema and serve interactive API
documentation automatically::
api = responder.API(
title="Pet Store",
version="1.0",
openapi="3.0.2",
docs_route="/docs",
)
This gives you:
- An OpenAPI schema at ``/schema.yml``
- Interactive Swagger UI documentation at ``/docs``
There are three ways to document your endpoints.
**Pydantic models** — the recommended approach for new APIs. Use
``request_model`` and ``response_model`` to annotate your routes, and
Responder will generate the schema automatically::
from pydantic import BaseModel
class PetIn(BaseModel):
name: str
age: int = 0
class PetOut(BaseModel):
id: int
name: str
age: int
@api.route("/pets", methods=["POST"],
request_model=PetIn, response_model=PetOut)
async def create_pet(req, resp):
data = await req.media()
resp.media = {"id": 1, **data}
This generates a full OpenAPI path with ``requestBody`` and ``responses``
schemas, all linked by ``$ref`` to your Pydantic models in
``components/schemas``.
You can also register standalone schemas with the ``@api.schema`` decorator::
@api.schema("Pet")
class Pet(BaseModel):
name: str
age: int = 0
**YAML docstrings** — inline your OpenAPI spec directly in the docstring.
This gives you full control over every detail::
@api.route("/pets")
def list_pets(req, resp):
"""A list of pets.
---
get:
description: Get all pets
responses:
200:
description: A list of pets
"""
resp.media = [{"name": "Fido"}]
**Marshmallow schemas** — if you're already using marshmallow for
validation, Responder integrates with it via the apispec plugin::
from marshmallow import Schema, fields
@api.schema("Pet")
class PetSchema(Schema):
name = fields.Str()
All three approaches can be mixed in the same API. Pydantic models,
marshmallow schemas, and YAML docstrings all contribute to the same
generated OpenAPI specification.
You can choose from multiple documentation themes:
``swagger_ui`` (default), ``redoc``, ``rapidoc``, or ``elements``.
Mounting Other Apps
-------------------
Responder can mount any WSGI or ASGI application at a subroute. This means
you can gradually migrate from Flask, or run multiple frameworks side by side::
from flask import Flask
flask_app = Flask(__name__)
@flask_app.route("/")
def hello():
return "Hello from Flask!"
api.mount("/flask", flask_app)
Requests to ``/flask/`` will be handled by Flask. Everything else goes
through Responder. Both WSGI and ASGI apps are supported — Responder
wraps WSGI apps automatically.
Cookies
-------
Reading and writing cookies is straightforward::
# Read cookies from the request
session_id = req.cookies.get("session_id")
# Set a cookie on the response
resp.cookies["hello"] = "world"
For more control over cookie directives, use ``set_cookie``::
resp.set_cookie(
"token",
value="abc123",
max_age=3600,
secure=True,
httponly=True,
path="/",
)
Supported directives: ``key``, ``value``, ``expires``, ``max_age``,
``domain``, ``path``, ``secure``, ``httponly``.
Cookie-Based Sessions
---------------------
Responder has built-in support for signed, cookie-based sessions. Just
read from and write to the ``session`` dictionary::
@api.route("/login")
def login(req, resp):
resp.session["username"] = "alice"
@api.route("/profile")
def profile(req, resp):
resp.media = {"user": req.session.get("username")}
The session data is stored in a cookie called ``Responder-Session``. It's
signed for tamper protection, so you can trust that the data originated
from your server.
.. warning::
For production use, always set a secret key::
api = responder.API(secret_key="your-secret-key-here")
Static Files
------------
Static files are served from the ``static/`` directory by default::
api = responder.API(static_dir="static", static_route="/static")
Place your CSS, JavaScript, images, and other assets in the ``static/``
directory and they'll be served automatically.
For single-page applications, you can serve ``index.html`` as the default
response for all unmatched routes::
api.add_route("/", static=True)
You can add additional static directories at runtime::
api.static_app.add_directory("extra_assets")
CORS
----
Enable Cross-Origin Resource Sharing for your API::
api = responder.API(cors=True, cors_params={
"allow_origins": ["https://example.com"],
"allow_methods": ["GET", "POST"],
"allow_headers": ["*"],
"allow_credentials": True,
"max_age": 600,
})
The default CORS policy is restrictive — you must explicitly enable the
origins, methods, and headers your frontend needs.
HSTS
----
Force all traffic to HTTPS with a single flag::
api = responder.API(enable_hsts=True)
This adds the ``Strict-Transport-Security`` header and redirects HTTP
requests to HTTPS.
Trusted Hosts
-------------
Protect against HTTP Host header attacks by restricting which hostnames
your application will respond to::
api = responder.API(allowed_hosts=["example.com", "*.example.com"])
Requests with a ``Host`` header that doesn't match any of the patterns
will receive a 400 Bad Request response. Wildcard domains are supported.
By default, all hostnames are allowed.
Server-Sent Events (SSE)
------------------------
Stream real-time updates to the client using Server-Sent Events. This is
great for live feeds, progress updates, and AI streaming responses::
@api.route("/events")
async def events(req, resp):
@resp.sse
async def stream():
for i in range(10):
yield {"data": f"message {i}"}
Each yielded value can be a string (treated as data) or a dict with
``data``, ``event``, ``id``, and ``retry`` fields::
yield {"event": "update", "data": "hello", "id": "1"}
yield "simple string message"
Streaming Files
---------------
For large files, use ``resp.stream_file()`` to stream the content without
loading the entire file into memory::
@api.route("/download")
def download(req, resp):
resp.stream_file("large-dataset.csv")
For small files where memory isn't a concern, ``resp.file()`` loads the
entire file at once — simpler but less efficient for large files.
After-Request Hooks
-------------------
Run code after every request, useful for logging, adding headers, or
cleanup::
@api.after_request()
def log_response(req, resp):
print(f"{req.method} {req.full_url} -> {resp.status_code}")
Route Groups
------------
Organize related routes with a shared URL prefix. Useful for API versioning
and logical grouping::
v1 = api.group("/v1")
@v1.route("/users")
def list_users(req, resp):
resp.media = []
@v1.route("/users/{user_id:int}")
def get_user(req, resp, *, user_id):
resp.media = {"id": user_id}
Request ID
----------
Auto-generate unique request IDs for tracing and debugging. If the client
sends an ``X-Request-ID`` header, it's forwarded; otherwise a new UUID is
generated::
api = responder.API(request_id=True)
Rate Limiting
-------------
Built-in token bucket rate limiter::
from responder.ext.ratelimit import RateLimiter
limiter = RateLimiter(requests=100, period=60) # 100 req/min
limiter.install(api)
When the limit is exceeded, clients receive a ``429 Too Many Requests``
response with ``Retry-After`` and ``X-RateLimit-Remaining`` headers.
MessagePack
-----------
In addition to JSON and YAML, Responder supports MessagePack for efficient
binary serialization::
# Decode MessagePack request body
data = await req.media("msgpack")
# Content negotiation also works — clients can send
# Accept: application/x-msgpack to receive MessagePack responses.
+19
View File
@@ -0,0 +1,19 @@
# 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()
+26
View File
@@ -0,0 +1,26 @@
# 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()
+25
View File
@@ -0,0 +1,25 @@
# 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()
Binary file not shown.

After

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

Some files were not shown because too many files have changed in this diff Show More