Skip to content

Commit

Permalink
Merge pull request #111 from pennyfx/controller_validation
Browse files Browse the repository at this point in the history
Add validation
  • Loading branch information
asampat3090 committed May 10, 2018
2 parents 078f30e + a661b10 commit 98c8d37
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 24 deletions.
9 changes: 6 additions & 3 deletions datmo/cli/command/tests/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ def test_session_create(self):
assert self.session_command.execute()

def test_session_select(self):
self.__set_variables()
self.test_session_create()

self.session_command.parse(["session", "select", "--name", "pizza"])
assert self.session_command.execute()
current = 0
Expand All @@ -69,12 +70,14 @@ def test_session_select(self):
assert current == 1

def test_session_ls(self):
self.__set_variables()
self.test_session_create()

self.session_command.parse(["session", "ls"])
assert self.session_command.execute()

def test_session_delete(self):
self.__set_variables()
self.test_session_create()

self.session_command.parse(["session", "delete", "--name", "pizza"])
assert self.session_command.execute()
session_removed = True
Expand Down
19 changes: 8 additions & 11 deletions datmo/core/controller/project.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import datetime

from datmo.core.util.validation import validate
from datmo.core.util.i18n import get as __
from datmo.core.controller.base import BaseController
from datmo.core.entity.model import Model
from datmo.core.entity.session import Session
from datmo.core.util.exceptions import (SessionDoesNotExistException,
RequiredArgumentMissing,
ProjectNotInitializedException)
from datmo.core.util.exceptions import (
SessionDoesNotExistException, RequiredArgumentMissing,
ProjectNotInitializedException, ValidationFailed)


class ProjectController(BaseController):
Expand Down Expand Up @@ -39,14 +40,10 @@ def init(self, name, description):

# If model is new validate inputs
if is_new_model:
# Error if name is blank or not given
if not name:
raise RequiredArgumentMissing(
__("error", "controller.project.init.arg", "name"))
# Error if description is None
if description is None:
raise RequiredArgumentMissing(
__("error", "controller.project.init.arg", "description"))
validate("create_project", {
"name": name,
"description": description
})

# Create model if new else update
if is_new_model:
Expand Down
4 changes: 4 additions & 0 deletions datmo/core/controller/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from datmo.core.util.i18n import get as __
from datmo.core.util.exceptions import (EntityNotFound, InvalidOperation,
ProjectNotInitializedException)
from datmo.core.util.validation import validate


class SessionController(BaseController):
Expand Down Expand Up @@ -36,6 +37,9 @@ def __init__(self, home):

def create(self, incoming_dictionary):
# Look for existing session first and return if it exists

validate("create_session", incoming_dictionary)

results = self.dal.session.query({
"model_id": self.model.id,
"name": incoming_dictionary['name']
Expand Down
13 changes: 13 additions & 0 deletions datmo/core/controller/snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from datmo.core.controller.environment.environment import EnvironmentController
from datmo.core.entity.snapshot import Snapshot
from datmo.core.util.i18n import get as __
from datmo.core.util.validation import validate
from datmo.core.util.json_store import JSONStore
from datmo.core.util.exceptions import (
FileIOException, RequiredArgumentMissing, ProjectNotInitializedException,
Expand Down Expand Up @@ -153,6 +154,8 @@ def create(self, incoming_dictionary):
"session_id": self.current_session.id,
}

validate("create_snapshot", incoming_dictionary)

# Message must be present
if "message" in incoming_dictionary:
create_dict['message'] = incoming_dictionary['message']
Expand Down Expand Up @@ -228,6 +231,16 @@ def create_from_task(self,
TaskNotComplete
if task specified has not been completed
"""

validate(
"create_snapshot_from_task", {
"message": message,
"task_id": task_id,
"label": label,
"config": config,
"stats": stats
})

task_obj = self.dal.task.get_by_id(task_id)

if not task_obj.status and not task_obj.after_snapshot_id:
Expand Down
17 changes: 14 additions & 3 deletions datmo/core/controller/tests/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from datmo.core.controller.task import TaskController
from datmo.core.entity.snapshot import Snapshot
from datmo.core.entity.task import Task
from datmo.core.util.exceptions import RequiredArgumentMissing
from datmo.core.util.exceptions import ValidationFailed


class TestProjectController():
Expand All @@ -35,15 +35,26 @@ def setup_method(self):
def teardown_method(self):
pass

def test_init(self):
def test_init_none(self):
# Test failed case
failed = False
try:
self.project.init(None, None)
except RequiredArgumentMissing:
except ValidationFailed:
failed = True
assert failed

def test_init_empty_str(self):
# Test failed case
failed = False
try:
self.project.init("", "")
except ValidationFailed:
failed = True
assert failed

def test_init(self):

result = self.project.init("test1", "test description")

# Tested with is_initialized
Expand Down
18 changes: 18 additions & 0 deletions datmo/core/util/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/python
from datmo.core.util.i18n import get as __


class InvalidProjectPathException(Exception):
Expand Down Expand Up @@ -163,3 +164,20 @@ class TaskInteractiveDetachException(ArgumentException):

class SnapshotCreateFromTaskArgs(ArgumentException):
pass


class ValidationFailed(Exception):
def __init__(self, error_obj):
self.errors = error_obj
super(ValidationFailed, self).__init__(
__("error", "exception.validationfailed", self.get_error_str()))

def get_error_str(self):
err_str = ''
for name in self.errors:
err_str += "'%s': %s\n" % (name, self.errors[name])
return err_str


class ValidationSchemaMissing(Exception):
pass
2 changes: 2 additions & 0 deletions datmo/core/util/lang/en.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@
"user is not logged into datmo. use `datmo setup` or `datmo login` to login",
},
"error": {
"exception.validationfailed":
"Validation failed: %s",
"sdk.snapshot.create.task.args":
"Error due to passing excluded args while creating snapshot from task: %s",
"cli.general":
Expand Down
44 changes: 44 additions & 0 deletions datmo/core/util/tests/test_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""
Tests for file_storage.py
"""
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import tempfile
import platform
import os
from io import open
try:
to_unicode = unicode
except NameError:
to_unicode = str

from datmo.core.util.validation import validate
from datmo.core.util.exceptions import ValidationFailed, ValidationSchemaMissing


class TestJSONStore():
def test_validate_success(self):
assert validate("create_project", {
"name": "foobar",
"description": "barbaz"
})

def test_validate_fail(self):
failed = False
try:
validate("create_project", {"name": 3})
except ValidationFailed as e:
failed = True
assert e.errors
assert isinstance(e.errors, dict)
assert failed

def test_invalid_scheme(self):
failed = False
try:
validate("invalid_schema", {"name": "foobar"})
except ValidationSchemaMissing as e:
failed = True
assert failed
24 changes: 24 additions & 0 deletions datmo/core/util/validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import os
import yaml
from cerberus import Validator
from cerberus.schema import SchemaError
from datmo.core.util.exceptions import ValidationFailed, ValidationSchemaMissing

# http://docs.python-cerberus.org/en/stable/usage.html

schema_yaml = open(
os.path.join(os.path.split(__file__)[0], "validation/schemas.yml"))

schemas = yaml.load(schema_yaml)


def validate(schema_name, values):
try:
v = Validator(schemas.get(schema_name))
response = v.validate(values)

if response == False:
raise ValidationFailed(v.errors)
return True
except SchemaError:
raise ValidationSchemaMissing(schema_name)
76 changes: 76 additions & 0 deletions datmo/core/util/validation/schemas.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# http://docs.python-cerberus.org/en/stable/validation-rules.html

create_project:
name:
empty: False
required: True
type: string
description:
required: True
type: string

create_session:
name:
required: True
type: string
current:
type: boolean

create_snapshot:
code_id:
type: string
commit_id:
type: string

environment_id:
type: string
environment_definition_filepath:
type: string

file_collection_id:
type: string
filepaths:
type: list

config:
type: dict
config_filepath:
type: string
config_filename:
type: string

stats:
type: dict
stats_filepath:
type: string
stats_filename:
type: string

message:
type: string
session_id:
type: string
task_id:
type: string
label:
type: string
language:
type: string

visible:
type: boolean

create_snapshot_from_task:
message:
type: string
task_id:
type: string
label:
nullable: true
type: string
config:
nullable: true
type: dict
stats:
nullable: true
type: dict
27 changes: 20 additions & 7 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,26 @@
description='Open source model tracking tool for developers',
long_description=long_description,
install_requires=[
"future>=0.16.0", "enum34>=1.1.6", "glob2>=0.5", "docker>=2.2.1",
"pyyaml>=3.12", "requests>=2.11.1", "prettytable>=0.7.2",
"rsfile>=2.1", "humanfriendly>=3.6.1", "python-slugify>=1.2.4",
"giturlparse.py>=0.0.5", "blitzdb>=0.2.12", "kids.cache>=0.0.7",
"pymongo>=3.6.0", "checksumdir>=1.1.4", "semver>=2.7.8",
"pipreqs>=0.4.9", "backports.ssl-match-hostname>=3.5.0.1",
"timeout-decorator==0.4.0"
"future>=0.16.0",
"enum34>=1.1.6",
"glob2>=0.5",
"docker>=2.2.1",
"pyyaml>=3.12",
"requests>=2.11.1",
"prettytable>=0.7.2",
"rsfile>=2.1",
"humanfriendly>=3.6.1",
"python-slugify>=1.2.4",
"giturlparse.py>=0.0.5",
"blitzdb>=0.2.12",
"kids.cache>=0.0.7",
"pymongo>=3.6.0",
"checksumdir>=1.1.4",
"semver>=2.7.8",
"pipreqs>=0.4.9",
"backports.ssl-match-hostname>=3.5.0.1",
"timeout-decorator==0.4.0",
"cerberus>=1.2",
],
tests_require=["pytest==3.0.4"],
entry_points={'console_scripts': ['datmo = datmo.cli.main:main']},
Expand Down

0 comments on commit 98c8d37

Please sign in to comment.