commit
aba45a2107
|
@ -25,7 +25,8 @@ import sys, os
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode']
|
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage',
|
||||||
|
'sphinx.ext.viewcode', 'sphinx.ext.intersphinx']
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
|
@ -86,6 +87,9 @@ pygments_style = 'sphinx'
|
||||||
# A list of ignored prefixes for module index sorting.
|
# A list of ignored prefixes for module index sorting.
|
||||||
#modindex_common_prefix = []
|
#modindex_common_prefix = []
|
||||||
|
|
||||||
|
intersphinx_mapping = {
|
||||||
|
'webob': ('http://docs.webob.org/en/latest/', None)
|
||||||
|
}
|
||||||
|
|
||||||
# -- Options for HTML output ---------------------------------------------------
|
# -- Options for HTML output ---------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,10 @@ Contents:
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
rationale
|
rationale
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
reference/index
|
reference/index
|
||||||
|
|
||||||
*Milla* is released under the terms of the `Apache License, version 2.0`_.
|
*Milla* is released under the terms of the `Apache License, version 2.0`_.
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
=====
|
||||||
|
milla
|
||||||
|
=====
|
||||||
|
|
||||||
|
.. automodule:: milla
|
||||||
|
:members:
|
||||||
|
:inherited-members:
|
|
@ -20,6 +20,13 @@ from milla.auth.decorators import *
|
||||||
from webob.exc import *
|
from webob.exc import *
|
||||||
from webob.request import *
|
from webob.request import *
|
||||||
from webob.response import *
|
from webob.response import *
|
||||||
|
import urllib
|
||||||
|
try:
|
||||||
|
import urllib.parse
|
||||||
|
except ImportError:
|
||||||
|
import urlparse
|
||||||
|
urllib.parse = urlparse
|
||||||
|
|
||||||
|
|
||||||
def allow(*methods):
|
def allow(*methods):
|
||||||
'''Specify the allowed HTTP verbs for a controller callable
|
'''Specify the allowed HTTP verbs for a controller callable
|
||||||
|
@ -35,3 +42,75 @@ def allow(*methods):
|
||||||
func.allowed_methods = methods
|
func.allowed_methods = methods
|
||||||
return func
|
return func
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class Response(Response):
|
||||||
|
''':py:class:`WebOb Response <webob.response.Response>` with minor tweaks
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
class Request(Request):
|
||||||
|
''':py:class:`WebOb Request <webob.request.BaseRequest>` with minor tweaks
|
||||||
|
'''
|
||||||
|
|
||||||
|
ResponseClass = Response
|
||||||
|
|
||||||
|
def relative_url(self, other_url, to_application=True, path_only=True,
|
||||||
|
**vars):
|
||||||
|
'''Create a new URL relative to the request URL
|
||||||
|
|
||||||
|
:param other_url: relative path to join with the request URL
|
||||||
|
:param to_application: If true, generated URL will be relative
|
||||||
|
to the application's root path, otherwise relative to the
|
||||||
|
server root
|
||||||
|
:param path_only: If true, scheme and host will be omitted
|
||||||
|
|
||||||
|
Any other keyword arguments will be encoded and appended to the URL
|
||||||
|
as querystring arguments.
|
||||||
|
'''
|
||||||
|
|
||||||
|
url = super(Request, self).relative_url(other_url, to_application)
|
||||||
|
if path_only:
|
||||||
|
url = urllib.parse.urlsplit(url).path
|
||||||
|
if vars:
|
||||||
|
url += '?' + urllib.urlencode(vars)
|
||||||
|
return url
|
||||||
|
|
||||||
|
def static_resource(self, path):
|
||||||
|
'''Return a URL to the given static resource
|
||||||
|
|
||||||
|
This method combines the defined static resource root URL with the
|
||||||
|
given path to construct a complete URL to the given resource. The
|
||||||
|
resource root should be defined in the application configuration
|
||||||
|
dictionary, under the name ``milla.static_root``, for example::
|
||||||
|
|
||||||
|
app = milla.Application(dispatcher)
|
||||||
|
app.config.update({
|
||||||
|
'milla.static_root': '/static/'
|
||||||
|
})
|
||||||
|
|
||||||
|
Then, calling ``static_resource`` on a :py:class:`Request` object
|
||||||
|
(i.e. inside a controller callable) would combine the given path
|
||||||
|
with ``/static/``, like this::
|
||||||
|
|
||||||
|
request.static_resource('/images/foo.png')
|
||||||
|
|
||||||
|
would return ``/static/images/foo.png``.
|
||||||
|
|
||||||
|
If no ``milla.static_root`` key is found in the configuration
|
||||||
|
dictionary, the path will be returned unaltered.
|
||||||
|
|
||||||
|
:param path: Path to the resource, relative to the defined root
|
||||||
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
|
root = self.config['milla.static_root']
|
||||||
|
except KeyError:
|
||||||
|
return path
|
||||||
|
|
||||||
|
if path.startswith('/'):
|
||||||
|
path = path[1:]
|
||||||
|
if not root.endswith('/'):
|
||||||
|
root += '/'
|
||||||
|
|
||||||
|
return urllib.parse.urljoin(root, path)
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
# Copyright 2011 Dustin C. Hatch
|
# Copyright 2011 Dustin C. Hatch
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
# You may obtain a copy of the License at
|
# You may obtain a copy of the License at
|
||||||
#
|
#
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
#
|
#
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -31,19 +31,19 @@ __all__ = ['Application']
|
||||||
|
|
||||||
class Application(object):
|
class Application(object):
|
||||||
'''Represents a Milla web application
|
'''Represents a Milla web application
|
||||||
|
|
||||||
Constructing an ``Application`` instance needs a dispatcher, or
|
Constructing an ``Application`` instance needs a dispatcher, or
|
||||||
alternatively, a root object that will be passed to a new
|
alternatively, a root object that will be passed to a new
|
||||||
:py:class:`milla.dispatch.traversal.Traverser`.
|
:py:class:`milla.dispatch.traversal.Traverser`.
|
||||||
|
|
||||||
:param root: A root object, passed to a traverser, which is
|
:param root: A root object, passed to a traverser, which is
|
||||||
automatically created if a root is given
|
automatically created if a root is given
|
||||||
:param dispatcher: An object implementing the dispatcher protocol
|
:param dispatcher: An object implementing the dispatcher protocol
|
||||||
|
|
||||||
``Application`` instances are WSGI applications.
|
``Application`` instances are WSGI applications.
|
||||||
|
|
||||||
.. py:attribute:: config
|
.. py:attribute:: config
|
||||||
|
|
||||||
A mapping of configuration settings. For each request, the
|
A mapping of configuration settings. For each request, the
|
||||||
configuration is copied and assigned to ``request.config``.
|
configuration is copied and assigned to ``request.config``.
|
||||||
'''
|
'''
|
||||||
|
@ -65,7 +65,7 @@ class Application(object):
|
||||||
else:
|
else:
|
||||||
return HTTPNotFound()(environ, start_response)
|
return HTTPNotFound()(environ, start_response)
|
||||||
|
|
||||||
request = webob.Request(environ)
|
request = milla.Request(environ)
|
||||||
request.config = self.config.copy()
|
request.config = self.config.copy()
|
||||||
|
|
||||||
# Sometimes, hacky applications will try to "emulate" some HTTP
|
# Sometimes, hacky applications will try to "emulate" some HTTP
|
||||||
|
@ -88,7 +88,7 @@ class Application(object):
|
||||||
start_response)
|
start_response)
|
||||||
|
|
||||||
start_response_wrapper = StartResponseWrapper(start_response)
|
start_response_wrapper = StartResponseWrapper(start_response)
|
||||||
request.start_response = start_response_wrapper
|
request.start_response = start_response_wrapper
|
||||||
try:
|
try:
|
||||||
# If the callable has an __before__ attribute, call it
|
# If the callable has an __before__ attribute, call it
|
||||||
if hasattr(func, '__before__'):
|
if hasattr(func, '__before__'):
|
||||||
|
@ -136,11 +136,11 @@ class Application(object):
|
||||||
return response.app_iter
|
return response.app_iter
|
||||||
|
|
||||||
class StartResponseWrapper():
|
class StartResponseWrapper():
|
||||||
|
|
||||||
def __init__(self, start_response):
|
def __init__(self, start_response):
|
||||||
self.start_response = start_response
|
self.start_response = start_response
|
||||||
self.called = False
|
self.called = False
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
self.called = True
|
self.called = True
|
||||||
return self.start_response(*args, **kwargs)
|
return self.start_response(*args, **kwargs)
|
||||||
|
|
|
@ -23,7 +23,8 @@ from one or more of these classes can make things significantly easier.
|
||||||
:Updater: $Author$
|
:Updater: $Author$
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import milla
|
import datetime
|
||||||
|
import milla.util
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,6 +54,9 @@ class FaviconController(Controller):
|
||||||
used as the favicon, defaults to 'image/x-icon' (Windows ICO format)
|
used as the favicon, defaults to 'image/x-icon' (Windows ICO format)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
#: Number of days in the future to set the cache expiration for the icon
|
||||||
|
EXPIRY_DAYS = 365
|
||||||
|
|
||||||
def __init__(self, icon=None, content_type='image/x-icon'):
|
def __init__(self, icon=None, content_type='image/x-icon'):
|
||||||
if icon:
|
if icon:
|
||||||
try:
|
try:
|
||||||
|
@ -72,4 +76,7 @@ class FaviconController(Controller):
|
||||||
response = milla.Response()
|
response = milla.Response()
|
||||||
response.app_iter = self.icon
|
response.app_iter = self.icon
|
||||||
response.headers['Content-Type'] = self.content_type
|
response.headers['Content-Type'] = self.content_type
|
||||||
|
expires = (datetime.datetime.utcnow() +
|
||||||
|
datetime.timedelta(days=self.EXPIRY_DAYS))
|
||||||
|
response.headers['Expires'] = milla.util.http_date(expires)
|
||||||
return response
|
return response
|
||||||
|
|
|
@ -24,12 +24,7 @@ import functools
|
||||||
import milla
|
import milla
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import urllib
|
import warnings
|
||||||
try:
|
|
||||||
import urllib.parse
|
|
||||||
except ImportError:
|
|
||||||
import urlparse
|
|
||||||
urllib.parse = urlparse
|
|
||||||
|
|
||||||
class Router(object):
|
class Router(object):
|
||||||
'''A dispatcher that maps arbitrary paths to controller callables
|
'''A dispatcher that maps arbitrary paths to controller callables
|
||||||
|
@ -224,11 +219,20 @@ class Generator(object):
|
||||||
A common pattern is to wrap this in a stub function::
|
A common pattern is to wrap this in a stub function::
|
||||||
|
|
||||||
url = Generator(request).generate
|
url = Generator(request).generate
|
||||||
|
|
||||||
|
.. deprecated:: 0.2
|
||||||
|
Use :py:meth:`milla.Request.relative_url` instead.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, request, path_only=True):
|
def __init__(self, request, path_only=True):
|
||||||
self.request = request
|
self.request = request
|
||||||
self.path_only = path_only
|
self.path_only = path_only
|
||||||
|
warnings.warn(
|
||||||
|
'Use of Generator is deprecated; '
|
||||||
|
'use milla.Request.relative_url instead',
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2
|
||||||
|
)
|
||||||
|
|
||||||
def generate(self, *segments, **vars):
|
def generate(self, *segments, **vars):
|
||||||
'''Combines segments and the application's URL into a new URL
|
'''Combines segments and the application's URL into a new URL
|
||||||
|
@ -238,10 +242,7 @@ class Generator(object):
|
||||||
while path.startswith('/'):
|
while path.startswith('/'):
|
||||||
path = path[1:]
|
path = path[1:]
|
||||||
|
|
||||||
url = self.request.relative_url(path, to_application=True)
|
return self.request.relative_url(path,
|
||||||
if self.path_only:
|
to_application=True,
|
||||||
split = urllib.parse.urlsplit(url)
|
path_only=self.path_only,
|
||||||
url = split.path
|
**vars)
|
||||||
if vars:
|
|
||||||
url += '?' + urllib.urlencode(vars)
|
|
||||||
return url
|
|
||||||
|
|
|
@ -21,6 +21,10 @@ Please give me a docstring!
|
||||||
:Updater: $Author$
|
:Updater: $Author$
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
from wsgiref.handlers import format_date_time
|
||||||
|
import datetime
|
||||||
|
import time
|
||||||
|
|
||||||
def asbool(val):
|
def asbool(val):
|
||||||
'''Test a value for truth
|
'''Test a value for truth
|
||||||
|
|
||||||
|
@ -46,4 +50,19 @@ def asbool(val):
|
||||||
pass
|
pass
|
||||||
if val in ('false', 'no', 'f', 'n', 'off', '0'):
|
if val in ('false', 'no', 'f', 'n', 'off', '0'):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def http_date(date):
|
||||||
|
'''Format a datetime object as a string in RFC 1123 format
|
||||||
|
|
||||||
|
This function returns a string representing the date according to
|
||||||
|
RFC 1123. The string returned will always be in English, as
|
||||||
|
required by the specification.
|
||||||
|
|
||||||
|
:param date: A :py:class:`datetime.datetime` object
|
||||||
|
:return: RFC 1123-formatted string
|
||||||
|
'''
|
||||||
|
|
||||||
|
stamp = time.mktime(date.timetuple())
|
||||||
|
return format_date_time(stamp)
|
||||||
|
|
Loading…
Reference in New Issue