pax_global_header00006660000000000000000000000064145227667560014536gustar00rootroot0000000000000052 comment=e7d75baacef1c476e0b60b643cc36a91470ba28e jupyter-cache-1.0.0/000077500000000000000000000000001452276675600142775ustar00rootroot00000000000000jupyter-cache-1.0.0/.github/000077500000000000000000000000001452276675600156375ustar00rootroot00000000000000jupyter-cache-1.0.0/.github/dependabot.yml000066400000000000000000000010211452276675600204610ustar00rootroot00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "pip" directory: "/" schedule: interval: "weekly" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" jupyter-cache-1.0.0/.github/workflows/000077500000000000000000000000001452276675600176745ustar00rootroot00000000000000jupyter-cache-1.0.0/.github/workflows/tests.yml000066400000000000000000000041771452276675600215720ustar00rootroot00000000000000name: continuous-integration on: push: branches: [master] tags: - 'v*' pull_request: jobs: pre-commit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python 3.9 uses: actions/setup-python@v4 with: python-version: "3.9" - uses: pre-commit/action@v3.0.0 tests: strategy: fail-fast: false matrix: os: [ubuntu-latest] python-version: ["3.9", "3.10", "3.11", "3.12"] include: - os: windows-latest python-version: "3.9" runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install -e .[cli,testing] - name: Run pytest run: | pytest --durations=10 --cov=jupyter_cache --cov-report=xml --cov-report=term-missing coverage xml # for some reason the tests/conftest.py::check_nbs fixture breaks pytest-cov's cov-report outputting # this is why we run `coverage xml` afterwards (required by codecov) - name: Upload to Codecov if: github.repository == 'executablebooks/jupyter-cache' && matrix.python-version == '3.10' && matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v3 with: name: jupyter-cache-pytests-py3.10 flags: pytests file: ./coverage.xml fail_ci_if_error: true publish: name: Publish to PyPi needs: [pre-commit, tests] if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') runs-on: ubuntu-latest steps: - name: Checkout source uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.10" - name: install flit run: | pip install flit~=3.4 - name: Build and publish run: | flit publish env: FLIT_USERNAME: __token__ FLIT_PASSWORD: ${{ secrets.PYPI_KEY }} jupyter-cache-1.0.0/.gitignore000066400000000000000000000035361452276675600162760ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ _archive/ .jupyter_cache/ *_old* .DS_Store .vscode/ ~$* _*.ipynb final_notebook.ipynb jupyter-cache-1.0.0/.pre-commit-config.yaml000066400000000000000000000014651452276675600205660ustar00rootroot00000000000000# Install pre-commit hooks via # pre-commit install exclude: > (?x)^( \.vscode/settings\.json )$ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - id: check-json - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/pyupgrade rev: v3.3.1 hooks: - id: pyupgrade args: [--py37-plus] - repo: https://github.com/pycqa/isort rev: 5.12.0 hooks: - id: isort - repo: https://github.com/psf/black rev: 23.3.0 hooks: - id: black - repo: https://github.com/PyCQA/flake8 rev: 6.0.0 hooks: - id: flake8 # - repo: https://github.com/pre-commit/mirrors-mypy # rev: v0.910 # hooks: # - id: mypy # additional_dependencies: [] jupyter-cache-1.0.0/.readthedocs.yml000066400000000000000000000003471452276675600173710ustar00rootroot00000000000000version: 2 build: os: ubuntu-22.04 tools: python: "3.11" python: install: - method: pip path: . extra_requirements: - cli - rtd sphinx: builder: html fail_on_warning: true jupyter-cache-1.0.0/CHANGELOG.md000066400000000000000000000132441452276675600161140ustar00rootroot00000000000000# CHANGELOG ## v0.6.1 2023-04-22 A patch release to fix compatibility with sqlalchemy <1.4. - FIX: compatibility with SQLAlchemy < 1.4.0 [#105](https://github.com/executablebooks/jupyter-cache/pull/105) @DimitriPapadopoulos ## v0.6.0 2023-04-21 This is a minor release to improve our packaging infrastructure and to support several new versions of dependencies. ### Breaking changes - ‼️ BREAKING: Drop Python 3.7, add Python 3.11, unpin myst-nb in docs [#96](https://github.com/executablebooks/jupyter-cache/pull/96) ([@choldgraf](https://github.com/choldgraf)) ### Updated versions - Update nbclient requirement from <0.6,>=0.2 to >=0.2,<0.8 [#103](https://github.com/executablebooks/jupyter-cache/pull/103) ([@choldgraf](https://github.com/choldgraf)) - UPDATE: SQLAlchemy 2.0 [#93](https://github.com/executablebooks/jupyter-cache/pull/93) ([@jzluo](https://github.com/jzluo), [@choldgraf](https://github.com/choldgraf)) - 🔧 MAINTAIN: setuptools -> flit [#82](https://github.com/executablebooks/jupyter-cache/pull/82) ([@chrisjsewell](https://github.com/chrisjsewell)) ### Contributors to this release The following people contributed discussions, new ideas, code and documentation contributions, and review. See [our definition of contributors](https://github-activity.readthedocs.io/en/latest/#how-does-this-tool-define-contributions-in-the-reports). ([GitHub contributors page for this release](https://github.com/executablebooks/jupyter-cache/graphs/contributors?from=2022-01-25&to=2023-04-21&type=c)) @AakashGfude ([activity](https://github.com/search?q=repo%3Aexecutablebooks%2Fjupyter-cache+involves%3AAakashGfude+updated%3A2022-01-25..2023-04-21&type=Issues)) | @choldgraf ([activity](https://github.com/search?q=repo%3Aexecutablebooks%2Fjupyter-cache+involves%3Acholdgraf+updated%3A2022-01-25..2023-04-21&type=Issues)) | @chrisjsewell ([activity](https://github.com/search?q=repo%3Aexecutablebooks%2Fjupyter-cache+involves%3Achrisjsewell+updated%3A2022-01-25..2023-04-21&type=Issues)) | @jstac ([activity](https://github.com/search?q=repo%3Aexecutablebooks%2Fjupyter-cache+involves%3Ajstac+updated%3A2022-01-25..2023-04-21&type=Issues)) | @jzluo ([activity](https://github.com/search?q=repo%3Aexecutablebooks%2Fjupyter-cache+involves%3Ajzluo+updated%3A2022-01-25..2023-04-21&type=Issues)) | @kloczek ([activity](https://github.com/search?q=repo%3Aexecutablebooks%2Fjupyter-cache+involves%3Akloczek+updated%3A2022-01-25..2023-04-21&type=Issues)) | @pre-commit-ci ([activity](https://github.com/search?q=repo%3Aexecutablebooks%2Fjupyter-cache+involves%3Apre-commit-ci+updated%3A2022-01-25..2023-04-21&type=Issues)) ## 0.5.0 - 2021-01-25 ♻️ REFACTOR: package API/CLI/documentation ([#74](https://github.com/executablebooks/jupyter-cache/pull/74)) This release includes major re-writes to key parts of the package, to improve the user interface, and add additional functionality for reading and executing notebooks. Key changes: 1. `stage`/`staging` is now rephrased to `notebook`, plus the addition of `project`, i.e. you add notebooks to a project, then execute them. 2. notebook `read_data` is specified per notebook in the project, allowing for multiple types of file to be read/executed via the CLI (e.g. text-based notebooks via ). Before, the read functions were passed directly to the API methods. 3. The executor can be specified with `jbcache execute --executor`, and a parallel notebook executor has been added. 4. Improved execution status indicator in `jbcache project list` and other CLI improvements. 5. Re-write of documentation, including better front page, with quick start guide and better logo. Dependencies have also been restructured, such that the CLI dependencies (`click`, `tabulate`) are now required, whereas `nbdime` is now optional (since it is only used for optional notebook diffing). ‼️ Breaking: The name of the SQL table `nbstage` has been changed to `nbproject`, and `read_data`/`exec_data` fields have been added to the `nbproject`. This means that reading will fail for caches creted using older versions of the package. However, the version of `jupyter-cache`, used to create the cache, is now recorded, allowing for the possibility of future automated migrations. ## 0.4.3 - 2021-07-29 ⬆️ Allow SQLAlchemy v1.4 ## 0.4.2 - 2021-01-17 🐛 FIX: nbfomat v4.5 cell IDs Version 4.5 notebooks now contain `cell.id` (see [JEP 0062](https://jupyter.org/enhancement-proposals/62-cell-id/cell-id.html#Case-loading-notebook-without-cell-id)). To deal with this, we always hash the notebooks as v4.4 (with ids removed), since IDs do not affect the execution output. Merging cached outputs into a notebook now also preserves the input notebook minor version, adding or removing `cell.id` where required. ## 0.4.1 - 2020-08-28 ⬆️ UPGRADE: nbclient v0.5 ## 0.4.0 - 2020-08-19 - 👌 IMPROVE: Add `allow_errors` execution option to `JupyterExecutorBasic.run_and_cache` This can also be set in the notebook metadata: `nb.metadata.execution.allow_errors` - 👌 IMPROVE: Add `run_in_temp` execution option to `JupyterExecutorBasic.run_and_cache` - ⬇️ DOWNGRADE: Relax pinning of nbclient Since there are reports of issues with version 0.3, see: [jupyter/nbclient#58](https://github.com/jupyter/nbclient/issues/58) - ♻️ REFACTOR: Extract single notebook execution into separate function Useful for upstream use. ## 0.3.0 - 2020-08-05 ### Improved 👌 - Moved execution functionality from [nbconvert](https://github.com/jupyter/nbconvert) to [nbclient](https://github.com/jupyter/nbclient) - Fixed UTF8 encoding (for Windows OS), thanks to @phaustin ### Fixed 🐛 - Moved testing from Travis CI to GitHub Actions (and added tests for Windows OS) jupyter-cache-1.0.0/LICENSE000066400000000000000000000020661452276675600153100ustar00rootroot00000000000000MIT License Copyright (c) 2022 ExecutableBookProject Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. jupyter-cache-1.0.0/README.md000066400000000000000000000067401452276675600155650ustar00rootroot00000000000000# jupyter-cache [![Github-CI][github-ci]][github-link] [![Coverage Status][codecov-badge]][codecov-link] [![Documentation Status][rtd-badge]][rtd-link] [![Code style: black][black-badge]][black-link] [![PyPI][pypi-badge]][pypi-link] A defined interface for working with a cache of jupyter notebooks. ## Why use jupyter-cache? If you have a number of notebooks whose execution outputs you want to ensure are kept up to date, without having to re-execute them every time (particularly for long running code, or text-based formats that do not store the outputs). The notebooks must have deterministic execution outputs: - You use the same environment to run them (e.g. the same installed packages) - They run no non-deterministic code (e.g. random numbers) - They do not depend on external resources (e.g. files or network connections) that change over time For example, it is utilised by [jupyter-book](https://jupyterbook.org/content/execute.html#caching-the-notebook-execution), to allow for fast document re-builds. ## Install ```bash pip install jupyter-cache ``` For development: ```bash git clone https://github.com/executablebooks/jupyter-cache cd jupyter-cache git checkout develop pip install -e .[cli,code_style,testing] ``` See the documentation for usage. ## Development Some desired requirements (not yet all implemented): - Persistent - Separates out "edits to content" from "edits to code cells". Cell rearranges and code cell changes should require a re-execution. Content changes should not. - Allow parallel access to notebooks (for execution) - Store execution statistics/reports - Store external assets: Notebooks being executed often require external assets: importing scripts/data/etc. These are prepared by the users. - Store execution artefacts: created during execution - A transparent and robust cache invalidation: imagine the user updating an external dependency or a Python module, or checking out a different git branch. ## Contributing jupyter-cache follows the [Executable Book Contribution Guide](https://executablebooks.org/en/latest/contributing.html). We'd love your help! ### Code Style Code style is tested using [flake8](http://flake8.pycqa.org), with the configuration set in `.flake8`, and code formatted with [black](https://github.com/ambv/black). Installing with `jupyter-cache[code_style]` makes the [pre-commit](https://pre-commit.com/) package available, which will ensure this style is met before commits are submitted, by reformatting the code and testing for lint errors. It can be setup by: ```shell >> cd jupyter-cache >> pre-commit install ``` Optionally you can run `black` and `flake8` separately: ```shell >> black . >> flake8 . ``` Editors like VS Code also have automatic code reformat utilities, which can adhere to this standard. [github-ci]: https://github.com/executablebooks/jupyter-cache/workflows/continuous-integration/badge.svg?branch=master [github-link]: https://github.com/executablebooks/jupyter-cache [codecov-badge]: https://codecov.io/gh/executablebooks/jupyter-cache/branch/master/graph/badge.svg [codecov-link]: https://codecov.io/gh/executablebooks/jupyter-cache [rtd-badge]: https://readthedocs.org/projects/jupyter-cache/badge/?version=latest [rtd-link]: https://jupyter-cache.readthedocs.io/en/latest/?badge=latest [black-badge]: https://img.shields.io/badge/code%20style-black-000000.svg [pypi-badge]: https://img.shields.io/pypi/v/jupyter-cache.svg [pypi-link]: https://pypi.org/project/jupyter-cache [black-link]: https://github.com/ambv/black jupyter-cache-1.0.0/codecov.yml000066400000000000000000000002421452276675600164420ustar00rootroot00000000000000coverage: status: project: default: target: 80% threshold: 0.5% patch: default: target: 75% threshold: 0.5% jupyter-cache-1.0.0/docs/000077500000000000000000000000001452276675600152275ustar00rootroot00000000000000jupyter-cache-1.0.0/docs/.gitignore000066400000000000000000000000161452276675600172140ustar00rootroot00000000000000_build/ _api/ jupyter-cache-1.0.0/docs/Makefile000066400000000000000000000014351452276675600166720ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) # raise warnings to errors html-strict: @$(SPHINXBUILD) -b html -nW --keep-going "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) $(O) clean: rm -r $(BUILDDIR) jupyter-cache-1.0.0/docs/_static/000077500000000000000000000000001452276675600166555ustar00rootroot00000000000000jupyter-cache-1.0.0/docs/_static/logo_square.svg000066400000000000000000000050001452276675600217110ustar00rootroot00000000000000 jupyter-cache-1.0.0/docs/_static/logo_wide.svg000066400000000000000000000266241452276675600213600ustar00rootroot00000000000000 jupyter-cache-1.0.0/docs/conf.py000066400000000000000000000140061452276675600165270ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Project information ----------------------------------------------------- project = "Jupyter Cache" copyright = "2020, Executable Book Project" author = "Executable Book Project" master_doc = "index" # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "myst_nb", "sphinx.ext.intersphinx", "sphinx_copybutton", # "sphinx.ext.autodoc", # "sphinx.ext.viewcode", ] myst_enable_extensions = ["colon_fence", "deflist"] nb_execution_mode = "off" html_theme_options = { "repository_url": "https://github.com/executablebooks/jupyter-cache", "use_repository_button": True, "use_edit_page_button": True, "use_issues_button": True, "repository_branch": "master", "path_to_docs": "docs", "home_page_in_toc": True, } # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [ "_build", "Thumbs.db", ".DS_Store", "**/example_nbs/*", "**/.jupyter_cache/**/*", "**/.ipynb_checkpoints/*", ] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "sphinx_book_theme" html_title = "Jupyter Cache" html_logo = "_static/logo_wide.svg" html_favicon = "_static/logo_square.svg" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] # html_css_files = ["css/custom.css"] intersphinx_mapping = {"python": ("https://docs.python.org/3.8", None)} autodoc_member_order = "bysource" nitpick_ignore = [ ("py:class", "Any"), ("py:class", "Tuple"), ("py:class", "ForwardRef"), ("py:class", "NoneType"), ] def setup(app): import importlib import os import shutil import traceback import click from click.testing import CliRunner from docutils import nodes from docutils.parsers.rst import directives from sphinx.util.docutils import SphinxDirective class JcacheClear(SphinxDirective): """A directive to clear the jupyter cache.""" def run(self): path = os.path.join(os.path.dirname(self.env.app.srcdir), ".jupyter_cache") if os.path.exists(path): shutil.rmtree(path) return [] class JcacheCli(SphinxDirective): """A directive to run a CLI command, and output a nicely formatted representation of the input command and its output. """ required_arguments = 1 # command final_argument_whitespace = False has_content = False option_spec = { "prog": directives.unchanged_required, "command": directives.unchanged_required, "args": directives.unchanged_required, "input": directives.unchanged_required, "allow-exception": directives.flag, } def run(self): modpath = self.arguments[0] try: module_name, attr_name = modpath.split(":", 1) except ValueError: raise self.error(f'"{modpath}" is not of format "module:command"') try: module = importlib.import_module(module_name) except Exception: raise self.error( f"Failed to import '{module_name}': {traceback.format_exc()}" ) if not hasattr(module, attr_name): raise self.error( f'Module "{module_name}" has no attribute "{attr_name}"' ) command = getattr(module, attr_name) if not isinstance(command, click.Group): raise self.error( f'"{modpath}" of type {type(command)}"" is not derived from "click.Group"' ) cmd_string = [self.options.get("prog", "jcache")] if command.name != cmd_string[0]: cmd_string.append(command.name) if "command" in self.options: cmd_string.append(self.options["command"]) command = command.commands[self.options["command"]] args = self.options.get("args", "") runner = CliRunner() root_path = os.path.dirname(self.env.app.srcdir) try: old_cwd = os.getcwd() os.chdir(root_path) result = runner.invoke( command, args.split(), input=self.options.get("input", None), env={} ) finally: os.chdir(old_cwd) if result.exception and "allow-exception" not in self.options: raise self.error( f"CLI raised exception: {result.exception}\n---\n{result.output}\n---\n" ) if result.exit_code != 0 and "allow-exception" not in self.options: raise self.error( f"CLI non-zero exit code: {result.exit_code}\n---\n{result.output}\n---\n" ) text = f"$ {' '.join(cmd_string)} {args}\n{result.output}" text = text.replace(root_path + os.sep, "../") node = nodes.literal_block(text, text, language="console") return [node] app.add_directive("jcache-clear", JcacheClear) app.add_directive("jcache-cli", JcacheCli) jupyter-cache-1.0.0/docs/develop/000077500000000000000000000000001452276675600166655ustar00rootroot00000000000000jupyter-cache-1.0.0/docs/develop/contributing.md000066400000000000000000000065671452276675600217340ustar00rootroot00000000000000# Contributing [![Github-CI][github-ci]][github-link] [![Coverage Status][codecov-badge]][codecov-link] [![Documentation Status][rtd-badge]][rtd-link] [![Code style: black][black-badge]][black-link] [![PyPI][pypi-badge]][pypi-link] ## Installation For package development: ```bash git clone https://github.com/executablebooks/jupyter-cache cd jupyter-cache git checkout develop pip install -e .[cli,code_style,testing,rtd] ``` ## Code Style Code style is tested using [flake8](http://flake8.pycqa.org), with the configuration set in `.flake8`, and code formatted with [black](https://github.com/ambv/black). Installing with `jupyter-cache[code_style]` makes the [pre-commit](https://pre-commit.com/) package available, which will ensure this style is met before commits are submitted, by reformatting the code and testing for lint errors. It can be setup by: ```shell >> cd jupyter-cache >> pre-commit install ``` Optionally you can run `black` and `flake8` separately: ```shell >> black . >> flake8 . ``` Editors like VS Code also have automatic code reformat utilities, which can adhere to this standard. ## Testing For code tests: ```shell >> cd jupyter-cache >> pytest ``` For documentation build tests: ```shell >> cd jupyter-cache/docs >> make clean >> make html-strict ``` ## Pull Requests To contribute, make Pull Requests to the `master` branch (this is the default branch). A PR can consist of one or multiple commits. Before you open a PR, make sure to clean up your commit history and create the commits that you think best divide up the total work as outlined above (use `git rebase` and `git commit --amend`). Ensure all commit messages clearly summarise the changes in the header and the problem that this commit is solving in the body. Merging pull requests: There are three ways of 'merging' pull requests on GitHub: - Squash and merge: take all commits, squash them into a single one and put it on top of the base branch. Choose this for pull requests that address a single issue and are well represented by a single commit. Make sure to clean the commit message (title & body) - Rebase and merge: take all commits and 'recreate' them on top of the base branch. All commits will be recreated with new hashes. Choose this for pull requests that require more than a single commit. Examples: PRs that contain multiple commits with individually significant changes; PRs that have commits from different authors (squashing commits would remove attribution) - Merge with merge commit: put all commits as they are on the base branch, with a merge commit on top Choose for collaborative PRs with many commits. Here, the merge commit provides actual benefits. [github-ci]: https://github.com/executablebooks/jupyter-cache/workflows/continuous-integration/badge.svg?branch=master [github-link]: https://github.com/executablebooks/jupyter-cache [codecov-badge]: https://codecov.io/gh/executablebooks/jupyter-cache/branch/master/graph/badge.svg [codecov-link]: https://codecov.io/gh/executablebooks/jupyter-cache [rtd-badge]: https://readthedocs.org/projects/jupyter-cache/badge/?version=latest [rtd-link]: https://jupyter-cache.readthedocs.io/en/latest/?badge=latest [black-badge]: https://img.shields.io/badge/code%20style-black-000000.svg [pypi-badge]: https://img.shields.io/pypi/v/jupyter-cache.svg [pypi-link]: https://pypi.org/project/jupyter-cache [black-link]: https://github.com/ambv/black jupyter-cache-1.0.0/docs/index.md000066400000000000000000000104421452276675600166610ustar00rootroot00000000000000# Jupyter Cache Execute and cache multiple Jupyter Notebook-like files via an [API](use/api) and [CLI](use/cli). 🤓 Smart re-execution : Notebooks will only be re-executed when **code cells** have changed (or code related metadata), not Markdown/Raw cells. 🧩 Pluggable execution modes : Select the executor for notebooks, including serial and parallel execution 📈 Execution reports : Timing statistics and exception tracebacks are stored for analysis 📖 [jupytext](https://jupytext.readthedocs.io) integration : Read and execute notebooks written in multiple formats ## Why use jupyter-cache? If you have a number of notebooks whose execution outputs you want to ensure are kept up to date, without having to re-execute them every time (particularly for long running code, or text-based formats that do not store the outputs). The notebooks must have deterministic execution outputs: - You use the same environment to run them (e.g. the same installed packages) - They run no non-deterministic code (e.g. random numbers) - They do not depend on external resources (e.g. files or network connections) that change over time For example, it is utilised by [jupyter-book](https://jupyterbook.org/content/execute.html#caching-the-notebook-execution), to allow for fast document re-builds. ## Installation Install `jupyter-cache`, via pip or Conda: ```bash pip install jupyter-cache ``` ```bash conda install jupyter-cache ``` ## Quick-start ```{jcache-clear} ``` Add one or more source notebook files to the "project" (a folder containing a database and a cache of executed notebooks): ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: add :args: tests/notebooks/basic_unrun.ipynb tests/notebooks/basic_failing.ipynb :input: y ``` These files are now ready for execution: ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: list ``` Now run the execution: ```{jcache-cli} jupyter_cache.cli.commands.cmd_project:cmnd_project :command: execute ``` Successfully executed files will now be associated with a record in the cache: ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: list ``` The cache record includes execution statistics: ```{jcache-cli} jupyter_cache.cli.commands.cmd_cache:cmnd_cache :command: info :args: 1 ``` Next time we execute, jupyter-cache will check which files require re-execution: ```{jcache-cli} jupyter_cache.cli.commands.cmd_project:cmnd_project :command: execute ``` The source files themselves will not be modified during/after execution. You can create a new "final" notebook, with the cached outputs merged into the source notebook with: ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: merge :args: 1 final_notebook.ipynb ``` You can also add notebooks with custom formats, such as those read by [jupytext](https://jupytext.readthedocs.io): ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: add :args: --reader jupytext tests/notebooks/basic.md ``` ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: list ``` ## Design considerations Although there are certainly other use cases, the principle use case this was written for is generating books / websites, created from multiple notebooks (and other text documents). It is desired that notebooks can be *auto-executed* **only** if the notebook had been modified in a way that may alter its code cell outputs. Some desired requirements (not yet all implemented): - A clear and robust API - The cache is persistent on disk - Notebook comparisons separate out "edits to content" from "edits to code cells". Cell rearranges and code cell changes should require a re-execution. Text content changes should not. - Allow parallel access to notebooks (for execution) - Store execution statistics/reports. - Store external assets: Notebooks being executed often require external assets: importing scripts/data/etc. These are prepared by the users. - Store execution artefacts: created during execution - A transparent and robust cache invalidation: imagine the user updating an external dependency or a Python module, or checking out a different git branch. ## Contents ```{toctree} :caption: Tutorials using/cli using/api ``` ```{toctree} :caption: Development develop/contributing ``` jupyter-cache-1.0.0/docs/using/000077500000000000000000000000001452276675600163545ustar00rootroot00000000000000jupyter-cache-1.0.0/docs/using/api.ipynb000066400000000000000000001173541452276675600202030ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "(use/api)=\n", "\n", "# Python API" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This page outlines how to utilise the cache programatically.\n", "We step throught the three aspects illustrated in the diagram below:\n", "[cacheing](use/api/cache), [staging](use/api/project) and [executing](use/api/execute).\n", "\n", "```{figure} images/execution_process.svg\n", ":width: 500 px\n", "\n", "Illustration of the execution process.\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```{note}\n", "The full Jupyter notebook for this page can accessed here; {nb-download}`api.ipynb`.\n", "Try it for yourself!\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Initialisation" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from pathlib import Path\n", "import nbformat as nbf\n", "from jupyter_cache import get_cache\n", "from jupyter_cache.base import CacheBundleIn\n", "from jupyter_cache.executors import load_executor, list_executors\n", "from jupyter_cache.utils import (\n", " tabulate_cache_records, \n", " tabulate_project_records\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First we setup a cache and ensure that it is cleared.\n", "\n", "```{important}\n", "Clearing a cache wipes its entire content, including any settings (such as cache limit).\n", "```" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "JupyterCacheBase('/Users/chrisjsewell/Documents/GitHub/jupyter-cache/docs/using/.jupyter_cache')" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cache = get_cache(\".jupyter_cache\")\n", "cache.clear_cache()\n", "cache" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[]\n", "[]\n" ] } ], "source": [ "print(cache.list_cache_records())\n", "print(cache.list_project_records())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(use/api/cache)=\n", "\n", "## Cacheing Notebooks" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To directly cache a notebook:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "NbCacheRecord(pk=1)" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "record = cache.cache_notebook_file(\n", " path=Path(\"example_nbs\", \"basic.ipynb\")\n", ")\n", "record" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This will add a physical copy of the notebook to tha cache (stripped of any text cells) and return the record that has been added to the cache database.\n", "\n", "```{important}\n", "The returned record is static, as in it will not update if the database is updated.\n", "```\n", "\n", "The record stores metadata for the notebook:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'description': '',\n", " 'hashkey': '94c17138f782c75df59e989fffa64e3a',\n", " 'created': datetime.datetime(2022, 1, 12, 15, 15, 27, 255299),\n", " 'accessed': datetime.datetime(2022, 1, 12, 15, 15, 27, 255312),\n", " 'data': {},\n", " 'uri': 'example_nbs/basic.ipynb',\n", " 'pk': 1}" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "record.to_dict()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```{important}\n", "The URI that the notebook is read from is stored, but does not have an impact on later comparison of notebooks. They are only compared by their internal content.\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can retrive cache records by their Primary Key (pk): " ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[NbCacheRecord(pk=1)]" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cache.list_cache_records()" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "NbCacheRecord(pk=1)" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cache.get_cache_record(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To load the entire notebook that is related to a pk:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "CacheBundleOut(nb=Notebook(cells=1), record=NbCacheRecord(pk=1), artifacts=NbArtifacts(paths=0))" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "nb_bundle = cache.get_cache_bundle(1)\n", "nb_bundle" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'cells': [{'cell_type': 'code',\n", " 'execution_count': 1,\n", " 'metadata': {},\n", " 'outputs': [{'name': 'stdout', 'output_type': 'stream', 'text': '1\\n'}],\n", " 'source': 'a=1\\nprint(a)'}],\n", " 'metadata': {'kernelspec': {'display_name': 'Python 3',\n", " 'language': 'python',\n", " 'name': 'python3'},\n", " 'language_info': {'codemirror_mode': {'name': 'ipython', 'version': 3},\n", " 'file_extension': '.py',\n", " 'mimetype': 'text/x-python',\n", " 'name': 'python',\n", " 'nbconvert_exporter': 'python',\n", " 'pygments_lexer': 'ipython3',\n", " 'version': '3.6.1'},\n", " 'test_name': 'notebook1'},\n", " 'nbformat': 4,\n", " 'nbformat_minor': 2}" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "nb_bundle.nb" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Trying to add a notebook to the cache that matches an existing one will result in a error, since the cache ensures that all notebook hashes are unique:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "tags": [ "raises-exception" ] }, "outputs": [ { "ename": "CachingError", "evalue": "Notebook already exists in cache and overwrite=False.", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mCachingError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m/var/folders/t2/xbl15_3n4tsb1vr_ccmmtmbr0000gn/T/ipykernel_99993/3576020660.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m record = cache.cache_notebook_file(\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0mpath\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mPath\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"example_nbs\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"basic.ipynb\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m )\n", "\u001b[0;32m~/Documents/GitHub/jupyter-cache/jupyter_cache/cache/main.py\u001b[0m in \u001b[0;36mcache_notebook_file\u001b[0;34m(self, path, uri, artifacts, data, check_validity, overwrite)\u001b[0m\n\u001b[1;32m 268\u001b[0m \"\"\"\n\u001b[1;32m 269\u001b[0m \u001b[0mnotebook\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnbf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpath\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnbf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mNO_CONVERT\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 270\u001b[0;31m return self.cache_notebook_bundle(\n\u001b[0m\u001b[1;32m 271\u001b[0m CacheBundleIn(\n\u001b[1;32m 272\u001b[0m \u001b[0mnotebook\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/Documents/GitHub/jupyter-cache/jupyter_cache/cache/main.py\u001b[0m in \u001b[0;36mcache_notebook_bundle\u001b[0;34m(self, bundle, check_validity, overwrite, description)\u001b[0m\n\u001b[1;32m 213\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mpath\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexists\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 214\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0moverwrite\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 215\u001b[0;31m raise CachingError(\n\u001b[0m\u001b[1;32m 216\u001b[0m \u001b[0;34m\"Notebook already exists in cache and overwrite=False.\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 217\u001b[0m )\n", "\u001b[0;31mCachingError\u001b[0m: Notebook already exists in cache and overwrite=False." ] } ], "source": [ "record = cache.cache_notebook_file(\n", " path=Path(\"example_nbs\", \"basic.ipynb\")\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we load a notebook external to the cache, then we can try to match it to one\n", "stored inside the cache:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'cells': [{'cell_type': 'markdown',\n", " 'metadata': {},\n", " 'source': '# a title\\n\\nsome text\\n'},\n", " {'cell_type': 'code',\n", " 'execution_count': 1,\n", " 'metadata': {},\n", " 'source': 'a=1\\nprint(a)',\n", " 'outputs': [{'name': 'stdout', 'output_type': 'stream', 'text': '1\\n'}]}],\n", " 'metadata': {'test_name': 'notebook1',\n", " 'kernelspec': {'display_name': 'Python 3',\n", " 'language': 'python',\n", " 'name': 'python3'},\n", " 'language_info': {'codemirror_mode': {'name': 'ipython', 'version': 3},\n", " 'file_extension': '.py',\n", " 'mimetype': 'text/x-python',\n", " 'name': 'python',\n", " 'nbconvert_exporter': 'python',\n", " 'pygments_lexer': 'ipython3',\n", " 'version': '3.6.1'}},\n", " 'nbformat': 4,\n", " 'nbformat_minor': 2}" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "notebook = nbf.read(str(Path(\"example_nbs\", \"basic.ipynb\")), 4)\n", "notebook" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "NbCacheRecord(pk=1)" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cache.match_cache_notebook(notebook)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notebooks are matched by a hash based only on aspects of the notebook that will affect its execution (and hence outputs). So changing text cells will match the cached notebook:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "notebook.cells[0].source = \"change some text\"" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "NbCacheRecord(pk=1)" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cache.match_cache_notebook(notebook)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But changing code cells will result in a different hash, and so will not be matched:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "notebook.cells[1].source = \"change some source code\"" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "tags": [ "raises-exception" ] }, "outputs": [ { "ename": "KeyError", "evalue": "'Cache record not found for NB with hashkey: 07e6a47c8c180cb7851ede6dbb088769'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m/var/folders/t2/xbl15_3n4tsb1vr_ccmmtmbr0000gn/T/ipykernel_99993/941642554.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mcache\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmatch_cache_notebook\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnotebook\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m~/Documents/GitHub/jupyter-cache/jupyter_cache/cache/main.py\u001b[0m in \u001b[0;36mmatch_cache_notebook\u001b[0;34m(self, nb)\u001b[0m\n\u001b[1;32m 333\u001b[0m \"\"\"\n\u001b[1;32m 334\u001b[0m \u001b[0m_\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mhashkey\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcreate_hashed_notebook\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnb\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 335\u001b[0;31m \u001b[0mcache_record\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mNbCacheRecord\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrecord_from_hashkey\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mhashkey\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdb\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 336\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mcache_record\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 337\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/Documents/GitHub/jupyter-cache/jupyter_cache/cache/db.py\u001b[0m in \u001b[0;36mrecord_from_hashkey\u001b[0;34m(hashkey, db)\u001b[0m\n\u001b[1;32m 158\u001b[0m )\n\u001b[1;32m 159\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mresult\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 160\u001b[0;31m raise KeyError(\n\u001b[0m\u001b[1;32m 161\u001b[0m \u001b[0;34m\"Cache record not found for NB with hashkey: {}\"\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mformat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mhashkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 162\u001b[0m )\n", "\u001b[0;31mKeyError\u001b[0m: 'Cache record not found for NB with hashkey: 07e6a47c8c180cb7851ede6dbb088769'" ] } ], "source": [ "cache.match_cache_notebook(notebook)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To understand the difference between an external notebook, and one stored in the cache, we can 'diff' them:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "nbdiff\n", "--- cached pk=1\n", "+++ other: \n", "\u001b[34m## inserted before nb/cells/0:\u001b[0m\n", "\u001b[32m+ code cell:\n", "\u001b[32m+ execution_count: 1\n", "\u001b[32m+ source:\n", "\u001b[32m+ change some source code\n", "\u001b[32m+ outputs:\n", "\u001b[32m+ output 0:\n", "\u001b[32m+ output_type: stream\n", "\u001b[32m+ name: stdout\n", "\u001b[32m+ text:\n", "\u001b[32m+ 1\n", "\n", "\u001b[0m\u001b[34m## deleted nb/cells/0:\u001b[0m\n", "\u001b[31m- code cell:\n", "\u001b[31m- execution_count: 1\n", "\u001b[31m- source:\n", "\u001b[31m- a=1\n", "\u001b[31m- print(a)\n", "\u001b[31m- outputs:\n", "\u001b[31m- output 0:\n", "\u001b[31m- output_type: stream\n", "\u001b[31m- name: stdout\n", "\u001b[31m- text:\n", "\u001b[31m- 1\n", "\n", "\u001b[0m\n" ] } ], "source": [ "print(cache.diff_nbnode_with_cache(1, notebook, as_str=True))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we cache this altered notebook, note that this will not remove the previously cached notebook:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "NbCacheRecord(pk=2)" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "nb_bundle = CacheBundleIn(\n", " nb=notebook,\n", " uri=Path(\"example_nbs\", \"basic.ipynb\"),\n", " data={\"tag\": \"mytag\"}\n", ")\n", "cache.cache_notebook_bundle(nb_bundle)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " ID Origin URI Created Accessed Hashkey\n", "---- ------------ ---------------- ---------------- --------------------------------\n", " 2 basic.ipynb 2022-01-12 15:16 2022-01-12 15:16 07e6a47c8c180cb7851ede6dbb088769\n", " 1 basic.ipynb 2022-01-12 15:15 2022-01-12 15:16 94c17138f782c75df59e989fffa64e3a\n" ] } ], "source": [ "print(tabulate_cache_records(\n", " cache.list_cache_records(), path_length=1, hashkeys=True\n", "))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notebooks are retained in the cache, until the cache limit is reached,\n", "at which point the oldest notebooks are removed." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1000" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cache.get_cache_limit()" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "cache.change_cache_limit(100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(use/api/project)=\n", "\n", "## Staging Notebooks for Execution" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notebooks can be staged, by adding the path as a stage record.\n", "\n", "```{important}\n", "This does not physically add the notebook to the cache,\n", "merely store its URI, for later use.\n", "```" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "NbProjectRecord(pk=1)" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "record = cache.add_nb_to_project(Path(\"example_nbs\", \"basic.ipynb\"))\n", "record" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'uri': '/Users/chrisjsewell/Documents/GitHub/jupyter-cache/docs/using/example_nbs/basic.ipynb',\n", " 'assets': [],\n", " 'created': datetime.datetime(2022, 1, 12, 15, 16, 27, 64960),\n", " 'traceback': '',\n", " 'read_data': {'name': 'nbformat', 'type': 'plugin'},\n", " 'pk': 1}" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "record.to_dict()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If the staged notbook relates to one in the cache, we will be able to retrieve the cache record:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "NbCacheRecord(pk=1)" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cache.get_cached_project_nb(1)" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " ID URI Reader Added Status\n", "---- ----------------------- -------- ---------------- --------\n", " 1 example_nbs/basic.ipynb nbformat 2022-01-12 15:16 ✅ [1]\n" ] } ], "source": [ "print(tabulate_project_records(\n", " cache.list_project_records(), path_length=2, cache=cache\n", "))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also retrieve a *merged* notebook.\n", "This is a copy of the source notebook with the following added to it from the cached notebook:\n", "\n", "- Selected notebook metadata keys (generally only those keys that affect its execution)\n", "- All code cells, with their outputs and metadata \n", " (only selected metadata can be merged if `cell_meta` is not `None`)\n", " \n", "In this way we create a notebook that is *fully* up-to-date for both its code and textual content:" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(1,\n", " {'cells': [{'cell_type': 'markdown',\n", " 'metadata': {},\n", " 'source': '# a title\\n\\nsome text\\n'},\n", " {'cell_type': 'code',\n", " 'execution_count': 1,\n", " 'metadata': {},\n", " 'outputs': [{'name': 'stdout', 'output_type': 'stream', 'text': '1\\n'}],\n", " 'source': 'a=1\\nprint(a)'}],\n", " 'metadata': {'test_name': 'notebook1',\n", " 'kernelspec': {'display_name': 'Python 3',\n", " 'language': 'python',\n", " 'name': 'python3'},\n", " 'language_info': {'codemirror_mode': {'name': 'ipython', 'version': 3},\n", " 'file_extension': '.py',\n", " 'mimetype': 'text/x-python',\n", " 'name': 'python',\n", " 'nbconvert_exporter': 'python',\n", " 'pygments_lexer': 'ipython3',\n", " 'version': '3.6.1'}},\n", " 'nbformat': 4,\n", " 'nbformat_minor': 2})" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cache.merge_match_into_file(\n", " cache.get_project_record(1).uri,\n", " nb_meta=('kernelspec', 'language_info', 'widgets'),\n", " cell_meta=None\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we add a notebook that cannot be found in the cache, it will be listed for execution:" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "NbProjectRecord(pk=2)" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "record = cache.add_nb_to_project(Path(\"example_nbs\", \"basic_failing.ipynb\"))\n", "record" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "cache.get_cached_project_nb(2) # returns None" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[NbProjectRecord(pk=2)]" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cache.list_unexecuted()" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " ID URI Reader Added Status\n", "---- ------------------------------- -------- ---------------- --------\n", " 1 example_nbs/basic.ipynb nbformat 2022-01-12 15:16 ✅ [1]\n", " 2 example_nbs/basic_failing.ipynb nbformat 2022-01-12 15:17 -\n" ] } ], "source": [ "print(tabulate_project_records(\n", " cache.list_project_records(), path_length=2, cache=cache\n", "))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To remove a notebook from the staging area:" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [], "source": [ "cache.remove_nb_from_project(1)" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " ID URI Reader Added Status\n", "---- ------------------------------- -------- ---------------- --------\n", " 2 example_nbs/basic_failing.ipynb nbformat 2022-01-12 15:17 -\n" ] } ], "source": [ "print(tabulate_project_records(\n", " cache.list_project_records(), path_length=2, cache=cache\n", "))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(use/api/execute)=\n", "\n", "## Execution" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we have some staged notebooks:" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "NbProjectRecord(pk=2)" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cache.clear_cache()\n", "cache.add_nb_to_project(Path(\"example_nbs\", \"basic.ipynb\"))\n", "cache.add_nb_to_project(Path(\"example_nbs\", \"basic_failing.ipynb\"))" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " ID URI Reader Added Status\n", "---- ------------------------------- -------- ---------------- --------\n", " 1 example_nbs/basic.ipynb nbformat 2022-01-12 15:17 -\n", " 2 example_nbs/basic_failing.ipynb nbformat 2022-01-12 15:17 -\n" ] } ], "source": [ "print(tabulate_project_records(\n", " cache.list_project_records(), path_length=2, cache=cache\n", "))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then we can select an executor (specified as entry points) to execute the notebook.\n", "\n", "```{tip}\n", "To view the executors log, make sure logging is enabled,\n", "or you can parse a logger directly to `load_executor()`.\n", "```" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'local-parallel', 'local-serial', 'temp-parallel', 'temp-serial'}" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list_executors()" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "JupyterExecutorLocalSerial(cache=JupyterCacheBase('/Users/chrisjsewell/Documents/GitHub/jupyter-cache/docs/using/.jupyter_cache'))" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from logging import basicConfig, INFO\n", "basicConfig(level=INFO)\n", "\n", "executor = load_executor(\"local-serial\", cache=cache)\n", "executor" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Calling `run_and_cache()` will run all staged notebooks that do not already have matches in the cache.\n", "It will return a dictionary with lists for:\n", "\n", "\n", "- **succeeded**: The notebook was executed successfully with no (or only expected) exceptions\n", "- **excepted**: A notebook cell was encountered that raised an unexpected exception\n", "- **errored**: An exception occured before/after the actual notebook execution\n", "\n", "```{tip}\n", "Code cells can be tagged with `raises-exception` to let the executor known that\n", "a cell *may* raise an exception (see [this issue on its behaviour](https://github.com/jupyter/nbconvert/issues/730)).\n", "```\n", "\n", "```{note}\n", "You can use the `filter_uris` and/or `filter_pks` options to only run selected staged notebooks.\n", "You can also specify the timeout for execution in seconds using the `timeout` option.\n", "```" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "INFO:jupyter_cache.executors.base:Executing 2 notebook(s) in serial\n", "INFO:jupyter_cache.executors.base:Executing: /Users/chrisjsewell/Documents/GitHub/jupyter-cache/docs/using/example_nbs/basic.ipynb\n", "INFO:jupyter_cache.executors.base:Execution Successful: /Users/chrisjsewell/Documents/GitHub/jupyter-cache/docs/using/example_nbs/basic.ipynb\n", "INFO:jupyter_cache.executors.base:Executing: /Users/chrisjsewell/Documents/GitHub/jupyter-cache/docs/using/example_nbs/basic_failing.ipynb\n", "WARNING:jupyter_cache.executors.base:Execution Excepted: /Users/chrisjsewell/Documents/GitHub/jupyter-cache/docs/using/example_nbs/basic_failing.ipynb\n", "CellExecutionError: An error occurred while executing the following cell:\n", "------------------\n", "raise Exception('oopsie!')\n", "------------------\n", "\n", "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n", "\u001b[0;31mException\u001b[0m Traceback (most recent call last)\n", "\u001b[0;32m/var/folders/t2/xbl15_3n4tsb1vr_ccmmtmbr0000gn/T/ipykernel_1308/340246212.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n", "\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'oopsie!'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0m\n", "\u001b[0;31mException\u001b[0m: oopsie!\n", "Exception: oopsie!\n", "\n" ] }, { "data": { "text/plain": [ "ExecutorRunResult(succeeded=['/Users/chrisjsewell/Documents/GitHub/jupyter-cache/docs/using/example_nbs/basic.ipynb'], excepted=['/Users/chrisjsewell/Documents/GitHub/jupyter-cache/docs/using/example_nbs/basic_failing.ipynb'], errored=[])" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "result = executor.run_and_cache()\n", "result" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Successfully executed notebooks will be added to the cache, and data about their execution (such as time taken) will be stored in the cache record:" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[NbCacheRecord(pk=1)]" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cache.list_cache_records()" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'description': '',\n", " 'hashkey': '94c17138f782c75df59e989fffa64e3a',\n", " 'created': datetime.datetime(2022, 1, 12, 15, 17, 45, 471862),\n", " 'accessed': datetime.datetime(2022, 1, 12, 15, 17, 45, 471871),\n", " 'data': {'execution_seconds': 1.8344826350000005},\n", " 'uri': '/Users/chrisjsewell/Documents/GitHub/jupyter-cache/docs/using/example_nbs/basic.ipynb',\n", " 'pk': 1}" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "record = cache.get_cache_record(1)\n", "record.to_dict()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notebooks which failed to run will **not** be added to the cache,\n", "but details about their execution (including the exception traceback)\n", "will be added to the stage record:" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Traceback (most recent call last):\n", " File \"/Users/chrisjsewell/Documents/GitHub/jupyter-cache/jupyter_cache/executors/utils.py\", line 58, in single_nb_execution\n", " executenb(\n", " File \"/Users/chrisjsewell/Documents/GitHub/jupyter-cache/.tox/py38/lib/python3.8/site-packages/nbclient/client.py\", line 1093, in execute\n", " return NotebookClient(nb=nb, resources=resources, km=km, **kwargs).execute()\n", " File \"/Users/chrisjsewell/Documents/GitHub/jupyter-cache/.tox/py38/lib/python3.8/site-packages/nbclient/util.py\", line 84, in wrapped\n", " return just_run(coro(*args, **kwargs))\n", " File \"/Users/chrisjsewell/Documents/GitHub/jupyter-cache/.tox/py38/lib/python3.8/site-packages/nbclient/util.py\", line 62, in just_run\n", " return loop.run_until_complete(coro)\n", " File \"/Users/chrisjsewell/Documents/GitHub/jupyter-cache/.tox/py38/lib/python3.8/site-packages/nest_asyncio.py\", line 81, in run_until_complete\n", " return f.result()\n", " File \"/Users/chrisjsewell/Documents/GitHub/jupyter-cache/.tox/py38/lib/python3.8/asyncio/futures.py\", line 178, in result\n", " raise self._exception\n", " File \"/Users/chrisjsewell/Documents/GitHub/jupyter-cache/.tox/py38/lib/python3.8/asyncio/tasks.py\", line 280, in __step\n", " result = coro.send(None)\n", " File \"/Users/chrisjsewell/Documents/GitHub/jupyter-cache/.tox/py38/lib/python3.8/site-packages/nbclient/client.py\", line 559, in async_execute\n", " await self.async_execute_cell(\n", " File \"/Users/chrisjsewell/Documents/GitHub/jupyter-cache/.tox/py38/lib/python3.8/site-packages/nbclient/client.py\", line 854, in async_execute_cell\n", " self._check_raise_for_error(cell, exec_reply)\n", " File \"/Users/chrisjsewell/Documents/GitHub/jupyter-cache/.tox/py38/lib/python3.8/site-packages/nbclient/client.py\", line 756, in _check_raise_for_error\n", " raise CellExecutionError.from_cell_and_msg(cell, exec_reply_content)\n", "nbclient.exceptions.CellExecutionError: An error occurred while executing the following cell:\n", "------------------\n", "raise Exception('oopsie!')\n", "------------------\n", "\n", "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n", "\u001b[0;31mException\u001b[0m Traceback (most recent call last)\n", "\u001b[0;32m/var/folders/t2/xbl15_3n4tsb1vr_ccmmtmbr0000gn/T/ipykernel_1308/340246212.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n", "\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'oopsie!'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0m\n", "\u001b[0;31mException\u001b[0m: oopsie!\n", "Exception: oopsie!\n", "\n", "\n" ] } ], "source": [ "record = cache.get_project_record(2)\n", "print(record.traceback)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We now have two staged records, and one cache record:" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " ID URI Reader Added Status\n", "---- ------------------------------- -------- ---------------- --------\n", " 1 example_nbs/basic.ipynb nbformat 2022-01-12 15:17 ✅ [1]\n", " 2 example_nbs/basic_failing.ipynb nbformat 2022-01-12 15:17 ❌\n" ] } ], "source": [ "print(tabulate_project_records(\n", " cache.list_project_records(), path_length=2, cache=cache\n", "))" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " ID Origin URI Created Accessed Hashkey\n", "---- ------------ ---------------- ---------------- --------------------------------\n", " 1 basic.ipynb 2022-01-12 15:17 2022-01-12 15:17 94c17138f782c75df59e989fffa64e3a\n" ] } ], "source": [ "print(tabulate_cache_records(\n", " cache.list_cache_records(), path_length=1, hashkeys=True\n", "))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Timeout\n", "A **timeout** argument can also be passed to `run_and_cache()` which takes value in seconds.\n", "Alternatively, timeout can also be specified inside the notebook metadata:\n", "\n", "```\n", "'execution': {\n", " 'timeout': 30\n", " }\n", "```\n", "```{note}\n", "Timeout specified in notebook metadata will take precedence over the one passed as an argument to `run_and_cache()`.\n", "```" ] } ], "metadata": { "celltoolbar": "Tags", "interpreter": { "hash": "8398b65b1e6feb38b0506d5ab1aedf8bf63748a9844a9c81ed9242850234e24f" }, "kernelspec": { "display_name": "Coconut", "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.12" } }, "nbformat": 4, "nbformat_minor": 2 } jupyter-cache-1.0.0/docs/using/cli.md000066400000000000000000000205411452276675600174470ustar00rootroot00000000000000(use/cli)= # Command-Line Note, you can follow this tutorial by cloning , and running these commands inside it.: tox ```{jcache-clear} ``` ```{jcache-cli} jupyter_cache.cli.commands.cmd_main:jcache :args: --help ``` The first time the cache is required, it will be lazily created: ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: list :input: y ``` You can specify the path to the cache, with the `--cache-path` option, or set the `JUPYTERCACHE` environment variable. You can also clear it at any time: ```{jcache-cli} jupyter_cache.cli.commands.cmd_project:cmnd_project :command: clear :input: y ``` ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: list :input: y ``` ````{tip} Execute this in the terminal for auto-completion: ```console eval "$(_JCACHE_COMPLETE=source jcache)" ``` ```` ## Adding notebooks to the project ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :args: --help ``` A project consist of a set of notebooks to be executed. When adding notebooks to the project, they are recorded by their URI (e.g. file path), i.e. no physical copying takes place until execution time. ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: add :args: tests/notebooks/basic.ipynb tests/notebooks/basic_failing.ipynb tests/notebooks/basic_unrun.ipynb tests/notebooks/complex_outputs.ipynb tests/notebooks/external_output.ipynb ``` You can list the notebooks in the project, at present none have an existing execution record in the cache: ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: list ``` You can remove a notebook from the project by its URI or ID: ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: remove :args: 4 ``` ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: list ``` or clear all notebooks from the project: ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: clear :input: y ``` ## Add a custom reader to read notebook files By default, notebook files are read using the [nbformat reader](https://nbformat.readthedocs.io/en/latest/api.html#nbformat.read). However, you can also specify a custom reader, defined by an entry point in the `jcache.readers` group. Included with jupyter_cache is the [jupytext](https://jupytext.readthedocs.io) reader, for formats like MyST Markdown: ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: add :args: --reader nbformat tests/notebooks/basic.ipynb tests/notebooks/basic_failing.ipynb ``` ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: add :args: --reader jupytext tests/notebooks/basic.md ``` ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: list ``` :::{important} To use the `jupytext` reader, you must have the `jupytext` package installed. ::: ## Executing the notebooks Simply call the `execute` command, to execute all notebooks in the project that do not have an existing record in the cache. Executors are defined by entry points in the `jcache.executors` group. jupyter-cache includes these executors: - `local-serial`: execute notebooks with the working directory set to their path, in serial mode (using a single process). - `local-parallel`: execute notebooks with the working directory set to their path, in parallel mode (using multiple processes). - `temp-serial`: execute notebooks with a temporary working directory, in serial mode (using a single process). - `temp-parallel`: execute notebooks with a temporary working directory, in parallel mode (using multiple processes). ```{jcache-cli} jupyter_cache.cli.commands.cmd_project:cmnd_project :command: execute :args: --executor local-serial ``` Successfully executed notebooks will now have a record in the cache, uniquely identified by the a hash of their code and metadata content: ```{jcache-cli} jupyter_cache.cli.commands.cmd_cache:cmnd_cache :command: list :args: --hashkeys ``` These records are then compared to the hashes of notebooks in the project, to find which have up-to-date executions. Note here both notebooks share the same cached notebook (denoted by `[1]` in the status): ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: list ``` Next time you execute the project, only notebooks which don't match a cached record will be executed: ```{jcache-cli} jupyter_cache.cli.commands.cmd_project:cmnd_project :command: execute :args: --executor local-serial -v CRITICAL ``` You can also `force` all notebooks to be re-executed: ```{jcache-cli} jupyter_cache.cli.commands.cmd_project:cmnd_project :command: execute :args: --force ``` If you modify a code cell, the notebook will no longer match a cached notebook or, if you wish to re-execute unchanged notebook(s) (for example if the runtime environment has changed), you can remove their records from the cache (keeping the project record): ```{jcache-cli} jupyter_cache.cli.commands.cmd_cache:cmnd_cache :command: clear :input: n :allow-exception: ``` :::{note} The number of notebooks in the cache is limited (current default 1000). Once this limit is reached, the oldest (last accessed) notebooks begin to be deleted. change this default with `jcache config cache-limit` ::: ## Analysing executed/excepted notebooks You can see the elapsed execution time of a notebook via its ID in the cache: ```{jcache-cli} jupyter_cache.cli.commands.cmd_cache:cmnd_cache :command: info :args: 1 ``` Failed execution tracebacks are also available on the project record: ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: info :args: --tb tests/notebooks/basic_failing.ipynb ``` ```{tip} Code cells can be tagged with `raises-exception` to let the executor known that a cell *may* raise an exception (see [this issue on its behaviour](https://github.com/jupyter/nbconvert/issues/730)). ``` ## Retrieving executed notebooks Notebooks added to the project are not modified in any way during or after execution: You can create a new "final" notebook, with the cached outputs merged into the source notebook with: ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: merge :args: tests/notebooks/basic.md final_notebook.ipynb ``` ## Invalidating cached notebooks If you want to invalidate a notebook's cached execution, for example if you have changed the notebook's execution environment, you can do so by calling the `invalidate` command: ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: invalidate :args: tests/notebooks/basic.ipynb ``` ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: list ``` ## Specifying notebooks with assets When executing in a temporary directory, you may want to specify additional "asset" files that also need to be be copied to this directory for the notebook to run. ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: remove :args: tests/notebooks/basic.ipynb ``` ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: add-with-assets :args: -nb tests/notebooks/basic.ipynb tests/notebooks/artifact_folder/artifact.txt ``` ```{jcache-cli} jupyter_cache.cli.commands.cmd_notebook:cmnd_notebook :command: info :args: tests/notebooks/basic.ipynb ``` ## Adding notebooks directly to the cache Pre-executed notebooks can be added to the cache directly, without executing them. A check will be made that the notebooks look to have been executed correctly, i.e. the cell execution counts go sequentially up from 1. ```{jcache-cli} jupyter_cache.cli.commands.cmd_cache:cmnd_cache :command: add :args: tests/notebooks/complex_outputs.ipynb :input: y ``` Or to skip the validation: ```{jcache-cli} jupyter_cache.cli.commands.cmd_cache:cmnd_cache :command: add :args: --no-validate tests/notebooks/external_output.ipynb ``` ```{jcache-cli} jupyter_cache.cli.commands.cmd_cache:cmnd_cache :command: list ``` :::{tip} To only show the latest versions of cached notebooks. ```console $ jcache cache list --latest-only ``` ::: ## Diffing notebooks You can diff any of the cached notebooks with any (external) notebook: ```{warning} This requires `pip install nbdime` ``` ```{jcache-cli} jupyter_cache.cli.commands.cmd_cache:cmnd_cache :command: diff :args: 1 tests/notebooks/basic_unrun.ipynb ``` jupyter-cache-1.0.0/docs/using/example_nbs/000077500000000000000000000000001452276675600206515ustar00rootroot00000000000000jupyter-cache-1.0.0/docs/using/example_nbs/basic.ipynb000066400000000000000000000017361452276675600230040ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# a title\n", "\n", "some text\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "source": [ "a=1\n", "print(a)" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n" ] } ] } ], "metadata": { "test_name": "notebook1", "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.6.1" } }, "nbformat": 4, "nbformat_minor": 2 } jupyter-cache-1.0.0/docs/using/example_nbs/basic_failing.ipynb000066400000000000000000000015261452276675600244720ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# a title\n", "\n", "some text\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "source": [ "raise Exception('oopsie!')" ], "outputs": [] } ], "metadata": { "test_name": "notebook1", "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.6.1" } }, "nbformat": 4, "nbformat_minor": 2 } jupyter-cache-1.0.0/docs/using/images/000077500000000000000000000000001452276675600176215ustar00rootroot00000000000000jupyter-cache-1.0.0/docs/using/images/execution_flow.pptx000066400000000000000000001362721452276675600236030ustar00rootroot00000000000000PK!`L [Content_Types].xml (̗n01ЖҊ˩ Rn۲ dQZ%=9Vg o r%RA@>zO&c2bBI,ZZڀ wp1 1W&ff@5 hZmPIU\Aڭ賑p3/ gI81HtƀK!sc-Ur*irm/0aM$@{ix^b̢Z; Xԥf!D*(bnj X/:ڱwbi؇~D5J"fHj`6dçAj">f3`(f-de*futYB2]QB25Kt[BZPQx6?욜+8(7 a]Y3~ A&QNA!5Xr j"!dnBańd-yW@䲥].{_fd&C 4Jhh 14Z )GLp= }/v/}7NoEYKn[zWvM\=f8Zݤ+f.S]^Ĥwǃx82}ץ]4MmzCz]Ԩ{γe4ˋ}E]Mfͬokn\/jR$j(ՙV?|^X6PM>Oc o4-@~|zМ{ ݃m's\ħZ,.>ZagtڷҾHI`@1YdؘHjFዄK)ƃ"Q򖝂wnEgPVC+ڠ֮ui-`F-27MCC> 4/)x}ːy#Bf횡 !ZףKv7K7N  9J,A(,IDBmlzP| e^6(N*>*(UQ7 ͐c=o @B$RP0G>?$c6!qEs|Gp'< O'>9ٓqfਫ਼ k@.A$hf/"Pd Z2Tx) 0FpTK1d/ޢvn>Jx!ED:[5NɀP  V'^C WP3D}57 tIRI!և6!'F,?D2|;dGI[P?Xu򏃿nS? uօҌEЅ沸P4y UZF @Rc0Sd*5 1'5ƕZ7NuVn?+B- ]TS"!m@baMQ]5XŨ%^<ݍi._6_tYZId`ySyzU4 Qi-)e/M"^Ov0݉W͂ZA! EjtA@h[Q> u~̇_˚ʾ[55: 5uꛦU~f\Bqo*^ 7θxZfsH/#ZXg@"뱢Ч&f3%D0e BEn;>n*>n*(f;"dQ۳uI`@@$<,0F+zܝ3vBIM:+`͞ea*ŤȿXV4dx ̧!#&BGdZƝa]ːOWwA [ahnaUneKq ˈ;MܥVՈ}V#e7jԾf<|Ef.1E2Q Bf_5jcFNfCB5j}&JqZ5>L9 P@ Ϻ&cgԪ YGԪvOS)\2vK 9dT*}he w{i ܂Vj#m|8dL>]`.|jDvk6@7:9d X`uqNJ; E#hR4  #|Nm y"Bw}db _3weU1eCK疝t~[##4$ M 8׀v9Ӣٟu`ΧY6-zwE`$nэJC+@)~+/"usw2ljB/?9)u댌2Qei*֒ _ZU-BI$WP";V#;w-j8604Pg bRoHs]y-)V&-0*$l=ô5\kd;ԏ"nPK!c\#7 ppt/slides/_rels/slide2.xml.relsϽj0=wW;,e)C>!mQ[:o1tx_?],(AC+lt>~n_'\08c 1\ۡҌ0JhA1Q!K-_I}4QgӐ{ׂm^0xKѮ O;-G*|ZY#@N5PK!c\#7 ppt/slides/_rels/slide1.xml.relsϽj0=wW;,e)C>!mQ[:o1tx_?],(AC+lt>~n_'\08c 1\ۡҌ0JhA1Q!K-_I}4QgӐ{ׂm^0xKѮ O;-G*|ZY#@N5PK!*3>Uppt/_rels/presentation.xml.rels (QK0C6m)t/"A? m0MBn6-Ň9=8 Y:ͶIY# KR`hJ[)x<\ M%5(GUq~|F-CV9b1Ő6w9-vĕN(}Ý,e\"@`v+3뭱7WDHB;[xȲˇ<~) h|_78ΊrO{EtGӋg^TVtivܻfGxyiy.jvlY6$?,oɸ!I:Zb|4Ȓ|p5ɦUIʏʮ.͊?t< rvVdY8^(fߋ߮/W/?-~C۬:OKizts^L?[ts>ZvSEdE]ѺݠptC>,B׎cYu#*Fǽ?ÚYGgBMTX8 OFhPdl:_5?d4(2? Inx%!ڮ0? OT[_(׊X Lsw>KMBTb*P̐d,i!!Su\qHbbBQrjz_M0 sNx2Ū_Qw}Sώo&׶..s>F#: bYۧ U4U\48l >^4՝AvSzgc#w1U4ڪڍf$f #́:R Lv*JM=dc[.O[4BomP t5as'.xTxαͫKгa)R(,`JhoYPƳ ń +cx,ˎgT,F`L(1s0qRW L(AJ5J\!@$ReASRҲ#{᧺NIn>0GxzX@aGpp3 Nшv檸`4t//"ܘ_ތRA&Sd@l‚<\-B2:f{z2AKiR960E̓UrwNVJivjat08=9#=ppF kǣ {-CIZ^6 2[TvGXMs Lfk' &[㵙ëX+$Jm1jAd"4dqxp1|-VV9n,gaXza_fb |7fp8zZP%,@`j*kc 8 H)!n+3 BEڄ0h >9w?Jkag'O E `J`@@$KcpVdR'A*CWJ1cK9ZLmV)x:1QFO"]\VQ\f w i `1@JfwLXGFqG;xGk䳙--fn)aU]c Vs6[i:ˈ[)Cľ(<*>T*K X0: "$U>܀5D5Tpsy+E/X/ "ty'5քr[@CrE"KaSKd}TqeTM`sjz9H0ڀ>|<Ȇ/D|g-C9JE pE%rH QB/H$vokX/\2d88dTmI:}K~N߃-4:Ԁib8dע];hm_y*H!DCojaK?dhg~&bZm;#4Xb-qpqH,dW! =(9R!Rl$n7X&d[C(~||h17]%Ѯf"yp*-[o?܍a6n,hu%5|T/U%: eTjאAͼ; S b ":!]  $yՄ(ɚW =@BԶq $yX:.RShɴwqs"qZؘ*')q2q_b)$S"[.oK/ĸ(ҹ(b2|ͭMm\ J:H}Jcӓ21]S;\}ӧ{Ͷ/f{"M#}|IrٮE?.T=`~$`P\p*j;D]xWۭ5T3Cyb(XH?e_ZqlQhg1B26}^q7oY1GBo 1N Cm;`\|Jg'5^0Y0 Mӻ&ѽPK!kl ppt/presentation.xml]n0W; (D!U.RԴpiPl&];v T+;Z[)$ gR%a'!V'lpC#٫:PcZv.f?M4Cn : C,ynGHJQB=[WsY;jX|On:$nӲJJD_rKðOBjC Q1*g_> ry!eApN'*r\b3,F)3ɖۓ[v={zY.o͇PW kSPoUΫpjFDKxޱU8Kks[?PK! [F~!ppt/slideLayouts/slideLayout1.xmlXn6}/gF"EIukPIn ѭwc70j?'_!%ywc'c˒3sFzꢩ-Cյ MEWV>C4e^w-_|0_h˓H0(_k)#5oE:.ŹU`7El۳jin tŦAs f.h݇.ih3lCŲ.6o`ὲ0uUr}k լݾ?z0R!L;Mk1v'7i~t!.կ4qحwkk>;TE5:w323&BGq2٣,#fe0CԎ(R8,%~ǻRwT)t6U![ELLb:1<1S4?h)<())𙈡?銏v@ub$SzS!Nd:ד]oeR2`:(&!gsEԕjcR^\Ϸ5I}j5%__ SM|W?:*\Uޢ8yUѐJoAraGJ(4u49Pu?3 x9$T1͈O E"7 F6!4|6g)it==rW-WM.Nt*lp`OodJ]( }( A74f o;hXU{cJ8a{،0ð a< [NO[Nll8a{؞+ð#Ua.O_\\r pi!O4vP`CGBHB?M ç/4y(Vsݢ -:Z愉1(}Q.&0q憻e:n#5C#u7+mb RMNm͝u3U"Q(KG[A-Ғs Hݬ!ǥڻ ;9pNpI&R24$L}lWK٭VDo;EI uZ7Ge% k߾~slNY"L3DGa{Ci0vRӛځŻi>qwI>ۼzMXm5TX7ynEz%tgb PK!qG8Y!ppt/slideLayouts/slideLayout2.xmlWݎ8_i.` 1Q3VvFx'M8} Lo\ds}`޾5ex7me,OW9Kږ-[{~m7FZ tak X[qPōS Ea7]7tZ_VUR^lDJ~BA%TO2Fb=L],ji*Y3Ke" ݕ`L{-Ka}^ *5θv?F3nyfn%=dX8_Ga8glu3;TG58483Cgqɣj]|#hDtbzԥRKduY0|'OUSF)$,@/ߊK6HhҎހ')&hn 3zf*(! 8IӕTʪayubb$5]i3:.VAmʇ-s}šW8?Ku##$.tA#?L쾼VR kCJݤRNT/dMs8p3@YHb/'\}#=˹yǝ0Ig$%@\lgnF0NkJ[ݱ ~t3LISlND!$8rh< @H]pLLkS/Lt2;gKF G/uj]lHs"fL&:?PK!aAU!ppt/slideLayouts/slideLayout3.xmlXێ6}/gFM6ɢN>@Jns%%)&&_,sH mS[[.k6|oڛum 2o˼Z`?PW]^K_8PyO٪M.խqJb7\sj_߭VU4c\*ú9ZL^A1!ɻ^e;ge VMAR^,jFM,y-mȅy:zn~_ r{-A&gۙLfݚ)fǬ1E'$#.!bcFeIQ0@a& gwJ|&b诺`"J:~=iJMvC3WAA | ] N4"Fc6;V]y[;ގAA.]x[ RW*Ꮕ4Keg1~B9չPx ,w˸w,^Vz TG8*8`E=&oˤdNu^uWl3UoB,SC@@ P^AiիiD Bv\ŕશTKE3^F]]YUFZXۼVKvdqƧ3qM׍=RB}kp\S'x7D(8%{H(Tx5 /=U;?E '^ࣗIj^ǫNWx=z Z4zekbEL2k^KB%%נ3mP i PB1I@z Cߠ6RZjnTFm|S1G scR>-a&(0|_&(309TʪYuF )Jȸyt2UjR}CLge]|8rKB4>p>42))[I1JM.TfCoTmfjsM30̀j20B%!H|zEy' |KU9Ga Q. JN})K( fД ݱ̧_`w1f4i',$ I0ԁbB∅1NvzHԎ.Fr~'KrUd2WPtcuQ!?n)gEgQLa5EVJ$ ]( !IAH"PDq0MY_4KwtR@1U+qH#A }m`bL{Ms<OF-!b nR8mX"zxUӀ9_ lX/|r&3~g~ P(8"+8(هY?ٛ9-$U GI F$4TCY,Oׂ1”RIÊ律T#x6͟sW^!D A0E "?Aj0 җWRQr R7ᏨyfM]1ú=CZ(a !" I{6PMr O J8 \ A7!tD@fnȏfДvʻmo_au1aeκMډG4 n@aBF δvzHkG n1|y$M^XQ"j:|6}nFZJPu9^/mPK!44>2!ppt/slideLayouts/slideLayout5.xmlY[nF/=D'FϠYl*IvV3CҒla 0P#j9^Ro]噱u1'(2IɧѴQDYYɵh&~mudqt].[CbAt8Ymu06BQD!uk}1MRb&QZL|/e.E2fV̀VAjHz;ٶeyrГ< 'G2x%FWUTMY_BU^quZiiC?M-Vz0bFW:WGDv>Ꜹj;ƋsEctt*.!̈́GqܴCD:=| C $ #@L7 6BavVCvB{2 {i\M9oeދcИN嗐6r6=3:Ƕwoy8,}}Mu\Ɵ(%Q׎X-M=u?zwṞmqÌBDɆ&=˔e]Wn\dM;k3TCQKH(YwіH5 t mFEr`>J7|\nVn*,Ţzʃ&t 'dRN`2+ORzB!Vԟeq G؆hr2(yTkJD:jGy[ҫ1Wj~DT%iޢh HQ}Tգ5 ` *QTJ֨[P(X=sVat#cx*a!=VaEhv*axv*ހe)V7ި "r16MZfˤbd0b,Zߖ61&lCX6 -(`O/VT/l;qOtbDMjZwz-'ƌ*g\+8LeXli==˺]F$#N}y}H0 $ Y@O,^kyRu2)m-nwYCU]ke/,XE_3!}#Np}T>RymXhtZnivZcKJڏZUֳa/ ! _3&(˜yJڮx~+ZQY|,MNelףsG>- $6E^,kql5[ J12hjr&cx~AoaY*TBpyu cz !Cs2L+nֻZ{?ei"Z &L|s\M@0ωa›,{Jy8-Cy@J6K 6bs4э5o_]aWsu=.p! m 8! 厇 ڑ'i*/E]|Vj8;UTdEӧ*%nz}?PK!! !ppt/slideLayouts/slideLayout6.xml̖n8Ww@fIVJ> *_kWjl3~ݸk{԰r܉' :e+3nn贼y|~KHpzoc R1$u [UFwѶ*!->h4^DW]puLKP΂ï(2uR #IG>)8YIP|T ۿ!lu`9i[> /[8Lԇl3#J(AQD<4#BHs] U݊KqWFRUi#xwsS;.Rc6y# oEkTXJ)D".AnUrB=PePRԛn~IpBAH|Py87üȟ_pA?I`Ei0G h,aܜ4{|߿iRuӛa,qJh;d1\%0@E2~nld+DUm` jdHvjl /"5;3)y{;am{gmM;ܬ??V(Iҡ>~{/o5҂ôk{'rޱNg|d墧nōr]7tz/Ϸ۶f=DJڵd6 6Aߒin;:|-&`st5 Pi(SNJh+a]blgaqӷAo7fKWǭ #kHQ6vV={k|·ޕOx;U]=nv *uњx0a!.&iۋvm*eEP;D܌$ 8r쇷* W`ї>bڷʳ8G/AP((H QnP2 ٬ gi}a24Yf cqh:Vq֛{@1I=Vmny꘾e ~Z.QLbx(qDHZTyF 0mϪf/^jP$`zAݽ;F;-s;n ƞ<4As+*YOӔcz*%GTqH(B#))eF^b_{*`tFu&TAچYO /|„( 82è _8afO2ǯpҤ8+"PV9˜(yA\Y0MNI!0߾۷6S׻E;Y83yBH"Va'$4K#ig䟙y?_HJtlǍ4rc}HCү߬Zt_#Wie DReIQWttûћ_yݜtEv aI;INGs!KLWu+۬nDȯ8kϒ,ȶqhhӾOtQJ$-/!3l1lM;I[owI4r՟aRހ39tZdVFXWB2Xs1¤QL5-窪of\Ek噢(FVK]w_29YR]XӑF}=VL{<ڃ7Fwpe. n8ѢOG_1' e`A05tOҖkm~όǠ{O2OۺgUZAϤ ^~8^LmC . @{89Gn }6W=0aF9OUR(kы|Ps4uq7{%(Ǔf!"B׶Ħ=ss"VnT+y&U:xszYC!iwk76Cke"Q+q?CE~Dm,ֻ̑$@*HD`M h̸P6,65E|^z1IȏDli<[Aw)u}l[ׅR~%n8ƺ?W]XD7 KK6 Kkob @!=5Yz Kzka{==~CHUK#w!*#wVC>pg<B)hX:&R'[J^h:kO0 {>@0š IG>CL T؋D'sO֘Y?Z=AyD.%,t]L0έc!q~hn9J.VWI68ئrs/[\˛s^(l T)!!B9}~D[Eʩ3;pn=ԞzZ/ʫ*8!b$ "H8}@2^yý]9ۣ۫&@l6 C TZ=E6 utJJ؀߾} E_̿fu5x1E!e)@LHhHywԟyԹڃ}:!ߧRcc7s]{d/E.i>,IJVC jPK!9!ppt/slideLayouts/slideLayout9.xmlXnHҾbO|5CRF(l8}3m*0sw /_mXgmթ_XUI*B4:Wi\Uvjd_^6']7JQu'񩹔9ͺdqn [mKmgi처˲geW0=d|XI6UUi"̛nDkAkڬ=zi&O65t` *O.Ԩ:.D˥čB1]sfjUms\zEk䩂 on̾~=6͢-f؜@܍l#Lv=2'z6>`PUUrXU.P8ѪOQD|F EBC梈P' ohl$m=5;y]/2 (lTec<ȧC,dÈ˙0xŬzG"N>vFUQמmDO6AWRYbA]G1%$b۲}*{Dn:Q?(dY,:y)oLJOۡoT{o`~B/gȳȓ,ͥ&dznsDhJ/6Vl^CoK}Q+|QI Tma;Q1h"y/آg>\ aOUlc(ii3P8DI(\[^G¢G@ٺIvPwx d;<3v0)0uh* "TT($D* et{BPNP ͝GP"ۮ}wDL<_0j#O "<S""byzGTcjޖq̑ O, /K<s_QOO戏mS#8<9N`SQChU/ނv=c,:tɞ#iII88[j**IQo4pԛ[s"C0r# Sͣ v 2/(#ûNѕ2( OYdf jwۣޢVZWsaOGk' <Ol{[Qu?8jgx*?|CL V-? 硍|Hs?GywE sv/?ػ=i<bσ8]\hs^v45_.2~g]k@ /98e,Piv04m\ u\#00큦^#l~훸y")F ݅gPK!t"ppt/slideLayouts/slideLayout10.xmlWێ6}/gn͈7ЍAMvQ;}g$z-DRc7X ~NCJZ-[x})Wݬ o7BѹFYMc |[^Qp ʰM3jZ6]ʜmY#**~]vqZ}$[XtM[sp=_T~eN+mvBM%gL-+V^q,$ڈqzmc!ʣ .njΪ^,ľb꥓e"**S5b W'UD[ 5?6H7 k+owc@:ed1ۢp&Q9[U/T(!qб(BSŎ-b1A(v]ky j(IҼaQ¨dcc-umu[쮤$qJQ=I y>-T֊mYuc:?$톋шr$ORBѵm`l#3IMD8  L3H IuNK7Qʂi6/@^Qiȃ'(b8BbO8ZA̞\q'4X(NA5នř;|/_}ꢚjډг FG\D\$ɤv: ? xז2Gl89}{ 9X+_H[]nHjuJP'9L=LO?PK!j¼"ppt/slideLayouts/slideLayout11.xmlXn8_i0Q'Ji{OAog:٪R_kq$@~&i2Un|>|̫׻6\ U.L|doڋ}ay^ɚmy3 /p'bYF70'VE^:ހ|'uП Uݾ?z0RM(5ݘeko͏w+Ѩc&x~-5$b,G{buzO5?Py9{IQIӄN9Sۈja~2iFQ=D툢(ʈRg1qj6 `wG*D7t+yTt|3Ί.)AcmEYE)yn*iSfE+> FہbJQQUlR4:Q%GMPݹN35u< GuaE]yfU&ϕ#l=ȥՏ&% [a9ʓO ^Vx C@Q,jޖg &LQ~ؓ]Oy]]B?CDF2*EqQ0LN.uyP9M+Kmm!Ӧ7j:q\a%/:xQ|J<u!#սO]t$V;/СF6J$@16b ۞oK(oXI^?q}L/ Lz ;!)bQ ]00q'4Uób#:X9 b kW-Olb RMۀ͝u-n:TǹԋKN>a(ni[I1ZM. uyVjozYW%7m{/Tpa֌86 OgPt$~ROGٽw4!v08F:vYjGI@0i%i `}E7uݛEGb $Qy.\8ba;=wy>swdm ;G6Ks+,ۼ&i *C2z>pPK!ђ7-ppt/slideLayouts/_rels/slideLayout11.xml.relsϽ 0]&4uE$ۛт}\MxRbZV ț`5ܮgIÛr\h\xpEQ{g(]m(')#;+b cdk6oyctW&Okl( !`$ we 蛗2p[_:l/mg잯|9g dh<,֪XjހK'0&4"^B|ѩZ5wxt3 1C@VHF 4Yn>=/^*G:w8X"9782O!WȁOޭllEgӈZ,jthExSF&4rFIhS|ȹ2K)]R. X#ڥa 'S+J,?0 06cї.೾Q wAР4i\5FQhõF V CV/!鵝p{ăU8!̶G6C̑ QD0c5Uu#>riجy"P5U*ʧ֓j$n23PKEY6cь!- e.3yP*";c3NVgG+eWވsqs5JScYxavކEy\4x +b*P""=׾~JCAEf?7Ț>5Rca1X2+PϚg1fEvԐ`KdB/ Af~P!Sɬd_gpK}r@)n4[h p׸gsEj珰obՏ7~Q#,f >wĄ؞}Ɂ KL*`FԨoKC`ewaإD)J_\kj_H /a+gS>,Ép$eQ,M2Y!Z `oZ7fH9:6<2 O-zr]0x.0 }1uNDQv/-Qf)źl2 rKtgQYb'<=P#fG!r#85kM|۱H8< /O9dJ?Vq H.BzjO-v"DzH?=I{ĈKa@SUv23V{tQOW|.2GtU5ڣK{wtYOU{tݞ;^ =[>FFm"*l[UUIiW]~,([Z@lU$B+y\rt5rhojd_G۪R>E{ӫ֭W^[;}-5$UD?.rͅwg͝l?X巣_y˸>q (TK )Ѵ7J7љY ^WqQ6pkc*6VHQrA놋|5[ Q?[i/!D:ҡؕtPMr@ +ψCl -1?BDUrs9A'A^C]8wTn3WxA.q wz3D_!8w=ȇnĹ@N(A?B!8w{{s\Q%/N72ը ^Oi61JkS>y$ym1~C}9gA1>;vĕcvA=1@;vlj?hFpXJTjOOPK!ђ7,ppt/slideLayouts/_rels/slideLayout1.xml.relsϽ 0]&4uE$ۛт}\MxRbZV ț`5ܮgIÛr\h\xpEQkPa0J{}SYנdzZ2at oްY[3T9]|"b-Fw@;I8r14\(9%a7C 0\U:|?OGyrHl )>q2*husϞ=ߟ?z˹vP^??|ۏkNjYǝ?NЌgOLhtܑ\]2ˌ G"JaQHJbhq֚j(vӳ;O&x$ F.F(iN``8Photoshop 3.08BIM8BIM%ُ B~" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzC  C  ?((((((((((or.d[:zkża_X[jZ̷rwrᦐ"} 8+4JT+=895ٞ!JjdϨ < qm$#cBa|UH<1 KfV {MR6ǡWK|;O^"/^=ʑ4@ J a \1<-}sO=`wBe'~F`drA%>:]_}tWXkڎ2ȓ4>; ?Q^o:\_rU >f)cԔW,O<*Ԛ(<(keWY@U,p:֓vz*bWE*b.U?mߧ 9](mߧ >%Os .QQ*OʷLiAf8OjkQEq?} lpv~hT?A!~+`=`T}#?$?ԿEA!~)F?t}#?$?ԿEI?5?6P[t}Yuxn #+~mb 4ϊ(c֚dzR;Sh:/}~ E7q@3{sܻ| ܾH4G 5W-]j?HvӴ4 %a2 J1s_.Wc;Mʤ64Ӓ5Gguq/BiF*6?䟵diemf"ؒ#aǹ5bڷ`Kc@P6dsھaмIk]-3_پ[8bc`@ʅ9C` (9#><ef?+s8@ ᇷ?u67o8#hGoI/<;C rn%N2{qҬW&Sqk৆RCF[pA\}8Kk-yO$'͈6Ntd{KRQNI*8zV| Xi/YOXU?uŗዋ 2:%qhq-mŇC *u[×+t>=F(uXZ < PC7pHp?=>2eT𧻓m}Y3>Z[_oC(vj|<H #*pA <⦚=4QErOru}JFWյIy#MO52LڢohuZ-6H&B%j5֊875. |pTg7jq5zF>!a6'x r&|b_( jlkM{x`$kC.38~0=z'RE,1d OsEk*#<8 Oߴ=kcXg+$Jj"Mv!9hfMSv/ӗIդ2QċS  [9=@x?LG\c̹JUH- R gğH,|CbT{f3I`X3ppQX?ejvG#= *@>@OFknmg(>8,¯ {.oH|K 0D[4%<$\xߢϖD('E"5ƌnO*0[sN \S_!Jѷzҏ,6袊McMcHPk{m<3*FZ9C |Z5o#::g"{ajeS[ß o].u6F)d34vḉ]R[Kɼ1p yipO8Q|CqcĊkc)RUVp 85[OEE^ǡ˩F4ugX[洲 FGAކxu9mMW͞,gg>y!iK,@PpF[ܻQf=%/7^lY6p 8$$ ~\Y[iyk*ª)M r ^[ljh[ʩ*m'vNnj5j[[~ѷŪhtwMvH1  [G9 |9GtB^=1*rNr]xHT1fcٔ@r}})VЍ|[^9]WtOFI4T% 9c92x~A[³EGU,z.㌟z!7f?sBe2md >Sz]'3A_%'3AO%AWΨIJܿ+Ts_/9I~˹tV6%*w0\d^+Tz}ͭEg(& X.$J`oC6<p3ֲ9+[yuk(oDHlڻ]>sE;gOZqjV0@hq'j^}WoS?tmc_&o*{ՕFL3M=*=-V8*8aAu#k@Q^&M,W7 vEHB;=W^vrm]pbI]- =H0wV.~KOihxZ.ymuRhr_o'?Wo خ bFs<—m~0V(#{:ŷ'on'baXkKKV)RHPJd2g|J.sgq5徟MXLtƛU/ o+wNƺݺZz:nrHGm>bfMEZ/(` L*r 0dI^'q2}o0.X!$Q0PO@n2hfމ6K5'3 F(Y෱GYFOa',W--ѣjw;),;U0VUk V񅼚ݵch,b\"MH@Tftdm$x%դvͺ?*H%eFQ f,y` fE|Ҵ mPV5܀l]oϣjs "y#chĪ_ H-+n#k1 o^,co+r`ʯ(6? wy%*gI ;2[jξRoCܭ3] f9m"FO~w١7UgyqaMI'zs4UnZoI/Ǎ/j闚>2A*TJrZeywO?MԘx;C#L+`L0TQuaנO^N?iX/~x&{F n(+ZrMn]v *(m6}ޒS5ݼ `bhAR`61qyܠRY)-; b@#Na1w⯈vjKQU4k_72cf T֏f+0:&G<ŝII2L䃃uEW3hPγ^Co!]-SdY ׊7~7G !?!hp# ϘB Ol7D.^ (f!$BȲ*IE8_2'tu[?i(t>V;[5)zR{sZQ_iT/U K ĵR0i;x'JOE}mo+7M$?B)-p+8Gmc>iP,SVV®{ '_N}k嶙wmv$1D7̐.!'@7~i5aEWY'SźNW.IZ\CKKv}z ; 溺_&IySi۽\l&s>*𾵨5;i0˪Z&;R2eZA'p|aMA,lvʰ`I[0灿GT𡵭Oxo\&xiUՐe( 0AExg מ}neQ;1gk7et>#[X18GVOuA'¦*çL p 2I1GĄT`n||<3=FT Y| t+)iс^XbW|I$vZ-zRYE eh8(C¨1XeID >[~F~eaP([^5M_^Y 4֢n#3pl0%KŭxW^Ovɐ JT`d9gΆwkl|sȨ_1P.{6~#=qEUYt $J4OFm]?qko/ g}Ez uj:ެx[;t]M8Eqm(Lp5{m0f߽zF\QO-JʵO?muUu_ Śj7qkv,݅5^XخJVBxWPTC[x#Ɵ|x WEe=<jM!Q}wo6-y?7֪9"/=(a [8ey~;q-@T^ψo4kCEh/-_&y@=1k,*SJ%kt{ߧ]~(mS˕$qx2B.4[( +cB+ȾW)mp!tN 2J6I'ӏZvM.#l77=ޛwYu: u>׆jN%R:4VQw.RmvI I5}-[QyY[?r4h.L1o]A>au/, IѦUl!j8+"3X'/?C]?8z~G_GdkG{4KBDžgR($ i>&[ݟA7Qi C\-#S 2 am?cg?X_}Q5z|^}i-폀'1kYR#""-B3m * tGºmy,: U2s>\ڻA.E?cGAf;]ti}2,z MGVz>{m>k7;E)LSj`kWWY"5"(EK ĀshdZSU# WY͋µ7JKKEkUwi"T;w 1V$Fa>Ҽ'c1ݬs 4G.KHIX P}o!K7G-nbVC=yk=]_?|G騢سrOr`dN;L2_Qt_Qt >}坮k-K<)WGA ;SvIRMY1w#3ЛUc coP> nŠVwJќ/*=y|?W?|?W?{1' P> n\ê6ՠŮcr-5 ."@}{(beQ\2<]|}g8hk[֩1H-Y_^cGH|3fO),l"7ڼ9+TUgssʘ!UfjI3%dzt6I#c"#8?]v _25yIzO#j?u)¾4w>!I<IUOcU[۷k̢Y9`ڼ(PK~ J9-4b$G4|Zd 󟙀 Cߗ?N_On:Q]k\_åëJGkq"p0P#9. }ǽi!bOޑonD@yRrH'9_6޽c5zkc!w8z>\Vɩ4#s.,5Zo <ߘ&I&<TpɃJH[P 7ҘZ#mQ%ڍpK) DZp5 kCSlGBxQ8슺G|MA|KvnMΟ=?ŭ3uaso 溴BchdTVkPK!hqEdocProps/app.xml (Tn1W?X{G,ٱgY^{e4; 4(›ϣ۷ʐ d^?#`Sڮ'*]\g$Da0$Bnol] >j%lde KDabpC({'U`## ouQwY8į*'sQEar]0؋*kF[h)"Zz\BHm %Y#Fhl E e^/ٓiCF"mkW1}bjt ;Ȟ00E_x!Ll8ހΓ୏2*4nNZؘ6H!z>s6  r◉Sb(ڃ3}$@8#R|D|p{cwgx꡵ma{ _F')wh?p=GbC :w"nnnƻ4i)ԎЬv+_z\wPK!tdocProps/core.xml (|]K0Cɵ]n.]) VQ lWM*}ӓ7-myeDSg$(t%aNɂ2iPI{&ԈQe @nkR+͒䚶(h/h$Ge%G}w $Zi:IZ|#[{ p?&]>a<>J]I $C PvAGgqY;#4̀Fx| oQPz.~7O:ةyx.U.\%Y'Y,g,+ 7q2 2IՔ7ߌ'6OPK-!`L [Content_Types].xmlPK-!ht _rels/.relsPK-! 3 WTppt/slides/slide1.xmlPK-!c\#7 ppt/slides/_rels/slide2.xml.relsPK-!c\#7 ppt/slides/_rels/slide1.xml.relsPK-!*3>Uppt/_rels/presentation.xml.relsPK-!D" L[ppt/slides/slide2.xmlPK-!kl "ppt/presentation.xmlPK-! [F~!h%ppt/slideLayouts/slideLayout1.xmlPK-!qG8Y!*ppt/slideLayouts/slideLayout2.xmlPK-!aAU!E/ppt/slideLayouts/slideLayout3.xmlPK-!3x!4ppt/slideLayouts/slideLayout4.xmlPK-!44>2!9ppt/slideLayouts/slideLayout5.xmlPK-!! !@ppt/slideLayouts/slideLayout6.xmlPK-!N+ !+Dppt/slideLayouts/slideLayout7.xmlPK-!1'f!Gppt/slideLayouts/slideLayout8.xmlPK-!9!Mppt/slideLayouts/slideLayout9.xmlPK-!t"Sppt/slideLayouts/slideLayout10.xmlPK-!j¼"jXppt/slideLayouts/slideLayout11.xmlPK-!ђ7-Q]ppt/slideLayouts/_rels/slideLayout11.xml.relsPK-!ђ7-X^ppt/slideLayouts/_rels/slideLayout10.xml.relsPK-!ђ7,__ppt/slideLayouts/_rels/slideLayout9.xml.relsPK-!ђ7,e`ppt/slideLayouts/_rels/slideLayout6.xml.relsPK-!ђ7,kappt/slideLayouts/_rels/slideLayout7.xml.relsPK-!i_!,qbppt/slideMasters/_rels/slideMaster1.xml.relsPK-!ђ7,cppt/slideLayouts/_rels/slideLayout8.xml.relsPK-!3fc6!dppt/slideMasters/slideMaster1.xmlPK-!ђ7,,mppt/slideLayouts/_rels/slideLayout1.xml.relsPK-!ђ7,2nppt/slideLayouts/_rels/slideLayout2.xml.relsPK-!ђ7,8oppt/slideLayouts/_rels/slideLayout3.xml.relsPK-!ђ7,>pppt/slideLayouts/_rels/slideLayout4.xml.relsPK-!ђ7,Dqppt/slideLayouts/_rels/slideLayout5.xml.relsPK-!{C] Jrppt/theme/theme1.xmlPK- !:D - -@ydocProps/thumbnail.jpegPK-!d#k2ppt/presProps.xmlPK-!Qppt/tableStyles.xmlPK-!w.ppt/viewProps.xmlPK-!hqEԪdocProps/app.xmlPK-!tdocProps/core.xmlPK'' ưjupyter-cache-1.0.0/docs/using/images/execution_process.svg000066400000000000000000002112361452276675600241100ustar00rootroot00000000000000 PROJECT FOLDER Notebook 1 Notebook 2 EXECUTION 1. Get notebook path from database 2. Get actual notebook from project folder 3. Check if notebook already exists in the cache ( via hash) 4. If not, read notebook and execute 5. If successful, write executed notebook to the cache. CACHE FOLDER COMMITTED NOTEBOOKS Notebook A Notebook B PROJECT DATABASE EXECUTOR Notebook 1 Copy Notebook 1 Executed HASH URI jupyter-cache-1.0.0/jupyter_cache/000077500000000000000000000000001452276675600171245ustar00rootroot00000000000000jupyter-cache-1.0.0/jupyter_cache/__init__.py000066400000000000000000000004651452276675600212420ustar00rootroot00000000000000# NOTE: never import anything here, in order to maintain CLI speed __version__ = "1.0.0" def get_cache(path, cache_cls=None): """Return a cache, with a given folder path.""" if cache_cls is None: from jupyter_cache.cache.main import JupyterCacheBase as cache_cls return cache_cls(path) jupyter-cache-1.0.0/jupyter_cache/base.py000066400000000000000000000261461452276675600204210ustar00rootroot00000000000000"""This module defines the abstract interface of the cache. API access to the cache should use this interface, with no assumptions about the backend storage/retrieval mechanisms. """ from abc import ABC, abstractmethod import io from pathlib import Path from typing import Iterable, List, Mapping, Optional, Tuple, Union import attr from attr.validators import instance_of, optional import nbformat as nbf # TODO make these abstract from jupyter_cache.cache.db import NbCacheRecord, NbProjectRecord from jupyter_cache.readers import DEFAULT_READ_DATA NB_VERSION = 4 class CachingError(Exception): """An error to raise when adding to the cache fails.""" class RetrievalError(Exception): """An error to raise when retrieving from the cache fails.""" class NbValidityError(Exception): """Signals a notebook may not be valid to cache. For example, because it has not yet been executed. """ def __init__(self, message, nb_bundle, *args, **kwargs): self.uri = nb_bundle.uri super().__init__(message, *args, **kwargs) @attr.s(frozen=True, slots=True) class ProjectNb: """A notebook read from a project""" pk: int = attr.ib( validator=instance_of(int), metadata={"help": "the ID of the notebook"}, ) uri: str = attr.ib( converter=str, validator=instance_of(str), metadata={"help": "the URI of the notebook"}, ) nb: nbf.NotebookNode = attr.ib( validator=instance_of(nbf.NotebookNode), repr=lambda nb: f"Notebook(cells={len(nb.cells)})", metadata={"help": "the notebook"}, ) assets: List[Path] = attr.ib( factory=list, metadata={"help": "File paths required to run the notebook"}, ) class NbArtifactsAbstract(ABC): """Container for artefacts of a notebook execution.""" @property @abstractmethod def relative_paths(self) -> List[Path]: """Return the list of paths (relative to the notebook folder).""" @abstractmethod def __iter__(self) -> Iterable[Tuple[Path, io.BufferedReader]]: """Yield the relative path and open files (in bytes mode)""" def __repr__(self): return f"{self.__class__.__name__}(paths={len(self.relative_paths)})" @attr.s(frozen=True, slots=True) class CacheBundleIn: """A container for notebooks and their associated data to cache.""" nb: nbf.NotebookNode = attr.ib( validator=instance_of(nbf.NotebookNode), repr=lambda nb: f"Notebook(cells={len(nb.cells)})", metadata={"help": "the notebook"}, ) uri: str = attr.ib( converter=str, validator=instance_of(str), metadata={"help": "the origin URI of the notebook"}, ) artifacts: Optional[NbArtifactsAbstract] = attr.ib( kw_only=True, default=None, metadata={"help": "artifacts created during the notebook execution"}, ) data: dict = attr.ib( kw_only=True, factory=dict, validator=instance_of(dict), metadata={"help": "additional data related to the execution"}, ) traceback: Optional[str] = attr.ib( kw_only=True, default=None, validator=optional(instance_of(str)), metadata={"help": "the traceback, if the execution excepted"}, ) @attr.s(frozen=True, slots=True) class CacheBundleOut: """A container for notebooks and their associated data that have been cached.""" nb: nbf.NotebookNode = attr.ib( validator=instance_of(nbf.NotebookNode), repr=lambda nb: f"Notebook(cells={len(nb.cells)})", metadata={"help": "the notebook"}, ) record: NbCacheRecord = attr.ib(metadata={"help": "the cache record"}) artifacts: Optional[NbArtifactsAbstract] = attr.ib( default=None, metadata={"help": "artifacts created during the notebook execution"}, ) class JupyterCacheAbstract(ABC): """An abstract cache for storing pre/post executed notebooks. Note: class instances should be pickleable. """ @abstractmethod def get_version(self) -> Optional[str]: """Return the version of the cache.""" @abstractmethod def clear_cache(self) -> None: """Clear the cache completely.""" @abstractmethod def cache_notebook_bundle( self, bundle: CacheBundleIn, check_validity: bool = True, overwrite: bool = False, ) -> NbCacheRecord: """Commit an executed notebook, returning its cache record. Note: non-code source text (e.g. markdown) is not stored in the cache. :param bundle: The notebook bundle :param check_validity: check that the notebook has been executed correctly, by asserting `execution_count`s are consecutive and start at 1. :param overwrite: Allow overwrite of cache with matching hash :return: The primary key of the cache """ @abstractmethod def cache_notebook_file( self, path: str, uri: Optional[str] = None, artifacts: List[str] = (), data: Optional[dict] = None, check_validity: bool = True, overwrite: bool = False, ) -> NbCacheRecord: """Commit an executed notebook, returning its cache record. Note: non-code source text (e.g. markdown) is not stored in the cache. :param path: path to the notebook :param uri: alternative URI to store in the cache record (defaults to path) :param artifacts: list of paths to outputs of the executed notebook. Artifacts must be in the same folder as the notebook (or a sub-folder) :param data: additional, JSONable, data about the cache :param check_validity: check that the notebook has been executed correctly, by asserting `execution_count`s are consecutive and start at 1. :param overwrite: Allow overwrite of cache with matching hash :return: The primary key of the cache """ @abstractmethod def list_cache_records(self) -> List[NbCacheRecord]: """Return a list of cached notebook records.""" @abstractmethod def get_cache_record(self, pk: int) -> NbCacheRecord: """Return the record of a cache, by its primary key""" @abstractmethod def get_cache_bundle(self, pk: int) -> CacheBundleOut: """Return an executed notebook bundle, by its primary key""" @abstractmethod def cache_artefacts_temppath(self, pk: int) -> Path: """Context manager to provide a temporary folder path to the notebook artifacts. Note this path is only guaranteed to exist within the scope of the context, and should only be used for read/copy operations:: with cache.cache_artefacts_temppath(1) as path: shutil.copytree(path, destination) """ @abstractmethod def match_cache_notebook(self, nb: nbf.NotebookNode) -> NbCacheRecord: """Match to an executed notebook, returning its primary key. :raises KeyError: if no match is found """ def match_cache_file(self, path: str) -> NbCacheRecord: """Match to an executed notebook, returning its primary key. :raises KeyError: if no match is found """ notebook = nbf.read(path, nbf.NO_CONVERT) return self.match_cache_notebook(notebook) @abstractmethod def merge_match_into_notebook( self, nb: nbf.NotebookNode, nb_meta=("kernelspec", "language_info", "widgets"), cell_meta=None, ) -> Tuple[int, nbf.NotebookNode]: """Match to an executed notebook and return a merged version :param nb: The input notebook :param nb_meta: metadata keys to merge from the cache (all if None) :param cell_meta: cell metadata keys to merge from the cache (all if None) :raises KeyError: if no match is found :return: pk, input notebook with cached code cells and metadata merged. """ def merge_match_into_file( self, path: str, nb_meta=("kernelspec", "language_info", "widgets"), cell_meta=None, ) -> Tuple[int, nbf.NotebookNode]: """Match to an executed notebook and return a merged version :param path: The input notebook path :param nb_meta: metadata keys to merge from the cache (all if None) :param cell_meta: cell metadata keys to merge from the cache (all if None) :raises KeyError: if no match is found :return: pk, input notebook with cached code cells and metadata merged. """ nb = nbf.read(str(path), nbf.NO_CONVERT) return self.merge_match_into_notebook(nb, nb_meta, cell_meta) @abstractmethod def diff_nbnode_with_cache( self, pk: int, nb: nbf.NotebookNode, uri: str = "", as_str=False, **kwargs ) -> Union[str, dict]: """Return a diff of a notebook to a cached one. Note: this will not diff markdown content, since it is not stored in the cache. """ def diff_nbfile_with_cache( self, pk: int, path: str, as_str=False, **kwargs ) -> Union[str, dict]: """Return a diff of a notebook to a cached one. Note: this will not diff markdown content, since it is not stored in the cache. """ nb = nbf.read(path, nbf.NO_CONVERT) return self.diff_nbnode_with_cache(pk, nb, uri=path, as_str=as_str, **kwargs) @abstractmethod def add_nb_to_project( self, uri: str, *, read_data: Mapping = DEFAULT_READ_DATA, assets: List[str] = (), ) -> NbProjectRecord: """Add a single notebook to the project. :param uri: The path to the file :param read_data: Data to generate a function, to read the uri and return a NotebookNode :param assets: The path of files required by the notebook to run. :raises ValueError: assets not within the same folder as the notebook URI. """ @abstractmethod def remove_nb_from_project(self, uri_or_pk: Union[int, str]): """Remove a notebook from the project.""" @abstractmethod def list_project_records( self, filter_uris: Optional[List[str]] = None, filter_pks: Optional[List[int]] = None, ) -> List[NbProjectRecord]: """Return a list of all notebook records in the project.""" @abstractmethod def get_project_record(self, uri_or_pk: Union[int, str]) -> NbProjectRecord: """Return the record of a notebook in the project, by its primary key or URI.""" @abstractmethod def get_project_notebook(self, uri_or_pk: Union[int, str]) -> ProjectNb: """Return a single notebook in the project, by its primary key or URI. :raises NbReadError: if the notebook cannot be read """ @abstractmethod def get_cached_project_nb( self, uri_or_pk: Union[int, str] ) -> Optional[NbCacheRecord]: """Get cache record for a notebook in the project. :param uri_or_pk: The URI of pk of the file in the project """ @abstractmethod def list_unexecuted( self, filter_uris: Optional[List[str]] = None, filter_pks: Optional[List[int]] = None, ) -> List[NbProjectRecord]: """List notebooks in the project, whose hash is not present in the cache.""" jupyter-cache-1.0.0/jupyter_cache/cache/000077500000000000000000000000001452276675600201675ustar00rootroot00000000000000jupyter-cache-1.0.0/jupyter_cache/cache/__init__.py000066400000000000000000000000001452276675600222660ustar00rootroot00000000000000jupyter-cache-1.0.0/jupyter_cache/cache/db.py000066400000000000000000000355771452276675600211470ustar00rootroot00000000000000from contextlib import contextmanager from datetime import datetime import os from pathlib import Path from typing import Any, Dict, List, Optional, Union from sqlalchemy import JSON, Column, DateTime, Integer, String, Text from sqlalchemy.engine import Engine, create_engine from sqlalchemy.exc import IntegrityError, OperationalError try: from sqlalchemy.orm import declarative_base # sqlalchemy >= 1.4.0 except ImportError: from sqlalchemy.ext.declarative import declarative_base # sqlalchemy < 1.4.0 from sqlalchemy.orm import sessionmaker, validates from sqlalchemy.sql.expression import desc from jupyter_cache import __version__ from jupyter_cache.utils import shorten_path OrmBase = declarative_base() DB_NAME = "global.db" # version changes: # 0.5.0: # - __version__.txt file written to cache on creation # - table: nbstage -> nbproject # - added read_data and exec_data fields to nbproject def create_db(path: Union[str, Path]) -> Engine: """Get or create a database at the given path. :param path: The path to the cache folder. """ exists = (Path(path) / DB_NAME).exists() engine = create_engine(f"sqlite:///{os.path.join(path, DB_NAME)}") if not exists: # add all the tables, and a version identifier OrmBase.metadata.create_all(engine) Path(path).joinpath("__version__.txt").write_text(__version__) return engine def get_version(path: Union[str, Path]) -> Optional[str]: """Attempt to get the version of the cache.""" version_file = Path(path).joinpath("__version__.txt") if version_file.exists(): return version_file.read_text().strip() @contextmanager def session_context(engine: Engine): """Open a connection to the database.""" session = sessionmaker(bind=engine)() try: yield session except OperationalError as exc: session.rollback() raise RuntimeError( "Unexpected error accessing jupyter cache, it may need to be cleared." ) from exc except Exception: session.rollback() raise finally: session.close() class Setting(OrmBase): """A settings key/value pair representation.""" __tablename__ = "settings" pk = Column(Integer(), primary_key=True) key = Column(String(36), nullable=False, unique=True) value = Column(JSON()) def __repr__(self): return "{}(pk={},{}={})".format( self.__class__.__name__, self.pk, self.key, self.value ) @staticmethod def set_value(key: str, value, db: Engine): with session_context(db) as session: # type: Session setting = session.query(Setting).filter_by(key=key).one_or_none() if setting is None: session.add(Setting(key=key, value=value)) else: setting.value = value try: session.commit() except IntegrityError: raise TypeError(value) @staticmethod def get_value(key: str, db: Engine, default=None): with session_context(db) as session: # type: Session result = session.query(Setting.value).filter_by(key=key).one_or_none() if result is None: if default is not None: result = [default] else: raise KeyError(f"Setting not found in DB: {key}") value = result[0] return value @staticmethod def get_dict(db: Engine) -> dict: with session_context(db) as session: # type: Session results = session.query(Setting.key, Setting.value).all() return {k: v for k, v in results} class NbProjectRecord(OrmBase): """A record of a notebook within the project.""" __tablename__ = "nbproject" pk = Column(Integer(), primary_key=True) uri = Column(String(255), nullable=False, unique=True) read_data = Column(JSON(), nullable=False) """Data on how to read the uri to a notebook.""" assets = Column(JSON(), nullable=False, default=list) """A list of file assets required for the notebook to run.""" exec_data = Column(JSON(), nullable=True) """Data on how to execute the notebook.""" created = Column(DateTime, nullable=False, default=datetime.utcnow) traceback = Column(Text(), nullable=True, default="") """A traceback is added if a notebook fails to execute fully.""" def __repr__(self): return f"{self.__class__.__name__}(pk={self.pk})" def to_dict(self): return {k: v for k, v in self.__dict__.items() if not k.startswith("_")} def format_dict( self, cache_record: Optional["NbCacheRecord"] = None, path_length: Optional[int] = None, assets: bool = True, read_error: Optional[str] = None, read_name: bool = True, ) -> dict: """Return data for display.""" status = "-" if cache_record: status = f"✅ [{cache_record.pk}]" elif self.traceback: status = "❌" elif read_error: status = "❗️ (unreadable)" data = { "ID": self.pk, "URI": str(shorten_path(self.uri, path_length)), "Reader": self.read_data.get("name", "-") if read_name else self.read_data, "Added": self.created.isoformat(" ", "minutes"), "Status": status, } if assets: data["Assets"] = len(self.assets) return data @validates("read_data") def validate_read_data(self, key, value): if not isinstance(value, dict): raise ValueError("read_data must be a dict") if "name" not in value: raise ValueError("read_data must have a name") return value @validates("assets") def validator_assets(self, key, value): return self.validate_assets(value) @staticmethod def validate_assets(paths, uri=None): """Validate asset paths are within same folder as the notebook URI""" if not ( isinstance(paths, (list, tuple)) and all(isinstance(v, str) for v in paths) ): raise TypeError(f"assets must be interable of strings: {paths}") if uri is None: return list(paths) uri_folder = Path(uri).parent for path in paths: try: Path(path).relative_to(uri_folder) except ValueError: raise ValueError(f"Asset '{path}' is not in folder '{uri_folder}''") return list(paths) @staticmethod def create_record( uri: str, db: Engine, read_data: Dict[str, Any], raise_on_exists=True, *, assets=(), ) -> "NbProjectRecord": assets = NbProjectRecord.validate_assets(assets, uri) with session_context(db) as session: # type: Session record = NbProjectRecord(uri=uri, read_data=read_data, assets=assets) session.add(record) try: session.commit() except IntegrityError: if raise_on_exists: raise ValueError(f"URI already in project: {uri}") return NbProjectRecord.record_from_uri(uri, db) session.refresh(record) session.expunge(record) return record def remove_pks(pks: List[int], db: Engine): with session_context(db) as session: # type: Session session.query(NbProjectRecord).filter(NbProjectRecord.pk.in_(pks)).delete( synchronize_session=False ) session.commit() def remove_uris(uris: List[str], db: Engine): with session_context(db) as session: # type: Session session.query(NbProjectRecord).filter(NbProjectRecord.uri.in_(uris)).delete( synchronize_session=False ) session.commit() @staticmethod def record_from_pk(pk: int, db: Engine) -> "NbProjectRecord": with session_context(db) as session: # type: Session result = session.query(NbProjectRecord).filter_by(pk=pk).one_or_none() if result is None: raise KeyError(f"Project record not found for NB with PK: {pk}") session.expunge(result) return result @staticmethod def record_from_uri(uri: str, db: Engine) -> "NbProjectRecord": with session_context(db) as session: # type: Session result = session.query(NbProjectRecord).filter_by(uri=uri).one_or_none() if result is None: raise KeyError(f"Project record not found for NB with URI: {uri}") session.expunge(result) return result @staticmethod def records_all(db: Engine) -> "NbProjectRecord": with session_context(db) as session: # type: Session results = session.query(NbProjectRecord).order_by(NbProjectRecord.pk).all() session.expunge_all() return results def remove_tracebacks(pks, db: Engine): """Remove all tracebacks.""" with session_context(db) as session: # type: Session session.query(NbProjectRecord).filter(NbProjectRecord.pk.in_(pks)).update( {NbProjectRecord.traceback: None}, synchronize_session=False ) session.commit() def set_traceback(uri: str, traceback: Optional[str], db: Engine): with session_context(db) as session: # type: Session result = session.query(NbProjectRecord).filter_by(uri=uri).one_or_none() if result is None: raise KeyError(f"Project record not found for NB with URI: {uri}") result.traceback = traceback try: session.commit() except IntegrityError: raise TypeError(traceback) class NbCacheRecord(OrmBase): """A record of an executed notebook cache.""" __tablename__ = "nbcache" pk = Column(Integer(), primary_key=True) hashkey = Column(String(255), nullable=False, unique=True) uri = Column(String(255), nullable=False, unique=False) description = Column(String(255), nullable=False, default="") data = Column(JSON()) """Extra data, such as the execution time.""" created = Column(DateTime, nullable=False, default=datetime.utcnow) accessed = Column( DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow ) def __repr__(self): return f"{self.__class__.__name__}(pk={self.pk})" def to_dict(self): return {k: v for k, v in self.__dict__.items() if not k.startswith("_")} def format_dict( self, hashkey=False, path_length=None, show_descript=False, show_data=True ): data = { "ID": self.pk, "Origin URI": str(shorten_path(self.uri, path_length)), "Created": self.created.isoformat(" ", "minutes"), "Accessed": self.accessed.isoformat(" ", "minutes"), } if show_descript: data["Description"] = self.description if hashkey: data["Hashkey"] = self.hashkey if show_data and self.data: data["Data"] = self.data return data @staticmethod def create_record(uri: str, hashkey: str, db: Engine, **kwargs) -> "NbCacheRecord": with session_context(db) as session: # type: Session record = NbCacheRecord(hashkey=hashkey, uri=uri, **kwargs) session.add(record) try: session.commit() except IntegrityError: raise ValueError(f"hashkey already exists:{hashkey}") session.refresh(record) session.expunge(record) return record def remove_record(pk: int, db: Engine): with session_context(db) as session: # type: Session record = session.get(NbCacheRecord, pk) session.delete(record) session.commit() def remove_records(pks: List[int], db: Engine): with session_context(db) as session: # type: Session session.query(NbCacheRecord).filter(NbCacheRecord.pk.in_(pks)).delete( synchronize_session=False ) session.commit() @staticmethod def record_from_hashkey(hashkey: str, db: Engine) -> "NbCacheRecord": with session_context(db) as session: # type: Session result = ( session.query(NbCacheRecord).filter_by(hashkey=hashkey).one_or_none() ) if result is None: raise KeyError(f"Cache record not found for NB with hashkey: {hashkey}") session.expunge(result) return result @staticmethod def record_from_pk(pk: int, db: Engine) -> "NbCacheRecord": with session_context(db) as session: # type: Session result = session.query(NbCacheRecord).filter_by(pk=pk).one_or_none() if result is None: raise KeyError(f"Cache record not found for NB with PK: {pk}") session.expunge(result) return result def touch(pk, db: Engine): """Touch a record, to change its last accessed time.""" with session_context(db) as session: # type: Session record = session.query(NbCacheRecord).filter_by(pk=pk).one_or_none() if record is None: raise KeyError(f"Cache record not found for NB with PK: {pk}") record.accessed = datetime.utcnow() session.commit() def touch_hashkey(hashkey, db: Engine): """Touch a record, to change its last accessed time.""" with session_context(db) as session: # type: Session record = ( session.query(NbCacheRecord).filter_by(hashkey=hashkey).one_or_none() ) if record is None: raise KeyError(f"Cache record not found for NB with hashkey: {hashkey}") record.accessed = datetime.utcnow() session.commit() @staticmethod def records_from_uri(uri: str, db: Engine) -> "NbCacheRecord": with session_context(db) as session: # type: Session results = session.query(NbCacheRecord).filter_by(uri=uri).all() session.expunge_all() return results @staticmethod def records_all(db: Engine) -> "NbCacheRecord": with session_context(db) as session: # type: Session results = session.query(NbCacheRecord).all() session.expunge_all() return results def records_to_delete(keep: int, db: Engine) -> List[int]: """Return pks of the oldest records, where keep is number to keep.""" with session_context(db) as session: # type: Session pks_to_keep = [ pk for pk, in session.query(NbCacheRecord.pk) .order_by(desc("accessed")) .limit(keep) .all() ] pks_to_delete = [ pk for pk, in session.query(NbCacheRecord.pk) .filter(NbCacheRecord.pk.notin_(pks_to_keep)) .all() ] return pks_to_delete jupyter-cache-1.0.0/jupyter_cache/cache/main.py000066400000000000000000000464301452276675600214740ustar00rootroot00000000000000from contextlib import contextmanager import copy import hashlib import io from pathlib import Path import shutil from typing import Iterable, List, Mapping, Optional, Tuple, Union import nbformat as nbf from jupyter_cache.base import ( # noqa: F401 NB_VERSION, CacheBundleIn, CacheBundleOut, CachingError, JupyterCacheAbstract, NbArtifactsAbstract, NbValidityError, ProjectNb, RetrievalError, ) from jupyter_cache.readers import DEFAULT_READ_DATA, NbReadError, get_reader from jupyter_cache.utils import to_relative_paths from .db import NbCacheRecord, NbProjectRecord, Setting, create_db, get_version CACHE_LIMIT_KEY = "cache_limit" DEFAULT_CACHE_LIMIT = 1000 class NbArtifacts(NbArtifactsAbstract): """Container for artefacts of a notebook execution.""" def __init__(self, paths: List[str], in_folder, check_existence=True): """Initiate NbArtifacts :param paths: list of paths :param check_existence: check the paths exist :param in_folder: The folder that all paths should be in (or subfolder). :raises IOError: if check_existence and file does not exist """ self.paths = [Path(p).absolute() for p in paths] self.in_folder = Path(in_folder).absolute() to_relative_paths(self.paths, self.in_folder, check_existence=check_existence) @property def relative_paths(self) -> List[Path]: """Return the list of paths (relative to the notebook folder).""" return to_relative_paths(self.paths, self.in_folder) def __iter__(self) -> Iterable[Tuple[Path, io.BufferedReader]]: """Yield the relative path and open files (in bytes mode)""" for path in self.paths: with path.open("rb") as handle: yield path.relative_to(self.in_folder), handle class JupyterCacheBase(JupyterCacheAbstract): def __init__(self, path): self._path = Path(path).absolute() self._db = None @property def path(self): if not self._path.exists(): self._path.mkdir(parents=True) return self._path @property def db(self): """a simple database for storing persistent global data.""" if self._db is None: self._db = create_db(self.path) return self._db def __repr__(self): return f"{self.__class__.__name__}({repr(str(self._path))})" def __getstate__(self): """For pickling instances, db must be removed.""" state = self.__dict__.copy() state["_db"] = None return state def get_version(self) -> Optional[str]: return get_version(self.path) def clear_cache(self): """Clear the cache completely.""" shutil.rmtree(self.path) self._db = None def _get_notebook_path_cache(self, hashkey, raise_on_missing=False) -> Path: """Retrieve a relative path in the cache to a notebook, from its hash.""" path = self.path.joinpath(Path("executed", hashkey, "base.ipynb")) if not path.exists() and raise_on_missing: raise RetrievalError(f"hashkey not in cache: {hashkey}") return path def _get_artifact_path_cache(self, hashkey) -> Path: """Retrieve a relative path in the cache to a notebook, from its hash.""" path = self.path.joinpath(Path("executed", hashkey, "artifacts")) return path def truncate_caches(self): """If the number of cached notebooks exceeds set limit, delete the oldest.""" cache_limit = Setting.get_value(CACHE_LIMIT_KEY, self.db, DEFAULT_CACHE_LIMIT) # TODO you could have better control over this by e.g. tagging certain caches # that should not be deleted. pks = NbCacheRecord.records_to_delete(cache_limit, self.db) for pk in pks: self.remove_cache(pk) def get_cache_limit(self): return Setting.get_value(CACHE_LIMIT_KEY, self.db, DEFAULT_CACHE_LIMIT) def change_cache_limit(self, size: int): assert isinstance(size, int) and size > 0 Setting.set_value(CACHE_LIMIT_KEY, size, self.db) def create_hashed_notebook( self, nb: nbf.NotebookNode, nb_metadata: Optional[Iterable[str]] = ("kernelspec",), cell_metadata: Optional[Iterable[str]] = None, ) -> Tuple[nbf.NotebookNode, str]: """Convert a notebook to a standard format and hash. Note: we always hash notebooks as version 4.4, to allow for matching notebooks of different versions :param nb_metadata: The notebook metadata keys to hash (if None, use all) :param cell_metadata: The cell metadata keys to hash (if None, use all) :return: (notebook, hash) """ # copy the notebook nb = copy.deepcopy(nb) # update the notebook to consistent version 4.4 nb = nbf.convert(nb, to_version=NB_VERSION) if nb.nbformat_minor > 5: raise CachingError("notebook version greater than 4.5 not yet supported") # remove non-code cells nb.cells = [cell for cell in nb.cells if cell.cell_type == "code"] # create notebook for hashing, with selected metadata hash_nb = nbf.from_dict( { "nbformat": nb.nbformat, "nbformat_minor": 4, # v4.5 include cell ids, which we do not cache "metadata": { k: v for k, v in nb.metadata.items() if nb_metadata is None or (k in nb_metadata) }, "cells": [ { "cell_type": cell.cell_type, "source": cell.source, "metadata": { k: v for k, v in cell.metadata.items() if cell_metadata is None or (k in cell_metadata) }, "execution_count": None, "outputs": [], } for cell in nb.cells if cell.cell_type == "code" ], } ) # hash notebook string = nbf.writes(hash_nb, nbf.NO_CONVERT) hash_string = hashlib.md5(string.encode()).hexdigest() return (nb, hash_string) def _validate_nb_bundle(self, nb_bundle: CacheBundleIn): """Validate that a notebook bundle should be cached. We check that the notebook has been executed correctly, by asserting `execution_count`s are consecutive and start at 1. """ execution_count = 1 for i, cell in enumerate(nb_bundle.nb.cells): if cell.cell_type != "code": continue if cell.execution_count != execution_count: raise NbValidityError( "Expected cell {} to have execution_count {} not {}".format( i, execution_count, cell.execution_count ), nb_bundle, ) execution_count += 1 # TODO check for output exceptions? # TODO assets def cache_notebook_bundle( self, bundle: CacheBundleIn, check_validity: bool = True, overwrite: bool = False, description="", ) -> NbCacheRecord: """Cache an executed notebook.""" # TODO it would be ideal to have some 'rollback' mechanism on exception if check_validity: self._validate_nb_bundle(bundle) hashed_nb, hashkey = self.create_hashed_notebook(bundle.nb) path = self._get_notebook_path_cache(hashkey) if path.exists(): if not overwrite: raise CachingError( "Notebook already exists in cache and overwrite=False." ) shutil.rmtree(path.parent) try: record = NbCacheRecord.record_from_hashkey(hashkey, self.db) except KeyError: pass else: NbCacheRecord.remove_record(record.pk, self.db) record = NbCacheRecord.create_record( uri=bundle.uri, hashkey=hashkey, db=self.db, data=bundle.data, description=description, ) path.parent.mkdir(parents=True) path.write_text(nbf.writes(hashed_nb, nbf.NO_CONVERT), encoding="utf8") # write artifacts artifact_folder = self._get_artifact_path_cache(hashkey) if artifact_folder.exists(): shutil.rmtree(artifact_folder) for rel_path, handle in bundle.artifacts or []: write_path = artifact_folder.joinpath(rel_path) write_path.parent.mkdir(parents=True, exist_ok=True) write_path.write_bytes(handle.read()) self.truncate_caches() return record def cache_notebook_file( self, path: str, uri: Optional[str] = None, artifacts: List[str] = (), data: Optional[dict] = None, check_validity: bool = True, overwrite: bool = False, ) -> NbCacheRecord: """Cache an executed notebook, returning its primary key. Note: non-code source text (e.g. markdown) is not stored in the cache. :param path: path to the notebook :param uri: alternative URI to store in the cache record (defaults to path) :param artifacts: list of paths to outputs of the executed notebook. Artifacts must be in the same folder as the notebook (or a sub-folder) :param data: additional, JSONable, data to store in the cache record :param check_validity: check that the notebook has been executed correctly, by asserting `execution_count`s are consecutive and start at 1. :param overwrite: Allow overwrite of cached notebooks with matching hash :return: The primary key of the cache record """ notebook = nbf.read(str(path), nbf.NO_CONVERT) return self.cache_notebook_bundle( CacheBundleIn( notebook, uri or str(path), artifacts=NbArtifacts(artifacts, in_folder=Path(path).parent), data=data or {}, ), check_validity=check_validity, overwrite=overwrite, ) def list_cache_records(self) -> List[NbCacheRecord]: return NbCacheRecord.records_all(self.db) def get_cache_record(self, pk: int) -> NbCacheRecord: return NbCacheRecord.record_from_pk(pk, self.db) def get_cache_bundle(self, pk: int) -> CacheBundleOut: record = NbCacheRecord.record_from_pk(pk, self.db) NbCacheRecord.touch(pk, self.db) path = self._get_notebook_path_cache(record.hashkey) artifact_folder = self._get_artifact_path_cache(record.hashkey) if not path.exists(): raise KeyError(f"Notebook file does not exist for cache record PK: {pk}") return CacheBundleOut( nbf.reads(path.read_text(encoding="utf8"), nbf.NO_CONVERT), record=record, artifacts=NbArtifacts( [p for p in artifact_folder.glob("**/*") if p.is_file()], in_folder=artifact_folder, ), ) @contextmanager def cache_artefacts_temppath(self, pk: int) -> Path: """Context manager to provide a temporary folder path to the notebook artifacts. Note this path is only guaranteed to exist within the scope of the context, and should only be used for read/copy operations:: with cache.cache_artefacts_temppath(1) as path: shutil.copytree(path, destination) """ record = NbCacheRecord.record_from_pk(pk, self.db) yield self._get_artifact_path_cache(record.hashkey) def remove_cache(self, pk: int): record = NbCacheRecord.record_from_pk(pk, self.db) path = self._get_notebook_path_cache(record.hashkey) if not path.exists(): raise KeyError(f"Notebook file does not exist for cache record PK: {pk}") shutil.rmtree(path.parent) NbCacheRecord.remove_records([pk], self.db) def match_cache_notebook(self, nb: nbf.NotebookNode) -> NbCacheRecord: """Match to an executed notebook, returning its primary key. :raises KeyError: if no match is found """ _, hashkey = self.create_hashed_notebook(nb) cache_record = NbCacheRecord.record_from_hashkey(hashkey, self.db) return cache_record def merge_match_into_notebook( self, nb: nbf.NotebookNode, nb_meta: Optional[Iterable[str]] = ("kernelspec", "language_info", "widgets"), cell_meta: Optional[Iterable[str]] = None, ) -> Tuple[int, nbf.NotebookNode]: """Match to an executed notebook and return a merged version :param nb: The input notebook :param nb_meta: metadata keys to merge from the cached notebook (all if None) :param cell_meta: cell metadata keys to merge from cached notebook (all if None) :raises KeyError: if no match is found :return: pk, input notebook with cached code cells and metadata merged. """ pk = self.match_cache_notebook(nb).pk cache_nb = self.get_cache_bundle(pk).nb nb = nbf.convert(copy.deepcopy(nb), NB_VERSION) if nb_meta is None: nb.metadata = cache_nb.metadata else: for key in nb_meta: if key in cache_nb.metadata: nb.metadata[key] = cache_nb.metadata[key] for idx in range(len(nb.cells)): if nb.cells[idx].cell_type == "code": cache_cell = cache_nb.cells.pop(0) in_cell = nb.cells[idx] if cell_meta is not None: # update the input metadata with select cached notebook metadata # then add the input metadata to the cached cell in_cell.metadata.update( {k: v for k, v in cache_cell.metadata.items() if k in cell_meta} ) cache_cell.metadata = in_cell.metadata if nb.nbformat_minor >= 5: cache_cell.id = in_cell.id else: cache_cell.pop("id", None) nb.cells[idx] = cache_cell return pk, nb def diff_nbnode_with_cache( self, pk: int, nb: nbf.NotebookNode, uri: str = "", as_str=False, **kwargs ): """Return a diff of a notebook to a cached one. Note: this will not diff markdown content, since it is not stored in the cache. """ try: import nbdime except ImportError: raise ImportError( "nbdime is required to diff notebooks, install with `pip install nbdime`" ) from nbdime.prettyprint import PrettyPrintConfig, pretty_print_diff cached_nb = self.get_cache_bundle(pk).nb nb, _ = self.create_hashed_notebook(nb) diff = nbdime.diff_notebooks(cached_nb, nb) if not as_str: return diff stream = io.StringIO() stream.writelines(["nbdiff\n", f"--- cached pk={pk}\n", f"+++ other: {uri}\n"]) pretty_print_diff( cached_nb, diff, "nb", PrettyPrintConfig(out=stream, **kwargs) ) return stream.getvalue() def add_nb_to_project( self, path: str, *, read_data: Mapping = DEFAULT_READ_DATA, assets: List[str] = (), ) -> NbProjectRecord: # check the reader can be loaded read_data = dict(read_data) _ = get_reader(read_data) # TODO should we test that the file can be read by the reader? return NbProjectRecord.create_record( str(Path(path).absolute()), self.db, raise_on_exists=False, read_data=read_data, assets=assets, ) # TODO physically copy to cache? # TODO assets def list_project_records( self, filter_uris: Optional[List[str]] = None, filter_pks: Optional[List[int]] = None, ) -> List[NbProjectRecord]: records = NbProjectRecord.records_all(self.db) if filter_uris is not None: records = [r for r in records if r.uri in filter_uris] if filter_pks is not None: records = [r for r in records if r.pk in filter_pks] return records def get_project_record(self, uri_or_pk: Union[int, str]) -> NbProjectRecord: if isinstance(uri_or_pk, int): record = NbProjectRecord.record_from_pk(uri_or_pk, self.db) else: record = NbProjectRecord.record_from_uri(uri_or_pk, self.db) return record def remove_nb_from_project(self, uri_or_pk: Union[int, str]): if isinstance(uri_or_pk, int): NbProjectRecord.remove_pks([uri_or_pk], self.db) else: NbProjectRecord.remove_uris([uri_or_pk], self.db) # TODO add discard all/multiple project records method def get_project_notebook(self, uri_or_pk: Union[int, str]) -> ProjectNb: if isinstance(uri_or_pk, int): record = NbProjectRecord.record_from_pk(uri_or_pk, self.db) else: record = NbProjectRecord.record_from_uri(uri_or_pk, self.db) if not Path(record.uri).exists(): raise OSError( f"The URI of the project record no longer exists: {record.uri}" ) try: reader = get_reader(record.read_data) notebook = reader(record.uri) assert isinstance( notebook, nbf.NotebookNode ), f"Reader did not return a v4 NotebookNode: {type(notebook)} {notebook}" except Exception as exc: raise NbReadError(f"Failed to read the notebook: {exc}") from exc return ProjectNb(record.pk, record.uri, notebook, record.assets) def get_cached_project_nb( self, uri_or_pk: Union[int, str] ) -> Optional[NbCacheRecord]: nb = self.get_project_notebook(uri_or_pk).nb _, hashkey = self.create_hashed_notebook(nb) try: return NbCacheRecord.record_from_hashkey(hashkey, self.db) except KeyError: return None def list_unexecuted( self, filter_uris: Optional[List[str]] = None, filter_pks: Optional[List[int]] = None, ) -> List[NbProjectRecord]: records = [] for record in self.list_project_records(filter_uris, filter_pks): nb = self.get_project_notebook(record.uri).nb _, hashkey = self.create_hashed_notebook(nb) try: NbCacheRecord.record_from_hashkey(hashkey, self.db) except KeyError: records.append(record) return records # removed until defined use case # def get_cache_codecell(self, pk: int, index: int) -> nbf.NotebookNode: # """Return a code cell from a cached notebook. # NOTE: the index **only** refers to the list of code cells, e.g. # `[codecell_0, textcell_1, codecell_2]` # would map {0: codecell_0, 1: codecell_2} # """ # nb_bundle = self.get_cache_bundle(pk) # _code_index = 0 # for cell in nb_bundle.nb.cells: # if cell.cell_type != "code": # continue # if _code_index == index: # return cell # _code_index += 1 # raise RetrievalError(f"Notebook contains less than {index+1} code cell(s)") jupyter-cache-1.0.0/jupyter_cache/cli/000077500000000000000000000000001452276675600176735ustar00rootroot00000000000000jupyter-cache-1.0.0/jupyter_cache/cli/__init__.py000066400000000000000000000024161452276675600220070ustar00rootroot00000000000000import os from pathlib import Path from typing import TYPE_CHECKING import click if TYPE_CHECKING: from jupyter_cache.base import JupyterCacheAbstract class CacheContext: """Context for retrieving the cache.""" def __init__(self, cache_path=None) -> None: if cache_path is None: self._cache_path = os.environ.get( "JUPYTERCACHE", os.path.join(os.getcwd(), ".jupyter_cache") ) else: self._cache_path = cache_path @property def cache_path(self) -> Path: return Path(self._cache_path) def get_cache(self, ask_on_missing=True) -> "JupyterCacheAbstract": """Get the cache.""" from jupyter_cache import get_cache if (not self.cache_path.exists()) and ask_on_missing: click.secho("Cache path: ", fg="green", nl=False) click.echo(str(self.cache_path)) if not click.confirm( "The cache does not yet exist, do you want to create it?" ): raise click.Abort() # gets created lazily return get_cache(self.cache_path) def set_cache_path(self, cache_path: str) -> None: self._cache_path = cache_path pass_cache = click.make_pass_decorator(CacheContext, ensure=True) jupyter-cache-1.0.0/jupyter_cache/cli/arguments.py000066400000000000000000000022441452276675600222540ustar00rootroot00000000000000import click NB_PATH = click.argument( "nbpath", metavar="NBPATH", type=click.Path(dir_okay=False, exists=True, readable=True, resolve_path=True), ) NB_PATHS = click.argument( "nbpaths", metavar="NBPATHS", nargs=-1, type=click.Path(dir_okay=False, exists=True, readable=True, resolve_path=True), ) ARTIFACT_PATHS = click.argument( "artifact_paths", metavar="ARTIFACT_PATHS", nargs=-1, type=click.Path(dir_okay=False, exists=True, readable=True, resolve_path=True), ) ARTIFACT_RPATH = click.argument("artifact_rpath", metavar="ARTIFACT_RPATH", type=str) ASSET_PATHS = click.argument( "asset_paths", metavar="ASSET_PATHS", nargs=-1, type=click.Path(dir_okay=False, exists=True, readable=True, resolve_path=True), ) OUTPUT_PATH = click.argument( "outpath", metavar="OUTPUT_PATH", type=click.Path(dir_okay=False, writable=True, resolve_path=True), ) PK = click.argument("pk", metavar="ID", type=int) PKS = click.argument("pks", metavar="IDs", nargs=-1, type=int) PK_OR_PATH = click.argument("pk_path", metavar="ID_OR_PATH", type=str) PK_OR_PATHS = click.argument("pk_paths", metavar="ID_OR_PATHS", nargs=-1) jupyter-cache-1.0.0/jupyter_cache/cli/commands/000077500000000000000000000000001452276675600214745ustar00rootroot00000000000000jupyter-cache-1.0.0/jupyter_cache/cli/commands/__init__.py000066400000000000000000000002661452276675600236110ustar00rootroot00000000000000"""The jupyter-cache CLI.""" from .cmd_cache import * # noqa: F401,F403,E402 from .cmd_notebook import * # noqa: F401,F403,E402 from .cmd_project import * # noqa: F401,F403,E402 jupyter-cache-1.0.0/jupyter_cache/cli/commands/cmd_cache.py000066400000000000000000000143451452276675600237430ustar00rootroot00000000000000import click from jupyter_cache.cli import arguments, options, pass_cache from jupyter_cache.cli.commands.cmd_main import jcache from jupyter_cache.utils import tabulate_cache_records @jcache.group("cache") @options.CACHE_PATH @pass_cache def cmnd_cache(cache, cache_path): """Work with cached execution(s) in a project.""" cache.set_cache_path(cache_path) @cmnd_cache.command("list") @click.option( "-l", "--latest-only", is_flag=True, help="Show only the most recent record per origin URI.", ) @click.option("-h", "--hashkeys", is_flag=True, help="Show the hashkey of notebook.") @options.PATH_LENGTH @pass_cache def list_caches(cache, latest_only, hashkeys, path_length): """List cached notebook records.""" db = cache.get_cache() records = db.list_cache_records() if not records: click.secho("No Cached Notebooks", fg="blue") # TODO optionally list number of artifacts if latest_only: latest_records = {} for record in records: if record.uri not in latest_records: latest_records[record.uri] = record continue if latest_records[record.uri].created < record.created: latest_records[record.uri] = record records = list(latest_records.values()) click.echo( tabulate_cache_records(records, hashkeys=hashkeys, path_length=path_length) ) @cmnd_cache.command("info") @arguments.PK @pass_cache def cached_info(cache, pk): """Show details of a cached notebook.""" import yaml db = cache.get_cache() try: record = db.get_cache_record(pk) except KeyError: click.secho(f"ID {pk} does not exist, Aborting!", fg="red") raise click.Abort() data = record.format_dict(hashkey=True, path_length=None) click.echo(yaml.safe_dump(data, sort_keys=False), nl=False) with db.cache_artefacts_temppath(pk) as folder: paths = [str(p.relative_to(folder)) for p in folder.glob("**/*") if p.is_file()] if not paths: click.echo("") return if paths: click.echo("Artifacts:") for path in paths: click.echo(f"- {path}") @cmnd_cache.command("cat-artefact") @arguments.PK @arguments.ARTIFACT_RPATH @pass_cache def cat_artifact(cache, pk, artifact_rpath): """Print the contents of a cached artefact.""" db = cache.get_cache() with db.cache_artefacts_temppath(pk) as path: artifact_path = path.joinpath(artifact_rpath) if not artifact_path.exists(): click.secho("Artifact does not exist", fg="red") raise click.Abort() if not artifact_path.is_file(): click.secho("Artifact is not a file", fg="red") raise click.Abort() text = artifact_path.read_text(encoding="utf8") click.echo(text) def cache_file(db, nbpath, validate, overwrite, artifact_paths=()): from jupyter_cache.base import NbValidityError click.echo(f"Caching: {nbpath}") try: db.cache_notebook_file( nbpath, artifacts=artifact_paths, check_validity=validate, overwrite=overwrite, ) except NbValidityError as error: click.secho("Validity Error: ", fg="red", nl=False) click.echo(str(error)) if click.confirm("The notebook may not have been executed, continue caching?"): try: db.cache_notebook_file( nbpath, artifacts=artifact_paths, check_validity=False, overwrite=overwrite, ) except OSError as error: click.secho("Artifact Error: ", fg="red", nl=False) click.echo(str(error)) return False except OSError as error: click.secho("Artifact Error: ", fg="red", nl=False) click.echo(str(error)) return False return True @cmnd_cache.command("add-with-artefacts") @arguments.ARTIFACT_PATHS @options.NB_PATH @options.VALIDATE_NB @options.OVERWRITE_CACHED @pass_cache def cache_nb(cache, artifact_paths, nbpath, validate, overwrite): """Cache a notebook, with possible artefact files.""" db = cache.get_cache() success = cache_file(db, nbpath, validate, overwrite, artifact_paths) if success: click.secho("Success!", fg="green") @cmnd_cache.command("add") @arguments.NB_PATHS @options.VALIDATE_NB @options.OVERWRITE_CACHED @pass_cache def cache_nbs(cache, nbpaths, validate, overwrite): """Cache notebook(s) that have already been executed.""" db = cache.get_cache() success = True for nbpath in nbpaths: # TODO deal with errors (print all at end? or option to ignore) if not cache_file(db, nbpath, validate, overwrite): success = False if success: click.secho("Success!", fg="green") @cmnd_cache.command("clear") @options.FORCE @pass_cache def clear_cache_cmd(cache, force): """Remove all executed notebooks from the cache.""" db = cache.get_cache() if not force: click.confirm( "Are you sure you want to permanently clear the cache!?", abort=True ) for record in db.list_cache_records(): db.remove_cache(record.pk) click.secho("Cache cleared!", fg="green") @cmnd_cache.command("remove") @arguments.PKS @options.REMOVE_ALL @pass_cache def remove_caches(cache, pks, remove_all): """Remove notebooks stored in the cache.""" from jupyter_cache.base import CachingError db = cache.get_cache() if remove_all: pks = [r.pk for r in db.list_cache_records()] for pk in pks: # TODO deal with errors (print all at end? or option to ignore) click.echo(f"Removing Cache ID = {pk}") try: db.remove_cache(pk) except KeyError: click.secho("Does not exist", fg="red") except CachingError as err: click.secho("Error: ", fg="red") click.echo(str(err)) click.secho("Success!", fg="green") @cmnd_cache.command("diff") @arguments.PK @arguments.NB_PATH @pass_cache def diff_nb(cache, pk, nbpath): """Print a diff of a notebook to one stored in the cache.""" db = cache.get_cache() click.echo(db.diff_nbfile_with_cache(pk, nbpath, as_str=True)) click.secho("Success!", fg="green") jupyter-cache-1.0.0/jupyter_cache/cli/commands/cmd_main.py000066400000000000000000000006101452276675600236120ustar00rootroot00000000000000"""The main `jcache` click group.""" import click from jupyter_cache.cli import options @click.group(context_settings={"help_option_names": ["-h", "--help"]}) @click.version_option( None, "-v", "--version", message="jupyter-cache version %(version)s" ) @options.PRINT_CACHE_PATH @options.AUTOCOMPLETE def jcache(*args, **kwargs): """The command line interface of jupyter-cache.""" jupyter-cache-1.0.0/jupyter_cache/cli/commands/cmd_notebook.py000066400000000000000000000145641452276675600245230ustar00rootroot00000000000000import logging import os import click import nbformat from jupyter_cache.cli import arguments, options, pass_cache, utils from jupyter_cache.cli.commands.cmd_main import jcache from jupyter_cache.readers import NbReadError from jupyter_cache.utils import tabulate_project_records logger = logging.getLogger(__name__) utils.setup_logger(logger) @jcache.group("notebook") @options.CACHE_PATH @pass_cache def cmnd_notebook(cache, cache_path): """Work with notebook(s) in a project.""" cache.set_cache_path(cache_path) @cmnd_notebook.command("add") @arguments.NB_PATHS @options.READER_KEY @pass_cache def add_notebooks(cache, nbpaths, reader): """Add notebook(s) to the project.""" db = cache.get_cache() for path in nbpaths: # TODO deal with errors (print all at end? or option to ignore) click.echo(f"Adding: {path}") db.add_nb_to_project(path, read_data={"name": reader, "type": "plugin"}) click.secho("Success!", fg="green") @cmnd_notebook.command("add-with-assets") @arguments.ASSET_PATHS @options.NB_PATH @options.READER_KEY @pass_cache def add_notebook(cache, nbpath, reader, asset_paths): """Add notebook(s) to the project, with possible asset files.""" db = cache.get_cache() db.add_nb_to_project( nbpath, read_data={"name": reader, "type": "plugin"}, assets=asset_paths ) click.secho("Success!", fg="green") @cmnd_notebook.command("clear") @options.FORCE @pass_cache def clear_nbs(cache, force): """Remove all notebooks from the project.""" db = cache.get_cache() if not force: click.confirm( "Are you sure you want to permanently clear the project!?", abort=True ) for record in db.list_project_records(): db.remove_nb_from_project(record.pk) click.secho("Project cleared!", fg="green") @cmnd_notebook.command("remove") @arguments.PK_OR_PATHS @pass_cache def remove_nbs(cache, pk_paths): """Remove notebook(s) from the project (by ID/URI).""" db = cache.get_cache() for pk_path in pk_paths: # TODO deal with errors (print all at end? or option to ignore) click.echo(f"Removing: {pk_path}") db.remove_nb_from_project( int(pk_path) if pk_path.isdigit() else os.path.abspath(pk_path) ) click.secho("Success!", fg="green") @cmnd_notebook.command("invalidate") @arguments.PK_OR_PATHS @options.INVALIDATE_ALL @pass_cache def invalidate_nbs(cache, pk_paths, invalidate_all): """Remove any matching cache of the notebook(s) (by ID/URI).""" db = cache.get_cache() if invalidate_all: pk_paths = [str(record.pk) for record in db.list_project_records()] for pk_path in pk_paths: # TODO deal with errors (print all at end? or option to ignore) click.echo(f"Invalidating: {pk_path}") record = db.get_cached_project_nb( int(pk_path) if pk_path.isdigit() else os.path.abspath(pk_path) ) if record is not None: db.remove_cache(record.pk) click.secho("Success!", fg="green") @cmnd_notebook.command("list") # @click.option( # "--compare/--no-compare", # default=True, # show_default=True, # help="Compare to cached notebooks (to find cache ID).", # ) @options.PATH_LENGTH @click.option( "--assets", is_flag=True, help="Show the number of assets associated with each notebook", ) @pass_cache def list_nbs_in_project(cache, path_length, assets): """List notebooks in the project.""" db = cache.get_cache() records = db.list_project_records() if not records: click.secho("No notebooks in project", fg="blue") click.echo( tabulate_project_records( records, path_length=path_length, cache=db, assets=assets ) ) @cmnd_notebook.command("info") @arguments.PK_OR_PATH @click.option( "--tb/--no-tb", default=True, show_default=True, help="Show traceback, if last execution failed.", ) @pass_cache def show_project_record(cache, pk_path, tb): """Show details of a notebook (by ID).""" import yaml db = cache.get_cache() try: record = db.get_project_record( int(pk_path) if pk_path.isdigit() else os.path.abspath(pk_path) ) except KeyError: click.secho(f"ID {pk_path} does not exist, Aborting!", fg="red") raise click.Abort() cache_record = None try: cache_record = db.get_cached_project_nb(record.uri) except NbReadError as exc: click.secho(f"File could not be read: {exc}", fg="red") data = record.format_dict( cache_record=cache_record, path_length=None, assets=False, read_name=False ) click.echo(yaml.safe_dump(data, sort_keys=False, allow_unicode=True).rstrip()) if record.assets: click.echo("Assets:") for path in record.assets: click.echo(f"- {path}") if record.traceback: click.secho("Failed Last Execution!", fg="red") if tb: click.echo(record.traceback) @cmnd_notebook.command("merge") @arguments.PK_OR_PATH @arguments.OUTPUT_PATH @pass_cache def merge_executed(cache, pk_path, outpath): """Create notebook merged with cached outputs (by ID/URI).""" db = cache.get_cache() nb = db.get_project_notebook( int(pk_path) if pk_path.isdigit() else os.path.abspath(pk_path) ).nb cached_pk, nb = db.merge_match_into_notebook(nb) nbformat.write(nb, outpath) click.echo(f"Merged with cache PK {cached_pk}") click.secho("Success!", fg="green") @cmnd_notebook.command("execute") @arguments.PK_OR_PATHS @options.EXECUTOR_KEY @options.EXEC_TIMEOUT @options.EXEC_FORCE(default=True) @options.set_log_level(logger) @pass_cache def execute_nbs(cache, pk_paths, executor, timeout, force): """Execute specific notebooks in the project.""" import yaml from jupyter_cache.executors import load_executor uris = [os.path.abspath(p) for p in pk_paths if not p.isdigit()] or None pks = [int(p) for p in pk_paths if p.isdigit()] or None db = cache.get_cache() try: executor = load_executor(executor, db, logger=logger) except ImportError as error: logger.error(str(error)) return 1 result = executor.run_and_cache( filter_pks=pks, filter_uris=uris, timeout=timeout, force=force ) click.secho( "Finished! Successfully executed notebooks have been cached.", fg="green" ) click.echo(yaml.safe_dump(result.as_json(), sort_keys=False)) jupyter-cache-1.0.0/jupyter_cache/cli/commands/cmd_project.py000066400000000000000000000047531452276675600243500ustar00rootroot00000000000000import logging import click from jupyter_cache.cli import options, pass_cache, utils from jupyter_cache.cli.commands.cmd_main import jcache logger = logging.getLogger(__name__) utils.setup_logger(logger) @jcache.group("project") @options.CACHE_PATH @pass_cache def cmnd_project(cache, cache_path): """Work with a project.""" cache.set_cache_path(cache_path) @cmnd_project.command("version") @pass_cache def version(cache): """Print the version of the cache.""" if not cache.cache_path.exists(): click.secho("No cache found.", fg="red") raise click.Abort() version = cache.get_cache().get_version() if version is None: click.secho("Cache version not found", fg="red") raise click.Abort() click.echo(version) @cmnd_project.command("clear") @options.FORCE @pass_cache def clear_cache(cache, force): """Clear the project cache completely.""" if not cache.cache_path.exists(): click.secho("Cache does not exist", fg="green") raise click.Abort() if not force: click.echo(f"Cache path: {cache.cache_path}") click.confirm( "Are you sure you want to permanently clear the cache!?", abort=True, ) cache.get_cache().clear_cache() click.secho("Cache cleared!", fg="green") @cmnd_project.command("cache-limit") @click.argument("limit", metavar="CACHE_LIMIT", type=int, required=False) @pass_cache def change_cache_limit(cache, limit): """Get/set maximum number of notebooks stored in the cache.""" db = cache.get_cache() if limit is None: limit = db.get_cache_limit() click.echo(f"Current cache limit: {limit}") else: db.change_cache_limit(limit) click.secho("Cache limit changed!", fg="green") @cmnd_project.command("execute") @options.EXECUTOR_KEY @options.EXEC_TIMEOUT @options.EXEC_FORCE(default=False) @options.set_log_level(logger) @pass_cache def execute_nbs(cache, executor, timeout, force): """Execute all outdated notebooks in the project.""" import yaml from jupyter_cache.executors import load_executor db = cache.get_cache() try: executor = load_executor(executor, db, logger=logger) except ImportError as error: logger.error(str(error)) return 1 result = executor.run_and_cache(timeout=timeout, force=force) click.secho( "Finished! Successfully executed notebooks have been cached.", fg="green" ) click.echo(yaml.safe_dump(result.as_json(), sort_keys=False)) jupyter-cache-1.0.0/jupyter_cache/cli/options.py000066400000000000000000000103151452276675600217400ustar00rootroot00000000000000import logging import os import click from jupyter_cache.entry_points import ENTRY_POINT_GROUP_EXEC, list_group_names from jupyter_cache.readers import list_readers def callback_autocomplete(ctx, param, value): if value and not ctx.resilient_parsing: click.echo("Execute this in the terminal for auto-completion:") click.echo('eval "$(_JCACHE_COMPLETE=source jcache)"') ctx.exit() AUTOCOMPLETE = click.option( "-a", "--autocomplete", help="Print the autocompletion command and exit.", is_flag=True, expose_value=True, is_eager=True, callback=callback_autocomplete, ) def default_cache_path(): return os.environ.get("JUPYTERCACHE", os.path.join(os.getcwd(), ".jupyter_cache")) def callback_print_cache_path(ctx, param, value): if value and not ctx.resilient_parsing: click.secho("Cache path: ", fg="green", nl=False) click.echo(default_cache_path()) ctx.exit() PRINT_CACHE_PATH = click.option( "-p", "--print-path", help="Print the current cache path and exit.", is_flag=True, expose_value=True, is_eager=True, callback=callback_print_cache_path, ) CACHE_PATH = click.option( "-p", "--cache-path", help="Path to project cache.", default=default_cache_path, show_default=".jupyter_cache", ) NB_PATH = click.option( "-nb", "--nbpath", required=True, help="The notebooks path.", type=click.Path(dir_okay=False, exists=True, readable=True, resolve_path=True), ) READER_KEY = click.option( "-r", "--reader", help="The notebook reader to use.", default="nbformat", type=click.Choice(list_readers()), show_default=True, ) EXECUTOR_KEY = click.option( "-e", "--executor", help="The executor to use.", default="local-serial", type=click.Choice(list_group_names(ENTRY_POINT_GROUP_EXEC)), show_default=True, ) EXEC_TIMEOUT = click.option( "-t", "--timeout", help="Execution timeout value in seconds.", default=30, show_default=True, ) def EXEC_FORCE(default=False): return click.option( "-f", "--force/--no-force", help="Execute a notebook even if it is cached.", is_flag=True, default=default, show_default=True, ) PATH_LENGTH = click.option( "-l", "--path-length", default=3, show_default=True, help="Maximum URI path." ) VALIDATE_NB = click.option( "--validate/--no-validate", default=True, show_default=True, help="Whether to validate that a notebook has been executed.", ) OVERWRITE_CACHED = click.option( "--overwrite/--no-overwrite", default=True, show_default=True, help="Whether to overwrite an existing notebook with the same hash.", ) FORCE = click.option( "-f", "--force", default=False, is_flag=True, help="Do not ask for confirmation." ) def confirm_remove_all(ctx, param, remove_all): if remove_all and not click.confirm("Are you sure you want to remove all?"): click.secho("Aborted!", bold=True, fg="red") ctx.exit() return remove_all REMOVE_ALL = click.option( "-a", "--all", "remove_all", is_flag=True, help="Remove all notebooks.", callback=confirm_remove_all, ) def confirm_invalidate_all(ctx, param, remove_all): if remove_all and not click.confirm("Are you sure you want to invalidate all?"): click.secho("Aborted!", bold=True, fg="red") ctx.exit() return remove_all INVALIDATE_ALL = click.option( "-a", "--all", "invalidate_all", is_flag=True, help="Invalidate all notebooks.", callback=confirm_invalidate_all, ) def set_log_level(logger): """Set the log level of the logger.""" def _callback(ctx, param, value): """Set logging level.""" level = getattr(logging, value.upper(), None) if level is None: raise click.BadParameter(f"Unknown log level: {value.upper()}") logger.setLevel(level) return click.option( "-v", "--verbosity", type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]), default="INFO", show_default=True, expose_value=False, callback=_callback, help="Set logging verbosity.", ) jupyter-cache-1.0.0/jupyter_cache/cli/utils.py000066400000000000000000000010221452276675600214000ustar00rootroot00000000000000import logging import click class ClickLogHandler(logging.Handler): _use_stderr = True def emit(self, record): try: msg = self.format(record) click.echo(msg, err=self._use_stderr) except Exception: self.handleError(record) def setup_logger(logger: logging.Logger) -> None: """Add handler to log to click.""" try: import click_log except ImportError: logger.addHandler(ClickLogHandler()) else: click_log.basic_config(logger) jupyter-cache-1.0.0/jupyter_cache/entry_points.py000066400000000000000000000026721452276675600222420ustar00rootroot00000000000000"""Module for dealing with entry points.""" from typing import Optional, Set # TODO importlib.metadata was introduced into the standard library in python 3.8 # so we can change this when we drop support for 3.7 # also, from importlib_metadata changed its API in v4.0, to use the python 3.10 API # however, because of https://github.com/python/importlib_metadata/issues/308 # we do not assume that we have this API, and instead use try/except for the new/old APIs from importlib_metadata import EntryPoint from importlib_metadata import entry_points as eps ENTRY_POINT_GROUP_READER = "jcache.readers" ENTRY_POINT_GROUP_EXEC = "jcache.executors" def list_group_names(group: str) -> Set[str]: """Return the entry points within a group.""" all_eps = eps() try: # importlib_metadata v4 / python 3.10 return all_eps.select(group=group).names except (AttributeError, TypeError): return {ep.name for ep in all_eps.get(group, [])} def get_entry_point(group: str, name: str) -> Optional[EntryPoint]: """Return the entry point with the given name in the given group.""" all_eps = eps() try: # importlib_metadata v4 / python 3.10 found = all_eps.select(group=group, name=name) ep = found[name] if name in found.names else None except (AttributeError, TypeError): found = {ep.name: ep for ep in all_eps.get(group, [])} ep = found[name] if name in found else None return ep jupyter-cache-1.0.0/jupyter_cache/executors/000077500000000000000000000000001452276675600211455ustar00rootroot00000000000000jupyter-cache-1.0.0/jupyter_cache/executors/__init__.py000066400000000000000000000001271452276675600232560ustar00rootroot00000000000000from .base import JupyterExecutorAbstract, list_executors, load_executor # noqa: F401 jupyter-cache-1.0.0/jupyter_cache/executors/base.py000066400000000000000000000076111452276675600224360ustar00rootroot00000000000000from abc import ABC, abstractmethod import logging from typing import Any, Dict, List, Optional, Set import attr from jupyter_cache.base import JupyterCacheAbstract from jupyter_cache.cache.db import NbProjectRecord from jupyter_cache.entry_points import ( ENTRY_POINT_GROUP_EXEC, get_entry_point, list_group_names, ) base_logger = logging.getLogger(__name__) class ExecutionError(Exception): pass @attr.s(slots=True) class ExecutorRunResult: """A container for the execution result.""" # URIs of notebooks which where successfully executed succeeded: List[str] = attr.ib(factory=list) # URIs of notebooks which excepted during execution excepted: List[str] = attr.ib(factory=list) # URIs of notebooks which errored before execution errored: List[str] = attr.ib(factory=list) def all(self) -> List[str]: """Return all notebooks.""" return self.succeeded + self.excepted + self.errored def as_json(self) -> Dict[str, Any]: """Return the result as a JSON serializable dict.""" return { "succeeded": self.succeeded, "excepted": self.excepted, "errored": self.errored, } class JupyterExecutorAbstract(ABC): """An abstract class for executing notebooks in a cache.""" def __init__(self, cache: JupyterCacheAbstract, logger=None): self._cache = cache self._logger = logger or logging.getLogger(__name__) def __repr__(self): return f"{self.__class__.__name__}(cache={self._cache})" @property def cache(self): return self._cache @property def logger(self): return self._logger def get_records( self, filter_uris: Optional[List[str]] = None, filter_pks: Optional[List[int]] = None, clear_tracebacks: bool = True, force: bool = False, ) -> List[NbProjectRecord]: """Return records to execute. :param clear_tracebacks: Remove any tracebacks from previous executions """ if force: execute_records = self.cache.list_project_records(filter_uris, filter_pks) else: execute_records = self.cache.list_unexecuted(filter_uris, filter_pks) if clear_tracebacks: NbProjectRecord.remove_tracebacks( [r.pk for r in execute_records], self.cache.db ) return execute_records @abstractmethod def run_and_cache( self, *, filter_uris: Optional[List[str]] = None, filter_pks: Optional[List[int]] = None, timeout: Optional[int] = 30, allow_errors: bool = False, force: bool = False, **kwargs: Any, ) -> ExecutorRunResult: """Run execution, cache successfully executed notebooks and return their URIs :param filter_uris: Filter the notebooks in the project to execute by these URIs :param filter_pks: Filter the notebooks in the project to execute by these PKs :param timeout: Maximum time in seconds to wait for a single cell to run for :param allow_errors: Whether to halt execution on the first cell exception (provided the cell is not tagged as an expected exception) :param force: Whether to force execution of all notebooks, even if they are cached :param kwargs: Additional keyword arguments to pass to the executor """ def list_executors() -> Set[str]: return list_group_names(ENTRY_POINT_GROUP_EXEC) def load_executor( entry_point: str, cache: JupyterCacheAbstract, logger=None ) -> JupyterExecutorAbstract: """Retrieve an initialised JupyterExecutor from an entry point.""" ep = get_entry_point(ENTRY_POINT_GROUP_EXEC, entry_point) if ep is None: raise ImportError( f"Entry point not found: {ENTRY_POINT_GROUP_EXEC}:{entry_point}" ) execute_cls = ep.load() return execute_cls(cache=cache, logger=logger) jupyter-cache-1.0.0/jupyter_cache/executors/basic.py000066400000000000000000000202531452276675600226020ustar00rootroot00000000000000import logging import multiprocessing as mproc import os from pathlib import Path import tempfile from typing import NamedTuple, Tuple from jupyter_cache.base import JupyterCacheAbstract, ProjectNb from jupyter_cache.cache.db import NbProjectRecord from jupyter_cache.executors.base import ExecutorRunResult, JupyterExecutorAbstract from jupyter_cache.executors.utils import ( ExecutionResult, copy_assets, create_cache_bundle, single_nb_execution, ) REPORT_LEVEL = logging.INFO + 1 logging.addLevelName(REPORT_LEVEL, "REPORT") class ProcessData(NamedTuple): """Data for the process worker.""" pk: int uri: str cache: JupyterCacheAbstract timeout: int allow_errors: bool class ExecutionWorkerBase: """Base execution worker. Note this must be pickleable. """ @property def logger(self) -> logging.Logger: raise NotImplementedError def log_info(self, msg: str): self.logger.info(msg) def execute(self, project_nb: ProjectNb, data: ProcessData) -> ExecutionResult: raise NotImplementedError def __call__(self, data: ProcessData) -> Tuple[int, str]: try: project_nb = data.cache.get_project_notebook(data.pk) except Exception: self.logger.error( "Failed Retrieving: %s" % data.uri, exc_info=True, ) return (2, data.uri) try: self.log_info("Executing: %s" % project_nb.uri) result = self.execute(project_nb, data) except Exception: self.logger.error( "Failed Executing: %s" % data.uri, exc_info=True, ) return (2, data.uri) if result.err: self.logger.warning( "Execution Excepted: %s\n%s: %s" % (project_nb.uri, type(result.err).__name__, str(result.err)) ) NbProjectRecord.set_traceback( project_nb.uri, result.exc_string, data.cache.db ) return (1, data.uri) self.log_info("Execution Successful: %s" % project_nb.uri) try: # TODO deal with artifact retrieval bundle = create_cache_bundle( project_nb, result.cwd, None, result.time, result.exc_string ) data.cache.cache_notebook_bundle( bundle, check_validity=False, overwrite=True ) except Exception: self.logger.error( "Failed Caching: %s" % data.uri, exc_info=True, ) return (2, data.uri) return (0, data.uri) class ExecutionWorkerLocalSerial(ExecutionWorkerBase): """Execution worker, that executes in local folder.""" def __init__(self, logger: logging.Logger) -> None: super().__init__() self._logger = logger @property def logger(self) -> logging.Logger: return self._logger @staticmethod def execute(project_nb: ProjectNb, data: ProcessData) -> ExecutionResult: cwd = str(Path(project_nb.uri).parent) return single_nb_execution( project_nb.nb, cwd=cwd, timeout=data.timeout, allow_errors=data.allow_errors, ) class ExecutionWorkerTempSerial(ExecutionWorkerBase): """Execution worker, that executes in temporary folder.""" def __init__(self, logger: logging.Logger) -> None: super().__init__() self._logger = logger @property def logger(self) -> logging.Logger: return self._logger @staticmethod def execute(project_nb: ProjectNb, data: ProcessData) -> ExecutionResult: with tempfile.TemporaryDirectory() as cwd: copy_assets(project_nb.uri, project_nb.assets, cwd) return single_nb_execution( project_nb.nb, cwd=cwd, timeout=data.timeout, allow_errors=data.allow_errors, ) class ExecutionWorkerLocalMProc(ExecutionWorkerBase): """Execution worker, that executes in local folder.""" @property def logger(self) -> logging.Logger: return mproc.get_logger() def log_info(self, msg: str): # multiprocessing logs a lot at info level that we do not want to see self.logger.log(REPORT_LEVEL, msg) @staticmethod def execute(project_nb: ProjectNb, data: ProcessData) -> ExecutionResult: cwd = str(Path(project_nb.uri).parent) return single_nb_execution( project_nb.nb, cwd=cwd, timeout=data.timeout, allow_errors=data.allow_errors, ) class ExecutionWorkerTempMProc(ExecutionWorkerBase): """Execution worker, that executes in temporary folder.""" @property def logger(self) -> logging.Logger: return mproc.get_logger() def log_info(self, msg: str): # multiprocessing logs a lot at info level that we do not want to see self.logger.log(REPORT_LEVEL, msg) @staticmethod def execute(project_nb: ProjectNb, data: ProcessData) -> ExecutionResult: with tempfile.TemporaryDirectory() as cwd: copy_assets(project_nb.uri, project_nb.assets, cwd) return single_nb_execution( project_nb.nb, cwd=cwd, timeout=data.timeout, allow_errors=data.allow_errors, ) class JupyterExecutorLocalSerial(JupyterExecutorAbstract): """An implementation of an executor; executing locally in serial.""" _EXECUTION_WORKER = ExecutionWorkerLocalSerial def run_and_cache( self, *, filter_uris=None, filter_pks=None, timeout=30, allow_errors=False, force=False, ) -> ExecutorRunResult: # Get the notebook that require re-execution execute_records = self.get_records( filter_uris, filter_pks, clear_tracebacks=True, force=force ) self.logger.info("Executing %s notebook(s) in serial" % len(execute_records)) results = [ self._EXECUTION_WORKER(self.logger)( ProcessData(record.pk, record.uri, self.cache, timeout, allow_errors) ) for record in execute_records ] return ExecutorRunResult( succeeded=[p for i, p in results if i == 0], excepted=[p for i, p in results if i == 1], errored=[p for i, p in results if i == 2], ) class JupyterExecutorTempSerial(JupyterExecutorLocalSerial): """An implementation of an executor; executing in a temporary folder in serial.""" _EXECUTION_WORKER = ExecutionWorkerTempSerial class JupyterExecutorLocalMproc(JupyterExecutorAbstract): """An implementation of an executor; executing locally in parallel.""" _EXECUTION_WORKER = ExecutionWorkerLocalMProc def run_and_cache( self, *, filter_uris=None, filter_pks=None, timeout=30, allow_errors=False, force=False, ) -> ExecutorRunResult: # Get the notebook that require re-execution execute_records = self.get_records( filter_uris, filter_pks, clear_tracebacks=True ) self.logger.info( "Executing %s notebook(s) over pool of %s processors" % (len(execute_records), os.cpu_count()) ) mproc.log_to_stderr( REPORT_LEVEL if self.logger.level == logging.INFO else self.logger.level ) with mproc.Pool() as pool: results = pool.map( self._EXECUTION_WORKER(), [ ProcessData( record.pk, record.uri, self.cache, timeout, allow_errors ) for record in execute_records ], ) return ExecutorRunResult( succeeded=[p for i, p in results if i == 0], excepted=[p for i, p in results if i == 1], errored=[p for i, p in results if i == 2], ) class JupyterExecutorTempMproc(JupyterExecutorLocalMproc): """An implementation of an executor; executing in a temporary directory and in parallel.""" _EXECUTION_WORKER = ExecutionWorkerTempMProc jupyter-cache-1.0.0/jupyter_cache/executors/utils.py000066400000000000000000000071011452276675600226560ustar00rootroot00000000000000from pathlib import Path import shutil import traceback from typing import Any, List, Optional, Union import attr from nbclient import execute as executenb from nbclient.client import CellExecutionError, CellTimeoutError from nbformat import NotebookNode from jupyter_cache.base import CacheBundleIn, ProjectNb from jupyter_cache.cache.main import NbArtifacts from jupyter_cache.utils import Timer, to_relative_paths @attr.s() class ExecutionResult: nb: NotebookNode = attr.ib() cwd: str = attr.ib() time: float = attr.ib() err: Optional[Union[CellExecutionError, CellTimeoutError]] = attr.ib(default=None) exc_string: Optional[str] = attr.ib(default=None) def single_nb_execution( nb: NotebookNode, cwd: Optional[str], timeout: Optional[int], allow_errors: bool, meta_override: bool = True, record_timing: bool = False, **kwargs: Any, ) -> ExecutionResult: """Execute notebook in place. :param cwd: If supplied, the kernel will run in this directory. :param timeout: The time to wait (in seconds) for output from executions. If a cell execution takes longer, a ``TimeoutError`` is raised. :param allow_errors: If ``False`` when a cell raises an error the execution is stopped and a ``CellExecutionError`` is raised. :param meta_override: If ``True`` then timeout and allow_errors may be overridden by equivalent keys in nb.metadata.execution :param kwargs: Additional keyword arguments to pass to the ``NotebookClient``. :returns: The execution time in seconds """ if meta_override and "execution" in nb.metadata: if "timeout" in nb.metadata.execution: timeout = nb.metadata.execution.timeout if "allow_errors" in nb.metadata.execution: allow_errors = nb.metadata.execution.allow_errors error = exc_string = None # TODO nbclient with record_timing=True will add execution data to each cell timer = Timer() with timer: try: executenb( nb, cwd=cwd, timeout=timeout, allow_errors=allow_errors, record_timing=record_timing, **kwargs, ) except (CellExecutionError, CellTimeoutError) as err: error = err exc_string = "".join(traceback.format_exc()) return ExecutionResult(nb, cwd, timer.last_split, error, exc_string) def copy_assets(uri: str, assets: List[str], folder: str) -> List[Path]: """Copy notebook assets to the folder the notebook will be executed in.""" asset_files = [] relative_paths = to_relative_paths(assets, Path(uri).parent) for path, rel_path in zip(assets, relative_paths): temp_file = Path(folder).joinpath(rel_path) temp_file.parent.mkdir(parents=True, exist_ok=True) shutil.copyfile(path, temp_file) asset_files.append(temp_file) return asset_files def create_cache_bundle( project_nb: ProjectNb, execdir: Optional[str], asset_files: Optional[List[Path]], exec_time: float, exec_tb: Optional[str], ) -> CacheBundleIn: """Create a cache bundle to save.""" return CacheBundleIn( project_nb.nb, project_nb.uri, # TODO retrieve assets that have changed file mtime? artifacts=NbArtifacts( [p for p in Path(execdir).glob("**/*") if p not in asset_files], execdir, ) if (execdir is not None and asset_files is not None) else None, data={"execution_seconds": exec_time}, traceback=exec_tb, ) jupyter-cache-1.0.0/jupyter_cache/readers.py000066400000000000000000000024061452276675600211250ustar00rootroot00000000000000"""Module for handling different functions to read "notebook-like" files.""" from typing import Any, Callable, Dict, Set import nbformat as nbf from .entry_points import ENTRY_POINT_GROUP_READER, get_entry_point, list_group_names DEFAULT_READ_DATA = (("name", "nbformat"), ("type", "plugin")) def nbf_reader(uri: str) -> nbf.NotebookNode: """Standard notebook reader.""" return nbf.read(uri, nbf.NO_CONVERT) def jupytext_reader(uri: str) -> nbf.NotebookNode: """Jupytext notebook reader.""" try: import jupytext except ImportError: raise ImportError("jupytext must be installed to use this reader") return jupytext.read(uri) def list_readers() -> Set[str]: """List all available readers.""" return list_group_names(ENTRY_POINT_GROUP_READER) def get_reader(data: Dict[str, Any]) -> Callable[[str], nbf.NotebookNode]: """Returns a function to read a file URI and return a notebook.""" if data.get("type") == "plugin": key = data.get("name", "") reader = get_entry_point(ENTRY_POINT_GROUP_READER, key) if reader is not None: return reader.load() raise ValueError(f"No reader found for: {data!r}") class NbReadError(IOError): """Error raised when a notebook cannot be read.""" jupyter-cache-1.0.0/jupyter_cache/utils.py000066400000000000000000000076241452276675600206470ustar00rootroot00000000000000"""Non-core imports in this module are lazily loaded, in order to improve CLI speed """ from pathlib import Path import time from typing import TYPE_CHECKING, List, Optional, Union from jupyter_cache.readers import NbReadError if TYPE_CHECKING: from jupyter_cache.base import JupyterCacheAbstract from jupyter_cache.cache.db import NbCacheRecord, NbProjectRecord def to_relative_paths( paths: List[Union[str, Path]], folder: Union[str, Path], check_existence: bool = False, ) -> List[Path]: """Make paths relative to a reference folder. :param paths: list of paths :param folder: The folder that all paths should be in (or subfolder). :param check_existence: check the paths exist :raises IOError: path is not relative or failed existence check """ rel_paths = [] folder = Path(folder).absolute() for path in paths: path = Path(path).absolute() if check_existence and not path.exists(): raise OSError(f"Path does not exist: {path}") if check_existence and not path.is_file(): raise OSError(f"Path is not a file: {path}") try: rel_path = path.relative_to(folder) except ValueError: raise OSError(f"Path '{path}' is not in folder '{folder}''") rel_paths.append(rel_path) return rel_paths class Timer: """Context manager for timing runtime.""" def __init__(self): self._last_time = time.perf_counter() self._split_time = 0.0 @property def last_split(self): return self._split_time def reset(self): """Reset timer.""" self._last_time = time.perf_counter() self._split_time = 0.0 def split(self): """Record a split time.""" self._split_time = time.perf_counter() - self._last_time def __enter__(self): """Reset timer.""" self.reset() return self def __exit__(self, *exc_info): """Record a split time.""" self.split() def shorten_path(file_path: Union[str, Path], length: Optional[int]) -> Path: """Split the path into separate parts, select the last 'length' elements and join them again """ if length is None: return Path(file_path) return Path(*Path(file_path).parts[-length:]) def tabulate_cache_records( records: List["NbCacheRecord"], hashkeys=False, path_length=None ) -> str: """Tabulate cache records. :param records: list of ``NbCacheRecord`` :param hashkeys: include a hashkey column :param path_length: truncate URI paths to x components """ import tabulate return tabulate.tabulate( [ r.format_dict(hashkey=hashkeys, path_length=path_length, show_data=False) for r in sorted(records, key=lambda r: r.accessed, reverse=True) ], headers="keys", ) def tabulate_project_records( records: List["NbProjectRecord"], path_length: Optional[int] = None, cache: Optional["JupyterCacheAbstract"] = None, assets=False, ) -> str: """Tabulate cache records. :param records: list of ``NbProjectRecord`` :param path_length: truncate URI paths to x components :param cache: If the cache is given, we use it to add a column of matched cached pk (if available) :param assets: Show the number of assets """ import tabulate rows = [] for record in records: cache_record = None read_error = None if cache is not None: try: cache_record = cache.get_cached_project_nb(record.uri) except NbReadError as exc: read_error = f"{exc.__class__.__name__}: {exc}" rows.append( record.format_dict( cache_record=cache_record, path_length=path_length, assets=assets, read_error=read_error, ) ) return tabulate.tabulate(rows, headers="keys") jupyter-cache-1.0.0/pyproject.toml000066400000000000000000000053141452276675600172160ustar00rootroot00000000000000[build-system] requires = ["flit_core >=3.4,<4"] build-backend = "flit_core.buildapi" [project] name = "jupyter-cache" dynamic = ["version"] description = "A defined interface for working with a cache of jupyter notebooks." authors = [{name = "Chris Sewell", email = "chrisj_sewell@hotmail.com"}] readme = "README.md" license = {file = "LICENSE"} classifiers = [ "Development Status :: 4 - Beta", "Framework :: Sphinx :: Extension", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Software Development :: Libraries :: Python Modules", ] keywords = ["sphinx extension material design web components"] requires-python = ">=3.9" dependencies = [ "attrs", "click", "importlib-metadata", "nbclient>=0.2", "nbformat", "pyyaml", "sqlalchemy>=1.3.12,<3", "tabulate", ] [project.urls] Homepage = "https://github.com/executablebooks/jupyter-cache" Documentation = "https://jupyter-cache.readthedocs.io" [project.entry-points."jcache.executors"] local-serial = "jupyter_cache.executors.basic:JupyterExecutorLocalSerial" temp-serial = "jupyter_cache.executors.basic:JupyterExecutorTempSerial" local-parallel = "jupyter_cache.executors.basic:JupyterExecutorLocalMproc" temp-parallel = "jupyter_cache.executors.basic:JupyterExecutorTempMproc" [project.entry-points."jcache.readers"] nbformat = "jupyter_cache.readers:nbf_reader" jupytext = "jupyter_cache.readers:jupytext_reader" [project.optional-dependencies] cli = ["click-log"] code_style = ["pre-commit>=2.12"] rtd = [ "nbdime", "ipykernel", "jupytext", # TODO: MyST-NB pins jupyter-cache, so this may create unresolved environments # But if we pin to `main` then we cannot build wheels. Need to fix. "myst-nb", "sphinx-book-theme", "sphinx-copybutton", ] testing = [ "nbdime", "coverage", "ipykernel", "jupytext", "matplotlib", "nbformat>=5.1", "numpy", "pandas", "pytest>=6,<8", "pytest-cov", "pytest-regressions", "sympy", ] [project.scripts] jcache = "jupyter_cache.cli.commands.cmd_main:jcache" [tool.flit.module] name = "jupyter_cache" [tool.flit.sdist] exclude = [ "docs/", "tests/", ] [tool.isort] profile = "black" src_paths = ["jupyter_cache", "tests"] force_sort_within_sections = true [tool.mypy] show_error_codes = true warn_unused_ignores = true warn_redundant_casts = true no_implicit_optional = true strict_equality = true jupyter-cache-1.0.0/tests/000077500000000000000000000000001452276675600154415ustar00rootroot00000000000000jupyter-cache-1.0.0/tests/notebooks/000077500000000000000000000000001452276675600174445ustar00rootroot00000000000000jupyter-cache-1.0.0/tests/notebooks/artifact_folder/000077500000000000000000000000001452276675600225745ustar00rootroot00000000000000jupyter-cache-1.0.0/tests/notebooks/artifact_folder/artifact.txt000066400000000000000000000000141452276675600251250ustar00rootroot00000000000000An artifact jupyter-cache-1.0.0/tests/notebooks/basic.ipynb000066400000000000000000000017361452276675600215770ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# a title\n", "\n", "some text\n" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "source": [ "a=1\n", "print(a)" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n" ] } ] } ], "metadata": { "test_name": "notebook1", "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.6.1" } }, "nbformat": 4, "nbformat_minor": 2 } jupyter-cache-1.0.0/tests/notebooks/basic.md000066400000000000000000000004201452276675600210430ustar00rootroot00000000000000--- jupytext: text_representation: extension: .md format_name: myst format_version: 0.13 jupytext_version: 1.11.3 kernelspec: display_name: Python 3 language: python name: python3 --- # a title some text ```{code-cell} ipython3 a=1 print(a) ``` jupyter-cache-1.0.0/tests/notebooks/basic_failing.ipynb000066400000000000000000000015261452276675600232650ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# a title\n", "\n", "some text\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "source": [ "raise Exception('oopsie!')" ], "outputs": [] } ], "metadata": { "test_name": "notebook1", "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.6.1" } }, "nbformat": 4, "nbformat_minor": 2 } jupyter-cache-1.0.0/tests/notebooks/basic_unrun.ipynb000066400000000000000000000015251452276675600230220ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# a title\n", "\n", "some text\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "source": [ "a=1\n", "print(a)" ], "outputs": [] } ], "metadata": { "test_name": "notebook1", "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.6.1" } }, "nbformat": 4, "nbformat_minor": 2 } jupyter-cache-1.0.0/tests/notebooks/basic_v4-5.ipynb000066400000000000000000000020201452276675600223350ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "id": "cell-id-0", "metadata": {}, "source": [ "# a title\n", "\n", "some text\n" ] }, { "cell_type": "code", "execution_count": 2, "id": "cell-id-1", "metadata": {}, "source": [ "a=1\n", "print(a)" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n" ] } ] } ], "metadata": { "test_name": "notebook1", "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.6.1" } }, "nbformat": 4, "nbformat_minor": 5 } jupyter-cache-1.0.0/tests/notebooks/complex_outputs.ipynb000066400000000000000000010173631452276675600237740ustar00rootroot00000000000000 { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "init_cell": true, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from ipypublish.scripts.ipynb_latex_setup import *" ] }, { "cell_type": "markdown", "metadata": { "ipub": { "slide": true } }, "source": [ "# Markdown" ] }, { "cell_type": "markdown", "metadata": { "ipub": { "slide": true } }, "source": [ "## General" ] }, { "cell_type": "markdown", "metadata": { "ipub": { "slide": true } }, "source": [ "Some markdown text.\n", "\n", "A list:\n", "\n", "- something\n", "- something else\n", "\n", "A numbered list\n", "\n", "1. something\n", "2. something else\n", "\n", "non-ascii characters TODO" ] }, { "cell_type": "markdown", "metadata": { "ipub": {} }, "source": [ "This is a long section of text, which we only want in a document (not a presentation)\n", "some text\n", "some more text\n", "some more text\n", "some more text\n", "some more text\n", "some more text\n", "some more text\n", "some more text\n", "some more text\n" ] }, { "cell_type": "markdown", "metadata": { "ipub": { "slide": true, "slideonly": true } }, "source": [ "This is an abbreviated section of the document text, which we only want in a presentation\n", "\n", "- summary of document text" ] }, { "cell_type": "markdown", "metadata": { "ipub": { "slide": true } }, "source": [ "## References and Citations" ] }, { "cell_type": "markdown", "metadata": { "ipub": { "slide": true } }, "source": [ "References to \\cref{fig:example}, \\cref{tbl:example}, =@eqn:example_sympy and \\cref{code:example_mpl}.\n", "\n", "A latex citation.\\cite{zelenyak_molecular_2016}\n", "\n", "A html citation.(Kirkeminde, 2012) " ] }, { "cell_type": "markdown", "metadata": { "ipub": { "slide": true } }, "source": [ "## Todo notes" ] }, { "cell_type": "markdown", "metadata": { "ipub": { "slide": true } }, "source": [ "\\todo[inline]{an inline todo}\n", "\n", "Some text.\\todo{a todo in the margins}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Text Output" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "ipub": { "text": { "format": { "backgroundcolor": "\\color{blue!10}" } } } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "This is some printed text,\n", "with a nicely formatted output.\n", "\n" ] } ], "source": [ "print(\"\"\"\n", "This is some printed text,\n", "with a nicely formatted output.\n", "\"\"\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Images and Figures" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "ipub": { "figure": { "caption": "A nice picture.", "label": "fig:example", "placement": "!bh" } } }, "outputs": [ { "data": { "image/jpeg": "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\nRZ6DC3UbnCO/0neC3KBaPBsCQC4z/wBFZPQHA40HX3P/AOqcuio9sdhyIUmU+o+bMZekeQed+tHU\nX0NqwmUm7JytWNeze0Aez9HU5rn2X7voLFo6XkdP6t05ufWGNssousZaW6Mfbs2WVe73e33sXoX2\neh+QzIfWw21z6dhaC9s/T9Ow+5m7+Ssj6x/Vmzq+TjZOM9tVzHNZc95OtQdvljfo+pU4vd/wqkxZ\nogCB9IN8UmpliZHi3quGP5vT0h4yRvMkhzT8Yn/vqtKqX/p2P4mwaf1jt/78rJUMdlk9/opMkmTl\nr//V64JwmCdVWda/Jbh412Y76OLU+4/9ba6wf9Jq846Qw7Bv1edXHzOr/wDpLtvrQbR9XOoek3cT\nW0WeVRfX9os/s1blx3S43/PXwT4/LJs8tuS9HhtG1saDwV4TEKnifRH+vZWpdt4nVVyvnu2GOlVO\nt69Hzv8AiLP+pKPUXQNPyKr1sv8A2TmwNPQs/wCpKMdx5rAPUPNzuntAqZqeAVoB54PGuqzOnmw1\ns0jQROvZXnetIiPMnT8m9GW5Z8g13bVRP8VR60SKHjxB80Wn7UHEEA6nWT/cqvWDYMZ0iTGvA/8A\nMko/MFsRUxqHE6A6KTrw94/6UrpaXAgeELl+h7tjo/0j/wAq6bFnaJ4T83zHzSfkj5Butj+5HYJ1\n7qrWTuOmistJnjw5UTDJnZo0O/dLXfcQVecNT8VRtj03btBBn7ldE7Ru0dAkecap0erFPosUykUy\nesf/2f/tGdpQaG90b3Nob3AgMy4wADhCSU0EBAAAAAAAPxwBWgADGyVHHAFaAAMbJUccAVoAAxsl\nRxwBWgADGyVHHAFaAAMbJUccAVoAAxslRxwBWgADGyVHHAIAAAIAAAA4QklNBCUAAAAAABCQadng\n9equgfESqXOgJChYOEJJTQQ6AAAAAADlAAAAEAAAAAEAAAAAAAtwcmludE91dHB1dAAAAAUAAAAA\nUHN0U2Jvb2wBAAAAAEludGVlbnVtAAAAAEludGUAAAAAQ2xybQAAAA9wcmludFNpeHRlZW5CaXRi\nb29sAAAAAAtwcmludGVyTmFtZVRFWFQAAAABAAAAAAAPcHJpbnRQcm9vZlNldHVwT2JqYwAAAAwA\nUAByAG8AbwBmACAAUwBlAHQAdQBwAAAAAAAKcHJvb2ZTZXR1cAAAAAEAAAAAQmx0bmVudW0AAAAM\nYnVpbHRpblByb29mAAAACXByb29mQ01ZSwA4QklNBDsAAAAAAi0AAAAQAAAAAQAAAAAAEnByaW50\nT3V0cHV0T3B0aW9ucwAAABcAAAAAQ3B0bmJvb2wAAAAAAENsYnJib29sAAAAAABSZ3NNYm9vbAAA\nAAAAQ3JuQ2Jvb2wAAAAAAENudENib29sAAAAAABMYmxzYm9vbAAAAAAATmd0dmJvb2wAAAAAAEVt\nbERib29sAAAAAABJbnRyYm9vbAAAAAAAQmNrZ09iamMAAAABAAAAAAAAUkdCQwAAAAMAAAAAUmQg\nIGRvdWJAb+AAAAAAAAAAAABHcm4gZG91YkBv4AAAAAAAAAAAAEJsICBkb3ViQG/gAAAAAAAAAAAA\nQnJkVFVudEYjUmx0AAAAAAAAAAAAAAAAQmxkIFVudEYjUmx0AAAAAAAAAAAAAAAAUnNsdFVudEYj\nUHhsQFIAAAAAAAAAAAAKdmVjdG9yRGF0YWJvb2wBAAAAAFBnUHNlbnVtAAAAAFBnUHMAAAAAUGdQ\nQwAAAABMZWZ0VW50RiNSbHQAAAAAAAAAAAAAAABUb3AgVW50RiNSbHQAAAAAAAAAAAAAAABTY2wg\nVW50RiNQcmNAWQAAAAAAAAAAABBjcm9wV2hlblByaW50aW5nYm9vbAAAAAAOY3JvcFJlY3RCb3R0\nb21sb25nAAAAAAAAAAxjcm9wUmVjdExlZnRsb25nAAAAAAAAAA1jcm9wUmVjdFJpZ2h0bG9uZwAA\nAAAAAAALY3JvcFJlY3RUb3Bsb25nAAAAAAA4QklNA+0AAAAAABAASAAAAAEAAQBIAAAAAQABOEJJ\nTQQmAAAAAAAOAAAAAAAAAAAAAD+AAAA4QklNBA0AAAAAAAQAAAAeOEJJTQQZAAAAAAAEAAAAHjhC\nSU0D8wAAAAAACQAAAAAAAAAAAQA4QklNJxAAAAAAAAoAAQAAAAAAAAABOEJJTQP1AAAAAABIAC9m\nZgABAGxmZgAGAAAAAAABAC9mZgABAKGZmgAGAAAAAAABADIAAAABAFoAAAAGAAAAAAABADUAAAAB\nAC0AAAAGAAAAAAABOEJJTQP4AAAAAABwAAD/////////////////////////////A+gAAAAA////\n/////////////////////////wPoAAAAAP////////////////////////////8D6AAAAAD/////\n////////////////////////A+gAADhCSU0EAAAAAAAAAgApOEJJTQQCAAAAAACGAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAA4QklNBDAAAAAAAEMBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB\nAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBADhCSU0ELQAAAAAABgABAAAANjhCSU0E\nCAAAAAAATAAAAAEAAAJAAAACQAAAAAwAAAAAAQAAQgAB//+cAAAAACJAAQAAH8AB///KQAD//84A\nAP///EAAAAAAAAAAAC5AAP//x4AB///9gAE4QklNBB4AAAAAAAQAAAAAOEJJTQQaAAAAAANjAAAA\nBgAAAAAAAAAAAAAA/gAAAXIAAAAXADEAMgAuADEANQAtAGgAbwBtAGUAcABhAGcAZQAtAGMAdQBy\nAGEAdABpAG8AbgAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAABcgAAAP4AAAAAAAAA\nAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAQAAAAAAAG51bGwAAAACAAAABmJvdW5k\nc09iamMAAAABAAAAAAAAUmN0MQAAAAQAAAAAVG9wIGxvbmcAAAAAAAAAAExlZnRsb25nAAAAAAAA\nAABCdG9tbG9uZwAAAP4AAAAAUmdodGxvbmcAAAFyAAAABnNsaWNlc1ZsTHMAAAABT2JqYwAAAAEA\nAAAAAAVzbGljZQAAABIAAAAHc2xpY2VJRGxvbmcAAAAAAAAAB2dyb3VwSURsb25nAAAAAAAAAAZv\ncmlnaW5lbnVtAAAADEVTbGljZU9yaWdpbgAAAA1hdXRvR2VuZXJhdGVkAAAAAFR5cGVlbnVtAAAA\nCkVTbGljZVR5cGUAAAAASW1nIAAAAAZib3VuZHNPYmpjAAAAAQAAAAAAAFJjdDEAAAAEAAAAAFRv\ncCBsb25nAAAAAAAAAABMZWZ0bG9uZwAAAAAAAAAAQnRvbWxvbmcAAAD+AAAAAFJnaHRsb25nAAAB\ncgAAAAN1cmxURVhUAAAAAQAAAAAAAG51bGxURVhUAAAAAQAAAAAAAE1zZ2VURVhUAAAAAQAAAAAA\nBmFsdFRhZ1RFWFQAAAABAAAAAAAOY2VsbFRleHRJc0hUTUxib29sAQAAAAhjZWxsVGV4dFRFWFQA\nAAABAAAAAAAJaG9yekFsaWduZW51bQAAAA9FU2xpY2VIb3J6QWxpZ24AAAAHZGVmYXVsdAAAAAl2\nZXJ0QWxpZ25lbnVtAAAAD0VTbGljZVZlcnRBbGlnbgAAAAdkZWZhdWx0AAAAC2JnQ29sb3JUeXBl\nZW51bQAAABFFU2xpY2VCR0NvbG9yVHlwZQAAAABOb25lAAAACXRvcE91dHNldGxvbmcAAAAAAAAA\nCmxlZnRPdXRzZXRsb25nAAAAAAAAAAxib3R0b21PdXRzZXRsb25nAAAAAAAAAAtyaWdodE91dHNl\ndGxvbmcAAAAAADhCSU0EKAAAAAAADAAAAAI/8AAAAAAAADhCSU0EFAAAAAAABAAAANc4QklNBAwA\nAAAAD1gAAAABAAAAoAAAAG4AAAHgAADOQAAADzwAGAAB/9j/7QAMQWRvYmVfQ00AAf/uAA5BZG9i\nZQBkgAAAAAH/2wCEAAwICAgJCAwJCQwRCwoLERUPDAwPFRgTExUTExgRDAwMDAwMEQwMDAwMDAwM\nDAwMDAwMDAwMDAwMDAwMDAwMDAwBDQsLDQ4NEA4OEBQODg4UFA4ODg4UEQwMDAwMEREMDAwMDAwR\nDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAG4AoAMBIgACEQEDEQH/3QAEAAr/xAE/\nAAABBQEBAQEBAQAAAAAAAAADAAECBAUGBwgJCgsBAAEFAQEBAQEBAAAAAAAAAAEAAgMEBQYHCAkK\nCxAAAQQBAwIEAgUHBggFAwwzAQACEQMEIRIxBUFRYRMicYEyBhSRobFCIyQVUsFiMzRygtFDByWS\nU/Dh8WNzNRaisoMmRJNUZEXCo3Q2F9JV4mXys4TD03Xj80YnlKSFtJXE1OT0pbXF1eX1VmZ2hpam\ntsbW5vY3R1dnd4eXp7fH1+f3EQACAgECBAQDBAUGBwcGBTUBAAIRAyExEgRBUWFxIhMFMoGRFKGx\nQiPBUtHwMyRi4XKCkkNTFWNzNPElBhaisoMHJjXC0kSTVKMXZEVVNnRl4vKzhMPTdePzRpSkhbSV\n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\njHucWh02P7zrsJ78qn12f2Lmx/oH/kVxpEKn13/kbN/4h/8A1JVeO480R+YeYc3pv80zyAhaYPft\nwsrprh6TJ8AtRp0E/FE7lmyfMWw09ln9YE4zx4z+RXfUAMTyqHWDGI8HuD+RKO4WQ+cPNdJFnoML\ndRucI7/Sd4LcoFo8GwJALjP/AEVk9AcDjQdfc/8A6py6Kj2x2HIhSZT6j5sxl6R5B5360dRfQ2rC\nZSbsnK1Y17N7QB7P0dTmufZfu+gsWjpeR0/q3Tm59YY2yyi6xlpbox9uzZZV7vd7fexehfZ6H5DM\nh9bDbXPp2FoL2z9P07D7mbv5KyPrH9WbOr5ONk4z21XMc1lz3k61B2+WN+j6lTi93/CqTFmiAIH0\ng3xSamWJkeLeq4Y/m9PSHjJG8ySHNPxif++q0qpf+nY/ibBp/WO3/vyslQx2WT3+ikySZOWv/9Xr\ngnCYJ1VZ1r8luHjXZjvo4tT7j/1trrB/0mrzjpDDsG/V51cfM6v/AOku2+tBtH1c6h6TdxNbRZ5V\nF9f2iz+zVuXHdLjf89fBPj8smzy25L0eG0bWxoPBXhMQqeJ9Ef69lal23idVXK+e7YY6VU63r0fO\n/wCIs/6ko9RdA0/IqvWy/wDZObA09Cz/AKkox3HmsA9Q83O6e0Cpmp4BWgHng8a6rM6ebDWzSNBE\n69led60iI8ydPyb0ZblnyDXdtVE/xVHrRIoePEHzRaftQcQQDqdZP9yq9YNgxnSJMa8D/wAySj8w\nWxFTGocToDopOvD3j/pSulpcCB4QuX6Hu2Oj/SP/ACrpsWdonhPzfMfNJ+SPkG62P7kdgnXuqtZO\n46aKy0mePDlRMMmdmjQ790td9xBV5w1PxVG2PTdu0EGfuV0TtG7R0CR5xqnR6sU+ixTKRTJ6x//Z\nOEJJTQQhAAAAAABdAAAAAQEAAAAPAEEAZABvAGIAZQAgAFAAaABvAHQAbwBzAGgAbwBwAAAAFwBB\nAGQAbwBiAGUAIABQAGgAbwB0AG8AcwBoAG8AcAAgAEMAQwAgADIAMAAxADUAAAABADhCSU0EBgAA\nAAAABwAGAAAAAQEA/+Ea52h0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8APD94cGFja2V0IGJl\nZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxu\nczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMwNjcgNzku\nMTU3NzQ3LCAyMDE1LzAzLzMwLTIzOjQwOjQyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9\nImh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2Ny\naXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEu\nMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhv\ndG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0i\naHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5h\nZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1sbnM6c3RSZWY9Imh0dHA6\nLy9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdGVEYXRl\nPSIyMDE1LTEwLTA4VDEyOjM4OjExLTA0OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxNS0xMi0xN1Qx\nMDo1OTo0NS0wNTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxNS0xMi0xN1QxMDo1OTo0NS0wNTow\nMCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNSAoTWFjaW50b3NoKSIg\nZGM6Zm9ybWF0PSJpbWFnZS9qcGVnIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiBwaG90b3Nob3A6\nSUNDUHJvZmlsZT0iVkEyNzAzIFNlcmllcyBDYWxpYnJhdGVkIiB4bXBNTTpJbnN0YW5jZUlEPSJ4\nbXAuaWlkOmUyYmQwYjU5LTU2YjEtNDNiZi1iN2UyLTMyNzQzYjFhOTNjMCIgeG1wTU06RG9jdW1l\nbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjk2MWFlZWE0LWU1NjUtMTE3OC04NGExLWVmNTVl\nNWVmOTVhZiIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjY1MjRlZTYyLTQwNDAt\nNDQ4Ni05NzMwLWNjNDgyZDBiNmRjNCI+IDxwaG90b3Nob3A6VGV4dExheWVycz4gPHJkZjpCYWc+\nIDxyZGY6bGkgcGhvdG9zaG9wOkxheWVyTmFtZT0iUG9wdWxhciBJbGx1c3RyYXRpb24gQ2F0ZWdv\ncmllcyIgcGhvdG9zaG9wOkxheWVyVGV4dD0iUG9wdWxhciBJbGx1c3RyYXRpb24gQ2F0ZWdvcmll\ncyIvPiA8cmRmOmxpIHBob3Rvc2hvcDpMYXllck5hbWU9IlBvcHVsYXIgVmVjdG9yIENhdGVnb3Jp\nZXMiIHBob3Rvc2hvcDpMYXllclRleHQ9IlBvcHVsYXIgVmVjdG9yIENhdGVnb3JpZXMiLz4gPHJk\nZjpsaSBwaG90b3Nob3A6TGF5ZXJOYW1lPSJQb3B1bGFyIFBob3RvIENhdGVnb3JpZXMiIHBob3Rv\nc2hvcDpMYXllclRleHQ9IlBvcHVsYXIgUGhvdG8gQ2F0ZWdvcmllcyIvPiA8cmRmOmxpIHBob3Rv\nc2hvcDpMYXllck5hbWU9IkJpZ3N0b2NrIFBpY2tzIiBwaG90b3Nob3A6TGF5ZXJUZXh0PSJCaWdz\ndG9jayBQaWNrcyIvPiA8cmRmOmxpIHBob3Rvc2hvcDpMYXllck5hbWU9IldpbnRlciBDb2xsZWN0\naW9uIiBwaG90b3Nob3A6TGF5ZXJUZXh0PSJXaW50ZXIgQ29sbGVjdGlvbiIvPiA8cmRmOmxpIHBo\nb3Rvc2hvcDpMYXllck5hbWU9IkJpZ3N0b2NrIFZpZGVvIiBwaG90b3Nob3A6TGF5ZXJUZXh0PSJC\naWdzdG9jayBWaWRlbyIvPiA8cmRmOmxpIHBob3Rvc2hvcDpMYXllck5hbWU9IkltYWdlcyBhbmQg\nVmlkZW8gZm9yIGV2ZXJ5b25lLiIgcGhvdG9zaG9wOkxheWVyVGV4dD0iSW1hZ2VzIGFuZCBWaWRl\nbyBmb3IgZXZlcnlvbmUuIi8+IDxyZGY6bGkgcGhvdG9zaG9wOkxheWVyTmFtZT0iT3ZlciAzMCBt\naWxsaW9uIHN0b2NrIHBob3RvcywgdmlkZW9zLCBhbmQgdmVjdG9ycy4iIHBob3Rvc2hvcDpMYXll\nclRleHQ9Ik92ZXIgMzAgbWlsbGlvbiBzdG9jayBwaG90b3MsIHZpZGVvcywgYW5kIHZlY3RvcnMu\nIi8+IDwvcmRmOkJhZz4gPC9waG90b3Nob3A6VGV4dExheWVycz4gPHBob3Rvc2hvcDpEb2N1bWVu\ndEFuY2VzdG9ycz4gPHJkZjpCYWc+IDxyZGY6bGk+MjMzOEI4RTQxMjc0MDA4QTkwQzhCRDI1NDc5\nNjBCQjM8L3JkZjpsaT4gPHJkZjpsaT4yREU0NDA2QzEwQjczNDIyQjRFRTZEOEIwRDMyNUZBODwv\ncmRmOmxpPiA8cmRmOmxpPjMxOTc2NTBFRTkzRjc5OEQ2QTJCQUYyMUFENjIyNzJCPC9yZGY6bGk+\nIDxyZGY6bGk+M0YyOTgxMzJFRjVERjdCRDFFN0U3OUM1MjVDMENBODY8L3JkZjpsaT4gPHJkZjps\naT41QUFBMDQ2RjFCRUQ1NTA3QjRGRTU4MkVEMTI1OTFBNDwvcmRmOmxpPiA8cmRmOmxpPjY1NjUz\nQUYxMEE1MUQ5MEZBMDdFQ0U2MzMyNjA4NEFBPC9yZGY6bGk+IDxyZGY6bGk+NzZFNDE3RTVFNDAx\nN0Q3ODU4MkI3OTFCOTYzQzlGM0I8L3JkZjpsaT4gPHJkZjpsaT44RjQwMDNDMUU1MzE4REU2MzVD\nODM0OTBENTE0ODMwQTwvcmRmOmxpPiA8cmRmOmxpPkExNDc0Q0I1QTEwRTM2RDQ2ODcwRjA0NDZF\nMDdEM0UwPC9yZGY6bGk+IDxyZGY6bGk+QTRENUQzQzczOTc4NTBERkZDQThDNjM2QTZEMkFBOUU8\nL3JkZjpsaT4gPHJkZjpsaT5BODE4NjdBREIyRDIwMzc2Q0FGMjNDNkM0NDU1QTYxRjwvcmRmOmxp\nPiA8cmRmOmxpPkMzMDkzMjZBRjJEMkUzQTE1QThBMjI0RUUwMjQ2NDQyPC9yZGY6bGk+IDxyZGY6\nbGk+QzRBQUJERDU3MUY5NkU5MjQ1QUYzMjU1REFDQkU5MDI8L3JkZjpsaT4gPHJkZjpsaT5DNTk3\nRkREMkRGMjlDRUUzQTA4OTA2REVGODIzNUZBMzwvcmRmOmxpPiA8cmRmOmxpPkQwRTQxMTkzRDBE\nODdBQkY2OUQzQTI3NDM1RkQ5Q0U2PC9yZGY6bGk+IDxyZGY6bGk+RDQ5M0IzODIwMDhERDAyMDA1\nODk0Rjg0QUI4QUNEQTU8L3JkZjpsaT4gPHJkZjpsaT5EQjFBQzlEMjZCMUY0MEM5QTVBMTFCN0E2\nQURDM0ExMjwvcmRmOmxpPiA8cmRmOmxpPkU2NEU5NTJBQ0UyOTAwMkVCNjhGMUJDRDAxQjg5MTFC\nPC9yZGY6bGk+IDxyZGY6bGk+RTY4ODU2ODVDQkRGMjMyNEU3MTE1OEJGMjE3NkUxRkU8L3JkZjps\naT4gPHJkZjpsaT5GNUU3NTc3QTZCM0EzNDFBNTFDQUFBQzVFM0E1OUIxMTwvcmRmOmxpPiA8cmRm\nOmxpPkZEMUQ3RTMxQzc1MTJCRTBBODkxQ0RBQTVDNzdERTk1PC9yZGY6bGk+IDxyZGY6bGk+eG1w\nLmRpZDo2NDk0NmNiOC00MTFhLTRiMDAtOTk2MS1jODJkZmZjNTQ2NGM8L3JkZjpsaT4gPC9yZGY6\nQmFnPiA8L3Bob3Rvc2hvcDpEb2N1bWVudEFuY2VzdG9ycz4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6\nU2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5p\naWQ6NjUyNGVlNjItNDA0MC00NDg2LTk3MzAtY2M0ODJkMGI2ZGM0IiBzdEV2dDp3aGVuPSIyMDE1\nLTEwLTMwVDE0OjI2OjM1LTA0OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3No\nb3AgQ0MgMjAxNSAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0\nOmFjdGlvbj0iY29udmVydGVkIiBzdEV2dDpwYXJhbWV0ZXJzPSJmcm9tIGltYWdlL3BuZyB0byBh\ncHBsaWNhdGlvbi92bmQuYWRvYmUucGhvdG9zaG9wIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJk\nZXJpdmVkIiBzdEV2dDpwYXJhbWV0ZXJzPSJjb252ZXJ0ZWQgZnJvbSBpbWFnZS9wbmcgdG8gYXBw\nbGljYXRpb24vdm5kLmFkb2JlLnBob3Rvc2hvcCIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2\nZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6NjQ5NDZjYjgtNDExYS00YjAwLTk5NjEtYzgy\nZGZmYzU0NjRjIiBzdEV2dDp3aGVuPSIyMDE1LTEwLTMwVDE0OjI2OjM1LTA0OjAwIiBzdEV2dDpz\nb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNSAoTWFjaW50b3NoKSIgc3RFdnQ6\nY2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNl\nSUQ9InhtcC5paWQ6NjM0NGY3MWMtNDhiZC00MGU4LWFhM2YtZjcyYTEwNmJjZTlhIiBzdEV2dDp3\naGVuPSIyMDE1LTEyLTE3VDEwOjU5OjQ1LTA1OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9i\nZSBQaG90b3Nob3AgQ0MgMjAxNSAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRm\nOmxpIHN0RXZ0OmFjdGlvbj0iY29udmVydGVkIiBzdEV2dDpwYXJhbWV0ZXJzPSJmcm9tIGFwcGxp\nY2F0aW9uL3ZuZC5hZG9iZS5waG90b3Nob3AgdG8gaW1hZ2UvanBlZyIvPiA8cmRmOmxpIHN0RXZ0\nOmFjdGlvbj0iZGVyaXZlZCIgc3RFdnQ6cGFyYW1ldGVycz0iY29udmVydGVkIGZyb20gYXBwbGlj\nYXRpb24vdm5kLmFkb2JlLnBob3Rvc2hvcCB0byBpbWFnZS9qcGVnIi8+IDxyZGY6bGkgc3RFdnQ6\nYWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplMmJkMGI1OS01NmIxLTQz\nYmYtYjdlMi0zMjc0M2IxYTkzYzAiIHN0RXZ0OndoZW49IjIwMTUtMTItMTdUMTA6NTk6NDUtMDU6\nMDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE1IChNYWNpbnRv\nc2gpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDwvcmRmOlNlcT4gPC94bXBNTTpIaXN0b3J5PiA8eG1w\nTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2MzQ0ZjcxYy00OGJkLTQw\nZTgtYWEzZi1mNzJhMTA2YmNlOWEiIHN0UmVmOmRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rv\nc2hvcDpjYTc1NGM2MC1iZmMwLTExNzgtYjgwYy1lNWFjMmFiZWU0MjgiIHN0UmVmOm9yaWdpbmFs\nRG9jdW1lbnRJRD0ieG1wLmRpZDo2NTI0ZWU2Mi00MDQwLTQ0ODYtOTczMC1jYzQ4MmQwYjZkYzQi\nLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+ICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPD94cGFja2V0IGVuZD0i\ndyI/Pv/iKWhJQ0NfUFJPRklMRQABAQAAKVhhcHBsAhAAAG1udHJSR0IgWFlaIAffAAkACgAPABcA\nGGFjc3BBUFBMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD21gABAAAAANMtYXBwbAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWRlc2MAAAFQAAAAc2RzY20A\nAAHEAAAATGNwcnQAAAIQAAAAI3d0cHQAAAI0AAAAFHJYWVoAAAJIAAAAFGdYWVoAAAJcAAAAFGJY\nWVoAAAJwAAAAFHJUUkMAAAKEAAAIDGFhcmcAAAqQAAAAIHZjZ3QAAAqwAAAGEm5kaW4AABDEAAAY\nPmNoYWQAACkEAAAALG1tb2QAACkwAAAAKGJUUkMAAAKEAAAIDGdUUkMAAAKEAAAIDGFhYmcAAAqQ\nAAAAIGFhZ2cAAAqQAAAAIGRlc2MAAAAAAAAAGVZBMjcwMyBTZXJpZXMgQ2FsaWJyYXRlZAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAbWx1YwAAAAAAAAABAAAADGVuVVMAAAAwAAAAHABWAEEAMgA3ADAA\nMwAgAFMAZQByAGkAZQBzACAAQwBhAGwAaQBiAHIAYQB0AGUAZHRleHQAAAAAQ29weXJpZ2h0IEFw\ncGxlIEluYy4sIDIwMTUAAFhZWiAAAAAAAADz2AABAAAAARYIWFlaIAAAAAAAAGwPAAA4qQAAApdY\nWVogAAAAAAAAYjYAALdyAAAR/1hZWiAAAAAAAAAokQAAD+UAAL6XY3VydgAAAAAAAAQAAAAABQAK\nAA8AFAAZAB4AIwAoAC0AMgA2ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQAJUA\nmgCfAKMAqACtALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMBGQEfASUBKwEy\nATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJAdEB2QHhAekB8gH6AgMC\nDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnECegKEAo4CmAKiAqwCtgLBAssC1QLgAusC9QMAAwsDFgMh\nAy0DOANDA08DWgNmA3IDfgOKA5YDogOuA7oDxwPTA+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4E\njASaBKgEtgTEBNME4QTwBP4FDQUcBSsFOgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3\nBkgGWQZqBnsGjAadBq8GwAbRBuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDII\nRghaCG4IggiWCKoIvgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqY\nCq4KxQrcCvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0mDUAN\nWg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P7BAJECYQQxBh\nEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMTIxNDE2MTgxOkE8UT\n5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZsFo8WshbWFvoXHRdBF2UXiReu\nF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxndGgQaKhpRGncanhrFGuwbFBs7G2MbihuyG9oc\nAhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4WHkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCY\nIMQg8CEcIUghdSGhIc4h+yInIlUigiKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZcl\nxyX3JicmVyaHJrcm6CcYJ0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2\nK2krnSvRLAUsOSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIx\nSjGCMbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbpNyQ3YDec\nN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9Ij1hPaE94D4gPmA+\noD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BEA0RHRIpEzkUSRVVFmkXe\nRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kdSWNJqUnwSjdKfUrESwxLU0uaS+JMKkxyTLpNAk1KTZNN\n3E4lTm5Ot08AT0lPk0/dUCdQcVC7UQZRUFGbUeZSMVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYP\nVlxWqVb3V0RXklfgWC9YfVjLWRpZaVm4WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1f\nD19hX7NgBWBXYKpg/GFPYaJh9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/\naJZo7GlDaZpp8WpIap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfBy\nS3KmcwFzXXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyB\nfOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6GcobXhzuH\nn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/jmaOzo82j56QBpBukNaRP5GokhGSepLj\nk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ/JpomtWbQpuvnByciZz3nWSd0p5Anq6f\nHZ+Ln/qgaaDYoUehtqImopajBqN2o+akVqTHpTilqaYapoum/adup+CoUqjEqTepqaocqo+rAqt1\nq+msXKzQrUStuK4trqGvFq+LsACwdbDqsWCx1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm4\n0blKucK6O7q1uy67p7whvJu9Fb2Pvgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZG\nxsPHQce/yD3IvMk6ybnKOMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnU\ny9VO1dHWVdbY11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj\n4+vkc+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/8ozz\nGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t//9wYXJhAAAAAAAD\nAAAAAmZmAADypwAADVkAABPQAAAKDnZjZ3QAAAAAAAAAAAADAQAAAgAAAJQA4gFvAgICsANEA9gE\ndgUOBbEGVwb/B6EIRwjtCZcKRgrvC5sMSwz7Da8OYQ8TD8kQghE6EfISrRNoFCUU4xWkFmYXJxft\nGLAZehpDGwsb2BylHXUeRB8YH+sgwSGYInIjSyQpJQkl6SbLJ64olCl7KmQrTyw8LSouGy8ML/4w\n8jHnMt4z1TTMNcQ2vDe0OK05qDqkO6E8oD2hPqM/p0CtQbVCvkPKRNdF5kb4SAxJIUo4S1JMbk2L\nTqxPzlDzUhlTQ1RvVZ1WzVgAWTVabFumXOBeHl9dYJ1h32MhZGRlqGbtaDBpdWq4a/xtPW59b7tw\n+HIyc2l0nnXQdv14KHlOenJ7kHysfcN+1X/kgO6B9oL8hACFBIYGhweIB4kGigOLAIv8jPiN8o7s\nj+WQ3ZHWks2TxZS7lbKWqZefmJWZi5qBm3ecbp1knlufUqBJoUGiOqMzpCylJqYhpx2oGqkYqher\nFqwYrRuuH68lsC2xNrJCs0+0XbVstnu3i7idua66wLvTvOe9+78QwCbBPcJUw2vEhMWcxrbH0Mjq\nygbLIcw+zVrOd8+V0LPR0tLx1BDVMNZQ13HYktmz2tXb9t0Y3jrfXOB/4aHixOPn5QrmLudT6Hfp\nnOrC6+jtD+4371/wiPGy8t30CPU19mP3kvjC+fP7Jvxa/Y/+xv//AAAAlAEiAa8CQgLwA6UEQgTy\nBagGXQcPB78IdwkzCe0KqgtnDCQM5w2mDmgPKQ/uELYReRJCEwoT0xSfFWsWNRcDF9MYohlyGkMb\nFRvrHMAdkx5rH0MgGSDyIc0ipyOEJF8lPiYdJvwn3Ci/KaAqgytmLEotMS4XLv0v5TDOMbcyoDOK\nNHU1YDZNNzk4JDkQOf466zvYPMU9sz6hP45AfUFsQltDSkQ7RS1GIEcUSAlI/0n3SvBL7EzqTelO\n6k/wUPhSA1MSVCVVPFZTV21Yh1mjWsBb31z/Xh9fQWBkYYhirGPSZPdmHmdDaGlpkWq3a91tBG4p\nb09wc3GWcrhz2XT5dhd3NHhPeWh6f3uUfKd9uH7If9eA54H2gwWEFIUjhjGHP4hNiVuKaYt2jISN\nko6fj6yQuJHFktKT3pTrlfiXBJgQmRyaKJs0nECdTJ5Xn2Ogb6F6ooajkaSdpaimtKe/qMqp1qrh\nq+ys+K4Drw+wGrEmsjGzPLRGtU+2VrdduGO5aLpsu3C8c712vnm/e8B+wYDCg8OFxInFjMaQx5TI\nmcmgyqbLrsy3zcHOzc/b0OrR+9MO1CPVO9ZV13PYk9m42t3cAt0n3k3fdOCb4cLi6uQU5T7maueX\n6MXp9eso7Fvtke7J8ATxQvKC88b1DvZZ96r4/vpY+7f9Hf6K//8AAACUASIBrwJBAvADhAQtBNYF\nfwYtBtwHjAhBCPMJqgphCxYL1AyNDUUOCA7DD4cQQxEJEccSjhNTFBsU4hWpFnIXPhgIGNYZpBpz\nG0UcFRzoHbsekB9mID4hFiHuIsojpSSDJV8mPicfJ/8o4inEKqgrjyx0LVwuQy8sMBYxATHsMtkz\nxTSyNaE2jjd+OGw5XDpLOzw8Lj0hPhM/Bz/+QPRB7ELlQ95E2UXVRtNH0EjQSdBK0kvVTNlN3U7k\nT+xQ9FH+UwlUFVUiVjBXP1hQWWFac1uHXJpdr17FX9tg8WIHYx9kN2VPZmZnf2iWaa5qxWvdbPNu\nCW8ecDNxR3JZc2t0e3WKdpd3pHivebd6vnvEfMh9zH7Pf9OA1YHYgtuD3ITehd+G4IfhiOKJ4orj\ni+OM443jjuOP45DjkeKS4pPilOKV4pbil+KY4pnjmuSb5Zzmneie6Z/roO6h8KL0o/ek+6YApwWo\nC6kRqhirIKwprTKuO69FsE+xWLJfs2a0brV0tnq3gbiGuYy6kruYvJ+9pb6sv7TAvMHFws/D2sTl\nxfLHAMgQySHKNMtJzGDNes6Vz7PQ1dH50yHUTdV91rHX5tkc2lHbiNy+3fbfLuBn4aHi3eQa5Vnm\nmufc6SLqaeu07QLuU++p8QLyYfPE9S72nvgW+Zb7Hvyx/lH//wAAbmRpbgAAAAAAABg2AACjgAAA\nVsAAAE8AAACegAAAKAAAAA8AAABQQAAAVEAAAeryAAHqcgABzcYAAwQAAAIAAAAAAAAAAQACAAMA\nBAAFAAcACAAKAAwADwARABQAFwAaAB0AIQAlACkALQAxADYAOgA/AEUASgBPAFUAWwBhAGgAbgB1\nAHwAgwCKAJIAmQChAKkAsgC6AMMAzADVAN4A5wDxAPsBBQEPARkBJAEuATkBRAFQAVsBZwFzAX8B\niwGXAaQBsAG9AcoB1wHlAfICAAIOAhwCKgI5AkcCVgJlAnQCgwKSAqICsgLBAtEC4gLyAwIDEwMk\nAzUDRgNXA2gDegOMA54DsAPCA9QD5gP5BAwEHgQxBEQEWARrBH8EkgSmBLoEzgTiBPYFCwUfBTQF\nSQVeBXMFiAWdBbMFyAXeBfQGCgYgBjYGTAZjBnkGkAamBr0G1AbrBwIHGgcxB0gHYAd4B5AHpwe/\nB9gH8AgICCAIOQhSCGoIgwicCLUIzgjnCQEJGgk0CU0JZwmBCZsJtQnPCekKAwoeCjgKUwptCogK\nowq+CtkK9AsPCyoLRgthC30LmQu1C9AL7AwJDCUMQQxeDHoMlwyzDNAM7Q0KDScNRQ1iDX8NnQ27\nDdgN9g4UDjMOUQ5vDo4OrA7LDuoPCQ8oD0cPZw+GD6YPxg/mEAYQJhBGEGcQhxCoEMgQ6REKESsR\nSxFtEY4RrxHQEfESExI0ElYSeBKZErsS3RL/EyETQxNlE4gTqhPME+8UERQ0FFcUeRScFL8U4hUF\nFSgVSxVuFZIVtRXYFfwWHxZDFmYWihauFtIW9hcZFz0XYheGF6oXzhfyGBcYOxhfGIQYqBjNGPIZ\nFhk7GWAZhRmqGc8Z9BoZGj4aYxqIGq0a0xr4Gx0bQxtoG44bsxvZG/4cJBxKHHAclRy7HOEdBx0t\nHVMdeR2fHcUd7B4SHjgeXh6FHqse0R74Hx4fRR9rH5IfuR/fIAYgLSBTIHogoSDIIO8hFiE9IWQh\niyGyIdkiACInIk4idiKdIsQi7CMTIzojYiOJI7Ej2CQAJCgkTyR3JJ8kxyTvJRclPyVnJY8ltyXf\nJgcmLyZYJoAmqCbRJvknIidKJ3MnnCfFJ+0oFig/KGgokSi7KOQpDSk2KWApiSmzKd0qBiowKloq\nhCquKtgrAissK1crgSusK9YsASwsLFcsgiytLNgtAy0uLVothS2xLd0uCS41LmEujS65LuYvEi8/\nL2wvmS/GL/MwITBOMHwwqjDXMQYxNDFiMZExvzHuMh0yTDJ7Mqsy2jMKMzozajObM8sz/DQtNF40\njzTBNPI1JDVWNYg1uzXtNiA2UzaHNro27jciN1Y3izfAN/U4KjhfOJU4yzkBOTg5bjmlOd06FDpM\nOoQ6vTr2Oy87aDuiO9w8FjxRPIs8xz0CPT49ej23PfQ+MT5vPq0+6z8qP2k/qT/oQClAaUCqQOtB\nLEFtQa9B8UIzQnVCuEL7Qz5DgUPERAhETESQRNRFGUVeRaNF6EYuRnNGuUcAR0ZHjUfTSBtIYkip\nSPFJOUmBScpKEkpbSqRK7Us3S4FLy0wVTF9Mqkz0TT9Ni03WTiJObk66TwZPUk+fT+xQOVCGUNRR\nIlFvUb5SDFJaUqlS+FNHU5ZT5lQ2VIZU1lUmVXZVx1YYVmlWulcMV11Xr1gBWFNYpVj4WUpZnVnw\nWkRal1rrWz5bklvmXDpcj1zjXThdjV3iXjdejV7iXzhfjl/kYDpgkGDnYT1hlGHrYkJimWLxY0hj\noGP3ZE9kp2T/ZVhlsGYJZmFmumcTZ2xnxWgeaHho0WkraYRp3mo4apJq7GtHa6Fr+2xWbLBtC21m\nbcFuHG53btJvLW+Ib+RwP3CbcPZxUnGucglyZXLBcx1zeXPVdDF0jXTpdUZ1onX+dlp2t3cTd3B3\nzHgpeIV44nk+eZt593pUerB7DXtpe8Z8Inx/fNx9OH2VffF+Tn6qfwZ/Y3+/gBuAeIDUgTGBjYHq\ngkeCpIMBg16Du4QYhHWE0oUwhY2F64ZIhqaHBIdih8CIHoh8iNqJOImXifWKVIqyixGLcIvPjC6M\njYzsjUuNqo4JjmmOyI8oj4eP55BHkKeRB5FnkceSJ5KIkuiTSJOplAqUapTLlSyVjZXulk+WsJcS\nl3OX1Jg2mJiY+Zlbmb2aH5qBmuObRZuonAqcbJzPnTKdlJ33nlqevZ8gn4Of5qBKoK2hEaF0odii\nPKKgowOjZ6PMpDCklKT4pV2lwaYmpoum76dUp7moHqiEqOmpTqm0qhmqf6rkq0qrsKwWrHys4q1J\nra+uFa58ruOvSa+wsBewfrDlsUyxtLIbsoKy6rNSs7m0IbSJtPG1WbXCtiq2krb7t2S3zLg1uJ65\nB7lwudq6Q7qsuxa7f7vpvFO8vb0nvZG9+75mvtC/O7+mwBDAe8DmwVHBvcIowpPC/8Nrw9fEQ8Sv\nxRvFh8X0xmDGzcc6x6bIE8iAyO7JW8nIyjbKo8sRy3/L7cxbzMnNN82mzhTOg87xz2DPz9A+0K3R\nHNGL0frSatLZ00nTuNQo1JjVCNV41ejWWNbI1znXqdga2IrY+9ls2dzaTdq+2y/boNwR3IPc9N1l\n3dfeSN663yzfneAP4IHg8+Fl4dfiSeK74y3joOQS5ITk9+Vp5dzmTubB5zTnpugZ6Izo/+ly6eXq\nWOrL6z7rsewk7JftCu197fHuZO7X70vvvvAx8KXxGPGM8f/yc/Lm81rzzfRB9LX1KPWc9g/2g/b3\n92r33vhS+MX5Ofmt+iD6lPsI+3v77/xi/Nb9Sv29/jH+pP8Y/4v//wAAAAAAAAABAAEAAgADAAQA\nBQAHAAgACgAMAA4AEAASABUAGAAbAB4AIQAkACgAKwAvADMANwA8AEAARQBKAE8AVABZAF8AZABq\nAHAAdgB8AIMAiQCQAJcAngClAK0AtAC8AMQAzADUANwA5QDuAPYA/wEIARIBGwElAS8BOQFDAU0B\nVwFiAWwBdwGCAY4BmQGkAbABvAHIAdQB4AHsAfkCBgISAh8CLQI6AkcCVQJjAnECfwKNApsCqgK4\nAscC1gLlAvQDBAMTAyMDMwNDA1MDYwN0A4QDlQOmA7cDyAPZA+oD/AQOBB8EMQREBFYEaAR7BI0E\noASzBMYE2QTtBQAFFAUoBTwFUAVkBXgFjQWhBbYFywXgBfUGCgYgBjUGSwZhBncGjQajBrkG0Abm\nBv0HFAcrB0IHWgdxB4gHoAe4B9AH6AgACBgIMQhJCGIIewiUCK0IxgjgCPkJEwktCUYJYAl7CZUJ\nrwnKCeQJ/woaCjUKUApsCocKowq+CtoK9gsSCy4LSwtnC4QLoAu9C9oL9wwVDDIMTwxtDIsMqQzH\nDOUNAw0iDUANXw1+DZ0NvA3bDfoOGg46DlkOeQ6ZDroO2g76DxsPPA9dD34Pnw/AD+IQAxAlEEcQ\naRCLEK4Q0BDzERYROBFcEX8RohHGEekSDRIxElUSehKeEsIS5xMMEzETVhN7E6ATxhPrFBEUNxRd\nFIMUqRTQFPYVHRVDFWoVkRW4Fd8WBhYuFlUWfRakFswW9BccF0QXbBeVF70X5RgOGDcYXxiIGLEY\n2hkDGSwZVRl/GagZ0Rn7GiQaThp3GqEayxr1Gx4bSBtyG5wbxhvwHBocRBxvHJkcwxztHRcdQh1s\nHZYdwB3qHhUePx5pHpMevh7oHxIfPB9mH5Afuh/kIA4gOCBiIIwgtiDhIQshNSFgIYohtSHgIgoi\nNSJgIositiLhIwwjNyNjI44juSPlJBAkPCRoJJMkvyTrJRclQyVvJZslxyX0JiAmTSZ5JqYm0ib/\nJywnWSeGJ7Mn4CgNKDsoaCiVKMMo8CkeKUwpeimoKdYqBCoyKmAqjyq9KuwrGitJK3grpyvWLAUs\nNCxjLJMswizyLSItUS2BLbEt4S4SLkIuci6jLtMvBC81L2Yvly/IL/kwKzBcMI4wwDDxMSMxVjGI\nMbox7TIfMlIyhTK4MuszHjNSM4UzuTPtNCE0VTSJNL408jUnNVw1kTXGNfs2MTZnNpw20jcJNz83\ndTesN+M4GjhROIg4wDj4OTA5aDmgOdk6ETpKOoM6vTr2OzA7ajukO948GTxTPI48yT0FPUA9fD24\nPfU+MT5uPqs+6D8mP2Q/oj/gQB5AXUCcQNtBGkFZQZlB2EIYQlhCl0LYQxhDWEOZQ9lEGkRbRJxE\n3UUfRWBFokXkRiZGaEaqRuxHL0dyR7RH90g6SH5IwUkFSUhJjEnQShRKWEqdSuFLJktrS7BL9Uw6\nTH9MxU0LTVBNlk3cTiNOaU6wTvZPPU+ET8tQE1BaUKFQ6VExUXlRwVIJUlJSmlLjUyxTdVO+VAdU\nUVSaVORVLlV4VcJWDFZXVqFW7Fc3V4JXzVgZWGRYsFj7WUdZk1nfWixaeFrFWxJbXlurW/lcRlyT\nXOFdL119XcteGV5nXrZfBV9TX6Jf8WBAYJBg32EvYX9hz2IfYm9iv2MQY2FjsWQCZFNkpWT2ZUhl\nmWXrZj1mj2bhZzRnhmfZaCxof2jSaSVpeGnMaiBqdGrHaxxrcGvEbBlsbWzCbRdtbG3CbhdubW7C\nbxhvbm/EcBtwcXDHcR5xdXHMciNyenLScylzgXPZdDF0iXThdTp1knXrdkR2nXb2d093qHgCeFx4\ntXkPeWl5xHoeenl603sue4l75Hw/fJt89n1Sfa5+CX5lfsJ/Hn96f9eANICRgO6BTIGqggiCZoLE\ngyODgoPhhEGEoYUBhWGFwYYihoOG5IdGh6eICYhriM2JMImTifaKWYq8iyCLg4vojEyMsI0VjXqN\n345EjqmPD491j9uQQZCnkQ6RdZHckkOSqpMRk3mT4ZRJlLGVGZWCleqWU5a8lyWXjpf3mGGYy5k0\nmZ6aCJpzmt2bR5uynB2ch5zynV2dyZ40np+fC592n+KgTqC6oSahkqH+omqi1qNDo6+kHKSIpPWl\nYaXOpjumqKcVp4Kn76hcqMmpNqmjqhCqfqrrq1irxawzrKCtDa17reiuVa7CrzCvnbAKsHew5bFS\nsb+yLLKZswazc7PgtE20ubUmtZO1/7Zstti3RbexuB24ibj1uWG5zbo5uqS7ELt7u+a8Uby8vSe9\nkr38vme+0b87v6XADsB4wOLBTMG2wiDCisL1w1/DysQ1xKDFC8V2xeHGTMa3xyPHjsf6yGbI0ck9\nyanKFcqByu3LWsvGzDLMn80LzXjN5c5Rzr7PK8+Y0AXQctDf0UzRudIm0pPTANNt09vUSNS11SPV\nkNX91mvW2NdF17PYINiO2PvZadnW2kPasdse24zb+dxm3NTdQd2u3hveid7232Pf0OA94KrhF+GE\n4fHiXuLK4zfjpOQQ5H3k6eVW5cLmLuaa5wbncufe6Enotekh6Yzp9+pj6s7rOeuj7A7seezj7U7t\nuO4i7ozu9e9f78nwMvCb8QTxbfHV8j7ypvMO83bz3vRG9K31FPV79eL2SPav9xX3e/fh+Eb4q/kQ\n+XX52vo++qL7Bvtp+838MPyT/PX9V/25/hv+fP7d/z7/n///AAAAAAAAAAEAAQACAAMABAAGAAcA\nCQAKAAwADwARABQAFgAZABwAHwAjACYAKgAuADIANgA7AD8ARABJAE4AUwBZAF4AZABqAHAAdgB9\nAIMAigCRAJgAnwCnAK8AtgC+AMYAzwDXAOAA6QDyAPsBBAENARcBIQErATUBPwFJAVQBXwFqAXUB\ngAGLAZcBowGvAbsBxwHTAeAB7AH5AgYCEwIgAi4COwJJAlcCZQJzAoICkAKfAq4CvQLMAtsC6gL6\nAwoDGgMqAzoDSgNaA2sDfAONA54DrwPAA9ID4wP1BAcEGQQrBD0EUARiBHUEiASbBK4EwQTVBOgE\n/AUQBSQFOAVMBWAFdQWJBZ4FswXIBd0F8wYIBh0GMwZJBl8GdQaLBqEGuAbOBuUG/AcTByoHQQdZ\nB3AHiAefB7cHzwfnB/8IGAgwCEkIYQh6CJMIrAjGCN8I+AkSCSwJRQlfCXkJlAmuCcgJ4wn9ChgK\nMwpOCmkKhQqgCrsK1wrzCw8LKwtHC2MLgAucC7kL1QvyDA8MLAxKDGcMhAyiDMAM3gz8DRoNOA1W\nDXUNlA2yDdEN8A4QDi8OTg5uDo4OrQ7NDu0PDg8uD08Pbw+QD7EP0g/zEBUQNhBYEHkQmxC9EN8R\nAREkEUYRaBGLEa4R0BHzEhYSORJdEoASoxLHEusTDhMyE1YTehOeE8MT5xQLFDAUVRR5FJ4UwxTo\nFQ0VMxVYFX0VoxXJFe4WFBY6FmAWhhatFtMW+RcgF0YXbReUF7sX4hgJGDAYVxh/GKYYzhj1GR0Z\nRRltGZUZvRnlGg0aNRpeGoYarxrYGwAbKRtSG3sbpBvOG/ccIBxKHHMcnRzHHPAdGh1EHW4dmB3D\nHe0eFx5CHmwelx7CHu0fFx9CH20fmR/EH+8gGiBGIHEgnSDJIPQhICFMIXghpCHRIf0iKSJWIoIi\nryLbIwgjNSNiI48jvCPpJBckRCRyJJ8kzST6JSglViWEJbIl4SYPJj0mbCaaJskm+CcmJ1UnhCez\nJ+MoEihBKHEooSjQKQApMClgKZApwCnxKiEqUiqCKrMq5CsVK0YrdyuoK9osCyw9LG4soCzSLQQt\nNy1pLZstzi4ALjMuZi6ZLswvAC8zL2cvmi/OMAIwNjBqMJ8w0zEIMTwxcTGmMdsyETJGMnwysjLn\nMx4zVDOKM8Ez9zQuNGU0nDTTNQs1QjV6NbI16jYjNls2lDbNNwY3Pzd4N7I36zglOF84mjjUOQ85\nSjmFOcA5+zo3OnM6rzrrOyg7ZDuhO948HDxZPJc81T0TPVI9kT3QPg8+Tj6OPs4/Dj9OP48/0EAR\nQFJAlEDWQRdBWUGcQd5CIUJjQqZC6UMsQ3BDs0P3RDtEf0TDRQdFTEWRRdZGG0ZgRqVG60cxR3ZH\nvEgDSElIkEjWSR1JZEmrSfNKOkqCSspLEktaS6NL60w0TH1Mxk0PTVhNok3rTjVOf07JTxRPXk+p\nT/RQP1CKUNVRIFFsUbhSBFJQUpxS6FM1U4JTz1QcVGlUtlUEVVFVn1XtVjtWiVbYVyZXdVfEWBNY\nYliyWQFZUVmhWfBaQVqRWuFbMluCW9NcJFx1XMddGF1qXbteDV5fXrFfBF9WX6lf/GBOYKFg9WFI\nYZth72JDYpZi6mM/Y5Nj52Q8ZJBk5WU6ZY9l5GY6Zo9m5Wc7Z5Bn5mg9aJNo6WlAaZZp7WpEaptq\n8mtJa6Fr+GxQbKds/21Xba9uCG5gbrhvEW9qb8JwG3B0cM1xJ3GAcdlyM3KNcuZzQHOac/R0T3Sp\ndQN1XnW4dhN2bnbJdyR3f3faeDV4kXjseUh5pHn/elt6t3sTe297y3wofIR84H09fZp99n5TfrB/\nDX9qf8eAJICBgN+BPYGbgfmCV4K2gxWDdIPThDKEkoTyhVKFsoYThnOG1Ic1h5eH+IhaiLuJHYmA\nieKKRYqniwqLbYvQjDSMl4z7jV+Nw44njoyO8I9Vj7qQH5CEkOqRT5G1khqSgJLmk02Ts5QZlICU\n55VNlbSWG5aDluqXUZe5mCGYiJjwmViZwJoompGa+Zthm8qcMpybnQSdbZ3Wnj+eqJ8Rn3qf46BN\noLahH6GJofKiXKLGoy+jmaQDpGyk1qVApaqmFKZ+puinUae7qCWoj6j5qWOpzao3qqGrC6t0q96s\nSKyyrRutha3vrliuwq8sr5Wv/rBosNGxOrGjsgyydbLes0ezsLQZtIG06rVStbq2IraKtvK3WrfC\nuCm4kLj4uV+5xrotupO6+rtgu8a8LLySvPi9Xb3Cvii+jL7xv1a/usAewILA5sFLwa/CFMJ4wt3D\nQsOnxAzEccTXxTzFocYHxm3G0sc4x57IBMhqyNDJNsmdygPKacrQyzbLncwDzGrM0c04zZ/OBc5s\nztPPOs+h0AjQcNDX0T7RpdIM0nPS29NC06nUEdR41N/VRtWu1hXWfNbk10vXstgZ2IHY6NlP2bba\nHdqE2uvbUtu53CDch9zu3VXdu94i3one799W37zgIuCI4O/hVeG74iHihuLs41Ljt+Qd5ILk5+VM\n5bHmFuZ75t/nROeo6AzocejU6TjpnOn/6mPqxusp64zr7uxR7LPtFe137dnuOu6c7v3vXu+/8B/w\nf/Dg8T/xn/H+8l7yvfMb83rz2PQ29JT08fVP9av2CPZk9sH3HPd499P4LviJ+OP5PfmX+fD6Sfqi\n+vv7U/ur/AL8Wfyw/Qb9XP2y/gf+XP6x/wX/Wf+s//8AAHNmMzIAAAAAAAELtwAABZb///NXAAAH\nKQAA/df///u3///9pgAAA9oAAMD2bW1vZAAAAAAAAFpjAABiKgAAAADNiQqAAAAAAAAAAAAAAAAA\nAAAAAP/uAA5BZG9iZQBkQAAAAAH/2wCEAAICAgICAgICAgIDAgICAwQDAgIDBAUEBAQEBAUGBQUF\nBQUFBgYHBwgHBwYJCQoKCQkMDAwMDAwMDAwMDAwMDAwBAwMDBQQFCQYGCQ0KCQoNDw4ODg4PDwwM\nDAwMDw8MDAwMDAwPDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAP4BcgMBEQACEQED\nEQH/3QAEAC//xAGiAAAABwEBAQEBAAAAAAAAAAAEBQMCBgEABwgJCgsBAAICAwEBAQEBAAAAAAAA\nAAEAAgMEBQYHCAkKCxAAAgEDAwIEAgYHAwQCBgJzAQIDEQQABSESMUFRBhNhInGBFDKRoQcVsUIj\nwVLR4TMWYvAkcoLxJUM0U5KismNzwjVEJ5OjszYXVGR0w9LiCCaDCQoYGYSURUaktFbTVSga8uPz\nxNTk9GV1hZWltcXV5fVmdoaWprbG1ub2N0dXZ3eHl6e3x9fn9zhIWGh4iJiouMjY6PgpOUlZaXmJ\nmam5ydnp+So6SlpqeoqaqrrK2ur6EQACAgECAwUFBAUGBAgDA20BAAIRAwQhEjFBBVETYSIGcYGR\nMqGx8BTB0eEjQhVSYnLxMyQ0Q4IWklMlomOywgdz0jXiRIMXVJMICQoYGSY2RRonZHRVN/Kjs8Mo\nKdPj84SUpLTE1OT0ZXWFlaW1xdXl9UZWZnaGlqa2xtbm9kdXZ3eHl6e3x9fn9zhIWGh4iJiouMjY\n6Pg5SVlpeYmZqbnJ2en5KjpKWmp6ipqqusra6vr/2gAMAwEAAhEDEQA/APoWD2Gwzkrt6JUqwXqB\n4Vwpdv0G/hTBau69QK98IQ3v9OFV67jELTu3jj5K2O46+JxUKg7V3PcfwxSupvT9fbFDdNwe9Opx\nS4AgkeHf54FVB06bdzirdPoxVo9vDFK3iCfl3xQup3798Urt6/Ptiq9R44qvAGFC4L7YFt1N8Vdx\nw0i2+OK2uAwLa0jfrU4aVqmBLdP7RhVoUINNjgVo1rUbj2xVogb/AIYqtI6b/OuFVu/WmJChaQO3\n44pWEffigtU3rXfvioaNa74Vb2ONq0BTptTwxVskH+GKFp3NNz4YLS0fAEfL2xVqnT8MVaIHc74q\n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\n", "text/plain": [ "" ] }, "execution_count": 3, "metadata": { "image/jpeg": { "height": 400 } }, "output_type": "execute_result" } ], "source": [ "Image('example.jpg',height=400)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Displaying a plot with its code" ] }, { "cell_type": "markdown", "metadata": { "ipub": { "caption": "fig:example_mpl" } }, "source": [ "A matplotlib figure, with the caption set in the markdowncell above the figure." ] }, { "cell_type": "markdown", "metadata": { "ipub": { "caption": "code:example_mpl" } }, "source": [ "The plotting code for a matplotlib figure (\\cref{fig:example_mpl})." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "ipub": { "code": { "asfloat": true, "caption": "a", "label": "code:example_mpl", "widefigure": false }, "figure": { "caption": "", "label": "fig:example_mpl", "widefigure": false } } }, "outputs": [ { "data": { "application/pdf": "JVBERi0xLjQKJazcIKu6CjEgMCBvYmoKPDwgL1R5cGUgL0NhdGFsb2cgL1BhZ2VzIDIgMCBSID4+\nCmVuZG9iago4IDAgb2JqCjw8IC9Gb250IDMgMCBSIC9YT2JqZWN0IDcgMCBSIC9FeHRHU3RhdGUg\nNCAwIFIgL1BhdHRlcm4gNSAwIFIKL1NoYWRpbmcgNiAwIFIgL1Byb2NTZXQgWyAvUERGIC9UZXh0\nIC9JbWFnZUIgL0ltYWdlQyAvSW1hZ2VJIF0gPj4KZW5kb2JqCjEwIDAgb2JqCjw8IC9UeXBlIC9Q\nYWdlIC9QYXJlbnQgMiAwIFIgL1Jlc291cmNlcyA4IDAgUgovTWVkaWFCb3ggWyAwIDAgNDU1LjM4\nNDg4NjYzMTIgMjU4LjU2MTE0NjA2NDMgXSAvQ29udGVudHMgOSAwIFIKL0dyb3VwIDw8IC9UeXBl\nIC9Hcm91cCAvUyAvVHJhbnNwYXJlbmN5IC9DUyAvRGV2aWNlUkdCID4+IC9Bbm5vdHMgWyBdID4+\nCmVuZG9iago5IDAgb2JqCjw8IC9MZW5ndGggMTEgMCBSIC9GaWx0ZXIgL0ZsYXRlRGVjb2RlID4+\nCnN0cmVhbQp4nMVYS28cNwy+q39CR/sQmRRFiuotRhoDAXpIa6CHIId047ywTpEHmvbf99Psekfj\nTNLYQJwFFvZ+I1L8KIqP4fgmnNzn+PJDpPgG30/xSXyKv88jx7N48uDi79ebi9/OTuPmQyDgl6Go\nJvHiXvFzO/7M6kmNuRhwWv58FcLbgH0gcwbVL0PQkmgv50mskZWuvZRk1+HtAi6anPb4oGSEsduL\n8C5ePTUTzrO+0opGaZQsZq6plPj+Iv4R38aT+3nyROLMam65aqBUbP8B4VRJ3bNobvH9SxB5g+/s\ns1EyjpJhIfnb2ZdXXt8jvIPbuuc5lpxTzdzYikusIGeFuOVGNW4u48mvFB/8dVh9jy3V0kiFCmlk\neLBlllqtNVlbn70moewEN4pCvhokChCzlfW5teTFpDVIcGSzpF5zViW4d0W/sCYtuboIFkdOnUrJ\nRrymHtqpmZhlV4FwTlhsJA4zV9nWVM06w+we7zWBgzWXolR5zXqcPoxlcqyKZQoNt6Lw+spqdknV\nmyq8Q7nb1rgRHJVl1ZUIt1S54rEVnOI91oztRCust3GDx+FxfHeIu7iLKEo+xhQuDJ4EqymLu/Li\nvgxoSX51XcJp2OtEdIbT83DyECQ0NZxmLqytxPMXQSFrlcCrByFTwpGWapkqHj8PR3Qcz5EeHubP\nRSvumjSvhPiDdX31z/vVKxuVJKKKoBXcNRp0/3IeDncOnAOXluAwcV2QHOEbs2SBsIKW1IZIulua\neZVmFk7GDTG0oDnCN6aJWE7IC6xOyHZ3TLOs0hSW5IxsIAuaI3xjmkIQFgjh5lG7Y5q2TrNZIqKG\ntLOgOcA3p+kQzi1nUxh8xzR9leZcasWRDCuD2GVQQta/hn5zyNbEhacUiYgVSUjF0lBQWv1B+Wfm\n6CjQTSnzguOM3o4jarYZHltFKflByWfmyD1loLxrWZAc4NuxZHQeZE7alOn7H+V68hlomiRiGGRL\nmjN8S5qon6Wq91tq3/8015PP0ABTSYXRCbQFzQG+Jc3myV1q7nH//U/z/5LP1Odn633aguYM344m\nGl3kWFat+MhkCt9F/pmsQ1tSUxO0YXAxunPI7Jra3k62ZIpiRwyOETNVM6/GXnoGuZyGMigMT+LR\ns2N0nsQ4raqcUSri0b+fQ9tnfwLM1gcQUS645kcX2+Nwfd2n1x9frUl/vPjnOD4N5492nvlGu3sY\nYg5paLorj4Yf/XTliRwf7YbQRfu7HEHXh8YvTIHh9/Vx8vKL4yQkbjSWLtfPmr66w8l92XX7j/og\nPM2PnfJ+LEZW8r0c4ohaJel5i80TppniecS3AUNGYnxKG3GuDG/vVyMZo2uytkQRwbu1mzDi0puW\nva0LHOur1p4Phh1R8/fMRvtmdDPSmeFtsIzc63mx2PjgyXm3ETyYBrUzPDPZLuAD7Xmv2UGrXt70\n9wanYeUtwFcn9GlmW3sD8JV3Bzd6AxDQdlzFUpbOcBdjzhjwFeP8CDsmT5NMNoFKijYb06sypu4C\nrKDLUhPM0MHxv0lBN4OpHb5VaIs+x23uPaj0+XgE69VOk4ID7EimxWuNo9aWGHOXl4UFfXjOpeWu\nYDZWwNGygsxAS/hAa3bBDG5CRZJB5kZTPMDV0b+YWh21VoSJWrGlAUDRAvSqMRhbbYXWCA4uGOGD\nu0atB8cOBgxnMNh6OK6B1HCuazGwj1jav1frrwmuqp2kPi80w/ir0xhMugv5/cswKCl0eBuGRIwC\n8vzZx6mGgAlm7dKcCu7PVcUAKVQ8lEhUvqlkIORRBZC5H4f/ACAvHKYKZW5kc3RyZWFtCmVuZG9i\nagoxMSAwIG9iagoxMzQ0CmVuZG9iagoxMyAwIG9iagpbIDYxMSA4MTUgNzYxIDY3OSA2NTIgNzM0\nIDcwNyA3NjEgNzA3IDc2MSA3MDcgNTcxIDU0MyA1NDMgODE1IDgxNSAyNzEgMjk5CjQ4OSA0ODkg\nNDg5IDQ4OSA0ODkgNzM0IDQzNSA0ODkgNzA3IDc2MSA0ODkgODgzIDk5MiA3NjEgMjcxIDI3MSA0\nODkgODE1IDQ4OQo4MTUgNzYxIDI3MSAzODAgMzgwIDQ4OSA3NjEgMjcxIDMyNiAyNzEgNDg5IDQ4\nOSA0ODkgNDg5IDQ4OSA0ODkgNDg5IDQ4OSA0ODkKNDg5IDQ4OSAyNzEgMjcxIDI3MSA3NjEgNDYy\nIDQ2MiA3NjEgNzM0IDY5MyA3MDcgNzQ3IDY2NiA2MzggNzY4IDczNCAzNTMgNTAzCjc2MSA2MTEg\nODk3IDczNCA3NjEgNjY2IDc2MSA3MjAgNTQzIDcwNyA3MzQgNzM0IDEwMDYgNzM0IDczNCA1OTgg\nMjcxIDQ4OQoyNzEgNDg5IDI3MSAyNzEgNDg5IDU0MyA0MzUgNTQzIDQzNSAyOTkgNDg5IDU0MyAy\nNzEgMjk5IDUxNiAyNzEgODE1IDU0MyA0ODkKNTQzIDUxNiAzODAgMzg2IDM4MCA1NDMgNTE2IDcw\nNyA1MTYgNTE2IDQzNSA0ODkgOTc5IDQ4OSA0ODkgNDg5IF0KZW5kb2JqCjE1IDAgb2JqCjw8IC9U\neXBlIC9Gb250RGVzY3JpcHRvciAvRm9udE5hbWUgL0NNUjEyIC9GbGFncyA0Ci9Gb250QkJveCBb\nIC0zNCAtMjUxIDk4OCA3NTAgXSAvSXRhbGljQW5nbGUgMCAvQXNjZW50IDc1MCAvRGVzY2VudCAt\nMjUxCi9DYXBIZWlnaHQgMTAwMCAvWEhlaWdodCA1MDAgL0ZvbnRGaWxlIDE2IDAgUiAvRm9udEZh\nbWlseSAoQ01SMTIpIC9TdGVtViA1MAo+PgplbmRvYmoKMTYgMCBvYmoKPDwgL0xlbmd0aDEgNDI4\nNiAvTGVuZ3RoMiAyNzg3MSAvTGVuZ3RoMyAwIC9MZW5ndGggMzAwMDIKL0ZpbHRlciAvRmxhdGVE\nZWNvZGUgPj4Kc3RyZWFtCnicjLcFUBza0jUKAQLBJcFl0ODuENzdPcAAg7u7BwgOwSVAgru7u0MC\n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\nIDAgUiA+PgplbmRvYmoKMTcgMCBvYmoKWyA2MDYgODE1IDc0OCA2NzkgNzI4IDgxMSA3NjUgNTcx\nIDY1MiA1OTggNzU3IDYyMiA1NTIgNTA3IDQzMyAzOTUgNDI3IDQ4Mwo0NTYgMzQ2IDU2MyA1NzEg\nNTg5IDQ4MyA0MjcgNTU1IDUwNSA1NTYgNDI1IDUyNyA1NzkgNjEzIDYzNiA2MDkgNDU4IDU3NyA4\nMDgKNTA1IDM1NCA2NDEgOTc5IDk3OSA5NzkgOTc5IDI3MSAyNzEgNDg5IDQ4OSA0ODkgNDg5IDQ4\nOSA0ODkgNDg5IDQ4OSA0ODkgNDg5CjQ4OSA0ODkgMjcxIDI3MSA3NjEgNDg5IDc2MSA0ODkgNTE2\nIDczNCA3NDMgNzAwIDgxMiA3MjQgNjMzIDc3MiA4MTEgNDMxIDU0MQo4MzMgNjY2IDk0NyA3ODQg\nNzQ4IDYzMSA3NzUgNzQ1IDYwMiA1NzMgNjY1IDU3MCA5MjQgODEyIDU2OCA2NzAgMzgwIDM4MCAz\nODAKOTc5IDk3OSA0MTAgNTEzIDQxNiA0MjEgNTA4IDQ1MyA0ODIgNDY4IDU2MyAzMzQgNDA1IDUw\nOSAyOTEgODU2IDU4NCA0NzAgNDkxCjQzNCA0NDEgNDYxIDM1MyA1NTcgNDczIDY5OSA1NTYgNDc3\nIDQ1NCAzMTIgMzc3IDYyMyA0ODkgMjcxIF0KZW5kb2JqCjE5IDAgb2JqCjw8IC9UeXBlIC9Gb250\nRGVzY3JpcHRvciAvRm9udE5hbWUgL0NNTUkxMiAvRmxhZ3MgNjgKL0ZvbnRCQm94IFsgLTMxIC0y\nNTAgMTAyNiA3NTAgXSAvSXRhbGljQW5nbGUgLTE0LjA0IC9Bc2NlbnQgNzUwCi9EZXNjZW50IC0y\nNTAgL0NhcEhlaWdodCAxMDAwIC9YSGVpZ2h0IDUwMCAvRm9udEZpbGUgMjAgMCBSCi9Gb250RmFt\naWx5IChDTU1JMTIpIC9TdGVtViA1MCA+PgplbmRvYmoKMjAgMCBvYmoKPDwgL0xlbmd0aDEgNDMy\nMyAvTGVuZ3RoMiAzMTg1MyAvTGVuZ3RoMyAwIC9MZW5ndGggMzM5NjQKL0ZpbHRlciAvRmxhdGVE\nZWNvZGUgPj4Kc3RyZWFtCnicjLcFVBRa9z5MSkqHgMIg3QwNgnR3dzM0DN3dUkpJN0hKdyvd3Y2E\n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\n80goEYrLqftqpk8h94Ow3baLUOBeA/384pssyNALlJzo0SZ4X4rroLMNPV8Nnvq1Z2HIsWvJwjCX\nh8Tv8hdAGzYxazTt62GCbv49ZZ6eR7xdttlyT28TABX7h1+CXZORHKBl4UPHbPrQEH7Db5eV/wsA\n3U37SkchfVHMIsKZgO0wMLrx04UzbkzwBv0MJhAcbHx+eHxpjoYzlCbc2AT0VyZ2JqWzEIz+z2xY\n6NpGTl/WTpAKZW5kc3RyZWFtCmVuZG9iagoxOCAwIG9iago8PCAvVHlwZSAvRm9udCAvU3VidHlw\nZSAvVHlwZTEgL0ZpcnN0Q2hhciAwIC9MYXN0Q2hhciAxMjcgL1dpZHRocyAxNyAwIFIKL0Jhc2VG\nb250IC9DTU1JMTIgL0ZvbnREZXNjcmlwdG9yIDE5IDAgUiA+PgplbmRvYmoKMjEgMCBvYmoKWyA1\nNzUgNzcyIDcxOSA2NDEgNjE1IDY5MyA2NjcgNzE5IDY2NyA3MTkgNjY3IDUyNSA0OTkgNDk5IDc0\nOCA3NDggMjQ5IDI3NQo0NTggNDU4IDQ1OCA0NTggNDU4IDY5MyA0MDYgNDU4IDY2NyA3MTkgNDU4\nIDgzNyA5NDEgNzE5IDI0OSAyNDkgNDU4IDc3MiA0NTgKNzcyIDcxOSAyNDkgMzU0IDM1NCA0NTgg\nNzE5IDI0OSAzMDEgMjQ5IDQ1OCA0NTggNDU4IDQ1OCA0NTggNDU4IDQ1OCA0NTggNDU4CjQ1OCA0\nNTggMjQ5IDI0OSAyNDkgNzE5IDQzMiA0MzIgNzE5IDY5MyA2NTQgNjY3IDcwNiA2MjggNjAyIDcy\nNiA2OTMgMzI3IDQ3MQo3MTkgNTc1IDg1MCA2OTMgNzE5IDYyOCA3MTkgNjgwIDUxMCA2NjcgNjkz\nIDY5MyA5NTQgNjkzIDY5MyA1NjMgMjQ5IDQ1OCAyNDkKNDU4IDI0OSAyNDkgNDU4IDUxMCA0MDYg\nNTEwIDQwNiAyNzUgNDU4IDUxMCAyNDkgMjc1IDQ4NCAyNDkgNzcyIDUxMCA0NTggNTEwCjQ4NCAz\nNTQgMzU5IDM1NCA1MTAgNDg0IDY2NyA0ODQgNDg0IDQwNiA0NTggOTE3IDQ1OCA0NTggNDU4IF0K\nZW5kb2JqCjIzIDAgb2JqCjw8IC9UeXBlIC9Gb250RGVzY3JpcHRvciAvRm9udE5hbWUgL0NNUjE3\nIC9GbGFncyA0Ci9Gb250QkJveCBbIC0zMyAtMjUwIDk0NSA3NDkgXSAvSXRhbGljQW5nbGUgMCAv\nQXNjZW50IDc0OSAvRGVzY2VudCAtMjUwCi9DYXBIZWlnaHQgMTAwMCAvWEhlaWdodCA1MDAgL0Zv\nbnRGaWxlIDI0IDAgUiAvRm9udEZhbWlseSAoQ01SMTcpIC9TdGVtViA1MAo+PgplbmRvYmoKMjQg\nMCBvYmoKPDwgL0xlbmd0aDEgNDI4NiAvTGVuZ3RoMiAyNzUxMSAvTGVuZ3RoMyAwIC9MZW5ndGgg\nMjk2MzQKL0ZpbHRlciAvRmxhdGVEZWNvZGUgPj4Kc3RyZWFtCnicjLcFVNXb1j4snQLSSG26u5GU\nDumS3sCm2XQ30t0oISFISLektHSDgHR3I5L/zbnnHvDcd4zvG4wBPHPVM9ea85nzR0WmpMokamJr\nBJK0BTsxsTGz8gPE3qiw8QBYWTmYWVnZkamo1CycrEF/m5GpNEAOjha2YP4nE8QcQEAniE0c6ASZ\n98YWDJB1tgawcQDYuPnZePhZWQHsrKx8/51o68APEAe6WJgA3jADZG3BIEdkKjFbO3cHCzNzJ8gx\n//0XQGtMB2Dj4+Nh/Gs5QNQG5GBhDAQD3gCdzEE2kBONgdYAVVtjC5CT+x9bAGgFzJ2c7PhZWFxd\nXZmBNo7Mtg5mQnSMAFcLJ3OACsgR5OACMgE8eAxQANqA/uMaMzIVQM3cwvE/dlVbUydXoAMIADFY\nWxiDwI6QFc5gE5ADAHI6QFVGHqBoBwL/Z7L8fyYwAv6+HAAbM9s/2/29+mEjC/Bfi4HGxrY2dkCw\nuwXYDGBqYQ0CKErKMzu5OTECgGCTh4lAa0dbyHqgC9DCGmgEmfAXcyBAUlQZAIR4+Ld7jsYOFnZO\njsyOFtYPLrI8bAO5ZgmwiZitjQ0I7OSIjPxAUNzCAWQMuXh3lv+8rRXY1hXs+TcwtQCbmD54YeJs\nx6IOtrB3BsmI/z0FYkJ+tJmBnABcrKysPHxcAJA9AORmbM7ysL2aux3or0G2BzPEBW9PO1s7gCnE\nC5C3hSkI8gfZ0xHoAgI4OTiDvD2fDvyJkNnYACYWxk4AI5CZBRj5cXeIGWT6Hwx5fgcLN4AOKyT6\n2ACsDz///KcHCTATW7C1++P0vx74b1f/Mb5+besG8GTi4AAwsXOxAvg4uQA8nHwA7z+X/+P4f53+\n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\nGGGry/WfUTu6kOdBjMcjIh0rTWBfdF5xUiyOinOITgFdgx0n31nSPj8UwNYsx+sD45PLzbBgDqUQ\nJnX7Sdp9URafbqnTbhDEcZRW+VtBXHBQhIthwAtQJ5emVADRPOmvDbIfgxxx9eVSGs+EpjJccoKm\nhI2p9yzk1MeSpHankL5wYH2u0hVoQGX1hS/q9S2CpJEzo2YAci2zqPZi2zbXHOWAvOsUwtNvfU16\niYkd3LEo6cSilzhqWTDxch9CSTg/WZgVUK3zSgso4w5PDE/mjmDfKGRx11lvkzURzaRMWjiWMu7G\nN13nJ+PkqOcAgvDAC0RbSDMInnDLS+F5n4Qpgb0TPAq+RUXSL9YAKuV5yMxIek4oM249a39Lr2FR\nb1K80nu74Khh2uXdqoKvqvYtKNmOSMhxrTG/UqHTwqAxW1rB9vkMmkU0TKAAgD/q2eYbWxHVsWxk\nl/pddVCahemMnuxkZLKs1/Evyf3L3q/bo7RGXYQ6FgaYPXLFWk2+5mkFcrOIaurq6bxt//VnAl22\n7bpbUiQsy8vLFcW9L4WyurwYshfVhwe7XPppvE+amUAD0DB2aIDVgAZT9FHH6Y1kRrALYEMgy9OG\n9Gj04FGEkoW+0I/4VFqvEBlTqNllG4k67tEnJEDLTRSHUtjDmtCV4L2VMNXzUlK06WljTSPwlh3u\ncvbUQrJs4KM/LrWbucySzAsS/zCt1c0m762FZPsmitmLaDUvR528arAWtjJq8bZIr2JlWeqFk1OF\njzk/tc+jQ3szLpBCZvyy+WBOtFANuXSRpJ8ipvyQRIOBkfw+482fW6cRc3veymLimCMdgyh5XChq\nYhl61DOkHUwITNd+O6MSUPohZR881MREN7CDwZq5QrFqpCHttnchzm5BNpJSBe3uYXFFk0SRJFPz\nru7wVuJjLOmW42Z4jDed9CZWjxrTmctYV1UM+itAowplbmRzdHJlYW0KZW5kb2JqCjIyIDAgb2Jq\nCjw8IC9UeXBlIC9Gb250IC9TdWJ0eXBlIC9UeXBlMSAvRmlyc3RDaGFyIDAgL0xhc3RDaGFyIDEy\nNyAvV2lkdGhzIDIxIDAgUgovQmFzZUZvbnQgL0NNUjE3IC9Gb250RGVzY3JpcHRvciAyMyAwIFIg\nPj4KZW5kb2JqCjMgMCBvYmoKPDwgL0YxIDE0IDAgUiAvRjIgMTggMCBSIC9GMyAyMiAwIFIgPj4K\nZW5kb2JqCjQgMCBvYmoKPDwgL0ExIDw8IC9UeXBlIC9FeHRHU3RhdGUgL0NBIDAgL2NhIDEgPj4K\nL0EyIDw8IC9UeXBlIC9FeHRHU3RhdGUgL0NBIDEgL2NhIDEgPj4KL0EzIDw8IC9UeXBlIC9FeHRH\nU3RhdGUgL0NBIDAuOCAvY2EgMC44ID4+ID4+CmVuZG9iago1IDAgb2JqCjw8ID4+CmVuZG9iago2\nIDAgb2JqCjw8ID4+CmVuZG9iago3IDAgb2JqCjw8IC9NMCAxMiAwIFIgPj4KZW5kb2JqCjEyIDAg\nb2JqCjw8IC9UeXBlIC9YT2JqZWN0IC9TdWJ0eXBlIC9Gb3JtIC9CQm94IFsgLTQuNSAtNC41IDQu\nNSA0LjUgXSAvTGVuZ3RoIDEzMgovRmlsdGVyIC9GbGF0ZURlY29kZSA+PgpzdHJlYW0KeJxtkDEO\nxDAIBHtewQfWwjY+kzZlvpEmipT/t+foTogobpCB9bCQ+SThjUaA8kU5yUcslzsrSbrVvDBqat1a\nbaNkxbR0hr928i78hzKcpCxDpOyFQPvLHRao3nT5g6hDEmfgrccMi/d0BG/4uUVcAJMFMb0EJifD\nk43b+EG00hcJRUdiCmVuZHN0cmVhbQplbmRvYmoKMiAwIG9iago8PCAvVHlwZSAvUGFnZXMgL0tp\nZHMgWyAxMCAwIFIgXSAvQ291bnQgMSA+PgplbmRvYmoKMjUgMCBvYmoKPDwgL0NyZWF0b3IgKG1h\ndHBsb3RsaWIgMi4wLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZykKL1Byb2R1Y2VyIChtYXRwbG90\nbGliIHBkZiBiYWNrZW5kKSAvQ3JlYXRpb25EYXRlIChEOjIwMTcwNzIwMTIzMDE2KzAxJzAwJykK\nPj4KZW5kb2JqCnhyZWYKMCAyNgowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwMTYgMDAwMDAg\nbiAKMDAwMDA5ODk4MiAwMDAwMCBuIAowMDAwMDk4NDQ5IDAwMDAwIG4gCjAwMDAwOTg1MDMgMDAw\nMDAgbiAKMDAwMDA5ODY0NSAwMDAwMCBuIAowMDAwMDk4NjY2IDAwMDAwIG4gCjAwMDAwOTg2ODcg\nMDAwMDAgbiAKMDAwMDAwMDA2NSAwMDAwMCBuIAowMDAwMDAwNDA3IDAwMDAwIG4gCjAwMDAwMDAy\nMDggMDAwMDAgbiAKMDAwMDAwMTgyNiAwMDAwMCBuIAowMDAwMDk4NzE5IDAwMDAwIG4gCjAwMDAw\nMDE4NDcgMDAwMDAgbiAKMDAwMDAzMjcxNSAwMDAwMCBuIAowMDAwMDAyMzgwIDAwMDAwIG4gCjAw\nMDAwMDI1OTggMDAwMDAgbiAKMDAwMDAzMjg0NyAwMDAwMCBuIAowMDAwMDY3Njg1IDAwMDAwIG4g\nCjAwMDAwMzMzNzkgMDAwMDAgbiAKMDAwMDAzMzYwNiAwMDAwMCBuIAowMDAwMDY3ODE4IDAwMDAw\nIG4gCjAwMDAwOTgzMTcgMDAwMDAgbiAKMDAwMDA2ODM1MCAwMDAwMCBuIAowMDAwMDY4NTY4IDAw\nMDAwIG4gCjAwMDAwOTkwNDIgMDAwMDAgbiAKdHJhaWxlcgo8PCAvU2l6ZSAyNiAvUm9vdCAxIDAg\nUiAvSW5mbyAyNSAwIFIgPj4Kc3RhcnR4cmVmCjk5MTkwCiUlRU9GCg==\n", "image/svg+xml": [ "\n", "\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.scatter(np.random.rand(10), np.random.rand(10), \n", " label='data label')\n", "plt.ylabel(r'a y label with latex $\\alpha$')\n", "plt.legend();" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Tables (with pandas)" ] }, { "cell_type": "markdown", "metadata": { "ipub": { "caption": "code:example_pd" } }, "source": [ "The plotting code for a pandas Dataframe table (\\cref{tbl:example})." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "ipub": { "code": { "asfloat": true, "caption": "", "label": "code:example_pd", "placement": "H", "widefigure": false }, "table": { "alternate": "gray!20", "caption": "An example of a table created with pandas dataframe.", "label": "tbl:example", "placement": "H" } } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
abcd
0$\\delta$l0.5830.279
1xm0.9140.021
2yn0.3330.116
\n", "
" ], "text/latex": [ "\\begin{tabular}{lllrr}\n", "\\toprule\n", "{} & a & b & c & d \\\\\n", "\\midrule\n", "0 & $\\delta$ & l & 0.583 & 0.279 \\\\\n", "1 & x & m & 0.914 & 0.021 \\\\\n", "2 & y & n & 0.333 & 0.116 \\\\\n", "\\bottomrule\n", "\\end{tabular}\n" ], "text/plain": [ " a b c d\n", "0 $\\delta$ l 0.583 0.279\n", "1 x m 0.914 0.021\n", "2 y n 0.333 0.116" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df = pd.DataFrame(np.random.rand(3,4),columns=['a','b','c','d'])\n", "df.a = ['$\\delta$','x','y']\n", "df.b = ['l','m','n']\n", "df.set_index(['a','b'])\n", "df.round(3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Equations (with ipython or sympy)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "ipub": { "equation": { "label": "eqn:example_ipy" } } }, "outputs": [ { "data": { "text/latex": [ "$$ a = b+c $$" ], "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Latex('$$ a = b+c $$')" ] }, { "cell_type": "markdown", "metadata": { "ipub": { "caption": "code:example_sym" } }, "source": [ "The plotting code for a sympy equation (=@eqn:example_sympy)." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "ipub": { "code": { "asfloat": true, "caption": "", "label": "code:example_sym", "placement": "H", "widefigure": false }, "equation": { "environment": "equation", "label": "eqn:example_sympy" } } }, "outputs": [ { "data": { "image/png": "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\nm+MAAAAASUVORK5CYII=\n", "text/latex": [ "$$\\left(\\sqrt{5} i\\right)^{\\alpha} \\left(\\frac{1}{2} - \\frac{2 i}{5} \\sqrt{5}\\right) + \\left(- \\sqrt{5} i\\right)^{\\alpha} \\left(\\frac{1}{2} + \\frac{2 i}{5} \\sqrt{5}\\right)$$" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "y = sym.Function('y')\n", "n = sym.symbols(r'\\alpha')\n", "f = y(n)-2*y(n-1/sym.pi)-5*y(n-2)\n", "sym.rsolve(f,y(n),[1,4])" ] } ], "metadata": { "celltoolbar": "Edit Metadata", "hide_input": false, "ipub": { "bibliography": "example.bib", "biboptions": [ "super", "sort" ], "bibstyle": "unsrtnat", "language": "portuges", "listcode": true, "listfigures": true, "listtables": true, "titlepage": { "author": "Authors Name", "email": "authors@email.com", "institution": [ "Institution1", "Institution2" ], "logo": "logo_example.png", "subtitle": "Sub-Title", "supervisors": [ "First Supervisor", "Second Supervisor" ], "tagline": "A tagline for the report.", "title": "Main-Title" }, "toc": {"depth": 2}, "pandoc": { "use_numref": true, "at_notation": true }, "sphinx": { "bib_title": "My Bibliography" } }, "jupytext": { "metadata_filter": { "notebook": "ipub" } }, "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.6.2" }, "latex_envs": { "LaTeX_envs_menu_present": true, "autocomplete": true, "bibliofile": "example.bib", "cite_by": "apalike", "current_citInitial": 1, "eqLabelWithNumbers": true, "eqNumInitial": 1, "hotkeys": { "equation": "Ctrl-E", "itemize": "Ctrl-I" }, "labels_anchors": false, "latex_user_defs": false, "report_style_numbering": false, "user_envs_cfg": true }, "nav_menu": {}, "toc": { "colors": { "hover_highlight": "#DAA520", "navigate_num": "#000000", "navigate_text": "#333333", "running_highlight": "#FF0000", "selected_highlight": "#FFD700", "sidebar_border": "#EEEEEE", "wrapper_background": "#FFFFFF" }, "moveMenuLeft": true, "nav_menu": { "height": "161px", "width": "252px" }, "navigate_menu": true, "number_sections": true, "sideBar": true, "threshold": 4, "toc_cell": false, "toc_section_display": "block", "toc_window_display": true, "widenNotebook": false }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 2 } jupyter-cache-1.0.0/tests/notebooks/complex_outputs_unrun.ipynb000066400000000000000000000212321452276675600252100ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "init_cell": true, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import pandas as pd\n", "pd.set_option('display.latex.repr', True)\n", "import sympy as sym\n", "sym.init_printing(use_latex=True)\n", "import numpy as np\n", "from IPython.display import Image, Latex" ] }, { "cell_type": "markdown", "metadata": { "ipub": { "slide": true } }, "source": [ "# Markdown" ] }, { "cell_type": "markdown", "metadata": { "ipub": { "slide": true } }, "source": [ "## General" ] }, { "cell_type": "markdown", "metadata": { "ipub": { "slide": true } }, "source": [ "Some markdown text.\n", "\n", "A list:\n", "\n", "- something\n", "- something else\n", "\n", "A numbered list\n", "\n", "1. something\n", "2. something else\n", "\n", "non-ascii characters TODO" ] }, { "cell_type": "markdown", "metadata": { "ipub": {} }, "source": [ "This is a long section of text, which we only want in a document (not a presentation)\n", "some text\n", "some more text\n", "some more text\n", "some more text\n", "some more text\n", "some more text\n", "some more text\n", "some more text\n", "some more text\n" ] }, { "cell_type": "markdown", "metadata": { "ipub": { "slide": true, "slideonly": true } }, "source": [ "This is an abbreviated section of the document text, which we only want in a presentation\n", "\n", "- summary of document text" ] }, { "cell_type": "markdown", "metadata": { "ipub": { "slide": true } }, "source": [ "## References and Citations" ] }, { "cell_type": "markdown", "metadata": { "ipub": { "slide": true } }, "source": [ "References to \\cref{fig:example}, \\cref{tbl:example}, =@eqn:example_sympy and \\cref{code:example_mpl}.\n", "\n", "A latex citation.\\cite{zelenyak_molecular_2016}\n", "\n", "A html citation.(Kirkeminde, 2012) " ] }, { "cell_type": "markdown", "metadata": { "ipub": { "slide": true } }, "source": [ "## Todo notes" ] }, { "cell_type": "markdown", "metadata": { "ipub": { "slide": true } }, "source": [ "\\todo[inline]{an inline todo}\n", "\n", "Some text.\\todo{a todo in the margins}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Text Output" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ipub": { "text": { "format": { "backgroundcolor": "\\color{blue!10}" } } } }, "outputs": [], "source": [ "print(\"\"\"\n", "This is some printed text,\n", "with a nicely formatted output.\n", "\"\"\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Images and Figures" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Displaying a plot with its code" ] }, { "cell_type": "markdown", "metadata": { "ipub": { "caption": "fig:example_mpl" } }, "source": [ "A matplotlib figure, with the caption set in the markdowncell above the figure." ] }, { "cell_type": "markdown", "metadata": { "ipub": { "caption": "code:example_mpl" } }, "source": [ "The plotting code for a matplotlib figure (\\cref{fig:example_mpl})." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Tables (with pandas)" ] }, { "cell_type": "markdown", "metadata": { "ipub": { "caption": "code:example_pd" } }, "source": [ "The plotting code for a pandas Dataframe table (\\cref{tbl:example})." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ipub": { "code": { "asfloat": true, "caption": "", "label": "code:example_pd", "placement": "H", "widefigure": false }, "table": { "alternate": "gray!20", "caption": "An example of a table created with pandas dataframe.", "label": "tbl:example", "placement": "H" } } }, "outputs": [], "source": [ "np.random.seed(0) \n", "df = pd.DataFrame(np.random.rand(3,4),columns=['a','b','c','d'])\n", "df.a = ['$\\delta$','x','y']\n", "df.b = ['l','m','n']\n", "df.set_index(['a','b'])\n", "df.round(3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Equations (with ipython or sympy)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ipub": { "equation": { "label": "eqn:example_ipy" } } }, "outputs": [], "source": [ "Latex('$$ a = b+c $$')" ] }, { "cell_type": "markdown", "metadata": { "ipub": { "caption": "code:example_sym" } }, "source": [ "The plotting code for a sympy equation (=@eqn:example_sympy)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ipub": { "code": { "asfloat": true, "caption": "", "label": "code:example_sym", "placement": "H", "widefigure": false }, "equation": { "environment": "equation", "label": "eqn:example_sympy" } } }, "outputs": [], "source": [ "y = sym.Function('y')\n", "n = sym.symbols(r'\\alpha')\n", "f = y(n)-2*y(n-1/sym.pi)-5*y(n-2)\n", "sym.rsolve(f,y(n),[1,4])" ] } ], "metadata": { "celltoolbar": "Edit Metadata", "hide_input": false, "ipub": { "bibliography": "example.bib", "biboptions": [ "super", "sort" ], "bibstyle": "unsrtnat", "language": "portuges", "listcode": true, "listfigures": true, "listtables": true, "pandoc": { "at_notation": true, "use_numref": true }, "sphinx": { "bib_title": "My Bibliography" }, "titlepage": { "author": "Authors Name", "email": "authors@email.com", "institution": [ "Institution1", "Institution2" ], "logo": "logo_example.png", "subtitle": "Sub-Title", "supervisors": [ "First Supervisor", "Second Supervisor" ], "tagline": "A tagline for the report.", "title": "Main-Title" }, "toc": { "depth": 2 } }, "jupytext": { "notebook_metadata_filter": "ipub" }, "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.6.10" }, "latex_envs": { "LaTeX_envs_menu_present": true, "autocomplete": true, "bibliofile": "example.bib", "cite_by": "apalike", "current_citInitial": 1, "eqLabelWithNumbers": true, "eqNumInitial": 1, "hotkeys": { "equation": "Ctrl-E", "itemize": "Ctrl-I" }, "labels_anchors": false, "latex_user_defs": false, "report_style_numbering": false, "user_envs_cfg": true }, "nav_menu": {}, "toc": { "colors": { "hover_highlight": "#DAA520", "navigate_num": "#000000", "navigate_text": "#333333", "running_highlight": "#FF0000", "selected_highlight": "#FFD700", "sidebar_border": "#EEEEEE", "wrapper_background": "#FFFFFF" }, "moveMenuLeft": true, "nav_menu": { "height": "161px", "width": "252px" }, "navigate_menu": true, "number_sections": true, "sideBar": true, "threshold": 4, "toc_cell": false, "toc_section_display": "block", "toc_window_display": true, "widenNotebook": false }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 2 } jupyter-cache-1.0.0/tests/notebooks/external_output.ipynb000066400000000000000000000020561452276675600237540ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# a title\n", "\n", "some text\n" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "source": [ "with open('artifact.txt', 'w', encoding='utf-8') as handle:\n", " handle.write('hi')" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n" ] } ] } ], "metadata": { "test_name": "notebook1", "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.7.6-final" } }, "nbformat": 4, "nbformat_minor": 2 } jupyter-cache-1.0.0/tests/notebooks/sleep_2.ipynb000066400000000000000000000012671452276675600220460ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import time\n", "time.sleep(2)" ] } ], "metadata": { "celltoolbar": "Edit Metadata", "hide_input": false, "execution": {}, "jupytext": {}, "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.6.10" } }, "nbformat": 4, "nbformat_minor": 2 } jupyter-cache-1.0.0/tests/notebooks/sleep_2_timeout_1.ipynb000066400000000000000000000013471452276675600240330ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "init_cell": true }, "outputs": [], "source": [ "import time\n", "time.sleep(2)" ] } ], "metadata": { "celltoolbar": "Edit Metadata", "hide_input": false, "execution": { "timeout": 1 }, "jupytext": {}, "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.6.10" } }, "nbformat": 4, "nbformat_minor": 2 } jupyter-cache-1.0.0/tests/test_cache.py000066400000000000000000000261561452276675600201270ustar00rootroot00000000000000import os import re import shutil from textwrap import dedent import nbformat as nbf import pytest from jupyter_cache import __version__ from jupyter_cache.base import NbValidityError from jupyter_cache.cache.main import JupyterCacheBase NB_PATH = os.path.join(os.path.realpath(os.path.dirname(__file__)), "notebooks") ANSI_REGEX = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") def test_get_version(tmp_path): cache = JupyterCacheBase(str(tmp_path)) cache.db assert cache.get_version() == __version__ def test_basic_workflow(tmp_path): cache = JupyterCacheBase(str(tmp_path)) with pytest.raises(NbValidityError): cache.cache_notebook_file(path=os.path.join(NB_PATH, "basic.ipynb")) cache.cache_notebook_file( path=os.path.join(NB_PATH, "basic.ipynb"), uri="basic.ipynb", check_validity=False, ) assert cache.list_cache_records()[0].uri == "basic.ipynb" pk = cache.match_cache_file(path=os.path.join(NB_PATH, "basic.ipynb")).pk nb_bundle = cache.get_cache_bundle(pk) assert nb_bundle.nb.metadata["kernelspec"] == { "display_name": "Python 3", "language": "python", "name": "python3", } assert set(nb_bundle.record.to_dict().keys()) == { "pk", "hashkey", "uri", "data", "created", "accessed", "description", } # assert cache.get_cache_codecell(pk, 0).source == "a=1\nprint(a)" path = os.path.join(NB_PATH, "basic_failing.ipynb") diff = cache.diff_nbfile_with_cache(pk, path, as_str=True, use_color=False) assert diff == dedent( f"""\ nbdiff --- cached pk=1 +++ other: {path} ## inserted before nb/cells/0: + code cell: + source: + raise Exception('oopsie!') ## deleted nb/cells/0: - code cell: - execution_count: 2 - source: - a=1 - print(a) - outputs: - output 0: - output_type: stream - name: stdout - text: - 1 """ ) cache.remove_cache(pk) assert cache.list_cache_records() == [] cache.cache_notebook_file( path=os.path.join(NB_PATH, "basic.ipynb"), uri="basic.ipynb", check_validity=False, ) with pytest.raises(ValueError): cache.add_nb_to_project(os.path.join(NB_PATH, "basic.ipynb"), assets=[""]) cache.add_nb_to_project( os.path.join(NB_PATH, "basic.ipynb"), assets=[os.path.join(NB_PATH, "basic.ipynb")], ) assert [r.pk for r in cache.list_project_records()] == [1] assert [r.pk for r in cache.list_unexecuted()] == [] cache.add_nb_to_project(os.path.join(NB_PATH, "basic_failing.ipynb")) assert [r.pk for r in cache.list_project_records()] == [1, 2] assert [r.pk for r in cache.list_unexecuted()] == [2] bundle = cache.get_project_notebook(os.path.join(NB_PATH, "basic_failing.ipynb")) assert bundle.nb.metadata cache.clear_cache() assert cache.list_cache_records() == [] def test_v4_2_to_v4_5(tmp_path): """Test that caching a v4.2 notebook can be recovered, if the notebook is updated to v4.5 (adding cell ids). """ cache = JupyterCacheBase(str(tmp_path)) cache_record = cache.cache_notebook_file( path=os.path.join(NB_PATH, "basic.ipynb"), uri="basic.ipynb", check_validity=False, ) (pk, nb) = cache.merge_match_into_notebook( nbf.read(os.path.join(NB_PATH, "basic_v4-5.ipynb"), nbf.NO_CONVERT) ) assert cache_record.pk == pk assert nb.nbformat_minor == 5, nb assert "id" in nb.cells[1], nb def test_v4_5_to_v4_2(tmp_path): """Test that caching a v4.5 notebook can be recovered, if the notebook is downgraded to v4.2 (removing cell ids). """ cache = JupyterCacheBase(str(tmp_path)) cache_record = cache.cache_notebook_file( path=os.path.join(NB_PATH, "basic_v4-5.ipynb"), uri="basic_v4-5.ipynb", check_validity=False, ) (pk, nb) = cache.merge_match_into_notebook( nbf.read(os.path.join(NB_PATH, "basic.ipynb"), nbf.NO_CONVERT) ) assert cache_record.pk == pk assert nb.nbformat_minor == 2, nb assert "id" not in nb.cells[1], nb def test_merge_match_into_notebook(tmp_path): cache = JupyterCacheBase(str(tmp_path)) cache.cache_notebook_file( path=os.path.join(NB_PATH, "basic.ipynb"), check_validity=False ) nb = nbf.read(os.path.join(NB_PATH, "basic_unrun.ipynb"), 4) pk, merged = cache.merge_match_into_notebook(nb) assert merged.cells[1] == { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": "1\n"}], "source": "a=1\nprint(a)", } def test_artifacts(tmp_path): cache = JupyterCacheBase(str(tmp_path)) with pytest.raises(IOError): cache.cache_notebook_file( path=os.path.join(NB_PATH, "basic.ipynb"), uri="basic.ipynb", artifacts=(os.path.join(NB_PATH),), check_validity=False, ) cache.cache_notebook_file( path=os.path.join(NB_PATH, "basic.ipynb"), uri="basic.ipynb", artifacts=(os.path.join(NB_PATH, "artifact_folder", "artifact.txt"),), check_validity=False, ) hashkey = cache.get_cache_record(1).hashkey assert { str(p.relative_to(tmp_path)) for p in tmp_path.glob("**/*") if p.is_file() } == { "global.db", "__version__.txt", f"executed/{hashkey}/base.ipynb", f"executed/{hashkey}/artifacts/artifact_folder/artifact.txt", } bundle = cache.get_cache_bundle(1) assert {str(p) for p in bundle.artifacts.relative_paths} == { "artifact_folder/artifact.txt" } text = list(h.read().decode() for r, h in bundle.artifacts)[0] assert text.rstrip() == "An artifact" with cache.cache_artefacts_temppath(1) as path: assert path.joinpath("artifact_folder").exists() @pytest.mark.parametrize( "executor_key", ["local-serial", "temp-serial", "local-parallel", "temp-parallel"] ) def test_execution(tmp_path, executor_key): from jupyter_cache.executors import load_executor db = JupyterCacheBase(str(tmp_path / "cache")) temp_nb_path = tmp_path / "notebooks" shutil.copytree(NB_PATH, temp_nb_path) db.add_nb_to_project(path=os.path.join(temp_nb_path, "basic_unrun.ipynb")) db.add_nb_to_project(path=os.path.join(temp_nb_path, "basic_failing.ipynb")) db.add_nb_to_project( path=os.path.join(temp_nb_path, "external_output.ipynb"), assets=(os.path.join(temp_nb_path, "basic.ipynb"),), ) executor = load_executor(executor_key, db) result = executor.run_and_cache() # print(result) json_result = result.as_json() json_result["succeeded"] = list(sorted(json_result.get("succeeded", []))) assert json_result == { "succeeded": [ os.path.join(temp_nb_path, "basic_unrun.ipynb"), os.path.join(temp_nb_path, "external_output.ipynb"), ], "excepted": [os.path.join(temp_nb_path, "basic_failing.ipynb")], "errored": [], } assert len(db.list_cache_records()) == 2 cache_record = db.get_cached_project_nb(1) bundle = db.get_cache_bundle(cache_record.pk) assert bundle.nb.cells[0] == { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": "1\n"}], "source": "a=1\nprint(a)", } assert "execution_seconds" in bundle.record.data # TODO artifacts # with db.cache_artefacts_temppath(2) as path: # paths = [str(p.relative_to(path)) for p in path.glob("**/*") if p.is_file()] # assert paths == ["artifact.txt"] # assert path.joinpath("artifact.txt").read_text(encoding="utf8") == "hi" project_record = db.get_project_record(2) assert project_record.traceback is not None assert "Exception: oopsie!" in ANSI_REGEX.sub("", project_record.traceback) def test_execution_jupytext(tmp_path): """Test execution with the jupytext reader.""" from jupyter_cache.executors import load_executor db = JupyterCacheBase(str(tmp_path / "cache")) temp_nb_path = tmp_path / "notebooks" shutil.copytree(NB_PATH, temp_nb_path) db.add_nb_to_project( path=os.path.join(temp_nb_path, "basic.md"), read_data={"name": "jupytext", "type": "plugin"}, ) executor = load_executor("local-serial", db) result = executor.run_and_cache() print(result) assert result.as_json() == { "succeeded": [ os.path.join(temp_nb_path, "basic.md"), ], "excepted": [], "errored": [], } assert len(db.list_cache_records()) == 1 def test_execution_timeout_config(tmp_path): """tests the timeout value passed to the executor""" from jupyter_cache.executors import load_executor db = JupyterCacheBase(str(tmp_path)) db.add_nb_to_project(path=os.path.join(NB_PATH, "sleep_2.ipynb")) executor = load_executor("local-serial", db) result = executor.run_and_cache(timeout=10) assert result.as_json() == { "succeeded": [os.path.join(NB_PATH, "sleep_2.ipynb")], "excepted": [], "errored": [], } db.clear_cache() db.add_nb_to_project(path=os.path.join(NB_PATH, "sleep_2.ipynb")) executor = load_executor("local-serial", db) result = executor.run_and_cache(timeout=1) assert result.as_json() == { "succeeded": [], "excepted": [os.path.join(NB_PATH, "sleep_2.ipynb")], "errored": [], } def test_execution_timeout_metadata(tmp_path): """tests the timeout metadata key in notebooks""" from jupyter_cache.executors import load_executor db = JupyterCacheBase(str(tmp_path)) db.add_nb_to_project(path=os.path.join(NB_PATH, "sleep_2_timeout_1.ipynb")) executor = load_executor("local-serial", db) result = executor.run_and_cache() assert result.as_json() == { "succeeded": [], "excepted": [os.path.join(NB_PATH, "sleep_2_timeout_1.ipynb")], "errored": [], } def test_execution_allow_errors_config(tmp_path): """tests the timeout value passed to the executor""" from jupyter_cache.executors import load_executor db = JupyterCacheBase(str(tmp_path)) db.add_nb_to_project(path=os.path.join(NB_PATH, "basic_failing.ipynb")) executor = load_executor("local-serial", db) result = executor.run_and_cache(allow_errors=True) assert result.as_json() == { "succeeded": [os.path.join(NB_PATH, "basic_failing.ipynb")], "excepted": [], "errored": [], } def test_run_in_temp_false(tmp_path): """tests the timeout value passed to the executor""" from jupyter_cache.executors import load_executor db = JupyterCacheBase(str(tmp_path)) db.add_nb_to_project(path=os.path.join(NB_PATH, "basic.ipynb")) executor = load_executor("temp-serial", db) result = executor.run_and_cache() assert result.as_json() == { "succeeded": [os.path.join(NB_PATH, "basic.ipynb")], "excepted": [], "errored": [], } jupyter-cache-1.0.0/tests/test_cli.py000066400000000000000000000221641452276675600176260ustar00rootroot00000000000000import os from pathlib import Path from click.testing import CliRunner import pytest from jupyter_cache.cache.main import JupyterCacheBase from jupyter_cache.cli import CacheContext from jupyter_cache.cli.commands import cmd_cache, cmd_main, cmd_notebook, cmd_project NB_PATH = os.path.join(os.path.realpath(os.path.dirname(__file__)), "notebooks") class Runner(CliRunner): def __init__(self, path) -> None: super().__init__() self._cache_path = path def create_cache(self) -> JupyterCacheBase: return JupyterCacheBase(str(self._cache_path)) def invoke(self, *args, **kwargs): return super().invoke(*args, **kwargs, obj=CacheContext(self._cache_path)) @pytest.fixture() def runner(tmp_path): return Runner(tmp_path) def test_base(runner: Runner): result = runner.invoke(cmd_main.jcache, "-v") assert result.exception is None, result.output assert result.exit_code == 0, result.output assert "jupyter-cache version" in result.output.strip(), result.output def test_clear_cache(runner: Runner): result = runner.invoke(cmd_project.clear_cache, input="y") assert result.exception is None, result.output assert result.exit_code == 0, result.output assert "Cache cleared!" in result.output.strip(), result.output def test_list_caches(runner: Runner): db = runner.create_cache() db.cache_notebook_file( path=os.path.join(NB_PATH, "basic.ipynb"), uri="basic.ipynb", check_validity=False, ) result = runner.invoke(cmd_cache.list_caches, []) assert result.exception is None, result.output assert result.exit_code == 0, result.output assert "basic.ipynb" in result.output.strip(), result.output def test_list_caches_latest_only(runner: Runner): db = runner.create_cache() db.cache_notebook_file( path=os.path.join(NB_PATH, "basic.ipynb"), uri="basic.ipynb", check_validity=False, ) db.cache_notebook_file( path=os.path.join(NB_PATH, "complex_outputs.ipynb"), uri="basic.ipynb", check_validity=False, ) result = runner.invoke(cmd_cache.list_caches, []) assert result.exception is None, result.output assert result.exit_code == 0, result.output assert len(result.output.strip().splitlines()) == 4, result.output result = runner.invoke(cmd_cache.list_caches, ["--latest-only"]) assert result.exception is None, result.output assert result.exit_code == 0, result.output assert len(result.output.strip().splitlines()) == 3, result.output def test_cache_with_artifact(runner: Runner): nb_path = os.path.join(NB_PATH, "basic.ipynb") a_path = os.path.join(NB_PATH, "artifact_folder", "artifact.txt") result = runner.invoke( cmd_cache.cache_nb, ["--no-validate", "-nb", nb_path, a_path] ) assert result.exception is None, result.output assert result.exit_code == 0, result.output assert "basic.ipynb" in result.output.strip(), result.output result = runner.invoke(cmd_cache.cached_info, ["1"]) assert result.exception is None, result.output assert result.exit_code == 0, result.output assert "- artifact_folder/artifact.txt" in result.output.strip(), result.output result = runner.invoke( cmd_cache.cat_artifact, ["1", "artifact_folder/artifact.txt"] ) assert result.exception is None, result.output assert result.exit_code == 0, result.output assert "An artifact" in result.output.strip(), result.output def test_cache_nbs(runner: Runner): db = runner.create_cache() path = os.path.join(NB_PATH, "basic.ipynb") result = runner.invoke(cmd_cache.cache_nbs, ["--no-validate", path]) assert result.exception is None, result.output assert result.exit_code == 0, result.output assert "basic.ipynb" in result.output.strip(), result.output assert db.list_cache_records()[0].uri == path def test_remove_caches(runner: Runner): db = runner.create_cache() db.cache_notebook_file( path=os.path.join(NB_PATH, "basic.ipynb"), uri="basic.ipynb", check_validity=False, ) result = runner.invoke(cmd_cache.remove_caches, ["1"]) assert result.exception is None, result.output assert result.exit_code == 0, result.output assert "Success" in result.output.strip(), result.output assert db.list_cache_records() == [] def test_diff_nbs(runner: Runner): db = runner.create_cache() path = os.path.join(NB_PATH, "basic.ipynb") path2 = os.path.join(NB_PATH, "basic_failing.ipynb") db.cache_notebook_file(path, check_validity=False) # nb_bundle = db.get_cache_bundle(1) # nb_bundle.nb.cells[0].source = "# New Title" # db.stage_notebook_bundle(nb_bundle) result = runner.invoke(cmd_cache.diff_nb, ["1", path2]) assert result.exception is None, result.output assert result.exit_code == 0, result.output print(result.output.splitlines()[2:]) assert result.output.splitlines()[1:] == [ "--- cached pk=1", f"+++ other: {path2}", "## inserted before nb/cells/0:", "+ code cell:", "+ source:", "+ raise Exception('oopsie!')", "", "## deleted nb/cells/0:", "- code cell:", "- execution_count: 2", "- source:", "- a=1", "- print(a)", "- outputs:", "- output 0:", "- output_type: stream", "- name: stdout", "- text:", "- 1", "", "", "Success!", ] def test_add_nbs_to_project(runner: Runner): db = runner.create_cache() path = os.path.join(NB_PATH, "basic.ipynb") result = runner.invoke(cmd_notebook.add_notebooks, [path]) assert result.exception is None, result.output assert result.exit_code == 0, result.output assert "basic.ipynb" in result.output.strip(), result.output assert db.list_project_records()[0].uri == path def test_remove_nbs_from_project(runner: Runner): db = runner.create_cache() path = os.path.join(NB_PATH, "basic.ipynb") result = runner.invoke(cmd_notebook.add_notebooks, [path]) result = runner.invoke(cmd_notebook.remove_nbs, [path]) assert result.exception is None, result.output assert result.exit_code == 0, result.output assert "basic.ipynb" in result.output.strip(), result.output assert db.list_project_records() == [] def test_clear_project(runner: Runner): db = runner.create_cache() path = os.path.join(NB_PATH, "basic.ipynb") result = runner.invoke(cmd_notebook.add_notebooks, [path]) result = runner.invoke(cmd_notebook.clear_nbs, [], input="y") assert result.exception is None, result.output assert result.exit_code == 0, result.output assert db.list_project_records() == [] def test_list_nbs_in_project(runner: Runner): db = runner.create_cache() db.cache_notebook_file( path=os.path.join(NB_PATH, "basic.ipynb"), check_validity=False ) db.add_nb_to_project(path=os.path.join(NB_PATH, "basic.ipynb")) db.add_nb_to_project(path=os.path.join(NB_PATH, "basic_failing.ipynb")) result = runner.invoke(cmd_notebook.list_nbs_in_project, []) assert result.exception is None, result.output assert result.exit_code == 0, result.output assert "basic.ipynb" in result.output.strip(), result.output def test_show_project_record(runner: Runner): db = runner.create_cache() db.cache_notebook_file( path=os.path.join(NB_PATH, "basic.ipynb"), check_validity=False ) db.add_nb_to_project(path=os.path.join(NB_PATH, "basic.ipynb")) result = runner.invoke(cmd_notebook.show_project_record, ["1"]) assert result.exception is None, result.output assert result.exit_code == 0, result.output assert "basic.ipynb" in result.output.strip(), result.output def test_project_execute(runner: Runner): db = runner.create_cache() db.add_nb_to_project(path=os.path.join(NB_PATH, "basic.ipynb")) result = runner.invoke(cmd_project.execute_nbs, []) assert result.exception is None, result.output assert result.exit_code == 0, result.output assert len(db.list_cache_records()) == 1 def test_project_merge(runner: Runner, tmp_path: Path): db = runner.create_cache() record = db.add_nb_to_project(path=os.path.join(NB_PATH, "basic_unrun.ipynb")) db.cache_notebook_file( path=os.path.join(NB_PATH, "basic.ipynb"), uri="basic.ipynb", check_validity=False, ) result = runner.invoke( cmd_notebook.merge_executed, [str(record.pk), str(tmp_path / "output.ipynb")], ) assert result.exception is None, result.output assert result.exit_code == 0, result.output assert (tmp_path / "output.ipynb").exists() def test_project_invalidate(runner: Runner): db = runner.create_cache() db.cache_notebook_file( path=os.path.join(NB_PATH, "basic.ipynb"), check_validity=False ) db.add_nb_to_project(path=os.path.join(NB_PATH, "basic.ipynb")) result = runner.invoke(cmd_notebook.invalidate_nbs, ["1"]) assert result.exception is None, result.output assert result.exit_code == 0, result.output assert db.list_project_records() assert not db.list_cache_records() jupyter-cache-1.0.0/tests/test_db.py000066400000000000000000000013021452276675600174330ustar00rootroot00000000000000import pytest from jupyter_cache.cache.db import NbCacheRecord, Setting, create_db def test_setting(tmp_path): db = create_db(tmp_path) Setting.set_value("a", 1, db) assert Setting.get_value("a", db) == 1 assert Setting.get_dict(db) == {"a": 1} def test_nb_record(tmp_path): db = create_db(tmp_path) bundle = NbCacheRecord.create_record("a", "b", db) assert bundle.hashkey == "b" with pytest.raises(ValueError): NbCacheRecord.create_record("a", "b", db) NbCacheRecord.create_record("a", "c", db, data="a") assert NbCacheRecord.record_from_hashkey("b", db).uri == "a" assert {b.hashkey for b in NbCacheRecord.records_from_uri("a", db)} == {"b", "c"} jupyter-cache-1.0.0/tox.ini000066400000000000000000000022651452276675600156170ustar00rootroot00000000000000# To use tox, see https://tox.readthedocs.io # Simply pip or conda install tox # If you use conda, you may also want to install tox-conda # then run `tox` or `tox -- {pytest args}` # To run in parallel using `tox -p` (this does not appear to work for this repo) # To rebuild the tox environment, for example when dependencies change, use # `tox -r` # Note: if the following error is encountered: `ImportError while loading conftest` # then then deleting compiled files has been found to fix it: `find . -name \*.pyc -delete` [tox] envlist = py38 [testenv] usedevelop = true [testenv:py{37,38,39,310}] extras = testing deps = black flake8 setenv = SQLALCHEMY_WARN_20 = 1 commands = pytest {posargs} [testenv:cli] ; extras = cli deps = ipykernel jupytext commands = jcache {posargs} [testenv:docs-{clean,update}] extras = cli rtd whitelist_externals = echo rm commands = clean: rm -rf docs/_build sphinx-build -nW --keep-going -b {posargs:html} docs/ docs/_build/{posargs:html} commands_post = echo "open file://{toxinidir}/docs/_build/{posargs:html}/index.html" [pytest] addopts = --ignore=_archive/ [flake8] max-line-length = 100 extend-ignore = E203