Skip to content

Commit

Permalink
Merge pull request #23 from Anaconda-Platform/packages-defaults
Browse files Browse the repository at this point in the history
Improvements to the default anaconda-project.yml
  • Loading branch information
havocp committed Mar 3, 2017
2 parents 0c50402 + 7231b62 commit ad9452b
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 13 deletions.
20 changes: 20 additions & 0 deletions anaconda_project/commands/test/test_environment_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,26 @@ def check(dirname):
}, check)


def test_export_env_spec_default_name(capsys, monkeypatch):
def check(dirname):
_monkeypatch_pwd(monkeypatch, dirname)

exported = os.path.join(dirname, "exported.yml")
code = _parse_args_and_run_subcommand(['anaconda-project', 'export-env-spec', exported])
assert code == 0

out, err = capsys.readouterr()
assert '' == err
assert ('Exported environment spec foo to %s.\n' % exported) == out

with_directory_contents_completing_project_file(
{
DEFAULT_PROJECT_FILENAME: 'env_specs:\n foo:\n channels: []\n packages:\n - bar\n' +
' bar:\n channels: []\n packages:\n - baz\n',
'envs/foo/bin/test': 'code here'
}, check)


def test_export_env_spec_no_filename(capsys, monkeypatch):
def check(dirname):
_monkeypatch_pwd(monkeypatch, dirname)
Expand Down
6 changes: 5 additions & 1 deletion anaconda_project/env_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,9 +285,13 @@ def to_json(self):
# have ordering... OrderedDict doesn't work because the
# yaml saver saves them as some "!!omap" nonsense. Other
# than ordering, the formatting isn't even preserved here.
template_json = ryaml.load("something:\n packages: []\n" + " channels: []\n",
template_json = ryaml.load("something:\n description: null\n" + " packages: []\n" + " channels: []\n",
Loader=ryaml.RoundTripLoader)

if self._description is not None:
template_json['something']['description'] = self._description
else:
del template_json['something']['description']
template_json['something']['packages'] = packages
template_json['something']['channels'] = channels

Expand Down
22 changes: 19 additions & 3 deletions anaconda_project/project_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,8 @@ def _default_content(self):
sections['packages'] = ("In the packages section, list any packages that must be installed\n" +
"before your code runs.\n" + "Use `anaconda-project add-packages` to add packages.\n")

sections['channels'] = (
"In the channels section, list any Conda channel URLs to be searched\n" + "for packages.\n" + "\n" +
"For example,\n" + "\n" + "channels:\n" + " - https://conda.anaconda.org/asmeurer\n")
sections['channels'] = ("In the channels section, list any Conda channel URLs to be searched\n" +
"for packages.\n" + "\n" + "For example,\n" + "\n" + "channels:\n" + " - mychannel\n")

sections['env_specs'] = (
"You can define multiple, named environment specs.\n" + "Each inherits any global packages or channels,\n" +
Expand Down Expand Up @@ -157,4 +156,21 @@ def comment_out(comment):
for env_spec in default_env_specs:
as_json['env_specs'][env_spec.name] = env_spec.to_json()

if len(default_env_specs) == 1:
# if there's only one env spec, keep it for name/description
# and put the packages and channels up in the global sections
spec_name = next(iter(as_json['env_specs']))
spec_json = as_json['env_specs'][spec_name]

def move_list_elements(src, dest):
# we want to preserve the dest list object with comments
del dest[:]
dest.extend(src)
del src[:]

if 'packages' in spec_json:
move_list_elements(spec_json['packages'], as_json['packages'])
if 'channels' in spec_json:
move_list_elements(spec_json['channels'], as_json['channels'])

return as_json
8 changes: 5 additions & 3 deletions anaconda_project/project_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,18 +446,20 @@ def export_env_spec(project, name, filename):
Args:
project (Project): the project
name (str): environment spec name
name (str): environment spec name or None for default
filename (str): file to export to
Returns:
``Status`` instance
"""
assert name is not None

failed = project.problems_status()
if failed is not None:
return failed

if name is None:
name = project.default_env_spec_name
assert name is not None

if name not in project.env_specs:
problem = "Environment spec {} doesn't exist.".format(name)
return SimpleStatus(success=False, description=problem)
Expand Down
19 changes: 18 additions & 1 deletion anaconda_project/test/test_env_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,14 +250,31 @@ def test_to_json():
inherit_from_names=(),
inherit_from=())
spec = EnvSpec(name="foo",
description="The Foo Spec",
conda_packages=['a', 'b'],
pip_packages=['c', 'd'],
channels=['x', 'y'],
inherit_from_names=('hi', ),
inherit_from=(hi, ))
json = spec.to_json()

assert {'channels': ['x', 'y'], 'inherit_from': 'hi', 'packages': ['a', 'b', {'pip': ['c', 'd']}]} == json
assert {'description': "The Foo Spec",
'channels': ['x', 'y'],
'inherit_from': 'hi',
'packages': ['a', 'b', {'pip': ['c', 'd']}]} == json


def test_to_json_no_description_no_pip_no_inherit():
# should be able to jsonify a spec with no description
spec = EnvSpec(name="foo",
conda_packages=['a', 'b'],
pip_packages=[],
channels=['x', 'y'],
inherit_from_names=(),
inherit_from=())
json = spec.to_json()

assert {'channels': ['x', 'y'], 'packages': ['a', 'b']} == json


def test_to_json_multiple_inheritance():
Expand Down
140 changes: 135 additions & 5 deletions anaconda_project/test/test_project_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@

from anaconda_project.internal.test.tmpfile_utils import with_directory_contents
from anaconda_project.project_file import ProjectFile, DEFAULT_PROJECT_FILENAME, possible_project_file_names
from anaconda_project.env_spec import EnvSpec

expected_default_file = """# This is an Anaconda project file.
expected_default_file_template = """# This is an Anaconda project file.
#
# Here you can describe your project and how to run it.
# Use `anaconda-project run` to run the project.
Expand Down Expand Up @@ -47,23 +48,29 @@
# Use `anaconda-project add-download` to add downloads.
#
downloads: {}
#
%s%s%s"""

empty_global_packages = """#
# In the packages section, list any packages that must be installed
# before your code runs.
# Use `anaconda-project add-packages` to add packages.
#
packages: []
#
"""

empty_global_channels = """#
# In the channels section, list any Conda channel URLs to be searched
# for packages.
#
# For example,
#
# channels:
# - https://conda.anaconda.org/asmeurer
# - mychannel
#
channels: []
#
"""

empty_default_env_specs = """#
# You can define multiple, named environment specs.
# Each inherits any global packages or channels,
# but can have its own unique ones also.
Expand All @@ -73,6 +80,15 @@
"""


def _make_file_contents(packages, channels, env_specs):
return expected_default_file_template % (packages, channels, env_specs)


expected_default_file = _make_file_contents(packages=empty_global_packages,
channels=empty_global_channels,
env_specs=empty_default_env_specs)


def test_create_missing_project_file():
def create_file(dirname):
filename = os.path.join(dirname, DEFAULT_PROJECT_FILENAME)
Expand Down Expand Up @@ -119,3 +135,117 @@ def read_missing_file(dirname):
assert project_file.get_value(["a", "b"]) is None

with_directory_contents(dict(), read_missing_file)


abc_xyz_populated_env_specs = """#
# You can define multiple, named environment specs.
# Each inherits any global packages or channels,
# but can have its own unique ones also.
# Use `anaconda-project add-env-spec` to add environment specs.
#
env_specs: {abc: {description: ABC, packages: [anaconda], channels: [mychannel]}}
"""

anaconda_global_packages = """#
# In the packages section, list any packages that must be installed
# before your code runs.
# Use `anaconda-project add-packages` to add packages.
#
packages: [anaconda]
"""

mychannel_global_channels = """#
# In the channels section, list any Conda channel URLs to be searched
# for packages.
#
# For example,
#
# channels:
# - mychannel
#
channels: [mychannel]
"""

abc_empty_env_spec = """#
# You can define multiple, named environment specs.
# Each inherits any global packages or channels,
# but can have its own unique ones also.
# Use `anaconda-project add-env-spec` to add environment specs.
#
env_specs: {abc: {description: ABC, packages: [], channels: []}}
"""

expected_one_env_spec_contents = _make_file_contents(packages=anaconda_global_packages,
channels=mychannel_global_channels,
env_specs=abc_empty_env_spec)


def test_create_missing_project_file_one_default_env_spec():
def create_file(dirname):
def default_env_specs_func():
return [EnvSpec(name='abc',
conda_packages=['anaconda'],
pip_packages=[],
channels=['mychannel'],
description="ABC",
inherit_from_names=(),
inherit_from=())]

filename = os.path.join(dirname, DEFAULT_PROJECT_FILENAME)
assert not os.path.exists(filename)
project_file = ProjectFile.load_for_directory(dirname, default_env_specs_func=default_env_specs_func)
assert project_file is not None
assert not os.path.exists(filename)
project_file.save()
assert os.path.exists(filename)
with codecs.open(filename, 'r', 'utf-8') as file:
contents = file.read()
assert expected_one_env_spec_contents == contents

with_directory_contents(dict(), create_file)


abc_xyz_env_specs = """#
# You can define multiple, named environment specs.
# Each inherits any global packages or channels,
# but can have its own unique ones also.
# Use `anaconda-project add-env-spec` to add environment specs.
#
env_specs: {abc: {description: ABC, packages: [anaconda], channels: [mychannel]},
xyz: {description: XYZ, packages: [foo], channels: [bar]}}
"""

expected_two_env_spec_contents = _make_file_contents(packages=empty_global_packages,
channels=empty_global_channels,
env_specs=abc_xyz_env_specs)


def test_create_missing_project_file_two_default_env_specs():
def create_file(dirname):
def default_env_specs_func():
return [EnvSpec(name='abc',
conda_packages=['anaconda'],
pip_packages=[],
channels=['mychannel'],
description="ABC",
inherit_from_names=(),
inherit_from=()), EnvSpec(name='xyz',
conda_packages=['foo'],
pip_packages=[],
channels=['bar'],
description="XYZ",
inherit_from_names=(),
inherit_from=())]

filename = os.path.join(dirname, DEFAULT_PROJECT_FILENAME)
assert not os.path.exists(filename)
project_file = ProjectFile.load_for_directory(dirname, default_env_specs_func=default_env_specs_func)
assert project_file is not None
assert not os.path.exists(filename)
project_file.save()
assert os.path.exists(filename)
with codecs.open(filename, 'r', 'utf-8') as file:
contents = file.read()
assert expected_two_env_spec_contents == contents

with_directory_contents(dict(), create_file)

0 comments on commit ad9452b

Please sign in to comment.