moved the code into src/ and added LICENSE file
[mevemon] / src / eveapi / apitest.py
1 #=============================================================================\r
2 # eveapi module demonstration script - Jamie van den Berge\r
3 #=============================================================================\r
4 #\r
5 # This file is in the Public Domain - Do with it as you please.\r
6\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
15 #\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
20 \r
21 import time\r
22 import tempfile\r
23 import cPickle\r
24 import zlib\r
25 import os\r
26 from os.path import join, exists\r
27 from httplib import HTTPException\r
28 \r
29 import eveapi\r
30 \r
31 api = eveapi.EVEAPIConnection()\r
32 \r
33 #----------------------------------------------------------------------------\r
34 print\r
35 print "EXAMPLE 1: GETTING THE ALLIANCE LIST"\r
36 print " (and showing alliances with 1000 or more members)"\r
37 print\r
38 \r
39 # Let's get the list of alliances.\r
40 # The API function we need to get the list is:\r
41 #\r
42 #    /eve/AllianceList.xml.aspx\r
43 #\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
47 \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
51 # members:\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
56 \r
57 \r
58 #-----------------------------------------------------------------------------\r
59 print\r
60 print "EXAMPLE 2: GETTING WALLET BALANCE OF ALL YOUR CHARACTERS"\r
61 print\r
62 \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
71 \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
74 #\r
75 #    /account/Characters.xml.aspx\r
76 #\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
80 \r
81 # Some tracking for later examples.\r
82 rich = 0\r
83 rich_charID = 0\r
84 \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
91 \r
92         if isk > rich:\r
93                 rich = isk\r
94                 rich_charID = character.characterID\r
95 \r
96 \r
97 \r
98 #-----------------------------------------------------------------------------\r
99 print\r
100 print "EXAMPLE 3: WHEN STUFF GOES WRONG"\r
101 print\r
102 \r
103 # Obviously you cannot assume an API call to succeed. There's a myriad of\r
104 # things that can go wrong:\r
105 #\r
106 # - Connection error\r
107 # - Server error\r
108 # - Invalid parameters passed\r
109 # - Hamsters died\r
110 #\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
117 # as well.\r
118 #\r
119 \r
120 try:\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
129         raise\r
130 \r
131 \r
132 #-----------------------------------------------------------------------------\r
133 print\r
134 print "EXAMPLE 4: GETTING CHARACTER SHEET INFORMATION"\r
135 print\r
136 \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
144 #\r
145 # The victim we'll use is the last character on the account we used in\r
146 # example 1.\r
147 me = auth.character(result2.characters[-1].characterID)\r
148 \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
152 # pickled.\r
153 skilltree = api.eve.SkillTree()\r
154 \r
155 # Now we have to fetch the charactersheet.\r
156 # Note that the call below is identical to:\r
157 #\r
158 #   acc.char.CharacterSheet(characterID=your_character_id)\r
159 #\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
163 \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
167 \r
168 total_sp = 0\r
169 total_skills = 0\r
170 \r
171 # Now the fun bit starts. We walk the skill tree, and for every group in the\r
172 # tree...\r
173 for g in skilltree.skillGroups:\r
174 \r
175         skills_trained_in_this_group = False\r
176 \r
177         # ... iterate over the skills in this group...\r
178         for skill in g.skills:\r
179 \r
180                 # see if we trained this skill by checking the character sheet object\r
181                 trained = sheet.skills.Get(skill.typeID, False)\r
182                 if trained:\r
183                         # yep, we trained this skill.\r
184 \r
185                         # print the group name if we haven't done so already\r
186                         if not skills_trained_in_this_group:\r
187                                 print g.groupName\r
188                                 skills_trained_in_this_group = True\r
189 \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
193                         total_skills += 1\r
194                         total_sp += trained.skillpoints\r
195 \r
196 \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
199 \r
200 \r
201 \r
202 #-----------------------------------------------------------------------------\r
203 print\r
204 print "EXAMPLE 5: USING ROWSETS"\r
205 print\r
206 \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
210 \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
214 # directly:\r
215 rowset.SortBy("shortName")\r
216 \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
220         print ticker,\r
221 print\r
222 \r
223 # The sort above modified the result inplace. There is another method, called\r
224 # SortedBy, which returns a new rowset. \r
225 \r
226 print\r
227 \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
232 #\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
235 #\r
236 # Anyway, to create an index:\r
237 alliances_by_ticker = rowset.IndexedBy("shortName")\r
238 \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
242 try:\r
243         print alliances_by_ticker.Get("ISD")\r
244 except :\r
245         print "Blimey! CCP let the ISD alliance expire -AGAIN-. How inconvenient!"\r
246 \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
249 \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
252 try:\r
253         print alliances_by_ticker.Get("123456")\r
254 except KeyError:\r
255         print "This concludes example 5"\r
256 \r
257 \r
258 \r
259 #-----------------------------------------------------------------------------\r
260 print\r
261 print "EXAMPLE 6: CACHING DATA"\r
262 print\r
263 \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
266 # handler. \r
267 \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
272 \r
273         def __init__(self, debug=False):\r
274                 self.debug = debug\r
275                 self.count = 0\r
276                 self.cache = {}\r
277                 self.tempdir = join(tempfile.gettempdir(), "eveapi")\r
278                 if not exists(self.tempdir):\r
279                         os.makedirs(self.tempdir)\r
280 \r
281         def log(self, what):\r
282                 if self.debug:\r
283                         print "[%d] %s" % (self.count, what)\r
284 \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
288 \r
289                 self.count += 1  # for logging\r
290 \r
291                 # see if we have the requested page cached...\r
292                 cached = self.cache.get(key, None)\r
293                 if cached:\r
294                         cacheFile = None\r
295                         #print "'%s': retrieving from memory" % path\r
296                 else:\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
303                                 f.close()\r
304 \r
305                 if cached:\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
310 \r
311                         # it's stale. purge it.\r
312                         self.log("%s: cache expired, purging!" % path)\r
313                         del self.cache[key]\r
314                         if cacheFile:\r
315                                 os.remove(cacheFile)\r
316 \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
320                 return None\r
321 \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
325 \r
326                 cachedFor = obj.cachedUntil - obj.currentTime\r
327                 if cachedFor:\r
328                         self.log("%s: cached (%d seconds)" % (path, cachedFor))\r
329 \r
330                         cachedUntil = time.time() + cachedFor\r
331 \r
332                         # store in memory\r
333                         cached = self.cache[key] = (cachedUntil, doc)\r
334 \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
339                         f.close()\r
340 \r
341 \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
346 \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
351 \r
352 # But the second time it should be returning the cached version\r
353 result = cachedApi.eve.SkillTree()\r
354 \r
355 \r
356 \r
357 #-----------------------------------------------------------------------------\r
358 print\r
359 print "EXAMPLE 7: TRANSACTION DATA"\r
360 print "(and doing more nifty stuff with rowsets)"\r
361 print\r
362 \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
365 \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
371 \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
375 \r
376 # Let's see how much we paid SCC in transaction tax in the first page\r
377 # of data!\r
378 \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
386 \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
391 amount = 0.0\r
392 date = 0\r
393 for taxAmount, date in entriesByRefType[54].Select("amount", "date"):\r
394         amount += -taxAmount\r
395 \r
396 print "You paid a %.2f ISK transaction tax since %s" %\\r
397         (amount, time.asctime(time.gmtime(date)))\r
398 \r
399 \r
400 # You might also want to see how much a certain item yielded you recently.\r
401 typeName = "Expanded Cargohold II"\r
402 amount = 0.0\r
403 \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
408 \r
409 print "%s sales yielded %.2f ISK since %s" %\\r
410         (typeName, amount, time.asctime(time.gmtime(row.transactionDateTime)))\r
411 \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
414 \r
415 # That's all folks!\r
416 \r