1 #=============================================================================
\r
2 # eveapi module demonstration script - Jamie van den Berge
\r
3 #=============================================================================
\r
5 # This file is in the Public Domain - Do with it as you please.
\r
7 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
\r
8 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
\r
9 # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
\r
10 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
\r
11 # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
\r
12 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
\r
13 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
\r
14 # OTHER DEALINGS IN THE SOFTWARE
\r
16 #----------------------------------------------------------------------------
\r
17 # Put your userID and apiKey (full access) here before running this script.
\r
18 YOUR_USERID = 123456
\r
19 YOUR_APIKEY = "hPx6rxdYfVNeGcuOgPKRL-ohhithere-aUg6OfxCtMH4FUn5GUzf8YqIQDdc5gF7"
\r
26 from os.path import join, exists
\r
27 from httplib import HTTPException
\r
31 api = eveapi.EVEAPIConnection()
\r
33 #----------------------------------------------------------------------------
\r
35 print "EXAMPLE 1: GETTING THE ALLIANCE LIST"
\r
36 print " (and showing alliances with 1000 or more members)"
\r
39 # Let's get the list of alliances.
\r
40 # The API function we need to get the list is:
\r
42 # /eve/AllianceList.xml.aspx
\r
44 # There is a 1:1 correspondence between folders/files and attributes on api
\r
45 # objects, so to call this particular function, we simply do this:
\r
46 result1 = api.eve.AllianceList()
\r
48 # This result contains a rowset object called "alliances". Rowsets are like
\r
49 # database tables and you can do various useful things with them. For now
\r
50 # we'll just iterate over it and display all alliances with more than 1000
\r
52 for alliance in result1.alliances:
\r
53 if alliance.memberCount >= 1000:
\r
54 print "%s <%s> has %d members" %\
\r
55 (alliance.name, alliance.shortName, alliance.memberCount)
\r
58 #-----------------------------------------------------------------------------
\r
60 print "EXAMPLE 2: GETTING WALLET BALANCE OF ALL YOUR CHARACTERS"
\r
63 # To get any info on character/corporation related stuff, we need to acquire
\r
64 # an authentication context. All API requests that require authentication need
\r
65 # to be called through this object. While it is possible to call such API
\r
66 # functions directly through the api object, you would have to specify the
\r
67 # userID and apiKey on every call. If you are iterating over many accounts,
\r
68 # that may actually be the better option. However, for these examples we only
\r
69 # use one account, so this is more convenient.
\r
70 auth = api.auth(userID=YOUR_USERID, apiKey=YOUR_APIKEY)
\r
72 # Now let's say you want to the wallet balance of all your characters.
\r
73 # The API function we need to get the characters on your account is:
\r
75 # /account/Characters.xml.aspx
\r
77 # As in example 1, this simply means adding folder names as attributes
\r
78 # and calling the function named after the base page name:
\r
79 result2 = auth.account.Characters()
\r
81 # Some tracking for later examples.
\r
85 # Now the best way to iterate over the characters on your account and show
\r
86 # the isk balance is probably this way:
\r
87 for character in result2.characters:
\r
88 wallet = auth.char.AccountBalance(characterID=character.characterID)
\r
89 isk = wallet.accounts[0].balance
\r
90 print character.name, "has", isk, "ISK."
\r
94 rich_charID = character.characterID
\r
98 #-----------------------------------------------------------------------------
\r
100 print "EXAMPLE 3: WHEN STUFF GOES WRONG"
\r
103 # Obviously you cannot assume an API call to succeed. There's a myriad of
\r
104 # things that can go wrong:
\r
106 # - Connection error
\r
108 # - Invalid parameters passed
\r
111 # Therefor it is important to handle errors properly. eveapi will raise
\r
112 # an AttributeError if the requested function does not exist on the server
\r
113 # (ie. when it returns a 404), a RuntimeError on any other webserver error
\r
114 # (such as 500 Internal Server error).
\r
115 # On top of this, you can get any of the httplib (which eveapi uses) and
\r
116 # socket (which httplib uses) exceptions so you might want to catch those
\r
121 # Try calling account/Characters without authentication context
\r
122 api.account.Characters()
\r
123 except eveapi.Error, e:
\r
124 print "Oops! eveapi returned the following error:"
\r
125 print "code:", e.code
\r
126 print "message:", e.message
\r
127 except Exception, e:
\r
128 print "Something went horribly wrong:", str(e)
\r
132 #-----------------------------------------------------------------------------
\r
134 print "EXAMPLE 4: GETTING CHARACTER SHEET INFORMATION"
\r
137 # We grab ourselves a character context object.
\r
138 # Note that this is a convenience function that takes care of passing the
\r
139 # characterID=x parameter to every API call much like auth() does (in fact
\r
140 # it's exactly like that, apart from the fact it also automatically adds the
\r
141 # "/char" folder). Again, it is possible to use the API functions directly
\r
142 # from the api or auth context, but then you have to provide the missing
\r
143 # keywords on every call (characterID in this case).
\r
145 # The victim we'll use is the last character on the account we used in
\r
147 me = auth.character(result2.characters[-1].characterID)
\r
149 # Now that we have a character context, we can display skills trained on
\r
150 # a character. First we have to get the skill tree. A real application
\r
151 # would cache this data; all objects returned by the api interface can be
\r
153 skilltree = api.eve.SkillTree()
\r
155 # Now we have to fetch the charactersheet.
\r
156 # Note that the call below is identical to:
\r
158 # acc.char.CharacterSheet(characterID=your_character_id)
\r
160 # But, as explained above, the context ("me") we created automatically takes
\r
161 # care of adding the characterID parameter and /char folder attribute.
\r
162 sheet = me.CharacterSheet()
\r
164 # This list should look familiar. They're the skillpoints at each level for
\r
165 # a rank 1 skill. We could use the formula, but this is much simpler :)
\r
166 sp = [0, 250, 1414, 8000, 45255, 256000]
\r
171 # Now the fun bit starts. We walk the skill tree, and for every group in the
\r
173 for g in skilltree.skillGroups:
\r
175 skills_trained_in_this_group = False
\r
177 # ... iterate over the skills in this group...
\r
178 for skill in g.skills:
\r
180 # see if we trained this skill by checking the character sheet object
\r
181 trained = sheet.skills.Get(skill.typeID, False)
\r
183 # yep, we trained this skill.
\r
185 # print the group name if we haven't done so already
\r
186 if not skills_trained_in_this_group:
\r
188 skills_trained_in_this_group = True
\r
190 # and display some info about the skill!
\r
191 print "- %s Rank(%d) - SP: %d/%d - Level: %d" %\
\r
192 (skill.typeName, skill.rank, trained.skillpoints, (skill.rank * sp[trained.level]), trained.level)
\r
194 total_sp += trained.skillpoints
\r
197 # And to top it off, display totals.
\r
198 print "You currently have %d skills and %d skill points" % (total_skills, total_sp)
\r
202 #-----------------------------------------------------------------------------
\r
204 print "EXAMPLE 5: USING ROWSETS"
\r
207 # For this one we will use the result1 that contains the alliance list from
\r
208 # the first example.
\r
209 rowset = result1.alliances
\r
211 # Now, what if we want to sort the alliances by ticker name. We could unpack
\r
212 # all alliances into a list and then use python's sort(key=...) on that list,
\r
213 # but that's not efficient. The rowset objects support sorting on columns
\r
215 rowset.SortBy("shortName")
\r
217 # Note the use of Select() here. The Select method speeds up iterating over
\r
218 # large rowsets considerably as no temporary row instances are created.
\r
219 for ticker in rowset.Select("shortName"):
\r
223 # The sort above modified the result inplace. There is another method, called
\r
224 # SortedBy, which returns a new rowset.
\r
228 # Another useful method of rowsets is IndexBy, which enables you to do direct
\r
229 # key lookups on columns. We already used this feature in example 3. Indeed
\r
230 # most rowsets returned are IndexRowsets already if the data has a primary
\r
231 # key attribute defined in the <rowset> tag in the XML data.
\r
233 # IndexRowsets are efficient, they reference the data from the rowset they
\r
234 # were created from, and create an index mapping on top of it.
\r
236 # Anyway, to create an index:
\r
237 alliances_by_ticker = rowset.IndexedBy("shortName")
\r
239 # Now use the Get() method to get a row directly.
\r
240 # Assumes ISD alliance exists. If it doesn't, we probably have bigger
\r
241 # problems than the unhandled exception here -_-
\r
243 print alliances_by_ticker.Get("ISD")
\r
245 print "Blimey! CCP let the ISD alliance expire -AGAIN-. How inconvenient!"
\r
247 # You may specify a default to return in case the row wasn't found:
\r
248 print alliances_by_ticker.Get("123456", 42)
\r
250 # If no default was specified and you try to look up a key that does not
\r
251 # exist, an appropriate exception will be raised:
\r
253 print alliances_by_ticker.Get("123456")
\r
255 print "This concludes example 5"
\r
259 #-----------------------------------------------------------------------------
\r
261 print "EXAMPLE 6: CACHING DATA"
\r
264 # For some calls you will want caching. To facilitate this, a customized
\r
265 # cache handler can be attached. Below is an example of a simple cache
\r
268 class MyCacheHandler(object):
\r
269 # Note: this is an example handler to demonstrate how to use them.
\r
270 # a -real- handler should probably be thread-safe and handle errors
\r
271 # properly (and perhaps use a better hashing scheme).
\r
273 def __init__(self, debug=False):
\r
277 self.tempdir = join(tempfile.gettempdir(), "eveapi")
\r
278 if not exists(self.tempdir):
\r
279 os.makedirs(self.tempdir)
\r
281 def log(self, what):
\r
283 print "[%d] %s" % (self.count, what)
\r
285 def retrieve(self, host, path, params):
\r
286 # eveapi asks if we have this request cached
\r
287 key = hash((host, path, frozenset(params.items())))
\r
289 self.count += 1 # for logging
\r
291 # see if we have the requested page cached...
\r
292 cached = self.cache.get(key, None)
\r
295 #print "'%s': retrieving from memory" % path
\r
297 # it wasn't cached in memory, but it might be on disk.
\r
298 cacheFile = join(self.tempdir, str(key) + ".cache")
\r
299 if exists(cacheFile):
\r
300 self.log("%s: retrieving from disk" % path)
\r
301 f = open(cacheFile, "rb")
\r
302 cached = self.cache[key] = cPickle.loads(zlib.decompress(f.read()))
\r
306 # check if the cached doc is fresh enough
\r
307 if time.time() < cached[0]:
\r
308 self.log("%s: returning cached document" % path)
\r
309 return cached[1] # return the cached XML doc
\r
311 # it's stale. purge it.
\r
312 self.log("%s: cache expired, purging!" % path)
\r
313 del self.cache[key]
\r
315 os.remove(cacheFile)
\r
317 self.log("%s: not cached, fetching from server..." % path)
\r
318 # we didn't get a cache hit so return None to indicate that the data
\r
319 # should be requested from the server.
\r
322 def store(self, host, path, params, doc, obj):
\r
323 # eveapi is asking us to cache an item
\r
324 key = hash((host, path, frozenset(params.items())))
\r
326 cachedFor = obj.cachedUntil - obj.currentTime
\r
328 self.log("%s: cached (%d seconds)" % (path, cachedFor))
\r
330 cachedUntil = time.time() + cachedFor
\r
333 cached = self.cache[key] = (cachedUntil, doc)
\r
335 # store in cache folder
\r
336 cacheFile = join(self.tempdir, str(key) + ".cache")
\r
337 f = open(cacheFile, "wb")
\r
338 f.write(zlib.compress(cPickle.dumps(cached, -1)))
\r
342 # Now try out the handler! Even though were initializing a new api object
\r
343 # here, a handler can be attached or removed from an existing one at any
\r
344 # time with its setcachehandler() method.
\r
345 cachedApi = eveapi.EVEAPIConnection(cacheHandler=MyCacheHandler(debug=True))
\r
347 # First time around this will fetch the document from the server. That is,
\r
348 # if this demo is run for the first time, otherwise it will attempt to load
\r
349 # the cache written to disk on the previous run.
\r
350 result = cachedApi.eve.SkillTree()
\r
352 # But the second time it should be returning the cached version
\r
353 result = cachedApi.eve.SkillTree()
\r
357 #-----------------------------------------------------------------------------
\r
359 print "EXAMPLE 7: TRANSACTION DATA"
\r
360 print "(and doing more nifty stuff with rowsets)"
\r
363 # okay since we have a caching api object now it is fairly safe to do this
\r
364 # example repeatedly without server locking you out for an hour every time!
\r
366 # Let's use the first character on the account (using the richest character
\r
367 # found in example 2). Note how we are chaining the various contexts here to
\r
368 # arrive directly at a character context. If you're not using any intermediate
\r
369 # contexts in the chain anyway, this is okay.
\r
370 me = cachedApi.auth(YOUR_USERID, YOUR_APIKEY).character(rich_charID)
\r
372 # Now fetch the journal. Since this character context was created through
\r
373 # the cachedApi object, it will still use the cachehandler from example 5.
\r
374 journal = me.WalletJournal()
\r
376 # Let's see how much we paid SCC in transaction tax in the first page
\r
379 # Righto, now we -could- sift through the rows and extract what we want,
\r
380 # but we can do it in a much more clever way using the GroupedBy method
\r
381 # of the rowset in the result. This creates a mapping that maps keys
\r
382 # to Rowsets of all rows with that key value in specified column.
\r
383 # These data structures are also quite efficient as the only extra data
\r
384 # created is the index and grouping.
\r
385 entriesByRefType = journal.entries.GroupedBy("refTypeID")
\r
387 # Also note that we're using a hardcoded refTypeID of 54 here. You're
\r
388 # supposed to use .eve.RefTypes() though (however they are not likely
\r
389 # to be changed anyway so we can get away with it)
\r
390 # Note the use of Select() to speed things up here.
\r
393 for taxAmount, date in entriesByRefType[54].Select("amount", "date"):
\r
394 amount += -taxAmount
\r
396 print "You paid a %.2f ISK transaction tax since %s" %\
\r
397 (amount, time.asctime(time.gmtime(date)))
\r
400 # You might also want to see how much a certain item yielded you recently.
\r
401 typeName = "Expanded Cargohold II"
\r
404 wallet = me.WalletTransactions()
\r
405 soldTx = wallet.transactions.GroupedBy("transactionType")["sell"]
\r
406 for row in soldTx.GroupedBy("typeName")[typeName]:
\r
407 amount += (row.quantity * row.price)
\r
409 print "%s sales yielded %.2f ISK since %s" %\
\r
410 (typeName, amount, time.asctime(time.gmtime(row.transactionDateTime)))
\r
412 # I'll leave walking the transaction pages as an excercise to the reader ;)
\r
413 # Please also see the eveapi module itself for more documentation.
\r
415 # That's all folks!
\r