From 508a51e99b8e8cfadf09e9523adafedda406bcbc Mon Sep 17 00:00:00 2001 From: Rohit Sanjay Date: Sat, 3 Aug 2024 01:04:51 -0700 Subject: [PATCH 01/17] Modernize setuptools packaging and automate releases --- .bumpversion.cfg | 2 +- .github/workflows/main.yml | 2 +- .github/workflows/publish.yml | 117 ++++++++++++++++++++++++++++++ README.md | 8 +-- RELEASING.md | 4 +- docs/requirements-doc.txt | 3 - pyproject.toml | 129 ++++++++++++++++++++++++++++++---- pytest.ini | 3 - requirements-dev.txt | 18 ----- requirements.txt | 2 - setup.cfg | 44 ------------ setup.py | 78 -------------------- testbook/__init__.py | 1 - testbook/_version.py | 1 - tox.ini | 6 +- 15 files changed, 244 insertions(+), 174 deletions(-) create mode 100644 .github/workflows/publish.yml delete mode 100644 docs/requirements-doc.txt delete mode 100644 pytest.ini delete mode 100644 requirements-dev.txt delete mode 100644 requirements.txt delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100644 testbook/_version.py diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 6f4d1e7..de48645 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -4,4 +4,4 @@ commit = True tag = True tag_name = {new_version} -[bumpversion:file:testbook/_version.py] +[bumpversion:file:pyproject.toml] diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0a09d68..bc2b873 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.6, 3.7, 3.8, 3.9, "3.10.0-rc.2"] + python-version: [3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12] env: OS: ubuntu-latest PYTHON: "3.8" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..059e18c --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,117 @@ +name: Publish testbook + +on: push + +jobs: + build: + name: Build distribution 📦 + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Install pypa/build + run: >- + python3 -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: python3 -m build + - name: Store the distribution packages + uses: actions/upload-artifact@v3 + with: + name: python-package-distributions + path: dist/ + + publish-to-pypi: + name: >- + Publish Python 🐍 distribution 📦 to PyPI + if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes + needs: + - build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/testbook + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + + steps: + - name: Download all the dists + uses: actions/download-artifact@v3 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + + github-release: + name: >- + Sign the Python 🐍 distribution 📦 with Sigstore + and upload them to GitHub Release + needs: + - publish-to-pypi + runs-on: ubuntu-latest + + permissions: + contents: write # IMPORTANT: mandatory for making GitHub Releases + id-token: write # IMPORTANT: mandatory for sigstore + + steps: + - name: Download all the dists + uses: actions/download-artifact@v3 + with: + name: python-package-distributions + path: dist/ + - name: Sign the dists with Sigstore + uses: sigstore/gh-action-sigstore-python@v2.1.1 + with: + inputs: >- + ./dist/*.tar.gz + ./dist/*.whl + - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + run: >- + gh release create + '${{ github.ref_name }}' + --repo '${{ github.repository }}' + --notes "" + - name: Upload artifact signatures to GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + # Upload to GitHub Release using the `gh` CLI. + # `dist/` contains the built packages, and the + # sigstore-produced signatures and certificates. + run: >- + gh release upload + '${{ github.ref_name }}' dist/** + --repo '${{ github.repository }}' + + publish-to-testpypi: + name: Publish Python 🐍 distribution 📦 to TestPyPI + needs: + - build + runs-on: ubuntu-latest + + environment: + name: testpypi + url: https://test.pypi.org/p/testbook + + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + + steps: + - name: Download all the dists + uses: actions/download-artifact@v3 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ diff --git a/README.md b/README.md index f0d2e07..2df2ce1 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,9 @@ [![Build Status](https://github.com/nteract/testbook/workflows/CI/badge.svg)](https://github.com/nteract/testbook/actions) [![image](https://codecov.io/github/nteract/testbook/coverage.svg?branch=master)](https://codecov.io/github/nteract/testbook?branch=master) [![Documentation Status](https://readthedocs.org/projects/testbook/badge/?version=latest)](https://testbook.readthedocs.io/en/latest/?badge=latest) -[![PyPI](https://img.shields.io/pypi/v/testbook.svg)](https://pypi.org/project/testbook/) -[![Python 3.6](https://img.shields.io/badge/python-3.6-blue.svg)](https://www.python.org/downloads/release/python-360/) -[![Python 3.7](https://img.shields.io/badge/python-3.7-blue.svg)](https://www.python.org/downloads/release/python-370/) -[![Python 3.8](https://img.shields.io/badge/python-3.8-blue.svg)](https://www.python.org/downloads/release/python-380/) -[![Python 3.9](https://img.shields.io/badge/python-3.9-blue.svg)](https://www.python.org/downloads/release/python-390/) +[![image](https://img.shields.io/pypi/v/testbook.svg)](https://pypi.python.org/pypi/testbook) +[![image](https://img.shields.io/pypi/l/testbook.svg)](https://github.com/astral-sh/testbook/blob/main/LICENSE) +[![image](https://img.shields.io/pypi/pyversions/testbook.svg)](https://pypi.python.org/pypi/testbook) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) # testbook diff --git a/RELEASING.md b/RELEASING.md index 79db022..a991b5b 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -3,7 +3,7 @@ ## Prerequisites - First check that the CHANGELOG is up to date for the next release version -- Ensure dev requirements are installed `pip install -r requirements-dev.txt` +- Ensure dev requirements are installed `pip install ".[dev]"` ## Push to GitHub @@ -19,6 +19,6 @@ git push upstream && git push upstream --tags ```bash rm -rf dist/* rm -rf build/* -python setup.py bdist_wheel +python -m build twine upload dist/* ``` diff --git a/docs/requirements-doc.txt b/docs/requirements-doc.txt deleted file mode 100644 index 7ee0a03..0000000 --- a/docs/requirements-doc.txt +++ /dev/null @@ -1,3 +0,0 @@ -Sphinx>=1.7,<3.0 -sphinx_book_theme==0.0.35 -myst-parser==0.9.1 diff --git a/pyproject.toml b/pyproject.toml index 8bfffb8..36496e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,29 +1,132 @@ -# Example configuration for Black. +[build-system] +build-backend = "setuptools.build_meta" -# NOTE: you have to use single-quoted strings in TOML for regular expressions. -# It's the equivalent of r-strings in Python. Multiline strings are treated as -# verbose regular expressions by Black. Use [ ] to denote a significant space -# character. +requires = [ "setuptools" ] + +[project] +name = "testbook_rohitsanjay" +version = "0.4.2" +description = "A unit testing framework for Jupyter Notebooks" +readme = "README.md" +keywords = [ "jupyter", "notebook", "nteract", "unit-testing" ] +license = { file = "LICENSE" } +maintainers = [ + { name = "Nteract Contributors", email = "nteract@googlegroups.com" }, + { name = "Rohit Sanjay", email = "sanjay.rohit2@gmail.com" }, + { name = "Matthew Seal", email = "mseal007@gmail.com" }, +] +authors = [ + { name = "Nteract Contributors", email = "nteract@googlegroups.com" }, + { name = "Rohit Sanjay", email = "sanjay.rohit2@gmail.com" }, + { name = "Matthew Seal", email = "mseal007@gmail.com" }, +] +requires-python = ">=3.6" + +classifiers = [ + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Programming Language :: Python", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Software Development :: Testing", + "Topic :: Software Development :: Testing :: Mocking", + "Topic :: Software Development :: Testing :: Unit", +] +dependencies = [ + "nbclient>=0.4", + "nbformat>=5.0.4", +] +optional-dependencies.dev = [ + "black; python_version>='3.6'", + "bumpversion", + "check-manifest", + "codecov", + "coverage", + "flake8", + "ipykernel", + "ipython", + "ipywidgets", + "pandas", + "pip>=18.1", + "pytest>=4.1", + "pytest-cov>=2.6.1", + "setuptools>=38.6", + "tox", + "build", + "twine>=1.11", + "xmltodict", +] +optional-dependencies.test = [ + "myst-parser==0.9.1", + "sphinx>=1.7,<3", + "sphinx-book-theme==0.0.35", +] +urls.Documentation = "https://testbook.readthedocs.io" +urls.Funding = "https://nteract.io" +urls.Issues = "https://github.com/nteract/testbook/issues" +urls.Repository = "https://github.com/nteract/testbook/" [tool.black] line-length = 100 -include = '\.pyi?$' exclude = ''' /( \.git - | \.hg | \.mypy_cache | \.tox | \.venv | _build - | buck-out | build | dist - - # The following are specific to Black, you probably don't want those. - | blib2to3 - | tests/data - | profiling )/ ''' skip-string-normalization = true + +[tool.flake8] +exclude = "__init__.py" +ignore = [ + "E20", # Extra space in brackets + "E231", + "E241", # Multiple spaces around "," + "E26", # Comments + "E4", # Import formatting + "E721", # Comparing types instead of isinstance + "E731", # Assigning lambda expression +] +max-line-length = 120 + +# Not sure I need this? +# [tool.bdist_wheel] +# universal = 0 + +[tool.pytest.ini_options] +filterwarnings = "always" +testpaths = [ + "testbook/tests/", +] + +[tool.coverage.run] +branch = false +omit = [ + "testbook/tests/*", + "testbook/_version.py", +] + +[tool.coverage.report] +exclude_lines = [ + "if self\\.debug:", + "pragma: no cover", + "raise AssertionError", + "raise NotImplementedError", + "if __name__ == '__main__':", +] +ignore_errors = true +omit = [ + "testbook/tests/*", + "testbook/_version.py", +] diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 805610b..0000000 --- a/pytest.ini +++ /dev/null @@ -1,3 +0,0 @@ -[pytest] -testpaths = - testbook/tests/ diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index d3b9874..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,18 +0,0 @@ -codecov -coverage -ipython -ipykernel -ipywidgets -pandas -pytest>=4.1 -pytest-cov>=2.6.1 -check-manifest -flake8 -tox -bumpversion -xmltodict -black; python_version >= '3.6' -pip>=18.1 -wheel>=0.31.0 -setuptools>=38.6.0 -twine>=1.11.0 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 0f9ff69..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -nbformat>=5.0.4 -nbclient>=0.4.0 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 58ca2c3..0000000 --- a/setup.cfg +++ /dev/null @@ -1,44 +0,0 @@ - -[flake8] -# References: -# https://flake8.readthedocs.io/en/latest/user/configuration.html -# https://flake8.readthedocs.io/en/latest/user/error-codes.html - -# Note: there cannot be spaces after comma's here -exclude = __init__.py -ignore = - # Extra space in brackets - E20, - # Multiple spaces around "," - E231,E241, - # Comments - E26, - # Import formatting - E4, - # Comparing types instead of isinstance - E721, - # Assigning lambda expression - E731 -max-line-length = 120 - -[bdist_wheel] -universal=0 - -[coverage:run] -branch = False -omit = - testbook/tests/* - testbook/_version.py - -[coverage:report] -exclude_lines = - if self\.debug: - pragma: no cover - raise AssertionError - raise NotImplementedError - if __name__ == .__main__.: -ignore_errors = True -omit = testbook/tests/*,testbook/_version.py - -[tool:pytest] -filterwarnings = always diff --git a/setup.py b/setup.py deleted file mode 100644 index 4b47e06..0000000 --- a/setup.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -"""" -setup.py - -See: -https://packaging.python.org/tutorials/packaging-projects/ -https://packaging.python.org/en/latest/distributing.html -https://github.com/pypa/sampleproject - -""" -import os -from setuptools import setup - - -local_path = os.path.dirname(__file__) -# Fix for tox which manipulates execution pathing -if not local_path: - local_path = '.' -here = os.path.abspath(local_path) - - -def version(): - with open(here + '/testbook/_version.py', 'r') as ver: - for line in ver.readlines(): - if line.startswith('version ='): - return line.split(' = ')[-1].strip()[1:-1] - raise ValueError('No version found in testbook/version.py') - - -def read(fname): - with open(fname, 'r') as fhandle: - return fhandle.read() - - -def read_reqs(fname): - req_path = os.path.join(here, fname) - return [req.strip() for req in read(req_path).splitlines() if req.strip()] - - -long_description = read(os.path.join(os.path.dirname(__file__), "README.md")) -requirements = read(os.path.join(os.path.dirname(__file__), "requirements.txt")) -dev_reqs = read_reqs(os.path.join(os.path.dirname(__file__), 'requirements-dev.txt')) -doc_reqs = read_reqs(os.path.join(os.path.dirname(__file__), 'docs/requirements-doc.txt')) -extras_require = {"test": dev_reqs, "dev": dev_reqs, "sphinx": doc_reqs} - -setup( - name='testbook', - version=version(), - description='A unit testing framework for Jupyter Notebooks', - author='nteract contributors', - author_email='nteract@googlegroups.com', - license='BSD', - # Note that this is a string of words separated by whitespace, not a list. - keywords='jupyter mapreduce nteract pipeline notebook', - long_description=long_description, - long_description_content_type='text/markdown', - url='https://github.com/nteract/testbook', - packages=['testbook'], - python_requires='>=3.6', - install_requires=requirements, - extras_require=extras_require, - project_urls={ - 'Documentation': 'https://testbook.readthedocs.io', - 'Funding': 'https://nteract.io', - 'Source': 'https://github.com/nteract/testbook/', - 'Tracker': 'https://github.com/nteract/testbook/issues', - }, - classifiers=[ - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - ], -) diff --git a/testbook/__init__.py b/testbook/__init__.py index e8f1368..3276cff 100644 --- a/testbook/__init__.py +++ b/testbook/__init__.py @@ -1,2 +1 @@ -from ._version import version as __version__ from .testbook import testbook diff --git a/testbook/_version.py b/testbook/_version.py deleted file mode 100644 index 678d2fe..0000000 --- a/testbook/_version.py +++ /dev/null @@ -1 +0,0 @@ -version = '0.4.2' diff --git a/tox.ini b/tox.ini index 8650731..b124bda 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] skipsdist = true -envlist = py{36,37,38,39,310}, flake8, dist, manifest, docs +envlist = py{36,37,38,39,310,311,312}, flake8, dist, manifest, docs [gh-actions] python = @@ -9,6 +9,8 @@ python = 3.8: py38, flake8, dist, manifest 3.9: py39 3.10: py310 + 3.11: py311 + 3.12: py312 # Linters [testenv:flake8] @@ -45,7 +47,7 @@ commands = # Black [testenv:black] description = apply black linter with desired rules -basepython = python3.6 +basepython = python3.11 deps = black commands = black . From 894cf0ae65ee1dea3336d5808ff404dfc8299c95 Mon Sep 17 00:00:00 2001 From: Rohit Sanjay Date: Sat, 3 Aug 2024 01:14:34 -0700 Subject: [PATCH 02/17] use pinned ubuntu 20.04 --- .github/workflows/main.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bc2b873..e0fa9ae 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,20 +9,16 @@ on: jobs: build-n-test-n-coverage: name: Build, test and code coverage - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: python-version: [3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12] - env: - OS: ubuntu-latest - PYTHON: "3.8" - steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -33,7 +29,7 @@ jobs: - name: Run the tests run: tox - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v4 with: file: ./coverage.xml flags: unittests From 40572a7b67f93eb4e9a4458d3823b83651ba53d2 Mon Sep 17 00:00:00 2001 From: Rohit Sanjay Date: Sat, 3 Aug 2024 01:16:40 -0700 Subject: [PATCH 03/17] string not numbers --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e0fa9ae..b9f272e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - name: Checkout uses: actions/checkout@v4 From 5ae6845a565d836bed2f1581a3e373d75e1d2d7c Mon Sep 17 00:00:00 2001 From: Rohit Sanjay Date: Sat, 3 Aug 2024 01:26:39 -0700 Subject: [PATCH 04/17] exclude init from flake eyes --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 36496e1..c0e09f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,7 +88,7 @@ exclude = ''' skip-string-normalization = true [tool.flake8] -exclude = "__init__.py" +exclude = "testbook/__init__.py" ignore = [ "E20", # Extra space in brackets "E231", From afbf9f877c2c8a179f57d0b83d7f2428b9e853a9 Mon Sep 17 00:00:00 2001 From: Rohit Sanjay Date: Sat, 3 Aug 2024 01:27:38 -0700 Subject: [PATCH 05/17] remove editable flag from install --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b9f272e..967278d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e .[test] + pip install .[test] pip install tox-gh-actions - name: Run the tests run: tox From 105b7ac0fdf0bef96d2e2ae509fa63bbdc119735 Mon Sep 17 00:00:00 2001 From: Rohit Sanjay Date: Sat, 3 Aug 2024 01:57:21 -0700 Subject: [PATCH 06/17] Replace flake8 and black with ruff --- docs/conf.py | 68 ++++++----- examples/dataframe-example/dataframe_test.py | 7 +- examples/requests-example/requests_test.py | 11 +- examples/stdout-example/stdout_test.py | 7 +- pyproject.toml | 115 +++++++++++++------ testbook/client.py | 69 ++++++----- testbook/exceptions.py | 1 + testbook/reference.py | 23 ++-- testbook/testbook.py | 14 ++- testbook/testbooknode.py | 10 +- testbook/tests/conftest.py | 14 +-- testbook/tests/test_client.py | 34 +++--- testbook/tests/test_datamodel.py | 20 ++-- testbook/tests/test_execute.py | 51 ++++---- testbook/tests/test_inject.py | 26 ++--- testbook/tests/test_patch.py | 11 +- testbook/tests/test_reference.py | 8 +- testbook/tests/test_testbook.py | 32 +++--- testbook/tests/test_translators.py | 29 ++--- testbook/translators.py | 42 ++++--- testbook/utils.py | 2 +- tox.ini | 31 ++--- 22 files changed, 369 insertions(+), 256 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index c64df53..5d98d67 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,15 +12,17 @@ # import os import sys +from importlib.metadata import version as read_version -sys.path.insert(0, os.path.abspath('..')) + +sys.path.insert(0, os.path.abspath("..")) # -- Project information ----------------------------------------------------- -project = 'testbook' -copyright = '2020, nteract team' -author = 'nteract team' +project = "testbook" +copyright = "2020, nteract team" +author = "nteract team" # -- General configuration --------------------------------------------------- @@ -29,40 +31,40 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.mathjax', - 'sphinx.ext.napoleon', - 'myst_parser', + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.mathjax", + "sphinx.ext.napoleon", + "myst_parser", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # -source_suffix = ['.rst', '.md'] +source_suffix = [".rst", ".md"] # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'testbook' -copyright = '2020, nteract team' -author = 'nteract team' +project = "testbook" +copyright = "2020, nteract team" +author = "nteract team" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # -import testbook + # The short X.Y version. -version = '.'.join(testbook.__version__.split('.')[0:2]) +version = read_version(project).split(".")[0:2] # The full version, including alpha/beta/rc tags. -release = testbook.__version__ +release = read_version(project) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -74,10 +76,10 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'UPDATE.md'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "UPDATE.md"] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -88,7 +90,7 @@ # 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_theme = "sphinx_book_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -112,14 +114,16 @@ # # This is required for the alabaster theme # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars -html_sidebars = {'**': ['about.html', 'navigation.html', 'relations.html', 'searchbox.html']} +html_sidebars = { + "**": ["about.html", "navigation.html", "relations.html", "searchbox.html"] +} html_title = "testbook" # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. -htmlhelp_basename = 'testbookdoc' +htmlhelp_basename = "testbookdoc" # -- Options for LaTeX output --------------------------------------------- @@ -142,14 +146,16 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). -latex_documents = [(master_doc, 'testbook.tex', 'testbook Documentation', 'nteract team', 'manual')] +latex_documents = [ + (master_doc, "testbook.tex", "testbook Documentation", "nteract team", "manual") +] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [(master_doc, 'testbook', 'testbook Documentation', [author], 1)] +man_pages = [(master_doc, "testbook", "testbook Documentation", [author], 1)] # -- Options for Texinfo output ------------------------------------------- @@ -160,14 +166,14 @@ texinfo_documents = [ ( master_doc, - 'testbook', - 'testbook Documentation', + "testbook", + "testbook Documentation", author, - 'testbook', - 'One line description of project.', - 'Miscellaneous', + "testbook", + "One line description of project.", + "Miscellaneous", ) ] # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/': None} +intersphinx_mapping = {"https://docs.python.org/": None} diff --git a/examples/dataframe-example/dataframe_test.py b/examples/dataframe-example/dataframe_test.py index 66e68b3..fa2ffbe 100644 --- a/examples/dataframe-example/dataframe_test.py +++ b/examples/dataframe-example/dataframe_test.py @@ -1,8 +1,9 @@ from testbook import testbook -@testbook('./dataframe-assertion-example.ipynb') + +@testbook("./dataframe-assertion-example.ipynb") def test_dataframe_manipulation(tb): - tb.execute_cell('imports') + tb.execute_cell("imports") # Inject a dataframe with code tb.inject( @@ -12,7 +13,7 @@ def test_dataframe_manipulation(tb): ) # Perform manipulation - tb.execute_cell('manipulation') + tb.execute_cell("manipulation") # Inject assertion into notebook tb.inject("assert len(df) == 1") diff --git a/examples/requests-example/requests_test.py b/examples/requests-example/requests_test.py index 23951ea..49c1757 100644 --- a/examples/requests-example/requests_test.py +++ b/examples/requests-example/requests_test.py @@ -1,9 +1,10 @@ from testbook import testbook -@testbook('./requests-test.ipynb', execute=True) + +@testbook("./requests-test.ipynb", execute=True) def test_get_details(tb): - with tb.patch('requests.get') as mock_get: - get_details = tb.ref('get_details') # get reference to function - get_details('https://my-api.com') + with tb.patch("requests.get") as mock_get: + get_details = tb.ref("get_details") # get reference to function + get_details("https://my-api.com") - mock_get.assert_called_with('https://my-api.com') + mock_get.assert_called_with("https://my-api.com") diff --git a/examples/stdout-example/stdout_test.py b/examples/stdout-example/stdout_test.py index edc78bb..ead54fb 100644 --- a/examples/stdout-example/stdout_test.py +++ b/examples/stdout-example/stdout_test.py @@ -1,7 +1,8 @@ from testbook import testbook -@testbook('stdout-assertion-example.ipynb', execute=True) + +@testbook("stdout-assertion-example.ipynb", execute=True) def test_stdout(tb): - assert tb.cell_output_text(1) == 'hello world!' + assert tb.cell_output_text(1) == "hello world!" - assert 'The current time is' in tb.cell_output_text(2) + assert "The current time is" in tb.cell_output_text(2) diff --git a/pyproject.toml b/pyproject.toml index c0e09f5..93c28f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,12 +43,11 @@ dependencies = [ "nbformat>=5.0.4", ] optional-dependencies.dev = [ - "black; python_version>='3.6'", + "ruff", "bumpversion", "check-manifest", "codecov", "coverage", - "flake8", "ipykernel", "ipython", "ipywidgets", @@ -72,38 +71,6 @@ urls.Funding = "https://nteract.io" urls.Issues = "https://github.com/nteract/testbook/issues" urls.Repository = "https://github.com/nteract/testbook/" -[tool.black] -line-length = 100 -exclude = ''' -/( - \.git - | \.mypy_cache - | \.tox - | \.venv - | _build - | build - | dist -)/ -''' -skip-string-normalization = true - -[tool.flake8] -exclude = "testbook/__init__.py" -ignore = [ - "E20", # Extra space in brackets - "E231", - "E241", # Multiple spaces around "," - "E26", # Comments - "E4", # Import formatting - "E721", # Comparing types instead of isinstance - "E731", # Assigning lambda expression -] -max-line-length = 120 - -# Not sure I need this? -# [tool.bdist_wheel] -# universal = 0 - [tool.pytest.ini_options] filterwarnings = "always" testpaths = [ @@ -130,3 +97,83 @@ omit = [ "testbook/tests/*", "testbook/_version.py", ] + +[tool.ruff] +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", + "testbook/__init__.py", +] + +# Same as Black. +line-length = 88 +indent-width = 4 + +# Assume Python 3.8 +target-version = "py38" + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or +# McCabe complexity (`C901`) by default. +select = ["E4", "E7", "E9", "F"] +ignore = [] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = false + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" diff --git a/testbook/client.py b/testbook/client.py index 22cd34b..8e5c9c9 100644 --- a/testbook/client.py +++ b/testbook/client.py @@ -25,10 +25,13 @@ class TestbookNotebookClient(NotebookClient): def __init__(self, nb, km=None, **kw): # Fix the ipykernel 5.5 issue where execute requests after errors are aborted - ea = kw.get('extra_arguments', []) - if not any(arg.startswith('--Kernel.stop_on_error_timeout') for arg in self.extra_arguments): - ea.append('--Kernel.stop_on_error_timeout=0') - kw['extra_arguments'] = ea + ea = kw.get("extra_arguments", []) + if not any( + arg.startswith("--Kernel.stop_on_error_timeout") + for arg in self.extra_arguments + ): + ea.append("--Kernel.stop_on_error_timeout=0") + kw["extra_arguments"] = ea super().__init__(nb, km=km, **kw) def ref(self, name: str) -> Union[TestbookObjectReference, Any]: @@ -80,7 +83,7 @@ def _execute_result(cell) -> List: return [ output["data"] for output in cell["outputs"] - if output["output_type"] == 'execute_result' + if output["output_type"] == "execute_result" ] @staticmethod @@ -88,10 +91,10 @@ def _output_text(cell) -> str: if "outputs" not in cell: raise ValueError("cell must be a code cell") - text = '' + text = "" for output in cell["outputs"]: - if 'text' in output: - text += output['text'] + if "text" in output: + text += output["text"] elif "data" in output and "text/plain" in output["data"]: text += output["data"]["text/plain"] @@ -105,11 +108,11 @@ def _cell_index(self, tag: Union[int, str]) -> int: if isinstance(tag, int): return tag elif not isinstance(tag, str): - raise TypeError('expected tag as str') + raise TypeError("expected tag as str") for idx, cell in enumerate(self.cells): - metadata = cell['metadata'] - if "tags" in metadata and tag in metadata['tags']: + metadata = cell["metadata"] + if "tags" in metadata and tag in metadata["tags"]: return idx raise TestbookCellTagNotFoundError("Cell tag '{}' not found".format(tag)) @@ -121,7 +124,7 @@ def execute_cell(self, cell, **kwargs) -> Union[Dict, List[Dict]]: if isinstance(cell, slice): start, stop = self._cell_index(cell.start), self._cell_index(cell.stop) if cell.step is not None: - raise TestbookError('testbook does not support step argument') + raise TestbookError("testbook does not support step argument") cell = range(start, stop + 1) elif isinstance(cell, str) or isinstance(cell, int): @@ -135,9 +138,11 @@ def execute_cell(self, cell, **kwargs) -> Union[Dict, List[Dict]]: executed_cells = [] for idx in cell_indexes: try: - cell = super().execute_cell(self.nb['cells'][idx], idx, **kwargs) + cell = super().execute_cell(self.nb["cells"][idx], idx, **kwargs) except CellExecutionError as ce: - raise TestbookRuntimeError(ce.evalue, ce, self._get_error_class(ce.ename)) + raise TestbookRuntimeError( + ce.evalue, ce, self._get_error_class(ce.ename) + ) executed_cells.append(cell) @@ -156,7 +161,7 @@ def cell_output_text(self, cell) -> str: Return cell text output """ cell_index = self._cell_index(cell) - return self._output_text(self.nb['cells'][cell_index]) + return self._output_text(self.nb["cells"][cell_index]) def cell_execute_result(self, cell: Union[int, str]) -> List[Dict[str, Any]]: """Return the execute results of cell at a given index or with a given tag. @@ -183,7 +188,7 @@ def cell_execute_result(self, cell: Union[int, str]) -> List[Dict[str, Any]]: If tag is not found """ cell_index = self._cell_index(cell) - return self._execute_result(self.nb['cells'][cell_index]) + return self._execute_result(self.nb["cells"][cell_index]) def inject( self, @@ -222,10 +227,12 @@ def inject( lines = dedent(code) elif callable(code): lines = getsource(code) + ( - dedent(self._construct_call_code(code.__name__, args, kwargs)) if run else '' + dedent(self._construct_call_code(code.__name__, args, kwargs)) + if run + else "" ) else: - raise TypeError('can only inject function or code block as str') + raise TypeError("can only inject function or code block as str") inject_idx = len(self.cells) @@ -239,11 +246,15 @@ def inject( code_cell = new_code_cell(lines) self.cells.insert(inject_idx, code_cell) - cell = TestbookNode(self.execute_cell(inject_idx)) if run else TestbookNode(code_cell) + cell = ( + TestbookNode(self.execute_cell(inject_idx)) + if run + else TestbookNode(code_cell) + ) if self._contains_error(cell): - eclass = self._get_error_class(cell.get('outputs')[0]['ename']) - evalue = cell.get('outputs')[0]['evalue'] + eclass = self._get_error_class(cell.get("outputs")[0]["ename"]) + evalue = cell.get("outputs")[0]["evalue"] raise TestbookRuntimeError(evalue, evalue, eclass) if run and pop: @@ -279,7 +290,7 @@ def value(self, code: str) -> Any: if not self._execute_result(result): raise TestbookExecuteResultNotFoundError( - 'code provided does not produce execute_result' + "code provided does not produce execute_result" ) save_varname = random_varname() @@ -304,18 +315,18 @@ def value(self, code: str) -> Any: outputs[0].evalue, outputs[0].traceback, outputs[0].ename ) - return outputs[0].data['application/json']['value'] + return outputs[0].data["application/json"]["value"] except TestbookRuntimeError: - e = TestbookSerializeError('could not JSON serialize output') + e = TestbookSerializeError("could not JSON serialize output") e.save_varname = save_varname raise e @contextmanager def patch(self, target, **kwargs): """Used as contextmanager to patch objects in the kernel""" - mock_object = f'_mock_{random_varname()}' - patcher = f'_patcher_{random_varname()}' + mock_object = f"_mock_{random_varname()}" + patcher = f"_patcher_{random_varname()}" self.inject( f""" @@ -335,8 +346,8 @@ def patch(self, target, **kwargs): @contextmanager def patch_dict(self, in_dict, values=(), clear=False, **kwargs): """Used as contextmanager to patch dictionaries in the kernel""" - mock_object = f'_mock_{random_varname()}' - patcher = f'_patcher_{random_varname()}' + mock_object = f"_mock_{random_varname()}" + patcher = f"_patcher_{random_varname()}" self.inject( f""" @@ -366,4 +377,4 @@ def _get_error_class(ename): @staticmethod def _contains_error(result): - return result.get('outputs') and result.get('outputs')[0].output_type == "error" + return result.get("outputs") and result.get("outputs")[0].output_type == "error" diff --git a/testbook/exceptions.py b/testbook/exceptions.py index 2f7f7aa..39c4970 100644 --- a/testbook/exceptions.py +++ b/testbook/exceptions.py @@ -1,5 +1,6 @@ class TestbookError(Exception): """Generic Testbook exception class""" + __test__ = False diff --git a/testbook/reference.py b/testbook/reference.py index 6c6641d..89ead9a 100644 --- a/testbook/reference.py +++ b/testbook/reference.py @@ -2,7 +2,7 @@ TestbookExecuteResultNotFoundError, TestbookAttributeError, TestbookSerializeError, - TestbookRuntimeError + TestbookRuntimeError, ) from .utils import random_varname from .translators import PythonTranslator @@ -52,7 +52,9 @@ def __next__(self): def __getitem__(self, key): try: - return self.tb.value(f"{self.name}.__getitem__({PythonTranslator.translate(key)})") + return self.tb.value( + f"{self.name}.__getitem__({PythonTranslator.translate(key)})" + ) except TestbookRuntimeError as e: if e.eclass is TypeError: raise TypeError(e.evalue) @@ -63,11 +65,14 @@ def __getitem__(self, key): def __setitem__(self, key, value): try: - return self.tb.inject("{name}[{key}] = {value}".format( - name=self.name, - key=PythonTranslator.translate(key), - value=PythonTranslator.translate(value) - ), pop=True) + return self.tb.inject( + "{name}[{key}] = {value}".format( + name=self.name, + key=PythonTranslator.translate(key), + value=PythonTranslator.translate(value), + ), + pop=True, + ) except TestbookRuntimeError as e: if e.eclass is TypeError: raise TypeError(e.evalue) @@ -77,7 +82,9 @@ def __setitem__(self, key, value): raise def __contains__(self, item): - return self.tb.value(f"{self.name}.__contains__({PythonTranslator.translate(item)})") + return self.tb.value( + f"{self.name}.__contains__({PythonTranslator.translate(item)})" + ) def __call__(self, *args, **kwargs): code = self.tb._construct_call_code(self.name, args, kwargs) diff --git a/testbook/testbook.py b/testbook/testbook.py index 8fb245b..ebe10af 100644 --- a/testbook/testbook.py +++ b/testbook/testbook.py @@ -28,15 +28,23 @@ class testbook: attribute_name = None def __init__( - self, nb, execute=None, timeout=60, kernel_name='python3', allow_errors=False, **kwargs + self, + nb, + execute=None, + timeout=60, + kernel_name="python3", + allow_errors=False, + **kwargs, ): self.execute = execute self.client = TestbookNotebookClient( - nbformat.read(nb, as_version=4) if not isinstance(nb, nbformat.NotebookNode) else nb, + nbformat.read(nb, as_version=4) + if not isinstance(nb, nbformat.NotebookNode) + else nb, timeout=timeout, allow_errors=allow_errors, kernel_name=kernel_name, - **kwargs + **kwargs, ) self.new = DEFAULT diff --git a/testbook/testbooknode.py b/testbook/testbooknode.py index 22404ec..b39fd66 100644 --- a/testbook/testbooknode.py +++ b/testbook/testbooknode.py @@ -11,10 +11,10 @@ def __init__(self, *args, **kw): @property def output_text(self) -> str: - text = '' - for output in self['outputs']: - if 'text' in output: - text += output['text'] + text = "" + for output in self["outputs"]: + if "text" in output: + text += output["text"] return text.strip() @@ -24,5 +24,5 @@ def execute_result(self): return [ output["data"] for output in self["outputs"] - if output["output_type"] == 'execute_result' + if output["output_type"] == "execute_result" ] diff --git a/testbook/tests/conftest.py b/testbook/tests/conftest.py index ed1b2f8..c6fbd7c 100644 --- a/testbook/tests/conftest.py +++ b/testbook/tests/conftest.py @@ -20,10 +20,10 @@ def notebook_generator(cells: Optional[List[NotebookNode]] = None) -> NotebookNo for name in kernelspec.find_kernel_specs(): ks = kernelspec.get_kernel_spec(name) metadata = { - 'kernelspec': { - 'name': name, - 'language': ks.language, - 'display_name': ks.display_name, + "kernelspec": { + "name": name, + "language": ks.language, + "display_name": ks.display_name, } } break @@ -32,12 +32,12 @@ def notebook_generator(cells: Optional[List[NotebookNode]] = None) -> NotebookNo all_cells = cells else: # Default cells all_cells = [ - new_code_cell('a = 2', metadata={"tags": []}), - new_code_cell('b=22\nb', metadata={"tags": ["test"]}), + new_code_cell("a = 2", metadata={"tags": []}), + new_code_cell("b=22\nb", metadata={"tags": ["test"]}), new_code_cell( "", metadata={"tags": ["dummy-outputs"]}, - outputs=[new_output('execute_result', data={"text/plain": "text"})], + outputs=[new_output("execute_result", data={"text/plain": "text"})], ), ] diff --git a/testbook/tests/test_client.py b/testbook/tests/test_client.py index edb1ed9..b019bb5 100644 --- a/testbook/tests/test_client.py +++ b/testbook/tests/test_client.py @@ -3,23 +3,26 @@ from ..testbook import testbook from ..client import TestbookNotebookClient -from ..exceptions import TestbookCellTagNotFoundError, TestbookExecuteResultNotFoundError +from ..exceptions import ( + TestbookCellTagNotFoundError, + TestbookExecuteResultNotFoundError, +) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def notebook(): - with testbook('testbook/tests/resources/inject.ipynb', execute=True) as tb: + with testbook("testbook/tests/resources/inject.ipynb", execute=True) as tb: yield tb -@pytest.mark.parametrize("cell_index_args, expected_result", [(2, 2), ('hello', 1)]) +@pytest.mark.parametrize("cell_index_args, expected_result", [(2, 2), ("hello", 1)]) def test_cell_index(cell_index_args, expected_result, notebook): assert notebook._cell_index(cell_index_args) == expected_result @pytest.mark.parametrize( "cell_index_args, expected_error", - [([1, 2, 3], TypeError), ('non-existent-tag', TestbookCellTagNotFoundError)], + [([1, 2, 3], TypeError), ("non-existent-tag", TestbookCellTagNotFoundError)], ) def test_cell_index_raises_error(cell_index_args, expected_error, notebook): with pytest.raises(expected_error): @@ -29,20 +32,20 @@ def test_cell_index_raises_error(cell_index_args, expected_error, notebook): @pytest.mark.parametrize( "var_name, expected_result", [ - ('sample_dict', {'foo': 'bar'}), - ('sample_list', ['foo', 'bar']), - ('sample_list + ["hello world"]', ['foo', 'bar', 'hello world']), - ('sample_int', 42), - ('sample_int * 2', 84), - ('sample_str', 'hello world'), - ('sample_str + " foo"', 'hello world foo'), + ("sample_dict", {"foo": "bar"}), + ("sample_list", ["foo", "bar"]), + ('sample_list + ["hello world"]', ["foo", "bar", "hello world"]), + ("sample_int", 42), + ("sample_int * 2", 84), + ("sample_str", "hello world"), + ('sample_str + " foo"', "hello world foo"), ], ) def test_value(var_name, expected_result, notebook): assert notebook.value(var_name) == expected_result -@pytest.mark.parametrize("code", [('sample_int *= 2'), ('print(sample_int)'), ('')]) +@pytest.mark.parametrize("code", [("sample_int *= 2"), ("print(sample_int)"), ("")]) def test_value_raises_error(code, notebook): with pytest.raises(TestbookExecuteResultNotFoundError): notebook.value(code) @@ -70,7 +73,10 @@ def test_value_raises_error(code, notebook): bar """, ), - ({"cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": []}, ""), + ( + {"cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": []}, + "", + ), ], ) def test_output_text(cell, expected_result): diff --git a/testbook/tests/test_datamodel.py b/testbook/tests/test_datamodel.py index 29ecc14..4330fb9 100644 --- a/testbook/tests/test_datamodel.py +++ b/testbook/tests/test_datamodel.py @@ -3,9 +3,9 @@ from ..testbook import testbook -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def notebook(): - with testbook('testbook/tests/resources/datamodel.ipynb', execute=True) as tb: + with testbook("testbook/tests/resources/datamodel.ipynb", execute=True) as tb: yield tb @@ -44,15 +44,15 @@ def test_getitem_raisesTypeError(notebook): mylist = notebook.ref("mylist") with pytest.raises(TypeError): - mylist['hello'] + mylist["hello"] def test_setitem(notebook): notebook.inject("mydict = {'key1': 'value1', 'key2': 'value1'}") mydict = notebook.ref("mydict") - mydict['key3'] = 'value3' - assert mydict['key3'] == 'value3' + mydict["key3"] = "value3" + assert mydict["key3"] == "value3" mylist = notebook.ref("mylist") mylist[2] = 10 @@ -70,14 +70,14 @@ def test_setitem_raisesTypeError(notebook): mylist = notebook.ref("mylist") with pytest.raises(TypeError): - mylist.__setitem__('key', 10) + mylist.__setitem__("key", 10) def test_contains(notebook): notebook.inject("mydict = {'key1': 'value1', 'key2': 'value1'}") mydict = notebook.ref("mydict") - assert 'key1' in mydict - assert 'key2' in mydict - assert mydict.__contains__('key1') - assert mydict.__contains__('key2') + assert "key1" in mydict + assert "key2" in mydict + assert mydict.__contains__("key1") + assert mydict.__contains__("key2") diff --git a/testbook/tests/test_execute.py b/testbook/tests/test_execute.py index 6c96f00..94de706 100644 --- a/testbook/tests/test_execute.py +++ b/testbook/tests/test_execute.py @@ -4,33 +4,36 @@ from ..exceptions import TestbookRuntimeError, TestbookError -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def notebook(): - with testbook('testbook/tests/resources/foo.ipynb', execute=True) as tb: + with testbook("testbook/tests/resources/foo.ipynb", execute=True) as tb: yield tb def test_execute_cell(notebook): notebook.execute_cell(1) - assert notebook.cell_output_text(1) == 'hello world\n[1, 2, 3]' + assert notebook.cell_output_text(1) == "hello world\n[1, 2, 3]" notebook.execute_cell([2, 3]) - assert notebook.cell_output_text(3) == 'foo' + assert notebook.cell_output_text(3) == "foo" def test_execute_and_show_pandas_output(notebook): notebook.execute_cell(4) - assert notebook.cell_output_text(4) == """col1 col2 + assert ( + notebook.cell_output_text(4) + == """col1 col2 0 1 3 1 2 4""" + ) def test_execute_cell_tags(notebook): - notebook.execute_cell('test1') - assert notebook.cell_output_text('test1') == 'hello world\n[1, 2, 3]' + notebook.execute_cell("test1") + assert notebook.cell_output_text("test1") == "hello world\n[1, 2, 3]" - notebook.execute_cell(['prepare_foo', 'execute_foo']) - assert notebook.cell_output_text('execute_foo') == 'foo' + notebook.execute_cell(["prepare_foo", "execute_foo"]) + assert notebook.cell_output_text("execute_foo") == "foo" def test_execute_cell_raises_error(notebook): @@ -38,52 +41,56 @@ def test_execute_cell_raises_error(notebook): try: notebook.inject("1/0", pop=True) except TestbookRuntimeError as e: - assert e.eclass == ZeroDivisionError + assert e.eclass is ZeroDivisionError raise def test_testbook_with_execute(notebook): - notebook.execute_cell('execute_foo') - assert notebook.cell_output_text('execute_foo') == 'foo' + notebook.execute_cell("execute_foo") + assert notebook.cell_output_text("execute_foo") == "foo" def test_testbook_with_execute_context_manager(notebook): - notebook.execute_cell('execute_foo') - assert notebook.cell_output_text('execute_foo') == 'foo' + notebook.execute_cell("execute_foo") + assert notebook.cell_output_text("execute_foo") == "foo" def test_testbook_range(): - with testbook('testbook/tests/resources/inject.ipynb') as tb: + with testbook("testbook/tests/resources/inject.ipynb") as tb: tb.execute_cell(range(4)) assert tb.code_cells_executed == 4 - with testbook('testbook/tests/resources/inject.ipynb', execute=range(4)) as tb: + with testbook("testbook/tests/resources/inject.ipynb", execute=range(4)) as tb: assert tb.code_cells_executed == 4 -@pytest.mark.parametrize("slice_params, expected_result", [(('hello', 'str'), 6), ((2, 5), 4)]) +@pytest.mark.parametrize( + "slice_params, expected_result", [(("hello", "str"), 6), ((2, 5), 4)] +) def test_testbook_slice(slice_params, expected_result): - with testbook('testbook/tests/resources/inject.ipynb') as tb: + with testbook("testbook/tests/resources/inject.ipynb") as tb: tb.execute_cell(slice(*slice_params)) assert tb.code_cells_executed == expected_result - with testbook('testbook/tests/resources/inject.ipynb', execute=slice(*slice_params)) as tb: + with testbook( + "testbook/tests/resources/inject.ipynb", execute=slice(*slice_params) + ) as tb: assert tb.code_cells_executed == expected_result def test_testbook_slice_raises_error(): with pytest.raises(TestbookError): - with testbook('testbook/tests/resources/inject.ipynb', execute=slice(3, 1, -1)): + with testbook("testbook/tests/resources/inject.ipynb", execute=slice(3, 1, -1)): pass -@testbook('testbook/tests/resources/exception.ipynb', execute=True) +@testbook("testbook/tests/resources/exception.ipynb", execute=True) def test_raise_exception(tb): with pytest.raises(TestbookRuntimeError): tb.ref("raise_my_exception")() -@testbook('testbook/tests/resources/inject.ipynb') +@testbook("testbook/tests/resources/inject.ipynb") def test_underscore(tb): tb.inject( """ diff --git a/testbook/tests/test_inject.py b/testbook/tests/test_inject.py index 3228316..2228e94 100644 --- a/testbook/tests/test_inject.py +++ b/testbook/tests/test_inject.py @@ -3,9 +3,9 @@ from ..testbook import testbook -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def notebook(): - with testbook('testbook/tests/resources/inject.ipynb', execute=True) as tb: + with testbook("testbook/tests/resources/inject.ipynb", execute=True) as tb: yield tb @@ -20,11 +20,11 @@ def inject_helper(*args, **kwargs): ([1, 2], None), ((1, 2), None), ((True, False), None), - (['a', 'b'], None), - ([1.1, float('nan'), float('inf'), float('-inf')], None), - ([{'key1': 'value1'}, {'key2': 'value2'}], None), - ((1, 2, False), {'key2': 'value2'}), - ((None, None, False), {'key2': 'value2'}), + (["a", "b"], None), + ([1.1, float("nan"), float("inf"), float("-inf")], None), + ([{"key1": "value1"}, {"key2": "value2"}], None), + ((1, 2, False), {"key2": "value2"}), + ((None, None, False), {"key2": "value2"}), ], ) def test_inject(args, kwargs, notebook): @@ -35,19 +35,19 @@ def test_inject(args, kwargs, notebook): "code_block, expected_text", [ ( - ''' + """ def foo(): print('I ran in the code block') foo() - ''', + """, "I ran in the code block", ), ( - ''' + """ def foo(arg): print(f'You passed {arg}') foo('bar') - ''', + """, "You passed bar", ), ], @@ -57,7 +57,7 @@ def test_inject_code_block(code_block, expected_text, notebook): def test_inject_raises_exception(notebook): - values = [3, {'key': 'value'}, ['a', 'b', 'c'], (1, 2, 3), {1, 2, 3}] + values = [3, {"key": "value"}, ["a", "b", "c"], (1, 2, 3), {1, 2, 3}] for value in values: with pytest.raises(TypeError): @@ -76,5 +76,5 @@ def test_inject_before_after(notebook): def test_inject_pop(notebook): - assert notebook.inject("1+1", pop=True).execute_result == [{'text/plain': '2'}] + assert notebook.inject("1+1", pop=True).execute_result == [{"text/plain": "2"}] assert notebook.cells[-1].source != "1+1" diff --git a/testbook/tests/test_patch.py b/testbook/tests/test_patch.py index 6865877..ea69fe0 100644 --- a/testbook/tests/test_patch.py +++ b/testbook/tests/test_patch.py @@ -4,9 +4,9 @@ import pytest -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def tb(): - with testbook('testbook/tests/resources/patch.ipynb', execute=True) as tb: + with testbook("testbook/tests/resources/patch.ipynb", execute=True) as tb: yield tb @@ -27,14 +27,15 @@ def test_patch_raises_error(self, target, func, tb): mock_obj.assert_called_once() def test_patch_return_value(self, tb): - with tb.patch("os.listdir", return_value=['file1', 'file2']) as mock_listdir: - assert tb.ref("listdir")() == ['file1', 'file2'] + with tb.patch("os.listdir", return_value=["file1", "file2"]) as mock_listdir: + assert tb.ref("listdir")() == ["file1", "file2"] mock_listdir.assert_called_once() class TestPatchDict: @pytest.mark.parametrize( - "in_dict, values", [("os.environ", {"PATH": "/usr/bin"})], + "in_dict, values", + [("os.environ", {"PATH": "/usr/bin"})], ) def test_patch_dict(self, in_dict, values, tb): with tb.patch_dict(in_dict, values, clear=True): diff --git a/testbook/tests/test_reference.py b/testbook/tests/test_reference.py index c766aa2..c9d476c 100644 --- a/testbook/tests/test_reference.py +++ b/testbook/tests/test_reference.py @@ -4,9 +4,9 @@ from ..exceptions import TestbookAttributeError, TestbookSerializeError -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def notebook(): - with testbook('testbook/tests/resources/reference.ipynb', execute=True) as tb: + with testbook("testbook/tests/resources/reference.ipynb", execute=True) as tb: yield tb @@ -52,7 +52,7 @@ def test_reference(notebook): # Check that when a non-serializeable object is returned, it returns # a reference to that object instead - f = Foo('bar') + f = Foo("bar") assert repr(f) == "\"\"" @@ -63,7 +63,7 @@ def test_reference(notebook): with pytest.raises(TestbookAttributeError): f.does_not_exist - assert f.say_hello() == 'Hello bar!' + assert f.say_hello() == "Hello bar!" # non JSON-serializeable output with pytest.raises(TestbookSerializeError): diff --git a/testbook/tests/test_testbook.py b/testbook/tests/test_testbook.py index 8247b09..84a9342 100644 --- a/testbook/tests/test_testbook.py +++ b/testbook/tests/test_testbook.py @@ -5,57 +5,59 @@ from ..testbook import testbook -@testbook('testbook/tests/resources/inject.ipynb', execute=True) +@testbook("testbook/tests/resources/inject.ipynb", execute=True) def test_testbook_execute_all_cells(tb): for cell in tb.cells[:-1]: assert cell.execution_count -@testbook('testbook/tests/resources/inject.ipynb', execute='hello') +@testbook("testbook/tests/resources/inject.ipynb", execute="hello") def test_testbook_class_decorator(tb): assert tb.inject("say_hello()") -@testbook('testbook/tests/resources/inject.ipynb') +@testbook("testbook/tests/resources/inject.ipynb") def test_testbook_class_decorator_execute_none(tb): assert tb.code_cells_executed == 0 -@testbook('testbook/tests/resources/inject.ipynb', execute=True) +@testbook("testbook/tests/resources/inject.ipynb", execute=True) def test_testbook_decorator_with_fixture(nb, tmp_path): assert True # Check that the decorator accept to be passed along side a fixture -@testbook('testbook/tests/resources/inject.ipynb', execute=True) -@pytest.mark.parametrize("cell_index_args, expected_result", [(2, 2), ('hello', 1)]) +@testbook("testbook/tests/resources/inject.ipynb", execute=True) +@pytest.mark.parametrize("cell_index_args, expected_result", [(2, 2), ("hello", 1)]) def test_testbook_decorator_with_markers(nb, cell_index_args, expected_result): assert nb._cell_index(cell_index_args) == expected_result -@pytest.mark.parametrize("cell_index_args, expected_result", [(2, 2), ('hello', 1)]) -@testbook('testbook/tests/resources/inject.ipynb', execute=True) -def test_testbook_decorator_with_markers_order_does_not_matter(nb, cell_index_args, expected_result): +@pytest.mark.parametrize("cell_index_args, expected_result", [(2, 2), ("hello", 1)]) +@testbook("testbook/tests/resources/inject.ipynb", execute=True) +def test_testbook_decorator_with_markers_order_does_not_matter( + nb, cell_index_args, expected_result +): assert nb._cell_index(cell_index_args) == expected_result def test_testbook_execute_all_cells_context_manager(): - with testbook('testbook/tests/resources/inject.ipynb', execute=True) as tb: + with testbook("testbook/tests/resources/inject.ipynb", execute=True) as tb: for cell in tb.cells[:-1]: assert cell.execution_count def test_testbook_class_decorator_context_manager(): - with testbook('testbook/tests/resources/inject.ipynb', execute='hello') as tb: + with testbook("testbook/tests/resources/inject.ipynb", execute="hello") as tb: assert tb.inject("say_hello()") def test_testbook_class_decorator_execute_none_context_manager(): - with testbook('testbook/tests/resources/inject.ipynb') as tb: + with testbook("testbook/tests/resources/inject.ipynb") as tb: assert tb.code_cells_executed == 0 def test_testbook_with_file_object(): - f = open('testbook/tests/resources/inject.ipynb') + f = open("testbook/tests/resources/inject.ipynb") with testbook(f) as tb: assert tb.code_cells_executed == 0 @@ -64,14 +66,14 @@ def test_testbook_with_file_object(): def test_testbook_with_notebook_node(): - nb = nbformat.read('testbook/tests/resources/inject.ipynb', as_version=4) + nb = nbformat.read("testbook/tests/resources/inject.ipynb", as_version=4) with testbook(nb) as tb: assert tb.code_cells_executed == 0 def test_function_with_testbook_decorator_returns_value(): - @testbook('testbook/tests/resources/inject.ipynb') + @testbook("testbook/tests/resources/inject.ipynb") def test_function(tb): return "This should be returned" diff --git a/testbook/tests/test_translators.py b/testbook/tests/test_translators.py index e21608a..6708412 100644 --- a/testbook/tests/test_translators.py +++ b/testbook/tests/test_translators.py @@ -1,4 +1,5 @@ """Sourced from https://github.com/nteract/papermill/blob/master/papermill/tests/test_translators.py""" + import pytest from .. import translators @@ -26,27 +27,29 @@ def __repr__(self): (["foo", '"bar"'], '["foo", "\\"bar\\""]'), ([{"foo": "bar"}], '[{"foo": "bar"}]'), ([{"foo": '"bar"'}], '[{"foo": "\\"bar\\""}]'), - (12345, '12345'), - (-54321, '-54321'), - (1.2345, '1.2345'), - (-5432.1, '-5432.1'), - (float('nan'), "float('nan')"), - (float('-inf'), "float('-inf')"), - (float('inf'), "float('inf')"), - (True, 'True'), - (False, 'False'), - (None, 'None'), - (Foo('bar'), '''""'''), + (12345, "12345"), + (-54321, "-54321"), + (1.2345, "1.2345"), + (-5432.1, "-5432.1"), + (float("nan"), "float('nan')"), + (float("-inf"), "float('-inf')"), + (float("inf"), "float('inf')"), + (True, "True"), + (False, "False"), + (None, "None"), + (Foo("bar"), '''""'''), ], ) def test_translate_type_python(test_input, expected): assert translators.PythonTranslator.translate(test_input) == expected -@pytest.mark.parametrize("test_input,expected", [(3.14, "3.14"), (False, "false"), (True, "true")]) +@pytest.mark.parametrize( + "test_input,expected", [(3.14, "3.14"), (False, "false"), (True, "true")] +) def test_translate_float(test_input, expected): assert translators.Translator.translate(test_input) == expected def test_translate_assign(): - assert translators.Translator.assign('var1', [1, 2, 3]) == "var1 = [1, 2, 3]" + assert translators.Translator.assign("var1", [1, 2, 3]) == "var1 = [1, 2, 3]" diff --git a/testbook/translators.py b/testbook/translators.py index 9597a27..7d926da 100644 --- a/testbook/translators.py +++ b/testbook/translators.py @@ -1,4 +1,5 @@ """Sourced from https://github.com/nteract/papermill/blob/master/papermill/translators.py""" + import math import sys @@ -7,16 +8,16 @@ class Translator(object): @classmethod def translate_raw_str(cls, val): """Reusable by most interpreters""" - return '{}'.format(val) + return "{}".format(val) @classmethod def translate_escaped_str(cls, str_val): """Reusable by most interpreters""" if isinstance(str_val, str): - str_val = str_val.encode('unicode_escape') + str_val = str_val.encode("unicode_escape") if sys.version_info >= (3, 0): - str_val = str_val.decode('utf-8') - str_val = str_val.replace('"', r'\"') + str_val = str_val.decode("utf-8") + str_val = str_val.replace('"', r"\"") return '"{}"'.format(str_val) @classmethod @@ -42,15 +43,19 @@ def translate_float(cls, val): @classmethod def translate_bool(cls, val): """Default behavior for translation""" - return 'true' if val else 'false' + return "true" if val else "false" @classmethod def translate_dict(cls, val): - raise NotImplementedError('dict type translation not implemented for {}'.format(cls)) + raise NotImplementedError( + "dict type translation not implemented for {}".format(cls) + ) @classmethod def translate_list(cls, val): - raise NotImplementedError('list type translation not implemented for {}'.format(cls)) + raise NotImplementedError( + "list type translation not implemented for {}".format(cls) + ) @classmethod def translate(cls, val): @@ -80,11 +85,13 @@ def translate(cls, val): @classmethod def comment(cls, cmt_str): - raise NotImplementedError('comment translation not implemented for {}'.format(cls)) + raise NotImplementedError( + "comment translation not implemented for {}".format(cls) + ) @classmethod def assign(cls, name, str_val): - return '{} = {}'.format(name, str_val) + return "{} = {}".format(name, str_val) class PythonTranslator(Translator): @@ -105,17 +112,20 @@ def translate_bool(cls, val): @classmethod def translate_dict(cls, val): - escaped = ', '.join( - ["{}: {}".format(cls.translate_str(k), cls.translate(v)) for k, v in val.items()] + escaped = ", ".join( + [ + "{}: {}".format(cls.translate_str(k), cls.translate(v)) + for k, v in val.items() + ] ) - return '{{{}}}'.format(escaped) + return "{{{}}}".format(escaped) @classmethod def translate_list(cls, val): - escaped = ', '.join([cls.translate(v) for v in val]) - return '[{}]'.format(escaped) + escaped = ", ".join([cls.translate(v) for v in val]) + return "[{}]".format(escaped) @classmethod def translate_tuple(cls, val): - escaped = ', '.join([cls.translate(v) for v in val]) + ', ' - return '({})'.format(escaped) + escaped = ", ".join([cls.translate(v) for v in val]) + ", " + return "({})".format(escaped) diff --git a/testbook/utils.py b/testbook/utils.py index 297a383..ef66a84 100644 --- a/testbook/utils.py +++ b/testbook/utils.py @@ -15,7 +15,7 @@ def random_varname(length=10): -------- random variable name as string of given length """ - return ''.join(random.choice(string.ascii_lowercase) for _ in range(length)) + return "".join(random.choice(string.ascii_lowercase) for _ in range(length)) def all_subclasses(klass): diff --git a/tox.ini b/tox.ini index b124bda..2a98f90 100644 --- a/tox.ini +++ b/tox.ini @@ -1,22 +1,21 @@ [tox] skipsdist = true -envlist = py{36,37,38,39,310,311,312}, flake8, dist, manifest, docs +envlist = py{36,37,38,39,310,311,312}, ruff, dist, manifest, docs [gh-actions] python = 3.6: py36 3.7: py37 - 3.8: py38, flake8, dist, manifest + 3.8: py38 3.9: py39 3.10: py310 - 3.11: py311 + 3.11: py311, ruff, dist, manifest 3.12: py312 # Linters -[testenv:flake8] +[testenv:ruff] skip_install = true -deps = flake8 -commands = flake8 testbook --count --ignore=E203,E731,F811,W503 --max-complexity=23 --max-line-length=104 --show-source --statistics +commands = ruff check # Manifest [testenv:manifest] @@ -44,13 +43,13 @@ commands = python setup.py bdist_wheel --dist-dir={distdir} /bin/bash -c 'python -m pip install -U --force-reinstall {distdir}/testbook*.whl' -# Black -[testenv:black] -description = apply black linter with desired rules +# ruff +[testenv:ruff] +description = apply ruff linter with desired rules basepython = python3.11 deps = - black -commands = black . + ruff +commands = ruff format [testenv] # disable Python's hash randomization for tests that stringify dicts, etc @@ -63,9 +62,11 @@ basepython = py38: python3.8 py39: python3.9 py310: python3.10 - flake8: python3.8 - manifest: python3.8 - dist: python3.8 - docs: python3.8 + py311: python3.11 + py312: python3.12 + ruff: python3.11 + manifest: python3.11 + dist: python3.11 + docs: python3.11 deps = .[dev] commands = pytest -vv --maxfail=2 --cov=testbook --cov-report=xml -W always {posargs} From 7333f19e702659620f1f277d19f1b010d0c3e668 Mon Sep 17 00:00:00 2001 From: Rohit Sanjay Date: Sat, 3 Aug 2024 03:05:38 -0700 Subject: [PATCH 07/17] misc --- .github/workflows/main.yml | 2 +- CONTRIBUTING.md | 4 ++-- MANIFEST.in | 2 -- docs/conf.py | 22 ++++++++-------------- docs/index.md | 6 +++--- pyproject.toml | 9 +++++---- tox.ini | 25 ++++++++----------------- 7 files changed, 27 insertions(+), 43 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 967278d..8a1fc56 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install .[test] + pip install .[dev] pip install tox-gh-actions - name: Run the tests run: tox diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4553c6f..14ac923 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,10 +48,10 @@ If you are contributing with documentation please jump to [building documentatio We need to install the development package before we can run the tests. If anything is confusing below, always resort to the relevant documentation. -For the most basic test runs against python 3.6 use this tox subset (callable after `pip install tox`): +For the most basic test runs against python 3.11 use this tox subset (callable after `pip install tox`): ```bash -tox -e py36 +tox -e py311 ``` This will just execute the unittests against python 3.6 in a new virtual env. The first run will take longer to setup the virtualenv, but will be fast after that point. diff --git a/MANIFEST.in b/MANIFEST.in index 7c57d42..34c23ba 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,8 +5,6 @@ recursive-include testbook *.yaml recursive-include testbook *.keep recursive-include testbook *.txt -include setup.py -include requirements*.txt include tox.ini include pytest.ini include README.md diff --git a/docs/conf.py b/docs/conf.py index 5d98d67..f414996 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,7 +21,7 @@ # -- Project information ----------------------------------------------------- project = "testbook" -copyright = "2020, nteract team" +copyright = "2024, nteract team" author = "nteract team" @@ -51,7 +51,7 @@ # General information about the project. project = "testbook" -copyright = "2020, nteract team" +copyright = "2024, nteract team" author = "nteract team" # The version info for the project you're documenting, acts as replacement for @@ -61,7 +61,7 @@ # The short X.Y version. -version = read_version(project).split(".")[0:2] +version = '.'.join(read_version(project).split(".")[0:2]) # The full version, including alpha/beta/rc tags. release = read_version(project) @@ -71,7 +71,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line foexitr these cases. -language = None +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -109,15 +109,6 @@ # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# This is required for the alabaster theme -# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars -html_sidebars = { - "**": ["about.html", "navigation.html", "relations.html", "searchbox.html"] -} - html_title = "testbook" # -- Options for HTMLHelp output ------------------------------------------ @@ -176,4 +167,7 @@ ] # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {"https://docs.python.org/": None} +intersphinx_mapping = {"python": ("https://docs.python.org/", None)} + +# Generate heading anchors for h1, h2 and h3. +myst_heading_anchors = 3 diff --git a/docs/index.md b/docs/index.md index da31548..1ff5927 100644 --- a/docs/index.md +++ b/docs/index.md @@ -41,9 +41,9 @@ def test_func(tb): ## Features - Write conventional unit tests for Jupyter Notebooks -- [Execute all or some specific cells before unit test](usage/index.html#using-execute-to-control-which-cells-are-executed-before-test) -- [Share kernel context across multiple tests](usage/index.html#share-kernel-context-across-multiple-tests) (using pytest fixtures) -- [Support for patching objects](usage/index.html#support-for-patching-objects) +- [Execute all or some specific cells before unit test](usage/index.md#using-execute-to-control-which-cells-are-executed-before-test) +- [Share kernel context across multiple tests](usage/index.md#share-kernel-context-across-multiple-tests) (using pytest fixtures) +- [Support for patching objects](usage/index.md#support-for-patching-objects) - Inject code into Jupyter notebooks - Works with any unit testing library - unittest, pytest or nose diff --git a/pyproject.toml b/pyproject.toml index 93c28f2..1d132d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,11 +61,12 @@ optional-dependencies.dev = [ "twine>=1.11", "xmltodict", ] -optional-dependencies.test = [ - "myst-parser==0.9.1", - "sphinx>=1.7,<3", - "sphinx-book-theme==0.0.35", +optional-dependencies.docs = [ + "myst-parser==3.0.1", + "sphinx==7.4.7", + "sphinx-book-theme==1.1.3", ] + urls.Documentation = "https://testbook.readthedocs.io" urls.Funding = "https://nteract.io" urls.Issues = "https://github.com/nteract/testbook/issues" diff --git a/tox.ini b/tox.ini index 2a98f90..0e5155d 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,7 @@ python = 3.12: py312 # Linters -[testenv:ruff] +[testenv:lint] skip_install = true commands = ruff check @@ -28,24 +28,15 @@ commands = check-manifest description = invoke sphinx-build to build the HTML docs skip_install = true deps = - .[sphinx] -extras = docs + .[docs] commands = - sphinx-build -d "{toxworkdir}/docs_doctree" docs "{toxworkdir}/docs_out" --color -W -bhtml {posargs} + sphinx-build -d "{toxworkdir}/docs_doctree" docs "{toxworkdir}/docs_out" --color -bhtml {posargs} python -c 'import pathlib; print("documentation available under file://\{0\}".format(pathlib.Path(r"{toxworkdir}") / "docs_out" / "index.html"))' - python "{toxinidir}/docs/conf.py" + python -c "import docs.conf" -# Distro -[testenv:dist] -skip_install = true -# Have to use /bin/bash or the `*` will cause that argument to get quoted by the tox command line... -commands = - python setup.py bdist_wheel --dist-dir={distdir} - /bin/bash -c 'python -m pip install -U --force-reinstall {distdir}/testbook*.whl' - -# ruff -[testenv:ruff] -description = apply ruff linter with desired rules +# Formatter +[testenv:format] +description = apply ruff formatter with desired rules basepython = python3.11 deps = ruff @@ -64,7 +55,7 @@ basepython = py310: python3.10 py311: python3.11 py312: python3.12 - ruff: python3.11 + lint: python3.11 manifest: python3.11 dist: python3.11 docs: python3.11 From e5b0ffb3a7607a8d933ad07fe957a05731551901 Mon Sep 17 00:00:00 2001 From: Rohit Sanjay Date: Sat, 3 Aug 2024 03:13:40 -0700 Subject: [PATCH 08/17] Drop support for 3.6 and 3.7 --- CONTRIBUTING.md | 4 ++-- LICENSE | 2 +- docs/index.md | 6 +++--- pyproject.toml | 4 +--- tox.ini | 6 +----- 5 files changed, 8 insertions(+), 14 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 14ac923..f80a54d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,7 +54,7 @@ For the most basic test runs against python 3.11 use this tox subset (callable a tox -e py311 ``` -This will just execute the unittests against python 3.6 in a new virtual env. The first run will take longer to setup the virtualenv, but will be fast after that point. +This will just execute the unittests against python 3.11 in a new virtual env. The first run will take longer to setup the virtualenv, but will be fast after that point. For a full test suite of all envs and linting checks simply run tox without any arguments @@ -62,7 +62,7 @@ For a full test suite of all envs and linting checks simply run tox without any tox ``` -This will require python3.5, python3.6, python3.7, and python 3.8 to be installed. +This will require python3.7, and python 3.8 to be installed. Alternavitely pytest can be used if you have an environment already setup which works or has custom packages not present in the tox build. diff --git a/LICENSE b/LICENSE index 596cc60..98d5d22 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2020, nteract +Copyright (c) 2024, nteract All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/docs/index.md b/docs/index.md index 1ff5927..a4572d3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,9 +5,9 @@ [![Coverage Status][codecov-badge]][codecov-link] [![Documentation Status][rtd-badge]][rtd-link] [![PyPI][pypi-badge]][pypi-link] -[![Python 3.6](https://img.shields.io/badge/python-3.6-blue.svg)](https://www.python.org/downloads/release/python-360/) -[![Python 3.7](https://img.shields.io/badge/python-3.7-blue.svg)](https://www.python.org/downloads/release/python-370/) -[![Python 3.8](https://img.shields.io/badge/python-3.8-blue.svg)](https://www.python.org/downloads/release/python-380/) +[![image](https://img.shields.io/pypi/v/testbook.svg)](https://pypi.python.org/pypi/testbook) +[![image](https://img.shields.io/pypi/l/testbook.svg)](https://github.com/astral-sh/testbook/blob/main/LICENSE) +[![image](https://img.shields.io/pypi/pyversions/testbook.svg)](https://pypi.python.org/pypi/testbook) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) **testbook** is a unit testing framework for testing code in Jupyter Notebooks. diff --git a/pyproject.toml b/pyproject.toml index 1d132d4..9843fa1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,15 +20,13 @@ authors = [ { name = "Rohit Sanjay", email = "sanjay.rohit2@gmail.com" }, { name = "Matthew Seal", email = "mseal007@gmail.com" }, ] -requires-python = ">=3.6" +requires-python = ">=3.8" classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", diff --git a/tox.ini b/tox.ini index 0e5155d..2cbdae0 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,9 @@ [tox] skipsdist = true -envlist = py{36,37,38,39,310,311,312}, ruff, dist, manifest, docs +envlist = py{38,39,310,311,312}, ruff, dist, manifest, docs [gh-actions] python = - 3.6: py36 - 3.7: py37 3.8: py38 3.9: py39 3.10: py310 @@ -48,8 +46,6 @@ setenv = PYTHONHASHSEED = 0 passenv = * basepython = - py36: python3.6 - py37: python3.7 py38: python3.8 py39: python3.9 py310: python3.10 From d47e51bf7f503034d8816508326171eade1c249f Mon Sep 17 00:00:00 2001 From: Rohit Sanjay Date: Sat, 3 Aug 2024 03:13:51 -0700 Subject: [PATCH 09/17] remove from CI --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8a1fc56..918bd9f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - name: Checkout uses: actions/checkout@v4 From df445d2b55f2b2e7e2ea73242dd808dac728d4c0 Mon Sep 17 00:00:00 2001 From: Rohit Sanjay Date: Sat, 3 Aug 2024 03:31:03 -0700 Subject: [PATCH 10/17] run docs for sanity check --- .readthedocs.yml | 4 ++-- tox.ini | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 92a1198..96afe15 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -14,9 +14,9 @@ build: image: latest python: - version: 3.7 + version: 3.11 install: - method: pip path: . extra_requirements: - - sphinx + - docs diff --git a/tox.ini b/tox.ini index 2cbdae0..8cc3954 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,13 @@ [tox] skipsdist = true -envlist = py{38,39,310,311,312}, ruff, dist, manifest, docs +envlist = py{38,39,310,311,312}, lint, manifest, docs [gh-actions] python = 3.8: py38 3.9: py39 3.10: py310 - 3.11: py311, ruff, dist, manifest + 3.11: py311, lint, manifest 3.12: py312 # Linters @@ -53,7 +53,6 @@ basepython = py312: python3.12 lint: python3.11 manifest: python3.11 - dist: python3.11 docs: python3.11 deps = .[dev] commands = pytest -vv --maxfail=2 --cov=testbook --cov-report=xml -W always {posargs} From 1aa1935c80d18c972d14e826dcc72004a54ea0c2 Mon Sep 17 00:00:00 2001 From: Rohit Sanjay Date: Sat, 3 Aug 2024 03:34:05 -0700 Subject: [PATCH 11/17] docs --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 8cc3954..eee5d8e 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,7 @@ python = 3.8: py38 3.9: py39 3.10: py310 - 3.11: py311, lint, manifest + 3.11: py311, lint, manifest, docs 3.12: py312 # Linters From 009ef9fd5470da9acc08bae2115616651499298d Mon Sep 17 00:00:00 2001 From: Rohit Sanjay Date: Sat, 3 Aug 2024 09:58:06 -0700 Subject: [PATCH 12/17] rename to testbook --- pyproject.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9843fa1..a7e1bbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,9 @@ [build-system] -build-backend = "setuptools.build_meta" - requires = [ "setuptools" ] +build-backend = "setuptools.build_meta" [project] -name = "testbook_rohitsanjay" +name = "testbook" version = "0.4.2" description = "A unit testing framework for Jupyter Notebooks" readme = "README.md" From 5d3e725e719a67dbc4caccecb6088311cee81d9b Mon Sep 17 00:00:00 2001 From: Rohit Sanjay Date: Sat, 3 Aug 2024 10:09:02 -0700 Subject: [PATCH 13/17] build and push to testpypi --- .github/workflows/main.yml | 113 ++++++++++++++++++++++++++++++++ .github/workflows/publish.yml | 117 ---------------------------------- 2 files changed, 113 insertions(+), 117 deletions(-) delete mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 918bd9f..596566c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,3 +35,116 @@ jobs: flags: unittests name: codecov-umbrella fail_ci_if_error: false + + build: + name: Build distribution 📦 + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Install pypa/build + run: >- + python3 -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: python3 -m build + - name: Store the distribution packages + uses: actions/upload-artifact@v3 + with: + name: python-package-distributions + path: dist/ + + publish-to-pypi: + name: >- + Publish Python 🐍 distribution 📦 to PyPI + if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes + needs: + - build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/testbook + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + + steps: + - name: Download all the dists + uses: actions/download-artifact@v3 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + + github-release: + name: >- + Sign the Python 🐍 distribution 📦 with Sigstore + and upload them to GitHub Release + needs: + - publish-to-pypi + runs-on: ubuntu-latest + + permissions: + contents: write # IMPORTANT: mandatory for making GitHub Releases + id-token: write # IMPORTANT: mandatory for sigstore + + steps: + - name: Download all the dists + uses: actions/download-artifact@v3 + with: + name: python-package-distributions + path: dist/ + - name: Sign the dists with Sigstore + uses: sigstore/gh-action-sigstore-python@v2.1.1 + with: + inputs: >- + ./dist/*.tar.gz + ./dist/*.whl + - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + run: >- + gh release create + '${{ github.ref_name }}' + --repo '${{ github.repository }}' + --notes "" + - name: Upload artifact signatures to GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + # Upload to GitHub Release using the `gh` CLI. + # `dist/` contains the built packages, and the + # sigstore-produced signatures and certificates. + run: >- + gh release upload + '${{ github.ref_name }}' dist/** + --repo '${{ github.repository }}' + + publish-to-testpypi: + name: Publish Python 🐍 distribution 📦 to TestPyPI + needs: + - build + runs-on: ubuntu-latest + + environment: + name: testpypi + url: https://test.pypi.org/p/testbook + + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + + steps: + - name: Download all the dists + uses: actions/download-artifact@v3 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 059e18c..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,117 +0,0 @@ -name: Publish testbook - -on: push - -jobs: - build: - name: Build distribution 📦 - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - name: Install pypa/build - run: >- - python3 -m - pip install - build - --user - - name: Build a binary wheel and a source tarball - run: python3 -m build - - name: Store the distribution packages - uses: actions/upload-artifact@v3 - with: - name: python-package-distributions - path: dist/ - - publish-to-pypi: - name: >- - Publish Python 🐍 distribution 📦 to PyPI - if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes - needs: - - build - runs-on: ubuntu-latest - environment: - name: pypi - url: https://pypi.org/p/testbook - permissions: - id-token: write # IMPORTANT: mandatory for trusted publishing - - steps: - - name: Download all the dists - uses: actions/download-artifact@v3 - with: - name: python-package-distributions - path: dist/ - - name: Publish distribution 📦 to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - - github-release: - name: >- - Sign the Python 🐍 distribution 📦 with Sigstore - and upload them to GitHub Release - needs: - - publish-to-pypi - runs-on: ubuntu-latest - - permissions: - contents: write # IMPORTANT: mandatory for making GitHub Releases - id-token: write # IMPORTANT: mandatory for sigstore - - steps: - - name: Download all the dists - uses: actions/download-artifact@v3 - with: - name: python-package-distributions - path: dist/ - - name: Sign the dists with Sigstore - uses: sigstore/gh-action-sigstore-python@v2.1.1 - with: - inputs: >- - ./dist/*.tar.gz - ./dist/*.whl - - name: Create GitHub Release - env: - GITHUB_TOKEN: ${{ github.token }} - run: >- - gh release create - '${{ github.ref_name }}' - --repo '${{ github.repository }}' - --notes "" - - name: Upload artifact signatures to GitHub Release - env: - GITHUB_TOKEN: ${{ github.token }} - # Upload to GitHub Release using the `gh` CLI. - # `dist/` contains the built packages, and the - # sigstore-produced signatures and certificates. - run: >- - gh release upload - '${{ github.ref_name }}' dist/** - --repo '${{ github.repository }}' - - publish-to-testpypi: - name: Publish Python 🐍 distribution 📦 to TestPyPI - needs: - - build - runs-on: ubuntu-latest - - environment: - name: testpypi - url: https://test.pypi.org/p/testbook - - permissions: - id-token: write # IMPORTANT: mandatory for trusted publishing - - steps: - - name: Download all the dists - uses: actions/download-artifact@v3 - with: - name: python-package-distributions - path: dist/ - - name: Publish distribution 📦 to TestPyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - repository-url: https://test.pypi.org/legacy/ From 1a3245454b04529136d1929ff7e1d77f3eb7bd7d Mon Sep 17 00:00:00 2001 From: Rohit Sanjay Date: Sat, 3 Aug 2024 10:12:03 -0700 Subject: [PATCH 14/17] remove automatic packaging --- .github/workflows/main.yml | 113 ------------------------------------- 1 file changed, 113 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 596566c..918bd9f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,116 +35,3 @@ jobs: flags: unittests name: codecov-umbrella fail_ci_if_error: false - - build: - name: Build distribution 📦 - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - name: Install pypa/build - run: >- - python3 -m - pip install - build - --user - - name: Build a binary wheel and a source tarball - run: python3 -m build - - name: Store the distribution packages - uses: actions/upload-artifact@v3 - with: - name: python-package-distributions - path: dist/ - - publish-to-pypi: - name: >- - Publish Python 🐍 distribution 📦 to PyPI - if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes - needs: - - build - runs-on: ubuntu-latest - environment: - name: pypi - url: https://pypi.org/p/testbook - permissions: - id-token: write # IMPORTANT: mandatory for trusted publishing - - steps: - - name: Download all the dists - uses: actions/download-artifact@v3 - with: - name: python-package-distributions - path: dist/ - - name: Publish distribution 📦 to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - - github-release: - name: >- - Sign the Python 🐍 distribution 📦 with Sigstore - and upload them to GitHub Release - needs: - - publish-to-pypi - runs-on: ubuntu-latest - - permissions: - contents: write # IMPORTANT: mandatory for making GitHub Releases - id-token: write # IMPORTANT: mandatory for sigstore - - steps: - - name: Download all the dists - uses: actions/download-artifact@v3 - with: - name: python-package-distributions - path: dist/ - - name: Sign the dists with Sigstore - uses: sigstore/gh-action-sigstore-python@v2.1.1 - with: - inputs: >- - ./dist/*.tar.gz - ./dist/*.whl - - name: Create GitHub Release - env: - GITHUB_TOKEN: ${{ github.token }} - run: >- - gh release create - '${{ github.ref_name }}' - --repo '${{ github.repository }}' - --notes "" - - name: Upload artifact signatures to GitHub Release - env: - GITHUB_TOKEN: ${{ github.token }} - # Upload to GitHub Release using the `gh` CLI. - # `dist/` contains the built packages, and the - # sigstore-produced signatures and certificates. - run: >- - gh release upload - '${{ github.ref_name }}' dist/** - --repo '${{ github.repository }}' - - publish-to-testpypi: - name: Publish Python 🐍 distribution 📦 to TestPyPI - needs: - - build - runs-on: ubuntu-latest - - environment: - name: testpypi - url: https://test.pypi.org/p/testbook - - permissions: - id-token: write # IMPORTANT: mandatory for trusted publishing - - steps: - - name: Download all the dists - uses: actions/download-artifact@v3 - with: - name: python-package-distributions - path: dist/ - - name: Publish distribution 📦 to TestPyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - repository-url: https://test.pypi.org/legacy/ From 181b4cfeab91ef780304262be113c2351f1aee26 Mon Sep 17 00:00:00 2001 From: Rohit Sanjay Date: Sat, 3 Aug 2024 10:16:21 -0700 Subject: [PATCH 15/17] remove _version references --- pyproject.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a7e1bbd..33e9110 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,6 @@ testpaths = [ branch = false omit = [ "testbook/tests/*", - "testbook/_version.py", ] [tool.coverage.report] @@ -93,7 +92,6 @@ exclude_lines = [ ignore_errors = true omit = [ "testbook/tests/*", - "testbook/_version.py", ] [tool.ruff] From 334e71239ae0b295aa4c7969a3048c1d23cb164d Mon Sep 17 00:00:00 2001 From: Rohit Sanjay Date: Sat, 3 Aug 2024 17:56:05 -0700 Subject: [PATCH 16/17] single quote --- docs/conf.py | 68 ++++++++--------- examples/dataframe-example/dataframe_test.py | 8 +- examples/requests-example/requests_test.py | 10 +-- examples/stdout-example/stdout_test.py | 6 +- pyproject.toml | 3 +- testbook/client.py | 78 ++++++++++---------- testbook/reference.py | 20 ++--- testbook/testbook.py | 2 +- testbook/testbooknode.py | 14 ++-- testbook/tests/conftest.py | 18 ++--- testbook/tests/test_client.py | 64 ++++++++-------- testbook/tests/test_datamodel.py | 40 +++++----- testbook/tests/test_execute.py | 46 ++++++------ testbook/tests/test_inject.py | 38 +++++----- testbook/tests/test_patch.py | 16 ++-- testbook/tests/test_reference.py | 32 ++++---- testbook/tests/test_testbook.py | 36 ++++----- testbook/tests/test_translators.py | 48 ++++++------ testbook/translators.py | 34 ++++----- testbook/utils.py | 2 +- 20 files changed, 291 insertions(+), 292 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index f414996..f643a14 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,14 +15,14 @@ from importlib.metadata import version as read_version -sys.path.insert(0, os.path.abspath("..")) +sys.path.insert(0, os.path.abspath('..')) # -- Project information ----------------------------------------------------- -project = "testbook" -copyright = "2024, nteract team" -author = "nteract team" +project = 'testbook' +copyright = '2024, nteract team' +author = 'nteract team' # -- General configuration --------------------------------------------------- @@ -31,28 +31,28 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.intersphinx", - "sphinx.ext.mathjax", - "sphinx.ext.napoleon", - "myst_parser", + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.mathjax', + 'sphinx.ext.napoleon', + 'myst_parser', ] # Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] +templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # -source_suffix = [".rst", ".md"] +source_suffix = ['.rst', '.md'] # The master toctree document. -master_doc = "index" +master_doc = 'index' # General information about the project. -project = "testbook" -copyright = "2024, nteract team" -author = "nteract team" +project = 'testbook' +copyright = '2024, nteract team' +author = 'nteract team' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -61,7 +61,7 @@ # The short X.Y version. -version = '.'.join(read_version(project).split(".")[0:2]) +version = '.'.join(read_version(project).split('.')[0:2]) # The full version, including alpha/beta/rc tags. release = read_version(project) @@ -71,15 +71,15 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line foexitr these cases. -language = "en" +language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "UPDATE.md"] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'UPDATE.md'] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" +pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -90,7 +90,7 @@ # 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_theme = 'sphinx_book_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -98,10 +98,10 @@ # html_theme_options = { - "path_to_docs": "docs", - "repository_url": "https://github.com/nteract/testbook", - "repository_branch": "main", - "use_edit_page_button": True, + 'path_to_docs': 'docs', + 'repository_url': 'https://github.com/nteract/testbook', + 'repository_branch': 'main', + 'use_edit_page_button': True, } # Add any paths that contain custom static files (such as style sheets) here, @@ -109,12 +109,12 @@ # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] -html_title = "testbook" +html_title = 'testbook' # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. -htmlhelp_basename = "testbookdoc" +htmlhelp_basename = 'testbookdoc' # -- Options for LaTeX output --------------------------------------------- @@ -138,7 +138,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, "testbook.tex", "testbook Documentation", "nteract team", "manual") + (master_doc, 'testbook.tex', 'testbook Documentation', 'nteract team', 'manual') ] @@ -146,7 +146,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [(master_doc, "testbook", "testbook Documentation", [author], 1)] +man_pages = [(master_doc, 'testbook', 'testbook Documentation', [author], 1)] # -- Options for Texinfo output ------------------------------------------- @@ -157,17 +157,17 @@ texinfo_documents = [ ( master_doc, - "testbook", - "testbook Documentation", + 'testbook', + 'testbook Documentation', author, - "testbook", - "One line description of project.", - "Miscellaneous", + 'testbook', + 'One line description of project.', + 'Miscellaneous', ) ] # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {"python": ("https://docs.python.org/", None)} +intersphinx_mapping = {'python': ('https://docs.python.org/', None)} # Generate heading anchors for h1, h2 and h3. myst_heading_anchors = 3 diff --git a/examples/dataframe-example/dataframe_test.py b/examples/dataframe-example/dataframe_test.py index fa2ffbe..4c10181 100644 --- a/examples/dataframe-example/dataframe_test.py +++ b/examples/dataframe-example/dataframe_test.py @@ -1,9 +1,9 @@ from testbook import testbook -@testbook("./dataframe-assertion-example.ipynb") +@testbook('./dataframe-assertion-example.ipynb') def test_dataframe_manipulation(tb): - tb.execute_cell("imports") + tb.execute_cell('imports') # Inject a dataframe with code tb.inject( @@ -13,7 +13,7 @@ def test_dataframe_manipulation(tb): ) # Perform manipulation - tb.execute_cell("manipulation") + tb.execute_cell('manipulation') # Inject assertion into notebook - tb.inject("assert len(df) == 1") + tb.inject('assert len(df) == 1') diff --git a/examples/requests-example/requests_test.py b/examples/requests-example/requests_test.py index 49c1757..49bd633 100644 --- a/examples/requests-example/requests_test.py +++ b/examples/requests-example/requests_test.py @@ -1,10 +1,10 @@ from testbook import testbook -@testbook("./requests-test.ipynb", execute=True) +@testbook('./requests-test.ipynb', execute=True) def test_get_details(tb): - with tb.patch("requests.get") as mock_get: - get_details = tb.ref("get_details") # get reference to function - get_details("https://my-api.com") + with tb.patch('requests.get') as mock_get: + get_details = tb.ref('get_details') # get reference to function + get_details('https://my-api.com') - mock_get.assert_called_with("https://my-api.com") + mock_get.assert_called_with('https://my-api.com') diff --git a/examples/stdout-example/stdout_test.py b/examples/stdout-example/stdout_test.py index ead54fb..95c0d51 100644 --- a/examples/stdout-example/stdout_test.py +++ b/examples/stdout-example/stdout_test.py @@ -1,8 +1,8 @@ from testbook import testbook -@testbook("stdout-assertion-example.ipynb", execute=True) +@testbook('stdout-assertion-example.ipynb', execute=True) def test_stdout(tb): - assert tb.cell_output_text(1) == "hello world!" + assert tb.cell_output_text(1) == 'hello world!' - assert "The current time is" in tb.cell_output_text(2) + assert 'The current time is' in tb.cell_output_text(2) diff --git a/pyproject.toml b/pyproject.toml index 33e9110..fdd0f68 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -148,8 +148,7 @@ unfixable = [] dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" [tool.ruff.format] -# Like Black, use double quotes for strings. -quote-style = "double" +quote-style = "single" # Like Black, indent with spaces, rather than tabs. indent-style = "space" diff --git a/testbook/client.py b/testbook/client.py index 8e5c9c9..639ce64 100644 --- a/testbook/client.py +++ b/testbook/client.py @@ -25,13 +25,13 @@ class TestbookNotebookClient(NotebookClient): def __init__(self, nb, km=None, **kw): # Fix the ipykernel 5.5 issue where execute requests after errors are aborted - ea = kw.get("extra_arguments", []) + ea = kw.get('extra_arguments', []) if not any( - arg.startswith("--Kernel.stop_on_error_timeout") + arg.startswith('--Kernel.stop_on_error_timeout') for arg in self.extra_arguments ): - ea.append("--Kernel.stop_on_error_timeout=0") - kw["extra_arguments"] = ea + ea.append('--Kernel.stop_on_error_timeout=0') + kw['extra_arguments'] = ea super().__init__(nb, km=km, **kw) def ref(self, name: str) -> Union[TestbookObjectReference, Any]: @@ -42,7 +42,7 @@ def ref(self, name: str) -> Union[TestbookObjectReference, Any]: # Check if exists self.inject(name, pop=True) try: - self.inject(f"import json; json.dumps({name})", pop=True) + self.inject(f'import json; json.dumps({name})', pop=True) return self.value(name) except Exception: return TestbookObjectReference(self, name) @@ -81,22 +81,22 @@ def _execute_result(cell) -> List: """ return [ - output["data"] - for output in cell["outputs"] - if output["output_type"] == "execute_result" + output['data'] + for output in cell['outputs'] + if output['output_type'] == 'execute_result' ] @staticmethod def _output_text(cell) -> str: - if "outputs" not in cell: - raise ValueError("cell must be a code cell") + if 'outputs' not in cell: + raise ValueError('cell must be a code cell') - text = "" - for output in cell["outputs"]: - if "text" in output: - text += output["text"] - elif "data" in output and "text/plain" in output["data"]: - text += output["data"]["text/plain"] + text = '' + for output in cell['outputs']: + if 'text' in output: + text += output['text'] + elif 'data' in output and 'text/plain' in output['data']: + text += output['data']['text/plain'] return text.strip() @@ -108,11 +108,11 @@ def _cell_index(self, tag: Union[int, str]) -> int: if isinstance(tag, int): return tag elif not isinstance(tag, str): - raise TypeError("expected tag as str") + raise TypeError('expected tag as str') for idx, cell in enumerate(self.cells): - metadata = cell["metadata"] - if "tags" in metadata and tag in metadata["tags"]: + metadata = cell['metadata'] + if 'tags' in metadata and tag in metadata['tags']: return idx raise TestbookCellTagNotFoundError("Cell tag '{}' not found".format(tag)) @@ -124,7 +124,7 @@ def execute_cell(self, cell, **kwargs) -> Union[Dict, List[Dict]]: if isinstance(cell, slice): start, stop = self._cell_index(cell.start), self._cell_index(cell.stop) if cell.step is not None: - raise TestbookError("testbook does not support step argument") + raise TestbookError('testbook does not support step argument') cell = range(start, stop + 1) elif isinstance(cell, str) or isinstance(cell, int): @@ -138,7 +138,7 @@ def execute_cell(self, cell, **kwargs) -> Union[Dict, List[Dict]]: executed_cells = [] for idx in cell_indexes: try: - cell = super().execute_cell(self.nb["cells"][idx], idx, **kwargs) + cell = super().execute_cell(self.nb['cells'][idx], idx, **kwargs) except CellExecutionError as ce: raise TestbookRuntimeError( ce.evalue, ce, self._get_error_class(ce.ename) @@ -161,7 +161,7 @@ def cell_output_text(self, cell) -> str: Return cell text output """ cell_index = self._cell_index(cell) - return self._output_text(self.nb["cells"][cell_index]) + return self._output_text(self.nb['cells'][cell_index]) def cell_execute_result(self, cell: Union[int, str]) -> List[Dict[str, Any]]: """Return the execute results of cell at a given index or with a given tag. @@ -188,7 +188,7 @@ def cell_execute_result(self, cell: Union[int, str]) -> List[Dict[str, Any]]: If tag is not found """ cell_index = self._cell_index(cell) - return self._execute_result(self.nb["cells"][cell_index]) + return self._execute_result(self.nb['cells'][cell_index]) def inject( self, @@ -229,15 +229,15 @@ def inject( lines = getsource(code) + ( dedent(self._construct_call_code(code.__name__, args, kwargs)) if run - else "" + else '' ) else: - raise TypeError("can only inject function or code block as str") + raise TypeError('can only inject function or code block as str') inject_idx = len(self.cells) if after is not None and before is not None: - raise ValueError("pass either before or after as kwargs") + raise ValueError('pass either before or after as kwargs') elif before is not None: inject_idx = self._cell_index(before) elif after is not None: @@ -253,8 +253,8 @@ def inject( ) if self._contains_error(cell): - eclass = self._get_error_class(cell.get("outputs")[0]["ename"]) - evalue = cell.get("outputs")[0]["evalue"] + eclass = self._get_error_class(cell.get('outputs')[0]['ename']) + evalue = cell.get('outputs')[0]['evalue'] raise TestbookRuntimeError(evalue, evalue, eclass) if run and pop: @@ -290,7 +290,7 @@ def value(self, code: str) -> Any: if not self._execute_result(result): raise TestbookExecuteResultNotFoundError( - "code provided does not produce execute_result" + 'code provided does not produce execute_result' ) save_varname = random_varname() @@ -309,24 +309,24 @@ def value(self, code: str) -> Any: try: outputs = self.inject(inject_code, pop=True).outputs - if outputs[0].output_type == "error": + if outputs[0].output_type == 'error': # will receive error when `allow_errors` is set to True raise TestbookRuntimeError( outputs[0].evalue, outputs[0].traceback, outputs[0].ename ) - return outputs[0].data["application/json"]["value"] + return outputs[0].data['application/json']['value'] except TestbookRuntimeError: - e = TestbookSerializeError("could not JSON serialize output") + e = TestbookSerializeError('could not JSON serialize output') e.save_varname = save_varname raise e @contextmanager def patch(self, target, **kwargs): """Used as contextmanager to patch objects in the kernel""" - mock_object = f"_mock_{random_varname()}" - patcher = f"_patcher_{random_varname()}" + mock_object = f'_mock_{random_varname()}' + patcher = f'_patcher_{random_varname()}' self.inject( f""" @@ -341,13 +341,13 @@ def patch(self, target, **kwargs): yield TestbookObjectReference(self, mock_object) - self.inject(f"{patcher}.stop()") + self.inject(f'{patcher}.stop()') @contextmanager def patch_dict(self, in_dict, values=(), clear=False, **kwargs): """Used as contextmanager to patch dictionaries in the kernel""" - mock_object = f"_mock_{random_varname()}" - patcher = f"_patcher_{random_varname()}" + mock_object = f'_mock_{random_varname()}' + patcher = f'_patcher_{random_varname()}' self.inject( f""" @@ -364,7 +364,7 @@ def patch_dict(self, in_dict, values=(), clear=False, **kwargs): yield TestbookObjectReference(self, mock_object) - self.inject(f"{patcher}.stop()") + self.inject(f'{patcher}.stop()') @staticmethod def _get_error_class(ename): @@ -377,4 +377,4 @@ def _get_error_class(ename): @staticmethod def _contains_error(result): - return result.get("outputs") and result.get("outputs")[0].output_type == "error" + return result.get('outputs') and result.get('outputs')[0].output_type == 'error' diff --git a/testbook/reference.py b/testbook/reference.py index 89ead9a..3a0b0c6 100644 --- a/testbook/reference.py +++ b/testbook/reference.py @@ -15,27 +15,27 @@ def __init__(self, tb, name): @property def _type(self): - return self.tb.value(f"type({self.name}).__name__") + return self.tb.value(f'type({self.name}).__name__') def __repr__(self): - return repr(self.tb.value(f"repr({self.name})")) + return repr(self.tb.value(f'repr({self.name})')) def __getattr__(self, name): if self.tb.value(f"hasattr({self.name}, '{name}')"): - return TestbookObjectReference(self.tb, f"{self.name}.{name}") + return TestbookObjectReference(self.tb, f'{self.name}.{name}') raise TestbookAttributeError(f"'{self._type}' object has no attribute {name}") def __eq__(self, rhs): return self.tb.value( - "{lhs} == {rhs}".format(lhs=self.name, rhs=PythonTranslator.translate(rhs)) + '{lhs} == {rhs}'.format(lhs=self.name, rhs=PythonTranslator.translate(rhs)) ) def __len__(self): - return self.tb.value(f"len({self.name})") + return self.tb.value(f'len({self.name})') def __iter__(self): - iterobjectname = f"___iter_object_{random_varname()}" + iterobjectname = f'___iter_object_{random_varname()}' self.tb.inject(f""" {iterobjectname} = iter({self.name}) """) @@ -43,7 +43,7 @@ def __iter__(self): def __next__(self): try: - return self.tb.value(f"next({self.name})") + return self.tb.value(f'next({self.name})') except TestbookRuntimeError as e: if e.eclass is StopIteration: raise StopIteration @@ -53,7 +53,7 @@ def __next__(self): def __getitem__(self, key): try: return self.tb.value( - f"{self.name}.__getitem__({PythonTranslator.translate(key)})" + f'{self.name}.__getitem__({PythonTranslator.translate(key)})' ) except TestbookRuntimeError as e: if e.eclass is TypeError: @@ -66,7 +66,7 @@ def __getitem__(self, key): def __setitem__(self, key, value): try: return self.tb.inject( - "{name}[{key}] = {value}".format( + '{name}[{key}] = {value}'.format( name=self.name, key=PythonTranslator.translate(key), value=PythonTranslator.translate(value), @@ -83,7 +83,7 @@ def __setitem__(self, key, value): def __contains__(self, item): return self.tb.value( - f"{self.name}.__contains__({PythonTranslator.translate(item)})" + f'{self.name}.__contains__({PythonTranslator.translate(item)})' ) def __call__(self, *args, **kwargs): diff --git a/testbook/testbook.py b/testbook/testbook.py index ebe10af..d70000b 100644 --- a/testbook/testbook.py +++ b/testbook/testbook.py @@ -32,7 +32,7 @@ def __init__( nb, execute=None, timeout=60, - kernel_name="python3", + kernel_name='python3', allow_errors=False, **kwargs, ): diff --git a/testbook/testbooknode.py b/testbook/testbooknode.py index b39fd66..9a3e2af 100644 --- a/testbook/testbooknode.py +++ b/testbook/testbooknode.py @@ -11,10 +11,10 @@ def __init__(self, *args, **kw): @property def output_text(self) -> str: - text = "" - for output in self["outputs"]: - if "text" in output: - text += output["text"] + text = '' + for output in self['outputs']: + if 'text' in output: + text += output['text'] return text.strip() @@ -22,7 +22,7 @@ def output_text(self) -> str: def execute_result(self): """Return data from execute_result outputs""" return [ - output["data"] - for output in self["outputs"] - if output["output_type"] == "execute_result" + output['data'] + for output in self['outputs'] + if output['output_type'] == 'execute_result' ] diff --git a/testbook/tests/conftest.py b/testbook/tests/conftest.py index c6fbd7c..68fcff0 100644 --- a/testbook/tests/conftest.py +++ b/testbook/tests/conftest.py @@ -20,10 +20,10 @@ def notebook_generator(cells: Optional[List[NotebookNode]] = None) -> NotebookNo for name in kernelspec.find_kernel_specs(): ks = kernelspec.get_kernel_spec(name) metadata = { - "kernelspec": { - "name": name, - "language": ks.language, - "display_name": ks.display_name, + 'kernelspec': { + 'name': name, + 'language': ks.language, + 'display_name': ks.display_name, } } break @@ -32,12 +32,12 @@ def notebook_generator(cells: Optional[List[NotebookNode]] = None) -> NotebookNo all_cells = cells else: # Default cells all_cells = [ - new_code_cell("a = 2", metadata={"tags": []}), - new_code_cell("b=22\nb", metadata={"tags": ["test"]}), + new_code_cell('a = 2', metadata={'tags': []}), + new_code_cell('b=22\nb', metadata={'tags': ['test']}), new_code_cell( - "", - metadata={"tags": ["dummy-outputs"]}, - outputs=[new_output("execute_result", data={"text/plain": "text"})], + '', + metadata={'tags': ['dummy-outputs']}, + outputs=[new_output('execute_result', data={'text/plain': 'text'})], ), ] diff --git a/testbook/tests/test_client.py b/testbook/tests/test_client.py index b019bb5..e57f4d5 100644 --- a/testbook/tests/test_client.py +++ b/testbook/tests/test_client.py @@ -9,20 +9,20 @@ ) -@pytest.fixture(scope="module") +@pytest.fixture(scope='module') def notebook(): - with testbook("testbook/tests/resources/inject.ipynb", execute=True) as tb: + with testbook('testbook/tests/resources/inject.ipynb', execute=True) as tb: yield tb -@pytest.mark.parametrize("cell_index_args, expected_result", [(2, 2), ("hello", 1)]) +@pytest.mark.parametrize('cell_index_args, expected_result', [(2, 2), ('hello', 1)]) def test_cell_index(cell_index_args, expected_result, notebook): assert notebook._cell_index(cell_index_args) == expected_result @pytest.mark.parametrize( - "cell_index_args, expected_error", - [([1, 2, 3], TypeError), ("non-existent-tag", TestbookCellTagNotFoundError)], + 'cell_index_args, expected_error', + [([1, 2, 3], TypeError), ('non-existent-tag', TestbookCellTagNotFoundError)], ) def test_cell_index_raises_error(cell_index_args, expected_error, notebook): with pytest.raises(expected_error): @@ -30,40 +30,40 @@ def test_cell_index_raises_error(cell_index_args, expected_error, notebook): @pytest.mark.parametrize( - "var_name, expected_result", + 'var_name, expected_result', [ - ("sample_dict", {"foo": "bar"}), - ("sample_list", ["foo", "bar"]), - ('sample_list + ["hello world"]', ["foo", "bar", "hello world"]), - ("sample_int", 42), - ("sample_int * 2", 84), - ("sample_str", "hello world"), - ('sample_str + " foo"', "hello world foo"), + ('sample_dict', {'foo': 'bar'}), + ('sample_list', ['foo', 'bar']), + ('sample_list + ["hello world"]', ['foo', 'bar', 'hello world']), + ('sample_int', 42), + ('sample_int * 2', 84), + ('sample_str', 'hello world'), + ('sample_str + " foo"', 'hello world foo'), ], ) def test_value(var_name, expected_result, notebook): assert notebook.value(var_name) == expected_result -@pytest.mark.parametrize("code", [("sample_int *= 2"), ("print(sample_int)"), ("")]) +@pytest.mark.parametrize('code', [('sample_int *= 2'), ('print(sample_int)'), ('')]) def test_value_raises_error(code, notebook): with pytest.raises(TestbookExecuteResultNotFoundError): notebook.value(code) @pytest.mark.parametrize( - "cell, expected_result", + 'cell, expected_result', [ ( { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ + 'cell_type': 'code', + 'execution_count': 9, + 'metadata': {}, + 'outputs': [ { - "name": "stdout", - "output_type": "stream", - "text": "hello world\n" "foo\n" "bar\n", + 'name': 'stdout', + 'output_type': 'stream', + 'text': 'hello world\n' 'foo\n' 'bar\n', } ], }, @@ -74,8 +74,8 @@ def test_value_raises_error(code, notebook): """, ), ( - {"cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": []}, - "", + {'cell_type': 'code', 'execution_count': 9, 'metadata': {}, 'outputs': []}, + '', ), ], ) @@ -84,7 +84,7 @@ def test_output_text(cell, expected_result): @pytest.mark.parametrize( - "cell", [{}, {"cell_type": "markdown", "metadata": {}, "source": ["# Hello"]}] + 'cell', [{}, {'cell_type': 'markdown', 'metadata': {}, 'source': ['# Hello']}] ) def test_output_text_raises_error(cell): with pytest.raises(ValueError): @@ -93,16 +93,16 @@ def test_output_text_raises_error(cell): def test_cell_execute_result_index(notebook_factory): nb = notebook_factory() - with testbook(nb, execute="test") as tb: - assert tb.cell_execute_result(1) == [{"text/plain": "22"}] - assert tb.cell_execute_result(2) == [{"text/plain": "text"}] + with testbook(nb, execute='test') as tb: + assert tb.cell_execute_result(1) == [{'text/plain': '22'}] + assert tb.cell_execute_result(2) == [{'text/plain': 'text'}] def test_cell_execute_result_tag(notebook_factory): nb = notebook_factory() - with testbook(nb, execute="test") as tb: - assert tb.cell_execute_result("test") == [{"text/plain": "22"}] - assert tb.cell_execute_result("dummy-outputs") == [{"text/plain": "text"}] + with testbook(nb, execute='test') as tb: + assert tb.cell_execute_result('test') == [{'text/plain': '22'}] + assert tb.cell_execute_result('dummy-outputs') == [{'text/plain': 'text'}] def test_cell_execute_result_indexerror(notebook_factory): @@ -116,4 +116,4 @@ def test_cell_execute_result_tagnotfound(notebook_factory): nb = notebook_factory([]) with testbook(nb) as tb: with pytest.raises(TestbookCellTagNotFoundError): - tb.cell_execute_result("test") + tb.cell_execute_result('test') diff --git a/testbook/tests/test_datamodel.py b/testbook/tests/test_datamodel.py index 4330fb9..3892f16 100644 --- a/testbook/tests/test_datamodel.py +++ b/testbook/tests/test_datamodel.py @@ -3,20 +3,20 @@ from ..testbook import testbook -@pytest.fixture(scope="module") +@pytest.fixture(scope='module') def notebook(): - with testbook("testbook/tests/resources/datamodel.ipynb", execute=True) as tb: + with testbook('testbook/tests/resources/datamodel.ipynb', execute=True) as tb: yield tb def test_len(notebook): - mylist = notebook.ref("mylist") + mylist = notebook.ref('mylist') assert len(mylist) == 5 def test_iter(notebook): - mylist = notebook.ref("mylist") + mylist = notebook.ref('mylist') expected = [] for x in mylist: @@ -26,7 +26,7 @@ def test_iter(notebook): def test_getitem(notebook): - mylist = notebook.ref("mylist") + mylist = notebook.ref('mylist') mylist.append(6) assert mylist[-1] == 6 @@ -34,50 +34,50 @@ def test_getitem(notebook): def test_getitem_raisesIndexError(notebook): - mylist = notebook.ref("mylist") + mylist = notebook.ref('mylist') with pytest.raises(IndexError): mylist[100] def test_getitem_raisesTypeError(notebook): - mylist = notebook.ref("mylist") + mylist = notebook.ref('mylist') with pytest.raises(TypeError): - mylist["hello"] + mylist['hello'] def test_setitem(notebook): notebook.inject("mydict = {'key1': 'value1', 'key2': 'value1'}") - mydict = notebook.ref("mydict") + mydict = notebook.ref('mydict') - mydict["key3"] = "value3" - assert mydict["key3"] == "value3" + mydict['key3'] = 'value3' + assert mydict['key3'] == 'value3' - mylist = notebook.ref("mylist") + mylist = notebook.ref('mylist') mylist[2] = 10 assert mylist[2] == 10 def test_setitem_raisesIndexError(notebook): - mylist = notebook.ref("mylist") + mylist = notebook.ref('mylist') with pytest.raises(IndexError): mylist.__setitem__(10, 100) def test_setitem_raisesTypeError(notebook): - mylist = notebook.ref("mylist") + mylist = notebook.ref('mylist') with pytest.raises(TypeError): - mylist.__setitem__("key", 10) + mylist.__setitem__('key', 10) def test_contains(notebook): notebook.inject("mydict = {'key1': 'value1', 'key2': 'value1'}") - mydict = notebook.ref("mydict") + mydict = notebook.ref('mydict') - assert "key1" in mydict - assert "key2" in mydict - assert mydict.__contains__("key1") - assert mydict.__contains__("key2") + assert 'key1' in mydict + assert 'key2' in mydict + assert mydict.__contains__('key1') + assert mydict.__contains__('key2') diff --git a/testbook/tests/test_execute.py b/testbook/tests/test_execute.py index 94de706..db39864 100644 --- a/testbook/tests/test_execute.py +++ b/testbook/tests/test_execute.py @@ -4,18 +4,18 @@ from ..exceptions import TestbookRuntimeError, TestbookError -@pytest.fixture(scope="module") +@pytest.fixture(scope='module') def notebook(): - with testbook("testbook/tests/resources/foo.ipynb", execute=True) as tb: + with testbook('testbook/tests/resources/foo.ipynb', execute=True) as tb: yield tb def test_execute_cell(notebook): notebook.execute_cell(1) - assert notebook.cell_output_text(1) == "hello world\n[1, 2, 3]" + assert notebook.cell_output_text(1) == 'hello world\n[1, 2, 3]' notebook.execute_cell([2, 3]) - assert notebook.cell_output_text(3) == "foo" + assert notebook.cell_output_text(3) == 'foo' def test_execute_and_show_pandas_output(notebook): @@ -29,68 +29,68 @@ def test_execute_and_show_pandas_output(notebook): def test_execute_cell_tags(notebook): - notebook.execute_cell("test1") - assert notebook.cell_output_text("test1") == "hello world\n[1, 2, 3]" + notebook.execute_cell('test1') + assert notebook.cell_output_text('test1') == 'hello world\n[1, 2, 3]' - notebook.execute_cell(["prepare_foo", "execute_foo"]) - assert notebook.cell_output_text("execute_foo") == "foo" + notebook.execute_cell(['prepare_foo', 'execute_foo']) + assert notebook.cell_output_text('execute_foo') == 'foo' def test_execute_cell_raises_error(notebook): with pytest.raises(TestbookRuntimeError): try: - notebook.inject("1/0", pop=True) + notebook.inject('1/0', pop=True) except TestbookRuntimeError as e: assert e.eclass is ZeroDivisionError raise def test_testbook_with_execute(notebook): - notebook.execute_cell("execute_foo") - assert notebook.cell_output_text("execute_foo") == "foo" + notebook.execute_cell('execute_foo') + assert notebook.cell_output_text('execute_foo') == 'foo' def test_testbook_with_execute_context_manager(notebook): - notebook.execute_cell("execute_foo") - assert notebook.cell_output_text("execute_foo") == "foo" + notebook.execute_cell('execute_foo') + assert notebook.cell_output_text('execute_foo') == 'foo' def test_testbook_range(): - with testbook("testbook/tests/resources/inject.ipynb") as tb: + with testbook('testbook/tests/resources/inject.ipynb') as tb: tb.execute_cell(range(4)) assert tb.code_cells_executed == 4 - with testbook("testbook/tests/resources/inject.ipynb", execute=range(4)) as tb: + with testbook('testbook/tests/resources/inject.ipynb', execute=range(4)) as tb: assert tb.code_cells_executed == 4 @pytest.mark.parametrize( - "slice_params, expected_result", [(("hello", "str"), 6), ((2, 5), 4)] + 'slice_params, expected_result', [(('hello', 'str'), 6), ((2, 5), 4)] ) def test_testbook_slice(slice_params, expected_result): - with testbook("testbook/tests/resources/inject.ipynb") as tb: + with testbook('testbook/tests/resources/inject.ipynb') as tb: tb.execute_cell(slice(*slice_params)) assert tb.code_cells_executed == expected_result with testbook( - "testbook/tests/resources/inject.ipynb", execute=slice(*slice_params) + 'testbook/tests/resources/inject.ipynb', execute=slice(*slice_params) ) as tb: assert tb.code_cells_executed == expected_result def test_testbook_slice_raises_error(): with pytest.raises(TestbookError): - with testbook("testbook/tests/resources/inject.ipynb", execute=slice(3, 1, -1)): + with testbook('testbook/tests/resources/inject.ipynb', execute=slice(3, 1, -1)): pass -@testbook("testbook/tests/resources/exception.ipynb", execute=True) +@testbook('testbook/tests/resources/exception.ipynb', execute=True) def test_raise_exception(tb): with pytest.raises(TestbookRuntimeError): - tb.ref("raise_my_exception")() + tb.ref('raise_my_exception')() -@testbook("testbook/tests/resources/inject.ipynb") +@testbook('testbook/tests/resources/inject.ipynb') def test_underscore(tb): tb.inject( """ @@ -104,6 +104,6 @@ def foo(x): tb.execute() - foo = tb.ref("foo") + foo = tb.ref('foo') assert foo(2) == 3 diff --git a/testbook/tests/test_inject.py b/testbook/tests/test_inject.py index 2228e94..44e43b1 100644 --- a/testbook/tests/test_inject.py +++ b/testbook/tests/test_inject.py @@ -3,9 +3,9 @@ from ..testbook import testbook -@pytest.fixture(scope="module") +@pytest.fixture(scope='module') def notebook(): - with testbook("testbook/tests/resources/inject.ipynb", execute=True) as tb: + with testbook('testbook/tests/resources/inject.ipynb', execute=True) as tb: yield tb @@ -14,17 +14,17 @@ def inject_helper(*args, **kwargs): @pytest.mark.parametrize( - "args, kwargs", + 'args, kwargs', [ (None, None), ([1, 2], None), ((1, 2), None), ((True, False), None), - (["a", "b"], None), - ([1.1, float("nan"), float("inf"), float("-inf")], None), - ([{"key1": "value1"}, {"key2": "value2"}], None), - ((1, 2, False), {"key2": "value2"}), - ((None, None, False), {"key2": "value2"}), + (['a', 'b'], None), + ([1.1, float('nan'), float('inf'), float('-inf')], None), + ([{'key1': 'value1'}, {'key2': 'value2'}], None), + ((1, 2, False), {'key2': 'value2'}), + ((None, None, False), {'key2': 'value2'}), ], ) def test_inject(args, kwargs, notebook): @@ -32,7 +32,7 @@ def test_inject(args, kwargs, notebook): @pytest.mark.parametrize( - "code_block, expected_text", + 'code_block, expected_text', [ ( """ @@ -40,7 +40,7 @@ def foo(): print('I ran in the code block') foo() """, - "I ran in the code block", + 'I ran in the code block', ), ( """ @@ -48,7 +48,7 @@ def foo(arg): print(f'You passed {arg}') foo('bar') """, - "You passed bar", + 'You passed bar', ), ], ) @@ -57,7 +57,7 @@ def test_inject_code_block(code_block, expected_text, notebook): def test_inject_raises_exception(notebook): - values = [3, {"key": "value"}, ["a", "b", "c"], (1, 2, 3), {1, 2, 3}] + values = [3, {'key': 'value'}, ['a', 'b', 'c'], (1, 2, 3), {1, 2, 3}] for value in values: with pytest.raises(TypeError): @@ -65,16 +65,16 @@ def test_inject_raises_exception(notebook): def test_inject_before_after(notebook): - notebook.inject("say_hello()", run=False, after="hello") - assert notebook.cells[notebook._cell_index("hello") + 1].source == "say_hello()" + notebook.inject('say_hello()', run=False, after='hello') + assert notebook.cells[notebook._cell_index('hello') + 1].source == 'say_hello()' - notebook.inject("say_bye()", before="hello") - assert notebook.cells[notebook._cell_index("hello") - 1].source == "say_bye()" + notebook.inject('say_bye()', before='hello') + assert notebook.cells[notebook._cell_index('hello') - 1].source == 'say_bye()' with pytest.raises(ValueError): - notebook.inject("say_hello()", before="hello", after="bye") + notebook.inject('say_hello()', before='hello', after='bye') def test_inject_pop(notebook): - assert notebook.inject("1+1", pop=True).execute_result == [{"text/plain": "2"}] - assert notebook.cells[-1].source != "1+1" + assert notebook.inject('1+1', pop=True).execute_result == [{'text/plain': '2'}] + assert notebook.cells[-1].source != '1+1' diff --git a/testbook/tests/test_patch.py b/testbook/tests/test_patch.py index ea69fe0..ba5e122 100644 --- a/testbook/tests/test_patch.py +++ b/testbook/tests/test_patch.py @@ -4,15 +4,15 @@ import pytest -@pytest.fixture(scope="module") +@pytest.fixture(scope='module') def tb(): - with testbook("testbook/tests/resources/patch.ipynb", execute=True) as tb: + with testbook('testbook/tests/resources/patch.ipynb', execute=True) as tb: yield tb class TestPatch: @pytest.mark.parametrize( - "target, func", [("os.listdir", "listdir"), ("os.popen", "get_branch")] + 'target, func', [('os.listdir', 'listdir'), ('os.popen', 'get_branch')] ) def test_patch_basic(self, target, func, tb): with tb.patch(target) as mock_obj: @@ -20,22 +20,22 @@ def test_patch_basic(self, target, func, tb): mock_obj.assert_called_once() @pytest.mark.parametrize( - "target, func", [("os.listdir", "listdir"), ("os.popen", "get_branch")] + 'target, func', [('os.listdir', 'listdir'), ('os.popen', 'get_branch')] ) def test_patch_raises_error(self, target, func, tb): with pytest.raises(TestbookRuntimeError), tb.patch(target) as mock_obj: mock_obj.assert_called_once() def test_patch_return_value(self, tb): - with tb.patch("os.listdir", return_value=["file1", "file2"]) as mock_listdir: - assert tb.ref("listdir")() == ["file1", "file2"] + with tb.patch('os.listdir', return_value=['file1', 'file2']) as mock_listdir: + assert tb.ref('listdir')() == ['file1', 'file2'] mock_listdir.assert_called_once() class TestPatchDict: @pytest.mark.parametrize( - "in_dict, values", - [("os.environ", {"PATH": "/usr/bin"})], + 'in_dict, values', + [('os.environ', {'PATH': '/usr/bin'})], ) def test_patch_dict(self, in_dict, values, tb): with tb.patch_dict(in_dict, values, clear=True): diff --git a/testbook/tests/test_reference.py b/testbook/tests/test_reference.py index c9d476c..6fac580 100644 --- a/testbook/tests/test_reference.py +++ b/testbook/tests/test_reference.py @@ -4,57 +4,57 @@ from ..exceptions import TestbookAttributeError, TestbookSerializeError -@pytest.fixture(scope="module") +@pytest.fixture(scope='module') def notebook(): - with testbook("testbook/tests/resources/reference.ipynb", execute=True) as tb: + with testbook('testbook/tests/resources/reference.ipynb', execute=True) as tb: yield tb def test_create_reference(notebook): - a = notebook.ref("a") - assert repr(a) == "[1, 2, 3]" + a = notebook.ref('a') + assert repr(a) == '[1, 2, 3]' def test_create_reference_getitem(notebook): - a = notebook["a"] - assert repr(a) == "[1, 2, 3]" + a = notebook['a'] + assert repr(a) == '[1, 2, 3]' def test_create_reference_get(notebook): - a = notebook.get("a") - assert repr(a) == "[1, 2, 3]" + a = notebook.get('a') + assert repr(a) == '[1, 2, 3]' def test_eq_in_notebook(notebook): - a = notebook.ref("a") + a = notebook.ref('a') a.append(4) assert a == [1, 2, 3, 4] def test_eq_in_notebook_ref(notebook): - a, b = notebook.ref("a"), notebook.ref("b") + a, b = notebook.ref('a'), notebook.ref('b') assert a == b def test_function_call(notebook): - double = notebook.ref("double") + double = notebook.ref('double') assert double([1, 2, 3]) == [2, 4, 6] def test_function_call_with_ref_object(notebook): - double, a = notebook.ref("double"), notebook.ref("a") + double, a = notebook.ref('double'), notebook.ref('a') assert double(a) == [2, 4, 6] def test_reference(notebook): - Foo = notebook.ref("Foo") + Foo = notebook.ref('Foo') # Check that when a non-serializeable object is returned, it returns # a reference to that object instead - f = Foo("bar") + f = Foo('bar') - assert repr(f) == "\"\"" + assert repr(f) == '""' # Valid attribute access assert f.say_hello() @@ -63,7 +63,7 @@ def test_reference(notebook): with pytest.raises(TestbookAttributeError): f.does_not_exist - assert f.say_hello() == "Hello bar!" + assert f.say_hello() == 'Hello bar!' # non JSON-serializeable output with pytest.raises(TestbookSerializeError): diff --git a/testbook/tests/test_testbook.py b/testbook/tests/test_testbook.py index 84a9342..986d5f4 100644 --- a/testbook/tests/test_testbook.py +++ b/testbook/tests/test_testbook.py @@ -5,35 +5,35 @@ from ..testbook import testbook -@testbook("testbook/tests/resources/inject.ipynb", execute=True) +@testbook('testbook/tests/resources/inject.ipynb', execute=True) def test_testbook_execute_all_cells(tb): for cell in tb.cells[:-1]: assert cell.execution_count -@testbook("testbook/tests/resources/inject.ipynb", execute="hello") +@testbook('testbook/tests/resources/inject.ipynb', execute='hello') def test_testbook_class_decorator(tb): - assert tb.inject("say_hello()") + assert tb.inject('say_hello()') -@testbook("testbook/tests/resources/inject.ipynb") +@testbook('testbook/tests/resources/inject.ipynb') def test_testbook_class_decorator_execute_none(tb): assert tb.code_cells_executed == 0 -@testbook("testbook/tests/resources/inject.ipynb", execute=True) +@testbook('testbook/tests/resources/inject.ipynb', execute=True) def test_testbook_decorator_with_fixture(nb, tmp_path): assert True # Check that the decorator accept to be passed along side a fixture -@testbook("testbook/tests/resources/inject.ipynb", execute=True) -@pytest.mark.parametrize("cell_index_args, expected_result", [(2, 2), ("hello", 1)]) +@testbook('testbook/tests/resources/inject.ipynb', execute=True) +@pytest.mark.parametrize('cell_index_args, expected_result', [(2, 2), ('hello', 1)]) def test_testbook_decorator_with_markers(nb, cell_index_args, expected_result): assert nb._cell_index(cell_index_args) == expected_result -@pytest.mark.parametrize("cell_index_args, expected_result", [(2, 2), ("hello", 1)]) -@testbook("testbook/tests/resources/inject.ipynb", execute=True) +@pytest.mark.parametrize('cell_index_args, expected_result', [(2, 2), ('hello', 1)]) +@testbook('testbook/tests/resources/inject.ipynb', execute=True) def test_testbook_decorator_with_markers_order_does_not_matter( nb, cell_index_args, expected_result ): @@ -41,23 +41,23 @@ def test_testbook_decorator_with_markers_order_does_not_matter( def test_testbook_execute_all_cells_context_manager(): - with testbook("testbook/tests/resources/inject.ipynb", execute=True) as tb: + with testbook('testbook/tests/resources/inject.ipynb', execute=True) as tb: for cell in tb.cells[:-1]: assert cell.execution_count def test_testbook_class_decorator_context_manager(): - with testbook("testbook/tests/resources/inject.ipynb", execute="hello") as tb: - assert tb.inject("say_hello()") + with testbook('testbook/tests/resources/inject.ipynb', execute='hello') as tb: + assert tb.inject('say_hello()') def test_testbook_class_decorator_execute_none_context_manager(): - with testbook("testbook/tests/resources/inject.ipynb") as tb: + with testbook('testbook/tests/resources/inject.ipynb') as tb: assert tb.code_cells_executed == 0 def test_testbook_with_file_object(): - f = open("testbook/tests/resources/inject.ipynb") + f = open('testbook/tests/resources/inject.ipynb') with testbook(f) as tb: assert tb.code_cells_executed == 0 @@ -66,15 +66,15 @@ def test_testbook_with_file_object(): def test_testbook_with_notebook_node(): - nb = nbformat.read("testbook/tests/resources/inject.ipynb", as_version=4) + nb = nbformat.read('testbook/tests/resources/inject.ipynb', as_version=4) with testbook(nb) as tb: assert tb.code_cells_executed == 0 def test_function_with_testbook_decorator_returns_value(): - @testbook("testbook/tests/resources/inject.ipynb") + @testbook('testbook/tests/resources/inject.ipynb') def test_function(tb): - return "This should be returned" + return 'This should be returned' - assert test_function() == "This should be returned" + assert test_function() == 'This should be returned' diff --git a/testbook/tests/test_translators.py b/testbook/tests/test_translators.py index 6708412..802e53e 100644 --- a/testbook/tests/test_translators.py +++ b/testbook/tests/test_translators.py @@ -14,30 +14,30 @@ def __repr__(self): @pytest.mark.parametrize( - "test_input,expected", + 'test_input,expected', [ - ("foo", '"foo"'), + ('foo', '"foo"'), ('{"foo": "bar"}', '"{\\"foo\\": \\"bar\\"}"'), - ({"foo": "bar"}, '{"foo": "bar"}'), - ({"foo": '"bar"'}, '{"foo": "\\"bar\\""}'), - ({"foo": ["bar"]}, '{"foo": ["bar"]}'), - ({"foo": {"bar": "baz"}}, '{"foo": {"bar": "baz"}}'), - ({"foo": {"bar": '"baz"'}}, '{"foo": {"bar": "\\"baz\\""}}'), - (["foo"], '["foo"]'), - (["foo", '"bar"'], '["foo", "\\"bar\\""]'), - ([{"foo": "bar"}], '[{"foo": "bar"}]'), - ([{"foo": '"bar"'}], '[{"foo": "\\"bar\\""}]'), - (12345, "12345"), - (-54321, "-54321"), - (1.2345, "1.2345"), - (-5432.1, "-5432.1"), - (float("nan"), "float('nan')"), - (float("-inf"), "float('-inf')"), - (float("inf"), "float('inf')"), - (True, "True"), - (False, "False"), - (None, "None"), - (Foo("bar"), '''""'''), + ({'foo': 'bar'}, '{"foo": "bar"}'), + ({'foo': '"bar"'}, '{"foo": "\\"bar\\""}'), + ({'foo': ['bar']}, '{"foo": ["bar"]}'), + ({'foo': {'bar': 'baz'}}, '{"foo": {"bar": "baz"}}'), + ({'foo': {'bar': '"baz"'}}, '{"foo": {"bar": "\\"baz\\""}}'), + (['foo'], '["foo"]'), + (['foo', '"bar"'], '["foo", "\\"bar\\""]'), + ([{'foo': 'bar'}], '[{"foo": "bar"}]'), + ([{'foo': '"bar"'}], '[{"foo": "\\"bar\\""}]'), + (12345, '12345'), + (-54321, '-54321'), + (1.2345, '1.2345'), + (-5432.1, '-5432.1'), + (float('nan'), "float('nan')"), + (float('-inf'), "float('-inf')"), + (float('inf'), "float('inf')"), + (True, 'True'), + (False, 'False'), + (None, 'None'), + (Foo('bar'), '''""'''), ], ) def test_translate_type_python(test_input, expected): @@ -45,11 +45,11 @@ def test_translate_type_python(test_input, expected): @pytest.mark.parametrize( - "test_input,expected", [(3.14, "3.14"), (False, "false"), (True, "true")] + 'test_input,expected', [(3.14, '3.14'), (False, 'false'), (True, 'true')] ) def test_translate_float(test_input, expected): assert translators.Translator.translate(test_input) == expected def test_translate_assign(): - assert translators.Translator.assign("var1", [1, 2, 3]) == "var1 = [1, 2, 3]" + assert translators.Translator.assign('var1', [1, 2, 3]) == 'var1 = [1, 2, 3]' diff --git a/testbook/translators.py b/testbook/translators.py index 7d926da..3278665 100644 --- a/testbook/translators.py +++ b/testbook/translators.py @@ -8,16 +8,16 @@ class Translator(object): @classmethod def translate_raw_str(cls, val): """Reusable by most interpreters""" - return "{}".format(val) + return '{}'.format(val) @classmethod def translate_escaped_str(cls, str_val): """Reusable by most interpreters""" if isinstance(str_val, str): - str_val = str_val.encode("unicode_escape") + str_val = str_val.encode('unicode_escape') if sys.version_info >= (3, 0): - str_val = str_val.decode("utf-8") - str_val = str_val.replace('"', r"\"") + str_val = str_val.decode('utf-8') + str_val = str_val.replace('"', r'\"') return '"{}"'.format(str_val) @classmethod @@ -43,18 +43,18 @@ def translate_float(cls, val): @classmethod def translate_bool(cls, val): """Default behavior for translation""" - return "true" if val else "false" + return 'true' if val else 'false' @classmethod def translate_dict(cls, val): raise NotImplementedError( - "dict type translation not implemented for {}".format(cls) + 'dict type translation not implemented for {}'.format(cls) ) @classmethod def translate_list(cls, val): raise NotImplementedError( - "list type translation not implemented for {}".format(cls) + 'list type translation not implemented for {}'.format(cls) ) @classmethod @@ -77,7 +77,7 @@ def translate(cls, val): return cls.translate_list(val) elif isinstance(val, tuple): return cls.translate_tuple(val) - elif val.__class__.__name__ == "TestbookObjectReference": + elif val.__class__.__name__ == 'TestbookObjectReference': return val.name # Use this generic translation as a last resort @@ -86,12 +86,12 @@ def translate(cls, val): @classmethod def comment(cls, cmt_str): raise NotImplementedError( - "comment translation not implemented for {}".format(cls) + 'comment translation not implemented for {}'.format(cls) ) @classmethod def assign(cls, name, str_val): - return "{} = {}".format(name, str_val) + return '{} = {}'.format(name, str_val) class PythonTranslator(Translator): @@ -112,20 +112,20 @@ def translate_bool(cls, val): @classmethod def translate_dict(cls, val): - escaped = ", ".join( + escaped = ', '.join( [ - "{}: {}".format(cls.translate_str(k), cls.translate(v)) + '{}: {}'.format(cls.translate_str(k), cls.translate(v)) for k, v in val.items() ] ) - return "{{{}}}".format(escaped) + return '{{{}}}'.format(escaped) @classmethod def translate_list(cls, val): - escaped = ", ".join([cls.translate(v) for v in val]) - return "[{}]".format(escaped) + escaped = ', '.join([cls.translate(v) for v in val]) + return '[{}]'.format(escaped) @classmethod def translate_tuple(cls, val): - escaped = ", ".join([cls.translate(v) for v in val]) + ", " - return "({})".format(escaped) + escaped = ', '.join([cls.translate(v) for v in val]) + ', ' + return '({})'.format(escaped) diff --git a/testbook/utils.py b/testbook/utils.py index ef66a84..297a383 100644 --- a/testbook/utils.py +++ b/testbook/utils.py @@ -15,7 +15,7 @@ def random_varname(length=10): -------- random variable name as string of given length """ - return "".join(random.choice(string.ascii_lowercase) for _ in range(length)) + return ''.join(random.choice(string.ascii_lowercase) for _ in range(length)) def all_subclasses(klass): From f84b83ea3015dcbc480b44b1198169971d8f88eb Mon Sep 17 00:00:00 2001 From: Rohit Sanjay Date: Sat, 3 Aug 2024 18:10:27 -0700 Subject: [PATCH 17/17] Remove codecov from optional deps --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fdd0f68..6315b64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,6 @@ optional-dependencies.dev = [ "ruff", "bumpversion", "check-manifest", - "codecov", "coverage", "ipykernel", "ipython",