Parcourir la source

Improving api wrapper logic, add tox

JoostSijm il y a 4 ans
Parent
commit
26e2a6bc7f

+ 1 - 2
src/rival_regions_wrapper/__init__.py

@@ -8,5 +8,4 @@ of some Rival Regions functionalities.
 
 from .authentication_handler import AuthenticationHandler
 from .middleware import LocalAuthentication, RemoteAuthentication
-from .api_wrapper import Profile, Storage, Market, ResourceState, Perks, Craft, Overview, War, \
-    Work, Article
+from .api_wrapper import ApiWrapper

+ 17 - 28
src/rival_regions_wrapper/api_wrapper/__init__.py

@@ -1,33 +1,5 @@
 """API wrapper for Rival Regions"""
 
-import os
-
-from dotenv import load_dotenv
-
-from rival_regions_wrapper import RemoteAuthentication, LocalAuthentication
-
-
-load_dotenv()
-
-USERNAME = os.environ.get('USERNAME', None)
-PASSWORD = os.environ.get('PASSWORD', None)
-LOGIN_METHOD = os.environ.get('LOGIN_METHOD', None)
-
-API_URL = os.environ.get('API_URL', None)
-AUTHORIZATION = os.environ.get('AUTHORIZATION', None)
-
-class MissingEnvironError(Exception):
-    """Error for missing environ"""
-
-if None in (USERNAME, PASSWORD, LOGIN_METHOD):
-    raise MissingEnvironError(
-        'Load the following variables in your user environment: '
-        'username, password, login_method'
-    )
-
-# api
-# MIDDLEWARE = RemoteAuthentication(API_URL, AUTHORIZATION)
-MIDDLEWARE = LocalAuthentication(USERNAME, PASSWORD, LOGIN_METHOD)
 
 from .profile import Profile
 from .storage import Storage
@@ -39,3 +11,20 @@ from .overview import Overview
 from .war import War
 from .work import Work
 from .article import Article
+
+
+class ApiWrapper:
+    """API wrapper"""
+    authentication = None
+
+    def __init__(self, authentication):
+        """Initialize API wrapper with authentication"""
+        self.authentication = authentication
+
+    def get(self, path):
+        """Send get requests"""
+        return self.authentication.get(path)
+
+    def post(self, path, data=None):
+        """Send post request"""
+        return self.authentication.post(path, data=data)

+ 5 - 6
src/rival_regions_wrapper/api_wrapper/article.py

@@ -5,17 +5,16 @@ import re
 
 from bs4 import BeautifulSoup
 
-from . import MIDDLEWARE
-
 
 class Article(object):
-    """Wrapper class for profile"""
+    """Wrapper class for article"""
+    def __init__(self, api_wrapper):
+        self.api_wrapper = api_wrapper
 
-    @staticmethod
-    def info(article_id):
+    def info(self, article_id):
         """Get artcile"""
         path = 'news/show/{}'.format(article_id)
-        response = MIDDLEWARE.get(path)
+        response = self.api_wrapper.get(path)
         soup = BeautifulSoup(response, 'html.parser')
 
         links = soup.select('.newspaper_links')

+ 9 - 11
src/rival_regions_wrapper/api_wrapper/craft.py

@@ -1,18 +1,17 @@
-"""Profile class"""
+"""CRAFT class"""
 
 import re
 
 from bs4 import BeautifulSoup
 
-from . import MIDDLEWARE
 
-
-class Craft(object):
+class Craft():
     """Wrapper class for crafting"""
+    def __init__(self, api_wrapper):
+        self.api_wrapper = api_wrapper
 
-    @staticmethod
-    def info(item):
-        """Get profile"""
+    def info(self, item):
+        """Get craft"""
         keys = {
             'oil': 3,
             'ore': 4,
@@ -37,7 +36,7 @@ class Craft(object):
         if isinstance(item, str) and item in keys:
             item = keys[item]
         path = 'storage/produce/{}'.format(item)
-        response = MIDDLEWARE.get(path)
+        response = self.api_wrapper.get(path)
         soup = BeautifulSoup(response, 'html.parser')
         resources = soup.select_one('.storage_produce_exp')
         resource_dict = {
@@ -61,8 +60,7 @@ class Craft(object):
         }
         return craft
 
-    @staticmethod
-    def produce(item, amount):
+    def produce(self, item, amount):
         """Craft item"""
         keys = {
             'oil': 3,
@@ -87,6 +85,6 @@ class Craft(object):
         }
         if isinstance(item, str) and item in keys:
             item = keys[item]
-        MIDDLEWARE.post('storage/newproduce/{}/{}'.format(item, amount))
+        self.api_wrapper.post('storage/newproduce/{}/{}'.format(item, amount))
         return True
         

+ 5 - 6
src/rival_regions_wrapper/api_wrapper/market.py

@@ -4,14 +4,13 @@ import re
 
 from bs4 import BeautifulSoup
 
-from . import MIDDLEWARE
 
-
-class Market(object):
+class Market():
     """Wrapper class for profile"""
+    def __init__(self, api_wrapper):
+        self.api_wrapper = api_wrapper
 
-    @staticmethod
-    def info(resource):
+    def info(self, resource):
         """Get profile"""
         keys = {
             'oil': 3,
@@ -37,7 +36,7 @@ class Market(object):
         if isinstance(resource, str) and resource in keys:
             resource = keys[resource]
         path = 'storage/listed/{}'.format(resource)
-        response = MIDDLEWARE.get(path)
+        response = self.api_wrapper.get(path)
         soup = BeautifulSoup(response, 'html.parser')
 
         offers_tree = soup.find_all(class_='list_link')

+ 7 - 9
src/rival_regions_wrapper/api_wrapper/overview.py

@@ -6,17 +6,16 @@ from datetime import timedelta
 from bs4 import BeautifulSoup
 from dateutil import parser
 
-from . import MIDDLEWARE
 
-
-class Overview(object):
+class Overview():
     """Wrapper class for perks"""
+    def __init__(self, api_wrapper):
+        self.api_wrapper = api_wrapper
 
-    @staticmethod
-    def info():
+    def info(self):
         """Get perks"""
         path = 'main/content'
-        response = MIDDLEWARE.get(path)
+        response = self.api_wrapper.get(path)
         soup = BeautifulSoup(response, 'html.parser')
         perks = soup.select('.perk_source_4')
         upgrade_perk = None
@@ -53,11 +52,10 @@ class Overview(object):
         }
         return overview
 
-    @staticmethod
-    def status():
+    def status(self):
         """Get current status"""
         path = 'main'
-        response = MIDDLEWARE.get(path)
+        response = self.api_wrapper.get(path)
         soup = BeautifulSoup(response, 'html.parser')
         profile_url = soup.select_one('#header_my_avatar')['action']
         party_url = soup.select_one('#party_menu_members')['action']

+ 8 - 10
src/rival_regions_wrapper/api_wrapper/perks.py

@@ -1,4 +1,4 @@
-"""Profile class"""
+"""Perks class"""
 
 import re
 from datetime import timedelta
@@ -6,17 +6,16 @@ from datetime import timedelta
 from bs4 import BeautifulSoup
 from dateutil import parser
 
-from . import MIDDLEWARE
 
-
-class Perks(object):
+class Perks():
     """Wrapper class for perks"""
+    def __init__(self, api_wrapper):
+        self.api_wrapper = api_wrapper
 
-    @staticmethod
-    def info():
+    def info(self):
         """Get perks"""
         path = 'main/content'
-        response = MIDDLEWARE.get(path)
+        response = self.api_wrapper.get(path)
         soup = BeautifulSoup(response, 'html.parser')
         perks = soup.select('.perk_source_4')
         upgrade_perk = None
@@ -43,8 +42,7 @@ class Perks(object):
         }
         return perks
 
-    @staticmethod
-    def upgrade(perk, upgrade_type):
+    def upgrade(self, perk, upgrade_type):
         """Craft item"""
         perk_keys = {
             'strength': 1,
@@ -61,6 +59,6 @@ class Perks(object):
         if isinstance(upgrade_type, str) and upgrade_type in upgrade_type_keys:
             upgrade_type = upgrade_type_keys[upgrade_type]
 
-        MIDDLEWARE.post('perks/up/{}/{}'.format(perk, upgrade_type))
+        self.api_wrapper.post('perks/up/{}/{}'.format(perk, upgrade_type))
         return True
         

+ 4 - 5
src/rival_regions_wrapper/api_wrapper/profile.py

@@ -4,18 +4,17 @@ import re
 
 from bs4 import BeautifulSoup
 
-from . import MIDDLEWARE
 
-
-class Profile(object):
+class Profile():
     """Wrapper class for profile"""
-    def __init__(self, profile_id):
+    def __init__(self, api_wrapper, profile_id):
+        self.api_wrapper = api_wrapper
         self.profile_id = profile_id
 
     def info(self):
         """Get profile"""
         path = 'slide/profile/{}'.format(self.profile_id)
-        response = MIDDLEWARE.get(path)
+        response = self.api_wrapper.get(path)
         soup = BeautifulSoup(response, 'html.parser')
         level = soup.select_one('div.oil:nth-child(2) > div:nth-child(2)').text
         perks = soup.select('table tr:nth-child(2) span')

+ 10 - 10
src/rival_regions_wrapper/api_wrapper/resource_state.py

@@ -1,18 +1,18 @@
-"""Profile class"""
+"""Resource state class"""
 
 import re
 
 from bs4 import BeautifulSoup
 
-from . import MIDDLEWARE
 
+class ResourceState():
+    """Wrapper class for resource state"""
+    def __init__(self, api_wrapper, state_id):
+        self.api_wrapper = api_wrapper
+        self.state_id = state_id
 
-class ResourceState(object):
-    """Wrapper class for profile"""
-
-    @staticmethod
-    def info(state_id, resource):
-        """Get profile"""
+    def info(self, resource):
+        """Get resource state"""
         keys = {
             3: 'oil',
             4: 'ore',
@@ -21,8 +21,8 @@ class ResourceState(object):
         }
         if isinstance(resource, int) and resource in keys:
             resource = keys[resource]
-        path = 'listed/stateresources/{}/{}'.format(state_id, resource)
-        response = MIDDLEWARE.get(path)
+        path = 'listed/stateresources/{}/{}'.format(self.state_id, resource)
+        response = self.api_wrapper.get(path)
         soup = BeautifulSoup(response, 'html.parser')
         regions_tree = soup.find_all(class_='list_link')
         regions = []

+ 8 - 9
src/rival_regions_wrapper/api_wrapper/storage.py

@@ -1,20 +1,19 @@
-"""Profile class"""
+"""Storage class"""
 
 import re
 
 from bs4 import BeautifulSoup
 
-from . import MIDDLEWARE
 
+class Storage():
+    """Wrapper class for storage"""
+    def __init__(self, api_wrapper):
+        self.api_wrapper = api_wrapper
 
-class Storage(object):
-    """Wrapper class for profile"""
-
-    @staticmethod
-    def info():
-        """Get profile"""
+    def info(self):
+        """storage info"""
         path = 'storage'
-        response = MIDDLEWARE.get(path)
+        response = self.api_wrapper.get(path)
         soup = BeautifulSoup(response, 'html.parser')
         keys = {
             'oil': 3,

+ 9 - 11
src/rival_regions_wrapper/api_wrapper/war.py

@@ -1,21 +1,20 @@
-"""Profile class"""
+"""War class"""
 
 import re
 from datetime import datetime, timedelta
 
 from bs4 import BeautifulSoup
 
-from . import MIDDLEWARE
 
+class War():
+    """Wrapper class for war"""
+    def __init__(self, api_wrapper):
+        self.api_wrapper = api_wrapper
 
-class War(object):
-    """Wrapper class for profile"""
-
-    @staticmethod
-    def page():
+    def page(self):
         """Get training war"""
         path = 'war'
-        response = MIDDLEWARE.get(path)
+        response = self.api_wrapper.get(path)
         soup = BeautifulSoup(response, 'html.parser')
         pattern = re.compile(r'war\/details\/\d+')
         script = soup.find('script', text=pattern)
@@ -29,11 +28,10 @@ class War(object):
         }
         return page
 
-    @staticmethod
-    def info(war_id):
+    def info(self, war_id):
         """Get war info"""
         path = 'war/details/{}'.format(war_id)
-        response = MIDDLEWARE.get(path)
+        response = self.api_wrapper.get(path)
         soup = BeautifulSoup(response, 'html.parser')
         war_info = {
             'damage': int(soup.select_one('.war_w_target_o').text.replace('.', '')),

+ 6 - 7
src/rival_regions_wrapper/api_wrapper/work.py

@@ -1,11 +1,9 @@
-"""Profile class"""
+"""Work class"""
 
 import re
 
 from bs4 import BeautifulSoup
 
-from . import MIDDLEWARE
-
 
 RESOURCE_DICT = {
     'oil': 'oil',
@@ -16,13 +14,14 @@ RESOURCE_DICT = {
 }
 
 class Work(object):
-    """Wrapper class for profile"""
+    """Wrapper class for work"""
+    def __init__(self, api_wrapper):
+        self.api_wrapper = api_wrapper
 
-    @staticmethod
-    def page():
+    def page(self):
         """Get work page"""
         path = 'work'
-        response = MIDDLEWARE.get(path)
+        response = self.api_wrapper.get(path)
         soup = BeautifulSoup(response, 'html.parser')
 
         factory = soup.select_one('.work_item:nth-child(9)')

+ 2 - 2
src/rival_regions_wrapper/authentication_handler.py

@@ -9,7 +9,7 @@ import re
 import time
 from datetime import datetime
 import json
-import pathlib
+import pathlib2
 
 import requests
 import cfscrape
@@ -40,7 +40,7 @@ LOGGER.addHandler(STREAM_HANDLER)
 LOGGER.addHandler(FILE_HANDLER)
 
 DATA_DIR = user_data_dir('rival_regions_wrapper', 'bergc')
-pathlib.Path(DATA_DIR).mkdir(parents=True, exist_ok=True) 
+pathlib2.Path(DATA_DIR).mkdir(parents=True, exist_ok=True) 
 
 
 class RRClientException(Exception):

+ 29 - 0
tests/conftest.py

@@ -0,0 +1,29 @@
+"""Test configuration"""
+
+import os
+
+import pytest
+from dotenv import load_dotenv
+
+from rival_regions_wrapper import RemoteAuthentication, LocalAuthentication, ApiWrapper
+
+
+load_dotenv()
+
+class MissingAuthenticationError(Exception):
+    """Error for missing authentication"""
+
+
+@pytest.fixture(scope="module")
+def api_wrapper():
+    """Set up wrapper before test"""
+    username = os.environ.get('USERNAME', None)
+    password = os.environ.get('PASSWORD', None)
+    login_method = os.environ.get('LOGIN_METHOD', None)
+    if None in (username, password, login_method):
+        raise MissingAuthenticationError(
+            'Load the following variables in your user environment: '
+            'username, password, login_method'
+        )
+    authentication = LocalAuthentication(username, password, login_method)
+    return ApiWrapper(authentication)

+ 28 - 27
tests/test_rival_regions_wrapper.py

@@ -14,9 +14,9 @@ def profile_keys():
     return ['profile_id', 'name', 'level', 'level_percentage', 'strenght', 'education', 'endurance']
 
 @pytest.mark.vcr()
-def test_profile_info(profile_keys):
+def test_profile_info(api_wrapper, profile_keys):
     """Test an API call to get client info"""
-    profile_instance = Profile(192852686)
+    profile_instance = Profile(api_wrapper, 192852686)
     response = profile_instance.info()
 
     assert isinstance(response, dict), "The response should be a dict"
@@ -44,9 +44,9 @@ def storage_keys():
     ]
 
 @pytest.mark.vcr()
-def test_storage_info(storage_keys):
+def test_storage_info(api_wrapper, storage_keys):
     """Test an API call to get storage info"""
-    response = Storage.info()
+    response = Storage(api_wrapper).info()
 
     assert isinstance(response, dict), "The response should be a dict"
     assert set(storage_keys).issubset(response.keys()), "All keys should be in the response"
@@ -57,10 +57,10 @@ def market_keys():
     return ['player_id', 'player_name', 'price', 'amount']
 
 @pytest.mark.vcr()
-def test_market_info(market_keys):
+def test_market_info(api_wrapper, market_keys):
     """Test an API call to get market info"""
     resource = 'oil'
-    response = Market.info(resource)
+    response = Market(api_wrapper).info(resource)
 
     assert isinstance(response, list), "The response should be a list"
     if response:
@@ -77,11 +77,11 @@ def resource_keys():
     return ['region_id', 'region_name', 'explored', 'maximum', 'deep_exploration', 'limit_left']
 
 @pytest.mark.vcr()
-def test_resource_state_info(resource_keys):
+def test_resource_state_info(api_wrapper, resource_keys):
     """Test an API call to get market info"""
     state = 3382
     resource = 'oil'
-    response = ResourceState.info(state, resource)
+    response = ResourceState(api_wrapper, state).info(resource)
 
     assert isinstance(response, list), "The response should be a list"
     if response:
@@ -100,9 +100,9 @@ def perks_keys():
     return ['strenght', 'education', 'endurance', 'upgrade_date', 'upgrade_perk']
 
 @pytest.mark.vcr()
-def test_perks_info(perks_keys):
+def test_perks_info(api_wrapper, perks_keys):
     """Test an API call to get perks info"""
-    response = Perks.info()
+    response = Perks(api_wrapper).info()
 
     assert isinstance(response, dict), "The response should be a dict"
     assert set(perks_keys).issubset(response.keys()), "All keys should be in the response"
@@ -113,7 +113,7 @@ def test_perks_info(perks_keys):
     assert isinstance(response['upgrade_perk'], int), "upgrade_perk should be an int"
 
 @pytest.mark.skip(reason="Update request")
-def test_perks_upgrade():
+def test_perks_upgrade(api_wrapper):
     """Test an API call to upgrade perk"""
     perk = 'endurance'
     upgrade_type = 'money'
@@ -124,11 +124,11 @@ def craft_keys():
     """Standard keys for craft"""
     return ['market_price', 'resources']
 
-@pytest.mark.vcr()
-def test_craft_produce():
+@pytest.mark.skip(reason="Update request")
+def test_craft_produce(api_wrapper):
     """Test an API call to produce new item"""
     item = 'energy_drink'
-    Craft.produce(item, 10)
+    Craft(api_wrapper).produce(item, 10)
 
     assert True
 
@@ -138,9 +138,9 @@ def overview_info_keys():
     return ['perks', 'war']
 
 @pytest.mark.vcr()
-def test_overview_info(overview_info_keys):
+def test_overview_info(api_wrapper, overview_info_keys):
     """Test an API call for overview"""
-    response = Overview.info()
+    response = Overview(api_wrapper).info()
 
     assert isinstance(response, dict), "The response hould be a dict"
     assert set(overview_info_keys).issubset(response.keys()), "All keys should be in the response"
@@ -152,27 +152,28 @@ def overview_status_keys():
     return ['profile_id', 'party_id', 'gold', 'money', 'level', 'exp']
 
 @pytest.mark.vcr()
-def test_overview_status(overview_status_keys):
+def test_overview_status(api_wrapper, overview_status_keys):
     """Test an API cal for status"""
-    response = Overview.status()
+    response = Overview(api_wrapper).status()
 
     assert isinstance(response, dict), "The response hould be a dict"
     assert set(overview_status_keys).issubset(response.keys()), "All keys should be in the response"
 
 @pytest.mark.vcr()
-def test_war_page():
+def test_war_page(api_wrapper):
     """Test getting training war"""
-    response = War.page()
+    response = War(api_wrapper).page()
 
     assert isinstance(response, dict), "The response should be a dict"
     assert isinstance(response['training_war'], int), "The training_war should be an int"
 
 @pytest.mark.vcr()
-def test_war_info():
+def test_war_info(api_wrapper):
     """Test war info"""
-    war_page = War.page()
+    war = War(api_wrapper)
+    war_page = war.page()
     war_id = war_page['training_war']
-    response = War.info(war_id)
+    response = war.info(war_id)
 
     assert isinstance(response, dict), "The response should be a dict"
     assert isinstance(response['damage'], int), "Damage should be an int"
@@ -189,9 +190,9 @@ def test_war_info():
     assert isinstance(response['war_units'], dict), "war units should be a dict"
 
 @pytest.mark.vcr()
-def test_work_info():
+def test_work_info(api_wrapper):
     """Test work info"""
-    response = Work.page()
+    response = Work(api_wrapper).page()
 
     assert isinstance(response, dict), "The response should be a dict"
     assert isinstance(response['factory'], dict), "Factory should be a dict"
@@ -199,10 +200,10 @@ def test_work_info():
     assert isinstance(response['work_exp'], dict), "Work exp should be a dict"
 
 @pytest.mark.vcr()
-def test_article_info():
+def test_article_info(api_wrapper):
     """Test article info"""
     article_id = 2708696
-    response = Article.info(article_id)
+    response = Article(api_wrapper).info(article_id)
 
     assert isinstance(response, dict), "The resonse should be a dict"
     assert isinstance(response['article_id'], int), "Article id should be an integer"

+ 15 - 0
tox.ini

@@ -0,0 +1,15 @@
+# tox (https://tox.readthedocs.io/) is a tool for running tests
+# in multiple virtualenvs. This configuration file will run the
+# test suite on all supported python versions. To use it, "pip install tox"
+# and then run "tox" from this directory.
+
+[tox]
+envlist = py27, py38
+
+[testenv]
+deps =
+    python-dotenv
+    pytest
+    pytest-vcr
+commands =
+    pytest