Skip to content

Commit

Permalink
Merge pull request #102 from datmo/optimize-cli-flows
Browse files Browse the repository at this point in the history
Optimize CLI workflows for initial project creation (init and status)
  • Loading branch information
asampat3090 committed May 8, 2018
2 parents 9218fca + dcf1cc0 commit 8e00834
Show file tree
Hide file tree
Showing 19 changed files with 696 additions and 189 deletions.
137 changes: 124 additions & 13 deletions datmo/cli/command/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,129 @@ def __init__(self, home, cli_helper, parser):
self.project_controller = ProjectController(home=home)

def init(self, name, description):
if not name:
name = self.cli_helper.prompt(
__("prompt", "cli.project.init.name"))
if not description:
description = self.cli_helper.prompt(
__("prompt", "cli.project.init.description"))
self.cli_helper.echo(
__("info", "cli.project.init", {
"name": name,
"path": self.home
}))
self.project_controller.init(name, description)
"""Initialize command
Parameters
----------
name : str
name for the project
description : str
description of the project
Returns
-------
datmo.core.entity.model.Model
"""
# Check if project already exists
is_new_model = False
if not self.project_controller.model:
is_new_model = True

if is_new_model: # Initialize a new project
self.cli_helper.echo(
__("info", "cli.project.init.create", {
"name": name,
"path": self.home
}))
if not name:
name = self.cli_helper.prompt(
__("prompt", "cli.project.init.name"))
if not description:
description = self.cli_helper.prompt(
__("prompt", "cli.project.init.description"))
try:
success = self.project_controller.init(name, description)
if success:
self.cli_helper.echo(
__("info", "cli.project.init.create.success", {
"name": name,
"path": self.home
}))
except:
self.cli_helper.echo(
__("info", "cli.project.init.create.failure", {
"name": name,
"path": self.home
}))
return None
else: # Update the current project
self.cli_helper.echo(
__("info", "cli.project.init.update", {
"name": self.project_controller.model.name,
"path": self.home
}))
if not name:
name = self.cli_helper.prompt(
__("prompt", "cli.project.init.name"))
if not description:
description = self.cli_helper.prompt(
__("prompt", "cli.project.init.description"))
# Update project parameter if given parameter is not falsy and different
if not name or name == self.project_controller.model.name:
name = self.project_controller.model.name
if not description or description == self.project_controller.model.description:
description = self.project_controller.model.description
# Update the project with the values given
try:
success = self.project_controller.init(name, description)
if success:
self.cli_helper.echo(
__("info", "cli.project.init.update.success", {
"name": name,
"path": self.home
}))
except:
self.cli_helper.echo(
__("info", "cli.project.init.update.failure", {
"name": name,
"path": self.home
}))
return None

self.cli_helper.echo("")

# Print out simple project meta data
for k, v in self.project_controller.model.to_dictionary().items():
if k != "config":
self.cli_helper.echo(str(k) + ": " + str(v))

return self.project_controller.model

def version(self):
return self.cli_helper.echo("datmo version: %s" % __version__)
return self.cli_helper.echo("datmo version: %s" % __version__)

def status(self):
status_dict, latest_snapshot, ascending_unstaged_tasks = self.project_controller.status(
)

# Print out simple project meta data
for k, v in status_dict.items():
if k != "config":
self.cli_helper.echo(str(k) + ": " + str(v))

self.cli_helper.echo("")

# Print out project config meta data
self.cli_helper.echo("config: ")
self.cli_helper.echo(status_dict['config'])

self.cli_helper.echo("")

# Print out latest snapshot info
self.cli_helper.echo("latest snapshot id: ")
if latest_snapshot:
self.cli_helper.echo(latest_snapshot.id)
else:
self.cli_helper.echo("no snapshots created yet")

self.cli_helper.echo("")

# Print out unstaged tasks
self.cli_helper.echo("unstaged task ids:")
if ascending_unstaged_tasks:
for task in ascending_unstaged_tasks:
self.cli_helper.echo(task.id)
else:
self.cli_helper.echo("no unstaged tasks")

return status_dict, latest_snapshot, ascending_unstaged_tasks
112 changes: 104 additions & 8 deletions datmo/cli/command/tests/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@


class TestProject():
def setup_class(self):
def setup_method(self):
# provide mountable tmp directory for docker
tempfile.tempdir = "/tmp" if not platform.system(
) == "Windows" else None
Expand All @@ -33,34 +33,130 @@ def setup_class(self):
self.cli_helper = Helper()
self.project = ProjectCommand(self.temp_dir, self.cli_helper, parser)

def teardown_class(self):
def teardown_method(self):
pass

def test_datmo_init(self):
def test_init_create_success(self):
test_name = "foobar"
test_description = "test model"
self.project.parse(
["init", "--name", "foobar", "--description", "test model"])
self.project.execute()
["init", "--name", test_name, "--description", test_description])
result = self.project.execute()
# test for desired side effects
assert os.path.exists(os.path.join(self.temp_dir, '.datmo'))
assert result.name == test_name
assert result.description == test_description

def test_init_create_failure(self):
self.project.parse(["init", "--name", "", "--description", ""])
# test if prompt opens
@self.project.cli_helper.input("\n\n")
def dummy(self):
return self.project.execute()

result = dummy(self)
assert not result

def test_init_update_success(self):
test_name = "foobar"
test_description = "test model"
self.project.parse(
["init", "--name", test_name, "--description", test_description])
result_1 = self.project.execute()
updated_name = "foobar2"
updated_description = "test model 2"
self.project.parse([
"init", "--name", updated_name, "--description",
updated_description
])
result_2 = self.project.execute()
# test for desired side effects
assert os.path.exists(os.path.join(self.temp_dir, '.datmo'))
assert result_2.id == result_1.id
assert result_2.name == updated_name
assert result_2.description == updated_description

def test_init_update_success_only_name(self):
test_name = "foobar"
test_description = "test model"
self.project.parse(
["init", "--name", test_name, "--description", test_description])
result_1 = self.project.execute()
updated_name = "foobar2"
self.project.parse(
["init", "--name", updated_name, "--description", ""])

@self.project.cli_helper.input("\n")
def dummy(self):
return self.project.execute()

result_2 = dummy(self)
# test for desired side effects
assert os.path.exists(os.path.join(self.temp_dir, '.datmo'))
assert result_2.id == result_1.id
assert result_2.name == updated_name
assert result_2.description == result_1.description

def test_datmo_init_invalid_arg(self):
def test_init_update_success_only_description(self):
test_name = "foobar"
test_description = "test model"
self.project.parse(
["init", "--name", test_name, "--description", test_description])
result_1 = self.project.execute()
updated_description = "test model 2"
self.project.parse(
["init", "--name", "", "--description", updated_description])

@self.project.cli_helper.input("\n")
def dummy(self):
return self.project.execute()

result_2 = dummy(self)
# test for desired side effects
assert os.path.exists(os.path.join(self.temp_dir, '.datmo'))
assert result_2.id == result_1.id
assert result_2.name == result_1.name
assert result_2.description == updated_description

def test_init_invalid_arg(self):
exception_thrown = False
try:
self.project.parse(["init", "--foobar", "foobar"])
except Exception:
exception_thrown = True
assert exception_thrown

def test_datmo_version(self):
def test_version(self):
self.project.parse(["version"])
result = self.project.execute()
# test for desired side effects
assert __version__ in result

def test_datmo_version_invalid_arg(self):
def test_version_invalid_arg(self):
exception_thrown = False
try:
self.project.parse(["version", "--foobar"])
except Exception:
exception_thrown = True
assert exception_thrown

def test_status(self):
test_name = "foobar"
test_description = "test model"
self.project.parse(
["init", "--name", test_name, "--description", test_description])
_ = self.project.execute()

self.project.parse(["status"])
result = self.project.execute()
assert isinstance(result[0], dict)
assert not result[1]
assert not result[2]

def test_status_invalid_arg(self):
exception_thrown = False
try:
self.project.parse(["status", "--foobar"])
except Exception:
exception_thrown = True
assert exception_thrown
32 changes: 28 additions & 4 deletions datmo/cli/driver/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@
standard_library.install_aliases()
from builtins import input

import os
import sys
import importlib
import inspect
from io import open
try:
to_unicode = unicode
except NameError:
to_unicode = str

from datmo.core.util.i18n import get as __
from datmo.core.util.exceptions import ArgumentException
Expand All @@ -15,9 +21,25 @@ class Helper():
def __init__(self):
pass

def echo(self, message):
print(message)
return message
def echo(self, value):
print(to_unicode(value))
return value

def input(self, input_msg):
def input_decorator(func):
def wrapper(*args, **kwargs):
with open(os.path.join("input"), "w") as f:
f.write(to_unicode(input_msg))

with open(os.path.join("input"), "r") as f:
sys.stdin = f
result = func(*args, **kwargs)
os.remove(os.path.join("input"))
return result

return wrapper

return input_decorator

def prompt(self, msg, default=None):
try:
Expand Down Expand Up @@ -72,4 +94,6 @@ def get_command_class(self, command_name):
return command_class[1]

def get_command_choices(self):
return ["init", "version", "--version", "-v", "snapshot", "task"]
return [
"init", "version", "--version", "-v", "status", "snapshot", "task"
]
30 changes: 21 additions & 9 deletions datmo/cli/driver/tests/test_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from future import standard_library
standard_library.install_aliases()
from builtins import input

import os
import sys
import tempfile
import platform
from io import open
try:
to_unicode = unicode
except NameError:
Expand Down Expand Up @@ -44,17 +46,27 @@ def test_init(self):
cli = Helper()
assert cli != None

def test_input(self):
cli = Helper()
test_msg = "test"

@cli.input(test_msg)
def dummy():
return input("test")

result = dummy()
assert test_msg in result

def test_prompt(self):
cli = Helper()
test_message = 'foobar'
with open(os.path.join(self.temp_dir, "test.txt"), "w") as f:
f.write(to_unicode(test_message))

with open(os.path.join(self.temp_dir, "test.txt"), "r") as f:
sys.stdin = f
i = cli.prompt("what is this test?")
assert i == test_message
os.remove(os.path.join(self.temp_dir, "test.txt"))

@cli.input(test_message)
def dummy():
return cli.prompt("what is this test?")

i = dummy()
assert i == test_message

# TODO: figure out how to replace "print" with a testable function
# https://stackoverflow.com/questions/4219717/how-to-assert-output-with-nosetest-unittest-in-python
Expand Down

0 comments on commit 8e00834

Please sign in to comment.