Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add spell checker and enforce docstrings #339

Merged
merged 1 commit into from
Dec 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@ jobs:
run: |
hatch run typing:test
hatch run lint:style
pipx run 'validate-pyproject[all]' pyproject.toml
pipx run interrogate -v nbformat
pipx run doc8 --max-line-length=200

docs:
runs-on: ubuntu-latest
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
Expand Down
9 changes: 5 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: end-of-file-fixer
- id: check-case-conflict
- id: check-ast
- id: check-docstring-first
- id: check-executables-have-shebangs
- id: requirements-txt-fixer
- id: check-added-large-files
- id: check-case-conflict
- id: check-merge-conflict
- id: check-json
- id: check-toml
- id: check-yaml
- id: debug-statements
- id: forbid-new-submodules
- id: check-builtin-literals
- id: end-of-file-fixer
- id: trailing-whitespace

- repo: https://github.com/python-jsonschema/check-jsonschema
Expand Down
7 changes: 7 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@
"sphinx.ext.napoleon",
]

try:
import enchant # type:ignore # noqa

extensions += ["sphinxcontrib.spelling"]
except ImportError:
pass

# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]

Expand Down
6 changes: 1 addition & 5 deletions nbformat/_struct.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
"""
Vendoring of old ipython_genutils Struct
"""


"""A dict subclass that supports attribute style access.

Can probably be replaced by types.SimpleNamespace from Python 3.3
Expand Down Expand Up @@ -206,6 +201,7 @@ def __dict_invert(self, data):
return outdict

def dict(self):
"""Get the dict representation of the struct."""
return self

def copy(self):
Expand Down
1 change: 1 addition & 0 deletions nbformat/_version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""The version information for nbformat."""
# Use "hatchling version xx.yy.zz" to handle version changes
import re

Expand Down
1 change: 1 addition & 0 deletions nbformat/corpus/tests/test_words.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@


def test_generate_corpus_id(recwarn):
"""Test generating a corpus id."""
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()
Expand Down
2 changes: 2 additions & 0 deletions nbformat/corpus/words.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Generate a corpus id."""
import uuid


def generate_corpus_id():
"""Generate a corpus id."""
return uuid.uuid4().hex[:8]
2 changes: 2 additions & 0 deletions nbformat/current.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@


class NBFormatError(ValueError):
"""An error raised for an nbformat error."""

pass


Expand Down
16 changes: 14 additions & 2 deletions nbformat/json_compat.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
"""
Common validator wrapper to provide a uniform usage of other schema validation
libraries.
"""
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

import os

Expand All @@ -15,41 +15,52 @@


class JsonSchemaValidator:
"""A json schema validator."""

name = "jsonschema"

def __init__(self, schema):
"""Initialize the validator."""
self._schema = schema
self._default_validator = _JsonSchemaValidator(schema) # Default
self._validator = self._default_validator

def validate(self, data):
"""Validate incoming data."""
self._default_validator.validate(data)

def iter_errors(self, data, schema=None):
"""Iterate over errors in incoming data."""
if schema is None:
return self._default_validator.iter_errors(data)
if hasattr(self._default_validator, "evolve"):
return self._default_validator.evolve(schema=schema).iter_errors(data)
return self._default_validator.iter_errors(data, schema)

def error_tree(self, errors):
"""Create an error tree for the errors."""
return ErrorTree(errors=errors)


class FastJsonSchemaValidator(JsonSchemaValidator):
"""A schema validator using fastjsonschema."""

name = "fastjsonschema"

def __init__(self, schema):
"""Initialize the validator."""
super().__init__(schema)
self._validator = fastjsonschema.compile(schema)

def validate(self, data):
"""Validate incoming data."""
try:
self._validator(data)
except _JsonSchemaException as error:
raise ValidationError(str(error), schema_path=error.path) from error

def iter_errors(self, data, schema=None):
"""Iterate over errors in incoming data."""
if schema is not None:
return super().iter_errors(data, schema)

Expand All @@ -63,6 +74,7 @@ def iter_errors(self, data, schema=None):
return errors

def error_tree(self, errors):
"""Create an error tree for the errors."""
# fastjsonschema's exceptions don't contain the same information that the jsonschema ValidationErrors
# do. This method is primarily used for introspecting metadata schema failures so that we can strip
# them if asked to do so in `nbformat.validate`.
Expand Down
1 change: 1 addition & 0 deletions nbformat/notebooknode.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class NotebookNode(Struct):
"""A dict-like node with attribute-access"""

def __setitem__(self, key, value):
"""Set an item on the notebook."""
if isinstance(value, Mapping) and not isinstance(value, NotebookNode):
value = from_dict(value)
super().__setitem__(key, value)
Expand Down
2 changes: 2 additions & 0 deletions nbformat/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@


class NotJSONError(ValueError):
"""An error raised when an object is not valid JSON."""

pass


Expand Down
4 changes: 4 additions & 0 deletions nbformat/sentinel.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@


class Sentinel:
"""Sentinel class for constants with useful reprs"""

def __init__(self, name, module, docstring=None):
"""Initialize the sentinel."""
self.name = name
self.module = module
if docstring:
self.__doc__ = docstring

def __repr__(self):
"""The string repr for the sentinel."""
return str(self.module) + "." + self.name
14 changes: 14 additions & 0 deletions nbformat/sign.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,13 @@ class MemorySignatureStore(SignatureStore):
cache_size = 65535

def __init__(self):
"""Initialize a memory signature store."""
# We really only want an ordered set, but the stdlib has OrderedDict,
# and it's easy to use a dict as a set.
self.data = OrderedDict()

def store_signature(self, digest, algorithm):
"""Store a signature."""
key = (digest, algorithm)
# Pop it so it goes to the end when we reinsert it
self.data.pop(key, None)
Expand All @@ -94,6 +96,7 @@ def _maybe_cull(self):
self.data.popitem(last=False)

def check_signature(self, digest, algorithm):
"""Check a signature."""
key = (digest, algorithm)
if key in self.data:
# Move it to the end (.move_to_end() method is new in Py3)
Expand All @@ -103,6 +106,7 @@ def check_signature(self, digest, algorithm):
return False

def remove_signature(self, digest, algorithm):
"""Remove a signature."""
self.data.pop((digest, algorithm), None)


Expand All @@ -119,11 +123,13 @@ class SQLiteSignatureStore(SignatureStore, LoggingConfigurable):
).tag(config=True)

def __init__(self, db_file, **kwargs):
"""Initialize a sql signature store."""
super().__init__(**kwargs)
self.db_file = db_file
self.db = self._connect_db(db_file)

def close(self):
"""Close the db."""
if self.db is not None:
self.db.close()

Expand Down Expand Up @@ -169,6 +175,7 @@ def _connect_db(self, db_file):
return db

def init_db(self, db):
"""Initialize the db."""
db.execute(
"""
CREATE TABLE IF NOT EXISTS nbsignatures
Expand All @@ -188,6 +195,7 @@ def init_db(self, db):
db.commit()

def store_signature(self, digest, algorithm):
"""Store a signature in the db."""
if self.db is None:
return
if not self.check_signature(digest, algorithm):
Expand All @@ -214,6 +222,7 @@ def store_signature(self, digest, algorithm):
self.cull_db()

def check_signature(self, digest, algorithm):
"""Check a signature against the db."""
if self.db is None:
return False
r = self.db.execute(
Expand All @@ -236,6 +245,7 @@ def check_signature(self, digest, algorithm):
return True

def remove_signature(self, digest, algorithm):
"""Remove a signature from the db."""
self.db.execute(
"""DELETE FROM nbsignatures WHERE
algorithm = ? AND
Expand Down Expand Up @@ -395,6 +405,7 @@ def _secret_default(self):
return secret

def __init__(self, **kwargs):
"""Initialize the notary."""
super().__init__(**kwargs)
self.store = self.store_factory()

Expand Down Expand Up @@ -540,6 +551,8 @@ def check_cells(self, nb):


class TrustNotebookApp(JupyterApp):
"""An application for handling notebook trust."""

version = __version__
description = """Sign one or more Jupyter notebooks with your key,
to trust their dynamic (HTML, Javascript) output.
Expand Down Expand Up @@ -594,6 +607,7 @@ def generate_new_key(self):
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)
Expand Down
1 change: 1 addition & 0 deletions nbformat/v1/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@


def upgrade(nb, orig_version=None):
"""Upgrade a notebook."""
raise ValueError("Cannot convert to v1 notebook format")
3 changes: 3 additions & 0 deletions nbformat/v1/nbbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@


class NotebookNode(Struct):
"""A notebook node object."""

pass


def from_dict(d):
"""Create notebook node(s) from an object."""
if isinstance(d, dict):
newd = NotebookNode()
for k, v in d.items():
Expand Down
6 changes: 6 additions & 0 deletions nbformat/v1/nbjson.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@


class JSONReader(NotebookReader):
"""A JSON notebook reader."""

def reads(self, s, **kwargs):
"""Convert a string to a notebook object."""
nb = json.loads(s, **kwargs)
return self.to_notebook(nb, **kwargs)

Expand All @@ -37,7 +40,10 @@ def to_notebook(self, d, **kwargs):


class JSONWriter(NotebookWriter):
"""A JSON notebook writer."""

def writes(self, nb, **kwargs):
"""Convert a notebook object to a string."""
kwargs["indent"] = 4
return json.dumps(nb, **kwargs)

Expand Down
4 changes: 4 additions & 0 deletions nbformat/v1/rwbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@


class NotebookReader:
"""The base notebook reader."""

def reads(self, s, **kwargs):
"""Read a notebook from a string."""
raise NotImplementedError("loads must be implemented in a subclass")
Expand All @@ -32,6 +34,8 @@ def read(self, fp, **kwargs):


class NotebookWriter:
"""The base notebook writer."""

def writes(self, nb, **kwargs):
"""Write a notebook to a string."""
raise NotImplementedError("loads must be implemented in a subclass")
Expand Down
3 changes: 3 additions & 0 deletions nbformat/v2/nbbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@


class NotebookNode(Struct):
"""A notebook node object."""

pass


def from_dict(d):
"""Create notebook node(s) from a value."""
if isinstance(d, dict):
newd = NotebookNode()
for k, v in d.items():
Expand Down
Loading