diff --git a/docs/guide/README.md b/docs/guide/README.md index 9059fb50..9e6838be 100644 --- a/docs/guide/README.md +++ b/docs/guide/README.md @@ -131,16 +131,9 @@ kernel environment; that is, the environment where the `ipykernel` package is installed. In most cases that will be the same as the notebook server environment where `jupyter` is installed. -If there is a `requirements.txt` file in the same directory as the notebook -file, its contents will be used. This allows you to directly control which -packages will be installed on the RStudio Connect server before the notebook is -rendered. If you use this option, you must ensure that all necessary packages -are listed in the `requirements.txt` file. - -If there isn't a requirements file, the command `pip freeze` will be used to -inspect the environment. The output of `pip freeze` lists all packages currently -installed, as well as their versions, which enables RStudio Connect to recreate -the same environment. +The command `pip freeze` will be used to inspect the environment. The output +of `pip freeze` lists all packages currently installed, as well as their +versions, which enables RStudio Connect to recreate the same environment. ### Handling conflicts diff --git a/rsconnect_jupyter/environment.py b/rsconnect_jupyter/environment.py index 35618070..a2aaf566 100644 --- a/rsconnect_jupyter/environment.py +++ b/rsconnect_jupyter/environment.py @@ -15,19 +15,16 @@ class EnvironmentException(Exception): pass -def detect_environment(dirname): +def detect_environment(): """Determine the python dependencies in the environment. - If requirements.txt exists in the notebook directory, - its contents will be used. Otherwise, the results - of `pip freeze` will be used. + `pip freeze` will be used to introspect the environment. Returns a dictionary containing the package spec filename and contents if successful, or a dictionary containing 'error' on failure. """ - result = (output_file(dirname, 'requirements.txt', 'pip') or - pip_freeze(dirname)) + result = pip_freeze() if result is not None: result['python'] = get_python_version() @@ -46,24 +43,19 @@ def get_default_locale(): return '.'.join(locale.getdefaultlocale()) -def get_version(binary): - # use os.path.realpath to traverse any symlinks +def get_version(module): try: - binary_path = os.path.realpath(os.path.join(exec_dir, binary)) - if not os.path.isfile(binary_path): - raise EnvironmentException("File not found: %s" % binary_path) - - args = [binary_path, "--version"] + args = [sys.executable, '-m', module, '--version'] proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) stdout, stderr = proc.communicate() match = version_re.search(stdout) if match: return match.group() - msg = "Failed to get version of '%s' from the output of: %s --version" % (binary, binary_path) + msg = "Failed to get version of '%s' from the output of: %s" % (module, ' '.join(args)) raise EnvironmentException(msg) except Exception as exc: - raise EnvironmentException("Error getting '%s' version: %s" % (binary, str(exc))) + raise EnvironmentException("Error getting '%s' version: %s" % (module, str(exc))) def output_file(dirname, filename, package_manager): @@ -81,9 +73,8 @@ def output_file(dirname, filename, package_manager): with open(path, 'r') as f: data = f.read() - # TODO TODO TODO TODO data = '\n'.join([line for line in data.split('\n') - if 'rsconnect_jupyter' not in line]) + if 'rsconnect' not in line]) return { 'filename': filename, @@ -95,7 +86,7 @@ def output_file(dirname, filename, package_manager): raise EnvironmentException('Error reading %s: %s' % (filename, str(exc))) -def pip_freeze(dirname): +def pip_freeze(): """Inspect the environment using `pip freeze`. Returns a dictionary containing the filename @@ -104,7 +95,7 @@ def pip_freeze(dirname): """ try: proc = subprocess.Popen( - ['pip', 'freeze'], + [sys.executable, '-m', 'pip', 'freeze'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) pip_stdout, pip_stderr = proc.communicate() @@ -117,7 +108,7 @@ def pip_freeze(dirname): raise EnvironmentException('Error during pip freeze: %s' % msg) pip_stdout = '\n'.join([line for line in pip_stdout.split('\n') - if 'rsconnect-jupyter' not in line]) + if 'rsconnect' not in line]) return { 'filename': 'requirements.txt', @@ -129,12 +120,7 @@ def pip_freeze(dirname): if __name__ == '__main__': try: - if len(sys.argv) < 2: - raise EnvironmentException('Usage: %s NOTEBOOK_PATH' % sys.argv[0]) - - notebook_path = sys.argv[1] - dirname = os.path.dirname(notebook_path) - result = detect_environment(dirname) + result = detect_environment() except EnvironmentException as exc: result = dict(error=str(exc)) diff --git a/rsconnect_jupyter/tests/test_bundle.py b/rsconnect_jupyter/tests/test_bundle.py index 015068cd..b8eb8aeb 100644 --- a/rsconnect_jupyter/tests/test_bundle.py +++ b/rsconnect_jupyter/tests/test_bundle.py @@ -40,7 +40,7 @@ def test_source_bundle1(self): # the test environment. Don't do this in the production code, which # runs in the notebook server. We need the introspection to run in # the kernel environment and not the notebook server environment. - environment = detect_environment(dir) + environment = detect_environment() notebook = self.read_notebook(nb_path) with make_source_bundle(notebook, environment, dir) as bundle, \ tarfile.open(mode='r:gz', fileobj=bundle) as tar: @@ -53,10 +53,16 @@ def test_source_bundle1(self): ]) reqs = tar.extractfile('requirements.txt').read() - self.assertEqual(reqs, b'numpy\npandas\nmatplotlib\n') + + # these are the dependencies declared in our setup.py + self.assertIn(b'notebook', reqs) + self.assertIn(b'nbformat', reqs) manifest = json.loads(tar.extractfile('manifest.json').read().decode('utf-8')) + # don't check requirements.txt since we don't know the checksum + del manifest['files']['requirements.txt'] + # don't check locale value, just require it be present del manifest['locale'] del manifest['python']['package_manager']['version'] @@ -77,9 +83,6 @@ def test_source_bundle1(self): u"files": { u"dummy.ipynb": { u"checksum": u"36873800b48ca5ab54760d60ba06703a" - }, - u"requirements.txt": { - u"checksum": u"5f2a5e862fe7afe3def4a57bb5cfb214" } } }) @@ -93,7 +96,7 @@ def test_source_bundle2(self): # the test environment. Don't do this in the production code, which # runs in the notebook server. We need the introspection to run in # the kernel environment and not the notebook server environment. - environment = detect_environment(dir) + environment = detect_environment() notebook = self.read_notebook(nb_path) with make_source_bundle(notebook, environment, dir, extra_files=['data.csv']) as bundle, \ @@ -159,7 +162,7 @@ def do_test_html_bundle(self, dir): # the test environment. Don't do this in the production code, which # runs in the notebook server. We need the introspection to run in # the kernel environment and not the notebook server environment. - environment = detect_environment(dir) + environment = detect_environment() notebook = self.read_notebook(nb_path) # borrowed these from the running notebook server in the container diff --git a/rsconnect_jupyter/tests/test_environment.py b/rsconnect_jupyter/tests/test_environment.py index 97a38c77..d7f32848 100644 --- a/rsconnect_jupyter/tests/test_environment.py +++ b/rsconnect_jupyter/tests/test_environment.py @@ -17,26 +17,8 @@ def get_dir(self, name): def python_version(self): return '.'.join(map(str, sys.version_info[:3])) - def test_file(self): - result = detect_environment(self.get_dir('pip1')) - - pip_version = result.pop('pip') - self.assertTrue(version_re.match(pip_version)) - - locale = result.pop('locale') - self.assertIsInstance(locale, str) - self.assertIn('.', locale) - - self.assertEqual(result, { - 'package_manager': 'pip', - 'source': 'file', - 'filename': 'requirements.txt', - 'contents': 'numpy\npandas\nmatplotlib\n', - 'python': self.python_version(), - }) - def test_pip_freeze(self): - result = detect_environment(self.get_dir('pip2')) + result = detect_environment() contents = result.pop('contents') # these are the dependencies declared in our setup.py