diff options
| -rw-r--r-- | README.md | 5 | ||||
| -rw-r--r-- | gamelocker/api.py | 21 | ||||
| -rw-r--r-- | gamelocker/strings.py | 193 | ||||
| -rw-r--r-- | tests/gamelocker_test.py | 95 |
4 files changed, 76 insertions, 238 deletions
@@ -24,3 +24,8 @@ Example usage: ``` [More examples](/examples) + +Running the tests: +```bash +GAMELOCKER_APIKEY=your.api.key PYTHONPATH=. python -m pytest tests +``` diff --git a/gamelocker/api.py b/gamelocker/api.py index 93b1068..8753cce 100644 --- a/gamelocker/api.py +++ b/gamelocker/api.py @@ -6,9 +6,9 @@ gamelocker.api This module implements the Gamelocker API. """ +import logging import requests import gamelocker.datatypes -import gamelocker.strings class Gamelocker(object): @@ -41,13 +41,19 @@ class Gamelocker(object): :rtype: dict """ headers = { - "Authorization": "Bearer " + self.apikey, + "Authorization": self.apikey, "X-TITLE-ID": self.title, "Accept": "application/vnd.api+json" } - http = requests.get(self._apiurl + method, - headers=headers, - params=params) + while True: + http = requests.get(self._apiurl + method, + headers=headers, + params=params) + if http.status_code != 429: + # 429 -> rate limit, retry + break + logging.info("You are being rate limited by the API") + http.raise_for_status() return http.json() @@ -137,7 +143,7 @@ class Gamelocker(object): See http://developer.vainglorygame.com/docs/#get-a-collection-of-matches for parameters. - :param params: (optional) Query parameters. + :param params: Query parameters. :type params: dict :param region: (optional) Shard to query. Defaults to "na". :type region: str @@ -145,6 +151,3 @@ class Gamelocker(object): :rtype: list of dict """ return self._get("matches", params=params, region=region) - - -pretty = gamelocker.strings.pretty diff --git a/gamelocker/strings.py b/gamelocker/strings.py deleted file mode 100644 index 6733005..0000000 --- a/gamelocker/strings.py +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# pylint: disable=invalid-name,missing-docstring -""" -gamelocker.strings - -(internal) A collection of mappings between strings returned by the API -and their common names. -Currently Vainglory-specific only. -""" - - -# TODO -class VaingloryStrings(object): - 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" - } - - 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": "Aegis", - "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(string): - """Returns prettified hero or item or original string.""" - if string in VaingloryStrings.heroes: - return VaingloryStrings.heroes[string] - if string in VaingloryStrings.items: - return VaingloryStrings.items[string] - if string in VaingloryStrings.item_ids: - return VaingloryStrings.item_ids[string] - return string diff --git a/tests/gamelocker_test.py b/tests/gamelocker_test.py index 9fce0a0..c7a27a9 100644 --- a/tests/gamelocker_test.py +++ b/tests/gamelocker_test.py @@ -1,16 +1,15 @@ #!/usr/bin/python +import os import pytest import requests import gamelocker import datetime class TestGamelocker: - apikey = "aaa.bbb.ccc" - @pytest.fixture def api(self): - return gamelocker.Gamelocker(self.apikey).Vainglory() + return gamelocker.Gamelocker(os.environ["GAMELOCKER_APIKEY"]).Vainglory() def test_req(self, api): with pytest.raises(requests.exceptions.HTTPError): @@ -22,44 +21,56 @@ class TestGamelocker: def test_map(self): assert gamelocker.datatypes.modulemap()["match"] is gamelocker.datatypes.Match - def test_strings(self, api): - assert gamelocker.strings.pretty("notexisting") == "notexisting" - assert gamelocker.strings.pretty("Boots2") == "Travel Boots" - assert gamelocker.strings.pretty("*1032_Item_TravelBoots*") == "Travel Boots" - assert gamelocker.strings.pretty("unknowntestitem") == "unknowntestitem" - - match = api.match("91cf2ee4-d7d0-11e6-ad79-062445d3d668") - assert isinstance(match.rosters[0].participants[0].actor, str) - - assert isinstance(match.rosters[0].stats["acesEarned"], int) - assert isinstance(match.rosters[0].participants[0].stats["items"][0], str) - def test_match(self, api): - match = api.match("91cf2ee4-d7d0-11e6-ad79-062445d3d668") - assert match.gameMode == "casual" + match = api.match("0955b904-fb19-11e6-802d-0667892d829e") + assert isinstance(match.gameMode, str) assert isinstance(match.rosters[0], gamelocker.datatypes.Roster) assert isinstance(match.rosters[0].participants[0], gamelocker.datatypes.Participant) assert isinstance(match.rosters[0].participants[0].player, gamelocker.datatypes.Player) assert isinstance(match.rosters[0].participants[0].player.name, str) + assert isinstance(match.rosters[0].participants[0].actor, str) + assert isinstance(match.rosters[0].stats["acesEarned"], int) + assert isinstance(match.rosters[0].participants[0].stats["items"][0], str) def test_matches(self, api): - matches = api.matches() + matches = api.matches(params={ + "filter[createdAt-start]": "2017-02-12T00:00:00Z", + "filter[playerNames]": "Kraken" + }) assert len(matches) > 0 assert isinstance(matches[0], gamelocker.datatypes.Match) assert matches[0].duration > 0 def test_region(self, api): assert len(api.matches(region="na", - params={"filter[playerNames]": "Kraken"})) > 0 + params={ + "filter[createdAt-start]": "2017-02-12T00:00:00Z", + "filter[playerNames]": "Kraken" + })) > 0 assert len(api.matches(region="eu", - params={"filter[playerNames]": "Bayu"})) > 0 + params={ + "filter[createdAt-start]": "2017-02-12T00:00:00Z", + "filter[playerNames]": "shutterfly" + })) > 0 assert len(api.matches(region="sg", - params={"filter[playerNames]": "idmonfish"})) > 0 + params={ + "filter[createdAt-start]": "2017-02-12T00:00:00Z", + "filter[playerNames]": "idmonfish" + })) > 0 def test_matchesfilters(self, api): - matches1 = api.matches({"page[limit]": 3}) + matches1 = api.matches({ + "filter[createdAt-start]": "2017-02-12T00:00:00Z", + "filter[playerNames]": "Kraken", + "page[limit]": 3 + }) assert len(matches1) == 3 - matches2 = api.matches({"page[limit]": 3, "page[offset]": 1}) + matches2 = api.matches({ + "filter[createdAt-start]": "2017-02-12T00:00:00Z", + "filter[playerNames]": "Kraken", + "page[limit]": 3, + "page[offset]": 1 + }) commons = 0 # 3 matches each, offset 1 -> 2 overlap for match1 in matches1: @@ -68,23 +79,31 @@ class TestGamelocker: commons += 1 assert commons == 2 - # broken on server side -# assert len(api.matches({"page[limit]": 42})) == 42 - -# matches = api.matches({"page[limit]": 10, "sort": "duration"}) -# assert matches[0].duration < matches[9].duration + assert len(api.matches(region="eu", params={ + "filter[createdAt-start]": "2017-02-12T00:00:00Z", + "filter[playerNames]": "shutterfly", + "page[limit]": 9 + })) == 9 def fromiso(s): return datetime.datetime.strptime(s, "%Y-%m-%dT%H:%M:%SZ") - start = "2017-01-10T02:25:00Z" - end = "2017-01-12T02:30:00Z" - matches = api.matches({"filter[createdAt-start]": start, "filter[createdAt-end]": end}) + start = "2017-02-20T02:25:00Z" + end = "2017-02-22T02:30:00Z" + matches = api.matches({ + "filter[playerNames]": "Kraken", + "filter[createdAt-start]": start, + "filter[createdAt-end]": end + }) for match in matches: assert fromiso(end) >= fromiso(match.createdAt) >= fromiso(start) nick = "MMotooks123" - matches = api.matches({"page[limit]": 5, "filter[playerNames]": nick}) + matches = api.matches({ + "filter[createdAt-start]": "2017-02-10T00:00:00Z", + "page[limit]": 5, + "filter[playerNames]": nick + }) for match in matches: success = False for roster in match.rosters: @@ -94,8 +113,12 @@ class TestGamelocker: break assert success - team = "HALO" - matches = api.matches({"page[limit]": 5, "filter[teamNames]": team}) + team = "3TB3" + matches = api.matches(region="na", params={ + "filter[createdAt-start]": "2017-02-10T00:00:00Z", + "page[limit]": 5, + "filter[teamNames]": team + }) for match in matches: success = False for roster in match.rosters: @@ -106,5 +129,5 @@ class TestGamelocker: assert success def test_player(self, api): - assert api.player("6abb30de-7cb8-11e4-8bd3-06eb725f8a76").name == "boombastic04" - assert "lossStreak" in api.player("6abb30de-7cb8-11e4-8bd3-06eb725f8a76").stats + assert api.player("57342aac-7ff5-11e5-98bf-0628b69bf6d1").name == "oldchoas" + assert "lossStreak" in api.player("57342aac-7ff5-11e5-98bf-0628b69bf6d1").stats |
