Skip to content

Commit

Permalink
Inform about updates (#226)
Browse files Browse the repository at this point in the history
  • Loading branch information
aniezurawski authored Apr 24, 2020
1 parent badf988 commit 4a1a72e
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 22 deletions.
8 changes: 8 additions & 0 deletions neptune/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,11 @@ class CannotResolveHostname(NeptuneException):
def __init__(self, host):
super(CannotResolveHostname, self).__init__(
"Cannot resolve hostname {}. Please contact Neptune support.".format(host))

class UnsupportedClientVersion(NeptuneException):
def __init__(self, version, minVersion, maxVersion):
super(UnsupportedClientVersion, self).__init__(
"This client version ({}) is not supported. Please install neptune-client{}".format(
version,
"==" + str(maxVersion) if maxVersion else ">=" + str(minVersion)
))
17 changes: 16 additions & 1 deletion neptune/internal/backends/client_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@

class ClientConfig(object):

def __init__(self, api_url, display_url):
def __init__(self, api_url, display_url, min_recommended_version, min_compatible_version, max_compatible_version):
self._api_url = api_url
self._display_url = display_url
self._min_recommended_version = min_recommended_version
self._min_compatible_version = min_compatible_version
self._max_compatible_version = max_compatible_version

@property
def api_url(self):
Expand All @@ -28,3 +31,15 @@ def api_url(self):
@property
def display_url(self):
return self._display_url

@property
def min_recommended_version(self):
return self._min_recommended_version

@property
def min_compatible_version(self):
return self._min_compatible_version

@property
def max_compatible_version(self):
return self._max_compatible_version
77 changes: 60 additions & 17 deletions neptune/internal/backends/hosted_neptune_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,22 @@
import os
import platform
import socket
import sys
import uuid
from functools import partial
from http.client import NOT_FOUND, UNPROCESSABLE_ENTITY # pylint:disable=no-name-in-module
from io import StringIO
from itertools import groupby

import click
import requests
import six
import urllib3
from bravado.client import SwaggerClient
from bravado.exception import HTTPBadRequest, HTTPNotFound, HTTPUnprocessableEntity, HTTPConflict
from bravado.requests_client import RequestsClient
from bravado_core.formatter import SwaggerFormat
from packaging import version
from requests.exceptions import HTTPError
from six.moves import urllib

Expand All @@ -41,7 +44,7 @@
from neptune.backend import Backend
from neptune.checkpoint import Checkpoint
from neptune.internal.backends.client_config import ClientConfig
from neptune.exceptions import FileNotFound, DeprecatedApiToken, CannotResolveHostname
from neptune.exceptions import FileNotFound, DeprecatedApiToken, CannotResolveHostname, UnsupportedClientVersion
from neptune.experiments import Experiment
from neptune.internal.backends.credentials import Credentials
from neptune.internal.utils.http import extract_response_field
Expand All @@ -63,6 +66,10 @@ def __init__(self, api_token=None, proxies=None):
if api_token == ANONYMOUS:
api_token = ANONYMOUS_API_TOKEN

# This is not a top-level import because of circular dependencies
from neptune import __version__
self.client_lib_version = __version__

self.credentials = Credentials(api_token)

ssl_verify = True
Expand All @@ -79,15 +86,13 @@ def __init__(self, api_token=None, proxies=None):
backend_client = self._get_swagger_client('{}/api/backend/swagger.json'.format(config_api_url))
self._client_config = self._create_client_config(self.credentials.api_token, backend_client)

self._verify_version()

self._set_swagger_clients(self._client_config, config_api_url, backend_client)

self.authenticator = self._create_authenticator(self.credentials.api_token, ssl_verify, proxies)
self._http_client.authenticator = self.authenticator

# This is not a top-level import because of circular dependencies
from neptune import __version__
self.client_lib_version = __version__

user_agent = 'neptune-client/{lib_version} ({system}, python {python_version})'.format(
lib_version=self.client_lib_version,
system=platform.platform(),
Expand Down Expand Up @@ -901,21 +906,64 @@ def _get_swagger_client(self, url):
validate_responses=False,
formats=[uuid_format]
),
http_client=self._http_client
)
http_client=self._http_client)

@with_api_exceptions_handler
def _create_authenticator(self, api_token, ssl_verify, proxies):
return NeptuneAuthenticator(
self.backend_swagger_client.api.exchangeApiToken(X_Neptune_Api_Token=api_token).response().result,
ssl_verify,
proxies
)
proxies)

@with_api_exceptions_handler
def _create_client_config(self, api_token, backend_client):
config = backend_client.api.getClientConfig(X_Neptune_Api_Token=api_token).response().result
return ClientConfig(api_url=config.apiUrl, display_url=config.applicationUrl)
min_recommended = None
min_compatible = None
max_compatible = None

if hasattr(config, "pyLibVersions"):
min_recommended = getattr(config.pyLibVersions, "minRecommendedVersion", None)
min_compatible = getattr(config.pyLibVersions, "minCompatibleVersion", None)
max_compatible = getattr(config.pyLibVersions, "maxCompatibleVersion", None)
else:
click.echo(
"ERROR: This client version is not supported by your Neptune instance. Please contant Neptune support.",
sys.stderr)
raise UnsupportedClientVersion(self.client_lib_version, None, "0.4.111")

return ClientConfig(
api_url=config.apiUrl,
display_url=config.applicationUrl,
min_recommended_version=version.parse(min_recommended) if min_recommended else None,
min_compatible_version=version.parse(min_compatible) if min_compatible else None,
max_compatible_version=version.parse(max_compatible) if max_compatible else None
)

def _verify_version(self):
parsed_version = version.parse(self.client_lib_version)

if self._client_config.min_compatible_version and self._client_config.min_compatible_version > parsed_version:
click.echo(
"ERROR: Minimal supported client version is {} (installed: {}). Please upgrade neptune-client".format(
self._client_config.min_compatible_version, self.client_lib_version),
sys.stderr)
raise UnsupportedClientVersion(self.client_lib_version,
self._client_config.min_compatible_version,
self._client_config.max_compatible_version)
if self._client_config.max_compatible_version and self._client_config.max_compatible_version < parsed_version:
click.echo(
"ERROR: Maximal supported client version is {} (installed: {}). Please downgrade neptune-client".format(
self._client_config.max_compatible_version, self.client_lib_version),
sys.stderr)
raise UnsupportedClientVersion(self.client_lib_version,
self._client_config.min_compatible_version,
self._client_config.max_compatible_version)
if self._client_config.min_recommended_version and self._client_config.min_recommended_version > parsed_version:
click.echo(
"WARNING: There is a new version of neptune-client {} (installed: {}).".format(
self._client_config.min_recommended_version, self.client_lib_version),
sys.stderr)

def _set_swagger_clients(self, client_config, client_config_api_addr, client_config_backend_client):
self.backend_swagger_client = (
Expand All @@ -938,10 +986,5 @@ def _verify_host_resolution(self, api_url, app_url):
raise CannotResolveHostname(host)


uuid_format = SwaggerFormat(
format='uuid',
to_python=lambda x: x,
to_wire=lambda x: x,
validate=lambda x: None,
description=''
)
uuid_format = SwaggerFormat(format='uuid', to_python=lambda x: x,
to_wire=lambda x: x, validate=lambda x: None, description='')
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ requests-oauthlib>=1.0.0
six>=1.12.0
websocket-client>=0.35.0
GitPython>=2.0.8
packaging
77 changes: 73 additions & 4 deletions tests/neptune/internal/backends/test_hosted_neptune_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import mock
from mock import MagicMock

from neptune.exceptions import DeprecatedApiToken, CannotResolveHostname
from neptune.exceptions import DeprecatedApiToken, CannotResolveHostname, UnsupportedClientVersion
from neptune.internal.backends.hosted_neptune_backend import HostedNeptuneBackend
from tests.neptune.api_models import ApiParameter

Expand All @@ -33,13 +33,54 @@
class TestHostedNeptuneBackend(unittest.TestCase):
# pylint:disable=protected-access

@mock.patch('bravado.client.SwaggerClient.from_url')
@mock.patch('neptune.__version__', '0.5.13')
def test_min_compatible_version_ok(self, swagger_client_factory):
# given
self._get_swagger_client_mock(swagger_client_factory, min_compatible='0.5.13')

# expect
HostedNeptuneBackend(api_token=API_TOKEN)

@mock.patch('bravado.client.SwaggerClient.from_url')
@mock.patch('neptune.__version__', '0.5.13')
def test_min_compatible_version_fail(self, swagger_client_factory):
# given
self._get_swagger_client_mock(swagger_client_factory, min_compatible='0.5.14')

# expect
with self.assertRaises(UnsupportedClientVersion) as ex:
HostedNeptuneBackend(api_token=API_TOKEN)

self.assertTrue("Please install neptune-client>=0.5.14" in str(ex.exception))

@mock.patch('bravado.client.SwaggerClient.from_url')
@mock.patch('neptune.__version__', '0.5.13')
def test_max_compatible_version_ok(self, swagger_client_factory):
# given
self._get_swagger_client_mock(swagger_client_factory, max_compatible='0.5.13')

# expect
HostedNeptuneBackend(api_token=API_TOKEN)

@mock.patch('bravado.client.SwaggerClient.from_url')
@mock.patch('neptune.__version__', '0.5.13')
def test_max_compatible_version_fail(self, swagger_client_factory):
# given
self._get_swagger_client_mock(swagger_client_factory, max_compatible='0.5.12')

# expect
with self.assertRaises(UnsupportedClientVersion) as ex:
HostedNeptuneBackend(api_token=API_TOKEN)

self.assertTrue("Please install neptune-client==0.5.12" in str(ex.exception))

@mock.patch('bravado.client.SwaggerClient.from_url')
@mock.patch('uuid.uuid4')
def test_convert_to_api_parameters(self, uuid4, swagger_client_factory):
# given
swagger_client = MagicMock()
swagger_client = self._get_swagger_client_mock(swagger_client_factory)
swagger_client.get_model.return_value = ApiParameter
swagger_client_factory.return_value = swagger_client

# and
some_uuid = str(uuid.uuid4())
Expand Down Expand Up @@ -76,14 +117,20 @@ def test_convert_to_api_parameters(self, uuid4, swagger_client_factory):
@mock.patch('bravado.client.SwaggerClient.from_url')
@mock.patch('neptune.internal.backends.credentials.os.getenv', return_value=API_TOKEN)
def test_should_take_default_credentials_from_env(self, env, swagger_client_factory):
# given
self._get_swagger_client_mock(swagger_client_factory)

# when
backend = HostedNeptuneBackend()

# then
self.assertEqual(API_TOKEN, backend.credentials.api_token)

@mock.patch('bravado.client.SwaggerClient.from_url')
def test_should_accept_given_api_token(self, _):
def test_should_accept_given_api_token(self, swagger_client_factory):
# given
self._get_swagger_client_mock(swagger_client_factory)

# when
session = HostedNeptuneBackend(API_TOKEN)

Expand Down Expand Up @@ -114,6 +161,28 @@ def test_cannot_resolve_host(self, gethostname_mock):
with self.assertRaises(CannotResolveHostname):
HostedNeptuneBackend(token)

@staticmethod
def _get_swagger_client_mock(
swagger_client_factory,
min_recommended=None,
min_compatible=None,
max_compatible=None):
py_lib_versions = type('py_lib_versions', (object,), {})()
setattr(py_lib_versions, "minRecommendedVersion", min_recommended)
setattr(py_lib_versions, "minCompatibleVersion", min_compatible)
setattr(py_lib_versions, "maxCompatibleVersion", max_compatible)

client_config = type('client_config_response_result', (object,), {})()
setattr(client_config, "pyLibVersions", py_lib_versions)
setattr(client_config, "apiUrl", None)
setattr(client_config, "applicationUrl", None)

swagger_client = MagicMock()
swagger_client.api.getClientConfig.return_value.response.return_value.result = client_config
swagger_client_factory.return_value = swagger_client

return swagger_client

class SomeClass(object):
pass

Expand Down

0 comments on commit 4a1a72e

Please sign in to comment.