Compare commits

..

46 Commits

Author SHA1 Message Date
taoufik07 f1b2f46a10 v2.0.6 2021-01-06 09:02:42 +01:00
Taoufik a0913e3f63 Merge pull request #447 from timgates42/bugfix_typo_marshmallow
docs: fix simple typo, mashmallow -> marshmallow
2020-12-24 08:44:43 +01:00
Tim Gates f90955a9b9 docs: fix simple typo, mashmallow -> marshmallow
There is a small typo in responder/ext/schema/__init__.py.

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

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

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

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

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

- remove   _notfound_wsgi_app, before_ws_requests, before_http_requests

they didn't seem used anywhere and the before_* method were broken, referring to
self.before_requests that doesn't exist anymore
2019-10-19 13:47:39 +02:00
Taoufik 03e34d56ab Merge pull request #398 from taoufik07/v2
v2.0.0
2019-10-19 12:22:32 +01:00
taoufik b470d10416 v2.0.0 2019-10-19 12:21:06 +01:00
65 changed files with 5016 additions and 743 deletions
+23
View File
@@ -0,0 +1,23 @@
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
@@ -0,0 +1,30 @@
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
@@ -0,0 +1,13 @@
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
@@ -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
+135 -4
View File
@@ -1,214 +1,345 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [v1.3.2] - 2019-08-15
## [v2.0.5] - 2019-12-15
### Added
- Update requirements to support python 3.8
## [v2.0.4] - 2019-11-19
### Fixed
- Fix static app resolving
## [v2.0.3] - 2019-09-20
### Fixed
- Fix template conflicts
## [v2.0.2] - 2019-09-20
### Fixed
- Fix template conflicts
## [v2.0.1] - 2019-09-20
### Fixed
- Fix template import
## [v2.0.0] - 2019-09-19
### Changed
- Refactor Router and Schema
## [v1.3.2] - 2019-08-15
### Added
- ASGI 3 support
- CI tests for python 3.8-dev
- Now requests have `state` a mapping object
### Deprecated
- ASGI 2
## [v1.3.1] - 2019-04-28
### Added
- Route params Converters
- Add search for documentation pages
### Changed
- Bump dependencies
## [v1.3.0] - 2019-02-22
### Fixed
- Versioning issue
- Multiple cookies.
- Whitenoise returns not found.
- Other bugfixes.
### Added
- Stream support via `resp.stream`.
- Cookie directives via `resp.set_cookie`.
- Add `resp.html` to send HTML.
- Other improvements.
## [v1.1.3] - 2019-01-12
### Changed
- Refactor `_route_for`
### Fixed
- Resolve startup/shutdwown events
## [v1.2.0] - 2018-12-29
### Added
- Documentations
### Changed
- Use Starlette's LifeSpan middleware
- Update denpendencies
### Fixed
- Fix route.is_class_based
- Fix test_500
- Typos
## [v1.1.2] - 2018-11-11
### Fixed
- Minor fixes for Open API
- Typos
## [v1.1.1] - 2018-10-29
### Changed
- Run sync views in a threadpoolexecutor.
## [v1.1.0] - 2018-10-27
### Added
- Support for `before_request`.
## [v1.0.5]- 2018-10-27
### Fixed
- Fix sessions.
## [v1.0.4] - 2018-10-27
### Fixed
- Potential bufix for cookies.
## [v1.0.3] - 2018-10-27
### Fixed
- Bugfix for redirects.
## [v1.0.2] - 2018-10-27
### Changed
- Improvement for static file hosting.
## [v1.0.1] - 2018-10-26
### Changed
- Improve cors configuration settings.
## [v1.0.0] - 2018-10-26
### Changed
- Move GraphQL support into a built-in plugin.
## [v0.3.3] - 2018-10-25
### Added
- CORS support
### Changed
- Improved exceptions.
## [v0.3.2] - 2018-10-25
### Changed
- Subtle improvements.
## [v0.3.1] - 2018-10-24
### Fixed
- Packaging fix.
## [v0.3.0] - 2018-10-24
### Changed
- Interactive Documentation endpoint.
- Minor improvements.
## [v0.2.3] - 2018-10-24
### Changed
- Overall improvements.
## [v0.2.2] - 2018-10-23
### Added
- Show traceback info when background tasks raise exceptions.
## [v0.2.1] - 2018-10-23
### Added
- api.requests.
## [v0.2.0] - 2018-10-22
### Added
- WebSocket support.
## [v0.1.6] - 2018-10-20
### Added
- 500 support.
## [v0.1.5] - 2018-10-20
### Added
- File upload support
### Changed
- Improvements to sequential media reading.
## [v0.1.4] - 2018-10-19
### Fixed
- Stability.
## [v0.1.3] - 2018-10-18
### Added
- Sessions support.
## [v0.1.2] - 2018-10-18
### Added
- Cookies support.
## [v0.1.1] - 2018-10-17
### Changed
- Default routes.
## [v0.1.0] - 2018-10-17
### Added
- Prototype of static application support.
## [v0.0.10] - 2018-10-17
### Fixed
- Bugfix for async class-based views.
## [v0.0.9] - 2018-10-17
### Fixed
- Bugfix for async class-based views.
## [v0.0.8] - 2018-10-17
### Added
- GraphiQL Support.
### Changed
- Improvement to route selection.
## [v0.0.7] - 2018-10-16
### Changed
- Immutable Request object.
## [v0.0.6] - 2018-10-16
### Added
- Ability to mount WSGI apps.
- Supply content-type when serving up the schema.
## [v0.0.5] - 2018-10-15
### Added
- OpenAPI Schema support.
- Safe load/dump yaml.
## [v0.0.4] - 2018-10-15
### Added
- Asynchronous support for data uploads.
### Fixed
- Bug fixes.
## [v0.0.3] - 2018-10-13
### Fixed
- Bug fixes.
## [v0.0.2] - 2018-10-13
### Changed
- Switch to ASGI/Starlette.
## [v0.0.1] - 2018-10-12
### Added
- Conception!
[Unreleased]: https://github.com/taoufik07/responder/compare/v1.3.2..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.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.1]: https://github.com/taoufik07/responder/compare/v2.0.0..v2.0.1
[v2.0.0]: https://github.com/taoufik07/responder/compare/v1.3.2..v2.0.0
[v1.3.2]: https://github.com/taoufik07/responder/compare/v1.3.1..v1.3.2
[v1.3.1]: https://github.com/taoufik07/responder/compare/v1.3.0..v1.3.1
[v1.3.0]: https://github.com/taoufik07/responder/compare/v1.2.0..v1.3.0
+2 -1
View File
@@ -5,11 +5,12 @@ name = "pypi"
[packages]
responder = {editable = true, path = "."}
pre-commit = "*"
[dev-packages]
pytest = "*"
"flake8" = "*"
black = "*"
black = "==20.8b1"
twine = "*"
flask = "*"
sphinx = "*"
Generated
+473 -294
View File
File diff suppressed because it is too large Load Diff
+35 -20
View File
@@ -9,45 +9,52 @@
[![](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).
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, 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
> "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/)
> "I love that you are exploring new patterns. Go go go!" — Danny Greenfield, author of [Two Scoops of Django]()
> "ASGI is going to enable all sorts of new high-performance web services. It's awesome
> to see Responder starting to take advantage of that." — Tom Christie author of
> [Django REST Framework](https://www.django-rest-framework.org/)
> "I love that you are exploring new patterns. Go go go!" — Danny Greenfield, author of
> [Two Scoops of Django]()
## 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
Install the stable release:
$ pipenv install responder
✨🍰✨
Or, install from the development branch:
$ pipenv install -e git+https://github.com/taoufik07/responder.git#egg=responder
Only **Python 3.6+** is supported.
# 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.text` sends back unicode, while setting `resp.html` sends back HTML.
@@ -55,16 +62,24 @@ The primary concept here is to bring the niceties that are brought forth from bo
- Case-insensitive `req.headers` dict (from Requests directly).
- `resp.status_code`, `req.method`, `req.url`, and other familiar friends.
## Ideas
- Flask-style route expression, with new capabilities -- all while using Python 3.6+'s new f-string syntax.
- I love Falcon's "every request and response is passed into to each view and mutated" methodology, especially `response.media`, and have used it here. In addition to supporting JSON, I have decided to support YAML as well, as Kubernetes is slowly taking over the world, and it uses YAML for all the things. Content-negotiation and all that.
- Flask-style route expression, with new capabilities -- all while using Python 3.6+'s
new f-string syntax.
- I love Falcon's "every request and response is passed into to each view and mutated"
methodology, especially `response.media`, and have used it here. In addition to
supporting JSON, I have decided to support YAML as well, as Kubernetes is slowly
taking over the world, and it uses YAML for all the things. Content-negotiation and
all that.
- **A built in testing client that uses the actual Requests you know and love**.
- The ability to mount other WSGI apps easily.
- Automatic gzipped-responses.
- In addition to Falcon's `on_get`, `on_post`, etc methods, Responder features an `on_request` method, which gets called on every type of request, much like Requests.
- In addition to Falcon's `on_get`, `on_post`, etc methods, Responder features an
`on_request` method, which gets called on every type of request, much like Requests.
- A production static file server is built-in.
- Uvicorn built-in as a production web server. I would have chosen Gunicorn, but it doesn't run on Windows. Plus, Uvicorn serves well to protect against slowloris attacks, making nginx unnecessary in production.
- GraphQL support, via Graphene. The goal here is to have any GraphQL query exposable at any route, magically.
- Uvicorn built-in as a production web server. I would have chosen Gunicorn, but it
doesn't run on Windows. Plus, Uvicorn serves well to protect against slowloris
attacks, making nginx unnecessary in production.
- GraphQL support, via Graphene. The goal here is to have any GraphQL query exposable at
any route, magically.
- Provide an official way to run webpack.
+1 -1
View File
@@ -4,7 +4,7 @@ atomicwrites==1.2.1
attrs==18.2.0
babel==2.6.0
black==18.9b0
bleach==3.0.2
bleach==3.1.4
certifi==2018.8.24
cffi==1.11.5
chardet==3.0.4
+4 -4
View File
@@ -1,7 +1,7 @@
/* Hide module name and default value for environment variable section */
div[id$='environment-variables'] code.descclassname {
display: none;
div[id$="environment-variables"] code.descclassname {
display: none;
}
div[id$='environment-variables'] em.property {
display: none;
div[id$="environment-variables"] em.property {
display: none;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+142 -132
View File
@@ -10,142 +10,152 @@
*/
var Konami = function (callback) {
var konami = {
addEvent: function (obj, type, fn, ref_obj) {
if (obj.addEventListener)
obj.addEventListener(type, fn, false);
else if (obj.attachEvent) {
// IE
obj["e" + type + fn] = fn;
obj[type + fn] = function () {
obj["e" + type + fn](window.event, ref_obj);
}
obj.attachEvent("on" + type, obj[type + fn]);
}
},
removeEvent: function (obj, eventName, eventCallback) {
if (obj.removeEventListener) {
obj.removeEventListener(eventName, eventCallback);
} else if (obj.attachEvent) {
obj.detachEvent(eventName);
}
},
input: "",
pattern: "38384040373937396665",
keydownHandler: function (e, ref_obj) {
if (ref_obj) {
konami = ref_obj;
} // IE
konami.input += e ? e.keyCode : event.keyCode;
if (konami.input.length > konami.pattern.length) {
konami.input = konami.input.substr((konami.input.length - konami.pattern.length));
}
if (konami.input === konami.pattern) {
konami.code(konami._currentLink);
konami.input = '';
e.preventDefault();
return false;
}
},
load: function (link) {
this._currentLink = link;
this.addEvent(document, "keydown", this.keydownHandler, this);
this.iphone.load(link);
},
unload: function () {
this.removeEvent(document, 'keydown', this.keydownHandler);
this.iphone.unload();
},
code: function (link) {
window.location = link
},
iphone: {
start_x: 0,
start_y: 0,
stop_x: 0,
stop_y: 0,
tap: false,
capture: false,
orig_keys: "",
keys: ["UP", "UP", "DOWN", "DOWN", "LEFT", "RIGHT", "LEFT", "RIGHT", "TAP", "TAP"],
input: [],
code: function (link) {
konami.code(link);
},
touchmoveHandler: function (e) {
if (e.touches.length === 1 && konami.iphone.capture === true) {
var touch = e.touches[0];
konami.iphone.stop_x = touch.pageX;
konami.iphone.stop_y = touch.pageY;
konami.iphone.tap = false;
konami.iphone.capture = false;
konami.iphone.check_direction();
}
},
touchendHandler: function () {
konami.iphone.input.push(konami.iphone.check_direction());
if (konami.iphone.input.length > konami.iphone.keys.length) konami.iphone.input.shift();
if (konami.iphone.input.length === konami.iphone.keys.length) {
var match = true;
for (var i = 0; i < konami.iphone.keys.length; i++) {
if (konami.iphone.input[i] !== konami.iphone.keys[i]) {
match = false;
}
}
if (match) {
konami.iphone.code(konami._currentLink);
}
}
},
touchstartHandler: function (e) {
konami.iphone.start_x = e.changedTouches[0].pageX;
konami.iphone.start_y = e.changedTouches[0].pageY;
konami.iphone.tap = true;
konami.iphone.capture = true;
},
load: function (link) {
this.orig_keys = this.keys;
konami.addEvent(document, "touchmove", this.touchmoveHandler);
konami.addEvent(document, "touchend", this.touchendHandler, false);
konami.addEvent(document, "touchstart", this.touchstartHandler);
},
unload: function () {
konami.removeEvent(document, 'touchmove', this.touchmoveHandler);
konami.removeEvent(document, 'touchend', this.touchendHandler);
konami.removeEvent(document, 'touchstart', this.touchstartHandler);
},
check_direction: function () {
x_magnitude = Math.abs(this.start_x - this.stop_x);
y_magnitude = Math.abs(this.start_y - this.stop_y);
x = ((this.start_x - this.stop_x) < 0) ? "RIGHT" : "LEFT";
y = ((this.start_y - this.stop_y) < 0) ? "DOWN" : "UP";
result = (x_magnitude > y_magnitude) ? x : y;
result = (this.tap === true) ? "TAP" : result;
return result;
}
var konami = {
addEvent: function (obj, type, fn, ref_obj) {
if (obj.addEventListener) obj.addEventListener(type, fn, false);
else if (obj.attachEvent) {
// IE
obj["e" + type + fn] = fn;
obj[type + fn] = function () {
obj["e" + type + fn](window.event, ref_obj);
};
obj.attachEvent("on" + type, obj[type + fn]);
}
},
removeEvent: function (obj, eventName, eventCallback) {
if (obj.removeEventListener) {
obj.removeEventListener(eventName, eventCallback);
} else if (obj.attachEvent) {
obj.detachEvent(eventName);
}
},
input: "",
pattern: "38384040373937396665",
keydownHandler: function (e, ref_obj) {
if (ref_obj) {
konami = ref_obj;
} // IE
konami.input += e ? e.keyCode : event.keyCode;
if (konami.input.length > konami.pattern.length) {
konami.input = konami.input.substr(konami.input.length - konami.pattern.length);
}
if (konami.input === konami.pattern) {
konami.code(konami._currentLink);
konami.input = "";
e.preventDefault();
return false;
}
},
load: function (link) {
this._currentLink = link;
this.addEvent(document, "keydown", this.keydownHandler, this);
this.iphone.load(link);
},
unload: function () {
this.removeEvent(document, "keydown", this.keydownHandler);
this.iphone.unload();
},
code: function (link) {
window.location = link;
},
iphone: {
start_x: 0,
start_y: 0,
stop_x: 0,
stop_y: 0,
tap: false,
capture: false,
orig_keys: "",
keys: [
"UP",
"UP",
"DOWN",
"DOWN",
"LEFT",
"RIGHT",
"LEFT",
"RIGHT",
"TAP",
"TAP",
],
input: [],
code: function (link) {
konami.code(link);
},
touchmoveHandler: function (e) {
if (e.touches.length === 1 && konami.iphone.capture === true) {
var touch = e.touches[0];
konami.iphone.stop_x = touch.pageX;
konami.iphone.stop_y = touch.pageY;
konami.iphone.tap = false;
konami.iphone.capture = false;
konami.iphone.check_direction();
}
}
},
touchendHandler: function () {
konami.iphone.input.push(konami.iphone.check_direction());
typeof callback === "string" && konami.load(callback);
if (typeof callback === "function") {
konami.code = callback;
konami.load();
}
if (konami.iphone.input.length > konami.iphone.keys.length)
konami.iphone.input.shift();
return konami;
if (konami.iphone.input.length === konami.iphone.keys.length) {
var match = true;
for (var i = 0; i < konami.iphone.keys.length; i++) {
if (konami.iphone.input[i] !== konami.iphone.keys[i]) {
match = false;
}
}
if (match) {
konami.iphone.code(konami._currentLink);
}
}
},
touchstartHandler: function (e) {
konami.iphone.start_x = e.changedTouches[0].pageX;
konami.iphone.start_y = e.changedTouches[0].pageY;
konami.iphone.tap = true;
konami.iphone.capture = true;
},
load: function (link) {
this.orig_keys = this.keys;
konami.addEvent(document, "touchmove", this.touchmoveHandler);
konami.addEvent(document, "touchend", this.touchendHandler, false);
konami.addEvent(document, "touchstart", this.touchstartHandler);
},
unload: function () {
konami.removeEvent(document, "touchmove", this.touchmoveHandler);
konami.removeEvent(document, "touchend", this.touchendHandler);
konami.removeEvent(document, "touchstart", this.touchstartHandler);
},
check_direction: function () {
x_magnitude = Math.abs(this.start_x - this.stop_x);
y_magnitude = Math.abs(this.start_y - this.stop_y);
x = this.start_x - this.stop_x < 0 ? "RIGHT" : "LEFT";
y = this.start_y - this.stop_y < 0 ? "DOWN" : "UP";
result = x_magnitude > y_magnitude ? x : y;
result = this.tap === true ? "TAP" : result;
return result;
},
},
};
typeof callback === "string" && konami.load(callback);
if (typeof callback === "function") {
konami.code = callback;
konami.load();
}
return konami;
};
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = Konami;
if (typeof module !== "undefined" && typeof module.exports !== "undefined") {
module.exports = Konami;
} else {
if (typeof define === 'function' && define.amd) {
define([], function() {
return Konami;
});
} else {
window.Konami = Konami;
}
if (typeof define === "function" && define.amd) {
define([], function () {
return Konami;
});
} else {
window.Konami = Konami;
}
}
+71 -37
View File
@@ -1,5 +1,11 @@
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/7584432/7586812/css/fonts.css" />
<script type="text/javascript">$('#searchbox').hide(0);</script>
<link
rel="stylesheet"
type="text/css"
href="https://cloud.typography.com/7584432/7586812/css/fonts.css"
/>
<script type="text/javascript">
$("#searchbox").hide(0);
</script>
<!--Alabaster (krTheme++) Hacks -->
<!-- CSS Adjustments (I'm very picky.) -->
@@ -39,9 +45,7 @@
}
.method {
margin-bottom: 2em;
}
.si,
@@ -80,8 +84,6 @@
margin-top: -1em;
}
/* "Quick Search" should be not be shown for now. */
div#searchbox h3 {
display: none;
@@ -118,10 +120,12 @@
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-127383416-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag('config', 'UA-127383416-1');
gtag("config", "UA-127383416-1");
</script>
<!-- There are no more hacks. -->
@@ -130,7 +134,10 @@
<script src="{{ pathto('_static/', 1) }}/konami.js"></script>
<script>
var easter_egg = new Konami('https://www.myfortunecookie.co.uk/fortunes/' + (Math.floor(Math.random() * 152) + 1));
var easter_egg = new Konami(
"https://www.myfortunecookie.co.uk/fortunes/" +
(Math.floor(Math.random() * 152) + 1)
);
</script>
<style>
@@ -140,67 +147,94 @@
</style>
<!-- GitHub Logo -->
<a href="https://github.com/kennethreitz/responder" class="github-corner" aria-label="View source on GitHub">
<svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;"
aria-hidden="true">
<a
href="https://github.com/kennethreitz/responder"
class="github-corner"
aria-label="View source on GitHub"
>
<svg
width="80"
height="80"
viewBox="0 0 250 250"
style="fill: #151513; color: #fff; position: absolute; top: 0; border: 0; right: 0"
aria-hidden="true"
>
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
fill="currentColor" class="octo-body"></path>
<path
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
fill="currentColor"
style="transform-origin: 130px 106px"
class="octo-arm"
></path>
<path
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
fill="currentColor"
class="octo-body"
></path>
</svg>
</a>
<style>
.github-corner:hover .octo-arm {
animation: octocat-wave 560ms ease-in-out
animation: octocat-wave 560ms ease-in-out;
}
@keyframes octocat-wave {
0%,
100% {
transform: rotate(0)
transform: rotate(0);
}
20%,
60% {
transform: rotate(-25deg)
transform: rotate(-25deg);
}
40%,
80% {
transform: rotate(10deg)
transform: rotate(10deg);
}
}
@media (max-width:500px) {
@media (max-width: 500px) {
.github-corner:hover .octo-arm {
animation: none
animation: none;
}
.github-corner .octo-arm {
animation: octocat-wave 560ms ease-in-out
animation: octocat-wave 560ms ease-in-out;
}
}
</style>
<!-- That was not a hack. That was art.
<!-- UserVoice JavaScript SDK (only needed once on a page) -->
<script>(function () { var uv = document.createElement('script'); uv.type = 'text/javascript'; uv.async = true; uv.src = '//widget.uservoice.com/f4AQraEfwInlMzkexfRLg.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(uv, s) })()</script>
<script>
(function () {
var uv = document.createElement("script");
uv.type = "text/javascript";
uv.async = true;
uv.src = "//widget.uservoice.com/f4AQraEfwInlMzkexfRLg.js";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(uv, s);
})();
</script>
<!-- A tab to launch the Classic Widget -->
<script>
UserVoice = window.UserVoice || [];
UserVoice.push(['showTab', 'classic_widget', {
mode: 'feedback',
primary_color: '#fa8c28',
link_color: '#0a8cc6',
forum_id: 913660,
tab_label: 'Got feedback?',
tab_color: '#00994f',
tab_position: 'bottom-left',
tab_inverted: true
}]);
UserVoice.push([
"showTab",
"classic_widget",
{
mode: "feedback",
primary_color: "#fa8c28",
link_color: "#0a8cc6",
forum_id: 913660,
tab_label: "Got feedback?",
tab_color: "#00994f",
tab_position: "bottom-left",
tab_inverted: true,
},
]);
</script>
+67 -28
View File
@@ -1,54 +1,93 @@
<p class="logo">
<a href="{{ pathto(master_doc) }}">
<img class="logo" src="{{ pathto('_static/responder.png', 1) }}" title="https://kennethreitz.org/tattoos" />
<img
class="logo"
src="{{ pathto('_static/responder.png', 1) }}"
title="https://kennethreitz.org/tattoos"
/>
</a>
</p>
<p>
<iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&repo=responder&type=watch&count=true&size=large"
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
<iframe
src="https://ghbtns.com/github-btn.html?user=kennethreitz&repo=responder&type=watch&count=true&size=large"
allowtransparency="true"
frameborder="0"
scrolling="0"
width="200px"
height="35px"
></iframe>
</p>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css"
/>
<style>
.algolia-autocomplete{
width: 100%;
height: 1.5em
}
.algolia-autocomplete a{
border-bottom: none !important;
}
#doc_search{
width: 100%;
height: 100%;
}
.algolia-autocomplete {
width: 100%;
height: 1.5em;
}
.algolia-autocomplete a {
border-bottom: none !important;
}
#doc_search {
width: 100%;
height: 100%;
}
</style>
<input id="doc_search" placeholder="Search the doc" autofocus/>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js" onload="docsearch({
<input id="doc_search" placeholder="Search the doc" autofocus />
<script
type="text/javascript"
src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js"
onload="docsearch({
apiKey: 'ac965312db252e0496283c75c6f76f0b',
indexName: 'python-responder',
inputSelector: '#doc_search',
debug: false // Set debug to true if you want to inspect the dropdown
})" async></script>
})"
async
></script>
<p>
<strong>Responder</strong> is a web service framework, written for human beings.
</p>
<p><strong>Responder</strong> is a web service framework, written for human beings.</p>
<h3>Stay Informed</h3>
<p>Receive updates on new releases and upcoming projects.</p>
<p><iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=true" allowtransparency="true"
frameborder="0" scrolling="0" width="200" height="20"></iframe></p>
<p><a href="https://twitter.com/kennethreitz" class="twitter-follow-button" data-show-count="false">Follow
@kennethreitz</a>
<script>!function (d, s, id) { var js, fjs = d.getElementsByTagName(s)[0], p = /^http:/.test(d.location) ? 'http' : 'https'; if (!d.getElementById(id)) { js = d.createElement(s); js.id = id; js.src = p + '://platform.twitter.com/widgets.js'; fjs.parentNode.insertBefore(js, fjs); } }(document, 'script', 'twitter-wjs');</script>
<p>
<iframe
src="https://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=true"
allowtransparency="true"
frameborder="0"
scrolling="0"
width="200"
height="20"
></iframe>
</p>
<p>
<a
href="https://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>
<ul>
<li><a href="http://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
<li><a href="http://pypi.python.org/pypi/responder">Responder @ PyPI</a></li>
<li><a href="http://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
+67 -28
View File
@@ -1,54 +1,93 @@
<p class="logo">
<a href="{{ pathto(master_doc) }}">
<img class="logo" src="{{ pathto('_static/responder.png', 1) }}" title="https://kennethreitz.org/tattoos" />
<img
class="logo"
src="{{ pathto('_static/responder.png', 1) }}"
title="https://kennethreitz.org/tattoos"
/>
</a>
</p>
<p>
<iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&repo=responder&type=watch&count=true&size=large"
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
<iframe
src="https://ghbtns.com/github-btn.html?user=kennethreitz&repo=responder&type=watch&count=true&size=large"
allowtransparency="true"
frameborder="0"
scrolling="0"
width="200px"
height="35px"
></iframe>
</p>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css"
/>
<style>
.algolia-autocomplete{
width: 100%;
height: 1.5em
}
.algolia-autocomplete a{
border-bottom: none !important;
}
#doc_search{
width: 100%;
height: 100%;
}
.algolia-autocomplete {
width: 100%;
height: 1.5em;
}
.algolia-autocomplete a {
border-bottom: none !important;
}
#doc_search {
width: 100%;
height: 100%;
}
</style>
<input id="doc_search" placeholder="Search the doc" autofocus/>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js" onload="docsearch({
<input id="doc_search" placeholder="Search the doc" autofocus />
<script
type="text/javascript"
src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js"
onload="docsearch({
apiKey: 'ac965312db252e0496283c75c6f76f0b',
indexName: 'python-responder',
inputSelector: '#doc_search',
debug: false // Set debug to true if you want to inspect the dropdown
})" async></script>
})"
async
></script>
<p>
<strong>Responder</strong> is a web service framework, written for human beings.
</p>
<p><strong>Responder</strong> is a web service framework, written for human beings.</p>
<h3>Stay Informed</h3>
<p>Receive updates on new releases and upcoming projects.</p>
<p><iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=true" allowtransparency="true"
frameborder="0" scrolling="0" width="200" height="20"></iframe></p>
<p><a href="https://twitter.com/kennethreitz" class="twitter-follow-button" data-show-count="false">Follow
@kennethreitz</a>
<script>!function (d, s, id) { var js, fjs = d.getElementsByTagName(s)[0], p = /^http:/.test(d.location) ? 'http' : 'https'; if (!d.getElementById(id)) { js = d.createElement(s); js.id = id; js.src = p + '://platform.twitter.com/widgets.js'; fjs.parentNode.insertBefore(js, fjs); } }(document, 'script', 'twitter-wjs');</script>
<p>
<iframe
src="https://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=true"
allowtransparency="true"
frameborder="0"
scrolling="0"
width="200"
height="20"
></iframe>
</p>
<p>
<a
href="https://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>
<ul>
<li><a href="http://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
<li><a href="http://pypi.python.org/pypi/responder">Responder @ PyPI</a></li>
<li><a href="http://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
+2 -1
View File
@@ -85,9 +85,10 @@ Usage::
def hello(req, resp, name):
resp.html = templates.render("hello.html", name=name)
Also a ``render_async`` is available::
templates = Templates(enable_async=True)
resp.html = await templates.render_async("hello.html", who=who)
You can also use the existing ``api.template(filename, *args, **kwargs)`` to render templates::
+3
View File
@@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta:__legacy__"
+2 -2
View File
@@ -1,5 +1,5 @@
build:
image: latest
image: latest
python:
version: 3.6
version: 3.6
+1 -1
View File
@@ -1 +1 @@
__version__ = "1.3.2"
__version__ = "2.0.6"
+11 -36
View File
@@ -13,7 +13,6 @@ from starlette.middleware.gzip import GZipMiddleware
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
from starlette.middleware.trustedhost import TrustedHostMiddleware
from starlette.middleware.sessions import SessionMiddleware
from starlette.routing import Lifespan
from starlette.staticfiles import StaticFiles
from starlette.testclient import TestClient
from starlette.websockets import WebSocket
@@ -31,10 +30,10 @@ from .templates import Templates
class API:
"""The primary web-service class.
:param static_dir: The directory to use for static files. Will be created for you if it doesn't already exist.
:param templates_dir: The directory to use for templates. Will be created for you if it doesn't already exist.
:param auto_escape: If ``True``, HTML and XML templates will automatically be escaped.
:param enable_hsts: If ``True``, send all responses to HTTPS URLs.
:param static_dir: The directory to use for static files. Will be created for you if it doesn't already exist.
:param templates_dir: The directory to use for templates. Will be created for you if it doesn't already exist.
:param auto_escape: If ``True``, HTML and XML templates will automatically be escaped.
:param enable_hsts: If ``True``, send all responses to HTTPS URLs.
"""
status_codes = status_codes
@@ -76,15 +75,6 @@ class API:
self.static_dir = static_dir
self.static_route = static_route
self.built_in_templates_dir = Path(
os.path.abspath(os.path.dirname(__file__) + "/templates")
)
if templates_dir is not None:
templates_dir = Path(os.path.abspath(templates_dir))
self.templates_dir = templates_dir or self.built_in_templates_dir
self.hsts_enabled = enable_hsts
self.cors = cors
self.cors_params = cors_params
@@ -98,10 +88,8 @@ class API:
allowed_hosts = ["*"]
self.allowed_hosts = allowed_hosts
# Make the static/templates directory if they don't exist.
for _dir in (self.static_dir, self.templates_dir):
if _dir is not None:
os.makedirs(_dir, exist_ok=True)
if self.static_dir is not None:
os.makedirs(self.static_dir, exist_ok=True)
if self.static_dir is not None:
self.mount(self.static_route, self.static_app)
@@ -128,9 +116,9 @@ class API:
if openapi or docs_route:
self.openapi = OpenAPISchema(
app=self,
title="Web Service",
version="1.0",
openapi="3.0.2",
title=title,
version=version,
openapi=openapi,
docs_route=docs_route,
description=description,
terms_of_service=terms_of_service,
@@ -153,11 +141,6 @@ class API:
self._static_app = StaticFiles(directory=self.static_dir)
return self._static_app
@staticmethod
def _notfound_wsgi_app(environ, start_response):
start_response("404 NOT FOUND", [("Content-Type", "text/plain")])
return [b"Not Found."]
def before_request(self, websocket=False):
def decorator(f):
self.router.before_request(f, websocket=websocket)
@@ -165,14 +148,6 @@ class API:
return decorator
@property
def before_http_requests(self):
return self.before_requests.get("http", [])
@property
def before_ws_requests(self):
return self.before_requests.get("ws", [])
def add_middleware(self, middleware_cls, **middleware_config):
self.app = middleware_cls(self.app, **middleware_config)
@@ -242,7 +217,7 @@ class API:
index = (self.static_dir / "index.html").resolve()
if os.path.exists(index):
with open(index, "r") as f:
resp.html = "Hello world !"
resp.html = f.read()
else:
resp.status_code = status_codes.HTTP_404
resp.text = "Not found."
@@ -287,7 +262,7 @@ class API:
: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):
"""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()
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:
assert name not in self.schemas
+1 -3
View File
@@ -283,9 +283,7 @@ class Response:
self.content = None #: A bytes representation of the response body.
self.mimetype = None
self.encoding = DEFAULT_ENCODING
self.media = (
None
) #: A Python object that will be content-negotiated and sent back to the client. Typically, in JSON formatting.
self.media = None #: A Python object that will be content-negotiated and sent back to the client. Typically, in JSON formatting.
self._stream = None
self.headers = (
{}
+47 -15
View File
@@ -2,8 +2,9 @@ import asyncio
import json
import re
import inspect
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
@@ -213,10 +214,10 @@ class Router:
self.default_endpoint = (
self.default_response if default_response is None else default_response
)
self.lifespan_handler = Lifespan()
self.before_requests = (
{"http": [], "ws": []} if before_requests is None else before_requests
)
self.events = defaultdict(list)
def add_route(
self,
@@ -228,7 +229,7 @@ class Router:
before_request=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 endpoint: The endpoint for the route -- can be callable, or class.
:param default: If ``True``, all unknown requests will route to this view.
@@ -256,10 +257,23 @@ class Router:
self.routes.append(route)
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)
def add_event_handler(self, event_type, handler):
assert event_type in (
"startup",
"shutdown",
), f"Only 'startup' and 'shutdown' events are supported, not {event_type}."
self.events[event_type].append(handler)
async def trigger_event(self, event_type):
for handler in self.events.get(event_type, []):
if asyncio.iscoroutinefunction(handler):
await handler()
else:
handler()
def before_request(self, endpoint, websocket=False):
if websocket:
self.before_requests.setdefault("ws", []).append(endpoint)
@@ -292,16 +306,42 @@ class Router:
return route
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):
assert scope["type"] in ("http", "websocket", "lifespan")
if scope["type"] == "lifespan":
await self.lifespan_handler(scope, receive, send)
await self.lifespan(scope, receive, send)
return
path = scope["path"]
root_path = scope.get("root_path", "")
# Check "primary" mounted routes first (before submounted apps)
route = self._resolve_route(scope)
scope["before_requests"] = self.before_requests
if route is not None:
await route(scope, receive, send)
return
# Call into a submounted app, if one exists.
for path_prefix, app in self.apps.items():
if path.startswith(path_prefix):
@@ -315,12 +355,4 @@ class Router:
await app(scope, receive, send)
return
route = self._resolve_route(scope)
scope["before_requests"] = self.before_requests
if route is not None:
await route(scope, receive, send)
return
await self.default_response(scope, receive, send)
await self.default_endpoint(scope, receive, send)
+3 -3
View File
@@ -5,12 +5,12 @@ from starlette.staticfiles import StaticFiles
class StaticFiles(StaticFiles):
"""I've created an issue to disccuss allowing multiple directories in starletter's `StaticFiles`.
https://github.com/encode/starlette/issues/625
I've also made a PR to add this method to starlette StaticFiles
Once accepted we will remove this.
https://github.com/encode/starlette/pull/626
"""
+6 -2
View File
@@ -4,10 +4,14 @@ import jinja2
class Templates:
def __init__(self, directory="templates", autoescape=True, context=None):
def __init__(
self, directory="templates", autoescape=True, context=None, enable_async=False
):
self.directory = directory
self._env = jinja2.Environment(
loader=jinja2.FileSystemLoader([str(self.directory)]), autoescape=autoescape
loader=jinja2.FileSystemLoader([str(self.directory)]),
autoescape=autoescape,
enable_async=enable_async,
)
self.default_context = {} if context is None else {**context}
self._env.globals.update(self.default_context)
+3 -3
View File
@@ -22,15 +22,14 @@ if sys.argv[-1] == "publish":
sys.exit()
required = [
"starlette==0.12.*",
"uvicorn>=0.7, <0.9",
"starlette==0.13.*",
"uvicorn>=0.11.7,<0.13",
"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",
@@ -40,6 +39,7 @@ required = [
"docopt",
"requests-toolbelt",
"apistar",
"itsdangerous",
]
+9
View File
@@ -56,3 +56,12 @@ def schema():
return f"Hello {name}"
return graphene.Schema(query=Query)
@pytest.fixture
def template_path(tmpdir):
# create a Jinja template file on the filesystem
template_name = "test.html"
template_file = tmpdir.mkdir("static").join(template_name)
template_file.write("{{ var }}")
return template_file
+64 -33
View File
@@ -8,6 +8,7 @@ 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.responses import PlainTextResponse
@@ -22,8 +23,7 @@ def test_api_basic_route(api):
def test_route_repr():
def home(req, resp):
"""Home page
"""
"""Home page"""
resp.text = "Hello !"
route = Route("/", home)
@@ -36,8 +36,7 @@ def test_route_repr():
def test_websocket_route_repr():
def chat_endpoint(ws):
"""Chat
"""
"""Chat"""
pass
route = WebSocketRoute("/", chat_endpoint)
@@ -590,6 +589,39 @@ def test_template_string_rendering(api):
assert r.text == "hello"
def test_template_rendering(template_path):
api = responder.API(templates_dir=template_path.dirpath())
@api.route("/")
def view(req, resp):
resp.content = api.template(template_path.basename, var="hello")
r = api.requests.get(api.url_for(view))
assert r.text == "hello"
def test_template(api, template_path):
templates = Templates(directory=template_path.dirpath())
@api.route("/{var}/")
def view(req, resp, var):
resp.html = templates.render(template_path.basename, var=var)
r = api.requests.get("/test/")
assert r.text == "test"
def test_template_async(api, template_path):
templates = Templates(directory=template_path.dirpath(), enable_async=True)
@api.route("/{var}/async")
async def view_async(req, resp, var):
resp.html = await templates.render_async(template_path.basename, var=var)
r = api.requests.get("/test/async")
assert r.text == "test"
def test_file_uploads(api):
@api.route("/")
async def upload(req, resp):
@@ -716,7 +748,7 @@ def test_redirects(api, session):
def one(req, resp):
resp.text = "redirected"
assert session.get("/1").url == "http://;/1"
assert session.get("/2").url == "http://;/1"
def test_session_thoroughly(api, session):
@@ -751,8 +783,12 @@ def test_before_response(api, session):
assert "x-pizza" in r.headers
def test_allowed_hosts():
api = responder.API(allowed_hosts=[";", "tenant.;"])
@pytest.mark.parametrize("enable_hsts", [True, False])
@pytest.mark.parametrize("cors", [True, False])
def test_allowed_hosts(enable_hsts, cors):
api = responder.API(
allowed_hosts=[";", "tenant.;"], enable_hsts=enable_hsts, cors=cors
)
@api.route("/")
def get(req, resp):
@@ -816,14 +852,15 @@ def create_asset(static_dir, name=None, parent_dir=None):
return asset
def test_staticfiles(tmpdir):
@pytest.mark.parametrize("static_route", [None, "/static", "/custom/static/route"])
def test_staticfiles(tmpdir, static_route):
static_dir = tmpdir.mkdir("static")
asset1 = create_asset(static_dir)
parent_dir = "css"
asset2 = create_asset(static_dir, name="asset2", parent_dir=parent_dir)
api = responder.API(static_dir=str(static_dir))
api = responder.API(static_dir=str(static_dir), static_route=static_route)
session = api.session()
static_route = api.static_route
@@ -847,30 +884,6 @@ def test_staticfiles(tmpdir):
assert r.status_code == api.status_codes.HTTP_404
def test_staticfiles_custom_route(tmpdir):
static_dir = tmpdir.mkdir("static")
static_route = "/custom/static/route"
asset = create_asset(static_dir)
api = responder.API(static_dir=str(static_dir), static_route=static_route)
session = api.session()
static_route = api.static_route
# ok
r = session.get(f"{static_route}/{asset.basename}")
assert r.status_code == api.status_codes.HTTP_200
# Asset not found
r = session.get(f"{static_route}/not_found.css")
assert r.status_code == api.status_codes.HTTP_404
# Not found on dir listing
r = session.get(f"{static_route}")
assert r.status_code == api.status_codes.HTTP_404
def test_staticfiles_none_dir(tmpdir):
api = responder.API(static_dir=None)
session = api.session()
@@ -985,3 +998,21 @@ def test_empty_req_text(api):
resp.text = "{}_{}".format(req.state.test2, req.state.test1)
assert api.requests.get(url("/")).text == "Foo_42"
def test_path_matches_route(api):
@api.route("/hello")
def home(req, resp):
resp.text = "hello world!"
route = api.path_matches_route({"type": "http", "path": "/hello"})
assert route.endpoint_name == "home"
assert not api.path_matches_route({"type": "http", "path": "/foo"})
def test_route_without_endpoint(api):
# test that a route without endpoint gets a default static response
api.add_route("/")
route = api.router.routes[0]
assert route.endpoint_name == "_static_response"