diff --git a/CHANGES.md b/CHANGES.md index 7eb06e0..7a94c4e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # XRLint Change History +## Version 0.2.0 (in development) + +- Make all docstrings comply to google-style + ## Version 0.1.0 (09.01.2025) - Added CLI option `--print-config PATH`, see same option in ESLint diff --git a/README.md b/README.md index bd9d528..ea0834f 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ XRLint is a [linting](https://en.wikipedia.org/wiki/Lint_(software)) tool and library for [xarray]() datasets. -Its design is heavily inspired by [ESLint](https://eslint.org/). +Its design is heavily inspired by the awesome [ESLint](https://eslint.org/). ## Features @@ -23,13 +23,13 @@ Its design is heavily inspired by [ESLint](https://eslint.org/). ## Inbuilt Rules -The following rule plugins are currently built into the code base: +The following plugins provide XRLint's [inbuilt rules](https://bcdev.github.io/xrlint/rule-ref/): -- `xrlint.plugins.core`: Implementing the rules for +- `xrlint.plugins.core`: implementing the rules for [tidy data](https://tutorial.xarray.dev/intermediate/data_cleaning/05.1_intro.html) and the [CF-Conventions](https://cfconventions.org/cf-conventions/cf-conventions.html). -- `xrlint.plugins.core`: Implementing the rules for +- `xrlint.plugins.core`: implementing the rules for [xcube datasets](https://xcube.readthedocs.io/en/latest/cubespec.html). Note, this plugin is fully optional. You must manually configure it to apply its rules. It may be moved into a separate GitHub repo diff --git a/docs/api.md b/docs/api.md index 52b60d9..c1204c0 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,84 +1,95 @@ # Python API -All described objects can be imported from the `xrlint.all` module. - -## Class `XRLint` - +This chapter provides a plain reference for the XRLint Python API. + +## Overview + +- The top-level API component is the class [XRLint][xrlint.cli.engine.XRLint] + which encapsulates the functionality of the [XRLint CLI](cli.md). +- The `linter` module provides the functionality for linting a single + dataset: + [new_linter()][xrlint.linter.new_linter] factory function and the + [Linter][xrlint.linter.Linter] class. +- The `plugin` module provides plugin related classes: + [Plugin][xrlint.plugin.Plugin] and its + metadata [PluginMeta][xrlint.plugin.PluginMeta]. +- The `config` module provides classes that represent + configuration information and provide related functionality: + [Config][xrlint.config.Config] and [ConfigList][xrlint.config.ConfigList]. +- The `rule` module provides rule related classes: + [Rule][xrlint.rule.Rule] comprising rule metadata + [RuleMeta][xrlint.rule.RuleMeta] and the rule operation + [RuleOp][xrlint.rule.RuleOp], as well as related to the latter + [RuleContext][xrlint.rule.RuleContext] and [RuleExit][xrlint.rule.RuleExit]. +- The `node` module defines the nodes passed to [xrlint.rule.RuleOp]: + base classes [None][xrlint.node.Node], [XarrayNode][xrlint.node.XarrayNode] + and the specific [DatasetNode][xrlint.node.DatasetNode], + [DataArray][xrlint.node.DataArrayNode], [AttrsNode][xrlint.node.AttrsNode], + and [AttrNode][xrlint.node.AttrNode] nodes. +- The `processor` module provides processor related classes: + [Processor][xrlint.processor.Processor] comprising processor metadata + [ProcessorMeta][xrlint.processor.ProcessorMeta] + and the processor operation [ProcessorOp][xrlint.processor.ProcessorOp]. +- The `result` module provides data classes that are used to + represent validation results: + [Result][xrlint.result.Result] composed of [Messages][xrlint.result.Message], + which again may contain [Suggestions][xrlint.result.Suggestion]. +- Finally, the `testing` module provides classes for rule testing: + [RuleTester][xrlint.testing.RuleTester] that is made up + of [RuleTest][xrlint.testing.RuleTest]s. + +Note: the `xrlint.all` convenience module exports all of the above from a + single module. + ::: xrlint.cli.engine.XRLint -## Function `new_linter()` - ::: xrlint.linter.new_linter -## Class `Linter` - ::: xrlint.linter.Linter -## Class `Config` - ::: xrlint.config.Config -## Class `ConfigList` - ::: xrlint.config.ConfigList -## Class `Plugin` - -::: xrlint.plugin.Plugin - -## Class `PluginMeta` - -::: xrlint.plugin.PluginMeta +::: xrlint.rule.Rule -## Class `Processor` +::: xrlint.rule.RuleMeta -::: xrlint.processor.Processor +::: xrlint.rule.RuleOp -## Class `ProcessorMeta` +::: xrlint.rule.RuleContext -::: xrlint.processor.ProcessorMeta +::: xrlint.rule.RuleExit -## Class `ProcessorOp` +::: xrlint.node.Node -::: xrlint.processor.ProcessorOp +::: xrlint.node.XarrayNode -## Class `RuleConfig` +::: xrlint.node.DatasetNode -::: xrlint.rule.RuleConfig +::: xrlint.node.DataArrayNode -## Class `Rule` +::: xrlint.node.AttrsNode -::: xrlint.rule.Rule +::: xrlint.node.AttrNode -## Class `RuleMeta` +::: xrlint.plugin.Plugin -::: xrlint.rule.RuleMeta +::: xrlint.plugin.PluginMeta -## Class `RuleOp` +::: xrlint.processor.Processor -::: xrlint.rule.RuleOp +::: xrlint.processor.ProcessorMeta + +::: xrlint.processor.ProcessorOp -## Class `RuleContext` +::: xrlint.result.Result -::: xrlint.rule.RuleContext +::: xrlint.result.Message -## Class `RuleTester` +::: xrlint.result.Suggestion ::: xrlint.testing.RuleTester -## Class `RuleTest` - ::: xrlint.testing.RuleTest -## Class `Result` - -::: xrlint.result.Result - -## Class `Message` - -::: xrlint.result.Message - -## Class `Suggestion` - -::: xrlint.result.Suggestion - diff --git a/docs/index.md b/docs/index.md index 27e155b..65d3f5a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -3,7 +3,7 @@ XRLint is a [linting](https://en.wikipedia.org/wiki/Lint_(software)) tool and library for [xarray]() datasets. -Its design is heavily inspired by [ESLint](https://eslint.org/). +Its design is heavily inspired by the awesome [ESLint](https://eslint.org/). ## Features @@ -18,13 +18,13 @@ Its design is heavily inspired by [ESLint](https://eslint.org/). ## Inbuilt Rules -The following rule plugins are currently built into the code base: +The following plugins provide XRLint's [inbuilt rules](rule-ref.md): -- `core`: Implementing the rules for +- `core`: implementing the rules for [tidy data](https://tutorial.xarray.dev/intermediate/data_cleaning/05.1_intro.html) and the [CF-Conventions](https://cfconventions.org/cf-conventions/cf-conventions.html). -- `xcube`: Implementing the rules for +- `xcube`: implementing the rules for [xcube datasets](https://xcube.readthedocs.io/en/latest/cubespec.html). Note, this plugin is fully optional. You must manually configure it to apply its rules. It may be moved into a separate GitHub repo diff --git a/environment.yml b/environment.yml index ba816c9..faf822f 100644 --- a/environment.yml +++ b/environment.yml @@ -13,6 +13,7 @@ dependencies: - black - flake8 - mkdocs + - mkdocs-autorefs - mkdocs-material - mkdocstrings - mkdocstrings-python diff --git a/mkdocs.yml b/mkdocs.yml index dd4c687..70166cc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -5,58 +5,59 @@ repo_name: bcdev/xrlint copyright: Copyright © 2025 Brockmann Consult nav: - - Overview: index.md - - Getting Started: start.md - - Configuration: config.md - - Rule Reference: rule-ref.md - - CLI: cli.md - - Python API: api.md - - About: about.md + - Overview: index.md + - Getting Started: start.md + - Configuration: config.md + - Rule Reference: rule-ref.md + - CLI: cli.md + - Python API: api.md + - About: about.md theme: - name: material - # logo: assets/logo.png - # favicon: assets/logo-small.png - palette: - # Palette toggle for light mode - - scheme: default - primary: blue grey - toggle: - icon: material/brightness-7 - name: Switch to dark mode - # Palette toggle for dark mode - - scheme: slate - primary: blue grey - toggle: - icon: material/brightness-4 - name: Switch to light mode + name: material + # logo: assets/logo.png + # favicon: assets/logo-small.png + palette: + # Palette toggle for light mode + - scheme: default + primary: blue grey + toggle: + icon: material/brightness-7 + name: Switch to dark mode + # Palette toggle for dark mode + - scheme: slate + primary: blue grey + toggle: + icon: material/brightness-4 + name: Switch to light mode markdown_extensions: - - attr_list - - admonition - - pymdownx.details - - pymdownx.superfences - - pymdownx.emoji: - emoji_index: !!python/name:material.extensions.emoji.twemoji - emoji_generator: !!python/name:material.extensions.emoji.to_svg + - attr_list + - admonition + - pymdownx.details + - pymdownx.superfences + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg extra: - social: - - icon: fontawesome/brands/github - link: https://github.com/bcdev/xrlint - - icon: fontawesome/brands/python - link: https://pypi.org/project/xrlint/ + social: + - icon: fontawesome/brands/github + link: https://github.com/bcdev/xrlint + - icon: fontawesome/brands/python + link: https://pypi.org/project/xrlint/ plugins: - - search - - autorefs - - mkdocstrings: - handlers: - python: - options: - show_root_toc_entry: false - show_root_heading: false - show_source: true - heading_level: 3 - annotations_path: brief - members_order: source + - search + - autorefs + - mkdocstrings: + handlers: + python: + options: + docstring_style: google + show_root_toc_entry: true + show_root_heading: true + show_source: true + heading_level: 2 + annotations_path: brief + members_order: source diff --git a/notebooks/mkdataset.py b/notebooks/mkdataset.py index 62b7413..01fd8bb 100644 --- a/notebooks/mkdataset.py +++ b/notebooks/mkdataset.py @@ -8,24 +8,18 @@ def make_dataset() -> xr.Dataset: """Create a dataset that passes xrlint core rules.""" - + return xr.Dataset( attrs=dict(title="SST-Climatology Subset"), coords={ "x": xr.DataArray( - np.linspace(-180, 180, nx), - dims="x", - attrs={"units": "degrees"} + np.linspace(-180, 180, nx), dims="x", attrs={"units": "degrees"} ), "y": xr.DataArray( - np.linspace(-90, 90, ny), - dims="y", - attrs={"units": "degrees"} + np.linspace(-90, 90, ny), dims="y", attrs={"units": "degrees"} ), "time": xr.DataArray( - [2010 + y for y in range(nt)], - dims="time", - attrs={"units": "years"} + [2010 + y for y in range(nt)], dims="time", attrs={"units": "years"} ), "spatial_ref": xr.DataArray( 0, @@ -38,27 +32,25 @@ def make_dataset() -> xr.Dataset: }, data_vars={ "sst": xr.DataArray( - np.random.random((nt, ny, nx)), - dims=["time", "y", "x"], - attrs={"units": "kelvin", "grid_mapping": "spatial_ref"} + np.random.random((nt, ny, nx)), + dims=["time", "y", "x"], + attrs={"units": "kelvin", "grid_mapping": "spatial_ref"}, ), "sst_anomaly": xr.DataArray( - np.random.random((nt, ny, nx)), - dims=["time", "y", "x"], - attrs={"units": "kelvin", "grid_mapping": "spatial_ref"} - ) + np.random.random((nt, ny, nx)), + dims=["time", "y", "x"], + attrs={"units": "kelvin", "grid_mapping": "spatial_ref"}, + ), }, ) -def make_dataset_with_issues() -> xr.Dataset: +def make_dataset_with_issues() -> xr.Dataset: """Create a dataset that produces issues with xrlint core rules.""" invalid_ds = make_dataset() invalid_ds.attrs = {} invalid_ds.sst.attrs["units"] = 1 invalid_ds["sst_avg"] = xr.DataArray( - np.random.random((nx, ny)), - dims=["x", "y"], - attrs={"units": "kelvin"} + np.random.random((nx, ny)), dims=["x", "y"], attrs={"units": "kelvin"} ) return invalid_ds diff --git a/pyproject.toml b/pyproject.toml index 7bb3a5b..9838811 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,6 +74,7 @@ dev = [ ] doc = [ "mkdocs", + "mkdocs-autorefs", "mkdocs-material", "mkdocstrings", "mkdocstrings-python" diff --git a/xrlint/cli/config.py b/xrlint/cli/config.py index 0d847f6..aab4eab 100644 --- a/xrlint/cli/config.py +++ b/xrlint/cli/config.py @@ -15,8 +15,10 @@ def read_config_list(config_path: str | Path | PathLike[str]) -> ConfigList: Args: config_path: configuration file path. + Returns: A configuration list instance. + Raises: TypeError: if `config_path` is not a path-like object FileNotFoundError: if configuration file could not be found diff --git a/xrlint/cli/engine.py b/xrlint/cli/engine.py index 5a110bb..879d349 100644 --- a/xrlint/cli/engine.py +++ b/xrlint/cli/engine.py @@ -33,10 +33,9 @@ class XRLint(FormatterContext): - """The engine behind the XRLint CLI application. - - The arguments are mostly 1:1 equivalents of the - CLI options. + """This class provides the engine behind the XRLint + CLI application. + It represents the highest level component in the Python API. """ # noinspection PyShadowingBuiltins @@ -123,9 +122,10 @@ def get_config_for_file(self, file_path: str) -> Config | None: Args: file_path: A file path. - Return: + + Returns: A configuration object or `None` if no item - in the configuration list applies. + in the configuration list applies. """ return self.config_list.compute_config(file_path) @@ -145,7 +145,8 @@ def verify_datasets(self, files: Iterable[str]) -> Iterator[Result]: Args: files: Iterable of files. - Return: + + Returns: Iterator of reports. """ global_filter = self.config_list.get_global_filter( @@ -173,7 +174,8 @@ def format_results(self, results: Iterable[Result]) -> str: Args: results: Iterable of results. - Return: + + Returns: A report in plain text. """ output_format = ( @@ -226,6 +228,7 @@ def get_files( file_paths: Iterable[str], global_filter: FileFilter ) -> Iterator[tuple[str, bool | None]]: """Provide an iterator for the list of files or directories. + Directories in `files` that are not filtered out will be recursively traversed. @@ -234,7 +237,8 @@ def get_files( global_filter: A file filter that includes files that covered by global file patterns and not excluded by global ignore patterns. - Return: + + Returns: An iterator of filtered files or directories. """ for file_path in file_paths: diff --git a/xrlint/config.py b/xrlint/config.py index b045ef8..0c2dc54 100644 --- a/xrlint/config.py +++ b/xrlint/config.py @@ -28,7 +28,9 @@ def get_core_plugin() -> "Plugin": return export_plugin() -def get_core_config(config_name: Literal["all", "recommended"] | None = None): +def get_core_config( + config_name: Literal["all", "recommended"] | None = None +) -> "Config": """Create a base configuration for the built-in plugins. Args: @@ -39,6 +41,7 @@ def get_core_config(config_name: Literal["all", "recommended"] | None = None): option either in `config` or `config_kwargs`. Otherwise, calling `verify_dataset()` without any rule configuration will never succeed for any given dataset. + Returns: A new `Config` object """ @@ -154,6 +157,7 @@ def from_value(cls, value: Any) -> "Config": value: A `Config` object, a `dict` containing the configuration properties, or `None` which converts into an empty configuration. + Returns: A `Config` object. """ @@ -216,13 +220,16 @@ def get_rule(self, rule_id: str) -> "Rule": """Get the rule for the given rule identifier `rule_id`. Args: - rule_id: The rule identifier including plugin namespace, if any. - Format `` (builtin rules) or `/`. + rule_id: The rule identifier including plugin namespace, + if any. Format `` (builtin rules) or + `/`. + Returns: A `Rule` object. + Raises: - ValueError: If either the plugin is unknown in this configuration - or the rule name is unknown. + ValueError: If either the plugin is unknown in this + configuration or the rule name is unknown. """ plugin_name, rule_name = split_config_spec(rule_id) plugin = self.get_plugin(plugin_name) @@ -417,6 +424,7 @@ def from_value(cls, value: Any) -> "ConfigList": converted into `Config` objects including configuration names of tyype `str`. The latter are resolved against the plugin configurations seen so far in the list. + Returns: A `ConfigList` object. """ @@ -473,10 +481,11 @@ def compute_config(self, file_path: str) -> Config | None: Args: file_path: A dataset file path. + Returns: A `Config` object which may be empty, or `None` - if `file_path` is not included by any `files` pattern - or intentionally ignored by global `ignores`. + if `file_path` is not included by any `files` pattern + or intentionally ignored by global `ignores`. """ config = None diff --git a/xrlint/linter.py b/xrlint/linter.py index 11b3847..6f94759 100644 --- a/xrlint/linter.py +++ b/xrlint/linter.py @@ -26,6 +26,7 @@ def new_linter( config: The `config` keyword argument passed to the `Linter` class config_kwargs: The `config_kwargs` keyword arguments passed to the `Linter` class + Returns: A new linter instance """ @@ -85,6 +86,7 @@ def verify_dataset( config_kwargs: Individual linter configuration options to be merged with `config` if any. The merged result will be merged with the linter's configuration. + Returns: Result of the verification. """ diff --git a/xrlint/node.py b/xrlint/node.py index 1adfce3..8e4e9b7 100644 --- a/xrlint/node.py +++ b/xrlint/node.py @@ -7,12 +7,20 @@ @dataclass(frozen=True, kw_only=True) class Node(ABC): + """Abstract base class for nodes passed to the methods of a + rule operation [xrlint.rule.RuleOp][].""" + path: str + """Node path. So users find where in the tree the issue occurred.""" + parent: Union["Node", None] + """Node parent. `None` for root nodes.""" @dataclass(frozen=True, kw_only=True) class XarrayNode(Node): + """Base class for `xr.Dataset` nodes.""" + def in_coords(self) -> bool: """Return `True` if this node is in `xr.Dataset.coords`.""" return ".coords[" in self.path @@ -27,22 +35,39 @@ def in_root(self) -> bool: @dataclass(frozen=True, kw_only=True) -class AttrNode(XarrayNode): - name: Any - value: Any - +class DatasetNode(XarrayNode): + """Dataset node.""" -@dataclass(frozen=True, kw_only=True) -class AttrsNode(XarrayNode): - attrs: dict[str, Any] + dataset: xr.Dataset + """The `xarray.Dataset` instance.""" @dataclass(frozen=True, kw_only=True) class DataArrayNode(XarrayNode): + """Data array node.""" + name: Hashable + """The name of the data array.""" + data_array: xr.DataArray + """The `xarray.DataArray` instance.""" @dataclass(frozen=True, kw_only=True) -class DatasetNode(XarrayNode): - dataset: xr.Dataset +class AttrsNode(XarrayNode): + """Attributes node.""" + + attrs: dict[str, Any] + """Attributes dictionary.""" + + +@dataclass(frozen=True, kw_only=True) +class AttrNode(XarrayNode): + """Attribute node.""" + + name: str + """Attribute name.""" + + value: Any + """Attribute value.""" + diff --git a/xrlint/processor.py b/xrlint/processor.py index f37fb90..d334248 100644 --- a/xrlint/processor.py +++ b/xrlint/processor.py @@ -21,6 +21,7 @@ def preprocess( Args: file_path: A file path opener_options: The configuration's `opener_options`. + Returns: A list of (dataset, file_path) pairs """ diff --git a/xrlint/result.py b/xrlint/result.py index b6e6311..62bb107 100644 --- a/xrlint/result.py +++ b/xrlint/result.py @@ -34,7 +34,7 @@ class Suggestion(ToDictMixin): """Not used yet.""" @classmethod - def from_value(cls, value: Any): + def from_value(cls, value: Any) -> "Suggestion": """Convert given `value` into a `Suggestion` object. If `value` is already a `Suggestion` then it is returned as-is. @@ -42,6 +42,7 @@ def from_value(cls, value: Any): Args: value: A `Suggestion` object or a `str` containing the suggestion text. + Returns: A `Suggestion` object. """ diff --git a/xrlint/rule.py b/xrlint/rule.py index 675ded6..4b1f07c 100644 --- a/xrlint/rule.py +++ b/xrlint/rule.py @@ -8,11 +8,12 @@ from xrlint.node import DatasetNode, DataArrayNode, AttrsNode, AttrNode from xrlint.result import Suggestion from xrlint.util.formatting import format_message_type_of, format_message_one_of +from xrlint.util.importutil import import_value from xrlint.util.todict import ToDictMixin class RuleContext(ABC): - """The context passed to the verifier of a rule. + """The context passed to a [xrlint.rule.RuleOp][] instance. You should never create instances of this class yourself. Instances of this interface are passed to the `RuleOp`'s @@ -77,6 +78,7 @@ def dataset(self, context: RuleContext, node: DatasetNode) -> None: Args: context: The current rule context. node: The dataset node. + Raises: RuleExit: to exit rule logic and further node traversal """ @@ -87,6 +89,7 @@ def data_array(self, context: RuleContext, node: DataArrayNode) -> None: Args: context: The current rule context. node: The data array (variable) node. + Raises: RuleExit: to exit rule logic and further node traversal """ @@ -97,6 +100,7 @@ def attrs(self, context: RuleContext, node: AttrsNode) -> None: Args: context: The current rule context. node: The attributes node. + Raises: RuleExit: to exit rule logic and further node traversal """ @@ -107,6 +111,7 @@ def attr(self, context: RuleContext, node: AttrNode) -> None: Args: context: The current rule context. node: The attribute node. + Raises: RuleExit: to exit rule logic and further node traversal """ @@ -129,17 +134,17 @@ class RuleMeta(ToDictMixin): """Rule documentation URL.""" schema: dict[str, Any] | list[dict[str, Any]] | bool | None = None - """JSON Schema used to specify and validate the rule verifier's + """JSON Schema used to specify and validate the rule operation options. It can take the following values: - - Use `None` (the default) to indicate that the rule verifier + - Use `None` (the default) to indicate that the rule operation as no options at all. - - Use a schema to indicate that the rule verifier + - Use a schema to indicate that the rule operation takes keyword arguments only. The schema's type must be `"object"`. - - Use a list of schemas to indicate that the rule verifier + - Use a list of schemas to indicate that the rule operation takes positional arguments only. If given, the number of schemas in the list specifies the number of positional arguments that must be configured. @@ -232,9 +237,11 @@ def from_value(cls, value: Any) -> "RuleConfig": - one of `2` (error), `1` (warn), `0` (off) Args: - value: A rule severity or a list where the first element is a rule - severity and subsequent elements are rule arguments. - If the value is already of type `RuleConfig`it is returned as-is. + value: A rule severity or a list where the first element + is a rule severity and subsequent elements are rule + arguments. If the value is already of type `RuleConfig` + it is returned as-is. + Returns: A `RuleConfig` object. """ diff --git a/xrlint/testing.py b/xrlint/testing.py index d172693..efeb9c3 100644 --- a/xrlint/testing.py +++ b/xrlint/testing.py @@ -28,10 +28,10 @@ class RuleTest: """A name that helps identifying the test case.""" args: tuple | list | None = None - """Optional positional arguments passed to the rule verifier's constructor.""" + """Optional positional arguments passed to the rule operation's constructor.""" kwargs: dict[str, Any] | None = None - """Optional keyword arguments passed to the rule verifier's constructor.""" + """Optional keyword arguments passed to the rule operation's constructor.""" expected: list[Message] | int | None = 0 """Expected messages. @@ -55,7 +55,7 @@ def __init__(self, **config: dict[str, Any]): def run( self, rule_name: str, - rule_verifier_class: Type[RuleOp], + rule_op_class: Type[RuleOp], *, valid: list[RuleTest] | None = None, invalid: list[RuleTest] | None = None, @@ -65,7 +65,7 @@ def run( Args: rule_name: the rule's name - rule_verifier_class: the class derived from `RuleVerifier` + rule_op_class: a class derived from `RuleOp` valid: list of tests that expect no reported problems invalid: list of tests that expect reported problems @@ -73,7 +73,7 @@ def run( AssertionError: if one of the checks fails """ tests = self._create_tests( - rule_name, rule_verifier_class, valid=valid, invalid=invalid + rule_name, rule_op_class, valid=valid, invalid=invalid ) for test in tests.values(): print(f"Rule {rule_name!r}: running {test.__name__}()...") @@ -84,7 +84,7 @@ def run( def define_test( cls, rule_name: str, - rule_verifier_class: Type[RuleOp], + rule_op_class: Type[RuleOp], *, valid: list[RuleTest] | None = None, invalid: list[RuleTest] | None = None, @@ -98,24 +98,25 @@ def define_test( Args: rule_name: the rule's name - rule_verifier_class: the class derived from `RuleVerifier` + rule_op_class: the class derived from `RuleOp` valid: list of tests that expect no reported problems invalid: list of tests that expect reported problems config: optional xrlint configuration + Returns: A new class derived from `unittest.TestCase`. """ tester = RuleTester(**(config or {})) tests = tester._create_tests( - rule_name, rule_verifier_class, valid=valid, invalid=invalid + rule_name, rule_op_class, valid=valid, invalid=invalid ) # noinspection PyTypeChecker - return type(f"{rule_verifier_class.__name__}Test", (unittest.TestCase,), tests) + return type(f"{rule_op_class.__name__}Test", (unittest.TestCase,), tests) def _create_tests( self, rule_name: str, - rule_verifier_class: Type[RuleOp], + rule_op_class: Type[RuleOp], valid: list[RuleTest] | None, invalid: list[RuleTest] | None, ) -> dict[str, Callable[[unittest.TestCase | None], None]]: @@ -123,14 +124,14 @@ def make_args(tests: list[RuleTest] | None, mode: Literal["valid", "invalid"]): return [(test, index, mode) for index, test in enumerate(tests or [])] return dict( - self._create_test(rule_name, rule_verifier_class, *args) + self._create_test(rule_name, rule_op_class, *args) for args in make_args(valid, "valid") + make_args(invalid, "invalid") ) def _create_test( self, rule_name: str, - rule_verifier_class: Type[RuleOp], + rule_op_class: Type[RuleOp], test: RuleTest, test_index: int, test_mode: Literal["valid", "invalid"], @@ -139,7 +140,7 @@ def _create_test( def test_fn(_self: unittest.TestCase): error_message = self._test_rule( - rule_name, rule_verifier_class, test, test_id, test_mode + rule_name, rule_op_class, test, test_id, test_mode ) if error_message: raise AssertionError(error_message) diff --git a/xrlint/util/filepattern.py b/xrlint/util/filepattern.py index 43cc3f1..0e4202a 100644 --- a/xrlint/util/filepattern.py +++ b/xrlint/util/filepattern.py @@ -108,6 +108,7 @@ def match(self, path: str) -> bool: Args: path: File system path or URI + Returns: `True` if `path` matches. """ diff --git a/xrlint/util/importutil.py b/xrlint/util/importutil.py index ab90750..31429a4 100644 --- a/xrlint/util/importutil.py +++ b/xrlint/util/importutil.py @@ -53,9 +53,11 @@ def import_value( the exported value, e.g., "export_plugin", "export_configs". factory: The 1-arg factory function that converts a value - into T. + into `T`. + Returns: - The imported value of type T. + The imported value of type `T`. + Raises: ValueImportError: if the value could not be imported """ diff --git a/xrlint/version.py b/xrlint/version.py index 9f7a875..b7fb2ae 100644 --- a/xrlint/version.py +++ b/xrlint/version.py @@ -1 +1 @@ -version = "0.1.0" +version = "0.2.0.dev0"