summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xwsgitools/digest.py19
-rw-r--r--wsgitools/filters.py113
-rw-r--r--wsgitools/middlewares.py44
3 files changed, 142 insertions, 34 deletions
diff --git a/wsgitools/digest.py b/wsgitools/digest.py
index 19fb975..28fb9c7 100755
--- a/wsgitools/digest.py
+++ b/wsgitools/digest.py
@@ -50,7 +50,9 @@ class AuthTokenGenerator:
self.getpass = getpass
def __call__(self, username, algo="md5"):
"""Generates an authentification token from a username.
- @type username: str"""
+ @type username: str
+ @rtype: str
+ """
assert algo.lower() in ["md5", "md5-sess"]
password = self.getpass(username)
if password is None:
@@ -161,7 +163,10 @@ class AuthDigestMiddleware:
return self.app(environ, modified_start_response)
def auth_response(self, credentials, reqmethod):
- """internal method generating authentication tokens"""
+ """internal method generating authentication tokens
+ @raise KeyError:
+ @raise ValueError:
+ """
username = credentials["username"]
algo = credentials["algorithm"]
uri = credentials["uri"]
@@ -191,7 +196,10 @@ class AuthDigestMiddleware:
self.nonces.pop(0)
def is_nonce(self, credentials):
- """internal method checking whether a nonce might be from this server"""
+ """internal method checking whether a nonce might be from this server
+ @raise KeyError:
+ @raise ValueError:
+ """
nonce = credentials["nonce"] # raises KeyError
# raises ValueError
nonce_time, nonce_value, nonce_hash = nonce.split(':')
@@ -200,7 +208,10 @@ class AuthDigestMiddleware:
return nonce_hash == token
def check_nonce(self, credentials):
- """internal method checking nonce validity"""
+ """internal method checking nonce validity
+ @raise KeyError:
+ @raise ValueError:
+ """
nonce = credentials["nonce"]
# raises ValueError
nonce_time, nonce_value, nonce_hash = nonce.split(':')
diff --git a/wsgitools/filters.py b/wsgitools/filters.py
index 759b20c..509ffde 100644
--- a/wsgitools/filters.py
+++ b/wsgitools/filters.py
@@ -14,12 +14,16 @@ class CloseableIterator:
def __init__(self, close_function, *iterators):
"""If close_function is not None, it will be the close attribute of
the created iterator object. Further parameters specify iterators
- that are to be concatenated."""
+ that are to be concatenated.
+ @type close_function: a function or None
+ """
if close_function is not None:
self.close = close_function
self.iterators = map(iter, iterators)
def __iter__(self):
- """iterator interface"""
+ """iterator interface
+ @rtype: gen()
+ """
return self
def next(self):
"""iterator interface"""
@@ -37,11 +41,14 @@ class CloseableList(list):
def __init__(self, close_function, *args):
"""If close_function is not None, it will be the close attribute of
the created list object. Other parameters are passed to the list
- constructor."""
+ constructor.
+ @type close_function: a function or None
+ """
if close_function is not None:
self.close = close_function
list.__init__(self, *args)
def __iter__(self):
+ """iterator interface"""
return CloseableIterator(getattr(self, "close", None),
list.__iter__(self))
@@ -69,7 +76,10 @@ class BaseWSGIFilter:
pass
def filter_environ(self, environ):
"""Receives a dict with the environment passed to the wsgi application
- and a dict must be returned. The default is to return the same dict."""
+ and a dict must be returned. The default is to return the same dict.
+ @type environ: {str: str}
+ @rtype: {str: str}
+ """
return environ
def filter_exc_info(self, exc_info):
"""Receives either None or a tuple passed as third argument to
@@ -79,28 +89,42 @@ class BaseWSGIFilter:
def filter_status(self, status):
"""Receives a status string passed as first argument to start_response
from the wrapped wsgi application. A valid HTTP status string must be
- returned."""
+ returned.
+ @type status: str
+ @rtype: str
+ """
return status
def filter_header(self, headername, headervalue):
"""This function is invoked for each (headername, headervalue) tuple in
the second argument to the start_response from the wrapped wsgi
application. Such a value or None for discarding the header must be
- returned."""
+ returned.
+ @type headername: str
+ @type headervalue: str
+ @rtype: (str, str)
+ """
return (headername, headervalue)
def filter_headers(self, headers):
"""A list of headers passed as the second argument to the start_response
from the wrapped wsgi application is passed to this function and such a
- list must also be returned."""
+ list must also be returned.
+ @type headers: [(str, str)]
+ @rtype: [(str, str)]
+ """
return headers
def filter_data(self, data):
"""For each string that is either written by the write callable or
returned from the wrapped wsgi application this method is invoked. It
- must return a string."""
+ must return a string.
+ @type data: str
+ @rtype: str
+ """
return data
def append_data(self):
"""This function can be used to append data to the response. A list of
strings or some kind of iterable yielding strings has to be returned.
The default is to return an empty list.
+ @rtype: gen([str])
"""
return []
def handle_close(self):
@@ -114,12 +138,16 @@ class WSGIFilterMiddleware:
def __init__(self, app, filterclass):
"""
@param app: is a wsgi application.
+ @type filterclass: BaseWSGIFilters subclass
@param filterclass: is a subclass of BaseWSGIFilter or some class that
implements the interface."""
self.app = app
self.filterclass = filterclass
def __call__(self, environ, start_response):
- """wsgi interface"""
+ """wsgi interface
+ @type environ: {str, str}
+ @rtype: gen([str])
+ """
assert isinstance(environ, dict)
reqfilter = self.filterclass()
environ = reqfilter.filter_environ(environ)
@@ -163,15 +191,23 @@ class RequestLogWSGIFilter(BaseWSGIFilter):
@classmethod
def creator(cls, log):
"""Returns a function creating RequestLogWSGIFilters on given log file.
- log has to be a file-like object."""
+ log has to be a file-like object.
+ @type log: file-like
+ """
return lambda:cls(log)
def __init__(self, log=sys.stdout):
+ """
+ @type log: file-like
+ """
assert hasattr(log, "write")
self.log = log
self.time = time.strftime("%d/%b/%Y:%T %z")
self.length = 0
def filter_environ(self, environ):
- """BaseWSGIFilter interface"""
+ """BaseWSGIFilter interface
+ @type environ: {str: str}
+ @rtype: {str: str}
+ """
assert isinstance(environ, dict)
self.remote = environ.get("REMOTE_ADDR", "?")
self.user = environ.get("REMOTE_USER", "-")
@@ -182,12 +218,18 @@ class RequestLogWSGIFilter(BaseWSGIFilter):
self.useragent = environ.get("HTTP_USER_AGENT", "-")
return environ
def filter_status(self, status):
- """BaseWSGIFilter interface"""
+ """BaseWSGIFilter interface
+ @type status: str
+ @rtype: str
+ """
assert isinstance(status, str)
self.status = status.split()[0]
return status
def filter_data(self, data):
- """BaseWSGIFilter interface"""
+ """BaseWSGIFilter interface
+ @type data: str
+ @rtype: str
+ """
self.length += len(data)
return data
def handle_close(self):
@@ -218,13 +260,21 @@ class TimerWSGIFilter(BaseWSGIFilter):
@classmethod
def creator(cls, pattern):
"""Returns a function creating TimerWSGIFilters with a given pattern
- beeing a string of exactly eight bytes."""
+ beeing a string of exactly eight bytes.
+ @type pattern: str
+ """
return lambda:cls(pattern)
def __init__(self, pattern="?GenTime"):
+ """
+ @type pattern: str
+ """
self.pattern = pattern
self.start = time.time()
def filter_data(self, data):
- """BaseWSGIFilter interface"""
+ """BaseWSGIFilter interface
+ @type data: str
+ @rtype: str
+ """
if data == self.pattern:
return "%8.3g" % (time.time() - self.start)
return data
@@ -235,15 +285,26 @@ class EncodeWSGIFilter(BaseWSGIFilter):
@classmethod
def creator(cls, charset):
"""Returns a function creating EncodeWSGIFilters with a given charset.
+ @type charset: str
"""
return lambda:cls(charset)
def __init__(self, charset="utf-8"):
+ """
+ @type charset: str
+ """
self.charset = charset
def filter_data(self, data):
- """BaseWSGIFilter interface"""
+ """BaseWSGIFilter interface
+ @type data: str
+ @rtype: str
+ """
return data.encode(self.charset)
def filter_header(self, header, value):
- """BaseWSGIFilter interface"""
+ """BaseWSGIFilter interface
+ @type header: str
+ @type value: str
+ @rtype (str, str)
+ """
if header.lower() != "content-type":
return (header, value)
return (header, "%s; charset=%s" % (value, self.charset))
@@ -256,7 +317,9 @@ class GzipWSGIFilter(BaseWSGIFilter):
self.sio = None
self.gzip = None
def filter_environ(self, environ):
- """BaseWSGIFilter interface"""
+ """BaseWSGIFilter interface
+ @type environ: {str: str}
+ """
assert isinstance(environ, dict)
if "HTTP_ACCEPT_ENCODING" in environ:
acceptenc = environ["HTTP_ACCEPT_ENCODING"].split(',')
@@ -267,13 +330,19 @@ class GzipWSGIFilter(BaseWSGIFilter):
self.gzip = gzip.GzipFile(fileobj=self.sio, mode="w")
return environ
def filter_headers(self, headers):
- """BaseWSGIFilter interface"""
+ """BaseWSGIFilter interface
+ @type headers: [(str, str)]
+ @rtype: [(str, str)]
+ """
assert isinstance(headers, list)
if self.compress:
headers.append(("Content-encoding", "gzip"))
return headers
def filter_data(self, data):
- """BaseWSGIFilter interface"""
+ """BaseWSGIFilter interface
+ @type data: str
+ @rtype: str
+ """
if not self.compress:
return data
self.gzip.write(data)
@@ -282,7 +351,9 @@ class GzipWSGIFilter(BaseWSGIFilter):
self.sio.truncate(0)
return data
def append_data(self):
- """BaseWSGIFilter interface"""
+ """BaseWSGIFilter interface
+ @rtype: [str]
+ """
if not self.compress:
return []
self.gzip.close()
diff --git a/wsgitools/middlewares.py b/wsgitools/middlewares.py
index bee0ac3..871105c 100644
--- a/wsgitools/middlewares.py
+++ b/wsgitools/middlewares.py
@@ -13,10 +13,17 @@ __all__.append("SubdirMiddleware")
class SubdirMiddleware:
"""Middleware choosing wsgi applications based on a dict."""
def __init__(self, default, mapping={}):
+ """
+ @type default: wsgi app
+ @type mapping: {str: wsgi app}
+ """
self.default = default
self.mapping = mapping
def __call__(self, environ, start_response):
- """wsgi interface"""
+ """wsgi interface
+ @type environ: {str: str}
+ @rtype: gen([str])
+ """
assert isinstance(environ, dict)
app = None
script = environ["PATH_INFO"]
@@ -45,7 +52,10 @@ class NoWriteCallableMiddleware:
"""Wraps wsgi application app."""
self.app = app
def __call__(self, environ, start_response):
- """wsgi interface"""
+ """wsgi interface
+ @type environ: {str, str}
+ @rtype: gen([str])
+ """
assert isinstance(environ, dict)
todo = []
def modified_start_response(status, headers, exc_info=None):
@@ -196,7 +206,9 @@ class CachingMiddleware:
self.cacheable = cacheable
self.cache = {}
def __call__(self, environ, start_response):
- """wsgi interface"""
+ """wsgi interface
+ @type environ: {str: str}
+ """
assert isinstance(environ, dict)
if not self.storable(environ):
return self.app(environ, start_response)
@@ -241,11 +253,17 @@ __all__.append("DictAuthChecker")
class DictAuthChecker:
"""Verifies usernames and passwords by looking them up in a dict."""
def __init__(self, users):
- """@param users: is a dict mapping usernames to password."""
+ """
+ @type users: {str: str}
+ @param users: is a dict mapping usernames to password."""
self.users = users
def __call__(self, username, password):
"""check_function interface taking username and password and resulting
- in a bool."""
+ in a bool.
+ @type username: str
+ @type password: str
+ @rtype: bool
+ """
return username in self.users and self.users[username] == password
__all__.append("BasicAuthMiddleware")
@@ -256,13 +274,17 @@ class BasicAuthMiddleware:
@param app: is a WSGI application.
@param check_function: is a function taking two arguments username and
password returning a bool indicating whether the request may is
- allowed."""
+ allowed.
+ @type realm: str
+ """
self.app = app
self.check_function = check_function
self.realm = realm
def __call__(self, environ, start_response):
- """wsgi interface"""
+ """wsgi interface
+ @type environ: {str: str}
+ """
assert isinstance(environ, dict)
auth = environ.get("HTTP_AUTHORIZATION")
if not auth or ' ' not in auth:
@@ -281,7 +303,9 @@ class BasicAuthMiddleware:
return self.authorization_required(environ, start_response)
def authorization_required(self, environ, start_response):
- """wsgi application for indicating authorization is required."""
+ """wsgi application for indicating authorization is required.
+ @type environ: {str: str}
+ """
status = "401 Authorization required"
html = "<html><head><title>Authorization required</title></head>" + \
"<body><h1>Authorization required</h1></body></html>\n"
@@ -301,7 +325,9 @@ class TracebackMiddleware:
"""app is the wsgi application to proxy."""
self.app = app
def __call__(self, environ, start_response):
- """wsgi interface"""
+ """wsgi interface
+ @type environ: {str: str}
+ """
try:
assert isinstance(environ, dict)
ret = self.app(environ, start_response)