diff --git a/eveapi.py b/eveapi.py index 0733f51..6167ee4 100644 --- a/eveapi.py +++ b/eveapi.py @@ -25,6 +25,11 @@ # OTHER DEALINGS IN THE SOFTWARE # #----------------------------------------------------------------------------- +# Version: 1.2.8 - 3 September 2012 +# - Added a "autocast_func" parameter to the EVEAPIConnection class. +# If this parameter is not None, it will be used in replacement +# of the default "_autocast" function. +# # Version: 1.2.7 - 3 September 2012 # - Added get() method to Row object. # @@ -164,7 +169,7 @@ class ServerError(Error): pass -def EVEAPIConnection(url="api.eveonline.com", cacheHandler=None, proxy=None, proxySSL=False): +def EVEAPIConnection(url="api.eveonline.com", cacheHandler=None, proxy=None, proxySSL=False, autocast_func=None): # Creates an API object through which you can call remote functions. # # The following optional arguments may be provided: @@ -201,13 +206,25 @@ def EVEAPIConnection(url="api.eveonline.com", cacheHandler=None, proxy=None, pro # will only be called if you returned None in the retrieve() for # this object. # + # autocast_func - plain function that will be used to cast XML values to the + # most probable type. The function must accept 2 parameters + # and return a casted value. It shouldn't raise exceptions. + # + # example: + # + # def my_autocast(key, value): + # .... + # blah blah + # .... + # return casted_value + # if not url.startswith("http"): url = "https://" + url p = urlparse.urlparse(url, "https") if p.path and p.path[-1] == "/": p.path = p.path[:-1] - ctx = _RootContext(None, p.path, {}, {}) + ctx = _RootContext(None, p.path, {}, {}, autocast_func) ctx._handler = cacheHandler ctx._scheme = p.scheme ctx._host = p.netloc @@ -216,22 +233,22 @@ def EVEAPIConnection(url="api.eveonline.com", cacheHandler=None, proxy=None, pro return ctx -def ParseXML(file_or_string): +def ParseXML(file_or_string, autocast=None): try: - return _ParseXML(file_or_string, False, None) + return _ParseXML(file_or_string, False, None, autocast=autocast) except TypeError: raise TypeError("XML data must be provided as string or file-like object") -def _ParseXML(response, fromContext, storeFunc): +def _ParseXML(response, fromContext, storeFunc, autocast=None): # pre/post-process XML or Element data if fromContext and isinstance(response, Element): obj = response elif type(response) in (str, unicode): - obj = _Parser().Parse(response, False) + obj = _Parser(autocast).Parse(response, False) elif hasattr(response, "read"): - obj = _Parser().Parse(response, True) + obj = _Parser(autocast).Parse(response, True) else: raise TypeError("retrieve method must return None, string, file-like object or an Element instance") @@ -272,9 +289,10 @@ def _ParseXML(response, fromContext, storeFunc): class _Context(object): - def __init__(self, root, path, parentDict, newKeywords=None): + def __init__(self, root, path, parentDict, newKeywords=None, autocast=None): self._root = root or self self._path = path + self.autocast = autocast if newKeywords: if parentDict: self.parameters = parentDict.copy() @@ -295,7 +313,7 @@ def context(self, *args, **kw): def __getattr__(self, this): # perform arcane attribute majick trick - return _Context(self._root, self._path + "/" + this, self.parameters) + return _Context(self._root, self._path + "/" + this, self.parameters, autocast=self.autocast) def __call__(self, **kw): if kw: @@ -317,18 +335,18 @@ def character(self, characterID): # returns a copy of this connection object but for every call made # through it, it will add the folder "/char" to the url, and the # characterID to the parameters passed. - return _Context(self._root, self._path + "/char", self.parameters, {"characterID":characterID}) + return _Context(self._root, self._path + "/char", self.parameters, {"characterID":characterID}, autocast=self.autocast) def corporation(self, characterID): # same as character except for the folder "/corp" - return _Context(self._root, self._path + "/corp", self.parameters, {"characterID":characterID}) + return _Context(self._root, self._path + "/corp", self.parameters, {"characterID":characterID}, autocast=self.autocast) class _RootContext(_Context): def auth(self, **kw): if len(kw) == 2 and (("keyID" in kw and "vCode" in kw) or ("userID" in kw and "apiKey" in kw)): - return _AuthContext(self._root, self._path, self.parameters, kw) + return _AuthContext(self._root, self._path, self.parameters, kw, autocast=self.autocast) raise ValueError("Must specify keyID and vCode") def setcachehandler(self, handler): @@ -388,7 +406,7 @@ def __call__(self, path, **kw): if retrieve_fallback: # implementor is handling fallbacks... try: - return _ParseXML(response, True, store and (lambda obj: cache.store(self._host, path, kw, response, obj))) + return _ParseXML(response, True, store and (lambda obj: cache.store(self._host, path, kw, response, obj)), autocast=self.autocast) except Error, e: response = retrieve_fallback(self._host, path, kw, reason=e) if response is not None: @@ -396,7 +414,7 @@ def __call__(self, path, **kw): raise else: # implementor is not handling fallbacks... - return _ParseXML(response, True, store and (lambda obj: cache.store(self._host, path, kw, response, obj))) + return _ParseXML(response, True, store and (lambda obj: cache.store(self._host, path, kw, response, obj)), autocast=self.autocast) #----------------------------------------------------------------------------- # XML Parser @@ -430,7 +448,10 @@ def _autocast(key, value): class _Parser(object): - + + def __init__(self, autocast=None): + self.autocast = autocast or _autocast + def Parse(self, data, isStream=False): self.container = self.root = None self._cdata = False @@ -524,7 +545,7 @@ def tag_start(self, name, attributes): row_idx = 0; hdr_idx = 0; numAttr*=2 for col in self.container._cols: if col == attributes[row_idx]: - fixed.append(_autocast(col, attributes[row_idx+1])) + fixed.append(self.autocast(col, attributes[row_idx+1])) row_idx += 2 else: fixed.append(None) @@ -534,7 +555,7 @@ def tag_start(self, name, attributes): 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([_autocast(attributes[i], attributes[i+1]) for i in xrange(0, len(attributes), 2)]) + self.container.append([self.autocast(attributes[i], attributes[i+1]) for i in xrange(0, len(attributes), 2)]) # this._isrow = True @@ -557,7 +578,7 @@ def tag_cdata(self, data): return this = self.container - data = _autocast(this._name, data) + data = self.autocast(this._name, data) if this._isrow: # sigh. anonymous data inside rows makes Entity cry. @@ -637,7 +658,7 @@ def tag_end(self, name): # 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 = [self.autocast(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): @@ -646,7 +667,7 @@ def tag_end(self, name): # 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 = [self.autocast(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) @@ -659,7 +680,7 @@ def tag_end(self, name): # 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]] = self.autocast(attributes[i], attributes[i+1]) return diff --git a/setup.py b/setup.py index 7be3cad..65bdd57 100755 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( # GENERAL INFO name = 'eveapi', - version = '1.2.6', + version = '1.2.8', description = 'Python library for accessing the EVE Online API.', author = 'Jamie van den Berge', author_email = 'jamie@hlekkir.com', @@ -31,6 +31,5 @@ 'Topic :: Games/Entertainment', ), # CONTENTS - zip_safe = True, py_modules = ['eveapi',], )