Skip to content

Plugins

Plugin system and built-in plugins for Nitpick.

Base Plugin

base

Base class for file checkers.

NitpickPlugin

NitpickPlugin(
    info: FileInfo, expected_config: JsonDict, autofix=False
)

Base class for Nitpick plugins.

:param data: File information (project, path, tags). :param expected_config: Expected configuration for the file :param autofix: Flag to modify files, if the plugin supports it (default: True).

Source code in src/nitpick/plugins/base.py
def __init__(self, info: FileInfo, expected_config: JsonDict, autofix=False) -> None:
    self.info = info
    self.filename = info.path_from_root
    self.reporter = Reporter(info, self.violation_base_code)

    self.file_path: Path = self.info.project.root / self.filename

    # Configuration for this file as a TOML dict, taken from the style file.
    self.expected_config: JsonDict = expected_config or {}

    self.autofix = self.fixable and autofix
    # Dirty flag to avoid changing files without need
    self.dirty: bool = False

    self._merge_special_configs()

initial_contents abstractmethod property

initial_contents: str

Suggested initial content when the file doesn't exist.

predefined_special_config

predefined_special_config() -> SpecialConfig

Create a predefined special configuration for this plugin.

Each plugin can override this method.

Source code in src/nitpick/plugins/base.py
def predefined_special_config(self) -> SpecialConfig:
    """Create a predefined special configuration for this plugin.

    Each plugin can override this method.
    """
    return SpecialConfig()

nitpick_file_dict

nitpick_file_dict() -> JsonDict

Nitpick configuration for this file as a TOML dict, taken from the style file.

Source code in src/nitpick/plugins/base.py
@MypyProperty
def nitpick_file_dict(self) -> JsonDict:
    """Nitpick configuration for this file as a TOML dict, taken from the style file."""
    return search_json(self.info.project.nitpick_section, f'files."{self.filename}"', {})

entry_point

entry_point() -> Iterator[Fuss]

Entry point of the Nitpick plugin.

Source code in src/nitpick/plugins/base.py
def entry_point(self) -> Iterator[Fuss]:
    """Entry point of the Nitpick plugin."""
    self.post_init()

    should_exist: bool = bool(self.info.project.nitpick_files_section.get(self.filename, True))
    if self.file_path.exists() and not should_exist:
        logger.info(f"{self}: File {self.filename} exists when it should not")
        # Only display this message if the style is valid.
        yield self.reporter.make_fuss(SharedViolations.DELETE_FILE)
        return

    has_config_dict = bool(self.expected_config or self.nitpick_file_dict)
    if not has_config_dict:
        return

    yield from self._enforce_file_configuration()

post_init

post_init()

Hook for plugin initialization after the instance was created.

The name mimics __post_init__() on dataclasses, without the magic double underscores. See the dataclasses documentation for more details.

Source code in src/nitpick/plugins/base.py
def post_init(self):  # noqa: B027
    """Hook for plugin initialization after the instance was created.

    The name mimics `__post_init__()` on dataclasses, without the magic double underscores.
    See the [dataclasses documentation](https://docs.python.org/3/library/dataclasses.html#post-init-processing) for more details.
    """

write_file

write_file(file_exists: bool) -> Fuss | None

Hook to write the new file when autofix mode is on.

Should be used by inherited classes.

Source code in src/nitpick/plugins/base.py
def write_file(
    self,
    file_exists: bool,  # pylint: disable=unused-argument # noqa: ARG002
) -> Fuss | None:
    """Hook to write the new file when autofix mode is on.

    Should be used by inherited classes.
    """
    return None

enforce_rules abstractmethod

enforce_rules() -> Iterator[Fuss]

Enforce rules for this file. It must be overridden by inherited classes if needed.

Source code in src/nitpick/plugins/base.py
@abc.abstractmethod
def enforce_rules(self) -> Iterator[Fuss]:
    """Enforce rules for this file. It must be overridden by inherited classes if needed."""

write_initial_contents

write_initial_contents(
    doc_class: type[BaseDoc],
    expected_dict: dict | None = None,
) -> str

Helper to write initial contents based on a format.

Source code in src/nitpick/plugins/base.py
def write_initial_contents(self, doc_class: type[BaseDoc], expected_dict: dict | None = None) -> str:
    """Helper to write initial contents based on a format."""
    if not expected_dict:
        expected_dict = self.expected_config

    formatted_str = doc_class(obj=expected_dict).reformatted
    if self.autofix:
        self.file_path.parent.mkdir(exist_ok=True, parents=True)
        self.file_path.write_text(formatted_str)
    return formatted_str

options: show_root_heading: true show_source: true members_order: source heading_level: 3

Built-in Plugins

INI Files

ini

INI files.

Violations

Violations(code: int, message: str = '', add_to_base=False)

Bases: ViolationEnum

Violations for this plugin.

Source code in src/nitpick/violations.py
def __init__(self, code: int, message: str = "", add_to_base=False) -> None:
    self.code = code
    self.message = message
    self.add_code = add_to_base

IniPlugin

IniPlugin(
    info: FileInfo, expected_config: JsonDict, autofix=False
)

Bases: NitpickPlugin

Enforce configurations and autofix INI files.

Examples of .ini files handled by this plugin:

Style examples enforcing values on INI files: flake8 configuration.

Source code in src/nitpick/plugins/base.py
def __init__(self, info: FileInfo, expected_config: JsonDict, autofix=False) -> None:
    self.info = info
    self.filename = info.path_from_root
    self.reporter = Reporter(info, self.violation_base_code)

    self.file_path: Path = self.info.project.root / self.filename

    # Configuration for this file as a TOML dict, taken from the style file.
    self.expected_config: JsonDict = expected_config or {}

    self.autofix = self.fixable and autofix
    # Dirty flag to avoid changing files without need
    self.dirty: bool = False

    self._merge_special_configs()

needs_top_section property

needs_top_section: bool

Return True if this .ini file needs a top section (e.g.: .editorconfig).

current_sections property

current_sections: set[str]

Current sections of the .ini file, including updated sections.

initial_contents property

initial_contents: str

Suggest the initial content for this missing file.

expected_sections property

expected_sections: set[str]

Expected sections (from the style config).

missing_sections property

missing_sections: set[str]

Missing sections.

post_init

post_init()

Post initialization after the instance was created.

Source code in src/nitpick/plugins/ini.py
def post_init(self):
    """Post initialization after the instance was created."""
    self.updater = ConfigUpdater()
    self.comma_separated_values = set(self.nitpick_file_dict.get(COMMA_SEPARATED_VALUES, []))

    if not self.needs_top_section:
        return
    if all(isinstance(v, dict) for v in self.expected_config.values()):
        return

    new_config = {TOP_SECTION: {}}
    for key, value in self.expected_config.items():
        if isinstance(value, dict):
            new_config[key] = value
            continue
        new_config[TOP_SECTION][key] = value
    self.expected_config = new_config

write_file

write_file(file_exists: bool) -> Fuss | None

Write the new file.

Source code in src/nitpick/plugins/ini.py
def write_file(self, file_exists: bool) -> Fuss | None:
    """Write the new file."""
    try:
        if self.needs_top_section:
            self.file_path.write_text(self.contents_without_top_section(str(self.updater)))
            return None

        if file_exists:
            self.updater.update_file()
        else:
            self.updater.write(self.file_path.open("w"))
    except ParsingError as err:
        return self.reporter.make_fuss(Violations.PARSING_ERROR, cls=err.__class__.__name__, msg=err)
    return None

contents_without_top_section staticmethod

contents_without_top_section(multiline_text: str) -> str

Remove the temporary top section from multiline text, and keep the newline at the end of the file.

Source code in src/nitpick/plugins/ini.py
@staticmethod
def contents_without_top_section(multiline_text: str) -> str:
    """Remove the temporary top section from multiline text, and keep the newline at the end of the file."""
    return "\n".join(line for line in multiline_text.splitlines() if TOP_SECTION not in line) + "\n"

get_missing_output

get_missing_output() -> str

Get a missing output string example from the missing sections in an INI file.

Source code in src/nitpick/plugins/ini.py
def get_missing_output(self) -> str:
    """Get a missing output string example from the missing sections in an INI file."""
    missing = self.missing_sections
    if not missing:
        return ""

    parser = ConfigParser()
    for section in sorted(missing, key=lambda s: "0" if s == TOP_SECTION else f"1{s}"):
        expected_config: dict = self.expected_config[section]
        if self.autofix:
            if self.updater.last_block:
                self.updater.last_block.add_after.space(1)
            self.updater.add_section(section)
            self.updater[section].update(expected_config)
            self.dirty = True
        parser[section] = expected_config
    return self.contents_without_top_section(self.get_example_cfg(parser))

enforce_rules

enforce_rules() -> Iterator[Fuss]

Enforce rules on missing sections and missing key/value pairs in an INI file.

Source code in src/nitpick/plugins/ini.py
def enforce_rules(self) -> Iterator[Fuss]:
    """Enforce rules on missing sections and missing key/value pairs in an INI file."""
    try:
        yield from self._read_file()
    except Error:
        return

    yield from self.enforce_missing_sections()

    csv_sections = {v.split(SECTION_SEPARATOR)[0] for v in self.comma_separated_values}
    missing_csv = csv_sections.difference(self.current_sections)
    if missing_csv:
        yield self.reporter.make_fuss(
            Violations.INVALID_COMMA_SEPARATED_VALUES_SECTION, ", ".join(sorted(missing_csv))
        )
        # Don't continue if the comma-separated values are invalid
        return

    for section in self.expected_sections.intersection(self.current_sections) - self.missing_sections:
        yield from self.enforce_section(section)

enforce_missing_sections

enforce_missing_sections() -> Iterator[Fuss]

Enforce missing sections.

Source code in src/nitpick/plugins/ini.py
def enforce_missing_sections(self) -> Iterator[Fuss]:
    """Enforce missing sections."""
    missing = self.get_missing_output()
    if missing:
        yield self.reporter.make_fuss(Violations.MISSING_SECTIONS, missing, self.autofix)

enforce_section

enforce_section(section: str) -> Iterator[Fuss]

Enforce rules for a section.

Source code in src/nitpick/plugins/ini.py
def enforce_section(self, section: str) -> Iterator[Fuss]:
    """Enforce rules for a section."""
    expected_dict = self.expected_config[section]
    actual_dict = {k: v.value for k, v in self.updater[section].items()}
    # TODO: refactor: add a class Ini(BaseDoc) and move this dictdiffer code there
    for diff_type, key, values in dictdiffer.diff(actual_dict, expected_dict):
        if diff_type == dictdiffer.CHANGE:
            if f"{section}.{key}" in self.comma_separated_values:
                yield from self.enforce_comma_separated_values(section, key, values[0], values[1])
            else:
                yield from self.compare_different_keys(section, key, values[0], values[1])
        elif diff_type == dictdiffer.ADD:
            yield from self.show_missing_keys(section, values)

enforce_comma_separated_values

enforce_comma_separated_values(
    section, key, raw_actual: Any, raw_expected: Any
) -> Iterator[Fuss]

Enforce sections and keys with comma-separated values.

The values might contain spaces.

Source code in src/nitpick/plugins/ini.py
def enforce_comma_separated_values(self, section, key, raw_actual: Any, raw_expected: Any) -> Iterator[Fuss]:
    """Enforce sections and keys with comma-separated values.

    The values might contain spaces.
    """
    actual_set = {s.strip() for s in raw_actual.split(",")}
    expected_set = {s.strip() for s in raw_expected.split(",")}
    missing = expected_set - actual_set
    if not missing:
        return

    joined_values = ",".join(sorted(missing))
    value_to_append = f",{joined_values}"
    if self.autofix:
        self.updater[section][key].value += value_to_append
        self.dirty = True
    section_header = "" if section == TOP_SECTION else f"[{section}]\n"
    # TODO: test: top section with separated values in https://github.com/andreoliwa/nitpick/issues/271
    yield self.reporter.make_fuss(
        Violations.MISSING_VALUES_IN_LIST,
        f"{section_header}{key} = (...){value_to_append}",
        key=key,
        fixed=self.autofix,
    )

compare_different_keys

compare_different_keys(
    section, key, raw_actual: Any, raw_expected: Any
) -> Iterator[Fuss]

Compare different keys, with special treatment when they are lists or numeric.

Source code in src/nitpick/plugins/ini.py
def compare_different_keys(self, section, key, raw_actual: Any, raw_expected: Any) -> Iterator[Fuss]:
    """Compare different keys, with special treatment when they are lists or numeric."""
    if isinstance(raw_actual, (int, float, bool)) or isinstance(raw_expected, (int, float, bool)):
        # A boolean "True" or "true" has the same effect on ConfigParser files.
        actual = str(raw_actual).lower()
        expected = str(raw_expected).lower()
    else:
        actual = raw_actual
        expected = raw_expected
    if actual == expected:
        return

    if self.autofix:
        self.updater[section][key].value = expected
        self.dirty = True
    if section == TOP_SECTION:
        yield self.reporter.make_fuss(
            Violations.TOP_SECTION_HAS_DIFFERENT_VALUE,
            f"{key} = {raw_expected}",
            key=key,
            actual=raw_actual,
            fixed=self.autofix,
        )
    else:
        yield self.reporter.make_fuss(
            Violations.OPTION_HAS_DIFFERENT_VALUE,
            f"[{section}]\n{key} = {raw_expected}",
            section=section,
            key=key,
            actual=raw_actual,
            fixed=self.autofix,
        )

show_missing_keys

show_missing_keys(
    section: str, values: list[tuple[str, Any]]
) -> Iterator[Fuss]

Show the keys that are not present in a section.

Source code in src/nitpick/plugins/ini.py
def show_missing_keys(self, section: str, values: list[tuple[str, Any]]) -> Iterator[Fuss]:
    """Show the keys that are not present in a section."""
    parser = ConfigParser()
    missing_dict = dict(values)
    parser[section] = missing_dict
    output = self.get_example_cfg(parser)
    self.add_options_before_space(section, missing_dict)

    if section == TOP_SECTION:
        yield self.reporter.make_fuss(
            Violations.TOP_SECTION_MISSING_OPTION, self.contents_without_top_section(output), self.autofix
        )
    else:
        yield self.reporter.make_fuss(Violations.MISSING_OPTION, output, self.autofix, section=section)

add_options_before_space

add_options_before_space(
    section: str, options: dict
) -> None

Add new options before a blank line in the end of the section.

Source code in src/nitpick/plugins/ini.py
def add_options_before_space(self, section: str, options: dict) -> None:
    """Add new options before a blank line in the end of the section."""
    if not self.autofix:
        return

    section_obj = self.updater[section]

    # Collect all trailing Space blocks
    # We need to collect them first before detaching to avoid NotAttachedError
    # when there are multiple consecutive spaces
    trailing_spaces = []
    for block in reversed(list(section_obj.iter_blocks())):
        if isinstance(block, Space):
            trailing_spaces.append(block)
        else:
            break  # Stop at the first non-Space block

    # Detach all trailing spaces
    space_removed = len(trailing_spaces) > 0
    for space in trailing_spaces:
        space.detach()

    section_obj.update(options)
    self.dirty = True

    # Add back a single space if we removed any
    if space_removed:
        section_obj.last_block.add_after.space(1)

get_example_cfg staticmethod

get_example_cfg(parser: ConfigParser) -> str

Print an example of a config parser in a string instead of a file.

Source code in src/nitpick/plugins/ini.py
@staticmethod
def get_example_cfg(parser: ConfigParser) -> str:
    """Print an example of a config parser in a string instead of a file."""
    string_stream = StringIO()
    parser.write(string_stream)
    return string_stream.getvalue().strip()

plugin_class

plugin_class() -> type[NitpickPlugin]

Handle INI files.

Source code in src/nitpick/plugins/ini.py
@hookimpl
def plugin_class() -> type[NitpickPlugin]:
    """Handle INI files."""
    return IniPlugin

can_handle

can_handle(info: FileInfo) -> type[NitpickPlugin] | None

Handle INI files.

Source code in src/nitpick/plugins/ini.py
@hookimpl
def can_handle(info: FileInfo) -> type[NitpickPlugin] | None:
    """Handle INI files."""
    if IniPlugin.identify_tags & info.tags:
        return IniPlugin
    return None

options: show_root_heading: true show_source: true members_order: source heading_level: 4

JSON Files

json

JSON files.

JsonFileSchema

Bases: BaseNitpickSchema

Validation schema for any JSON file added to the style.

JsonPlugin

JsonPlugin(
    info: FileInfo, expected_config: JsonDict, autofix=False
)

Bases: NitpickPlugin

Enforce configurations and autofix JSON files.

Add the configurations for the file name you wish to check. Style example: the default config for package.json.

Source code in src/nitpick/plugins/base.py
def __init__(self, info: FileInfo, expected_config: JsonDict, autofix=False) -> None:
    self.info = info
    self.filename = info.path_from_root
    self.reporter = Reporter(info, self.violation_base_code)

    self.file_path: Path = self.info.project.root / self.filename

    # Configuration for this file as a TOML dict, taken from the style file.
    self.expected_config: JsonDict = expected_config or {}

    self.autofix = self.fixable and autofix
    # Dirty flag to avoid changing files without need
    self.dirty: bool = False

    self._merge_special_configs()

initial_contents property

initial_contents: str

Suggest the initial content for this missing file.

enforce_rules

enforce_rules() -> Iterator[Fuss]

Enforce rules for missing keys and JSON content.

Source code in src/nitpick/plugins/json.py
def enforce_rules(self) -> Iterator[Fuss]:
    """Enforce rules for missing keys and JSON content."""
    json_doc = JsonDoc(path=self.file_path)
    blender: JsonDict = json_doc.as_object.copy() if self.autofix else {}

    comparison = Comparison(json_doc, self.expected_dict_from_contains_keys(), self.special_config)()
    if comparison.missing:
        yield from self.report(SharedViolations.MISSING_VALUES, blender, comparison.missing)

    comparison = Comparison(json_doc, self.expected_dict_from_contains_json(), self.special_config)()
    if comparison.has_changes:
        yield from chain(
            self.report(SharedViolations.DIFFERENT_VALUES, blender, comparison.diff),
            self.report(SharedViolations.MISSING_VALUES, blender, comparison.missing),
        )

    if self.autofix and self.dirty and blender:
        self.file_path.write_text(JsonDoc(obj=unflatten_quotes(blender)).reformatted)

expected_dict_from_contains_keys

expected_dict_from_contains_keys()

Expected dict created from "contains_keys" values.

Source code in src/nitpick/plugins/json.py
def expected_dict_from_contains_keys(self):
    """Expected dict created from "contains_keys" values."""
    return unflatten_quotes(
        dict.fromkeys(set(self.expected_config.get(KEY_CONTAINS_KEYS) or []), VALUE_PLACEHOLDER)
    )

expected_dict_from_contains_json

expected_dict_from_contains_json()

Expected dict created from "contains_json" values.

Source code in src/nitpick/plugins/json.py
def expected_dict_from_contains_json(self):
    """Expected dict created from "contains_json" values."""
    expected_config = {}
    # TODO: feat: accept key as a jmespath expression, value is valid JSON
    for key, json_string in (self.expected_config.get(KEY_CONTAINS_JSON) or {}).items():
        try:
            expected_config[key] = json.loads(json_string)
        except json.JSONDecodeError as err:  # noqa: PERF203
            # This should not happen, because the style was already validated before.
            # Maybe the NIP??? code was disabled by the user?
            logger.error(f"{err} on {KEY_CONTAINS_JSON} while checking {self.file_path}")
            continue
    return expected_config

report

report(
    violation: ViolationEnum,
    blender: JsonDict,
    change: BaseDoc | None,
)

Report a violation while optionally modifying the JSON dict.

Source code in src/nitpick/plugins/json.py
def report(self, violation: ViolationEnum, blender: JsonDict, change: BaseDoc | None):
    """Report a violation while optionally modifying the JSON dict."""
    if not change:
        return
    if blender:
        blender.update(flatten_quotes(change.as_object))
        self.dirty = True
    yield self.reporter.make_fuss(violation, change.reformatted, prefix="", fixed=self.autofix)

plugin_class

plugin_class() -> type[NitpickPlugin]

Handle JSON files.

Source code in src/nitpick/plugins/json.py
@hookimpl
def plugin_class() -> type[NitpickPlugin]:
    """Handle JSON files."""
    return JsonPlugin

can_handle

can_handle(info: FileInfo) -> type[NitpickPlugin] | None

Handle JSON files.

Source code in src/nitpick/plugins/json.py
@hookimpl
def can_handle(info: FileInfo) -> type[NitpickPlugin] | None:
    """Handle JSON files."""
    if JsonPlugin.identify_tags & info.tags:
        return JsonPlugin
    return None

options: show_root_heading: true show_source: true members_order: source heading_level: 4

TOML Files

toml

TOML files.

TomlPlugin

TomlPlugin(
    info: FileInfo, expected_config: JsonDict, autofix=False
)

Bases: NitpickPlugin

Enforce configurations and autofix TOML files.

E.g.: pyproject.toml (PEP 518).

See also the [tool.poetry] section of the pyproject.toml file.

Style example: Python 3.10 version constraint. There are many other examples in the library.

Source code in src/nitpick/plugins/base.py
def __init__(self, info: FileInfo, expected_config: JsonDict, autofix=False) -> None:
    self.info = info
    self.filename = info.path_from_root
    self.reporter = Reporter(info, self.violation_base_code)

    self.file_path: Path = self.info.project.root / self.filename

    # Configuration for this file as a TOML dict, taken from the style file.
    self.expected_config: JsonDict = expected_config or {}

    self.autofix = self.fixable and autofix
    # Dirty flag to avoid changing files without need
    self.dirty: bool = False

    self._merge_special_configs()

initial_contents property

initial_contents: str

Suggest the initial content for this missing file.

enforce_rules

enforce_rules() -> Iterator[Fuss]

Enforce rules for missing key/value pairs in the TOML file.

Source code in src/nitpick/plugins/toml.py
def enforce_rules(self) -> Iterator[Fuss]:
    """Enforce rules for missing key/value pairs in the TOML file."""
    toml_doc = TomlDoc(path=self.file_path)
    comparison = Comparison(toml_doc, self.expected_config, self.special_config)()
    if not comparison.has_changes:
        return

    document = parse(toml_doc.as_string) if self.autofix else None
    yield from chain(
        self.report(SharedViolations.DIFFERENT_VALUES, document, cast("TomlDoc", comparison.diff)),
        self.report(
            SharedViolations.MISSING_VALUES,
            document,
            cast("TomlDoc", comparison.missing),
            cast("TomlDoc", comparison.replace),
        ),
    )
    if self.autofix and self.dirty:
        self.file_path.write_text(dumps(document))

report

report(
    violation: ViolationEnum,
    document: TOMLDocument | None,
    change: TomlDoc | None,
    replacement: TomlDoc | None = None,
)

Report a violation while optionally modifying the TOML document.

Source code in src/nitpick/plugins/toml.py
def report(
    self,
    violation: ViolationEnum,
    document: TOMLDocument | None,
    change: TomlDoc | None,
    replacement: TomlDoc | None = None,
):
    """Report a violation while optionally modifying the TOML document."""
    if not (change or replacement):
        return
    if self.autofix:
        real_change = cast("TomlDoc", replacement or change)
        traverse_toml_tree(document, real_change.as_object)
        self.dirty = True

    to_display = cast("TomlDoc", change or replacement)
    yield self.reporter.make_fuss(violation, to_display.reformatted.strip(), prefix="", fixed=self.autofix)

plugin_class

plugin_class() -> type[NitpickPlugin]

Handle TOML files.

Source code in src/nitpick/plugins/toml.py
@hookimpl
def plugin_class() -> type[NitpickPlugin]:
    """Handle TOML files."""
    return TomlPlugin

can_handle

can_handle(info: FileInfo) -> type[NitpickPlugin] | None

Handle TOML files.

Source code in src/nitpick/plugins/toml.py
@hookimpl
def can_handle(info: FileInfo) -> type[NitpickPlugin] | None:
    """Handle TOML files."""
    if TomlPlugin.identify_tags & info.tags:
        return TomlPlugin
    return None

options: show_root_heading: true show_source: true members_order: source heading_level: 4

YAML Files

yaml

YAML files.

YamlPlugin

YamlPlugin(
    info: FileInfo, expected_config: JsonDict, autofix=False
)

Bases: NitpickPlugin

Enforce configurations and autofix YAML files.

Warning

The plugin tries to preserve comments in the YAML file by using the ruamel.yaml package. It works for most cases. If your comment was removed, place them in a different place of the fil and try again. If it still doesn't work, please report a bug.

Known issue: lists like args and additional_dependencies might be joined in a single line, and comments between items will be removed. Move your comments outside these lists, and they should be preserved.

Note

No validation of .pre-commit-config.yaml will be done anymore in this generic YAML plugin. Nitpick will not validate hooks and missing keys as it did before; it's not the purpose of this package.

Source code in src/nitpick/plugins/base.py
def __init__(self, info: FileInfo, expected_config: JsonDict, autofix=False) -> None:
    self.info = info
    self.filename = info.path_from_root
    self.reporter = Reporter(info, self.violation_base_code)

    self.file_path: Path = self.info.project.root / self.filename

    # Configuration for this file as a TOML dict, taken from the style file.
    self.expected_config: JsonDict = expected_config or {}

    self.autofix = self.fixable and autofix
    # Dirty flag to avoid changing files without need
    self.dirty: bool = False

    self._merge_special_configs()

initial_contents property

initial_contents: str

Suggest the initial content for this missing file.

predefined_special_config

predefined_special_config() -> SpecialConfig

Predefined special config, with list keys for .pre-commit-config.yaml and GitHub Workflow files.

Source code in src/nitpick/plugins/yaml.py
def predefined_special_config(self) -> SpecialConfig:
    """Predefined special config, with list keys for .pre-commit-config.yaml and GitHub Workflow files."""
    spc = SpecialConfig()
    # pylint: disable=assigning-non-slot
    if self.filename == PRE_COMMIT_CONFIG_YAML:
        spc.list_keys.from_plugin = {"repos": "hooks.id"}
    elif self.filename.startswith(".github/workflows"):
        spc.list_keys.from_plugin = {"jobs.*.steps": "name"}
    return spc

enforce_rules

enforce_rules() -> Iterator[Fuss]

Enforce rules for missing data in the YAML file.

Source code in src/nitpick/plugins/yaml.py
def enforce_rules(self) -> Iterator[Fuss]:
    """Enforce rules for missing data in the YAML file."""
    if KEY_CONTAINS in self.expected_config:
        # If the expected configuration has this key, it means that this config is being handled by TextPlugin.
        # TODO: fix: allow a YAML file with a "contains" key on its root (how?)
        return

    yaml_doc = YamlDoc(path=self.file_path)
    comparison = Comparison(yaml_doc, self._remove_yaml_subkey(self.expected_config), self.special_config)()
    if not comparison.has_changes:
        return

    yield from chain(
        self.report(SharedViolations.DIFFERENT_VALUES, yaml_doc.as_object, cast("YamlDoc", comparison.diff)),
        self.report(
            SharedViolations.MISSING_VALUES,
            yaml_doc.as_object,
            cast("YamlDoc", comparison.missing),
            cast("YamlDoc", comparison.replace),
        ),
    )
    if self.autofix and self.dirty:
        yaml_doc.updater.dump(yaml_doc.as_object, self.file_path)

report

report(
    violation: ViolationEnum,
    yaml_object: YamlObject,
    change: YamlDoc | None,
    replacement: YamlDoc | None = None,
)

Report a violation while optionally modifying the YAML document.

Source code in src/nitpick/plugins/yaml.py
def report(
    self,
    violation: ViolationEnum,
    yaml_object: YamlObject,
    change: YamlDoc | None,
    replacement: YamlDoc | None = None,
):
    """Report a violation while optionally modifying the YAML document."""
    if not (change or replacement):
        return
    if self.autofix:
        real_change = cast("YamlDoc", replacement or change)
        traverse_yaml_tree(yaml_object, real_change.as_object)
        self.dirty = True

    to_display = cast("YamlDoc", change or replacement)
    yield self.reporter.make_fuss(violation, to_display.reformatted.strip(), prefix="", fixed=self.autofix)

plugin_class

plugin_class() -> type[NitpickPlugin]

Handle YAML files.

Source code in src/nitpick/plugins/yaml.py
@hookimpl
def plugin_class() -> type[NitpickPlugin]:
    """Handle YAML files."""
    return YamlPlugin

can_handle

can_handle(info: FileInfo) -> type[NitpickPlugin] | None

Handle YAML files.

Source code in src/nitpick/plugins/yaml.py
@hookimpl
def can_handle(info: FileInfo) -> type[NitpickPlugin] | None:
    """Handle YAML files."""
    if YamlPlugin.identify_tags & info.tags:
        return YamlPlugin
    return None

options: show_root_heading: true show_source: true members_order: source heading_level: 4

Text Files

text

Text files.

TextItemSchema

Bases: Schema

Validation schema for the object inside contains.

TextSchema

Bases: Schema

Validation schema for the text file TOML configuration.

Violations

Violations(code: int, message: str = '', add_to_base=False)

Bases: ViolationEnum

Violations for this plugin.

Source code in src/nitpick/violations.py
def __init__(self, code: int, message: str = "", add_to_base=False) -> None:
    self.code = code
    self.message = message
    self.add_code = add_to_base

TextPlugin

TextPlugin(
    info: FileInfo, expected_config: JsonDict, autofix=False
)

Bases: NitpickPlugin

Enforce configuration on text files.

To check if some.txt file contains the lines abc and def (in any order):

[["some.txt".contains]]
line = "abc"

[["some.txt".contains]]
line = "def"
Source code in src/nitpick/plugins/base.py
def __init__(self, info: FileInfo, expected_config: JsonDict, autofix=False) -> None:
    self.info = info
    self.filename = info.path_from_root
    self.reporter = Reporter(info, self.violation_base_code)

    self.file_path: Path = self.info.project.root / self.filename

    # Configuration for this file as a TOML dict, taken from the style file.
    self.expected_config: JsonDict = expected_config or {}

    self.autofix = self.fixable and autofix
    # Dirty flag to avoid changing files without need
    self.dirty: bool = False

    self._merge_special_configs()

initial_contents property

initial_contents: str

Suggest the initial content for this missing file.

enforce_rules

enforce_rules() -> Iterator[Fuss]

Enforce rules for missing lines.

Source code in src/nitpick/plugins/text.py
def enforce_rules(self) -> Iterator[Fuss]:
    """Enforce rules for missing lines."""
    expected = OrderedSet(self._expected_lines())
    actual = OrderedSet(self.file_path.read_text().split("\n"))
    missing = expected - actual
    if missing:
        yield self.reporter.make_fuss(Violations.MISSING_LINES, "\n".join(sorted(missing)))

plugin_class

plugin_class() -> type[NitpickPlugin]

Handle text files.

Source code in src/nitpick/plugins/text.py
@hookimpl
def plugin_class() -> type[NitpickPlugin]:
    """Handle text files."""
    return TextPlugin

can_handle

can_handle(info: FileInfo) -> type[NitpickPlugin] | None

Handle text files.

Source code in src/nitpick/plugins/text.py
@hookimpl
def can_handle(info: FileInfo) -> type[NitpickPlugin] | None:
    """Handle text files."""
    if TextPlugin.identify_tags & info.tags:
        return TextPlugin
    return None

options: show_root_heading: true show_source: true members_order: source heading_level: 4