Skip to content

Commit

Permalink
Working implementation using the wsgi.handlers.CGIHandler implementat…
Browse files Browse the repository at this point in the history
…ion over the previous CGI implementation.

Still only working for detailed predictions.
More generic handling of the namespace-qualified tags, now in the initialiser of the base class.
New base class for Status queries e.g. LineStatusQuery and StationStatusQuery now only implement the _parse_xml method of StatusQuery, which now implements the common methods _make_filename and _process_request.
  • Loading branch information
FDeSousa committed May 7, 2013
1 parent a36a7dd commit ee5e637
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 181 deletions.
237 changes: 97 additions & 140 deletions py/query.py
Expand Up @@ -8,6 +8,7 @@
import os
import status
import urllib2
import logging

try:
import xml.etree.cElementTree as etree
Expand Down Expand Up @@ -58,12 +59,16 @@ class BaseQuery(object):
__metaclass__ = ABCMeta

query = ""
cache_expiry_time = 0
cache_expiry_time = 30
xmlns = "http://trackernet.lul.co.uk"
params = (REQUEST, )
tags = {}

def __init__(self, form):
self.request_url = self._process_request(form)
self.cache_filename = self._make_filename(form)
self.form = form
# Get strings for the namespace-qualified tags
for key, val in self.tags.items():
self.tags[key] = etree.QName(self.xmlns, val).text

@abstractmethod
def _process_request(self, form):
Expand Down Expand Up @@ -96,8 +101,7 @@ def _get_cache(self):
except (IOError, OSError) as e:
if e.errno == errno.ENOENT:
return None
else:
raise
raise e

def _make_folders(self, filename):
foldername = os.path.dirname(filename)
Expand Down Expand Up @@ -126,197 +130,152 @@ def _write_json(self, json):
raise

def fetch(self):
cached = self._get_cache()
self.request_url = self._process_request(self.form)
self.cache_filename = self._make_filename(self.form)

resp_json = self._get_cache()

if cached is None:
res = self._request()
if not resp_json:
try:
res = self._request()

statuscode = status.StatusCodes.getstatuscode(int(res.getcode()))
if statuscode.iserror:
raise status.ResponseError(statuscode, 'Failed to fetch XML')
elif not statuscode.canhavebody:
raise status.ResponseError(statuscode, 'No content in response')
statuscode = status.StatusCodes.getstatuscode(int(res.getcode()))
if statuscode.iserror:
raise status.ResponseError(statuscode, 'Failed to fetch XML')
elif not statuscode.canhavebody:
raise status.ResponseError(statuscode, 'No content in response')

resp = self._get_xml(res)
resp_json = json.dumps(resp)
self._write_json(resp_json)
else:
resp_json = cached
resp = self._get_xml(res)
resp_json = json.dumps(resp)
self._write_json(resp_json)
except urllib2.HTTPError as httpe:
statuscode = status.StatusCodes.getstatuscode(int(httpe.code))
raise status.RequestError(statuscode)

return resp_json


class DetailedPredictionQuery(BaseQuery):
"""DetailedPredictionQuery"""
query = PREDICTION_DETAILED
cache_expiry_time = 30

def __init__(self, form):
super(DetailedPredictionQuery, self).__init__(form)
# Namespace-qualified tags
self.created_tag = etree.QName(self.xmlns, 'WhenCreated').text
self.line_tag = etree.QName(self.xmlns, 'Line').text
self.linename_tag = etree.QName(self.xmlns, 'LineName').text
self.station_tag = etree.QName(self.xmlns, 'S').text
self.platform_tag = etree.QName(self.xmlns, 'P').text
self.train_tag = etree.QName(self.xmlns, 'T').text
params = (REQUEST, LINE, STATION)
tags = {
'created_tag': 'WhenCreated',
'line_tag': 'Line',
'linename_tag': 'LineName',
'station_tag': 'S',
'platform_tag': 'P',
'train_tag': 'T'
}

def _process_request(self, form):
line = form.getfirst(LINE)
station = form.getfirst(STATION)
self.line = form[LINE]
self.station = form[STATION]

if line not in LINES_LIST:
raise ValueError("Line code '%s' is not valid" % line)
if not station:
if self.line not in LINES_LIST:
raise ValueError("Line code '{}' is not valid".format(self.line))
if not self.station:
raise ValueError("Station code is empty")

request_url = "{0}/{1}/{2}/{3}".format(BASE_URL, self.query,
line, station)
return request_url
url = "{0}/{1}/{2}/{3}".format(BASE_URL, self.query,
self.line, self.station)
return url

def _make_filename(self, form):
line = form.getfirst(LINE)
station = form.getfirst(STATION)

if line not in LINES_LIST:
raise ValueError("Line code '%s' is not valid" % line)
if not station:
raise ValueError("Station code is empty")

cache_filename = os.path.join('.', BASE_FILE, self.query, line, station)

cache_filename = os.path.join('.', BASE_FILE, self.query,
self.line, self.station)
return cache_filename + FILE_EXTENSION

def _parse_xml(self, xml):
root = xml.getroot()

resp = {
'information': {
# Informational parts of response
'created': root.find(self.created_tag).text,
'linecode': root.find(self.line_tag).text,
'linename': root.find(self.linename_tag).text,
'stations': [
# List of Stations
{
'stationcode': station.attrib['Code'],
'stationname': station.attrib['N'],
'platforms': [
# List of Platforms for a Station
{
'platformname': platform.attrib['N'],
'platformnumber': platform.attrib['Num'],
'trains': [
# List of Trains for a Platform
{
'lcid': train.attrib['LCID'],
'timeto': train.attrib['SecondsTo'],
'location': train.attrib['TimeTo'],
'destination': train.attrib['Destination'],
'destcode': train.attrib['DestCode'],
'tripno': train.attrib['TripNo']
}
for train in platform.findall(self.train_tag)
] # End of trains list comprehension
}
for platform in station.findall(self.platform_tag)
] # End of platforms list comprehension
}
for station in root.findall(self.station_tag)
] # End of stations list comprehension
}
resp = {'information': {
# Informational parts of response
'created': root.find(self.tags['created_tag']).text,
'linecode': root.find(self.tags['line_tag']).text,
'linename': root.find(self.tags['linename_tag']).text,
'stations': [{
# List of Stations
'stationcode': station.attrib['Code'],
'stationname': station.attrib['N'],
'platforms': [{
# List of Platforms for a Station
'platformname': platform.attrib['N'],
'platformnumber': platform.attrib['Num'],
'trains': [{
# List of Trains for a Platform
'lcid': train.attrib['LCID'],
'timeto': train.attrib['SecondsTo'],
'location': train.attrib['TimeTo'],
'destination': train.attrib['Destination'],
'destcode': train.attrib['DestCode'],
'tripno': train.attrib['TripNo']
} for train in platform.findall(self.tags['train_tag'])]}
# End of trains list comprehension
for platform in station.findall(self.tags['platform_tag'])]}
# End of platforms list comprehension
for station in root.findall(self.tags['station_tag'])]}
# End of stations list comprehension
}

return resp


class SummaryPredictionQuery(BaseQuery):
"""SummaryPredictionQuery"""
query = PREDICTION_SUMMARY
cache_expiry_time = 30

def __init__(self, form):
super(SummaryPredictionQuery, self).__init__(form)
params = (REQUEST, LINE)

def _process_request(self, form):
request = form.getfirst(REQUEST)
line = form.getfirst(LINE)
self.line = form[LINE]

if line not in LINES_LIST:
raise ValueError("Line code '%s' is not valid" % line)
if self.line not in LINES_LIST:
raise ValueError("Line code '{}' is not valid".format(self.line))

request_url = "{0}/{1}/{2}".format(BASE_URL, PREDICTION_SUMMARY, line)
request_url = "{0}/{1}/{2}".format(BASE_URL, self.query, self.line)
return request_url

def _make_filename(self, form):
line = form.getfirst(LINE)

if line not in LINES_LIST:
raise ValueError("Line code '%s' is not valid" % line)

cache_filename = os.path.join('.', BASE_FILE, self.query, line)

cache_filename = os.path.join('.', BASE_FILE, self.query, self.line)
return cache_filename + FILE_EXTENSION

def _parse_xml(self, xml):
pass


class LineStatusQuery(BaseQuery):
"""LineStatusQuery"""
query = LINE_STATUS
cache_expiry_time = 30
class StatusQuery(BaseQuery):
__metaclass__ = ABCMeta

def __init__(self, form):
super(LineStatusQuery, self).__init__(form)
params = (REQUEST, INCIDENTS_ONLY)

def _process_request(self, form):
incidents_only = form.getfirst(INCIDENTS_ONLY)
incidents_only = _parse_boolean(incidents_only)
self.incidents_only = _parse_boolean(form[INCIDENTS_ONLY])

request_url = "{0}/{1}".format(BASE_URL, LINE_STATUS)
if incidents_only:
request_url = "{0}/{1}".format(BASE_URL, self.query)
if self.incidents_only:
request_url = "{0}/{1}".format(request_url, INCIDENTS_ONLY)
return request_url

def _make_filename(self, form):
incidents_only = form.getfirst(INCIDENTS_ONLY)
incidents_only = _parse_boolean(incidents_only)

cache_filename = os.path.join('.', BASE_FILE, self.query,
INCIDENTS_ONLY if incidents_only else 'full')

INCIDENTS_ONLY if self.incidents_only
else 'full')
return cache_filename + FILE_EXTENSION

@abstractmethod
def _parse_xml(self, xml):
pass


class StationStatusQuery(BaseQuery):
"""StationStatusQuery"""
query = STATION_STATUS
cache_expiry_time = 30

def __init__(self, form):
super(StationStatusQuery, self).__init__(form)

def _process_request(self, form):
incidents_only = form.getfirst(INCIDENTS_ONLY)
incidents_only = _parse_boolean(incidents_only)

request_url = "{0}/{1}".format(BASE_URL, STATION_STATUS)
if incidents_only:
request_url = "{0}/{1}".format(request_url, INCIDENTS_ONLY)
return request_url
class LineStatusQuery(StatusQuery):
"""LineStatusQuery"""
query = LINE_STATUS

def _make_filename(self, form):
incidents_only = form.getfirst(INCIDENTS_ONLY)
incidents_only = _parse_boolean(incidents_only)
def _parse_xml(self, xml):
pass

cache_filename = os.path.join('.', BASE_FILE, self.query,
INCIDENTS_ONLY if incidents_only else 'full')

return cache_filename + FILE_EXTENSION
class StationStatusQuery(StatusQuery):
"""StationStatusQuery"""
query = STATION_STATUS

def _parse_xml(self, xml):
pass
Expand All @@ -326,9 +285,7 @@ class StationListQuery(BaseQuery):
"""StationListQuery"""
query = "stationslist"
cache_expiry_time = 2419200 # Four weeks in seconds

def __init__(self, form):
super(StationListQuery, self).__init__(form)
params = (REQUEST, )

def _process_request(self, form):
pass
Expand Down
9 changes: 8 additions & 1 deletion py/status.py
Expand Up @@ -102,14 +102,21 @@ def gethttpheader(cls, code):
header = 'HTTP/1.1 {0.code} {0.message}'.format(statuscode)
return header

@classmethod
def gethttpstatus(cls, code):
statuscode = cls.getstatuscode(code)
http_status = '{0.code} {0.message}'.format(statuscode)
return http_status


# Exceptions
class BaseStatusError(Exception):
def __init__(self, status=None, message=None):
self.status = (StatusCodes.getstatuscode(status)
or StatusCodes.HTTP_BAD_REQUEST)
self.httpheader = StatusCodes.gethttpheader(self.status)
self.message = (message or self.httpheader)
self.httpstatus = StatusCodes.gethttpstatus(self.status)
self.message = message if self.status.canhavebody else None


class RequestError(BaseStatusError):
Expand Down

0 comments on commit ee5e637

Please sign in to comment.