Compare commits

..

672 Commits

Author SHA1 Message Date
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 a1bfbda05b Merge branch 'master' into cli 2018-10-17 03:15:49 -07:00
kennethreitz f309ad7746 cli 2018-10-16 05:14:48 -07:00
112 changed files with 10238 additions and 2274 deletions
+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"
+54
View File
@@ -0,0 +1,54 @@
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: Python ${{ matrix.python-version }} on ${{ matrix.os }}"
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest"]
python-version: ["3.13"]
env:
UV_SYSTEM_PYTHON: true
steps:
- uses: actions/checkout@v4
- 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 and documentation dependencies
run: |
uv pip install '.[develop,docs]'
- name: Run link checker
run: |
poe docs-linkcheck
- name: Build static HTML documentation
run: |
poe docs-html
+55
View File
@@ -0,0 +1,55 @@
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 and validate package
run: |
uv pip install '.[develop,test]'
poe check
+3
View File
@@ -1,10 +1,13 @@
.venv*
.vscode/
.cache
.idea
.python-version
.coverage
.pytest_cache
.DS_Store
coverage.xml
.coverage*
__pycache__
tests/__pycache__
+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
-12
View File
@@ -1,12 +0,0 @@
language: python
python:
- "3.6"
# command to install dependencies
install:
- "pip install pipenv --upgrade-strategy=only-if-needed"
- "pipenv install --dev"
# command to run the dependencies
script:
- "pytest"
+405 -19
View File
@@ -1,36 +1,422 @@
# v0.1.0
# Changelog
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).
## [Unreleased]
## [v3.0.0] - 2026-03-22
### Added
- Platform: Added support for Python 3.10 - Python 3.13
- CLI: `responder run` now also accepts a filesystem path on its `<target>`
argument, enabling usage on single-file applications.
- CLI: `responder run` now also accepts URLs.
### Changed
- Platform: Minimum Python version is now 3.9 (dropped 3.6, 3.7, 3.8)
- Dependencies: Dramatically reduced core dependency count (10 → 5)
- Removed `requests`, `requests-toolbelt`, `rfc3986`, `whitenoise`
- Moved `apispec` and `marshmallow` to `openapi` optional extra
- Replaced `rfc3986` with stdlib `urllib.parse`
- Replaced `requests-toolbelt` multipart decoder with `python-multipart`
- Replaced deprecated `starlette.middleware.wsgi` with `a2wsgi`
- Switched from WhiteNoise to ServeStatic
- Dependencies: Pinned `starlette[full]>=0.40` (was unpinned)
- GraphQL: Upgraded to `graphene>=3` and `graphql-core>=3.1`
(from `graphene<3` and `graphql-server-core`, which is unmaintained)
- GraphQL: Updated GraphiQL UI from 0.12.0 (2018) to 3.0.6 with React 18
- Extensions: All of CLI-, GraphQL-, and OpenAPI-Support modules are
extensions now, found within the `responder.ext` module namespace.
- Packaging: Migrated from `setup.py` to declarative `pyproject.toml`
### Removed
- Platform: Removed support for EOL Python 3.6, 3.7, 3.8
- Status codes: Removed deprecated `resume_incomplete` and `resume`
aliases for HTTP 308 (marked for removal in 3.0)
- CLI: `responder run --build` ceased to exist
### Fixed
- Routing: Fixed dispatching `static_route=None` on Windows
- uvicorn: `--debug` now maps to uvicorn's `log_level = "debug"`
- Tests: Fixed deprecated httpx TestClient usage
## [v2.0.5] - 2019-12-15
### Added
- Update requirements to support python 3.8
## [v2.0.4] - 2019-11-19
### Fixed
- Fix static app resolving
## [v2.0.3] - 2019-09-20
### Fixed
- Fix template conflicts
## [v2.0.2] - 2019-09-20
### Fixed
- Fix template conflicts
## [v2.0.1] - 2019-09-20
### Fixed
- Fix template import
## [v2.0.0] - 2019-09-19
### Changed
- Refactor Router and Schema
## [v1.3.2] - 2019-08-15
### Added
- ASGI 3 support
- CI tests for python 3.8-dev
- Now requests have `state` a mapping object
### Deprecated
- ASGI 2
## [v1.3.1] - 2019-04-28
### Added
- Route params Converters
- Add search for documentation pages
### Changed
- Bump dependencies
## [v1.3.0] - 2019-02-22
### Fixed
- Versioning issue
- Multiple cookies.
- Whitenoise returns not found.
- Other bugfixes.
### Added
- Stream support via `resp.stream`.
- Cookie directives via `resp.set_cookie`.
- Add `resp.html` to send HTML.
- Other improvements.
## [v1.1.3] - 2019-01-12
### Changed
- Refactor `_route_for`
### Fixed
- Resolve startup/shutdwown events
## [v1.2.0] - 2018-12-29
### Added
- Documentations
### Changed
- Use Starlette's LifeSpan middleware
- Update denpendencies
### Fixed
- Fix route.is_class_based
- Fix test_500
- Typos
## [v1.1.2] - 2018-11-11
### Fixed
- Minor fixes for Open API
- Typos
## [v1.1.1] - 2018-10-29
### Changed
- Run sync views in a threadpoolexecutor.
## [v1.1.0] - 2018-10-27
### Added
- Support for `before_request`.
## [v1.0.5]- 2018-10-27
### Fixed
- Fix sessions.
## [v1.0.4] - 2018-10-27
### Fixed
- Potential bufix for cookies.
## [v1.0.3] - 2018-10-27
### Fixed
- Bugfix for redirects.
## [v1.0.2] - 2018-10-27
### Changed
- Improvement for static file hosting.
## [v1.0.1] - 2018-10-26
### Changed
- Improve cors configuration settings.
## [v1.0.0] - 2018-10-26
### Changed
- Move GraphQL support into a built-in plugin.
## [v0.3.3] - 2018-10-25
### Added
- CORS support
### Changed
- Improved exceptions.
## [v0.3.2] - 2018-10-25
### Changed
- Subtle improvements.
## [v0.3.1] - 2018-10-24
### Fixed
- Packaging fix.
## [v0.3.0] - 2018-10-24
### Changed
- Interactive Documentation endpoint.
- Minor improvements.
## [v0.2.3] - 2018-10-24
### Changed
- Overall improvements.
## [v0.2.2] - 2018-10-23
### Added
- Show traceback info when background tasks raise exceptions.
## [v0.2.1] - 2018-10-23
### Added
- api.requests.
## [v0.2.0] - 2018-10-22
### Added
- WebSocket support.
## [v0.1.6] - 2018-10-20
### Added
- 500 support.
## [v0.1.5] - 2018-10-20
### Added
- File upload support
### Changed
- Improvements to sequential media reading.
## [v0.1.4] - 2018-10-19
### Fixed
- Stability.
## [v0.1.3] - 2018-10-18
### Added
- Sessions support.
## [v0.1.2] - 2018-10-18
### Added
- Cookies support.
## [v0.1.1] - 2018-10-17
### Changed
- Default routes.
## [v0.1.0] - 2018-10-17
### Added
- Prototype of static application support.
# v0.0.10
- Bufgix for async class-based views.
## [v0.0.10] - 2018-10-17
### Fixed
# v0.0.9
- Bugfix for async class-based views.
# v0.0.8
## [v0.0.9] - 2018-10-17
### Fixed
- Bugfix for async class-based views.
## [v0.0.8] - 2018-10-17
### Added
- GraphiQL Support.
### Changed
- Improvement to route selection.
# v0.0.7
- Immutable Request object.
## [v0.0.7] - 2018-10-16
# v0.0.6:
- Ability to mount WSGI apps.
- Supply content-type when serving up the schema.
### Changed
# v0.0.5:
- OpenAPI Schema support.
- Safe load/dump yaml.
- Immutable Request object.
# v0.0.4:
- Asyncronous support for data uploads.
- Bug fixes.
## [v0.0.6] - 2018-10-16
### Added
- Ability to mount WSGI apps.
- Supply content-type when serving up the schema.
## [v0.0.5] - 2018-10-15
### Added
- OpenAPI Schema support.
- Safe load/dump yaml.
## [v0.0.4] - 2018-10-15
### Added
- Asynchronous support for data uploads.
### Fixed
# v0.0.3:
- Bug fixes.
# v0.0.2
## [v0.0.3] - 2018-10-13
### Fixed
- Bug fixes.
## [v0.0.2] - 2018-10-13
### Changed
- Switch to ASGI/Starlette.
# v0.0.1
## [v0.0.1] - 2018-10-12
### Added
- Conception!
[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
[v2.0.3]: https://github.com/kennethreitz/responder/compare/v2.0.2..v2.0.3
[v2.0.2]: https://github.com/kennethreitz/responder/compare/v2.0.1..v2.0.2
[v2.0.1]: https://github.com/kennethreitz/responder/compare/v2.0.0..v2.0.1
[v2.0.0]: https://github.com/kennethreitz/responder/compare/v1.3.2..v2.0.0
[v1.3.2]: https://github.com/kennethreitz/responder/compare/v1.3.1..v1.3.2
[v1.3.1]: https://github.com/kennethreitz/responder/compare/v1.3.0..v1.3.1
[v1.3.0]: https://github.com/kennethreitz/responder/compare/v1.2.0..v1.3.0
[v1.2.0]: https://github.com/kennethreitz/responder/compare/v1.1.3..v1.2.0
[v1.1.3]: https://github.com/kennethreitz/responder/compare/v1.1.2..v1.1.3
[v1.1.2]: https://github.com/kennethreitz/responder/compare/v1.1.1..v1.1.2
[v1.1.1]: https://github.com/kennethreitz/responder/compare/v1.1.0..v1.1.1
[v1.1.0]: https://github.com/kennethreitz/responder/compare/v1.0.5..v1.1.0
[v1.0.5]: https://github.com/kennethreitz/responder/compare/v1.0.4..v1.0.5
[v1.0.4]: https://github.com/kennethreitz/responder/compare/v1.0.3..v1.0.4
[v1.0.3]: https://github.com/kennethreitz/responder/compare/v1.0.2..v1.0.3
[v1.0.2]: https://github.com/kennethreitz/responder/compare/v1.0.1..v1.0.2
[v1.0.1]: https://github.com/kennethreitz/responder/compare/v1.0.0..v1.0.1
[v1.0.0]: https://github.com/kennethreitz/responder/compare/v0.3.3..v1.0.0
[v0.3.3]: https://github.com/kennethreitz/responder/compare/v0.3.2..v0.3.3
[v0.3.2]: https://github.com/kennethreitz/responder/compare/v0.3.1..v0.3.2
[v0.3.1]: https://github.com/kennethreitz/responder/compare/v0.3.0..v0.3.1
[v0.3.0]: https://github.com/kennethreitz/responder/compare/v0.2.3..v0.3.0
[v0.2.3]: https://github.com/kennethreitz/responder/compare/v0.2.2..v0.2.3
[v0.2.2]: https://github.com/kennethreitz/responder/compare/v0.2.1..v0.2.2
[v0.2.1]: https://github.com/kennethreitz/responder/compare/v0.2.0..v0.2.1
[v0.2.0]: https://github.com/kennethreitz/responder/compare/v0.1.6..v0.2.0
[v0.1.6]: https://github.com/kennethreitz/responder/compare/v0.1.5..v0.1.6
[v0.1.5]: https://github.com/kennethreitz/responder/compare/v0.1.4..v0.1.5
[v0.1.4]: https://github.com/kennethreitz/responder/compare/v0.1.3..v0.1.4
[v0.1.3]: https://github.com/kennethreitz/responder/compare/v0.1.2..v0.1.3
[v0.1.2]: https://github.com/kennethreitz/responder/compare/v0.1.1..v0.1.2
[v0.1.1]: https://github.com/kennethreitz/responder/compare/v0.1.0..v0.1.1
[v0.1.0]: https://github.com/kennethreitz/responder/compare/v0.0.10..v0.1.0
[v0.0.10]: https://github.com/kennethreitz/responder/compare/v0.0.9..v0.0.10
[v0.0.9]: https://github.com/kennethreitz/responder/compare/v0.0.8..v0.0.9
[v0.0.8]: https://github.com/kennethreitz/responder/compare/v0.0.7..v0.0.8
[v0.0.7]: https://github.com/kennethreitz/responder/compare/v0.0.6..v0.0.7
[v0.0.6]: https://github.com/kennethreitz/responder/compare/v0.0.5..v0.0.6
[v0.0.5]: https://github.com/kennethreitz/responder/compare/v0.0.4..v0.0.5
[v0.0.4]: https://github.com/kennethreitz/responder/compare/v0.0.3..v0.0.4
[v0.0.3]: https://github.com/kennethreitz/responder/compare/v0.0.2..v0.0.3
[v0.0.2]: https://github.com/kennethreitz/responder/compare/v0.0.1..v0.0.2
[v0.0.1]: https://github.com/kennethreitz/responder/compare/v0.0.0..v0.0.1
+175 -10
View File
@@ -1,13 +1,178 @@
Copyright 2018 Kenneth Reitz
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
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
-22
View File
@@ -1,22 +0,0 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
responder = {editable = true, path = "."}
[dev-packages]
pytest = "*"
"flake8" = "*"
black = "*"
twine = "*"
flask = "*"
sphinx = "*"
marshmallow = "*"
[requires]
python_version = "3.7"
[pipenv]
allow_prereleases = true
Generated
-648
View File
@@ -1,648 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "9b959d9507c521f6088646507633207db03afec6ac31aeab07adf0d737dbb45b"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.7"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"aiofiles": {
"hashes": [
"sha256:021ea0ba314a86027c166ecc4b4c07f2d40fc0f4b3a950d1868a0f2571c2bbee",
"sha256:1e644c2573f953664368de28d2aa4c89dfd64550429d0c27c4680ccd3aa4985d"
],
"version": "==0.4.0"
},
"aniso8601": {
"hashes": [
"sha256:7849749cf00ae0680ad2bdfe4419c7a662bef19c03691a19e008c8b9a5267802",
"sha256:94f90871fcd314a458a3d4eca1c84448efbd200e86f55fe4c733c7a40149ef50"
],
"version": "==3.0.2"
},
"apispec": {
"hashes": [
"sha256:c2e6ac6471aaf7c6ec6d12714821898910c6b3c87c189de9a2e3754786b86ada",
"sha256:fa7dfa8a292bae9b1e70c44a50bf61901805821726c5b804568c9f2501f57ebb"
],
"version": "==1.0.0b3"
},
"asgiref": {
"hashes": [
"sha256:9b05dcd41a6a89ca8c6e7f7e4089c3f3e76b5af60aebb81ae6d455ad81989c97",
"sha256:b21dc4c43d7aba5a844f4c48b8f49d56277bc34937fd9f9cb93ec97fde7e3082"
],
"version": "==2.3.2"
},
"async-timeout": {
"hashes": [
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
],
"version": "==3.0.1"
},
"certifi": {
"hashes": [
"sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c",
"sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a"
],
"version": "==2018.10.15"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"click": {
"hashes": [
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
],
"version": "==7.0"
},
"graphene": {
"hashes": [
"sha256:b8ec446d17fa68721636eaad3d6adc1a378cb6323e219814c8f98c9928fc9642",
"sha256:faa26573b598b22ffd274e2fd7a4c52efa405dcca96e01a62239482246248aa3"
],
"version": "==2.1.3"
},
"graphql-core": {
"hashes": [
"sha256:889e869be5574d02af77baf1f30b5db9ca2959f1c9f5be7b2863ead5a3ec6181",
"sha256:9462e22e32c7f03b667373ec0a84d95fba10e8ce2ead08f29fbddc63b671b0c1"
],
"version": "==2.1"
},
"graphql-relay": {
"hashes": [
"sha256:2716b7245d97091af21abf096fabafac576905096d21ba7118fba722596f65db"
],
"version": "==0.4.5"
},
"graphql-server-core": {
"hashes": [
"sha256:e5f82add4b3d5580aa1f1e7d9f00e944ad3abe1b65eb337e611d6a77cc20f231"
],
"version": "==1.1.1"
},
"h11": {
"hashes": [
"sha256:acca6a44cb52a32ab442b1779adf0875c443c689e9e028f8d831a3769f9c5208",
"sha256:f2b1ca39bfed357d1f19ac732913d5f9faa54a5062eca7d2ec3a916cfb7ae4c7"
],
"version": "==0.8.1"
},
"idna": {
"hashes": [
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
],
"version": "==2.7"
},
"jinja2": {
"hashes": [
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
],
"version": "==2.10"
},
"markupsafe": {
"hashes": [
"sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
],
"version": "==1.0"
},
"marshmallow": {
"hashes": [
"sha256:82b201ad767eb54de371c08cb1db6ca4ad2a728fa41b831e3781bf944815eb38",
"sha256:c250f37ac0e249a8287394a60d91f6240b674642ad999e66cd09463dbccd1d4f"
],
"version": "==3.0.0b18"
},
"parse": {
"hashes": [
"sha256:9dd6048ea212cd032a342f9f6aa2b7bc222f7407c7e37bdc2777fecd36897437"
],
"version": "==1.9.0"
},
"promise": {
"hashes": [
"sha256:2ebbfc10b7abf6354403ed785fe4f04b9dfd421eb1a474ac8d187022228332af",
"sha256:348f5f6c3edd4fd47c9cd65aed03ac1b31136d375aa63871a57d3e444c85655c"
],
"version": "==2.2.1"
},
"python-multipart": {
"hashes": [
"sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"
],
"version": "==0.0.5"
},
"pyyaml": {
"hashes": [
"sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb",
"sha256:3108529b78577327d15eec243f0ff348a0640b0c3478d67ad7f5648f93bac3e2",
"sha256:3c17fb92c8ba2f525e4b5f7941d850e7a48c3a59b32d331e2502a3cdc6648e76",
"sha256:8d6d96001aa7f0a6a4a95e8143225b5d06e41b1131044913fecb8f85a125714b",
"sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b"
],
"version": "==4.2b4"
},
"requests": {
"hashes": [
"sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
"sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
],
"version": "==2.19.1"
},
"responder": {
"editable": true,
"path": "."
},
"rfc3986": {
"hashes": [
"sha256:632b8fcd2ac37f24334316227f909be4f9d0738cbf409404cff6fa5f69a24093",
"sha256:8458571c4c57e1cf23593ad860bb601b6a604df6217f829c2bc70dc4b5af941b"
],
"version": "==1.1.0"
},
"rx": {
"hashes": [
"sha256:13a1d8d9e252625c173dc795471e614eadfe1cf40ffc684e08b8fff0d9748c23",
"sha256:7357592bc7e881a95e0c2013b73326f704953301ab551fbc8133a6fadab84105"
],
"version": "==1.6.1"
},
"six": {
"hashes": [
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
],
"version": "==1.11.0"
},
"starlette": {
"hashes": [
"sha256:2c7ec085440fce7146a9be2b6d53b7110c3866ce6fa03d901efdc1fbe97e0f36"
],
"version": "==0.4.2"
},
"urllib3": {
"hashes": [
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
],
"version": "==1.23"
},
"uvicorn": {
"hashes": [
"sha256:8de03999a936d8704f07cc3b1d3a3edb6922a068b64d84b4f5e49604c8b70a11"
],
"version": "==0.3.12"
},
"websockets": {
"hashes": [
"sha256:0e2f7d6567838369af074f0ef4d0b802d19fa1fee135d864acc656ceefa33136",
"sha256:2a16dac282b2fdae75178d0ed3d5b9bc3258dabfae50196cbb30578d84b6f6a6",
"sha256:5a1fa6072405648cb5b3688e9ed3b94be683ce4a4e5723e6f5d34859dee495c1",
"sha256:5c1f55a1274df9d6a37553fef8cff2958515438c58920897675c9bc70f5a0538",
"sha256:669d1e46f165e0ad152ed8197f7edead22854a6c90419f544e0f234cc9dac6c4",
"sha256:695e34c4dbea18d09ab2c258994a8bf6a09564e762655408241f6a14592d2908",
"sha256:6b2e03d69afa8d20253455e67b64de1a82ff8612db105113cccec35d3f8429f0",
"sha256:79ca7cdda7ad4e3663ea3c43bfa8637fc5d5604c7737f19a8964781abbd1148d",
"sha256:7fd2dd9a856f72e6ed06f82facfce01d119b88457cd4b47b7ae501e8e11eba9c",
"sha256:82c0354ac39379d836719a77ee360ef865377aa6fdead87909d50248d0f05f4d",
"sha256:8f3b956d11c5b301206382726210dc1d3bee1a9ccf7aadf895aaf31f71c3716c",
"sha256:91ec98640220ae05b34b79ee88abf27f97ef7c61cf525eec57ea8fcea9f7dddb",
"sha256:952be9540d83dba815569d5cb5f31708801e0bbfc3a8c5aef1890b57ed7e58bf",
"sha256:99ac266af38ba1b1fe13975aea01ac0e14bb5f3a3200d2c69f05385768b8568e",
"sha256:9fa122e7adb24232247f8a89f2d9070bf64b7869daf93ac5e19546b409e47e96",
"sha256:a0873eadc4b8ca93e2e848d490809e0123eea154aa44ecd0109c4d0171869584",
"sha256:cb998bd4d93af46b8b49ecf5a72c0a98e5cc6d57fdca6527ba78ad89d6606484",
"sha256:e02e57346f6a68523e3c43bbdf35dde5c440318d1f827208ae455f6a2ace446d",
"sha256:e79a5a896bcee7fff24a788d72e5c69f13e61369d055f28113e71945a7eb1559",
"sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff",
"sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454"
],
"version": "==6.0"
}
},
"develop": {
"alabaster": {
"hashes": [
"sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359",
"sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"
],
"version": "==0.7.12"
},
"appdirs": {
"hashes": [
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
"sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
],
"version": "==1.4.3"
},
"atomicwrites": {
"hashes": [
"sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0",
"sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"
],
"version": "==1.2.1"
},
"attrs": {
"hashes": [
"sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69",
"sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"
],
"version": "==18.2.0"
},
"babel": {
"hashes": [
"sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669",
"sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23"
],
"version": "==2.6.0"
},
"black": {
"hashes": [
"sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739",
"sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"
],
"index": "pypi",
"version": "==18.9b0"
},
"bleach": {
"hashes": [
"sha256:48d39675b80a75f6d1c3bdbffec791cf0bbbab665cf01e20da701c77de278718",
"sha256:73d26f018af5d5adcdabf5c1c974add4361a9c76af215fe32fdec8a6fc5fb9b9"
],
"version": "==3.0.2"
},
"certifi": {
"hashes": [
"sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c",
"sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a"
],
"version": "==2018.10.15"
},
"cffi": {
"hashes": [
"sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743",
"sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef",
"sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50",
"sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f",
"sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30",
"sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93",
"sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257",
"sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b",
"sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3",
"sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e",
"sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc",
"sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04",
"sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6",
"sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359",
"sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596",
"sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b",
"sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd",
"sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95",
"sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5",
"sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e",
"sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6",
"sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca",
"sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31",
"sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1",
"sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2",
"sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085",
"sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801",
"sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4",
"sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184",
"sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917",
"sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f",
"sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb"
],
"version": "==1.11.5"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"click": {
"hashes": [
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
],
"version": "==7.0"
},
"cmarkgfm": {
"hashes": [
"sha256:0186dccca79483e3405217993b83b914ba4559fe9a8396efc4eea56561b74061",
"sha256:1a625afc6f62da428df96ec325dc30866cc5781520cbd904ff4ec44cf018171c",
"sha256:207b7673ff4e177374c572feeae0e4ef33be620ec9171c08fd22e2b796e03e3d",
"sha256:275905bb371a99285c74931700db3f0c078e7603bed383e8cf1a09f3ee05a3de",
"sha256:50098f1c4950722521f0671e54139e0edc1837d63c990cf0f3d2c49607bb51a2",
"sha256:50ed116d0b60a07df0dc7b180c28569064b9d37d1578d4c9021cff04d725cb63",
"sha256:61a72def110eed903cd1848245897bcb80d295cd9d13944d4f9f30cba5b76655",
"sha256:64186fb75d973a06df0e6ea12879533b71f6e7ba1ab01ffee7fc3e7534758889",
"sha256:665303d34d7f14f10d7b0651082f25ebf7107f29ef3d699490cac16cdc0fc8ce",
"sha256:70b18f843aec58e4e64aadce48a897fe7c50426718b7753aaee399e72df64190",
"sha256:761ee7b04d1caee2931344ac6bfebf37102ffb203b136b676b0a71a3f0ea3c87",
"sha256:811527e9b7280b136734ed6cb6845e5fbccaeaa132ddf45f0246cbe544016957",
"sha256:987b0e157f70c72a84f3c2f9ef2d7ab0f26c08f2bf326c12c087ff9eebcb3ff5",
"sha256:9fc6a2183d0a9b0974ec7cdcdad42bd78a3be674cc3e65f87dd694419b3b0ab7",
"sha256:a3d17ee4ae739fe16f7501a52255c2e287ac817cfd88565b9859f70520afffea",
"sha256:ba5b5488719c0f2ced0aa1986376f7baff1a1653a8eb5fdfcf3f84c7ce46ef8d",
"sha256:c573ea89dd95d41b6d8cf36799c34b6d5b1eac4aed0212dee0f0a11fb7b01e8f",
"sha256:c5f1b9e8592d2c448c44e6bc0d91224b16ea5f8293908b1561de1f6d2d0658b1",
"sha256:cbe581456357d8f0674d6a590b1aaf46c11d01dd0a23af147a51a798c3818034",
"sha256:cf219bec69e601fe27e3974b7307d2f06082ab385d42752738ad2eb630a47d65",
"sha256:cf5014eb214d814a83a7a47407272d5db10b719dbeaf4d3cfe5969309d0fcf4b",
"sha256:d08bad67fa18f7e8ff738c090628ee0cbf0505d74a991c848d6d04abfe67b697",
"sha256:d6f716d7b1182bf35862b5065112f933f43dd1aa4f8097c9bcfb246f71528a34",
"sha256:e08e479102627641c7cb4ece421c6ed4124820b1758765db32201136762282d9",
"sha256:e20ac21418af0298437d29599f7851915497ce9f2866bc8e86b084d8911ee061",
"sha256:e25f53c37e319241b9a412382140dffac98ca756ba8f360ac7ab5e30cad9670a",
"sha256:e8932bddf159064f04e946fbb64693753488de21586f20e840b3be51745c8c09",
"sha256:f20900f16377f2109783ae9348d34bc80530808439591c3d3df73d5c7ef1a00c"
],
"version": "==0.4.2"
},
"colorama": {
"hashes": [
"sha256:a3d89af5db9e9806a779a50296b5fdb466e281147c2c235e8225ecc6dbf7bbf3",
"sha256:c9b54bebe91a6a803e0772c8561d53f2926bfeb17cd141fbabcb08424086595c"
],
"markers": "sys_platform == 'win32'",
"version": "==0.4.0"
},
"docutils": {
"hashes": [
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
"sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274",
"sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6"
],
"version": "==0.14"
},
"flake8": {
"hashes": [
"sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0",
"sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37"
],
"index": "pypi",
"version": "==3.5.0"
},
"flask": {
"hashes": [
"sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48",
"sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05"
],
"index": "pypi",
"version": "==1.0.2"
},
"future": {
"hashes": [
"sha256:e39ced1ab767b5936646cedba8bcce582398233d6a627067d4c6a454c90cfedb"
],
"version": "==0.16.0"
},
"idna": {
"hashes": [
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
],
"version": "==2.7"
},
"imagesize": {
"hashes": [
"sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8",
"sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"
],
"version": "==1.1.0"
},
"itsdangerous": {
"hashes": [
"sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519"
],
"version": "==0.24"
},
"jinja2": {
"hashes": [
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
],
"version": "==2.10"
},
"markupsafe": {
"hashes": [
"sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
],
"version": "==1.0"
},
"marshmallow": {
"hashes": [
"sha256:82b201ad767eb54de371c08cb1db6ca4ad2a728fa41b831e3781bf944815eb38",
"sha256:c250f37ac0e249a8287394a60d91f6240b674642ad999e66cd09463dbccd1d4f"
],
"version": "==3.0.0b18"
},
"mccabe": {
"hashes": [
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
],
"version": "==0.6.1"
},
"more-itertools": {
"hashes": [
"sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092",
"sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e",
"sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"
],
"version": "==4.3.0"
},
"packaging": {
"hashes": [
"sha256:0886227f54515e592aaa2e5a553332c73962917f2831f1b0f9b9f4380a4b9807",
"sha256:f95a1e147590f204328170981833854229bb2912ac3d5f89e2a8ccd2834800c9"
],
"version": "==18.0"
},
"pkginfo": {
"hashes": [
"sha256:5878d542a4b3f237e359926384f1dde4e099c9f5525d236b1840cf704fa8d474",
"sha256:a39076cb3eb34c333a0dd390b568e9e1e881c7bf2cc0aee12120636816f55aee"
],
"version": "==1.4.2"
},
"pluggy": {
"hashes": [
"sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
"sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"
],
"version": "==0.7.1"
},
"py": {
"hashes": [
"sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694",
"sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6"
],
"version": "==1.7.0"
},
"pycodestyle": {
"hashes": [
"sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766",
"sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9"
],
"version": "==2.3.1"
},
"pycparser": {
"hashes": [
"sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
],
"version": "==2.19"
},
"pyflakes": {
"hashes": [
"sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
"sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805"
],
"version": "==1.6.0"
},
"pygments": {
"hashes": [
"sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d",
"sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"
],
"version": "==2.2.0"
},
"pyparsing": {
"hashes": [
"sha256:bc6c7146b91af3f567cf6daeaec360bc07d45ffec4cf5353f4d7a208ce7ca30a",
"sha256:d29593d8ebe7b57d6967b62494f8c72b03ac0262b1eed63826c6f788b3606401"
],
"version": "==2.2.2"
},
"pytest": {
"hashes": [
"sha256:7e258ee50338f4e46957f9e09a0f10fb1c2d05493fa901d113a8dafd0790de4e",
"sha256:9332147e9af2dcf46cd7ceb14d5acadb6564744ddff1fe8c17f0ce60ece7d9a2"
],
"index": "pypi",
"version": "==3.8.2"
},
"pytz": {
"hashes": [
"sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053",
"sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277"
],
"version": "==2018.5"
},
"readme-renderer": {
"hashes": [
"sha256:237ca8705ffea849870de41101dba41543561da05c0ae45b2f1c547efa9843d2",
"sha256:f75049a3a7afa57165551e030dd8f9882ebf688b9600535a3f7e23596651875d"
],
"version": "==22.0"
},
"requests": {
"hashes": [
"sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
"sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
],
"version": "==2.19.1"
},
"requests-toolbelt": {
"hashes": [
"sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237",
"sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5"
],
"version": "==0.8.0"
},
"six": {
"hashes": [
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
],
"version": "==1.11.0"
},
"snowballstemmer": {
"hashes": [
"sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128",
"sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89"
],
"version": "==1.2.1"
},
"sphinx": {
"hashes": [
"sha256:652eb8c566f18823a022bb4b6dbc868d366df332a11a0226b5bc3a798a479f17",
"sha256:d222626d8356de702431e813a05c68a35967e3d66c6cd1c2c89539bb179a7464"
],
"index": "pypi",
"version": "==1.8.1"
},
"sphinxcontrib-websupport": {
"hashes": [
"sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd",
"sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9"
],
"version": "==1.1.0"
},
"toml": {
"hashes": [
"sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
"sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
],
"version": "==0.10.0"
},
"tqdm": {
"hashes": [
"sha256:a0be569511161220ff709a5b60d0890d47921f746f1c737a11d965e1b29e7b2e",
"sha256:e293e6d7a7f41a529a27f8d6624ab11544ccbfe82a205af6fad102545099fc21"
],
"version": "==4.27.0"
},
"twine": {
"hashes": [
"sha256:7d89bc6acafb31d124e6e5b295ef26ac77030bf098960c2a4c4e058335827c5c",
"sha256:fad6f1251195f7ddd1460cb76d6ea106c93adb4e56c41e0da79658e56e547d2c"
],
"index": "pypi",
"version": "==1.12.1"
},
"urllib3": {
"hashes": [
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
],
"version": "==1.23"
},
"webencodings": {
"hashes": [
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
"sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
],
"version": "==0.5.1"
},
"werkzeug": {
"hashes": [
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
"sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
],
"version": "==0.14.1"
}
}
}
+65 -138
View File
@@ -1,170 +1,97 @@
# Responder: a familiar HTTP Service Framework for Python
[![Build Status](https://travis-ci.org/kennethreitz/responder.svg?branch=master)](https://travis-ci.org/kennethreitz/responder)
[![Documentation Status](https://readthedocs.org/projects/mybinder/badge/?version=latest)](https://responder.readthedocs.io/en/latest/)
[![image](https://img.shields.io/pypi/v/responder.svg)](https://pypi.org/project/responder/)
[![image](https://img.shields.io/pypi/l/responder.svg)](https://pypi.org/project/responder/)
[![image](https://img.shields.io/pypi/pyversions/responder.svg)](https://pypi.org/project/responder/)
[![image](https://img.shields.io/github/contributors/kennethreitz/responder.svg)](https://github.com/kennethreitz/responder/graphs/contributors)
[![ci-tests](https://github.com/kennethreitz/responder/actions/workflows/test.yaml/badge.svg)](https://github.com/kennethreitz/responder/actions/workflows/test.yaml)
[![ci-docs](https://github.com/kennethreitz/responder/actions/workflows/docs.yaml/badge.svg)](https://github.com/kennethreitz/responder/actions/workflows/docs.yaml)
[![Documentation Status](https://github.com/kennethreitz/responder/actions/workflows/pages/pages-build-deployment/badge.svg)](https://responder.kennethreitz.org/)
[![version](https://img.shields.io/pypi/v/responder.svg)](https://pypi.org/project/responder/)
[![license](https://img.shields.io/pypi/l/responder.svg)](https://pypi.org/project/responder/)
[![python-versions](https://img.shields.io/pypi/pyversions/responder.svg)](https://pypi.org/project/responder/)
[![downloads](https://static.pepy.tech/badge/responder/month)](https://pepy.tech/project/responder)
[![contributors](https://img.shields.io/github/contributors/kennethreitz/responder.svg)](https://github.com/kennethreitz/responder/graphs/contributors)
[![status](https://img.shields.io/pypi/status/responder.svg)](https://pypi.org/project/responder/)
[![](https://github.com/kennethreitz/responder/raw/master/ext/small.jpg)](http://python-responder.org/)
[![responder-synopsis](https://farm2.staticflickr.com/1959/43750081370_a4e20752de_o_d.png)](https://responder.readthedocs.io)
The Python world certainly doesn't need more web frameworks. But, it does need more creativity, so I thought I'd spread some [Hacktoberfest](https://hacktoberfest.digitalocean.com/) spirit around, bring some of my ideas to the table, and see what I could come up with.
```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()
```
That `async` declaration is optional. [View documentation](http://python-responder.org).
This gets you a ASGI app, with a production static files server pre-installed, jinja2 templating (without additional imports), and a production webserver based on uvloop, serving up requests with gzip compression automatically.
Responder is powered by [Starlette](https://www.starlette.io/).
[View documentation](https://responder.readthedocs.io).
Responder gets you an ASGI app, with a production static files server pre-installed,
Jinja templating, and a production webserver based on uvloop, automatically serving
up requests with gzip compression.
The `async` declaration within the example program is optional.
## Testimonials
> "Pleasantly very taken with python-responder. [@kennethreitz](https://twitter.com/kennethreitz) at his absolute best." —Rudraksh M.K.
> "Pleasantly very taken with python-responder.
> [@kennethreitz](https://x.com/kennethreitz42) at his absolute best." —Rudraksh
> M.K.
> "ASGI is going to enable all sorts of new high-performance web services. It's awesome to see Responder starting to take advantage of that." — Tom Christie author of [Django REST Framework](https://www.django-rest-framework.org/)
> "ASGI is going to enable all sorts of new high-performance web services. It's awesome
> to see Responder starting to take advantage of that." — Tom Christie author of
> [Django REST Framework](https://www.django-rest-framework.org/)
> "I love that you are exploring new patterns. Go go go!" — Danny Greenfield, author of [Two Scoops of Django]()
> "Love what I have seen while it's in progress! Many features of Responder are from my wishlist for Flask, and it's even faster and even easier than Flask!" — Luna C.
> "I love that you are exploring new patterns. Go go go!" — Danny Greenfield, author of
> [Two Scoops of Django](https://www.feldroy.com/two-scoops-press#two-scoops-of-django)
## More Examples
Class-based views (and setting some headers and stuff):
```python
@api.route("/{greeting}")
class GreetingResource:
def on_request(req, resp, *, greeting): # or on_get...
resp.text = f"{greeting}, world!"
resp.headers.update({'X-Life': '42'})
resp.status_code = api.status_codes.HTTP_416
```
Render a template, with arguments:
```python
@api.route("/{greeting}")
def greet_world(req, resp, *, greeting):
resp.content = api.template("index.html", greeting=greeting)
```
The `api` instance is available as an object during template rendering.
Here, you can spawn off a background thread to run any function, out-of-request:
```python
@api.route("/")
def hello(req, resp):
@api.background.task
def sleep(s=10):
time.sleep(s)
print("slept!")
sleep()
resp.content = "processing"
```
And even serve a GraphQL API:
```python
import graphene
class Query(graphene.ObjectType):
hello = graphene.String(name=graphene.String(default_value="stranger"))
def resolve_hello(self, info, name):
return "Hello " + name
api.add_route("/graph", graphene.Schema(query=Query))
```
We can then send a query to our service:
```pycon
>>> requests = api.session()
>>> r = requests.get("http://;/graph", params={"query": "{ hello }"})
>>> r.json()
{'data': {'hello': 'Hello stranger'}}
```
Or, request YAML back:
```pycon
>>> r = requests.get("http://;/graph", params={"query": "{ hello(name:\"john\") }"}, headers={"Accept": "application/x-yaml"})
>>> print(r.text)
data: {hello: Hello john}
```
Want HSTS?
```
api = responder.API(enable_hsts=True)
```
Boom.
See
[the documentation's feature tour](https://responder.readthedocs.io/tour.html)
for more details on features available in Responder.
# Installing Responder
Install the latest release:
Install the most recent stable release:
pip install --upgrade responder
$ pipenv install responder
✨🍰✨
Alternatively, install directly from the repository:
pip install 'responder @ git+https://github.com/kennethreitz/responder.git'
Or, install from the development branch:
$ pipenv install -e git+https://github.com/kennethreitz/responder.git#egg=responder
Only **Python 3.6+** is supported.
Responder supports **Python 3.9+**.
# The Basic Idea
The primary concept here is to bring the niceties that are brought forth from both Flask and Falcon and unify them into a single framework, along with some new ideas I have. I also wanted to take some of the API primitives that are instilled in the Requests library and put them into a web framework. So, you'll find a lot of parallels here with Requests.
The primary concept here is to bring the niceties from both Flask and Falcon and
unify them into a single framework. You'll find a familiar API with a clean,
Pythonic design.
- Setting `resp.text` sends back unicode, while setting `resp.content` sends back bytes.
- Setting `resp.media` sends back JSON/YAML (`.text`/`.content` override this).
- Case-insensitive `req.headers` dict (from Requests directly).
- Setting `resp.text` sends back unicode, while setting `resp.html` sends back HTML.
- Setting `resp.media` sends back JSON/YAML (`.text`/`.html`/`.content` override this).
- Setting `resp.content` sends back bytes.
- Use `resp.file("path")` to serve files with automatic content-type detection.
- Case-insensitive `req.headers` dict.
- `resp.status_code`, `req.method`, `req.url`, and other familiar friends.
## Ideas
## Features
- Flask-style route expression, with new capabilities -- all while using Python 3.6+'s new f-string syntax.
- I love Falcon's "every request and response is passed into to each view and mutated" methodology, especially `response.media`, and have used it here. In addition to supporting JSON, I have decided to support YAML as well, as Kubernetes is slowly taking over the world, and it uses YAML for all the things. Content-negotiation and all that.
- **A built in testing client that uses the actual Requests you know and love**.
- The ability to mount other WSGI apps easily.
- Automatic gzipped-responses.
- In addition to Falcon's `on_get`, `on_post`, etc methods, Responder features an `on_request` method, which gets called on every type of request, much like Requests.
- A production static file server is built-in.
- Uvicorn built-in as a production web server. I would have chosen Gunicorn, but it doesn't run on Windows. Plus, Uvicorn serves well to protect against slowloris attacks, making nginx unnecessary in production.
- GraphQL support, via Graphene. The goal here is to have any GraphQL query exposable at any route, magically.
- Flask-style route expressions with f-string syntax and type convertors
(`str`, `int`, `float`, `uuid`, `path`).
- HTTP method filtering: `@api.route("/data", methods=["GET"])`.
- Every request and response is passed into each view and mutated — including
`response.media` for JSON/YAML content negotiation.
- Built-in test client powered by Starlette's TestClient.
- Mount other WSGI/ASGI apps at subroutes.
- Automatic gzip compression.
- Class-based views with `on_get`, `on_post`, `on_request` methods.
- GraphQL support via Graphene with `api.graphql()`.
- OpenAPI schema generation with interactive docs.
- Lifespan context managers for startup/shutdown.
- Custom exception handlers.
- Before-request hooks with short-circuit support.
- Cookie-based sessions.
- WebSocket support.
- Background tasks.
- Production uvicorn server built-in.
## Future Ideas
## Development
- Cookie-based sessions are currently an afterthought, as this is an API framework, but websites are APIs too.
- If frontend websites are supported, provide an official way to run webpack.
See [Development Sandbox](https://responder.kennethreitz.org/sandbox.html).
# The Goal
## Supported by
The primary goal here is to learn, not to get adoption. Though, who knows how these things will pan out.
[![JetBrains logo.](https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg)](https://jb.gg/OpenSourceSupport)
----------
[![hacktoberfest](https://hacktoberfest.digitalocean.com/assets/hacktoberfest-2018-social-card-c8d2e1489f647f2e0a26e6f598adeb760872818905b34cd437afc7ac2857ceab.png)](https://hacktoberfest.digitalocean.com/)
Special thanks to the kind people at JetBrains s.r.o. for supporting us with
excellent development tooling.
-48
View File
@@ -1,48 +0,0 @@
alabaster==0.7.12
appdirs==1.4.3
atomicwrites==1.2.1
attrs==18.2.0
babel==2.6.0
black==18.9b0
bleach==3.0.2
certifi==2018.8.24
cffi==1.11.5
chardet==3.0.4
click==7.0
cmarkgfm==0.4.2
colorama==0.4.0 ; sys_platform == 'win32'
docutils==0.14
flake8==3.5.0
flask==1.0.2
future==0.16.0
idna==2.7
imagesize==1.1.0
itsdangerous==0.24
jinja2==2.10
markupsafe==1.0
mccabe==0.6.1
more-itertools==4.3.0
packaging==18.0
pkginfo==1.4.2
pluggy==0.7.1
py==1.7.0
pycodestyle==2.3.1
pycparser==2.19
pyflakes==1.6.0
pygments==2.2.0
pyparsing==2.2.2
pytest==3.8.2
pytz==2018.5
readme-renderer==22.0
requests-toolbelt==0.8.0
requests==2.19.1
six==1.11.0
snowballstemmer==1.2.1
sphinx==1.8.1
sphinxcontrib-websupport==1.1.0
toml==0.10.0
tqdm==4.26.0
twine==1.12.1
urllib3==1.23
webencodings==0.5.1
werkzeug==0.14.1
+4 -4
View File
@@ -1,7 +1,7 @@
/* Hide module name and default value for environment variable section */
div[id$='environment-variables'] code.descclassname {
display: none;
div[id$="environment-variables"] code.descclassname {
display: none;
}
div[id$='environment-variables'] em.property {
display: none;
div[id$="environment-variables"] em.property {
display: none;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+142 -132
View File
@@ -10,142 +10,152 @@
*/
var Konami = function (callback) {
var konami = {
addEvent: function (obj, type, fn, ref_obj) {
if (obj.addEventListener)
obj.addEventListener(type, fn, false);
else if (obj.attachEvent) {
// IE
obj["e" + type + fn] = fn;
obj[type + fn] = function () {
obj["e" + type + fn](window.event, ref_obj);
}
obj.attachEvent("on" + type, obj[type + fn]);
}
},
removeEvent: function (obj, eventName, eventCallback) {
if (obj.removeEventListener) {
obj.removeEventListener(eventName, eventCallback);
} else if (obj.attachEvent) {
obj.detachEvent(eventName);
}
},
input: "",
pattern: "38384040373937396665",
keydownHandler: function (e, ref_obj) {
if (ref_obj) {
konami = ref_obj;
} // IE
konami.input += e ? e.keyCode : event.keyCode;
if (konami.input.length > konami.pattern.length) {
konami.input = konami.input.substr((konami.input.length - konami.pattern.length));
}
if (konami.input === konami.pattern) {
konami.code(konami._currentLink);
konami.input = '';
e.preventDefault();
return false;
}
},
load: function (link) {
this._currentLink = link;
this.addEvent(document, "keydown", this.keydownHandler, this);
this.iphone.load(link);
},
unload: function () {
this.removeEvent(document, 'keydown', this.keydownHandler);
this.iphone.unload();
},
code: function (link) {
window.location = link
},
iphone: {
start_x: 0,
start_y: 0,
stop_x: 0,
stop_y: 0,
tap: false,
capture: false,
orig_keys: "",
keys: ["UP", "UP", "DOWN", "DOWN", "LEFT", "RIGHT", "LEFT", "RIGHT", "TAP", "TAP"],
input: [],
code: function (link) {
konami.code(link);
},
touchmoveHandler: function (e) {
if (e.touches.length === 1 && konami.iphone.capture === true) {
var touch = e.touches[0];
konami.iphone.stop_x = touch.pageX;
konami.iphone.stop_y = touch.pageY;
konami.iphone.tap = false;
konami.iphone.capture = false;
konami.iphone.check_direction();
}
},
touchendHandler: function () {
konami.iphone.input.push(konami.iphone.check_direction());
if (konami.iphone.input.length > konami.iphone.keys.length) konami.iphone.input.shift();
if (konami.iphone.input.length === konami.iphone.keys.length) {
var match = true;
for (var i = 0; i < konami.iphone.keys.length; i++) {
if (konami.iphone.input[i] !== konami.iphone.keys[i]) {
match = false;
}
}
if (match) {
konami.iphone.code(konami._currentLink);
}
}
},
touchstartHandler: function (e) {
konami.iphone.start_x = e.changedTouches[0].pageX;
konami.iphone.start_y = e.changedTouches[0].pageY;
konami.iphone.tap = true;
konami.iphone.capture = true;
},
load: function (link) {
this.orig_keys = this.keys;
konami.addEvent(document, "touchmove", this.touchmoveHandler);
konami.addEvent(document, "touchend", this.touchendHandler, false);
konami.addEvent(document, "touchstart", this.touchstartHandler);
},
unload: function () {
konami.removeEvent(document, 'touchmove', this.touchmoveHandler);
konami.removeEvent(document, 'touchend', this.touchendHandler);
konami.removeEvent(document, 'touchstart', this.touchstartHandler);
},
check_direction: function () {
x_magnitude = Math.abs(this.start_x - this.stop_x);
y_magnitude = Math.abs(this.start_y - this.stop_y);
x = ((this.start_x - this.stop_x) < 0) ? "RIGHT" : "LEFT";
y = ((this.start_y - this.stop_y) < 0) ? "DOWN" : "UP";
result = (x_magnitude > y_magnitude) ? x : y;
result = (this.tap === true) ? "TAP" : result;
return result;
}
var konami = {
addEvent: function (obj, type, fn, ref_obj) {
if (obj.addEventListener) obj.addEventListener(type, fn, false);
else if (obj.attachEvent) {
// IE
obj["e" + type + fn] = fn;
obj[type + fn] = function () {
obj["e" + type + fn](window.event, ref_obj);
};
obj.attachEvent("on" + type, obj[type + fn]);
}
},
removeEvent: function (obj, eventName, eventCallback) {
if (obj.removeEventListener) {
obj.removeEventListener(eventName, eventCallback);
} else if (obj.attachEvent) {
obj.detachEvent(eventName);
}
},
input: "",
pattern: "38384040373937396665",
keydownHandler: function (e, ref_obj) {
if (ref_obj) {
konami = ref_obj;
} // IE
konami.input += e ? e.keyCode : event.keyCode;
if (konami.input.length > konami.pattern.length) {
konami.input = konami.input.substr(konami.input.length - konami.pattern.length);
}
if (konami.input === konami.pattern) {
konami.code(konami._currentLink);
konami.input = "";
e.preventDefault();
return false;
}
},
load: function (link) {
this._currentLink = link;
this.addEvent(document, "keydown", this.keydownHandler, this);
this.iphone.load(link);
},
unload: function () {
this.removeEvent(document, "keydown", this.keydownHandler);
this.iphone.unload();
},
code: function (link) {
window.location = link;
},
iphone: {
start_x: 0,
start_y: 0,
stop_x: 0,
stop_y: 0,
tap: false,
capture: false,
orig_keys: "",
keys: [
"UP",
"UP",
"DOWN",
"DOWN",
"LEFT",
"RIGHT",
"LEFT",
"RIGHT",
"TAP",
"TAP",
],
input: [],
code: function (link) {
konami.code(link);
},
touchmoveHandler: function (e) {
if (e.touches.length === 1 && konami.iphone.capture === true) {
var touch = e.touches[0];
konami.iphone.stop_x = touch.pageX;
konami.iphone.stop_y = touch.pageY;
konami.iphone.tap = false;
konami.iphone.capture = false;
konami.iphone.check_direction();
}
}
},
touchendHandler: function () {
konami.iphone.input.push(konami.iphone.check_direction());
typeof callback === "string" && konami.load(callback);
if (typeof callback === "function") {
konami.code = callback;
konami.load();
}
if (konami.iphone.input.length > konami.iphone.keys.length)
konami.iphone.input.shift();
return konami;
if (konami.iphone.input.length === konami.iphone.keys.length) {
var match = true;
for (var i = 0; i < konami.iphone.keys.length; i++) {
if (konami.iphone.input[i] !== konami.iphone.keys[i]) {
match = false;
}
}
if (match) {
konami.iphone.code(konami._currentLink);
}
}
},
touchstartHandler: function (e) {
konami.iphone.start_x = e.changedTouches[0].pageX;
konami.iphone.start_y = e.changedTouches[0].pageY;
konami.iphone.tap = true;
konami.iphone.capture = true;
},
load: function (link) {
this.orig_keys = this.keys;
konami.addEvent(document, "touchmove", this.touchmoveHandler);
konami.addEvent(document, "touchend", this.touchendHandler, false);
konami.addEvent(document, "touchstart", this.touchstartHandler);
},
unload: function () {
konami.removeEvent(document, "touchmove", this.touchmoveHandler);
konami.removeEvent(document, "touchend", this.touchendHandler);
konami.removeEvent(document, "touchstart", this.touchstartHandler);
},
check_direction: function () {
x_magnitude = Math.abs(this.start_x - this.stop_x);
y_magnitude = Math.abs(this.start_y - this.stop_y);
x = this.start_x - this.stop_x < 0 ? "RIGHT" : "LEFT";
y = this.start_y - this.stop_y < 0 ? "DOWN" : "UP";
result = x_magnitude > y_magnitude ? x : y;
result = this.tap === true ? "TAP" : result;
return result;
},
},
};
typeof callback === "string" && konami.load(callback);
if (typeof callback === "function") {
konami.code = callback;
konami.load();
}
return konami;
};
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = Konami;
if (typeof module !== "undefined" && typeof module.exports !== "undefined") {
module.exports = Konami;
} else {
if (typeof define === 'function' && define.amd) {
define([], function() {
return Konami;
});
} else {
window.Konami = Konami;
}
if (typeof define === "function" && define.amd) {
define([], function () {
return Konami;
});
} else {
window.Konami = Konami;
}
}
+74 -38
View File
@@ -1,5 +1,13 @@
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/7584432/7586812/css/fonts.css" />
<script type="text/javascript">$('#searchbox').hide(0);</script>
<link
rel="stylesheet"
type="text/css"
href="https://cloud.typography.com/7584432/7586812/css/fonts.css"
/>
<script async defer src="https://buttons.github.io/buttons.js"></script>
<script type="text/javascript">
$("#searchbox").hide(0);
</script>
<!--Alabaster (krTheme++) Hacks -->
<!-- CSS Adjustments (I'm very picky.) -->
@@ -26,7 +34,7 @@
.class em,
.descname,
.method em {
font-family: "Operator Mono SSm A", "Operator Mono SSm B" !important;
font-family: "Operator Mono SSm A", "Operator Mono SSm B", monospace !important;
font-weight: 400 !important;
}
@@ -39,9 +47,7 @@
}
.method {
margin-bottom: 2em;
}
.si,
@@ -80,8 +86,6 @@
margin-top: -1em;
}
/* "Quick Search" should be not be shown for now. */
div#searchbox h3 {
display: none;
@@ -118,10 +122,12 @@
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-127383416-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag('config', 'UA-127383416-1');
gtag("config", "UA-127383416-1");
</script>
<!-- There are no more hacks. -->
@@ -130,7 +136,10 @@
<script src="{{ pathto('_static/', 1) }}/konami.js"></script>
<script>
var easter_egg = new Konami('https://www.myfortunecookie.co.uk/fortunes/' + (Math.floor(Math.random() * 152) + 1));
var easter_egg = new Konami(
"https://www.myfortunecookie.co.uk/fortunes/" +
(Math.floor(Math.random() * 152) + 1)
);
</script>
<style>
@@ -140,67 +149,94 @@
</style>
<!-- GitHub Logo -->
<a href="https://github.com/kennethreitz/responder" class="github-corner" aria-label="View source on GitHub">
<svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;"
aria-hidden="true">
<a
href="https://github.com/kennethreitz/responder"
class="github-corner"
aria-label="View source on GitHub"
>
<svg
width="80"
height="80"
viewBox="0 0 250 250"
style="fill: #151513; color: #fff; position: absolute; top: 0; border: 0; right: 0"
aria-hidden="true"
>
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
fill="currentColor" class="octo-body"></path>
<path
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
fill="currentColor"
style="transform-origin: 130px 106px"
class="octo-arm"
></path>
<path
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
fill="currentColor"
class="octo-body"
></path>
</svg>
</a>
<style>
.github-corner:hover .octo-arm {
animation: octocat-wave 560ms ease-in-out
animation: octocat-wave 560ms ease-in-out;
}
@keyframes octocat-wave {
0%,
100% {
transform: rotate(0)
transform: rotate(0);
}
20%,
60% {
transform: rotate(-25deg)
transform: rotate(-25deg);
}
40%,
80% {
transform: rotate(10deg)
transform: rotate(10deg);
}
}
@media (max-width:500px) {
@media (max-width: 500px) {
.github-corner:hover .octo-arm {
animation: none
animation: none;
}
.github-corner .octo-arm {
animation: octocat-wave 560ms ease-in-out
animation: octocat-wave 560ms ease-in-out;
}
}
</style>
<!-- That was not a hack. That was art.
<!-- UserVoice JavaScript SDK (only needed once on a page) -->
<script>(function () { var uv = document.createElement('script'); uv.type = 'text/javascript'; uv.async = true; uv.src = '//widget.uservoice.com/f4AQraEfwInlMzkexfRLg.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(uv, s) })()</script>
<script>
(function () {
var uv = document.createElement("script");
uv.type = "text/javascript";
uv.async = true;
uv.src = "//widget.uservoice.com/f4AQraEfwInlMzkexfRLg.js";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(uv, s);
})();
</script>
<!-- A tab to launch the Classic Widget -->
<script>
UserVoice = window.UserVoice || [];
UserVoice.push(['showTab', 'classic_widget', {
mode: 'feedback',
primary_color: '#fa8c28',
link_color: '#0a8cc6',
forum_id: 913660,
tab_label: 'Got feedback?',
tab_color: '#00994f',
tab_position: 'bottom-left',
tab_inverted: true
}]);
UserVoice.push([
"showTab",
"classic_widget",
{
mode: "feedback",
primary_color: "#fa8c28",
link_color: "#0a8cc6",
forum_id: 913660,
tab_label: "Got feedback?",
tab_color: "#00994f",
tab_position: "bottom-left",
tab_inverted: true,
},
]);
</script>
+69 -14
View File
@@ -1,33 +1,88 @@
<p class="logo">
<a href="{{ pathto(master_doc) }}">
<img class="logo" src="{{ pathto('_static/responder.png', 1) }}" title="https://kennethreitz.org/tattoos" />
<img
class="logo"
src="{{ pathto('_static/responder.png', 1) }}"
title="https://kennethreitz.org/tattoos"
/>
</a>
</p>
<p>
<iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&repo=responder&type=watch&count=true&size=large"
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
<a class="github-button"
href="https://github.com/kennethreitz/responder"
data-color-scheme="no-preference: light; light: light; dark: light;"
data-size="large"
data-show-count="true"
aria-label="Star kennethreitz/responder on GitHub">Star</a>
</p>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css"
/>
<style>
.algolia-autocomplete {
width: 100%;
height: 1.5em;
}
.algolia-autocomplete a {
border-bottom: none !important;
}
#doc_search {
width: 100%;
height: 100%;
}
</style>
<input id="doc_search" placeholder="Search the doc" autofocus />
<script
type="text/javascript"
src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js"
onload="docsearch({
apiKey: 'ac965312db252e0496283c75c6f76f0b',
indexName: 'python-responder',
inputSelector: '#doc_search',
debug: false // Set debug to true if you want to inspect the dropdown
})"
async
></script>
<p>
<strong>Responder</strong> is a web service framework, written for human beings.
</p>
<p><strong>Responder</strong> is a web service framework, written for human beings.</p>
<h3>Stay Informed</h3>
<p>Receive updates on new releases and upcoming projects.</p>
<p><iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=true" allowtransparency="true"
frameborder="0" scrolling="0" width="200" height="20"></iframe></p>
<p><a href="https://twitter.com/kennethreitz" class="twitter-follow-button" data-show-count="false">Follow
@kennethreitz</a>
<script>!function (d, s, id) { var js, fjs = d.getElementsByTagName(s)[0], p = /^http:/.test(d.location) ? 'http' : 'https'; if (!d.getElementById(id)) { js = d.createElement(s); js.id = id; js.src = p + '://platform.twitter.com/widgets.js'; fjs.parentNode.insertBefore(js, fjs); } }(document, 'script', 'twitter-wjs');</script>
<p>
<a class="github-button"
href="https://github.com/kennethreitz"
data-color-scheme="no-preference: light; light: light; dark: light;"
data-size="medium"
data-show-count="true"
aria-label="Follow @kennethreitz on GitHub">Follow @kennethreitz</a>
</p>
<p>
<a
href="https://x.com/kennethreitz42"
class="twitter-follow-button"
data-show-count="false"
>Follow @kennethreitz</a
>
<script>
!(function (d, s, id) {
var js,
fjs = d.getElementsByTagName(s)[0],
p = /^http:/.test(d.location) ? "http" : "https";
if (!d.getElementById(id)) {
js = d.createElement(s);
js.id = id;
js.src = p + "://platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js, fjs);
}
})(document, "script", "twitter-wjs");
</script>
</p>
<h3>Useful Links</h3>
<ul>
<li><a href="http://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
<li><a href="http://pypi.python.org/pypi/responder">Responder @ PyPI</a></li>
<li><a href="http://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
+73 -13
View File
@@ -1,33 +1,93 @@
<p class="logo">
<a href="{{ pathto(master_doc) }}">
<img class="logo" src="{{ pathto('_static/responder.png', 1) }}" title="https://kennethreitz.org/tattoos" />
<img
class="logo"
src="{{ pathto('_static/responder.png', 1) }}"
title="https://kennethreitz.org/tattoos"
/>
</a>
</p>
<p>
<iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&repo=responder&type=watch&count=true&size=large"
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
<iframe
src="https://ghbtns.com/github-btn.html?user=kennethreitz&repo=responder&type=watch&count=true&size=large"
allowtransparency="true"
frameborder="0"
scrolling="0"
width="200px"
height="35px"
></iframe>
</p>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css"
/>
<style>
.algolia-autocomplete {
width: 100%;
height: 1.5em;
}
.algolia-autocomplete a {
border-bottom: none !important;
}
#doc_search {
width: 100%;
height: 100%;
}
</style>
<input id="doc_search" placeholder="Search the doc" autofocus />
<script
type="text/javascript"
src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js"
onload="docsearch({
apiKey: 'ac965312db252e0496283c75c6f76f0b',
indexName: 'python-responder',
inputSelector: '#doc_search',
debug: false // Set debug to true if you want to inspect the dropdown
})"
async
></script>
<p>
<strong>Responder</strong> is a web service framework, written for human beings.
</p>
<p><strong>Responder</strong> is a web service framework, written for human beings.</p>
<h3>Stay Informed</h3>
<p>Receive updates on new releases and upcoming projects.</p>
<p><iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=true" allowtransparency="true"
frameborder="0" scrolling="0" width="200" height="20"></iframe></p>
<p><a href="https://twitter.com/kennethreitz" class="twitter-follow-button" data-show-count="false">Follow
@kennethreitz</a>
<script>!function (d, s, id) { var js, fjs = d.getElementsByTagName(s)[0], p = /^http:/.test(d.location) ? 'http' : 'https'; if (!d.getElementById(id)) { js = d.createElement(s); js.id = id; js.src = p + '://platform.twitter.com/widgets.js'; fjs.parentNode.insertBefore(js, fjs); } }(document, 'script', 'twitter-wjs');</script>
<p>
<iframe
src="https://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=true"
allowtransparency="true"
frameborder="0"
scrolling="0"
width="200"
height="20"
></iframe>
</p>
<p>
<a
href="https://x.com/kennethreitz42"
class="twitter-follow-button"
data-show-count="false"
>Follow @kennethreitz</a
>
<script>
!(function (d, s, id) {
var js,
fjs = d.getElementsByTagName(s)[0],
p = /^http:/.test(d.location) ? "http" : "https";
if (!d.getElementById(id)) {
js = d.createElement(s);
js.id = id;
js.src = p + "://platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js, fjs);
}
})(document, "script", "twitter-wjs");
</script>
</p>
<h3>Useful Links</h3>
<ul>
<li><a href="http://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
<li><a href="http://pypi.python.org/pypi/responder">Responder @ PyPI</a></li>
<li><a href="http://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
+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
+74 -4
View File
@@ -20,7 +20,7 @@
# -- Project information -----------------------------------------------------
project = "responder"
copyright = "2018, A Kenneth Reitz project"
copyright = "2018-2026, A Kenneth Reitz project"
author = "Kenneth Reitz"
# The short X.Y version
@@ -57,6 +57,11 @@ extensions = [
"sphinx.ext.ifconfig",
"sphinx.ext.viewcode",
"sphinx.ext.githubpages",
"myst_parser",
"sphinx_copybutton",
"sphinx_design",
"sphinx_design_elements",
"sphinxext.opengraph",
]
# Add any paths that contain templates here, relative to this directory.
@@ -66,7 +71,7 @@ templates_path = ["_templates"]
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = ".rst"
source_suffix = {".rst": "restructuredtext"}
# The master toctree document.
master_doc = "index"
@@ -76,7 +81,7 @@ master_doc = "index"
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
language = "en"
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
@@ -211,12 +216,77 @@ epub_exclude_files = ["search.html"]
# -- Extension configuration -------------------------------------------------
# -- Options for link checker ----------------------------------------------
linkcheck_ignore = [
# Feldroy.com links are ignored because it blocks GHA.
r"https://www.feldroy.com/.*",
]
linkcheck_anchors_ignore_for_url = [
# Requires JavaScript.
# After opting-in to new GitHub issues, Sphinx can no longer grok the HTML anchor references.
r"https://github.com",
]
# -- Options for intersphinx extension ---------------------------------------
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {"https://docs.python.org/": None}
intersphinx_mapping = {"python": ("https://docs.python.org/3", None)}
# -- Options for todo extension ----------------------------------------------
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
# -- Options for MyST --------------------------------------------------------
myst_heading_anchors = 3
myst_enable_extensions = [
"attrs_block",
"attrs_inline",
"colon_fence",
"deflist",
"fieldlist",
"html_admonition",
"html_image",
"linkify",
"replacements",
"strikethrough",
"substitution",
"tasklist",
]
myst_substitutions = {}
# -- Options for OpenGraph ---------------------------------------------------
#
# When making changes, check them using the RTD PR preview URL on https://www.opengraph.xyz/.
#
# About text lengths
#
# Original documentation says:
# - ogp_description_length
# Configure the amount of characters taken from a page. The default of 200 is probably good
# for most people. If something other than a number is used, it defaults back to 200.
# -- https://sphinxext-opengraph.readthedocs.io/en/latest/#options
#
# Other people say:
# - og:title 40 chars
# - og:description has 2 max lengths:
# When the link is used in a Post, it's 300 chars. When a link is used in a Comment, it's 110 chars.
# So you can either treat it as 110, or, write your Descriptions to 300 but make sure the first 110
# is the critical part and still makes sense when it gets cut off.
# -- https://stackoverflow.com/questions/8914476/facebook-open-graph-meta-tags-maximum-content-length
ogp_site_url = "https://responder.kennethreitz.org/"
ogp_description_length = 300
ogp_site_name = "Responder Documentation"
ogp_image = "https://responder.kennethreitz.org/_static/responder.png"
ogp_image_alt = False
ogp_use_first_image = False
ogp_type = "website"
ogp_enable_meta_description = True
# -- Options for sphinx-copybutton -------------------------------------------
copybutton_remove_prompts = True
copybutton_line_continuation_character = "\\"
copybutton_prompt_text = r">>> |\.\.\. |\$ |sh\$ |PS> |cr> |mysql> |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: "
copybutton_prompt_is_regexp = True
+55
View File
@@ -0,0 +1,55 @@
Deploying Responder
===================
You can deploy Responder anywhere you can deploy a basic Python application.
Docker Deployment
-----------------
Assuming an existing ``api.py`` containing your Responder application.
``Dockerfile``::
FROM python:3.13-slim
WORKDIR /app
COPY . .
RUN pip install responder
ENV PORT=80
EXPOSE 80
CMD ["python", "api.py"]
That's it!
Cloud Deployment
----------------
Responder automatically honors the ``PORT`` environment variable, which is
set by most cloud platforms (Fly.io, Railway, Render, Google Cloud Run, etc.).
The basics::
$ mkdir my-api
$ cd my-api
Write out an ``api.py``::
import responder
api = responder.API()
@api.route("/")
async def hello(req, resp):
resp.text = "hello, world!"
if __name__ == "__main__":
api.run()
Deploy with your platform of choice. Responder will bind to ``0.0.0.0``
on the port specified by ``PORT`` automatically.
Running with Uvicorn Directly
-----------------------------
For production deployments, you can also run your app directly with uvicorn::
uvicorn api:api --host 0.0.0.0 --port 8000 --workers 4
+75 -58
View File
@@ -6,24 +6,25 @@
A familiar HTTP Service Framework
=================================
|Build Status| |image1| |image2| |image3| |image4| |image5|
|ci-tests| |version| |license| |python-versions| |downloads| |contributors| |say-thanks|
.. |Build Status| image:: https://travis-ci.org/kennethreitz/responder.svg?branch=master
:target: https://travis-ci.org/kennethreitz/responder
.. |image1| image:: https://img.shields.io/pypi/v/responder.svg
.. |ci-tests| image:: https://github.com/kennethreitz/responder/actions/workflows/test.yaml/badge.svg
:target: https://github.com/kennethreitz/responder/actions/workflows/test.yaml
.. |ci-docs| image:: https://github.com/kennethreitz/responder/actions/workflows/docs.yaml/badge.svg
:target: https://github.com/kennethreitz/responder/actions/workflows/docs.yaml
.. |version| image:: https://img.shields.io/pypi/v/responder.svg
:target: https://pypi.org/project/responder/
.. |image2| image:: https://img.shields.io/pypi/l/responder.svg
.. |license| image:: https://img.shields.io/pypi/l/responder.svg
:target: https://pypi.org/project/responder/
.. |image3| image:: https://img.shields.io/pypi/pyversions/responder.svg
.. |python-versions| image:: https://img.shields.io/pypi/pyversions/responder.svg
:target: https://pypi.org/project/responder/
.. |image4| image:: https://img.shields.io/github/contributors/kennethreitz/responder.svg
.. |downloads| image:: https://static.pepy.tech/badge/responder/month
:target: https://www.pepy.tech/projects/responder
.. |contributors| image:: https://img.shields.io/github/contributors/kennethreitz/responder.svg
:target: https://github.com/kennethreitz/responder/graphs/contributors
.. |image5| image:: https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg
.. |say-thanks| image:: https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg
:target: https://saythanks.io/to/kennethreitz
The Python world certainly doesn't need more web frameworks. But, it does need more creativity, so I thought I'd
spread some `Hacktoberfest <https://hacktoberfest.digitalocean.com/>`_ spirit around, bring some of my ideas to the table, and see what I could come up with.
.. code:: python
import responder
@@ -37,65 +38,51 @@ spread some `Hacktoberfest <https://hacktoberfest.digitalocean.com/>`_ spirit ar
if __name__ == '__main__':
api.run()
That ``async`` declaration is optional.
Responder is powered by `Starlette`_.
This gets you a ASGI app, with a production static files server
pre-installed, jinja2 templating (without additional imports), and a
production webserver based on uvloop, serving up requests with gzip
compression automatically.
The example program demonstrates an `ASGI`_ application using `Responder`_,
including production-ready components like the `uvicorn`_ webserver, based
on `uvloop`_, and the `Jinja`_ templating library pre-installed.
The ``async`` declaration within the example program is optional.
Features
--------
- A pleasant API, with a single import statement.
- Class-based views without inheritence.
- ASGI framework, the future of Python web services.
- Class-based views without inheritance.
- `ASGI`_, the future of Python web services.
- Asynchronous Python frameworks and applications.
- Automatic gzip compression.
- WebSocket support!
- The ability to mount any ASGI / WSGI app at a subroute.
- *f-string syntax* route declaration.
- `f-string syntax`_ route declaration.
- Mutable response object, passed into each view. No need to return anything.
- Background tasks, spawned off in a ``ThreadPoolExecutor``.
- GraphQL (with *GraphiQL*) support!
- OpenAPI schema generation.
- OpenAPI schema generation, with interactive documentation!
- Single-page webapp support!
Testimonials
------------
“Pleasantly very taken with python-responder.
`@kennethreitz <https://twitter.com/kennethreitz>`_ at his absolute
best.”
—Rudraksh M.K.
`@kennethreitz`_ at his absolute best.”
— Rudraksh M.K.
..
"ASGI is going to enable all sorts of new high-performance web services. It's awesome to see Responder starting to take advantage of that."
Tom Christie, author of `Django REST Framework`_
Tom Christie, author of `Django REST Framework`_
..
“I love that you are exploring new patterns. Go go go!”
— Danny Greenfield, author of `Two Scoops of Django`_
— Danny Greenfield, author of `Two Scoops of Django`_
..
“The most ambitious crossover event in history.”
—Pablo Cabezas, `on Tom Christie joining the project`_
.. _APIStar: https://github.com/encode/apistar
.. _Django REST Framework: https://www.django-rest-framework.org/
.. _Two Scoops of Django:
.. _on Tom Christie joining the project: https://twitter.com/pabloteleco/status/1050841098321620992?s=20
User Guides
-----------
@@ -104,18 +91,41 @@ User Guides
quickstart
tour
deployment
testing
api
cli
Installing Responder
--------------------
Use ``uv`` for fast installation.
.. code-block:: shell
$ pipenv install responder
✨🍰✨
uv pip install --upgrade 'responder'
Only **Python 3.6+** is supported.
Or use standard pip where ``uv`` is not available.
.. code-block:: shell
pip install --upgrade 'responder'
Responder supports **Python 3.9+**.
Development
-----------
If you are looking at installing Responder
for hacking on it, please refer to the :ref:`sandbox` documentation.
.. toctree::
:maxdepth: 1
changes
Sandbox <sandbox>
backlog
The Basic Idea
@@ -123,36 +133,43 @@ The Basic Idea
The primary concept here is to bring the niceties that are brought forth from both Flask and Falcon and unify them into a single framework, along with some new ideas I have. I also wanted to take some of the API primitives that are instilled in the Requests library and put them into a web framework. So, you'll find a lot of parallels here with Requests.
- Setting ``resp.text`` sends back unicode, while setting ``resp.content`` sends back bytes.
- Setting ``resp.media`` sends back JSON/YAML (``.text``/``.content`` override this).
- Setting ``resp.content`` sends back bytes.
- Setting ``resp.text`` sends back unicode, while setting ``resp.html`` sends back HTML.
- Setting ``resp.media`` sends back JSON/YAML (``.text``/``.html``/``.content`` override this).
- Case-insensitive ``req.headers`` dict (from Requests directly).
- ``resp.status_code``, ``req.method``, ``req.url``, and other familiar friends.
Ideas
-----
- Flask-style route expression, with new capabilities -- all while using Python 3.6+'s new f-string syntax.
- I love Falcon's "every request and response is passed into to each view and mutated" methodology, especially ``response.media``, and have used it here. In addition to supporting JSON, I have decided to support YAML as well, as Kubernetes is slowly taking over the world, and it uses YAML for all the things. Content-negotiation and all that.
- **A built in testing client that uses the actual Requests you know and love**.
- Flask-style route expression, with new capabilities -- using Python's f-string syntax.
- I love Falcon's "every request and response is passed into each view and mutated" methodology, especially ``response.media``, and have used it here. In addition to supporting JSON, I have decided to support YAML as well, as Kubernetes is slowly taking over the world, and it uses YAML for all the things. Content-negotiation and all that.
- **A built in testing client** powered by Starlette's TestClient.
- The ability to mount other WSGI apps easily.
- Automatic gzipped-responses.
- In addition to Falcon's ``on_get``, ``on_post``, etc methods, Responder features an ``on_request`` method, which gets called on every type of request, much like Requests.
- A production static files server is built-in.
- Uvicorn built-in as a production web server. I would have chosen Gunicorn, but it doesn't run on Windows. Plus, Uvicorn serves well to protect against slowloris attacks, making nginx unneccessary in production.
- `uvicorn`_ is built-in as a production web server. I would have chosen Gunicorn, but it doesn't run on Windows. Plus, uvicorn serves well to protect against `Slowloris`_ attacks, making Nginx unnecessary in production.
- GraphQL support, via Graphene. The goal here is to have any GraphQL query exposable at any route, magically.
Future Ideas
------------
- Cookie-based sessions are currently an afterthought, as this is an API framework, but websites are APIs too.
- If frontend websites are supported, provide an official way to run webpack.
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
.. _@kennethreitz: https://x.com/kennethreitz42
.. _ASGI: https://en.wikipedia.org/wiki/Asynchronous_Server_Gateway_Interface
.. _Django REST Framework: https://www.django-rest-framework.org/
.. _f-string syntax: https://docs.python.org/3/whatsnew/3.6.html#pep-498-formatted-string-literals
.. _Jinja: https://jinja.palletsprojects.com/en/stable/
.. _ServeStatic: https://archmonger.github.io/ServeStatic/latest/
.. _Slowloris: https://en.wikipedia.org/wiki/Slowloris_(computer_security)
.. _Starlette: https://www.starlette.io/
.. _Responder: https://responder.kennethreitz.org/
.. _Two Scoops of Django: https://www.feldroy.com/two-scoops-press#two-scoops-of-django
.. _uvicorn: https://www.uvicorn.org/
.. _uvloop: https://uvloop.readthedocs.io/
+55 -5
View File
@@ -34,7 +34,7 @@ Next, we can run our web service easily, with ``api.run()``::
This will spin up a production web server on port ``5042``, ready for incoming HTTP requests.
Note: you can pass ``port=5000`` if you want to customize the port. The ``PORT`` environment variable for established web service providers (e.g. Heroku) will automatically be honored.
Note: you can pass ``port=5000`` if you want to customize the port. The ``PORT`` environment variable for established web service providers (e.g. Heroku) will automatically be honored and will set the listening address to ``0.0.0.0`` automatically (also configurable through the ``address`` keyword argument).
Accept Route Arguments
@@ -48,6 +48,14 @@ If you want dynamic URLs, you can use Python's familiar *f-string syntax* to dec
A ``GET`` request to ``/hello/brettcannon`` will result in a response of ``hello, brettcannon!``.
Type convertors are also available::
@api.route("/add/{a:int}/{b:int}")
async def add(req, resp, *, a, b):
resp.text = f"{a} + {b} = {a + b}"
Supported types: ``str``, ``int``, ``float``, ``uuid``, and ``path``.
Returning JSON / YAML
---------------------
@@ -65,13 +73,30 @@ If the client requests YAML instead (with a header of ``Accept: application/x-ya
Rendering a Template
--------------------
If you want to render a template, simply use ``api.template``. No need for additional imports::
Responder provides a built-in light `Jinja`_ wrapper ``templates.Templates``
Usage::
from responder.templates import Templates
templates = Templates()
@api.route("/hello/{name}/html")
def hello(req, resp, name):
resp.html = templates.render("hello.html", name=name)
Also a ``render_async`` is available::
templates = Templates(enable_async=True)
resp.html = await templates.render_async("hello.html", who=who)
You can also use the existing ``api.template(filename, *args, **kwargs)`` to render templates::
@api.route("/hello/{who}/html")
def hello_html(req, resp, *, who):
resp.content = api.template('hello.html', who=who)
resp.html = api.template('hello.html', who=who)
The ``api`` instance is available as an object during template rendering.
Setting Response Status Code
----------------------------
@@ -90,7 +115,7 @@ If you want to set a response header, like ``X-Pizza: 42``, simply modify the ``
@api.route("/pizza")
def pizza_pizza(req, resp):
resp.headers['X-Pizza'] = 42
resp.headers['X-Pizza'] = '42'
That's it!
@@ -124,3 +149,28 @@ Here, we'll process our data in the background, while responding immediately to
resp.media = {'success': True}
A ``POST`` request to ``/incoming`` will result in an immediate response of ``{'success': true}``.
Here's a sample code to post a file with background::
@api.route("/")
async def upload_file(req, resp):
@api.background.task
def process_data(data):
with open(f"./{data['file']['filename']}", 'wb') as f:
f.write(data['file']['content'])
data = await req.media(format='files')
process_data(data)
resp.media = {'success': 'ok'}
You can test file uploads using the built-in test client::
files = {'file': ('hello.txt', b'hello, world!', 'text/plain')}
r = api.requests.post(api.url_for(upload_file), files=files)
print(r.json())
.. _Jinja: https://jinja.palletsprojects.com/en/stable/
+35
View File
@@ -0,0 +1,35 @@
(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 runtime extensions and development tools.
```shell
uv pip install --upgrade --editable '.[develop,docs,release,test]'
```
## Operations
Invoke linter and software tests.
```shell
source .venv/bin/activate
poe check
```
Format code.
```shell
poe format
```
Documentation authoring.
```shell
poe docs-autobuild
```
+48
View File
@@ -0,0 +1,48 @@
Building and Testing with Responder
===================================
Responder comes with a first-class, well supported test client for your ASGI web services (powered by Starlette's TestClient).
Here, we'll go over the basics of setting up and testing a Responder application.
The Basics
----------
Your project should look like this::
api.py test_api.py
``$ cat api.py``::
import responder
api = responder.API()
@api.route("/")
def hello_world(req, resp):
resp.text = "hello, world!"
if __name__ == "__main__":
api.run()
Writing Tests
-------------
``$ cat test_api.py``::
import pytest
import api as service
@pytest.fixture
def api():
return service.api
def test_hello_world(api):
r = api.requests.get("/")
assert r.text == "hello, world!"
``$ pytest``::
...
========================== 1 passed in 0.10 seconds ==========================
+388 -34
View File
@@ -2,6 +2,21 @@ Feature Tour
============
Route Method Filtering
----------------------
You can restrict routes to specific HTTP methods::
@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}
Class-Based Views
-----------------
@@ -9,12 +24,67 @@ Class-based views (and setting some headers and stuff)::
@api.route("/{greeting}")
class GreetingResource:
def on_request(req, resp, *, greeting): # or on_get...
def on_request(self, req, resp, *, greeting): # or on_get...
resp.text = f"{greeting}, world!"
resp.headers.update({'X-Life': '42'})
resp.status_code = api.status_codes.HTTP_416
Lifespan Events
---------------
Use the lifespan context manager for startup and shutdown logic::
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app):
# Startup: connect to database, etc.
print("Starting up...")
yield
# Shutdown: clean up resources
print("Shutting down...")
api = responder.API(lifespan=lifespan)
You can also use the traditional event decorators::
@api.on_event('startup')
async def startup():
print("Starting up...")
@api.on_event('shutdown')
async def shutdown():
print("Shutting down...")
Serving Files
-------------
Serve files from disk with automatic content-type detection::
@api.route("/download")
def download(req, resp):
resp.file("reports/annual.pdf")
You can also specify the content type explicitly::
@api.route("/image")
def image(req, resp):
resp.file("photos/cat.jpg", content_type="image/jpeg")
Custom Error Handling
---------------------
Register handlers for specific exception types::
@api.exception_handler(ValueError)
async def handle_value_error(req, resp, exc):
resp.status_code = 400
resp.media = {"error": str(exc)}
Background Tasks
----------------
@@ -34,51 +104,73 @@ Here, you can spawn off a background thread to run any function, out-of-request:
GraphQL
-------
Responder supports GraphQL, a query language for APIs that enables clients to
request exactly the data they need.
For more information about GraphQL, visit https://graphql.org/.
Serve a GraphQL API::
import graphene
from responder.ext.graphql import GraphQLView
class Query(graphene.ObjectType):
hello = graphene.String(name=graphene.String(default_value="stranger"))
def resolve_hello(self, info, name):
return "Hello " + name
return f"Hello {name}"
api.add_route("/graph", graphene.Schema(query=Query))
schema = graphene.Schema(query=Query)
view = GraphQLView(api=api, schema=schema)
api.add_route("/graph", view)
Visiting the endpoint will render a *GraphiQL* instance, in the browser.
You can make use of Responder's Request and Response objects in your GraphQL resolvers through ``info.context['request']`` and ``info.context['response']``.
Built-in Testing Client (Requests)
----------------------------------
We can then send a query to our service::
>>> requests = api.session()
>>> r = requests.get("http://;/graph", params={"query": "{ hello }"})
>>> r.json()
{'data': {'hello': 'Hello stranger'}}
Or, request YAML back::
>>> r = requests.get("http://;/graph", params={"query": "{ hello(name:\"john\") }"}, headers={"Accept": "application/x-yaml"})
>>> print(r.text)
data: {hello: Hello john}
OpenAPI Schema Support
----------------------
Responder comes with built-in support for OpenAPI / marshmallow::
Responder comes with built-in support for OpenAPI / marshmallow.
.. note::
If you're upgrading from a previous version, note that the OpenAPI module
has been renamed from ``responder.ext.schema`` to ``responder.ext.openapi``.
Update your imports accordingly.
New in Responder 1.4.0::
import responder
from responder.ext.openapi import OpenAPISchema
from marshmallow import Schema, fields
api = responder.API(title="Web Service", version="1.0", openapi="3.0")
contact = {
"name": "API Support",
"url": "http://www.example.com/support",
"email": "support@example.com",
}
license = {
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
}
api = responder.API()
schema = OpenAPISchema(
app=api,
title="Web Service",
version="1.0",
openapi="3.0.2",
description="A simple pet store",
terms_of_service="http://example.com/terms/",
contact=contact,
license=license,
)
@api.schema("Pet")
@schema.schema("Pet")
class PetSchema(Schema):
name = fields.Str()
@@ -92,37 +184,133 @@ Responder comes with built-in support for OpenAPI / marshmallow::
responses:
200:
description: A pet to be returned
schema:
$ref = "#/components/schemas/Pet"
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
"""
resp.media = PetSchema().dump({"name": "little orange"})
Old way *It's recommended to use the code above* ::
import responder
from marshmallow import Schema, fields
contact = {
"name": "API Support",
"url": "http://www.example.com/support",
"email": "support@example.com",
}
license = {
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
}
api = responder.API(
title="Web Service",
version="1.0",
openapi="3.0.2",
description="A simple pet store",
terms_of_service="http://example.com/terms/",
contact=contact,
license=license,
)
@api.schema("Pet")
class PetSchema(Schema):
name = fields.Str()
@api.route("/")
def route(req, resp):
"""A cute furry animal endpoint.
---
get:
description: Get a random pet
responses:
200:
description: A pet to be returned
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
"""
resp.media = PetSchema().dump({"name": "little orange"})
::
>>> r = api.session().get("http://;/schema.yml")
>>> print(r.text)
components:
parameters: {}
schemas:
parameters: {}
responses: {}
schemas:
Pet:
properties:
properties:
name: {type: string}
type: object
info: {title: Web Service, version: 1.0}
openapi: '3.0'
type: object
securitySchemes: {}
info:
contact: {email: support@example.com, name: API Support, url: 'http://www.example.com/support'}
description: This is a sample server for a pet store.
license: {name: Apache 2.0, url: 'https://www.apache.org/licenses/LICENSE-2.0.html'}
termsOfService: http://example.com/terms/
title: Web Service
version: 1.0
openapi: 3.0.2
paths:
/:
get:
description: Get a random pet
responses:
200: {description: A pet to be returned, schema: $ref = "#/components/schemas/Pet"}
200: {description: A pet to be returned, schema: $ref: "#/components/schemas/Pet"}
tags: []
Mount a WSGI App (e.g. Flask)
-----------------------------
Interactive Documentation
-------------------------
Responder can automatically supply API Documentation for you. Using the example above
The new and recommended way::
from responder.ext.openapi import OpenAPISchema
api = responder.API()
schema = OpenAPISchema(
app=api,
title="Web Service",
version="1.0",
openapi="3.0.2",
...
docs_route='/docs',
...
description=description,
terms_of_service=terms_of_service,
contact=contact,
license=license,
)
The old way::
api = responder.API(
title="Web Service",
version="1.0",
openapi="3.0.2",
docs_route='/docs',
description=description,
terms_of_service=terms_of_service,
contact=contact,
license=license,
)
This will make ``/docs`` render interactive documentation for your API.
Mount a WSGI / ASGI Apps (e.g. Flask, Starlette,...)
----------------------------------------------------
Responder gives you the ability to mount another ASGI / WSGI app at a subroute::
@@ -140,10 +328,135 @@ Responder gives you the ability to mount another ASGI / WSGI app at a subroute::
That's it!
Single-Page Web Apps
--------------------
If you have a single-page webapp, you can tell Responder to serve up your ``static/index.html`` at a route, like so::
api.add_route("/", static=True)
This will make ``index.html`` the default response to all undefined routes.
Reading / Writing Cookies
-------------------------
Responder makes it very easy to interact with cookies from a Request, or add some to a Response::
>>> resp.cookies["hello"] = "world"
>>> req.cookies
{"hello": "world"}
To set cookies directives, you should use `resp.set_cookie`::
>>> resp.set_cookie("hello", value="world", max_age=60)
Supported directives:
* ``key`` - **Required**
* ``value`` - [OPTIONAL] - Defaults to ``""``.
* ``expires`` - Defaults to ``None``.
* ``max_age`` - Defaults to ``None``.
* ``domain`` - Defaults to ``None``.
* ``path`` - Defaults to ``"/"``.
* ``secure`` - Defaults to ``False``.
* ``httponly`` - Defaults to ``True``.
For more information see `directives <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie>`_
Using Cookie-Based Sessions
---------------------------
Responder has built-in support for cookie-based sessions. To enable cookie-based sessions, simply add something to the ``resp.session`` dictionary::
>>> resp.session['username'] = 'kennethreitz'
A cookie called ``Responder-Session`` will be set, which contains all the data in ``resp.session``. It is signed, for verification purposes.
You can easily read a Request's session data, that can be trusted to have originated from the API::
>>> req.session
{'username': 'kennethreitz'}
**Note**: if you are using this in production, you should pass the ``secret_key`` argument to ``API(...)``::
api = responder.API(secret_key=os.environ['SECRET_KEY'])
Using ``before_request``
------------------------
If you'd like a view to be executed before every request, simply do the following::
@api.route(before_request=True)
def prepare_response(req, resp):
resp.headers["X-Pizza"] = "42"
Now all requests to your HTTP Service will include an ``X-Pizza`` header.
For ``websockets``::
@api.route(before_request=True, websocket=True)
def prepare_response(ws):
await ws.accept()
WebSocket Support
-----------------
Responder supports 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}!")
await ws.close()
Accepting the connection::
await websocket.accept()
Sending and receiving data::
await websocket.send_{format}(data)
await websocket.receive_{format}(data)
Supported formats: ``text``, ``json``, ``bytes``.
Closing the connection::
await websocket.close()
Using Requests Test Client
--------------------------
Responder comes with a first-class, well supported test client for your ASGI web services (powered by Starlette's TestClient).
Here's an example of a test (written with pytest)::
import myapi
@pytest.fixture
def api():
return myapi.api
def test_response(api):
hello = "hello, world!"
@api.route('/some-url')
def some_view(req, resp):
resp.text = hello
r = api.requests.get(url=api.url_for(some_view))
assert r.text == hello
HSTS (Redirect to HTTPS)
------------------------
Want HSTS?
Want HSTS (to redirect all traffic to HTTPS)?
::
@@ -151,3 +464,44 @@ Want HSTS?
Boom.
CORS
----
Want `CORS <https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/>`_ ?
::
api = responder.API(cors=True)
The default parameters used by **Responder** are restrictive by default, so you'll need to explicitly enable particular origins, methods, or headers, in order for browsers to be permitted to use them in a Cross-Domain context.
In order to set custom parameters, you need to set the ``cors_params`` argument of ``api``, a dictionary containing the following entries:
* ``allow_origins`` - A list of origins that should be permitted to make cross-origin requests. eg. ``['https://example.org', 'https://www.example.org']``. You can use ``['*']`` to allow any origin.
* ``allow_origin_regex`` - A regex string to match against origins that should be permitted to make cross-origin requests. eg. ``'https://.*\.example\.org'``.
* ``allow_methods`` - A list of HTTP methods that should be allowed for cross-origin requests. Defaults to `['GET']`. You can use ``['*']`` to allow all standard methods.
* ``allow_headers`` - A list of HTTP request headers that should be supported for cross-origin requests. Defaults to ``[]``. You can use ``['*']`` to allow all headers. The ``Accept``, ``Accept-Language``, ``Content-Language`` and ``Content-Type`` headers are always allowed for CORS requests.
* ``allow_credentials`` - Indicate that cookies should be supported for cross-origin requests. Defaults to ``False``.
* ``expose_headers`` - Indicate any response headers that should be made accessible to the browser. Defaults to ``[]``.
* ``max_age`` - Sets a maximum time in seconds for browsers to cache CORS responses. Defaults to ``60``.
Trusted Hosts
-------------
Make sure that all the incoming requests headers have a valid ``host``, that matches one of the provided patterns in the ``allowed_hosts`` attribute, in order to prevent HTTP Host Header attacks.
A 400 response will be raised, if a request does not match any of the provided patterns in the ``allowed_hosts`` attribute.
::
api = responder.API(allowed_hosts=['example.com', 'tenant.example.com'])
* ``allowed_hosts`` - A list of allowed hostnames.
Note:
* By default, all hostnames are allowed.
* Wildcard domains such as ``*.example.com`` are supported.
* To allow any hostname use ``allowed_hosts=["*"]``.
+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()
+230
View File
@@ -0,0 +1,230 @@
[build-system]
build-backend = "setuptools.build_meta"
requires = [
"setuptools>=42",
]
[project]
name = "responder"
description = "A familiar HTTP Service Framework for Python."
readme = "README.md"
license = {text = "Apache 2.0"}
authors = [
{ name = "Kenneth Reitz", email = "me@kennethreitz.org" },
]
requires-python = ">=3.9"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython",
"Topic :: Internet :: WWW/HTTP",
]
dynamic = ["version"]
dependencies = [
"a2wsgi",
"apispec>=1.0.0",
"chardet",
"docopt-ng",
"graphene>=3",
"graphql-core>=3.1",
"marshmallow",
"pueblo[sfa-full]>=0.0.11",
"python-multipart",
"starlette[full]>=0.40",
"uvicorn[standard]",
]
[project.optional-dependencies]
develop = [
"poethepoet",
"pyproject-fmt",
"ruff",
"validate-pyproject",
]
docs = [
"alabaster<1.1",
"myst-parser[linkify]",
"sphinx>=5,<9",
"sphinx-autobuild",
"sphinx-copybutton",
"sphinx-design-elements",
"sphinxext.opengraph",
]
release = ["build", "twine"]
test = [
"flask",
"mypy",
"pytest",
"pytest-cov",
"pytest-mock",
"pytest-rerunfailures",
]
[project.scripts]
responder = "responder.ext.cli:cli"
[project.urls]
Homepage = "https://github.com/kennethreitz/responder"
Documentation = "https://responder.kennethreitz.org"
Repository = "https://github.com/kennethreitz/responder"
Issues = "https://github.com/kennethreitz/responder/issues"
[tool.setuptools.dynamic]
version = {attr = "responder.__version__.__version__"}
[tool.setuptools.package-data]
responder = ["py.typed"]
[tool.setuptools.packages.find]
exclude = ["tests"]
[tool.ruff]
line-length = 90
extend-exclude = [
"docs/source/conf.py",
]
lint.select = [
# Builtins
"A",
# Bugbear
"B",
# comprehensions
"C4",
# Pycodestyle
"E",
# eradicate
"ERA",
# Pyflakes
"F",
# isort
"I",
# pandas-vet
"PD",
# return
"RET",
# Bandit
"S",
# print
"T20",
"W",
# flake8-2020
"YTT",
]
lint.extend-ignore = [
"S101", # Allow use of `assert`.
]
lint.per-file-ignores."responder/util/cmd.py" = [ "A005" ] # Module shadows a Python standard-library module
lint.per-file-ignores."tests/*" = [
"ERA001", # Found commented-out code.
"S101", # Allow use of `assert`, and `print`.
]
[tool.pytest.ini_options]
addopts = """
-rfEXs -p pytester --strict-markers --verbosity=3
--cov --cov-report=term-missing --cov-report=xml
"""
filterwarnings = [
"error::UserWarning",
]
log_level = "DEBUG"
log_cli_level = "DEBUG"
log_format = "%(asctime)-15s [%(name)-36s] %(levelname)-8s: %(message)s"
minversion = "2.0"
testpaths = [
"responder",
"tests",
]
markers = [
]
xfail_strict = true
[tool.coverage.run]
branch = false
omit = [
"*.html",
"tests/*",
]
[tool.coverage.report]
fail_under = 0
show_missing = true
exclude_lines = [
"# pragma: no cover",
"raise NotImplemented",
]
[tool.mypy]
packages = [
"responder",
]
exclude = [
]
check_untyped_defs = true
explicit_package_bases = true
ignore_missing_imports = true
implicit_optional = true
install_types = true
namespace_packages = true
non_interactive = true
[tool.poe.tasks]
check = [
"test",
]
docs-autobuild = [
{ cmd = "sphinx-autobuild --open-browser --watch docs/source docs/source docs/build" },
]
docs-html = [
{ cmd = "sphinx-build -W --keep-going docs/source docs/build" },
]
docs-linkcheck = [
{ cmd = "sphinx-build -W --keep-going -b linkcheck docs/source docs/build" },
]
format = [
{ cmd = "ruff format ." },
# Configure Ruff not to auto-fix (remove!):
# unused imports (F401), unused variables (F841), `print` statements (T201), and commented-out code (ERA001).
{ cmd = "ruff check --fix --ignore=ERA --ignore=F401 --ignore=F841 --ignore=T20 --ignore=ERA001 ." },
{ cmd = "pyproject-fmt --keep-full-version pyproject.toml" },
]
lint = [
{ cmd = "ruff format --check ." },
{ cmd = "ruff check ." },
{ cmd = "validate-pyproject pyproject.toml" },
{ cmd = "mypy" },
]
release = [
{ cmd = "python -m build" },
{ cmd = "twine upload --skip-existing dist/*" },
]
[tool.poe.tasks.test]
cmd = "pytest"
help = "Invoke software tests"
[tool.poe.tasks.test.args.expression]
options = [ "-k" ]
[tool.poe.tasks.test.args.marker]
options = [ "-m" ]
-4
View File
@@ -1,4 +0,0 @@
[pytest]
; addopts= -rsxX -s -v --strict
filterwarnings =
error::UserWarning
-5
View File
@@ -1,5 +0,0 @@
build:
image: latest
python:
version: 3.6
+18 -1
View File
@@ -1 +1,18 @@
from .core import *
"""
Responder - a familiar HTTP Service Framework.
This module exports the core functionality of the Responder framework,
including the API, Request, and Response classes.
"""
from . import ext
from .__version__ import __version__
from .core import API, Request, Response
__all__ = [
"API",
"Request",
"Response",
"__version__",
"ext",
]
-4
View File
@@ -1,4 +0,0 @@
from .cli import main
if __name__ == "__main__":
main()
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.1.0"
__version__ = "3.0.0"
+309 -298
View File
@@ -1,154 +1,219 @@
import asyncio
import os
import json
from functools import partial
from pathlib import Path
__all__ = ["API"]
import uvicorn
from starlette.middleware.cors import CORSMiddleware
from starlette.middleware.errors import ServerErrorMiddleware
from starlette.middleware.exceptions import ExceptionMiddleware
from starlette.middleware.gzip import GZipMiddleware
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
from starlette.middleware.sessions import SessionMiddleware
from starlette.middleware.trustedhost import TrustedHostMiddleware
import asyncio
import jinja2
from graphql_server import encode_execution_results, json_encode, default_format_error
from starlette.routing import Router
from starlette.staticfiles import StaticFiles
from starlette.testclient import TestClient
from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin
from apispec import yaml_utils
from asgiref.wsgi import WsgiToAsgi
from . import models
from . import status_codes
from .routes import Route
from .formats import get_formats
from .background import BackgroundQueue
from .templates import GRAPHIQL
from .formats import get_formats
from .models import Request, Response
from .routes import Router
from .staticfiles import StaticFiles
from .statics import DEFAULT_CORS_PARAMS, DEFAULT_OPENAPI_THEME, DEFAULT_SECRET_KEY
from .templates import Templates
# TODO: consider moving status codes here
class API:
"""The primary web-service class.
:param static_dir: The directory to use for static files. Will be created for you if it doesn't already exist.
:param templates_dir: The directory to use for templates. Will be created for you if it doesn't already exist.
:param enable_hsts: If ``True``, send all responses to HTTPS URLs.
"""
:param static_dir: The directory to use for static files. Will be created for you if it doesn't already exist.
:param templates_dir: The directory to use for templates. Will be created for you if it doesn't already exist.
:param auto_escape: If ``True``, HTML and XML templates will automatically be escaped.
:param enable_hsts: If ``True``, send all responses to HTTPS URLs.
:param openapi_theme: OpenAPI documentation theme, must be one of ``elements``, ``rapidoc``, ``redoc``, ``swagger_ui``
""" # noqa: E501
status_codes = status_codes
def __init__(
self,
*,
debug=False,
title=None,
version=None,
description=None,
terms_of_service=None,
contact=None,
license=None, # noqa: A002
openapi=None,
openapi_route="/schema.yml",
static_dir="static",
static_route="/static",
templates_dir="templates",
auto_escape=True,
secret_key=DEFAULT_SECRET_KEY,
enable_hsts=False,
docs_route=None,
cors=False,
cors_params=DEFAULT_CORS_PARAMS,
allowed_hosts=None,
openapi_theme=DEFAULT_OPENAPI_THEME,
lifespan=None,
):
self.title = title
self.version = version
self.openapi_version = openapi
self.static_dir = Path(os.path.abspath(static_dir))
self.background = BackgroundQueue()
self.secret_key = secret_key
self.router = Router(lifespan=lifespan)
if static_dir is not None:
if static_route is None:
static_route = ""
static_dir = Path(static_dir).resolve()
self.static_dir = static_dir
self.static_route = static_route
self.templates_dir = Path(os.path.abspath(templates_dir))
self.built_in_templates_dir = Path(
os.path.abspath(os.path.dirname(__file__) + "/templates")
)
self.routes = {}
self.schemas = {}
self.hsts_enabled = enable_hsts
self.static_files = StaticFiles(directory=str(self.static_dir))
self.apps = {self.static_route: self.static_files}
self.cors = cors
self.cors_params = cors_params
self.debug = debug
if not allowed_hosts:
allowed_hosts = ["*"]
self.allowed_hosts = allowed_hosts
if self.static_dir is not None:
self.static_dir.mkdir(parents=True, exist_ok=True)
self.mount(self.static_route, self.static_app)
self.formats = get_formats()
# Make the static/templates directory if they don't exist.
for _dir in (self.static_dir, self.templates_dir):
os.makedirs(_dir, exist_ok=True)
# Cached requests session.
self._session = None
self.background = BackgroundQueue()
if self.openapi_version:
self.add_route(openapi_route, self.schema_response)
self.default_endpoint = None
self.app = ExceptionMiddleware(self.router, debug=debug)
self.add_middleware(GZipMiddleware)
self.static_enabled = False
if self.hsts_enabled:
self.add_middleware(HTTPSRedirectMiddleware)
self.add_middleware(TrustedHostMiddleware, allowed_hosts=self.allowed_hosts)
if self.cors:
self.add_middleware(CORSMiddleware, **self.cors_params)
self.add_middleware(ServerErrorMiddleware, debug=debug)
self.add_middleware(SessionMiddleware, secret_key=self.secret_key)
if openapi or docs_route:
try:
from .ext.openapi import OpenAPISchema
except ImportError as ex:
raise ImportError(
"The dependencies for the OpenAPI extension are not installed. "
"Install them using: pip install responder"
) from ex
self.openapi = OpenAPISchema(
app=self,
title=title,
version=version,
openapi=openapi,
docs_route=docs_route,
description=description,
terms_of_service=terms_of_service,
contact=contact,
license=license,
openapi_route=openapi_route,
static_route=static_route,
openapi_theme=openapi_theme,
)
self.templates = Templates(directory=templates_dir)
@property
def _apispec(self):
spec = APISpec(
title=self.title,
version=self.version,
openapi_version=self.openapi_version,
plugins=[MarshmallowPlugin()],
)
def requests(self):
"""A test client connected to the ASGI app. Lazily initialized."""
return self.session()
for route in self.routes:
if self.routes[route].description:
operations = yaml_utils.load_operations_from_docstring(
self.routes[route].description
@property
def static_app(self):
if not hasattr(self, "_static_app"):
assert self.static_dir is not None
self._static_app = StaticFiles(directory=self.static_dir)
return self._static_app
def before_request(self, websocket=False):
def decorator(f):
self.router.before_request(f, websocket=websocket)
return f
return decorator
def add_middleware(self, middleware_cls, **middleware_config):
self.app = middleware_cls(self.app, **middleware_config)
def exception_handler(self, exception_cls):
"""Register a handler for a specific exception type.
Usage::
@api.exception_handler(ValueError)
async def handle_value_error(req, resp, exc):
resp.status_code = 400
resp.media = {"error": str(exc)}
"""
def decorator(func):
async def _handler(request, exc):
from starlette.responses import Response as StarletteResp
req = Request(request.scope, request.receive, formats=get_formats())
resp = Response(req=req, formats=get_formats())
if asyncio.iscoroutinefunction(func):
await func(req, resp, exc)
else:
func(req, resp, exc)
if resp.status_code is None:
resp.status_code = 500
body, headers = await resp.body
return StarletteResp(
content=body, status_code=resp.status_code, headers=headers
)
spec.add_path(path=route, operations=operations)
for name, schema in self.schemas.items():
spec.definition(name, schema=schema)
# Register with the ExceptionMiddleware
self.router._exception_handlers = getattr(
self.router, "_exception_handlers", {}
)
self.router._exception_handlers[exception_cls] = _handler
# Also register on the ASGI app chain
from starlette.middleware.exceptions import ExceptionMiddleware as EM
return spec
app = self.app
while app is not None:
if isinstance(app, EM):
app.add_exception_handler(exception_cls, _handler)
break
app = getattr(app, "app", None)
return func
@property
def openapi(self):
return self._apispec.to_yaml()
def __call__(self, scope):
path = scope["path"]
root_path = scope.get("root_path", "")
# Call into a submounted app, if one exists.
for path_prefix, app in self.apps.items():
if path.startswith(path_prefix):
scope["path"] = path[len(path_prefix) :]
scope["root_path"] = root_path + path_prefix
try:
return app(scope)
except TypeError:
app = WsgiToAsgi(app)
return app(scope)
# Call the main dispatcher.
async def asgi(receive, send):
nonlocal scope, self
req = models.Request(scope, receive=receive)
resp = await self._dispatch_request(req)
await resp(receive, send)
return asgi
def add_schema(self, name, schema, check_existing=True):
"""Adds a mashmallow schema to the API specification."""
if check_existing:
assert name not in self.schemas
self.schemas[name] = schema
return decorator
def schema(self, name, **options):
"""Decorator for creating new routes around function and class definitions.
"""
Decorator for creating new routes around function and class definitions.
Usage::
from marshmallow import Schema, fields
@api.schema("Pet")
class PetSchema(Schema):
name = fields.Str()
"""
def decorator(f):
self.add_schema(name=name, schema=f, **options)
self.openapi.add_schema(name=name, schema=f, **options)
return f
return decorator
@@ -157,167 +222,112 @@ class API:
"""Given a path portion of a URL, tests that it matches against any registered route.
:param path: The path portion of a URL, to test all known routes against.
"""
for (route, route_object) in self.routes.items():
if route_object.does_match(path):
""" # noqa: E501 (Line too long)
for route in self.router.routes:
match, _ = route.matches(path)
if match:
return route
return None
async def _dispatch_request(self, req):
# Set formats on Request object.
req.formats = self.formats
route = self.path_matches_route(req.url.path)
resp = models.Response(req=req, formats=self.formats)
if self.hsts_enabled:
if req.url.startswith("http://"):
url = req.url.replace("http://", "https://", 1)
self.redirect(resp, location=url)
if route:
try:
params = self.routes[route].incoming_matches(req.url.path)
result = self.routes[route].endpoint(req, resp, **params)
if hasattr(result, "cr_running"):
await result
# The request is using class-based views.
except TypeError as e:
try:
view = self.routes[route].endpoint(**params)
except TypeError:
view = self.routes[route].endpoint
if self.routes[route].is_graphql:
await self.graphql_response(req, resp, schema=view)
else:
# WSGI App.
# try:
# return view(
# environ=req._environ, start_response=req._start_response
# )
# except TypeError:
# pass
pass
# Run on_request first.
try:
r = getattr(view, "on_request")(req, resp)
if hasattr(r, "send"):
await r
except AttributeError:
pass
# Then on_get.
method = req.method
try:
r = getattr(view, f"on_{method}")(req, resp)
if hasattr(r, "send"):
await r
except AttributeError:
pass
else:
self.default_response(req, resp)
return resp
def add_route(self, route, endpoint=None, *, static=True, check_existing=True):
"""Add a route to the API.
def add_route(
self,
route=None,
endpoint=None,
*,
default=False,
static=True,
check_existing=True,
websocket=False,
before_request=False,
methods=None,
):
"""Adds a route to the API.
:param route: A string representation of the route.
:param endpoint: The endpoint for the route -- can be a callable, a class, or graphene schema (GraphQL).
:param static: if True, and no endpoint was passed, render "static/index.html", and forward all undefined routes to this view.
:param check_existing: If ``True``, an AssertionError will be raised, if the route is already defined.
"""
if check_existing:
assert route not in self.routes
:param endpoint: The endpoint for the route -- can be a callable, or a class.
:param default: If ``True``, all unknown requests will route to this view.
:param static: If ``True``, and no endpoint was passed, render "static/index.html".
Also, it will become a default route.
:param methods: Optional list of HTTP methods (e.g. ``["GET", "POST"]``).
""" # noqa: E501
if not endpoint and static:
self.static_enabled = True
endpoint = self.static_response
self.routes[route] = Route(route, endpoint)
# TODO: A better datastructer or sort it once the app is loaded
self.routes = dict(
sorted(self.routes.items(), key=lambda item: item[1]._weight())
if static:
assert self.static_dir is not None
if not endpoint:
endpoint = self._static_response
default = True
self.router.add_route(
route,
endpoint,
default=default,
websocket=websocket,
before_request=before_request,
check_existing=check_existing,
methods=methods,
)
def default_response(self, req, resp):
if self.static_enabled:
self.static_response(req, resp)
async def _static_response(self, req, resp):
assert self.static_dir is not None
index = (self.static_dir / "index.html").resolve()
if index.exists():
resp.html = index.read_text()
else:
resp.status_code = status_codes.HTTP_404
resp.status_code = status_codes.HTTP_404 # type: ignore[attr-defined]
resp.text = "Not found."
def static_response(self, req, resp):
index = (self.static_dir / "index.html").resolve()
if os.path.exists(index):
with open(index, "r") as f:
resp.text = f.read()
def schema_response(self, req, resp):
resp.status_code = status_codes.HTTP_200
resp.headers["Content-Type"] = "application/x-yaml"
resp.content = self.openapi
def redirect(
self, resp, location, *, set_text=True, status_code=status_codes.HTTP_301
self,
resp,
location,
*,
set_text=True,
status_code=status_codes.HTTP_301, # type: ignore[attr-defined]
):
"""Redirects a given response to a given location.
"""
Redirects a given response to a given location.
:param resp: The Response to mutate.
:param location: The location of the redirect.
:param set_text: If ``True``, sets the Redirect body content automatically.
:param status_code: an `API.status_codes` attribute, or an integer, representing the HTTP status code of the redirect.
:param status_code: an `API.status_codes` attribute, or an integer,
representing the HTTP status code of the redirect.
"""
resp.redirect(location, set_text=set_text, status_code=status_code)
def on_event(self, event_type: str, **args):
"""Decorator for registering functions or coroutines to run at certain events
Supported events: startup, shutdown
Usage::
@api.on_event('startup')
async def open_database_connection_pool():
...
@api.on_event('shutdown')
async def close_database_connection_pool():
...
"""
assert resp.status_code.is_300(status_code)
def decorator(func):
self.add_event_handler(event_type, func, **args)
return func
resp.status_code = status_code
if set_text:
resp.text = f"Redirecting to: {location}"
resp.headers.update({"Location": location})
return decorator
@staticmethod
async def _resolve_graphql_query(req):
if "json" in req.mimetype:
return (await req.media("json"))["query"]
def add_event_handler(self, event_type, handler):
"""Adds an event handler to the API.
# Support query/q in form data.
# Form data is awaiting https://github.com/encode/starlette/pull/102
# if "query" in req.media("form"):
# return req.media("form")["query"]
# if "q" in req.media("form"):
# return req.media("form")["q"]
:param event_type: A string in ("startup", "shutdown")
:param handler: The function to run. Can be either a function or a coroutine.
"""
# Support query/q in params.
if "query" in req.params:
return req.params["query"]
if "q" in req.params:
return req.params["q"]
self.router.add_event_handler(event_type, handler)
# Otherwise, the request text is used (typical).
# TODO: Make some assertions about content-type here.
return req.text
async def graphql_response(self, req, resp, schema):
show_graphiql = req.method == "get" and req.accepts("text/html")
if show_graphiql:
resp.content = self.template_string(GRAPHIQL, endpoint=req.url.path)
return
query = await self._resolve_graphql_query(req)
result = schema.execute(query)
result, status_code = encode_execution_results(
[result],
is_batch=False,
format_error=default_format_error,
encode=partial(json_encode, pretty=False),
)
resp.media = json.loads(result)
return (query, result, status_code)
def route(self, route, **options):
def route(self, route=None, **options):
"""Decorator for creating new routes around function and class definitions.
Usage::
@@ -334,105 +344,106 @@ class API:
return decorator
def graphql(self, route="/graphql", *, schema):
"""Mount a GraphQL API at the given route.
Usage::
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))
:param route: The URL path for the GraphQL endpoint.
:param schema: A Graphene schema instance.
"""
from .ext.graphql import GraphQLView
self.add_route(route, GraphQLView(api=self, schema=schema))
def mount(self, route, app):
"""Mounts an WSGI / ASGI application at a given route.
:param route: String representation of the route to be used (shouldn't be parameterized).
:param route: String representation of the route to be used
(shouldn't be parameterized).
:param app: The other WSGI / ASGI app.
"""
self.apps.update({route: app})
self.router.apps.update({route: app})
def session(self, base_url="http://;"):
"""Testing HTTP client. Returns a Requests session object, able to send HTTP requests to the Responder application.
"""Testing HTTP client. Returns a Starlette TestClient instance,
able to send HTTP requests to the Responder application.
:param base_url: The URL to mount the connection adaptor to.
:param base_url: The base URL for the test client.
"""
if self._session is None:
self._session = TestClient(self)
from starlette.testclient import TestClient
self._session = TestClient(self, base_url=base_url)
return self._session
def url_for(self, endpoint, testing=False, **params):
# TODO: Absolute_url
def url_for(self, endpoint, **params):
"""Given an endpoint, returns a rendered URL for its route.
:param view: The route endpoint you're searching for.
:param endpoint: The route endpoint you're searching for.
:param params: Data to pass into the URL generator (for parameterized URLs).
"""
for (route, route_object) in self.routes.items():
if route_object.endpoint == endpoint:
return route_object.url(testing=testing, **params)
raise ValueError
return self.router.url_for(endpoint, **params)
def static_url(self, asset):
"""Given a static asset, return its URL path."""
return f"{self.static_route}/{str(asset)}"
def template(self, filename, *args, **kwargs):
r"""Render a Jinja2 template file with the provided values.
def template(self, name, auto_escape=True, **values):
"""Renders the given `jinja2 <http://jinja.pocoo.org/docs/>`_ template, with provided values supplied.
Note: The current ``api`` instance is always passed into the view.
:param name: The filename of the jinja2 template, in ``templates_dir``.
:param auto_escape: If ``True``, HTML and XML will automatically be escaped.
:param values: Data to pass into the template.
:param filename: The filename of the jinja2 template, in ``templates_dir``.
:param \*args: Data to pass into the template.
:param \*\*kwargs: Data to pass into the template.
"""
# Give reference to self.
values.update(api=self)
return self.templates.render(filename, *args, **kwargs)
env = jinja2.Environment(
loader=jinja2.FileSystemLoader(
[str(self.templates_dir), str(self.built_in_templates_dir)],
followlinks=True,
),
autoescape=jinja2.select_autoescape(["html", "xml"] if auto_escape else []),
)
def template_string(self, source, *args, **kwargs):
r"""Render a Jinja2 template string with the provided values.
template = env.get_template(name)
return template.render(**values)
def template_string(self, s, auto_escape=True, **values):
"""Renders the given `jinja2 <http://jinja.pocoo.org/docs/>`_ template string, with provided values supplied.
Note: The current ``api`` instance is always passed into the view.
:param s: The template to use.
:param auto_escape: If ``True``, HTML and XML will automatically be escaped.
:param values: Data to pass into the template.
:param source: The template to use, a Jinja2 template string.
:param \*args: Data to pass into the template.
:param \*\*kwargs: Data to pass into the template.
"""
# Give reference to self.
values.update(api=self)
return self.templates.render_string(source, *args, **kwargs)
if auto_escape:
env = jinja2.Environment(
loader=jinja2.BaseLoader,
autoescape=jinja2.select_autoescape(["html", "xml"]),
)
else:
env = jinja2.Environment(
loader=jinja2.BaseLoader, autoescape=jinja2.select_autoescape([])
)
def serve(self, *, address=None, port=None, debug=False, **options):
"""
Run the application with uvicorn.
template = env.from_string(s)
return template.render(**values)
def run(self, address=None, port=None, **options):
"""Runs the application with uvicorn. If the ``PORT`` environment
variable is set, requests will be served on that port automatically to all
known hosts.
If the ``PORT`` environment variable is set, requests will be served on that port
automatically to all known hosts.
:param address: The address to bind to.
:param port: The port to bind to. If none is provided, one will be selected at random.
:param debug: Whether to run application in debug mode.
:param options: Additional keyword arguments to send to ``uvicorn.run()``.
"""
""" # noqa: E501
if "PORT" in os.environ:
if address is None:
address = "0.0.0.0"
address = "0.0.0.0" # noqa: S104
port = int(os.environ["PORT"])
if address is None:
address = "127.0.0.1"
if port is None:
port = 5042
if debug:
options["log_level"] = "debug"
uvicorn.run(self, host=address, port=port, **options)
def run(self, **kwargs):
if "debug" not in kwargs:
kwargs.update({"debug": self.debug})
self.serve(**kwargs)
async def __call__(self, scope, receive, send):
await self.app(scope, receive, send)
+19 -4
View File
@@ -1,5 +1,11 @@
import multiprocessing
import asyncio
import concurrent.futures
import multiprocessing
import traceback
from starlette.concurrency import run_in_threadpool
__all__ = ["BackgroundQueue"]
class BackgroundQueue:
@@ -12,16 +18,25 @@ class BackgroundQueue:
self.results = []
def run(self, f, *args, **kwargs):
self.pool._max_workers = self.n
self.pool._adjust_thread_count()
f = self.pool.submit(f, *args, **kwargs)
self.results.append(f)
return f
def task(self, f):
def on_future_done(fs):
try:
fs.result()
except Exception:
traceback.print_exc()
def do_task(*args, **kwargs):
result = self.run(f, *args, **kwargs)
result.add_done_callback(on_future_done)
return result
return do_task
async def __call__(self, func, *args, **kwargs) -> None:
if asyncio.iscoroutinefunction(func):
return await asyncio.create_task(func(*args, **kwargs))
return await run_in_threadpool(func, *args, **kwargs)
+6
View File
@@ -1,2 +1,8 @@
from .api import API
from .models import Request, Response
__all__ = [
"API",
"Request",
"Response",
]
View File
+129
View File
@@ -0,0 +1,129 @@
"""
Responder CLI.
A web framework for Python.
Commands:
run Start the application server
build Build frontend assets using npm
Usage:
responder
responder run [--debug] [--limit-max-requests=] <target>
responder build [<target>]
responder --version
Options:
-h --help Show this screen.
-v --version Show version.
--debug Enable debug mode with verbose logging.
--limit-max-requests=<n> Maximum number of requests to handle before shutting down.
Arguments:
<target> For run: Python module specifier (e.g., "app:api" loads api from app.py)
Format: "module.submodule:variable_name" where variable_name is your API instance
For build: Directory containing package.json (default: current directory)
Examples:
responder run app:api # Run the 'api' instance from app.py
responder run myapp/core.py:application # Run the 'application' instance from myapp/core.py
responder build # Build frontend assets
""" # noqa: E501
import logging
import platform
import subprocess
import sys
import typing as t
from pathlib import Path
import docopt
from responder.__version__ import __version__
from responder.util.python import InvalidTarget, load_target
logger = logging.getLogger(__name__)
def cli() -> None:
"""
Main entry point for the Responder CLI.
Parses command line arguments and executes the appropriate command.
Supports running the application, building assets, and displaying version info.
"""
args = docopt.docopt(__doc__, argv=None, version=__version__, options_first=False)
setup_logging(args["--debug"])
target: t.Optional[str] = args["<target>"]
build: bool = args["build"]
debug: bool = args["--debug"]
run: bool = args["run"]
if build:
target_path = Path(target).resolve() if target else Path.cwd()
if not target_path.is_dir() or not (target_path / "package.json").exists():
logger.error(
f"Invalid target directory or missing package.json: {target_path}"
)
sys.exit(1)
npm_cmd = "npm.cmd" if platform.system() == "Windows" else "npm"
try:
logger.info("Starting frontend asset build")
# S603, S607 are addressed by validating the target directory.
subprocess.check_call( # noqa: S603, S607
[npm_cmd, "run", "build"],
cwd=target_path,
timeout=300,
)
logger.info("Frontend asset build completed successfully")
except FileNotFoundError:
logger.error("npm not found. Please install Node.js and npm.")
sys.exit(1)
except subprocess.CalledProcessError as e:
logger.error(f"Build failed with exit code {e.returncode}")
sys.exit(1)
if run:
if not target:
logger.error("Target argument is required for run command")
sys.exit(1)
# Maximum request limit. Terminating afterward. Suitable for software testing.
limit_max_requests = args["--limit-max-requests"]
if limit_max_requests is not None:
try:
limit_max_requests = int(limit_max_requests)
if limit_max_requests <= 0:
logger.error("limit-max-requests must be a positive integer")
sys.exit(1)
except ValueError:
logger.error("limit-max-requests must be a valid integer")
sys.exit(1)
# Load application from target.
try:
api = load_target(target=target)
except InvalidTarget as ex:
raise ValueError(
f"{ex}. "
"Use either a Python module entrypoint specification, "
"a filesystem path, or a remote URL. "
"See also https://responder.kennethreitz.org/cli.html."
) from ex
# Launch Responder API server (uvicorn).
api.run(debug=debug, limit_max_requests=limit_max_requests)
def setup_logging(debug: bool) -> None:
"""
Configure logging based on debug mode.
Args:
debug: When True, sets logging level to DEBUG; otherwise, sets to INFO
"""
log_level = logging.DEBUG if debug else logging.INFO
logging.basicConfig(
level=log_level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
+66
View File
@@ -0,0 +1,66 @@
import json
from .templates import GRAPHIQL
class GraphQLView:
def __init__(self, *, api, schema):
self.api = api
self.schema = schema
@staticmethod
async def _resolve_graphql_query(req, resp):
if "json" in req.mimetype:
json_media = await req.media("json")
if "query" not in json_media:
resp.status_code = 400
resp.media = {"errors": ["'query' key is required in the JSON payload"]}
return None, None, None
return (
json_media["query"],
json_media.get("variables"),
json_media.get("operationName"),
)
# Support query/q in params.
if "query" in req.params:
return req.params["query"], None, None
if "q" in req.params:
return req.params["q"], None, None
# Otherwise, the request text is used (typical).
return await req.text, None, None
async def graphql_response(self, req, resp):
show_graphiql = req.method == "get" and req.accepts("text/html")
if show_graphiql:
resp.content = self.api.templates.render_string(
GRAPHIQL, endpoint=req.url.path
)
return None
query, variables, operation_name = await self._resolve_graphql_query(req, resp)
if query is None:
return None
context = {"request": req, "response": resp}
result = self.schema.execute(
query, variables=variables, operation_name=operation_name, context=context
)
response_data = {}
if result.errors:
response_data["errors"] = [{"message": str(e)} for e in result.errors]
if result.data is not None:
response_data["data"] = result.data
resp.media = response_data
status_code = 200 if not result.errors else 400
return (query, json.dumps(response_data), status_code)
async def on_request(self, req, resp):
await self.graphql_response(req, resp)
async def __call__(self, req, resp):
await self.on_request(req, resp)
+34
View File
@@ -0,0 +1,34 @@
# ruff: noqa: E501
GRAPHIQL = """
{% set GRAPHIQL_VERSION = '3.0.6' %}
{% set REACT_VERSION = '18.2.0' %}
<!DOCTYPE html>
<html>
<head>
<style>
body {
height: 100%;
margin: 0;
width: 100%;
overflow: hidden;
}
#graphiql {
height: 100vh;
}
</style>
<link href="//cdn.jsdelivr.net/npm/graphiql@{{ GRAPHIQL_VERSION }}/graphiql.min.css" rel="stylesheet"/>
</head>
<body>
<div id="graphiql">Loading...</div>
<script crossorigin src="//cdn.jsdelivr.net/npm/react@{{ REACT_VERSION }}/umd/react.production.min.js"></script>
<script crossorigin src="//cdn.jsdelivr.net/npm/react-dom@{{ REACT_VERSION }}/umd/react-dom.production.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/graphiql@{{ GRAPHIQL_VERSION }}/graphiql.min.js"></script>
<script>
const fetcher = GraphiQL.createFetcher({ url: '{{ endpoint }}' });
const root = ReactDOM.createRoot(document.getElementById('graphiql'));
root.render(React.createElement(GraphiQL, { fetcher: fetcher }));
</script>
</body>
</html>
""".strip()
+138
View File
@@ -0,0 +1,138 @@
from pathlib import Path
from apispec import APISpec, yaml_utils
from apispec.ext.marshmallow import MarshmallowPlugin
from responder import status_codes
from responder.statics import API_THEMES, DEFAULT_OPENAPI_THEME
from responder.templates import Templates
class OpenAPISchema:
def __init__(
self,
app,
title,
version,
plugins=None,
description=None,
terms_of_service=None,
contact=None,
license=None, # noqa: A002
openapi=None,
openapi_route="/schema.yml",
docs_route="/docs/",
static_route="/static",
openapi_theme=DEFAULT_OPENAPI_THEME,
):
self.app = app
self.schemas = {}
self.title = title
self.version = version
self.description = description
self.terms_of_service = terms_of_service
self.contact = contact
self.license = license
self.openapi_version = openapi
self.openapi_route = openapi_route
self.docs_theme = (
openapi_theme if openapi_theme in API_THEMES else DEFAULT_OPENAPI_THEME
)
self.docs_route = docs_route
self.plugins = [MarshmallowPlugin()] if plugins is None else plugins
if self.openapi_version is not None:
self.app.add_route(self.openapi_route, self.schema_response)
if self.docs_route is not None:
self.app.add_route(self.docs_route, self.docs_response)
theme_path = (Path(__file__).parent / "docs").resolve()
self.templates = Templates(directory=theme_path)
self.static_route = static_route
@property
def _apispec(self):
info = {}
if self.description is not None:
info["description"] = self.description
if self.terms_of_service is not None:
info["termsOfService"] = self.terms_of_service
if self.contact is not None:
info["contact"] = self.contact
if self.license is not None:
info["license"] = self.license
spec = APISpec(
title=self.title,
version=self.version,
openapi_version=self.openapi_version,
plugins=self.plugins,
info=info,
)
for route in self.app.router.routes:
if route.description:
operations = yaml_utils.load_operations_from_docstring(route.description)
spec.path(path=route.route, operations=operations)
for name, schema in self.schemas.items():
spec.components.schema(name, schema=schema)
return spec
@property
def openapi(self):
return self._apispec.to_yaml()
def add_schema(self, name, schema, check_existing=True):
"""Adds a marshmallow schema to the API specification."""
if check_existing:
assert name not in self.schemas
self.schemas[name] = schema
def schema(self, name, **options):
"""Decorator for creating new routes around function and class definitions.
Usage::
from marshmallow import Schema, fields
@api.schema("Pet")
class PetSchema(Schema):
name = fields.Str()
"""
def decorator(f):
self.add_schema(name=name, schema=f, **options)
return f
return decorator
@property
def docs(self):
return self.templates.render(
f"{self.docs_theme}.html",
title=self.title,
version=self.version,
schema_url="/schema.yml",
)
def static_url(self, asset):
"""Given a static asset, return its URL path."""
assert self.static_route is not None
return f"{self.static_route}/{str(asset)}"
def docs_response(self, req, resp):
resp.html = self.docs
def schema_response(self, req, resp):
resp.status_code = status_codes.HTTP_200 # type: ignore[attr-defined]
resp.headers["Content-Type"] = "application/x-yaml"
resp.content = self.openapi
+24
View File
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<title>{{ title }} {{ version }}</title>
<!-- Embed elements Elements via Web Component -->
<script src="https://unpkg.com/@stoplight/elements/web-components.min.js"></script>
<link
rel="stylesheet"
href="https://unpkg.com/@stoplight/elements/styles.min.css"
/>
</head>
<body>
<elements-api
apiDescriptionUrl="{{ schema_url }}"
router="hash"
layout="sidebar"
/>
</body>
</html>
+16
View File
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<!-- Important: must specify -->
<html>
<head>
<title>{{ title }} {{ version }}</title>
<meta charset="utf-8" />
<!-- Important: rapi-doc uses utf8 characters -->
<script
type="module"
src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"
></script>
</head>
<body>
<rapi-doc spec-url="{{ schema_url }}" show-header="false"> </rapi-doc>
</body>
</html>
+23
View File
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title>{{ title }} {{ version }}</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700"
rel="stylesheet"
/>
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<redoc spec-url="{{ schema_url }}"></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"></script>
</body>
</html>
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>{{ title }} {{ version }}</title>
<link
rel="stylesheet"
type="text/css"
href="https://unpkg.com/swagger-ui-dist/swagger-ui.css"
/>
<style>
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
background: #fafafa;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-standalone-preset.js"></script>
<script>
window.onload = function () {
const ui = SwaggerUIBundle({
url: "{{ schema_url }}",
dom_id: "#swagger-ui",
deepLinking: true,
presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
plugins: [SwaggerUIBundle.plugins.DownloadUrl],
layout: "BaseLayout",
});
};
</script>
</body>
</html>
+129 -8
View File
@@ -1,27 +1,148 @@
import yaml
from __future__ import annotations
import json
from urllib.parse import urlencode
import yaml
from python_multipart import MultipartParser
from .models import QueryDict
class _PartData:
__slots__ = ("headers", "body", "header_field")
def __init__(self):
self.headers: dict[str, str] = {}
self.body = b""
self.header_field = ""
def _parse_multipart(content: bytes, content_type: str) -> list[_PartData]:
"""Parse multipart form data into a list of parts with headers and body."""
boundary = None
for segment in content_type.split(";"):
segment = segment.strip()
if segment.startswith("boundary="):
boundary = segment.split("=", 1)[1].strip('"')
break
if boundary is None:
return []
parts: list[_PartData] = []
current: list[_PartData | None] = [None]
def on_part_begin():
current[0] = _PartData()
def on_part_data(data, start, end):
current[0].body += data[start:end] # type: ignore[union-attr]
def on_header_field(data, start, end):
current[0].header_field = data[start:end].decode("utf-8") # type: ignore[union-attr]
def on_header_value(data, start, end):
part = current[0]
assert part is not None
part.headers[part.header_field] = data[start:end].decode("utf-8")
def on_part_end():
parts.append(current[0]) # type: ignore[arg-type]
parser = MultipartParser(
boundary.encode(),
{ # type: ignore[arg-type]
"on_part_begin": on_part_begin,
"on_part_data": on_part_data,
"on_header_field": on_header_field,
"on_header_value": on_header_value,
"on_part_end": on_part_end,
},
)
parser.write(content)
parser.finalize()
return parts
async def format_form(r, encode=False):
if not encode:
return await r._starlette.form()
if encode:
return None
if "multipart/form-data" in r.headers.get("Content-Type"):
parts = _parse_multipart(await r.content, r.mimetype)
queries = []
for part in parts:
header = part.headers.get("Content-Disposition", "")
text = part.body.decode("utf-8")
for section in [h.strip() for h in header.split(";")]:
split = section.split("=")
if len(split) > 1:
key = split[1]
key = key[1:-1]
queries.append((key, text))
content = urlencode(queries)
return QueryDict(content)
return QueryDict(await r.text)
async def format_yaml(r, encode=False):
if encode:
r.headers.update({"Content-Type": "application/x-yaml"})
return yaml.safe_dump(r.media)
else:
return yaml.safe_load(await r.content)
return yaml.safe_load(await r.content)
async def format_json(r, encode=False):
if encode:
r.headers.update({"Content-Type": "application/json"})
return json.dumps(r.media)
else:
return json.loads(await r.content)
return json.loads(await r.content)
async def format_files(r, encode=False):
if encode:
return None
parts = _parse_multipart(await r.content, r.mimetype)
dump = {}
for part in parts:
header = part.headers.get("Content-Disposition", "")
mimetype = part.headers.get("Content-Type", None)
filename = None
formname = None
for section in [h.strip() for h in header.split(";")]:
split = section.split("=")
if len(split) > 1:
key = split[0]
value = split[1]
value = value[1:-1]
if key == "filename":
filename = value
elif key == "name":
formname = value
if formname is None:
continue
if mimetype is None:
dump[formname] = part.body
else:
dump[formname] = {
"filename": filename,
"content": part.body,
"content-type": mimetype,
}
return dump
def get_formats():
return {"json": format_json, "yaml": format_yaml, "form": format_form}
return {
"json": format_json,
"yaml": format_yaml,
"form": format_form,
"files": format_files,
}
+264 -91
View File
@@ -1,21 +1,51 @@
import io
import json
import gzip
from __future__ import annotations
import chardet
import rfc3986
import graphene
import yaml
from requests.structures import CaseInsensitiveDict
from starlette.datastructures import MutableHeaders
import functools
import inspect
from collections.abc import Callable
from http.cookies import SimpleCookie
from urllib.parse import parse_qs, urlparse
__all__ = ["Request", "Response", "QueryDict"]
try:
import chardet
except ImportError:
chardet = None # type: ignore[assignment]
from starlette.requests import Request as StarletteRequest
from starlette.responses import Response as StarletteResponse
from starlette.requests import State
from starlette.responses import (
Response as StarletteResponse,
)
from starlette.responses import (
StreamingResponse as StarletteStreamingResponse,
)
from urllib.parse import parse_qs
from .status_codes import HTTP_200
from .statics import DEFAULT_ENCODING
from .status_codes import HTTP_301 # type: ignore[attr-defined]
class CaseInsensitiveDict(dict):
"""A case-insensitive dict for HTTP headers."""
def __setitem__(self, key, value):
super().__setitem__(key.lower(), value)
def __getitem__(self, key):
return super().__getitem__(key.lower())
def __contains__(self, key):
return super().__contains__(key.lower())
def get(self, key, default=None):
return super().get(key.lower(), default)
def update(self, other=None, **kwargs):
if other:
for key, value in other.items():
self[key] = value
for key, value in kwargs.items():
self[key] = value
class QueryDict(dict):
@@ -86,25 +116,35 @@ class QueryDict(dict):
yield from super().items()
# TODO: add slots
class Request:
__slots__ = [
"_starlette",
"formats",
"_headers",
"_encoding",
"api",
"_content",
"_cookies",
]
def __init__(self, scope, receive):
def __init__(self, scope, receive, api=None, formats=None):
self._starlette = StarletteRequest(scope, receive)
self.formats = None
self.formats = formats
self._encoding = None
self.api = api
self._content = None
headers = CaseInsensitiveDict()
for header, value in self._starlette.headers.items():
headers[header] = value
headers: CaseInsensitiveDict = CaseInsensitiveDict()
for key, value in self._starlette.headers.items():
headers[key] = value
self._headers = headers
self._cookies = None
@property
def session(self):
"""The session data, in dict form, from the Request."""
return self._starlette.session
@property
def headers(self):
@@ -115,6 +155,11 @@ class Request:
def mimetype(self):
return self.headers.get("Content-Type", "")
@property
def is_json(self):
"""Returns ``True`` if the request content type is JSON."""
return "json" in self.mimetype
@property
def method(self):
"""The incoming HTTP method used for the request, lower-cased."""
@@ -128,7 +173,22 @@ class Request:
@property
def url(self):
"""The parsed URL of the Request."""
return rfc3986.urlparse(self.full_url)
return urlparse(self.full_url)
@property
def cookies(self):
"""The cookies sent in the Request, as a dictionary."""
if self._cookies is None:
cookies = {}
cookie_header = self.headers.get("Cookie", "")
bc: SimpleCookie = SimpleCookie(cookie_header)
for key, morsel in bc.items():
cookies[key] = morsel.value
self._cookies = cookies
return self._cookies
@property
def params(self):
@@ -138,6 +198,29 @@ class Request:
except AttributeError:
return QueryDict({})
@property
def path_params(self) -> dict:
"""The path parameters extracted from the URL route."""
return self._starlette.path_params
@property
def client(self):
"""The client's address as a (host, port) named tuple, or None."""
return self._starlette.client
@property
def state(self) -> State:
"""
Use the state to store additional information.
This can be a very helpful feature, if you want to hand over
information from a middelware or a route decorator to the
actual route handler.
Usage: ``request.state.time_started = time.time()``
"""
return self._starlette.state
@property
async def encoding(self):
"""The encoding of the Request's body. Can be set, manually. Must be awaited."""
@@ -145,13 +228,7 @@ class Request:
if self._encoding:
return self._encoding
# Then try what's defined by the Request.
elif await self.declared_encoding:
return self.declared_encoding
# Then, automatically detect the encoding.
else:
return await self.apparent_encoding
return await self.apparent_encoding
@encoding.setter
def encoding(self, value):
@@ -160,27 +237,36 @@ class Request:
@property
async def content(self):
"""The Request body, as bytes. Must be awaited."""
return await self._starlette.body()
if not self._content:
self._content = await self._starlette.body()
return self._content
@property
async def text(self):
"""The Request body, as unicode. Must be awaited."""
return (await self._starlette.body()).decode(await self.encoding)
return (await self.content).decode(await self.encoding)
@property
async def declared_encoding(self):
if "Encoding" in self.headers:
return self.headers["Encoding"]
return None
@property
async def apparent_encoding(self):
"""The apparent encoding, provided by the chardet library. Must be awaited."""
"""The apparent encoding, detected automatically. Must be awaited.
Uses chardet for detection if installed, otherwise falls back to UTF-8.
"""
declared_encoding = await self.declared_encoding
if declared_encoding:
return declared_encoding
else:
return chardet.detect(await self.content)["encoding"]
if chardet is not None:
return chardet.detect(await self.content)["encoding"] or DEFAULT_ENCODING
return DEFAULT_ENCODING
@property
def is_secure(self):
@@ -190,59 +276,131 @@ class Request:
"""Returns ``True`` if the incoming Request accepts the given ``content_type``."""
return content_type in self.headers.get("Accept", [])
async def media(self, format=None):
"""Renders incoming json/yaml/form data as Python objects.
async def media(self, format: str | Callable = None): # noqa: A002
"""Renders incoming json/yaml/form data as Python objects. Must be awaited.
:param format: The name of the format being used. Alternatively accepts a custom callable for the format type.
:param format: The name of the format being used.
Alternatively, accepts a custom callable for the format type.
"""
if format is None:
format = "yaml" if "yaml" in self.mimetype or "" else "json"
format = "form" if "form" in self.mimetype or "" else format
format = "yaml" if "yaml" in self.mimetype or "" else "json" # noqa: A001
format = "form" if "form" in self.mimetype or "" else format # noqa: A001
formatter: Callable
if isinstance(format, str):
try:
formatter = self.formats[format]
except KeyError as ex:
raise ValueError(f"Unable to process data in '{format}' format") from ex
elif callable(format):
formatter = format
if format in self.formats:
return await self.formats[format](self)
else:
return await format(self)
raise TypeError(f"Invalid 'format' argument: {format}")
return await formatter(self)
def content_setter(mimetype):
def getter(instance):
return instance.content
def setter(instance, value):
instance.content = value
instance.mimetype = mimetype
return property(fget=getter, fset=setter)
class Response:
__slots__ = [
"req",
"status_code",
"text",
"content",
"encoding",
"media",
"headers",
"formats",
"cookies",
"session",
"mimetype",
"_stream",
]
text = content_setter("text/plain")
html = content_setter("text/html")
def __init__(self, req, *, formats):
self.req = req
self.status_code = HTTP_200 #: The HTTP Status Code to use for the Response.
self.text = None #: A unicode representation of the response body.
#: The HTTP Status Code to use for the Response.
self.status_code: int | None = None
self.content = None #: A bytes representation of the response body.
self.mimetype = None
self.encoding = DEFAULT_ENCODING
self.media = (
None
) #: A Python object that will be content-negotiated and sent back to the client. Typically, in JSON formatting.
self.headers = (
{}
) #: A Python dictionary of {Key: value}, representing the headers of the response.
self.media = None #: A Python object that will be content-negotiated and
#: sent back to the client. Typically, in JSON formatting.
self._stream = None
self.headers = {} #: A Python dictionary of ``{key: value}``,
#: representing the headers of the response.
self.formats = formats
self.cookies: SimpleCookie = SimpleCookie() #: The cookies set in the Response
self.session = (
req.session
) #: The cookie-based session data, in dict form, to add to the Response.
def stream(self, func, *args, **kwargs):
assert inspect.isasyncgenfunction(func)
self._stream = functools.partial(func, *args, **kwargs)
return func
def file(self, path, *, content_type=None):
"""Serve a file from disk as the response.
:param path: Path to the file to serve.
:param content_type: Optional MIME type override.
"""
from pathlib import Path
path = Path(path)
self.content = path.read_bytes()
if content_type:
self.mimetype = content_type
else:
import mimetypes
guessed = mimetypes.guess_type(str(path))[0]
self.mimetype = guessed or "application/octet-stream"
def redirect(self, location, *, set_text=True, status_code=HTTP_301):
self.status_code = status_code
if set_text:
self.text = f"Redirecting to: {location}"
self.headers.update({"Location": location})
@property
async def body(self):
if self.content:
return (self.content, {})
if self._stream is not None:
return (self._stream(), {})
if self.text:
return (self.text.encode(self.encoding), {"Encoding": self.encoding})
if self.content is not None:
headers = {}
content = self.content
if self.mimetype is not None:
headers["Content-Type"] = self.mimetype
if self.mimetype == "text/plain" and self.encoding is not None:
headers["Encoding"] = self.encoding
if isinstance(content, str):
content = content.encode(self.encoding)
return (content, headers)
for format in self.formats:
if self.req.accepts(format):
return (await self.formats[format](self, encode=True)), {}
for format_ in self.formats:
if self.req.accepts(format_):
return (await self.formats[format_](self, encode=True)), {}
# Default to JSON anyway.
return (
@@ -250,44 +408,59 @@ class Response:
{"Content-Type": "application/json"},
)
@property
async def gzipped_body(self):
def set_cookie(
self,
key,
value="",
expires=None,
path="/",
domain=None,
max_age=None,
secure=False,
httponly=True,
):
self.cookies[key] = value
morsel = self.cookies[key]
if expires is not None:
morsel["expires"] = expires
if path is not None:
morsel["path"] = path
if domain is not None:
morsel["domain"] = domain
if max_age is not None:
morsel["max-age"] = max_age
morsel["secure"] = secure
morsel["httponly"] = httponly
def _prepare_cookies(self, starlette_response):
cookie_header = (
(b"set-cookie", morsel.output(header="").lstrip().encode("latin-1"))
for morsel in self.cookies.values()
)
starlette_response.raw_headers.extend(cookie_header)
async def __call__(self, scope, receive, send):
body, headers = await self.body
if isinstance(body, str):
body = body.encode(self.encoding)
if "gzip" in self.req.headers["Accept-Encoding"].lower():
gzip_buffer = io.BytesIO()
gzip_file = gzip.GzipFile(mode="wb", fileobj=gzip_buffer)
gzip_file.write(body)
gzip_file.close()
new_headers = {
"Content-Encoding": "gzip",
"Vary": "Accept-Encoding",
"Content-Length": str(len(body)),
}
headers.update(new_headers)
return (gzip_buffer.getvalue(), headers)
else:
return (body, headers)
async def __call__(self, receive, send):
body, headers = await self.body
if len(await self.body) > 500:
body, headers = await self.gzipped_body
if self.headers:
headers.update(self.headers)
response = StarletteResponse(
body, status_code=self.status_code, headers=headers
)
await response(receive, send)
response_cls: type[StarletteResponse] | type[StarletteStreamingResponse]
if self._stream is not None:
response_cls = StarletteStreamingResponse
else:
response_cls = StarletteResponse
response = response_cls(body, status_code=self.status_code_safe, headers=headers)
self._prepare_cookies(response)
class Schema(graphene.Schema):
def on_request(self, req, resp):
pass
await response(scope, receive, send)
@property
def ok(self):
return 200 <= self.status_code_safe < 300
@property
def status_code_safe(self) -> int:
if self.status_code is None:
raise RuntimeError("HTTP status code has not been defined")
return self.status_code
View File
+369 -41
View File
@@ -1,66 +1,394 @@
import asyncio
import inspect
import re
from parse import parse, search
import traceback
from collections import defaultdict
__all__ = ["Route", "WebSocketRoute", "Router"]
from starlette.concurrency import run_in_threadpool
from starlette.exceptions import HTTPException
from starlette.types import ASGIApp
from starlette.websockets import WebSocket, WebSocketClose
from . import status_codes
from .formats import get_formats
from .models import Request, Response
_UUID_RE = r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
_CONVERTORS = {
"int": (int, r"\d+"),
"str": (str, r"[^/]+"),
"float": (float, r"\d+(.\d+)?"),
"path": (str, r".+"),
"uuid": (str, _UUID_RE),
}
PARAM_RE = re.compile("{([a-zA-Z_][a-zA-Z0-9_]*)(:[a-zA-Z_][a-zA-Z0-9_]*)?}")
def memoize(f):
def helper(self, s):
memoize_key = f"{f.__name__}:{s}"
if memoize_key not in self._memo:
self._memo[memoize_key] = f(self, s)
return self._memo[memoize_key]
def compile_path(path):
path_re = "^"
param_convertors = {}
idx = 0
return helper
for match in PARAM_RE.finditer(path):
param_name, convertor_type = match.groups(default="str")
convertor_type = convertor_type.lstrip(":")
assert convertor_type in _CONVERTORS.keys(), (
f"Unknown path convertor '{convertor_type}'"
)
convertor, convertor_re = _CONVERTORS[convertor_type]
path_re += path[idx : match.start()]
path_re += rf"(?P<{param_name}>{convertor_re})"
param_convertors[param_name] = convertor
idx = match.end()
path_re += path[idx:] + "$"
return re.compile(path_re), param_convertors
class Route:
def __init__(self, route, endpoint):
class BaseRoute:
def matches(self, scope):
raise NotImplementedError()
async def __call__(self, scope, receive, send):
raise NotImplementedError()
class Route(BaseRoute):
def __init__(self, route, endpoint, *, before_request=False, methods=None):
assert route.startswith("/"), "Route path must start with '/'"
self.route = route
self.endpoint = endpoint
self._memo = {}
self.before_request = before_request
self.methods = {m.upper() for m in methods} if methods else None
self.path_re, self.param_convertors = compile_path(route)
# Strip type annotations for URL generation (e.g. {id:int} -> {id})
self._url_template = PARAM_RE.sub(r"{\1}", route)
def __repr__(self):
return f"<Route {self.route!r}={self.endpoint!r}>"
def __eq__(self, other):
if hasattr(other, "route"):
# Being compared to other routes.
return self.route == other.route
else:
# Strings.
return self.does_match(other)
def url(self, **params):
return self._url_template.format(**params)
@property
def endpoint_name(self):
return self.endpoint.__name__
@property
def description(self):
return self.endpoint.__doc__
@property
def has_parameters(self):
return all([("{" in self.route), ("}" in self.route)])
def matches(self, scope):
if scope["type"] != "http":
return False, {}
@memoize
def does_match(self, s):
if s == self.route:
return True
if self.methods and scope.get("method", "").upper() not in self.methods:
return False, {}
named = self.incoming_matches(s)
return bool(len(named))
path = scope["path"]
match = self.path_re.match(path)
@memoize
def incoming_matches(self, s):
results = parse(self.route, s)
return results.named if results else {}
if match is None:
return False, {}
def url(self, testing=False, **params):
url = self.route.format(**params)
if testing:
url = f"http://;{url}"
matched_params = match.groupdict()
for key, value in matched_params.items():
matched_params[key] = self.param_convertors[key](value)
return url
return True, {"path_params": {**matched_params}}
def _weight(self):
params_count = -len(set(re.findall(r"{([a-zA-Z]\w*)}", self.route)))
return params_count != 0, params_count
async def __call__(self, scope, receive, send):
request = Request(scope, receive, formats=get_formats())
response = Response(req=request, formats=get_formats())
path_params = scope.get("path_params", {})
before_requests = scope.get("before_requests", [])
for before_request in before_requests.get("http", []):
if asyncio.iscoroutinefunction(before_request):
await before_request(request, response)
else:
await run_in_threadpool(before_request, request, response)
# If a before_request hook set a status code, short-circuit
if response.status_code is not None:
await response(scope, receive, send)
return
views = []
if inspect.isclass(self.endpoint):
endpoint = self.endpoint()
on_request = getattr(endpoint, "on_request", None)
if on_request:
views.append(on_request)
method_name = f"on_{request.method}"
try:
view = getattr(endpoint, method_name)
views.append(view)
except AttributeError as ex:
if on_request is None:
raise HTTPException(status_code=status_codes.HTTP_405) from ex # type: ignore[attr-defined]
else:
views.append(self.endpoint)
for view in views:
# Check __call__ for class-based views (e.g. GraphQL)
if asyncio.iscoroutinefunction(view) or asyncio.iscoroutinefunction(
view.__call__
):
await view(request, response, **path_params)
else:
await run_in_threadpool(view, request, response, **path_params)
if response.status_code is None:
response.status_code = status_codes.HTTP_200 # type: ignore[attr-defined]
await response(scope, receive, send)
def __eq__(self, other):
return self.route == other.route and self.endpoint == other.endpoint
def __hash__(self):
return hash(self.route) ^ hash(self.endpoint) ^ hash(self.before_request)
class WebSocketRoute(BaseRoute):
def __init__(self, route, endpoint, *, before_request=False):
assert route.startswith("/"), "Route path must start with '/'"
self.route = route
self.endpoint = endpoint
self.before_request = before_request
self.path_re, self.param_convertors = compile_path(route)
self._url_template = PARAM_RE.sub(r"{\1}", route)
def __repr__(self):
return f"<Route {self.route!r}={self.endpoint!r}>"
def url(self, **params):
return self._url_template.format(**params)
@property
def is_graphql(self):
return hasattr(self.endpoint, "get_graphql_type")
def endpoint_name(self):
return self.endpoint.__name__
@property
def description(self):
return self.endpoint.__doc__
def matches(self, scope):
if scope["type"] != "websocket":
return False, {}
path = scope["path"]
match = self.path_re.match(path)
if match is None:
return False, {}
matched_params = match.groupdict()
for key, value in matched_params.items():
matched_params[key] = self.param_convertors[key](value)
return True, {"path_params": {**matched_params}}
async def __call__(self, scope, receive, send):
ws = WebSocket(scope, receive, send)
before_requests = scope.get("before_requests", [])
for before_request in before_requests.get("ws", []):
await before_request(ws)
await self.endpoint(ws)
def __eq__(self, other):
return self.route == other.route and self.endpoint == other.endpoint
def __hash__(self):
return hash(self.route) ^ hash(self.endpoint) ^ hash(self.before_request)
class Router:
def __init__(
self, routes=None, default_response=None, before_requests=None, lifespan=None
):
self.routes = [] if routes is None else list(routes)
self.apps: dict[str, ASGIApp] = {}
self.default_endpoint = (
self.default_response if default_response is None else default_response
)
self.before_requests = (
{"http": [], "ws": []} if before_requests is None else before_requests
)
self.events = defaultdict(list)
self._lifespan_handler = lifespan
def add_route(
self,
route=None,
endpoint=None,
*,
default=False,
websocket=False,
before_request=False,
check_existing=False,
methods=None,
):
"""Adds a route to the router.
:param route: A string representation of the route
:param endpoint: The endpoint for the route -- can be callable, or class.
:param default: If ``True``, all unknown requests will route to this view.
:param methods: Optional list of HTTP methods (e.g. ["GET", "POST"]).
"""
if before_request:
if websocket:
self.before_requests.setdefault("ws", []).append(endpoint)
else:
self.before_requests.setdefault("http", []).append(endpoint)
return
if check_existing:
assert not self.routes or route not in (item.route for item in self.routes), (
f"Route '{route}' already exists"
)
if default:
self.default_endpoint = endpoint
if websocket:
route = WebSocketRoute(route, endpoint)
else:
route = Route(route, endpoint, methods=methods)
self.routes.append(route)
def mount(self, route, app):
"""Mounts ASGI / WSGI applications at a given route"""
self.apps.update({route: app})
def add_event_handler(self, event_type, handler):
assert event_type in (
"startup",
"shutdown",
), f"Only 'startup' and 'shutdown' events are supported, not {event_type}."
self.events[event_type].append(handler)
async def trigger_event(self, event_type):
for handler in self.events.get(event_type, []):
if asyncio.iscoroutinefunction(handler):
await handler()
else:
handler()
def before_request(self, endpoint, websocket=False):
if websocket:
self.before_requests.setdefault("ws", []).append(endpoint)
else:
self.before_requests.setdefault("http", []).append(endpoint)
def url_for(self, endpoint, **params):
for route in self.routes:
if endpoint in (route.endpoint, route.endpoint.__name__):
return route.url(**params)
return None
async def default_response(self, scope, receive, send):
if scope["type"] == "websocket":
websocket_close = WebSocketClose()
await websocket_close(scope, receive, send)
return
request = Request(scope, receive)
response = Response(request, formats=get_formats()) # noqa: F841
raise HTTPException(status_code=status_codes.HTTP_404) # type: ignore[attr-defined]
def _resolve_route(self, scope):
for route in self.routes:
matches, child_scope = route.matches(scope)
if matches:
scope.update(child_scope)
return route
return None
async def lifespan(self, scope, receive, send):
message = await receive()
assert message["type"] == "lifespan.startup"
if self._lifespan_handler is not None:
# Modern lifespan context manager pattern
try:
ctx = self._lifespan_handler(scope.get("app"))
await ctx.__aenter__()
except BaseException:
msg = traceback.format_exc()
await send({"type": "lifespan.startup.failed", "message": msg})
raise
await send({"type": "lifespan.startup.complete"})
message = await receive()
assert message["type"] == "lifespan.shutdown"
await ctx.__aexit__(None, None, None)
else:
# Legacy on_event("startup") / on_event("shutdown") pattern
try:
await self.trigger_event("startup")
except BaseException:
msg = traceback.format_exc()
await send({"type": "lifespan.startup.failed", "message": msg})
raise
await send({"type": "lifespan.startup.complete"})
message = await receive()
assert message["type"] == "lifespan.shutdown"
await self.trigger_event("shutdown")
await send({"type": "lifespan.shutdown.complete"})
async def __call__(self, scope, receive, send):
assert scope["type"] in ("http", "websocket", "lifespan")
if scope["type"] == "lifespan":
await self.lifespan(scope, receive, send)
return
path = scope["path"]
root_path = scope.get("root_path", "")
# Check "primary" mounted routes first (before submounted apps)
route = self._resolve_route(scope)
scope["before_requests"] = self.before_requests
if route is not None:
await route(scope, receive, send)
return
# Call into a submounted app, if one exists.
for path_prefix, app in self.apps.items():
if path.startswith(path_prefix):
scope["path"] = path[len(path_prefix) :]
scope["root_path"] = root_path + path_prefix
try:
await app(scope, receive, send)
return
except TypeError:
from a2wsgi import WSGIMiddleware
app = WSGIMiddleware(app)
await app(scope, receive, send)
return
await self.default_endpoint(scope, receive, send)
+8
View File
@@ -0,0 +1,8 @@
from starlette.staticfiles import StaticFiles as StarletteStaticFiles
class StaticFiles(StarletteStaticFiles):
"""Extension to Starlette's StaticFiles with support for multiple directories."""
def add_directory(self, directory: str) -> None:
self.all_directories = [*self.all_directories, *self.get_directories(directory)]
+14
View File
@@ -1 +1,15 @@
API_THEMES = ["elements", "rapidoc", "redoc", "swagger_ui"]
DEFAULT_ENCODING = "utf-8"
DEFAULT_OPENAPI_THEME = "swagger_ui"
DEFAULT_SESSION_COOKIE = "Responder-Session"
DEFAULT_SECRET_KEY = "NOTASECRET" # noqa: S105
DEFAULT_CORS_PARAMS = {
"allow_origins": (),
"allow_methods": ("GET",),
"allow_headers": (),
"allow_credentials": False,
"allow_origin_regex": None,
"expose_headers": (),
"max_age": 600,
}
+1 -7
View File
@@ -1,5 +1,3 @@
# from: https://github.com/requests/requests/blob/master/requests/status_codes.py
codes = {
# Informational.
100: ("continue",),
@@ -26,11 +24,7 @@ codes = {
305: ("use_proxy",),
306: ("switch_proxy",),
307: ("temporary_redirect", "temporary_moved", "temporary"),
308: (
"permanent_redirect",
"resume_incomplete",
"resume",
), # These 2 to be removed in 3.0
308: ("permanent_redirect",),
# Client Error.
400: ("bad_request", "bad"),
401: ("unauthorized",),
+61
View File
@@ -0,0 +1,61 @@
from contextlib import contextmanager
import jinja2
__all__ = ["Templates"]
class Templates:
def __init__(
self, directory="templates", autoescape=True, context=None, enable_async=False
):
self.directory = directory
self._env = jinja2.Environment(
loader=jinja2.FileSystemLoader([str(self.directory)]),
autoescape=autoescape, # noqa: S701
enable_async=enable_async,
)
self.default_context = {} if context is None else {**context}
self._env.globals.update(self.default_context)
@property
def context(self):
return self._env.globals
@context.setter
def context(self, context):
self._env.globals = {**self.default_context, **context}
def get_template(self, name):
return self._env.get_template(name)
def render(self, template, *args, **kwargs):
"""Renders the given `jinja2 <http://jinja.pocoo.org/docs/>`_ template, with provided values supplied.
:param template: The filename of the jinja2 template.
:param **kwargs: Data to pass into the template.
:param **kwargs: Data to pass into the template.
""" # noqa: E501
return self.get_template(template).render(*args, **kwargs)
@contextmanager
def _async(self):
self._env.is_async = True
try:
yield
finally:
self._env.is_async = False
async def render_async(self, template, *args, **kwargs):
with self._async():
return await self.get_template(template).render_async(*args, **kwargs)
def render_string(self, source, *args, **kwargs):
"""Renders the given `jinja2 <http://jinja.pocoo.org/docs/>`_ template string, with provided values supplied.
:param source: The template to use.
:param *args, **kwargs: Data to pass into the template.
:param **kwargs: Data to pass into the template.
""" # noqa: E501
template = self._env.from_string(source)
return template.render(*args, **kwargs)
-145
View File
@@ -1,145 +0,0 @@
GRAPHIQL = """
{% set GRAPHIQL_VERSION = '0.12.0' %}
<!--
* Copyright (c) Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
-->
<!DOCTYPE html>
<html>
<head>
<style>
body {
height: 100%;
margin: 0;
width: 100%;
overflow: hidden;
}
#graphiql {
height: 100vh;
}
</style>
<!--
This GraphiQL example depends on Promise and fetch, which are available in
modern browsers, but can be "polyfilled" for older browsers.
GraphiQL itself depends on React DOM.
If you do not want to rely on a CDN, you can host these files locally or
include them directly in your favored resource bunder.
-->
<link href="//cdn.jsdelivr.net/npm/graphiql@{{ GRAPHIQL_VERSION }}/graphiql.css" rel="stylesheet"/>
<script src="//cdn.jsdelivr.net/npm/whatwg-fetch@2.0.3/fetch.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/react@16.2.0/umd/react.production.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/react-dom@16.2.0/umd/react-dom.production.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/graphiql@{{ GRAPHIQL_VERSION }}/graphiql.min.js"></script>
</head>
<body>
<div id="graphiql">Loading...</div>
<script>
/**
* This GraphiQL example illustrates how to use some of GraphiQL's props
* in order to enable reading and updating the URL parameters, making
* link sharing of queries a little bit easier.
*
* This is only one example of this kind of feature, GraphiQL exposes
* various React params to enable interesting integrations.
*/
// Parse the search string to get url parameters.
var search = window.location.search;
var parameters = {};
search.substr(1).split('&').forEach(function (entry) {
var eq = entry.indexOf('=');
if (eq >= 0) {
parameters[decodeURIComponent(entry.slice(0, eq))] =
decodeURIComponent(entry.slice(eq + 1));
}
});
// if variables was provided, try to format it.
if (parameters.variables) {
try {
parameters.variables =
JSON.stringify(JSON.parse(parameters.variables), null, 2);
} catch (e) {
// Do nothing, we want to display the invalid JSON as a string, rather
// than present an error.
}
}
// When the query and variables string is edited, update the URL bar so
// that it can be easily shared
function onEditQuery(newQuery) {
parameters.query = newQuery;
updateURL();
}
function onEditVariables(newVariables) {
parameters.variables = newVariables;
updateURL();
}
function onEditOperationName(newOperationName) {
parameters.operationName = newOperationName;
updateURL();
}
function updateURL() {
var newSearch = '?' + Object.keys(parameters).filter(function (key) {
return Boolean(parameters[key]);
}).map(function (key) {
return encodeURIComponent(key) + '=' +
encodeURIComponent(parameters[key]);
}).join('&');
history.replaceState(null, null, newSearch);
}
// Defines a GraphQL fetcher using the fetch API. You're not required to
// use fetch, and could instead implement graphQLFetcher however you like,
// as long as it returns a Promise or Observable.
function graphQLFetcher(graphQLParams) {
// This example expects a GraphQL server at the path /graphql.
// Change this to point wherever you host your GraphQL server.
return fetch('{{ endpoint }}', {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(graphQLParams),
credentials: 'include',
}).then(function (response) {
return response.text();
}).then(function (responseBody) {
try {
return JSON.parse(responseBody);
} catch (error) {
return responseBody;
}
});
}
// Render <GraphiQL /> into the body.
// See the README in the top level of this module to learn more about
// how you can customize GraphiQL by providing different values or
// additional child elements.
ReactDOM.render(
React.createElement(GraphiQL, {
fetcher: graphQLFetcher,
query: parameters.query,
variables: parameters.variables,
operationName: parameters.operationName,
onEditQuery: onEditQuery,
onEditVariables: onEditVariables,
onEditOperationName: onEditOperationName
}),
document.getElementById('graphiql')
);
</script>
</body>
</html>
""".strip()
View File
+242
View File
@@ -0,0 +1,242 @@
# ruff: noqa: S603 # Subprocess call - output not captured
# ruff: noqa: S607 # Starting a process with a partial executable path
# Security considerations for subprocess usage:
# 1. Only execute the 'responder' binary from PATH
# 2. Validate all user inputs before passing to subprocess
# 3. Use Path.resolve() to prevent path traversal
import functools
import logging
import os
import shutil
import signal
import socket
import subprocess
import sys
import threading
import time
from pathlib import Path
logger = logging.getLogger(__name__)
class ResponderProgram:
"""
Utility class for managing Responder program execution.
This class provides methods for:
- Locating the responder executable in PATH
- Building frontend assets using npm
Example:
>>> program_path = ResponderProgram.path()
>>> build_status = ResponderProgram.build(Path("app_dir"))
"""
@staticmethod
@functools.lru_cache(maxsize=None)
def path():
name = "responder"
if sys.platform == "win32":
name = "responder.exe"
program = shutil.which(name)
if program is None:
paths = os.environ.get("PATH", "").split(os.pathsep)
raise RuntimeError(
f"Could not find '{name}' executable in PATH. "
f"Please install Responder with 'pip install --upgrade responder'. "
f"Searched in: {', '.join(paths)}"
)
logger.debug(f"Found responder program: {program}")
return program
@classmethod
def build(cls, path: Path) -> int:
"""
Invoke `responder build` command.
Args:
path: Path to the application to build
Returns:
int: The return code from the build process
Raises:
ValueError: If the path is invalid
RuntimeError: If the responder executable is not found
subprocess.SubprocessError: If the build process fails
"""
if not isinstance(path, Path):
raise ValueError(f"Expected a Path object, got {type(path).__name__}")
if not path.exists():
raise ValueError(f"Path does not exist: {path}")
if not path.is_dir():
raise FileNotFoundError(f"Path is not a directory: {path}")
command = [
cls.path(),
"build",
str(path),
]
return subprocess.call(command)
class ResponderServer(threading.Thread):
"""
A threaded wrapper around the `responder run` command for testing purposes.
This class allows running a Responder application in a separate thread,
making it suitable for integration testing scenarios.
Args:
target (str): The path to the Responder application to run
port (int, optional): The port to run the server on. Defaults to 5042.
limit_max_requests (int, optional): Maximum number of requests to handle
before shutting down. Useful for testing scenarios.
Example:
>>> server = ResponderServer("app.py", port=8000)
>>> server.start()
>>> # Run tests
>>> server.stop()
"""
def __init__(self, target: str, port: int = 5042, limit_max_requests: int = None):
super().__init__()
self._stopping = False
# Validate input variables.
if not target or not isinstance(target, str):
raise ValueError("Target must be a non-empty string")
if not isinstance(port, int) or port < 1:
raise ValueError("Port must be a positive integer")
if limit_max_requests is not None and (
not isinstance(limit_max_requests, int) or limit_max_requests < 1
):
raise ValueError("limit_max_requests must be a positive integer if specified")
# Check if port is available.
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(("localhost", port))
except OSError as ex:
raise ValueError(f"Port {port} is already in use") from ex
# Instance variables after validation.
self.target = target
self.port = port
self.limit_max_requests = limit_max_requests
self.shutdown_timeout = 5 # seconds
# Allow the thread to be terminated when the main program exits.
self.process: subprocess.Popen
self.daemon = True
self._process_lock = threading.Lock()
# Setup signal handlers.
signal.signal(signal.SIGTERM, self._signal_handler)
signal.signal(signal.SIGINT, self._signal_handler)
def run(self):
command = [
ResponderProgram.path(),
"run",
self.target,
]
if self.limit_max_requests is not None:
command += [f"--limit-max-requests={self.limit_max_requests}"]
# Preserve existing environment
env = os.environ.copy()
if self.port is not None:
env["PORT"] = str(self.port)
with self._process_lock:
self.process = subprocess.Popen(
command,
env=env,
universal_newlines=True,
)
self.process.wait()
def stop(self):
"""
Gracefully stop the process (API).
"""
if self._stopping:
return
with self._process_lock:
self._stop()
def _stop(self):
"""
Gracefully stop the process (impl).
"""
self._stopping = True
if self.process and self.process.poll() is None:
logger.info("Attempting to terminate server process...")
self.process.terminate()
try:
# Wait for graceful shutdown.
self.process.wait(timeout=self.shutdown_timeout)
logger.info("Server process terminated gracefully")
except subprocess.TimeoutExpired:
logger.warning(
"Server process did not terminate gracefully, forcing kill"
)
self.process.kill() # Force kill if not terminated
def _signal_handler(self, signum, frame):
"""
Handle termination signals gracefully.
"""
logger.info("Received signal %d, shutting down...", signum)
self.stop()
def wait_until_ready(self, timeout=30, request_timeout=1, delay=0.1) -> bool:
"""
Wait until the server is ready to accept connections.
Args:
timeout (int, optional): Maximum time to wait in seconds. Defaults to 30.
Returns:
bool: True if server is ready and accepting connections, False otherwise.
"""
start_time = time.time()
last_error = None
while time.time() - start_time < timeout:
if not self.is_running():
if self.process is None:
logger.error("Server process was never started")
else:
returncode = self.process.poll()
logger.error("Server process exited with code: %d", returncode)
return False
try:
with socket.create_connection(
("localhost", self.port), timeout=request_timeout
):
return True
except (
socket.timeout,
ConnectionRefusedError,
socket.gaierror,
OSError,
) as ex:
last_error = ex
logger.debug(f"Server not ready yet: {ex}")
time.sleep(delay)
logger.error(
"Server failed to start within %d seconds. Last error: %s",
timeout,
last_error,
)
return False
def is_running(self):
"""
Check if the server process is still running.
"""
return self.process is not None and self.process.poll() is None
+44
View File
@@ -0,0 +1,44 @@
import logging
import typing as t
from pueblo.sfa.core import InvalidTarget, SingleFileApplication
__all__ = [
"InvalidTarget",
"SingleFileApplication",
"load_target",
]
logger = logging.getLogger(__name__)
def load_target(target: str, default_property: str = "api", method: str = "run") -> t.Any:
"""
Load Python code from a file path or module name.
Warning:
This function executes arbitrary Python code. Ensure the target is from a trusted
source to prevent security vulnerabilities.
Args:
target: Module address (e.g., 'acme.app:foo'), file path (e.g., '/path/to/acme/app.py'),
or URL.
default_property: Name of the property to load if not specified in target (default: "api")
method: Name of the method to invoke on the API instance (default: "run")
Returns:
The API instance, loaded from the given property.
Raises:
ValueError: If target format is invalid
ImportError: If module cannot be imported
AttributeError: If property or method is not found
Example:
>>> api = load_target("myapp.api:server")
>>> api.run()
""" # noqa: E501
app = SingleFileApplication.from_spec(spec=target, default_property=default_property)
app.load()
return app.entrypoint
-154
View File
@@ -1,154 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import codecs
import os
import sys
from shutil import rmtree
from setuptools import find_packages, setup, Command
here = os.path.abspath(os.path.dirname(__file__))
with codecs.open(os.path.join(here, "README.md"), encoding="utf-8") as f:
long_description = "\n" + f.read()
about = {}
with open(os.path.join(here, "responder", "__version__.py")) as f:
exec(f.read(), about)
if sys.argv[-1] == "publish":
os.system("python setup.py sdist bdist_wheel upload")
sys.exit()
required = [
"starlette",
"uvicorn",
"aiofiles",
"pyyaml",
"requests",
"graphene",
"graphql-server-core>=1.1",
"jinja2",
"parse",
"uvloop ; sys_platform != 'win32'",
"rfc3986",
"python-multipart",
"chardet",
"apispec>=1.0.0b1",
"marshmallow",
"asgiref",
]
# https://pypi.python.org/pypi/stdeb/0.8.5#quickstart-2-just-tell-me-the-fastest-way-to-make-a-deb
class DebCommand(Command):
"""Support for setup.py deb"""
description = "Build and publish the .deb package."
user_options = []
@staticmethod
def status(s):
"""Prints things in bold."""
print("\033[1m{0}\033[0m".format(s))
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
try:
self.status("Removing previous builds…")
rmtree(os.path.join(here, "deb_dist"))
except FileNotFoundError:
pass
self.status(u"Creating debian mainfest…")
os.system(
"python setup.py --command-packages=stdeb.command sdist_dsc -z artful --package3=pipenv --depends3=python3-virtualenv-clone"
)
self.status(u"Building .deb…")
os.chdir("deb_dist/pipenv-{0}".format(about["__version__"]))
os.system("dpkg-buildpackage -rfakeroot -uc -us")
class UploadCommand(Command):
"""Support setup.py publish."""
description = "Build and publish the package."
user_options = []
@staticmethod
def status(s):
"""Prints things in bold."""
print("\033[1m{0}\033[0m".format(s))
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
try:
self.status("Removing previous builds…")
rmtree(os.path.join(here, "dist"))
except FileNotFoundError:
pass
self.status("Building Source distribution…")
os.system("{0} setup.py sdist bdist_wheel".format(sys.executable))
self.status("Uploading the package to PyPI via Twine…")
os.system("twine upload dist/*")
self.status("Pushing git tags…")
os.system("git tag v{0}".format(about["__version__"]))
os.system("git push --tags")
sys.exit()
setup(
name="responder",
version=about["__version__"],
description="A sorta familiar HTTP framework.",
long_description=long_description,
long_description_content_type="text/markdown",
author="Kenneth Reitz",
author_email="me@kennethreitz.org",
url="https://github.com/kennethreitz/responder",
packages=find_packages(exclude=["tests"]),
# entry_points={
# "console_scripts": ["responder=responder:cli"]
# },
package_data={
# "": ["LICENSE", "NOTICES"],
# "pipenv.vendor.requests": ["*.pem"],
# "pipenv.vendor.certifi": ["*.pem"],
# "pipenv.vendor.click_completion": ["*.j2"],
# "pipenv.patched.notpip._vendor.certifi": ["*.pem"],
# "pipenv.patched.notpip._vendor.requests": ["*.pem"],
# "pipenv.patched.notpip._vendor.distlib._backport": ["sysconfig.cfg"],
# "pipenv.patched.notpip._vendor.distlib": [
# "t32.exe",
# "t64.exe",
# "w32.exe",
# "w64.exe",
# ],
},
python_requires=">=3.6",
setup_requires=[],
install_requires=required,
extras_require={},
include_package_data=True,
license="Apache 2.0",
classifiers=[
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
],
cmdclass={"upload": UploadCommand, "deb": DebCommand},
)

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