#!/usr/bin/env python
"""
TODO: Doc This
"""
"""
Seshat
Web App/API framework built on top of gevent
Main framework app
For more information, see: https://github.com/JoshAshby/
http://xkcd.com/353/
Josh Ashby
2014
http://joshashby.com
joshuaashby@joshashby.com
"""
import logging
logger = logging.getLogger("seshat.request")
import cgi
import tempfile
import urlparse
from headers import RequestHeaders
def parse_bool(p):
if p == "True" or p == "true":
return True
elif p == "False" or p == "false":
return False
else:
# Well fuck
return False
[docs]class FileObject(object):
"""
Provides a File like object which supports the common file operations,
along with providing some additional metadata which is sent from the
client.
"""
_template = "< FileObject @ {id} Filename: {filename} Data: {data} >"
def __init__(self, file_obj):
self.filename = file_obj.filename
self.name = file_obj.name
self.type = file_obj.type
self.expanded_type = self.type.split("/")
self.file = file_obj.file
self.extension = ""
parts = self.filename.split(".", 1)
if len(parts) > 1:
self.extension = parts[1]
[docs] def read(self):
return self.file.read()
[docs] def readline(self):
return self.file.readline()
[docs] def seek(self, where):
return self.file.seek(where)
[docs] def readlines(self):
return self.file.readlines()
[docs] def auto_read(self):
self.seek(0)
data = self.read()
self.seek(0)
return data
def __repr__(self):
string = self._template.format(**{
"id": id(self),
"filename": self.filename,
"data": len(self.auto_read())
})
return string
[docs]class Request(object):
"""
Represents the request from the server, and contains various information
and utilities.
"""
def __init__(self, env):
self.params = {}
self.files = {}
self.env = env
"""The raw environ `dict` which is provided by the wsgi server."""
url = env["PATH_INFO"] if "PATH_INFO" in env else ""
self.url = urlparse.urlparse(url)
self.url.host = env["HTTP_HOST"] if "HTTP_HOST" in env else ""
"""A `urlparse` result of the requests path"""
self.method = env["REQUEST_METHOD"].upper() if "REQUEST_METHOD" in env else "GET"
"""The HTTP method by which the request was made, in all caps."""
self.remote = env["HTTP_X_REAL_IP"] if "HTTP_X_REAL_IP" in env else "Unknown IP"
"""The clients IP, otherwise `Unknown IP` This is set from
HTTP_X_REAL_IP which is a header that I personally have nginx append to
the request before handing it to Seshat."""
self._parse_params()
self.headers = RequestHeaders(env)
"""Headers which were sent with the request, in the form of a
`.RequestHeaders` object"""
self.url_params = None
"""When a match is made in the route table, this is set to a `dict` containing the matched pattern groups in the url."""
def _parse_params(self):
all_mem = {}
all_files = {}
if "wsgi.input" in self.env:
temp_file = tempfile.TemporaryFile()
temp_file.write(self.env['wsgi.input'].read()) # or use buffered read()
temp_file.seek(0)
form = cgi.FieldStorage(fp=temp_file, environ=self.env, keep_blank_values=True)
if isinstance(form.value, list):
for bit in form:
if hasattr(form[bit], "filename") and form[bit].filename is not None:
fi = FileObject(form[bit])
all_files[fi.name] = fi
else:
all_mem[bit] = form.getvalue(bit)
temp_file.close()
self.params = all_mem
self.files = all_files
[docs] def get_param(self, parameter, default="", cast=str):
"""
Allows you to get a parameter from the request. If the parameter does
not exist, or is empty, then a default will be returned. You can also
choose to optionally cast the parameter.
If a parameter has multiple values then this will return a list of all
those values.
:param parameter: The name of the parameter to get
:param default: The default to return if the parameter is nonexistent
or empty
:param cast: An optional cast for the parameter.
"""
try:
p = self.params[parameter]
if type(default) == bool:
p = parse_bool(p)
elif cast and cast is not str:
if cast is bool:
p = parse_bool(p)
else:
p = cast(p)
return p
except:
return default
[docs] def get_file(self, name):
"""
Along with getting parameters, one may wish to retrieve other data such
as files sent.
This provides an interface for getting a file like
:py:class:`.FileObject` which can be used like a normal file but also
holds some meta data sent with the request. If no file by the given
name is found then this will return `None`
"""
if name in self.files and self.files[name].filename:
return self.files[name]
else:
return None
@property
[docs] def id(self):
"""
This is more or less a backwards compatability thing. Provides access
to the `id` element of `url_params` `dict` if it is present otherwise
returns `None`
"""
return self.url_params["id"] if "id" in self.url_params else None