diff --git a/rsconnect/bundle.py b/rsconnect/bundle.py index 37ef2962..81606e9c 100644 --- a/rsconnect/bundle.py +++ b/rsconnect/bundle.py @@ -32,6 +32,7 @@ "__pycache__/", "env/", "packrat/", + "renv/", "rsconnect-python/", "rsconnect/", "venv/", @@ -401,6 +402,23 @@ def create_glob_set(directory, excludes): return GlobSet(work) +def is_environment_dir(directory): + python_path = join(directory, "bin", "python") + return exists(python_path) + + +def list_environment_dirs(directory): + # type: (...) -> typing.List[str] + """Returns a list of subdirectories in `directory` that appear to contain virtual environments.""" + envs = [] + + for name in os.listdir(directory): + path = join(directory, name) + if is_environment_dir(path): + envs.append(name) + return envs + + def _create_api_file_list( directory, # type: str requirements_file_name, # type: str @@ -429,6 +447,7 @@ def _create_api_file_list( excludes = list(excludes) if excludes else [] excludes.append("manifest.json") excludes.append(requirements_file_name) + excludes.extend(list_environment_dirs(directory)) glob_set = create_glob_set(directory, excludes) file_list = [] @@ -470,6 +489,9 @@ def make_api_manifest( :param excludes: a sequence of glob patterns that will exclude matched files. :return: the manifest and a list of the files involved. """ + if is_environment_dir(directory): + excludes = list(excludes or []) + ["bin/", "lib/"] + relevant_files = _create_api_file_list(directory, environment.filename, extra_files, excludes) manifest = make_source_manifest(entry_point, environment, app_mode) diff --git a/rsconnect/main.py b/rsconnect/main.py index f07492d6..68c43b63 100644 --- a/rsconnect/main.py +++ b/rsconnect/main.py @@ -39,7 +39,7 @@ ) from . import api -from .bundle import make_manifest_bundle +from .bundle import is_environment_dir, make_manifest_bundle from .metadata import ServerStore, AppStore from .models import AppModes @@ -403,6 +403,20 @@ def _warn_if_no_requirements_file(directory): ) +def _warn_if_environment_directory(directory): + """ + Issue a warning if the deployment directory is itself a virtualenv (yikes!). + + :param directory: the directory to check in. + """ + if is_environment_dir(directory): + click.secho( + " Warning: The deployment directory appears to be a python virtual environment.\n" + " Excluding the 'bin' and 'lib' directories.", + fg="yellow", + ) + + def _warn_on_ignored_conda_env(environment): """ Checks for a discovered Conda environment and produces a warning that it will be ignored when @@ -575,8 +589,10 @@ def deploy_notebook( click.secho(' Deploying %s to server "%s"' % (file, connect_server.url)) - _warn_on_ignored_manifest(dirname(file)) - _warn_if_no_requirements_file(dirname(file)) + base_dir = dirname(file) + _warn_on_ignored_manifest(base_dir) + _warn_if_no_requirements_file(base_dir) + _warn_if_environment_directory(base_dir) with cli_feedback("Inspecting Python environment"): python, environment = get_python_env_info(file, python, conda, force_generate) @@ -588,7 +604,7 @@ def deploy_notebook( _warn_on_ignored_conda_env(environment) if force_generate: - _warn_on_ignored_requirements(dirname(file), environment.filename) + _warn_on_ignored_requirements(base_dir, environment.filename) with cli_feedback("Creating deployment bundle"): bundle = create_notebook_deployment_bundle( @@ -881,6 +897,7 @@ def _deploy_by_framework( _warn_on_ignored_manifest(directory) _warn_if_no_requirements_file(directory) + _warn_if_environment_directory(directory) with cli_feedback("Inspecting Python environment"): _, environment = get_python_env_info(module_file, python, conda, force_generate)