Skip to content

Commit

Permalink
Merge pull request #45 from jaraco/feature/notebook-deps-py2
Browse files Browse the repository at this point in the history
Add support for reading from Jupyter notebooks
  • Loading branch information
jaraco committed May 6, 2020
2 parents 051581f + 4bf29d9 commit 697dc43
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 6 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
v6.3.0
------

Add support for reading deps from Jupyter Notebooks.

v6.2.0
------

Expand Down
81 changes: 81 additions & 0 deletions examples/plotter.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# A sin wave plot\n",
"A simple demo plotting a sin wave to demonstrate executing a notebook with dependencies. Invoke with:\n",
"\n",
"```\n",
"pip-run -- -m notebook plotter.ipynb\n",
"```\n",
"\n",
"Or render with\n",
"\n",
"```\n",
"pip-run -- -m nbconvert --execute plotter.ipynb\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"__requires__ = ['jupyter', 'matplotlib', 'numpy']\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"%matplotlib inline"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"x = np.arange(0,4*np.pi,0.1) # start,stop,step\n",
"y = np.sin(x)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"plt.plot(x,y)\n",
"plt.show()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.2"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
33 changes: 28 additions & 5 deletions pip_run/scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import itertools
import io
import re
import json


try:
Expand All @@ -13,6 +14,8 @@
import pkg_resources


__metaclass__ = type

if sys.version_info < (3,):
filter = itertools.ifilter
map = itertools.imap
Expand All @@ -37,12 +40,12 @@ def __init__(self, script):
self.script = script

@classmethod
def load(cls, script_path):
with io.open(script_path) as stream:
return cls(stream.read())
def try_read(cls, script_path):
results = (subclass._try_read(script_path) for subclass in cls.__subclasses__())
return next(filter(None, results), Dependencies())

@classmethod
def try_read(cls, script_path):
def _try_read(cls, script_path):
"""
Attempt to load the dependencies from the script,
but return an empty list if unsuccessful.
Expand All @@ -51,7 +54,7 @@ def try_read(cls, script_path):
reader = cls.load(script_path)
return reader.read()
except Exception:
return Dependencies()
pass

@classmethod
def search(cls, params):
Expand Down Expand Up @@ -105,6 +108,26 @@ def strip_f(match):
return re.sub(r'\bf[\'"]', strip_f, removed)


class SourceDepsReader(DepsReader):
@classmethod
def load(cls, script_path):
with io.open(script_path) as stream:
return cls(stream.read())


class NotebookDepsReader(DepsReader):
@classmethod
def load(cls, script_path):
doc = json.load(open(script_path))
lines = (
line
for cell in doc['cells']
for line in cell['source'] + ['\n']
if cell['cell_type'] == 'code' and not line.startswith('%')
)
return cls(''.join(lines))


def run(cmdline):
"""
Execute the script as if it had been invoked naturally.
Expand Down
56 changes: 55 additions & 1 deletion pip_run/tests/test_scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import sys
import subprocess

import pytest
import nbformat

from pip_run import scripts


Expand All @@ -27,7 +30,7 @@ def test_pkg_imported(tmpdir):
assert 'Successfully imported path.py' in out


class TestDepsReader:
class TestSourceDepsReader:
def test_reads_files_with_attribute_assignment(self):
script = textwrap.dedent(
'''
Expand Down Expand Up @@ -81,6 +84,57 @@ def test_fstrings_allowed(self):
assert reqs == ['foo']


class TestNotebookDepsReader:
@pytest.fixture
def notebook_factory(self, tmpdir, request):
class Factory:
def __init__(self):
self.nb = nbformat.v4.new_notebook()
self.path = tmpdir / (request.node.name + '.ipynb')

@property
def filename(self):
return str(self.path)

def write(self):
nbformat.write(self.nb, self.filename)

def add_code(self, code):
self.nb['cells'].append(nbformat.v4.new_code_cell(code))

def add_markdown(self, text):
self.nb['cells'].append(nbformat.v4.new_markdown_cell(text))

return Factory()

def test_one_code_block(self, notebook_factory):
notebook_factory.add_code('__requires__ = ["matplotlib"]')
notebook_factory.write()
reqs = scripts.DepsReader.try_read(notebook_factory.filename)
assert reqs == ['matplotlib']

def test_multiple_code_blocks(self, notebook_factory):
notebook_factory.add_code('__requires__ = ["matplotlib"]')
notebook_factory.add_code("import matplotlib")
notebook_factory.write()
reqs = scripts.DepsReader.try_read(notebook_factory.filename)
assert reqs == ['matplotlib']

def test_code_and_markdown(self, notebook_factory):
notebook_factory.add_code('__requires__ = ["matplotlib"]')
notebook_factory.add_markdown("Mark this down please")
notebook_factory.write()
reqs = scripts.DepsReader.try_read(notebook_factory.filename)
assert reqs == ['matplotlib']

def test_jupyter_directives(self, notebook_factory):
notebook_factory.add_code('__requires__ = ["matplotlib"]')
notebook_factory.add_code("%matplotlib inline\nimport matplotlib")
notebook_factory.write()
reqs = scripts.DepsReader.try_read(notebook_factory.filename)
assert reqs == ['matplotlib']


def test_pkg_loaded_from_alternate_index(tmpdir):
"""
Create a script that loads cython from an alternate index
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ testing =
pytest-cov

# local
nbformat

docs =
# upstream
Expand Down

0 comments on commit 697dc43

Please sign in to comment.