1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
# pylint: disable=too-few-public-methods,fixme
# TODO: generate documentation
"""
requests.api
This module implements the Gamelocker API.
"""
import requests
class Utils(object):
"""Utility functions."""
def search_dict(self, dic, attr, val):
"""Find a key-value pair recursively in a dictionary.
:param dic: Dictionary to search.
:type dic: dict
:param attr: Key to look for.
:type attr: str
:param val: Value the key needs to have.
:param val: object
:returns: (bool) Whether the dictionary contains the pair.
:rtype: bool
"""
for key in dic:
if isinstance(dic[key], dict):
if self.search_dict(dic[key], attr, val):
return True
else:
if key == attr and dic[key] == val:
return True
return False
class Response(object):
"""A generic API response object.
:param errors: `errors` as returned by the API.
:param data: `errors` as returned by the API.
:param links: `links` as returned by the API.
:param included: `included` as returned by the API.
:param raw: Raw response.
:type raw: dict
"""
def __init__(self, data):
"""Constructs a :class:`Response <Response>`.
:param data: Dictionary from API response JSON.
:type data: dict
:return: :class:`Response <Response>` object
:rtype: gamelocker.Response
"""
if "errors" in data:
self.errors = data["errors"]
else:
self.errors = None
if "data" in data:
self.data = data["data"]
else:
self.data = None
if "links" in data:
self.links = data["links"]
else:
self.links = None
if "included" in data:
self.included = data["included"]
else:
self.included = None
self.raw = data
if self.errors:
raise AttributeError(
"API returned errors: {errors}".format(
errors=repr(self.errors)))
def filter(self, fid, ftype):
"""Returns an element from `data` matching the criteria.
:param fid: ID to look for.
:type fid: str
:param ftype: Obect type to look for.
:type ftype: str
:return: Element matching the filter criteria
:rtype: dict
"""
for include in self.included:
if include["id"] == fid and include["type"] == ftype:
return include
for datum in self.data:
if datum["id"] == fid and datum["type"] == ftype:
return datum
return None
# TODO: implement a Matches.players()
class Matches(object):
"""A collection of :class:`Match <Match>` objects.
:param matches: A list of matches.
:type matches: list
:param length: The number of matches.
:type length: int
"""
def __init__(self, data, matches=None):
"""Constructs a :class:`Matches <Matches>`.
:param data: API data.
:type data: gamelocker.Response
:param matches: (optional) List of matches to construct
collection from. Overrides `data` matches.
:type matches: list of :class:`Matches`
:return: :class:`Matches <Matches>` object
:rtype: gamelocker.Matches
"""
self._data = data
self.matches = []
if matches is not None:
self.matches = matches
else:
for match in self._data.data:
self.matches.append(Match(self._data, match["id"]))
self.length = len(self.matches)
def __getitem__(self, key):
return self.matches[key]
def where(self, attribute, value):
"""Searches for matches where the condition is met.
:param attribute: Attribute to look for.
:type attribute: str
:param value: Requested value for that attribute.
:type value: str, int, object
:return: Collection of :class:`Match` matching the criteria.
:rtype: gamelocker.Matches
"""
matches = []
for match in self.matches:
if match.has(attribute, value):
matches.append(match)
return Matches(self._data, matches)
class Match(object):
"""A Match record.
:param attributes: `attributes` as returned by the API.
:param rosters: List of :class:`Roster <Roster>` objects
related to the match.
:type rosters: list
"""
def __init__(self, data, mid):
"""Constructs a :class:`Match <Match>` from a :class:`Response`.
:param data: API data.
:type data: gamelocker.Response
:param mid: Match ID.
:type mid: str
:return: :class:`Match <Match>` object
:rtype: gamelocker.Match
"""
self._data = data
self._id = mid
match = self._data.filter(mid, "match")
self.attributes = match["attributes"]
self.rosters = [Roster(self._data, r["id"])
for r in match["relationships"]["rosters"]["data"]]
# TODO refactor duplicated code
def has(self, attribute, value, recurse=True):
"""Checks whether Match or (optionally) a child owns an `attribute`
with `value`.
:param attribute: Attribute to look for.
:type attribute: str
:param value: Requested value for that attribute.
:type value: str, int, object
:param recurse: Whether to search relationships or not.
:type recurse: bool
:return: bool
:rtype: bool
"""
if Utils().search_dict(self.attributes, attribute, value):
return True
if recurse:
for roster in self.rosters:
if roster.has(attribute, value):
return True
return False
class Roster(object):
"""A Roster object.
:param attributes: `attributes` as returned by the API.
:param participants: List of :class:`Participant <Participant>` objects
related to the roster.
:type rosters: list
"""
def __init__(self, data, rid):
"""Constructs a specified :class:`Roster <Roster>` from a :class:`Response`.
:param data: API data.
:type data: gamelocker.Response
:param rid: Roster ID.
:type rid: str
:return: :class:`Roster <Roster>` object
:rtype: gamelocker.Roster
"""
self._data = data
self._id = rid
roster = self._data.filter(rid, "roster")
self.attributes = roster["attributes"]
self.participants = [
Participant(self._data, p["id"])
for p in roster["relationships"]["participants"]["data"]
]
def has(self, attribute, value, recurse=True):
"""Checks whether Roster or (optionally) a child owns an `attribute`
with `value`.
:param attribute: Attribute to look for.
:type attribute: str
:param value: Requested value for that attribute.
:type value: str, int, object
:param recurse: Whether to search relationships or not.
:type recurse: bool
:return: bool
:rtype: bool
"""
if Utils().search_dict(self.attributes, attribute, value):
return True
if recurse:
for participant in self.participants:
if participant.has(attribute, value):
return True
return False
class Participant(object):
"""A Participant object.
:param attributes: `attributes` as returned by the API.
:param player: :class:`Player <Player>` object related to the participant.
:type player: gamelocker.Player
"""
def __init__(self, data, pid):
"""Constructs a specified :class:`Participant <Participant>`
from a :class:`Response`.
:param data: API data.
:type data: gamelocker.Response
:param pid: Participant ID.
:type pid: str
:return: :class:`Participant <Participant>` object
:rtype: gamelocker.Participant
"""
self._data = data
self._id = pid
participant = self._data.filter(pid, "participant")
self.attributes = participant["attributes"]
self.player = Player(self._data,
participant["relationships"]["player"]
["data"]["id"])
def has(self, attribute, value):
"""Checks whether Participant or (optionally) owns an `attribute`
with `value`.
:param attribute: Attribute to look for.
:type attribute: str
:param value: Requested value for that attribute.
:type value: str, int, object
:return: bool
:rtype: bool
"""
if Utils().search_dict(self.attributes, attribute, value):
return True
return False
class Player(object):
"""A Player object.
:param attributes: `attributes` as returned by the API.
"""
def __init__(self, data, pid):
"""Constructs a specified :class:`Player <Player>` from a :class:`Response`.
:param data: API data.
:type data: gamelocker.Response
:param pid: Player ID.
:type pid: str
:return: :class:`Player <Player>` object
:rtype: gamelocker.Player
"""
self._data = data
self._id = pid
player = self._data.filter(pid, "player")
self.attributes = player["attributes"]
class Gamelocker(object):
"""Implementation of the Gamelocker API.
:param apikey: API key used.
:type apikey: str
:param title: Title data is fetched for.
:type title: str
"""
def __init__(self, apikey, datacenter="dc01"):
"""Constructs a :class:`Gamelocker <Gamelocker>`.
:param apikey: API key to authenticate with.
:type apikey: str
:param datacenter: (optional) API endpoint datacenter to use.
:type datacenter: str
:return: :class:`Gamelocker <Gamelocker>` object
:rtype: gamelocker.Gamelocker
Usage::
>>> import gamelocker
>>> gamelocker.Gamelocker("getoffmylawn").status()
"v1.0.5"
"""
self.apikey = apikey
self._apiurl = "https://api." + datacenter + ".gamelockerapp.com/"
self.title = ""
def _req(self, method, params=None):
"""Sends a GET request to the API endpoint.
:param method: Method to query.
:param params: (optional) Parameters to send.
:return: Parsed JSON object.
:rtype: dict
"""
headers = {
"Authorization": "Bearer " + self.apikey,
"X-TITLE-ID": self.title,
"Accept": "application/vnd.api+json"
}
http = requests.get(self._apiurl + method,
headers=headers,
params=params)
http.raise_for_status()
return Response(http.json())
def vainglory(self):
"""Sets title to Vainglory.
:return: :class:`Gamelocker <Gamelocker>` object
:rtype: gamelocker.Gamelocker
"""
self.title = "semc-vainglory"
return self
def status(self):
"""Returns the API version.
:return: API version.
:rtype: str
"""
return self._req("status").raw["version"]
def matches(self, limit=None, offset=None, sort=None):
"""Returns a list of recent matches.
:param limit: Maximum number of matches to return.
:type limit: int
:param offset: Offset parameter for pagination.
:type limit: int
:param sort: Sort query to use.
:type sort: str
:return: List of matches.
:rtype: list of dict
"""
params = dict()
# TODO: deprecate by ?limit=x&offset=y soon
if limit:
params["page[limit]"] = limit
if offset:
params["page[offset]"] = offset
if sort: # TODO make this nice and usable
params["sort"] = sort
return Matches(self._req("matches", params))
|