diff --git a/anaconda_project/api.py b/anaconda_project/api.py index a743a095..b9d2d979 100644 --- a/anaconda_project/api.py +++ b/anaconda_project/api.py @@ -218,46 +218,6 @@ def prepare_project_check(self, command=command, extra_command_args=extra_command_args) - def prepare_project_browser(self, - project, - environ, - env_spec_name=None, - command_name=None, - command=None, - extra_command_args=None, - io_loop=None, - show_url=None): - """Prepare a project to run one of its commands. - - This version uses a browser-based UI to allow the user to - see and choose how to meet project requirements. - - See ``prepare_project_locally()`` for additional details - that also apply to this method. - - Args: - project (Project): from the ``load_project`` method - environ (dict): os.environ or the previously-prepared environ; not modified in-place - env_spec_name (str): the package set name to require, or None for default - command_name (str): which named command to choose from the project, None for default - command (ProjectCommand): a command object (alternative to command_name) - extra_command_args (list): extra args to include in the returned command argv - io_loop (IOLoop): tornado IOLoop to use, None for default - show_url (function): function that's passed the URL to open it for the user - - Returns: - a ``PrepareResult`` instance, which has a ``failed`` flag - - """ - return prepare.prepare_with_browser_ui(project=project, - environ=environ, - env_spec_name=env_spec_name, - command_name=command_name, - command=command, - extra_command_args=extra_command_args, - io_loop=io_loop, - show_url=show_url) - def unprepare(self, project, prepare_result, whitelist=None): """Attempt to clean up project-scoped resources allocated by prepare(). diff --git a/anaconda_project/internal/cli/prepare_with_mode.py b/anaconda_project/internal/cli/prepare_with_mode.py index 197adbd9..93359cc3 100644 --- a/anaconda_project/internal/cli/prepare_with_mode.py +++ b/anaconda_project/internal/cli/prepare_with_mode.py @@ -18,7 +18,6 @@ # these UI_MODE_ strings are used as values for command line options, so they are user-visible -UI_MODE_BROWSER = "browser" # ASK_QUESTIONS mode is supposed to ask about default actions too, # like whether to start servers. It isn't implemented yet. UI_MODE_TEXT_ASK_QUESTIONS = "ask" @@ -27,7 +26,7 @@ UI_MODE_TEXT_ASSUME_YES_DEVELOPMENT = "development_defaults" UI_MODE_TEXT_ASSUME_NO = "check" -_all_ui_modes = (UI_MODE_BROWSER, UI_MODE_TEXT_ASK_QUESTIONS, UI_MODE_TEXT_DEVELOPMENT_DEFAULTS_OR_ASK, +_all_ui_modes = (UI_MODE_TEXT_ASK_QUESTIONS, UI_MODE_TEXT_DEVELOPMENT_DEFAULTS_OR_ASK, UI_MODE_TEXT_ASSUME_YES_PRODUCTION, UI_MODE_TEXT_ASSUME_YES_DEVELOPMENT, UI_MODE_TEXT_ASSUME_NO) @@ -90,7 +89,7 @@ def prepare_with_ui_mode_printing_errors(project, Args: project (Project): the project environ (dict): the environment to prepare (None to use os.environ) - ui_mode (str): one of ``UI_MODE_BROWSER``, ``UI_MODE_TEXT_ASSUME_YES_DEVELOPMENT``, + ui_mode (str): one of ``UI_MODE_TEXT_ASSUME_YES_DEVELOPMENT``, ``UI_MODE_TEXT_ASSUME_YES_PRODUCTION``, ``UI_MODE_TEXT_ASSUME_NO`` env_spec_name (str): the environment spec name to require, or None for default command_name (str): command name to use or None for default @@ -103,54 +102,51 @@ def prepare_with_ui_mode_printing_errors(project, """ assert ui_mode in _all_ui_modes # the arg parser should have guaranteed this - if ui_mode == UI_MODE_BROWSER: - result = prepare.prepare_with_browser_ui(project, - environ, - env_spec_name=env_spec_name, - command_name=command_name, - command=command, - extra_command_args=extra_command_args, - keep_going_until_success=True) - else: - ask = False - if ui_mode == UI_MODE_TEXT_ASSUME_YES_PRODUCTION: - provide_mode = PROVIDE_MODE_PRODUCTION - elif ui_mode == UI_MODE_TEXT_ASSUME_YES_DEVELOPMENT: - provide_mode = PROVIDE_MODE_DEVELOPMENT - elif ui_mode == UI_MODE_TEXT_ASSUME_NO: - provide_mode = PROVIDE_MODE_CHECK - elif ui_mode == UI_MODE_TEXT_DEVELOPMENT_DEFAULTS_OR_ASK: - provide_mode = PROVIDE_MODE_DEVELOPMENT - ask = True - - assert ui_mode != UI_MODE_TEXT_ASK_QUESTIONS # Not implemented yet - - # TODO: this could let you fix the suggestions if they are fixable. - # (Note that we fix fatal problems in project_load.py, but we only - # display suggestions when we do a manual prepare, run, etc.) - suggestions = project.suggestions - if len(suggestions) > 0: - print("Potential issues with this project:") - for suggestion in project.suggestions: - print(" * " + suggestion) - print("") - - environ = None - while True: - result = prepare.prepare_without_interaction(project, - environ, - mode=provide_mode, - env_spec_name=env_spec_name, - command_name=command_name, - command=command, - extra_command_args=extra_command_args) - - if result.failed: - if ask and _interactively_fix_missing_variables(project, result): - environ = result.environ - continue # re-prepare, building on our previous environ - - # if we didn't continue, quit. - break + ask = False + if ui_mode == UI_MODE_TEXT_ASSUME_YES_PRODUCTION: + provide_mode = PROVIDE_MODE_PRODUCTION + elif ui_mode == UI_MODE_TEXT_ASSUME_YES_DEVELOPMENT: + provide_mode = PROVIDE_MODE_DEVELOPMENT + elif ui_mode == UI_MODE_TEXT_ASSUME_NO: + provide_mode = PROVIDE_MODE_CHECK + elif ui_mode == UI_MODE_TEXT_DEVELOPMENT_DEFAULTS_OR_ASK: + provide_mode = PROVIDE_MODE_DEVELOPMENT + ask = True + + # We might implement this by using + # Provider.read_config/Provider.set_config_values_as_strings + # or some new version of those; dig the old ui_server.py out + # of git history to see how we used those methods to implement + # an interactive HTML UI. read_config/set_config_values still + # exist on Provider in case they are useful to implement this. + assert ui_mode != UI_MODE_TEXT_ASK_QUESTIONS # Not implemented yet + + # TODO: this could let you fix the suggestions if they are fixable. + # (Note that we fix fatal problems in project_load.py, but we only + # display suggestions when we do a manual prepare, run, etc.) + suggestions = project.suggestions + if len(suggestions) > 0: + print("Potential issues with this project:") + for suggestion in project.suggestions: + print(" * " + suggestion) + print("") + + environ = None + while True: + result = prepare.prepare_without_interaction(project, + environ, + mode=provide_mode, + env_spec_name=env_spec_name, + command_name=command_name, + command=command, + extra_command_args=extra_command_args) + + if result.failed: + if ask and _interactively_fix_missing_variables(project, result): + environ = result.environ + continue # re-prepare, building on our previous environ + + # if we didn't continue, quit. + break return result diff --git a/anaconda_project/internal/cli/test/test_prepare.py b/anaconda_project/internal/cli/test/test_prepare.py index f9a65973..a768d293 100644 --- a/anaconda_project/internal/cli/test/test_prepare.py +++ b/anaconda_project/internal/cli/test/test_prepare.py @@ -74,144 +74,6 @@ def test_prepare_command_assume_no(monkeypatch): _test_prepare_command(monkeypatch, UI_MODE_TEXT_ASSUME_NO) -def _form_names(response, provider): - from anaconda_project.internal.plugin_html import _BEAUTIFUL_SOUP_BACKEND - from bs4 import BeautifulSoup - - if response.code != 200: - raise Exception("got a bad http response " + repr(response)) - - soup = BeautifulSoup(response.body, _BEAUTIFUL_SOUP_BACKEND) - named_elements = soup.find_all(attrs={'name': True}) - names = set() - for element in named_elements: - if provider in element['name']: - names.add(element['name']) - return names - - -def _prefix_form(form_names, form): - prefixed = dict() - for (key, value) in form.items(): - found = False - for name in form_names: - if name.endswith("." + key): - prefixed[name] = value - found = True - break - if not found: - raise RuntimeError("Form field %s in %r could not be prefixed from %r" % (key, form, form_names)) - return prefixed - - -def _monkeypatch_open_new_tab(monkeypatch): - from tornado.ioloop import IOLoop - - http_results = {} - - def mock_open_new_tab(url): - from anaconda_project.internal.test.http_utils import http_get_async, http_post_async - from tornado import gen - - @gen.coroutine - def do_http(): - http_results['get'] = yield http_get_async(url) - - # pick our environment (using inherited one) - form_names = _form_names(http_results['get'], provider='CondaEnvProvider') - form = _prefix_form(form_names, {'source': 'inherited'}) - response = yield http_post_async(url, form=form) - assert response.code == 200 - - # now do the next round of stuff - http_results['post'] = yield http_post_async(url, body="") - - IOLoop.current().add_callback(do_http) - - monkeypatch.setattr('webbrowser.open_new_tab', mock_open_new_tab) - - return http_results - - -def test_main(monkeypatch, capsys): - can_connect_args = _monkeypatch_can_connect_to_socket_to_succeed(monkeypatch) - _monkeypatch_open_new_tab(monkeypatch) - - def mock_conda_create(prefix, pkgs, channels, stdout_callback, stderr_callback): - raise RuntimeError("this test should not create an environment in %s with pkgs %r" % (prefix, pkgs)) - - monkeypatch.setattr('anaconda_project.internal.conda_api.create', mock_conda_create) - - def main_redis_url(dirname): - project_dir_disable_dedicated_env(dirname) - main(Args(directory=dirname, mode='browser')) - - with_directory_contents_completing_project_file( - {DEFAULT_PROJECT_FILENAME: """ -services: - REDIS_URL: redis -"""}, main_redis_url) - - assert can_connect_args['port'] == 6379 - - out, err = capsys.readouterr() - assert "# Configure the project at " in out - assert "" == err - - -def test_main_dirname_not_provided_use_pwd(monkeypatch, capsys): - can_connect_args = _monkeypatch_can_connect_to_socket_to_succeed(monkeypatch) - _monkeypatch_open_new_tab(monkeypatch) - - def main_redis_url(dirname): - from os.path import abspath as real_abspath - - def mock_abspath(path): - if path == ".": - return dirname - else: - return real_abspath(path) - - monkeypatch.setattr('os.path.abspath', mock_abspath) - project_dir_disable_dedicated_env(dirname) - code = _parse_args_and_run_subcommand(['anaconda-project', 'prepare', '--mode=browser']) - assert code == 0 - - with_directory_contents_completing_project_file( - {DEFAULT_PROJECT_FILENAME: """ -services: - REDIS_URL: redis -"""}, main_redis_url) - - assert can_connect_args['port'] == 6379 - - out, err = capsys.readouterr() - assert "# Configure the project at " in out - assert "" == err - - -def test_main_dirname_provided_use_it(monkeypatch, capsys): - can_connect_args = _monkeypatch_can_connect_to_socket_to_succeed(monkeypatch) - _monkeypatch_open_new_tab(monkeypatch) - - def main_redis_url(dirname): - project_dir_disable_dedicated_env(dirname) - code = _parse_args_and_run_subcommand(['anaconda-project', 'prepare', '--directory', dirname, '--mode=browser']) - assert code == 0 - - with_directory_contents_completing_project_file( - {DEFAULT_PROJECT_FILENAME: """ -services: - REDIS_URL: redis -"""}, main_redis_url) - - assert can_connect_args['port'] == 6379 - - out, err = capsys.readouterr() - assert "# Configure the project at " in out - assert "" == err - - def _monkeypatch_can_connect_to_socket_to_fail_to_find_redis(monkeypatch): def mock_can_connect_to_socket(host, port, timeout_seconds=0.5): if port == 6379: @@ -224,7 +86,6 @@ def mock_can_connect_to_socket(host, port, timeout_seconds=0.5): def test_main_fails_to_redis(monkeypatch, capsys): _monkeypatch_can_connect_to_socket_to_fail_to_find_redis(monkeypatch) - _monkeypatch_open_new_tab(monkeypatch) from anaconda_project.internal.cli.prepare_with_mode import prepare_with_ui_mode_printing_errors as real_prepare diff --git a/anaconda_project/internal/plugin_html.py b/anaconda_project/internal/plugin_html.py deleted file mode 100644 index f23b8e90..00000000 --- a/anaconda_project/internal/plugin_html.py +++ /dev/null @@ -1,97 +0,0 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- -# Copyright © 2016, Continuum Analytics, Inc. All rights reserved. -# -# The full license is in the file LICENSE.txt, distributed with this software. -# ---------------------------------------------------------------------------- -from __future__ import absolute_import, print_function - -from bs4 import BeautifulSoup - -_FORM_FIELD_ELEMENTS = ['input', 'textarea', 'select'] - -_BEAUTIFUL_SOUP_BACKEND = "html.parser" - - -def _set_element_value(element, value): - assert value is not None - value_string = str(value) - if isinstance(value, bool): - value_bool = value - elif 'value' in element.attrs and element['value'] == value_string: - value_bool = (value_string == element['value']) - else: - value_bool = False - if element.name == 'input': - if element['type'] == 'checkbox': - if value_bool: - element['checked'] = '' - else: - del element['checked'] - elif element['type'] == 'radio': - if value_bool: - element['checked'] = '' - else: - del element['checked'] - elif element['type'] == 'hidden': - # we don't know what to do with these; right now - # we use them as a hack to go next to checkboxes - # and be sure we always send a value for checkbox - # query params - pass - else: - element['value'] = value_string - elif element.name == 'textarea': - element.string = value_string - elif element.name == 'select': - options = element.find_all('option') - for option in options: - if 'value' in option.attrs: - option_string = option['value'] - else: - option_string = option.string - if option_string == value_string: - option['selected'] = '' - else: - del option['selected'] - - -def cleanup_and_scope_form(html, prefix, values): - # - parse the html - # - be sure it's a