#!/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 Cookie
import uuid
import cgi
import tempfile
import urlparse
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 BaseRequest(object):
"""
Represents the request from the server, and contains various information
and utilities. Also the place to store the session object.
"""
cookie_name = "sid"
"""The name of the cookie"""
def __init__(self, env):
self.params = {}
self.files = {}
self._env = env
self._raw_url = env["PATH_INFO"]
self.url = urlparse.urlparse(env["PATH_INFO"])
"""A `urlparse` result of the requests path"""
self._parse_params()
self._parse_cookie()
self._parse_auth()
self.build_session()
self.build_cfg()
self.method = self._env["REQUEST_METHOD"].upper()
"""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`"""
self.user_agent = env["HTTP_USER_AGENT"] if "HTTP_USER_AGENT" in env else "Unknown User Agent"
"""The user agent, unparsed, or the string `Unknown User Agent`"""
self.referer = env["HTTP_REFERER"] if "HTTP_REFERER" in env else ""
"""The referal URL if it exists, otherwise an empty string."""
self.remote_accepts = []
if "HTTP_ACCEPT" in env:
r = env["HTTP_ACCEPT"].split(",")
for bit in r:
q = 1
b = bit.split(";")
if len(b) > 1:
c = b[1].split("=")
if len(c) > 1:
q = float(c[1].strip(" "))
self.remote_accepts.append((b[0], q))
self.pre_id_url = None
self.id = None
self.command = None
[docs] def accepts(self, t):
"""
Determines if the given mimetype is accepted by the client.
"""
a = [ i for i in self.remote_accepts if t in i[0] ]
return len(a) > 0
def _post_route(self, extended):
if extended:
parts = extended.split('/', 1)
self.id = parts[0]
if len(parts) > 1:
self.command = parts[1]
else:
self.command = None
if self.id:
self.pre_id_url = self.url.path.split(self.id)[0].strip("/").split("/")
else:
self.pre_id_url = self.url.path.strip("/").split("/")
def _parse_params(self):
all_mem = {}
all_files = {}
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
def _parse_cookie(self):
cookie = Cookie.SimpleCookie()
try:
cookie.load(self._env["HTTP_COOKIE"])
self.session_cookie = { value.key: value.value for key, value in cookie.iteritems() }
self.session_ID = self.session_cookie[self.cookie_name]
except Exception:
self.session_ID = str(uuid.uuid4())
self.session_cookie = {self.cookie_name: self.session_ID}
def _parse_auth(self):
if "HTTP_AUTHORIZATION" in self._env:
auth_parts = self._env["HTTP_AUTHORIZATION"].split(" ")
if len(auth_parts) > 1:
if auth_parts[0].lower() == "basic":
name, passwd = base64.b64decode(auth_parts[1]).split(":")
self.auth = {"username": name, "password": passwd}
else:
self.auth = None
[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
def id_extended(self):
if self.command is None:
return str(self.id)
else:
return "/".join([self.id, self.command])
[docs] def build_session(self):
"""
Called during the objects instantiation.
Override to set the requests `session` property.
"""
pass
[docs] def build_cfg(self):
"""
Called during the objects instantiation.
Override to set the requests `cfg` property.
"""
pass
[docs] def log(self, head):
"""
Called right at the end of the request when the response is being
returned to the client. This is useful for logging to a database or log
file.
:param head: The reponses :py:class:`.Head` object which was returned
to the client.
"""
pass