Skip to content

Commit

Permalink
Merge pull request #78 from Anaconda-Platform/variables-per-env
Browse files Browse the repository at this point in the history
Move variables/services/downloads to be per-env-spec
  • Loading branch information
havocp committed Jun 7, 2017
2 parents f070d07 + 266372e commit c6547eb
Show file tree
Hide file tree
Showing 26 changed files with 1,298 additions and 512 deletions.
70 changes: 50 additions & 20 deletions anaconda_project/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ def set_properties(self, project, name=None, icon=None, description=None):
"""
return project_ops.set_properties(project=project, name=name, icon=icon, description=description)

def add_variables(self, project, vars_to_add, defaults):
def add_variables(self, project, env_spec_name, vars_to_add, defaults):
"""Add variables in anaconda-project.yml, optionally setting their defaults.
Returns a ``Status`` instance which evaluates to True on
Expand All @@ -305,15 +305,19 @@ def add_variables(self, project, vars_to_add, defaults):
Args:
project (Project): the project
env_spec_name (str): environment spec name or None for all environment specs
vars_to_add (list of str): variable names
defaults (dict): dictionary from keys to defaults, can be empty
Returns:
``Status`` instance
"""
return project_ops.add_variables(project=project, vars_to_add=vars_to_add, defaults=defaults)
return project_ops.add_variables(project=project,
env_spec_name=env_spec_name,
vars_to_add=vars_to_add,
defaults=defaults)

def remove_variables(self, project, vars_to_remove, env_spec_name=None):
def remove_variables(self, project, env_spec_name, vars_to_remove, prepare_result=None):
"""Remove variables from anaconda-project.yml and unset their values in local project state.
Returns a ``Status`` instance which evaluates to True on
Expand All @@ -322,15 +326,19 @@ def remove_variables(self, project, vars_to_remove, env_spec_name=None):
Args:
project (Project): the project
env_spec_name (str): environment spec name or None for all environment specs
vars_to_remove (list of tuple): key-value pairs
env_spec_name (str): name of env spec to use
prepare_result (PrepareResult): result of a previous prepare or None
Returns:
``Status`` instance
"""
return project_ops.remove_variables(project=project, vars_to_remove=vars_to_remove, env_spec_name=env_spec_name)
return project_ops.remove_variables(project=project,
env_spec_name=env_spec_name,
vars_to_remove=vars_to_remove,
prepare_result=prepare_result)

def set_variables(self, project, vars_and_values, env_spec_name=None):
def set_variables(self, project, env_spec_name, vars_and_values, prepare_result=None):
"""Set variables' values in anaconda-project-local.yml.
Returns a ``Status`` instance which evaluates to True on
Expand All @@ -339,15 +347,19 @@ def set_variables(self, project, vars_and_values, env_spec_name=None):
Args:
project (Project): the project
env_spec_name (str): environment spec name or None for all environment specs
vars_and_values (list of tuple): key-value pairs
env_spec_name (str): name of env spec to use
prepare_result (PrepareResult): result of a previous prepare or None
Returns:
``Status`` instance
"""
return project_ops.set_variables(project=project, vars_and_values=vars_and_values, env_spec_name=env_spec_name)
return project_ops.set_variables(project=project,
env_spec_name=env_spec_name,
vars_and_values=vars_and_values,
prepare_result=prepare_result)

def unset_variables(self, project, vars_to_unset, env_spec_name=None):
def unset_variables(self, project, env_spec_name, vars_to_unset, prepare_result=None):
"""Unset variables' values in anaconda-project-local.yml.
Returns a ``Status`` instance which evaluates to True on
Expand All @@ -356,15 +368,19 @@ def unset_variables(self, project, vars_to_unset, env_spec_name=None):
Args:
project (Project): the project
env_spec_name (str): environment spec name or None for all environment specs
vars_to_unset (list of str): variable names
env_spec_name (str): name of env spec to use
prepare_result (PrepareResult): result of a previous prepare or None
Returns:
``Status`` instance
"""
return project_ops.unset_variables(project=project, vars_to_unset=vars_to_unset, env_spec_name=env_spec_name)
return project_ops.unset_variables(project=project,
env_spec_name=env_spec_name,
vars_to_unset=vars_to_unset,
prepare_result=prepare_result)

def add_download(self, project, env_var, url, filename=None, hash_algorithm=None, hash_value=None):
def add_download(self, project, env_spec_name, env_var, url, filename=None, hash_algorithm=None, hash_value=None):
"""Attempt to download the URL; if successful, add it as a download to the project.
The returned ``Status`` should be a ``RequirementStatus`` for
Expand All @@ -375,6 +391,7 @@ def add_download(self, project, env_var, url, filename=None, hash_algorithm=None
Args:
project (Project): the project
env_spec_name (str): environment spec name or None for all environment specs
env_var (str): env var to store the local filename
url (str): url to download
filename (optional, str): Name to give file or directory after downloading
Expand All @@ -386,13 +403,14 @@ def add_download(self, project, env_var, url, filename=None, hash_algorithm=None
``Status`` instance
"""
return project_ops.add_download(project=project,
env_spec_name=env_spec_name,
env_var=env_var,
url=url,
filename=filename,
hash_algorithm=hash_algorithm,
hash_value=hash_value)

def remove_download(self, project, prepare_result, env_var):
def remove_download(self, project, env_spec_name, env_var, prepare_result=None):
"""Remove file or directory referenced by ``env_var`` from file system and the project.
The returned ``Status`` will be an instance of ``SimpleStatus``. A False
Expand All @@ -401,13 +419,17 @@ def remove_download(self, project, prepare_result, env_var):
Args:
project (Project): the project
prepare_result (PrepareResult): result of a previous prepare
env_spec_name (str): environment spec name or None for all environment specs
env_var (str): env var to store the local filename
prepare_result (PrepareResult): result of a previous prepare
Returns:
``Status`` instance
"""
return project_ops.remove_download(project=project, prepare_result=prepare_result, env_var=env_var)
return project_ops.remove_download(project=project,
env_spec_name=env_spec_name,
env_var=env_var,
prepare_result=prepare_result)

def add_env_spec(self, project, name, packages, channels):
"""Attempt to create the environment spec and add it to anaconda-project.yml.
Expand Down Expand Up @@ -672,7 +694,7 @@ def remove_command(self, project, name):
"""
return project_ops.remove_command(project=project, name=name)

def add_service(self, project, service_type, variable_name=None):
def add_service(self, project, env_spec_name, service_type, variable_name=None):
"""Add a service to anaconda-project.yml.
The returned ``Status`` should be a ``RequirementStatus`` for
Expand All @@ -683,15 +705,19 @@ def add_service(self, project, service_type, variable_name=None):
Args:
project (Project): the project
env_spec_name (str): environment spec name or None for all environment specs
service_type (str): which kind of service
variable_name (str): environment variable name (None for default)
Returns:
``Status`` instance
"""
return project_ops.add_service(project=project, service_type=service_type, variable_name=variable_name)
return project_ops.add_service(project=project,
env_spec_name=env_spec_name,
service_type=service_type,
variable_name=variable_name)

def remove_service(self, project, prepare_result, variable_name):
def remove_service(self, project, env_spec_name, variable_name, prepare_result=None):
"""Remove a service to anaconda-project.yml.
Returns a ``Status`` instance which evaluates to True on
Expand All @@ -700,13 +726,17 @@ def remove_service(self, project, prepare_result, variable_name):
Args:
project (Project): the project
prepare_result (PrepareResult): result of a previous prepare
env_spec_name (str): environment spec name or None for all environment specs
variable_name (str): environment variable name for the service requirement
prepare_result (PrepareResult): result of a previous prepare or None
Returns:
``Status`` instance
"""
return project_ops.remove_service(project=project, prepare_result=prepare_result, variable_name=variable_name)
return project_ops.remove_service(project=project,
env_spec_name=env_spec_name,
variable_name=variable_name,
prepare_result=prepare_result)

def clean(self, project, prepare_result):
"""Blow away auto-provided state for the project.
Expand Down
4 changes: 3 additions & 1 deletion anaconda_project/archiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,9 @@ def _archive_project(project, filename):
frontend.error("%s has been modified but not saved." % project.project_file.basename)
return SimpleStatus(success=False, description="Can't create an archive.", errors=frontend.pop_errors())

infos = _enumerate_archive_files(project.directory_path, frontend, requirements=project.requirements)
infos = _enumerate_archive_files(project.directory_path,
frontend,
requirements=project.union_of_requirements_for_all_envs)
if infos is None:
return SimpleStatus(success=False,
description="Failed to list files in the project.",
Expand Down
11 changes: 9 additions & 2 deletions anaconda_project/env_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,14 @@ def import_hash(self):
return self._import_hash

def _get_inherited(self, public_attr, key_func=None):
private_attr = '_' + public_attr

def getter(spec):
return getattr(spec, private_attr)

return self._get_inherited_with_getter(getter, key_func=key_func)

def _get_inherited_with_getter(self, getter, key_func=None):
def _linearized_ancestors(specs, accumulator):
for spec in specs:
if spec not in accumulator:
Expand All @@ -215,10 +223,9 @@ def _linearized_ancestors(specs, accumulator):
_linearized_ancestors([self], ancestors)
assert ancestors[-1] is self

private_attr = '_' + public_attr
to_combine = []
for spec in ancestors:
to_combine.append(getattr(spec, private_attr))
to_combine.append(getter(spec))
combined = []
for item in to_combine:
combined = _combine_keeping_last_duplicate(combined, item, key_func=key_func)
Expand Down
26 changes: 15 additions & 11 deletions anaconda_project/internal/cli/download_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@
from anaconda_project.provide import PROVIDE_MODE_CHECK


def add_download(project_dir, filename_variable, download_url, filename, hash_algorithm, hash_value):
def add_download(project_dir, env_spec_name, filename_variable, download_url, filename, hash_algorithm, hash_value):
"""Add an item to the downloads section."""
project = load_project(project_dir)
if (hash_algorithm or hash_value) and not bool(hash_algorithm and hash_value):
print("Error: mutually dependant parameters: --hash-algorithm and --hash-value.", file=sys.stderr)
return 1
status = project_ops.add_download(project,
env_spec_name=env_spec_name,
env_var=filename_variable,
url=download_url,
filename=filename,
Expand All @@ -37,14 +38,17 @@ def add_download(project_dir, filename_variable, download_url, filename, hash_al
return 1


def remove_download(project_dir, filename_variable):
def remove_download(project_dir, env_spec_name, filename_variable):
"""Remove a download requirement from project and from file system."""
project = load_project(project_dir)
# we can remove a download even if prepare fails, so disable
# printing errors in the frontend.
with project.null_frontend():
result = prepare_without_interaction(project, mode=PROVIDE_MODE_CHECK)
status = project_ops.remove_download(project, result, env_var=filename_variable)
result = prepare_without_interaction(project, env_spec_name=env_spec_name, mode=PROVIDE_MODE_CHECK)
status = project_ops.remove_download(project,
env_spec_name=env_spec_name,
env_var=filename_variable,
prepare_result=result)
if status:
print(status.status_description)
print("Removed {} from the project file.".format(filename_variable))
Expand All @@ -54,31 +58,31 @@ def remove_download(project_dir, filename_variable):
return 1


def list_downloads(project_dir):
def list_downloads(project_dir, env_spec_name):
"""List the downloads present in project."""
project = load_project(project_dir)
if console_utils.print_project_problems(project):
return 1

if project.downloads:
if project.downloads(env_spec_name):
print("Downloads for project: {}\n".format(project_dir))
console_utils.print_names_and_descriptions(project.download_requirements, name_attr='title')
console_utils.print_names_and_descriptions(project.download_requirements(env_spec_name), name_attr='title')
else:
print("No downloads found in project.")
return 0


def main_add(args):
"""Start the download command and return exit status code."""
return add_download(args.directory, args.filename_variable, args.download_url, args.filename, args.hash_algorithm,
args.hash_value)
return add_download(args.directory, args.env_spec, args.filename_variable, args.download_url, args.filename,
args.hash_algorithm, args.hash_value)


def main_remove(args):
"""Start the remove download command and return exit status code."""
return remove_download(args.directory, args.filename_variable)
return remove_download(args.directory, args.env_spec, args.filename_variable)


def main_list(args):
"""Start the list download command and return exit status code."""
return list_downloads(args.directory)
return list_downloads(args.directory, args.env_spec)
11 changes: 11 additions & 0 deletions anaconda_project/internal/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ def add_env_spec_name_arg(preset, required):
preset.set_defaults(main=upload.main)

preset = subparsers.add_parser('add-variable', help="Add a required environment variable to the project")
add_env_spec_arg(preset)
preset.add_argument('vars_to_add', metavar='VARS_TO_ADD', default=None, nargs=REMAINDER)
preset.add_argument('--default',
metavar='DEFAULT_VALUE',
Expand All @@ -145,28 +146,33 @@ def add_env_spec_name_arg(preset, required):
preset.set_defaults(main=variable_commands.main_add)

preset = subparsers.add_parser('remove-variable', help="Remove an environment variable from the project")
add_env_spec_arg(preset)
add_directory_arg(preset)
preset.add_argument('vars_to_remove', metavar='VARS_TO_REMOVE', default=None, nargs=REMAINDER)
preset.set_defaults(main=variable_commands.main_remove)

preset = subparsers.add_parser('list-variables', help="List all variables on the project")
add_env_spec_arg(preset)
add_directory_arg(preset)
preset.set_defaults(main=variable_commands.main_list)

preset = subparsers.add_parser('set-variable',
help="Set an environment variable value in anaconda-project-local.yml")
add_env_spec_arg(preset)
preset.add_argument('vars_and_values', metavar='VARS_AND_VALUES', default=None, nargs=REMAINDER)
add_directory_arg(preset)
preset.set_defaults(main=variable_commands.main_set)

preset = subparsers.add_parser('unset-variable',
help="Unset an environment variable value from anaconda-project-local.yml")
add_env_spec_arg(preset)
add_directory_arg(preset)
preset.add_argument('vars_to_unset', metavar='VARS_TO_UNSET', default=None, nargs=REMAINDER)
preset.set_defaults(main=variable_commands.main_unset)

preset = subparsers.add_parser('add-download', help="Add a URL to be downloaded before running commands")
add_directory_arg(preset)
add_env_spec_arg(preset)
preset.add_argument('filename_variable', metavar='ENV_VAR_FOR_FILENAME', default=None)
preset.add_argument('download_url', metavar='DOWNLOAD_URL', default=None)
preset.add_argument('--filename', help="The name to give the file/folder after downloading it", default=None)
Expand All @@ -179,11 +185,13 @@ def add_env_spec_name_arg(preset, required):

preset = subparsers.add_parser('remove-download', help="Remove a download from the project and from the filesystem")
add_directory_arg(preset)
add_env_spec_arg(preset)
preset.add_argument('filename_variable', metavar='ENV_VAR_FOR_FILENAME', default=None)
preset.set_defaults(main=download_commands.main_remove)

preset = subparsers.add_parser('list-downloads', help="List all downloads on the project")
add_directory_arg(preset)
add_env_spec_arg(preset)
preset.set_defaults(main=download_commands.main_list)

service_types = PluginRegistry().list_service_types()
Expand All @@ -194,17 +202,20 @@ def add_service_variable_name(preset):

preset = subparsers.add_parser('add-service', help="Add a service to be available before running commands")
add_directory_arg(preset)
add_env_spec_arg(preset)
add_service_variable_name(preset)
preset.add_argument('service_type', metavar='SERVICE_TYPE', default=None, choices=service_choices)
preset.set_defaults(main=service_commands.main_add)

preset = subparsers.add_parser('remove-service', help="Remove a service from the project")
add_directory_arg(preset)
add_env_spec_arg(preset)
preset.add_argument('variable', metavar='SERVICE_REFERENCE', default=None)
preset.set_defaults(main=service_commands.main_remove)

preset = subparsers.add_parser('list-services', help="List services present in the project")
add_directory_arg(preset)
add_env_spec_arg(preset)
preset.set_defaults(main=service_commands.main_list)

def add_package_args(preset):
Expand Down
2 changes: 1 addition & 1 deletion anaconda_project/internal/cli/prepare_with_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def _interactively_fix_missing_variables(project, result):
values[status.requirement.env_var] = reply

if len(values) > 0:
status = project_ops.set_variables(project, values.items())
status = project_ops.set_variables(project, result.env_spec_name, values.items(), result)
if status:
return True
else:
Expand Down

0 comments on commit c6547eb

Please sign in to comment.