Skip to content

Commit

Permalink
Merge pull request #156 from rstudio/mmarchetti-requirements-txt
Browse files Browse the repository at this point in the history
Remove requirements.txt support
  • Loading branch information
mmarchetti authored Jan 30, 2019
2 parents e58f4da + 808cea6 commit 9690f50
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 62 deletions.
13 changes: 3 additions & 10 deletions docs/guide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
38 changes: 12 additions & 26 deletions rsconnect_jupyter/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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):
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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()
Expand All @@ -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',
Expand All @@ -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))

Expand Down
17 changes: 10 additions & 7 deletions rsconnect_jupyter/tests/test_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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']
Expand All @@ -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"
}
}
})
Expand All @@ -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, \
Expand Down Expand Up @@ -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
Expand Down
20 changes: 1 addition & 19 deletions rsconnect_jupyter/tests/test_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 9690f50

Please sign in to comment.