Compare commits

..

1 Commits

Author SHA1 Message Date
taoufik 8a46a87b3e Fix missing openapi title, version and openapi_version and black 2019-12-03 19:38:12 +01:00
61 changed files with 675 additions and 4893 deletions
-23
View File
@@ -1,23 +0,0 @@
name: Lint
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.7
- name: Install dependencies
run: |
python -m pip install pipenv
pipenv install --dev --system
- name: Lint
run: pre-commit run --all-files --show-diff-on-failure
-30
View File
@@ -1,30 +0,0 @@
name: Test
on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
python-version: [3.6, 3.7, 3.8]
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install pipenv
pipenv install --dev --system
- name: Tests
run: |
python --version
pytest
-13
View File
@@ -1,13 +0,0 @@
repos:
- repo: https://github.com/psf/black
rev: 20.8b1
hooks:
- id: black
types: [python]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.2.1
hooks:
- id: prettier
args: [--prose-wrap=always, --print-width=88]
exclude: ^docs/source/_static/
+18
View File
@@ -0,0 +1,18 @@
# 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
+3 -110
View File
@@ -1,340 +1,233 @@
# 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/), and The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and 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.5..HEAD [Unreleased]: https://github.com/taoufik07/responder/compare/v2.0.4..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
+1 -2
View File
@@ -5,12 +5,11 @@ name = "pypi"
[packages] [packages]
responder = {editable = true, path = "."} responder = {editable = true, path = "."}
pre-commit = "*"
[dev-packages] [dev-packages]
pytest = "*" pytest = "*"
"flake8" = "*" "flake8" = "*"
black = "==20.8b1" black = "*"
twine = "*" twine = "*"
flask = "*" flask = "*"
sphinx = "*" sphinx = "*"
Generated
+297 -469
View File
File diff suppressed because it is too large Load Diff
+20 -35
View File
@@ -9,52 +9,45 @@
[![](https://farm2.staticflickr.com/1959/43750081370_a4e20752de_o_d.png)](https://responder.readthedocs.io) [![](https://farm2.staticflickr.com/1959/43750081370_a4e20752de_o_d.png)](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 Powered by [Starlette](https://www.starlette.io/). That `async` declaration is optional. [View documentation](https://responder.readthedocs.io).
templating (without additional imports), and a production webserver based on uvloop,
serving up requests with gzip compression automatically. 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.
## Testimonials ## Testimonials
> "Pleasantly very taken with python-responder. > "Pleasantly very taken with python-responder. [@kennethreitz](https://twitter.com/kennethreitz) at his absolute best." —Rudraksh M.K.
> [@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 > "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/)
> to see Responder starting to take advantage of that." — Tom Christie author of
> [Django REST Framework](https://www.django-rest-framework.org/) > "I love that you are exploring new patterns. Go go go!" — Danny Greenfield, author of [Two Scoops of Django]()
> "I love that you are exploring new patterns. Go go go!" — Danny Greenfield, author of
> [Two Scoops of Django]()
## More Examples ## More Examples
See See [the documentation's feature tour](https://responder.readthedocs.io/en/latest/tour.html) for more details on features available in Responder.
[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 stable release:
$ pipenv install responder $ pipenv install responder
✨🍰✨ ✨🍰✨
Or, install from the development branch: Or, install from the development branch:
$ pipenv install -e git+https://github.com/taoufik07/responder.git#egg=responder $ 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 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.
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.
@@ -62,24 +55,16 @@ Requests.
- 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 - Flask-style route expression, with new capabilities -- all while using Python 3.6+'s new f-string syntax.
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.
- 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 - 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.
`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 - 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.
doesn't run on Windows. Plus, Uvicorn serves well to protect against slowloris - GraphQL support, via Graphene. The goal here is to have any GraphQL query exposable at any route, magically.
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.
+1 -1
View File
@@ -4,7 +4,7 @@ atomicwrites==1.2.1
attrs==18.2.0 attrs==18.2.0
babel==2.6.0 babel==2.6.0
black==18.9b0 black==18.9b0
bleach==3.1.4 bleach==3.0.2
certifi==2018.8.24 certifi==2018.8.24
cffi==1.11.5 cffi==1.11.5
chardet==3.0.4 chardet==3.0.4
+4 -4
View File
@@ -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
+131 -141
View File
@@ -10,152 +10,142 @@
*/ */
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) obj.addEventListener(type, fn, false); if (obj.addEventListener)
else if (obj.attachEvent) { obj.addEventListener(type, fn, false);
// IE else if (obj.attachEvent) {
obj["e" + type + fn] = fn; // IE
obj[type + fn] = function () { obj["e" + type + fn] = fn;
obj["e" + type + fn](window.event, ref_obj); obj[type + fn] = function () {
}; 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) { },
if (obj.removeEventListener) { removeEvent: function (obj, eventName, eventCallback) {
obj.removeEventListener(eventName, eventCallback); if (obj.removeEventListener) {
} else if (obj.attachEvent) { obj.removeEventListener(eventName, eventCallback);
obj.detachEvent(eventName); } else if (obj.attachEvent) {
} obj.detachEvent(eventName);
}, }
input: "", },
pattern: "38384040373937396665", input: "",
keydownHandler: function (e, ref_obj) { pattern: "38384040373937396665",
if (ref_obj) { keydownHandler: function (e, ref_obj) {
konami = ref_obj; if (ref_obj) {
} // IE konami = ref_obj;
konami.input += e ? e.keyCode : event.keyCode; } // IE
if (konami.input.length > konami.pattern.length) { konami.input += e ? e.keyCode : event.keyCode;
konami.input = konami.input.substr(konami.input.length - konami.pattern.length); if (konami.input.length > konami.pattern.length) {
} konami.input = konami.input.substr((konami.input.length - konami.pattern.length));
if (konami.input === konami.pattern) { }
konami.code(konami._currentLink); if (konami.input === konami.pattern) {
konami.input = ""; konami.code(konami._currentLink);
e.preventDefault(); konami.input = '';
return false; e.preventDefault();
} return false;
}, }
load: function (link) { },
this._currentLink = link; load: function (link) {
this.addEvent(document, "keydown", this.keydownHandler, this); this._currentLink = link;
this.iphone.load(link); this.addEvent(document, "keydown", this.keydownHandler, this);
}, this.iphone.load(link);
unload: function () { },
this.removeEvent(document, "keydown", this.keydownHandler); unload: function () {
this.iphone.unload(); this.removeEvent(document, 'keydown', this.keydownHandler);
}, this.iphone.unload();
code: function (link) { },
window.location = link; code: function (link) {
}, window.location = link
iphone: { },
start_x: 0, iphone: {
start_y: 0, start_x: 0,
stop_x: 0, start_y: 0,
stop_y: 0, stop_x: 0,
tap: false, stop_y: 0,
capture: false, tap: false,
orig_keys: "", capture: false,
keys: [ orig_keys: "",
"UP", keys: ["UP", "UP", "DOWN", "DOWN", "LEFT", "RIGHT", "LEFT", "RIGHT", "TAP", "TAP"],
"UP", input: [],
"DOWN", code: function (link) {
"DOWN", konami.code(link);
"LEFT", },
"RIGHT", touchmoveHandler: function (e) {
"LEFT", if (e.touches.length === 1 && konami.iphone.capture === true) {
"RIGHT", var touch = e.touches[0];
"TAP", konami.iphone.stop_x = touch.pageX;
"TAP", konami.iphone.stop_y = touch.pageY;
], konami.iphone.tap = false;
input: [], konami.iphone.capture = false;
code: function (link) { konami.iphone.check_direction();
konami.code(link); }
}, },
touchmoveHandler: function (e) { touchendHandler: function () {
if (e.touches.length === 1 && konami.iphone.capture === true) { konami.iphone.input.push(konami.iphone.check_direction());
var touch = e.touches[0];
konami.iphone.stop_x = touch.pageX; if (konami.iphone.input.length > konami.iphone.keys.length) konami.iphone.input.shift();
konami.iphone.stop_y = touch.pageY;
konami.iphone.tap = false; if (konami.iphone.input.length === konami.iphone.keys.length) {
konami.iphone.capture = false; var match = true;
konami.iphone.check_direction(); for (var i = 0; i < konami.iphone.keys.length; i++) {
} if (konami.iphone.input[i] !== konami.iphone.keys[i]) {
}, match = false;
touchendHandler: function () { }
konami.iphone.input.push(konami.iphone.check_direction()); }
if (match) {
if (konami.iphone.input.length > konami.iphone.keys.length) konami.iphone.code(konami._currentLink);
konami.iphone.input.shift(); }
}
if (konami.iphone.input.length === konami.iphone.keys.length) { },
var match = true; touchstartHandler: function (e) {
for (var i = 0; i < konami.iphone.keys.length; i++) { konami.iphone.start_x = e.changedTouches[0].pageX;
if (konami.iphone.input[i] !== konami.iphone.keys[i]) { konami.iphone.start_y = e.changedTouches[0].pageY;
match = false; 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;
} }
}
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); typeof callback === "string" && konami.load(callback);
if (typeof callback === "function") { if (typeof callback === "function") {
konami.code = callback; konami.code = callback;
konami.load(); konami.load();
} }
return konami; return konami;
}; };
if (typeof module !== "undefined" && typeof module.exports !== "undefined") {
module.exports = Konami; if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = Konami;
} else { } 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;
} }
} }
+37 -71
View File
@@ -1,11 +1,5 @@
<link <link rel="stylesheet" type="text/css" href="https://cloud.typography.com/7584432/7586812/css/fonts.css" />
rel="stylesheet" <script type="text/javascript">$('#searchbox').hide(0);</script>
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.) -->
@@ -45,7 +39,9 @@
} }
.method { .method {
margin-bottom: 2em; margin-bottom: 2em;
} }
.si, .si,
@@ -84,6 +80,8 @@
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;
@@ -120,12 +118,10 @@
<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() { function gtag() { dataLayer.push(arguments); }
dataLayer.push(arguments); gtag('js', new Date());
}
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. -->
@@ -134,10 +130,7 @@
<script src="{{ pathto('_static/', 1) }}/konami.js"></script> <script src="{{ pathto('_static/', 1) }}/konami.js"></script>
<script> <script>
var easter_egg = new Konami( var easter_egg = new Konami('https://www.myfortunecookie.co.uk/fortunes/' + (Math.floor(Math.random() * 152) + 1));
"https://www.myfortunecookie.co.uk/fortunes/" +
(Math.floor(Math.random() * 152) + 1)
);
</script> </script>
<style> <style>
@@ -147,94 +140,67 @@
</style> </style>
<!-- GitHub Logo --> <!-- GitHub Logo -->
<a <a href="https://github.com/kennethreitz/responder" class="github-corner" aria-label="View source on GitHub">
href="https://github.com/kennethreitz/responder" <svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;"
class="github-corner" aria-hidden="true">
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 <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"
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
fill="currentColor" <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"
style="transform-origin: 130px 106px" fill="currentColor" class="octo-body"></path>
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> <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>
(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([ UserVoice.push(['showTab', 'classic_widget', {
"showTab", mode: 'feedback',
"classic_widget", primary_color: '#fa8c28',
{ link_color: '#0a8cc6',
mode: "feedback", forum_id: 913660,
primary_color: "#fa8c28", tab_label: 'Got feedback?',
link_color: "#0a8cc6", tab_color: '#00994f',
forum_id: 913660, tab_position: 'bottom-left',
tab_label: "Got feedback?", tab_inverted: true
tab_color: "#00994f", }]);
tab_position: "bottom-left",
tab_inverted: true,
},
]);
</script> </script>
+28 -67
View File
@@ -1,93 +1,54 @@
<p class="logo"> <p class="logo">
<a href="{{ pathto(master_doc) }}"> <a href="{{ pathto(master_doc) }}">
<img <img class="logo" src="{{ pathto('_static/responder.png', 1) }}" title="https://kennethreitz.org/tattoos" />
class="logo"
src="{{ pathto('_static/responder.png', 1) }}"
title="https://kennethreitz.org/tattoos"
/>
</a> </a>
</p> </p>
<p> <p>
<iframe <iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&repo=responder&type=watch&count=true&size=large"
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>
allowtransparency="true"
frameborder="0"
scrolling="0"
width="200px"
height="35px"
></iframe>
</p> </p>
<link <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css" />
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 <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js" onload="docsearch({
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><strong>Responder</strong> is a web service framework, written for human beings.</p> <p>
<strong>Responder</strong> is a web service framework, written for human beings.
</p>
<h3>Stay Informed</h3> <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> <p><iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=true" allowtransparency="true"
<iframe frameborder="0" scrolling="0" width="200" height="20"></iframe></p>
src="https://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=true"
allowtransparency="true" <p><a href="https://twitter.com/kennethreitz" class="twitter-follow-button" data-show-count="false">Follow
frameborder="0" @kennethreitz</a>
scrolling="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>
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>
+28 -67
View File
@@ -1,93 +1,54 @@
<p class="logo"> <p class="logo">
<a href="{{ pathto(master_doc) }}"> <a href="{{ pathto(master_doc) }}">
<img <img class="logo" src="{{ pathto('_static/responder.png', 1) }}" title="https://kennethreitz.org/tattoos" />
class="logo"
src="{{ pathto('_static/responder.png', 1) }}"
title="https://kennethreitz.org/tattoos"
/>
</a> </a>
</p> </p>
<p> <p>
<iframe <iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&repo=responder&type=watch&count=true&size=large"
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>
allowtransparency="true"
frameborder="0"
scrolling="0"
width="200px"
height="35px"
></iframe>
</p> </p>
<link <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css" />
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 <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js" onload="docsearch({
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><strong>Responder</strong> is a web service framework, written for human beings.</p> <p>
<strong>Responder</strong> is a web service framework, written for human beings.
</p>
<h3>Stay Informed</h3> <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> <p><iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=true" allowtransparency="true"
<iframe frameborder="0" scrolling="0" width="200" height="20"></iframe></p>
src="https://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=true"
allowtransparency="true" <p><a href="https://twitter.com/kennethreitz" class="twitter-follow-button" data-show-count="false">Follow
frameborder="0" @kennethreitz</a>
scrolling="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>
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
View File
@@ -1,3 +0,0 @@
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta:__legacy__"
+2 -2
View File
@@ -1,5 +1,5 @@
build: build:
image: latest image: latest
python: python:
version: 3.6 version: 3.6
+1 -1
View File
@@ -1 +1 @@
__version__ = "2.0.6" __version__ = "2.0.4"
+9 -7
View File
@@ -13,6 +13,7 @@ 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.trustedhost import TrustedHostMiddleware
from starlette.middleware.sessions import SessionMiddleware from starlette.middleware.sessions import SessionMiddleware
from starlette.routing import Lifespan
from starlette.staticfiles import StaticFiles from starlette.staticfiles import StaticFiles
from starlette.testclient import TestClient from starlette.testclient import TestClient
from starlette.websockets import WebSocket from starlette.websockets import WebSocket
@@ -30,10 +31,10 @@ 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.
""" """
status_codes = status_codes status_codes = status_codes
@@ -43,12 +44,13 @@ class API:
*, *,
debug=False, debug=False,
title=None, title=None,
version=None, version="1.0",
description=None, description=None,
terms_of_service=None, terms_of_service=None,
contact=None, contact=None,
license=None, license=None,
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",
@@ -118,7 +120,7 @@ class API:
app=self, app=self,
title=title, title=title,
version=version, version=version,
openapi=openapi, openapi=openapi_version,
docs_route=docs_route, docs_route=docs_route,
description=description, description=description,
terms_of_service=terms_of_service, terms_of_service=terms_of_service,
@@ -262,7 +264,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.add_event_handler(event_type, handler) self.router.lifespan_handler.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.
+1 -1
View File
@@ -97,7 +97,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 marshmallow schema to the API specification.""" """Adds a mashmallow schema to the API specification."""
if check_existing: if check_existing:
assert name not in self.schemas assert name not in self.schemas
+7 -38
View File
@@ -2,9 +2,8 @@ import asyncio
import json import json
import re import re
import inspect import inspect
import traceback
from collections import defaultdict
from starlette.routing import Lifespan
from starlette.middleware.wsgi import WSGIMiddleware from starlette.middleware.wsgi import WSGIMiddleware
from starlette.websockets import WebSocket, WebSocketClose from starlette.websockets import WebSocket, WebSocketClose
from starlette.concurrency import run_in_threadpool from starlette.concurrency import run_in_threadpool
@@ -214,10 +213,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,
@@ -229,7 +228,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.
@@ -257,23 +256,10 @@ 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)
@@ -306,28 +292,11 @@ 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(scope, receive, send) await self.lifespan_handler(scope, receive, send)
return return
path = scope["path"] path = scope["path"]
@@ -355,4 +324,4 @@ class Router:
await app(scope, receive, send) await app(scope, receive, send)
return return
await self.default_endpoint(scope, receive, send) await self.default_response(scope, receive, send)
+3 -2
View File
@@ -22,14 +22,15 @@ if sys.argv[-1] == "publish":
sys.exit() sys.exit()
required = [ required = [
"starlette==0.13.*", "starlette==0.12.*",
"uvicorn>=0.11.7,<0.13", "uvicorn>=0.7, <0.9",
"aiofiles", "aiofiles",
"pyyaml", "pyyaml",
"requests", "requests",
"graphene<3.0", "graphene<3.0",
"graphql-server-core>=1.1", "graphql-server-core>=1.1",
"jinja2", "jinja2",
"uvloop; sys_platform != 'win32' and sys_platform != 'cygwin' and sys_platform != 'cli'",
"rfc3986", "rfc3986",
"python-multipart", "python-multipart",
"chardet", "chardet",
+5 -3
View File
@@ -23,7 +23,8 @@ 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)
@@ -36,7 +37,8 @@ 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)
@@ -748,7 +750,7 @@ def test_redirects(api, session):
def one(req, resp): def one(req, resp):
resp.text = "redirected" resp.text = "redirected"
assert session.get("/2").url == "http://;/1" assert session.get("/1").url == "http://;/1"
def test_session_thoroughly(api, session): def test_session_thoroughly(api, session):