Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
48dd05a
Initial commit adding option to hide code cells when rendering Jupyte…
bcwu Jun 9, 2021
ea19966
add types-six to requirements.txt
bcwu Jun 10, 2021
dd98b85
update types
bcwu Jun 10, 2021
8be15a2
skip parameter type check
bcwu Jun 10, 2021
a06a30c
add types-click to requirements.txt
bcwu Jun 10, 2021
8267f39
Add types-Flask to requirements.txt
bcwu Jun 10, 2021
3448524
rename noinput
bcwu Jun 23, 2021
60ee7d9
Merge branch 'master' into hide-code-cells-jupyter
bcwu Jun 23, 2021
2d3b32c
add feature to hide input cells tagged with 'remove_input'
bcwu Jun 28, 2021
eddfc7c
Merge branch 'hide-code-cells-jupyter' of github.com:rstudio/rsconnec…
bcwu Jun 28, 2021
12e5781
rename no_input to hide_all_input; rename no_tag_input to hide_tagge…
bcwu Jul 12, 2021
1e84f06
Update changelog for hide Jupyter code cell API
bcwu Jul 12, 2021
32120f5
Merge branch 'master' into hide-code-cells-jupyter
bcwu Jul 13, 2021
db19964
update parameters to match click
bcwu Jul 14, 2021
49b44e4
Merge branch 'hide-code-cells-jupyter' of github.com:rstudio/rsconnec…
bcwu Jul 14, 2021
948a32b
fix hide tagged input not working for static mode
bcwu Jul 16, 2021
8e7e687
hide_all_input will take precedence if both hide_all_input and hide_t…
bcwu Jul 19, 2021
226f379
Revert "hide_all_input will take precedence if both hide_all_input an…
bcwu Jul 19, 2021
cb05102
hide_all_input will take precedence when both hide_all_input and hide…
bcwu Jul 19, 2021
a7cc3f5
update manifest creation so hide input options don't overwrite one an…
bcwu Jul 20, 2021
1987836
update defaults
bcwu Jul 20, 2021
6040f1b
add jupyter manifest handling without changing default manifest format
bcwu Jul 20, 2021
4ff75fb
update defaults
bcwu Jul 20, 2021
9ff59ba
add Python 2 compatibility for hide cell feature
bcwu Jul 23, 2021
2922c74
Update README with Hide Jupyter Notebook Input Code Cells
bcwu Jul 26, 2021
7bdbb8f
move version checking
bcwu Jul 26, 2021
ff6c7fd
update readme wrt hide input cell Jupyter dependencies
bcwu Jul 26, 2021
dddd5d5
update TagRemovePreprocessor command for Python 2
bcwu Jul 26, 2021
428426e
update TagRemovePreprocessor for Python 2
bcwu Jul 26, 2021
e1bd07c
update TagRemovePreprocessor to remove quotes for jupyter cli parsing
bcwu Jul 27, 2021
dd07a54
Merge branch 'master' into hide-code-cells-jupyter
bcwu Jul 29, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).


## [1.5.4] - TBD

### Added
Expand All @@ -15,6 +16,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
rsconnect-python does not inspect the file contents to identify the object name, which must be
one of the default names that Connect expects (`app`, `application`, `create_app`, or `make_app`).

- Ability to hide code cells when rendering Jupyter notebooks.

After setting up Connect and rsconnect-python, the user can render a Jupyter notebook without its corresponding code cells by passing the ' hide-all-input' flag through the rsconnect cli:

```
rsconnect deploy notebook \
-n server \
-k APIKey \
--hide-all-input \
hello_world.ipynb
```

To selectively hide the input of cells, the user can add a tag call 'hide_input' to the cell, then pass the ' hide-tagged-input' flag through the rsconnect cli:

```
rsconnect deploy notebook \
-n server \
-k APIKey \
--hide-tagged-input \
hello_world.ipynb
```

## [1.5.3] - 2021-05-06

### Added
Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -513,3 +513,35 @@ directory specified above.
<div style="display:none">
Generated from <code>rsconnect-python {{ rsconnect_python.version }}</code>
</div>

### Hide Jupyter Notebook Input Code Cells

The user can render a Jupyter notebook without its corresponding input code cells by passing the '--hide-all-input' flag through the cli:

```
rsconnect deploy notebook \
--server https://connect.example.org:3939 \
--api-key my-api-key \
--hide-all-input \
my-notebook.ipynb
```

To selectively hide input cells in a Jupyter notebook, the user needs to follow a two step process:
1. tag cells with the 'hide_input' tag,
2. then pass the ' --hide-tagged-input' flag through the cli:

```
rsconnect deploy notebook \
--server https://connect.example.org:3939 \
--api-key my-api-key \
--hide-tagged-input \
my-notebook.ipynb
```

By default, rsconnect-python does not install Jupyter notebook related depenencies. These dependencies are installed via rsconnect-jupyter. When the user is using the hide input features in rsconnect-python by itself without rsconnect-jupyter, he/she needs to install the following package depenecies:

```
notebook
nbformat
nbconvert>=5.6.1
```
35 changes: 29 additions & 6 deletions rsconnect/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,8 @@ def deploy_jupyter_notebook(
conda_mode=False,
force_generate=False,
log_callback=None,
hide_all_input=False,
hide_tagged_input=False,
):
"""
A function to deploy a Jupyter notebook to Connect. Depending on the files involved
Expand All @@ -496,6 +498,8 @@ def deploy_jupyter_notebook(
(the default) the lines from the deployment log will be returned as a sequence.
If a log callback is provided, then None will be returned for the log lines part
of the return tuple.
:param hide_all_input: if True, will hide all input cells when rendering output
:param hide_tagged_input: If True, will hide input code cells with the 'hide_input' tag when rendering output
:return: the ultimate URL where the deployed app may be accessed and the sequence
of log lines. The log lines value will be None if a log callback was provided.
"""
Expand All @@ -504,7 +508,9 @@ def deploy_jupyter_notebook(
connect_server, app_store, file_name, new, app_id, title, static
)
python, environment = get_python_env_info(file_name, python, conda_mode=conda_mode, force_generate=force_generate,)
bundle = create_notebook_deployment_bundle(file_name, extra_files, app_mode, python, environment)
bundle = create_notebook_deployment_bundle(
file_name, extra_files, app_mode, python, environment, hide_all_input, hide_tagged_input
)
return _finalize_deploy(
connect_server,
app_store,
Expand Down Expand Up @@ -1097,6 +1103,8 @@ def get_python_env_info(file_name, python, conda_mode=False, force_generate=Fals

def create_notebook_deployment_bundle(
file_name, extra_files, app_mode, python, environment, extra_files_need_validating=True,
hide_all_input=None,
hide_tagged_input=None,
):
"""
Create an in-memory bundle, ready to deploy.
Expand All @@ -1107,6 +1115,8 @@ def create_notebook_deployment_bundle(
:param python: information about the version of Python being used.
:param environment: environmental information.
:param extra_files_need_validating: a flag indicating whether the list of extra
:param hide_all_input: if True, will hide all input cells when rendering output
:param hide_tagged_input: If True, will hide input code cells with the 'hide_input' tag when rendering output
files should be validated or not. Part of validating includes qualifying each
with the parent directory of the notebook file. If you provide False here, make
sure the names are properly qualified first.
Expand All @@ -1119,13 +1129,13 @@ def create_notebook_deployment_bundle(

if app_mode == AppModes.STATIC:
try:
return make_notebook_html_bundle(file_name, python)
return make_notebook_html_bundle(file_name, python, hide_all_input, hide_tagged_input)
except subprocess.CalledProcessError as exc:
# Jupyter rendering failures are often due to
# user code failing, vs. an internal failure of rsconnect-python.
raise api.RSConnectException(str(exc))
else:
return make_notebook_source_bundle(file_name, environment, extra_files)
return make_notebook_source_bundle(file_name, environment, extra_files, hide_all_input, hide_tagged_input)


def create_api_deployment_bundle(
Expand Down Expand Up @@ -1190,7 +1200,13 @@ def spool_deployment_log(connect_server, app, log_callback):


def create_notebook_manifest_and_environment_file(
entry_point_file, environment, app_mode=None, extra_files=None, force=True
entry_point_file,
environment,
app_mode=None,
extra_files=None,
force=True,
hide_all_input=False,
hide_tagged_input=False,
):
"""
Creates and writes a manifest.json file for the given notebook entry point file.
Expand All @@ -1206,13 +1222,18 @@ def create_notebook_manifest_and_environment_file(
:param extra_files: any extra files that should be included in the manifest.
:param force: if True, forces the environment file to be written. even if it
already exists.
:param hide_all_input: if True, will hide all input cells when rendering output
:param hide_tagged_input: If True, will hide input code cells with the 'hide_input' tag when rendering output
:return:
"""
if not write_notebook_manifest_json(entry_point_file, environment, app_mode, extra_files) or force:
if (
not write_notebook_manifest_json(entry_point_file, environment, app_mode, extra_files, hide_all_input, hide_tagged_input)
or force
):
write_environment_file(environment, dirname(entry_point_file))


def write_notebook_manifest_json(entry_point_file, environment, app_mode=None, extra_files=None):
def write_notebook_manifest_json(entry_point_file, environment, app_mode, extra_files, hide_all_input, hide_tagged_input):
"""
Creates and writes a manifest.json file for the given entry point file. If
the application mode is not provided, an attempt will be made to resolve one
Expand All @@ -1225,6 +1246,8 @@ def write_notebook_manifest_json(entry_point_file, environment, app_mode=None, e
:param app_mode: the application mode to assume. If this is None, the extension
portion of the entry point file name will be used to derive one.
:param extra_files: any extra files that should be included in the manifest.
:param hide_all_input: if True, will hide all input cells when rendering output
:param hide_tagged_input: If True, will hide input code cells with the 'hide_input' tag when rendering output
:return: whether or not the environment file (requirements.txt, environment.yml,
etc.) that goes along with the manifest exists.
"""
Expand Down
28 changes: 26 additions & 2 deletions rsconnect/bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ def bundle_add_buffer(bundle, filename, contents):
bundle.addfile(file_info, buf)


def write_manifest(relative_dir, nb_name, environment, output_dir):
# type: (str, str, Environment, str) -> typing.Tuple[list, list]
def write_manifest(relative_dir, nb_name, environment, output_dir, hide_all_input=False, hide_tagged_input=False):
# type: (...) -> typing.Tuple[list, list]
"""Create a manifest for source publishing the specified notebook.

The manifest will be written to `manifest.json` in the output directory..
Expand All @@ -148,6 +148,12 @@ def write_manifest(relative_dir, nb_name, environment, output_dir):
"""
manifest_filename = "manifest.json"
manifest = make_source_manifest(nb_name, environment, AppModes.JUPYTER_NOTEBOOK)
if hide_all_input:
if 'jupyter' not in manifest: manifest['jupyter']= {}
manifest['jupyter'].update({'hide_all_input': hide_all_input})
if hide_tagged_input:
if 'jupyter' not in manifest: manifest['jupyter']= {}
manifest['jupyter'].update({'hide_tagged_input': hide_tagged_input})
manifest_file = join(output_dir, manifest_filename)
created = []
skipped = []
Expand Down Expand Up @@ -205,6 +211,8 @@ def make_notebook_source_bundle(
file, # type: str
environment, # type: Environment
extra_files=None, # type: typing.Optional[typing.List[str]]
hide_all_input=False,
hide_tagged_input=False,
):
# type: (...) -> typing.IO[bytes]
"""Create a bundle containing the specified notebook and python environment.
Expand All @@ -217,6 +225,12 @@ def make_notebook_source_bundle(
nb_name = basename(file)

manifest = make_source_manifest(nb_name, environment, AppModes.JUPYTER_NOTEBOOK)
if hide_all_input:
if 'jupyter' not in manifest: manifest['jupyter']= {}
manifest['jupyter'].update({'hide_all_input': hide_all_input})
if hide_tagged_input:
if 'jupyter' not in manifest: manifest['jupyter']= {}
manifest['jupyter'].update({'hide_tagged_input': hide_tagged_input})
manifest_add_file(manifest, nb_name, base_dir)
manifest_add_buffer(manifest, environment.filename, environment.contents)

Expand Down Expand Up @@ -259,6 +273,8 @@ def make_html_manifest(filename):
def make_notebook_html_bundle(
filename, # type: str
python, # type: str
hide_all_input=False,
hide_tagged_input=False,
check_output=subprocess.check_output, # type: typing.Callable
):
# type: (...) -> typing.IO[bytes]
Expand All @@ -274,6 +290,14 @@ def make_notebook_html_bundle(
"--to=html",
filename,
]
if hide_all_input and hide_tagged_input or hide_all_input:
cmd.append('--no-input')
elif hide_tagged_input:
version = check_output([python, '--version']).decode("utf-8")
if version >= 'Python 3':
cmd.append('--TagRemovePreprocessor.remove_input_tags=hide_input')
else:
cmd.append("--TagRemovePreprocessor.remove_input_tags=['hide_input']")
try:
output = check_output(cmd)
except subprocess.CalledProcessError:
Expand Down
22 changes: 18 additions & 4 deletions rsconnect/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,8 @@ def _deploy_bundle(
"--force-generate", "-g", is_flag=True, help='Force generating "requirements.txt", even if it already exists.',
)
@click.option("--verbose", "-v", is_flag=True, help="Print detailed messages.")
@click.option("--hide-all-input", is_flag=True, default=False, help="Hide all input cells when rendering output")
@click.option("--hide-tagged-input", is_flag=True, default=False, help="Hide input code cells with the 'hide_input' tag")
@click.argument("file", type=click.Path(exists=True, dir_okay=False, file_okay=True))
@click.argument(
"extra_files", nargs=-1, type=click.Path(exists=True, dir_okay=False, file_okay=True),
Expand All @@ -542,6 +544,8 @@ def deploy_notebook(
verbose,
file,
extra_files,
hide_all_input,
hide_tagged_input,
):
set_verbosity(verbose)

Expand Down Expand Up @@ -570,8 +574,9 @@ def deploy_notebook(
_warn_on_ignored_requirements(dirname(file), environment.filename)

with cli_feedback("Creating deployment bundle"):
bundle = create_notebook_deployment_bundle(file, extra_files, app_mode, python, environment, False)

bundle = create_notebook_deployment_bundle(
file, extra_files, app_mode, python, environment, False, hide_all_input, hide_tagged_input
)
_deploy_bundle(
connect_server, app_store, file, app_id, app_mode, deployment_name, title, default_title, bundle,
)
Expand Down Expand Up @@ -936,12 +941,16 @@ def write_manifest():
@click.option(
"--force-generate", "-g", is_flag=True, help='Force generating "requirements.txt", even if it already exists.',
)
@click.option("--hide-all-input", help="Hide all input cells when rendering output")
@click.option("--hide-tagged-input", is_flag=True, default=None, help="Hide input code cells with the 'hide_input' tag")
@click.option("--verbose", "-v", "verbose", is_flag=True, help="Print detailed messages")
@click.argument("file", type=click.Path(exists=True, dir_okay=False, file_okay=True))
@click.argument(
"extra_files", nargs=-1, type=click.Path(exists=True, dir_okay=False, file_okay=True),
)
def write_manifest_notebook(overwrite, python, conda, force_generate, verbose, file, extra_files):
def write_manifest_notebook(
overwrite, python, conda, force_generate, verbose, file, extra_files, hide_all_input=None, hide_tagged_input=None
):
set_verbosity(verbose)
with cli_feedback("Checking arguments"):
validate_file_is_notebook(file)
Expand All @@ -960,7 +969,12 @@ def write_manifest_notebook(overwrite, python, conda, force_generate, verbose, f

with cli_feedback("Creating manifest.json"):
environment_file_exists = write_notebook_manifest_json(
file, environment, AppModes.JUPYTER_NOTEBOOK, extra_files
file,
environment,
AppModes.JUPYTER_NOTEBOOK,
extra_files,
hide_all_input,
hide_tagged_input,
)

if environment_file_exists and not force_generate:
Expand Down