summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorschneefux <schneefux+commit@schneefux.xyz>2017-01-15 19:35:30 +0100
committerschneefux <schneefux+commit@schneefux.xyz>2017-01-15 19:35:39 +0100
commitbe0b8968651f98b962a18c4e750d6396c0fdaf47 (patch)
tree5d577ecb713068cc91feeb3f1c22ae134dbf236a
parent517ac9c3f5b342327ddb248436fc7b47ce54b885 (diff)
downloadpython-gamelocker-be0b8968651f98b962a18c4e750d6396c0fdaf47.tar.gz
python-gamelocker-be0b8968651f98b962a18c4e750d6396c0fdaf47.zip
add functions to fancify items
-rw-r--r--gamelocker/api.py19
-rw-r--r--gamelocker/datatypes.py16
-rw-r--r--gamelocker/janus.py4
-rw-r--r--gamelocker/strings.py242
-rw-r--r--tests/gamelocker_test.py37
5 files changed, 308 insertions, 10 deletions
diff --git a/gamelocker/api.py b/gamelocker/api.py
index 6857eba..c1383d6 100644
--- a/gamelocker/api.py
+++ b/gamelocker/api.py
@@ -3,7 +3,7 @@
# TODO: generate documentation
"""
-requests.api
+gamelocker.api
This module implements the Gamelocker API.
"""
@@ -11,6 +11,7 @@ This module implements the Gamelocker API.
import datetime
import requests
import gamelocker.datatypes
+import gamelocker.strings
class Gamelocker(object):
@@ -157,12 +158,18 @@ class Gamelocker(object):
:return: List of matches.
:rtype: list of dict
"""
+ max_limit = 50 # as set by the API
+
params = dict()
# TODO: deprecate by ?limit=x&offset=y soon
if limit:
params["page[limit]"] = limit
+ else:
+ limit = max_limit
if offset:
params["page[offset]"] = offset
+ else:
+ offset = 0
if sort: # TODO make this nice and usable
params["sort"] = sort
if player:
@@ -178,4 +185,12 @@ class Gamelocker(object):
createdAtEnd = createdAtEnd.isoformat()
params["filter[createdAt-end]"] = createdAtEnd
- return self._get("matches", params=params)
+ # split request to batches of 50
+ matches = []
+ for batch in range(0, limit, max_limit):
+ params["page[limit]"] = min(limit, max_limit)
+ params["page[offset]"] = batch*max_limit+offset
+ matches += self._get("matches", params=params)
+ limit -= max_limit
+
+ return matches
diff --git a/gamelocker/datatypes.py b/gamelocker/datatypes.py
index cf89fad..74ce965 100644
--- a/gamelocker/datatypes.py
+++ b/gamelocker/datatypes.py
@@ -1,9 +1,15 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# pylint: disable=missing-docstring
+"""
+gamelocker.datatypes
+
+Classes and utility functions to map API responses to objects.
+"""
import inspect
import sys
+import gamelocker.strings
from gamelocker.janus import DataMessage, Attribute
@@ -38,15 +44,15 @@ class Player(DataMessage):
key_id = attr(str, "id")
name = attr(str, "name")
- stats = attr(dict, "stats")
+ stats = attr(gamelocker.strings.Stats, "stats")
class Participant(DataMessage):
type_name = "participant"
key_id = attr(str, "id")
- actor = attr(str, "actor")
- stats = attr(dict, "stats")
+ actor = attr(gamelocker.strings.Hero, "actor")
+ stats = attr(gamelocker.strings.Stats, "stats")
player = rel(Player, "player")
@@ -62,7 +68,7 @@ class Roster(DataMessage):
type_name = "roster"
key_id = attr(str, "id")
- stats = attr(dict, "stats")
+ stats = attr(gamelocker.strings.Stats, "stats")
participants = rel(Participant, "participants")
team = rel(Team, "team")
@@ -77,7 +83,7 @@ class Match(DataMessage):
gameMode = attr(str, "gameMode")
patchVersion = attr(str, "patchVersion")
region = attr(str, "region")
- stats = attr(dict, "stats")
+ stats = attr(gamelocker.strings.Stats, "stats")
rosters = rel(Roster, "rosters")
diff --git a/gamelocker/janus.py b/gamelocker/janus.py
index c4fd384..99c0a57 100644
--- a/gamelocker/janus.py
+++ b/gamelocker/janus.py
@@ -267,7 +267,7 @@ class Attribute(object): #Attribute Class to map Data from input Object to Messa
transformation to json.
"""
- __primitive_types = (str,str,int,int,float,bool) #all allowed types for attribute values. (lists and dicts are also allowed, but treated differently)
+ __primitive_types = (str,int,float,bool) #all allowed types for attribute values. (lists and dicts are also allowed, but treated differently)
value = None #holds the actual value of this attribute once it is set.
@@ -289,7 +289,7 @@ class Attribute(object): #Attribute Class to map Data from input Object to Messa
sets all needed configurations and checks if value is a primitive type or list or dict.
"""
- if value_type in self.__primitive_types or value_type == list or value_type == dict or issubclass(value_type,DataMessage):
+ if issubclass(value_type, self.__primitive_types + (list, dict)) or issubclass(value_type,DataMessage):
self.value_type = value_type
self.name = name
self.required = required
diff --git a/gamelocker/strings.py b/gamelocker/strings.py
new file mode 100644
index 0000000..2df2647
--- /dev/null
+++ b/gamelocker/strings.py
@@ -0,0 +1,242 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# pylint: disable=invalid-name,missing-docstring
+"""
+gamelocker.strings
+
+A collection of mappings between strings returned by the API
+and their common names.
+"""
+
+
+# this is quite of a hack.
+class Stats(dict):
+ """A hybrid between `dict` and `object`
+ that types :class:`LazyObject`'s to strings."""
+ def __typeit(self, obj):
+ if isinstance(obj, (tuple, list)):
+ l = []
+ for o in obj:
+ l.append(self.__typeit(o))
+ return l
+ if isinstance(obj, dict):
+ return Stats(obj)
+ if isinstance(obj, str):
+ return LazyObject(obj)
+ return obj
+
+ def __get(self, name):
+ if name in self:
+ return self.__typeit(self[name])
+ else:
+ raise AttributeError("No such attribute: " + name)
+
+ def __getattr__(self, name):
+ return self.__get(name)
+
+ def __setattr__(self, name, value):
+ self[name] = value
+
+ def __delattr__(self, name):
+ if name in self:
+ del self[name]
+ else:
+ raise AttributeError("No such attribute: " + name)
+
+ @property
+ def items(self):
+ return self.__get("items")
+
+class LazyObject(str):
+ """Can be both :class:`Hero` or :class:`Item`."""
+ def pretty(self):
+ for cls in (Hero, Item):
+ prettystr = cls(self).pretty()
+ if prettystr != self:
+ return prettystr
+ return self
+
+
+class Hero(str):
+ heroes = {
+ "*Adagio*": "Adagio",
+ "*Alpha*": "Alpha",
+ "*Ardan*": "Ardan",
+ "*Baron*": "Baron",
+ "*Blackfeather*": "Blackfeather",
+ "*Catherine*": "Catherine",
+ "*Celeste*": "Celeste",
+ "*Flicker*": "Flicker",
+ "*Fortress*": "Fortress",
+ "*Glaive*": "Glaive",
+ "*Gwen*": "Gwen",
+ "*Hero009*": "Krul",
+ "*Hero010*": "Skaarf",
+ "*Hero016*": "Rona",
+ "*Idris*": "Idris",
+ "*Joule*": "Joule",
+ "*Kestrel*": "Kestrel",
+ "*Koshka*": "Koshka",
+ "*Lance*": "Lance",
+ "*Lyra*": "Lyra",
+ "*Ozo*": "Ozo",
+ "*Petal*": "Petal",
+ "*Phinn*": "Phinn",
+ "*Reim*": "Reim",
+ "*Ringo*": "Ringo",
+ "*Samuel*": "Samuel",
+ "*SAW*": "SAW",
+ "*Sayoc*": "Taka",
+ "*Skye*": "Skye",
+ "*Vox*": "Vox"
+ }
+
+ def pretty(self):
+ if self in self.heroes:
+ return self.heroes[self]
+ return self
+
+
+class Item(str):
+ items = {
+ "Aftershock": "Aftershock",
+ "Armor2": "Coat of Plates",
+ "Armor3": "Metal Jacket",
+ "Armor Shredder": "Bonesaw",
+ "Atlas Pauldron": "Atlas Pauldron",
+ "AttackSpeed1": "Swift Shooter",
+ "AttackSpeed2": "Blazing Salvo",
+ "BarbedNeedle": "Barbed Needle",
+ "Boots1": "Sprint Boots",
+ "Boots2": "Travel Boots",
+ "Boots3": "Journey Boots",
+ "BreakingPoint": "Breaking Point",
+ "Broken Myth": "Broken Myth",
+ "Clockwork": "Clockwork",
+ "Cogwheel": "Chronograph",
+ "Contraption": "Contraption",
+ "Cooldown1": "Hourglass",
+ "Critical": "Tyrant's Monocle",
+ "Crucible": "Crucible",
+ "Crystal1": "Crystal Bit",
+ "Crystal2": "Eclipse Prism",
+ "Crystal3": "Shatterglass",
+ "Crystal Matrix": "Alternating Current",
+ "Echo": "Echo",
+ "EveOfHarvest": "Eve of Harvest",
+ "Flare": "Flare",
+ "Flaregun": "Flare Gun",
+ "Fountain of Renewal": "Fountain of Renewal",
+ "Frostburn": "Frostburn",
+ "Halcyon Chargers": "Halcyon Chargers",
+ "Health2": "Dragonheart",
+ "Heavy Prism": "Heavy Prism",
+ "Heavy Steel": "Heavy Steel",
+ "IronguardContract": "Ironguard Contract",
+ "Lifewell": "Lifespring",
+ "Light Armor": "Light Armor",
+ "Light Shield": "Light Shield",
+ "LuckyStrike": "Lucky Strike",
+ "Minion Candy": "Minion Candy",
+ "MinionsFoot": "Minion's Foot",
+ "Mulled Wine": "Halcyon Potion",
+ "NullwaveGauntlet": "Nullwave Gauntlet",
+ "Oakheart": "Oakheart",
+ "PiercingShard": "Piercing Shard",
+ "PiercingSpear": "Piercing Spear",
+ "PoisonedShiv": "Poisoned Shiv",
+ "Protector Contract": "Protector Contract",
+ "Reflex Block": "Reflex Block",
+ "Scout Trap": "Scout Trap",
+ "Serpent Mask": "Serpent Mask",
+ "Shield 2": "Kinetic Shield",
+ "Shiversteel": "Shiversteel",
+ "Six Sins": "Six Sins",
+ "SlumberingHusk": "Slumbering Husk",
+ "Steam Battery": "Energy Battery",
+ "Stormcrown": "Stormcrown",
+ "StormguardBanner": "Stormguard Banner",
+ "Tension Bow": "Tension Bow",
+ "Tornado Trigger": "Tornado Trigger",
+ "Void Battery": "Void Battery",
+ "War Treads": "War Treads",
+ "Weapon3": "Sorrowblade",
+ "Weapon Blade": "Weapon Blade"
+ }
+
+ item_ids = {
+ "*1000_Item_HalcyonPotion*": "Halcyon Potion",
+ "*1002_Item_WeaponBlade*": "Weapon Blade",
+ "*1003_Item_CrystalBit*": "Crystal Bit",
+ "*1004_Item_SwiftShooter*": "Swift Shooter",
+ "*1005_Item_SixSins*": "Six Sins",
+ "*1009_Item_EclipsePrism*": "Eclipse Prism",
+ "*1010_Item_BlazingSalvo*": " Blazing Salvo",
+ "*1012_Item_Sorrowblade*": "Sorrowblade",
+ "*1013_Item_Shatterglass*": "Shatterglass",
+ "*1014_Item_TornadoTrigger*": "Tornado Trigger",
+ "*1015_Item_Oakheart*": "Oakheart",
+ "*1016_Item_Dragonheart*": "Dragonheart",
+ "*1017_Item_LightArmor*": "Light Armor",
+ "*1022_Item_CoatOfPlates*": "Coat of Plates",
+ "*1024_Item_MetalJacket*": "Metal Jacket",
+ "*1025_Item_EnergyBattery*": "Energy Battery",
+ "*1026_Item_Hourglass*": "Hourglass",
+ "*1027_Item_VoidBattery*": "Void Battery",
+ "*1028_Item_Chronograph*": "Chronograph",
+ "*1029_Item_Clockwork*": "Clockwork",
+ "*1030_Item_SprintBoots*": "Sprint Boots",
+ "*1032_Item_TravelBoots*": "Travel Boots",
+ "*1034_Item_SerpentMask*": "Serpent Mask",
+ "*1035_Item_TensionBow*": "Tension Bow",
+ "*1038_Item_Flare*": "Flare",
+ "*1039_Item_Bonesaw*": "Bonesaw",
+ "*1041_Item_MinionCandy*": "Minion Candy",
+ "*1042_Item_Shiversteel*": "Shiversteel",
+ "*1043_Item_ReflexBlock*": "Reflex Block",
+ "*1044_Item_Frostburn*": "Frostburn",
+ "*1045_Item_FountainOfRenewal*": "Fountain of Renewal",
+ "*1046_Item_Crucible*": "Crucible",
+ "*1047_Item_JourneyBoots*": "Journey Boots",
+ "*1049_Item_TyrantsMonocle*": "Tyrant's Monocle",
+ "*1050_Item_Aftershock*": "Aftershock",
+ "*1052_Item_WeaponInfusion*": "Weapon Infusion",
+ "*1053_Item_CrystalInfusion*": "Crystal Infusion",
+ "*1054_Item_ScoutTrap*": "Scout Trap",
+ "*1055_Item_BrokenMyth*": "Broken Myth",
+ "*1056_Item_WarTreads*": "War Treads",
+ "*1057_Item_AtlasPauldron*": "Atlas Pauldron",
+ "*1059_Item_BookOfEulogies*": "Book of Eulogies",
+ "*1060_Item_BarbedNeedle*": "Barbed Needle",
+ "*1061_Item_LightShield*": "Light Shield",
+ "*1062_Item_KineticShield*": "Kinetic Shield",
+ "*1063_Item_Aegis*": "Aegis",
+ "*1064_Item_Lifespring*": "Lifespring",
+ "*1065_Item_HeavySteel*": "Heavy Steel",
+ "*1066_Item_PiercingSpear*": "Piercing Spear",
+ "*1067_Item_BreakingPoint*": "Breaking Point",
+ "*1068_Item_LuckyStrike*": "Lucky Strike",
+ "*1069_Item_AlternatingCurrent*": "Alternating Current",
+ "*1070_Item_PiercingShard*": "Piercing Shard",
+ "*1071_Item_EveOfHarvest*": "Eve of Harvest",
+ "*1072_Item_HeavyPrism*": "Heavy Prism",
+ "*1073_Item_IronguardContract*": "Ironguard Contract",
+ "*1074_Item_StormguardBanner*": "Stormguard Banner",
+ "*1079_Item_Contraption*": "Contraption",
+ "*1080_Item_MinionsFoot*": "Minion's Foot",
+ "*1084_Item_ProtectorContract*": "Protector Contract",
+ "*1087_Item_HalcyonChargers*": "Halcyon Chargers",
+ "*1088_Item_Flaregun*": "Flare Gun",
+ "*1090_Item_Stormcrown*": "Stormcrown",
+ "*1092_Item_PoisonedShiv*": "Poisoned Shiv",
+ "*1095_Item_NullwaveGauntlet*": "Nullwave Gauntlet",
+ "*1097_Item_Echo*": "Echo",
+ "*1105_Item_SlumberingHusk*": "Slumbering Husk"
+ }
+
+ def pretty(self):
+ if self in self.items:
+ return self.items[self]
+ if self in self.item_ids:
+ return self.item_ids[self]
+ return self
diff --git a/tests/gamelocker_test.py b/tests/gamelocker_test.py
index d9f967d..373269a 100644
--- a/tests/gamelocker_test.py
+++ b/tests/gamelocker_test.py
@@ -22,6 +22,30 @@ class TestGamelocker:
def test_map(self):
assert gamelocker.datatypes.modulemap()["match"] is gamelocker.datatypes.Match
+ def test_strings(self, api):
+ stats = gamelocker.strings.Stats({"foo": 1, "bar": 2, "baz": {"deep": True}})
+ assert stats.foo == 1
+ assert stats.baz.deep == True
+
+ taka = gamelocker.strings.Hero("*Sayoc*")
+ assert taka == "*Sayoc*"
+ assert taka.pretty() == "Taka"
+ assert gamelocker.strings.Hero("notexisting") == "notexisting"
+
+ assert gamelocker.strings.Item("Boots2").pretty() == "Travel Boots"
+ assert gamelocker.strings.Item("*1032_Item_TravelBoots*").pretty() == "Travel Boots"
+ assert gamelocker.strings.Item("unknowntestitem").pretty() == "unknowntestitem"
+
+ assert gamelocker.strings.LazyObject("Boots2").pretty() == "Travel Boots"
+ assert gamelocker.strings.LazyObject("*Sayoc*").pretty() == "Taka"
+
+ match = api.match("91cf2ee4-d7d0-11e6-ad79-062445d3d668")
+ assert isinstance(match.rosters[0].participants[0].actor, gamelocker.strings.Hero)
+
+ assert isinstance(match.rosters[0].stats.acesEarned, int)
+ assert isinstance(match.rosters[0].participants[0].stats.items[0], gamelocker.strings.LazyObject)
+ assert isinstance(match.rosters[0].participants[0].stats.items[0].pretty(), str)
+
def test_match(self, api):
match = api.match("91cf2ee4-d7d0-11e6-ad79-062445d3d668")
assert match.gameMode == "casual"
@@ -37,7 +61,18 @@ class TestGamelocker:
assert matches[0].duration > 0
def test_matchesfilters(self, api):
- assert len(api.matches(limit=5)) == 5
+ matches1 = api.matches(limit=3)
+ assert len(matches1) == 3
+ matches2 = api.matches(limit=3, offset=1)
+
+ commons = 0 # 3 matches each, offset 1 -> 2 overlap
+ for match1 in matches1:
+ for match2 in matches2:
+ if match1.id == match2.id:
+ commons += 1
+ assert commons == 2
+
+ assert len(api.matches(limit=52)) == 52
# TODO uncomment as soon as the API is up
# matches = api.matches(limit=10, sort="duration")