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
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
# pylint: disable=missing-docstring
"""
gamelocker.datatypes
(internal) Classes and utility functions to map API responses to objects.
"""
import inspect
import sys
from gamelocker.janus import DataMessage, Attribute
def attr(typ, key, relation=False):
"""Convenience function to specify an attribute or a relation.
:param typ: Allowed data type or class.
:type typ: object
:param key: Object name, mapping name and key mapping name.
:type key: str
:param link: (optional) Whether this is a relation.
:return: `DataMessage` `Attribute` class.
:rtype: :class:`Attribute`
"""
if relation:
return Attribute(value_type=typ, name=key,
mapping=key, key_mapping=key)
else:
return Attribute(value_type=typ, name=key,
mapping=key)
def rel(typ, key):
"""Convenience function to specify a relation.
See `attr()`.
"""
return attr(typ, key, True)
class Player(DataMessage):
type_name = "player"
key_id = attr(str, "id")
name = attr(str, "name")
stats = attr(dict, "stats")
class Participant(DataMessage):
type_name = "participant"
key_id = attr(str, "id")
actor = attr(str, "actor")
stats = attr(dict, "stats")
player = rel(Player, "player")
class Team(DataMessage):
type_name = "team"
key_id = attr(str, "id")
name = rel(str, "name")
class Roster(DataMessage):
type_name = "roster"
key_id = attr(str, "id")
stats = attr(dict, "stats")
participants = rel(Participant, "participants")
team = rel(Team, "team")
class Match(DataMessage):
type_name = "match"
key_id = attr(str, "id")
createdAt = attr(str, "createdAt")
duration = attr(int, "duration")
gameMode = attr(str, "gameMode")
patchVersion = attr(str, "patchVersion")
region = attr(str, "region")
stats = attr(dict, "stats")
rosters = rel(Roster, "rosters")
def modulemap():
"""Returns a dictionary where `type_name`s are mapped
to corresponding classes from `gamelocker.datatypes`.
:return: A dictionary with keys of `type_name`
and values of :class:`DataMessage` subclasses.
:rtype: :class:`gamelocker.janus.DataMessage`
"""
typemap = dict()
classes = inspect.getmembers(sys.modules[__name__], inspect.isclass)
for name, value in classes:
if name == "Attribute" or name == "DataMessage":
continue
typemap[value.type_name] = value
return typemap
def data_to_object(data):
"""Given a data dictionary from the API, returns mapped objects or None.
:param data: Dictionary from API request.
:type data: dict
:return: An object.
:rtype: :class:`gamelocker.janus.DataMessage`
"""
# find the appropriate class from /datatypes.py
typemap = modulemap()
datatype = None
for key, dataclass in typemap.items():
if key == data["type"]:
datatype = dataclass
if datatype is None:
raise NotImplementedError(
"Data type " + data["type"] + " is not implemented yet.")
# load data into class
element = datatype()
element.map_message(data)
return element
def link_to_object(obj, relations):
"""Replaces references in `obj`
by their corresponding objects from `relations`.
:param obj: Master object to populate.
:type obj: :class:`gamelocker.janus.DataMessage`
:param relations: Object pool to populate from.
:type obj: list of :class:`gamelocker.janus.DataMessage`
:return: The populated master object.
:rtype: :class:`gamelocker.janus.DataMessage`
"""
for att in dir(obj):
# loop through all relations that object could possibly have
if not(isinstance(object.__getattribute__(obj, att), Attribute) and
issubclass(
object.__getattribute__(obj, att).value_type, DataMessage)
and not att.startswith("__")):
continue
# try to get the objects that are being linked to
children = object.__getattribute__(obj, att).value
if children is None:
# there is no reference -> master class has object
# but it was not populated with data
# so it is (hopefully) optional.
continue
# can either be a reference or a list of references
if isinstance(children, (list, tuple)):
newlist = []
for child in children:
# replaces every element
for relation in relations:
# see below
if relation.id == child.id:
relation = link_to_object(relation, relations)
newlist.append(relation)
break
object.__setattr__(obj, att, newlist)
else:
for relation in relations:
if relation.id == children.id:
# replace unpopulated relation
# with object from `relations` that has data
# also, replace all references in that object (recursively)
relation = link_to_object(relation, relations)
object.__setattr__(obj, att, relation)
break
return obj
|