Update to eveapi 1.3.0, and move to service.

This commit is contained in:
blitzmann
2014-08-14 01:59:03 -04:00
parent 11891340af
commit a5773a3fd6

View File

@@ -1,7 +1,7 @@
#-----------------------------------------------------------------------------
# eveapi - EVE Online API access
#
# Copyright (c)2007 Jamie "Entity" van den Berge <entity@vapor.com>
# Copyright (c)2007-2014 Jamie "Entity" van den Berge <jamie@hlekkir.com>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
@@ -25,8 +25,47 @@
# OTHER DEALINGS IN THE SOFTWARE
#
#-----------------------------------------------------------------------------
# Version: 1.1.9-2 - 30 September 2011
# - merge workaround provided by Entity to make it work with http proxies
#
# Version: 1.3.0 - 27 May 2014
# - Added set_user_agent() module-level function to set the User-Agent header
# to be used for any requests by the library. If this function is not used,
# a warning will be thrown for every API request.
#
# Version: 1.2.9 - 14 September 2013
# - Updated error handling: Raise an AuthenticationError in case
# the API returns HTTP Status Code 403 - Forbidden
#
# Version: 1.2.8 - 9 August 2013
# - the XML value cast function (_autocast) can now be changed globally to a
# custom one using the set_cast_func(func) module-level function.
#
# Version: 1.2.7 - 3 September 2012
# - Added get() method to Row object.
#
# Version: 1.2.6 - 29 August 2012
# - Added finer error handling + added setup.py to allow distributing eveapi
# through pypi.
#
# Version: 1.2.5 - 1 August 2012
# - Row objects now have __hasattr__ and __contains__ methods
#
# Version: 1.2.4 - 12 April 2012
# - API version of XML response now available as _meta.version
#
# Version: 1.2.3 - 10 April 2012
# - fix for tags of the form <tag attr=bla ... />
#
# Version: 1.2.2 - 27 February 2012
# - fix for the workaround in 1.2.1.
#
# Version: 1.2.1 - 23 February 2012
# - added workaround for row tags missing attributes that were defined
# in their rowset (this should fix ContractItems)
#
# Version: 1.2.0 - 18 February 2012
# - fix handling of empty XML tags.
# - improved proxy support a bit.
#
# Version: 1.1.9 - 2 September 2011
# - added workaround for row tags with attributes that were not defined
# in their rowset (this should fix AssetList)
@@ -112,22 +151,53 @@ import httplib
import urlparse
import urllib
import copy
import warnings
from xml.parsers import expat
from time import strptime
from calendar import timegm
proxy = None
proxySSL = False
_default_useragent = "eveapi.py/1.3"
_useragent = None # use set_user_agent() to set this.
#-----------------------------------------------------------------------------
def set_cast_func(func):
"""Sets an alternative value casting function for the XML parser.
The function must have 2 arguments; key and value. It should return a
value or object of the type appropriate for the given attribute name/key.
func may be None and will cause the default _autocast function to be used.
"""
global _castfunc
_castfunc = _autocast if func is None else func
def set_user_agent(user_agent_string):
"""Sets a User-Agent for any requests sent by the library."""
global _useragent
_useragent = user_agent_string
class Error(StandardError):
def __init__(self, code, message):
self.code = code
self.args = (message.rstrip("."),)
def __unicode__(self):
return u'%s [code=%s]' % (self.args[0], self.code)
class RequestError(Error):
pass
class AuthenticationError(Error):
pass
class ServerError(Error):
pass
def EVEAPIConnection(url="api.eveonline.com", cacheHandler=None, proxy=None):
def EVEAPIConnection(url="api.eveonline.com", cacheHandler=None, proxy=None, proxySSL=False):
# Creates an API object through which you can call remote functions.
#
# The following optional arguments may be provided:
@@ -137,6 +207,8 @@ def EVEAPIConnection(url="api.eveonline.com", cacheHandler=None, proxy=None):
# proxy - (host,port) specifying a proxy server through which to request
# the API pages. Specifying a proxy overrides default proxy.
#
# proxySSL - True if the proxy requires SSL, False otherwise.
#
# cacheHandler - an object which must support the following interface:
#
# retrieve(host, path, params)
@@ -173,6 +245,7 @@ def EVEAPIConnection(url="api.eveonline.com", cacheHandler=None, proxy=None):
ctx._scheme = p.scheme
ctx._host = p.netloc
ctx._proxy = proxy or globals()["proxy"]
ctx._proxySSL = proxySSL or globals()["proxySSL"]
return ctx
@@ -197,7 +270,14 @@ def _ParseXML(response, fromContext, storeFunc):
error = getattr(obj, "error", False)
if error:
raise Error(error.code, error.data)
if error.code >= 500:
raise ServerError(error.code, error.data)
elif error.code >= 200:
raise AuthenticationError(error.code, error.data)
elif error.code >= 100:
raise RequestError(error.code, error.data)
else:
raise Error(error.code, error.data)
result = getattr(obj, "result", False)
if not result:
@@ -304,36 +384,35 @@ class _RootContext(_Context):
response = None
if response is None:
if self._scheme == "https":
connectionclass = httplib.HTTPSConnection
else:
connectionclass = httplib.HTTPConnection
if not _useragent:
warnings.warn("No User-Agent set! Please use the set_user_agent() module-level function before accessing the EVE API.", stacklevel=3)
if self._proxy is None:
req = path
if self._scheme == "https":
connectionclass = httplib.HTTPSConnection
conn = httplib.HTTPSConnection(self._host)
else:
connectionclass = httplib.HTTPConnection
http = connectionclass(self._host)
if kw:
http.request("POST", path, urllib.urlencode(kw), {"Content-type": "application/x-www-form-urlencoded"})
else:
http.request("GET", path)
conn = httplib.HTTPConnection(self._host)
else:
connectionclass = httplib.HTTPConnection
http = connectionclass(*self._proxy)
if kw:
http.request("POST", self._scheme+'://'+self._host+path, urllib.urlencode(kw), {"Content-type": "application/x-www-form-urlencoded"})
req = self._scheme+'://'+self._host+path
if self._proxySSL:
conn = httplib.HTTPSConnection(*self._proxy)
else:
http.request("GET", self._scheme+'://'+self._host+path)
conn = httplib.HTTPConnection(*self._proxy)
response = http.getresponse()
if kw:
conn.request("POST", req, urllib.urlencode(kw), {"Content-type": "application/x-www-form-urlencoded", "User-Agent": _useragent or _default_useragent})
else:
conn.request("GET", req, "", {"User-Agent": _useragent or _default_useragent})
response = conn.getresponse()
if response.status != 200:
if response.status == httplib.NOT_FOUND:
raise AttributeError("'%s' not available on API server (404 Not Found)" % path)
elif response.status == httplib.FORBIDDEN:
raise AuthenticationError(response.status, 'HTTP 403 - Forbidden')
else:
raise RuntimeError("'%s' request failed (%d %s)" % (path, response.status, response.reason))
raise ServerError(response.status, "'%s' request failed (%s)" % (path, response.reason))
if cache:
store = True
@@ -348,7 +427,7 @@ class _RootContext(_Context):
# implementor is handling fallbacks...
try:
return _ParseXML(response, True, store and (lambda obj: cache.store(self._host, path, kw, response, obj)))
except Error, reason:
except Error, e:
response = retrieve_fallback(self._host, path, kw, reason=e)
if response is not None:
return response
@@ -386,6 +465,7 @@ def _autocast(key, value):
# couldn't cast. return string unchanged.
return value
_castfunc = _autocast
class _Parser(object):
@@ -460,20 +540,42 @@ class _Parser(object):
# really assume the rest of the xml is going to be what we expect.
if name != "eveapi":
raise RuntimeError("Invalid API response")
try:
this.version = attributes[attributes.index("version")+1]
except KeyError:
raise RuntimeError("Invalid API response")
self.root = this
if isinstance(self.container, Rowset) and (self.container.__catch == this._name):
# <hack>
# - check for missing columns attribute (see above)
# - check for missing columns attribute (see above).
# - check for missing row attributes.
# - check for extra attributes that were not defined in the rowset,
# such as rawQuantity in the assets lists.
# In either case the tag is assumed to be correct and the rowset's
# columns are overwritten with the tag's version.
if not self.container._cols or (len(attributes)/2 > len(self.container._cols)):
self.container._cols = attributes[0::2]
# columns are overwritten with the tag's version, if required.
numAttr = len(attributes)/2
numCols = len(self.container._cols)
if numAttr < numCols and (attributes[-2] == self.container._cols[-1]):
# the row data is missing attributes that were defined in the rowset.
# missing attributes' values will be set to None.
fixed = []
row_idx = 0; hdr_idx = 0; numAttr*=2
for col in self.container._cols:
if col == attributes[row_idx]:
fixed.append(_castfunc(col, attributes[row_idx+1]))
row_idx += 2
else:
fixed.append(None)
hdr_idx += 1
self.container.append(fixed)
else:
if not self.container._cols or (numAttr > numCols):
# the row data contains more attributes than were defined.
self.container._cols = attributes[0::2]
self.container.append([_castfunc(attributes[i], attributes[i+1]) for i in xrange(0, len(attributes), 2)])
# </hack>
self.container.append([_autocast(attributes[i], attributes[i+1]) for i in xrange(0, len(attributes), 2)])
this._isrow = True
this._attributes = this._attributes2 = None
else:
@@ -481,10 +583,11 @@ class _Parser(object):
this._attributes = attributes
this._attributes2 = []
self.container = this
self.container = self._last = this
self.has_cdata = False
def tag_cdata(self, data):
self.has_cdata = True
if self._cdata:
# unset cdata flag to indicate it's been handled.
self._cdata = False
@@ -493,7 +596,7 @@ class _Parser(object):
return
this = self.container
data = _autocast(this._name, data)
data = _castfunc(this._name, data)
if this._isrow:
# sigh. anonymous data inside rows makes Entity cry.
@@ -518,6 +621,7 @@ class _Parser(object):
def tag_end(self, name):
this = self.container
if this is self.root:
del this._attributes
#this.__dict__.pop("_attributes", None)
@@ -553,13 +657,26 @@ class _Parser(object):
# really happen, but it doesn't hurt to handle this case!
sibling = getattr(self.container, this._name, None)
if sibling is None:
self.container._attributes2.append(this._name)
setattr(self.container, this._name, this)
if (not self.has_cdata) and (self._last is this) and (name != "rowset"):
if attributes:
# tag of the form <tag attribute=bla ... />
e = Element()
e._name = this._name
setattr(self.container, this._name, e)
for i in xrange(0, len(attributes), 2):
setattr(e, attributes[i], attributes[i+1])
else:
# tag of the form: <tag />, treat as empty string.
setattr(self.container, this._name, "")
else:
self.container._attributes2.append(this._name)
setattr(self.container, this._name, this)
# Note: there aren't supposed to be any NON-rowset tags containing
# multiples of some tag or attribute. Code below handles this case.
elif isinstance(sibling, Rowset):
# its doppelganger is a rowset, append this as a row to that.
row = [_autocast(attributes[i], attributes[i+1]) for i in xrange(0, len(attributes), 2)]
row = [_castfunc(attributes[i], attributes[i+1]) for i in xrange(0, len(attributes), 2)]
row.extend([getattr(this, col) for col in attributes2])
sibling.append(row)
elif isinstance(sibling, Element):
@@ -568,7 +685,7 @@ class _Parser(object):
# into a Rowset, adding the sibling element and this one.
rs = Rowset()
rs.__catch = rs._name = this._name
row = [_autocast(attributes[i], attributes[i+1]) for i in xrange(0, len(attributes), 2)]+[getattr(this, col) for col in attributes2]
row = [_castfunc(attributes[i], attributes[i+1]) for i in xrange(0, len(attributes), 2)]+[getattr(this, col) for col in attributes2]
rs.append(row)
row = [getattr(sibling, attributes[i]) for i in xrange(0, len(attributes), 2)]+[getattr(sibling, col) for col in attributes2]
rs.append(row)
@@ -581,7 +698,7 @@ class _Parser(object):
# Now fix up the attributes and be done with it.
for i in xrange(0, len(attributes), 2):
this.__dict__[attributes[i]] = _autocast(attributes[i], attributes[i+1])
this.__dict__[attributes[i]] = _castfunc(attributes[i], attributes[i+1])
return
@@ -630,6 +747,18 @@ class Row(object):
raise TypeError("Incompatible comparison type")
return cmp(self._cols, other._cols) or cmp(self._row, other._row)
def __hasattr__(self, this):
if this in self._cols:
return self._cols.index(this) < len(self._row)
return False
__contains__ = __hasattr__
def get(self, this, default=None):
if (this in self._cols) and (self._cols.index(this) < len(self._row)):
return self._row[self._cols.index(this)]
return default
def __getattr__(self, this):
try:
return self._row[self._cols.index(this)]