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

Docs Improvements #285

Merged
merged 8 commits into from
Aug 6, 2024
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
9 changes: 7 additions & 2 deletions .github/workflows/test_suite_python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ jobs:
python-version: ${{matrix.python-version}}
cache: 'pip'
- name: Install dependencies
run: python -m pip install .[test]
run: python -m pip install .[test,docs]
- name: Lint with Ruff
run: |
python -m ruff check --output-format=github .
Expand All @@ -87,7 +87,12 @@ jobs:
sccache: 'true'
manylinux: auto
working-directory: bindings/python
- name: Run tests
- name: Prepare tests
run: |
python -m pip install openchecks --find-links target/wheels
- name: Run tests
run: |
python -m pytest --hypothesis-profile default -n=auto tests/
- name: Run doc tests
run: |
python -m sphinx -b=doctest docs/source docs/build
2 changes: 2 additions & 0 deletions .github/workflows/test_suite_rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ jobs:
if: ${{ matrix.os == 'ubuntu-latest' }}
- name: Generate coverage report from Rust tests
run: cargo llvm-cov --all-features --lcov --output-path lcov.info
- name: Run doc tests
run: cargo test --doc
- uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
with:
files: lcov.info
Expand Down
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ async-trait = "0.1.81"
bitflags = "2.6.0"

[dev-dependencies]
tokio = { version = "1.38.1", features = ["time", "rt", "macros"] }
tokio = { version = "1.38.1", features = [
"time",
"rt",
"rt-multi-thread",
"macros",
] }

[features]
arbitrary = ["dep:arbitrary", "bitflags/arbitrary"]
4 changes: 2 additions & 2 deletions bindings/python/docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information

project = "Untitled Checks Framework"
copyright = "2022, Scott Wilson"
project = "Open Checks Framework"
copyright = "2024, Scott Wilson"
author = "Scott Wilson"
release = "0.1.0"

Expand Down
9 changes: 8 additions & 1 deletion bindings/python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,14 @@ test = [
"pytest-xdist ~= 3.5",
"ruff ~= 0.2",
]
docs = ["myst-parser ~= 3.0", "sphinx ~= 7.2", "sphinx-rtd-theme ~= 2.0"]
docs = [
"myst-parser ~= 1.0; python_version == \"3.7\"",
"myst-parser ~= 3.0; python_version > \"3.7\"",
"sphinx ~= 5.3; python_version == \"3.7\"",
"sphinx ~= 6.2; python_version >= \"3.8\" and python_version < \"3.9\"",
"sphinx ~= 7.2; python_version >= \"3.9\"",
"sphinx-rtd-theme ~= 2.0",
]
fuzz = ["openchecks[test]", "atheris ~= 2.3"]

[tool.maturin]
Expand Down
195 changes: 191 additions & 4 deletions bindings/python/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ impl From<CheckHint> for base_openchecks::CheckHint {

#[pymethods]
impl CheckHint {
/// The check supports no extra features.
///
/// This should be considered the most conservative check *feature*. For
/// example, no auto-fix, check cannot be skipped before running, etc.
#[classattr]
#[allow(non_snake_case)]
pub(crate) fn NONE() -> Self {
Expand Down Expand Up @@ -143,6 +147,21 @@ impl CheckHintIterator {
}
}

/// CheckMetadata()
///
/// The check metadata.
///
/// This stores the information about the check that is either useful for humans
/// (the :code:`title`) and :code:`description`) or useful for systems that uses
/// the check (:code:`hint`). For example, a user interface could use the title
/// and description to render information for an artist to inform them about
/// what the check will validate and how it will fix issues (if supported). The
/// hint then could be used to render other useful information such as whether
/// the check supports automatic fixes in general, whether it could be
/// overridden by a supervisor, etc.
///
/// This should not be inherited directly. Use :code:`BaseCheck` or
/// :code:`AsyncBaseCheck` instead.
#[pyclass(subclass)]
pub(crate) struct CheckMetadata {}

Expand Down Expand Up @@ -196,9 +215,86 @@ impl CheckMetadata {
}
}

/// BaseCheck
/// BaseCheck()
///
/// The base check class to be inherited from.
///
/// This is responsible for validating the input data and returning a result
/// such as pass or fail. It can also provide extra data such as what caused the
/// status (for example, the scene nodes that are named incorrectly).
///
/// If the check supports it, then the data being validated can be automatically
/// fixed.
///
/// Example:
///
/// Simple Check
/// ------------
///
/// .. testsetup::
///
/// from openchecks import CheckResult, Item, BaseCheck, Status, run
///
/// .. testcode::
///
/// class IsEvenCheck(BaseCheck):
/// def __init__(self, value: int) -> None:
/// self.__value = value
/// super().__init__()
///
/// def title(self) -> str:
/// return "Is Even Check"
///
/// def description(self) -> str:
/// return "Check if the number is even."
///
/// def check(self) -> CheckResult:
/// if self.__value % 2 == 0:
/// return CheckResult.passed("Number is even.")
/// else:
/// return CheckResult.failed("Number is not even.")
///
/// check = IsEvenCheck(2)
/// result = run(check)
/// assert result.status() == Status.Passed
///
/// Check with Automatic Fix
/// ------------------------
///
/// .. testsetup::
///
/// from openchecks import CheckResult, Item, BaseCheck, Status, auto_fix, run
///
/// .. testcode::
///
/// class IsZeroCheck(BaseCheck):
/// def __init__(self, value: int) -> None:
/// self.__value = value
/// super().__init__()
///
/// def title(self) -> str:
/// return "Is Zero Check"
///
/// def description(self) -> str:
/// return "Check if the number is zero."
///
/// def check(self) -> CheckResult:
/// if self.__value == 0:
/// return CheckResult.passed("Number is zero.")
/// else:
/// return CheckResult.failed("Number is not zero.", can_fix=True)
///
/// def auto_fix(self) -> None:
/// self.__value = 0
///
/// check = IsZeroCheck(1)
/// result = run(check)
/// assert result.status() == Status.Failed
///
/// if result.can_fix():
/// result = auto_fix(check)
/// assert result.status() == Status.Passed
///
/// The base check to subclass.
#[pyclass(extends = CheckMetadata, subclass)]
#[derive(Debug)]
pub(crate) struct BaseCheck {}
Expand All @@ -216,6 +312,9 @@ impl BaseCheck {
/// Run a validation on the input data and output the result of the
/// validation.
///
/// Raises:
/// NotImplementedError: The check has not been implemented.
///
/// Returns:
/// CheckResult[T]: The result of the check.
pub(crate) fn check(&self) -> PyResult<CheckResult> {
Expand All @@ -225,14 +324,102 @@ impl BaseCheck {
/// auto_fix(self)
///
/// Automatically fix the issue detected by the :code:`Check.check` method.
///
/// Raises:
/// NotImplementedError: The automatic fix has not been implemented.
pub(crate) fn auto_fix(&self) -> PyResult<()> {
Err(PyNotImplementedError::new_err("auto_fix not implemented"))
}
}

/// AsyncBaseCheck
/// AsyncBaseCheck()
///
/// The base check class to be inherited from for async code.
///
/// This is responsible for validating the input data and returning a result
/// such as pass or fail. It can also provide extra data such as what caused the
/// status (for example, the scene nodes that are named incorrectly).
///
/// If the check supports it, then the data being validated can be automatically
/// fixed.
///
/// Example:
///
/// Simple Check
/// ------------
///
/// .. testsetup::
/// import asyncio
///
/// from openchecks import CheckResult, Item, AsyncBaseCheck, Status, async_run
///
/// .. testcode::
///
/// class IsEvenCheck(AsyncBaseCheck):
/// def __init__(self, value: int) -> None:
/// self.__value = value
/// super().__init__()
///
/// def title(self) -> str:
/// return "Is Even Check"
///
/// def description(self) -> str:
/// return "Check if the number is even."
///
/// async def async_check(self) -> CheckResult:
/// if self.__value % 2 == 0:
/// return CheckResult.passed("Number is even.")
/// else:
/// return CheckResult.failed("Number is not even.")
///
/// async def main():
/// check = IsEvenCheck(2)
/// result = await async_run(check)
/// assert result.status() == Status.Passed
///
/// asyncio.run(main())
///
/// Check with Automatic Fix
/// ------------------------
///
/// .. testsetup::
///
/// import asyncio
///
/// from openchecks import CheckResult, Item, AsyncBaseCheck, Status, async_auto_fix, async_run
///
/// .. testcode::
///
/// class IsZeroCheck(AsyncBaseCheck):
/// def __init__(self, value: int) -> None:
/// self.__value = value
/// super().__init__()
///
/// def title(self) -> str:
/// return "Is Zero Check"
///
/// def description(self) -> str:
/// return "Check if the number is zero."
///
/// async def async_check(self) -> CheckResult:
/// if self.__value == 0:
/// return CheckResult.passed("Number is zero.")
/// else:
/// return CheckResult.failed("Number is not zero.", can_fix=True)
///
/// async def async_auto_fix(self) -> None:
/// self.__value = 0
///
/// async def main():
/// check = IsZeroCheck(1)
/// result = await async_run(check)
/// assert result.status() == Status.Failed
///
/// if result.can_fix():
/// result = await async_auto_fix(check)
/// assert result.status() == Status.Passed
///
/// The base check to subclass.
/// asyncio.run(main())
#[pyclass(extends = CheckMetadata, subclass)]
#[derive(Debug)]
pub(crate) struct AsyncBaseCheck {}
Expand Down
56 changes: 56 additions & 0 deletions bindings/python/src/item.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,50 @@
use pyo3::{intern, prelude::*, types::PyString};

/// Item(value: T, type_hint: Optional[str] = None) -> None
///
/// The item is a wrapper to make a result item more user interface friendly.
///
/// Result items represent the objects that caused a result. For example, if a
/// check failed because the bones in a character rig are not properly named,
/// then the items would contain the bones that are named incorrectly.
///
/// The item wrapper makes the use of items user interface friendly because it
/// implements item sorting and a string representation of the item.
///
/// Example:
///
/// .. testsetup::
///
/// from __future__ import annotations
///
/// from openchecks import Item
///
/// class SceneNode:
/// def __init__(self, name):
/// self.__name = name
///
/// def name(self):
/// return self.__name
///
/// .. testcode::
///
/// class SceneItem(Item):
/// def __str__(self) -> str:
/// return self.value().name()
///
/// def __eq__(self, other: SceneItem) -> bool:
/// return self.value().name() == other.value().name()
///
/// def __lt__(self, other: SceneItem) -> bool:
/// return self.value().name() < other.value().name()
///
/// a = SceneItem(SceneNode("a"))
/// b = SceneItem(SceneNode("b"))
///
/// assert a != b
/// assert a < b
/// assert str(a) == "a"
///
#[pyclass(subclass)]
#[derive(Debug, Clone)]
pub(crate) struct Item {
Expand Down Expand Up @@ -82,10 +127,21 @@ impl Item {
}
}

/// value(self) -> T
///
/// The wrapped value
fn value<'py>(&'py self, py: Python<'py>) -> PyResult<&'py PyAny> {
Ok(self.value.as_ref(py))
}

/// type_hint(self) -> Optional[str]
///
/// A type hint can be used to add a hint to a system that the given type
/// represents something else. For example, the value could be a string, but
/// this is a scene path.
///
/// A user interface could use this hint to select the item in the
/// application.
fn type_hint(&self) -> Option<&str> {
match &self.type_hint {
Some(type_hint) => Some(type_hint.as_str()),
Expand Down
Loading
Loading