From 03c7519eae351a146efec5850cc88108e178432f Mon Sep 17 00:00:00 2001 From: Rafal Jankowski Date: Mon, 18 Oct 2021 12:40:23 +0200 Subject: [PATCH 1/9] Sample --- neptune/management/internal/api.py | 24 +++++++++++++++----- neptune/management/internal/dto.py | 34 ++++++++++++++++++++++++++++ neptune/management/internal/types.py | 17 ++++++++++---- 3 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 neptune/management/internal/dto.py diff --git a/neptune/management/internal/api.py b/neptune/management/internal/api.py index d2a59236e..5eafe7ffa 100644 --- a/neptune/management/internal/api.py +++ b/neptune/management/internal/api.py @@ -15,6 +15,7 @@ # import re import os +import warnings from typing import Optional, List, Dict from bravado.client import SwaggerClient @@ -43,6 +44,7 @@ BadRequestException, ProjectsLimitReached, ) +from neptune.management.internal.dto import ProjectMemberRoleDTO def _get_token(api_token: Optional[str] = None) -> str: @@ -84,14 +86,14 @@ def create_project( name: str, key: str, workspace: Optional[str] = None, - visibility: ProjectVisibility = ProjectVisibility.PRIVATE, + visibility: str = ProjectVisibility.PRIVATE, description: Optional[str] = None, api_token: Optional[str] = None ) -> str: verify_type('name', name, str) verify_type('key', key, str) verify_type('workspace', workspace, (str, type(None))) - verify_type('visibility', visibility, ProjectVisibility) + verify_type('visibility', visibility, str) verify_type('description', description, (str, type(None))) verify_type('api_token', api_token, (str, type(None))) @@ -116,7 +118,7 @@ def create_project( 'description': description, 'projectKey': key, 'organizationId': workspace_name_to_id[workspace], - 'visibility': visibility.value + 'visibility': visibility }, **DEFAULT_REQUEST_KWARGS } @@ -159,16 +161,26 @@ def delete_project(name: str, workspace: Optional[str] = None, api_token: Option def add_project_member( name: str, username: str, - role: MemberRole, + role: str, workspace: Optional[str] = None, api_token: Optional[str] = None ): verify_type('name', name, str) verify_type('username', username, str) - verify_type('role', role, MemberRole) + verify_type('role', role, str) verify_type('workspace', workspace, (str, type(None))) verify_type('api_token', api_token, (str, type(None))) + __DEPRECATED_ROLES__ = { + ProjectMemberRole.MEMBER: ProjectMemberRole.CONTRIBUTOR, + ProjectMemberRole.MANAGER: ProjectMemberRole.OWNER + } + if role in __DEPRECATED_ROLES__: + warnings.warn( + f"The role '{role}' was renamed to '{__DEPRECATED_ROLES__.get(role)}'", + DeprecationWarning) + role = __DEPRECATED_ROLES__.get(role, role) + backend_client = _get_backend_client(api_token=api_token) project_identifier = normalize_project_name(name=name, workspace=workspace) @@ -176,7 +188,7 @@ def add_project_member( 'projectIdentifier': project_identifier, 'member': { 'userId': username, - 'role': role.value + 'role': ProjectMemberRoleDTO.from_str(role).value }, **DEFAULT_REQUEST_KWARGS } diff --git a/neptune/management/internal/dto.py b/neptune/management/internal/dto.py new file mode 100644 index 000000000..ab3017f36 --- /dev/null +++ b/neptune/management/internal/dto.py @@ -0,0 +1,34 @@ +# +# Copyright (c) 2021, Neptune Labs Sp. z o.o. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from enum import Enum + +from neptune.new.internal.utils import verify_type + + +class ProjectMemberRoleDTO(Enum): + VIEWER = 'viewer' + MEMBER = 'member' + MANAGER = 'manager' + + @staticmethod + def from_str(role: str): + verify_type('role', role, str) + + return { + 'viewer': ProjectMemberRoleDTO.VIEWER, + 'contributor': ProjectMemberRoleDTO.MEMBER, + 'owner': ProjectMemberRoleDTO.MANAGER + }.get(role) diff --git a/neptune/management/internal/types.py b/neptune/management/internal/types.py index 6514d394a..90c43d957 100644 --- a/neptune/management/internal/types.py +++ b/neptune/management/internal/types.py @@ -13,15 +13,22 @@ # See the License for the specific language governing permissions and # limitations under the License. # -from enum import Enum - - -class ProjectVisibility(Enum): +class ProjectVisibility: PRIVATE = 'priv' PUBLIC = 'pub' -class MemberRole(Enum): +class ProjectMemberRole: VIEWER = 'viewer' MEMBER = 'member' MANAGER = 'manager' + OWNER = 'owner' + CONTRIBUTOR = 'contributor' + + __DEPRECATED_ATTRIBUTES__ = { + MEMBER: CONTRIBUTOR, + MANAGER: OWNER + } + + +MemberRole = ProjectMemberRole From 4524026e040789d6f6eb7f47924f1c6b5dec7a98 Mon Sep 17 00:00:00 2001 From: Rafal Jankowski Date: Mon, 18 Oct 2021 12:45:07 +0200 Subject: [PATCH 2/9] Deprecation moved to DTO --- neptune/management/internal/api.py | 10 ---------- neptune/management/internal/dto.py | 10 ++++++++++ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/neptune/management/internal/api.py b/neptune/management/internal/api.py index 5eafe7ffa..4e0a1b047 100644 --- a/neptune/management/internal/api.py +++ b/neptune/management/internal/api.py @@ -171,16 +171,6 @@ def add_project_member( verify_type('workspace', workspace, (str, type(None))) verify_type('api_token', api_token, (str, type(None))) - __DEPRECATED_ROLES__ = { - ProjectMemberRole.MEMBER: ProjectMemberRole.CONTRIBUTOR, - ProjectMemberRole.MANAGER: ProjectMemberRole.OWNER - } - if role in __DEPRECATED_ROLES__: - warnings.warn( - f"The role '{role}' was renamed to '{__DEPRECATED_ROLES__.get(role)}'", - DeprecationWarning) - role = __DEPRECATED_ROLES__.get(role, role) - backend_client = _get_backend_client(api_token=api_token) project_identifier = normalize_project_name(name=name, workspace=workspace) diff --git a/neptune/management/internal/dto.py b/neptune/management/internal/dto.py index ab3017f36..bae1affed 100644 --- a/neptune/management/internal/dto.py +++ b/neptune/management/internal/dto.py @@ -27,6 +27,16 @@ class ProjectMemberRoleDTO(Enum): def from_str(role: str): verify_type('role', role, str) + __DEPRECATED_ROLES__ = { + ProjectMemberRole.MEMBER: ProjectMemberRole.CONTRIBUTOR, + ProjectMemberRole.MANAGER: ProjectMemberRole.OWNER + } + if role in __DEPRECATED_ROLES__: + warnings.warn( + f"The role '{role}' was renamed to '{__DEPRECATED_ROLES__.get(role)}'", + DeprecationWarning) + role = __DEPRECATED_ROLES__.get(role, role) + return { 'viewer': ProjectMemberRoleDTO.VIEWER, 'contributor': ProjectMemberRoleDTO.MEMBER, From 5f4e1d790af525cf07195fed3b3860950163ae13 Mon Sep 17 00:00:00 2001 From: Rafal Jankowski Date: Mon, 18 Oct 2021 13:03:19 +0200 Subject: [PATCH 3/9] And back --- neptune/management/internal/api.py | 10 ++++++++++ neptune/management/internal/dto.py | 10 ---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/neptune/management/internal/api.py b/neptune/management/internal/api.py index 4e0a1b047..5eafe7ffa 100644 --- a/neptune/management/internal/api.py +++ b/neptune/management/internal/api.py @@ -171,6 +171,16 @@ def add_project_member( verify_type('workspace', workspace, (str, type(None))) verify_type('api_token', api_token, (str, type(None))) + __DEPRECATED_ROLES__ = { + ProjectMemberRole.MEMBER: ProjectMemberRole.CONTRIBUTOR, + ProjectMemberRole.MANAGER: ProjectMemberRole.OWNER + } + if role in __DEPRECATED_ROLES__: + warnings.warn( + f"The role '{role}' was renamed to '{__DEPRECATED_ROLES__.get(role)}'", + DeprecationWarning) + role = __DEPRECATED_ROLES__.get(role, role) + backend_client = _get_backend_client(api_token=api_token) project_identifier = normalize_project_name(name=name, workspace=workspace) diff --git a/neptune/management/internal/dto.py b/neptune/management/internal/dto.py index bae1affed..ab3017f36 100644 --- a/neptune/management/internal/dto.py +++ b/neptune/management/internal/dto.py @@ -27,16 +27,6 @@ class ProjectMemberRoleDTO(Enum): def from_str(role: str): verify_type('role', role, str) - __DEPRECATED_ROLES__ = { - ProjectMemberRole.MEMBER: ProjectMemberRole.CONTRIBUTOR, - ProjectMemberRole.MANAGER: ProjectMemberRole.OWNER - } - if role in __DEPRECATED_ROLES__: - warnings.warn( - f"The role '{role}' was renamed to '{__DEPRECATED_ROLES__.get(role)}'", - DeprecationWarning) - role = __DEPRECATED_ROLES__.get(role, role) - return { 'viewer': ProjectMemberRoleDTO.VIEWER, 'contributor': ProjectMemberRoleDTO.MEMBER, From 530d18206d22bc9013fc9075006a0b618c136864 Mon Sep 17 00:00:00 2001 From: Rafal Jankowski Date: Mon, 18 Oct 2021 15:43:18 +0200 Subject: [PATCH 4/9] Added more DTOs --- neptune/management/internal/api.py | 21 ++------- neptune/management/internal/dto.py | 46 +++++++++++++++++-- neptune/management/internal/types.py | 14 +++--- .../internal/backends/test_hosted_client.py | 12 +++-- 4 files changed, 64 insertions(+), 29 deletions(-) diff --git a/neptune/management/internal/api.py b/neptune/management/internal/api.py index 5eafe7ffa..ecf984991 100644 --- a/neptune/management/internal/api.py +++ b/neptune/management/internal/api.py @@ -15,7 +15,6 @@ # import re import os -import warnings from typing import Optional, List, Dict from bravado.client import SwaggerClient @@ -44,7 +43,7 @@ BadRequestException, ProjectsLimitReached, ) -from neptune.management.internal.dto import ProjectMemberRoleDTO +from neptune.management.internal.dto import ProjectVisibilityDTO, ProjectMemberRoleDTO, WorkspaceMemberRoleDTO def _get_token(api_token: Optional[str] = None) -> str: @@ -118,7 +117,7 @@ def create_project( 'description': description, 'projectKey': key, 'organizationId': workspace_name_to_id[workspace], - 'visibility': visibility + 'visibility': ProjectVisibilityDTO.from_str(visibility) }, **DEFAULT_REQUEST_KWARGS } @@ -171,16 +170,6 @@ def add_project_member( verify_type('workspace', workspace, (str, type(None))) verify_type('api_token', api_token, (str, type(None))) - __DEPRECATED_ROLES__ = { - ProjectMemberRole.MEMBER: ProjectMemberRole.CONTRIBUTOR, - ProjectMemberRole.MANAGER: ProjectMemberRole.OWNER - } - if role in __DEPRECATED_ROLES__: - warnings.warn( - f"The role '{role}' was renamed to '{__DEPRECATED_ROLES__.get(role)}'", - DeprecationWarning) - role = __DEPRECATED_ROLES__.get(role, role) - backend_client = _get_backend_client(api_token=api_token) project_identifier = normalize_project_name(name=name, workspace=workspace) @@ -188,7 +177,7 @@ def add_project_member( 'projectIdentifier': project_identifier, 'member': { 'userId': username, - 'role': ProjectMemberRoleDTO.from_str(role).value + 'role': ProjectMemberRoleDTO.from_str(role) }, **DEFAULT_REQUEST_KWARGS } @@ -221,7 +210,7 @@ def get_project_member_list( try: result = backend_client.api.listProjectMembers(**params).response().result - return {f'{m.registeredMemberInfo.username}': m.role for m in result} + return {f'{m.registeredMemberInfo.username}': ProjectMemberRoleDTO.to_domain(m.role) for m in result} except HTTPNotFound as e: raise ProjectNotFound(name=project_identifier) from e @@ -271,6 +260,6 @@ def get_workspace_member_list(name: str, api_token: Optional[str] = None) -> Dic try: result = backend_client.api.listOrganizationMembers(**params).response().result - return {f'{m.registeredMemberInfo.username}': m.role for m in result} + return {f'{m.registeredMemberInfo.username}': WorkspaceMemberRoleDTO.to_domain(m.role) for m in result} except HTTPNotFound as e: raise WorkspaceNotFound(workspace=name) from e diff --git a/neptune/management/internal/dto.py b/neptune/management/internal/dto.py index ab3017f36..82999fe52 100644 --- a/neptune/management/internal/dto.py +++ b/neptune/management/internal/dto.py @@ -17,6 +17,22 @@ from neptune.new.internal.utils import verify_type +from neptune.management.internal.types import ProjectVisibility, ProjectMemberRole, WorkspaceMemberRole + + +class ProjectVisibilityDTO(Enum): + PRIVATE = 'priv' + PUBLIC = 'pub' + + @staticmethod + def from_str(visibility: str) -> str: + verify_type('visibility', visibility, str) + + return { + ProjectVisibility.PRIVATE: ProjectVisibilityDTO.PRIVATE, + ProjectVisibility.PUBLIC: ProjectVisibilityDTO.PUBLIC + }.get(visibility).value + class ProjectMemberRoleDTO(Enum): VIEWER = 'viewer' @@ -24,11 +40,33 @@ class ProjectMemberRoleDTO(Enum): MANAGER = 'manager' @staticmethod - def from_str(role: str): + def from_str(role: str) -> str: verify_type('role', role, str) return { - 'viewer': ProjectMemberRoleDTO.VIEWER, - 'contributor': ProjectMemberRoleDTO.MEMBER, - 'owner': ProjectMemberRoleDTO.MANAGER + ProjectMemberRole.VIEWER: ProjectMemberRoleDTO.VIEWER, + ProjectMemberRole.CONTRIBUTOR: ProjectMemberRoleDTO.MEMBER, + ProjectMemberRole.OWNER: ProjectMemberRoleDTO.MANAGER + }.get(role).value + + @staticmethod + def to_domain(role: str) -> str: + verify_type('role', role, str) + + return { + ProjectMemberRoleDTO.VIEWER.value: ProjectMemberRole.VIEWER, + ProjectMemberRoleDTO.MANAGER.value: ProjectMemberRole.OWNER, + ProjectMemberRoleDTO.MEMBER.value: ProjectMemberRole.CONTRIBUTOR + }.get(role) + + +class WorkspaceMemberRoleDTO(Enum): + OWNER = 'owner' + MEMBER = 'member' + + @staticmethod + def to_domain(role: str) -> str: + return { + WorkspaceMemberRoleDTO.OWNER.value: WorkspaceMemberRole.ADMIN, + WorkspaceMemberRoleDTO.MEMBER.value: WorkspaceMemberRole.MEMBER }.get(role) diff --git a/neptune/management/internal/types.py b/neptune/management/internal/types.py index 90c43d957..360a93ed9 100644 --- a/neptune/management/internal/types.py +++ b/neptune/management/internal/types.py @@ -20,15 +20,17 @@ class ProjectVisibility: class ProjectMemberRole: VIEWER = 'viewer' - MEMBER = 'member' - MANAGER = 'manager' OWNER = 'owner' CONTRIBUTOR = 'contributor' - __DEPRECATED_ATTRIBUTES__ = { - MEMBER: CONTRIBUTOR, - MANAGER: OWNER - } + # Deprecated + MEMBER = CONTRIBUTOR + MANAGER = OWNER MemberRole = ProjectMemberRole + + +class WorkspaceMemberRole: + ADMIN = 'admin' + MEMBER = 'member' diff --git a/tests/neptune/new/internal/backends/test_hosted_client.py b/tests/neptune/new/internal/backends/test_hosted_client.py index 55176b5fd..efac16741 100644 --- a/tests/neptune/new/internal/backends/test_hosted_client.py +++ b/tests/neptune/new/internal/backends/test_hosted_client.py @@ -127,7 +127,7 @@ def test_workspace_members(self, swagger_client_factory): ) ), Mock( - role='admin', + role='owner', registeredMemberInfo=Mock( username='tester2' ) @@ -180,10 +180,16 @@ def test_project_members(self, swagger_client_factory): ) ), Mock( - role='admin', + role='manager', registeredMemberInfo=Mock( username='tester2' ) + ), + Mock( + role='viewer', + registeredMemberInfo=Mock( + username='tester3' + ) ) ] swagger_client.api.listProjectMembers.return_value.response = BravadoResponseMock( @@ -194,7 +200,7 @@ def test_project_members(self, swagger_client_factory): returned_members = get_project_member_list(name='org/proj', api_token=API_TOKEN) # then: - self.assertEqual({'tester1': 'member', 'tester2': 'admin'}, returned_members) + self.assertEqual({'tester1': 'contributor', 'tester2': 'owner', 'tester3': 'viewer'}, returned_members) def test_project_members_empty(self, swagger_client_factory): swagger_client = self._get_swagger_client_mock(swagger_client_factory) From 4596a7949518bd60fb8ee4d7b71888394f9f7a0a Mon Sep 17 00:00:00 2001 From: Jakub Czakon Date: Mon, 18 Oct 2021 16:17:05 +0200 Subject: [PATCH 5/9] added fixed docstirngs --- neptune/management/internal/api.py | 157 +++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/neptune/management/internal/api.py b/neptune/management/internal/api.py index ecf984991..e36cfb345 100644 --- a/neptune/management/internal/api.py +++ b/neptune/management/internal/api.py @@ -65,6 +65,22 @@ def _get_backend_client(api_token: Optional[str] = None) -> SwaggerClient: @with_api_exceptions_handler def get_project_list(api_token: Optional[str] = None) -> List[str]: + """Get a list of projects you have access to. + Args: + api_token(str, optional): User’s API token. Defaults to `None`. + If `None`, the value of `NEPTUNE_API_TOKEN` environment variable will be taken. + .. note:: + It is strongly recommended to use `NEPTUNE_API_TOKEN` environment variable rather than placing your + API token in plain text in your source code. + Returns: + ``List[str]``: list of project names of a format 'WORKSPACE/PROJECT' + Examples: + >>> from neptune import management + >>> management.get_project_list() + You may also want to check `management API reference`_. + .. _management API reference: + https://docs.neptune.ai/api-reference/management + """ verify_type('api_token', api_token, (str, type(None))) backend_client = _get_backend_client(api_token=api_token) @@ -89,6 +105,38 @@ def create_project( description: Optional[str] = None, api_token: Optional[str] = None ) -> str: + """Creates a new project in your Neptune workspace. + Args: + name(str): The name of the project in Neptune in the format 'WORKSPACE/PROJECT'. + If workspace argument was set, it should only contain 'PROJECT' instead of 'WORKSPACE/PROJECT'. + key(str): Project identifier. It has to be contain 1-10 upper case letters or numbers. + For example, 'GOOD5' + workspace(str, optional): Name of your Neptune workspace. + If you specify it, change the format of the name argument to 'PROJECT' instead of 'WORKSPACE/PROJECT'. + If 'None' it will be parsed from the `name` argument. + visibility(str, optional): level of visibility you want your project to have. + Can be set to: + - 'pub' for public projects + - 'priv' for private projects + If 'None' it will be set to 'priv' + description(str, optional): Project description. + If 'None', it will be left empty. + api_token(str, optional): User’s API token. Defaults to `None`. + If `None`, the value of `NEPTUNE_API_TOKEN` environment variable will be taken. + .. note:: + It is strongly recommended to use `NEPTUNE_API_TOKEN` environment variable rather than placing your + API token in plain text in your source code. + Returns: + ``str``: name of the new project you created. + Examples: + >>> from neptune import management + >>> management.create_project(name="awesome-team/amazing-project", + ... key="AMA", + ... visibility='pub') + You may also want to check `management API reference`_. + .. _management API reference: + https://docs.neptune.ai/api-reference/management + """ verify_type('name', name, str) verify_type('key', key, str) verify_type('workspace', workspace, (str, type(None))) @@ -136,6 +184,25 @@ def create_project( @with_api_exceptions_handler def delete_project(name: str, workspace: Optional[str] = None, api_token: Optional[str] = None): + """Deletes a project from your Neptune workspace. + Args: + name(str): The name of the project in Neptune in the format 'WORKSPACE/PROJECT'. + If workspace argument was set, it should only contain 'PROJECT' instead of 'WORKSPACE/PROJECT'. + workspace(str, optional): Name of your Neptune workspace. + If you specify it, change the format of the name argument to 'PROJECT' instead of 'WORKSPACE/PROJECT'. + If 'None' it will be parsed from the name argument. + api_token(str, optional): User’s API token. Defaults to `None`. + If `None`, the value of `NEPTUNE_API_TOKEN` environment variable will be taken. + .. note:: + It is strongly recommended to use `NEPTUNE_API_TOKEN` environment variable rather than placing your + API token in plain text in your source code. + Examples: + >>> from neptune import management + >>> management.delete_project(name="awesome-team/amazing-project") + You may also want to check `management API reference`_. + .. _management API reference: + https://docs.neptune.ai/api-reference/management + """ verify_type('name', name, str) verify_type('workspace', workspace, (str, type(None))) verify_type('api_token', api_token, (str, type(None))) @@ -164,6 +231,36 @@ def add_project_member( workspace: Optional[str] = None, api_token: Optional[str] = None ): + """Adds member to the Neptune project. + Args: + name(str): The name of the project in Neptune in the format 'WORKSPACE/PROJECT'. + If workspace argument was set, it should only contain 'PROJECT' instead of 'WORKSPACE/PROJECT'. + username(str): Name of the user you want to add to the project. + role(str): level of permissions the user should have in a project. + Can be set to: + - 'viewer': can only view project content and members + - 'contributor': can view and edit project content and only view members + - 'owner': can view and edit project content and members + For more information, see `user roles in a project docs`_. + workspace(str, optional): Name of your Neptune workspace. + If you specify it, change the format of the name argument to 'PROJECT' instead of 'WORKSPACE/PROJECT'. + If 'None' it will be parsed from the name argument. + api_token(str, optional): User’s API token. Defaults to `None`. + If `None`, the value of `NEPTUNE_API_TOKEN` environment variable will be taken. + .. note:: + It is strongly recommended to use `NEPTUNE_API_TOKEN` environment variable rather than placing your + API token in plain text in your source code. + Examples: + >>> from neptune import management + >>> management.add_project_member(name="awesome-team/amazing-project", + ... username="johny", + ... role='contributor') + You may also want to check `management API reference`_. + .. _management API reference: + https://docs.neptune.ai/api-reference/management + .. _user roles in a project docs: + https://docs.neptune.ai/administration/user-management#roles-in-a-project + """ verify_type('name', name, str) verify_type('username', username, str) verify_type('role', role, str) @@ -196,6 +293,27 @@ def get_project_member_list( workspace: Optional[str] = None, api_token: Optional[str] = None ) -> Dict[str, str]: + """Get a list of members for a project. + Args: + name(str): The name of the project in Neptune in the format 'WORKSPACE/PROJECT'. + If workspace argument was set it should only contain 'PROJECT' instead of 'WORKSPACE/PROJECT'. + workspace(str, optional): Name of your Neptune workspace. + If you specify change the format of the name argument to 'PROJECT' instead of 'WORKSPACE/PROJECT'. + If 'None' it will be parsed from the name argument. + api_token(str, optional): User’s API token. Defaults to `None`. + If `None`, the value of `NEPTUNE_API_TOKEN` environment variable will be taken. + .. note:: + It is strongly recommended to use `NEPTUNE_API_TOKEN` environment variable rather than placing your + API token in plain text in your source code. + Returns: + ``Dict[str, str]``: Dictionary with usernames as keys and MemberRoles ('owner', 'member', 'viewer') as values. + Examples: + >>> from neptune import management + >>> management.get_project_member_list(name="awesome-team/amazing-project") + You may also want to check `management API reference`_. + .. _management API reference: + https://docs.neptune.ai/api-reference/management + """ verify_type('name', name, str) verify_type('workspace', workspace, (str, type(None))) verify_type('api_token', api_token, (str, type(None))) @@ -222,6 +340,27 @@ def remove_project_member( workspace: Optional[str] = None, api_token: Optional[str] = None ): + """Removes member from the Neptune project. + Args: + name(str): The name of the project in Neptune in the format 'WORKSPACE/PROJECT'. + If workspace argument was set, it should only contain 'PROJECT' instead of 'WORKSPACE/PROJECT'. + username(str): name of the user you want to remove from the project. + workspace(str, optional): Name of your Neptune workspace. + If you specify change the format of the name argument to 'PROJECT' instead of 'WORKSPACE/PROJECT'. + If 'None' it will be parsed from the name argument. + api_token(str, optional): User’s API token. Defaults to `None`. + If `None`, the value of `NEPTUNE_API_TOKEN` environment variable will be taken. + .. note:: + It is strongly recommended to use `NEPTUNE_API_TOKEN` environment variable rather than placing your + API token in plain text in your source code. + Examples: + >>> from neptune import management + >>> management.remove_project_member(name="awesome-team/amazing-project", + ... username="johny") + You may also want to check `management API reference`_. + .. _management API reference: + https://docs.neptune.ai/api-reference/management + """ verify_type('name', name, str) verify_type('username', username, str) verify_type('workspace', workspace, (str, type(None))) @@ -248,6 +387,24 @@ def remove_project_member( @with_api_exceptions_handler def get_workspace_member_list(name: str, api_token: Optional[str] = None) -> Dict[str, str]: + """Get a list of members of a workspace. + Args: + name(str, optional): Name of your Neptune workspace. + api_token(str, optional): User’s API token. Defaults to `None`. + If `None`, the value of `NEPTUNE_API_TOKEN` environment variable will be taken. + .. note:: + It is strongly recommended to use `NEPTUNE_API_TOKEN` environment variable rather than placing your + API token in plain text in your source code. + Returns: + ``Dict[str, str]``: Dictionary with usernames as keys and MemberRoles as values. + For example, {'johny':'owner', 'kate':'contributor', 'mark':'viewer'}. + Examples: + >>> from neptune import management + >>> management.get_workspace_member_list(name="awesome-team") + You may also want to check `management API reference`_. + .. _management API reference: + https://docs.neptune.ai/api-reference/management + """ verify_type('name', name, str) verify_type('api_token', api_token, (str, type(None))) From b42e3aa36b9e23359550e0008540261a4982c29b Mon Sep 17 00:00:00 2001 From: Jakub Czakon Date: Mon, 18 Oct 2021 16:18:54 +0200 Subject: [PATCH 6/9] fixed formatting --- neptune/management/internal/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neptune/management/internal/api.py b/neptune/management/internal/api.py index e36cfb345..7b02c7c9f 100644 --- a/neptune/management/internal/api.py +++ b/neptune/management/internal/api.py @@ -132,7 +132,7 @@ def create_project( >>> from neptune import management >>> management.create_project(name="awesome-team/amazing-project", ... key="AMA", - ... visibility='pub') + ... visibility="pub") You may also want to check `management API reference`_. .. _management API reference: https://docs.neptune.ai/api-reference/management @@ -254,7 +254,7 @@ def add_project_member( >>> from neptune import management >>> management.add_project_member(name="awesome-team/amazing-project", ... username="johny", - ... role='contributor') + ... role="contributor") You may also want to check `management API reference`_. .. _management API reference: https://docs.neptune.ai/api-reference/management From d8a07c7686b32b1612e27f91dccab3d09f5e5fad Mon Sep 17 00:00:00 2001 From: Rafal Jankowski Date: Tue, 19 Oct 2021 11:06:11 +0200 Subject: [PATCH 7/9] After review --- neptune/management/exceptions.py | 5 ++++ neptune/management/internal/api.py | 4 +-- neptune/management/internal/dto.py | 29 ++++++++++++------- .../internal/backends/test_hosted_client.py | 28 ++++++++++++++++++ 4 files changed, 53 insertions(+), 13 deletions(-) diff --git a/neptune/management/exceptions.py b/neptune/management/exceptions.py index 595fc4a1f..79f94413f 100644 --- a/neptune/management/exceptions.py +++ b/neptune/management/exceptions.py @@ -96,6 +96,11 @@ class ProjectsLimitReached(ManagementOperationFailure): description = 'Project number limit reached.' +class UnsupportedValue(ManagementOperationFailure): + code = 12 + description = '{enum} cannot have value {value}' + + class BadRequestException(ManagementOperationFailure): code = 400 description = 'Your request has encountered following validation errors: {validation_errors}' diff --git a/neptune/management/internal/api.py b/neptune/management/internal/api.py index ecf984991..15dc0ccda 100644 --- a/neptune/management/internal/api.py +++ b/neptune/management/internal/api.py @@ -117,7 +117,7 @@ def create_project( 'description': description, 'projectKey': key, 'organizationId': workspace_name_to_id[workspace], - 'visibility': ProjectVisibilityDTO.from_str(visibility) + 'visibility': ProjectVisibilityDTO.from_str(visibility).value }, **DEFAULT_REQUEST_KWARGS } @@ -177,7 +177,7 @@ def add_project_member( 'projectIdentifier': project_identifier, 'member': { 'userId': username, - 'role': ProjectMemberRoleDTO.from_str(role) + 'role': ProjectMemberRoleDTO.from_str(role).value }, **DEFAULT_REQUEST_KWARGS } diff --git a/neptune/management/internal/dto.py b/neptune/management/internal/dto.py index 82999fe52..5108968a2 100644 --- a/neptune/management/internal/dto.py +++ b/neptune/management/internal/dto.py @@ -17,6 +17,7 @@ from neptune.new.internal.utils import verify_type +from neptune.management.exceptions import UnsupportedValue from neptune.management.internal.types import ProjectVisibility, ProjectMemberRole, WorkspaceMemberRole @@ -25,13 +26,16 @@ class ProjectVisibilityDTO(Enum): PUBLIC = 'pub' @staticmethod - def from_str(visibility: str) -> str: + def from_str(visibility: str) -> 'ProjectVisibilityDTO': verify_type('visibility', visibility, str) - return { - ProjectVisibility.PRIVATE: ProjectVisibilityDTO.PRIVATE, - ProjectVisibility.PUBLIC: ProjectVisibilityDTO.PUBLIC - }.get(visibility).value + try: + return { + ProjectVisibility.PRIVATE: ProjectVisibilityDTO.PRIVATE, + ProjectVisibility.PUBLIC: ProjectVisibilityDTO.PUBLIC + }[visibility] + except KeyError as e: + raise UnsupportedValue(enum=ProjectVisibilityDTO.__name__, value=visibility) from e class ProjectMemberRoleDTO(Enum): @@ -40,14 +44,17 @@ class ProjectMemberRoleDTO(Enum): MANAGER = 'manager' @staticmethod - def from_str(role: str) -> str: + def from_str(role: str) -> 'ProjectMemberRoleDTO': verify_type('role', role, str) - return { - ProjectMemberRole.VIEWER: ProjectMemberRoleDTO.VIEWER, - ProjectMemberRole.CONTRIBUTOR: ProjectMemberRoleDTO.MEMBER, - ProjectMemberRole.OWNER: ProjectMemberRoleDTO.MANAGER - }.get(role).value + try: + return { + ProjectMemberRole.VIEWER: ProjectMemberRoleDTO.VIEWER, + ProjectMemberRole.CONTRIBUTOR: ProjectMemberRoleDTO.MEMBER, + ProjectMemberRole.OWNER: ProjectMemberRoleDTO.MANAGER + }[role] + except KeyError as e: + raise UnsupportedValue(enum=ProjectMemberRoleDTO.__name__, value=role) from e @staticmethod def to_domain(role: str) -> str: diff --git a/tests/neptune/new/internal/backends/test_hosted_client.py b/tests/neptune/new/internal/backends/test_hosted_client.py index efac16741..8d3dbf238 100644 --- a/tests/neptune/new/internal/backends/test_hosted_client.py +++ b/tests/neptune/new/internal/backends/test_hosted_client.py @@ -38,6 +38,7 @@ UserNotExistsOrWithoutAccess, UserAlreadyHasAccess, AccessRevokedOnMemberRemoval, + UnsupportedValue, ) from neptune.new.internal.backends.utils import verify_host_resolution from neptune.new.internal.backends.hosted_client import ( @@ -279,6 +280,26 @@ def test_create_project_already_exists(self, swagger_client_factory): with self.assertRaises(ProjectAlreadyExists): create_project(name='org/proj', key='PRJ', api_token=API_TOKEN) + def test_create_project_unknown_visibility(self, swagger_client_factory): + swagger_client = self._get_swagger_client_mock(swagger_client_factory) + + # given: + organization = Mock( + id=str(uuid.uuid4()) + ) + organization.name = 'org' + organizations = [ + organization + ] + + # when: + swagger_client.api.listOrganizations.return_value.response = BravadoResponseMock( + result=organizations, + ) + + with self.assertRaises(UnsupportedValue): + create_project(name='org/proj', key='PRJ', visibility="unknown_value", api_token=API_TOKEN) + def test_create_project_no_workspace(self, swagger_client_factory): swagger_client = self._get_swagger_client_mock(swagger_client_factory) @@ -339,6 +360,13 @@ def test_add_project_member_project_not_found(self, swagger_client_factory): with self.assertRaises(ProjectNotFound): add_project_member(name='org/proj', username='tester', role=MemberRole.VIEWER, api_token=API_TOKEN) + def test_add_project_member_unknown_role(self, swagger_client_factory): + _ = self._get_swagger_client_mock(swagger_client_factory) + + # then: + with self.assertRaises(UnsupportedValue): + add_project_member(name='org/proj', username='tester', role='unknown_role', api_token=API_TOKEN) + def test_add_project_member_member_without_access(self, swagger_client_factory): swagger_client = self._get_swagger_client_mock(swagger_client_factory) From 6a94d3c35e9e58efb80da0425bc9854bbf390919 Mon Sep 17 00:00:00 2001 From: Rafal Jankowski Date: Tue, 19 Oct 2021 11:52:23 +0200 Subject: [PATCH 8/9] After review part 2 --- neptune/management/internal/dto.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/neptune/management/internal/dto.py b/neptune/management/internal/dto.py index 5108968a2..2fd2960eb 100644 --- a/neptune/management/internal/dto.py +++ b/neptune/management/internal/dto.py @@ -25,8 +25,8 @@ class ProjectVisibilityDTO(Enum): PRIVATE = 'priv' PUBLIC = 'pub' - @staticmethod - def from_str(visibility: str) -> 'ProjectVisibilityDTO': + @classmethod + def from_str(cls, visibility: str) -> 'ProjectVisibilityDTO': verify_type('visibility', visibility, str) try: @@ -35,7 +35,7 @@ def from_str(visibility: str) -> 'ProjectVisibilityDTO': ProjectVisibility.PUBLIC: ProjectVisibilityDTO.PUBLIC }[visibility] except KeyError as e: - raise UnsupportedValue(enum=ProjectVisibilityDTO.__name__, value=visibility) from e + raise UnsupportedValue(enum=cls.__name__, value=visibility) from e class ProjectMemberRoleDTO(Enum): @@ -43,8 +43,8 @@ class ProjectMemberRoleDTO(Enum): MEMBER = 'member' MANAGER = 'manager' - @staticmethod - def from_str(role: str) -> 'ProjectMemberRoleDTO': + @classmethod + def from_str(cls, role: str) -> 'ProjectMemberRoleDTO': verify_type('role', role, str) try: @@ -54,7 +54,7 @@ def from_str(role: str) -> 'ProjectMemberRoleDTO': ProjectMemberRole.OWNER: ProjectMemberRoleDTO.MANAGER }[role] except KeyError as e: - raise UnsupportedValue(enum=ProjectMemberRoleDTO.__name__, value=role) from e + raise UnsupportedValue(enum=cls.__name__, value=role) from e @staticmethod def to_domain(role: str) -> str: From 641004b8286af9aeaa7c6a2af768d94e1404c147 Mon Sep 17 00:00:00 2001 From: Jakub Czakon Date: Tue, 19 Oct 2021 14:13:48 +0200 Subject: [PATCH 9/9] updated MemberRole to ProjectMemberRole and WorkspaceMemberRole --- neptune/management/internal/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/neptune/management/internal/api.py b/neptune/management/internal/api.py index 1c18d3626..1263f8141 100644 --- a/neptune/management/internal/api.py +++ b/neptune/management/internal/api.py @@ -306,7 +306,8 @@ def get_project_member_list( It is strongly recommended to use `NEPTUNE_API_TOKEN` environment variable rather than placing your API token in plain text in your source code. Returns: - ``Dict[str, str]``: Dictionary with usernames as keys and MemberRoles ('owner', 'member', 'viewer') as values. + ``Dict[str, str]``: Dictionary with usernames as keys and ProjectMemberRoles + ('owner', 'contributor', 'viewer') as values. Examples: >>> from neptune import management >>> management.get_project_member_list(name="awesome-team/amazing-project") @@ -396,8 +397,7 @@ def get_workspace_member_list(name: str, api_token: Optional[str] = None) -> Dic It is strongly recommended to use `NEPTUNE_API_TOKEN` environment variable rather than placing your API token in plain text in your source code. Returns: - ``Dict[str, str]``: Dictionary with usernames as keys and MemberRoles as values. - For example, {'johny':'owner', 'kate':'contributor', 'mark':'viewer'}. + ``Dict[str, str]``: Dictionary with usernames as keys and `WorkspaceMemberRole` ('member', 'admin') as values. Examples: >>> from neptune import management >>> management.get_workspace_member_list(name="awesome-team")