Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User inputs validation #44

Merged
merged 1 commit into from
Aug 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions primehub/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,8 @@ def _ensure_config_details(self, config: PrimeHubConfig):
except BaseException:
pass

def request(self, variables: dict, query: str):
return Client(self.primehub_config).request(variables, query)
def request(self, variables: dict, query: str, error_handler: Callable = None):
return Client(self.primehub_config).request(variables, query, error_handler)

def request_logs(self, endpint: str, follow: bool, tail: int):
return Client(self.primehub_config).request_logs(endpint, follow, tail)
Expand Down
9 changes: 8 additions & 1 deletion primehub/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import primehub as ph
from primehub.config import CliConfig
from primehub.utils import create_logger, PrimeHubException, PrimeHubReturnsRequiredException
from primehub.utils import create_logger, PrimeHubException, PrimeHubReturnsRequiredException, ResourceNotFoundException
from primehub.utils.argparser import create_command_parser, create_action_parser
from primehub.utils.decorators import find_actions, find_action_method, find_action_info
from primehub.utils.permission import has_permission_flag, enable_ask_for_permission_feature
Expand Down Expand Up @@ -246,6 +246,13 @@ def main(sdk=None):
hide_help = True
exit_normally = False
sys.exit(1)
except ResourceNotFoundException as e:
logger.debug('got PrimeHubReturnsRequiredException')
hide_help = True
exit_normally = False
type_name, key, key_type = e.args
print(f'Cannot find resource type "{type_name}" with [{key_type}: {key}]', file=sdk.stderr)
sys.exit(1)
except PrimeHubException as e:
hide_help = True
exit_normally = False
Expand Down
93 changes: 71 additions & 22 deletions primehub/deployments.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Iterator

from primehub import Helpful, cmd, Module, has_data_from_stdin
from primehub.utils import resource_not_found, PrimeHubException
from primehub.utils.permission import ask_for_permission
from primehub.utils.optionals import toggle_flag
import time
Expand All @@ -9,6 +10,48 @@
import sys


def _error_handler(response):
import re

if 'errors' in response:
message = [x for x in response['errors'] if 'message' in x]
if message:
message = message[0]['message']
result = re.findall(r'phdeployments.primehub.io "([^"]+)" not found', message)
if result:
resource_not_found('schedule', result[0], 'id')


def invalid_config(message: str):
example = """
{"name":"quickstart-iris","modelImage":"infuseai/sklearn-prepackaged:v0.1.0","modelURI":"gs://seldon-models/sklearn/iris","env":[],"metadata":{},"instanceType":"cpu-1","replicas":1,"updateMessage":"","id":"quickstart-iris-ghdgk","endpointAccessType":"public"}
""".strip() # noqa: E501

import textwrap

docs = """
We take examples from:
https://docs.primehub.io/docs/model-deployment-tutorial-prepackaged-image

Definition example:
"""
docs = textwrap.dedent(docs)
explain = f'{message}\n\n{docs}{json.dumps(json.loads(example), indent=2)}'
raise PrimeHubException(explain)


def verify_requires(config):
# verify required fields in the config
if not config:
invalid_config('Deployment definition is required.')
if 'id' not in config:
invalid_config('id is required')
if 'instanceType' not in config:
invalid_config('instanceType is required')
if 'modelImage' not in config:
invalid_config('modelImage is required')


class Deployments(Helpful, Module):
"""
The deployments module provides functions to manage PrimeHub Deployments
Expand Down Expand Up @@ -69,7 +112,7 @@ def get(self, id):
:param id: The deployment id

:rtype dict
:return The detail infromation of a deployment
:return The detail information of a deployment
"""
query = """
query ($where: PhDeploymentWhereUniqueInput!) {
Expand All @@ -96,7 +139,7 @@ def get(self, id):
}
}
"""
results = self.request({'where': {'id': id}}, query)
results = self.request({'where': {'id': id}}, query, _error_handler)
return results['data']['phDeployment']

@cmd(name='get-history', description='Get history of a deployment by id')
Expand Down Expand Up @@ -135,11 +178,10 @@ def get_history(self, id):
}
}
"""
results = self.request({'where': {'id': id}}, query)
results = self.request({'where': {'id': id}}, query, _error_handler)
return results['data']['phDeployment']['history']

# TODO: add -f
# TODO: handel invalid config
@cmd(name='create', description='Create a deployment', optionals=[('file', str)])
def create_cmd(self, **kwargs):
"""
Expand All @@ -149,7 +191,7 @@ def create_cmd(self, **kwargs):
:param file: The file path of deployment configurations

:rtype dict
:return The detail infromation of the created deployment
:return The detail information of the created deployment
"""
config = {}
filename = kwargs.get('file', None)
Expand All @@ -162,7 +204,6 @@ def create_cmd(self, **kwargs):

return self.create(config)

# TODO: add validation for config
def create(self, config):
"""
Create a deployments with config
Expand All @@ -171,7 +212,7 @@ def create(self, config):
:param config: The deployment config

:rtype dict
:return The detail infromation of the created deployment
:return The detail information of the created deployment
"""
query = """
mutation ($data: PhDeploymentCreateInput!) {
Expand All @@ -198,12 +239,18 @@ def create(self, config):
}
}
"""

verify_requires(config)
config['groupId'] = self.group_id
self.verify_dependency(config)
results = self.request({'data': config}, query)
return results['data']['createPhDeployment']

def verify_dependency(self, config):
if 'instanceType' in config:
self.primehub.instancetypes.get(config['instanceType'])

# TODO: add -f
# TODO: handel invalid config
@cmd(name='update', description='Update a deployment by id', optionals=[('file', str)])
def update_cmd(self, id, **kwargs):
"""
Expand All @@ -213,7 +260,7 @@ def update_cmd(self, id, **kwargs):
:param file: The file path of deployment configurations

:rtype dict
:return The detail infromation of the updated deployment
:return The detail information of the updated deployment
"""
config = {}
filename = kwargs.get('file', None)
Expand All @@ -226,7 +273,6 @@ def update_cmd(self, id, **kwargs):

return self.update(id, config)

# TODO: add validation for config
def update(self, id, config):
"""
Update a deployment with config
Expand All @@ -238,7 +284,7 @@ def update(self, id, config):
:param config: The deployment config

:rtype dict
:return The detail infromation of the updated deployment
:return The detail information of the updated deployment
"""
query = """
mutation ($data: PhDeploymentUpdateInput!, $where: PhDeploymentWhereUniqueInput!) {
Expand All @@ -265,7 +311,10 @@ def update(self, id, config):
}
}
"""
results = self.request({'data': config, 'where': {'id': id}}, query)
if not config:
invalid_config('Deployment definition is required.')
self.verify_dependency(config)
results = self.request({'data': config, 'where': {'id': id}}, query, _error_handler)
return results['data']['updatePhDeployment']

@cmd(name='start', description='Start a deployment by id')
Expand All @@ -277,7 +326,7 @@ def start(self, id):
:param id: The deployment id

:rtype dict
:return The detail infromation of the started deployment
:return The detail information of the started deployment
"""
query = """
mutation ($where:PhDeploymentWhereUniqueInput!) {
Expand All @@ -304,7 +353,7 @@ def start(self, id):
}
}
"""
results = self.request({'where': {'id': id}}, query)
results = self.request({'where': {'id': id}}, query, _error_handler)
return results['data']['deployPhDeployment']

@cmd(name='stop', description='Stop a deployment by id')
Expand All @@ -316,7 +365,7 @@ def stop(self, id):
:param id: The deployment id

:rtype dict
:return The detail infromation of the stopped deployment
:return The detail information of the stopped deployment
"""
query = """
mutation ($where:PhDeploymentWhereUniqueInput!) {
Expand All @@ -340,20 +389,20 @@ def stop(self, id):
}
}
"""
results = self.request({'where': {'id': id}}, query)
results = self.request({'where': {'id': id}}, query, _error_handler)
return results['data']['stopPhDeployment']

@ask_for_permission
@cmd(name='delete', description='Delete a deployment by id')
def delete(self, id):
def delete(self, id, **kwargs):
"""
Delete a deployment by id

:type id: str
:param id: The deployment id

:rtype dict
:return The detail infromation of the deleted deployment
:return The detail information of the deleted deployment
"""
query = """
mutation ($where: PhDeploymentWhereUniqueInput!) {
Expand All @@ -362,7 +411,7 @@ def delete(self, id):
}
}
"""
results = self.request({'where': {'id': id}}, query)
results = self.request({'where': {'id': id}}, query, _error_handler)
return results['data']['deletePhDeployment']

# TODO: handle invalid pod
Expand Down Expand Up @@ -404,7 +453,7 @@ def logs(self, id, **kwargs) -> Iterator[str]:
follow = kwargs.get('follow', False)
tail = kwargs.get('tail', 10)

results = self.primehub.request({'where': {'id': id}}, query)
results = self.primehub.request({'where': {'id': id}}, query, _error_handler)
pods = results['data']['phDeployment']['pods']
endpoints = [p['logEndpoint'] for p in pods if p['name'].startswith(pod_name)]
return self.primehub.request_logs(endpoints[0], follow, tail)
Expand All @@ -421,7 +470,7 @@ def wait(self, id, **kwargs):
:param timeout: The timeout in second

:rtype dict
:return The detail infromation of the deployment
:return The detail information of the deployment
"""
query = """
query ($where: PhDeploymentWhereUniqueInput!) {
Expand All @@ -434,7 +483,7 @@ def wait(self, id, **kwargs):
timeout = kwargs.get('timeout', 0)
start_time = time.time()
while True:
results = self.request({'where': {'id': id}}, query)
results = self.request({'where': {'id': id}}, query, _error_handler)
status = results['data']['phDeployment']['status']
stop = results['data']['phDeployment']['stop']
if (not stop and status == 'Deployed') or (stop and status == 'Stopped'):
Expand Down
22 changes: 12 additions & 10 deletions primehub/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
import os

from primehub.utils.optionals import toggle_flag
from primehub.utils import create_logger
from primehub.utils import create_logger, SharedFileException

logger = create_logger('cmd-files')


def invalid(message):
raise SharedFileException(message)


def _normalize_dest_path(path):
if path is None:
raise ValueError('path is required')
Expand Down Expand Up @@ -89,12 +93,12 @@ def list(self, path):

items = self._execute_list(path, recursive=True, limit=1)
if not items or items[0]['name']:
logger.warning(f'{path} No such file or directory')
invalid(f'No such file or directory: {path}')
return []

# file
if not os.path.basename(path): # trailing slash
logger.warning(f'{path} Not a directory')
invalid(f'Not a directory: {path}')
return []

items[0]['name'] = os.path.basename(path)
Expand Down Expand Up @@ -138,10 +142,8 @@ def _execute_list(self, path, **kwargs):
'options': {'recursive': recursive, 'limit': limit}},
query)
items = results['data']['files']['items']

return items

# TODO: handel path or dest does not exist
@cmd(name='download', description='Download shared files', optionals=[('recursive', toggle_flag)])
def download(self, path, dest, **kwargs):
"""
Expand Down Expand Up @@ -192,29 +194,29 @@ def _generate_download_list(self, path, dest, **kwargs):
dest_isfile = os.path.isfile(dest_norm)
dest_dir = os.path.dirname(dest)
if dest_dir and not os.path.isdir(dest_dir):
logger.warning(f'{path} No such file or directory')
invalid(f'No such file or directory: {dest_dir}')
return []

items = self._execute_list(path, limit=1)
if items: # directory
if dest_isfile:
logger.warning(f'{dest} Not a directory')
invalid(f'Not a directory: {dest}')
return []

if not recursive:
logger.warning(f'{path} is a directoy (not downloaded)')
invalid(f'{path} is a directory, please download it recursively')
return []

transform = not any(os.path.basename(path))

else: # file or not exist
items = self._execute_list(path, recursive=True, limit=1)
if not items or items[0]['name']:
logger.warning(f'{path} No such file or directory')
invalid(f'No such file or directory: {path}')
return []

if not os.path.basename(path): # trailing slash
logger.warning(f'{path} Not a directory')
invalid(f'Not a directory: {path}')
return []

transform = not os.path.isdir(dest)
Expand Down
3 changes: 3 additions & 0 deletions primehub/groups.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Optional

from primehub import Helpful, cmd, Module
from primehub.utils import resource_not_found


class Groups(Helpful, Module):
Expand Down Expand Up @@ -53,6 +54,8 @@ def get(self, group_name: str) -> Optional[dict]:
group = [x for x in groups if x['name'] == group_name]
if group:
return group[0]

resource_not_found('group', group_name, 'name')
return None

def help_description(self):
Expand Down
Loading