diff --git a/pyiss/__init__.py b/pyiss/__init__.py new file mode 100644 index 0000000..e42dd72 --- /dev/null +++ b/pyiss/__init__.py @@ -0,0 +1,154 @@ +from datetime import datetime +import requests +from voluptuous import Schema, Required, All, Any, Length, Range + + +class ISS: + """ + + """ + + API_URL = "http://api.open-notify.org/" + API_CURRENT_LOCATION = "iss-now.json" + API_PASS_TIMES = "iss-pass.json" + API_PEOPLE = "astros.json" + + def __init__(self): + return + + def people_in_space(self): + """ + :return: Return a dict with number of people in space right now and + their name and their craft + :rtype: dict + """ + data = requests.get(self.API_URL + self.API_PEOPLE) + + if data.status_code is 200: + return data.json() + else: + raise Exception("Error server n {}".format( + data.status_code)) + + def current_location(self): + """ + + :return: A dict with latitude and longitude of ISS + :rtype: dict + """ + data = requests.get(self.API_URL + self.API_CURRENT_LOCATION) + + if data.status_code is 200: + return data.json()['iss_position'] + else: + raise Exception("Error server n {}".format( + data.status_code)) + + def pass_times(self, latitude, longitude, altitude=None, number=None): + """ + + :param latitude: latitude in degrees of location you want iss pass + above + :type latitude: float + :param longitude: longitude in degrees of location you want iss pass + above + :type longitude: float + :param altitude: altitude in meters of location you want iss pass + above, default is 100 when not given + :type altitude: float + :param number: number of next pass above the location, default is 5 + if not given. Min is 1, max is 100 + :type number: int + :return: a list of the next pass of the ISS with the risetime and + the duration + :rtype: list + """ + + # Check input + schema = Schema({ + Required('lat'): All(Any(int, float), Range(min=-80, max=80)), + Required('long'): All(Any(int, float), Range(min=-180, max=180)), + 'alt': Any(None, All(Any(int, float), Range(min=0, max=10000))), + 'number': Any(None, All(int, Range(min=1, max=100))) + }) + schema({'lat' : latitude, 'long': longitude, 'alt' : altitude, 'number': number}) + + #Build request + payload = {'lat': latitude, 'lon': longitude} + + if altitude is not None: + payload['alt'] = altitude + + if number is not None: + payload['n'] = number + + data = requests.get(self.API_URL + self.API_PASS_TIMES, + params=payload) + + #Check error + if data.status_code is 200: + return data.json()['response'] + else: + raise Exception("Error server n {}".format( + data.status_code)) + + def number_of_people_in_space(self): + """ + :return: The number of people in space right now + :rtype: int + """ + return self.people_in_space()['number'] + + def next_rise(self, latitude, longitude, altitude=None): + """ + + :param latitude: latitude in degrees of location you want iss pass + above + :type latitude: float + :param longitude: longitude in degrees of location you want iss pass + above + :type longitude: float + :param altitude: altitude in meters of location you want iss pass + above, default is 100 when not given + :type altitude: float + :return: Return the next date when ISS will be over 10 degree above the + horizon + :rtype: datetime + """ + rise = self.pass_times(latitude, longitude, altitude, + 2) + timestamp = rise[0]['risetime'] + + return datetime.fromtimestamp(timestamp) + + def is_ISS_above(self, latitude, longitude, altitude=None): + """ + + :param latitude: latitude in degrees of location you want iss pass + above + :type latitude: float + :param longitude: longitude in degrees of location you want iss pass + above + :type longitude: float + :param altitude: altitude in meters of location you want iss pass + above, default is 100 when not given + :type altitude: float + :return: True if the ISS is above the location, False if not + :rtype: bool + """ + test = self.pass_times(latitude, longitude, altitude, 2) + # 2 results where asked so if API return only 1, that mean ISS is + # above the location + return len(test) is 1 + + +if __name__ == '__main__': + iss = ISS() + # print (type(iss.people_in_space())) + # print(iss.current_location()) + # print (type(iss.pass_times(5, 8))) + #print(iss.pass_times(1,1)) + # print (iss.number_of_people_in_space()) + # print (iss.seconds_before_next_rise(-50.2322, 76.5668)) + # print (iss.is_ISS_above(4,71,7 )) + print(iss.next_rise(2, 5, 6).timestamp()) diff --git a/pyiss/tests/__init__.py b/pyiss/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyiss/tests/test_current_location.py b/pyiss/tests/test_current_location.py new file mode 100644 index 0000000..1d8736a --- /dev/null +++ b/pyiss/tests/test_current_location.py @@ -0,0 +1,54 @@ +from unittest import TestCase +from httmock import all_requests, HTTMock, response +import pyiss + + +class TestCurrent_location(TestCase): + def setUp(self): + """ + + Instantiate the Http Request Mock, the ISS class call and the json response + + """ + + #Json response + self.json_current_location = {"timestamp": 1481410143, "message": "success", "iss_position": {"latitude": "6.8272", "longitude": "-160.2689"}} + + #HTTP Mock + @all_requests + def correct_response(url, request): + headers = {'content-type': 'application/json', + 'Set-Cookie': 'foo=bar;'} + return response(200, self.json_current_location, headers, None, 5, + request) + self.http_correct = correct_response + + @all_requests + def wrong_response(url, request): + headers = {'content-type': 'application/json', + 'Set-Cookie': 'foo=bar;'} + return response(403, self.json_current_location, headers, None, 5, + request) + self.http_wrong = wrong_response + + self.iss = pyiss.ISS() + + def test_current_location_json_return(self): + """ + + Test that the function return the right dict answer + + """ + with HTTMock(self.http_correct): + response = self.iss.current_location() + location = {"latitude": "6.8272", "longitude": "-160.2689"} + self.assertDictEqual(response, location) + + def test_current_location_error_server(self): + """ + + Test that the function raise an exception if the server response is not correct + + """ + with HTTMock(self.http_wrong): + self.assertRaises(Exception, self.iss.current_location ) diff --git a/pyiss/tests/test_is_ISS_above.py b/pyiss/tests/test_is_ISS_above.py new file mode 100644 index 0000000..423d71e --- /dev/null +++ b/pyiss/tests/test_is_ISS_above.py @@ -0,0 +1,133 @@ +from unittest import TestCase +from httmock import all_requests, HTTMock, response +import pyiss + + +class TestIs_ISS_above(TestCase): + def setUp(self): + """ + + Instantiate the Http Request Mock, the ISS class call and the json response + + """ + + # Json response + self.json_is_ISS_above_false = { + "message": "success", + "request": { + "altitude": 100, + "datetime": 1481418788, + "latitude": 15.0, + "longitude": 20.0, + "passes": 5 + }, + "response": [ + { + "duration": 348, + "risetime": 1481448840 + }, + { + "duration": 634, + "risetime": 1481454465 + } + ] + } + + self.json_is_ISS_above_true = { + "message": "success", + "request": { + "altitude": 100, + "datetime": 1481418788, + "latitude": 15.0, + "longitude": 20.0, + "passes": 5 + }, + "response": [ + { + "duration": 348, + "risetime": 1481448840 + } + ] + } + + # HTTP Mock + @all_requests + def true_response(url, request): + headers = {'content-type': 'application/json', + 'Set-Cookie': 'foo=bar;'} + return response(200, self.json_is_ISS_above_true, headers, None, 5, + request) + + self.http_true = true_response + + @all_requests + def false_response(url, request): + headers = {'content-type': 'application/json', + 'Set-Cookie': 'foo=bar;'} + return response(200, self.json_is_ISS_above_false, headers, None, + 5, + request) + + self.http_false = false_response + + @all_requests + def wrong_response(url, request): + headers = {'content-type': 'application/json', + 'Set-Cookie': 'foo=bar;'} + return response(403, self.json_is_ISS_above, headers, None, 5, + request) + + self.http_wrong = wrong_response + + self.iss = pyiss.ISS() + + def test_is_ISS_above_true(self): + """ + + Test that json match a true answer + + """ + with HTTMock(self.http_true): + response = self.iss.is_ISS_above(20, 15) + + self.assertTrue(response) + + def test_is_ISS_above_false(self): + """ + + Test that json match a false answer + + """ + with HTTMock(self.http_false): + response = self.iss.is_ISS_above(20, 15) + + self.assertFalse(response) + + def test_is_ISS_above_error_server(self): + """ + + Test that the function raise an exception if the server response is not correct + + """ + with HTTMock(self.http_wrong): + self.assertRaises(Exception, self.iss.is_ISS_above, 15, 20) + + def test_is_ISS_above_input_bound(self): + """ + + Test that input raise exception using voluptuous + Each set of data test a boundary + + """ + with HTTMock(self.http_true): + data = [[-80.1, 1, 1], [80.1, 1, 1], [1, -180.1, 1], + [1, 180.1, 1], [1, 1, -1], [1, 1, 10000.1]] + for value in data: + self.assertRaises(Exception, self.iss.is_ISS_above, value[0], + value[1], value[2]) + + data = [[-80, 1, 1], [80, 1, 1], [1, -180, 1], + [1, 180, 1], [1, 1, 0], [1, 1, 10000]] + for value in data: + self.assertTrue(self.iss.is_ISS_above(value[0], + value[1], value[2])) diff --git a/pyiss/tests/test_next_rise.py b/pyiss/tests/test_next_rise.py new file mode 100644 index 0000000..b7c0593 --- /dev/null +++ b/pyiss/tests/test_next_rise.py @@ -0,0 +1,113 @@ +from unittest import TestCase +from httmock import all_requests, HTTMock, response +import pyiss +import datetime + + +class TestNext_rise(TestCase): + def setUp(self): + """ + + Instantiate the Http Request Mock, the ISS class call and the json response + + """ + + # Json response + self.json_next_rise = { + "message": "success", + "request": { + "altitude": 100, + "datetime": 1481418788, + "latitude": 15.0, + "longitude": 20.0, + "passes": 5 + }, + "response": [ + { + "duration": 348, + "risetime": 1481448840 + }, + { + "duration": 634, + "risetime": 1481454465 + }, + { + "duration": 220, + "risetime": 1481460482 + }, + { + "duration": 224, + "risetime": 1481484335 + }, + { + "duration": 640, + "risetime": 1481489937 + } + ] + } + + self.timestamp_next_rise = self.json_next_rise['response'][0][ + "risetime"] + + # HTTP Mock + @all_requests + def correct_response(url, request): + headers = {'content-type': 'application/json', + 'Set-Cookie': 'foo=bar;'} + return response(200, self.json_next_rise, headers, None, 5, + request) + + self.http_correct = correct_response + + @all_requests + def wrong_response(url, request): + headers = {'content-type': 'application/json', + 'Set-Cookie': 'foo=bar;'} + return response(403, self.json_next_rise, headers, None, 5, + request) + + self.http_wrong = wrong_response + + self.iss = pyiss.ISS() + + def test_next_rise_json_return(self): + """ + + Test that the function return the right datetime + + """ + with HTTMock(self.http_correct): + response = self.iss.next_rise(20, 15) + + self.assertEqual(self.timestamp_next_rise, response.timestamp()) + + def test_next_rise_error_server(self): + """ + + Test that the function raise an exception if the server response is not correct + + """ + with HTTMock(self.http_wrong): + self.assertRaises(Exception, self.iss.next_rise, 15, 20) + + def test_next_rise_input_bound(self): + """ + + Test that input raise exception using voluptuous + Each set of data test a boundary + + """ + with HTTMock(self.http_correct): + data = [[-80.1, 1, 1], [80.1, 1, 1], [1, -180.1, 1], + [1, 180.1, 1], [1, 1, -1], [1, 1, 10000.1]] + for value in data: + self.assertRaises(Exception, self.iss.next_rise, value[0], + value[1], value[2]) + + data = [[-80, 1, 1], [80, 1, 1], [1, -180, 1], + [1, 180, 1], [1, 1, 0], [1, 1, 10000]] + for value in data: + self.assertEqual(self.timestamp_next_rise, + self.iss.next_rise(value[0], + value[1], + value[2]).timestamp()) diff --git a/pyiss/tests/test_number_of_people_in_space.py b/pyiss/tests/test_number_of_people_in_space.py new file mode 100644 index 0000000..357c249 --- /dev/null +++ b/pyiss/tests/test_number_of_people_in_space.py @@ -0,0 +1,64 @@ +from unittest import TestCase +from httmock import all_requests, HTTMock, response +import pyiss + + +class TestNumber_of_people_in_space(TestCase): + def setUp(self): + """ + + Instantiate the Http Request Mock, the ISS class call and the json response + + """ + + # Json response + self.json_number_of_people_in_space = { + "people": [{"craft": "ISS", "name": "Sergey Rizhikov"}, + {"craft": "ISS", "name": "Andrey Borisenko"}, + {"craft": "ISS", "name": "Shane Kimbrough"}, + {"craft": "ISS", "name": "Oleg Novitskiy"}, + {"craft": "ISS", "name": "Thomas Pesquet"}, + {"craft": "ISS", "name": "Peggy Whitson"}], + "message": "success", "number": 6} + + # HTTP Mock + @all_requests + def correct_response(url, request): + headers = {'content-type': 'application/json', + 'Set-Cookie': 'foo=bar;'} + return response(200, self.json_number_of_people_in_space, headers, + None, 5, + request) + + self.http_correct = correct_response + + @all_requests + def wrong_response(url, request): + headers = {'content-type': 'application/json', + 'Set-Cookie': 'foo=bar;'} + return response(403, self.json_number_of_people_in_space, headers, + None, 5, + request) + + self.http_wrong = wrong_response + + self.iss = pyiss.ISS() + + def test_number_of_people_in_space_json_return(self): + """ + + Test that the function return the right number + + """ + with HTTMock(self.http_correct): + self.assertEqual(self.iss.number_of_people_in_space(), + self.json_number_of_people_in_space['number']) + + def test_number_of_people_in_space_error_server(self): + """ + + Test that the function raise an exception if the server response is not correct + + """ + with HTTMock(self.http_wrong): + self.assertRaises(Exception, self.iss.number_of_people_in_space) diff --git a/pyiss/tests/test_pass_times.py b/pyiss/tests/test_pass_times.py new file mode 100644 index 0000000..329d93d --- /dev/null +++ b/pyiss/tests/test_pass_times.py @@ -0,0 +1,112 @@ +from unittest import TestCase +from httmock import all_requests, HTTMock, response +import pyiss + + +class TestPass_times(TestCase): + def setUp(self): + """ + + Instantiate the Http Request Mock, the ISS class call and the json response + + """ + + # Json response + self.json_pass_times = { + "message": "success", + "request": { + "altitude": 100, + "datetime": 1481418788, + "latitude": 15.0, + "longitude": 20.0, + "passes": 5 + }, + "response": [ + { + "duration": 348, + "risetime": 1481448840 + }, + { + "duration": 634, + "risetime": 1481454465 + }, + { + "duration": 220, + "risetime": 1481460482 + }, + { + "duration": 224, + "risetime": 1481484335 + }, + { + "duration": 640, + "risetime": 1481489937 + } + ] + } + + self.location = self.json_pass_times['response'] + + #HTTP Mock + @all_requests + def correct_response(url, request): + headers = {'content-type': 'application/json', + 'Set-Cookie': 'foo=bar;'} + return response(200, self.json_pass_times, headers, None, 5, + request) + self.http_correct = correct_response + + @all_requests + def wrong_response(url, request): + headers = {'content-type': 'application/json', + 'Set-Cookie': 'foo=bar;'} + return response(403, self.json_pass_times, headers, None, 5, + request) + self.http_wrong = wrong_response + + self.iss = pyiss.ISS() + + def test_pass_times_json_return(self): + """ + + Test that the function return the right list answer + + """ + with HTTMock(self.http_correct): + response = self.iss.pass_times(20, 15) + + self.assertListEqual(self.location, response) + + def test_pass_times_error_server(self): + """ + + Test that the function raise an exception if the server response is not correct + + """ + with HTTMock(self.http_wrong): + self.assertRaises(Exception, self.iss.pass_times, 15,20) + + + def test_pass_times_input_bound(self): + """ + + Test that input raise exception using voluptuous + Each set of data test a boundary + + """ + with HTTMock(self.http_correct): + data = [[-80.1, 1, 1, 1], [80.1, 1, 1, 1], [1, -180.1, 1, 1], + [1, 180.1, 1, 1], [1, 1, -1, 1], [1, 1, 10000.1, 1], + [1, 1, 1, 0], [1, 1, 1, 101], [1, 1, 1, 5.1]] + for value in data: + self.assertRaises(Exception, self.iss.pass_times, value[0], + value[1], value[2], value[3]) + + data = [[-80, 1, 1, 1], [80, 1, 1, 1], [1, -180, 1, 1], + [1, 180, 1, 1], [1, 1, 0, 1], [1, 1, 10000, 1], + [1, 1, 1, 1], [1, 1, 1, 100]] + for value in data: + self.assertListEqual(self.location, + self.iss.pass_times(value[0], + value[1], value[2], + value[3])) diff --git a/pyiss/tests/test_people_in_space.py b/pyiss/tests/test_people_in_space.py new file mode 100644 index 0000000..9d2dcb3 --- /dev/null +++ b/pyiss/tests/test_people_in_space.py @@ -0,0 +1,60 @@ +from unittest import TestCase +from httmock import all_requests, HTTMock, response +import pyiss + + +class TestPeople_in_space(TestCase): + def setUp(self): + """ + + Instantiate the Http Request Mock, the ISS class call and the json response + + """ + + #Json response + self.json_people_in_space = { + "people": [{"craft": "ISS", "name": "Sergey Rizhikov"}, + {"craft": "ISS", "name": "Andrey Borisenko"}, + {"craft": "ISS", "name": "Shane Kimbrough"}, + {"craft": "ISS", "name": "Oleg Novitskiy"}, + {"craft": "ISS", "name": "Thomas Pesquet"}, + {"craft": "ISS", "name": "Peggy Whitson"}], + "message": "success", "number": 6} + + #HTTP Mock + @all_requests + def correct_response(url, request): + headers = {'content-type': 'application/json', + 'Set-Cookie': 'foo=bar;'} + return response(200, self.json_people_in_space, headers, None, 5, + request) + self.http_correct = correct_response + + @all_requests + def wrong_response(url, request): + headers = {'content-type': 'application/json', + 'Set-Cookie': 'foo=bar;'} + return response(403, self.json_people_in_space, headers, None, 5, + request) + self.http_wrong = wrong_response + + self.iss = pyiss.ISS() + + def test_people_in_space_json_return(self): + """ + + Test that the function return the right json answer + + """ + with HTTMock(self.http_correct): + response = self.iss.people_in_space() + self.assertDictEqual(response, self.json_people_in_space) + + def test_people_in_space_error_server(self): + """ + + Test that the function raise an exception if the server response is not correct + + """ + with HTTMock(self.http_wrong): + self.assertRaises(Exception, self.iss.people_in_space) \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..224a779 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[metadata] +description-file = README.md \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..11026da --- /dev/null +++ b/setup.py @@ -0,0 +1,18 @@ +from setuptools import setup +setup( + name = 'pyiss', + packages = ['pyiss'], # this must be the same as the name above + install_requires = ['requests', 'httmock'], + version = '1.0', + description = 'a simple python3 library for info about the current ' + 'International Space Station location', + author = 'Hydreliox', + author_email = 'hydreliox@gmail.com', + url = 'https://github.com/HydrelioxGitHub/pyiss', # use the URL to the + # github repo + download_url = 'https://github.com/HydrelioxGitHub/pyiss/tarball/1.0', + keywords = ['ISS', 'space', 'station', 'API'], # arbitrary keywords + classifiers = [], + test_suite='nose.collector', + tests_require=['nose'], +) \ No newline at end of file