mirror of
https://github.com/kennethreitz/responder.git
synced 2026-06-05 23:00:17 +00:00
Compare commits
96 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cc0fe78382 | |||
| 413028b636 | |||
| 3edf979a8c | |||
| cd75deeb4e | |||
| b71bb5ddb9 | |||
| 27a9459f22 | |||
| b39c539d57 | |||
| 718b53cce2 | |||
| 2e0b4975f7 | |||
| a118a5dc4b | |||
| 69c1d7f185 | |||
| fba2f135a3 | |||
| 4006de72cd | |||
| b3c7252197 | |||
| 398ac3343e | |||
| 8b197ba361 | |||
| e700aa2937 | |||
| 3894550642 | |||
| 43fd041138 | |||
| 363af5338d | |||
| 55430a4366 | |||
| f7c6a3ae97 | |||
| dcadba1425 | |||
| de08b15ae8 | |||
| 0cfca6d906 | |||
| a73e413a66 | |||
| 87931a25d0 | |||
| 1fd9a682dd | |||
| 5d3e650901 | |||
| 48d082e6a5 | |||
| 87e22481e8 | |||
| e48ce6c301 | |||
| e9613500da | |||
| c2943accd0 | |||
| 649a255657 | |||
| 7eaaaaafe1 | |||
| ae09b88978 | |||
| e3e307fd68 | |||
| 89f0724029 | |||
| bebe62adaf | |||
| eb9cddc8c2 | |||
| 7c19eca78a | |||
| ed28b11d21 | |||
| 46cdd4a245 | |||
| ac91b172e6 | |||
| ed0da6d462 | |||
| 555e9bff65 | |||
| bf43d9f202 | |||
| e239cc304d | |||
| 3285bd57c7 | |||
| 3090fb9e68 | |||
| e90bd24ebe | |||
| a0acc03a97 | |||
| 8a668e6efe | |||
| 4c75742e4d | |||
| 796fdc2ddf | |||
| a8caa3054b | |||
| 2ef9e133ad | |||
| 2ec570ad61 | |||
| 02aa338970 | |||
| 882250bd86 | |||
| 3809eda2f2 | |||
| b32eda70d2 | |||
| f1b2f46a10 | |||
| cf82dac4ad | |||
| a0913e3f63 | |||
| f90955a9b9 | |||
| 3736c9229d | |||
| a802853367 | |||
| 96ca88fe88 | |||
| a57570210a | |||
| 7682e94b35 | |||
| 8bbebe113c | |||
| 7c921f827b | |||
| 4cc055f93a | |||
| e596a8b457 | |||
| fd2da55880 | |||
| 975e9b5643 | |||
| c0036e0474 | |||
| 103816e27a | |||
| b7c1684ab4 | |||
| 16bd6ca266 | |||
| 20bae4712b | |||
| a7aa80c690 | |||
| df89d1d58b | |||
| 477cddd29c | |||
| 9b8cf3a1b1 | |||
| 2871a3c07f | |||
| 13763296dd | |||
| 783b22ab1c | |||
| 109937adf4 | |||
| 63ea9cc4e0 | |||
| ec40a0c4c3 | |||
| 0855d1a378 | |||
| 77fe17d350 | |||
| 0b8a031ccb |
@@ -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"
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
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 }} on ${{ matrix.os }}"
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [
|
||||||
|
"ubuntu-latest",
|
||||||
|
"macos-12",
|
||||||
|
"macos-latest",
|
||||||
|
]
|
||||||
|
python-version: [
|
||||||
|
"3.10",
|
||||||
|
"3.11",
|
||||||
|
"3.12",
|
||||||
|
"3.13",
|
||||||
|
"pypy3.10",
|
||||||
|
]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
- uses: yezz123/setup-uv@v4
|
||||||
|
- run: uv pip install --editable '.[graphql,develop,test]' --system
|
||||||
|
- run: poe check
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
.venv*
|
||||||
.vscode/
|
.vscode/
|
||||||
.cache
|
.cache
|
||||||
.idea
|
.idea
|
||||||
|
|||||||
-18
@@ -1,18 +0,0 @@
|
|||||||
# travis use trusty by default
|
|
||||||
dist: xenial
|
|
||||||
|
|
||||||
language: python
|
|
||||||
python:
|
|
||||||
- 3.6
|
|
||||||
- 3.7
|
|
||||||
- "3.8-dev"
|
|
||||||
|
|
||||||
# command to install dependencies
|
|
||||||
install:
|
|
||||||
- pip install pipenv --upgrade-strategy=only-if-needed
|
|
||||||
- pipenv install --dev
|
|
||||||
|
|
||||||
# command to run the dependencies
|
|
||||||
script:
|
|
||||||
- black responder tests setup.py --check
|
|
||||||
- pytest
|
|
||||||
+110
-3
@@ -1,233 +1,340 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
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/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [v2.0.5] - 2019-12-15
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Update requirements to support python 3.8
|
||||||
|
|
||||||
## [v2.0.4] - 2019-11-19
|
## [v2.0.4] - 2019-11-19
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix static app resolving
|
- Fix static app resolving
|
||||||
|
|
||||||
## [v2.0.3] - 2019-09-20
|
## [v2.0.3] - 2019-09-20
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix template conflicts
|
- Fix template conflicts
|
||||||
|
|
||||||
## [v2.0.2] - 2019-09-20
|
## [v2.0.2] - 2019-09-20
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix template conflicts
|
- Fix template conflicts
|
||||||
|
|
||||||
## [v2.0.1] - 2019-09-20
|
## [v2.0.1] - 2019-09-20
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix template import
|
- Fix template import
|
||||||
|
|
||||||
## [v2.0.0] - 2019-09-19
|
## [v2.0.0] - 2019-09-19
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Refactor Router and Schema
|
- Refactor Router and Schema
|
||||||
|
|
||||||
## [v1.3.2] - 2019-08-15
|
## [v1.3.2] - 2019-08-15
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- ASGI 3 support
|
- ASGI 3 support
|
||||||
- CI tests for python 3.8-dev
|
- CI tests for python 3.8-dev
|
||||||
- Now requests have `state` a mapping object
|
- Now requests have `state` a mapping object
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
|
||||||
- ASGI 2
|
- ASGI 2
|
||||||
|
|
||||||
## [v1.3.1] - 2019-04-28
|
## [v1.3.1] - 2019-04-28
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Route params Converters
|
- Route params Converters
|
||||||
- Add search for documentation pages
|
- Add search for documentation pages
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Bump dependencies
|
- Bump dependencies
|
||||||
|
|
||||||
## [v1.3.0] - 2019-02-22
|
## [v1.3.0] - 2019-02-22
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Versioning issue
|
- Versioning issue
|
||||||
- Multiple cookies.
|
- Multiple cookies.
|
||||||
- Whitenoise returns not found.
|
- Whitenoise returns not found.
|
||||||
- Other bugfixes.
|
- Other bugfixes.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Stream support via `resp.stream`.
|
- Stream support via `resp.stream`.
|
||||||
- Cookie directives via `resp.set_cookie`.
|
- Cookie directives via `resp.set_cookie`.
|
||||||
- Add `resp.html` to send HTML.
|
- Add `resp.html` to send HTML.
|
||||||
- Other improvements.
|
- Other improvements.
|
||||||
|
|
||||||
## [v1.1.3] - 2019-01-12
|
## [v1.1.3] - 2019-01-12
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Refactor `_route_for`
|
- Refactor `_route_for`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Resolve startup/shutdwown events
|
- Resolve startup/shutdwown events
|
||||||
|
|
||||||
## [v1.2.0] - 2018-12-29
|
## [v1.2.0] - 2018-12-29
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Documentations
|
- Documentations
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Use Starlette's LifeSpan middleware
|
- Use Starlette's LifeSpan middleware
|
||||||
- Update denpendencies
|
- Update denpendencies
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix route.is_class_based
|
- Fix route.is_class_based
|
||||||
- Fix test_500
|
- Fix test_500
|
||||||
- Typos
|
- Typos
|
||||||
|
|
||||||
## [v1.1.2] - 2018-11-11
|
## [v1.1.2] - 2018-11-11
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Minor fixes for Open API
|
- Minor fixes for Open API
|
||||||
- Typos
|
- Typos
|
||||||
|
|
||||||
## [v1.1.1] - 2018-10-29
|
## [v1.1.1] - 2018-10-29
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Run sync views in a threadpoolexecutor.
|
- Run sync views in a threadpoolexecutor.
|
||||||
|
|
||||||
## [v1.1.0] - 2018-10-27
|
## [v1.1.0] - 2018-10-27
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Support for `before_request`.
|
- Support for `before_request`.
|
||||||
|
|
||||||
## [v1.0.5]- 2018-10-27
|
## [v1.0.5]- 2018-10-27
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix sessions.
|
- Fix sessions.
|
||||||
|
|
||||||
## [v1.0.4] - 2018-10-27
|
## [v1.0.4] - 2018-10-27
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Potential bufix for cookies.
|
- Potential bufix for cookies.
|
||||||
|
|
||||||
## [v1.0.3] - 2018-10-27
|
## [v1.0.3] - 2018-10-27
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Bugfix for redirects.
|
- Bugfix for redirects.
|
||||||
|
|
||||||
## [v1.0.2] - 2018-10-27
|
## [v1.0.2] - 2018-10-27
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Improvement for static file hosting.
|
- Improvement for static file hosting.
|
||||||
|
|
||||||
## [v1.0.1] - 2018-10-26
|
## [v1.0.1] - 2018-10-26
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Improve cors configuration settings.
|
- Improve cors configuration settings.
|
||||||
|
|
||||||
## [v1.0.0] - 2018-10-26
|
## [v1.0.0] - 2018-10-26
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Move GraphQL support into a built-in plugin.
|
- Move GraphQL support into a built-in plugin.
|
||||||
|
|
||||||
## [v0.3.3] - 2018-10-25
|
## [v0.3.3] - 2018-10-25
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- CORS support
|
- CORS support
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Improved exceptions.
|
- Improved exceptions.
|
||||||
|
|
||||||
## [v0.3.2] - 2018-10-25
|
## [v0.3.2] - 2018-10-25
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Subtle improvements.
|
- Subtle improvements.
|
||||||
|
|
||||||
## [v0.3.1] - 2018-10-24
|
## [v0.3.1] - 2018-10-24
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Packaging fix.
|
- Packaging fix.
|
||||||
|
|
||||||
## [v0.3.0] - 2018-10-24
|
## [v0.3.0] - 2018-10-24
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Interactive Documentation endpoint.
|
- Interactive Documentation endpoint.
|
||||||
- Minor improvements.
|
- Minor improvements.
|
||||||
|
|
||||||
## [v0.2.3] - 2018-10-24
|
## [v0.2.3] - 2018-10-24
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Overall improvements.
|
- Overall improvements.
|
||||||
|
|
||||||
## [v0.2.2] - 2018-10-23
|
## [v0.2.2] - 2018-10-23
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Show traceback info when background tasks raise exceptions.
|
- Show traceback info when background tasks raise exceptions.
|
||||||
|
|
||||||
## [v0.2.1] - 2018-10-23
|
## [v0.2.1] - 2018-10-23
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- api.requests.
|
- api.requests.
|
||||||
|
|
||||||
## [v0.2.0] - 2018-10-22
|
## [v0.2.0] - 2018-10-22
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- WebSocket support.
|
- WebSocket support.
|
||||||
|
|
||||||
## [v0.1.6] - 2018-10-20
|
## [v0.1.6] - 2018-10-20
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- 500 support.
|
- 500 support.
|
||||||
|
|
||||||
## [v0.1.5] - 2018-10-20
|
## [v0.1.5] - 2018-10-20
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- File upload support
|
- File upload support
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Improvements to sequential media reading.
|
- Improvements to sequential media reading.
|
||||||
|
|
||||||
## [v0.1.4] - 2018-10-19
|
## [v0.1.4] - 2018-10-19
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Stability.
|
- Stability.
|
||||||
|
|
||||||
## [v0.1.3] - 2018-10-18
|
## [v0.1.3] - 2018-10-18
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Sessions support.
|
- Sessions support.
|
||||||
|
|
||||||
## [v0.1.2] - 2018-10-18
|
## [v0.1.2] - 2018-10-18
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Cookies support.
|
- Cookies support.
|
||||||
|
|
||||||
## [v0.1.1] - 2018-10-17
|
## [v0.1.1] - 2018-10-17
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Default routes.
|
- Default routes.
|
||||||
|
|
||||||
## [v0.1.0] - 2018-10-17
|
## [v0.1.0] - 2018-10-17
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Prototype of static application support.
|
- Prototype of static application support.
|
||||||
|
|
||||||
## [v0.0.10] - 2018-10-17
|
## [v0.0.10] - 2018-10-17
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Bugfix for async class-based views.
|
- Bugfix for async class-based views.
|
||||||
|
|
||||||
## [v0.0.9] - 2018-10-17
|
## [v0.0.9] - 2018-10-17
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Bugfix for async class-based views.
|
- Bugfix for async class-based views.
|
||||||
|
|
||||||
## [v0.0.8] - 2018-10-17
|
## [v0.0.8] - 2018-10-17
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- GraphiQL Support.
|
- GraphiQL Support.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Improvement to route selection.
|
- Improvement to route selection.
|
||||||
|
|
||||||
## [v0.0.7] - 2018-10-16
|
## [v0.0.7] - 2018-10-16
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Immutable Request object.
|
- Immutable Request object.
|
||||||
|
|
||||||
## [v0.0.6] - 2018-10-16
|
## [v0.0.6] - 2018-10-16
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Ability to mount WSGI apps.
|
- Ability to mount WSGI apps.
|
||||||
- Supply content-type when serving up the schema.
|
- Supply content-type when serving up the schema.
|
||||||
|
|
||||||
## [v0.0.5] - 2018-10-15
|
## [v0.0.5] - 2018-10-15
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- OpenAPI Schema support.
|
- OpenAPI Schema support.
|
||||||
- Safe load/dump yaml.
|
- Safe load/dump yaml.
|
||||||
|
|
||||||
## [v0.0.4] - 2018-10-15
|
## [v0.0.4] - 2018-10-15
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Asynchronous support for data uploads.
|
- Asynchronous support for data uploads.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Bug fixes.
|
- Bug fixes.
|
||||||
|
|
||||||
## [v0.0.3] - 2018-10-13
|
## [v0.0.3] - 2018-10-13
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Bug fixes.
|
- Bug fixes.
|
||||||
|
|
||||||
## [v0.0.2] - 2018-10-13
|
## [v0.0.2] - 2018-10-13
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Switch to ASGI/Starlette.
|
- Switch to ASGI/Starlette.
|
||||||
|
|
||||||
## [v0.0.1] - 2018-10-12
|
## [v0.0.1] - 2018-10-12
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Conception!
|
- Conception!
|
||||||
|
|
||||||
[Unreleased]: https://github.com/taoufik07/responder/compare/v2.0.4..HEAD
|
[unreleased]: https://github.com/taoufik07/responder/compare/v2.0.5..HEAD
|
||||||
|
[v2.0.5]: https://github.com/taoufik07/responder/compare/v2.0.4..v2.0.5
|
||||||
[v2.0.4]: https://github.com/taoufik07/responder/compare/v2.0.3..v2.0.4
|
[v2.0.4]: https://github.com/taoufik07/responder/compare/v2.0.3..v2.0.4
|
||||||
[v2.0.3]: https://github.com/taoufik07/responder/compare/v2.0.2..v2.0.3
|
[v2.0.3]: https://github.com/taoufik07/responder/compare/v2.0.2..v2.0.3
|
||||||
[v2.0.2]: https://github.com/taoufik07/responder/compare/v2.0.1..v2.0.2
|
[v2.0.2]: https://github.com/taoufik07/responder/compare/v2.0.1..v2.0.2
|
||||||
|
|||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
# Development Sandbox
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Acquire sources and install project in editable mode.
|
||||||
|
```shell
|
||||||
|
git clone https://github.com/kennethreitz/responder
|
||||||
|
cd responder
|
||||||
|
python3 -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
pip install --editable '.[graphql,develop,release,test]'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Operations
|
||||||
|
|
||||||
|
Invoke linter and software tests.
|
||||||
|
```shell
|
||||||
|
poe check
|
||||||
|
```
|
||||||
|
|
||||||
|
Format code.
|
||||||
|
```shell
|
||||||
|
poe format
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Release
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git tag v2.1.0
|
||||||
|
git push --tags
|
||||||
|
poe release
|
||||||
|
```
|
||||||
@@ -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
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
1. Definitions.
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|||||||
@@ -1,20 +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 = "*"
|
|
||||||
pytest-cov = "*"
|
|
||||||
|
|
||||||
[pipenv]
|
|
||||||
allow_prereleases = true
|
|
||||||
Generated
-762
@@ -1,762 +0,0 @@
|
|||||||
{
|
|
||||||
"_meta": {
|
|
||||||
"hash": {
|
|
||||||
"sha256": "ea12c0d556a3ca0848b0eba291a11a5ea98a701f0885c2d030b2aeb1e5b9c15f"
|
|
||||||
},
|
|
||||||
"pipfile-spec": 6,
|
|
||||||
"requires": {},
|
|
||||||
"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:513d2b6637b7853806ae79ffaca6f3e8754bdd547048f5ccc1420aec4b714f1e",
|
|
||||||
"sha256:d10a4bf949f619f719b227ef5386e31f49a2b6d453004b21f02661ccc8670c7b"
|
|
||||||
],
|
|
||||||
"version": "==7.0.0"
|
|
||||||
},
|
|
||||||
"apispec": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:5fdaa1173b32515cc83f9d413a49a6c37fafc2b87f6b40e95923d3e85f0942c5",
|
|
||||||
"sha256:9e88c51517a6515612e818459f61c1bc06c00f2313e5187828bdbabaa7461473"
|
|
||||||
],
|
|
||||||
"version": "==3.0.0"
|
|
||||||
},
|
|
||||||
"apistar": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:8da0d3f15748c8ed6e68914ba5b8f6dd5dff5afbe137950d07103575df0bce73"
|
|
||||||
],
|
|
||||||
"version": "==0.7.2"
|
|
||||||
},
|
|
||||||
"certifi": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50",
|
|
||||||
"sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef"
|
|
||||||
],
|
|
||||||
"version": "==2019.9.11"
|
|
||||||
},
|
|
||||||
"chardet": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
|
||||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
|
||||||
],
|
|
||||||
"version": "==3.0.4"
|
|
||||||
},
|
|
||||||
"click": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
|
|
||||||
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
|
|
||||||
],
|
|
||||||
"version": "==7.0"
|
|
||||||
},
|
|
||||||
"docopt": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"
|
|
||||||
],
|
|
||||||
"version": "==0.6.2"
|
|
||||||
},
|
|
||||||
"graphene": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:09165f03e1591b76bf57b133482db9be6dac72c74b0a628d3c93182af9c5a896",
|
|
||||||
"sha256:2cbe6d4ef15cfc7b7805e0760a0e5b80747161ce1b0f990dfdc0d2cf497c12f9"
|
|
||||||
],
|
|
||||||
"version": "==2.1.8"
|
|
||||||
},
|
|
||||||
"graphql-core": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:1488f2a5c2272dc9ba66e3042a6d1c30cea0db4c80bd1e911c6791ad6187d91b",
|
|
||||||
"sha256:da64c472d720da4537a2e8de8ba859210b62841bd47a9be65ca35177f62fe0e4"
|
|
||||||
],
|
|
||||||
"version": "==2.2.1"
|
|
||||||
},
|
|
||||||
"graphql-relay": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:0e94201af4089e1f81f07d7bd8f84799768e39d70fa1ea16d1df505b46cc6335",
|
|
||||||
"sha256:75aa0758971e252964cb94068a4decd472d2a8295229f02189e3cbca1f10dbb5",
|
|
||||||
"sha256:7fa74661246e826ef939ee92e768f698df167a7617361ab399901eaebf80dce6"
|
|
||||||
],
|
|
||||||
"version": "==2.0.0"
|
|
||||||
},
|
|
||||||
"graphql-server-core": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:e5f82add4b3d5580aa1f1e7d9f00e944ad3abe1b65eb337e611d6a77cc20f231"
|
|
||||||
],
|
|
||||||
"version": "==1.1.1"
|
|
||||||
},
|
|
||||||
"h11": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:acca6a44cb52a32ab442b1779adf0875c443c689e9e028f8d831a3769f9c5208",
|
|
||||||
"sha256:f2b1ca39bfed357d1f19ac732913d5f9faa54a5062eca7d2ec3a916cfb7ae4c7"
|
|
||||||
],
|
|
||||||
"version": "==0.8.1"
|
|
||||||
},
|
|
||||||
"httptools": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:e00cbd7ba01ff748e494248183abc6e153f49181169d8a3d41bb49132ca01dfc"
|
|
||||||
],
|
|
||||||
"version": "==0.0.13"
|
|
||||||
},
|
|
||||||
"idna": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
|
||||||
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
|
|
||||||
],
|
|
||||||
"version": "==2.8"
|
|
||||||
},
|
|
||||||
"itsdangerous": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
|
|
||||||
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
|
|
||||||
],
|
|
||||||
"version": "==1.1.0"
|
|
||||||
},
|
|
||||||
"jinja2": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f",
|
|
||||||
"sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"
|
|
||||||
],
|
|
||||||
"version": "==2.10.3"
|
|
||||||
},
|
|
||||||
"markupsafe": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
|
|
||||||
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
|
|
||||||
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
|
|
||||||
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
|
|
||||||
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
|
|
||||||
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
|
|
||||||
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
|
|
||||||
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
|
|
||||||
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
|
|
||||||
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
|
|
||||||
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
|
|
||||||
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
|
|
||||||
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
|
|
||||||
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
|
|
||||||
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
|
|
||||||
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
|
|
||||||
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
|
|
||||||
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
|
|
||||||
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
|
|
||||||
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
|
|
||||||
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
|
|
||||||
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
|
|
||||||
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
|
|
||||||
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
|
|
||||||
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
|
|
||||||
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
|
|
||||||
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
|
|
||||||
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"
|
|
||||||
],
|
|
||||||
"version": "==1.1.1"
|
|
||||||
},
|
|
||||||
"marshmallow": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:077b4612f5d3b9333b736fdc6b963d2b46d409070f44ff3e6c4109645c673e83",
|
|
||||||
"sha256:9a2f3e8ea5f530a9664e882d7d04b58650f46190178b2264c72b7d20399d28f0"
|
|
||||||
],
|
|
||||||
"version": "==3.2.1"
|
|
||||||
},
|
|
||||||
"promise": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:2ebbfc10b7abf6354403ed785fe4f04b9dfd421eb1a474ac8d187022228332af",
|
|
||||||
"sha256:348f5f6c3edd4fd47c9cd65aed03ac1b31136d375aa63871a57d3e444c85655c"
|
|
||||||
],
|
|
||||||
"version": "==2.2.1"
|
|
||||||
},
|
|
||||||
"python-multipart": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"
|
|
||||||
],
|
|
||||||
"version": "==0.0.5"
|
|
||||||
},
|
|
||||||
"pyyaml": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9",
|
|
||||||
"sha256:01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4",
|
|
||||||
"sha256:5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8",
|
|
||||||
"sha256:5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696",
|
|
||||||
"sha256:7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34",
|
|
||||||
"sha256:7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9",
|
|
||||||
"sha256:87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73",
|
|
||||||
"sha256:9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299",
|
|
||||||
"sha256:a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b",
|
|
||||||
"sha256:b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae",
|
|
||||||
"sha256:b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681",
|
|
||||||
"sha256:bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41",
|
|
||||||
"sha256:f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"
|
|
||||||
],
|
|
||||||
"version": "==5.1.2"
|
|
||||||
},
|
|
||||||
"requests": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
|
|
||||||
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
|
|
||||||
],
|
|
||||||
"version": "==2.22.0"
|
|
||||||
},
|
|
||||||
"requests-toolbelt": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f",
|
|
||||||
"sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"
|
|
||||||
],
|
|
||||||
"version": "==0.9.1"
|
|
||||||
},
|
|
||||||
"responder": {
|
|
||||||
"editable": true,
|
|
||||||
"path": "."
|
|
||||||
},
|
|
||||||
"rfc3986": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:0344d0bd428126ce554e7ca2b61787b6a28d2bbd19fc70ed2dd85efe31176405",
|
|
||||||
"sha256:df4eba676077cefb86450c8f60121b9ae04b94f65f85b69f3f731af0516b7b18"
|
|
||||||
],
|
|
||||||
"version": "==1.3.2"
|
|
||||||
},
|
|
||||||
"rx": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:13a1d8d9e252625c173dc795471e614eadfe1cf40ffc684e08b8fff0d9748c23",
|
|
||||||
"sha256:7357592bc7e881a95e0c2013b73326f704953301ab551fbc8133a6fadab84105"
|
|
||||||
],
|
|
||||||
"version": "==1.6.1"
|
|
||||||
},
|
|
||||||
"six": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
|
|
||||||
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
|
|
||||||
],
|
|
||||||
"version": "==1.12.0"
|
|
||||||
},
|
|
||||||
"starlette": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:e41ef52e711a82ef95c195674e5d8d41c75c6b1d6f5a275637eedd4cc2150a7f"
|
|
||||||
],
|
|
||||||
"version": "==0.12.10"
|
|
||||||
},
|
|
||||||
"typesystem": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:ba2bd10f1c5844d08dd8841e777bdee55bfca569bf21cb96cd0f91e0a4f66cd8"
|
|
||||||
],
|
|
||||||
"version": "==0.2.4"
|
|
||||||
},
|
|
||||||
"urllib3": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398",
|
|
||||||
"sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86"
|
|
||||||
],
|
|
||||||
"version": "==1.25.6"
|
|
||||||
},
|
|
||||||
"uvicorn": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:8aa44f9d9c3082ef693950387ea25d376e32944df6d4071dbd8edc3c25a40c74"
|
|
||||||
],
|
|
||||||
"version": "==0.8.6"
|
|
||||||
},
|
|
||||||
"uvloop": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:0fcd894f6fc3226a962ee7ad895c4f52e3f5c3c55098e21efb17c071849a0573",
|
|
||||||
"sha256:2f31de1742c059c96cb76b91c5275b22b22b965c886ee1fced093fa27dde9e64",
|
|
||||||
"sha256:459e4649fcd5ff719523de33964aa284898e55df62761e7773d088823ccbd3e0",
|
|
||||||
"sha256:67867aafd6e0bc2c30a079603a85d83b94f23c5593b3cc08ec7e58ac18bf48e5",
|
|
||||||
"sha256:8c200457e6847f28d8bb91c5e5039d301716f5f2fce25646f5fb3fd65eda4a26",
|
|
||||||
"sha256:958906b9ca39eb158414fbb7d6b8ef1b7aee4db5c8e8e5d00fcbb69a1ce9dca7",
|
|
||||||
"sha256:ac1dca3d8f3ef52806059e81042ee397ac939e5a86c8a3cea55d6b087db66115",
|
|
||||||
"sha256:b284c22d8938866318e3b9d178142b8be316c52d16fcfe1560685a686718a021",
|
|
||||||
"sha256:c48692bf4587ce281d641087658eca275a5ad3b63c78297bbded96570ae9ce8f",
|
|
||||||
"sha256:fefc3b2b947c99737c348887db2c32e539160dcbeb7af9aa6b53db7a283538fe"
|
|
||||||
],
|
|
||||||
"version": "==0.12.2"
|
|
||||||
},
|
|
||||||
"websockets": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:04b42a1b57096ffa5627d6a78ea1ff7fad3bc2c0331ffc17bc32a4024da7fea0",
|
|
||||||
"sha256:08e3c3e0535befa4f0c4443824496c03ecc25062debbcf895874f8a0b4c97c9f",
|
|
||||||
"sha256:10d89d4326045bf5e15e83e9867c85d686b612822e4d8f149cf4840aab5f46e0",
|
|
||||||
"sha256:232fac8a1978fc1dead4b1c2fa27c7756750fb393eb4ac52f6bc87ba7242b2fa",
|
|
||||||
"sha256:4bf4c8097440eff22bc78ec76fe2a865a6e658b6977a504679aaf08f02c121da",
|
|
||||||
"sha256:51642ea3a00772d1e48fb0c492f0d3ae3b6474f34d20eca005a83f8c9c06c561",
|
|
||||||
"sha256:55d86102282a636e195dad68aaaf85b81d0bef449d7e2ef2ff79ac450bb25d53",
|
|
||||||
"sha256:564d2675682bd497b59907d2205031acbf7d3fadf8c763b689b9ede20300b215",
|
|
||||||
"sha256:5d13bf5197a92149dc0badcc2b699267ff65a867029f465accfca8abab95f412",
|
|
||||||
"sha256:5eda665f6789edb9b57b57a159b9c55482cbe5b046d7db458948370554b16439",
|
|
||||||
"sha256:5edb2524d4032be4564c65dc4f9d01e79fe8fad5f966e5b552f4e5164fef0885",
|
|
||||||
"sha256:79691794288bc51e2a3b8de2bc0272ca8355d0b8503077ea57c0716e840ebaef",
|
|
||||||
"sha256:7fcc8681e9981b9b511cdee7c580d5b005f3bb86b65bde2188e04a29f1d63317",
|
|
||||||
"sha256:8e447e05ec88b1b408a4c9cde85aa6f4b04f06aa874b9f0b8e8319faf51b1fee",
|
|
||||||
"sha256:90ea6b3e7787620bb295a4ae050d2811c807d65b1486749414f78cfd6fb61489",
|
|
||||||
"sha256:9e13239952694b8b831088431d15f771beace10edfcf9ef230cefea14f18508f",
|
|
||||||
"sha256:d40f081187f7b54d7a99d8a5c782eaa4edc335a057aa54c85059272ed826dc09",
|
|
||||||
"sha256:e1df1a58ed2468c7b7ce9a2f9752a32ad08eac2bcd56318625c3647c2cd2da6f",
|
|
||||||
"sha256:e98d0cec437097f09c7834a11c69d79fe6241729b23f656cfc227e93294fc242",
|
|
||||||
"sha256:f8d59627702d2ff27cb495ca1abdea8bd8d581de425c56e93bff6517134e0a9b",
|
|
||||||
"sha256:fc30cdf2e949a2225b012a7911d1d031df3d23e99b7eda7dfc982dc4a860dae9"
|
|
||||||
],
|
|
||||||
"version": "==7.0"
|
|
||||||
},
|
|
||||||
"whitenoise": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:22f79cf8f1f509639330f93886acaece8ec5ac5e9600c3b981d33c34e8a42dfd",
|
|
||||||
"sha256:6dfea214b7c12efd689007abf9afa87a426586e9dbc051873ad2c8e535e2a1ac"
|
|
||||||
],
|
|
||||||
"version": "==4.1.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"develop": {
|
|
||||||
"alabaster": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359",
|
|
||||||
"sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"
|
|
||||||
],
|
|
||||||
"version": "==0.7.12"
|
|
||||||
},
|
|
||||||
"appdirs": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
|
|
||||||
"sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
|
|
||||||
],
|
|
||||||
"version": "==1.4.3"
|
|
||||||
},
|
|
||||||
"atomicwrites": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4",
|
|
||||||
"sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"
|
|
||||||
],
|
|
||||||
"version": "==1.3.0"
|
|
||||||
},
|
|
||||||
"attrs": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
|
|
||||||
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
|
|
||||||
],
|
|
||||||
"version": "==19.3.0"
|
|
||||||
},
|
|
||||||
"babel": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab",
|
|
||||||
"sha256:e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28"
|
|
||||||
],
|
|
||||||
"version": "==2.7.0"
|
|
||||||
},
|
|
||||||
"black": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf",
|
|
||||||
"sha256:68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==19.3b0"
|
|
||||||
},
|
|
||||||
"bleach": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16",
|
|
||||||
"sha256:3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa"
|
|
||||||
],
|
|
||||||
"version": "==3.1.0"
|
|
||||||
},
|
|
||||||
"certifi": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50",
|
|
||||||
"sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef"
|
|
||||||
],
|
|
||||||
"version": "==2019.9.11"
|
|
||||||
},
|
|
||||||
"chardet": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
|
||||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
|
||||||
],
|
|
||||||
"version": "==3.0.4"
|
|
||||||
},
|
|
||||||
"click": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
|
|
||||||
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
|
|
||||||
],
|
|
||||||
"version": "==7.0"
|
|
||||||
},
|
|
||||||
"coverage": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:17a417c691de3fc88de027832267313e5ed2b2ea3956745b562c4c389e44d05b",
|
|
||||||
"sha256:24307e67ebd9dc06fcbab9b7fef87412a97746c1baabb04ed8a93d5c2ccfe5ba",
|
|
||||||
"sha256:2a5d44a9d8426bd3699123864e63f008dc8dea9df22d5216a141a25d4670f22c",
|
|
||||||
"sha256:3726b8f5461e103a40e380f52b4b4ccdf2eda55d5d72f037cee43627992b4462",
|
|
||||||
"sha256:39dd15bbc4880a64399e180925bbc21c0c316a3065f6455d2512039f5cb59b94",
|
|
||||||
"sha256:3bb121f5dd156aab4fba2ebad6b0ad605bc5dc305931140dc614b101aa9d81ed",
|
|
||||||
"sha256:3bfdea9226eaed97736c973a7d6d0bbf9e1c1f1c7391c8e9c2bb2d0dbae49156",
|
|
||||||
"sha256:43be906a16239c1aa9f3742e3e6b0a5dd24781a13ce401f063262e9b4e93b69f",
|
|
||||||
"sha256:4a54cac1b39b2925041a41bcd1f191898fe401618627d7c3abf127c32a1c6dd1",
|
|
||||||
"sha256:4e58d65b90d6f26b3ccca7cf0fe573ef847347b8734af596a087a21eebb681f5",
|
|
||||||
"sha256:50229727d9baf0cd7f5ee6b194bf9dea708e9a20823d93f9e04d710b0a60e757",
|
|
||||||
"sha256:5141cdb010e9cd6939e37b8c2769d535cb535d80ef94f927c8a306f2e05a4736",
|
|
||||||
"sha256:748ba2b950425b9aef9d1bde2d6af7023585505016bd634e578f76ada4a30465",
|
|
||||||
"sha256:75e635bc6730c88b04421b25a0afc47b9b80efc1ed57630839196eb475722e50",
|
|
||||||
"sha256:78556f51dbfb33f18794eee29a4a8542fd2e301aa0d072653930793974dced03",
|
|
||||||
"sha256:7de17133509210ecc256535bab2f9a5547f3016c44f984fe12b4c10d81a4623f",
|
|
||||||
"sha256:83bf376555898fe2dc50d111a34b0152b504e454ed1e13cdcda6e5d50ba0ed5b",
|
|
||||||
"sha256:87730b5e4c3a42674fe8f0ecbb0d556c59c7e12b11a65c2178f2787252a80dfd",
|
|
||||||
"sha256:9bb7819c020c20c6200764879f0b10b323d6d4719aa7b0ae316c9e35730f9e2d",
|
|
||||||
"sha256:9c825788acb13d49ac20455433f3b862029aa497e97faba8c998555a042a6b91",
|
|
||||||
"sha256:b2bb4941c8838fc9ea2fca3c52e6dd865d39bbbc014bde249161bf8fcccf2152",
|
|
||||||
"sha256:c1b44c6c680f137910cb0f5481a2ae9899787ca7019f110a3708d9e99df941be",
|
|
||||||
"sha256:c52c2bc67bd3ff8db685f7c5f03e34a95bddd58a535630161f28d1c485d61e22",
|
|
||||||
"sha256:d6845e46338695c571759be1c770b013c477111e785b26151ec9feb6cd063543",
|
|
||||||
"sha256:e292b32dfc80d9f271af2d52df95455248322156e764763c4bfb2385b2e33533"
|
|
||||||
],
|
|
||||||
"version": "==5.0a8"
|
|
||||||
},
|
|
||||||
"docutils": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0",
|
|
||||||
"sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827",
|
|
||||||
"sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99"
|
|
||||||
],
|
|
||||||
"version": "==0.15.2"
|
|
||||||
},
|
|
||||||
"entrypoints": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19",
|
|
||||||
"sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"
|
|
||||||
],
|
|
||||||
"version": "==0.3"
|
|
||||||
},
|
|
||||||
"flake8": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548",
|
|
||||||
"sha256:8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==3.7.8"
|
|
||||||
},
|
|
||||||
"flask": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52",
|
|
||||||
"sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==1.1.1"
|
|
||||||
},
|
|
||||||
"idna": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
|
||||||
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
|
|
||||||
],
|
|
||||||
"version": "==2.8"
|
|
||||||
},
|
|
||||||
"imagesize": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8",
|
|
||||||
"sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"
|
|
||||||
],
|
|
||||||
"version": "==1.1.0"
|
|
||||||
},
|
|
||||||
"importlib-metadata": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26",
|
|
||||||
"sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"
|
|
||||||
],
|
|
||||||
"markers": "python_version < '3.8'",
|
|
||||||
"version": "==0.23"
|
|
||||||
},
|
|
||||||
"itsdangerous": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
|
|
||||||
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
|
|
||||||
],
|
|
||||||
"version": "==1.1.0"
|
|
||||||
},
|
|
||||||
"jinja2": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f",
|
|
||||||
"sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"
|
|
||||||
],
|
|
||||||
"version": "==2.10.3"
|
|
||||||
},
|
|
||||||
"markupsafe": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
|
|
||||||
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
|
|
||||||
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
|
|
||||||
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
|
|
||||||
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
|
|
||||||
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
|
|
||||||
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
|
|
||||||
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
|
|
||||||
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
|
|
||||||
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
|
|
||||||
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
|
|
||||||
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
|
|
||||||
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
|
|
||||||
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
|
|
||||||
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
|
|
||||||
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
|
|
||||||
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
|
|
||||||
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
|
|
||||||
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
|
|
||||||
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
|
|
||||||
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
|
|
||||||
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
|
|
||||||
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
|
|
||||||
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
|
|
||||||
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
|
|
||||||
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
|
|
||||||
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
|
|
||||||
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"
|
|
||||||
],
|
|
||||||
"version": "==1.1.1"
|
|
||||||
},
|
|
||||||
"marshmallow": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:077b4612f5d3b9333b736fdc6b963d2b46d409070f44ff3e6c4109645c673e83",
|
|
||||||
"sha256:9a2f3e8ea5f530a9664e882d7d04b58650f46190178b2264c72b7d20399d28f0"
|
|
||||||
],
|
|
||||||
"version": "==3.2.1"
|
|
||||||
},
|
|
||||||
"mccabe": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
|
|
||||||
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
|
|
||||||
],
|
|
||||||
"version": "==0.6.1"
|
|
||||||
},
|
|
||||||
"more-itertools": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832",
|
|
||||||
"sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"
|
|
||||||
],
|
|
||||||
"version": "==7.2.0"
|
|
||||||
},
|
|
||||||
"packaging": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47",
|
|
||||||
"sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"
|
|
||||||
],
|
|
||||||
"version": "==19.2"
|
|
||||||
},
|
|
||||||
"pkginfo": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:7424f2c8511c186cd5424bbf31045b77435b37a8d604990b79d4e70d741148bb",
|
|
||||||
"sha256:a6d9e40ca61ad3ebd0b72fbadd4fba16e4c0e4df0428c041e01e06eb6ee71f32"
|
|
||||||
],
|
|
||||||
"version": "==1.5.0.1"
|
|
||||||
},
|
|
||||||
"pluggy": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6",
|
|
||||||
"sha256:fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34"
|
|
||||||
],
|
|
||||||
"version": "==0.13.0"
|
|
||||||
},
|
|
||||||
"py": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa",
|
|
||||||
"sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"
|
|
||||||
],
|
|
||||||
"version": "==1.8.0"
|
|
||||||
},
|
|
||||||
"pycodestyle": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
|
|
||||||
"sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
|
|
||||||
],
|
|
||||||
"version": "==2.5.0"
|
|
||||||
},
|
|
||||||
"pyflakes": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0",
|
|
||||||
"sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"
|
|
||||||
],
|
|
||||||
"version": "==2.1.1"
|
|
||||||
},
|
|
||||||
"pygments": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127",
|
|
||||||
"sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"
|
|
||||||
],
|
|
||||||
"version": "==2.4.2"
|
|
||||||
},
|
|
||||||
"pyparsing": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80",
|
|
||||||
"sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"
|
|
||||||
],
|
|
||||||
"version": "==2.4.2"
|
|
||||||
},
|
|
||||||
"pytest": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:7e4800063ccfc306a53c461442526c5571e1462f61583506ce97e4da6a1d88c8",
|
|
||||||
"sha256:ca563435f4941d0cb34767301c27bc65c510cb82e90b9ecf9cb52dc2c63caaa0"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==5.2.1"
|
|
||||||
},
|
|
||||||
"pytest-cov": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b",
|
|
||||||
"sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==2.8.1"
|
|
||||||
},
|
|
||||||
"pytz": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
|
|
||||||
"sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
|
|
||||||
],
|
|
||||||
"version": "==2019.3"
|
|
||||||
},
|
|
||||||
"readme-renderer": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:bb16f55b259f27f75f640acf5e00cf897845a8b3e4731b5c1a436e4b8529202f",
|
|
||||||
"sha256:c8532b79afc0375a85f10433eca157d6b50f7d6990f337fa498c96cd4bfc203d"
|
|
||||||
],
|
|
||||||
"version": "==24.0"
|
|
||||||
},
|
|
||||||
"requests": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
|
|
||||||
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
|
|
||||||
],
|
|
||||||
"version": "==2.22.0"
|
|
||||||
},
|
|
||||||
"requests-toolbelt": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f",
|
|
||||||
"sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"
|
|
||||||
],
|
|
||||||
"version": "==0.9.1"
|
|
||||||
},
|
|
||||||
"six": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
|
|
||||||
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
|
|
||||||
],
|
|
||||||
"version": "==1.12.0"
|
|
||||||
},
|
|
||||||
"snowballstemmer": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0",
|
|
||||||
"sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"
|
|
||||||
],
|
|
||||||
"version": "==2.0.0"
|
|
||||||
},
|
|
||||||
"sphinx": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:0d586b0f8c2fc3cc6559c5e8fd6124628110514fda0e5d7c82e682d749d2e845",
|
|
||||||
"sha256:839a3ed6f6b092bb60f492024489cc9e6991360fb9f52ed6361acd510d261069"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==2.2.0"
|
|
||||||
},
|
|
||||||
"sphinxcontrib-applehelp": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897",
|
|
||||||
"sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"
|
|
||||||
],
|
|
||||||
"version": "==1.0.1"
|
|
||||||
},
|
|
||||||
"sphinxcontrib-devhelp": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34",
|
|
||||||
"sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"
|
|
||||||
],
|
|
||||||
"version": "==1.0.1"
|
|
||||||
},
|
|
||||||
"sphinxcontrib-htmlhelp": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422",
|
|
||||||
"sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"
|
|
||||||
],
|
|
||||||
"version": "==1.0.2"
|
|
||||||
},
|
|
||||||
"sphinxcontrib-jsmath": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178",
|
|
||||||
"sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"
|
|
||||||
],
|
|
||||||
"version": "==1.0.1"
|
|
||||||
},
|
|
||||||
"sphinxcontrib-qthelp": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20",
|
|
||||||
"sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"
|
|
||||||
],
|
|
||||||
"version": "==1.0.2"
|
|
||||||
},
|
|
||||||
"sphinxcontrib-serializinghtml": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227",
|
|
||||||
"sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"
|
|
||||||
],
|
|
||||||
"version": "==1.1.3"
|
|
||||||
},
|
|
||||||
"toml": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
|
|
||||||
"sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
|
|
||||||
],
|
|
||||||
"version": "==0.10.0"
|
|
||||||
},
|
|
||||||
"tqdm": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:abc25d0ce2397d070ef07d8c7e706aede7920da163c64997585d42d3537ece3d",
|
|
||||||
"sha256:dd3fcca8488bb1d416aa7469d2f277902f26260c45aa86b667b074cd44b3b115"
|
|
||||||
],
|
|
||||||
"version": "==4.36.1"
|
|
||||||
},
|
|
||||||
"twine": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:5319dd3e02ac73fcddcd94f035b9631589ab5d23e1f4699d57365199d85261e1",
|
|
||||||
"sha256:9fe7091715c7576df166df8ef6654e61bada39571783f2fd415bdcba867c6993"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==2.0.0"
|
|
||||||
},
|
|
||||||
"urllib3": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398",
|
|
||||||
"sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86"
|
|
||||||
],
|
|
||||||
"version": "==1.25.6"
|
|
||||||
},
|
|
||||||
"wcwidth": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e",
|
|
||||||
"sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"
|
|
||||||
],
|
|
||||||
"version": "==0.1.7"
|
|
||||||
},
|
|
||||||
"webencodings": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
|
|
||||||
"sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
|
|
||||||
],
|
|
||||||
"version": "==0.5.1"
|
|
||||||
},
|
|
||||||
"werkzeug": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7",
|
|
||||||
"sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4"
|
|
||||||
],
|
|
||||||
"version": "==0.16.0"
|
|
||||||
},
|
|
||||||
"zipp": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e",
|
|
||||||
"sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"
|
|
||||||
],
|
|
||||||
"version": "==0.6.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +1,62 @@
|
|||||||
# Responder: a familiar HTTP Service Framework for Python
|
# Responder: a familiar HTTP Service Framework for Python
|
||||||
|
|
||||||
[](https://travis-ci.org/taoufik07/responder)
|
[](https://github.com/kennethreitz/responder/actions/workflows/test.yaml)
|
||||||
[](https://responder.readthedocs.io/en/latest/)
|
[](https://responder.kennethreitz.org/)
|
||||||
[](https://pypi.org/project/responder/)
|
[](https://pypi.org/project/responder/)
|
||||||
[](https://pypi.org/project/responder/)
|
[](https://pypi.org/project/responder/)
|
||||||
[](https://pypi.org/project/responder/)
|
[](https://pypi.org/project/responder/)
|
||||||
[](https://github.com/taoufik07/responder/graphs/contributors)
|
[](https://github.com/kennethreitz/responder/graphs/contributors)
|
||||||
|
[](https://pepy.tech/project/responder/)
|
||||||
|
[](https://pypi.org/project/responder/)
|
||||||
|
[](https://pypi.org/project/responder/)
|
||||||
|
|
||||||
[](https://responder.readthedocs.io)
|
[](https://responder.readthedocs.io)
|
||||||
|
|
||||||
|
Powered by [Starlette](https://www.starlette.io/). That `async` declaration is optional.
|
||||||
|
[View documentation](https://responder.readthedocs.io).
|
||||||
|
|
||||||
Powered by [Starlette](https://www.starlette.io/). That `async` declaration is optional. [View documentation](https://responder.readthedocs.io).
|
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,
|
||||||
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.
|
serving up requests with gzip compression automatically.
|
||||||
|
|
||||||
|
|
||||||
## Testimonials
|
## 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://twitter.com/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](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
|
||||||
> "I love that you are exploring new patterns. Go go go!" — Danny Greenfield, author of [Two Scoops of Django]()
|
> [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]()
|
||||||
|
|
||||||
## More Examples
|
## More Examples
|
||||||
|
|
||||||
See [the documentation's feature tour](https://responder.readthedocs.io/en/latest/tour.html) for more details on features available in Responder.
|
See
|
||||||
|
[the documentation's feature tour](https://responder.readthedocs.io/en/latest/tour.html)
|
||||||
|
for more details on features available in Responder.
|
||||||
|
|
||||||
# Installing Responder
|
# Installing Responder
|
||||||
|
|
||||||
Install the stable release:
|
Install the most recent stable release:
|
||||||
|
|
||||||
|
pip install --upgrade responder
|
||||||
|
|
||||||
$ pipenv install responder
|
Or, 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/taoufik07/responder.git#egg=responder
|
|
||||||
|
|
||||||
Only **Python 3.6+** is supported.
|
Only **Python 3.6+** is supported.
|
||||||
|
|
||||||
|
|
||||||
# The Basic Idea
|
# 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 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.content` sends back bytes.
|
- Setting `resp.content` sends back bytes.
|
||||||
- Setting `resp.text` sends back unicode, while setting `resp.html` sends back HTML.
|
- Setting `resp.text` sends back unicode, while setting `resp.html` sends back HTML.
|
||||||
@@ -55,16 +64,28 @@ The primary concept here is to bring the niceties that are brought forth from bo
|
|||||||
- Case-insensitive `req.headers` dict (from Requests directly).
|
- Case-insensitive `req.headers` dict (from Requests directly).
|
||||||
- `resp.status_code`, `req.method`, `req.url`, and other familiar friends.
|
- `resp.status_code`, `req.method`, `req.url`, and other familiar friends.
|
||||||
|
|
||||||
|
|
||||||
## Ideas
|
## Ideas
|
||||||
|
|
||||||
- Flask-style route expression, with new capabilities -- all while using Python 3.6+'s new f-string syntax.
|
- Flask-style route expression, with new capabilities -- all while using Python 3.6+'s
|
||||||
- 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.
|
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**.
|
- **A built in testing client that uses the actual Requests you know and love**.
|
||||||
- The ability to mount other WSGI apps easily.
|
- The ability to mount other WSGI apps easily.
|
||||||
- Automatic gzipped-responses.
|
- 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.
|
- 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.
|
- 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.
|
- Uvicorn built-in as a production web server. I would have chosen Gunicorn, but it
|
||||||
- GraphQL support, via Graphene. The goal here is to have any GraphQL query exposable at any route, magically.
|
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.
|
||||||
- Provide an official way to run webpack.
|
- Provide an official way to run webpack.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
See [Development Sandbox](DEVELOP.md).
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# ruff: noqa: S605, S607
|
||||||
|
"""
|
||||||
|
Build and publish a .deb package.
|
||||||
|
https://pypi.python.org/pypi/stdeb/0.8.5#quickstart-2-just-tell-me-the-fastest-way-to-make-a-deb
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from shutil import rmtree
|
||||||
|
|
||||||
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
def get_version():
|
||||||
|
import responder
|
||||||
|
|
||||||
|
return responder.__version__
|
||||||
|
|
||||||
|
|
||||||
|
def run():
|
||||||
|
version = get_version()
|
||||||
|
try:
|
||||||
|
print("Removing previous builds")
|
||||||
|
rmtree(os.path.join(here, "deb_dist"))
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
print("Creating Debian package manifest")
|
||||||
|
os.system(
|
||||||
|
"python setup.py --command-packages=stdeb.command sdist_dsc "
|
||||||
|
"-z artful --package3=pipenv --depends3=python3-virtualenv-clone"
|
||||||
|
)
|
||||||
|
print("Building .deb")
|
||||||
|
os.chdir(f"deb_dist/pipenv-{version}")
|
||||||
|
os.system("dpkg-buildpackage -rfakeroot -uc -us")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run()
|
||||||
+6
-48
@@ -1,48 +1,6 @@
|
|||||||
alabaster==0.7.12
|
alabaster<0.8
|
||||||
appdirs==1.4.3
|
jinja2<3.2
|
||||||
atomicwrites==1.2.1
|
markupsafe<4
|
||||||
attrs==18.2.0
|
readme-renderer<45
|
||||||
babel==2.6.0
|
sphinx>=5,<9
|
||||||
black==18.9b0
|
sphinxcontrib-websupport<2.1
|
||||||
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.15.5
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* Hide module name and default value for environment variable section */
|
/* Hide module name and default value for environment variable section */
|
||||||
div[id$='environment-variables'] code.descclassname {
|
div[id$="environment-variables"] code.descclassname {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
div[id$='environment-variables'] em.property {
|
div[id$="environment-variables"] em.property {
|
||||||
display: none;
|
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
@@ -10,142 +10,152 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
var Konami = function (callback) {
|
var Konami = function (callback) {
|
||||||
var konami = {
|
var konami = {
|
||||||
addEvent: function (obj, type, fn, ref_obj) {
|
addEvent: function (obj, type, fn, ref_obj) {
|
||||||
if (obj.addEventListener)
|
if (obj.addEventListener) obj.addEventListener(type, fn, false);
|
||||||
obj.addEventListener(type, fn, false);
|
else if (obj.attachEvent) {
|
||||||
else if (obj.attachEvent) {
|
// IE
|
||||||
// IE
|
obj["e" + type + fn] = fn;
|
||||||
obj["e" + type + fn] = fn;
|
obj[type + fn] = function () {
|
||||||
obj[type + fn] = function () {
|
obj["e" + type + fn](window.event, ref_obj);
|
||||||
obj["e" + type + fn](window.event, ref_obj);
|
};
|
||||||
}
|
obj.attachEvent("on" + type, obj[type + fn]);
|
||||||
obj.attachEvent("on" + type, obj[type + fn]);
|
}
|
||||||
}
|
},
|
||||||
},
|
removeEvent: function (obj, eventName, eventCallback) {
|
||||||
removeEvent: function (obj, eventName, eventCallback) {
|
if (obj.removeEventListener) {
|
||||||
if (obj.removeEventListener) {
|
obj.removeEventListener(eventName, eventCallback);
|
||||||
obj.removeEventListener(eventName, eventCallback);
|
} else if (obj.attachEvent) {
|
||||||
} else if (obj.attachEvent) {
|
obj.detachEvent(eventName);
|
||||||
obj.detachEvent(eventName);
|
}
|
||||||
}
|
},
|
||||||
},
|
input: "",
|
||||||
input: "",
|
pattern: "38384040373937396665",
|
||||||
pattern: "38384040373937396665",
|
keydownHandler: function (e, ref_obj) {
|
||||||
keydownHandler: function (e, ref_obj) {
|
if (ref_obj) {
|
||||||
if (ref_obj) {
|
konami = ref_obj;
|
||||||
konami = ref_obj;
|
} // IE
|
||||||
} // IE
|
konami.input += e ? e.keyCode : event.keyCode;
|
||||||
konami.input += e ? e.keyCode : event.keyCode;
|
if (konami.input.length > konami.pattern.length) {
|
||||||
if (konami.input.length > konami.pattern.length) {
|
konami.input = konami.input.substr(konami.input.length - konami.pattern.length);
|
||||||
konami.input = konami.input.substr((konami.input.length - konami.pattern.length));
|
}
|
||||||
}
|
if (konami.input === konami.pattern) {
|
||||||
if (konami.input === konami.pattern) {
|
konami.code(konami._currentLink);
|
||||||
konami.code(konami._currentLink);
|
konami.input = "";
|
||||||
konami.input = '';
|
e.preventDefault();
|
||||||
e.preventDefault();
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
},
|
||||||
},
|
load: function (link) {
|
||||||
load: function (link) {
|
this._currentLink = link;
|
||||||
this._currentLink = link;
|
this.addEvent(document, "keydown", this.keydownHandler, this);
|
||||||
this.addEvent(document, "keydown", this.keydownHandler, this);
|
this.iphone.load(link);
|
||||||
this.iphone.load(link);
|
},
|
||||||
},
|
unload: function () {
|
||||||
unload: function () {
|
this.removeEvent(document, "keydown", this.keydownHandler);
|
||||||
this.removeEvent(document, 'keydown', this.keydownHandler);
|
this.iphone.unload();
|
||||||
this.iphone.unload();
|
},
|
||||||
},
|
code: function (link) {
|
||||||
code: function (link) {
|
window.location = link;
|
||||||
window.location = link
|
},
|
||||||
},
|
iphone: {
|
||||||
iphone: {
|
start_x: 0,
|
||||||
start_x: 0,
|
start_y: 0,
|
||||||
start_y: 0,
|
stop_x: 0,
|
||||||
stop_x: 0,
|
stop_y: 0,
|
||||||
stop_y: 0,
|
tap: false,
|
||||||
tap: false,
|
capture: false,
|
||||||
capture: false,
|
orig_keys: "",
|
||||||
orig_keys: "",
|
keys: [
|
||||||
keys: ["UP", "UP", "DOWN", "DOWN", "LEFT", "RIGHT", "LEFT", "RIGHT", "TAP", "TAP"],
|
"UP",
|
||||||
input: [],
|
"UP",
|
||||||
code: function (link) {
|
"DOWN",
|
||||||
konami.code(link);
|
"DOWN",
|
||||||
},
|
"LEFT",
|
||||||
touchmoveHandler: function (e) {
|
"RIGHT",
|
||||||
if (e.touches.length === 1 && konami.iphone.capture === true) {
|
"LEFT",
|
||||||
var touch = e.touches[0];
|
"RIGHT",
|
||||||
konami.iphone.stop_x = touch.pageX;
|
"TAP",
|
||||||
konami.iphone.stop_y = touch.pageY;
|
"TAP",
|
||||||
konami.iphone.tap = false;
|
],
|
||||||
konami.iphone.capture = false;
|
input: [],
|
||||||
konami.iphone.check_direction();
|
code: function (link) {
|
||||||
}
|
konami.code(link);
|
||||||
},
|
},
|
||||||
touchendHandler: function () {
|
touchmoveHandler: function (e) {
|
||||||
konami.iphone.input.push(konami.iphone.check_direction());
|
if (e.touches.length === 1 && konami.iphone.capture === true) {
|
||||||
|
var touch = e.touches[0];
|
||||||
if (konami.iphone.input.length > konami.iphone.keys.length) konami.iphone.input.shift();
|
konami.iphone.stop_x = touch.pageX;
|
||||||
|
konami.iphone.stop_y = touch.pageY;
|
||||||
if (konami.iphone.input.length === konami.iphone.keys.length) {
|
konami.iphone.tap = false;
|
||||||
var match = true;
|
konami.iphone.capture = false;
|
||||||
for (var i = 0; i < konami.iphone.keys.length; i++) {
|
konami.iphone.check_direction();
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
touchendHandler: function () {
|
||||||
|
konami.iphone.input.push(konami.iphone.check_direction());
|
||||||
|
|
||||||
typeof callback === "string" && konami.load(callback);
|
if (konami.iphone.input.length > konami.iphone.keys.length)
|
||||||
if (typeof callback === "function") {
|
konami.iphone.input.shift();
|
||||||
konami.code = callback;
|
|
||||||
konami.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
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") {
|
||||||
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
module.exports = Konami;
|
||||||
module.exports = Konami;
|
|
||||||
} else {
|
} else {
|
||||||
if (typeof define === 'function' && define.amd) {
|
if (typeof define === "function" && define.amd) {
|
||||||
define([], function() {
|
define([], function () {
|
||||||
return Konami;
|
return Konami;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
window.Konami = Konami;
|
window.Konami = Konami;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/7584432/7586812/css/fonts.css" />
|
<link
|
||||||
<script type="text/javascript">$('#searchbox').hide(0);</script>
|
rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
href="https://cloud.typography.com/7584432/7586812/css/fonts.css"
|
||||||
|
/>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$("#searchbox").hide(0);
|
||||||
|
</script>
|
||||||
<!--Alabaster (krTheme++) Hacks -->
|
<!--Alabaster (krTheme++) Hacks -->
|
||||||
|
|
||||||
<!-- CSS Adjustments (I'm very picky.) -->
|
<!-- CSS Adjustments (I'm very picky.) -->
|
||||||
@@ -39,9 +45,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.method {
|
.method {
|
||||||
|
|
||||||
margin-bottom: 2em;
|
margin-bottom: 2em;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.si,
|
.si,
|
||||||
@@ -80,8 +84,6 @@
|
|||||||
margin-top: -1em;
|
margin-top: -1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* "Quick Search" should be not be shown for now. */
|
/* "Quick Search" should be not be shown for now. */
|
||||||
div#searchbox h3 {
|
div#searchbox h3 {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -118,10 +120,12 @@
|
|||||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-127383416-1"></script>
|
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-127383416-1"></script>
|
||||||
<script>
|
<script>
|
||||||
window.dataLayer = window.dataLayer || [];
|
window.dataLayer = window.dataLayer || [];
|
||||||
function gtag() { dataLayer.push(arguments); }
|
function gtag() {
|
||||||
gtag('js', new Date());
|
dataLayer.push(arguments);
|
||||||
|
}
|
||||||
|
gtag("js", new Date());
|
||||||
|
|
||||||
gtag('config', 'UA-127383416-1');
|
gtag("config", "UA-127383416-1");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- There are no more hacks. -->
|
<!-- There are no more hacks. -->
|
||||||
@@ -130,7 +134,10 @@
|
|||||||
|
|
||||||
<script src="{{ pathto('_static/', 1) }}/konami.js"></script>
|
<script src="{{ pathto('_static/', 1) }}/konami.js"></script>
|
||||||
<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>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -140,67 +147,94 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- GitHub Logo -->
|
<!-- GitHub Logo -->
|
||||||
<a href="https://github.com/kennethreitz/responder" class="github-corner" aria-label="View source on GitHub">
|
<a
|
||||||
<svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;"
|
href="https://github.com/kennethreitz/responder"
|
||||||
aria-hidden="true">
|
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="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"
|
<path
|
||||||
fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></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"
|
||||||
<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"
|
||||||
fill="currentColor" class="octo-body"></path>
|
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>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<style>
|
<style>
|
||||||
.github-corner:hover .octo-arm {
|
.github-corner:hover .octo-arm {
|
||||||
animation: octocat-wave 560ms ease-in-out
|
animation: octocat-wave 560ms ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes octocat-wave {
|
@keyframes octocat-wave {
|
||||||
|
|
||||||
0%,
|
0%,
|
||||||
100% {
|
100% {
|
||||||
transform: rotate(0)
|
transform: rotate(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
20%,
|
20%,
|
||||||
60% {
|
60% {
|
||||||
transform: rotate(-25deg)
|
transform: rotate(-25deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
40%,
|
40%,
|
||||||
80% {
|
80% {
|
||||||
transform: rotate(10deg)
|
transform: rotate(10deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width:500px) {
|
@media (max-width: 500px) {
|
||||||
.github-corner:hover .octo-arm {
|
.github-corner:hover .octo-arm {
|
||||||
animation: none
|
animation: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.github-corner .octo-arm {
|
.github-corner .octo-arm {
|
||||||
animation: octocat-wave 560ms ease-in-out
|
animation: octocat-wave 560ms ease-in-out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<!-- That was not a hack. That was art.
|
<!-- That was not a hack. That was art.
|
||||||
|
|
||||||
<!-- UserVoice JavaScript SDK (only needed once on a page) -->
|
<!-- 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 -->
|
<!-- A tab to launch the Classic Widget -->
|
||||||
<script>
|
<script>
|
||||||
UserVoice = window.UserVoice || [];
|
UserVoice = window.UserVoice || [];
|
||||||
UserVoice.push(['showTab', 'classic_widget', {
|
UserVoice.push([
|
||||||
mode: 'feedback',
|
"showTab",
|
||||||
primary_color: '#fa8c28',
|
"classic_widget",
|
||||||
link_color: '#0a8cc6',
|
{
|
||||||
forum_id: 913660,
|
mode: "feedback",
|
||||||
tab_label: 'Got feedback?',
|
primary_color: "#fa8c28",
|
||||||
tab_color: '#00994f',
|
link_color: "#0a8cc6",
|
||||||
tab_position: 'bottom-left',
|
forum_id: 913660,
|
||||||
tab_inverted: true
|
tab_label: "Got feedback?",
|
||||||
}]);
|
tab_color: "#00994f",
|
||||||
|
tab_position: "bottom-left",
|
||||||
|
tab_inverted: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,54 +1,93 @@
|
|||||||
<p class="logo">
|
<p class="logo">
|
||||||
<a href="{{ pathto(master_doc) }}">
|
<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>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&repo=responder&type=watch&count=true&size=large"
|
<iframe
|
||||||
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></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>
|
</p>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css" />
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css"
|
||||||
|
/>
|
||||||
<style>
|
<style>
|
||||||
.algolia-autocomplete{
|
.algolia-autocomplete {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 1.5em
|
height: 1.5em;
|
||||||
}
|
}
|
||||||
.algolia-autocomplete a{
|
.algolia-autocomplete a {
|
||||||
border-bottom: none !important;
|
border-bottom: none !important;
|
||||||
}
|
}
|
||||||
#doc_search{
|
#doc_search {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<input id="doc_search" placeholder="Search the doc" autofocus/>
|
<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({
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js"
|
||||||
|
onload="docsearch({
|
||||||
apiKey: 'ac965312db252e0496283c75c6f76f0b',
|
apiKey: 'ac965312db252e0496283c75c6f76f0b',
|
||||||
indexName: 'python-responder',
|
indexName: 'python-responder',
|
||||||
inputSelector: '#doc_search',
|
inputSelector: '#doc_search',
|
||||||
debug: false // Set debug to true if you want to inspect the dropdown
|
debug: false // Set debug to true if you want to inspect the dropdown
|
||||||
})" async></script>
|
})"
|
||||||
|
async
|
||||||
|
></script>
|
||||||
|
|
||||||
<p>
|
<p><strong>Responder</strong> is a web service framework, written for human beings.</p>
|
||||||
<strong>Responder</strong> is a web service framework, written for human beings.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Stay Informed</h3>
|
<h3>Stay Informed</h3>
|
||||||
<p>Receive updates on new releases and upcoming projects.</p>
|
<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"
|
<p>
|
||||||
frameborder="0" scrolling="0" width="200" height="20"></iframe></p>
|
<iframe
|
||||||
|
src="https://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=true"
|
||||||
<p><a href="https://twitter.com/kennethreitz" class="twitter-follow-button" data-show-count="false">Follow
|
allowtransparency="true"
|
||||||
@kennethreitz</a>
|
frameborder="0"
|
||||||
<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>
|
scrolling="0"
|
||||||
|
width="200"
|
||||||
|
height="20"
|
||||||
|
></iframe>
|
||||||
</p>
|
</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>
|
||||||
|
|
||||||
<h3>Useful Links</h3>
|
<h3>Useful Links</h3>
|
||||||
<ul>
|
<ul>
|
||||||
|
|
||||||
<li><a href="http://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
|
<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://pypi.python.org/pypi/responder">Responder @ PyPI</a></li>
|
||||||
<li><a href="http://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
|
<li><a href="http://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
|
||||||
|
|||||||
@@ -1,54 +1,93 @@
|
|||||||
<p class="logo">
|
<p class="logo">
|
||||||
<a href="{{ pathto(master_doc) }}">
|
<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>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&repo=responder&type=watch&count=true&size=large"
|
<iframe
|
||||||
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></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>
|
</p>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css" />
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css"
|
||||||
|
/>
|
||||||
<style>
|
<style>
|
||||||
.algolia-autocomplete{
|
.algolia-autocomplete {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 1.5em
|
height: 1.5em;
|
||||||
}
|
}
|
||||||
.algolia-autocomplete a{
|
.algolia-autocomplete a {
|
||||||
border-bottom: none !important;
|
border-bottom: none !important;
|
||||||
}
|
}
|
||||||
#doc_search{
|
#doc_search {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<input id="doc_search" placeholder="Search the doc" autofocus/>
|
<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({
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js"
|
||||||
|
onload="docsearch({
|
||||||
apiKey: 'ac965312db252e0496283c75c6f76f0b',
|
apiKey: 'ac965312db252e0496283c75c6f76f0b',
|
||||||
indexName: 'python-responder',
|
indexName: 'python-responder',
|
||||||
inputSelector: '#doc_search',
|
inputSelector: '#doc_search',
|
||||||
debug: false // Set debug to true if you want to inspect the dropdown
|
debug: false // Set debug to true if you want to inspect the dropdown
|
||||||
})" async></script>
|
})"
|
||||||
|
async
|
||||||
|
></script>
|
||||||
|
|
||||||
<p>
|
<p><strong>Responder</strong> is a web service framework, written for human beings.</p>
|
||||||
<strong>Responder</strong> is a web service framework, written for human beings.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Stay Informed</h3>
|
<h3>Stay Informed</h3>
|
||||||
<p>Receive updates on new releases and upcoming projects.</p>
|
<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"
|
<p>
|
||||||
frameborder="0" scrolling="0" width="200" height="20"></iframe></p>
|
<iframe
|
||||||
|
src="https://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=true"
|
||||||
<p><a href="https://twitter.com/kennethreitz" class="twitter-follow-button" data-show-count="false">Follow
|
allowtransparency="true"
|
||||||
@kennethreitz</a>
|
frameborder="0"
|
||||||
<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>
|
scrolling="0"
|
||||||
|
width="200"
|
||||||
|
height="20"
|
||||||
|
></iframe>
|
||||||
</p>
|
</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>
|
||||||
|
|
||||||
<h3>Useful Links</h3>
|
<h3>Useful Links</h3>
|
||||||
<ul>
|
<ul>
|
||||||
|
|
||||||
<li><a href="http://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
|
<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://pypi.python.org/pypi/responder">Responder @ PyPI</a></li>
|
||||||
<li><a href="http://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
|
<li><a href="http://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
|
||||||
|
|||||||
+3
-15
@@ -20,23 +20,11 @@
|
|||||||
# -- Project information -----------------------------------------------------
|
# -- Project information -----------------------------------------------------
|
||||||
|
|
||||||
project = "responder"
|
project = "responder"
|
||||||
copyright = "2018, A Kenneth Reitz project"
|
copyright = "2024, A Kenneth Reitz project"
|
||||||
author = "Kenneth Reitz"
|
author = "Kenneth Reitz"
|
||||||
|
|
||||||
# The short X.Y version
|
# The short X.Y version
|
||||||
import os
|
version = ""
|
||||||
|
|
||||||
# Path hackery to get current version number.
|
|
||||||
here = os.path.abspath(os.path.dirname(__file__))
|
|
||||||
|
|
||||||
about = {}
|
|
||||||
with open(os.path.join(here, "..", "..", "responder", "__version__.py")) as f:
|
|
||||||
exec(f.read(), about)
|
|
||||||
|
|
||||||
version = about["__version__"]
|
|
||||||
# The full version, including alpha/beta/rc tags
|
|
||||||
release = about["__version__"]
|
|
||||||
|
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
|
|
||||||
@@ -76,7 +64,7 @@ master_doc = "index"
|
|||||||
#
|
#
|
||||||
# This is also used if you do content translation via gettext catalogs.
|
# This is also used if you do content translation via gettext catalogs.
|
||||||
# Usually you set "language" from the command line for these cases.
|
# 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
|
# List of patterns, relative to source directory, that match files and
|
||||||
# directories to ignore when looking for source files.
|
# directories to ignore when looking for source files.
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ A familiar HTTP Service Framework
|
|||||||
|
|
||||||
|Build Status| |image1| |image2| |image3| |image4| |image5|
|
|Build Status| |image1| |image2| |image3| |image4| |image5|
|
||||||
|
|
||||||
.. |Build Status| image:: https://travis-ci.org/kennethreitz/responder.svg?branch=master
|
.. |Build Status| image:: https://github.com/kennethreitz/responder/actions/workflows/test.yaml/badge.svg
|
||||||
:target: https://travis-ci.org/kennethreitz/responder
|
:target: https://github.com/kennethreitz/responder/actions/workflows/test.yaml
|
||||||
.. |image1| image:: https://img.shields.io/pypi/v/responder.svg
|
.. |image1| image:: https://img.shields.io/pypi/v/responder.svg
|
||||||
:target: https://pypi.org/project/responder/
|
:target: https://pypi.org/project/responder/
|
||||||
.. |image2| image:: https://img.shields.io/pypi/l/responder.svg
|
.. |image2| image:: https://img.shields.io/pypi/l/responder.svg
|
||||||
@@ -83,7 +83,7 @@ Testimonials
|
|||||||
|
|
||||||
|
|
||||||
.. _Django REST Framework: https://www.django-rest-framework.org/
|
.. _Django REST Framework: https://www.django-rest-framework.org/
|
||||||
.. _Two Scoops of Django: https://www.twoscoopspress.com/products/two-scoops-of-django-1-11
|
.. _Two Scoops of Django: https://www.feldroy.com/two-scoops-press#two-scoops-of-django
|
||||||
|
|
||||||
User Guides
|
User Guides
|
||||||
-----------
|
-----------
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ Supported directives:
|
|||||||
* ``secure`` - Defaults to ``False``.
|
* ``secure`` - Defaults to ``False``.
|
||||||
* ``httponly`` - Defaults to ``True``.
|
* ``httponly`` - Defaults to ``True``.
|
||||||
|
|
||||||
For more information see `directives <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Directives>`_
|
For more information see `directives <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie>`_
|
||||||
|
|
||||||
|
|
||||||
Using Cookie-Based Sessions
|
Using Cookie-Based Sessions
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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()
|
||||||
+121
@@ -0,0 +1,121 @@
|
|||||||
|
[build-system]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
requires = [
|
||||||
|
"setuptools>=42", # At least v42 of setuptools required.
|
||||||
|
"versioningit",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 90
|
||||||
|
|
||||||
|
extend-exclude = [
|
||||||
|
"bin/mkdeb.py",
|
||||||
|
"docs/source/conf.py",
|
||||||
|
"setup.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."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.versioningit]
|
||||||
|
|
||||||
|
[tool.poe.tasks]
|
||||||
|
|
||||||
|
check = [
|
||||||
|
"lint",
|
||||||
|
"test",
|
||||||
|
]
|
||||||
|
|
||||||
|
docs-autobuild = [
|
||||||
|
{ cmd = "sphinx-autobuild --open-browser --watch 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" ]
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
[pytest]
|
|
||||||
;addopts= -rsxX -s -v --strict
|
|
||||||
filterwarnings =
|
|
||||||
error::UserWarning
|
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
build:
|
build:
|
||||||
image: latest
|
image: latest
|
||||||
|
|
||||||
python:
|
python:
|
||||||
version: 3.6
|
version: 3.6
|
||||||
|
|||||||
+16
-1
@@ -1,2 +1,17 @@
|
|||||||
from .core import *
|
from importlib.metadata import PackageNotFoundError, version
|
||||||
|
|
||||||
from . import ext
|
from . import ext
|
||||||
|
from .core import API, Request, Response
|
||||||
|
|
||||||
|
try:
|
||||||
|
__version__ = version("responder")
|
||||||
|
except PackageNotFoundError: # pragma: no cover
|
||||||
|
__version__ = "unknown"
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"API",
|
||||||
|
"Request",
|
||||||
|
"Response",
|
||||||
|
"ext",
|
||||||
|
"__version__",
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
from .cli import main
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
__version__ = "2.0.4"
|
|
||||||
+36
-34
@@ -1,41 +1,35 @@
|
|||||||
import json
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import jinja2
|
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from starlette.exceptions import ExceptionMiddleware
|
from starlette.exceptions import ExceptionMiddleware
|
||||||
from starlette.middleware.wsgi import WSGIMiddleware
|
|
||||||
from starlette.middleware.errors import ServerErrorMiddleware
|
|
||||||
from starlette.middleware.cors import CORSMiddleware
|
from starlette.middleware.cors import CORSMiddleware
|
||||||
|
from starlette.middleware.errors import ServerErrorMiddleware
|
||||||
from starlette.middleware.gzip import GZipMiddleware
|
from starlette.middleware.gzip import GZipMiddleware
|
||||||
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
|
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
|
||||||
from starlette.middleware.trustedhost import TrustedHostMiddleware
|
|
||||||
from starlette.middleware.sessions import SessionMiddleware
|
from starlette.middleware.sessions import SessionMiddleware
|
||||||
from starlette.routing import Lifespan
|
from starlette.middleware.trustedhost import TrustedHostMiddleware
|
||||||
from starlette.staticfiles import StaticFiles
|
|
||||||
from starlette.testclient import TestClient
|
from starlette.testclient import TestClient
|
||||||
from starlette.websockets import WebSocket
|
|
||||||
|
|
||||||
from . import models, status_codes
|
from . import status_codes
|
||||||
from .background import BackgroundQueue
|
from .background import BackgroundQueue
|
||||||
|
from .ext.schema import OpenAPISchema as OpenAPISchema
|
||||||
from .formats import get_formats
|
from .formats import get_formats
|
||||||
from .routes import Router
|
from .routes import Router
|
||||||
from .statics import DEFAULT_API_THEME, DEFAULT_CORS_PARAMS, DEFAULT_SECRET_KEY
|
|
||||||
from .ext.schema import Schema as OpenAPISchema
|
|
||||||
from .staticfiles import StaticFiles
|
from .staticfiles import StaticFiles
|
||||||
|
from .statics import DEFAULT_CORS_PARAMS, DEFAULT_OPENAPI_THEME, DEFAULT_SECRET_KEY
|
||||||
from .templates import Templates
|
from .templates import Templates
|
||||||
|
|
||||||
|
|
||||||
class API:
|
class API:
|
||||||
"""The primary web-service class.
|
"""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 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 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 auto_escape: If ``True``, HTML and XML templates will automatically be escaped.
|
||||||
:param enable_hsts: If ``True``, send all responses to HTTPS URLs.
|
: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
|
status_codes = status_codes
|
||||||
|
|
||||||
@@ -44,13 +38,12 @@ class API:
|
|||||||
*,
|
*,
|
||||||
debug=False,
|
debug=False,
|
||||||
title=None,
|
title=None,
|
||||||
version="1.0",
|
version=None,
|
||||||
description=None,
|
description=None,
|
||||||
terms_of_service=None,
|
terms_of_service=None,
|
||||||
contact=None,
|
contact=None,
|
||||||
license=None,
|
license=None, # noqa: A002
|
||||||
openapi=None,
|
openapi=None,
|
||||||
openapi_version="3.0.2",
|
|
||||||
openapi_route="/schema.yml",
|
openapi_route="/schema.yml",
|
||||||
static_dir="static",
|
static_dir="static",
|
||||||
static_route="/static",
|
static_route="/static",
|
||||||
@@ -62,6 +55,7 @@ class API:
|
|||||||
cors=False,
|
cors=False,
|
||||||
cors_params=DEFAULT_CORS_PARAMS,
|
cors_params=DEFAULT_CORS_PARAMS,
|
||||||
allowed_hosts=None,
|
allowed_hosts=None,
|
||||||
|
openapi_theme=DEFAULT_OPENAPI_THEME,
|
||||||
):
|
):
|
||||||
self.background = BackgroundQueue()
|
self.background = BackgroundQueue()
|
||||||
|
|
||||||
@@ -86,7 +80,7 @@ class API:
|
|||||||
# if not debug:
|
# if not debug:
|
||||||
# raise RuntimeError(
|
# raise RuntimeError(
|
||||||
# "You need to specify `allowed_hosts` when debug is set to False"
|
# "You need to specify `allowed_hosts` when debug is set to False"
|
||||||
# )
|
# ) # noqa: ERA001
|
||||||
allowed_hosts = ["*"]
|
allowed_hosts = ["*"]
|
||||||
self.allowed_hosts = allowed_hosts
|
self.allowed_hosts = allowed_hosts
|
||||||
|
|
||||||
@@ -120,7 +114,7 @@ class API:
|
|||||||
app=self,
|
app=self,
|
||||||
title=title,
|
title=title,
|
||||||
version=version,
|
version=version,
|
||||||
openapi=openapi_version,
|
openapi=openapi,
|
||||||
docs_route=docs_route,
|
docs_route=docs_route,
|
||||||
description=description,
|
description=description,
|
||||||
terms_of_service=terms_of_service,
|
terms_of_service=terms_of_service,
|
||||||
@@ -128,6 +122,7 @@ class API:
|
|||||||
license=license,
|
license=license,
|
||||||
openapi_route=openapi_route,
|
openapi_route=openapi_route,
|
||||||
static_route=static_route,
|
static_route=static_route,
|
||||||
|
openapi_theme=openapi_theme,
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Update docs for templates
|
# TODO: Update docs for templates
|
||||||
@@ -172,11 +167,12 @@ class API:
|
|||||||
"""Given a path portion of a URL, tests that it matches against any registered route.
|
"""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.
|
:param path: The path portion of a URL, to test all known routes against.
|
||||||
"""
|
""" # noqa: E501 (Line too long)
|
||||||
for route in self.router.routes:
|
for route in self.router.routes:
|
||||||
match, _ = route.matches(path)
|
match, _ = route.matches(path)
|
||||||
if match:
|
if match:
|
||||||
return route
|
return route
|
||||||
|
return None
|
||||||
|
|
||||||
def add_route(
|
def add_route(
|
||||||
self,
|
self,
|
||||||
@@ -194,8 +190,9 @@ class API:
|
|||||||
:param route: A string representation of the route.
|
:param route: A string representation of the route.
|
||||||
:param endpoint: The endpoint for the route -- can be a callable, or a class.
|
: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 default: If ``True``, all unknown requests will route to this view.
|
||||||
:param static: If ``True``, and no endpoint was passed, render "static/index.html", and it will become a default route.
|
:param static: If ``True``, and no endpoint was passed, render "static/index.html".
|
||||||
"""
|
Also, it will become a default route.
|
||||||
|
""" # noqa: E501
|
||||||
|
|
||||||
# Path
|
# Path
|
||||||
if static:
|
if static:
|
||||||
@@ -231,7 +228,8 @@ class API:
|
|||||||
:param resp: The Response to mutate.
|
:param resp: The Response to mutate.
|
||||||
:param location: The location of the redirect.
|
:param location: The location of the redirect.
|
||||||
:param set_text: If ``True``, sets the Redirect body content automatically.
|
: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)
|
resp.redirect(location, set_text=set_text, status_code=status_code)
|
||||||
|
|
||||||
@@ -264,7 +262,7 @@ class API:
|
|||||||
:param handler: The function to run. Can be either a function or a coroutine.
|
:param handler: The function to run. Can be either a function or a coroutine.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.router.lifespan_handler.add_event_handler(event_type, handler)
|
self.router.add_event_handler(event_type, handler)
|
||||||
|
|
||||||
def route(self, route=None, **options):
|
def route(self, route=None, **options):
|
||||||
"""Decorator for creating new routes around function and class definitions.
|
"""Decorator for creating new routes around function and class definitions.
|
||||||
@@ -286,13 +284,15 @@ class API:
|
|||||||
def mount(self, route, app):
|
def mount(self, route, app):
|
||||||
"""Mounts an WSGI / ASGI application at a given route.
|
"""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.
|
:param app: The other WSGI / ASGI app.
|
||||||
"""
|
"""
|
||||||
self.router.apps.update({route: app})
|
self.router.apps.update({route: app})
|
||||||
|
|
||||||
def session(self, base_url="http://;"):
|
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 Requests session object,
|
||||||
|
able to send HTTP requests to the Responder application.
|
||||||
|
|
||||||
:param base_url: The URL to mount the connection adaptor to.
|
:param base_url: The URL to mount the connection adaptor to.
|
||||||
"""
|
"""
|
||||||
@@ -312,11 +312,13 @@ class API:
|
|||||||
|
|
||||||
def template(self, filename, *args, **kwargs):
|
def template(self, filename, *args, **kwargs):
|
||||||
"""Renders the given `jinja2 <http://jinja.pocoo.org/docs/>`_ template, with provided values supplied.
|
"""Renders the given `jinja2 <http://jinja.pocoo.org/docs/>`_ template, with provided values supplied.
|
||||||
|
|
||||||
Note: The current ``api`` instance is by default passed into the view. This is set in the dict ``api.jinja_values_base``.
|
Note: The current ``api`` instance is by default passed into the view. This is set in the dict ``api.jinja_values_base``.
|
||||||
|
|
||||||
:param filename: The filename of the jinja2 template, in ``templates_dir``.
|
:param filename: The filename of the jinja2 template, in ``templates_dir``.
|
||||||
:param *args: Data to pass into the template.
|
:param *args: Data to pass into the template.
|
||||||
:param *kwargs: Date to pass into the template.
|
:param *kwargs: Date to pass into the template.
|
||||||
"""
|
""" # noqa: E501
|
||||||
return self.templates.render(filename, *args, **kwargs)
|
return self.templates.render(filename, *args, **kwargs)
|
||||||
|
|
||||||
def template_string(self, source, *args, **kwargs):
|
def template_string(self, source, *args, **kwargs):
|
||||||
@@ -325,7 +327,7 @@ class API:
|
|||||||
:param source: The template to use.
|
:param source: The template to use.
|
||||||
:param *args: Data to pass into the template.
|
:param *args: Data to pass into the template.
|
||||||
:param **kwargs: Data to pass into the template.
|
:param **kwargs: Data to pass into the template.
|
||||||
"""
|
""" # noqa: E501
|
||||||
return self.templates.render_string(source, *args, **kwargs)
|
return self.templates.render_string(source, *args, **kwargs)
|
||||||
|
|
||||||
def serve(self, *, address=None, port=None, debug=False, **options):
|
def serve(self, *, address=None, port=None, debug=False, **options):
|
||||||
@@ -335,13 +337,13 @@ class API:
|
|||||||
|
|
||||||
:param address: The address to bind to.
|
: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 port: The port to bind to. If none is provided, one will be selected at random.
|
||||||
:param debug: Run uvicorn server in debug mode.
|
:param debug: Whether to run application in debug mode.
|
||||||
:param options: Additional keyword arguments to send to ``uvicorn.run()``.
|
:param options: Additional keyword arguments to send to ``uvicorn.run()``.
|
||||||
"""
|
""" # noqa: E501
|
||||||
|
|
||||||
if "PORT" in os.environ:
|
if "PORT" in os.environ:
|
||||||
if address is None:
|
if address is None:
|
||||||
address = "0.0.0.0"
|
address = "0.0.0.0" # noqa: S104
|
||||||
port = int(os.environ["PORT"])
|
port = int(os.environ["PORT"])
|
||||||
|
|
||||||
if address is None:
|
if address is None:
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import functools
|
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from starlette.concurrency import run_in_threadpool
|
from starlette.concurrency import run_in_threadpool
|
||||||
|
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ class BackgroundQueue:
|
|||||||
def on_future_done(fs):
|
def on_future_done(fs):
|
||||||
try:
|
try:
|
||||||
fs.result()
|
fs.result()
|
||||||
except:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
def do_task(*args, **kwargs):
|
def do_task(*args, **kwargs):
|
||||||
@@ -40,5 +40,4 @@ class BackgroundQueue:
|
|||||||
async def __call__(self, func, *args, **kwargs) -> None:
|
async def __call__(self, func, *args, **kwargs) -> None:
|
||||||
if asyncio.iscoroutinefunction(func):
|
if asyncio.iscoroutinefunction(func):
|
||||||
return await asyncio.ensure_future(func(*args, **kwargs))
|
return await asyncio.ensure_future(func(*args, **kwargs))
|
||||||
else:
|
return await run_in_threadpool(func, *args, **kwargs)
|
||||||
return await run_in_threadpool(func, *args, **kwargs)
|
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
"""Responder.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
responder
|
|
||||||
responder run [--build] [--debug] <module>
|
|
||||||
responder build
|
|
||||||
responder --version
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h --help Show this screen.
|
|
||||||
-v --version Show version.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
import docopt
|
|
||||||
from .__version__ import __version__
|
|
||||||
|
|
||||||
|
|
||||||
def cli():
|
|
||||||
args = docopt.docopt(
|
|
||||||
__doc__, argv=None, help=True, version=__version__, options_first=False
|
|
||||||
)
|
|
||||||
|
|
||||||
module = args["<module>"]
|
|
||||||
build = args["build"] or args["--build"]
|
|
||||||
run = args["run"]
|
|
||||||
|
|
||||||
if build:
|
|
||||||
os.system("npm run build")
|
|
||||||
|
|
||||||
if run:
|
|
||||||
split_module = module.split(":")
|
|
||||||
|
|
||||||
if len(split_module) > 1:
|
|
||||||
module = split_module[0]
|
|
||||||
prop = split_module[1]
|
|
||||||
else:
|
|
||||||
prop = "api"
|
|
||||||
|
|
||||||
app = __import__(module)
|
|
||||||
getattr(app, prop).run()
|
|
||||||
+6
-1
@@ -1,3 +1,8 @@
|
|||||||
from .api import API
|
from .api import API
|
||||||
from .models import Request, Response
|
from .models import Request, Response
|
||||||
from .cli import cli
|
|
||||||
|
__all__ = [
|
||||||
|
"API",
|
||||||
|
"Request",
|
||||||
|
"Response",
|
||||||
|
]
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
from .graphql import GraphQLView
|
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
import json
|
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
from graphql_server import default_format_error, encode_execution_results, json_encode
|
|
||||||
|
|
||||||
from .templates import GRAPHIQL
|
|
||||||
|
|
||||||
|
|
||||||
class GraphQLView:
|
|
||||||
def __init__(self, *, api, schema):
|
|
||||||
self.api = api
|
|
||||||
self.schema = schema
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def _resolve_graphql_query(req):
|
|
||||||
# TODO: Get variables and operation_name from form data, params, request text?
|
|
||||||
|
|
||||||
if "json" in req.mimetype:
|
|
||||||
json_media = await req.media("json")
|
|
||||||
return (
|
|
||||||
json_media["query"],
|
|
||||||
json_media.get("variables"),
|
|
||||||
json_media.get("operationName"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# 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"], None, None
|
|
||||||
# if "q" in req.media("form"):
|
|
||||||
# return req.media("form")["q"], None, None
|
|
||||||
|
|
||||||
# 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).
|
|
||||||
# TODO: Make some assertions about content-type here.
|
|
||||||
return req.text, None, None
|
|
||||||
|
|
||||||
async def graphql_response(self, req, resp, schema):
|
|
||||||
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
|
|
||||||
|
|
||||||
query, variables, operation_name = await self._resolve_graphql_query(req)
|
|
||||||
context = {"request": req, "response": resp}
|
|
||||||
result = schema.execute(
|
|
||||||
query, variables=variables, operation_name=operation_name, context=context
|
|
||||||
)
|
|
||||||
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)
|
|
||||||
|
|
||||||
async def on_request(self, req, resp):
|
|
||||||
await self.graphql_response(req, resp, self.schema)
|
|
||||||
|
|
||||||
async def __call__(self, req, resp):
|
|
||||||
await self.on_request(req, resp)
|
|
||||||
@@ -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()
|
|
||||||
@@ -1,18 +1,14 @@
|
|||||||
import os
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import apistar
|
|
||||||
import jinja2
|
|
||||||
import yaml
|
|
||||||
from apispec import APISpec, yaml_utils
|
from apispec import APISpec, yaml_utils
|
||||||
from apispec.ext.marshmallow import MarshmallowPlugin
|
from apispec.ext.marshmallow import MarshmallowPlugin
|
||||||
|
|
||||||
from responder.statics import DEFAULT_API_THEME
|
|
||||||
from responder.staticfiles import StaticFiles
|
|
||||||
from responder import status_codes
|
from responder import status_codes
|
||||||
|
from responder.statics import API_THEMES, DEFAULT_OPENAPI_THEME
|
||||||
|
from responder.templates import Templates
|
||||||
|
|
||||||
|
|
||||||
class Schema:
|
class OpenAPISchema:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
app,
|
app,
|
||||||
@@ -22,11 +18,12 @@ class Schema:
|
|||||||
description=None,
|
description=None,
|
||||||
terms_of_service=None,
|
terms_of_service=None,
|
||||||
contact=None,
|
contact=None,
|
||||||
license=None,
|
license=None, # noqa: A002
|
||||||
openapi=None,
|
openapi=None,
|
||||||
openapi_route="/schema.yml",
|
openapi_route="/schema.yml",
|
||||||
docs_route="/docs/",
|
docs_route="/docs/",
|
||||||
static_route="/static",
|
static_route="/static",
|
||||||
|
openapi_theme=DEFAULT_OPENAPI_THEME,
|
||||||
):
|
):
|
||||||
self.app = app
|
self.app = app
|
||||||
self.schemas = {}
|
self.schemas = {}
|
||||||
@@ -40,7 +37,9 @@ class Schema:
|
|||||||
self.openapi_version = openapi
|
self.openapi_version = openapi
|
||||||
self.openapi_route = openapi_route
|
self.openapi_route = openapi_route
|
||||||
|
|
||||||
self.docs_theme = DEFAULT_API_THEME
|
self.docs_theme = (
|
||||||
|
openapi_theme if openapi_theme in API_THEMES else DEFAULT_OPENAPI_THEME
|
||||||
|
)
|
||||||
self.docs_route = docs_route
|
self.docs_route = docs_route
|
||||||
|
|
||||||
self.plugins = [MarshmallowPlugin()] if plugins is None else plugins
|
self.plugins = [MarshmallowPlugin()] if plugins is None else plugins
|
||||||
@@ -51,17 +50,13 @@ class Schema:
|
|||||||
if self.docs_route is not None:
|
if self.docs_route is not None:
|
||||||
self.app.add_route(self.docs_route, self.docs_response)
|
self.app.add_route(self.docs_route, self.docs_response)
|
||||||
|
|
||||||
theme_path = (
|
theme_path = (Path(__file__).parent / "docs").resolve()
|
||||||
Path(apistar.__file__).parent / "themes" / self.docs_theme / "static"
|
self.templates = Templates(directory=theme_path)
|
||||||
).resolve()
|
|
||||||
|
|
||||||
self.static_route = static_route
|
self.static_route = static_route
|
||||||
|
|
||||||
self.app.static_app.add_directory(theme_path)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _apispec(self):
|
def _apispec(self):
|
||||||
|
|
||||||
info = {}
|
info = {}
|
||||||
if self.description is not None:
|
if self.description is not None:
|
||||||
info["description"] = self.description
|
info["description"] = self.description
|
||||||
@@ -82,9 +77,7 @@ class Schema:
|
|||||||
|
|
||||||
for route in self.app.router.routes:
|
for route in self.app.router.routes:
|
||||||
if route.description:
|
if route.description:
|
||||||
operations = yaml_utils.load_operations_from_docstring(
|
operations = yaml_utils.load_operations_from_docstring(route.description)
|
||||||
route.description
|
|
||||||
)
|
|
||||||
spec.path(path=route.route, operations=operations)
|
spec.path(path=route.route, operations=operations)
|
||||||
|
|
||||||
for name, schema in self.schemas.items():
|
for name, schema in self.schemas.items():
|
||||||
@@ -97,7 +90,7 @@ class Schema:
|
|||||||
return self._apispec.to_yaml()
|
return self._apispec.to_yaml()
|
||||||
|
|
||||||
def add_schema(self, name, schema, check_existing=True):
|
def add_schema(self, name, schema, check_existing=True):
|
||||||
"""Adds a mashmallow schema to the API specification."""
|
"""Adds a marshmallow schema to the API specification."""
|
||||||
if check_existing:
|
if check_existing:
|
||||||
assert name not in self.schemas
|
assert name not in self.schemas
|
||||||
|
|
||||||
@@ -124,25 +117,10 @@ class Schema:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def docs(self):
|
def docs(self):
|
||||||
|
return self.templates.render(
|
||||||
loader = jinja2.PrefixLoader(
|
f"{self.docs_theme}.html",
|
||||||
{
|
title=self.title,
|
||||||
self.docs_theme: jinja2.PackageLoader(
|
version=self.version,
|
||||||
"apistar", os.path.join("themes", self.docs_theme, "templates")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
env = jinja2.Environment(autoescape=True, loader=loader)
|
|
||||||
document = apistar.document.Document()
|
|
||||||
document.content = yaml.safe_load(self.openapi)
|
|
||||||
|
|
||||||
template = env.get_template("/".join([self.docs_theme, "index.html"]))
|
|
||||||
|
|
||||||
return template.render(
|
|
||||||
document=document,
|
|
||||||
langs=["javascript", "python"],
|
|
||||||
code_style=None,
|
|
||||||
static_url=self.static_url,
|
|
||||||
schema_url="/schema.yml",
|
schema_url="/schema.yml",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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@next/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>
|
||||||
+34
-38
@@ -9,10 +9,10 @@ from .models import QueryDict
|
|||||||
|
|
||||||
async def format_form(r, encode=False):
|
async def format_form(r, encode=False):
|
||||||
if encode:
|
if encode:
|
||||||
pass
|
return None
|
||||||
elif "multipart/form-data" in r.headers.get("Content-Type"):
|
if "multipart/form-data" in r.headers.get("Content-Type"):
|
||||||
decode = decoder.MultipartDecoder(await r.content, r.mimetype)
|
decode = decoder.MultipartDecoder(await r.content, r.mimetype)
|
||||||
querys = list()
|
queries = []
|
||||||
for part in decode.parts:
|
for part in decode.parts:
|
||||||
header = part.headers.get(b"Content-Disposition").decode("utf-8")
|
header = part.headers.get(b"Content-Disposition").decode("utf-8")
|
||||||
text = part.text
|
text = part.text
|
||||||
@@ -22,63 +22,59 @@ async def format_form(r, encode=False):
|
|||||||
if len(split) > 1:
|
if len(split) > 1:
|
||||||
key = split[1]
|
key = split[1]
|
||||||
key = key[1:-1]
|
key = key[1:-1]
|
||||||
querys.append((key, text))
|
queries.append((key, text))
|
||||||
|
|
||||||
content = urlencode(querys)
|
content = urlencode(queries)
|
||||||
return QueryDict(content)
|
return QueryDict(content)
|
||||||
else:
|
return QueryDict(await r.text)
|
||||||
return QueryDict(await r.text)
|
|
||||||
|
|
||||||
|
|
||||||
async def format_yaml(r, encode=False):
|
async def format_yaml(r, encode=False):
|
||||||
if encode:
|
if encode:
|
||||||
r.headers.update({"Content-Type": "application/x-yaml"})
|
r.headers.update({"Content-Type": "application/x-yaml"})
|
||||||
return yaml.safe_dump(r.media)
|
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):
|
async def format_json(r, encode=False):
|
||||||
if encode:
|
if encode:
|
||||||
r.headers.update({"Content-Type": "application/json"})
|
r.headers.update({"Content-Type": "application/json"})
|
||||||
return json.dumps(r.media)
|
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):
|
async def format_files(r, encode=False):
|
||||||
if encode:
|
if encode:
|
||||||
pass
|
return None
|
||||||
else:
|
decoded = decoder.MultipartDecoder(await r.content, r.mimetype)
|
||||||
decoded = decoder.MultipartDecoder(await r.content, r.mimetype)
|
dump = {}
|
||||||
dump = {}
|
for part in decoded.parts:
|
||||||
for part in decoded.parts:
|
header = part.headers[b"Content-Disposition"].decode("utf-8")
|
||||||
header = part.headers[b"Content-Disposition"].decode("utf-8")
|
mimetype = part.headers.get(b"Content-Type", None)
|
||||||
mimetype = part.headers.get(b"Content-Type", None)
|
filename = None
|
||||||
filename = None
|
|
||||||
|
|
||||||
for section in [h.strip() for h in header.split(";")]:
|
for section in [h.strip() for h in header.split(";")]:
|
||||||
split = section.split("=")
|
split = section.split("=")
|
||||||
if len(split) > 1:
|
if len(split) > 1:
|
||||||
key = split[0]
|
key = split[0]
|
||||||
value = split[1]
|
value = split[1]
|
||||||
|
|
||||||
value = value[1:-1]
|
value = value[1:-1]
|
||||||
|
|
||||||
if key == "filename":
|
if key == "filename":
|
||||||
filename = value
|
filename = value
|
||||||
elif key == "name":
|
elif key == "name":
|
||||||
formname = value
|
formname = value
|
||||||
|
|
||||||
if mimetype is None:
|
if mimetype is None:
|
||||||
dump[formname] = part.content
|
dump[formname] = part.content
|
||||||
else:
|
else:
|
||||||
dump[formname] = {
|
dump[formname] = {
|
||||||
"filename": filename,
|
"filename": filename,
|
||||||
"content": part.content,
|
"content": part.content,
|
||||||
"content-type": mimetype.decode("utf-8"),
|
"content-type": mimetype.decode("utf-8"),
|
||||||
}
|
}
|
||||||
return dump
|
return dump
|
||||||
|
|
||||||
|
|
||||||
def get_formats():
|
def get_formats():
|
||||||
|
|||||||
+26
-26
@@ -1,29 +1,24 @@
|
|||||||
import functools
|
import functools
|
||||||
import io
|
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
import typing as t
|
||||||
import gzip
|
|
||||||
from urllib.parse import parse_qs
|
|
||||||
from base64 import b64decode
|
|
||||||
from http.cookies import SimpleCookie
|
from http.cookies import SimpleCookie
|
||||||
|
from urllib.parse import parse_qs
|
||||||
|
|
||||||
import chardet
|
import chardet
|
||||||
import rfc3986
|
import rfc3986
|
||||||
import graphene
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
from requests.structures import CaseInsensitiveDict
|
|
||||||
from requests.cookies import RequestsCookieJar
|
from requests.cookies import RequestsCookieJar
|
||||||
|
from requests.structures import CaseInsensitiveDict
|
||||||
from starlette.datastructures import MutableHeaders
|
from starlette.requests import Request as StarletteRequest
|
||||||
from starlette.requests import Request as StarletteRequest, State
|
from starlette.requests import State
|
||||||
from starlette.responses import (
|
from starlette.responses import (
|
||||||
Response as StarletteResponse,
|
Response as StarletteResponse,
|
||||||
|
)
|
||||||
|
from starlette.responses import (
|
||||||
StreamingResponse as StarletteStreamingResponse,
|
StreamingResponse as StarletteStreamingResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .status_codes import HTTP_200, HTTP_301
|
|
||||||
from .statics import DEFAULT_ENCODING
|
from .statics import DEFAULT_ENCODING
|
||||||
|
from .status_codes import HTTP_301
|
||||||
|
|
||||||
|
|
||||||
class QueryDict(dict):
|
class QueryDict(dict):
|
||||||
@@ -213,6 +208,7 @@ class Request:
|
|||||||
async def declared_encoding(self):
|
async def declared_encoding(self):
|
||||||
if "Encoding" in self.headers:
|
if "Encoding" in self.headers:
|
||||||
return self.headers["Encoding"]
|
return self.headers["Encoding"]
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
async def apparent_encoding(self):
|
async def apparent_encoding(self):
|
||||||
@@ -232,20 +228,20 @@ class Request:
|
|||||||
"""Returns ``True`` if the incoming Request accepts the given ``content_type``."""
|
"""Returns ``True`` if the incoming Request accepts the given ``content_type``."""
|
||||||
return content_type in self.headers.get("Accept", [])
|
return content_type in self.headers.get("Accept", [])
|
||||||
|
|
||||||
async def media(self, format=None):
|
async def media(self, format: t.Union[str, t.Callable] = None): # noqa: A001, A002
|
||||||
"""Renders incoming json/yaml/form data as Python objects. Must be awaited.
|
"""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:
|
if format is None:
|
||||||
format = "yaml" if "yaml" in self.mimetype or "" else "json"
|
format = "yaml" if "yaml" in self.mimetype or "" else "json" # noqa: A001
|
||||||
format = "form" if "form" in self.mimetype or "" else format
|
format = "form" if "form" in self.mimetype or "" else format # noqa: A001
|
||||||
|
|
||||||
if format in self.formats:
|
if format in self.formats:
|
||||||
return await self.formats[format](self)
|
return await self.formats[format](self)
|
||||||
else:
|
return await format(self)
|
||||||
return await format(self)
|
|
||||||
|
|
||||||
|
|
||||||
def content_setter(mimetype):
|
def content_setter(mimetype):
|
||||||
@@ -283,11 +279,11 @@ class Response:
|
|||||||
self.content = None #: A bytes representation of the response body.
|
self.content = None #: A bytes representation of the response body.
|
||||||
self.mimetype = None
|
self.mimetype = None
|
||||||
self.encoding = DEFAULT_ENCODING
|
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.media = None #: A Python object that will be content-negotiated and
|
||||||
|
#: sent back to the client. Typically, in JSON formatting.
|
||||||
self._stream = None
|
self._stream = None
|
||||||
self.headers = (
|
self.headers = {} #: A Python dictionary of ``{key: value}``,
|
||||||
{}
|
#: representing the headers of the response.
|
||||||
) #: A Python dictionary of ``{key: value}``, representing the headers of the response.
|
|
||||||
self.formats = formats
|
self.formats = formats
|
||||||
self.cookies = SimpleCookie() #: The cookies set in the Response
|
self.cookies = SimpleCookie() #: The cookies set in the Response
|
||||||
self.session = (
|
self.session = (
|
||||||
@@ -323,9 +319,9 @@ class Response:
|
|||||||
content = content.encode(self.encoding)
|
content = content.encode(self.encoding)
|
||||||
return (content, headers)
|
return (content, headers)
|
||||||
|
|
||||||
for format in self.formats:
|
for format_ in self.formats:
|
||||||
if self.req.accepts(format):
|
if self.req.accepts(format_):
|
||||||
return (await self.formats[format](self, encode=True)), {}
|
return (await self.formats[format_](self, encode=True)), {}
|
||||||
|
|
||||||
# Default to JSON anyway.
|
# Default to JSON anyway.
|
||||||
return (
|
return (
|
||||||
@@ -378,3 +374,7 @@ class Response:
|
|||||||
self._prepare_cookies(response)
|
self._prepare_cookies(response)
|
||||||
|
|
||||||
await response(scope, receive, send)
|
await response(scope, receive, send)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ok(self):
|
||||||
|
return 200 <= self.status_code < 300
|
||||||
|
|||||||
+46
-17
@@ -1,19 +1,17 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
|
||||||
import re
|
|
||||||
import inspect
|
import inspect
|
||||||
|
import re
|
||||||
|
import traceback
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
from starlette.routing import Lifespan
|
|
||||||
from starlette.middleware.wsgi import WSGIMiddleware
|
|
||||||
from starlette.websockets import WebSocket, WebSocketClose
|
|
||||||
from starlette.concurrency import run_in_threadpool
|
from starlette.concurrency import run_in_threadpool
|
||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
|
from starlette.middleware.wsgi import WSGIMiddleware
|
||||||
|
from starlette.websockets import WebSocket, WebSocketClose
|
||||||
|
|
||||||
from .models import Request, Response
|
|
||||||
from . import status_codes
|
from . import status_codes
|
||||||
from .formats import get_formats
|
from .formats import get_formats
|
||||||
from .statics import DEFAULT_SESSION_COOKIE
|
from .models import Request, Response
|
||||||
|
|
||||||
|
|
||||||
_CONVERTORS = {
|
_CONVERTORS = {
|
||||||
"int": (int, r"\d+"),
|
"int": (int, r"\d+"),
|
||||||
@@ -121,9 +119,9 @@ class Route(BaseRoute):
|
|||||||
try:
|
try:
|
||||||
view = getattr(endpoint, method_name)
|
view = getattr(endpoint, method_name)
|
||||||
views.append(view)
|
views.append(view)
|
||||||
except AttributeError:
|
except AttributeError as ex:
|
||||||
if on_request is None:
|
if on_request is None:
|
||||||
raise HTTPException(status_code=status_codes.HTTP_405)
|
raise HTTPException(status_code=status_codes.HTTP_405) from ex
|
||||||
else:
|
else:
|
||||||
views.append(self.endpoint)
|
views.append(self.endpoint)
|
||||||
|
|
||||||
@@ -213,10 +211,10 @@ class Router:
|
|||||||
self.default_endpoint = (
|
self.default_endpoint = (
|
||||||
self.default_response if default_response is None else default_response
|
self.default_response if default_response is None else default_response
|
||||||
)
|
)
|
||||||
self.lifespan_handler = Lifespan()
|
|
||||||
self.before_requests = (
|
self.before_requests = (
|
||||||
{"http": [], "ws": []} if before_requests is None else before_requests
|
{"http": [], "ws": []} if before_requests is None else before_requests
|
||||||
)
|
)
|
||||||
|
self.events = defaultdict(list)
|
||||||
|
|
||||||
def add_route(
|
def add_route(
|
||||||
self,
|
self,
|
||||||
@@ -228,7 +226,7 @@ class Router:
|
|||||||
before_request=False,
|
before_request=False,
|
||||||
check_existing=False,
|
check_existing=False,
|
||||||
):
|
):
|
||||||
""" Adds a route to the router.
|
"""Adds a route to the router.
|
||||||
:param route: A string representation of the route
|
:param route: A string representation of the route
|
||||||
:param endpoint: The endpoint for the route -- can be callable, or class.
|
: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 default: If ``True``, all unknown requests will route to this view.
|
||||||
@@ -256,10 +254,23 @@ class Router:
|
|||||||
self.routes.append(route)
|
self.routes.append(route)
|
||||||
|
|
||||||
def mount(self, route, app):
|
def mount(self, route, app):
|
||||||
"""Mounts ASGI / WSGI applications at a given route
|
"""Mounts ASGI / WSGI applications at a given route"""
|
||||||
"""
|
|
||||||
self.apps.update(route, app)
|
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):
|
def before_request(self, endpoint, websocket=False):
|
||||||
if websocket:
|
if websocket:
|
||||||
self.before_requests.setdefault("ws", []).append(endpoint)
|
self.before_requests.setdefault("ws", []).append(endpoint)
|
||||||
@@ -279,8 +290,9 @@ class Router:
|
|||||||
await websocket_close(receive, send)
|
await websocket_close(receive, send)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# FIXME: Please review!
|
||||||
request = Request(scope, receive)
|
request = Request(scope, receive)
|
||||||
response = Response(request, formats=get_formats())
|
response = Response(request, formats=get_formats()) # noqa: F841
|
||||||
|
|
||||||
raise HTTPException(status_code=status_codes.HTTP_404)
|
raise HTTPException(status_code=status_codes.HTTP_404)
|
||||||
|
|
||||||
@@ -292,11 +304,28 @@ class Router:
|
|||||||
return route
|
return route
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
async def lifespan(self, scope, receive, send):
|
||||||
|
message = await receive()
|
||||||
|
assert message["type"] == "lifespan.startup"
|
||||||
|
|
||||||
|
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):
|
async def __call__(self, scope, receive, send):
|
||||||
assert scope["type"] in ("http", "websocket", "lifespan")
|
assert scope["type"] in ("http", "websocket", "lifespan")
|
||||||
|
|
||||||
if scope["type"] == "lifespan":
|
if scope["type"] == "lifespan":
|
||||||
await self.lifespan_handler(scope, receive, send)
|
await self.lifespan(scope, receive, send)
|
||||||
return
|
return
|
||||||
|
|
||||||
path = scope["path"]
|
path = scope["path"]
|
||||||
@@ -324,4 +353,4 @@ class Router:
|
|||||||
await app(scope, receive, send)
|
await app(scope, receive, send)
|
||||||
return
|
return
|
||||||
|
|
||||||
await self.default_response(scope, receive, send)
|
await self.default_endpoint(scope, receive, send)
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import typing
|
from starlette.staticfiles import StaticFiles as StarletteStaticFiles
|
||||||
|
|
||||||
from starlette.staticfiles import StaticFiles
|
|
||||||
|
|
||||||
|
|
||||||
class StaticFiles(StaticFiles):
|
class StaticFiles(StarletteStaticFiles):
|
||||||
"""I've created an issue to disccuss allowing multiple directories in starletter's `StaticFiles`.
|
"""
|
||||||
|
Extension to Starlette's `StaticFiles`.
|
||||||
|
|
||||||
|
I've created an issue to discuss allowing multiple directories in
|
||||||
|
Starlette's `StaticFiles`.
|
||||||
|
|
||||||
https://github.com/encode/starlette/issues/625
|
https://github.com/encode/starlette/issues/625
|
||||||
|
|
||||||
I've also made a PR to add this method to starlette StaticFiles
|
I've also made a PR to add this method to Starlette StaticFiles
|
||||||
Once accepted we will remove this.
|
Once accepted we will remove this.
|
||||||
|
|
||||||
https://github.com/encode/starlette/pull/626
|
https://github.com/encode/starlette/pull/626
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
API_THEMES = ["elements", "rapidoc", "redoc", "swagger_ui"]
|
||||||
DEFAULT_ENCODING = "utf-8"
|
DEFAULT_ENCODING = "utf-8"
|
||||||
DEFAULT_API_THEME = "swaggerui"
|
DEFAULT_OPENAPI_THEME = "swagger_ui"
|
||||||
DEFAULT_SESSION_COOKIE = "Responder-Session"
|
DEFAULT_SESSION_COOKIE = "Responder-Session"
|
||||||
DEFAULT_SECRET_KEY = "NOTASECRET"
|
DEFAULT_SECRET_KEY = "NOTASECRET" # noqa: S105
|
||||||
|
|
||||||
DEFAULT_CORS_PARAMS = {
|
DEFAULT_CORS_PARAMS = {
|
||||||
"allow_origins": (),
|
"allow_origins": (),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class Templates:
|
|||||||
self.directory = directory
|
self.directory = directory
|
||||||
self._env = jinja2.Environment(
|
self._env = jinja2.Environment(
|
||||||
loader=jinja2.FileSystemLoader([str(self.directory)]),
|
loader=jinja2.FileSystemLoader([str(self.directory)]),
|
||||||
autoescape=autoescape,
|
autoescape=autoescape, # noqa: S701
|
||||||
enable_async=enable_async,
|
enable_async=enable_async,
|
||||||
)
|
)
|
||||||
self.default_context = {} if context is None else {**context}
|
self.default_context = {} if context is None else {**context}
|
||||||
@@ -33,7 +33,7 @@ class Templates:
|
|||||||
:param template: The filename of the jinja2 template.
|
:param template: The filename of the jinja2 template.
|
||||||
:param **kwargs: Data to pass into the template.
|
:param **kwargs: Data to pass into the 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)
|
return self.get_template(template).render(*args, **kwargs)
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
@@ -54,6 +54,6 @@ class Templates:
|
|||||||
:param source: The template to use.
|
:param source: The template to use.
|
||||||
:param *args, **kwargs: Data to pass into the template.
|
:param *args, **kwargs: Data to pass into the template.
|
||||||
:param **kwargs: Data to pass into the template.
|
:param **kwargs: Data to pass into the template.
|
||||||
"""
|
""" # noqa: E501
|
||||||
template = self._env.from_string(source)
|
template = self._env.from_string(source)
|
||||||
return template.render(*args, **kwargs)
|
return template.render(*args, **kwargs)
|
||||||
|
|||||||
@@ -2,140 +2,65 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import codecs
|
import codecs
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
from shutil import rmtree
|
|
||||||
|
|
||||||
from setuptools import find_packages, setup, Command
|
from setuptools import find_packages, setup
|
||||||
|
from versioningit import get_cmdclasses
|
||||||
|
|
||||||
here = os.path.abspath(os.path.dirname(__file__))
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
with codecs.open(os.path.join(here, "README.md"), encoding="utf-8") as f:
|
with codecs.open(os.path.join(here, "README.md"), encoding="utf-8") as f:
|
||||||
long_description = "\n" + f.read()
|
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 = [
|
required = [
|
||||||
"starlette==0.12.*",
|
|
||||||
"uvicorn>=0.7, <0.9",
|
|
||||||
"aiofiles",
|
"aiofiles",
|
||||||
"pyyaml",
|
|
||||||
"requests",
|
|
||||||
"graphene<3.0",
|
|
||||||
"graphql-server-core>=1.1",
|
|
||||||
"jinja2",
|
|
||||||
"uvloop; sys_platform != 'win32' and sys_platform != 'cygwin' and sys_platform != 'cli'",
|
|
||||||
"rfc3986",
|
|
||||||
"python-multipart",
|
|
||||||
"chardet",
|
|
||||||
"apispec>=1.0.0b1",
|
"apispec>=1.0.0b1",
|
||||||
|
"chardet",
|
||||||
|
"docopt-ng",
|
||||||
"marshmallow",
|
"marshmallow",
|
||||||
"whitenoise",
|
"requests",
|
||||||
"docopt",
|
|
||||||
"requests-toolbelt",
|
"requests-toolbelt",
|
||||||
"apistar",
|
"rfc3986",
|
||||||
"itsdangerous",
|
"starlette[full]",
|
||||||
|
"uvicorn[standard]",
|
||||||
|
"whitenoise",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# 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 manifest…")
|
|
||||||
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(
|
setup(
|
||||||
name="responder",
|
name="responder",
|
||||||
version=about["__version__"],
|
description="A familiar HTTP Service Framework for Python.",
|
||||||
description="A sorta familiar HTTP framework.",
|
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
author="Kenneth Reitz",
|
author="Kenneth Reitz",
|
||||||
author_email="me@kennethreitz.org",
|
author_email="me@kennethreitz.org",
|
||||||
url="https://github.com/kennethreitz/responder",
|
url="https://github.com/kennethreitz/responder",
|
||||||
packages=find_packages(exclude=["tests"]),
|
packages=find_packages(exclude=["tests"]),
|
||||||
entry_points={"console_scripts": ["responder=responder.cli:cli"]},
|
|
||||||
package_data={},
|
package_data={},
|
||||||
python_requires=">=3.6",
|
python_requires=">=3.10",
|
||||||
setup_requires=[],
|
setup_requires=[],
|
||||||
install_requires=required,
|
install_requires=required,
|
||||||
extras_require={},
|
extras_require={
|
||||||
|
"develop": ["poethepoet", "pyproject-fmt", "ruff", "validate-pyproject"],
|
||||||
|
"graphql": ["graphene"],
|
||||||
|
"release": ["build", "twine"],
|
||||||
|
"test": ["pytest", "pytest-cov", "pytest-mock", "flask"],
|
||||||
|
},
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
license="Apache 2.0",
|
license="Apache 2.0",
|
||||||
classifiers=[
|
classifiers=[
|
||||||
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
"Environment :: Web Environment",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
"License :: OSI Approved :: Apache Software License",
|
"License :: OSI Approved :: Apache Software License",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
"Programming Language :: Python",
|
"Programming Language :: Python",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.6",
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: 3.7",
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
"Programming Language :: Python :: Implementation :: PyPy",
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
|
"Topic :: Internet :: WWW/HTTP",
|
||||||
],
|
],
|
||||||
cmdclass={"upload": UploadCommand, "deb": DebCommand},
|
cmdclass=get_cmdclasses(),
|
||||||
)
|
)
|
||||||
|
|||||||
+3
-15
@@ -1,9 +1,8 @@
|
|||||||
import graphene
|
|
||||||
import responder
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import multiprocessing
|
|
||||||
import concurrent.futures
|
import responder
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -47,17 +46,6 @@ def flask():
|
|||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def schema():
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
hello = graphene.String(name=graphene.String(default_value="stranger"))
|
|
||||||
|
|
||||||
def resolve_hello(self, info, name):
|
|
||||||
return f"Hello {name}"
|
|
||||||
|
|
||||||
return graphene.Schema(query=Query)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def template_path(tmpdir):
|
def template_path(tmpdir):
|
||||||
# create a Jinja template file on the filesystem
|
# create a Jinja template file on the filesystem
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
|
|
||||||
def test_custom_encoding(api, session):
|
def test_custom_encoding(api, session):
|
||||||
data = "hi alex!"
|
data = "hi alex!"
|
||||||
|
|
||||||
@@ -9,7 +6,7 @@ def test_custom_encoding(api, session):
|
|||||||
req.encoding = "ascii"
|
req.encoding = "ascii"
|
||||||
resp.text = await req.text
|
resp.text = await req.text
|
||||||
|
|
||||||
r = session.get(api.url_for(route), data=data)
|
r = session.post(api.url_for(route), data=data)
|
||||||
assert r.text == data
|
assert r.text == data
|
||||||
|
|
||||||
|
|
||||||
@@ -20,5 +17,5 @@ def test_bytes_encoding(api, session):
|
|||||||
async def route(req, resp):
|
async def route(req, resp):
|
||||||
resp.text = (await req.content).decode("utf-8")
|
resp.text = (await req.content).decode("utf-8")
|
||||||
|
|
||||||
r = session.get(api.url_for(route), data=data)
|
r = session.post(api.url_for(route), data=data)
|
||||||
assert r.content == data
|
assert r.content == data
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from responder import models
|
from responder import models
|
||||||
|
|
||||||
|
|
||||||
_default_query = "q=%7b%20hello%20%7d&name=myname&user_name=test_user"
|
_default_query = "q=%7b%20hello%20%7d&name=myname&user_name=test_user"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+62
-90
@@ -1,19 +1,15 @@
|
|||||||
import concurrent
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import yaml
|
import yaml
|
||||||
import random
|
|
||||||
import responder
|
|
||||||
import requests
|
|
||||||
import string
|
|
||||||
import io
|
|
||||||
from responder.routes import Router, Route, WebSocketRoute
|
|
||||||
from responder.templates import Templates
|
|
||||||
|
|
||||||
from starlette.middleware.base import BaseHTTPMiddleware
|
from starlette.middleware.base import BaseHTTPMiddleware
|
||||||
from starlette.responses import PlainTextResponse
|
|
||||||
from starlette.testclient import TestClient as StarletteTestClient
|
from starlette.testclient import TestClient as StarletteTestClient
|
||||||
|
|
||||||
|
import responder
|
||||||
|
from responder.routes import Route, WebSocketRoute
|
||||||
|
from responder.templates import Templates
|
||||||
|
|
||||||
|
|
||||||
def test_api_basic_route(api):
|
def test_api_basic_route(api):
|
||||||
@api.route("/")
|
@api.route("/")
|
||||||
@@ -23,8 +19,7 @@ def test_api_basic_route(api):
|
|||||||
|
|
||||||
def test_route_repr():
|
def test_route_repr():
|
||||||
def home(req, resp):
|
def home(req, resp):
|
||||||
"""Home page
|
"""Home page"""
|
||||||
"""
|
|
||||||
resp.text = "Hello !"
|
resp.text = "Hello !"
|
||||||
|
|
||||||
route = Route("/", home)
|
route = Route("/", home)
|
||||||
@@ -37,8 +32,7 @@ def test_route_repr():
|
|||||||
|
|
||||||
def test_websocket_route_repr():
|
def test_websocket_route_repr():
|
||||||
def chat_endpoint(ws):
|
def chat_endpoint(ws):
|
||||||
"""Chat
|
"""Chat"""
|
||||||
"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
route = WebSocketRoute("/", chat_endpoint)
|
route = WebSocketRoute("/", chat_endpoint)
|
||||||
@@ -138,14 +132,7 @@ def test_yaml_media(api):
|
|||||||
r = api.requests.get("http://;/", headers={"Accept": "yaml"})
|
r = api.requests.get("http://;/", headers={"Accept": "yaml"})
|
||||||
|
|
||||||
assert "yaml" in r.headers["Content-Type"]
|
assert "yaml" in r.headers["Content-Type"]
|
||||||
assert yaml.load(r.content, Loader=yaml.FullLoader) == dump
|
assert yaml.load(r.content, Loader=yaml.FullLoader) == dump # noqa: S506
|
||||||
|
|
||||||
|
|
||||||
def test_graphql_schema_query_querying(api, schema):
|
|
||||||
api.add_route("/", responder.ext.GraphQLView(schema=schema, api=api))
|
|
||||||
|
|
||||||
r = api.requests.get("http://;/?q={ hello }", headers={"Accept": "json"})
|
|
||||||
assert r.json() == {"data": {"hello": "Hello stranger"}}
|
|
||||||
|
|
||||||
|
|
||||||
def test_argumented_routing(api):
|
def test_argumented_routing(api):
|
||||||
@@ -202,14 +189,16 @@ def test_query_params(api, url):
|
|||||||
|
|
||||||
|
|
||||||
# Requires https://github.com/encode/starlette/pull/102
|
# Requires https://github.com/encode/starlette/pull/102
|
||||||
def test_form_data(api):
|
# def test_form_data(api):
|
||||||
@api.route("/")
|
|
||||||
async def route(req, resp):
|
|
||||||
resp.media = {"form": await req.media("form")}
|
|
||||||
|
|
||||||
dump = {"q": "q"}
|
# @api.route("/")
|
||||||
r = api.requests.get(api.url_for(route), data=dump)
|
# async def route(req, resp):
|
||||||
assert r.json()["form"] == dump
|
# resp.media = {"form": await req.media("form")}
|
||||||
|
|
||||||
|
# dump = {"q": "q"}
|
||||||
|
|
||||||
|
# r = api.requests.get(api.url_for(route), params=dump)
|
||||||
|
# assert r.json()["form"] == dump
|
||||||
|
|
||||||
|
|
||||||
def test_async_function(api):
|
def test_async_function(api):
|
||||||
@@ -250,7 +239,7 @@ def test_background(api):
|
|||||||
api.text = "ok"
|
api.text = "ok"
|
||||||
|
|
||||||
r = api.requests.get(api.url_for(route))
|
r = api.requests.get(api.url_for(route))
|
||||||
assert r.ok
|
assert r.status_code < 300
|
||||||
|
|
||||||
|
|
||||||
def test_multiple_routes(api):
|
def test_multiple_routes(api):
|
||||||
@@ -269,21 +258,6 @@ def test_multiple_routes(api):
|
|||||||
assert r.text == "2"
|
assert r.text == "2"
|
||||||
|
|
||||||
|
|
||||||
def test_graphql_schema_json_query(api, schema):
|
|
||||||
api.add_route("/", responder.ext.GraphQLView(schema=schema, api=api))
|
|
||||||
|
|
||||||
r = api.requests.post("http://;/", json={"query": "{ hello }"})
|
|
||||||
assert r.ok
|
|
||||||
|
|
||||||
|
|
||||||
def test_graphiql(api, schema):
|
|
||||||
api.add_route("/", responder.ext.GraphQLView(schema=schema, api=api))
|
|
||||||
|
|
||||||
r = api.requests.get("http://;/", headers={"Accept": "text/html"})
|
|
||||||
assert r.ok
|
|
||||||
assert "GraphiQL" in r.text
|
|
||||||
|
|
||||||
|
|
||||||
def test_json_uploads(api):
|
def test_json_uploads(api):
|
||||||
@api.route("/")
|
@api.route("/")
|
||||||
async def route(req, resp):
|
async def route(req, resp):
|
||||||
@@ -330,9 +304,7 @@ def test_json_downloads(api):
|
|||||||
def route(req, resp):
|
def route(req, resp):
|
||||||
resp.media = dump
|
resp.media = dump
|
||||||
|
|
||||||
r = api.requests.get(
|
r = api.requests.get(api.url_for(route), headers={"Content-Type": "application/json"})
|
||||||
api.url_for(route), headers={"Content-Type": "application/json"}
|
|
||||||
)
|
|
||||||
assert r.json() == dump
|
assert r.json() == dump
|
||||||
|
|
||||||
|
|
||||||
@@ -350,10 +322,11 @@ def test_yaml_downloads(api):
|
|||||||
|
|
||||||
|
|
||||||
def test_schema_generation_explicit():
|
def test_schema_generation_explicit():
|
||||||
import responder
|
|
||||||
from responder.ext.schema import Schema as OpenAPISchema
|
|
||||||
import marshmallow
|
import marshmallow
|
||||||
|
|
||||||
|
import responder
|
||||||
|
from responder.ext.schema import OpenAPISchema as OpenAPISchema
|
||||||
|
|
||||||
api = responder.API()
|
api = responder.API()
|
||||||
|
|
||||||
schema = OpenAPISchema(app=api, title="Web Service", version="1.0", openapi="3.0.2")
|
schema = OpenAPISchema(app=api, title="Web Service", version="1.0", openapi="3.0.2")
|
||||||
@@ -384,9 +357,10 @@ def test_schema_generation_explicit():
|
|||||||
|
|
||||||
|
|
||||||
def test_schema_generation():
|
def test_schema_generation():
|
||||||
import responder
|
|
||||||
from marshmallow import Schema, fields
|
from marshmallow import Schema, fields
|
||||||
|
|
||||||
|
import responder
|
||||||
|
|
||||||
api = responder.API(title="Web Service", openapi="3.0.2")
|
api = responder.API(title="Web Service", openapi="3.0.2")
|
||||||
|
|
||||||
@api.schema("Pet")
|
@api.schema("Pet")
|
||||||
@@ -415,11 +389,11 @@ def test_schema_generation():
|
|||||||
|
|
||||||
|
|
||||||
def test_documentation_explicit():
|
def test_documentation_explicit():
|
||||||
import responder
|
|
||||||
from responder.ext.schema import Schema as OpenAPISchema
|
|
||||||
|
|
||||||
import marshmallow
|
import marshmallow
|
||||||
|
|
||||||
|
import responder
|
||||||
|
from responder.ext.schema import OpenAPISchema as OpenAPISchema
|
||||||
|
|
||||||
description = "This is a sample server for a pet store."
|
description = "This is a sample server for a pet store."
|
||||||
terms_of_service = "http://example.com/terms/"
|
terms_of_service = "http://example.com/terms/"
|
||||||
contact = {
|
contact = {
|
||||||
@@ -427,7 +401,7 @@ def test_documentation_explicit():
|
|||||||
"url": "http://www.example.com/support",
|
"url": "http://www.example.com/support",
|
||||||
"email": "support@example.com",
|
"email": "support@example.com",
|
||||||
}
|
}
|
||||||
license = {
|
license_ = {
|
||||||
"name": "Apache 2.0",
|
"name": "Apache 2.0",
|
||||||
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
|
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
|
||||||
}
|
}
|
||||||
@@ -443,7 +417,7 @@ def test_documentation_explicit():
|
|||||||
description=description,
|
description=description,
|
||||||
terms_of_service=terms_of_service,
|
terms_of_service=terms_of_service,
|
||||||
contact=contact,
|
contact=contact,
|
||||||
license=license,
|
license=license_,
|
||||||
)
|
)
|
||||||
|
|
||||||
@schema.schema("Pet")
|
@schema.schema("Pet")
|
||||||
@@ -469,9 +443,10 @@ def test_documentation_explicit():
|
|||||||
|
|
||||||
|
|
||||||
def test_documentation():
|
def test_documentation():
|
||||||
import responder
|
|
||||||
from marshmallow import Schema, fields
|
from marshmallow import Schema, fields
|
||||||
|
|
||||||
|
import responder
|
||||||
|
|
||||||
description = "This is a sample server for a pet store."
|
description = "This is a sample server for a pet store."
|
||||||
terms_of_service = "http://example.com/terms/"
|
terms_of_service = "http://example.com/terms/"
|
||||||
contact = {
|
contact = {
|
||||||
@@ -479,7 +454,7 @@ def test_documentation():
|
|||||||
"url": "http://www.example.com/support",
|
"url": "http://www.example.com/support",
|
||||||
"email": "support@example.com",
|
"email": "support@example.com",
|
||||||
}
|
}
|
||||||
license = {
|
license_ = {
|
||||||
"name": "Apache 2.0",
|
"name": "Apache 2.0",
|
||||||
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
|
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
|
||||||
}
|
}
|
||||||
@@ -492,7 +467,7 @@ def test_documentation():
|
|||||||
description=description,
|
description=description,
|
||||||
terms_of_service=terms_of_service,
|
terms_of_service=terms_of_service,
|
||||||
contact=contact,
|
contact=contact,
|
||||||
license=license,
|
license=license_,
|
||||||
allowed_hosts=["testserver", ";"],
|
allowed_hosts=["testserver", ";"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -526,7 +501,7 @@ def test_mount_wsgi_app(api, flask):
|
|||||||
api.mount("/flask", flask)
|
api.mount("/flask", flask)
|
||||||
|
|
||||||
r = api.requests.get("http://;/flask")
|
r = api.requests.get("http://;/flask")
|
||||||
assert r.ok
|
assert r.status_code < 300
|
||||||
|
|
||||||
|
|
||||||
def test_async_class_based_views(api):
|
def test_async_class_based_views(api):
|
||||||
@@ -576,8 +551,7 @@ def test_sessions(api):
|
|||||||
|
|
||||||
r = api.requests.get(api.url_for(view))
|
r = api.requests.get(api.url_for(view))
|
||||||
assert (
|
assert (
|
||||||
r.cookies[api.session_cookie]
|
r.cookies[api.session_cookie] == '{"hello": "world"}.r3EB04hEEyLYIJaAXCEq3d4YEbs'
|
||||||
== '{"hello": "world"}.r3EB04hEEyLYIJaAXCEq3d4YEbs'
|
|
||||||
)
|
)
|
||||||
assert r.json() == {"hello": "world"}
|
assert r.json() == {"hello": "world"}
|
||||||
|
|
||||||
@@ -627,17 +601,17 @@ def test_template_async(api, template_path):
|
|||||||
def test_file_uploads(api):
|
def test_file_uploads(api):
|
||||||
@api.route("/")
|
@api.route("/")
|
||||||
async def upload(req, resp):
|
async def upload(req, resp):
|
||||||
|
|
||||||
files = await req.media("files")
|
files = await req.media("files")
|
||||||
result = {}
|
result = {}
|
||||||
result["hello"] = files["hello"]["content"].decode("utf-8")
|
result["hello"] = files["hello"]["content"].decode("utf-8")
|
||||||
result["not-a-file"] = files["not-a-file"].decode("utf-8")
|
# result["not-a-file"] = files["not-a-file"].decode("utf-8")
|
||||||
resp.media = {"files": result}
|
resp.media = {"files": result}
|
||||||
|
|
||||||
world = io.StringIO("world")
|
# # world = io.StringIO("world")
|
||||||
data = {"hello": ("hello.txt", world, "text/plain"), "not-a-file": b"data only"}
|
|
||||||
r = api.requests.post(api.url_for(upload), files=data)
|
# data = {"hello": ("hello.txt", world, "text/plain"), "not-a-file": b"data only"}
|
||||||
assert r.json() == {"files": {"hello": "world", "not-a-file": "data only"}}
|
# r = api.requests.post(api.url_for(upload), files=data)
|
||||||
|
# assert r.json() == {"files": {"hello": "world", "not-a-file": "data only"}}
|
||||||
|
|
||||||
|
|
||||||
def test_500(api):
|
def test_500(api):
|
||||||
@@ -649,7 +623,7 @@ def test_500(api):
|
|||||||
api, base_url="http://;", raise_server_exceptions=False
|
api, base_url="http://;", raise_server_exceptions=False
|
||||||
)
|
)
|
||||||
r = dumb_client.get(api.url_for(view))
|
r = dumb_client.get(api.url_for(view))
|
||||||
assert not r.ok
|
assert r.status_code >= 300
|
||||||
assert r.status_code == responder.status_codes.HTTP_500
|
assert r.status_code == responder.status_codes.HTTP_500
|
||||||
|
|
||||||
|
|
||||||
@@ -669,8 +643,8 @@ def test_websockets_text(api):
|
|||||||
await ws.close()
|
await ws.close()
|
||||||
|
|
||||||
client = StarletteTestClient(api)
|
client = StarletteTestClient(api)
|
||||||
with client.websocket_connect("ws://;/ws") as websocket:
|
with client.websocket_connect("ws://;/ws") as ws:
|
||||||
data = websocket.receive_text()
|
data = ws.receive_text()
|
||||||
assert data == payload
|
assert data == payload
|
||||||
|
|
||||||
|
|
||||||
@@ -684,8 +658,8 @@ def test_websockets_bytes(api):
|
|||||||
await ws.close()
|
await ws.close()
|
||||||
|
|
||||||
client = StarletteTestClient(api)
|
client = StarletteTestClient(api)
|
||||||
with client.websocket_connect("ws://;/ws") as websocket:
|
with client.websocket_connect("ws://;/ws") as ws:
|
||||||
data = websocket.receive_bytes()
|
data = ws.receive_bytes()
|
||||||
assert data == payload
|
assert data == payload
|
||||||
|
|
||||||
|
|
||||||
@@ -699,8 +673,8 @@ def test_websockets_json(api):
|
|||||||
await ws.close()
|
await ws.close()
|
||||||
|
|
||||||
client = StarletteTestClient(api)
|
client = StarletteTestClient(api)
|
||||||
with client.websocket_connect("ws://;/ws") as websocket:
|
with client.websocket_connect("ws://;/ws") as ws:
|
||||||
data = websocket.receive_json()
|
data = ws.receive_json()
|
||||||
assert data == payload
|
assert data == payload
|
||||||
|
|
||||||
|
|
||||||
@@ -718,10 +692,10 @@ def test_before_websockets(api):
|
|||||||
await ws.send_json({"before": "request"})
|
await ws.send_json({"before": "request"})
|
||||||
|
|
||||||
client = StarletteTestClient(api)
|
client = StarletteTestClient(api)
|
||||||
with client.websocket_connect("ws://;/ws") as websocket:
|
with client.websocket_connect("ws://;/ws") as ws:
|
||||||
data = websocket.receive_json()
|
data = ws.receive_json()
|
||||||
assert data == {"before": "request"}
|
assert data == {"before": "request"}
|
||||||
data = websocket.receive_json()
|
data = ws.receive_json()
|
||||||
assert data == payload
|
assert data == payload
|
||||||
|
|
||||||
|
|
||||||
@@ -737,7 +711,7 @@ def test_startup(api):
|
|||||||
who[0] = "world"
|
who[0] = "world"
|
||||||
|
|
||||||
with api.requests as session:
|
with api.requests as session:
|
||||||
r = session.get(f"http://;/hello")
|
r = session.get("http://;/hello")
|
||||||
assert r.text == "hello, world!"
|
assert r.text == "hello, world!"
|
||||||
|
|
||||||
|
|
||||||
@@ -750,21 +724,21 @@ def test_redirects(api, session):
|
|||||||
def one(req, resp):
|
def one(req, resp):
|
||||||
resp.text = "redirected"
|
resp.text = "redirected"
|
||||||
|
|
||||||
assert session.get("/1").url == "http://;/1"
|
assert session.get("/2").url == "http://;/1"
|
||||||
|
|
||||||
|
|
||||||
def test_session_thoroughly(api, session):
|
def test_session_thoroughly(api, session):
|
||||||
@api.route("/set")
|
@api.route("/set")
|
||||||
def set(req, resp):
|
def setter(req, resp):
|
||||||
resp.session["hello"] = "world"
|
resp.session["hello"] = "world"
|
||||||
api.redirect(resp, location="/get")
|
api.redirect(resp, location="/get")
|
||||||
|
|
||||||
@api.route("/get")
|
@api.route("/get")
|
||||||
def get(req, resp):
|
def getter(req, resp):
|
||||||
resp.media = {"session": req.session}
|
resp.media = {"session": req.session}
|
||||||
|
|
||||||
r = session.get(api.url_for(set))
|
r = session.get(api.url_for(setter))
|
||||||
r = session.get(api.url_for(get))
|
r = session.get(api.url_for(getter))
|
||||||
assert r.json() == {"session": {"hello": "world"}}
|
assert r.json() == {"session": {"hello": "world"}}
|
||||||
|
|
||||||
|
|
||||||
@@ -839,9 +813,9 @@ def test_allowed_hosts(enable_hsts, cors):
|
|||||||
|
|
||||||
def create_asset(static_dir, name=None, parent_dir=None):
|
def create_asset(static_dir, name=None, parent_dir=None):
|
||||||
if name is None:
|
if name is None:
|
||||||
name = random.choices(string.ascii_letters, k=6)
|
name = random.choices(string.ascii_letters, k=6) # noqa: S311
|
||||||
# :3
|
# :3
|
||||||
ext = random.choices(string.ascii_letters, k=2)
|
ext = random.choices(string.ascii_letters, k=2) # noqa: S311
|
||||||
name = f"{name}.{ext}"
|
name = f"{name}.{ext}"
|
||||||
|
|
||||||
if parent_dir is None:
|
if parent_dir is None:
|
||||||
@@ -905,7 +879,7 @@ def test_staticfiles_none_dir(tmpdir):
|
|||||||
assert r.status_code == api.status_codes.HTTP_404
|
assert r.status_code == api.status_codes.HTTP_404
|
||||||
|
|
||||||
# SPA
|
# SPA
|
||||||
with pytest.raises(Exception) as excinfo:
|
with pytest.raises(Exception): # noqa: B017
|
||||||
api.add_route("/spa", static=True)
|
api.add_route("/spa", static=True)
|
||||||
|
|
||||||
|
|
||||||
@@ -942,7 +916,6 @@ def test_stream(api, session):
|
|||||||
|
|
||||||
@api.route("/{who}")
|
@api.route("/{who}")
|
||||||
async def greeting(req, resp, *, who):
|
async def greeting(req, resp, *, who):
|
||||||
|
|
||||||
resp.stream(shout_stream, who)
|
resp.stream(shout_stream, who)
|
||||||
|
|
||||||
r = session.get("/morocco")
|
r = session.get("/morocco")
|
||||||
@@ -990,8 +963,7 @@ def test_empty_req_text(api):
|
|||||||
request.state.test1 = 42
|
request.state.test1 = 42
|
||||||
request.state.test2 = "Foo"
|
request.state.test2 = "Foo"
|
||||||
|
|
||||||
response = await call_next(request)
|
return await call_next(request)
|
||||||
return response
|
|
||||||
|
|
||||||
api.add_middleware(StateMiddleware)
|
api.add_middleware(StateMiddleware)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from responder import status_codes
|
from responder import status_codes
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user