From 230f8231870302aa85d102c992755a95172e4482 Mon Sep 17 00:00:00 2001 From: Jakub Date: Mon, 12 Oct 2020 10:59:08 +0200 Subject: [PATCH] Added docs links to error messages (#281) * added exceptions with docs links * copy fix * fixed indentation * updated crucial error messages * added library not installed exception, fixed linting * fixed library constructor missing input and format * fix to library not installed error * added getting help link * changed the help message * changed get help message * renamed exceptions * fixed formatting, changed imported class in tests * fixes * fixed orded params * added os styles dispatch * fixed dict merging * fixed parsing inputs for librarynotinstalled exception --- neptune/__init__.py | 18 +- neptune/exceptions.py | 224 ++++++++++++++++++++--- neptune/internal/backends/credentials.py | 4 +- neptune/projects.py | 4 +- neptune/sessions.py | 4 +- tests/neptune/test_project.py | 4 +- 6 files changed, 219 insertions(+), 39 deletions(-) diff --git a/neptune/__init__.py b/neptune/__init__.py index 08e452e39..4708c170c 100644 --- a/neptune/__init__.py +++ b/neptune/__init__.py @@ -17,9 +17,10 @@ import os import threading -from neptune import envs from neptune import constants -from neptune.exceptions import MissingProjectQualifiedName, Uninitialized, InvalidNeptuneBackend +from neptune import envs +from neptune.exceptions import NeptuneMissingProjectQualifiedNameException, NeptuneUninitializedException, \ + InvalidNeptuneBackend from neptune.internal.backends.hosted_neptune_backend import HostedNeptuneBackend from neptune.internal.backends.offline_backend import OfflineBackend from neptune.projects import Project @@ -112,8 +113,9 @@ def init(project_qualified_name=None, api_token=None, proxies=None, backend=None :class:`~neptune.projects.Project` object that is used to create or list experiments, notebooks, etc. Raises: - `MissingApiToken`: When ``api_token`` is None and ``NEPTUNE_API_TOKEN`` environment variable was not set. - `MissingProjectQualifiedName`: When ``project_qualified_name`` is None + `NeptuneMissingApiTokenException`: When ``api_token`` is None + and ``NEPTUNE_API_TOKEN`` environment variable was not set. + `NeptuneMissingProjectQualifiedNameException`: When ``project_qualified_name`` is None and ``NEPTUNE_PROJECT`` environment variable was not set. `InvalidApiKey`: When given ``api_token`` is malformed. `Unauthorized`: When given ``api_token`` is invalid. @@ -153,7 +155,7 @@ def init(project_qualified_name=None, api_token=None, proxies=None, backend=None session = Session(backend=backend) if project_qualified_name is None: - raise MissingProjectQualifiedName() + raise NeptuneMissingProjectQualifiedNameException() project = session.get_project(project_qualified_name) @@ -176,7 +178,7 @@ def set_project(project_qualified_name): :class:`~neptune.projects.Project` object that is used to create or list experiments, notebooks, etc. Raises: - `MissingApiToken`: When library was not initialized previously by ``init`` call and + `NeptuneMissingApiTokenException`: When library was not initialized previously by ``init`` call and ``NEPTUNE_API_TOKEN`` environment variable is not set. Examples: @@ -223,7 +225,7 @@ def create_experiment(name=None, # pylint: disable=global-statement global project if project is None: - raise Uninitialized() + raise NeptuneUninitializedException() return project.create_experiment( name=name, @@ -249,7 +251,7 @@ def get_experiment(): # pylint: disable=global-statement global project if project is None: - raise Uninitialized() + raise NeptuneUninitializedException() # pylint: disable=protected-access return project._get_current_experiment() diff --git a/neptune/exceptions.py b/neptune/exceptions.py index f92480d19..a0062dbd2 100644 --- a/neptune/exceptions.py +++ b/neptune/exceptions.py @@ -13,18 +13,75 @@ # See the License for the specific language governing permissions and # limitations under the License. # +import platform + from neptune import envs +UNIX_STYLES = {'h1': '\033[95m', + 'h2': '\033[94m', + 'python': '\033[96m', + 'bash': '\033[95m', + 'warning': '\033[93m', + 'correct': '\033[92m', + 'fail': '\033[91m', + 'bold': '\033[1m', + 'underline': '\033[4m', + 'end': '\033[0m'} + +WINDOWS_STYLES = {'h1': '', + 'h2': '', + 'python': '', + 'bash': '', + 'warning': '', + 'correct': '', + 'fail': '', + 'bold': '', + 'underline': '', + 'end': ''} + +EMPTY_STYLES = {'h1': '', + 'h2': '', + 'python': '', + 'bash': '', + 'warning': '', + 'correct': '', + 'fail': '', + 'bold': '', + 'underline': '', + 'end': ''} + +if platform.system() in ['Linux', 'Darwin']: + STYLES = UNIX_STYLES +elif platform.system() == 'Windows': + STYLES = WINDOWS_STYLES +else: + STYLES = EMPTY_STYLES + class NeptuneException(Exception): pass -class Uninitialized(NeptuneException): +class NeptuneUninitializedException(NeptuneException): def __init__(self): - super(Uninitialized, self).__init__( - "You must initialize neptune-client first. " - "For more information, please visit: https://github.com/neptune-ai/neptune-client#initialize-neptune") + message = """ +{h1} +----NeptuneUninitializedException--------------------------------------------------------------------------------------- +{end} +You must initialize neptune-client before you create an experiment. + +Looks like you forgot to add: + {python}neptune.init(project_qualified_name='WORKSPACE_NAME/PROJECT_NAME', api_token='YOUR_API_TOKEN'){end} + +before you ran: + {python}neptune.create_experiment(){end} + +You may also want to check the following docs pages: + - https://docs.neptune.ai/getting-started/quick-starts/log_first_experiment.html + +{correct}Need help?{end}-> https://docs.neptune.ai/getting-started/getting-help.html +""".format(**STYLES) + super(NeptuneUninitializedException, self).__init__(message) class FileNotFound(NeptuneException): @@ -59,9 +116,24 @@ def __init__(self): super(NoChannelValue, self).__init__('No channel value provided.') -class LibraryNotInstalled(NeptuneException): +class NeptuneLibraryNotInstalledException(NeptuneException): def __init__(self, library): - super(LibraryNotInstalled, self).__init__("Library {} is not installed".format(library)) + message = """ +{h1} +----NeptuneLibraryNotInstalledException--------------------------------------------------------------------------------- +{end} +Looks like library {library} wasn't installed. + +To install run: + {bash}pip install {library}{end} + +You may also want to check the following docs pages: + - https://docs.neptune.ai/getting-started/installation/index.html + +{correct}Need help?{end}-> https://docs.neptune.ai/getting-started/getting-help.html +""" + inputs = dict(list({'library': library}.items()) + list(STYLES.items())) + super(NeptuneLibraryNotInstalledException, self).__init__(message.format(**inputs)) class InvalidChannelValue(NeptuneException): @@ -71,32 +143,135 @@ def __init__(self, expected_type, actual_type): expected=expected_type, actual=actual_type)) -class NoExperimentContext(NeptuneException): +class NeptuneNoExperimentContextException(NeptuneException): def __init__(self): - super(NoExperimentContext, self).__init__('Unable to find current active experiment') + message = """ +{h1} +----NeptuneNoExperimentContextException--------------------------------------------------------------------------------- +{end} +Neptune couldn't find an active experiment. +Looks like you forgot to run: + {python}neptune.create_experiment(){end} + +You may also want to check the following docs pages: + - https://docs.neptune.ai/getting-started/quick-starts/log_first_experiment.html -class MissingApiToken(NeptuneException): - def __init__(self): - super(MissingApiToken, self).__init__('Missing API token. Use "{}" environment ' - 'variable or pass it as an argument to neptune.init. ' - 'Open this link to get your API token ' - 'https://ui.neptune.ai/get_my_api_token' - .format(envs.API_TOKEN_ENV_NAME)) +{correct}Need help?{end}-> https://docs.neptune.ai/getting-started/getting-help.html +""" + super(NeptuneNoExperimentContextException, self).__init__(message.format(**STYLES)) -class MissingProjectQualifiedName(NeptuneException): +class NeptuneMissingApiTokenException(NeptuneException): def __init__(self): - super(MissingProjectQualifiedName, self).__init__('Missing project qualified name. Use "{}" environment ' - 'variable or pass it as an argument' - .format(envs.PROJECT_ENV_NAME)) + message = """ +{h1} +----NeptuneMissingApiTokenException------------------------------------------------------------------------------------- +{end} +Neptune client couldn't find your API token. + +Learn how to get it in this docs page: +https://docs.neptune.ai/security-and-privacy/api-tokens/how-to-find-and-set-neptune-api-token.html + +There are two options to add it: + - specify it in your code + - set an environment variable in your operating system. + +{h2}CODE{end} +Pass the token to {bold}neptune.init(){end} via {bold}api_token{end} argument: + {python}neptune.init(project_qualified_name='WORKSPACE_NAME/PROJECT_NAME', api_token='YOUR_API_TOKEN'){end} + +{h2}ENVIRONMENT VARIABLE{end} {correct}(Recommended option){end} +or export or set an environment variable depending on your operating system: + + {correct}Linux/Unix{end} + In your terminal run: + {bash}export {env_api_token}=YOUR_API_TOKEN{end} + + {correct}Windows{end} + In your CMD run: + {bash}set {env_api_token}=YOUR_API_TOKEN{end} + +and skip the {bold}api_token{end} argument of {bold}neptune.init(){end}: + {python}neptune.init(project_qualified_name='WORKSPACE_NAME/PROJECT_NAME'){end} + +You may also want to check the following docs pages: + - https://docs.neptune.ai/security-and-privacy/api-tokens/how-to-find-and-set-neptune-api-token.html + - https://docs.neptune.ai/getting-started/quick-starts/log_first_experiment.html + +{correct}Need help?{end}-> https://docs.neptune.ai/getting-started/getting-help.html +""" + inputs = dict(list({'env_api_token': envs.API_TOKEN_ENV_NAME}.items()) + list(STYLES.items())) + super(NeptuneMissingApiTokenException, self).__init__(message.format(**inputs)) + + +class NeptuneMissingProjectQualifiedNameException(NeptuneException): + def __init__(self): + message = """ +{h1} +----NeptuneMissingProjectQualifiedNameException------------------------------------------------------------------------- +{end} +Neptune client couldn't find your project name. + +There are two options two add it: + - specify it in your code + - set an environment variable in your operating system. + +{h2}CODE{end} +Pass it to {bold}neptune.init(){end} via {bold}project_qualified_name{end} argument: + {python}neptune.init(project_qualified_name='WORKSPACE_NAME/PROJECT_NAME', api_token='YOUR_API_TOKEN'){end} + +{h2}ENVIRONMENT VARIABLE{end} +or export or set an environment variable depending on your operating system: + + {correct}Linux/Unix{end} + In your terminal run: + {bash}export {env_project}=WORKSPACE_NAME/PROJECT_NAME{end} + {correct}Windows{end} + In your CMD run: + {bash}set {env_project}=WORKSPACE_NAME/PROJECT_NAME{end} -class IncorrectProjectQualifiedName(NeptuneException): +and skip the {bold}project_qualified_name{end} argument of {bold}neptune.init(){end}: + {python}neptune.init(api_token='YOUR_API_TOKEN'){end} + +You may also want to check the following docs pages: + - https://docs.neptune.ai/workspace-project-and-user-management/index.html + - https://docs.neptune.ai/getting-started/quick-starts/log_first_experiment.html + +{correct}Need help?{end}-> https://docs.neptune.ai/getting-started/getting-help.html +""" + inputs = dict(list({'env_project': envs.PROJECT_ENV_NAME}.items()) + list(STYLES.items())) + super(NeptuneMissingProjectQualifiedNameException, self).__init__(message.format(**inputs)) + + +class NeptuneIncorrectProjectQualifiedNameException(NeptuneException): def __init__(self, project_qualified_name): - super(IncorrectProjectQualifiedName, self).__init__('Incorrect project qualified name "{}". ' - 'Should be in format "namespace/project_name".' - .format(project_qualified_name)) + message = """ +{h1} +----NeptuneIncorrectProjectQualifiedNameException----------------------------------------------------------------------- +{end} +Project qualified name {fail}"{project_qualified_name}"{end} you specified was incorrect. + +The correct project qualified name should look like this {correct}WORKSPACE/PROJECT_NAME{end}. +It has two parts: + - {correct}WORKSPACE{end}: which can be your username or your organization name + - {correct}PROJECT_NAME{end}: which is the actual project name you chose + +For example, a project {correct}neptune-ai/credit-default-prediction{end} parts are: + - {correct}neptune-ai{end}: {underline}WORKSPACE{end} our company organization name + - {correct}credit-default-prediction{end}: {underline}PROJECT_NAME{end} a project name + +The URL to this project looks like this: https://ui.neptune.ai/neptune-ai/credit-default-prediction + +You may also want to check the following docs pages: + - https://docs.neptune.ai/workspace-project-and-user-management/index.html + - https://docs.neptune.ai/getting-started/quick-starts/log_first_experiment.html + +{correct}Need help?{end}-> https://docs.neptune.ai/getting-started/getting-help.html +""" + inputs = dict(list({'project_qualified_name': project_qualified_name}.items()) + list(STYLES.items())) + super(NeptuneIncorrectProjectQualifiedNameException, self).__init__(message.format(**inputs)) class InvalidNeptuneBackend(NeptuneException): @@ -107,16 +282,19 @@ def __init__(self, provided_backend_name): 'e.g. using {}=offline allows you to run your code without logging anything to Neptune' .format(envs.BACKEND, provided_backend_name, envs.BACKEND)) + class DeprecatedApiToken(NeptuneException): def __init__(self, app_url): super(DeprecatedApiToken, self).__init__( "Your API token is deprecated. Please visit {} to get a new one.".format(app_url)) + 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__( diff --git a/neptune/internal/backends/credentials.py b/neptune/internal/backends/credentials.py index d19980203..5ed59bd93 100644 --- a/neptune/internal/backends/credentials.py +++ b/neptune/internal/backends/credentials.py @@ -21,7 +21,7 @@ from neptune import envs from neptune.api_exceptions import InvalidApiKey -from neptune.exceptions import MissingApiToken +from neptune.exceptions import NeptuneMissingApiTokenException _logger = logging.getLogger(__name__) @@ -66,7 +66,7 @@ def __init__(self, api_token=None): self._api_token = api_token if self.api_token is None: - raise MissingApiToken() + raise NeptuneMissingApiTokenException() token_dict = self._api_token_to_dict(self.api_token) self._token_origin_address = token_dict['api_address'] diff --git a/neptune/projects.py b/neptune/projects.py index b9b23a9dd..c28c3097d 100644 --- a/neptune/projects.py +++ b/neptune/projects.py @@ -27,7 +27,7 @@ import six from neptune.envs import NOTEBOOK_ID_ENV_NAME, NOTEBOOK_PATH_ENV_NAME -from neptune.exceptions import NoExperimentContext +from neptune.exceptions import NeptuneNoExperimentContextException from neptune.experiments import Experiment from neptune.internal.abort import DefaultAbortImpl from neptune.internal.notebooks.notebooks import create_checkpoint @@ -609,7 +609,7 @@ def _get_current_experiment(self): if self._experiments_stack: return self._experiments_stack[-1] else: - raise NoExperimentContext() + raise NeptuneNoExperimentContextException() def _push_new_experiment(self, new_experiment): with self.__lock: diff --git a/neptune/sessions.py b/neptune/sessions.py index 4bd0414f5..459d1c34f 100644 --- a/neptune/sessions.py +++ b/neptune/sessions.py @@ -20,7 +20,7 @@ from neptune.api_exceptions import ProjectNotFound from neptune.internal.backends.hosted_neptune_backend import HostedNeptuneBackend -from neptune.exceptions import IncorrectProjectQualifiedName +from neptune.exceptions import NeptuneIncorrectProjectQualifiedNameException from neptune.patterns import PROJECT_QUALIFIED_NAME_PATTERN from neptune.projects import Project @@ -165,7 +165,7 @@ def get_project(self, project_qualified_name): raise ProjectNotFound(project_qualified_name) if not re.match(PROJECT_QUALIFIED_NAME_PATTERN, project_qualified_name): - raise IncorrectProjectQualifiedName(project_qualified_name) + raise NeptuneIncorrectProjectQualifiedNameException(project_qualified_name) return self._backend.get_project(project_qualified_name) diff --git a/tests/neptune/test_project.py b/tests/neptune/test_project.py index 7b16d41af..4b38f77a6 100644 --- a/tests/neptune/test_project.py +++ b/tests/neptune/test_project.py @@ -24,7 +24,7 @@ from mock import MagicMock, patch from munch import Munch -from neptune.exceptions import NoExperimentContext +from neptune.exceptions import NeptuneNoExperimentContextException from neptune.experiments import Experiment from neptune.model import LeaderboardEntry from neptune.projects import Project @@ -220,7 +220,7 @@ def test_pop_experiment_from_stack(self): # pylint: disable=protected-access def test_empty_stack(self): # expect - with self.assertRaises(NoExperimentContext): + with self.assertRaises(NeptuneNoExperimentContextException): self.project._get_current_experiment() def test_create_experiment_with_relative_upload_sources(self):