From 30d93a2e5e92c75e779934a6ddf814e040408c47 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 5 Dec 2023 11:05:28 -0600 Subject: [PATCH] Update ruff and typings (#388) --- .github/scripts/create_npmrc.py | 10 +++--- .github/scripts/parse_ref.py | 14 ++++---- .pre-commit-config.yaml | 8 ++--- docs/conf.py | 15 ++++---- nbformat/__init__.py | 19 +++++----- nbformat/_imports.py | 8 ++--- nbformat/_struct.py | 20 ++++++----- nbformat/_version.py | 2 ++ nbformat/converter.py | 10 +++--- nbformat/corpus/tests/test_words.py | 7 ++-- nbformat/corpus/words.py | 2 ++ nbformat/current.py | 12 +++---- nbformat/json_compat.py | 1 + nbformat/notebooknode.py | 8 ++--- nbformat/reader.py | 5 ++- nbformat/sentinel.py | 1 + nbformat/sign.py | 55 ++++++++++++++-------------- nbformat/v1/__init__.py | 1 + nbformat/v1/convert.py | 1 + nbformat/v1/nbbase.py | 8 ++--- nbformat/v1/nbjson.py | 1 + nbformat/v1/rwbase.py | 1 + nbformat/v2/__init__.py | 3 +- nbformat/v2/convert.py | 5 +-- nbformat/v2/nbbase.py | 14 ++++---- nbformat/v2/nbjson.py | 3 +- nbformat/v2/nbpy.py | 13 +++---- nbformat/v2/nbxml.py | 2 +- nbformat/v2/rwbase.py | 1 + nbformat/v3/__init__.py | 3 +- nbformat/v3/convert.py | 18 +++++----- nbformat/v3/nbbase.py | 21 +++++------ nbformat/v3/nbjson.py | 3 +- nbformat/v3/nbpy.py | 19 +++++----- nbformat/v3/rwbase.py | 7 ++-- nbformat/v4/__init__.py | 1 + nbformat/v4/convert.py | 25 +++++++------ nbformat/v4/nbbase.py | 10 +++--- nbformat/v4/nbjson.py | 5 +-- nbformat/v4/rwbase.py | 1 + nbformat/validator.py | 45 +++++++++++------------ nbformat/warnings.py | 5 +-- pyproject.toml | 56 +++++++++++++++++++---------- scripts/jupyter-trust | 1 + tests/base.py | 1 + tests/test_api.py | 5 +-- tests/test_convert.py | 1 + tests/test_nbformat.py | 16 ++++++--- tests/test_reader.py | 1 + tests/test_sign.py | 1 + tests/test_validator.py | 1 + tests/v1/nbexamples.py | 2 ++ tests/v1/test_json.py | 2 ++ tests/v1/test_nbbase.py | 2 ++ tests/v2/nbexamples.py | 2 ++ tests/v2/test_json.py | 2 ++ tests/v2/test_nbbase.py | 2 ++ tests/v2/test_nbpy.py | 2 ++ tests/v3/formattest.py | 2 ++ tests/v3/nbexamples.py | 2 ++ tests/v3/test_json.py | 2 ++ tests/v3/test_misc.py | 2 ++ tests/v3/test_nbbase.py | 2 ++ tests/v3/test_nbpy.py | 2 ++ tests/v4/formattest.py | 2 ++ tests/v4/nbexamples.py | 2 ++ tests/v4/test_convert.py | 6 ++-- tests/v4/test_json.py | 2 ++ tests/v4/test_nbbase.py | 1 + tests/v4/test_validate.py | 1 + 70 files changed, 302 insertions(+), 234 deletions(-) diff --git a/.github/scripts/create_npmrc.py b/.github/scripts/create_npmrc.py index 31edab0a..9c28d2f3 100644 --- a/.github/scripts/create_npmrc.py +++ b/.github/scripts/create_npmrc.py @@ -1,10 +1,10 @@ -#!/usr/bin/env python - # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. # Standard library imports -import os +from __future__ import annotations + +from pathlib import Path def create_npmrc(): @@ -12,8 +12,8 @@ def create_npmrc(): Create NPM configuration file in user home directory to use authentication token from environment variables. """ - fpath = os.path.expanduser("~/.npmrc") - with open(fpath, "w") as fh: + fpath = Path("~/.npmrc").expanduser() + with fpath.open("w") as fh: fh.write("//registry.npmjs.org/:_authToken=${NPM_TOKEN}") diff --git a/.github/scripts/parse_ref.py b/.github/scripts/parse_ref.py index f27e9187..b4a1dd5b 100644 --- a/.github/scripts/parse_ref.py +++ b/.github/scripts/parse_ref.py @@ -1,14 +1,15 @@ -#!/usr/bin/env python - # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. # Standard library imports +from __future__ import annotations + import os +from pathlib import Path # Constants -HERE = os.path.abspath(os.path.dirname(__file__)) -REPO_ROOT = os.path.dirname(os.path.dirname(HERE)) +HERE = Path(__file__).parent.resolve() +REPO_ROOT = HERE.parent.parent def parse_ref(current_ref): @@ -22,10 +23,11 @@ def parse_ref(current_ref): The github reference string. """ if not current_ref.startswith("refs/tags/"): - raise Exception(f"Invalid ref `{current_ref}`!") # noqa + msg = f"Invalid ref `{current_ref}`!" + raise Exception(msg) tag_name = current_ref.replace("refs/tags/", "") - print(tag_name) # noqa + print(tag_name) # noqa: T201 if __name__ == "__main__": diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a2fea467..6e4efa37 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.27.1 + rev: 0.27.2 hooks: - id: check-github-workflows @@ -56,7 +56,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.6.1" + rev: "v1.7.1" hooks: - id: mypy files: "^nbformat" @@ -65,7 +65,7 @@ repos: ["jsonschema>=2.6", "traitlets>=5.13", "jupyter_core>5.4"] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.5 + rev: v0.1.6 hooks: - id: ruff types_or: [python, jupyter] @@ -76,7 +76,7 @@ repos: types_or: [python, jupyter] - repo: https://github.com/scientific-python/cookie - rev: "2023.10.27" + rev: "2023.11.17" hooks: - id: sp-repo-review additional_dependencies: ["repo-review[cli]"] diff --git a/docs/conf.py b/docs/conf.py index 977bf598..775061a7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# # nbformat documentation build configuration file, created by # sphinx-quickstart on Thu May 14 17:26:52 2015. # @@ -16,13 +14,14 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) +from __future__ import annotations -import os import shutil +from pathlib import Path import nbformat -HERE = os.path.abspath(os.path.dirname(__file__)) +HERE = Path(__file__).parent.resolve() # -- General configuration ------------------------------------------------ @@ -40,7 +39,7 @@ ] try: - import enchant # type:ignore # noqa + import enchant # noqa: F401 extensions += ["sphinxcontrib.spelling"] except ImportError: @@ -62,7 +61,7 @@ # General information about the project. project = "nbformat" -copyright = "2015, Jupyter Development Team" # noqa +copyright = "2015, Jupyter Development Team" author = "Jupyter Development Team" # numpydoc configuration @@ -313,5 +312,5 @@ def setup(_): - dest = os.path.join(HERE, "changelog.md") - shutil.copy(os.path.join(HERE, "..", "CHANGELOG.md"), dest) + dest = Path(HERE, "changelog.md") + shutil.copy(Path(HERE, "..", "CHANGELOG.md"), dest) diff --git a/nbformat/__init__.py b/nbformat/__init__.py index 9b93ef23..d0fcf853 100644 --- a/nbformat/__init__.py +++ b/nbformat/__init__.py @@ -5,6 +5,9 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations + +from pathlib import Path from traitlets.log import get_logger @@ -39,12 +42,12 @@ 4: v4, } -from . import reader # noqa -from .converter import convert # noqa -from .notebooknode import NotebookNode, from_dict # noqa -from .v4 import nbformat as current_nbformat # noqa -from .v4 import nbformat_minor as current_nbformat_minor # noqa -from .validator import ValidationError, validate # noqa +from . import reader # noqa: E402 +from .converter import convert # noqa: E402 +from .notebooknode import NotebookNode, from_dict # noqa: E402 +from .v4 import nbformat as current_nbformat # noqa: E402 +from .v4 import nbformat_minor as current_nbformat_minor # noqa: E402 +from .validator import ValidationError, validate # noqa: E402 class NBFormatError(ValueError): @@ -165,7 +168,7 @@ def read(fp, as_version, capture_validation_error=None, **kwargs): try: buf = fp.read() except AttributeError: - with open(fp, encoding="utf-8") as f: + with open(fp, encoding="utf8") as f: # noqa: PTH123 return reads(f.read(), as_version, capture_validation_error, **kwargs) return reads(buf, as_version, capture_validation_error, **kwargs) @@ -202,7 +205,7 @@ def write(nb, fp, version=NO_CONVERT, capture_validation_error=None, **kwargs): if not s.endswith("\n"): fp.write("\n") except AttributeError: - with open(fp, "w", encoding="utf-8") as f: + with Path(fp).open("w", encoding="utf8") as f: f.write(s) if not s.endswith("\n"): f.write("\n") diff --git a/nbformat/_imports.py b/nbformat/_imports.py index fc20d5bd..83b01394 100644 --- a/nbformat/_imports.py +++ b/nbformat/_imports.py @@ -6,6 +6,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations def import_item(name): @@ -26,7 +27,7 @@ def import_item(name): """ parts = name.rsplit(".", 1) - if len(parts) == 2: # noqa + if len(parts) == 2: # called with 'foo.bar....' package, obj = parts module = __import__(package, fromlist=[obj]) @@ -35,6 +36,5 @@ def import_item(name): except AttributeError: raise ImportError("No module named %s" % obj) from None return pak - else: - # called with un-dotted string - return __import__(parts[0]) + # called with un-dotted string + return __import__(parts[0]) diff --git a/nbformat/_struct.py b/nbformat/_struct.py index b3c25c66..23aa45f9 100644 --- a/nbformat/_struct.py +++ b/nbformat/_struct.py @@ -2,6 +2,8 @@ Can probably be replaced by types.SimpleNamespace from Python 3.3 """ +from __future__ import annotations + from typing import Any, Dict __all__ = ["Struct"] @@ -88,7 +90,7 @@ def __setattr__(self, key, value): you can't set a class member """ # If key is an str it might be a class member or instance var - if isinstance(key, str): # noqa + if isinstance(key, str): # noqa: SIM102 # I can't simply call hasattr here because it calls getattr, which # calls self.__getattr__, which returns True for keys in # self._data. But I only want keys in the class and in @@ -196,12 +198,12 @@ def __dict_invert(self, data): outdict = {} for k, lst in data.items(): if isinstance(lst, str): - lst = lst.split() # noqa + lst = lst.split() # noqa: PLW2901 for entry in lst: outdict[entry] = k return outdict - def dict(self): # noqa + def dict(self): """Get the dict representation of the struct.""" return self @@ -217,7 +219,7 @@ def copy(self): """ return Struct(dict.copy(self)) - def hasattr(self, key): # noqa + def hasattr(self, key): """hasattr function available as a method. Implemented like has_key. @@ -331,11 +333,11 @@ def merge(self, __loc_data__=None, __conflict_solve=None, **kw): # policies for conflict resolution: two argument functions which return # the value that will go in the new struct - preserve = lambda old, new: old # noqa - update = lambda old, new: new # noqa - add = lambda old, new: old + new # noqa - add_flip = lambda old, new: new + old # noqa # note change of order! - add_s = lambda old, new: old + " " + new # noqa + preserve = lambda old, new: old + update = lambda old, new: new + add = lambda old, new: old + new + add_flip = lambda old, new: new + old # note change of order! + add_s = lambda old, new: old + " " + new # default policy is to keep current keys when there's a conflict conflict_solve = dict.fromkeys(self, preserve) diff --git a/nbformat/_version.py b/nbformat/_version.py index 02e0d9b7..447bf88d 100644 --- a/nbformat/_version.py +++ b/nbformat/_version.py @@ -1,5 +1,7 @@ """The version information for nbformat.""" # Use "hatchling version xx.yy.zz" to handle version changes +from __future__ import annotations + import re from importlib.metadata import version diff --git a/nbformat/converter.py b/nbformat/converter.py index 4d71e5e5..1e88a0cd 100644 --- a/nbformat/converter.py +++ b/nbformat/converter.py @@ -2,6 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations from . import versions from .reader import get_version @@ -40,7 +41,7 @@ def convert(nb, to_version): return nb # If the version exist, try to convert to it one step at a time. - elif to_version in versions: + if to_version in versions: # Get the the version that this recursion will convert to as a step # closer to the final revision. Make sure the newer of the conversion # functions is used to perform the conversion. @@ -63,7 +64,6 @@ def convert(nb, to_version): # Recursively convert until target version is reached. return convert(converted, to_version) - else: - raise ValueError( - "Cannot convert notebook to v%d because that version doesn't exist" % (to_version) - ) + raise ValueError( + "Cannot convert notebook to v%d because that version doesn't exist" % (to_version) + ) diff --git a/nbformat/corpus/tests/test_words.py b/nbformat/corpus/tests/test_words.py index 2e26deed..f344ed91 100644 --- a/nbformat/corpus/tests/test_words.py +++ b/nbformat/corpus/tests/test_words.py @@ -2,13 +2,14 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations from nbformat.corpus import words def test_generate_corpus_id(recwarn): """Test generating a corpus id.""" - assert len(words.generate_corpus_id()) > 7 # noqa + assert len(words.generate_corpus_id()) > 7 # 1 in 4294967296 (2^32) times this will fail - assert words.generate_corpus_id() != words.generate_corpus_id() # noqa - assert len(recwarn) == 0 # noqa + assert words.generate_corpus_id() != words.generate_corpus_id() + assert len(recwarn) == 0 diff --git a/nbformat/corpus/words.py b/nbformat/corpus/words.py index 42ef72ef..24d7889c 100644 --- a/nbformat/corpus/words.py +++ b/nbformat/corpus/words.py @@ -1,4 +1,6 @@ """Generate a corpus id.""" +from __future__ import annotations + import uuid diff --git a/nbformat/current.py b/nbformat/current.py index ce1daf04..c5ea4f60 100644 --- a/nbformat/current.py +++ b/nbformat/current.py @@ -6,7 +6,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. - +from __future__ import annotations import re import warnings @@ -83,8 +83,6 @@ class NBFormatError(ValueError): """An error raised for an nbformat error.""" - pass - def _warn_format(): warnings.warn( @@ -150,7 +148,7 @@ def writes_py(nb, **kwargs): # High level API -def reads(s, format="DEPRECATED", version=current_nbformat, **kwargs): # noqa +def reads(s, format="DEPRECATED", version=current_nbformat, **kwargs): """Read a notebook from a string and return the NotebookNode object. This function properly handles notebooks of any version. The notebook @@ -179,7 +177,7 @@ def reads(s, format="DEPRECATED", version=current_nbformat, **kwargs): # noqa return nb -def writes(nb, format="DEPRECATED", version=current_nbformat, **kwargs): # noqa +def writes(nb, format="DEPRECATED", version=current_nbformat, **kwargs): """Write a notebook to a string in a given format in the current nbformat version. This function always writes the notebook in the current nbformat version. @@ -209,7 +207,7 @@ def writes(nb, format="DEPRECATED", version=current_nbformat, **kwargs): # noqa return versions[version].writes_json(nb, **kwargs) -def read(fp, format="DEPRECATED", **kwargs): # noqa +def read(fp, format="DEPRECATED", **kwargs): """Read a notebook from a file and return the NotebookNode object. This function properly handles notebooks of any version. The notebook @@ -228,7 +226,7 @@ def read(fp, format="DEPRECATED", **kwargs): # noqa return reads(fp.read(), **kwargs) -def write(nb, fp, format="DEPRECATED", **kwargs): # noqa +def write(nb, fp, format="DEPRECATED", **kwargs): """Write a notebook to a file in a given format in the current nbformat version. This function always writes the notebook in the current nbformat version. diff --git a/nbformat/json_compat.py b/nbformat/json_compat.py index 581d0fdb..8385609c 100644 --- a/nbformat/json_compat.py +++ b/nbformat/json_compat.py @@ -4,6 +4,7 @@ """ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import os diff --git a/nbformat/notebooknode.py b/nbformat/notebooknode.py index 370ee8a9..8b3d11ae 100644 --- a/nbformat/notebooknode.py +++ b/nbformat/notebooknode.py @@ -1,4 +1,5 @@ """NotebookNode - adding attribute access to dicts""" +from __future__ import annotations from collections.abc import Mapping @@ -23,7 +24,7 @@ def update(self, *args, **kwargs): raise TypeError("update expected at most 1 arguments, got %d" % len(args)) if args: other = args[0] - if isinstance(other, Mapping): # noqa + if isinstance(other, Mapping): # noqa: SIM114 for key in other: self[key] = other[key] elif hasattr(other, "keys"): @@ -45,7 +46,6 @@ def from_dict(d): """ if isinstance(d, dict): return NotebookNode({k: from_dict(v) for k, v in d.items()}) - elif isinstance(d, (tuple, list)): + if isinstance(d, (tuple, list)): return [from_dict(i) for i in d] - else: - return d + return d diff --git a/nbformat/reader.py b/nbformat/reader.py index c97e5ab5..bf376277 100644 --- a/nbformat/reader.py +++ b/nbformat/reader.py @@ -2,6 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import json @@ -11,8 +12,6 @@ class NotJSONError(ValueError): """An error raised when an object is not valid JSON.""" - pass - def parse_json(s, **kwargs): """Parse a JSON string into a dict.""" @@ -21,7 +20,7 @@ def parse_json(s, **kwargs): except ValueError as e: message = f"Notebook does not appear to be JSON: {s!r}" # Limit the error message to 80 characters. Display whatever JSON will fit. - if len(message) > 80: # noqa + if len(message) > 80: message = message[:77] + "..." raise NotJSONError(message) from e return nb_dict diff --git a/nbformat/sentinel.py b/nbformat/sentinel.py index 56e5ac5c..be510b28 100644 --- a/nbformat/sentinel.py +++ b/nbformat/sentinel.py @@ -2,6 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations class Sentinel: diff --git a/nbformat/sign.py b/nbformat/sign.py index 470fc15e..06b1abe8 100644 --- a/nbformat/sign.py +++ b/nbformat/sign.py @@ -12,6 +12,9 @@ from contextlib import contextmanager from datetime import datetime, timezone from hmac import HMAC +from pathlib import Path + +from jupyter_core.paths import jupyter_data_dir try: import sqlite3 @@ -37,7 +40,6 @@ def convert_datetime(val): from base64 import encodebytes from jupyter_core.application import JupyterApp, base_flags -from jupyter_core.paths import jupyter_data_dir from traitlets import Any, Bool, Bytes, Callable, Enum, Instance, Integer, Unicode, default, observe from traitlets.config import LoggingConfigurable @@ -79,7 +81,6 @@ def close(self): If the store maintains any open connections (e.g. to a database), they should be closed. """ - pass class MemorySignatureStore(SignatureStore): @@ -170,7 +171,7 @@ def _connect_db(self, db_file): old_db_location, ) try: - os.rename(db_file, old_db_location) + Path(db_file).rename(old_db_location) db = sqlite3.connect(db_file, **kwargs) self.init_db(db) except (sqlite3.DatabaseError, sqlite3.OperationalError, OSError): @@ -291,7 +292,7 @@ def yield_everything(obj): if isinstance(obj, dict): for key in sorted(obj): value = obj[key] - assert isinstance(key, str) # noqa + assert isinstance(key, str) yield key.encode() yield from yield_everything(value) elif isinstance(obj, (list, tuple)): @@ -308,11 +309,11 @@ def yield_code_cells(nb): nbformat version independent """ - if nb.nbformat >= 4: # noqa + if nb.nbformat >= 4: for cell in nb["cells"]: if cell["cell_type"] == "code": yield cell - elif nb.nbformat == 3: # noqa + elif nb.nbformat == 3: for ws in nb["worksheets"]: for cell in ws["cells"]: if cell["cell_type"] == "code": @@ -372,7 +373,7 @@ def factory(): def _db_file_default(self): if not self.data_dir: return ":memory:" - return os.path.join(self.data_dir, "nbsignatures.db") + return str(Path(self.data_dir) / "nbsignatures.db") algorithm = Enum( algorithms, @@ -396,15 +397,15 @@ def _digestmod_default(self): def _secret_file_default(self): if not self.data_dir: return "" - return os.path.join(self.data_dir, "notebook_secret") + return str(Path(self.data_dir) / "notebook_secret") secret = Bytes(help="""The secret key with which notebooks are signed.""").tag(config=True) @default("secret") def _secret_default(self): # note : this assumes an Application is running - if os.path.exists(self.secret_file): - with open(self.secret_file, "rb") as f: + if Path(self.secret_file).exists(): + with Path(self.secret_file).open("rb") as f: return f.read() else: secret = encodebytes(os.urandom(1024)) @@ -419,10 +420,10 @@ def __init__(self, **kwargs): def _write_secret_file(self, secret): """write my secret to my secret_file""" self.log.info("Writing notebook-signing key to %s", self.secret_file) - with open(self.secret_file, "wb") as f: + with Path(self.secret_file).open("wb") as f: f.write(secret) try: - os.chmod(self.secret_file, 0o600) + Path(self.secret_file).chmod(0o600) except OSError: self.log.warning("Could not set permissions on %s", self.secret_file) return secret @@ -455,7 +456,7 @@ def check_signature(self, nb): - the requested scheme is available from hashlib - the computed hash from notebook_signature matches the stored hash """ - if nb.nbformat < 3: # noqa + if nb.nbformat < 3: return False signature = self.compute_signature(nb) return self.store.check_signature(signature, self.algorithm) @@ -465,7 +466,7 @@ def sign(self, nb): Stores hash algorithm and hmac digest in a local database of trusted notebooks. """ - if nb.nbformat < 3: # noqa + if nb.nbformat < 3: return signature = self.compute_signature(nb) self.store.store_signature(signature, self.algorithm) @@ -487,7 +488,7 @@ def mark_cells(self, nb, trusted): This function is the inverse of check_cells """ - if nb.nbformat < 3: # noqa + if nb.nbformat < 3: return for cell in yield_code_cells(nb): @@ -509,7 +510,7 @@ def _check_cell(self, cell, nbformat_version): return True # explicitly safe output - if nbformat_version >= 4: # noqa + if nbformat_version >= 4: unsafe_output_types = ["execute_result", "display_data"] safe_keys = {"output_type", "execution_count", "metadata"} else: # v3 @@ -535,7 +536,7 @@ def check_cells(self, nb): This function is the inverse of mark_cells. """ - if nb.nbformat < 3: # noqa + if nb.nbformat < 3: return False trusted = True for cell in yield_code_cells(nb): @@ -593,38 +594,38 @@ def _notary_default(self): def sign_notebook_file(self, notebook_path): """Sign a notebook from the filesystem""" - if not os.path.exists(notebook_path): - self.log.error("Notebook missing: %s" % notebook_path) + if not Path(notebook_path).exists(): + self.log.error("Notebook missing: %s", notebook_path) self.exit(1) - with open(notebook_path, encoding="utf-8") as f: + with Path(notebook_path).open(encoding="utf8") as f: nb = read(f, NO_CONVERT) self.sign_notebook(nb, notebook_path) def sign_notebook(self, nb, notebook_path=""): """Sign a notebook that's been loaded""" if self.notary.check_signature(nb): - print("Notebook already signed: %s" % notebook_path) # noqa + print("Notebook already signed: %s" % notebook_path) # noqa: T201 else: - print("Signing notebook: %s" % notebook_path) # noqa + print("Signing notebook: %s" % notebook_path) # noqa: T201 self.notary.sign(nb) def generate_new_key(self): """Generate a new notebook signature key""" - print("Generating new notebook key: %s" % self.notary.secret_file) # noqa + print("Generating new notebook key: %s" % self.notary.secret_file) # noqa: T201 self.notary._write_secret_file(os.urandom(1024)) def start(self): """Start the trust notebook app.""" if self.reset: - if os.path.exists(self.notary.db_file): - print("Removing trusted signature cache: %s" % self.notary.db_file) # noqa - os.remove(self.notary.db_file) + if Path(self.notary.db_file).exists(): + print("Removing trusted signature cache: %s" % self.notary.db_file) # noqa: T201 + Path(self.notary.db_file).unlink() self.generate_new_key() return if not self.extra_args: self.log.debug("Reading notebook from stdin") nb_s = sys.stdin.read() - assert isinstance(nb_s, str) # noqa + assert isinstance(nb_s, str) nb = reads(nb_s, NO_CONVERT) self.sign_notebook(nb, "") else: diff --git a/nbformat/v1/__init__.py b/nbformat/v1/__init__.py index 64cff7dd..9da3b55e 100644 --- a/nbformat/v1/__init__.py +++ b/nbformat/v1/__init__.py @@ -10,6 +10,7 @@ # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- +from __future__ import annotations from .convert import upgrade from .nbbase import NotebookNode, new_code_cell, new_notebook, new_text_cell diff --git a/nbformat/v1/convert.py b/nbformat/v1/convert.py index e6c14146..fb7028f8 100644 --- a/nbformat/v1/convert.py +++ b/nbformat/v1/convert.py @@ -10,6 +10,7 @@ # ----------------------------------------------------------------------------- # Code # ----------------------------------------------------------------------------- +from __future__ import annotations def upgrade(nb, orig_version=None): diff --git a/nbformat/v1/nbbase.py b/nbformat/v1/nbbase.py index b523bbfc..760d82f9 100644 --- a/nbformat/v1/nbbase.py +++ b/nbformat/v1/nbbase.py @@ -15,6 +15,7 @@ # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- +from __future__ import annotations from nbformat._struct import Struct @@ -26,8 +27,6 @@ class NotebookNode(Struct): """A notebook node object.""" - pass - def from_dict(d): """Create notebook node(s) from an object.""" @@ -36,10 +35,9 @@ def from_dict(d): for k, v in d.items(): newd[k] = from_dict(v) return newd - elif isinstance(d, (tuple, list)): + if isinstance(d, (tuple, list)): return [from_dict(i) for i in d] - else: - return d + return d def new_code_cell(code=None, prompt_number=None): diff --git a/nbformat/v1/nbjson.py b/nbformat/v1/nbjson.py index a064a9c7..00223432 100644 --- a/nbformat/v1/nbjson.py +++ b/nbformat/v1/nbjson.py @@ -15,6 +15,7 @@ # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- +from __future__ import annotations import json diff --git a/nbformat/v1/rwbase.py b/nbformat/v1/rwbase.py index 18b85dc8..67dc56c5 100644 --- a/nbformat/v1/rwbase.py +++ b/nbformat/v1/rwbase.py @@ -19,6 +19,7 @@ # ----------------------------------------------------------------------------- # Code # ----------------------------------------------------------------------------- +from __future__ import annotations class NotebookReader: diff --git a/nbformat/v2/__init__.py b/nbformat/v2/__init__.py index 610605ff..6b03af22 100644 --- a/nbformat/v2/__init__.py +++ b/nbformat/v2/__init__.py @@ -15,6 +15,7 @@ # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- +from __future__ import annotations import os @@ -77,7 +78,7 @@ def parse_filename(fname): (fname, name, format) : (unicode, unicode, unicode) The filename, notebook name and format. """ - basename, ext = os.path.splitext(fname) + basename, ext = os.path.splitext(fname) # noqa: PTH122 if ext in [".ipynb", ".json"]: format_ = "json" elif ext == ".py": diff --git a/nbformat/v2/convert.py b/nbformat/v2/convert.py index cd16036e..701b57e9 100644 --- a/nbformat/v2/convert.py +++ b/nbformat/v2/convert.py @@ -16,6 +16,7 @@ # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- +from __future__ import annotations from .nbbase import new_code_cell, new_notebook, new_text_cell, new_worksheet @@ -47,8 +48,8 @@ def upgrade(nb, from_version=1): ws.cells.append(newcell) newnb.worksheets.append(ws) return newnb - else: - raise ValueError("Cannot convert a notebook from v%s to v2" % from_version) + + raise ValueError("Cannot convert a notebook from v%s to v2" % from_version) def downgrade(nb): diff --git a/nbformat/v2/nbbase.py b/nbformat/v2/nbbase.py index 8d06acfb..079d6f1e 100644 --- a/nbformat/v2/nbbase.py +++ b/nbformat/v2/nbbase.py @@ -20,6 +20,7 @@ # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- +from __future__ import annotations from nbformat._struct import Struct @@ -31,8 +32,6 @@ class NotebookNode(Struct): """A notebook node object.""" - pass - def from_dict(d): """Create notebook node(s) from a value.""" @@ -41,13 +40,12 @@ def from_dict(d): for k, v in d.items(): newd[k] = from_dict(v) return newd - elif isinstance(d, (tuple, list)): + if isinstance(d, (tuple, list)): return [from_dict(i) for i in d] - else: - return d + return d -def new_output( # noqa +def new_output( output_type=None, output_text=None, output_png=None, @@ -100,7 +98,7 @@ def new_output( # noqa def new_code_cell( - input=None, # noqa: A002 + input=None, prompt_number=None, outputs=None, language="python", @@ -166,7 +164,7 @@ def new_notebook(metadata=None, worksheets=None): def new_metadata( name=None, authors=None, - license=None, # noqa: A002 + license=None, created=None, modified=None, gistid=None, diff --git a/nbformat/v2/nbjson.py b/nbformat/v2/nbjson.py index bfa1eaf3..e538e70c 100644 --- a/nbformat/v2/nbjson.py +++ b/nbformat/v2/nbjson.py @@ -15,6 +15,7 @@ # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- +from __future__ import annotations import copy import json @@ -44,7 +45,7 @@ def reads(self, s, **kwargs): """Convert a string to a notebook.""" nb = json.loads(s, **kwargs) nb = self.to_notebook(nb, **kwargs) - return nb + return nb # noqa: RET504 def to_notebook(self, d, **kwargs): """Convert a string to a notebook.""" diff --git a/nbformat/v2/nbpy.py b/nbformat/v2/nbpy.py index 015d7265..dfb024bf 100644 --- a/nbformat/v2/nbpy.py +++ b/nbformat/v2/nbpy.py @@ -15,9 +15,9 @@ # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- +from __future__ import annotations import re -from typing import List from .nbbase import new_code_cell, new_notebook, new_text_cell, new_worksheet from .rwbase import NotebookReader, NotebookWriter @@ -32,8 +32,6 @@ class PyReaderError(Exception): """An error raised by the PyReader.""" - pass - class PyReader(NotebookReader): """A Python notebook reader.""" @@ -42,11 +40,11 @@ def reads(self, s, **kwargs): """Convert a string to a notebook.""" return self.to_notebook(s, **kwargs) - def to_notebook(self, s, **kwargs): # noqa + def to_notebook(self, s, **kwargs): """Convert a string to a notebook.""" lines = s.splitlines() cells = [] - cell_lines: List[str] = [] + cell_lines: list[str] = [] state = "codecell" for line in lines: if line.startswith("# ") or _encoding_declaration_re.match(line): @@ -76,8 +74,7 @@ def to_notebook(self, s, **kwargs): # noqa if cell is not None: cells.append(cell) ws = new_worksheet(cells=cells) - nb = new_notebook(worksheets=[ws]) - return nb + return new_notebook(worksheets=[ws]) def new_cell(self, state, lines): """Create a new cell.""" @@ -104,7 +101,7 @@ def _remove_comments(self, lines): new_lines.append(line) text = "\n".join(new_lines) text = text.strip("\n") - return text + return text # noqa: RET504 def split_lines_into_blocks(self, lines): """Split lines into code blocks.""" diff --git a/nbformat/v2/nbxml.py b/nbformat/v2/nbxml.py index 065f2f8e..44e00c6a 100644 --- a/nbformat/v2/nbxml.py +++ b/nbformat/v2/nbxml.py @@ -1,6 +1,6 @@ """REMOVED: Read and write notebook files as XML. """ - +from __future__ import annotations REMOVED_MSG = """\ Reading notebooks as XML has been removed to harden security and avoid diff --git a/nbformat/v2/rwbase.py b/nbformat/v2/rwbase.py index 4ec04ec1..2a89d18e 100644 --- a/nbformat/v2/rwbase.py +++ b/nbformat/v2/rwbase.py @@ -15,6 +15,7 @@ # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- +from __future__ import annotations from base64 import decodebytes, encodebytes diff --git a/nbformat/v3/__init__.py b/nbformat/v3/__init__.py index 8932f360..a87f9bdf 100644 --- a/nbformat/v3/__init__.py +++ b/nbformat/v3/__init__.py @@ -3,6 +3,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations __all__ = [ "NotebookNode", @@ -85,7 +86,7 @@ def parse_filename(fname): (fname, name, format) : (unicode, unicode, unicode) The filename, notebook name and format. """ - basename, ext = os.path.splitext(fname) + basename, ext = os.path.splitext(fname) # noqa: PTH122 if ext in [".ipynb", ".json"]: format_ = "json" elif ext == ".py": diff --git a/nbformat/v3/convert.py b/nbformat/v3/convert.py index 0719487e..8b7d69b5 100644 --- a/nbformat/v3/convert.py +++ b/nbformat/v3/convert.py @@ -2,6 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations from .nbbase import nbformat, nbformat_minor @@ -35,7 +36,7 @@ def upgrade(nb, from_version=2, from_minor=0): from_minor : int The original minor version of the notebook to convert (only relevant for v >= 3). """ - if from_version == 2: # noqa + if from_version == 2: # Mark the original nbformat so consumers know it has been converted. nb.nbformat = nbformat nb.nbformat_minor = nbformat_minor @@ -46,17 +47,16 @@ def upgrade(nb, from_version=2, from_minor=0): for cell in ws["cells"]: cell.setdefault("metadata", {}) return nb - elif from_version == 3: # noqa + if from_version == 3: if from_minor != nbformat_minor: nb.orig_nbformat_minor = from_minor nb.nbformat_minor = nbformat_minor return nb - else: - msg = ( - "Cannot convert a notebook directly from v%s to v3. " - "Try using the nbformat.convert module." % from_version - ) - raise ValueError(msg) + msg = ( + "Cannot convert a notebook directly from v%s to v3. " + "Try using the nbformat.convert module." % from_version + ) + raise ValueError(msg) def heading_to_md(cell): @@ -79,7 +79,7 @@ def downgrade(nb): nb : NotebookNode The Python representation of the notebook to convert. """ - if nb.nbformat != 3: # noqa + if nb.nbformat != 3: return nb nb.nbformat = 2 for ws in nb.worksheets: diff --git a/nbformat/v3/nbbase.py b/nbformat/v3/nbbase.py index a6c034c4..da480016 100644 --- a/nbformat/v3/nbbase.py +++ b/nbformat/v3/nbbase.py @@ -8,6 +8,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import warnings @@ -26,8 +27,6 @@ class NotebookNode(Struct): """A notebook node object.""" - pass - def from_dict(d): """Create notebook node(s) from an object.""" @@ -36,10 +35,9 @@ def from_dict(d): for k, v in d.items(): newd[k] = from_dict(v) return newd - elif isinstance(d, (tuple, list)): + if isinstance(d, (tuple, list)): return [from_dict(i) for i in d] - else: - return d + return d def str_passthrough(obj): @@ -63,13 +61,12 @@ def cast_str(obj): stacklevel=3, ) return obj.decode("ascii", "replace") - else: - if not isinstance(obj, str): - raise AssertionError - return obj + if not isinstance(obj, str): + raise AssertionError + return obj -def new_output( # noqa +def new_output( output_type, output_text=None, output_png=None, @@ -135,7 +132,7 @@ def new_output( # noqa def new_code_cell( - input=None, # noqa + input=None, prompt_number=None, outputs=None, language="python", @@ -219,7 +216,7 @@ def new_notebook(name=None, metadata=None, worksheets=None): def new_metadata( name=None, authors=None, - license=None, # noqa: A002 + license=None, created=None, modified=None, gistid=None, diff --git a/nbformat/v3/nbjson.py b/nbformat/v3/nbjson.py index 3af7cc71..405ea98a 100644 --- a/nbformat/v3/nbjson.py +++ b/nbformat/v3/nbjson.py @@ -2,6 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import copy import json @@ -28,7 +29,7 @@ def reads(self, s, **kwargs): nb = json.loads(s, **kwargs) nb = self.to_notebook(nb, **kwargs) nb = strip_transient(nb) - return nb + return nb # noqa: RET504 def to_notebook(self, d, **kwargs): """Convert a dict to a notebook.""" diff --git a/nbformat/v3/nbpy.py b/nbformat/v3/nbpy.py index cde4ca43..a24a7ecc 100644 --- a/nbformat/v3/nbpy.py +++ b/nbformat/v3/nbpy.py @@ -15,9 +15,9 @@ # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- +from __future__ import annotations import re -from typing import List from .nbbase import ( nbformat, @@ -40,8 +40,6 @@ class PyReaderError(Exception): """An error raised for a pyreader error.""" - pass - class PyReader(NotebookReader): """A python notebook reader.""" @@ -50,11 +48,11 @@ def reads(self, s, **kwargs): """Convert a string to a notebook""" return self.to_notebook(s, **kwargs) - def to_notebook(self, s, **kwargs): # noqa + def to_notebook(self, s, **kwargs): """Convert a string to a notebook""" lines = s.splitlines() cells = [] - cell_lines: List[str] = [] + cell_lines: list[str] = [] kwargs = {} state = "codecell" for line in lines: @@ -82,7 +80,7 @@ def to_notebook(self, s, **kwargs): # noqa cell_lines = [] kwargs = {} # VERSIONHACK: plaintext -> raw - elif line.startswith("# ") or line.startswith("# "): + elif line.startswith(("# ", "# ")): cell = self.new_cell(state, cell_lines, **kwargs) if cell is not None: cells.append(cell) @@ -110,10 +108,9 @@ def to_notebook(self, s, **kwargs): # noqa if cell is not None: cells.append(cell) ws = new_worksheet(cells=cells) - nb = new_notebook(worksheets=[ws]) - return nb + return new_notebook(worksheets=[ws]) - def new_cell(self, state, lines, **kwargs): # noqa + def new_cell(self, state, lines, **kwargs): """Create a new cell.""" if state == "codecell": input_ = "\n".join(lines) @@ -147,7 +144,7 @@ def _remove_comments(self, lines): new_lines.append(line) text = "\n".join(new_lines) text = text.strip("\n") - return text + return text # noqa: RET504 def split_lines_into_blocks(self, lines): """Split lines into code blocks.""" @@ -167,7 +164,7 @@ def split_lines_into_blocks(self, lines): class PyWriter(NotebookWriter): """A Python notebook writer.""" - def writes(self, nb, **kwargs): # noqa + def writes(self, nb, **kwargs): """Convert a notebook to a string.""" lines = ["# -*- coding: utf-8 -*-"] lines.extend( diff --git a/nbformat/v3/rwbase.py b/nbformat/v3/rwbase.py index 53f70649..d0f50fd7 100644 --- a/nbformat/v3/rwbase.py +++ b/nbformat/v3/rwbase.py @@ -2,7 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. - +from __future__ import annotations from base64 import decodebytes, encodebytes @@ -40,9 +40,8 @@ def _join_lines(lines): if lines and lines[0].endswith(("\n", "\r")): # created by splitlines(True) return "".join(lines) - else: - # created by splitlines() - return "\n".join(lines) + # created by splitlines() + return "\n".join(lines) def rejoin_lines(nb): diff --git a/nbformat/v4/__init__.py b/nbformat/v4/__init__.py index 044a33f4..0653ff42 100644 --- a/nbformat/v4/__init__.py +++ b/nbformat/v4/__init__.py @@ -2,6 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations __all__ = [ "nbformat", diff --git a/nbformat/v4/convert.py b/nbformat/v4/convert.py index 438e6409..6a83d652 100644 --- a/nbformat/v4/convert.py +++ b/nbformat/v4/convert.py @@ -2,6 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import json import re @@ -25,7 +26,7 @@ def _warn_if_invalid(nb, version): get_logger().error("Notebook JSON is not valid v%i: %s", version, e) -def upgrade(nb, from_version=None, from_minor=None): # noqa +def upgrade(nb, from_version=None, from_minor=None): """Convert a notebook to latest v4. Parameters @@ -41,15 +42,14 @@ def upgrade(nb, from_version=None, from_minor=None): # noqa from_version = nb["nbformat"] if not from_minor: if "nbformat_minor" not in nb: - if from_version == 4: # noqa + if from_version == 4: msg = "The v4 notebook does not include the nbformat minor, which is needed." raise validator.ValidationError(msg) - else: - from_minor = 0 + from_minor = 0 else: from_minor = nb["nbformat_minor"] - if from_version == 3: # noqa + if from_version == 3: # Validate the notebook before conversion _warn_if_invalid(nb, from_version) @@ -77,7 +77,7 @@ def upgrade(nb, from_version=None, from_minor=None): # noqa # Validate the converted notebook before returning it _warn_if_invalid(nb, nbformat) return nb - elif from_version == 4: # noqa + if from_version == 4: if from_minor == nbformat_minor: return nb @@ -85,7 +85,7 @@ def upgrade(nb, from_version=None, from_minor=None): # noqa # if from_minor < 3: # if from_minor < 4: - if from_minor < 5: # noqa + if from_minor < 5: for cell in nb.cells: cell.id = random_cell_id() @@ -93,11 +93,10 @@ def upgrade(nb, from_version=None, from_minor=None): # noqa nb.nbformat_minor = nbformat_minor return nb - else: - raise ValueError( - "Cannot convert a notebook directly from v%s to v4. " - "Try using the nbformat.convert module." % from_version - ) + raise ValueError( + "Cannot convert a notebook directly from v%s to v4. " + "Try using the nbformat.convert module." % from_version + ) def upgrade_cell(cell): @@ -154,7 +153,7 @@ def downgrade_cell(cell): source = cell.get("source", "") if "\n" not in source and source.startswith("#"): match = re.match(r"(#+)\s*(.*)", source) - assert match is not None # noqa + assert match is not None prefix, text = match.groups() cell.cell_type = "heading" cell.source = text diff --git a/nbformat/v4/nbbase.py b/nbformat/v4/nbbase.py index 95514016..7b9e9892 100644 --- a/nbformat/v4/nbbase.py +++ b/nbformat/v4/nbbase.py @@ -8,6 +8,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations from nbformat.corpus.words import generate_corpus_id as random_cell_id from nbformat.notebooknode import NotebookNode @@ -91,27 +92,26 @@ def output_from_msg(msg): data=content["data"], execution_count=content["execution_count"], ) - elif msg_type == "stream": + if msg_type == "stream": return new_output( output_type=msg_type, name=content["name"], text=content["text"], ) - elif msg_type == "display_data": + if msg_type == "display_data": return new_output( output_type=msg_type, metadata=content["metadata"], data=content["data"], ) - elif msg_type == "error": + if msg_type == "error": return new_output( output_type=msg_type, ename=content["ename"], evalue=content["evalue"], traceback=content["traceback"], ) - else: - raise ValueError("Unrecognized output msg type: %r" % msg_type) + raise ValueError("Unrecognized output msg type: %r" % msg_type) def new_code_cell(source="", **kwargs): diff --git a/nbformat/v4/nbjson.py b/nbformat/v4/nbjson.py index 32834de5..34929503 100644 --- a/nbformat/v4/nbjson.py +++ b/nbformat/v4/nbjson.py @@ -2,6 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import copy import json @@ -28,7 +29,7 @@ def reads(self, s, **kwargs): """Read a JSON string into a Notebook object""" nb = json.loads(s, **kwargs) nb = self.to_notebook(nb, **kwargs) - return nb + return nb # noqa: RET504 def to_notebook(self, d, **kwargs): """Convert a disk-format notebook dict to in-memory NotebookNode @@ -38,7 +39,7 @@ def to_notebook(self, d, **kwargs): nb = from_dict(d) nb = rejoin_lines(nb) nb = strip_transient(nb) - return nb + return nb # noqa: RET504 class JSONWriter(NotebookWriter): diff --git a/nbformat/v4/rwbase.py b/nbformat/v4/rwbase.py index eb0f4179..10f0077d 100644 --- a/nbformat/v4/rwbase.py +++ b/nbformat/v4/rwbase.py @@ -2,6 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations def _is_json_mime(mime): diff --git a/nbformat/validator.py b/nbformat/validator.py index e65e3cf8..30ea71aa 100644 --- a/nbformat/validator.py +++ b/nbformat/validator.py @@ -1,15 +1,15 @@ """Notebook format validators.""" # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. - +from __future__ import annotations import json -import os import pprint import warnings from copy import deepcopy +from pathlib import Path from textwrap import dedent -from typing import Any, Optional, Tuple +from typing import Any, Optional from ._imports import import_item from .corpus.words import generate_corpus_id @@ -37,7 +37,7 @@ def _relax_additional_properties(obj): """relax any `additionalProperties`""" if isinstance(obj, dict): for key, value in obj.items(): - value = ( # noqa + value = ( # noqa: PLW2901 True if key == "additionalProperties" else _relax_additional_properties(value) ) obj[key] = value @@ -102,18 +102,16 @@ def _get_schema_json(v, version=None, version_minor=None): Gets the json schema from a given imported library and nbformat version. """ if (version, version_minor) in v.nbformat_schema: - schema_path = os.path.join( - os.path.dirname(v.__file__), v.nbformat_schema[(version, version_minor)] - ) + schema_path = str(Path(v.__file__).parent / v.nbformat_schema[(version, version_minor)]) elif version_minor > v.nbformat_minor: # load the latest schema - schema_path = os.path.join(os.path.dirname(v.__file__), v.nbformat_schema[(None, None)]) + schema_path = str(Path(v.__file__).parent / v.nbformat_schema[(None, None)]) else: msg = "Cannot find appropriate nbformat schema file." raise AttributeError(msg) - with open(schema_path, encoding="utf-8") as f: + with Path(schema_path).open(encoding="utf8") as f: schema_json = json.load(f) - return schema_json + return schema_json # noqa: RET504 def isvalid(nbjson, ref=None, version=None, version_minor=None): @@ -172,18 +170,17 @@ def _truncate_obj(obj): if len(obj) > _ITEM_LIMIT: truncated_dict["..."] = "%i keys truncated" % (len(obj) - _ITEM_LIMIT) return truncated_dict - elif isinstance(obj, list): + if isinstance(obj, list): truncated_list = [_truncate_obj(item) for item in obj[:_ITEM_LIMIT]] if len(obj) > _ITEM_LIMIT: truncated_list.append("...%i items truncated..." % (len(obj) - _ITEM_LIMIT)) return truncated_list - elif isinstance(obj, str): + if isinstance(obj, str): truncated_str = obj[:_STR_LIMIT] if len(obj) > _STR_LIMIT: truncated_str += "..." return truncated_str - else: - return obj + return obj class NotebookValidationError(ValidationError): # type:ignore[misc] @@ -262,7 +259,7 @@ def better_validation_error(error, version, version_minor): if better.ref is None: better.ref = ref return better - except Exception: # noqa + except Exception: # noqa: S110 # if it fails for some reason, # let the original error through pass @@ -276,7 +273,7 @@ def normalize( *, relax_add_props: bool = False, strip_invalid_metadata: bool = False, -) -> Tuple[int, Any]: +) -> tuple[int, Any]: """ Normalise a notebook prior to validation. @@ -331,7 +328,7 @@ def _normalize( repair_duplicate_cell_ids: bool, relax_add_props: bool, strip_invalid_metadata: bool, -) -> Tuple[int, Any]: +) -> tuple[int, Any]: """ Private normalisation routine. @@ -409,7 +406,7 @@ def _dep_warn(field): ) -def validate( # noqa +def validate( nbdict: Any = None, ref: Optional[str] = None, version: Optional[int] = None, @@ -456,19 +453,17 @@ def validate( # noqa Please explicitly call `normalize` if you need to normalize notebooks. """ - assert isinstance(ref, str) or ref is None # noqa + assert isinstance(ref, str) or ref is None if strip_invalid_metadata is _deprecated: strip_invalid_metadata = False else: _dep_warn("strip_invalid_metadata") - pass if repair_duplicate_cell_ids is _deprecated: repair_duplicate_cell_ids = True else: _dep_warn("repair_duplicate_cell_ids") - pass # backwards compatibility for nbjson argument if nbdict is not None: @@ -491,8 +486,8 @@ def validate( # noqa version, version_minor = 1, 0 if ref is None: - assert isinstance(version, int) # noqa - assert isinstance(version_minor, int) # noqa + assert isinstance(version, int) + assert isinstance(version_minor, int) _normalize( nbdict, version, @@ -534,7 +529,7 @@ def _get_errors( return iter(errors) -def _strip_invalida_metadata( # noqa +def _strip_invalida_metadata( nbdict: Any, version: int, version_minor: int, relax_add_props: bool ) -> int: """ @@ -598,7 +593,7 @@ def _strip_invalida_metadata( # noqa rel_path = error.relative_path error_for_intended_schema = error.schema_path[0] == schema_index is_top_level_metadata_key = ( - len(rel_path) == 2 and rel_path[0] == "metadata" # noqa + len(rel_path) == 2 and rel_path[0] == "metadata" ) if error_for_intended_schema and is_top_level_metadata_key: nbdict["cells"][cell_idx]["metadata"].pop(rel_path[1], None) diff --git a/nbformat/warnings.py b/nbformat/warnings.py index 45156c43..4c8d5bf1 100644 --- a/nbformat/warnings.py +++ b/nbformat/warnings.py @@ -1,6 +1,7 @@ """ Warnings that can be emitted by nbformat. """ +from __future__ import annotations class MissingIDFieldWarning(FutureWarning): @@ -15,8 +16,6 @@ class MissingIDFieldWarning(FutureWarning): """ - pass - class DuplicateCellId(FutureWarning): """ @@ -28,5 +27,3 @@ class DuplicateCellId(FutureWarning): We subclass FutureWarning as we will change the behavior in the future. """ - - pass diff --git a/pyproject.toml b/pyproject.toml index 78ff7946..3eed064c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,7 +133,6 @@ exclude_lines = [ files = "nbformat" python_version = "3.8" strict = true -show_error_codes = true enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] disable_error_code = ["no-untyped-def", "no-untyped-call"] warn_unreachable = true @@ -145,22 +144,36 @@ line-length = 100 exclude = ["^/tests.*ipynb$"] [tool.ruff.lint] -select = [ - "A", "B", "C", "DTZ", "E", "EM", "F", "FBT", "I", "ICN", "N", - "PLC", "PLE", "PLR", "PLW", "Q", "RUF", "S", "SIM", "T", "TID", "UP", - "W", "YTT", +extend-select = [ + "B", # flake8-bugbear + "I", # isort + "C4", # flake8-comprehensions + "EM", # flake8-errmsg + "ICN", # flake8-import-conventions + "G", # flake8-logging-format + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # pylint + "PTH", # flake8-use-pathlib + "PT", # flake8-pytest-style + "RET", # flake8-return + "RUF", # Ruff-specific + "SIM", # flake8-simplify + "T20", # flake8-print + "UP", # pyupgrade + "YTT", # flake8-2020 + "EXE", # flake8-executable + "PYI", # flake8-pyi + "S", # flake8-bandit ] ignore = [ - # Q000 Single quotes found but double quotes preferred - "Q000", - # FBT001 Boolean positional arg in function definition - "FBT001", "FBT002", "FBT003", - # E501 Line too long (158 > 100 characters) - "E501", - # SIM105 Use `contextlib.suppress(...)` - "SIM105", - # PLR0913 Too many arguments to function call - "PLR0913", + "PLR", # Design related pylint codes + "E501", # Line too long (158 > 100 characters) + "SIM105", # Use `contextlib.suppress(...)` + "UP007", # Use `X | Y` for type annotations + "RET503", # Missing explicit `return` at the end of function able... + "S101", # Use of `assert` detected" + "E731", # Do not assign a `lambda` expression" ] unfixable = [ # Don't touch print statements @@ -168,6 +181,7 @@ unfixable = [ # Don't touch noqa lines "RUF100", ] +isort.required-imports = ["from __future__ import annotations"] [tool.ruff.lint.per-file-ignores] # B011 Do not call assert False since python -O removes these calls @@ -179,13 +193,17 @@ unfixable = [ # N802 Function name `assertIn` should be lowercase # RUF001 contains ambiguous unicode character '–' (did you mean '-'?) # S101 Use of `assert` detected -# PLR2004 Magic value used in comparison # PLC1901 `cell.source == ""` can be simplified -"tests/*" = ["B011", "F841", "C408", "E402", "T201", "B007", "N802", "RUF001", "RUF002", "S101", "PLR2004", - "PLC1901"] +"tests/*" = ["B011", "F841", "C408", "E402", "T201", "B007", "N802", "RUF001", "RUF002", "S101", "PTH", "PT009", "PT011", "PT004", "PT027", + "PLC1901", "PGH004"] # F401 `nbxml.to_notebook` imported but unused "nbformat/*__init__.py" = ["F401"] +[tool.ruff.lint.flake8-pytest-style] +fixture-parentheses = false +mark-parentheses = false +parametrize-names-type = "csv" + [tool.coverage.run] relative_files = true source = ["nbformat"] @@ -203,7 +221,7 @@ fail-under=100 ignore = ["W002"] [tool.repo-review] -ignore = ["PY007", "GH102"] +ignore = ["GH102"] [tool.codespell] skip = "*.ipynb" diff --git a/scripts/jupyter-trust b/scripts/jupyter-trust index ac544b88..73aeedff 100755 --- a/scripts/jupyter-trust +++ b/scripts/jupyter-trust @@ -1,4 +1,5 @@ #!/usr/bin/env python +from __future__ import annotations def main(): diff --git a/tests/base.py b/tests/base.py index 395c3475..82c08b0b 100644 --- a/tests/base.py +++ b/tests/base.py @@ -4,6 +4,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import os import unittest diff --git a/tests/test_api.py b/tests/test_api.py index 3ee1856f..f2d76f3e 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -2,12 +2,13 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import json import os import pathlib from tempfile import TemporaryDirectory -from typing import Any, Dict +from typing import Any from jsonschema import ValidationError from pep440 import is_canonical @@ -70,7 +71,7 @@ def test_read_write_pathlib_object(self): def test_capture_validation_error(self): """Test that validation error can be captured on read() and write()""" - validation_error: Dict[str, Any] = {} + validation_error: dict[str, Any] = {} path = os.path.join(self._get_files_path(), "invalid.ipynb") nb = read(path, as_version=4, capture_validation_error=validation_error) assert not isvalid(nb) diff --git a/tests/test_convert.py b/tests/test_convert.py index 00548d75..af9b6d92 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -2,6 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations from nbformat import current_nbformat from nbformat.converter import convert diff --git a/tests/test_nbformat.py b/tests/test_nbformat.py index 2eacab8d..8c8d27c7 100644 --- a/tests/test_nbformat.py +++ b/tests/test_nbformat.py @@ -1,3 +1,7 @@ +from __future__ import annotations + +from pathlib import Path + import pytest from nbformat import read @@ -6,16 +10,18 @@ def test_read_invalid_iowrapper(tmpdir): ipynb_filepath = tmpdir.join("empty.ipynb") - ipynb_filepath.write("{}") + Path(ipynb_filepath).write_text("{}", encoding="utf8") - with pytest.raises(ValidationError) as excinfo, ipynb_filepath.open() as fp: + with pytest.raises(ValidationError) as excinfo, Path(ipynb_filepath).open( + encoding="utf8" + ) as fp: read(fp, as_version=4) assert "cells" in str(excinfo.value) def test_read_invalid_filepath(tmpdir): ipynb_filepath = tmpdir.join("empty.ipynb") - ipynb_filepath.write("{}") + Path(ipynb_filepath).write_text("{}", encoding="utf8") with pytest.raises(ValidationError) as excinfo: read(str(ipynb_filepath), as_version=4) @@ -24,10 +30,10 @@ def test_read_invalid_filepath(tmpdir): def test_read_invalid_pathlikeobj(tmpdir): ipynb_filepath = tmpdir.join("empty.ipynb") - ipynb_filepath.write("{}") + Path(ipynb_filepath).write_text("{}", encoding="utf8") with pytest.raises(ValidationError) as excinfo: - read(ipynb_filepath, as_version=4) + read(str(ipynb_filepath), as_version=4) assert "cells" in str(excinfo.value) diff --git a/tests/test_reader.py b/tests/test_reader.py index 009a1128..0b86c17d 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -11,6 +11,7 @@ # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- +from __future__ import annotations from nbformat.reader import get_version, read from nbformat.validator import ValidationError diff --git a/tests/test_sign.py b/tests/test_sign.py index 97d8ad8f..e7691512 100644 --- a/tests/test_sign.py +++ b/tests/test_sign.py @@ -2,6 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import codecs import copy diff --git a/tests/test_validator.py b/tests/test_validator.py index fc1b0659..188cacea 100644 --- a/tests/test_validator.py +++ b/tests/test_validator.py @@ -2,6 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import json import os diff --git a/tests/v1/nbexamples.py b/tests/v1/nbexamples.py index a93450ef..b4994eb4 100644 --- a/tests/v1/nbexamples.py +++ b/tests/v1/nbexamples.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from nbformat.v1.nbbase import new_code_cell, new_notebook, new_text_cell nb0 = new_notebook() diff --git a/tests/v1/test_json.py b/tests/v1/test_json.py index e81d62ef..121804fe 100644 --- a/tests/v1/test_json.py +++ b/tests/v1/test_json.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from unittest import TestCase from nbformat.v1.nbjson import reads, writes diff --git a/tests/v1/test_nbbase.py b/tests/v1/test_nbbase.py index abb3e133..22bcce0c 100644 --- a/tests/v1/test_nbbase.py +++ b/tests/v1/test_nbbase.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from unittest import TestCase from nbformat.v1.nbbase import new_code_cell, new_notebook, new_text_cell diff --git a/tests/v2/nbexamples.py b/tests/v2/nbexamples.py index af3d485d..54cc205d 100644 --- a/tests/v2/nbexamples.py +++ b/tests/v2/nbexamples.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os from base64 import encodebytes diff --git a/tests/v2/test_json.py b/tests/v2/test_json.py index d82a61c5..70efff3e 100644 --- a/tests/v2/test_json.py +++ b/tests/v2/test_json.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from unittest import TestCase from nbformat.v2.nbjson import reads, writes diff --git a/tests/v2/test_nbbase.py b/tests/v2/test_nbbase.py index 5c60d8c8..67716be3 100644 --- a/tests/v2/test_nbbase.py +++ b/tests/v2/test_nbbase.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from unittest import TestCase from nbformat.v2.nbbase import ( diff --git a/tests/v2/test_nbpy.py b/tests/v2/test_nbpy.py index 08d410bf..7e5b865e 100644 --- a/tests/v2/test_nbpy.py +++ b/tests/v2/test_nbpy.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from unittest import TestCase from nbformat.v2.nbpy import writes diff --git a/tests/v3/formattest.py b/tests/v3/formattest.py index 5c72eb31..0f8bfde6 100644 --- a/tests/v3/formattest.py +++ b/tests/v3/formattest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import shutil import tempfile diff --git a/tests/v3/nbexamples.py b/tests/v3/nbexamples.py index 9cbe3280..ff9d7b8d 100644 --- a/tests/v3/nbexamples.py +++ b/tests/v3/nbexamples.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os from base64 import encodebytes diff --git a/tests/v3/test_json.py b/tests/v3/test_json.py index 32918414..5885656a 100644 --- a/tests/v3/test_json.py +++ b/tests/v3/test_json.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import copy import json from base64 import decodebytes diff --git a/tests/v3/test_misc.py b/tests/v3/test_misc.py index a5c77559..86405bb7 100644 --- a/tests/v3/test_misc.py +++ b/tests/v3/test_misc.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os from unittest import TestCase diff --git a/tests/v3/test_nbbase.py b/tests/v3/test_nbbase.py index 84330890..7c44f9a8 100644 --- a/tests/v3/test_nbbase.py +++ b/tests/v3/test_nbbase.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from unittest import TestCase import pytest diff --git a/tests/v3/test_nbpy.py b/tests/v3/test_nbpy.py index 4ebbcfde..db7feaf6 100644 --- a/tests/v3/test_nbpy.py +++ b/tests/v3/test_nbpy.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from unittest import TestCase from nbformat.v3 import nbpy diff --git a/tests/v4/formattest.py b/tests/v4/formattest.py index 5c72eb31..0f8bfde6 100644 --- a/tests/v4/formattest.py +++ b/tests/v4/formattest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import shutil import tempfile diff --git a/tests/v4/nbexamples.py b/tests/v4/nbexamples.py index f1e4a67b..9cf4d98e 100644 --- a/tests/v4/nbexamples.py +++ b/tests/v4/nbexamples.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os from base64 import encodebytes diff --git a/tests/v4/test_convert.py b/tests/v4/test_convert.py index 95803b90..5a96a1a3 100644 --- a/tests/v4/test_convert.py +++ b/tests/v4/test_convert.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import copy import os from unittest import mock @@ -8,7 +10,7 @@ from nbformat.v4 import convert from nbformat.v4.nbjson import reads -from ..v3 import nbexamples as v3examples # noqa +from ..v3 import nbexamples as v3examples from . import nbexamples @@ -55,7 +57,7 @@ def test_upgrade_heading(): def test_downgrade_heading(): v3h = v3.new_heading_cell v4m = v4.new_markdown_cell - v3m = lambda source: v3.new_text_cell("markdown", source) # noqa + v3m = lambda source: v3.new_text_cell("markdown", source) for v4cell, expected in [ ( v4m(source="# foo"), diff --git a/tests/v4/test_json.py b/tests/v4/test_json.py index 064afc1e..a16586a8 100644 --- a/tests/v4/test_json.py +++ b/tests/v4/test_json.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import json import os import sys diff --git a/tests/v4/test_nbbase.py b/tests/v4/test_nbbase.py index 96fe69f7..be9d9d19 100644 --- a/tests/v4/test_nbbase.py +++ b/tests/v4/test_nbbase.py @@ -1,4 +1,5 @@ """Tests for the Python API for composing notebook elements""" +from __future__ import annotations from nbformat.v4.nbbase import ( NotebookNode, diff --git a/tests/v4/test_validate.py b/tests/v4/test_validate.py index 6d9e62f6..c7d97312 100644 --- a/tests/v4/test_validate.py +++ b/tests/v4/test_validate.py @@ -2,6 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import os