diff --git a/.travis.yml b/.travis.yml index 06c3d59..0cf673c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,8 @@ install: - pip install -r test_requirements.txt before_install: - export DEVICE_HIVE_TRANSPORT_URLS='http://playground.dev.devicehive.com/api/rest,ws://playground.dev.devicehive.com/api/websocket' - - openssl aes-256-cbc -K $encrypted_1ea9ebbf5537_key -iv $encrypted_1ea9ebbf5537_iv -in refresh_token.txt.enc -out refresh_token.txt -d - - export DEVICE_HIVE_REFRESH_TOKEN=$(cat refresh_token.txt) -script: pytest -xv tests --transport-urls=$DEVICE_HIVE_TRANSPORT_URLS --refresh-token=$DEVICE_HIVE_REFRESH_TOKEN + - openssl aes-256-cbc -K $encrypted_1ea9ebbf5537_key -iv $encrypted_1ea9ebbf5537_iv -in admin_refresh_token.txt.enc -out admin_refresh_token.txt -d + - export DEVICE_HIVE_ADMIN_REFRESH_TOKEN=$(cat admin_refresh_token.txt) + - openssl aes-256-cbc -K $encrypted_94dc46d8330a_key -iv $encrypted_94dc46d8330a_iv -in user_refresh_token.txt.enc -out user_refresh_token.txt -d + - export DEVICE_HIVE_USER_REFRESH_TOKEN=$(cat user_refresh_token.txt) +script: pytest -xv tests --transport-urls=$DEVICE_HIVE_TRANSPORT_URLS --admin-refresh-token=$DEVICE_HIVE_ADMIN_REFRESH_TOKEN --user-refresh-token=$DEVICE_HIVE_USER_REFRESH_TOKEN diff --git a/README.md b/README.md index 8cde962..46424f8 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ If you want to use `Websocket` protocol you need only to specify the url: url = 'ws://playground.dev.devicehive.com/api/websocket' ``` -### Authentication. +### Authentication There are three ways of initial authentication: @@ -474,7 +474,7 @@ class SimpleHandler(Handler): self.api.disconnect() ``` -## Extended example: +## Extended example Here we will create one endpoint which sends notifications and other endpoint which receives these notifications. diff --git a/refresh_token.txt.enc b/admin_refresh_token.txt.enc similarity index 100% rename from refresh_token.txt.enc rename to admin_refresh_token.txt.enc diff --git a/devicehive/api_handler.py b/devicehive/api_handler.py index da9760f..29eb5b3 100644 --- a/devicehive/api_handler.py +++ b/devicehive/api_handler.py @@ -16,7 +16,7 @@ class ApiHandler(Handler): def __init__(self, transport, auth, handler_class, handler_args, handler_kwargs): - Handler.__init__(self, transport) + super(ApiHandler, self).__init__(transport) self._api = Api(self._transport, auth) self._handler = handler_class(self._api, *handler_args, **handler_kwargs) diff --git a/devicehive/api_request.py b/devicehive/api_request.py index 9b7947e..c580659 100644 --- a/devicehive/api_request.py +++ b/devicehive/api_request.py @@ -2,6 +2,10 @@ from devicehive.api_response import ApiResponseError from devicehive.transports.transport import TransportError import uuid +import logging + + +logger = logging.getLogger(__name__) class ApiRequest(object): @@ -81,10 +85,16 @@ def response_key(self, key): self._params['response_key'] = key def execute(self, error_message): - response = self._api.transport.request(self._uuid(), self._action, - self._request.copy(), + uuid = self._uuid() + request = self._request.copy() + logger.debug('Request id: %s. Action: %s. Request: %s.', uuid, + self._action, request) + response = self._api.transport.request(uuid, self._action, request, **self._params) api_response = ApiResponse(response, self._params['response_key']) + logger.debug('Response id: %s. Action: %s. Success: %s. Response: %s.', + api_response.id, api_response.action, api_response.success, + api_response.response) if api_response.success: return api_response.response raise ApiResponseError(error_message, self._api.transport.name, @@ -97,13 +107,13 @@ class AuthApiRequest(ApiRequest): def execute(self, error_message): self.header(*self._api.token.auth_header) try: - return ApiRequest.execute(self, error_message) + return super(AuthApiRequest, self).execute(error_message) except ApiResponseError as api_response_error: if api_response_error.code != 401: raise self._api.token.auth() self.header(*self._api.token.auth_header) - return ApiRequest.execute(self, error_message) + return super(AuthApiRequest, self).execute(error_message) class SubscriptionApiRequest(object): @@ -170,7 +180,7 @@ class AuthSubscriptionApiRequest(SubscriptionApiRequest): """Auth subscription api request class.""" def __init__(self, api): - SubscriptionApiRequest.__init__(self) + super(AuthSubscriptionApiRequest, self).__init__() auth_header_name, auth_header_value = api.token.auth_header self._params['headers'][auth_header_name] = auth_header_value self._params['response_error_handler'] = self.response_error_handler diff --git a/devicehive/api_response.py b/devicehive/api_response.py index 3cc3943..7a61231 100644 --- a/devicehive/api_response.py +++ b/devicehive/api_response.py @@ -54,7 +54,7 @@ def __init__(self, message, transport_name, code, error): message = '%s Transport: %s. Code: %s. Error: %s' % (message, transport_name, code, error) - Exception.__init__(self, message) + super(ApiResponseError, self).__init__(message) self._transport_name = transport_name self._code = code self._error = error diff --git a/devicehive/data_formats/json_data_format.py b/devicehive/data_formats/json_data_format.py index 815116a..98b0dcd 100644 --- a/devicehive/data_formats/json_data_format.py +++ b/devicehive/data_formats/json_data_format.py @@ -6,7 +6,7 @@ class JsonDataFormat(DataFormat): """Json data format class.""" def __init__(self): - DataFormat.__init__(self, 'json', self.TEXT_DATA_TYPE) + super(JsonDataFormat, self).__init__('json', self.TEXT_DATA_TYPE) def encode(self, data): return json.dumps(data) diff --git a/devicehive/device_hive.py b/devicehive/device_hive.py index f723b01..e5cbb95 100644 --- a/devicehive/device_hive.py +++ b/devicehive/device_hive.py @@ -39,6 +39,7 @@ def connect(self, transport_url, **options): connect_timeout = options.pop('connect_timeout', 30) max_num_connect = options.pop('max_num_connect', 10) connect_interval = options.pop('connect_interval', 1) + transport_alive_timeout = options.pop('transport_alive_timeout', 0.01) self._api_handler_options['auth'] = auth self._init_transport() connect_time = time.time() @@ -47,7 +48,8 @@ def connect(self, transport_url, **options): if self._transport.connected: self._transport.disconnect() self._transport.connect(transport_url, **options) - self._transport.join() + while self._transport.is_alive(): + time.sleep(transport_alive_timeout) exception_info = self._transport.exception_info if exception_info and not isinstance(exception_info[1], self._transport.error): diff --git a/devicehive/transports/http_transport.py b/devicehive/transports/http_transport.py index 870a2ec..39beb80 100644 --- a/devicehive/transports/http_transport.py +++ b/devicehive/transports/http_transport.py @@ -11,8 +11,10 @@ class HttpTransport(Transport): def __init__(self, data_format_class, data_format_options, handler_class, handler_options): - Transport.__init__(self, 'http', HttpTransportError, data_format_class, - data_format_options, handler_class, handler_options) + super(HttpTransport, self).__init__('http', HttpTransportError, + data_format_class, + data_format_options, handler_class, + handler_options) self._url = None self._options = None self._events_queue_timeout = None diff --git a/devicehive/transports/transport.py b/devicehive/transports/transport.py index 5327d92..7d57882 100644 --- a/devicehive/transports/transport.py +++ b/devicehive/transports/transport.py @@ -108,6 +108,9 @@ def disconnect(self): def join(self, timeout=None): self._connection_thread.join(timeout) + def is_alive(self): + return self._connection_thread.is_alive() + def send_request(self, request_id, action, request, **params): raise NotImplementedError diff --git a/devicehive/transports/websocket_transport.py b/devicehive/transports/websocket_transport.py index 07cb1f3..175b72e 100644 --- a/devicehive/transports/websocket_transport.py +++ b/devicehive/transports/websocket_transport.py @@ -11,9 +11,11 @@ class WebsocketTransport(Transport): def __init__(self, data_format_class, data_format_options, handler_class, handler_options): - Transport.__init__(self, 'websocket', WebsocketTransportError, - data_format_class, data_format_options, - handler_class, handler_options) + super(WebsocketTransport, self).__init__('websocket', + WebsocketTransportError, + data_format_class, + data_format_options, + handler_class, handler_options) self._websocket = websocket.WebSocket() self._pong_received = False self._event_queue = [] diff --git a/examples/echo.py b/examples/echo.py index 08d7a44..4afde2b 100644 --- a/examples/echo.py +++ b/examples/echo.py @@ -1,11 +1,32 @@ from devicehive import Handler from devicehive import DeviceHive +import logging.config + + +LOGGING = { + 'version': 1, + 'handlers': { + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + }, + }, + 'loggers': { + 'devicehive.api_request': { + 'handlers': ['console'], + 'level': 'DEBUG', + 'propagate': False + }, + } +} + +logging.config.dictConfig(LOGGING) class EchoHandler(Handler): def __init__(self, api, device_id='example-echo-device'): - Handler.__init__(self, api) + super(EchoHandler, self).__init__(api) self._device_id = device_id self._device = None diff --git a/examples/raspi_led_thermo.py b/examples/raspi_led_thermo.py index 1e07ab4..71a13e6 100644 --- a/examples/raspi_led_thermo.py +++ b/examples/raspi_led_thermo.py @@ -65,7 +65,7 @@ class SampleHandler(Handler): INTERVAL_SECONDS = 5 def __init__(self, api, device_id=DEVICE_ID): - Handler.__init__(self, api) + super(SampleHandler, self).__init__(api) self._device_id = device_id self._device = None self._sensor = TempSensor() diff --git a/setup.py b/setup.py index a67f04e..0e8597b 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup(name='devicehive', - version='2.0.1', + version='2.0.2', author='DataArt (http://dataart.com)', author_email='info@devicehive.com', url='https://devicehive.com', diff --git a/tests/conftest.py b/tests/conftest.py index df42358..7aaa1ed 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,17 +3,24 @@ def pytest_addoption(parser): parser.addoption('--transport-urls', action='store', help='Transport urls') - parser.addoption('--refresh-token', action='store', help='Refresh token') + parser.addoption('--admin-refresh-token', action='store', + help='Admin refresh tokens') + parser.addoption('--user-refresh-token', action='store', + help='User refresh tokens') def pytest_generate_tests(metafunc): if metafunc.module.__name__.find('.test_api') == -1: return transport_urls = metafunc.config.option.transport_urls.split(',') - refresh_token = metafunc.config.option.refresh_token + refresh_tokens = {'admin': metafunc.config.option.admin_refresh_token, + 'user': metafunc.config.option.user_refresh_token} tests = [] ids = [] for transport_url in transport_urls: - tests.append(Test(transport_url, refresh_token)) - ids.append(transport_url) + for token_type, refresh_token in refresh_tokens.items(): + if not refresh_token: + continue + tests.append(Test(transport_url, refresh_token, token_type)) + ids.append('%s:%s' % (token_type, transport_url)) metafunc.parametrize('test', tests, ids=ids) diff --git a/tests/test.py b/tests/test.py index a02246c..c7d59fa 100644 --- a/tests/test.py +++ b/tests/test.py @@ -9,7 +9,7 @@ class TestHandler(Handler): def __init__(self, api, handle_connect, handle_command_insert, handle_command_update, handle_notification): - Handler.__init__(self, api) + super(TestHandler, self).__init__(api) self._handle_connect = handle_connect self._handle_command_insert = handle_command_insert self._handle_command_update = handle_command_update @@ -44,16 +44,19 @@ def disconnect(self): class Test(object): """Test class.""" - def __init__(self, transport_url, refresh_token): + def __init__(self, transport_url, refresh_token, token_type): self._transport_url = transport_url self._refresh_token = refresh_token + self._token_type = token_type self._transport_name = DeviceHive.transport_name(self._transport_url) def generate_id(self, key=None): time_key = repr(time.time()) if not key: - return '%s-%s' % (self._transport_name, time_key) - return '%s-%s-%s' % (self._transport_name, key, time_key) + return '%s-%s-%s' % (self._transport_name, self._token_type, + time_key) + return '%s-%s-%s-%s' % (self._transport_name, key, self._token_type, + time_key) @property def transport_name(self): @@ -67,6 +70,24 @@ def http_transport(self): def websocket_transport(self): return self._transport_name == 'websocket' + @property + def user_refresh_token(self): + return self._token_type == 'user' + + @property + def admin_refresh_token(self): + return self._token_type == 'admin' + + def only_user_implementation(self): + if self.user_refresh_token: + return + pytest.skip('Implemented only for user refresh token.') + + def only_admin_implementation(self): + if self.admin_refresh_token: + return + pytest.skip('Implemented only for admin refresh token.') + def only_http_implementation(self): if self.http_transport: return diff --git a/tests/test_api.py b/tests/test_api.py index 5c4dddb..29d4503 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -47,10 +47,9 @@ def handle_connect(handler): handler.api.create_token(user_id) assert False except ApiResponseError as api_response_error: - # TODO: add http support after server response will be fixed. - if test.websocket_transport: - assert api_response_error.code == 404 + assert api_response_error.code == 404 + test.only_admin_implementation() test.run(handle_connect) @@ -125,7 +124,6 @@ def handle_connect(handler): except DeviceError: pass [device.remove() for device in devices] - # TODO: add http support after server response will be fixed. if test.http_transport: return try: @@ -230,7 +228,6 @@ def handle_connect(handler): except DeviceError: pass [device.remove() for device in devices] - # TODO: add http support after server response will be fixed. if test.http_transport: return try: @@ -348,7 +345,6 @@ def handle_connect(handler): except DeviceError: pass [device.remove() for device in devices] - # TODO: add http support after server response will be fixed. if test.http_transport: return try: @@ -450,7 +446,12 @@ def handle_connect(handler): handler.api.get_device(device_id) assert False except ApiResponseError as api_response_error: - assert api_response_error.code == 404 + if test.admin_refresh_token: + assert api_response_error.code == 404 + # TODO: uncomment after server response for ws for user token will + # be fixed + # else: + # assert api_response_error.code == 403 test.run(handle_connect) @@ -501,27 +502,26 @@ def handle_connect(handler): name_pattern = test_id + '%' networks = handler.api.list_networks(name_pattern=name_pattern) assert len(networks) == len(options) - # TODO: add websocket transport after server response will be fixed. - if test.http_transport: - network_0, network_1 = handler.api.list_networks( - name_pattern=name_pattern, sort_field='name', sort_order='ASC') - assert network_0.name == options[0]['name'] - assert network_1.name == options[1]['name'] - network_0, network_1 = handler.api.list_networks( - name_pattern=name_pattern, sort_field='name', sort_order='DESC') - assert network_0.name == options[1]['name'] - assert network_1.name == options[0]['name'] - network, = handler.api.list_networks(name_pattern=name_pattern, - sort_field='name', - sort_order='ASC', take=1) - assert network.name == options[0]['name'] - network, = handler.api.list_networks(name_pattern=name_pattern, - sort_field='name', - sort_order='ASC', take=1, - skip=1) - assert network.name == options[1]['name'] + network_0, network_1 = handler.api.list_networks( + name_pattern=name_pattern, sort_field='name', sort_order='ASC') + assert network_0.name == options[0]['name'] + assert network_1.name == options[1]['name'] + network_0, network_1 = handler.api.list_networks( + name_pattern=name_pattern, sort_field='name', sort_order='DESC') + assert network_0.name == options[1]['name'] + assert network_1.name == options[0]['name'] + network, = handler.api.list_networks(name_pattern=name_pattern, + sort_field='name', + sort_order='ASC', take=1) + assert network.name == options[0]['name'] + network, = handler.api.list_networks(name_pattern=name_pattern, + sort_field='name', + sort_order='ASC', take=1, + skip=1) + assert network.name == options[1]['name'] [test_network.remove() for test_network in test_networks] + test.only_admin_implementation() test.run(handle_connect) @@ -543,6 +543,7 @@ def handle_connect(handler): except ApiResponseError as api_response_error: assert api_response_error.code == 404 + test.only_admin_implementation() test.run(handle_connect) @@ -562,6 +563,7 @@ def handle_connect(handler): assert api_response_error.code == 403 network.remove() + test.only_admin_implementation() test.run(handle_connect) @@ -616,6 +618,7 @@ def handle_connect(handler): assert user.login == options[1]['login'] [test_user.remove() for test_user in test_users] + test.only_admin_implementation() test.run(handle_connect) @@ -652,6 +655,7 @@ def handle_connect(handler): except ApiResponseError as api_response_error: assert api_response_error.code == 404 + test.only_admin_implementation() test.run(handle_connect) @@ -677,4 +681,5 @@ def handle_connect(handler): assert api_response_error.code == 403 user.remove() + test.only_admin_implementation() test.run(handle_connect) diff --git a/tests/test_api_command.py b/tests/test_api_command.py index 1349bec..961622f 100644 --- a/tests/test_api_command.py +++ b/tests/test_api_command.py @@ -18,8 +18,11 @@ def handle_connect(handler): command.save() assert False except ApiResponseError as api_response_error: - # TODO: uncomment after server response will be fixed. - # assert api_response_error.code() == 404 - pass + if test.admin_refresh_token: + assert api_response_error.code == 404 + # TODO: uncomment after server response for ws for user token will + # be fixed + # else: + # assert api_response_error.code == 403 test.run(handle_connect) diff --git a/tests/test_api_device.py b/tests/test_api_device.py index b6eaea3..f1ec488 100644 --- a/tests/test_api_device.py +++ b/tests/test_api_device.py @@ -50,7 +50,12 @@ def handle_connect(handler): device_1.remove() assert False except ApiResponseError as api_response_error: - assert api_response_error.code == 404 + if test.admin_refresh_token: + assert api_response_error.code == 404 + # TODO: uncomment after server response for ws for user token will + # be fixed + # else: + # assert api_response_error.code == 403 test.run(handle_connect) @@ -117,7 +122,6 @@ def handle_connect(handler): assert False except DeviceError: pass - # TODO: add http support after server response will be fixed. if test.http_transport: return try: @@ -149,12 +153,6 @@ def handle_connect(handler): assert False except DeviceError: pass - # TODO: add after server response will be fixed. - # try: - # device_1.unsubscribe_insert_commands() - # assert False - # except ApiResponseError as api_response_error: - # assert api_response_error.code == 403 test.run(handle_connect) @@ -223,7 +221,6 @@ def handle_connect(handler): assert False except DeviceError: pass - # TODO: add http support after server response will be fixed. if test.http_transport: return try: @@ -255,12 +252,6 @@ def handle_connect(handler): assert False except DeviceError: pass - # TODO: add after server response will be fixed. - # try: - # device_1.unsubscribe_update_commands() - # assert False - # except ApiResponseError as api_response_error: - # assert api_response_error.code == 403 test.run(handle_connect) @@ -315,7 +306,12 @@ def handle_connect(handler): device_1.list_commands() assert False except ApiResponseError as api_response_error: - assert api_response_error.code == 404 + if test.admin_refresh_token: + assert api_response_error.code == 404 + # TODO: uncomment after server response for ws for user token will + # be fixed + # else: + # assert api_response_error.code == 403 test.run(handle_connect) @@ -366,9 +362,12 @@ def handle_connect(handler): device_1.send_command(command_name) assert False except ApiResponseError as api_response_error: - # TODO: uncomment after server response will be fixed. - # assert api_response_error.code() == 404 - pass + if test.admin_refresh_token: + assert api_response_error.code == 404 + # TODO: uncomment after server response for ws for user token will + # be fixed + # else: + # assert api_response_error.code == 403 test.run(handle_connect) @@ -437,7 +436,6 @@ def handle_connect(handler): assert False except DeviceError: pass - # TODO: add http support after server response will be fixed. if test.http_transport: return try: @@ -469,12 +467,6 @@ def handle_connect(handler): assert False except DeviceError: pass - # TODO: add after server response will be fixed. - # try: - # device_1.unsubscribe_notifications() - # assert False - # except ApiResponseError as api_response_error: - # assert api_response_error.code == 403 test.run(handle_connect) @@ -537,7 +529,12 @@ def handle_connect(handler): device_1.list_commands() assert False except ApiResponseError as api_response_error: - assert api_response_error.code == 404 + if test.admin_refresh_token: + assert api_response_error.code == 404 + # TODO: uncomment after server response for ws for user token will + # be fixed + # else: + # assert api_response_error.code == 403 test.run(handle_connect) @@ -573,6 +570,11 @@ def handle_connect(handler): device_1.send_notification(notification_name) assert False except ApiResponseError as api_response_error: - assert api_response_error.code == 404 + if test.admin_refresh_token: + assert api_response_error.code == 404 + # TODO: uncomment after server response for ws for user token will + # be fixed + # else: + # assert api_response_error.code == 403 test.run(handle_connect) diff --git a/tests/test_api_network.py b/tests/test_api_network.py index ccb1b3f..119f899 100644 --- a/tests/test_api_network.py +++ b/tests/test_api_network.py @@ -28,6 +28,7 @@ def handle_connect(handler): # assert api_response_error.code == 404 pass + test.only_admin_implementation() test.run(handle_connect) @@ -53,4 +54,5 @@ def handle_connect(handler): except ApiResponseError as api_response_error: assert api_response_error.code == 404 + test.only_admin_implementation() test.run(handle_connect) diff --git a/tests/test_api_user.py b/tests/test_api_user.py index 8e7a8a4..1cc1cd8 100644 --- a/tests/test_api_user.py +++ b/tests/test_api_user.py @@ -29,6 +29,7 @@ def handle_connect(handler): except UserError: pass + test.only_admin_implementation() test.run(handle_connect) @@ -55,6 +56,7 @@ def handle_connect(handler): except ApiResponseError as api_response_error: assert api_response_error.code == 404 + test.only_admin_implementation() test.run(handle_connect) @@ -86,6 +88,7 @@ def handle_connect(handler): except ApiResponseError as api_response_error: assert api_response_error.code == 404 + test.only_admin_implementation() test.run(handle_connect) @@ -120,6 +123,7 @@ def handle_connect(handler): except ApiResponseError as api_response_error: assert api_response_error.code == 404 + test.only_admin_implementation() test.run(handle_connect) @@ -154,6 +158,7 @@ def handle_connect(handler): assert api_response_error.code == 404 network.remove() + test.only_admin_implementation() test.run(handle_connect) @@ -185,9 +190,8 @@ def handle_connect(handler): user_1.unassign_network(network.id) assert False except ApiResponseError as api_response_error: - # TODO: add http support after server response will be fixed. - if test.http_transport: - assert api_response_error.code == 404 + assert api_response_error.code == 404 network.remove() + test.only_admin_implementation() test.run(handle_connect) diff --git a/user_refresh_token.txt.enc b/user_refresh_token.txt.enc new file mode 100644 index 0000000..16bb085 Binary files /dev/null and b/user_refresh_token.txt.enc differ