mirror of
https://github.com/kennethreitz/responder.git
synced 2026-06-05 23:00:17 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f34f3c1661 | |||
| d4f83c978c | |||
| 212f280c19 | |||
| f3e2450636 | |||
| d6d496018d | |||
| 78be7fc772 | |||
| 6ebadd8469 | |||
| 557750c8d4 | |||
| e85ef27e6c | |||
| 4ca961a1b4 | |||
| 6a9110e9c1 | |||
| 51ffce09ae | |||
| 1c4e96b365 | |||
| 0db70e8edd | |||
| e46b3a5e19 | |||
| fdd3d4d85a | |||
| a1bfbda05b | |||
| f309ad7746 |
@@ -1,3 +1,6 @@
|
||||
# v0.1.4
|
||||
- Stability.
|
||||
|
||||
# v0.1.3
|
||||
- Sessions support.
|
||||
|
||||
|
||||
Generated
+12
-4
@@ -72,6 +72,12 @@
|
||||
],
|
||||
"version": "==7.0"
|
||||
},
|
||||
"docopt": {
|
||||
"hashes": [
|
||||
"sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"
|
||||
],
|
||||
"version": "==0.6.2"
|
||||
},
|
||||
"graphene": {
|
||||
"hashes": [
|
||||
"sha256:b8ec446d17fa68721636eaad3d6adc1a378cb6323e219814c8f98c9928fc9642",
|
||||
@@ -114,9 +120,10 @@
|
||||
},
|
||||
"itsdangerous": {
|
||||
"hashes": [
|
||||
"sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519"
|
||||
"sha256:a7de3201740a857380421ef286166134e10fe58846bcefbc9d6424a69a0b99ec",
|
||||
"sha256:aca4fc561b7671115a2156f625f2eaa5e0e3527e0adf2870340e7968c0a81f85"
|
||||
],
|
||||
"version": "==0.24"
|
||||
"version": "==1.0.0"
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
@@ -473,9 +480,10 @@
|
||||
},
|
||||
"itsdangerous": {
|
||||
"hashes": [
|
||||
"sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519"
|
||||
"sha256:a7de3201740a857380421ef286166134e10fe58846bcefbc9d6424a69a0b99ec",
|
||||
"sha256:aca4fc561b7671115a2156f625f2eaa5e0e3527e0adf2870340e7968c0a81f85"
|
||||
],
|
||||
"version": "==0.24"
|
||||
"version": "==1.0.0"
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
|
||||
@@ -169,6 +169,11 @@ Responder has built-in support for cookie-based sessions. To enable cookie-based
|
||||
|
||||
A cookie called ``Responder-Session`` will be set, which contains all the data in ``resp.session``. It is signed, for verification purposes.
|
||||
|
||||
You can easily read a Request's session data, that can be trusted to have originated from the API::
|
||||
|
||||
>>> req.session
|
||||
{'username': 'kennethreitz'}
|
||||
|
||||
**Note**: if you are using this in production, you should pass the ``secret_key`` argument to ``API(...)``.
|
||||
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.1.3"
|
||||
__version__ = "0.1.4"
|
||||
|
||||
+35
-39
@@ -32,6 +32,7 @@ class API:
|
||||
|
||||
: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.
|
||||
"""
|
||||
|
||||
@@ -47,6 +48,7 @@ class API:
|
||||
static_dir="static",
|
||||
static_route="/static",
|
||||
templates_dir="templates",
|
||||
auto_escape=True,
|
||||
secret_key="NOTASECRET",
|
||||
enable_hsts=False,
|
||||
):
|
||||
@@ -62,6 +64,7 @@ class API:
|
||||
)
|
||||
self.routes = {}
|
||||
self.schemas = {}
|
||||
self.session_cookie = 'Responder-Session'
|
||||
|
||||
self.hsts_enabled = enable_hsts
|
||||
self.static_files = StaticFiles(directory=str(self.static_dir))
|
||||
@@ -86,6 +89,20 @@ class API:
|
||||
if self.hsts_enabled:
|
||||
self.add_middleware(HTTPSRedirectMiddleware)
|
||||
|
||||
# Jinja enviroment
|
||||
self.jinja_env = jinja2.Environment(
|
||||
loader=jinja2.FileSystemLoader(
|
||||
[str(self.templates_dir), str(self.built_in_templates_dir)],
|
||||
followlinks=True,
|
||||
),
|
||||
autoescape=jinja2.select_autoescape(
|
||||
["html", "xml"] if auto_escape else []
|
||||
)
|
||||
)
|
||||
self.jinja_values_base = {
|
||||
"api": self, # Give reference to self.
|
||||
}
|
||||
|
||||
@property
|
||||
def _apispec(self):
|
||||
spec = APISpec(
|
||||
@@ -191,7 +208,7 @@ class API:
|
||||
|
||||
if resp.session:
|
||||
data = self._signer.sign(json.dumps(resp.session).encode("utf-8"))
|
||||
resp.cookies["Responder-Session"] = data.decode("utf-8")
|
||||
resp.cookies[self.session_cookie] = data.decode("utf-8")
|
||||
|
||||
async def _dispatch_request(self, req):
|
||||
# Set formats on Request object.
|
||||
@@ -216,13 +233,6 @@ class API:
|
||||
if self.routes[route].is_graphql:
|
||||
await self.graphql_response(req, resp, schema=view)
|
||||
else:
|
||||
# WSGI App.
|
||||
# try:
|
||||
# return view(
|
||||
# environ=req._environ, start_response=req._start_response
|
||||
# )
|
||||
# except TypeError:
|
||||
# pass
|
||||
pass
|
||||
|
||||
# Run on_request first.
|
||||
@@ -406,52 +416,38 @@ class API:
|
||||
"""Given a static asset, return its URL path."""
|
||||
return f"{self.static_route}/{str(asset)}"
|
||||
|
||||
def template(self, name_, auto_escape=True, **values):
|
||||
def template(self, name_, **values):
|
||||
"""Renders the given `jinja2 <http://jinja.pocoo.org/docs/>`_ template, with provided values supplied.
|
||||
|
||||
Note: The current ``api`` instance is always passed into the view.
|
||||
Note: The current ``api`` instance is by default passed into the view. This is set in the dict ``api.jinja_values_base``.
|
||||
|
||||
:param name_: The filename of the jinja2 template, in ``templates_dir``.
|
||||
:param auto_escape: If ``True``, HTML and XML will automatically be escaped.
|
||||
:param values: Data to pass into the template.
|
||||
"""
|
||||
# Give reference to self.
|
||||
values.update(api=self)
|
||||
# Prepopulate values with base
|
||||
values = {
|
||||
**self.jinja_values_base,
|
||||
**values,
|
||||
}
|
||||
|
||||
env = jinja2.Environment(
|
||||
loader=jinja2.FileSystemLoader(
|
||||
[str(self.templates_dir), str(self.built_in_templates_dir)],
|
||||
followlinks=True,
|
||||
),
|
||||
autoescape=jinja2.select_autoescape(["html", "xml"] if auto_escape else []),
|
||||
)
|
||||
|
||||
template = env.get_template(name_)
|
||||
template = self.jinja_env.get_template(name_)
|
||||
return template.render(**values)
|
||||
|
||||
def template_string(self, s, auto_escape=True, **values):
|
||||
def template_string(self, s_, **values):
|
||||
"""Renders the given `jinja2 <http://jinja.pocoo.org/docs/>`_ template string, with provided values supplied.
|
||||
|
||||
Note: The current ``api`` instance is always passed into the view.
|
||||
Note: The current ``api`` instance is by default passed into the view. This is set in the dict ``api.jinja_values_base``.
|
||||
|
||||
:param s: The template to use.
|
||||
:param auto_escape: If ``True``, HTML and XML will automatically be escaped.
|
||||
:param s_: The template to use.
|
||||
:param values: Data to pass into the template.
|
||||
"""
|
||||
# Give reference to self.
|
||||
values.update(api=self)
|
||||
# Prepopulate values with base
|
||||
values = {
|
||||
**self.jinja_values_base,
|
||||
**values,
|
||||
}
|
||||
|
||||
if auto_escape:
|
||||
env = jinja2.Environment(
|
||||
loader=jinja2.BaseLoader,
|
||||
autoescape=jinja2.select_autoescape(["html", "xml"]),
|
||||
)
|
||||
else:
|
||||
env = jinja2.Environment(
|
||||
loader=jinja2.BaseLoader, autoescape=jinja2.select_autoescape([])
|
||||
)
|
||||
|
||||
template = env.from_string(s)
|
||||
template = self.jinja_env.from_string(s_)
|
||||
return template.render(**values)
|
||||
|
||||
def run(self, address=None, port=None, **options):
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
"""Responder.
|
||||
|
||||
Usage:
|
||||
responder
|
||||
responder run [--build] [--debug] <module>
|
||||
responder build
|
||||
responder --version
|
||||
|
||||
Options:
|
||||
-h --help Show this screen.
|
||||
-v --version Show version.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import docopt
|
||||
from .__version__ import __version__
|
||||
|
||||
|
||||
def cli():
|
||||
args = docopt.docopt(
|
||||
__doc__, argv=None, help=True, version=__version__, options_first=False
|
||||
)
|
||||
|
||||
module = args["<module>"]
|
||||
build = args["build"] or args["--build"]
|
||||
run = args["run"]
|
||||
|
||||
if build:
|
||||
os.system("npm run build")
|
||||
|
||||
if run:
|
||||
split_module = module.split(":")
|
||||
|
||||
if len(split_module) > 1:
|
||||
module = split_module[0]
|
||||
prop = split_module[1]
|
||||
else:
|
||||
prop = "api"
|
||||
|
||||
app = __import__(module)
|
||||
getattr(app, prop).run()
|
||||
@@ -1,2 +1,3 @@
|
||||
from .api import API
|
||||
from .models import Request, Response
|
||||
from .cli import cli
|
||||
|
||||
+5
-4
@@ -108,7 +108,7 @@ class Request:
|
||||
def session(self):
|
||||
"""The session data, in dict form, from the Request."""
|
||||
if "Responder-Session" in self.cookies:
|
||||
data = self.cookies["Responder-Session"]
|
||||
data = self.cookies[self.api.session_cookie]
|
||||
data = self.api._signer.unsign(data)
|
||||
return json.loads(data)
|
||||
return {}
|
||||
@@ -143,7 +143,6 @@ class Request:
|
||||
cookies = RequestsCookieJar()
|
||||
cookie_header = self.headers.get("cookie", "")
|
||||
|
||||
# if cookie_header:
|
||||
bc = SimpleCookie(cookie_header)
|
||||
for k, v in bc.items():
|
||||
cookies[k] = v
|
||||
@@ -251,10 +250,12 @@ class Response:
|
||||
) #: A Python object that will be content-negotiated and sent back to the client. Typically, in JSON formatting.
|
||||
self.headers = (
|
||||
{}
|
||||
) #: A Python dictionary of {Key: value}, representing the headers of the response.
|
||||
) #: A Python dictionary of ``{key: value}``, representing the headers of the response.
|
||||
self.formats = formats
|
||||
self.cookies = {} #: The cookies set in the Response, as a dictionary
|
||||
self.session = req.session.copy() #: """The *cookie-based* session data, in dict form, to add to the Response."""
|
||||
self.session = (
|
||||
req.session.copy()
|
||||
) #: The cookie-based session data, in dict form, to add to the Response.
|
||||
|
||||
@property
|
||||
async def body(self):
|
||||
|
||||
@@ -38,6 +38,7 @@ required = [
|
||||
"apispec>=1.0.0b1",
|
||||
"marshmallow",
|
||||
"asgiref",
|
||||
"docopt",
|
||||
"itsdangerous",
|
||||
]
|
||||
|
||||
@@ -118,9 +119,7 @@ setup(
|
||||
author_email="me@kennethreitz.org",
|
||||
url="https://github.com/kennethreitz/responder",
|
||||
packages=find_packages(exclude=["tests"]),
|
||||
# entry_points={
|
||||
# "console_scripts": ["responder=responder:cli"]
|
||||
# },
|
||||
entry_points={"console_scripts": ["responder=responder.cli:cli"]},
|
||||
package_data={
|
||||
# "": ["LICENSE", "NOTICES"],
|
||||
# "pipenv.vendor.requests": ["*.pem"],
|
||||
|
||||
@@ -395,5 +395,13 @@ def test_sessions(api, session):
|
||||
assert "Responder-Session" in r.cookies
|
||||
|
||||
r = session.get(api.url_for(view))
|
||||
assert r.cookies['Responder-Session'] == '{"hello": "world"}.r3EB04hEEyLYIJaAXCEq3d4YEbs'
|
||||
assert r.cookies['Responder-Session'] == '{"hello": "world"}.lJVWJULPqR9kdao_oT4pUglV281bxHfGvcKQ7XF8qNqaiIZlRcMvqKNdA1-d5z7DycAx5eqmzJZoqWPP759-Cw'
|
||||
assert r.json() == {"hello": "world"}
|
||||
|
||||
def test_template_rendering(api, session):
|
||||
@api.route('/')
|
||||
def view(req, resp):
|
||||
resp.content = api.template_string("{{ var }}", var="hello")
|
||||
|
||||
r = session.get(api.url_for(view))
|
||||
assert r.text == "hello"
|
||||
|
||||
Reference in New Issue
Block a user