diff --git a/README.md b/README.md index ef9f9ebb..73947014 100644 --- a/README.md +++ b/README.md @@ -40,10 +40,6 @@ Get all Br cosmetics. - `language` [GameLanguage] (Optional) - Specify the language of the shop. Default is set to english ###### Returns Returns a list of `BrCosmetic` objects. -###### Raises -- `ServerOutage` when the servers are not available -- `RateLimted` when the rate limit has been hit -- `Unauthorized` when no or a wrong API key has been provided ___ @@ -56,12 +52,6 @@ Search one o multiple items by their id. - `language` [GameLanguage] (Optional) - Specify the language of the shop. Default is set to english ###### Returns Returns a list of `BrCosmetic` objects. -###### Raises -- `ServerOutage` if the servers are not available -- `RateLimted` when the rate limit has been hit -- `Unauthorized` when no or a wrong API key has been provided -- `NotFound` if the cosmetic with the given id wasn't found -- `MissingIDParameter` if no id was provided ___ @@ -74,11 +64,6 @@ Search all cosmetics which fit to the search parameters - `language` [GameLanguage] (Optional) - Specify the language of the shop. Default is set to english ###### Returns Returns a list of `BrCosmetic` objects. -###### Raises -- `ServerOutage` if the servers are not available -- `RateLimted` when the rate limit has been hit -- `Unauthorized` when no or a wrong API key has been provided -- `MissingSearchParameter` if no search parameter was provided ___ @@ -91,11 +76,6 @@ Search the first cosmetics which fit to the search parameters - `language` [GameLanguage] (Optional) - Specify the language of the shop. Default is set to english ###### Returns Returns a `BrCosmetic` objects. -###### Raises -- `ServerOutage` if the servers are not available -- `RateLimted` when the rate limit has been hit -- `Unauthorized` when no or a wrong API key has been provided -- `MissingIDParameter` if no id was provided ___ @@ -107,11 +87,7 @@ Get the latest Fortnite shop. ###### Parameters - `language` [GameLanguage] (Optional) - Specify the language of the shop. Default is set to english ###### Returns -Returns a `Shop` object. -###### Raises -- `Server Outage` if the servers are not available -- `RateLimted` when the rate limit has been hit -- `Unauthorized` when no or a wrong API key has been provided +Returns a `Shop` object. ___ @@ -124,10 +100,6 @@ Get the latest Fortnite news of all game modes. - `language` [GameLanguage] (Optional) - Specify the language of the shop. Default is set to english ###### Returns Returns a `News` object. -###### Raises -- `ServerOutage` if the servers are not available -- `RateLimted` when the rate limit has been hit -- `Unauthorized` when no or a wrong API key has been provided ___ @@ -141,10 +113,6 @@ Get the latest Fortnite news of a specified game mode. - `language` [GameLanguage] (Optional) - Specify the language of the shop. Default is set to english ###### Returns Returns a `GameModeNews` object. -###### Raises -- `ServerOutage` if the servers are not available -- `RateLimted` when the rate limit has been hit -- `Unauthorized` when no or a wrong API key has been provided ___ @@ -154,14 +122,9 @@ api.creator_code.fetch() ``` Get information about a creator code. ###### Parameters -- `creator_code` [str] - Specify a creator code. +- `slug` [str] - Specify a creator code. ###### Returns Returns a `CreatorCode` object. -###### Raises -- `ServerOutage` if the servers are not available -- `RateLimted` when the rate limit has been hit -- `Unauthorized` when no or a wrong API key has been provided -- `NotFound` if the creator code wasn't found ___ @@ -170,13 +133,9 @@ api.creator_code.exists() ``` Check if a creator code exists. ###### Parameters -- `creator_code` [str] - Specify a creator code. +- `slug` [str] - Specify a creator code. ###### Returns Returns a `bool` object. -###### Raises -- `ServerOutage` if the servers are not available -- `RateLimted` when the rate limit has been hit -- `Unauthorized` when no or a wrong API key has been provided ___ @@ -186,14 +145,9 @@ api.creator_code.search_all() ``` Search a creator code by name. All results are provided. ###### Parameters -- `creator_code` [str] - Specify a creator code. +- `slug` [str] - Specify a creator code. ###### Returns Returns a `list` of `CreatorCode` objects. -###### Raises -- `ServerOutage` if the servers are not available -- `RateLimted` when the rate limit has been hit -- `Unauthorized` when no or a wrong API key has been provided -- `NotFound` if the creator code wasn't found ___ @@ -203,14 +157,9 @@ api.creator_code.search_first() ``` Search a creator code by name. Only the first result is provided. ###### Parameters -- `creator_code` [str] - Specify a creator code. +- `slug` [str] - Specify a creator code. ###### Returns Returns a `CreatorCode` object. -###### Raises -- `ServerOutage` if the servers are not available -- `RateLimted` when the rate limit has been hit -- `Unauthorized` when no or a wrong API key has been provided -- `NotFound` if the creator code wasn't found ## Contribute Every type of contribution is appreciated! diff --git a/fortnite_api/__init__.py b/fortnite_api/__init__.py index 7648b86f..688aa1f8 100644 --- a/fortnite_api/__init__.py +++ b/fortnite_api/__init__.py @@ -1,4 +1,4 @@ -__version__ = '1.0.3' +__version__ = '1.0.4' from .api import FortniteAPI from .cosmetics import * diff --git a/fortnite_api/api.py b/fortnite_api/api.py index 3420bb0c..a9b7e791 100644 --- a/fortnite_api/api.py +++ b/fortnite_api/api.py @@ -4,9 +4,12 @@ class FortniteAPI: - def __init__(self, api_key, run_async=False): + def __init__(self, api_key: str, run_async: bool = False): + if type(api_key) is not str: + raise TypeError('api_key require a value of type {0}'.format(str(str))) self.http = SyncHTTPClient() if not run_async else AsyncHTTPClient() self.http.add_header('x-api-key', api_key) + self.cosmetics = SyncCosmeticsEndpoints(self) if not run_async else AsyncCosmeticsEndpoints(self) self.creator_code = SyncCreatorCodeEndpoints(self) if not run_async else AsyncCreatorCodeEndpoints(self) self.news = SyncNewsEndpoints(self) if not run_async else AsyncNewsEndpoints(self) diff --git a/fortnite_api/endpoints.py b/fortnite_api/endpoints.py index 58c6a8f3..15ea4760 100644 --- a/fortnite_api/endpoints.py +++ b/fortnite_api/endpoints.py @@ -1,55 +1,55 @@ import typing +from enum import Enum from .creator_code import CreatorCode from .enums import GameLanguage, MatchMethod, NewsType -from .errors import NotFound +from .errors import MissingSearchParameter, MissingIDParameter, NotFound from .cosmetics import BrCosmetic from .news import GameModeNews, News from .shop import BrShop +_SEARCH_PARAMETERS = { + 'language': [None, [GameLanguage]], + 'search_language': ['searchLanguage', [GameLanguage]], + 'match_method': ['matchMethod', [MatchMethod]], + 'type': [None, [str, None]], + 'backend_type': ['backendType', [str, None]], + 'rarity': [None, [str, None]], + 'display_rarity': ['displayRarity', [str, None]], + 'backend_rarity': ['backendRarity', [str, None]], + 'name': [None, [str, None]], + 'short_description': ['shortDescription', [str, None]], + 'description': [None, [str, None]], + 'set': ['set', [str, None]], + 'set_text': ['setText', [str, None]], + 'series': [None, [str, None]], + 'backend_series': ['backendSeries', [str, None]], + 'has_small_icon': ['hasSmallIcon', [bool, None]], + 'has_icon': ['hasIcon', [bool, None]], + 'has_featured_image': ['hasFeaturedImage', [bool, None]], + 'has_background_image': ['hasBackgroundImage', [bool, None]], + 'has_cover_art': ['hasCoverArt', [bool, None]], + 'has_decal': ['hasDecal', [bool, None]], + 'has_variants': ['hasVariants', [bool, None]], + 'has_gameplay_tags': ['hasGameplayTags', [bool, None]], + 'gameplay_tag': ['gameplayTag', [str, None]] +} + def _parse_search_parameter(**search_parameters): - parameters = { - 'language': search_parameters.get('language', GameLanguage.ENGLISH).value, - 'searchLanguage': search_parameters.get('search_language', GameLanguage.ENGLISH).value, - 'matchMethod': search_parameters.get('match_method', MatchMethod.FULL).value - } - if search_parameters.__contains__('type'): - parameters['type'] = search_parameters['type'] - if search_parameters.__contains__('backend_type'): - parameters['backendType'] = search_parameters['backend_type'] - if search_parameters.__contains__('rarity'): - parameters['rarity'] = search_parameters['rarity'] - if search_parameters.__contains__('backend_rarity'): - parameters['backendRarity'] = search_parameters['backend_rarity'] - if search_parameters.__contains__('name'): - parameters['name'] = search_parameters['name'] - if search_parameters.__contains__('short_description'): - parameters['shortDescription'] = search_parameters['short_description'] - if search_parameters.__contains__('description'): - parameters['description'] = search_parameters['description'] - if search_parameters.__contains__('set'): - parameters['set'] = search_parameters['set'] - if search_parameters.__contains__('series'): - parameters['series'] = search_parameters['series'] - if search_parameters.__contains__('has_small_icon'): - parameters['hasSmallIcon'] = search_parameters['has_small_icon'] - if search_parameters.__contains__('has_icon'): - parameters['hasIcon'] = search_parameters['has_icon'] - if search_parameters.__contains__('has_featured_image'): - parameters['hasFeaturedImage'] = search_parameters['has_featured_image'] - if search_parameters.__contains__('has_background_image'): - parameters['hasBackgroundImage'] = search_parameters['has_background_image'] - if search_parameters.__contains__('has_cover_art'): - parameters['hasCoverArt'] = search_parameters['has_cover_art'] - if search_parameters.__contains__('has_decal'): - parameters['hasDecal'] = search_parameters['has_decal'] - if search_parameters.__contains__('has_variants'): - parameters['hasVariants'] = search_parameters['has_variants'] - if search_parameters.__contains__('has_gameplay_tags'): - parameters['hasGameplayTags'] = search_parameters['has_gameplay_tags'] - if search_parameters.__contains__('gameplay_tag'): - parameters['gameplayTag'] = search_parameters['gameplay_tag'] + parameters = {} # TODO: Empty string as search parameter + for key, value in search_parameters.items(): + search_parameter_data = _SEARCH_PARAMETERS.get(key) + if search_parameter_data is None: + continue + if type(value) not in search_parameter_data[1]: + types = ' or '.join([str(t) for t in search_parameter_data[1]]) + raise TypeError('{0} require a value of type {1}'.format(key, types)) + key = search_parameter_data[0] if search_parameter_data[0] else key + value = value if not isinstance(value, Enum) else value.value if value is not None else '' + parameters[key] = str(value) if value is not None else '' + if len(parameters) == 0: + raise MissingSearchParameter('at least one search parameter is required') return parameters @@ -67,8 +67,9 @@ def search_by_id(self, *cosmetic_id: str, language: GameLanguage = GameLanguage. cosmetic_ids = list(cosmetic_id) params = {'language': language.value} - if len(cosmetic_ids) < 1: - return None + if len(cosmetic_ids) == 0: + raise MissingIDParameter('at least one cosmetic id is required') + endpoint = 'cosmetics/br/search/ids' endpoint += '?id=' + cosmetic_ids[0] del cosmetic_ids[0] @@ -101,8 +102,9 @@ async def search_by_id(self, *cosmetic_id: str, language: GameLanguage = GameLan cosmetic_ids = list(cosmetic_id) params = {'language': language.value} - if len(cosmetic_ids) < 1: - return None + if len(cosmetic_ids) == 0: + raise MissingIDParameter('at least one cosmetic id is required') + endpoint = 'cosmetics/br/search/ids' endpoint += '?id=' + cosmetic_ids[0] del cosmetic_ids[0] @@ -116,10 +118,7 @@ async def search_all(self, **search_parameters) -> typing.List[BrCosmetic]: return [BrCosmetic(item_data) for item_data in data['data']] async def search_first(self, **search_parameters) -> BrCosmetic: - data = await self._client.http.get('cosmetics/br/search', - params=_parse_search_parameter(**search_parameters)) - if data['status'] == 400: - raise NotFound('The requested cosmetic was not found.') + data = await self._client.http.get('cosmetics/br/search', params=_parse_search_parameter(**search_parameters)) return BrCosmetic(data['data']) @@ -128,34 +127,27 @@ class SyncCreatorCodeEndpoints: def __init__(self, client): self._client = client - def fetch(self, creator_code: str) -> CreatorCode: - params = {'slug': creator_code} + def fetch(self, slug: str) -> CreatorCode: + params = {'slug': slug} data = self._client.http.get('creatorcode', params=params) - if data['status'] == 400: - raise NotFound('The requested Creator Code was not found.') return CreatorCode(data['data']) - def exists(self, creator_code: str) -> bool: + def exists(self, slug: str) -> bool: try: - self.fetch(creator_code) + self.fetch(slug) return True except NotFound: return False - def search_first(self, creator_code: str) -> CreatorCode: - params = {'slug': creator_code} + def search_first(self, slug: str) -> CreatorCode: + params = {'slug': slug} data = self._client.http.get('creatorcode/search', params=params) - if data['status'] == 400: - raise NotFound('The requested Creator Code was not found.') return CreatorCode(data['data']) - def search_all(self, creator_code: str) -> typing.List[CreatorCode]: - params = {'slug': creator_code} + def search_all(self, slug: str) -> typing.List[CreatorCode]: + params = {'slug': slug} data = self._client.http.get('creatorcode/search/all', params=params) - creator_codes = [CreatorCode(creator_code_data) for creator_code_data in data['data']] - if len(creator_codes) == 0: - raise NotFound('The requested Creator Code was not found.') - return creator_codes + return [CreatorCode(creator_code_data) for creator_code_data in data['data']] class AsyncCreatorCodeEndpoints: @@ -163,34 +155,27 @@ class AsyncCreatorCodeEndpoints: def __init__(self, client): self._client = client - async def fetch(self, creator_code: str) -> CreatorCode: - params = {'slug': creator_code} + async def fetch(self, slug: str) -> CreatorCode: + params = {'slug': slug} data = await self._client.http.get('creatorcode', params=params) - if data['status'] == 400: - raise NotFound('The requested Creator Code was not found.') return CreatorCode(data['data']) - async def exists(self, creator_code: str) -> bool: + async def exists(self, slug: str) -> bool: try: - await self.fetch(creator_code) + await self.fetch(slug) return True except NotFound: return False - async def search_first(self, creator_code: str) -> CreatorCode: - params = {'slug': creator_code} + async def search_first(self, slug: str) -> CreatorCode: + params = {'slug': slug} data = await self._client.http.get('creatorcode/search', params=params) - if data['status'] == 400: - raise NotFound('The requested Creator Code was not found.') return CreatorCode(data['data']) - async def search_all(self, creator_code: str) -> typing.List[CreatorCode]: - params = {'slug': creator_code} + async def search_all(self, slug: str) -> typing.List[CreatorCode]: + params = {'slug': slug} data = await self._client.http.get('creatorcode/search/all', params=params) - creator_codes = [CreatorCode(creator_code_data) for creator_code_data in data['data']] - if len(creator_codes) == 0: - raise NotFound('The requested Creator Code was not found.') - return creator_codes + return [CreatorCode(creator_code_data) for creator_code_data in data['data']] class SyncNewsEndpoints: diff --git a/fortnite_api/errors.py b/fortnite_api/errors.py index cb4ce8ae..51b46e72 100644 --- a/fortnite_api/errors.py +++ b/fortnite_api/errors.py @@ -1,22 +1,26 @@ -class NotFound(Exception): # Add to search by ID +class FortniteAPIException(Exception): pass -class MissingSearchParameter(Exception): +class NotFound(FortniteAPIException): pass -class MissingIDParameter(Exception): +class MissingSearchParameter(FortniteAPIException): pass -class ServerOutage(Exception): +class MissingIDParameter(FortniteAPIException): pass -class RateLimited(Exception): +class ServiceUnavailable(FortniteAPIException): pass -class Unauthorized(Exception): +class RateLimited(FortniteAPIException): + pass + + +class Unauthorized(FortniteAPIException): pass diff --git a/fortnite_api/http.py b/fortnite_api/http.py index edd665f5..874d93e4 100644 --- a/fortnite_api/http.py +++ b/fortnite_api/http.py @@ -1,9 +1,7 @@ -from json import JSONDecodeError - import aiohttp import requests -from .errors import ServerOutage, RateLimited, Unauthorized, NotFound +from .errors import ServiceUnavailable, RateLimited, Unauthorized, NotFound BASE_URL = 'https://fortnite-api.com/' @@ -21,24 +19,26 @@ def remove_header(self, key): def get(self, endpoint, params=None): response = requests.get(BASE_URL + endpoint, params=params, headers=self.headers) - try: - data = response.json() - if response.status_code == 401: - raise Unauthorized(data.get('error', 'Error message not provided!')) - elif response.status_code == 404: - raise NotFound(data.get('error', 'Error message not provided!')) - elif response.status_code == 429: - raise RateLimited(data.get('error', 'Error message not provided!')) + data = response.json() + if response.status_code == 200: return data - except JSONDecodeError: - raise ServerOutage('The Fortnite-API.com server is currently unavailable.') + elif response.status_code == 401: + raise Unauthorized(data.get('error', 'Error message not provided!')) + elif response.status_code == 404: + raise NotFound(data.get('error', 'Error message not provided!')) + elif response.status_code == 429: + raise RateLimited(data.get('error', 'Error message not provided!')) + elif response.status_code == 503: + raise ServiceUnavailable(data.get('error', 'Error message not provided!')) + else: + raise Exception(data.get('error', 'Error message not provided') + '. Status Code: {0}' + .format(str(response.status_code))) class AsyncHTTPClient: def __init__(self): self.headers = {} - self.session = aiohttp.ClientSession() def add_header(self, key, val): self.headers[key] = val @@ -47,16 +47,19 @@ def remove_header(self, key): return self.headers.pop(key) async def get(self, endpoint, params=None): - async with self.session.get(BASE_URL + endpoint, params=params, headers=self.headers) as response: - try: + async with aiohttp.ClientSession() as session: + async with session.get(BASE_URL + endpoint, params=params, headers=self.headers) as response: data = await response.json() + if response.status == 200: + return data if response.status == 401: raise Unauthorized(data.get('error', 'Error message not provided!')) elif response.status == 404: raise NotFound(data.get('error', 'Error message not provided!')) elif response.status == 429: raise RateLimited(data.get('error', 'Error message not provided!')) - return data - except aiohttp.ContentTypeError: - raise ServerOutage('The Fortnite-API.com server is currently unavailable.') - + elif response.status == 503: + raise ServiceUnavailable(data.get('error', 'Error message not provided!')) + else: + raise Exception(data.get('error', 'Error message not provided') + '. Status Code: {0}' + .format(str(response.status))) diff --git a/setup.py b/setup.py index cdddf513..10194dc0 100644 --- a/setup.py +++ b/setup.py @@ -33,8 +33,8 @@ long_description_content_type="text/markdown", install_requires=['requests>=2.22.0', 'aiohttp>=3.3.0,<3.6.0'], python_requires='>=3.5.3', - download_url='https://github.com/Fortnite-API/py-wrapper/archive/v1.0.2.tar.gz', - keywords=['fortnite', 'fortnite-api.com', 'shop', 'cosmetics'], + download_url='https://github.com/Fortnite-API/py-wrapper/archive/v1.0.3.tar.gz', + keywords=['fortnite', 'fortnite-api.com', 'shop', 'cosmetics', 'fortnite api', 'fortnite shop'], classifiers=[ 'License :: OSI Approved :: MIT License', 'Intended Audience :: Developers', @@ -43,6 +43,7 @@ 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Libraries :: Python Modules', diff --git a/tests.py b/tests.py index c7c3441f..994c5821 100644 --- a/tests.py +++ b/tests.py @@ -1,8 +1,28 @@ +import asyncio + import fortnite_api -from fortnite_api import MatchMethod -fn = fortnite_api.FortniteAPI('api_key') +api_key = 'ac54ca08e4715a72da4ee30eaf9086ef0c89bcbe32e7cc23fd3dee0c1ec82e32' + + +def sync_test(): + fn = fortnite_api.FortniteAPI(api_key) + print(fn.shop.fetch()) + print([[i.name, i.type.value] for i in fn.cosmetics.search_all(name='Drift')]) + print(fn.cosmetics.search_first(name='Drift').id) + print(fn.creator_code.search_first('EasyFnStats').user.name) + +async def async_test(): + fn = fortnite_api.FortniteAPI(api_key, run_async=True) + print(await fn.shop.fetch()) + print([[i.name, i.type.value] for i in (await fn.cosmetics.search_all(name='Drift'))]) + print((await fn.cosmetics.search_first(name='Drift')).id) + print((await fn.creator_code.search_first('EasyFnStats')).user.name) if __name__ == '__main__': - print([e.name for e in fn.cosmetics.search_all(name='ome', match_method=MatchMethod.STARTS)]) + print('------ Sync Tests ------') + sync_test() + loop = asyncio.get_event_loop() + print('------ Async Tests ------') + loop.run_until_complete(async_test())