Added get_characters() function in controller. Very rough, needs work.
authorDanny Campbell <danny.campbell@gmail.com>
Sat, 17 Apr 2010 03:01:42 +0000 (21:01 -0600)
committerDanny Campbell <danny.campbell@gmail.com>
Sat, 17 Apr 2010 03:01:42 +0000 (21:01 -0600)
apitest.py [deleted file]
eveapi.py [deleted file]
eveapi/__init__.py [new file with mode: 0644]
eveapi/apitest.py [new file with mode: 0644]
eveapi/eveapi.py [new file with mode: 0644]
mevemon.py
ui/diablo/ui.py

diff --git a/apitest.py b/apitest.py
deleted file mode 100644 (file)
index b37b9b3..0000000
+++ /dev/null
@@ -1,416 +0,0 @@
-#=============================================================================\r
-# eveapi module demonstration script - Jamie van den Berge\r
-#=============================================================================\r
-#\r
-# This file is in the Public Domain - Do with it as you please.\r
-# \r
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r
-# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r
-# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r
-# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r
-# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r
-# OTHER DEALINGS IN THE SOFTWARE\r
-#\r
-#----------------------------------------------------------------------------\r
-# Put your userID and apiKey (full access) here before running this script.\r
-YOUR_USERID = 123456\r
-YOUR_APIKEY = "hPx6rxdYfVNeGcuOgPKRL-ohhithere-aUg6OfxCtMH4FUn5GUzf8YqIQDdc5gF7"\r
-\r
-import time\r
-import tempfile\r
-import cPickle\r
-import zlib\r
-import os\r
-from os.path import join, exists\r
-from httplib import HTTPException\r
-\r
-import eveapi\r
-\r
-api = eveapi.EVEAPIConnection()\r
-\r
-#----------------------------------------------------------------------------\r
-print\r
-print "EXAMPLE 1: GETTING THE ALLIANCE LIST"\r
-print " (and showing alliances with 1000 or more members)"\r
-print\r
-\r
-# Let's get the list of alliances.\r
-# The API function we need to get the list is:\r
-#\r
-#    /eve/AllianceList.xml.aspx\r
-#\r
-# There is a 1:1 correspondence between folders/files and attributes on api\r
-# objects, so to call this particular function, we simply do this:\r
-result1 = api.eve.AllianceList()\r
-\r
-# This result contains a rowset object called "alliances". Rowsets are like\r
-# database tables and you can do various useful things with them. For now\r
-# we'll just iterate over it and display all alliances with more than 1000\r
-# members:\r
-for alliance in result1.alliances:\r
-       if alliance.memberCount >= 1000:\r
-               print "%s <%s> has %d members" %\\r
-                       (alliance.name, alliance.shortName, alliance.memberCount)\r
-\r
-\r
-#-----------------------------------------------------------------------------\r
-print\r
-print "EXAMPLE 2: GETTING WALLET BALANCE OF ALL YOUR CHARACTERS"\r
-print\r
-\r
-# To get any info on character/corporation related stuff, we need to acquire\r
-# an authentication context. All API requests that require authentication need\r
-# to be called through this object. While it is possible to call such API\r
-# functions directly through the api object, you would have to specify the\r
-# userID and apiKey on every call. If you are iterating over many accounts,\r
-# that may actually be the better option. However, for these examples we only\r
-# use one account, so this is more convenient.\r
-auth = api.auth(userID=YOUR_USERID, apiKey=YOUR_APIKEY)\r
-\r
-# Now let's say you want to the wallet balance of all your characters.\r
-# The API function we need to get the characters on your account is:\r
-#\r
-#    /account/Characters.xml.aspx\r
-#\r
-# As in example 1, this simply means adding folder names as attributes\r
-# and calling the function named after the base page name:\r
-result2 = auth.account.Characters()\r
-\r
-# Some tracking for later examples.\r
-rich = 0\r
-rich_charID = 0\r
-\r
-# Now the best way to iterate over the characters on your account and show\r
-# the isk balance is probably this way:\r
-for character in result2.characters:\r
-       wallet = auth.char.AccountBalance(characterID=character.characterID)\r
-       isk = wallet.accounts[0].balance\r
-       print character.name, "has", isk, "ISK."\r
-\r
-       if isk > rich:\r
-               rich = isk\r
-               rich_charID = character.characterID\r
-\r
-\r
-\r
-#-----------------------------------------------------------------------------\r
-print\r
-print "EXAMPLE 3: WHEN STUFF GOES WRONG"\r
-print\r
-\r
-# Obviously you cannot assume an API call to succeed. There's a myriad of\r
-# things that can go wrong:\r
-#\r
-# - Connection error\r
-# - Server error\r
-# - Invalid parameters passed\r
-# - Hamsters died\r
-#\r
-# Therefor it is important to handle errors properly. eveapi will raise\r
-# an AttributeError if the requested function does not exist on the server\r
-# (ie. when it returns a 404), a RuntimeError on any other webserver error\r
-# (such as 500 Internal Server error).\r
-# On top of this, you can get any of the httplib (which eveapi uses) and\r
-# socket (which httplib uses) exceptions so you might want to catch those\r
-# as well.\r
-#\r
-\r
-try:\r
-       # Try calling account/Characters without authentication context\r
-       api.account.Characters()\r
-except eveapi.Error, e:\r
-       print "Oops! eveapi returned the following error:"\r
-       print "code:", e.code\r
-       print "message:", e.message\r
-except Exception, e:\r
-       print "Something went horribly wrong:", str(e)\r
-       raise\r
-\r
-\r
-#-----------------------------------------------------------------------------\r
-print\r
-print "EXAMPLE 4: GETTING CHARACTER SHEET INFORMATION"\r
-print\r
-\r
-# We grab ourselves a character context object.\r
-# Note that this is a convenience function that takes care of passing the\r
-# characterID=x parameter to every API call much like auth() does (in fact\r
-# it's exactly like that, apart from the fact it also automatically adds the\r
-# "/char" folder). Again, it is possible to use the API functions directly\r
-# from the api or auth context, but then you have to provide the missing\r
-# keywords on every call (characterID in this case).\r
-#\r
-# The victim we'll use is the last character on the account we used in\r
-# example 1.\r
-me = auth.character(result2.characters[-1].characterID)\r
-\r
-# Now that we have a character context, we can display skills trained on\r
-# a character. First we have to get the skill tree. A real application\r
-# would cache this data; all objects returned by the api interface can be\r
-# pickled.\r
-skilltree = api.eve.SkillTree()\r
-\r
-# Now we have to fetch the charactersheet.\r
-# Note that the call below is identical to:\r
-#\r
-#   acc.char.CharacterSheet(characterID=your_character_id)\r
-#\r
-# But, as explained above, the context ("me") we created automatically takes\r
-# care of adding the characterID parameter and /char folder attribute.\r
-sheet = me.CharacterSheet()\r
-\r
-# This list should look familiar. They're the skillpoints at each level for\r
-# a rank 1 skill. We could use the formula, but this is much simpler :)\r
-sp = [0, 250, 1414, 8000, 45255, 256000]\r
-\r
-total_sp = 0\r
-total_skills = 0\r
-\r
-# Now the fun bit starts. We walk the skill tree, and for every group in the\r
-# tree...\r
-for g in skilltree.skillGroups:\r
-\r
-       skills_trained_in_this_group = False\r
-\r
-       # ... iterate over the skills in this group...\r
-       for skill in g.skills:\r
-\r
-               # see if we trained this skill by checking the character sheet object\r
-               trained = sheet.skills.Get(skill.typeID, False)\r
-               if trained:\r
-                       # yep, we trained this skill.\r
-\r
-                       # print the group name if we haven't done so already\r
-                       if not skills_trained_in_this_group:\r
-                               print g.groupName\r
-                               skills_trained_in_this_group = True\r
-\r
-                       # and display some info about the skill!\r
-                       print "- %s Rank(%d) - SP: %d/%d - Level: %d" %\\r
-                               (skill.typeName, skill.rank, trained.skillpoints, (skill.rank * sp[trained.level]), trained.level)\r
-                       total_skills += 1\r
-                       total_sp += trained.skillpoints\r
-\r
-\r
-# And to top it off, display totals.\r
-print "You currently have %d skills and %d skill points" % (total_skills, total_sp)\r
-\r
-\r
-\r
-#-----------------------------------------------------------------------------\r
-print\r
-print "EXAMPLE 5: USING ROWSETS"\r
-print\r
-\r
-# For this one we will use the result1 that contains the alliance list from\r
-# the first example.\r
-rowset = result1.alliances\r
-\r
-# Now, what if we want to sort the alliances by ticker name. We could unpack\r
-# all alliances into a list and then use python's sort(key=...) on that list,\r
-# but that's not efficient. The rowset objects support sorting on columns\r
-# directly:\r
-rowset.SortBy("shortName")\r
-\r
-# Note the use of Select() here. The Select method speeds up iterating over\r
-# large rowsets considerably as no temporary row instances are created.\r
-for ticker in rowset.Select("shortName"):\r
-       print ticker,\r
-print\r
-\r
-# The sort above modified the result inplace. There is another method, called\r
-# SortedBy, which returns a new rowset. \r
-\r
-print\r
-\r
-# Another useful method of rowsets is IndexBy, which enables you to do direct\r
-# key lookups on columns. We already used this feature in example 3. Indeed\r
-# most rowsets returned are IndexRowsets already if the data has a primary\r
-# key attribute defined in the <rowset> tag in the XML data.\r
-#\r
-# IndexRowsets are efficient, they reference the data from the rowset they\r
-# were created from, and create an index mapping on top of it.\r
-#\r
-# Anyway, to create an index:\r
-alliances_by_ticker = rowset.IndexedBy("shortName")\r
-\r
-# Now use the Get() method to get a row directly.\r
-# Assumes ISD alliance exists. If it doesn't, we probably have bigger\r
-# problems than the unhandled exception here -_-\r
-try:\r
-       print alliances_by_ticker.Get("ISD")\r
-except :\r
-       print "Blimey! CCP let the ISD alliance expire -AGAIN-. How inconvenient!"\r
-\r
-# You may specify a default to return in case the row wasn't found:\r
-print alliances_by_ticker.Get("123456", 42)\r
-\r
-# If no default was specified and you try to look up a key that does not\r
-# exist, an appropriate exception will be raised:\r
-try:\r
-       print alliances_by_ticker.Get("123456")\r
-except KeyError:\r
-       print "This concludes example 5"\r
-\r
-\r
-\r
-#-----------------------------------------------------------------------------\r
-print\r
-print "EXAMPLE 6: CACHING DATA"\r
-print\r
-\r
-# For some calls you will want caching. To facilitate this, a customized\r
-# cache handler can be attached. Below is an example of a simple cache\r
-# handler. \r
-\r
-class MyCacheHandler(object):\r
-       # Note: this is an example handler to demonstrate how to use them.\r
-       # a -real- handler should probably be thread-safe and handle errors\r
-       # properly (and perhaps use a better hashing scheme).\r
-\r
-       def __init__(self, debug=False):\r
-               self.debug = debug\r
-               self.count = 0\r
-               self.cache = {}\r
-               self.tempdir = join(tempfile.gettempdir(), "eveapi")\r
-               if not exists(self.tempdir):\r
-                       os.makedirs(self.tempdir)\r
-\r
-       def log(self, what):\r
-               if self.debug:\r
-                       print "[%d] %s" % (self.count, what)\r
-\r
-       def retrieve(self, host, path, params):\r
-               # eveapi asks if we have this request cached\r
-               key = hash((host, path, frozenset(params.items())))\r
-\r
-               self.count += 1  # for logging\r
-\r
-               # see if we have the requested page cached...\r
-               cached = self.cache.get(key, None)\r
-               if cached:\r
-                       cacheFile = None\r
-                       #print "'%s': retrieving from memory" % path\r
-               else:\r
-                       # it wasn't cached in memory, but it might be on disk.\r
-                       cacheFile = join(self.tempdir, str(key) + ".cache")\r
-                       if exists(cacheFile):\r
-                               self.log("%s: retrieving from disk" % path)\r
-                               f = open(cacheFile, "rb")\r
-                               cached = self.cache[key] = cPickle.loads(zlib.decompress(f.read()))\r
-                               f.close()\r
-\r
-               if cached:\r
-                       # check if the cached doc is fresh enough\r
-                       if time.time() < cached[0]:\r
-                               self.log("%s: returning cached document" % path)\r
-                               return cached[1]  # return the cached XML doc\r
-\r
-                       # it's stale. purge it.\r
-                       self.log("%s: cache expired, purging!" % path)\r
-                       del self.cache[key]\r
-                       if cacheFile:\r
-                               os.remove(cacheFile)\r
-\r
-               self.log("%s: not cached, fetching from server..." % path)\r
-               # we didn't get a cache hit so return None to indicate that the data\r
-               # should be requested from the server.\r
-               return None\r
-\r
-       def store(self, host, path, params, doc, obj):\r
-               # eveapi is asking us to cache an item\r
-               key = hash((host, path, frozenset(params.items())))\r
-\r
-               cachedFor = obj.cachedUntil - obj.currentTime\r
-               if cachedFor:\r
-                       self.log("%s: cached (%d seconds)" % (path, cachedFor))\r
-\r
-                       cachedUntil = time.time() + cachedFor\r
-\r
-                       # store in memory\r
-                       cached = self.cache[key] = (cachedUntil, doc)\r
-\r
-                       # store in cache folder\r
-                       cacheFile = join(self.tempdir, str(key) + ".cache")\r
-                       f = open(cacheFile, "wb")\r
-                       f.write(zlib.compress(cPickle.dumps(cached, -1)))\r
-                       f.close()\r
-\r
-\r
-# Now try out the handler! Even though were initializing a new api object\r
-# here, a handler can be attached or removed from an existing one at any\r
-# time with its setcachehandler() method.\r
-cachedApi = eveapi.EVEAPIConnection(cacheHandler=MyCacheHandler(debug=True))\r
-\r
-# First time around this will fetch the document from the server. That is,\r
-# if this demo is run for the first time, otherwise it will attempt to load\r
-# the cache written to disk on the previous run.\r
-result = cachedApi.eve.SkillTree()\r
-\r
-# But the second time it should be returning the cached version\r
-result = cachedApi.eve.SkillTree()\r
-\r
-\r
-\r
-#-----------------------------------------------------------------------------\r
-print\r
-print "EXAMPLE 7: TRANSACTION DATA"\r
-print "(and doing more nifty stuff with rowsets)"\r
-print\r
-\r
-# okay since we have a caching api object now it is fairly safe to do this\r
-# example repeatedly without server locking you out for an hour every time!\r
-\r
-# Let's use the first character on the account (using the richest character\r
-# found in example 2). Note how we are chaining the various contexts here to\r
-# arrive directly at a character context. If you're not using any intermediate\r
-# contexts in the chain anyway, this is okay.\r
-me = cachedApi.auth(YOUR_USERID, YOUR_APIKEY).character(rich_charID)\r
-\r
-# Now fetch the journal. Since this character context was created through \r
-# the cachedApi object, it will still use the cachehandler from example 5.\r
-journal = me.WalletJournal()\r
-\r
-# Let's see how much we paid SCC in transaction tax in the first page\r
-# of data!\r
-\r
-# Righto, now we -could- sift through the rows and extract what we want,\r
-# but we can do it in a much more clever way using the GroupedBy method\r
-# of the rowset in the result. This creates a mapping that maps keys\r
-# to Rowsets of all rows with that key value in specified column.\r
-# These data structures are also quite efficient as the only extra data\r
-# created is the index and grouping.\r
-entriesByRefType = journal.entries.GroupedBy("refTypeID")\r
-\r
-# Also note that we're using a hardcoded refTypeID of 54 here. You're\r
-# supposed to use .eve.RefTypes() though (however they are not likely\r
-# to be changed anyway so we can get away with it)\r
-# Note the use of Select() to speed things up here.\r
-amount = 0.0\r
-date = 0\r
-for taxAmount, date in entriesByRefType[54].Select("amount", "date"):\r
-       amount += -taxAmount\r
-\r
-print "You paid a %.2f ISK transaction tax since %s" %\\r
-       (amount, time.asctime(time.gmtime(date)))\r
-\r
-\r
-# You might also want to see how much a certain item yielded you recently.\r
-typeName = "Expanded Cargohold II"\r
-amount = 0.0\r
-\r
-wallet = me.WalletTransactions()\r
-soldTx = wallet.transactions.GroupedBy("transactionType")["sell"]\r
-for row in soldTx.GroupedBy("typeName")[typeName]:\r
-       amount += (row.quantity * row.price)\r
-\r
-print "%s sales yielded %.2f ISK since %s" %\\r
-       (typeName, amount, time.asctime(time.gmtime(row.transactionDateTime)))\r
-\r
-# I'll leave walking the transaction pages as an excercise to the reader ;)\r
-# Please also see the eveapi module itself for more documentation.\r
-\r
-# That's all folks!\r
-\r
diff --git a/eveapi.py b/eveapi.py
deleted file mode 100644 (file)
index 4633c14..0000000
--- a/eveapi.py
+++ /dev/null
@@ -1,787 +0,0 @@
-#-----------------------------------------------------------------------------\r
-# eveapi - EVE Online API access\r
-#\r
-# Copyright (c)2007 Jamie "Entity" van den Berge <entity@vapor.com>\r
-# \r
-# Permission is hereby granted, free of charge, to any person\r
-# obtaining a copy of this software and associated documentation\r
-# files (the "Software"), to deal in the Software without\r
-# restriction, including without limitation the rights to use,\r
-# copy, modify, merge, publish, distribute, sublicense, and/or sell\r
-# copies of the Software, and to permit persons to whom the\r
-# Software is furnished to do so, subject to the following\r
-# conditions:\r
-# \r
-# The above copyright notice and this permission notice shall be\r
-# included in all copies or substantial portions of the Software.\r
-#\r
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r
-# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r
-# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r
-# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r
-# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r
-# OTHER DEALINGS IN THE SOFTWARE\r
-#\r
-#-----------------------------------------------------------------------------\r
-# Version: 1.1.1 - 10 Januari 2010\r
-# - Fixed bug that causes nested tags to not appear in rows of rowsets created\r
-#      from normal Elements. This should fix the corp.MemberSecurity method,\r
-#   which now returns all data for members. [jehed]\r
-#\r
-# Version: 1.1.0 - 15 Januari 2009\r
-# - Added Select() method to Rowset class. Using it avoids the creation of\r
-#   temporary row instances, speeding up iteration considerably.\r
-# - Added ParseXML() function, which can be passed arbitrary API XML file or\r
-#   string objects.\r
-# - Added support for proxy servers. A proxy can be specified globally or\r
-#   per api connection instance. [suggestion by graalman]\r
-# - Some minor refactoring.\r
-# - Fixed deprecation warning when using Python 2.6.\r
-#\r
-# Version: 1.0.7 - 14 November 2008\r
-# - Added workaround for rowsets that are missing the (required!) columns\r
-#   attribute. If missing, it will use the columns found in the first row.\r
-#   Note that this is will still break when expecting columns, if the rowset\r
-#   is empty. [Flux/Entity]\r
-#\r
-# Version: 1.0.6 - 18 July 2008\r
-# - Enabled expat text buffering to avoid content breaking up. [BigWhale]\r
-#\r
-# Version: 1.0.5 - 03 February 2008\r
-# - Added workaround to make broken XML responses (like the "row:name" bug in\r
-#   eve/CharacterID) work as intended.\r
-# - Bogus datestamps before the epoch in XML responses are now set to 0 to\r
-#   avoid breaking certain date/time functions. [Anathema Matou]\r
-#\r
-# Version: 1.0.4 - 23 December 2007\r
-# - Changed _autocast() to use timegm() instead of mktime(). [Invisible Hand]\r
-# - Fixed missing attributes of elements inside rows. [Elandra Tenari]\r
-#\r
-# Version: 1.0.3 - 13 December 2007\r
-# - Fixed keyless columns bugging out the parser (in CorporationSheet for ex.)\r
-#\r
-# Version: 1.0.2 - 12 December 2007\r
-# - Fixed parser not working with indented XML.\r
-#\r
-# Version: 1.0.1\r
-# - Some micro optimizations\r
-#\r
-# Version: 1.0\r
-# - Initial release\r
-#\r
-# Requirements:\r
-#   Python 2.4+\r
-#\r
-#-----------------------------------------------------------------------------\r
-\r
-import httplib\r
-import urllib\r
-import copy\r
-\r
-from xml.parsers import expat\r
-from time import strptime\r
-from calendar import timegm\r
-\r
-proxy = None\r
-\r
-#-----------------------------------------------------------------------------\r
-\r
-class Error(StandardError):\r
-       def __init__(self, code, message):\r
-               self.code = code\r
-               self.args = (message.rstrip("."),)\r
-\r
-\r
-def EVEAPIConnection(url="api.eve-online.com", cacheHandler=None, proxy=None):\r
-       # Creates an API object through which you can call remote functions.\r
-       #\r
-       # The following optional arguments may be provided:\r
-       #\r
-       # url - root location of the EVEAPI server\r
-       #\r
-       # proxy - (host,port) specifying a proxy server through which to request\r
-       #         the API pages. Specifying a proxy overrides default proxy.\r
-       #\r
-       # cacheHandler - an object which must support the following interface:\r
-       #\r
-       #      retrieve(host, path, params)\r
-       #\r
-       #          Called when eveapi wants to fetch a document.\r
-       #          host is the address of the server, path is the full path to\r
-       #          the requested document, and params is a dict containing the\r
-       #          parameters passed to this api call (userID, apiKey etc).\r
-       #          The method MUST return one of the following types:\r
-       #\r
-       #           None - if your cache did not contain this entry\r
-       #           str/unicode - eveapi will parse this as XML\r
-       #           Element - previously stored object as provided to store()\r
-       #           file-like object - eveapi will read() XML from the stream.\r
-       #\r
-       #      store(host, path, params, doc, obj)\r
-       #\r
-       #          Called when eveapi wants you to cache this item.\r
-       #          You can use obj to get the info about the object (cachedUntil\r
-       #          and currentTime, etc) doc is the XML document the object\r
-       #          was generated from. It's generally best to cache the XML, not\r
-       #          the object, unless you pickle the object. Note that this method\r
-       #          will only be called if you returned None in the retrieve() for\r
-       #          this object.\r
-       #\r
-\r
-       if url.lower().startswith("http://"):\r
-               url = url[7:]\r
-\r
-       if "/" in url:\r
-               url, path = url.split("/", 1)\r
-       else:\r
-               path = ""\r
-\r
-       ctx = _RootContext(None, path, {}, {})\r
-       ctx._handler = cacheHandler\r
-       ctx._host = url\r
-       ctx._proxy = proxy or globals()["proxy"]\r
-       return ctx\r
-\r
-\r
-def ParseXML(file_or_string):\r
-       try:\r
-               return _ParseXML(file_or_string, False, None)\r
-       except TypeError:\r
-               raise TypeError("XML data must be provided as string or file-like object")\r
-\r
-\r
-def _ParseXML(response, fromContext, storeFunc):\r
-       # pre/post-process XML or Element data\r
-\r
-       if fromContext and isinstance(response, Element):\r
-               obj = response\r
-       elif type(response) in (str, unicode):\r
-               obj = _Parser().Parse(response, False)\r
-       elif hasattr(response, "read"):\r
-               obj = _Parser().Parse(response, True)\r
-       else:\r
-               raise TypeError("retrieve method must return None, string, file-like object or an Element instance")\r
-\r
-       error = getattr(obj, "error", False)\r
-       if error:\r
-               raise Error(error.code, error.data)\r
-\r
-       result = getattr(obj, "result", False)\r
-       if not result:\r
-               raise RuntimeError("API object does not contain result")\r
-\r
-       if fromContext and storeFunc:\r
-               # call the cache handler to store this object\r
-               storeFunc(obj)\r
-\r
-       # make metadata available to caller somehow\r
-       result._meta = obj\r
-\r
-       return result\r
-\r
-\r
-       \r
-\r
-\r
-#-----------------------------------------------------------------------------\r
-# API Classes\r
-#-----------------------------------------------------------------------------\r
-\r
-_listtypes = (list, tuple, dict)\r
-_unspecified = []\r
-\r
-class _Context(object):\r
-\r
-       def __init__(self, root, path, parentDict, newKeywords=None):\r
-               self._root = root or self\r
-               self._path = path\r
-               if newKeywords:\r
-                       if parentDict:\r
-                               self.parameters = parentDict.copy()\r
-                       else:\r
-                               self.parameters = {}\r
-                       self.parameters.update(newKeywords)\r
-               else:\r
-                       self.parameters = parentDict or {}\r
-\r
-       def context(self, *args, **kw):\r
-               if kw or args:\r
-                       path = self._path\r
-                       if args:\r
-                               path += "/" + "/".join(args)\r
-                       return self.__class__(self._root, path, self.parameters, kw)\r
-               else:\r
-                       return self\r
-\r
-       def __getattr__(self, this):\r
-               # perform arcane attribute majick trick\r
-               return _Context(self._root, self._path + "/" + this, self.parameters)\r
-\r
-       def __call__(self, **kw):\r
-               if kw:\r
-                       # specified keywords override contextual ones\r
-                       for k, v in self.parameters.iteritems():\r
-                               if k not in kw:\r
-                                       kw[k] = v\r
-               else:\r
-                       # no keywords provided, just update with contextual ones.\r
-                       kw.update(self.parameters)\r
-\r
-               # now let the root context handle it further\r
-               return self._root(self._path, **kw)\r
-\r
-\r
-class _AuthContext(_Context):\r
-\r
-       def character(self, characterID):\r
-               # returns a copy of this connection object but for every call made\r
-               # through it, it will add the folder "/char" to the url, and the\r
-               # characterID to the parameters passed.\r
-               return _Context(self._root, self._path + "/char", self.parameters, {"characterID":characterID})\r
-\r
-       def corporation(self, characterID):\r
-               # same as character except for the folder "/corp"\r
-               return _Context(self._root, self._path + "/corp", self.parameters, {"characterID":characterID})\r
-\r
-\r
-class _RootContext(_Context):\r
-\r
-       def auth(self, userID=None, apiKey=None):\r
-               # returns a copy of this object but for every call made through it, the\r
-               # userID and apiKey will be added to the API request.\r
-               if userID and apiKey:\r
-                       return _AuthContext(self._root, self._path, self.parameters, {"userID":userID, "apiKey":apiKey})\r
-               raise ValueError("Must specify userID and apiKey")\r
-\r
-       def setcachehandler(self, handler):\r
-               self._root._handler = handler\r
-\r
-       def __call__(self, path, **kw):\r
-               # convert list type arguments to something the API likes\r
-               for k, v in kw.iteritems():\r
-                       if isinstance(v, _listtypes):\r
-                               kw[k] = ','.join(map(str, list(v)))\r
-\r
-               cache = self._root._handler\r
-\r
-               # now send the request\r
-               path += ".xml.aspx"\r
-\r
-               if cache:\r
-                       response = cache.retrieve(self._host, path, kw)\r
-               else:\r
-                       response = None\r
-\r
-               if response is None:\r
-                       if self._proxy is None:\r
-                               http = httplib.HTTPConnection(self._host)\r
-                               if kw:\r
-                                       http.request("POST", path, urllib.urlencode(kw), {"Content-type": "application/x-www-form-urlencoded"})\r
-                               else:\r
-                                       http.request("GET", path)\r
-                       else:\r
-                               http = httplib.HTTPConnection(*self._proxy)\r
-                               if kw:\r
-                                       http.request("POST", 'http://'+self._host+path, urllib.urlencode(kw), {"Content-type": "application/x-www-form-urlencoded"})\r
-                               else:\r
-                                       http.request("GET", 'http://'+self._host+path)\r
-\r
-                       response = http.getresponse()\r
-                       if response.status != 200:\r
-                               if response.status == httplib.NOT_FOUND:\r
-                                       raise AttributeError("'%s' not available on API server (404 Not Found)" % path)\r
-                               else:\r
-                                       raise RuntimeError("'%s' request failed (%d %s)" % (path, response.status, response.reason))\r
-\r
-                       if cache:\r
-                               store = True\r
-                               response = response.read()\r
-                       else:\r
-                               store = False\r
-               else:\r
-                       store = False\r
-\r
-               return _ParseXML(response, True, store and (lambda obj: cache.store(self._host, path, kw, response, obj)))\r
-\r
-\r
-#-----------------------------------------------------------------------------\r
-# XML Parser\r
-#-----------------------------------------------------------------------------\r
-\r
-def _autocast(s):\r
-       # attempts to cast an XML string to the most probable type.\r
-       try:\r
-               if s.strip("-").isdigit():\r
-                       return int(s)\r
-       except ValueError:\r
-               pass\r
-\r
-       try:\r
-               return float(s)\r
-       except ValueError:\r
-               pass\r
-\r
-       if len(s) == 19 and s[10] == ' ':\r
-               # it could be a date string\r
-               try:\r
-                       return max(0, int(timegm(strptime(s, "%Y-%m-%d %H:%M:%S"))))\r
-               except OverflowError:\r
-                       pass\r
-               except ValueError:\r
-                       pass\r
-\r
-       # couldn't cast. return string unchanged.\r
-       return s\r
-\r
-\r
-class _Parser(object):\r
-\r
-       def Parse(self, data, isStream=False):\r
-               self.container = self.root = None\r
-               p = expat.ParserCreate()\r
-               p.StartElementHandler = self.tag_start\r
-               p.CharacterDataHandler = self.tag_cdata\r
-               p.EndElementHandler = self.tag_end\r
-               p.ordered_attributes = True\r
-               p.buffer_text = True\r
-\r
-               if isStream:\r
-                       p.ParseFile(data)\r
-               else:\r
-                       p.Parse(data, True)\r
-               return self.root\r
-               \r
-\r
-       def tag_start(self, name, attributes):\r
-               # <hack>\r
-               # If there's a colon in the tag name, cut off the name from the colon\r
-               # onward. This is a workaround to make certain bugged XML responses\r
-               # (such as eve/CharacterID.xml.aspx) work.\r
-               if ":" in name:\r
-                       name = name[:name.index(":")]\r
-               # </hack>\r
-\r
-               if name == "rowset":\r
-                       # for rowsets, use the given name\r
-                       try:\r
-                               columns = attributes[attributes.index('columns')+1].split(",")\r
-                       except ValueError:\r
-                               # rowset did not have columns tag set (this is a bug in API)\r
-                               # columns will be extracted from first row instead.\r
-                               columns = []\r
-\r
-                       try:\r
-                               priKey = attributes[attributes.index('key')+1]\r
-                               this = IndexRowset(cols=columns, key=priKey)\r
-                       except ValueError:\r
-                               this = Rowset(cols=columns)\r
-\r
-\r
-                       this._name = attributes[attributes.index('name')+1]\r
-                       this.__catch = "row" # tag to auto-add to rowset.\r
-               else:\r
-                       this = Element()\r
-                       this._name = name\r
-\r
-               this.__parent = self.container\r
-\r
-               if self.root is None:\r
-                       # We're at the root. The first tag has to be "eveapi" or we can't\r
-                       # really assume the rest of the xml is going to be what we expect.\r
-                       if name != "eveapi":\r
-                               raise RuntimeError("Invalid API response")\r
-                       self.root = this\r
-\r
-               if isinstance(self.container, Rowset) and (self.container.__catch == this._name):\r
-                       # check for missing columns attribute (see above)\r
-                       if not self.container._cols:\r
-                               self.container._cols = attributes[0::2]\r
-\r
-                       self.container.append([_autocast(attributes[i]) for i in range(1, len(attributes), 2)])\r
-                       this._isrow = True\r
-                       this._attributes = this._attributes2 = None\r
-               else:\r
-                       this._isrow = False\r
-                       this._attributes = attributes\r
-                       this._attributes2 = []\r
-       \r
-               self.container = this\r
-\r
-\r
-       def tag_cdata(self, data):\r
-               if data == "\r\n" or data.strip() != data:\r
-                       return\r
-\r
-               this = self.container\r
-               data = _autocast(data)\r
-\r
-               if this._attributes:\r
-                       # this tag has attributes, so we can't simply assign the cdata\r
-                       # as an attribute to the parent tag, as we'll lose the current\r
-                       # tag's attributes then. instead, we'll assign the data as\r
-                       # attribute of this tag.\r
-                       this.data = data\r
-               else:\r
-                       # this was a simple <tag>data</tag> without attributes.\r
-                       # we won't be doing anything with this actual tag so we can just\r
-                       # bind it to its parent (done by __tag_end)\r
-                       setattr(this.__parent, this._name, data)\r
-\r
-\r
-       def tag_end(self, name):\r
-               this = self.container\r
-               if this is self.root:\r
-                       del this._attributes\r
-                       #this.__dict__.pop("_attributes", None)\r
-                       return\r
-\r
-               # we're done with current tag, so we can pop it off. This means that\r
-               # self.container will now point to the container of element 'this'.\r
-               self.container = this.__parent\r
-               del this.__parent\r
-\r
-               attributes = this.__dict__.pop("_attributes")\r
-               attributes2 = this.__dict__.pop("_attributes2")\r
-               if attributes is None:\r
-                       # already processed this tag's closure early, in tag_start()\r
-                       return\r
-\r
-               if self.container._isrow:\r
-                       # Special case here. tags inside a row! Such tags have to be\r
-                       # added as attributes of the row.\r
-                       parent = self.container.__parent\r
-\r
-                       # get the row line for this element from its parent rowset\r
-                       _row = parent._rows[-1]\r
-\r
-                       # add this tag's value to the end of the row\r
-                       _row.append(getattr(self.container, this._name, this))\r
-\r
-                       # fix columns if neccessary.\r
-                       if len(parent._cols) < len(_row):\r
-                               parent._cols.append(this._name)\r
-               else:\r
-                       # see if there's already an attribute with this name (this shouldn't\r
-                       # really happen, but it doesn't hurt to handle this case!\r
-                       sibling = getattr(self.container, this._name, None)\r
-                       if sibling is None:\r
-                               self.container._attributes2.append(this._name)\r
-                               setattr(self.container, this._name, this)\r
-                       # Note: there aren't supposed to be any NON-rowset tags containing\r
-                       # multiples of some tag or attribute. Code below handles this case.\r
-                       elif isinstance(sibling, Rowset):\r
-                               # its doppelganger is a rowset, append this as a row to that.\r
-                               row = [_autocast(attributes[i]) for i in range(1, len(attributes), 2)]\r
-                               row.extend([getattr(this, col) for col in attributes2])\r
-                               sibling.append(row)\r
-                       elif isinstance(sibling, Element):\r
-                               # parent attribute is an element. This means we're dealing\r
-                               # with multiple of the same sub-tag. Change the attribute\r
-                               # into a Rowset, adding the sibling element and this one.\r
-                               rs = Rowset()\r
-                               rs.__catch = rs._name = this._name\r
-                               row = [_autocast(attributes[i]) for i in range(1, len(attributes), 2)]+[getattr(this, col) for col in attributes2]\r
-                               rs.append(row)\r
-                               row = [getattr(sibling, attributes[i]) for i in range(0, len(attributes), 2)]+[getattr(sibling, col) for col in attributes2]\r
-                               rs.append(row)\r
-                               rs._cols = [attributes[i] for i in range(0, len(attributes), 2)]+[col for col in attributes2]\r
-                               setattr(self.container, this._name, rs)\r
-                       else:\r
-                               # something else must have set this attribute already.\r
-                               # (typically the <tag>data</tag> case in tag_data())\r
-                               pass\r
-\r
-               # Now fix up the attributes and be done with it.\r
-               for i in range(1, len(attributes), 2):\r
-                       this.__dict__[attributes[i-1]] = _autocast(attributes[i])\r
-\r
-               return\r
-\r
-\r
-\r
-\r
-#-----------------------------------------------------------------------------\r
-# XML Data Containers\r
-#-----------------------------------------------------------------------------\r
-# The following classes are the various container types the XML data is\r
-# unpacked into.\r
-#\r
-# Note that objects returned by API calls are to be treated as read-only. This\r
-# is not enforced, but you have been warned.\r
-#-----------------------------------------------------------------------------\r
-\r
-class Element(object):\r
-       # Element is a namespace for attributes and nested tags\r
-       def __str__(self):\r
-               return "<Element '%s'>" % self._name\r
-\r
-\r
-class Row(object):\r
-       # A Row is a single database record associated with a Rowset.\r
-       # The fields in the record are accessed as attributes by their respective\r
-       # column name.\r
-       #\r
-       # To conserve resources, Row objects are only created on-demand. This is\r
-       # typically done by Rowsets (e.g. when iterating over the rowset).\r
-       \r
-       def __init__(self, cols=None, row=None):\r
-               self._cols = cols or []\r
-               self._row = row or []\r
-\r
-       def __nonzero__(self):\r
-               return True\r
-\r
-       def __ne__(self, other):\r
-               return self.__cmp__(other)\r
-\r
-       def __eq__(self, other):\r
-               return self.__cmp__(other) == 0\r
-\r
-       def __cmp__(self, other):\r
-               if type(other) != type(self):\r
-                       raise TypeError("Incompatible comparison type")\r
-               return cmp(self._cols, other._cols) or cmp(self._row, other._row)\r
-\r
-       def __getattr__(self, this):\r
-               try:\r
-                       return self._row[self._cols.index(this)]\r
-               except:\r
-                       raise AttributeError, this\r
-\r
-       def __getitem__(self, this):\r
-               return self._row[self._cols.index(this)]\r
-\r
-       def __str__(self):\r
-               return "Row(" + ','.join(map(lambda k, v: "%s:%s" % (str(k), str(v)), self._cols, self._row)) + ")"\r
-\r
-\r
-class Rowset(object):\r
-       # Rowsets are collections of Row objects.\r
-       #\r
-       # Rowsets support most of the list interface:\r
-       #   iteration, indexing and slicing\r
-       #\r
-       # As well as the following methods: \r
-       #\r
-       #   IndexedBy(column)\r
-       #     Returns an IndexRowset keyed on given column. Requires the column to\r
-       #     be usable as primary key.\r
-       #\r
-       #   GroupedBy(column)\r
-       #     Returns a FilterRowset keyed on given column. FilterRowset objects\r
-       #     can be accessed like dicts. See FilterRowset class below.\r
-       #\r
-       #   SortBy(column, reverse=True)\r
-       #     Sorts rowset in-place on given column. for a descending sort,\r
-       #     specify reversed=True.\r
-       #\r
-       #   SortedBy(column, reverse=True)\r
-       #     Same as SortBy, except this retuens a new rowset object instead of\r
-       #     sorting in-place.\r
-       #\r
-       #   Select(columns, row=False)\r
-       #     Yields a column values tuple (value, ...) for each row in the rowset.\r
-       #     If only one column is requested, then just the column value is\r
-       #     provided instead of the values tuple.\r
-       #     When row=True, each result will be decorated with the entire row.\r
-       #\r
-\r
-       def IndexedBy(self, column):\r
-               return IndexRowset(self._cols, self._rows, column)\r
-\r
-       def GroupedBy(self, column):\r
-               return FilterRowset(self._cols, self._rows, column)\r
-\r
-       def SortBy(self, column, reverse=False):\r
-               ix = self._cols.index(column)\r
-               self.sort(key=lambda e: e[ix], reverse=reverse)\r
-\r
-       def SortedBy(self, column, reverse=False):\r
-               rs = self[:]\r
-               rs.SortBy(column, reverse)\r
-               return rs\r
-\r
-       def Select(self, *columns, **options):\r
-               if len(columns) == 1:\r
-                       i = self._cols.index(columns[0])\r
-                       if options.get("row", False):\r
-                               for line in self._rows:\r
-                                       yield (line, line[i])\r
-                       else:\r
-                               for line in self._rows:\r
-                                       yield line[i]\r
-               else:\r
-                       i = map(self._cols.index, columns)\r
-                       if options.get("row", False):\r
-                               for line in self._rows:\r
-                                       yield line, [line[x] for x in i]\r
-                       else:\r
-                               for line in self._rows:\r
-                                       yield [line[x] for x in i]\r
-\r
-\r
-       # -------------\r
-\r
-       def __init__(self, cols=None, rows=None):\r
-               self._cols = cols or []\r
-               self._rows = rows or []\r
-\r
-       def append(self, row):\r
-               if isinstance(row, list):\r
-                       self._rows.append(row)\r
-               elif isinstance(row, Row) and len(row._cols) == len(self._cols):\r
-                       self._rows.append(row._row)\r
-               else:\r
-                       raise TypeError("incompatible row type")\r
-\r
-       def __add__(self, other):\r
-               if isinstance(other, Rowset):\r
-                       if len(other._cols) == len(self._cols):\r
-                               self._rows += other._rows\r
-               raise TypeError("rowset instance expected")\r
-\r
-       def __nonzero__(self):\r
-               return not not self._rows\r
-\r
-       def __len__(self):\r
-               return len(self._rows)\r
-\r
-       def copy(self):\r
-               return self[:]\r
-\r
-       def __getitem__(self, ix):\r
-               if type(ix) is slice:\r
-                       return Rowset(self._cols, self._rows[ix])\r
-               return Row(self._cols, self._rows[ix])\r
-\r
-       def sort(self, *args, **kw):\r
-               self._rows.sort(*args, **kw)\r
-\r
-       def __str__(self):\r
-               return ("Rowset(columns=[%s], rows=%d)" % (','.join(self._cols), len(self)))\r
-\r
-       def __getstate__(self):\r
-               return (self._cols, self._rows)\r
-\r
-       def __setstate__(self, state):\r
-               self._cols, self._rows = state\r
-\r
-\r
-\r
-class IndexRowset(Rowset):\r
-       # An IndexRowset is a Rowset that keeps an index on a column.\r
-       #\r
-       # The interface is the same as Rowset, but provides an additional method:\r
-       #\r
-       #   Get(key [, default])\r
-       #     Returns the Row mapped to provided key in the index. If there is no\r
-       #     such key in the index, KeyError is raised unless a default value was\r
-       #     specified.\r
-       #\r
-\r
-       def Get(self, key, *default):\r
-               row = self._items.get(key, None)\r
-               if row is None:\r
-                       if default:\r
-                               return default[0]\r
-                       raise KeyError, key\r
-               return Row(self._cols, row)\r
-\r
-       # -------------\r
-\r
-       def __init__(self, cols=None, rows=None, key=None):\r
-               try:\r
-                       self._ki = ki = cols.index(key)\r
-               except IndexError:\r
-                       raise ValueError("Rowset has no column %s" % key)\r
-\r
-               Rowset.__init__(self, cols, rows)\r
-               self._key = key\r
-               self._items = dict((row[ki], row) for row in self._rows)\r
-\r
-       def __getitem__(self, ix):\r
-               if type(ix) is slice:\r
-                       return IndexRowset(self._cols, self._rows[ix], self._key)\r
-               return Rowset.__getitem__(self, ix)\r
-\r
-       def append(self, row):\r
-               Rowset.append(self, row)\r
-               self._items[row[self._ki]] = row\r
-\r
-       def __getstate__(self):\r
-               return (Rowset.__getstate__(self), self._items, self._ki)\r
-\r
-       def __setstate__(self, state):\r
-               state, self._items, self._ki = state\r
-               Rowset.__setstate__(self, state)\r
-\r
-\r
-class FilterRowset(object):\r
-       # A FilterRowset works much like an IndexRowset, with the following\r
-       # differences:\r
-       # - FilterRowsets are accessed much like dicts\r
-       # - Each key maps to a Rowset, containing only the rows where the value\r
-       #   of the column this FilterRowset was made on matches the key.\r
-\r
-       def __init__(self, cols=None, rows=None, key=None, key2=None, dict=None):\r
-               if dict is not None:\r
-                       self._items = items = dict\r
-               elif cols is not None:\r
-                       self._items = items = {}\r
-\r
-                       idfield = cols.index(key)\r
-                       if not key2:\r
-                               for row in rows:\r
-                                       id = row[idfield]\r
-                                       if id in items:\r
-                                               items[id].append(row)\r
-                                       else:\r
-                                               items[id] = [row]\r
-                       else:\r
-                               idfield2 = cols.index(key2)\r
-                               for row in rows:\r
-                                       id = row[idfield]\r
-                                       if id in items:\r
-                                               items[id][row[idfield2]] = row\r
-                                       else:\r
-                                               items[id] = {row[idfield2]:row}\r
-\r
-               self._cols = cols\r
-               self.key = key\r
-               self.key2 = key2\r
-               self._bind()\r
-\r
-       def _bind(self):\r
-               items = self._items\r
-               self.keys = items.keys\r
-               self.iterkeys = items.iterkeys\r
-               self.__contains__ = items.__contains__\r
-               self.has_key = items.has_key\r
-               self.__len__ = items.__len__\r
-               self.__iter__ = items.__iter__\r
-\r
-       def copy(self):\r
-               return FilterRowset(self._cols[:], None, self.key, self.key2, dict=copy.deepcopy(self._items))\r
-\r
-       def get(self, key, default=_unspecified):\r
-               try:\r
-                       return self[key]\r
-               except KeyError:\r
-                       if default is _unspecified:\r
-                               raise\r
-               return default\r
-\r
-       def __getitem__(self, i):\r
-               if self.key2:\r
-                       return IndexRowset(self._cols, None, self.key2, self._items.get(i, {}))\r
-               return Rowset(self._cols, self._items[i])\r
-\r
-       def __getstate__(self):\r
-               return (self._cols, self._rows, self._items, self.key, self.key2)\r
-\r
-       def __setstate__(self, state):\r
-               self._cols, self._rows, self._items, self.key, self.key2 = state\r
-               self._bind()\r
-\r
diff --git a/eveapi/__init__.py b/eveapi/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/eveapi/apitest.py b/eveapi/apitest.py
new file mode 100644 (file)
index 0000000..b37b9b3
--- /dev/null
@@ -0,0 +1,416 @@
+#=============================================================================\r
+# eveapi module demonstration script - Jamie van den Berge\r
+#=============================================================================\r
+#\r
+# This file is in the Public Domain - Do with it as you please.\r
+# \r
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r
+# OTHER DEALINGS IN THE SOFTWARE\r
+#\r
+#----------------------------------------------------------------------------\r
+# Put your userID and apiKey (full access) here before running this script.\r
+YOUR_USERID = 123456\r
+YOUR_APIKEY = "hPx6rxdYfVNeGcuOgPKRL-ohhithere-aUg6OfxCtMH4FUn5GUzf8YqIQDdc5gF7"\r
+\r
+import time\r
+import tempfile\r
+import cPickle\r
+import zlib\r
+import os\r
+from os.path import join, exists\r
+from httplib import HTTPException\r
+\r
+import eveapi\r
+\r
+api = eveapi.EVEAPIConnection()\r
+\r
+#----------------------------------------------------------------------------\r
+print\r
+print "EXAMPLE 1: GETTING THE ALLIANCE LIST"\r
+print " (and showing alliances with 1000 or more members)"\r
+print\r
+\r
+# Let's get the list of alliances.\r
+# The API function we need to get the list is:\r
+#\r
+#    /eve/AllianceList.xml.aspx\r
+#\r
+# There is a 1:1 correspondence between folders/files and attributes on api\r
+# objects, so to call this particular function, we simply do this:\r
+result1 = api.eve.AllianceList()\r
+\r
+# This result contains a rowset object called "alliances". Rowsets are like\r
+# database tables and you can do various useful things with them. For now\r
+# we'll just iterate over it and display all alliances with more than 1000\r
+# members:\r
+for alliance in result1.alliances:\r
+       if alliance.memberCount >= 1000:\r
+               print "%s <%s> has %d members" %\\r
+                       (alliance.name, alliance.shortName, alliance.memberCount)\r
+\r
+\r
+#-----------------------------------------------------------------------------\r
+print\r
+print "EXAMPLE 2: GETTING WALLET BALANCE OF ALL YOUR CHARACTERS"\r
+print\r
+\r
+# To get any info on character/corporation related stuff, we need to acquire\r
+# an authentication context. All API requests that require authentication need\r
+# to be called through this object. While it is possible to call such API\r
+# functions directly through the api object, you would have to specify the\r
+# userID and apiKey on every call. If you are iterating over many accounts,\r
+# that may actually be the better option. However, for these examples we only\r
+# use one account, so this is more convenient.\r
+auth = api.auth(userID=YOUR_USERID, apiKey=YOUR_APIKEY)\r
+\r
+# Now let's say you want to the wallet balance of all your characters.\r
+# The API function we need to get the characters on your account is:\r
+#\r
+#    /account/Characters.xml.aspx\r
+#\r
+# As in example 1, this simply means adding folder names as attributes\r
+# and calling the function named after the base page name:\r
+result2 = auth.account.Characters()\r
+\r
+# Some tracking for later examples.\r
+rich = 0\r
+rich_charID = 0\r
+\r
+# Now the best way to iterate over the characters on your account and show\r
+# the isk balance is probably this way:\r
+for character in result2.characters:\r
+       wallet = auth.char.AccountBalance(characterID=character.characterID)\r
+       isk = wallet.accounts[0].balance\r
+       print character.name, "has", isk, "ISK."\r
+\r
+       if isk > rich:\r
+               rich = isk\r
+               rich_charID = character.characterID\r
+\r
+\r
+\r
+#-----------------------------------------------------------------------------\r
+print\r
+print "EXAMPLE 3: WHEN STUFF GOES WRONG"\r
+print\r
+\r
+# Obviously you cannot assume an API call to succeed. There's a myriad of\r
+# things that can go wrong:\r
+#\r
+# - Connection error\r
+# - Server error\r
+# - Invalid parameters passed\r
+# - Hamsters died\r
+#\r
+# Therefor it is important to handle errors properly. eveapi will raise\r
+# an AttributeError if the requested function does not exist on the server\r
+# (ie. when it returns a 404), a RuntimeError on any other webserver error\r
+# (such as 500 Internal Server error).\r
+# On top of this, you can get any of the httplib (which eveapi uses) and\r
+# socket (which httplib uses) exceptions so you might want to catch those\r
+# as well.\r
+#\r
+\r
+try:\r
+       # Try calling account/Characters without authentication context\r
+       api.account.Characters()\r
+except eveapi.Error, e:\r
+       print "Oops! eveapi returned the following error:"\r
+       print "code:", e.code\r
+       print "message:", e.message\r
+except Exception, e:\r
+       print "Something went horribly wrong:", str(e)\r
+       raise\r
+\r
+\r
+#-----------------------------------------------------------------------------\r
+print\r
+print "EXAMPLE 4: GETTING CHARACTER SHEET INFORMATION"\r
+print\r
+\r
+# We grab ourselves a character context object.\r
+# Note that this is a convenience function that takes care of passing the\r
+# characterID=x parameter to every API call much like auth() does (in fact\r
+# it's exactly like that, apart from the fact it also automatically adds the\r
+# "/char" folder). Again, it is possible to use the API functions directly\r
+# from the api or auth context, but then you have to provide the missing\r
+# keywords on every call (characterID in this case).\r
+#\r
+# The victim we'll use is the last character on the account we used in\r
+# example 1.\r
+me = auth.character(result2.characters[-1].characterID)\r
+\r
+# Now that we have a character context, we can display skills trained on\r
+# a character. First we have to get the skill tree. A real application\r
+# would cache this data; all objects returned by the api interface can be\r
+# pickled.\r
+skilltree = api.eve.SkillTree()\r
+\r
+# Now we have to fetch the charactersheet.\r
+# Note that the call below is identical to:\r
+#\r
+#   acc.char.CharacterSheet(characterID=your_character_id)\r
+#\r
+# But, as explained above, the context ("me") we created automatically takes\r
+# care of adding the characterID parameter and /char folder attribute.\r
+sheet = me.CharacterSheet()\r
+\r
+# This list should look familiar. They're the skillpoints at each level for\r
+# a rank 1 skill. We could use the formula, but this is much simpler :)\r
+sp = [0, 250, 1414, 8000, 45255, 256000]\r
+\r
+total_sp = 0\r
+total_skills = 0\r
+\r
+# Now the fun bit starts. We walk the skill tree, and for every group in the\r
+# tree...\r
+for g in skilltree.skillGroups:\r
+\r
+       skills_trained_in_this_group = False\r
+\r
+       # ... iterate over the skills in this group...\r
+       for skill in g.skills:\r
+\r
+               # see if we trained this skill by checking the character sheet object\r
+               trained = sheet.skills.Get(skill.typeID, False)\r
+               if trained:\r
+                       # yep, we trained this skill.\r
+\r
+                       # print the group name if we haven't done so already\r
+                       if not skills_trained_in_this_group:\r
+                               print g.groupName\r
+                               skills_trained_in_this_group = True\r
+\r
+                       # and display some info about the skill!\r
+                       print "- %s Rank(%d) - SP: %d/%d - Level: %d" %\\r
+                               (skill.typeName, skill.rank, trained.skillpoints, (skill.rank * sp[trained.level]), trained.level)\r
+                       total_skills += 1\r
+                       total_sp += trained.skillpoints\r
+\r
+\r
+# And to top it off, display totals.\r
+print "You currently have %d skills and %d skill points" % (total_skills, total_sp)\r
+\r
+\r
+\r
+#-----------------------------------------------------------------------------\r
+print\r
+print "EXAMPLE 5: USING ROWSETS"\r
+print\r
+\r
+# For this one we will use the result1 that contains the alliance list from\r
+# the first example.\r
+rowset = result1.alliances\r
+\r
+# Now, what if we want to sort the alliances by ticker name. We could unpack\r
+# all alliances into a list and then use python's sort(key=...) on that list,\r
+# but that's not efficient. The rowset objects support sorting on columns\r
+# directly:\r
+rowset.SortBy("shortName")\r
+\r
+# Note the use of Select() here. The Select method speeds up iterating over\r
+# large rowsets considerably as no temporary row instances are created.\r
+for ticker in rowset.Select("shortName"):\r
+       print ticker,\r
+print\r
+\r
+# The sort above modified the result inplace. There is another method, called\r
+# SortedBy, which returns a new rowset. \r
+\r
+print\r
+\r
+# Another useful method of rowsets is IndexBy, which enables you to do direct\r
+# key lookups on columns. We already used this feature in example 3. Indeed\r
+# most rowsets returned are IndexRowsets already if the data has a primary\r
+# key attribute defined in the <rowset> tag in the XML data.\r
+#\r
+# IndexRowsets are efficient, they reference the data from the rowset they\r
+# were created from, and create an index mapping on top of it.\r
+#\r
+# Anyway, to create an index:\r
+alliances_by_ticker = rowset.IndexedBy("shortName")\r
+\r
+# Now use the Get() method to get a row directly.\r
+# Assumes ISD alliance exists. If it doesn't, we probably have bigger\r
+# problems than the unhandled exception here -_-\r
+try:\r
+       print alliances_by_ticker.Get("ISD")\r
+except :\r
+       print "Blimey! CCP let the ISD alliance expire -AGAIN-. How inconvenient!"\r
+\r
+# You may specify a default to return in case the row wasn't found:\r
+print alliances_by_ticker.Get("123456", 42)\r
+\r
+# If no default was specified and you try to look up a key that does not\r
+# exist, an appropriate exception will be raised:\r
+try:\r
+       print alliances_by_ticker.Get("123456")\r
+except KeyError:\r
+       print "This concludes example 5"\r
+\r
+\r
+\r
+#-----------------------------------------------------------------------------\r
+print\r
+print "EXAMPLE 6: CACHING DATA"\r
+print\r
+\r
+# For some calls you will want caching. To facilitate this, a customized\r
+# cache handler can be attached. Below is an example of a simple cache\r
+# handler. \r
+\r
+class MyCacheHandler(object):\r
+       # Note: this is an example handler to demonstrate how to use them.\r
+       # a -real- handler should probably be thread-safe and handle errors\r
+       # properly (and perhaps use a better hashing scheme).\r
+\r
+       def __init__(self, debug=False):\r
+               self.debug = debug\r
+               self.count = 0\r
+               self.cache = {}\r
+               self.tempdir = join(tempfile.gettempdir(), "eveapi")\r
+               if not exists(self.tempdir):\r
+                       os.makedirs(self.tempdir)\r
+\r
+       def log(self, what):\r
+               if self.debug:\r
+                       print "[%d] %s" % (self.count, what)\r
+\r
+       def retrieve(self, host, path, params):\r
+               # eveapi asks if we have this request cached\r
+               key = hash((host, path, frozenset(params.items())))\r
+\r
+               self.count += 1  # for logging\r
+\r
+               # see if we have the requested page cached...\r
+               cached = self.cache.get(key, None)\r
+               if cached:\r
+                       cacheFile = None\r
+                       #print "'%s': retrieving from memory" % path\r
+               else:\r
+                       # it wasn't cached in memory, but it might be on disk.\r
+                       cacheFile = join(self.tempdir, str(key) + ".cache")\r
+                       if exists(cacheFile):\r
+                               self.log("%s: retrieving from disk" % path)\r
+                               f = open(cacheFile, "rb")\r
+                               cached = self.cache[key] = cPickle.loads(zlib.decompress(f.read()))\r
+                               f.close()\r
+\r
+               if cached:\r
+                       # check if the cached doc is fresh enough\r
+                       if time.time() < cached[0]:\r
+                               self.log("%s: returning cached document" % path)\r
+                               return cached[1]  # return the cached XML doc\r
+\r
+                       # it's stale. purge it.\r
+                       self.log("%s: cache expired, purging!" % path)\r
+                       del self.cache[key]\r
+                       if cacheFile:\r
+                               os.remove(cacheFile)\r
+\r
+               self.log("%s: not cached, fetching from server..." % path)\r
+               # we didn't get a cache hit so return None to indicate that the data\r
+               # should be requested from the server.\r
+               return None\r
+\r
+       def store(self, host, path, params, doc, obj):\r
+               # eveapi is asking us to cache an item\r
+               key = hash((host, path, frozenset(params.items())))\r
+\r
+               cachedFor = obj.cachedUntil - obj.currentTime\r
+               if cachedFor:\r
+                       self.log("%s: cached (%d seconds)" % (path, cachedFor))\r
+\r
+                       cachedUntil = time.time() + cachedFor\r
+\r
+                       # store in memory\r
+                       cached = self.cache[key] = (cachedUntil, doc)\r
+\r
+                       # store in cache folder\r
+                       cacheFile = join(self.tempdir, str(key) + ".cache")\r
+                       f = open(cacheFile, "wb")\r
+                       f.write(zlib.compress(cPickle.dumps(cached, -1)))\r
+                       f.close()\r
+\r
+\r
+# Now try out the handler! Even though were initializing a new api object\r
+# here, a handler can be attached or removed from an existing one at any\r
+# time with its setcachehandler() method.\r
+cachedApi = eveapi.EVEAPIConnection(cacheHandler=MyCacheHandler(debug=True))\r
+\r
+# First time around this will fetch the document from the server. That is,\r
+# if this demo is run for the first time, otherwise it will attempt to load\r
+# the cache written to disk on the previous run.\r
+result = cachedApi.eve.SkillTree()\r
+\r
+# But the second time it should be returning the cached version\r
+result = cachedApi.eve.SkillTree()\r
+\r
+\r
+\r
+#-----------------------------------------------------------------------------\r
+print\r
+print "EXAMPLE 7: TRANSACTION DATA"\r
+print "(and doing more nifty stuff with rowsets)"\r
+print\r
+\r
+# okay since we have a caching api object now it is fairly safe to do this\r
+# example repeatedly without server locking you out for an hour every time!\r
+\r
+# Let's use the first character on the account (using the richest character\r
+# found in example 2). Note how we are chaining the various contexts here to\r
+# arrive directly at a character context. If you're not using any intermediate\r
+# contexts in the chain anyway, this is okay.\r
+me = cachedApi.auth(YOUR_USERID, YOUR_APIKEY).character(rich_charID)\r
+\r
+# Now fetch the journal. Since this character context was created through \r
+# the cachedApi object, it will still use the cachehandler from example 5.\r
+journal = me.WalletJournal()\r
+\r
+# Let's see how much we paid SCC in transaction tax in the first page\r
+# of data!\r
+\r
+# Righto, now we -could- sift through the rows and extract what we want,\r
+# but we can do it in a much more clever way using the GroupedBy method\r
+# of the rowset in the result. This creates a mapping that maps keys\r
+# to Rowsets of all rows with that key value in specified column.\r
+# These data structures are also quite efficient as the only extra data\r
+# created is the index and grouping.\r
+entriesByRefType = journal.entries.GroupedBy("refTypeID")\r
+\r
+# Also note that we're using a hardcoded refTypeID of 54 here. You're\r
+# supposed to use .eve.RefTypes() though (however they are not likely\r
+# to be changed anyway so we can get away with it)\r
+# Note the use of Select() to speed things up here.\r
+amount = 0.0\r
+date = 0\r
+for taxAmount, date in entriesByRefType[54].Select("amount", "date"):\r
+       amount += -taxAmount\r
+\r
+print "You paid a %.2f ISK transaction tax since %s" %\\r
+       (amount, time.asctime(time.gmtime(date)))\r
+\r
+\r
+# You might also want to see how much a certain item yielded you recently.\r
+typeName = "Expanded Cargohold II"\r
+amount = 0.0\r
+\r
+wallet = me.WalletTransactions()\r
+soldTx = wallet.transactions.GroupedBy("transactionType")["sell"]\r
+for row in soldTx.GroupedBy("typeName")[typeName]:\r
+       amount += (row.quantity * row.price)\r
+\r
+print "%s sales yielded %.2f ISK since %s" %\\r
+       (typeName, amount, time.asctime(time.gmtime(row.transactionDateTime)))\r
+\r
+# I'll leave walking the transaction pages as an excercise to the reader ;)\r
+# Please also see the eveapi module itself for more documentation.\r
+\r
+# That's all folks!\r
+\r
diff --git a/eveapi/eveapi.py b/eveapi/eveapi.py
new file mode 100644 (file)
index 0000000..4633c14
--- /dev/null
@@ -0,0 +1,787 @@
+#-----------------------------------------------------------------------------\r
+# eveapi - EVE Online API access\r
+#\r
+# Copyright (c)2007 Jamie "Entity" van den Berge <entity@vapor.com>\r
+# \r
+# Permission is hereby granted, free of charge, to any person\r
+# obtaining a copy of this software and associated documentation\r
+# files (the "Software"), to deal in the Software without\r
+# restriction, including without limitation the rights to use,\r
+# copy, modify, merge, publish, distribute, sublicense, and/or sell\r
+# copies of the Software, and to permit persons to whom the\r
+# Software is furnished to do so, subject to the following\r
+# conditions:\r
+# \r
+# The above copyright notice and this permission notice shall be\r
+# included in all copies or substantial portions of the Software.\r
+#\r
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r
+# OTHER DEALINGS IN THE SOFTWARE\r
+#\r
+#-----------------------------------------------------------------------------\r
+# Version: 1.1.1 - 10 Januari 2010\r
+# - Fixed bug that causes nested tags to not appear in rows of rowsets created\r
+#      from normal Elements. This should fix the corp.MemberSecurity method,\r
+#   which now returns all data for members. [jehed]\r
+#\r
+# Version: 1.1.0 - 15 Januari 2009\r
+# - Added Select() method to Rowset class. Using it avoids the creation of\r
+#   temporary row instances, speeding up iteration considerably.\r
+# - Added ParseXML() function, which can be passed arbitrary API XML file or\r
+#   string objects.\r
+# - Added support for proxy servers. A proxy can be specified globally or\r
+#   per api connection instance. [suggestion by graalman]\r
+# - Some minor refactoring.\r
+# - Fixed deprecation warning when using Python 2.6.\r
+#\r
+# Version: 1.0.7 - 14 November 2008\r
+# - Added workaround for rowsets that are missing the (required!) columns\r
+#   attribute. If missing, it will use the columns found in the first row.\r
+#   Note that this is will still break when expecting columns, if the rowset\r
+#   is empty. [Flux/Entity]\r
+#\r
+# Version: 1.0.6 - 18 July 2008\r
+# - Enabled expat text buffering to avoid content breaking up. [BigWhale]\r
+#\r
+# Version: 1.0.5 - 03 February 2008\r
+# - Added workaround to make broken XML responses (like the "row:name" bug in\r
+#   eve/CharacterID) work as intended.\r
+# - Bogus datestamps before the epoch in XML responses are now set to 0 to\r
+#   avoid breaking certain date/time functions. [Anathema Matou]\r
+#\r
+# Version: 1.0.4 - 23 December 2007\r
+# - Changed _autocast() to use timegm() instead of mktime(). [Invisible Hand]\r
+# - Fixed missing attributes of elements inside rows. [Elandra Tenari]\r
+#\r
+# Version: 1.0.3 - 13 December 2007\r
+# - Fixed keyless columns bugging out the parser (in CorporationSheet for ex.)\r
+#\r
+# Version: 1.0.2 - 12 December 2007\r
+# - Fixed parser not working with indented XML.\r
+#\r
+# Version: 1.0.1\r
+# - Some micro optimizations\r
+#\r
+# Version: 1.0\r
+# - Initial release\r
+#\r
+# Requirements:\r
+#   Python 2.4+\r
+#\r
+#-----------------------------------------------------------------------------\r
+\r
+import httplib\r
+import urllib\r
+import copy\r
+\r
+from xml.parsers import expat\r
+from time import strptime\r
+from calendar import timegm\r
+\r
+proxy = None\r
+\r
+#-----------------------------------------------------------------------------\r
+\r
+class Error(StandardError):\r
+       def __init__(self, code, message):\r
+               self.code = code\r
+               self.args = (message.rstrip("."),)\r
+\r
+\r
+def EVEAPIConnection(url="api.eve-online.com", cacheHandler=None, proxy=None):\r
+       # Creates an API object through which you can call remote functions.\r
+       #\r
+       # The following optional arguments may be provided:\r
+       #\r
+       # url - root location of the EVEAPI server\r
+       #\r
+       # proxy - (host,port) specifying a proxy server through which to request\r
+       #         the API pages. Specifying a proxy overrides default proxy.\r
+       #\r
+       # cacheHandler - an object which must support the following interface:\r
+       #\r
+       #      retrieve(host, path, params)\r
+       #\r
+       #          Called when eveapi wants to fetch a document.\r
+       #          host is the address of the server, path is the full path to\r
+       #          the requested document, and params is a dict containing the\r
+       #          parameters passed to this api call (userID, apiKey etc).\r
+       #          The method MUST return one of the following types:\r
+       #\r
+       #           None - if your cache did not contain this entry\r
+       #           str/unicode - eveapi will parse this as XML\r
+       #           Element - previously stored object as provided to store()\r
+       #           file-like object - eveapi will read() XML from the stream.\r
+       #\r
+       #      store(host, path, params, doc, obj)\r
+       #\r
+       #          Called when eveapi wants you to cache this item.\r
+       #          You can use obj to get the info about the object (cachedUntil\r
+       #          and currentTime, etc) doc is the XML document the object\r
+       #          was generated from. It's generally best to cache the XML, not\r
+       #          the object, unless you pickle the object. Note that this method\r
+       #          will only be called if you returned None in the retrieve() for\r
+       #          this object.\r
+       #\r
+\r
+       if url.lower().startswith("http://"):\r
+               url = url[7:]\r
+\r
+       if "/" in url:\r
+               url, path = url.split("/", 1)\r
+       else:\r
+               path = ""\r
+\r
+       ctx = _RootContext(None, path, {}, {})\r
+       ctx._handler = cacheHandler\r
+       ctx._host = url\r
+       ctx._proxy = proxy or globals()["proxy"]\r
+       return ctx\r
+\r
+\r
+def ParseXML(file_or_string):\r
+       try:\r
+               return _ParseXML(file_or_string, False, None)\r
+       except TypeError:\r
+               raise TypeError("XML data must be provided as string or file-like object")\r
+\r
+\r
+def _ParseXML(response, fromContext, storeFunc):\r
+       # pre/post-process XML or Element data\r
+\r
+       if fromContext and isinstance(response, Element):\r
+               obj = response\r
+       elif type(response) in (str, unicode):\r
+               obj = _Parser().Parse(response, False)\r
+       elif hasattr(response, "read"):\r
+               obj = _Parser().Parse(response, True)\r
+       else:\r
+               raise TypeError("retrieve method must return None, string, file-like object or an Element instance")\r
+\r
+       error = getattr(obj, "error", False)\r
+       if error:\r
+               raise Error(error.code, error.data)\r
+\r
+       result = getattr(obj, "result", False)\r
+       if not result:\r
+               raise RuntimeError("API object does not contain result")\r
+\r
+       if fromContext and storeFunc:\r
+               # call the cache handler to store this object\r
+               storeFunc(obj)\r
+\r
+       # make metadata available to caller somehow\r
+       result._meta = obj\r
+\r
+       return result\r
+\r
+\r
+       \r
+\r
+\r
+#-----------------------------------------------------------------------------\r
+# API Classes\r
+#-----------------------------------------------------------------------------\r
+\r
+_listtypes = (list, tuple, dict)\r
+_unspecified = []\r
+\r
+class _Context(object):\r
+\r
+       def __init__(self, root, path, parentDict, newKeywords=None):\r
+               self._root = root or self\r
+               self._path = path\r
+               if newKeywords:\r
+                       if parentDict:\r
+                               self.parameters = parentDict.copy()\r
+                       else:\r
+                               self.parameters = {}\r
+                       self.parameters.update(newKeywords)\r
+               else:\r
+                       self.parameters = parentDict or {}\r
+\r
+       def context(self, *args, **kw):\r
+               if kw or args:\r
+                       path = self._path\r
+                       if args:\r
+                               path += "/" + "/".join(args)\r
+                       return self.__class__(self._root, path, self.parameters, kw)\r
+               else:\r
+                       return self\r
+\r
+       def __getattr__(self, this):\r
+               # perform arcane attribute majick trick\r
+               return _Context(self._root, self._path + "/" + this, self.parameters)\r
+\r
+       def __call__(self, **kw):\r
+               if kw:\r
+                       # specified keywords override contextual ones\r
+                       for k, v in self.parameters.iteritems():\r
+                               if k not in kw:\r
+                                       kw[k] = v\r
+               else:\r
+                       # no keywords provided, just update with contextual ones.\r
+                       kw.update(self.parameters)\r
+\r
+               # now let the root context handle it further\r
+               return self._root(self._path, **kw)\r
+\r
+\r
+class _AuthContext(_Context):\r
+\r
+       def character(self, characterID):\r
+               # returns a copy of this connection object but for every call made\r
+               # through it, it will add the folder "/char" to the url, and the\r
+               # characterID to the parameters passed.\r
+               return _Context(self._root, self._path + "/char", self.parameters, {"characterID":characterID})\r
+\r
+       def corporation(self, characterID):\r
+               # same as character except for the folder "/corp"\r
+               return _Context(self._root, self._path + "/corp", self.parameters, {"characterID":characterID})\r
+\r
+\r
+class _RootContext(_Context):\r
+\r
+       def auth(self, userID=None, apiKey=None):\r
+               # returns a copy of this object but for every call made through it, the\r
+               # userID and apiKey will be added to the API request.\r
+               if userID and apiKey:\r
+                       return _AuthContext(self._root, self._path, self.parameters, {"userID":userID, "apiKey":apiKey})\r
+               raise ValueError("Must specify userID and apiKey")\r
+\r
+       def setcachehandler(self, handler):\r
+               self._root._handler = handler\r
+\r
+       def __call__(self, path, **kw):\r
+               # convert list type arguments to something the API likes\r
+               for k, v in kw.iteritems():\r
+                       if isinstance(v, _listtypes):\r
+                               kw[k] = ','.join(map(str, list(v)))\r
+\r
+               cache = self._root._handler\r
+\r
+               # now send the request\r
+               path += ".xml.aspx"\r
+\r
+               if cache:\r
+                       response = cache.retrieve(self._host, path, kw)\r
+               else:\r
+                       response = None\r
+\r
+               if response is None:\r
+                       if self._proxy is None:\r
+                               http = httplib.HTTPConnection(self._host)\r
+                               if kw:\r
+                                       http.request("POST", path, urllib.urlencode(kw), {"Content-type": "application/x-www-form-urlencoded"})\r
+                               else:\r
+                                       http.request("GET", path)\r
+                       else:\r
+                               http = httplib.HTTPConnection(*self._proxy)\r
+                               if kw:\r
+                                       http.request("POST", 'http://'+self._host+path, urllib.urlencode(kw), {"Content-type": "application/x-www-form-urlencoded"})\r
+                               else:\r
+                                       http.request("GET", 'http://'+self._host+path)\r
+\r
+                       response = http.getresponse()\r
+                       if response.status != 200:\r
+                               if response.status == httplib.NOT_FOUND:\r
+                                       raise AttributeError("'%s' not available on API server (404 Not Found)" % path)\r
+                               else:\r
+                                       raise RuntimeError("'%s' request failed (%d %s)" % (path, response.status, response.reason))\r
+\r
+                       if cache:\r
+                               store = True\r
+                               response = response.read()\r
+                       else:\r
+                               store = False\r
+               else:\r
+                       store = False\r
+\r
+               return _ParseXML(response, True, store and (lambda obj: cache.store(self._host, path, kw, response, obj)))\r
+\r
+\r
+#-----------------------------------------------------------------------------\r
+# XML Parser\r
+#-----------------------------------------------------------------------------\r
+\r
+def _autocast(s):\r
+       # attempts to cast an XML string to the most probable type.\r
+       try:\r
+               if s.strip("-").isdigit():\r
+                       return int(s)\r
+       except ValueError:\r
+               pass\r
+\r
+       try:\r
+               return float(s)\r
+       except ValueError:\r
+               pass\r
+\r
+       if len(s) == 19 and s[10] == ' ':\r
+               # it could be a date string\r
+               try:\r
+                       return max(0, int(timegm(strptime(s, "%Y-%m-%d %H:%M:%S"))))\r
+               except OverflowError:\r
+                       pass\r
+               except ValueError:\r
+                       pass\r
+\r
+       # couldn't cast. return string unchanged.\r
+       return s\r
+\r
+\r
+class _Parser(object):\r
+\r
+       def Parse(self, data, isStream=False):\r
+               self.container = self.root = None\r
+               p = expat.ParserCreate()\r
+               p.StartElementHandler = self.tag_start\r
+               p.CharacterDataHandler = self.tag_cdata\r
+               p.EndElementHandler = self.tag_end\r
+               p.ordered_attributes = True\r
+               p.buffer_text = True\r
+\r
+               if isStream:\r
+                       p.ParseFile(data)\r
+               else:\r
+                       p.Parse(data, True)\r
+               return self.root\r
+               \r
+\r
+       def tag_start(self, name, attributes):\r
+               # <hack>\r
+               # If there's a colon in the tag name, cut off the name from the colon\r
+               # onward. This is a workaround to make certain bugged XML responses\r
+               # (such as eve/CharacterID.xml.aspx) work.\r
+               if ":" in name:\r
+                       name = name[:name.index(":")]\r
+               # </hack>\r
+\r
+               if name == "rowset":\r
+                       # for rowsets, use the given name\r
+                       try:\r
+                               columns = attributes[attributes.index('columns')+1].split(",")\r
+                       except ValueError:\r
+                               # rowset did not have columns tag set (this is a bug in API)\r
+                               # columns will be extracted from first row instead.\r
+                               columns = []\r
+\r
+                       try:\r
+                               priKey = attributes[attributes.index('key')+1]\r
+                               this = IndexRowset(cols=columns, key=priKey)\r
+                       except ValueError:\r
+                               this = Rowset(cols=columns)\r
+\r
+\r
+                       this._name = attributes[attributes.index('name')+1]\r
+                       this.__catch = "row" # tag to auto-add to rowset.\r
+               else:\r
+                       this = Element()\r
+                       this._name = name\r
+\r
+               this.__parent = self.container\r
+\r
+               if self.root is None:\r
+                       # We're at the root. The first tag has to be "eveapi" or we can't\r
+                       # really assume the rest of the xml is going to be what we expect.\r
+                       if name != "eveapi":\r
+                               raise RuntimeError("Invalid API response")\r
+                       self.root = this\r
+\r
+               if isinstance(self.container, Rowset) and (self.container.__catch == this._name):\r
+                       # check for missing columns attribute (see above)\r
+                       if not self.container._cols:\r
+                               self.container._cols = attributes[0::2]\r
+\r
+                       self.container.append([_autocast(attributes[i]) for i in range(1, len(attributes), 2)])\r
+                       this._isrow = True\r
+                       this._attributes = this._attributes2 = None\r
+               else:\r
+                       this._isrow = False\r
+                       this._attributes = attributes\r
+                       this._attributes2 = []\r
+       \r
+               self.container = this\r
+\r
+\r
+       def tag_cdata(self, data):\r
+               if data == "\r\n" or data.strip() != data:\r
+                       return\r
+\r
+               this = self.container\r
+               data = _autocast(data)\r
+\r
+               if this._attributes:\r
+                       # this tag has attributes, so we can't simply assign the cdata\r
+                       # as an attribute to the parent tag, as we'll lose the current\r
+                       # tag's attributes then. instead, we'll assign the data as\r
+                       # attribute of this tag.\r
+                       this.data = data\r
+               else:\r
+                       # this was a simple <tag>data</tag> without attributes.\r
+                       # we won't be doing anything with this actual tag so we can just\r
+                       # bind it to its parent (done by __tag_end)\r
+                       setattr(this.__parent, this._name, data)\r
+\r
+\r
+       def tag_end(self, name):\r
+               this = self.container\r
+               if this is self.root:\r
+                       del this._attributes\r
+                       #this.__dict__.pop("_attributes", None)\r
+                       return\r
+\r
+               # we're done with current tag, so we can pop it off. This means that\r
+               # self.container will now point to the container of element 'this'.\r
+               self.container = this.__parent\r
+               del this.__parent\r
+\r
+               attributes = this.__dict__.pop("_attributes")\r
+               attributes2 = this.__dict__.pop("_attributes2")\r
+               if attributes is None:\r
+                       # already processed this tag's closure early, in tag_start()\r
+                       return\r
+\r
+               if self.container._isrow:\r
+                       # Special case here. tags inside a row! Such tags have to be\r
+                       # added as attributes of the row.\r
+                       parent = self.container.__parent\r
+\r
+                       # get the row line for this element from its parent rowset\r
+                       _row = parent._rows[-1]\r
+\r
+                       # add this tag's value to the end of the row\r
+                       _row.append(getattr(self.container, this._name, this))\r
+\r
+                       # fix columns if neccessary.\r
+                       if len(parent._cols) < len(_row):\r
+                               parent._cols.append(this._name)\r
+               else:\r
+                       # see if there's already an attribute with this name (this shouldn't\r
+                       # really happen, but it doesn't hurt to handle this case!\r
+                       sibling = getattr(self.container, this._name, None)\r
+                       if sibling is None:\r
+                               self.container._attributes2.append(this._name)\r
+                               setattr(self.container, this._name, this)\r
+                       # Note: there aren't supposed to be any NON-rowset tags containing\r
+                       # multiples of some tag or attribute. Code below handles this case.\r
+                       elif isinstance(sibling, Rowset):\r
+                               # its doppelganger is a rowset, append this as a row to that.\r
+                               row = [_autocast(attributes[i]) for i in range(1, len(attributes), 2)]\r
+                               row.extend([getattr(this, col) for col in attributes2])\r
+                               sibling.append(row)\r
+                       elif isinstance(sibling, Element):\r
+                               # parent attribute is an element. This means we're dealing\r
+                               # with multiple of the same sub-tag. Change the attribute\r
+                               # into a Rowset, adding the sibling element and this one.\r
+                               rs = Rowset()\r
+                               rs.__catch = rs._name = this._name\r
+                               row = [_autocast(attributes[i]) for i in range(1, len(attributes), 2)]+[getattr(this, col) for col in attributes2]\r
+                               rs.append(row)\r
+                               row = [getattr(sibling, attributes[i]) for i in range(0, len(attributes), 2)]+[getattr(sibling, col) for col in attributes2]\r
+                               rs.append(row)\r
+                               rs._cols = [attributes[i] for i in range(0, len(attributes), 2)]+[col for col in attributes2]\r
+                               setattr(self.container, this._name, rs)\r
+                       else:\r
+                               # something else must have set this attribute already.\r
+                               # (typically the <tag>data</tag> case in tag_data())\r
+                               pass\r
+\r
+               # Now fix up the attributes and be done with it.\r
+               for i in range(1, len(attributes), 2):\r
+                       this.__dict__[attributes[i-1]] = _autocast(attributes[i])\r
+\r
+               return\r
+\r
+\r
+\r
+\r
+#-----------------------------------------------------------------------------\r
+# XML Data Containers\r
+#-----------------------------------------------------------------------------\r
+# The following classes are the various container types the XML data is\r
+# unpacked into.\r
+#\r
+# Note that objects returned by API calls are to be treated as read-only. This\r
+# is not enforced, but you have been warned.\r
+#-----------------------------------------------------------------------------\r
+\r
+class Element(object):\r
+       # Element is a namespace for attributes and nested tags\r
+       def __str__(self):\r
+               return "<Element '%s'>" % self._name\r
+\r
+\r
+class Row(object):\r
+       # A Row is a single database record associated with a Rowset.\r
+       # The fields in the record are accessed as attributes by their respective\r
+       # column name.\r
+       #\r
+       # To conserve resources, Row objects are only created on-demand. This is\r
+       # typically done by Rowsets (e.g. when iterating over the rowset).\r
+       \r
+       def __init__(self, cols=None, row=None):\r
+               self._cols = cols or []\r
+               self._row = row or []\r
+\r
+       def __nonzero__(self):\r
+               return True\r
+\r
+       def __ne__(self, other):\r
+               return self.__cmp__(other)\r
+\r
+       def __eq__(self, other):\r
+               return self.__cmp__(other) == 0\r
+\r
+       def __cmp__(self, other):\r
+               if type(other) != type(self):\r
+                       raise TypeError("Incompatible comparison type")\r
+               return cmp(self._cols, other._cols) or cmp(self._row, other._row)\r
+\r
+       def __getattr__(self, this):\r
+               try:\r
+                       return self._row[self._cols.index(this)]\r
+               except:\r
+                       raise AttributeError, this\r
+\r
+       def __getitem__(self, this):\r
+               return self._row[self._cols.index(this)]\r
+\r
+       def __str__(self):\r
+               return "Row(" + ','.join(map(lambda k, v: "%s:%s" % (str(k), str(v)), self._cols, self._row)) + ")"\r
+\r
+\r
+class Rowset(object):\r
+       # Rowsets are collections of Row objects.\r
+       #\r
+       # Rowsets support most of the list interface:\r
+       #   iteration, indexing and slicing\r
+       #\r
+       # As well as the following methods: \r
+       #\r
+       #   IndexedBy(column)\r
+       #     Returns an IndexRowset keyed on given column. Requires the column to\r
+       #     be usable as primary key.\r
+       #\r
+       #   GroupedBy(column)\r
+       #     Returns a FilterRowset keyed on given column. FilterRowset objects\r
+       #     can be accessed like dicts. See FilterRowset class below.\r
+       #\r
+       #   SortBy(column, reverse=True)\r
+       #     Sorts rowset in-place on given column. for a descending sort,\r
+       #     specify reversed=True.\r
+       #\r
+       #   SortedBy(column, reverse=True)\r
+       #     Same as SortBy, except this retuens a new rowset object instead of\r
+       #     sorting in-place.\r
+       #\r
+       #   Select(columns, row=False)\r
+       #     Yields a column values tuple (value, ...) for each row in the rowset.\r
+       #     If only one column is requested, then just the column value is\r
+       #     provided instead of the values tuple.\r
+       #     When row=True, each result will be decorated with the entire row.\r
+       #\r
+\r
+       def IndexedBy(self, column):\r
+               return IndexRowset(self._cols, self._rows, column)\r
+\r
+       def GroupedBy(self, column):\r
+               return FilterRowset(self._cols, self._rows, column)\r
+\r
+       def SortBy(self, column, reverse=False):\r
+               ix = self._cols.index(column)\r
+               self.sort(key=lambda e: e[ix], reverse=reverse)\r
+\r
+       def SortedBy(self, column, reverse=False):\r
+               rs = self[:]\r
+               rs.SortBy(column, reverse)\r
+               return rs\r
+\r
+       def Select(self, *columns, **options):\r
+               if len(columns) == 1:\r
+                       i = self._cols.index(columns[0])\r
+                       if options.get("row", False):\r
+                               for line in self._rows:\r
+                                       yield (line, line[i])\r
+                       else:\r
+                               for line in self._rows:\r
+                                       yield line[i]\r
+               else:\r
+                       i = map(self._cols.index, columns)\r
+                       if options.get("row", False):\r
+                               for line in self._rows:\r
+                                       yield line, [line[x] for x in i]\r
+                       else:\r
+                               for line in self._rows:\r
+                                       yield [line[x] for x in i]\r
+\r
+\r
+       # -------------\r
+\r
+       def __init__(self, cols=None, rows=None):\r
+               self._cols = cols or []\r
+               self._rows = rows or []\r
+\r
+       def append(self, row):\r
+               if isinstance(row, list):\r
+                       self._rows.append(row)\r
+               elif isinstance(row, Row) and len(row._cols) == len(self._cols):\r
+                       self._rows.append(row._row)\r
+               else:\r
+                       raise TypeError("incompatible row type")\r
+\r
+       def __add__(self, other):\r
+               if isinstance(other, Rowset):\r
+                       if len(other._cols) == len(self._cols):\r
+                               self._rows += other._rows\r
+               raise TypeError("rowset instance expected")\r
+\r
+       def __nonzero__(self):\r
+               return not not self._rows\r
+\r
+       def __len__(self):\r
+               return len(self._rows)\r
+\r
+       def copy(self):\r
+               return self[:]\r
+\r
+       def __getitem__(self, ix):\r
+               if type(ix) is slice:\r
+                       return Rowset(self._cols, self._rows[ix])\r
+               return Row(self._cols, self._rows[ix])\r
+\r
+       def sort(self, *args, **kw):\r
+               self._rows.sort(*args, **kw)\r
+\r
+       def __str__(self):\r
+               return ("Rowset(columns=[%s], rows=%d)" % (','.join(self._cols), len(self)))\r
+\r
+       def __getstate__(self):\r
+               return (self._cols, self._rows)\r
+\r
+       def __setstate__(self, state):\r
+               self._cols, self._rows = state\r
+\r
+\r
+\r
+class IndexRowset(Rowset):\r
+       # An IndexRowset is a Rowset that keeps an index on a column.\r
+       #\r
+       # The interface is the same as Rowset, but provides an additional method:\r
+       #\r
+       #   Get(key [, default])\r
+       #     Returns the Row mapped to provided key in the index. If there is no\r
+       #     such key in the index, KeyError is raised unless a default value was\r
+       #     specified.\r
+       #\r
+\r
+       def Get(self, key, *default):\r
+               row = self._items.get(key, None)\r
+               if row is None:\r
+                       if default:\r
+                               return default[0]\r
+                       raise KeyError, key\r
+               return Row(self._cols, row)\r
+\r
+       # -------------\r
+\r
+       def __init__(self, cols=None, rows=None, key=None):\r
+               try:\r
+                       self._ki = ki = cols.index(key)\r
+               except IndexError:\r
+                       raise ValueError("Rowset has no column %s" % key)\r
+\r
+               Rowset.__init__(self, cols, rows)\r
+               self._key = key\r
+               self._items = dict((row[ki], row) for row in self._rows)\r
+\r
+       def __getitem__(self, ix):\r
+               if type(ix) is slice:\r
+                       return IndexRowset(self._cols, self._rows[ix], self._key)\r
+               return Rowset.__getitem__(self, ix)\r
+\r
+       def append(self, row):\r
+               Rowset.append(self, row)\r
+               self._items[row[self._ki]] = row\r
+\r
+       def __getstate__(self):\r
+               return (Rowset.__getstate__(self), self._items, self._ki)\r
+\r
+       def __setstate__(self, state):\r
+               state, self._items, self._ki = state\r
+               Rowset.__setstate__(self, state)\r
+\r
+\r
+class FilterRowset(object):\r
+       # A FilterRowset works much like an IndexRowset, with the following\r
+       # differences:\r
+       # - FilterRowsets are accessed much like dicts\r
+       # - Each key maps to a Rowset, containing only the rows where the value\r
+       #   of the column this FilterRowset was made on matches the key.\r
+\r
+       def __init__(self, cols=None, rows=None, key=None, key2=None, dict=None):\r
+               if dict is not None:\r
+                       self._items = items = dict\r
+               elif cols is not None:\r
+                       self._items = items = {}\r
+\r
+                       idfield = cols.index(key)\r
+                       if not key2:\r
+                               for row in rows:\r
+                                       id = row[idfield]\r
+                                       if id in items:\r
+                                               items[id].append(row)\r
+                                       else:\r
+                                               items[id] = [row]\r
+                       else:\r
+                               idfield2 = cols.index(key2)\r
+                               for row in rows:\r
+                                       id = row[idfield]\r
+                                       if id in items:\r
+                                               items[id][row[idfield2]] = row\r
+                                       else:\r
+                                               items[id] = {row[idfield2]:row}\r
+\r
+               self._cols = cols\r
+               self.key = key\r
+               self.key2 = key2\r
+               self._bind()\r
+\r
+       def _bind(self):\r
+               items = self._items\r
+               self.keys = items.keys\r
+               self.iterkeys = items.iterkeys\r
+               self.__contains__ = items.__contains__\r
+               self.has_key = items.has_key\r
+               self.__len__ = items.__len__\r
+               self.__iter__ = items.__iter__\r
+\r
+       def copy(self):\r
+               return FilterRowset(self._cols[:], None, self.key, self.key2, dict=copy.deepcopy(self._items))\r
+\r
+       def get(self, key, default=_unspecified):\r
+               try:\r
+                       return self[key]\r
+               except KeyError:\r
+                       if default is _unspecified:\r
+                               raise\r
+               return default\r
+\r
+       def __getitem__(self, i):\r
+               if self.key2:\r
+                       return IndexRowset(self._cols, None, self.key2, self._items.get(i, {}))\r
+               return Rowset(self._cols, self._items[i])\r
+\r
+       def __getstate__(self):\r
+               return (self._cols, self._rows, self._items, self.key, self.key2)\r
+\r
+       def __setstate__(self, state):\r
+               self._cols, self._rows, self._items, self.key, self.key2 = state\r
+               self._bind()\r
+\r
index c1533f6..ce7cc63 100644 (file)
@@ -1,6 +1,6 @@
 import hildon
 import gtk
-import eveapi
+from eveapi import eveapi
 
 # we will store our preferences in gconf
 import gnome.gconf
@@ -37,8 +37,34 @@ class mEveMon():
 
     def set_uid(self, uid):
         self.gconf.set_string("/apps/maemo/mevemon/eve_uid", uid)
-       
 
+    # really quick hack to get character list. doesn't handle errors well, and if it can't get the gconf settings it just returns the placeholders, when in reality it should tell the UI or something. basically half finished, just uploading to show ry... FIXME --danny
+    def get_characters( self ):
+        ui_char_list = []
+        print 'get_characters() called.'
+        placeholder_chars = [("Character 1", "avatar.png"), ("Character 2", "avatar.png")]
+        api = eveapi.EVEAPIConnection()
+        uid = self.get_uid()
+        api_key = self.get_api_key()
+        if ( uid and api_key ):
+            auth = api.auth( userID = uid, apiKey = api_key )
+            try:
+                api_char_list = auth.account.Characters()
+            except eveapi.Error, e:
+                print "Sorry, eveapi returned error code %s." % e.code
+                print '"' + e.message + '"'
+                return placeholder_chars
+            except Exception, e:
+                print "The sky is falling! Unknown error: ", str( e )
+                raise
+            print "grabbing character list:"
+            for character in api_char_list.characters:
+                print character
+                ui_char_list.append( ( character.name, "avatar.png" ) )
+            return ui_char_list
+        else:
+            return placeholder_chars
+        
 if __name__ == "__main__":
     app = mEveMon()
     app.run()
index d8327f8..8c4eaf0 100644 (file)
@@ -47,8 +47,8 @@ class mEveMonUI():
 
         # temporary hard-coding until we can fetch the data with eveapi
         # something like:
-        # char list = self.controller.get_characters()
-        char_list = [("Character 1", "avatar.png"), ("Character 2", "avatar.png")]
+        char_list = self.controller.get_characters()
+        #char_list = [("Character 1", "avatar.png"), ("Character 2", "avatar.png")]
 
         for name, icon in char_list:
             liter = lstore.append()