diff --git a/.gitignore b/.gitignore index f5b36e2..4d705f6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,10 @@ /venv /.pytest_cache /aerisweather.egg-info -/tests/keys.py /dist /AerisWeatherPythonDemo/.idea /AerisWeatherPythonDemo/venv -/AerisWeatherPythonDemo/keys.py \ No newline at end of file +/AerisWeatherPythonDemo/keys.py +__pycache__ +.tox +build/ diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..22c263a --- /dev/null +++ b/.python-version @@ -0,0 +1,4 @@ +3.6.12 +3.7.9 +3.8.7 +3.9.1 diff --git a/aerisweather/aerisweather.py b/aerisweather/aerisweather.py index 0656886..3fa967c 100644 --- a/aerisweather/aerisweather.py +++ b/aerisweather/aerisweather.py @@ -8,6 +8,7 @@ from aerisweather.requests.RequestQuery import RequestQuery from aerisweather.requests.RequestSort import RequestSort from aerisweather.responses.AlertsResponse import AlertsResponse +from aerisweather.responses.ConditionsResponse import ConditionsResponse from aerisweather.responses.CustomResponse import CustomResponse from aerisweather.responses.ForecastsResponse import ForecastsResponse from aerisweather.responses.ObservationsResponse import ObservationsResponse @@ -170,6 +171,8 @@ def response(endpoint_type: EndpointType, if endpoint_type == EndpointType.ALERTS: return AlertsResponse(response_json) + elif endpoint_type == EndpointType.CONDITIONS: + return ConditionsResponse(response_json) elif endpoint_type == EndpointType.FORECASTS: return ForecastsResponse(response_json) elif endpoint_type == EndpointType.OBSERVATIONS: @@ -445,6 +448,38 @@ def alerts(self, return self.request(endpoint=endpoint) + def conditions(self, + location: RequestLocation = None, + action: RequestAction = None, + filter_: [RequestFilter] = None, + sort: RequestSort = None, + params: Dict[ParameterType, str] = None, + query: Dict[RequestQuery, str] = None): + """ Performs an API request to get conditions data for a specified location. + + Params: + - location: Optional - RequestLocation - the location for which the request is processed + - action: Optional - RequestAction - the API request action option + - filter_: Optional - [RequestFilter] - a list of API request filters + - sort: Optional - RequestSort - the API request sort option + - params: Optional - Dict[ParameterType, str] - a list of API request parameters + - query: Optional - Dict[RequestQuery, str] - a list of API request quesries + + Returns: + - a list of ConditionsResponse objects if successful + - an empty list if there is no data + """ + + endpoint = Endpoint(endpoint_type=EndpointType.CONDITIONS, + location=location, + action=action, + filter_=filter_, + sort=sort, + params=params, + query=query) + + return self.request(endpoint=endpoint) + def forecasts(self, location: RequestLocation = None, action: RequestAction = None, diff --git a/aerisweather/requests/Endpoint.py b/aerisweather/requests/Endpoint.py index dbced2e..0a0abdc 100644 --- a/aerisweather/requests/Endpoint.py +++ b/aerisweather/requests/Endpoint.py @@ -26,6 +26,7 @@ class EndpointType(Enum): """ ALERTS = "advisories" + CONDITIONS = "conditions" CONVECTIVE_OUTLOOK = "convective/outlook" FORECASTS = "forecasts" OBSERVATIONS = "observations" diff --git a/aerisweather/requests/ParameterType.py b/aerisweather/requests/ParameterType.py index 4095d76..06a6c5b 100644 --- a/aerisweather/requests/ParameterType.py +++ b/aerisweather/requests/ParameterType.py @@ -25,6 +25,16 @@ class ALERTS(Enum): SKIP = "skip" SORT = "sort" + @skip + class CONDITIONS(Enum): + P = "p" + FOR = "for" + FROM = "from" + TO = "to" + PLIMIT = "plimit" + PSKIP = "pskip" + PSORT = "psort" + @skip class FORECASTS(Enum): CALLBACK = "callback" diff --git a/aerisweather/requests/RequestAction.py b/aerisweather/requests/RequestAction.py index fcd3b41..d37b7b9 100644 --- a/aerisweather/requests/RequestAction.py +++ b/aerisweather/requests/RequestAction.py @@ -19,6 +19,12 @@ class ALERTS(Enum): SEARCH = "search" WITHIN = "within" + @skip + class CONDITIONS(Enum): + ID = "id" + CLOSEST = "closest" + ROUTE = "route" + @skip class CONVECTIVE_OUTLOOK(Enum): AFFECTS = "affects" diff --git a/aerisweather/responses/AerisProfile.py b/aerisweather/responses/AerisProfile.py index 5d17c66..2226edc 100644 --- a/aerisweather/responses/AerisProfile.py +++ b/aerisweather/responses/AerisProfile.py @@ -67,6 +67,16 @@ def neighbors(self) -> List[str]: return neighbors +class AerisProfileConditions(AerisProfile): + """Defines an object for the Aeris API profile data returned in an Aeris API Conditions responses""" + + def __init__(self, json_data): + """ Constructor """ + + self.data = json_data + super().__init__(self.data) + + class AerisProfileFires(AerisProfile): """Defines an object for the Aeris API profile data returned in an Aeris API Fires responses""" diff --git a/aerisweather/responses/ConditionsPeriod.py b/aerisweather/responses/ConditionsPeriod.py new file mode 100644 index 0000000..f0117b9 --- /dev/null +++ b/aerisweather/responses/ConditionsPeriod.py @@ -0,0 +1,189 @@ +from typing import Optional + + +class ConditionsPeriod: + """ + Object describing a period from the conditions endpoint. + """ + # A conditions endpoint period takes the form: + # + # { + # "timestamp": 1612215840, + # "dateTimeISO": "2021-02-01T16:44:00-05:00", + # "tempC": 2.58, + # "tempF": 36.64, + # "feelslikeC": -1.26, + # "feelslikeF": 29.74, + # "dewpointC": -3.26, + # "dewpointF": 26.13, + # "humidity": 62, + # "pressureMB": 1014.5, + # "pressureIN": 29.96, + # "windDir": "NW", + # "windDirDEG": 320, + # "windSpeedKTS": 17.95, + # "windSpeedKPH": 33.24, + # "windSpeedMPH": 20.66, + # "windGustKTS": 26.9, + # "windGustKPH": 49.82, + # "windGustMPH": 30.96, + # "precipMM": 0, + # "precipIN": 0, + # "snowCM": 0, + # "snowIN": 0, + # "visibilityKM": 19.911, + # "visibilityMI": 12.372, + # "sky": 95, + # "cloudsCoded": "OV", + # "weather": "Cloudy", + # "weatherCoded": "::OV", + # "weatherPrimary": "Cloudy", + # "weatherPrimaryCoded": "::OV", + # "icon": "cloudy.png", + # "solradWM2": 83, + # "uvi": 0, + # "isDay": true + # } + + def __init__(self, data) -> None: + self.data = data + + @property + def timestamp(self) -> int: + return self.data["timestamp"] + + @property + def dateTimeISO(self) -> str: + return self.data["dateTimeISO"] + + @property + def tempC(self) -> Optional[float]: + return self.data["tempC"] + + @property + def tempF(self) -> Optional[float]: + return self.data["tempF"] + + @property + def feelslikeC(self) -> Optional[float]: + return self.data["feelsLikeC"] + + @property + def feelslikeF(self) -> Optional[float]: + return self.data["feelsLikeF"] + + @property + def dewpointC(self) -> Optional[float]: + return self.data["dewpointC"] + + @property + def dewpointF(self) -> Optional[float]: + return self.data["dewpointF"] + + @property + def humidity(self) -> Optional[int]: + return self.data["humidity"] + + @property + def pressureMB(self) -> Optional[float]: + return self.data["pressureMB"] + + @property + def pressureIN(self) -> Optional[float]: + return self.data["pressureIN"] + + @property + def windDir(self) -> Optional[str]: + return self.data["windDir"] + + @property + def windDirDEG(self) -> Optional[int]: + return self.data["windDirDEG"] + + @property + def windSpeedKTS(self) -> Optional[float]: + return self.data["windSpeedKTS"] + + @property + def windSpeedKPH(self) -> Optional[float]: + return self.data["windSpeedKPH"] + + @property + def windSpeedMPH(self) -> Optional[float]: + return self.data["windSpeedMPH"] + + @property + def windGustKTS(self) -> Optional[float]: + return self.data["windGustKTS"] + + @property + def windGustKPH(self) -> Optional[float]: + return self.data["windGustKPH"] + + @property + def windGustMPH(self) -> Optional[float]: + return self.data["windGustMPH"] + + @property + def precipMM(self) -> Optional[int]: + return self.data["precipMM"] + + @property + def precipIN(self) -> Optional[float]: + return self.data["precipIN"] + + @property + def snowCM(self) -> Optional[float]: + return self.data["snowCM"] + + @property + def snowIN(self) -> Optional[float]: + return self.data["snowIN"] + + @property + def visibilityKM(self) -> Optional[float]: + return self.data["visibilityKM"] + + @property + def visibilityMI(self) -> Optional[float]: + return self.data["visibilityMI"] + + @property + def sky(self) -> Optional[float]: + return self.data["sky"] + + @property + def cloudsCoded(self) -> Optional[str]: + return self.data["cloudsCoded"] + + @property + def weather(self) -> Optional[str]: + return self.data["weather"] + + @property + def weatherCoded(self) -> Optional[str]: + return self.data["weatherCoded"] + + @property + def weatherPrimary(self) -> Optional[str]: + return self.data["weatherPrimary"] + + @property + def weatherPrimaryCoded(self) -> Optional[str]: + return self.data["weatherPrimaryCoded"] + + @property + def icon(self) -> Optional[str]: + return self.data["icon"] + + @property + def solradWM2(self) -> Optional[int]: + return self.data["solradWM2"] + + @property + def uvi(self) -> Optional[float]: + return self.data["uvi"] + + @property + def isDay(self) -> bool: + return self.data["isDay"] diff --git a/aerisweather/responses/ConditionsResponse.py b/aerisweather/responses/ConditionsResponse.py new file mode 100644 index 0000000..9da8851 --- /dev/null +++ b/aerisweather/responses/ConditionsResponse.py @@ -0,0 +1,99 @@ +from typing import List + +from aerisweather.responses.AerisProfile import AerisProfileConditions +from aerisweather.responses.ConditionsPeriod import ConditionsPeriod +from aerisweather.responses.Response import Response + + +class ConditionsResponse(Response): + """ + Defines the object that stores conditions for a location. + """ + + # URL / response body for conditions follows: + # + # https://api.aerisapi.com/conditions?p=33.953,-84.55&client_id=$AERIS_CLIENT_ID&client_secret=$AERIS_CLIENT_SECRET + # + # + # { + # "success": true, + # "error": null, + # "response": [ + # { + # "loc": { + # "lat": 33.953, + # "long": -84.55 + # }, + # "place": { + # "name": "marietta", + # "state": "ga", + # "country": "us" + # }, + # "periods": [ + # { + # "timestamp": 1612215840, + # "dateTimeISO": "2021-02-01T16:44:00-05:00", + # "tempC": 2.58, + # "tempF": 36.64, + # "feelslikeC": -1.26, + # "feelslikeF": 29.74, + # "dewpointC": -3.26, + # "dewpointF": 26.13, + # "humidity": 62, + # "pressureMB": 1014.5, + # "pressureIN": 29.96, + # "windDir": "NW", + # "windDirDEG": 320, + # "windSpeedKTS": 17.95, + # "windSpeedKPH": 33.24, + # "windSpeedMPH": 20.66, + # "windGustKTS": 26.9, + # "windGustKPH": 49.82, + # "windGustMPH": 30.96, + # "precipMM": 0, + # "precipIN": 0, + # "snowCM": 0, + # "snowIN": 0, + # "visibilityKM": 19.911, + # "visibilityMI": 12.372, + # "sky": 95, + # "cloudsCoded": "OV", + # "weather": "Cloudy", + # "weatherCoded": "::OV", + # "weatherPrimary": "Cloudy", + # "weatherPrimaryCoded": "::OV", + # "icon": "cloudy.png", + # "solradWM2": 83, + # "uvi": 0, + # "isDay": true + # } + # ], + # "profile": { + # "tz": "America/New_York", + # "tzname": "EST", + # "tzoffset": -18000, + # "isDST": false, + # "elevFT": null, + # "elevM": null + # } + # } + # ] + # } + + def __init__(self, json_data): + super().__init__(json_data=json_data) + profile_data = self.data.get("profile", None) + if profile_data is not None: + self._profile = AerisProfileConditions(profile_data) + + period_data = self.data.get("periods", None) + if period_data is not None: + self._periods = [ConditionsPeriod(d) for d in period_data] + + @property + def profile(self) -> AerisProfileConditions: + return self._profile + + @property + def periods(self) -> List[ConditionsPeriod]: + return self._periods.copy() diff --git a/aerisweather/responses/ForecastPeriod.py b/aerisweather/responses/ForecastPeriod.py index d7195b5..5867250 100644 --- a/aerisweather/responses/ForecastPeriod.py +++ b/aerisweather/responses/ForecastPeriod.py @@ -473,6 +473,21 @@ def isDay(self) -> bool: else: return True + @property + def solradWM2(self) -> float: + """ Solar radiation over the forecast period, described in watts per square meter. """ + return self.period["solradWM2"] + + @property + def solradMinWM2(self) -> float: + """ Minimum expected solar radiation over the forecast period, described in watts per square meter. """ + return self.period["solradMinWM2"] + + @property + def solradMaxWM2(self) -> float: + """ Maximum expected solar radiation over the forecast period, described in watts per square meter. """ + return self.period["solradMaxWM2"] + @property def sunrise(self): """ Sunrise time as a UNIX timestamp. Provided when using filter=day (default) or filter=daynight. diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..bf31c7a --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,11 @@ +appdirs==1.4.4 +distlib==0.3.1 +filelock==3.0.12 +packaging==20.9 +pluggy==0.13.1 +py==1.10.0 +pyparsing==2.4.7 +six==1.15.0 +toml==0.10.2 +tox==3.21.3 +virtualenv==20.4.2 diff --git a/setup.py b/setup.py index a56b5de..4478dbe 100644 --- a/setup.py +++ b/setup.py @@ -9,13 +9,13 @@ version=__version__, packages=find_packages(exclude=['tests']), - install_requires=['aenum>=2.1.0'], + install_requires=['aenum>=3.0.0'], url='https://aerisweather.com', license='MIT', - author='sshie', - author_email='sshie@aerisweather.com', + author='AerisWeather', + author_email='pypi@aerisweather.com', description='The AerisWeather Python SDK allows a developer to quickly and easily add weather content and functionality to their Python applications.', long_description=open('README.rst').read() diff --git a/tests/keys.py b/tests/keys.py index 0e3a4dd..220b51f 100644 --- a/tests/keys.py +++ b/tests/keys.py @@ -1,13 +1,14 @@ """ -Replace the placeholders below with valid credentials from an active AerisWeather API account. - Don't have an active AerisWeather API client account? Accessing the API data requires an active AerisWeather API subscription, and registration of your application or namespace. You can sign up for a free developer account at the https://www.aerisweather.com/signup/ to get your client ID and secret. """ +import os + app_id = "com.aerisweather.pythonsdkdemo" -client_id = "" -client_secret = "" + +client_id = os.environ["AERIS_CLIENT_ID"] +client_secret = os.environ["AERIS_CLIENT_SECRET"] diff --git a/tests/responses/forecasts.txt b/tests/responses/forecasts.txt index 6074d36..997f021 100644 --- a/tests/responses/forecasts.txt +++ b/tests/responses/forecasts.txt @@ -31,6 +31,9 @@ "minHumidity": 75, "humidity": 74, "uvi": 5, + "solradWM2": 3424, + "solradMinWM2": 221, + "solradMaxWM2": 320, "pressureMB": 1001, "pressureIN": 29.56, "sky": 78, @@ -133,6 +136,9 @@ "minHumidity": 60, "humidity": 51, "uvi": 5, + "solradWM2": 3424, + "solradMinWM2": 221, + "solradMaxWM2": 320, "pressureMB": 1014, "pressureIN": 29.94, "sky": 38, @@ -210,4 +216,4 @@ } } ] -} \ No newline at end of file +} diff --git a/tests/test_alerts.py b/tests/test_alerts.py index 61eac1b..d7f9d70 100644 --- a/tests/test_alerts.py +++ b/tests/test_alerts.py @@ -1,5 +1,6 @@ import json +import os import unittest from urllib.error import URLError @@ -16,6 +17,8 @@ from aerisweather.utils.AerisError import AerisError from tests.keys import client_id, client_secret, app_id +script_dir = os.path.dirname(__file__) + class TestAlerts(unittest.TestCase): """ Defines tests modules for the Aeris API Alerts/Advisories class """ @@ -23,7 +26,7 @@ class TestAlerts(unittest.TestCase): def test_static_data(self): """ Test the code against a known source of data """ - file = open("./responses/alerts.txt", "r") + file = open(os.path.join(script_dir, "responses/alerts.txt"), "r") try: json_obj = json.loads(file.read()) diff --git a/tests/test_custom_endpoint.py b/tests/test_custom_endpoint.py deleted file mode 100644 index 4af09da..0000000 --- a/tests/test_custom_endpoint.py +++ /dev/null @@ -1,130 +0,0 @@ -import logging -import unittest -from urllib.error import URLError - -from aerisweather.aerisweather import AerisWeather -from aerisweather.requests.Endpoint import Endpoint, EndpointType -from aerisweather.requests.RequestLocation import RequestLocation -from aerisweather.responses.AerisProfile import AerisProfileRiversGauges -from aerisweather.responses.CustomResponse import CustomResponse -from aerisweather.responses.ForecastPeriod import ForecastPeriod -from aerisweather.responses.ForecastsResponse import ForecastsResponse -from aerisweather.responses.RiversCrests import RiversCrests -from aerisweather.responses.RiversCrestsRecent import RiversCrestsRecent -from aerisweather.utils.AerisError import AerisError -from tests.keys import client_id, client_secret, app_id - - -class TestCustomEndpoint(unittest.TestCase): - """ Defines tests modules for custom or unknown endpoints and their attributes """ - - def test_api_response(self): - """ Test against a live response from the API """ - - try: - awx = AerisWeather(app_id=app_id, - client_id=client_id, - client_secret=client_secret) - - # Valid endpoint, not in our Endpoint Enum - run this to test a beta or pre-release endpoint - EndpointType.custom = "stormreports" - endpt = Endpoint(EndpointType.CUSTOM, location=RequestLocation(postal_code="54660")) - try: - resp_list = awx.request(endpt) - - if len(resp_list) > 0: - response = resp_list[0] - assert type(response) is CustomResponse - else: - print(EndpointType.custom + ": no data") - except AerisError as ae_ex: - logging.basicConfig(level=logging.ERROR) - logger = logging.getLogger(' stormreports endpoint test ') - logger.error(str(ae_ex)) - except Exception as ex: - logging.basicConfig(level=logging.ERROR) - logger = logging.getLogger(' stormreports endpoint test ') - logger.error(str(ex)) - - # You can also use the custom endpoint type to request data from a known valid endpoint, for cases - # where new API data fields have not yet been added to an endpoint's response class. - EndpointType.custom = "forecasts" - f_list = awx.request(endpoint=Endpoint(endpoint_type=EndpointType.CUSTOM, - location=RequestLocation(postal_code="54660"))) - - forecast = f_list[0] - assert type(forecast) is CustomResponse - assert len(forecast.periods) > 0 - period = forecast.periods[0] # type: ForecastPeriod - assert period.weather is not None - - # Another example, with lots of nested lists, etc. - # rivers/gauges - EndpointType.custom = "rivers/gauges" - rg_list = awx.request(endpoint=Endpoint(endpoint_type=EndpointType.CUSTOM, - location=RequestLocation(postal_code="57101"))) - - if len(rg_list) > 0: - rg = rg_list[0] - assert type(rg) is CustomResponse - profile = rg.profile # type: AerisProfileRiversGauges - crests = profile.crests # type: RiversCrests - recent = crests.recent # type: [RiversCrestsRecent] - assert len(recent) > 0 - assert recent[0].heightFT is not None - else: - print(str(EndpointType.custom) + ": no data") - - # Unknown and invalid endpoint - EndpointType.custom = "bogus/endpoint" - print(str(EndpointType.custom) + ": expecting invalid request") - - invalid_list = awx.request(endpoint=Endpoint(endpoint_type=EndpointType.CUSTOM, - location=RequestLocation(postal_code="57101"))) - # the results of this call will be a thrown AerisError exception. - - except URLError as url_err: - print("URL Error: " + url_err.reason) - raise url_err - - except AerisError as aeris_err: - assert aeris_err.code == "invalid_request" - print("AerisError: " + "Level: " + aeris_err.level.value + " - " + str(aeris_err)) - # raise aeris_err - - except Exception as ex: - print(ex.args) - raise ex - - def test_custom_endpoint_method(self): - """ Test the AerisWeather.custom_endpoint method """ - - try: - awx = AerisWeather(app_id=app_id, - client_id=client_id, - client_secret=client_secret) - - EndpointType.custom = "forecasts" - f_list = awx.custom_endpoint(location=RequestLocation(postal_code="54660")) - - for forecast in f_list: - assert type(forecast) is CustomResponse - assert len(forecast.periods) > 0 - period = forecast.periods[0] # type: ForecastPeriod - assert period.weather is not None - - except URLError as url_err: - print("URL Error: " + url_err.reason) - raise url_err - - except AerisError as aeris_err: - print("AerisError: " + str(aeris_err)) - raise aeris_err - - except Exception as ex: - print(ex.args) - raise ex - - -suite = unittest.TestLoader().loadTestsFromTestCase(TestCustomEndpoint) -unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/tests/test_forecasts.py b/tests/test_forecasts.py index 962065c..1ba2264 100644 --- a/tests/test_forecasts.py +++ b/tests/test_forecasts.py @@ -1,5 +1,6 @@ import json +import os import unittest from urllib.error import URLError @@ -15,6 +16,8 @@ from aerisweather.utils.AerisError import AerisError from tests.keys import client_id, client_secret, app_id +script_dir = os.path.dirname(__file__) + class TestForecasts(unittest.TestCase): """ Defines tests modules for the Aeris API Forecasts class """ @@ -22,7 +25,7 @@ class TestForecasts(unittest.TestCase): def test_static_data(self): """ Test the Forecasts code against a known source of data """ - file = open("./responses/forecasts.txt", "r") + file = open(os.path.join(script_dir, "responses/forecasts.txt"), "r") try: json_obj = json.loads(file.read()) @@ -62,6 +65,9 @@ def test_static_data(self): assert p0.minHumidity == 75 assert p0.humidity == 74 assert p0.uvi == 5 + assert p0.solradWM2 == 3424 + assert p0.solradMinWM2 == 221 + assert p0.solradMaxWM2 == 320 assert p0.pressureMB == 1001 assert p0.pressureIN == 29.56 assert p0.sky == 78 diff --git a/tests/test_observations.py b/tests/test_observations.py index 302bdea..f8c39e5 100644 --- a/tests/test_observations.py +++ b/tests/test_observations.py @@ -1,5 +1,6 @@ import json +import os import unittest from urllib.error import URLError @@ -18,6 +19,8 @@ from aerisweather.utils.AerisError import AerisError from tests.keys import client_id, client_secret, app_id +script_dir = os.path.dirname(__file__) + class TestObservations(unittest.TestCase): """ Defines tests modules for the Aeris API Observation class """ @@ -25,7 +28,7 @@ class TestObservations(unittest.TestCase): def test_static_data(self): """ Test the Observation code against a known source of data """ - file = open("./responses/observations.txt", "r") + file = open(os.path.join(script_dir, "responses/observations.txt"), "r") try: json_obj = json.loads(file.read()) diff --git a/tests/test_observations_summary.py b/tests/test_observations_summary.py index bbbb344..859c838 100644 --- a/tests/test_observations_summary.py +++ b/tests/test_observations_summary.py @@ -1,5 +1,6 @@ import json +import os import unittest from urllib.error import URLError @@ -29,6 +30,8 @@ from aerisweather.utils.AerisError import AerisError from tests.keys import client_id, client_secret, app_id +script_dir = os.path.dirname(__file__) + class TestObservationsSummary(unittest.TestCase): """ Defines tests modules for the Aeris API ObservationsSummary class """ @@ -36,7 +39,7 @@ class TestObservationsSummary(unittest.TestCase): def test_static_data(self): """ Test the ObservationsSummary code against a known source of data """ - file = open("./responses/observations_summary.txt", "r") + file = open(os.path.join(script_dir, "responses/observations_summary.txt"), "r") try: json_obj = json.loads(file.read()) diff --git a/tests/test_places.py b/tests/test_places.py index 4b236cb..a3ef049 100644 --- a/tests/test_places.py +++ b/tests/test_places.py @@ -1,5 +1,6 @@ import json +import os import unittest from urllib.error import URLError @@ -16,13 +17,16 @@ from tests.keys import client_id, client_secret, app_id +script_dir = os.path.dirname(__file__) + + class TestPlaces(unittest.TestCase): """ Defines tests modules for the Aeris API Places class """ def test_static_data(self): """ Test the Places code against a known source of data """ - file = open("./responses/places.txt", "r") + file = open(os.path.join(script_dir, "responses/places.txt"), "r") try: json_obj = json.loads(file.read()) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..65076e3 --- /dev/null +++ b/tox.ini @@ -0,0 +1,9 @@ +[tox] +envlist = py36,py37,py38,py39 + +[testenv] +deps = ipython +setenv = + AERIS_CLIENT_ID = {env:AERIS_CLIENT_ID} + AERIS_CLIENT_SECRET = {env:AERIS_CLIENT_SECRET} +commands = {posargs:python -m unittest discover tests}