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

[WIP] Utilize environment.yml or requirements.txt directly #275

Closed
wants to merge 6 commits into from
Closed
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
2 changes: 1 addition & 1 deletion anaconda_project/internal/cli/activate.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def activate(dirname, ui_mode, conda_environment, command_name):
Returns:
None on failure or a list of lines to print.
"""
project = load_project(dirname)
project = load_project(dirname, save=False)
result = prepare_with_ui_mode_printing_errors(project,
ui_mode=ui_mode,
env_spec_name=conda_environment,
Expand Down
2 changes: 1 addition & 1 deletion anaconda_project/internal/cli/clean.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def clean_command(project_dir):
Returns:
exit code
"""
project = load_project(project_dir)
project = load_project(project_dir, save=False)
# we don't want to print errors during this prepare, clean
# can proceed even though the prepare fails.
with project.null_frontend():
Expand Down
4 changes: 2 additions & 2 deletions anaconda_project/internal/cli/command_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def list_commands(project_dir):
Returns:
int exit code
"""
project = load_project(project_dir)
project = load_project(project_dir, save=False)
if console_utils.print_project_problems(project):
return 1

Expand All @@ -111,7 +111,7 @@ def list_default_command(project_dir):
Returns:
int exit code
"""
project = load_project(project_dir)
project = load_project(project_dir, save=False)
if console_utils.print_project_problems(project):
return 1

Expand Down
2 changes: 1 addition & 1 deletion anaconda_project/internal/cli/download_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def remove_download(project_dir, env_spec_name, filename_variable):

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

Expand Down
14 changes: 8 additions & 6 deletions anaconda_project/internal/cli/environment_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def remove_env_spec(project_dir, name):

def export_env_spec(project_dir, name, filename):
"""Save an environment.yml file."""
project = load_project(project_dir)
project = load_project(project_dir, save=False)
status = project_ops.export_env_spec(project, name=name, filename=filename)
return _handle_status(status)

Expand Down Expand Up @@ -101,7 +101,7 @@ def remove_platforms(project, environment, platforms):

def list_env_specs(project_dir):
"""List environments in the project."""
project = load_project(project_dir)
project = load_project(project_dir, save=False)
if console_utils.print_project_problems(project):
return 1
print("Environments for project: {}\n".format(project_dir))
Expand All @@ -111,7 +111,7 @@ def list_env_specs(project_dir):

def list_packages(project_dir, environment):
"""List the packages for an environment in the project."""
project = load_project(project_dir)
project = load_project(project_dir, save=False)
if console_utils.print_project_problems(project):
return 1
if environment is None:
Expand All @@ -120,14 +120,16 @@ def list_packages(project_dir, environment):
if env is None:
print("Project doesn't have an environment called '{}'".format(environment), file=sys.stderr)
return 1
print("Packages for environment '{}':\n".format(env.name))
print("Conda Packages for environment '{}':\n".format(env.name))
print("\n".join(sorted(env.conda_packages)), end='\n\n')
print("Pip Packages for environment '{}':\n".format(env.name))
print("\n".join(sorted(env.pip_packages)), end='\n\n')
return 0


def list_platforms(project_dir, environment):
"""List the platforms for an environment in the project."""
project = load_project(project_dir)
project = load_project(project_dir, save=False)
if console_utils.print_project_problems(project):
return 1
if environment is None:
Expand Down Expand Up @@ -161,7 +163,7 @@ def update(project_dir, env_spec_name):

def unlock(project_dir, env_spec_name):
"""Unlock dependency versions."""
project = load_project(project_dir)
project = load_project(project_dir, save=False)
if console_utils.print_project_problems(project):
return 1
status = project_ops.unlock(project, env_spec_name=env_spec_name)
Expand Down
1 change: 1 addition & 0 deletions anaconda_project/internal/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ def add_env_spec_name_arg(preset, required):
preset = subparsers.add_parser('prepare', help="Set up the project requirements, but does not run the project")
preset.add_argument('--all', action='store_true', help="Prepare all environments", default=None)
preset.add_argument('--refresh', action='store_true', help='Remove and recreate the environment', default=None)
preset.add_argument('--python', type=str, help='Specify Python version if using requirements.txt', default=None)
add_prepare_args(preset)
preset.set_defaults(main=prepare.main)

Expand Down
14 changes: 10 additions & 4 deletions anaconda_project/internal/cli/prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,26 @@
# -----------------------------------------------------------------------------
"""The ``prepare`` command configures a project to run, asking the user questions if necessary."""
from __future__ import absolute_import, print_function
from os import path

import anaconda_project.internal.cli.console_utils as console_utils
from anaconda_project.internal.cli.prepare_with_mode import prepare_with_ui_mode_printing_errors
from anaconda_project.internal.cli.project_load import load_project


def prepare_command(project_dir, ui_mode, conda_environment, command_name, all=False, refresh=False):
def prepare_command(project_dir, ui_mode, conda_environment, command_name, all=False, refresh=False, python=None):
"""Configure the project to run.

Returns:
Prepare result (can be treated as True on success).
"""
project = load_project(project_dir)
project_dir = project.directory_path
project = load_project(project_dir, save=False)

if path.isfile(path.join(project_dir, 'requirements.txt')):
default = project.env_specs[project.default_env_spec_name]
if not default.conda_packages and (python is not None):
default._conda_packages = [f'python={python}']

if console_utils.print_project_problems(project):
return False
if all:
Expand All @@ -37,7 +43,7 @@ def prepare_command(project_dir, ui_mode, conda_environment, command_name, all=F

def main(args):
"""Start the prepare command and return exit status code."""
if prepare_command(args.directory, args.mode, args.env_spec, args.command, args.all, args.refresh):
if prepare_command(args.directory, args.mode, args.env_spec, args.command, args.all, args.refresh, args.python):
print("The project is ready to run commands.")
print("Use `anaconda-project list-commands` to see what's available.")
return 0
Expand Down
5 changes: 3 additions & 2 deletions anaconda_project/internal/cli/project_load.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def partial_error(self, data):
sys.stderr.flush()


def load_project(dirname):
def load_project(dirname, save=True):
"""Load a Project, fixing it if needed and possible."""
project = Project(dirname, frontend=CliFrontend(), must_exist=True)

Expand Down Expand Up @@ -65,6 +65,7 @@ def load_project(dirname):
# this happen 3 times before we give up.
regressions += (len(problems) >= len(o_problems))
if not problems:
project.save()
if save:
project.save()

return project
2 changes: 1 addition & 1 deletion anaconda_project/internal/cli/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def run_command(project_dir, ui_mode, conda_environment, command_name, extra_com
Returns:
Does not return if successful.
"""
project = load_project(project_dir)
project = load_project(project_dir, save=False)

if project.has_bootstrap_env_spec() and not project.is_running_in_bootstrap_env():
print("Project should be ran by bootstrap env... fixing.")
Expand Down
2 changes: 1 addition & 1 deletion anaconda_project/internal/cli/service_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def remove_service(project_dir, env_spec_name, variable_name):

def list_services(project_dir, env_spec_name):
"""List the services listed on the project."""
project = load_project(project_dir)
project = load_project(project_dir, save=False)
if console_utils.print_project_problems(project):
return 1

Expand Down
2 changes: 1 addition & 1 deletion anaconda_project/internal/cli/variable_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def remove_variables(project_dir, env_spec_name, vars_to_remove):

def list_variables(project_dir, env_spec_name):
"""List variables present in project."""
project = load_project(project_dir)
project = load_project(project_dir, save=False)
if console_utils.print_project_problems(project):
return 1
print("Variables for project: {}\n".format(project_dir))
Expand Down
64 changes: 35 additions & 29 deletions anaconda_project/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,13 @@ def accept_project_creation(project):
if not project_exists:
problems.append("Project directory '%s' does not exist." % self.directory_path)
elif self.must_exist and not os.path.isfile(project_file.filename):
problems.append(
ProjectProblem(text="Project file '%s' does not exist." % os.path.basename(project_file.filename),
fix_prompt="Create file '%s'?" % project_file.filename,
fix_function=accept_project_creation))
filenames = ("environment.yml", "environment.yaml", 'requirements.txt')
filenames = map(lambda f: os.path.isfile(os.path.join(self.directory_path, f)), filenames)
if not any(filenames):
problems.append(
ProjectProblem(text="Project file '%s' does not exist." % os.path.basename(project_file.filename),
fix_prompt="Create file '%s'?" % project_file.filename,
fix_function=accept_project_creation))

if project_file.corrupted:
problems.append(
Expand Down Expand Up @@ -220,7 +223,6 @@ def accept_project_creation(project):
# options in the variables section, and after _update_env_specs
# since we use those
self._update_conda_env_requirements(requirements, problems, project_file)

# this MUST be after we update env reqs so we have the valid env spec names
self._update_commands(problems, project_file, requirements)

Expand Down Expand Up @@ -827,30 +829,34 @@ def set_env_spec_platforms(project):
importable_spec = None

if importable_spec is not None:
if old is None:
text = "Environment spec '%s' from %s is not in %s." % (importable_spec.name, importable_filename,
os.path.basename(project_file.filename))
prompt = "Add env spec %s to %s?" % (importable_spec.name, os.path.basename(project_file.filename))
else:
text = "Environment spec '%s' from %s is out of sync with %s. Diff:\n%s" % (
importable_spec.name, importable_filename, os.path.basename(
project_file.filename), importable_spec.diff_from(old))
prompt = "Overwrite env spec %s with the changes from %s?" % (importable_spec.name, importable_filename)

def overwrite_env_spec_from_importable(project):
project.project_file.set_value(['env_specs', importable_spec.name], importable_spec.to_json())

def remember_no_import_importable(project):
project.project_file.set_value(['skip_imports', 'environment'], importable_spec.logical_hash)

# we don't set the filename here because it isn't really an error in the
# file, it ends up reading strangely.
problems.append(
ProjectProblem(text=text,
fix_prompt=prompt,
fix_function=overwrite_env_spec_from_importable,
no_fix_function=remember_no_import_importable))
elif env_specs_is_empty or env_specs_is_missing:
project_file.set_value(['env_specs', importable_spec.name], importable_spec.to_json())
project_file.use_changes_without_saving()
# print(project_file._yaml)
# if old is None:
# text = "Environment spec '%s' from %s is not in %s." % (importable_spec.name, importable_filename,
# os.path.basename(project_file.filename))
# prompt = "Add env spec %s to %s?" % (importable_spec.name, os.path.basename(project_file.filename))
# else:
# text = "Environment spec '%s' from %s is out of sync with %s. Diff:\n%s" % (
# importable_spec.name, importable_filename, os.path.basename(project_file.filename),
# importable_spec.diff_from(old))
# prompt = "Overwrite env spec %s with the changes from %s?" % (importable_spec.name, importable_filename)

# def overwrite_env_spec_from_importable(project):
# project.project_file.set_value(['env_specs', importable_spec.name], importable_spec.to_json())

# def remember_no_import_importable(project):
# project.project_file.set_value(['skip_imports', 'environment'], importable_spec.logical_hash)

# # we don't set the filename here because it isn't really an error in the
# # file, it ends up reading strangely.
# problems.append(
# ProjectProblem(
# text=text,
# fix_prompt=prompt,
# fix_function=overwrite_env_spec_from_importable,
# no_fix_function=remember_no_import_importable))
if env_specs_is_empty or env_specs_is_missing:
# we do NOT want to add this problem if we merely
# failed to parse individual env specs; it must be
# safe to overwrite the env_specs key, so it has to
Expand Down