From 8a33c699f4427a4aebf49a0f737988022d9dcd4f Mon Sep 17 00:00:00 2001 From: Jeff Kala Date: Thu, 6 Aug 2026 14:36:38 -0600 Subject: [PATCH 1/6] Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool Template: ``` { "template": "https://github.com/networktocode-llc/cookiecutter-ntc.git", "dir": "python", "ref": "main", "path": null } ``` Cookie: ``` { "remote": "https://github.com/networktocode/schema-enforcer.git", "path": "/Users/jeffkala/Documents/GitHub/outputs/schema-enforcer", "repository_path": "/Users/jeffkala/Documents/GitHub/outputs/schema-enforcer", "dir": "", "branch_prefix": "drift-manager/develop", "context": { "codeowner_github_usernames": "@cmsirbu @glennmatthews", "full_name": "Network to Code, LLC", "email": "info@networktocode.com", "github_org": "networktocode", "description": "Tool/Framework for testing structured data against schema definitions", "project_name": "Schema Enforcer", "project_slug": "schema-enforcer", "repo_url": "https://github.com/networktocode/schema-enforcer", "base_url": "schema-enforcer", "project_python_name": "schema_enforcer", "project_python_base_version": "3.10", "project_with_config_settings": "no", "generate_docs": "yes", "version": "1.5.2", "original_publish_year": "2020", "_template": "https://github.com/networktocode-llc/cookiecutter-ntc.git", "_output_dir": "/Users/jeffkala/Documents/GitHub/outputs", "_repo_dir": "/Users/jeffkala/.cookiecutters/cookiecutter-ntc/python", "_checkout": "main" }, "drift_managed_branch": "develop", "remote_name": "origin", "pull_request_strategy": "PullRequestStrategy.CREATE", "post_actions": [], "baked_commit_ref": "", "draft": false } ``` CLI Arguments: ``` { "cookie_dir": "", "input": true, "json_filename": "", "output_dir": "../outputs", "push": true, "template": "https://github.com/networktocode-llc/cookiecutter-ntc.git", "template_dir": "python", "template_ref": "main", "pull_request": null, "post_action": [], "disable_post_actions": false, "draft": null, "drift_managed_branch": "develop" } ``` --- .cookiecutter.json | 30 ++ .dockerignore | 32 +- .github/CODEOWNERS | 5 + .github/ISSUE_TEMPLATE/bug_report.md | 4 + .github/ISSUE_TEMPLATE/feature_request.md | 3 + .../pull_request_template.md | 6 + .github/workflows/ci.yml | 174 ++++++++++ .github/workflows/prepare_release.yml | 178 ++++++++++ .github/workflows/release.yml | 165 +++++++++ .gitignore | 33 ++ .readthedocs.yml | 27 ++ .yamllint.yml | 5 + Dockerfile | 29 ++ LICENSE | 18 +- README.md | 48 +++ bin/ensure_release_notes.py | 97 ++++++ changes/+main.housekeeping | 1 + changes/.gitignore | 1 + docs/admin/install.md | 22 ++ docs/admin/release_notes/index.md | 3 + docs/admin/release_notes/version_1.0.md | 40 +++ docs/admin/uninstall.md | 7 + docs/admin/upgrade.md | 7 + docs/assets/extra.css | 152 ++++++++ docs/assets/favicon.ico | Bin 0 -> 568 bytes docs/assets/networktocode_bw.png | Bin 0 -> 7562 bytes docs/assets/networktocode_logo.png | Bin 0 -> 5464 bytes docs/assets/overrides/partials/copyright.html | 21 ++ docs/dev/arch_decision.md | 3 + docs/dev/contributing.md | 77 +++++ docs/dev/dev_environment.md | 98 ++++++ docs/dev/extending.md | 4 + docs/dev/release_checklist.md | 192 +++++++++++ docs/generate_code_reference_pages.py | 20 ++ docs/images/networktocode_logo.svg | 150 ++++++++ docs/index.md | 6 + docs/user/faq.md | 1 + docs/user/lib_getting_started.md | 19 + docs/user/lib_overview.md | 16 + docs/user/lib_use_cases.md | 12 + example.invoke.yml | 7 + mkdocs.yml | 140 ++++++++ pyproject.toml | 194 +++++++++++ schema_enforcer/__init__.py | 6 + schema_enforcer/api.py | 3 + schema_enforcer/cli.py | 30 ++ schema_enforcer/log.py | 74 ++++ tasks.py | 324 ++++++++++++++++++ tests/integration/__init__.py | 1 + tests/unit/__init__.py | 1 + tests/unit/conftest.py | 10 + tests/unit/test_basics.py | 29 ++ tests/unit/test_cli.py | 16 + tests/unit/test_logging.py | 58 ++++ towncrier_header.txt | 9 + towncrier_template.j2 | 43 +++ 56 files changed, 2649 insertions(+), 2 deletions(-) create mode 100644 .cookiecutter.json create mode 100644 .github/workflows/prepare_release.yml create mode 100644 .github/workflows/release.yml create mode 100644 .readthedocs.yml create mode 100644 bin/ensure_release_notes.py create mode 100644 changes/+main.housekeeping create mode 100644 changes/.gitignore create mode 100644 docs/admin/install.md create mode 100644 docs/admin/release_notes/index.md create mode 100644 docs/admin/release_notes/version_1.0.md create mode 100644 docs/admin/uninstall.md create mode 100644 docs/admin/upgrade.md create mode 100644 docs/assets/extra.css create mode 100644 docs/assets/favicon.ico create mode 100644 docs/assets/networktocode_bw.png create mode 100644 docs/assets/networktocode_logo.png create mode 100644 docs/assets/overrides/partials/copyright.html create mode 100644 docs/dev/arch_decision.md create mode 100644 docs/dev/contributing.md create mode 100644 docs/dev/dev_environment.md create mode 100644 docs/dev/extending.md create mode 100644 docs/dev/release_checklist.md create mode 100644 docs/generate_code_reference_pages.py create mode 100644 docs/images/networktocode_logo.svg create mode 100644 docs/index.md create mode 100644 docs/user/faq.md create mode 100644 docs/user/lib_getting_started.md create mode 100644 docs/user/lib_overview.md create mode 100644 docs/user/lib_use_cases.md create mode 100644 example.invoke.yml create mode 100644 mkdocs.yml create mode 100644 schema_enforcer/api.py create mode 100644 schema_enforcer/log.py create mode 100644 tests/integration/__init__.py create mode 100644 tests/unit/__init__.py create mode 100644 tests/unit/conftest.py create mode 100644 tests/unit/test_basics.py create mode 100644 tests/unit/test_cli.py create mode 100644 tests/unit/test_logging.py create mode 100644 towncrier_header.txt create mode 100644 towncrier_template.j2 diff --git a/.cookiecutter.json b/.cookiecutter.json new file mode 100644 index 0000000..90e40ae --- /dev/null +++ b/.cookiecutter.json @@ -0,0 +1,30 @@ +{ + "cookiecutter": { + "codeowner_github_usernames": "@cmsirbu @glennmatthews", + "full_name": "Network to Code, LLC", + "email": "info@networktocode.com", + "github_org": "networktocode", + "description": "Tool/Framework for testing structured data against schema definitions", + "project_name": "Schema Enforcer", + "project_slug": "schema-enforcer", + "repo_url": "https://github.com/networktocode/schema-enforcer", + "base_url": "schema-enforcer", + "project_python_name": "schema_enforcer", + "project_python_base_version": "3.10", + "project_with_config_settings": "no", + "generate_docs": "yes", + "version": "1.5.2", + "original_publish_year": "2020", + "_drift_manager": { + "template": "https://github.com/networktocode-llc/cookiecutter-ntc.git", + "template_dir": "python", + "template_ref": "main", + "cookie_dir": "", + "pull_request_strategy": "create", + "post_actions": [], + "draft": false, + "baked_commit_ref": "0eee1191e60bc8680cc53a79f8296380048cd703", + "drift_managed_branch": "develop" + } + } +} diff --git a/.dockerignore b/.dockerignore index c3d27c8..3394303 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ +<<<<<<< HEAD **/*.pyc **/*.pyo **/*.log @@ -6,4 +7,33 @@ Dockerfile docker-compose.yml .env -docs/_build \ No newline at end of file +docs/_build +======= +# Docker related +development/Dockerfile +development/docker-compose*.yml +development/*.env +*.env +environments/ + +# Python +**/*.pyc +**/*.pyo +**/__pycache__/ +**/.pytest_cache/ +**/.venv/ + + +# Other +docs/_build +FAQ.md +.git/ +.gitignore +.github +tasks.py +LICENSE +**/*.log +**/.vscode/ +invoke*.yml +tasks.py +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 89432fa..4f83244 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,4 @@ +<<<<<<< HEAD # This is a comment. # Each line is a file pattern followed by one or more owners. @@ -11,3 +12,7 @@ # Order is important; the last matching pattern takes the most # precedence. +======= +# Default owner(s) of all files in this repository +* @cmsirbu @glennmatthews +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index a2c0b4f..0fe1e0a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,7 +4,11 @@ about: Report a reproducible bug in the current release of schema-enforcer --- ### Environment +<<<<<<< HEAD * Python version: +======= +* Python version: +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) * schema-enforcer version: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index b300f10..ba9e68d 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -5,7 +5,10 @@ about: Propose a new feature or enhancement --- ### Environment +<<<<<<< HEAD * Python version: +======= +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) * schema-enforcer version: + +

+ +
+ + + + +
+

+ +## Overview + +> Developer Note: Add a long (2-3 paragraphs) description of what the library does, what problems it solves, etc. + +## Documentation + +Full documentation for this library can be found over on the [Schema Enforcer Docs](https://schema-enforcer.readthedocs.io/) website: + +- [User Guide](https://schema-enforcer.readthedocs.io/en/latest/user/app_overview/) - Overview, Using the Library, Getting Started. +- [Administrator Guide](https://schema-enforcer.readthedocs.io/en/latest/admin/install/) - How to Install, Configure, Upgrade, or Uninstall the Library. +- [Developer Guide](https://schema-enforcer.readthedocs.io/en/latest/dev/contributing/) - Extending the Library, Code Reference, Contribution Guide. +- [Release Notes / Changelog](https://schema-enforcer.readthedocs.io/en/latest/admin/release_notes/). +- [Frequently Asked Questions](https://schema-enforcer.readthedocs.io/en/latest/user/faq/). + +### Contributing to the Documentation + +You can find all the Markdown source for the App documentation under the [`docs`](https://github.com/networktocode/schema-enforcer/tree/develop/docs) folder in this repository. For simple edits, a Markdown capable editor is sufficient: clone the repository and edit away. + +If you need to view the fully-generated documentation site, you can build it with [MkDocs](https://www.mkdocs.org/). A container hosting the documentation can be started using the `invoke` commands (details in the [Development Environment Guide](https://schema-enforcer/dev/dev_environment/#docker-development-environment)) on [http://localhost:8001](http://localhost:8001). Using this container, as your changes to the documentation are saved, they will be automatically rebuilt and any pages currently being viewed will be reloaded in your browser. + +Any PRs with fixes or improvements are very welcome! + +## Questions + +For any questions or comments, please check the [FAQ](https://schema-enforcer.readthedocs.io/en/latest/user/faq/) first. Feel free to also swing by the [Network to Code Slack](https://networktocode.slack.com/) (channel `#networktocode`), sign up [here](http://slack.networktocode.com/) if you don't have an account. +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) diff --git a/bin/ensure_release_notes.py b/bin/ensure_release_notes.py new file mode 100644 index 0000000..7479433 --- /dev/null +++ b/bin/ensure_release_notes.py @@ -0,0 +1,97 @@ +"""Ensure that release notes exist for a given version. + +This script will do the following: + Ensure a release notes file exists at `docs/admin/release_notes/version_{version}.md`. + Ensure the `mkdocs.yml` file is updated to add the release notes file to the navigation. + Ensure the `pyproject.toml` `tool.towncrier.filename` is updated to reference the release notes file. + +It shouldn't be necessary to run this file manually. It is automatically called by `invoke generate-release-notes`. + +Example: + $ python bin/ensure_release_notes.py --version '1.0' +""" + +import argparse + +try: + import tomllib +except ImportError: + import tomli as tomllib + +from pathlib import Path + + +def release_notes_pyproject_toml(version): + """Update the pyproject.toml file to set the towncrier filename for the given version.""" + pyproject_file = Path(__file__).parent.parent / "pyproject.toml" + pyproject_content = pyproject_file.read_text() + pyproject_data = tomllib.loads(pyproject_content) + release_notes_file = f"docs/admin/release_notes/version_{version}.md" + + # Update the towncrier filename + if pyproject_data["tool"]["towncrier"].get("filename", "") != release_notes_file: + pyproject_data["tool"]["towncrier"]["filename"] = release_notes_file + + # Write back the updated content to pyproject.toml + # tomllib is not used to write the file because it is not roundtrippable + new_pyproject_content = [] + in_towncrier_section = False + for line in pyproject_content.splitlines(): + if line.strip() == "[tool.towncrier]": + in_towncrier_section = True + new_pyproject_content.append(line) + continue + if in_towncrier_section: + if line.strip().startswith("filename"): + new_pyproject_content.append(f'filename = "docs/admin/release_notes/version_{version}.md"') + in_towncrier_section = False # Only replace the first occurrence + else: + new_pyproject_content.append(line) + else: + new_pyproject_content.append(line) + + pyproject_file.write_text("\n".join(new_pyproject_content)) + # Add a newline at the end of the file if it doesn't exist + if not pyproject_file.read_text().endswith("\n"): + pyproject_file.write_text(pyproject_file.read_text() + "\n") + # Remind the user to update the release notes file. + print( + f"\033[33mRemember to update the Release Overview section in the release notes file: {release_notes_file}\033[0m" + ) + + +def ensure_release_notes_file(version): + """Ensure that the release notes file for the given version exists and is referenced in mkdocs.yml.""" + release_notes_file = Path(__file__).parent.parent / "docs" / "admin" / "release_notes" / f"version_{version}.md" + if not release_notes_file.exists(): + # Create a new release notes file with a basic template from towncrier_header.txt + towncrier_header = Path(__file__).parent.parent / "towncrier_header.txt" + content = towncrier_header.read_text().format(version=version) + release_notes_file.write_text(content) + + +def ensure_mkdocs_version(version): + """Ensure that mkdocs.yml includes the new release notes file in the navigation.""" + mkdocs_yml_file = Path(__file__).parent.parent / "mkdocs.yml" + mkdocs_yml_content = mkdocs_yml_file.read_text() + release_notes_nav_entry = f' - v{version}: "admin/release_notes/version_{version}.md"\n' + if release_notes_nav_entry in mkdocs_yml_content: + return + + # Add the new release notes entry to the mkdocs.yml content + if "Release Notes:" in mkdocs_yml_content: + mkdocs_yml_content = mkdocs_yml_content.replace( + ' - "admin/release_notes/index.md"\n', + f' - "admin/release_notes/index.md"\n{release_notes_nav_entry}', + ) + + mkdocs_yml_file.write_text(mkdocs_yml_content) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Ensure release notes exist for a given version.") + parser.add_argument("--version", help="The version number (e.g. 2.2)") + args = parser.parse_args() + ensure_release_notes_file(args.version) + ensure_mkdocs_version(args.version) + release_notes_pyproject_toml(args.version) diff --git a/changes/+main.housekeeping b/changes/+main.housekeeping new file mode 100644 index 0000000..3433adf --- /dev/null +++ b/changes/+main.housekeeping @@ -0,0 +1 @@ +Rebaked from the cookie `main`. diff --git a/changes/.gitignore b/changes/.gitignore new file mode 100644 index 0000000..f935021 --- /dev/null +++ b/changes/.gitignore @@ -0,0 +1 @@ +!.gitignore diff --git a/docs/admin/install.md b/docs/admin/install.md new file mode 100644 index 0000000..9bd1215 --- /dev/null +++ b/docs/admin/install.md @@ -0,0 +1,22 @@ +# Installation + +Option 1: Install from PyPI. + +```bash +pip install schema-enforcer +``` + +Option 2: Manually install via Poetry. + +```bash +git clone https://github.com/networktocode/schema-enforcer.git +cd schema-enforcer +curl -sSL https://install.python-poetry.org | python3 - +poetry install +``` + +Option 3: Install from a GitHub branch, such as develop as shown below. + +```bash +pip install git+https://github.com/networktocode/schema-enforcer.git@develop +``` diff --git a/docs/admin/release_notes/index.md b/docs/admin/release_notes/index.md new file mode 100644 index 0000000..12cb516 --- /dev/null +++ b/docs/admin/release_notes/index.md @@ -0,0 +1,3 @@ +# Release Notes + +All the published release notes can be found via the navigation menu. All patch releases are included in the same minor release (e.g. `v1.2`) document. diff --git a/docs/admin/release_notes/version_1.0.md b/docs/admin/release_notes/version_1.0.md new file mode 100644 index 0000000..e0d8167 --- /dev/null +++ b/docs/admin/release_notes/version_1.0.md @@ -0,0 +1,40 @@ +# v1.0 Release Notes + +!!! warning "Developer Note - Remove Me!" + Guiding Principles: + + - Changelogs are for humans, not machines. + - There should be an entry for every single version. + - The same types of changes should be grouped. + - Versions and sections should be linkable. + - The latest version comes first. + - The release date of each version is displayed. + - Mention whether you follow Semantic Versioning. + + Types of changes: + + - `Added` for new features. + - `Changed` for changes in existing functionality. + - `Deprecated` for soon-to-be removed features. + - `Removed` for now removed features. + - `Fixed` for any bug fixes. + - `Security` in case of vulnerabilities. + + +This document describes all new features and changes in the release `1.0`. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Release Overview + +- Major features or milestones +- Achieved in this `x.y` release +- Changes to compatibility with Nautobot and/or other apps, libraries etc. + +## [v1.0.0] - 2026-08-06 + +### Added + +### Changed + +### Fixed + +- [#123](https://github.com/networktocode/schema-enforcer/issues/123) Fixed Tag filtering not working in job launch form. diff --git a/docs/admin/uninstall.md b/docs/admin/uninstall.md new file mode 100644 index 0000000..41b477e --- /dev/null +++ b/docs/admin/uninstall.md @@ -0,0 +1,7 @@ +# Uninstall + +Uninstall from environment. + +```bash +pip uninstall schema-enforcer +``` diff --git a/docs/admin/upgrade.md b/docs/admin/upgrade.md new file mode 100644 index 0000000..d8bf384 --- /dev/null +++ b/docs/admin/upgrade.md @@ -0,0 +1,7 @@ +# Upgrading the Library + +Upgrade from PyPI. + +```bash +pip install schema-enforcer --upgrade +``` diff --git a/docs/assets/extra.css b/docs/assets/extra.css new file mode 100644 index 0000000..50884f4 --- /dev/null +++ b/docs/assets/extra.css @@ -0,0 +1,152 @@ +:root>* { + --md-accent-fg-color: #ff8504; + --md-primary-fg-color: #ff8504; + --md-typeset-a-color: #0097ff; +} + +[data-md-color-scheme="slate"] { + --md-default-bg-color: hsla(var(--md-hue), 0%, 15%, 1); + --md-typeset-a-color: #0097ff; +} + +/* Accessibility: Increase fonts for dark theme */ +[data-md-color-scheme="slate"] .md-typeset { + font-size: 0.9rem; +} + +[data-md-color-scheme="slate"] .md-typeset table:not([class]) { + font-size: 0.7rem; +} + +.md-tabs__link { + font-size: 0.8rem; +} + +.md-tabs__link--active { + color: var(--md-primary-fg-color); +} + +.md-header__button.md-logo :is(img, svg) { + height: 2rem; +} + +.md-header__button.md-logo :-webkit-any(img, svg) { + height: 2rem; +} + +.md-header__title { + font-size: 1.2rem; +} + +img.logo { + height: 100px; +} + +img.copyright-logo { + height: 24px; + vertical-align: middle; +} + +[data-md-color-primary=black] .md-header { + background-color: #212121; +} + +@media screen and (min-width: 76.25em) { + [data-md-color-primary=black] .md-tabs { + background-color: #212121; + } +} + +/* Customization for mkdocstrings */ +/* Indentation. */ +div.doc-contents:not(.first) { + padding-left: 25px; + border-left: .2rem solid var(--md-typeset-table-color); +} + +/* Mark external links as such. */ +a.autorefs-external::after { + /* https://primer.style/octicons/arrow-up-right-24 */ + background-image: url('data:image/svg+xml,'); + content: ' '; + + display: inline-block; + position: relative; + top: 0.1em; + margin-left: 0.2em; + margin-right: 0.1em; + + height: 1em; + width: 1em; + border-radius: 100%; + background-color: var(--md-typeset-a-color); +} + +a.autorefs-external:hover::after { + background-color: var(--md-accent-fg-color); +} + + +/* Customization for mkdocs-version-annotations */ +:root { + /* Icon for "version-added" admonition: Material Design Icons "plus-box-outline" */ + --md-admonition-icon--version-added: url('data:image/svg+xml;charset=utf-8,'); + /* Icon for "version-changed" admonition: Material Design Icons "delta" */ + --md-admonition-icon--version-changed: url('data:image/svg+xml;charset=utf-8,'); + /* Icon for "version-removed" admonition: Material Design Icons "minus-circle-outline" */ + --md-admonition-icon--version-removed: url('data:image/svg+xml;charset=utf-8,'); +} + +/* "version-added" admonition in green */ +.md-typeset .admonition.version-added, +.md-typeset details.version-added { + border-color: rgb(0, 200, 83); +} + +.md-typeset .version-added>.admonition-title, +.md-typeset .version-added>summary { + background-color: rgba(0, 200, 83, .1); +} + +.md-typeset .version-added>.admonition-title::before, +.md-typeset .version-added>summary::before { + background-color: rgb(0, 200, 83); + -webkit-mask-image: var(--md-admonition-icon--version-added); + mask-image: var(--md-admonition-icon--version-added); +} + +/* "version-changed" admonition in orange */ +.md-typeset .admonition.version-changed, +.md-typeset details.version-changed { + border-color: rgb(255, 145, 0); +} + +.md-typeset .version-changed>.admonition-title, +.md-typeset .version-changed>summary { + background-color: rgba(255, 145, 0, .1); +} + +.md-typeset .version-changed>.admonition-title::before, +.md-typeset .version-changed>summary::before { + background-color: rgb(255, 145, 0); + -webkit-mask-image: var(--md-admonition-icon--version-changed); + mask-image: var(--md-admonition-icon--version-changed); +} + +/* "version-removed" admonition in red */ +.md-typeset .admonition.version-removed, +.md-typeset details.version-removed { + border-color: rgb(255, 82, 82); +} + +.md-typeset .version-removed>.admonition-title, +.md-typeset .version-removed>summary { + background-color: rgba(255, 82, 82, .1); +} + +.md-typeset .version-removed>.admonition-title::before, +.md-typeset .version-removed>summary::before { + background-color: rgb(255, 82, 82); + -webkit-mask-image: var(--md-admonition-icon--version-removed); + mask-image: var(--md-admonition-icon--version-removed); +} diff --git a/docs/assets/favicon.ico b/docs/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..9685c83bff4f67163334bbbf165b3bbccbf87c7e GIT binary patch literal 568 zcmV-80>}M{P) zO^8lW9LMp`nVu}BNz$ZQSw?=?76!WD zc9eyoZvWqd{diXvM$1A^2X-6YmWA_WVMPaS2rtUQR9RTLoPm&B@5W7h$C(hls)NhI z2HeIPoDI>F47V0j*p5qh5TajM11YG5Q@D&dTnf>bn#bWt`oTQTh3I|8$j+45VoK~~ z)<9cOKPGViS1=o*pEX=>%I-8~F&UzT8m@(tDY3^G57FnWfwsV5Rk>HH%KbFM4dMp2 zVhj&MtmXP~4To?QGa>q!HIRZ}6xZ<_VF0y1HZbmjS%S#Bv}$VN34nwdp(dJV4} literal 0 HcmV?d00001 diff --git a/docs/assets/networktocode_bw.png b/docs/assets/networktocode_bw.png new file mode 100644 index 0000000000000000000000000000000000000000..075c4926d570b9db02c8808f34f40c8eb5f18a90 GIT binary patch literal 7562 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 Lq@`FVZx!)Bwj{xy literal 0 HcmV?d00001 diff --git a/docs/assets/overrides/partials/copyright.html b/docs/assets/overrides/partials/copyright.html new file mode 100644 index 0000000..cbf6bde --- /dev/null +++ b/docs/assets/overrides/partials/copyright.html @@ -0,0 +1,21 @@ + + + + + + +
diff --git a/docs/dev/arch_decision.md b/docs/dev/arch_decision.md new file mode 100644 index 0000000..cbe4d49 --- /dev/null +++ b/docs/dev/arch_decision.md @@ -0,0 +1,3 @@ +# Architecture Decision Records + +The intention is to document deviations from a standard pattern. diff --git a/docs/dev/contributing.md b/docs/dev/contributing.md new file mode 100644 index 0000000..58ea40a --- /dev/null +++ b/docs/dev/contributing.md @@ -0,0 +1,77 @@ +# Contributing + +Pull requests are welcomed and automatically built and tested against multiple versions of Python through GitHub Actions. + +Except for unit tests, testing is only supported on Python 3.13. + +The project is packaged with a light development environment based on `Docker` to help with the local development of the project and to run tests within GitHub Actions. + +The project is following Network to Code software development guidelines and is leveraging the following: + +- Python linting and formatting: `pylint` and `ruff`. +- YAML linting is done with `yamllint`. + +Documentation is built using [mkdocs](https://www.mkdocs.org/). The [Docker based development environment](dev_environment.md#docker-development-environment) can be started by running `invoke docs` [http://localhost:8001](http://localhost:8001) that auto-refreshes when you make any changes to your local files. + +## Creating Changelog Fragments + +All pull requests to `next` or `develop` must include a changelog fragment file in the `./changes` directory. To create a fragment, use your GitHub issue number and fragment type as the filename. For example, `2362.added`. Valid fragment types are `added`, `changed`, `deprecated`, `fixed`, `removed`, and `security`. The change summary is added to the file in plain text. Change summaries should be complete sentences, starting with a capital letter and ending with a period, and be in past tense. Each line of the change fragment will generate a single change entry in the release notes. Use multiple lines in the same file if your change needs to generate multiple release notes in the same category. If the change needs to create multiple entries in separate categories, create multiple files. + +!!! example + + **Wrong** + ```plaintext title="changes/1234.fixed" + fix critical bug in documentation + ``` + + **Right** + ```plaintext title="changes/1234.fixed" + Fixed critical bug in documentation. + ``` + +!!! example "Multiple Entry Example" + + This will generate 2 entries in the `fixed` category and one entry in the `changed` category. + + ```plaintext title="changes/1234.fixed" + Fixed critical bug in documentation. + Fixed release notes generation. + ``` + + ```plaintext title="changes/1234.changed" + Changed release notes generation. + ``` + +## Branching Policy + +The branching policy includes the following tenets: + +- The develop branch is the primary branch to develop off of. +- If there is a reason to have a patch version, the maintainers may use cherry-picking strategy. +- PRs intended to add new features should be sourced from the develop branch. +- PRs intended to address bug fixes and security patches should be sourced from the develop branch. +- PRs intended to add new features that break backward compatibility should be discussed before a PR is created. + +Schema Enforcer will observe Semantic Versioning, as of 1.0. This may result in an quick turn around in minor versions to keep pace with an ever growing feature set. + +## Release Policy + +Schema Enforcer has currently no intended scheduled release schedule, and will release new features in minor versions. + +When a new release is created the following should happen. + +- A release PR is created with: + - Update to the changelog in `docs/admin/release_notes/version_..md` file to reflect the changes. + - Change the version from `..-beta` to `..` in pyproject.toml. + - Set the PR to the main +- Ensure the tests for the PR pass. +- Merge the PR. +- Create a new tag: + - The tag should be in the form of `v..`. + - The title should be in the form of `v..`. + - The description should be the changes that were added to the `version_..md` document. +- If merged into `main`, then push from `main` to `develop`, in order to retain the merge commit created when the PR was merged +- A post release PR is created with. + - Change the version from `..` to `..-beta` pyproject.toml. + - Set the PR to the `develop`. + - Once tests pass, merge. diff --git a/docs/dev/dev_environment.md b/docs/dev/dev_environment.md new file mode 100644 index 0000000..eba2623 --- /dev/null +++ b/docs/dev/dev_environment.md @@ -0,0 +1,98 @@ +# Building Your Development Environment + +## Quickstart + +The development environment can be used in two ways: + +1. `Recommended` All services are spun up using Docker and a local mount so you can develop locally, but schema-enforcer is spun up within the Docker container. +2. With a local poetry environment if you wish to develop outside of Docker. + +This is a quick reference guide if you're already familiar with the development environment provided, which you can read more about later in this document. + +### Invoke + +The [Invoke](http://www.pyinvoke.org/) library is used to provide some helper commands based on the environment. There are a few configuration parameters which can be passed to Invoke to override the default configuration: + +- `local`: a boolean flag indicating if invoke tasks should be run on the host or inside the docker containers (default: False, commands will be run in docker containers) + +Using **Invoke** these configuration options can be overridden using [several methods](https://docs.pyinvoke.org/en/stable/concepts/configuration.html). Perhaps the simplest is setting an environment variable `INVOKE_SCHEMA-ENFORCER_VARIABLE_NAME` where `VARIABLE_NAME` is the variable you are trying to override. There is an example `invoke.yml` (`invoke.example.yml`) in this directory which can be used as a starting point. + +### Docker Development Environment + +!!! tip + This is the recommended option for development. + +This project is managed by [Python Poetry](https://python-poetry.org/) and has a few requirements to setup your development environment: + +1. Install Poetry, see the [Poetry Documentation](https://python-poetry.org/docs/#installation) for your operating system. +2. Install Docker, see the [Docker documentation](https://docs.docker.com/get-docker/) for your operating system. + +Once you have Poetry and Docker installed you can run the following commands (in the root of the repository) to install all other development dependencies in an isolated Python virtual environment: + +```shell +poetry shell +poetry install +invoke build +invoke start +``` + +Live documentation can be viewed at [http://localhost:8001](http://localhost:8001). + +To either stop or destroy the development environment use the following options. + +- **invoke stop** - Stop the containers, but keep all underlying systems intact +- **invoke destroy** - Stop and remove all containers, volumes, etc. (This results in data loss due to the volume being deleted) + +## Poetry + +Poetry is used in lieu of the "virtualenv" commands and is leveraged in both environments. The virtual environment will provide all of the Python packages required to manage the development environment such as **Invoke**. See the [Local Development Environment](#full-docker-development-environment) section to see how to install schema-enforcer if you're going to be developing locally (i.e. not using the Docker container). + +The `pyproject.toml` file outlines all of the relevant dependencies for the project: + +- `tool.poetry.dependencies` - the main list of dependencies. +- `tool.poetry.group.dev.dependencies` - development dependencies, to facilitate linting, testing, and documentation building. + +The `poetry shell` command is used to create and enable a virtual environment managed by Poetry, so all commands ran going forward are executed within the virtual environment. This is similar to running the `source venv/bin/activate` command with virtualenvs. To install project dependencies in the virtual environment, you should run `poetry install` - this will install **both** project and development dependencies. + +For more details about Poetry and its commands please check out its [online documentation](https://python-poetry.org/docs/). + +## Full Docker Development Environment + +This project is set up with a number of **Invoke** tasks consumed as simple CLI commands to get developing fast. You'll use a few `invoke` commands to get your environment up and running. + +## CLI Helper Commands + +The project features a CLI helper based on [invoke](http://www.pyinvoke.org/) to help setup the development environment. The commands are listed below in 3 categories: +- `dev environment` +- `utility` +- `testing` + +Each command can be executed with `invoke `. Each command also has its own help `invoke --help` + +### Local dev environment + +``` + build Build all docker images. + clean Remove the project specific image. + docs Build and serve docs locally. + rebuild Clean the Docker image and then rebuild without using cache. +``` + +### Utility + +``` + cli Enter the image to perform troubleshooting or dev work. + clean Remove stopped containers that source for image `schema-enforcer:` + generate-release-notes Generate Release Notes using Towncrier. +``` + +### Testing + +``` + autoformat (a) Run code autoformatting. + pylint Run pylint for the specified name and Python version. + ruff Run ruff to perform code formatting and/or linting. + pytest Run pytest for the specified name and Python version. + tests Run all tests for the specified name and Python version. + yamllint Run yamllint to validate formatting adheres to NTC defined YAML standards. +``` \ No newline at end of file diff --git a/docs/dev/extending.md b/docs/dev/extending.md new file mode 100644 index 0000000..e974b82 --- /dev/null +++ b/docs/dev/extending.md @@ -0,0 +1,4 @@ +# Extending the Library + +Extending the library is welcome, however it is best to open an issue first, to ensure that a PR would be accepted and makes sense in terms of features and design. + diff --git a/docs/dev/release_checklist.md b/docs/dev/release_checklist.md new file mode 100644 index 0000000..9ac859a --- /dev/null +++ b/docs/dev/release_checklist.md @@ -0,0 +1,192 @@ +# Release Checklist + +This document is intended for library maintainers and outlines the steps to perform when releasing a new version of the library. + +!!! important + Before starting, make sure your **local** `develop`, `main` are all up to date with upstream! + + ``` + git fetch + git switch develop && git pull + ``` + +Choose your own adventure: + +- Patch release from `develop`? Jump [here](#all-releases-from-develop). +- Minor release? Continue with [Minor Version Bumps](#minor-version-bumps) and then [All Releases from `develop`](#all-releases-from-develop). + +## Minor Version Bumps + +### Update Requirements + +Every minor version release should refresh `poetry.lock`, so that it lists the most recent stable release of each package. To do this: + +0. Run `poetry update --dry-run` to have Poetry automatically tell you what package updates are available and the versions it would upgrade to. This requires an existing environment created from the lock file (i.e. via `poetry install`). +1. Review each requirement's release notes for any breaking or otherwise noteworthy changes. +2. Run `poetry update ` to update the package versions in `poetry.lock` as appropriate. +3. If a required package requires updating to a new release not covered in the version constraints for a package as defined in `pyproject.toml`, (e.g. `Django ~3.1.7` would never install `Django >=4.0.0`), update it manually in `pyproject.toml`. +4. Run `poetry install` to install the refreshed versions of all required packages. +5. Run all tests (`poetry run invoke tests`) and check that the UI and API function as expected. + +### Update Documentation + +If there are any changes to the compatibility matrix (such as a bump in the minimum supported Nautobot version), update it accordingly. + +Commit any resulting changes from the following sections to the documentation before proceeding with the release. + +!!! tip + Fire up the documentation server in your development environment with `poetry run mkdocs serve`! This allows you to view the documentation site locally (the link is in the output of the command) and automatically rebuilds it as you make changes. + +### Verify the Installation and Upgrade Steps + +Follow the [installation instructions](../admin/install.md) to perform a new production installation of the library. If possible, also test the [upgrade process](../admin/upgrade.md) from the previous released version. + +The goal of this step is to walk through the entire install process *as documented* to make sure nothing there needs to be changed or updated, to catch any errors or omissions in the documentation, and to ensure that it is current with each release. + +--- + +## All Releases from `develop` + +### Verify CI Build Status + +Ensure that continuous integration testing on the `develop` branch is completing successfully. + +### Bump the Version + +Update the package version using `poetry version` if necessary. This command shows the current version of the project or bumps the version of the project and writes the new version back to `pyproject.toml` if a valid bump rule is provided. + +The new version must be a valid semver string or a valid bump rule: `patch`, `minor`, `major`, `prepatch`, `preminor`, `premajor`, `prerelease`. Always try to use a bump rule when you can. + +Display the current version with no arguments: + +```no-highlight +> poetry version +schema-enforcer 1.0.0-beta.2 +``` + +Bump pre-release versions using `prerelease`: + +```no-highlight +> poetry version prerelease +Bumping version from 1.0.0-beta.2 to 1.0.0-beta.3 +``` + +For major versions, use `major`: + +```no-highlight +> poetry version major +Bumping version from 1.0.0-beta.2 to 1.0.0 +``` + +For patch versions, use `minor`: + +```no-highlight +> poetry version minor +Bumping version from 1.0.0 to 1.1.0 +``` + +And lastly, for patch versions, you guessed it, use `patch`: + +```no-highlight +> poetry version patch +Bumping version from 1.1.0 to 1.1.1 +``` + +Please see the [official Poetry documentation on `version`](https://python-poetry.org/docs/cli/#version) for more information. + +### Update the Changelog + +!!! important + The changelog must adhere to the [Keep a Changelog](https://keepachangelog.com/) style guide. + +This guide uses `1.4.2` as the new version in its examples, so change it to match the version you bumped to in the previous step! Every. single. time. you. copy/paste commands :) + +First, create a release branch off of `develop` (`git switch -c release-1.4.2 develop`). + +> You will need to have the project's poetry environment built at this stage, as the towncrier command runs **locally only**. If you don't have it, run `poetry install` first. +Generate release notes with `invoke generate-release-notes --version 1.4.2` and answer `yes` to the prompt `Is it okay if I remove those files? [Y/n]:`. This will update the release notes in `docs/admin/release_notes/version_X.Y.md`, stage that file in git, and `git rm` all the fragments that have now been incorporated into the release notes. + +There are two possibilities: + +1. If you're releasing a new major or minor version, rename the `version_X.Y.md` file accordingly (e.g. rename to `docs/admin/release_notes/version_1.4.md`). Update the `Release Overview` and add this new page to the table of contents within `mkdocs.yml`. +2. If you're releasing a patch version, copy your version's section from the `version_X.Y.md` file into the already existing `docs/admin/release_notes/version_1.4.md` file. Delete the `version_X.Y.md` file. + +Stage all the changes (`git add`) and check the diffs to verify all of the changes are correct (`git diff --cached`). + +Commit `git commit -m "Release v1.4.2"` and `git push` the staged changes. + +### Submit Release Pull Request + +Submit a pull request titled `Release v1.4.2` to merge your release branch into `main`. Copy the documented release notes into the pull request's body. + +!!! important + Do not squash merge this branch into `main`. Make sure to select `Create a merge commit` when merging in GitHub. + +Once CI has completed on the PR, merge it. + +### Create a New Release in GitHub + +Draft a [new release](https://github.com/networktocode/schema-enforcer/releases/new) with the following parameters. + +* **Tag:** Input current version (e.g. `v1.4.2`) and select `Create new tag: v1.4.2 on publish` +* **Target:** `main` +* **Title:** Version and date (e.g. `v1.4.2 - 2024-04-02`) + +Click "Generate Release Notes" and edit the auto-generated content as follows: + +- Change the entries generated by GitHub to only the usernames of the contributors. e.g. `* Updated dockerfile by @ntc_user in https://github.com/networktocode/schema-enforcer/pull/123` -> `* @ntc_user`. + - This should give you the list for the new `Contributors` section. + - Make sure there are no duplicated entries. +- Replace the content of the `What's Changed` section with the description of changes from the release PR (what towncrier generated). +- If it exists, leave the `New Contributors` list as it is. + +The release notes should look as follows: + +```markdown +## What's Changed + +**Towncrier generated Changed/Fixed/Housekeeping etc. sections here** + +## Contributors + +* @alice +* @bob + +## New Contributors + +* @bob + +**Full Changelog**: https://github.com/networktocode/schema-enforcer/compare/v1.4.1...v1.4.2 +``` + +Publish the release! + +### Create a PR from `main` back to `develop` + +First, sync your `main` branch with upstream changes: `git switch main && git pull`. + +Create a new branch from `main` called `release-1.4.2-to-develop` and use `poetry version prepatch` to bump the development version to the next release. + +For example, if you just released `v1.4.2`: + +```no-highlight +> git switch -c release-1.4.2-to-develop main +Switched to a new branch 'release-1.4.2-to-develop' +> poetry version prepatch +Bumping version from 1.4.2 to 1.4.3a1 +> git add pyproject.toml && git commit -m "Bump version" +> git push +``` + +!!! important + Do not squash merge this branch into `develop`. Make sure to select `Create a merge commit` when merging in GitHub. + +Open a new PR from `release-1.4.2-to-develop` against `develop`, wait for CI to pass, and merge it. + +### Final checks + +At this stage, the CI should be running or finished for the `v1.4.2` tag and a package successfully published to PyPI and added into the GitHub Release. Double check that's the case. + +Documentation should also have been built for the tag on ReadTheDocs and if you're reading this page online, refresh it and look for the new version in the little version fly-out menu down at the bottom right of the page. + +All done! diff --git a/docs/generate_code_reference_pages.py b/docs/generate_code_reference_pages.py new file mode 100644 index 0000000..67556e2 --- /dev/null +++ b/docs/generate_code_reference_pages.py @@ -0,0 +1,20 @@ +"""Generate code reference pages.""" + +from pathlib import Path + +import mkdocs_gen_files + +for file_path in Path("schema-enforcer").rglob("*.py"): + module_path = file_path.with_suffix("") + doc_path = file_path.with_suffix(".md") + full_doc_path = Path("code-reference", doc_path) + + parts = list(module_path.parts) + if parts[-1] == "__init__": + parts = parts[:-1] + + with mkdocs_gen_files.open(full_doc_path, "w") as fd: + IDENTIFIER = ".".join(parts) + print(f"::: {IDENTIFIER}", file=fd) + + mkdocs_gen_files.set_edit_path(full_doc_path, file_path) diff --git a/docs/images/networktocode_logo.svg b/docs/images/networktocode_logo.svg new file mode 100644 index 0000000..348e524 --- /dev/null +++ b/docs/images/networktocode_logo.svg @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..f32fd72 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,6 @@ +--- +hide: + - navigation +--- + +--8<-- "README.md" diff --git a/docs/user/faq.md b/docs/user/faq.md new file mode 100644 index 0000000..318b08d --- /dev/null +++ b/docs/user/faq.md @@ -0,0 +1 @@ +# Frequently Asked Questions diff --git a/docs/user/lib_getting_started.md b/docs/user/lib_getting_started.md new file mode 100644 index 0000000..0d50fdb --- /dev/null +++ b/docs/user/lib_getting_started.md @@ -0,0 +1,19 @@ +# Getting Started with the Library + +This document provides a step-by-step tutorial on how to get the library going and how to use it. + +## Install the Library + +To install the library, please follow the instructions detailed in the [Installation Guide](../admin/install.md). + +## First steps with the Library + +!!! warning "Developer Note - Remove Me!" + What (with screenshots preferably) does it look like to perform the simplest workflow within the library once installed? + +## What are the next steps? + +!!! warning "Developer Note - Remove Me!" + After taking the first steps, what else could the users look at doing. + +You can check out the [Use Cases](./lib_use_cases.md) section for more examples. \ No newline at end of file diff --git a/docs/user/lib_overview.md b/docs/user/lib_overview.md new file mode 100644 index 0000000..062968f --- /dev/null +++ b/docs/user/lib_overview.md @@ -0,0 +1,16 @@ +# Library Overview + +This document provides an overview of the library including critical information and important considerations. + +## Description + + +## Audience (User Personas) - Who should use this Library? + +!!! warning "Developer Note - Remove Me!" + Who is this meant for/ who is the common user of this library? + +## Authors and Maintainers + +!!! warning "Developer Note - Remove Me!" + Add the team and/or the main individuals maintaining this project. Include historical maintainers as well. diff --git a/docs/user/lib_use_cases.md b/docs/user/lib_use_cases.md new file mode 100644 index 0000000..d8a999e --- /dev/null +++ b/docs/user/lib_use_cases.md @@ -0,0 +1,12 @@ +# Using the Library + +This document describes common use-cases and scenarios for this library. + +## General Usage + +## Use-cases and common workflows + +## Screenshots + +!!! warning "Developer Note - Remove Me!" + Ideally captures every view exposed by the Library. Should include a relevant dataset. diff --git a/example.invoke.yml b/example.invoke.yml new file mode 100644 index 0000000..a7a9f65 --- /dev/null +++ b/example.invoke.yml @@ -0,0 +1,7 @@ +--- +"schema-enforcer": + python_ver: "3.10" + local: false + # image_name: "schema-enforcer" + # image_ver: "latest" + # pwd: "." diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..91af916 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,140 @@ +--- +dev_addr: "127.0.0.1:8001" +edit_uri: "edit/main/schema-enforcer/docs" +site_dir: "schema-enforcer/static/schema-enforcer/docs" +site_name: "Schema Enforcer Documentation" +site_url: "https://schema-enforcer.readthedocs.io/en/latest/" +repo_url: "https://github.com/networktocode/schema-enforcer" +copyright: "Copyright © The Authors" +theme: + name: "material" + navigation_depth: 4 + custom_dir: "docs/assets/overrides" + hljs_languages: + - "python" + - "yaml" + features: + - "content.action.edit" + - "content.action.view" + - "content.code.copy" + - "navigation.footer" + - "navigation.indexes" + - "navigation.tabs" + - "navigation.tabs.sticky" + - "navigation.tracking" + - "search.highlight" + - "search.share" + - "search.suggest" + favicon: "assets/favicon.ico" + logo: "assets/networktocode_logo.svg" + palette: + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: "default" + primary: "black" + toggle: + icon: "material/weather-sunny" + name: "Switch to dark mode" + + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: "slate" + primary: "black" + toggle: + icon: "material/weather-night" + name: "Switch to light mode" +extra_css: + - "assets/extra.css" + +extra: + generator: false + ntc_sponsor: true + social: + - icon: "fontawesome/solid/rss" + link: "https://blog.networktocode.com/" + name: "Network to Code Blog" + - icon: "fontawesome/brands/youtube" + link: "https://www.youtube.com/channel/UCwBh-dDdoqzxXKyvTw3BuTw" + name: "Network to Code Videos" + - icon: "fontawesome/brands/slack" + link: "https://www.networktocode.com/community/" + name: "Network to Code Community" + - icon: "fontawesome/brands/github" + link: "https://github.com/networktocode/" + name: "GitHub Organization" + - icon: "fontawesome/brands/twitter" + link: "https://twitter.com/networktocode" + name: "Network to Code Twitter" +markdown_extensions: + - "markdown_version_annotations": + admonition_tag: "???" + - "admonition" + - "toc": + permalink: true + - "attr_list" + - "md_in_html" + - "markdown_data_tables": + base_path: "docs" + - "pymdownx.details" + # Need pymdownx.emoji for Grid icon search + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - "pymdownx.highlight": + anchor_linenums: true + - "pymdownx.inlinehilite" + - "pymdownx.snippets" + - "pymdownx.superfences": + custom_fences: + - name: "mermaid" + class: "mermaid" + format: !!python/name:pymdownx.superfences.fence_code_format + - "pymdownx.tabbed": + "alternate_style": true + - "pymdownx.tilde" + +plugins: + - "search" + - "gen-files": + scripts: + - "docs/generate_code_reference_pages.py" + - "glightbox": + manual: true # See https://blueswen.github.io/mkdocs-glightbox/flexibility/enable-by-image-or-page/ + - "section-index" + - "mkdocstrings": + default_handler: "python" + handlers: + python: + paths: ["."] + options: + heading_level: 1 + show_root_heading: true + show_root_members_full_path: true + show_source: false + +validation: + absolute_links: "warn" + anchors: "warn" + omitted_files: "warn" + unrecognized_links: "warn" + +nav: + - Overview: "index.md" + - User Guide: + - Library Overview: "user/lib_overview.md" + - Getting Started: "user/lib_getting_started.md" + - Using the Library: "user/lib_use_cases.md" + - Frequently Asked Questions: "user/faq.md" + - Administrator Guide: + - Install and Configure: "admin/install.md" + - Upgrade: "admin/upgrade.md" + - Uninstall: "admin/uninstall.md" + - Release Notes: + - "admin/release_notes/index.md" + - v1.0: "admin/release_notes/version_1.0.md" + - Developer Guide: + - Extending the Library: "dev/extending.md" + - Contributing to the Library: "dev/contributing.md" + - Development Environment: "dev/dev_environment.md" + - Release Checklist: "dev/release_checklist.md" + - Architecture Design Records: "dev/arch_decision.md" diff --git a/pyproject.toml b/pyproject.toml index 745861a..ae5e983 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,4 +1,5 @@ [tool.poetry] +<<<<<<< HEAD name = "schema-enforcer" version = "1.5.2a0" description = "Tool/Framework for testing structured data against schema definitions" @@ -7,6 +8,17 @@ license = "Apache-2.0" readme = "README.md" homepage = "https://github.com/networktocode/schema-enforcer" repository = "https://github.com/networktocode/schema-enforcer" +======= +name = "schema_enforcer" +version = "1.5.2" +description = "Tool/Framework for testing structured data against schema definitions" +authors = ["Network to Code, LLC "] +readme = "README.md" +homepage = "https://schema-enforcer.readthedocs.io/" +repository = "https://github.com/networktocode/schema-enforcer" +documentation = "https://schema-enforcer.readthedocs.io/" +license = "Apache-2.0" +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) classifiers = [ "Intended Audience :: Developers", "Development Status :: 5 - Production/Stable", @@ -15,6 +27,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", +<<<<<<< HEAD "Programming Language :: Python :: 3.14", ] include = [ @@ -93,6 +106,116 @@ no-docstring-rgx="^(_|test_)" disable = """, line-too-long, """ +======= +] +include = [ + "LICENSE", + "README.md", +] + +[tool.poetry.dependencies] +python = ">=3.10,<3.14" +click = "*" + +[tool.poetry.group.dev.dependencies] +coverage = "*" +pytest = "*" +mock = "*" +pyyaml = "^6.0.1" +pylint = "^3.1.0" +yamllint = "^1.35.1" +invoke = "^2.2.0" +toml = "^0.10.2" +attrs = "^23.2.0" +towncrier = ">=23.6.0,<=24.8.0" +ruff = "*" +Markdown = "*" + +[tool.poetry.group.docs.dependencies] +# Rendering docs to HTML +mkdocs = "1.6.1" +# Embedding YAML files into Markdown documents as tables +markdown-data-tables = "1.0.0" +# Render custom markdown for version added/changed/remove notes +markdown-version-annotations = "1.0.1" +# Automatically generate some files as part of mkdocs build +mkdocs-gen-files = "0.5.0" +# Image lightboxing in mkdocs +mkdocs-glightbox = "0.4.0" +# Use Jinja2 templating in docs - see settings.md +mkdocs-macros-plugin = "1.3.7" +# Material for mkdocs theme +mkdocs-material = "9.6.15" +# Handle docs redirections +mkdocs-redirects = "1.2.2" +# Automatically handle index pages for docs sections +mkdocs-section-index = "0.3.10" +# Automatic documentation from sources, for MkDocs +mkdocstrings = "0.27.0" +# Python-specific extension to mkdocstrings +mkdocstrings-python = "1.13.0" +griffe = "1.1.1" + +[tool.poetry.scripts] +schema_enforcer = 'schema_enforcer.cli:main' + + + +[tool.ruff] +line-length = 120 +target-version = "py310" + +[tool.ruff.lint] +select = [ + "D", # pydocstyle + "F", "E", "W", # flake8 + "S", # bandit + "I", # isort +] +ignore = [ + # warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. + "D203", # 1 blank line required before class docstring + + # D212 is enabled by default in google convention, and complains if we have a docstring like: + # """ + # My docstring is on the line after the opening quotes instead of on the same line as them. + # """ + # We've discussed and concluded that we consider this to be a valid style choice. + "D212", # Multi-line docstring summary should start at the first line + "D213", # Multi-line docstring summary should start at the second line + + # Produces a lot of issues in the current codebase. + "D401", # First line of docstring should be in imperative mood + "D407", # Missing dashed underline after section + "D416", # Section name ends in colon + "E501", # Line too long +] + +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.ruff.lint.per-file-ignores] +"tests/*" = [ + "D", + "S" +] + + +[tool.pylint.master] +ignore=[".venv", "tests"] + +[tool.pylint.basic] +# No docstrings required for private methods (Pylint default), or for test_ functions, or for inner Meta classes. +no-docstring-rgx = "^(_|test_|Meta$)" + + +[tool.pylint.messages_control] +disable = [ + "line-too-long", + "duplicate-code", + "cyclic-import", + ] +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) [tool.pylint.miscellaneous] # Don't flag TODO as a failure, let us commit with things that still need to be done in the code @@ -101,6 +224,7 @@ notes = """, XXX, """ +<<<<<<< HEAD [tool.pylint.SIMILARITIES] min-similarity-lines = 15 @@ -113,3 +237,73 @@ addopts = "-vv --doctest-modules" [build-system] requires = ["poetry-core>=2.0.0,<3.0.0"] build-backend = "poetry.core.masonry.api" +======= +[build-system] +requires = ["poetry-core>=2.0.0,<3.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.pytest.ini_options] +python_paths = "./" +testpaths = [ + "tests/" +] +addopts = "-vv --doctest-modules -p no:warnings --ignore-glob='*mock*'" + +[tool.towncrier] +package = "schema-enforcer" +directory = "changes" +filename = "docs/admin/release_notes/version_X.Y.md" +template = "towncrier_template.j2" +start_string = "" +issue_format = "[#{issue}](https://github.com/networktocode/schema-enforcer/issues/{issue})" + +[[tool.towncrier.type]] +directory = "breaking" +name = "Breaking Changes" +showcontent = true + +[[tool.towncrier.type]] +directory = "security" +name = "Security" +showcontent = true + +[[tool.towncrier.type]] +directory = "added" +name = "Added" +showcontent = true + +[[tool.towncrier.type]] +directory = "changed" +name = "Changed" +showcontent = true + +[[tool.towncrier.type]] +directory = "deprecated" +name = "Deprecated" +showcontent = true + +[[tool.towncrier.type]] +directory = "removed" +name = "Removed" +showcontent = true + +[[tool.towncrier.type]] +directory = "fixed" +name = "Fixed" +showcontent = true + +[[tool.towncrier.type]] +directory = "dependencies" +name = "Dependencies" +showcontent = true + +[[tool.towncrier.type]] +directory = "documentation" +name = "Documentation" +showcontent = true + +[[tool.towncrier.type]] +directory = "housekeeping" +name = "Housekeeping" +showcontent = true +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) diff --git a/schema_enforcer/__init__.py b/schema_enforcer/__init__.py index 2e9928e..97649d5 100644 --- a/schema_enforcer/__init__.py +++ b/schema_enforcer/__init__.py @@ -1,5 +1,11 @@ """Initialization file for library.""" +<<<<<<< HEAD # pylint: disable=C0114 __version__ = "1.1.3" +======= +from importlib import metadata + +__version__ = metadata.version(__name__) +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) diff --git a/schema_enforcer/api.py b/schema_enforcer/api.py new file mode 100644 index 0000000..a92b3f3 --- /dev/null +++ b/schema_enforcer/api.py @@ -0,0 +1,3 @@ +"""Example API.""" + +# Fill in with information regarding Python API for project diff --git a/schema_enforcer/cli.py b/schema_enforcer/cli.py index f49fd3c..09a9ae3 100644 --- a/schema_enforcer/cli.py +++ b/schema_enforcer/cli.py @@ -1,3 +1,4 @@ +<<<<<<< HEAD """main cli commands.""" import sys @@ -358,3 +359,32 @@ def ansible( print(colored("ALL SCHEMA VALIDATION CHECKS PASSED", "green")) else: sys.exit(1) +======= +"""Example cli using click.""" + +import logging + +import click + +from schema_enforcer.log import initialize_logging + +# Import necessary project related things to use in CLI + +log = logging.getLogger(__name__) + + +@click.command() +@click.option("--test", default="Test Output", help="Test argument") +@click.option( + "--log-level", + default="INFO", + type=click.Choice(["NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]), + help="Logging level", +) +@click.option("--log-file", default=None, help="Log file to output to debug logs to.") +def main(test, log_level, log_file): + """Entrypoint into CLI app.""" + initialize_logging(level=log_level, filename=log_file) + log.info("Entrypoint of the CLI app.") + print(test) +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) diff --git a/schema_enforcer/log.py b/schema_enforcer/log.py new file mode 100644 index 0000000..9bc3899 --- /dev/null +++ b/schema_enforcer/log.py @@ -0,0 +1,74 @@ +""" +Logging utilities for Schema Enforcer. + +This module contains helpers and wrappers for making logging more consistent across applications. + +How to use me: + + >>> from schema_enforcer.log import initialize_logging + >>> log = initialize_logging(level="debug") +""" + +import logging.config + +APP = "schema_enforcer" + + +def initialize_logging(config=None, level="INFO", filename=None): + """Initialize logging using sensible defaults. + + Args: + config (dict): User provided configuration dictionary. + level (str): The level of logging for STDOUT logging. + filename (str): Where to output debug logging to file. + + """ + if not config: + config = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "standard": { + "format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s", + "datefmt": "%Y-%m-%dT%H:%M:%S%z", + }, + "debug": { + "format": "%(asctime)s [%(levelname)s] [%(module)s] [%(funcName)s] %(name)s: %(message)s", + "datefmt": "%Y-%m-%dT%H:%M:%S%z", + }, + }, + "handlers": { + "standard": { + "class": "logging.StreamHandler", + "formatter": "standard", + "level": level.upper(), + }, + }, + "loggers": { + "": { + "handlers": ["standard"], + "level": "DEBUG", + } + }, + } + + # If a filename is passed in, let's add a FileHandler + if filename: + config["handlers"].update( + { + "file_output": { + "class": "logging.FileHandler", + "formatter": "debug", + "level": "DEBUG", + "filename": filename, + } + } + ) + config["loggers"][""]["handlers"].append("file_output") + + # Configure the logging + logging.config.dictConfig(config) + + # Initialize root logger and advise logging has been initialized + log = logging.getLogger(APP) + log.debug("Logging initialized.") diff --git a/tasks.py b/tasks.py index 85b36f3..ca03061 100644 --- a/tasks.py +++ b/tasks.py @@ -1,6 +1,7 @@ """Tasks for use with Invoke.""" import os +<<<<<<< HEAD import sys from invoke import task @@ -17,6 +18,13 @@ def project_ver(): """Find version from pyproject.toml to use for docker image tagging.""" with open("pyproject.toml", "rb") as config_file: return tomllib.load(config_file)["tool"]["poetry"].get("version", "latest") +======= +import re +from pathlib import Path + +from invoke import Collection, Exit +from invoke import task as invoke_task +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) def is_truthy(arg): @@ -25,7 +33,10 @@ def is_truthy(arg): Examples: >>> is_truthy('yes') True +<<<<<<< HEAD +======= +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) Args: arg (str): Truthy string (True values are y, yes, t, true, on and 1; false values are n, no, f, false, off and 0. Raises ValueError if val is anything else. @@ -36,6 +47,7 @@ def is_truthy(arg): val = str(arg).lower() if val in ("y", "yes", "t", "true", "on", "1"): return True +<<<<<<< HEAD elif val in ("n", "no", "f", "false", "off", "0"): return False else: @@ -85,10 +97,63 @@ def run_cmd(context, exec_cmd, with_ansible=False): context (invoke.task): Invoke task object. exec_cmd (str): Command to run. with_ansible (bool): Whether to run the command in a container that has ansible installed +======= + if val in ("n", "no", "f", "false", "off", "0"): + return False + raise ValueError(f"Invalid truthy value: `{arg}`") + + +# Use pyinvoke configuration for default values, see http://docs.pyinvoke.org/en/stable/concepts/configuration.html +# Variables may be overwritten in invoke.yml or by the environment variables INVOKE_SCHEMA-ENFORCER_xxx +namespace = Collection("schema_enforcer") +namespace.configure( + { + "schema_enforcer": { + "project_name": "schema_enforcer", + "python_ver": "3.10", + "local": is_truthy(os.getenv("INVOKE_SCHEMA-ENFORCER_LOCAL", "false")), + "image_name": "schema_enforcer", + "image_ver": os.getenv("INVOKE_SCHEMA-ENFORCER_IMAGE_VER", "latest"), + "pwd": Path(__file__).parent, + } + } +) + + +# pylint: disable=keyword-arg-before-vararg +def task(function=None, *args, **kwargs): + """Task decorator to override the default Invoke task decorator and add each task to the invoke namespace.""" + + def task_wrapper(function=None): + """Wrapper around invoke.task to add the task to the namespace as well.""" + if args or kwargs: + task_func = invoke_task(*args, **kwargs)(function) + else: + task_func = invoke_task(function) + namespace.add_task(task_func) + return task_func + + if function: + # The decorator was called with no arguments + return task_wrapper(function) + # The decorator was called with arguments + return task_wrapper + + +def run_command(context, exec_cmd, port=None, rm=True): + """Wrapper to run the invoke task commands. + + Args: + context ([invoke.task]): Invoke task object. + exec_cmd ([str]): Command to run. + port (int): Used to serve local docs. + rm (bool): Whether to remove the container after running the command. +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) Returns: result (obj): Contains Invoke result from running task. """ +<<<<<<< HEAD name = _get_image_name(with_ansible) if INVOKE_LOCAL: @@ -97,10 +162,30 @@ def run_cmd(context, exec_cmd, with_ansible=False): else: print(f"DOCKER - Running command: {exec_cmd} container: {name}") result = context.run(f"docker run -it -v {PWD}:/local {name} sh -c '{exec_cmd}'", pty=True) +======= + if is_truthy(context.schema_enforcer.local): + print(f"LOCAL - Running command {exec_cmd}") + result = context.run(exec_cmd, pty=True) + else: + print( + f"DOCKER - Running command: {exec_cmd} container: {context.schema_enforcer.image_name}:{context.schema_enforcer.image_ver}" + ) + if port: + result = context.run( + f"docker run -it {'--rm' if rm else ''} -p {port} -v {context.schema_enforcer.pwd}:/local {context.schema_enforcer.image_name}:{context.schema_enforcer.image_ver} sh -c '{exec_cmd}'", + pty=True, + ) + else: + result = context.run( + f"docker run -it {'--rm' if rm else ''} -v {context.schema_enforcer.pwd}:/local {context.schema_enforcer.image_name}:{context.schema_enforcer.image_ver} sh -c '{exec_cmd}'", + pty=True, + ) +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) return result +<<<<<<< HEAD @task def build_image( context, cache=True, force_rm=False, hide=False, with_ansible=False @@ -152,6 +237,11 @@ def clean_image(context, with_ansible=False): context.run(f"docker rmi {name} --force") +======= +# ------------------------------------------------------------------------------ +# BUILD +# ------------------------------------------------------------------------------ +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) @task( help={ "cache": "Whether to use Docker's cache when building images (default enabled)", @@ -160,6 +250,7 @@ def clean_image(context, with_ansible=False): } ) def build(context, cache=True, force_rm=False, hide=False): +<<<<<<< HEAD """This will build an image with the provided name and python version. Args: @@ -170,10 +261,47 @@ def build(context, cache=True, force_rm=False, hide=False): """ build_image(context, cache, force_rm, hide=hide) build_image(context, cache, force_rm, hide=hide, with_ansible=True) +======= + """Build a Docker image.""" + print(f"Building image {context.schema_enforcer.image_name}:{context.schema_enforcer.image_ver}") + command = f"docker build --tag {context.schema_enforcer.image_name}:{context.schema_enforcer.image_ver} --build-arg PYTHON_VER={context.schema_enforcer.python_ver} -f Dockerfile ." + + if not cache: + command += " --no-cache" + if force_rm: + command += " --force-rm" + + result = context.run(command, hide=hide) + if result.exited != 0: + print( + f"Failed to build image {context.schema_enforcer.image_name}:{context.schema_enforcer.image_ver}\nError: {result.stderr}" + ) + + +@task +def generate_packages(context): + """Generate all Python packages inside docker and copy the file locally under dist/.""" + command = "poetry build" + run_command(context, command) + + +@task( + help={ + "check": ( + "If enabled, check for outdated dependencies in the poetry.lock file, " + "instead of generating a new one. (default: disabled)" + ) + } +) +def lock(context, check=False): + """Generate poetry.lock inside the library container.""" + run_command(context, f"poetry {'check' if check else 'lock --no-update'}") +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) @task def clean(context): +<<<<<<< HEAD """This will remove a specific image. Args: @@ -241,21 +369,123 @@ def flake8(context): """ exec_cmd = "flake8 ." run_cmd(context, exec_cmd, with_ansible=True) +======= + """Remove the project specific image.""" + print( + f"Attempting to forcefully remove image {context.schema_enforcer.image_name}:{context.schema_enforcer.image_ver}" + ) + context.run(f"docker rmi {context.schema_enforcer.image_name}:{context.schema_enforcer.image_ver} --force") + print(f"Successfully removed image {context.schema_enforcer.image_name}:{context.schema_enforcer.image_ver}") + + +@task +def rebuild(context): + """Clean the Docker image and then rebuild without using cache.""" + clean(context) + build(context, cache=False) + + +@task +def coverage(context): + """Run the coverage report against pytest.""" + exec_cmd = "coverage run --source=schema_enforcer -m pytest" + run_command(context, exec_cmd) + run_command(context, "coverage report") + run_command(context, "coverage html") + + +@task( + help={ + "pattern": "Only run tests which match the given substring. Can be used multiple times.", + "label": "Module path to run (e.g., tests/unit/test_foo.py). Can be used multiple times.", + }, + iterable=["pattern", "label"], +) +def pytest(context, pattern=None, label=None): + """Run pytest test cases.""" + exec_cmd = "pytest -vv --doctest-modules schema_enforcer/ && coverage run --source=schema_enforcer -m pytest && coverage report" + run_command(context, exec_cmd) + + doc_test_cmd = "pytest -vv --doctest-modules schema_enforcer/" + pytest_cmd = "coverage run --source=schema_enforcer -m pytest" + if pattern: + pytest_cmd += "".join([f" -k {_pattern}" for _pattern in pattern]) + if label: + pytest_cmd += "".join([f" {_label}" for _label in label]) + coverage_cmd = "coverage report" + exec_cmd = " && ".join([doc_test_cmd, pytest_cmd, coverage_cmd]) + run_command(context, exec_cmd) + + +@task(aliases=("a",)) +def autoformat(context): + """Run code autoformatting.""" + ruff(context, action=["format"], fix=True) + + +@task( + help={ + "action": "Available values are `['lint', 'format']`. Can be used multiple times. (default: `['lint', 'format']`)", + "target": "File or directory to inspect, repeatable (default: all files in the project will be inspected)", + "fix": "Automatically fix selected actions. May not be able to fix all issues found. (default: False)", + "output_format": "See https://docs.astral.sh/ruff/settings/#output-format for details. (default: `concise`)", + }, + iterable=["action", "target"], +) +def ruff(context, action=None, target=None, fix=False, output_format="concise"): + """Run ruff to perform code formatting and/or linting.""" + if not action: + action = ["lint", "format"] + if not target: + target = ["."] + + exit_code = 0 + + if "format" in action: + command = "ruff format " + if not fix: + command += "--check " + command += " ".join(target) + if not run_command(context, command): + exit_code = 1 + + if "lint" in action: + command = "ruff check " + if fix: + command += "--fix " + command += f"--output-format {output_format} " + command += " ".join(target) + if not run_command(context, command): + exit_code = 1 + + if exit_code != 0: + raise Exit(code=exit_code) +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) @task def pylint(context): +<<<<<<< HEAD """This will run pylint for the specified name and Python version. +======= + """Run pylint for the specified name and Python version. +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) Args: context (obj): Used to run specific commands """ +<<<<<<< HEAD exec_cmd = "pylint **/*.py" run_cmd(context, exec_cmd, with_ansible=True) +======= + exec_cmd = 'find . -name "*.py" | grep -vE "tests/unit" | xargs pylint' + run_command(context, exec_cmd) +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) @task def yamllint(context): +<<<<<<< HEAD """This will run yamllint to validate formatting adheres to NTC defined YAML standards. Args: @@ -271,10 +501,25 @@ def yamllint(context): @task def pydocstyle(context): """This will run pydocstyle to validate docstring formatting adheres to NTC defined standards. +======= + """Run yamllint to validate formatting adheres to NTC defined YAML standards. Args: context (obj): Used to run specific commands """ + exec_cmd = "yamllint ." + run_command(context, exec_cmd) + + +@task +def cli(context): + """Enter the image to perform troubleshooting or dev work. +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) + + Args: + context (obj): Used to run specific commands + """ +<<<<<<< HEAD exec_cmd = "pydocstyle ." run_cmd(context, exec_cmd, with_ansible=True) @@ -305,10 +550,44 @@ def tests(context): bandit(context) pytest(context) pytest_without_ansible(context) +======= + dev = f"docker run -it -v {context.schema_enforcer.pwd}:/local {context.schema_enforcer.image_name}:{context.schema_enforcer.image_ver} /bin/bash" + context.run(f"{dev}", pty=True) + + +@task( + help={ + "lint-only": "Only run linters; unit tests will be excluded. (default: False)", + } +) +def tests(context, lint_only=False): + """Run all tests for the specified name and Python version. + + Args: + context (obj): Used to run specific commands + lint_only (bool): If True, only run linters and skip unit tests. + """ + # If we are not running locally, start the docker containers so we don't have to for each test + # Sorted loosely from fastest to slowest + print("Running ruff...") + ruff(context) + print("Running yamllint...") + yamllint(context) + print("Running poetry check...") + lock(context, check=True) + print("Running pylint...") + pylint(context) + print("Running mkdocs...") + build_and_check_docs(context) + if not lint_only: + print("Running unit tests...") + pytest(context) +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) print("All tests have passed!") @task +<<<<<<< HEAD def cli(context, with_ansible=False): """This will enter the image to perform troubleshooting or dev work. @@ -319,3 +598,48 @@ def cli(context, with_ansible=False): name = _get_image_name(with_ansible) dev = f"docker run -it -v {PWD}:/local {name} /bin/bash" context.run(f"{dev}", pty=True) +======= +def build_and_check_docs(context): + """Build documentation and test the configuration.""" + command = "mkdocs build --no-directory-urls --strict" + run_command(context, command) + + # Check for the existence of a release notes file for the current version if it's not a prerelease. + version = context.run("poetry version --short", hide=True) + match = re.match(r"^(\d+)\.(\d+)\.\d+$", version.stdout.strip()) + if match: + major = match.group(1) + minor = match.group(2) + release_notes_file = Path(__file__).parent / "docs" / "admin" / "release_notes" / f"version_{major}.{minor}.md" + if not release_notes_file.exists(): + print(f"Release notes file `version_{major}.{minor}.md` does not exist.") + raise Exit(code=1) + + +@task +def docs(context): + """Build and serve docs locally for development.""" + exec_cmd = "mkdocs serve -v" + run_command(context, exec_cmd, port="8001:8001") + + +@task( + help={ + "version": "Version of schema_enforcer to generate the release notes for.", + "date": "Date of the release (default: today).", + } +) +def generate_release_notes(context, version="", date=""): + """Generate Release Notes using Towncrier.""" + if not version: + version = context.run("poetry version --short", hide=True).stdout.strip() + + version_major_minor = ".".join(version.split(".")[:2]) + context.run(f"poetry run python bin/ensure_release_notes.py --version {version_major_minor}") + + command = f"poetry run towncrier build --version {version} --yes" + if date: + command += f" --date {date}" + # Due to issues with git repo ownership in the containers, this must always run locally. + context.run(command) +>>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 0000000..c66cd71 --- /dev/null +++ b/tests/integration/__init__.py @@ -0,0 +1 @@ +"""Integration tests package.""" diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..ea3f8b9 --- /dev/null +++ b/tests/unit/__init__.py @@ -0,0 +1 @@ +"""Unit tests package.""" diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py new file mode 100644 index 0000000..b917a15 --- /dev/null +++ b/tests/unit/conftest.py @@ -0,0 +1,10 @@ +"""Used to setup fixtures to be used through tests""" + +import pytest +from click.testing import CliRunner + + +@pytest.fixture +def cli_runner(): + """Provide CLI runner for Click tests.""" + return CliRunner() diff --git a/tests/unit/test_basics.py b/tests/unit/test_basics.py new file mode 100644 index 0000000..c14ba4d --- /dev/null +++ b/tests/unit/test_basics.py @@ -0,0 +1,29 @@ +"""Basic tests that do not require Schema Enforcer.""" + +import os +import re +import unittest + +import toml + + +class TestDocsReleaseNotes(unittest.TestCase): + """Test that mkdocs has the release notes for the current version.""" + + def test_version_file_found(self): + """Verify that if the current version has no letters, which would see in alpha or beta has an associated release note file.""" + parent_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) + poetry_path = os.path.join(parent_path, "pyproject.toml") + project_version = toml.load(poetry_path)["tool"]["poetry"]["version"] + + docs_path = os.path.join(parent_path, "docs") + release_notes_files = [file for file in os.listdir(f"{docs_path}/admin/release_notes/") if file.endswith(".md")] + version_pattern = re.compile(r"^(\d+)\.(\d+)\.\d+$") + + match = version_pattern.match(project_version) + # If there is no match, then it is likely an alpha or beta version and we can skip this test. + if match: + major, minor = match.groups() + version_str = f"version_{major}.{minor}.md" + if version_str not in release_notes_files: + self.fail(f"Release note file for version {version_str} not found in release notes folder.") diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py new file mode 100644 index 0000000..2bb8272 --- /dev/null +++ b/tests/unit/test_cli.py @@ -0,0 +1,16 @@ +"""Example Test using Fixtures.""" + +import mock + +from schema_enforcer import cli + + +@mock.patch("schema_enforcer.cli.log") +def test_cli_logging(log, cli_runner): + """Assert our logging gets called in CLI app.""" + result = cli_runner.invoke(cli.main, ["--test", "ntc"]) + + assert result.exit_code == 0 + assert result.output == "ntc\n" + log.info.assert_called() + log.info.assert_called_with("Entrypoint of the CLI app.") diff --git a/tests/unit/test_logging.py b/tests/unit/test_logging.py new file mode 100644 index 0000000..c1d18f1 --- /dev/null +++ b/tests/unit/test_logging.py @@ -0,0 +1,58 @@ +"""Validate schema_enforcer logging works.""" + +import mock + +import schema_enforcer + + +@mock.patch("logging.config.dictConfig") +@mock.patch("logging.getLogger") +def test_initialize_logging_default(get_logger, basic_cfg): + """Test initialize_logging using defaults.""" + schema_enforcer.log.initialize_logging() + + basic_cfg.assert_called_once() + initial_call = basic_cfg.mock_calls[0].args[0] + assert set(initial_call.keys()) == set(["version", "disable_existing_loggers", "formatters", "handlers", "loggers"]) + assert initial_call["handlers"]["standard"]["level"] == "INFO" + + get_logger.assert_called_once() + assert get_logger.mock_calls[0].args == ("schema_enforcer",) + assert get_logger.mock_calls[1].args == ("Logging initialized.",) + + +@mock.patch("logging.config.dictConfig") +@mock.patch("logging.getLogger") +def test_initialize_logging_user_defined_config(get_logger, basic_cfg): + """Test initialize_logging with user defined config.""" + config = {"version": 1, "disable_existing_loggers": False} + schema_enforcer.log.initialize_logging(config=config) + + basic_cfg.assert_called_once() + initial_call = basic_cfg.mock_calls[0].args[0] + assert initial_call == config + + get_logger.assert_called_once() + assert get_logger.mock_calls[0].args == ("schema_enforcer",) + assert get_logger.mock_calls[1].args == ("Logging initialized.",) + + +@mock.patch("logging.config.dictConfig") +@mock.patch("logging.getLogger") +def test_initialize_logging_filename(get_logger, basic_cfg): + """Test initialize_logging with filename.""" + schema_enforcer.log.initialize_logging(filename="output.log") + + basic_cfg.assert_called_once() + initial_call = basic_cfg.mock_calls[0].args[0] + assert set(initial_call.keys()) == set(["version", "disable_existing_loggers", "formatters", "handlers", "loggers"]) + assert initial_call["handlers"]["standard"]["level"] == "INFO" + assert initial_call["handlers"]["file_output"]["filename"] == "output.log" + assert initial_call["handlers"]["file_output"]["level"] == "DEBUG" + assert initial_call["handlers"]["file_output"]["formatter"] == "debug" + assert initial_call["handlers"]["file_output"]["class"] == "logging.FileHandler" + assert "file_output" in initial_call["loggers"][""]["handlers"] + + get_logger.assert_called_once() + assert get_logger.mock_calls[0].args == ("schema_enforcer",) + assert get_logger.mock_calls[1].args == ("Logging initialized.",) diff --git a/towncrier_header.txt b/towncrier_header.txt new file mode 100644 index 0000000..a350a18 --- /dev/null +++ b/towncrier_header.txt @@ -0,0 +1,9 @@ +# v{version} Release Notes + +This document describes all new features and changes in the release. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Release Overview + +- Major features or milestones + + diff --git a/towncrier_template.j2 b/towncrier_template.j2 new file mode 100644 index 0000000..94c509e --- /dev/null +++ b/towncrier_template.j2 @@ -0,0 +1,43 @@ + +# v{{ versiondata.version.split(".")[:2] | join(".") }} Release Notes + +This document describes all new features and changes in the release. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Release Overview + +- Major features or milestones +- Changes to compatibility with Nautobot and/or other apps, libraries etc. + +{% if render_title %} +## [v{{ versiondata.version }} ({{ versiondata.date }})](https://github.com/networktocode/schema-enforcer/releases/tag/v{{ versiondata.version}}) + +{% endif %} +{% for section, _ in sections.items() %} +{% if sections[section] %} +{% for category, val in definitions.items() if category in sections[section] %} +{% if sections[section][category]|length != 0 %} +### {{ definitions[category]['name'] }} + +{% if definitions[category]['showcontent'] %} +{% for text, values in sections[section][category].items() %} +{% for item in text.split('\n') %} +{% if values %} +- {{ values|join(', ') }} - {{ item.strip() }} +{% else %} +- {{ item.strip() }} +{% endif %} +{% endfor %} +{% endfor %} + +{% else %} +- {{ sections[section][category]['']|join(', ') }} + +{% endif %} +{% endif %} +{% endfor %} +{% else %} +No significant changes. + +{% endif %} +{% endfor %} + From b89c1c4faaeb032eaacbaa8280954d623b2293d6 Mon Sep 17 00:00:00 2001 From: Jeff Kala Date: Fri, 7 Aug 2026 10:10:00 -0600 Subject: [PATCH 2/6] first pass of conflict resolution and cookie setup --- .bandit.yml | 6 - .dockerignore | 12 - .flake8 | 5 - .github/CODEOWNERS | 16 -- .github/ISSUE_TEMPLATE/bug_report.md | 4 - .github/ISSUE_TEMPLATE/feature_request.md | 4 - .../pull_request_template.md | 6 - .github/workflows/ci.yml | 251 ------------------ .gitignore | 14 - .pydocstyle.ini | 5 - .yamllint.yml | 4 - CONTRIBUTING.md | 12 - Dockerfile | 25 -- LICENSE | 17 -- README.md | 217 +-------------- docs/{ => admin}/configuration.md | 6 +- docs/admin/install.md | 45 ++++ docs/admin/release_notes/version_1.0.md | 35 +-- .../admin/release_notes/version_1.1.md | 48 +--- docs/admin/release_notes/version_1.2.md | 28 ++ docs/admin/release_notes/version_1.3.md | 11 + docs/admin/release_notes/version_1.4.md | 11 + docs/admin/release_notes/version_1.5.md | 19 ++ docs/{ => user}/ansible_command.md | 0 docs/{ => user}/custom_validators.md | 0 docs/user/lib_getting_started.md | 128 ++++++++- docs/user/lib_overview.md | 15 +- docs/user/lib_use_cases.md | 15 +- .../mapping_data_files_to_schemas.md | 0 docs/{ => user}/schema_command.md | 0 docs/{ => user}/validate_command.md | 0 pyproject.toml | 81 +----- schema_enforcer/__init__.py | 6 - schema_enforcer/api.py | 3 - schema_enforcer/log.py | 74 ------ tests/conftest.py | 53 ---- tests/unit/conftest.py | 57 +++- tests/{ => unit}/test_ansible_inventory.py | 0 tests/unit/test_cli.py | 16 -- tests/{ => unit}/test_cli_ansible_exists.py | 0 .../{ => unit}/test_cli_ansible_not_exists.py | 0 tests/{ => unit}/test_config_settings.py | 0 .../test_instances_instance_file.py | 0 .../test_instances_instance_file_manager.py | 0 tests/{ => unit}/test_jsonschema.py | 0 tests/unit/test_logging.py | 58 ---- .../test_schemas_pydantic_validators.py | 0 .../{ => unit}/test_schemas_schema_manager.py | 0 tests/{ => unit}/test_schemas_validator.py | 0 tests/{ => unit}/test_utils.py | 0 tests/{ => unit}/test_validator.py | 0 51 files changed, 327 insertions(+), 980 deletions(-) delete mode 100644 .bandit.yml delete mode 100644 .flake8 delete mode 100644 .pydocstyle.ini delete mode 100644 CONTRIBUTING.md rename docs/{ => admin}/configuration.md (80%) rename CHANGELOG.md => docs/admin/release_notes/version_1.1.md (70%) create mode 100644 docs/admin/release_notes/version_1.2.md create mode 100644 docs/admin/release_notes/version_1.3.md create mode 100644 docs/admin/release_notes/version_1.4.md create mode 100644 docs/admin/release_notes/version_1.5.md rename docs/{ => user}/ansible_command.md (100%) rename docs/{ => user}/custom_validators.md (100%) rename docs/{ => user}/mapping_data_files_to_schemas.md (100%) rename docs/{ => user}/schema_command.md (100%) rename docs/{ => user}/validate_command.md (100%) delete mode 100644 schema_enforcer/api.py delete mode 100644 schema_enforcer/log.py delete mode 100644 tests/conftest.py rename tests/{ => unit}/test_ansible_inventory.py (100%) delete mode 100644 tests/unit/test_cli.py rename tests/{ => unit}/test_cli_ansible_exists.py (100%) rename tests/{ => unit}/test_cli_ansible_not_exists.py (100%) rename tests/{ => unit}/test_config_settings.py (100%) rename tests/{ => unit}/test_instances_instance_file.py (100%) rename tests/{ => unit}/test_instances_instance_file_manager.py (100%) rename tests/{ => unit}/test_jsonschema.py (100%) delete mode 100644 tests/unit/test_logging.py rename tests/{ => unit}/test_schemas_pydantic_validators.py (100%) rename tests/{ => unit}/test_schemas_schema_manager.py (100%) rename tests/{ => unit}/test_schemas_validator.py (100%) rename tests/{ => unit}/test_utils.py (100%) rename tests/{ => unit}/test_validator.py (100%) diff --git a/.bandit.yml b/.bandit.yml deleted file mode 100644 index 56f7a83..0000000 --- a/.bandit.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -skips: [] -# No need to check for security issues in the test scripts! -exclude_dirs: - - "./tests/" - - "./.venv/" diff --git a/.dockerignore b/.dockerignore index 3394303..2270f49 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,14 +1,3 @@ -<<<<<<< HEAD -**/*.pyc -**/*.pyo -**/*.log -.git/ -.gitignore -Dockerfile -docker-compose.yml -.env -docs/_build -======= # Docker related development/Dockerfile development/docker-compose*.yml @@ -36,4 +25,3 @@ LICENSE **/.vscode/ invoke*.yml tasks.py ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) diff --git a/.flake8 b/.flake8 deleted file mode 100644 index f1227b1..0000000 --- a/.flake8 +++ /dev/null @@ -1,5 +0,0 @@ -[flake8] -# E501: Line length is enforced by Black, so flake8 doesn't need to check it -# W503: Black disagrees with this rule, as does PEP 8; Black wins -ignore = E501, W503 -exclude = .venv \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4f83244..34fac01 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,18 +1,2 @@ -<<<<<<< HEAD - -# This is a comment. -# Each line is a file pattern followed by one or more owners. -# See: https://docs.github.com/en/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/about-code-owners - -# These owners will be the default owners for everything in the repo. -# Unless a later match takes precedence, these will be requested for -# review when someone opens a pull request. Once approved, PR creators -# are encouraged to merge their own PRs. -* @cmsirbu @glennmatthews - -# Order is important; the last matching pattern takes the most -# precedence. -======= # Default owner(s) of all files in this repository * @cmsirbu @glennmatthews ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 0fe1e0a..6b200d3 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,11 +4,7 @@ about: Report a reproducible bug in the current release of schema-enforcer --- ### Environment -<<<<<<< HEAD -* Python version: -======= * Python version: ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) * schema-enforcer version: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index ba9e68d..393609c 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -5,10 +5,6 @@ about: Propose a new feature or enhancement --- ### Environment -<<<<<<< HEAD -* Python version: -======= ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) * schema-enforcer version: -

- +
@@ -224,7 +12,7 @@ To avoid extra work and temporary links, make sure that publishing docs (or merg ## Overview -> Developer Note: Add a long (2-3 paragraphs) description of what the library does, what problems it solves, etc. +Schema Enforcer provides a framework for testing structured data against schema definitions using [JSONSchema](https://json-schema.org/understanding-json-schema/index.html). ## Documentation @@ -247,4 +35,3 @@ Any PRs with fixes or improvements are very welcome! ## Questions For any questions or comments, please check the [FAQ](https://schema-enforcer.readthedocs.io/en/latest/user/faq/) first. Feel free to also swing by the [Network to Code Slack](https://networktocode.slack.com/) (channel `#networktocode`), sign up [here](http://slack.networktocode.com/) if you don't have an account. ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) diff --git a/docs/configuration.md b/docs/admin/configuration.md similarity index 80% rename from docs/configuration.md rename to docs/admin/configuration.md index e308c9d..676047b 100644 --- a/docs/configuration.md +++ b/docs/admin/configuration.md @@ -1,6 +1,6 @@ # Configuration -Various settings can be configured in [TOML format](https://toml.io/en/) by use of a pyproject.toml file in the folder from which the tool is run. A set of intuitive default configuration values exist. If a pyproject.toml file is defined, it will override the defaults for settings it declares, and leave the defaults in place for settings it does not declare. +Various settings can be configured in [TOML format](https://toml.io/en/) by use of a `pyproject.toml` file in the folder from which the tool is run. A set of intuitive default configuration values exist. If a `pyproject.toml` file is defined, it will override the defaults for settings it declares, and leave the defaults in place for settings it does not declare. ## Customizing Project Config @@ -8,7 +8,7 @@ The CLI tool uses a configuration section beginning with `tool.schema_enforcer` ### Default Configuration Settings -The following parameters can be specified within the pyproject.toml file used to configure the `schema enforcer` tool. The below text snippet lists the default for each of these configuration parameters. If a pyproject.toml file defines a subset of the available parameters, this susbset defined will override the defaults. Any parameter not defined in the pyproject.toml file will fall back to it's default value (as listed below). +The following parameters can be specified within the `pyproject.toml` file used to configure the `schema enforcer` tool. The below text snippet lists the default for each of these configuration parameters. If a `pyproject.toml` file defines a subset of the available parameters, this susbset defined will override the defaults. Any parameter not defined in the `pyproject.toml` file will fall back to it's default value (as listed below). ```toml [tool.schema_enforcer] @@ -49,4 +49,4 @@ The table below enumerates each individual setting, it's expected type, it's def | data_file_exclude_filenames | list | [".yamllint.yml", ".travis.yml"] | The list of filenames to exclude when searching for structured data files | | data_file_automap | bool | true | Whether or not to map top level keys in a data file to the top level properties defined in a schema | | ansible_inventory | str | None | The ansible inventory file to use when building an inventory of hosts against which to check for schema adherence | -| schema_mapping | dict | {} | A mapping of structured data file names (keys) to lists of schema IDs (values) against which the data file should be checked for adherence | \ No newline at end of file +| schema_mapping | dict | {} | A mapping of structured data file names (keys) to lists of schema IDs (values) against which the data file should be checked for adherence | diff --git a/docs/admin/install.md b/docs/admin/install.md index 9bd1215..e5239a6 100644 --- a/docs/admin/install.md +++ b/docs/admin/install.md @@ -20,3 +20,48 @@ Option 3: Install from a GitHub branch, such as develop as shown below. ```bash pip install git+https://github.com/networktocode/schema-enforcer.git@develop ``` + +## Configuration Settings + +Schema enforcer will work with default settings, however, a `pyproject.toml` file can be placed at the root of the path in which `schema-enforcer` is run in order to override default settings or declare configuration for more advanced features. Inside of this `pyproject.toml` file, `tool.schema_enforcer` sections can be used to declare settings for schema enforcer. Take for example the `pyproject.toml` file in example 2. + +```shell +bash$ cd examples/example2 && tree -L 2 +. +├── README.md +├── hostvars +│ ├── chi-beijing-rt1 +│ ├── eng-london-rt1 +│ └── ger-berlin-rt1 +├── invalid +├── pyproject.toml +└── schema + ├── definitions + └── schemas + +8 directories, 2 files +``` + +In this toml file, a schema mapping is declared which tells schema enforcer which structured data files should be checked by which schema IDs. + + +```shell +bash$ cat pyproject.toml +[tool.schema_enforcer.schema_mapping] +# Map structured data filename to schema IDs +'dns_v1.yml' = ['schemas/dns_servers'] +'dns_v2.yml' = ['schemas/dns_servers_v2'] +'syslog.yml' = ['schemas/syslog_servers'] +``` + +> More information on available configuration settings can be found in the [configuration](../admin/configuration.md) + +## Supported Formats + +By default, schema enforcer installs the jsonschema `format_nongpl` extra (in version <1.2.0) or `format-nongpl` (in versions >=1.2.0). This extra allows the use of formats that can be used in schema definitions (e.g. ipv4, hostname...etc). The `format_nongpl` or `format-nongpl` extra only installs transitive dependencies that are not licensed under GPL. The `iri` and `iri-reference` formats are defined by the `rfc3987` transitive dependency which is licensed under GPL. As such, `iri` and `iri-reference` formats are *not* supported by `format-nongpl`/`format_nongpl`. If you have a need to use `iri` and/or `iri-reference` formats, you can do so by running the following pip command (or it's poetry equivalent): + +``` +pip install 'jsonschema[rfc3987]' +``` + +See the "Validating Formats" section in the [jsonschema documentation](https://github.com/python-jsonschema/jsonschema/blob/main/docs/validate.rst) for more information. diff --git a/docs/admin/release_notes/version_1.0.md b/docs/admin/release_notes/version_1.0.md index e0d8167..6b80e28 100644 --- a/docs/admin/release_notes/version_1.0.md +++ b/docs/admin/release_notes/version_1.0.md @@ -1,40 +1,11 @@ # v1.0 Release Notes -!!! warning "Developer Note - Remove Me!" - Guiding Principles: - - - Changelogs are for humans, not machines. - - There should be an entry for every single version. - - The same types of changes should be grouped. - - Versions and sections should be linkable. - - The latest version comes first. - - The release date of each version is displayed. - - Mention whether you follow Semantic Versioning. - - Types of changes: - - - `Added` for new features. - - `Changed` for changes in existing functionality. - - `Deprecated` for soon-to-be removed features. - - `Removed` for now removed features. - - `Fixed` for any bug fixes. - - `Security` in case of vulnerabilities. - - This document describes all new features and changes in the release `1.0`. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Release Overview -- Major features or milestones -- Achieved in this `x.y` release -- Changes to compatibility with Nautobot and/or other apps, libraries etc. - -## [v1.0.0] - 2026-08-06 - -### Added - -### Changed +- Initial Major Release -### Fixed +## v1.0.0 - 2021-01-26 -- [#123](https://github.com/networktocode/schema-enforcer/issues/123) Fixed Tag filtering not working in job launch form. +Schema Enforcer Initial Release diff --git a/CHANGELOG.md b/docs/admin/release_notes/version_1.1.md similarity index 70% rename from CHANGELOG.md rename to docs/admin/release_notes/version_1.1.md index 3f94949..3dd8338 100644 --- a/CHANGELOG.md +++ b/docs/admin/release_notes/version_1.1.md @@ -1,44 +1,12 @@ -# Changelog +# v1.1 Release Notes -## v1.5.1 - 2025-11-12 +This document describes all new features and changes in the release `1.1`. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -- #179 Added Python 3.13 support. +## Release Overview -## v1.5.0 - 2025-11-10 - -This housekeeping release updates the supported Python version to a 3.10 minimum and the optional Ansible dependency to 2.16. It also updated most dependencies, fixed tests, and CI. - -* #175 Update all dependencies and other housekeeping -* #173 Fix Ubuntu runner version - -## v1.4.0 - 2024-03-11 - -- #165 Support Pydantic Models for Validation - -## v1.3.0 - 2024-02-13 - -- #161 Migrate Schema enforcer to use pydanticv2 - -## v1.2.2 - -- #156 Add support for jsonschema 4.18 -- Remove support for python version 3.7 - -## v1.2.1 - -### Changes - -- #152 Update requirement for rich to `>=9.5` - -## v1.2.0 - 2023-06-05 - -### Adds - -- Support for versions of jsonschema >= 4.6 - -### Removes - -- Support for versions of jsonschema < 4.6. See #141 for details. +- Adds information about jsonschema formats. +- Changes required dependencies to options for Ansible. +- Update CI and dev standards. ## v1.1.5 - 2022-07-27 @@ -95,7 +63,3 @@ This housekeeping release updates the supported Python version to a 3.10 minimum - `docs/mapping_schemas.md` renamed to `docs/mapping_data_files_to_schemas.md` - Simplifies the invoke tasks used for development - Schema enforcer now exits if an invalid schema is found while loading schemas [#99](https://github.com/networktocode/schema-enforcer/issues/99) - -## v1.0.0 - 2021-01-26 - -Schema Enforcer Initial Release diff --git a/docs/admin/release_notes/version_1.2.md b/docs/admin/release_notes/version_1.2.md new file mode 100644 index 0000000..2a74e96 --- /dev/null +++ b/docs/admin/release_notes/version_1.2.md @@ -0,0 +1,28 @@ +# v1.2 Release Notes + +This document describes all new features and changes in the release `1.2`. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Release Overview + +- Dependency updates. + +## v1.2.2 + +- #156 Add support for jsonschema 4.18 +- Remove support for python version 3.7 + +## v1.2.1 + +### Changes + +- #152 Update requirement for rich to `>=9.5` + +## v1.2.0 - 2023-06-05 + +### Adds + +- Support for versions of jsonschema >= 4.6 + +### Removes + +- Support for versions of jsonschema < 4.6. See #141 for details. diff --git a/docs/admin/release_notes/version_1.3.md b/docs/admin/release_notes/version_1.3.md new file mode 100644 index 0000000..48aeee5 --- /dev/null +++ b/docs/admin/release_notes/version_1.3.md @@ -0,0 +1,11 @@ +# v1.3 Release Notes + +This document describes all new features and changes in the release `1.3`. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Release Overview + +- Migrate Pydantic version. + +## v1.3.0 - 2024-02-13 + +- #161 Migrate Schema enforcer to use Pydanticv2 diff --git a/docs/admin/release_notes/version_1.4.md b/docs/admin/release_notes/version_1.4.md new file mode 100644 index 0000000..81570e7 --- /dev/null +++ b/docs/admin/release_notes/version_1.4.md @@ -0,0 +1,11 @@ +# v1.4 Release Notes + +This document describes all new features and changes in the release `1.4`. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Release Overview + +- Add more Pydantic functionality. + +## v1.4.0 - 2024-03-11 + +- #165 Support Pydantic Models for Validation diff --git a/docs/admin/release_notes/version_1.5.md b/docs/admin/release_notes/version_1.5.md new file mode 100644 index 0000000..4071a42 --- /dev/null +++ b/docs/admin/release_notes/version_1.5.md @@ -0,0 +1,19 @@ +# v1.5 Release Notes + +This document describes all new features and changes in the release `1.5`. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Release Overview + +- Add Python 3.13 support. +- Fix CI and dev standards best practices. + +## v1.5.1 - 2025-11-12 + +- #179 Added Python 3.13 support. + +## v1.5.0 - 2025-11-10 + +This housekeeping release updates the supported Python version to a 3.10 minimum and the optional Ansible dependency to 2.16. It also updated most dependencies, fixed tests, and CI. + +* #175 Update all dependencies and other housekeeping +* #173 Fix Ubuntu runner version diff --git a/docs/ansible_command.md b/docs/user/ansible_command.md similarity index 100% rename from docs/ansible_command.md rename to docs/user/ansible_command.md diff --git a/docs/custom_validators.md b/docs/user/custom_validators.md similarity index 100% rename from docs/custom_validators.md rename to docs/user/custom_validators.md diff --git a/docs/user/lib_getting_started.md b/docs/user/lib_getting_started.md index 0d50fdb..c471821 100644 --- a/docs/user/lib_getting_started.md +++ b/docs/user/lib_getting_started.md @@ -8,12 +8,128 @@ To install the library, please follow the instructions detailed in the [Installa ## First steps with the Library -!!! warning "Developer Note - Remove Me!" - What (with screenshots preferably) does it look like to perform the simplest workflow within the library once installed? +Once schema-enforcer has been installed, the `schema-enforcer validate` command can be used run schema validations of YAML/JSON instance files against the defined schema. -## What are the next steps? +```shell +bash$ schema-enforcer --help +Usage: schema-enforcer [OPTIONS] COMMAND [ARGS]... + +Options: + --help Show this message and exit. + +Commands: + ansible Validate the hostvar for all hosts within an Ansible... + schema Manage your schemas + validate Validates instance files against defined schema +``` + +To run the schema validations, the command `schema-enforcer validate` can be run. + +```shell +bash$ schema-enforcer validate +schema-enforcer validate +ALL SCHEMA VALIDATION CHECKS PASSED +``` + +To acquire more context regarding what files specifically passed schema validation, the `--show-pass` flag can be passed in. + +```shell +bash$ schema-enforcer validate --show-pass +PASS [FILE] ./eng-london-rt1/ntp.yml +PASS [FILE] ./eng-london-rt1/dns.yml +PASS [FILE] ./chi-beijing-rt1/syslog.yml +PASS [FILE] ./chi-beijing-rt1/dns.yml +ALL SCHEMA VALIDATION CHECKS PASSED +``` + +If we modify one of the addresses in the `chi-beijing-rt1/dns.yml` file so that it's value is the boolean `true` instead of an IP address string, then run the `schema-enforcer` tool, the validation will fail with an error message. + +```yaml +bash$ cat chi-beijing-rt1/dns.yml +# jsonschema: schemas/dns_servers +--- +dns_servers: + - address: true + - address: "10.2.2.2" +``` +```shell +bash$ test-schema validate +FAIL | [ERROR] True is not of type 'string' [FILE] ./chi-beijing-rt1/dns.yml [PROPERTY] dns_servers:0:address +bash$ echo $? +1 +``` + +When a structured data file fails schema validation, `schema-enforcer` exits with a code of 1. + +When `schema-enforcer` runs, it assumes directory hierarchy which should be in place from the folder in which the tool is run. -!!! warning "Developer Note - Remove Me!" - After taking the first steps, what else could the users look at doing. +- `schema-enforcer` will search for **schema definition files** nested inside of `./schema/schemas/` which end in `.yml`, `.yaml`, or `.json`. +- `schema-enforcer` will do a recursive search for **structured data files** starting in the current working diretory (`./`). It does this by searching all directories (including the current working directory) for files ending in `.yml`, `.yaml`, or `.json`. The `schema` folder and it's subdirectories are excluded from this search by default. + +```cli +bash$ cd examples/example1 +bash$ tree +. +├── chi-beijing-rt1 +│ ├── dns.yml +│ └── syslog.yml +├── eng-london-rt1 +│ ├── dns.yml +│ └── ntp.yml +└── schema + └── schemas + ├── dns.yml + ├── ntp.yml + └── syslog.yml + +4 directories, 7 files +``` + +In the above example, `chi-beijing-rt1` is a directory with structured data files containing some configuration for a router named `chi-beijing-rt1`. There are two structured data files inside of this folder, `dns.yml` and `syslog.yml`. Similarly, the `eng-london-rt1` directory contains definition files for a router named `eng-london-rt1` -- `dns.yml` and `ntp.yml`. + +The file `chi-beijing-rt1/dns.yml` defines the DNS servers `chi-beijing.rt1` should use. The data in this file includes a simple hash-type data structure with a key of `dns_servers` and a value of an array. Each element in this array is a hash-type object with a key of `address` and a value which is the string of an IP address. + +```yaml +bash$ cat chi-beijing-rt1/dns.yml +# jsonschema: schemas/dns_servers +--- +dns_servers: + - address: "10.1.1.1" + - address: "10.2.2.2" +``` +> Note: The line `# jsonschema: schemas/dns_servers` tells `schema-enforcer` the ID of the schema which the structured data defined in the file should be validated against. The schema ID is defined by the `$id` top level key in a schema definition. More information on how the structured data is mapped to a schema ID to which it should adhere can be found in the [mapping_schemas README](./docs/mapping_schemas.md) + +The file `schema/schemas/dns.yml` is a schema definition file. It contains a schema definition for ntp servers written in JSONSchema. The data in `chi-beijing-rt1/dns.yml` and `eng-london-rt1/dns.yml` should adhere to the schema defined in this schema definition file. + +```yaml +bash$ cat schema/schemas/dns.yml +--- +$schema: "http://json-schema.org/draft-07/schema#" +$id: "schemas/dns_servers" +description: "DNS Server Configuration schema." +type: "object" +properties: + dns_servers: + type: "array" + items: + type: "object" + properties: + name: + type: "string" + address: + type: "string" + format: "ipv4" + vrf: + type: "string" + required: + - "address" + uniqueItems: true +required: + - "dns_servers" +``` + +> Note: The cat of the schema definition file may be a little scary if you haven't seen JSONSchema before. Don't worry too much if it is difficult to parse right now. The important thing to note is that this file contains a schema definition to which the structured data in the files `chi-beijing-rt1/dns.yml` and `eng-london-rt1/dns.yml` should adhere. + +## What are the next steps? -You can check out the [Use Cases](./lib_use_cases.md) section for more examples. \ No newline at end of file +You can check out the [Use Cases](./lib_use_cases.md) section for more examples. diff --git a/docs/user/lib_overview.md b/docs/user/lib_overview.md index 062968f..232fadd 100644 --- a/docs/user/lib_overview.md +++ b/docs/user/lib_overview.md @@ -4,13 +4,20 @@ This document provides an overview of the library including critical information ## Description +Schema Enforcer requires that two different elements be defined by the user: + +- Schema Definition Files: These are files which define the schema to which a given set of data should adhere. +- Structured Data Files: These are files which contain data that should adhere to the schema defined in one (or multiple) of the schema definition files. + +> Note: Data which needs to be validated against a schema definition can come in the form of Structured Data Files or Ansible host vars. Ansible is not installed by default when schema-enforcer is installed. In order to use Ansible features, ansible must already be available or must be declared as an optional dependency when schema-enforcer upon installation. In the interest of brevity and simplicity, this README.md contains discussion only of Structured Data Files -- for more information on how to use `schema-enforcer` with ansible host vars, see [the ansible_command README](docs/ansible_command.md) ## Audience (User Personas) - Who should use this Library? -!!! warning "Developer Note - Remove Me!" - Who is this meant for/ who is the common user of this library? +The intended audience is those who are programming with Python and specifically with JSON encoded data. Whether you are a seasoned veteran or a casual coder, this library should help to validate structured data or ansible host vars against a schema. ## Authors and Maintainers -!!! warning "Developer Note - Remove Me!" - Add the team and/or the main individuals maintaining this project. Include historical maintainers as well. +- @cmsirbu +- @glennmatthews +- @PhillSimonds +- @dgarros diff --git a/docs/user/lib_use_cases.md b/docs/user/lib_use_cases.md index d8a999e..44962ed 100644 --- a/docs/user/lib_use_cases.md +++ b/docs/user/lib_use_cases.md @@ -4,9 +4,18 @@ This document describes common use-cases and scenarios for this library. ## General Usage +Schema Enforcer provides a framework for testing structured data against schema definitions using [JSONSchema](https://json-schema.org/understanding-json-schema/index.html). + +This library can be used to validated files, structured data, and Ansible HostVars against a schema. + ## Use-cases and common workflows -## Screenshots +- [Mapping Structured Data Files to Schema Files](../user/mapping_data_files_to_schemas.md) +- [The `ansible` command](../user/ansible_command.md) +- [The `validate` command](../user/validate_command.md) +- [The `schema` command](../user/schema_command.md) +- [Implementing custom validators](../user/custom_validators.md) + +## Examples -!!! warning "Developer Note - Remove Me!" - Ideally captures every view exposed by the Library. Should include a relevant dataset. +For most detailed examples visit the [Examples](https://github.com/networktocode/schema-enforcer/tree/develop/examples) directory. diff --git a/docs/mapping_data_files_to_schemas.md b/docs/user/mapping_data_files_to_schemas.md similarity index 100% rename from docs/mapping_data_files_to_schemas.md rename to docs/user/mapping_data_files_to_schemas.md diff --git a/docs/schema_command.md b/docs/user/schema_command.md similarity index 100% rename from docs/schema_command.md rename to docs/user/schema_command.md diff --git a/docs/validate_command.md b/docs/user/validate_command.md similarity index 100% rename from docs/validate_command.md rename to docs/user/validate_command.md diff --git a/pyproject.toml b/pyproject.toml index ae5e983..7f3c731 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,24 +1,13 @@ [tool.poetry] -<<<<<<< HEAD name = "schema-enforcer" version = "1.5.2a0" description = "Tool/Framework for testing structured data against schema definitions" -authors = ["Network to Code, LLC "] -license = "Apache-2.0" -readme = "README.md" -homepage = "https://github.com/networktocode/schema-enforcer" -repository = "https://github.com/networktocode/schema-enforcer" -======= -name = "schema_enforcer" -version = "1.5.2" -description = "Tool/Framework for testing structured data against schema definitions" authors = ["Network to Code, LLC "] readme = "README.md" homepage = "https://schema-enforcer.readthedocs.io/" repository = "https://github.com/networktocode/schema-enforcer" documentation = "https://schema-enforcer.readthedocs.io/" license = "Apache-2.0" ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) classifiers = [ "Intended Audience :: Developers", "Development Status :: 5 - Production/Stable", @@ -27,14 +16,14 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", -<<<<<<< HEAD "Programming Language :: Python :: 3.14", ] + include = [ - "CHANGELOG.md", "LICENSE", "README.md", ] + packages = [ { include = "schema_enforcer" }, ] @@ -63,60 +52,6 @@ ansible = { version = ">=9.0.0", optional = true } ansible = ["ansible"] ansible-core = ["ansible-core"] -[tool.poetry.group.dev.dependencies] -pytest = "*" -requests_mock = "*" -pyyaml = "*" -black = "*" -pylint = "*" -pydocstyle = "*" -yamllint = "*" -bandit = "*" -invoke = "*" -flake8 = "*" - -[tool.poetry.scripts] -schema-enforcer = "schema_enforcer.cli:main" - -[tool.black] -line-length = 120 -include = '\.pyi?$' -exclude = ''' - /( - \.git - | \.tox - | \.venv - | env/ - | _build - | build - | dist - )/ - ''' - -[tool.pylint.master] -ignore=".venv" - -[tool.pylint.basic] -# No docstrings required for private methods (pylint default) or for test_ functions. -no-docstring-rgx="^(_|test_)" - -[tool.pylint.messages_control] -# Line length is enforced by Black, so pylint doesn't need to check it. -# Pylint and Black disagree about how to format multi-line arrays; Black wins. -disable = """, - line-too-long, - """ -======= -] -include = [ - "LICENSE", - "README.md", -] - -[tool.poetry.dependencies] -python = ">=3.10,<3.14" -click = "*" - [tool.poetry.group.dev.dependencies] coverage = "*" pytest = "*" @@ -130,6 +65,7 @@ attrs = "^23.2.0" towncrier = ">=23.6.0,<=24.8.0" ruff = "*" Markdown = "*" +requests_mock = "*" [tool.poetry.group.docs.dependencies] # Rendering docs to HTML @@ -157,9 +93,7 @@ mkdocstrings-python = "1.13.0" griffe = "1.1.1" [tool.poetry.scripts] -schema_enforcer = 'schema_enforcer.cli:main' - - +schema-enforcer = "schema_enforcer.cli:main" [tool.ruff] line-length = 120 @@ -215,7 +149,6 @@ disable = [ "duplicate-code", "cyclic-import", ] ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) [tool.pylint.miscellaneous] # Don't flag TODO as a failure, let us commit with things that still need to be done in the code @@ -224,7 +157,6 @@ notes = """, XXX, """ -<<<<<<< HEAD [tool.pylint.SIMILARITIES] min-similarity-lines = 15 @@ -234,10 +166,6 @@ testpaths = [ ] addopts = "-vv --doctest-modules" -[build-system] -requires = ["poetry-core>=2.0.0,<3.0.0"] -build-backend = "poetry.core.masonry.api" -======= [build-system] requires = ["poetry-core>=2.0.0,<3.0.0"] build-backend = "poetry.core.masonry.api" @@ -306,4 +234,3 @@ showcontent = true directory = "housekeeping" name = "Housekeeping" showcontent = true ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) diff --git a/schema_enforcer/__init__.py b/schema_enforcer/__init__.py index 97649d5..4d89135 100644 --- a/schema_enforcer/__init__.py +++ b/schema_enforcer/__init__.py @@ -1,11 +1,5 @@ """Initialization file for library.""" -<<<<<<< HEAD -# pylint: disable=C0114 - -__version__ = "1.1.3" -======= from importlib import metadata __version__ = metadata.version(__name__) ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) diff --git a/schema_enforcer/api.py b/schema_enforcer/api.py deleted file mode 100644 index a92b3f3..0000000 --- a/schema_enforcer/api.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Example API.""" - -# Fill in with information regarding Python API for project diff --git a/schema_enforcer/log.py b/schema_enforcer/log.py deleted file mode 100644 index 9bc3899..0000000 --- a/schema_enforcer/log.py +++ /dev/null @@ -1,74 +0,0 @@ -""" -Logging utilities for Schema Enforcer. - -This module contains helpers and wrappers for making logging more consistent across applications. - -How to use me: - - >>> from schema_enforcer.log import initialize_logging - >>> log = initialize_logging(level="debug") -""" - -import logging.config - -APP = "schema_enforcer" - - -def initialize_logging(config=None, level="INFO", filename=None): - """Initialize logging using sensible defaults. - - Args: - config (dict): User provided configuration dictionary. - level (str): The level of logging for STDOUT logging. - filename (str): Where to output debug logging to file. - - """ - if not config: - config = { - "version": 1, - "disable_existing_loggers": False, - "formatters": { - "standard": { - "format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s", - "datefmt": "%Y-%m-%dT%H:%M:%S%z", - }, - "debug": { - "format": "%(asctime)s [%(levelname)s] [%(module)s] [%(funcName)s] %(name)s: %(message)s", - "datefmt": "%Y-%m-%dT%H:%M:%S%z", - }, - }, - "handlers": { - "standard": { - "class": "logging.StreamHandler", - "formatter": "standard", - "level": level.upper(), - }, - }, - "loggers": { - "": { - "handlers": ["standard"], - "level": "DEBUG", - } - }, - } - - # If a filename is passed in, let's add a FileHandler - if filename: - config["handlers"].update( - { - "file_output": { - "class": "logging.FileHandler", - "formatter": "debug", - "level": "DEBUG", - "filename": filename, - } - } - ) - config["loggers"][""]["handlers"].append("file_output") - - # Configure the logging - logging.config.dictConfig(config) - - # Initialize root logger and advise logging has been initialized - log = logging.getLogger(APP) - log.debug("Logging initialized.") diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index ef7024c..0000000 --- a/tests/conftest.py +++ /dev/null @@ -1,53 +0,0 @@ -"""conftest file for pytest""" - -import glob -import os -from schema_enforcer.utils import load_file -from schema_enforcer.schemas.jsonschema import JsonSchema - -FIXTURES_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fixtures", "test_jsonschema") -FORMAT_CHECK_ERROR_MESSAGE_MAPPING = { - "incorrect_regex_format": "'[' is not a 'regex'", - "incorrect_date_format": "'2021-111-28' is not a 'date'", - "incorrect_hostname_format": "'ntc@ntc.com' is not a 'hostname'", - "incorrect_uri_format": "'sftp//' is not a 'uri'", - "incorrect_jsonptr_format": "'fakejsonptr' is not a 'json-pointer'", - "incorrect_email_format": "'networktocode.code.com' is not a 'email'", - "incorrect_ipv4_format": "'10.1.1.300' is not a 'ipv4'", - "incorrect_ipv6_format": "'2001:00000:3238:DFE1:63:0000:0000:FEFB' is not a 'ipv6'", - "incorrect_time_format": "'20:20:33333+00:00' is not a 'time'", - "incorrect_datetime_format": "'January 29th 2021' is not a 'date-time'", -} - - -def pytest_generate_tests(metafunc): - """Pytest_generate_tests prehook""" - if metafunc.function.__name__ == "test_format_checkers": - schema_files = glob.glob(f"{FIXTURES_DIR}/schema/schemas/incorrect_*.yml") - schema_instances = [] - for schema_file in schema_files: - schema_instance = JsonSchema( - schema=load_file(schema_file), - filename=os.path.basename(schema_file), - root=os.path.join(FIXTURES_DIR, "schema", "schemas"), - ) - schema_instances.append(schema_instance) - - data_files = glob.glob(f"{FIXTURES_DIR}/hostvars/spa-madrid-rt1/incorrect_*.yml") - data_instances = [] - for data_file in data_files: - data = load_file(data_file) - data_instances.append(data) - - metafunc.parametrize( - "schema_instance,data_instance, expected_error_message", - [ - ( - schema_instances[i], - data_instances[i], - FORMAT_CHECK_ERROR_MESSAGE_MAPPING.get(os.path.basename(schema_files[i])[:-4]), - ) - for i in range(0, len(schema_instances)) - ], - ids=[os.path.basename(schema_files[i])[:-4] for i in range(0, len(schema_instances))], - ) diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index b917a15..ef7024c 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -1,10 +1,53 @@ -"""Used to setup fixtures to be used through tests""" +"""conftest file for pytest""" -import pytest -from click.testing import CliRunner +import glob +import os +from schema_enforcer.utils import load_file +from schema_enforcer.schemas.jsonschema import JsonSchema +FIXTURES_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fixtures", "test_jsonschema") +FORMAT_CHECK_ERROR_MESSAGE_MAPPING = { + "incorrect_regex_format": "'[' is not a 'regex'", + "incorrect_date_format": "'2021-111-28' is not a 'date'", + "incorrect_hostname_format": "'ntc@ntc.com' is not a 'hostname'", + "incorrect_uri_format": "'sftp//' is not a 'uri'", + "incorrect_jsonptr_format": "'fakejsonptr' is not a 'json-pointer'", + "incorrect_email_format": "'networktocode.code.com' is not a 'email'", + "incorrect_ipv4_format": "'10.1.1.300' is not a 'ipv4'", + "incorrect_ipv6_format": "'2001:00000:3238:DFE1:63:0000:0000:FEFB' is not a 'ipv6'", + "incorrect_time_format": "'20:20:33333+00:00' is not a 'time'", + "incorrect_datetime_format": "'January 29th 2021' is not a 'date-time'", +} -@pytest.fixture -def cli_runner(): - """Provide CLI runner for Click tests.""" - return CliRunner() + +def pytest_generate_tests(metafunc): + """Pytest_generate_tests prehook""" + if metafunc.function.__name__ == "test_format_checkers": + schema_files = glob.glob(f"{FIXTURES_DIR}/schema/schemas/incorrect_*.yml") + schema_instances = [] + for schema_file in schema_files: + schema_instance = JsonSchema( + schema=load_file(schema_file), + filename=os.path.basename(schema_file), + root=os.path.join(FIXTURES_DIR, "schema", "schemas"), + ) + schema_instances.append(schema_instance) + + data_files = glob.glob(f"{FIXTURES_DIR}/hostvars/spa-madrid-rt1/incorrect_*.yml") + data_instances = [] + for data_file in data_files: + data = load_file(data_file) + data_instances.append(data) + + metafunc.parametrize( + "schema_instance,data_instance, expected_error_message", + [ + ( + schema_instances[i], + data_instances[i], + FORMAT_CHECK_ERROR_MESSAGE_MAPPING.get(os.path.basename(schema_files[i])[:-4]), + ) + for i in range(0, len(schema_instances)) + ], + ids=[os.path.basename(schema_files[i])[:-4] for i in range(0, len(schema_instances))], + ) diff --git a/tests/test_ansible_inventory.py b/tests/unit/test_ansible_inventory.py similarity index 100% rename from tests/test_ansible_inventory.py rename to tests/unit/test_ansible_inventory.py diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py deleted file mode 100644 index 2bb8272..0000000 --- a/tests/unit/test_cli.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Example Test using Fixtures.""" - -import mock - -from schema_enforcer import cli - - -@mock.patch("schema_enforcer.cli.log") -def test_cli_logging(log, cli_runner): - """Assert our logging gets called in CLI app.""" - result = cli_runner.invoke(cli.main, ["--test", "ntc"]) - - assert result.exit_code == 0 - assert result.output == "ntc\n" - log.info.assert_called() - log.info.assert_called_with("Entrypoint of the CLI app.") diff --git a/tests/test_cli_ansible_exists.py b/tests/unit/test_cli_ansible_exists.py similarity index 100% rename from tests/test_cli_ansible_exists.py rename to tests/unit/test_cli_ansible_exists.py diff --git a/tests/test_cli_ansible_not_exists.py b/tests/unit/test_cli_ansible_not_exists.py similarity index 100% rename from tests/test_cli_ansible_not_exists.py rename to tests/unit/test_cli_ansible_not_exists.py diff --git a/tests/test_config_settings.py b/tests/unit/test_config_settings.py similarity index 100% rename from tests/test_config_settings.py rename to tests/unit/test_config_settings.py diff --git a/tests/test_instances_instance_file.py b/tests/unit/test_instances_instance_file.py similarity index 100% rename from tests/test_instances_instance_file.py rename to tests/unit/test_instances_instance_file.py diff --git a/tests/test_instances_instance_file_manager.py b/tests/unit/test_instances_instance_file_manager.py similarity index 100% rename from tests/test_instances_instance_file_manager.py rename to tests/unit/test_instances_instance_file_manager.py diff --git a/tests/test_jsonschema.py b/tests/unit/test_jsonschema.py similarity index 100% rename from tests/test_jsonschema.py rename to tests/unit/test_jsonschema.py diff --git a/tests/unit/test_logging.py b/tests/unit/test_logging.py deleted file mode 100644 index c1d18f1..0000000 --- a/tests/unit/test_logging.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Validate schema_enforcer logging works.""" - -import mock - -import schema_enforcer - - -@mock.patch("logging.config.dictConfig") -@mock.patch("logging.getLogger") -def test_initialize_logging_default(get_logger, basic_cfg): - """Test initialize_logging using defaults.""" - schema_enforcer.log.initialize_logging() - - basic_cfg.assert_called_once() - initial_call = basic_cfg.mock_calls[0].args[0] - assert set(initial_call.keys()) == set(["version", "disable_existing_loggers", "formatters", "handlers", "loggers"]) - assert initial_call["handlers"]["standard"]["level"] == "INFO" - - get_logger.assert_called_once() - assert get_logger.mock_calls[0].args == ("schema_enforcer",) - assert get_logger.mock_calls[1].args == ("Logging initialized.",) - - -@mock.patch("logging.config.dictConfig") -@mock.patch("logging.getLogger") -def test_initialize_logging_user_defined_config(get_logger, basic_cfg): - """Test initialize_logging with user defined config.""" - config = {"version": 1, "disable_existing_loggers": False} - schema_enforcer.log.initialize_logging(config=config) - - basic_cfg.assert_called_once() - initial_call = basic_cfg.mock_calls[0].args[0] - assert initial_call == config - - get_logger.assert_called_once() - assert get_logger.mock_calls[0].args == ("schema_enforcer",) - assert get_logger.mock_calls[1].args == ("Logging initialized.",) - - -@mock.patch("logging.config.dictConfig") -@mock.patch("logging.getLogger") -def test_initialize_logging_filename(get_logger, basic_cfg): - """Test initialize_logging with filename.""" - schema_enforcer.log.initialize_logging(filename="output.log") - - basic_cfg.assert_called_once() - initial_call = basic_cfg.mock_calls[0].args[0] - assert set(initial_call.keys()) == set(["version", "disable_existing_loggers", "formatters", "handlers", "loggers"]) - assert initial_call["handlers"]["standard"]["level"] == "INFO" - assert initial_call["handlers"]["file_output"]["filename"] == "output.log" - assert initial_call["handlers"]["file_output"]["level"] == "DEBUG" - assert initial_call["handlers"]["file_output"]["formatter"] == "debug" - assert initial_call["handlers"]["file_output"]["class"] == "logging.FileHandler" - assert "file_output" in initial_call["loggers"][""]["handlers"] - - get_logger.assert_called_once() - assert get_logger.mock_calls[0].args == ("schema_enforcer",) - assert get_logger.mock_calls[1].args == ("Logging initialized.",) diff --git a/tests/test_schemas_pydantic_validators.py b/tests/unit/test_schemas_pydantic_validators.py similarity index 100% rename from tests/test_schemas_pydantic_validators.py rename to tests/unit/test_schemas_pydantic_validators.py diff --git a/tests/test_schemas_schema_manager.py b/tests/unit/test_schemas_schema_manager.py similarity index 100% rename from tests/test_schemas_schema_manager.py rename to tests/unit/test_schemas_schema_manager.py diff --git a/tests/test_schemas_validator.py b/tests/unit/test_schemas_validator.py similarity index 100% rename from tests/test_schemas_validator.py rename to tests/unit/test_schemas_validator.py diff --git a/tests/test_utils.py b/tests/unit/test_utils.py similarity index 100% rename from tests/test_utils.py rename to tests/unit/test_utils.py diff --git a/tests/test_validator.py b/tests/unit/test_validator.py similarity index 100% rename from tests/test_validator.py rename to tests/unit/test_validator.py From d892d5235ee5a2c59f24ba6e9b988060d751f2b3 Mon Sep 17 00:00:00 2001 From: Jeff Kala Date: Fri, 7 Aug 2026 10:16:52 -0600 Subject: [PATCH 3/6] few additional conflict resolutions --- .github/workflows/ci.yml | 34 ++++++++++----------- schema_enforcer/cli.py | 65 ++++++++++++++-------------------------- 2 files changed, 39 insertions(+), 60 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 838a332..0b57bec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,14 +11,14 @@ on: # yamllint disable-line rule:truthy rule:comments pull_request: ~ env: - INVOKE_SCHEMA-ENFORCER_IMAGE_NAME: "schema-enforcer" - INVOKE_SCHEMA-ENFORCER_IMAGE_VER: "latest" + INVOKE_SCHEMA_ENFORCER_IMAGE_NAME: "schema-enforcer" + INVOKE_SCHEMA_ENFORCER_IMAGE_VER: "latest" jobs: ruff-format: runs-on: "ubuntu-latest" env: - INVOKE_SCHEMA-ENFORCER_LOCAL: "True" + INVOKE_SCHEMA_ENFORCER_LOCAL: "True" steps: - name: "Check out repository code" uses: "actions/checkout@v4" @@ -31,7 +31,7 @@ jobs: ruff-lint: runs-on: "ubuntu-latest" env: - INVOKE_SCHEMA-ENFORCER_LOCAL: "True" + INVOKE_SCHEMA_ENFORCER_LOCAL: "True" steps: - name: "Check out repository code" uses: "actions/checkout@v4" @@ -44,7 +44,7 @@ jobs: check-docs-build: runs-on: "ubuntu-latest" env: - INVOKE_SCHEMA-ENFORCER_LOCAL: "True" + INVOKE_SCHEMA_ENFORCER_LOCAL: "True" steps: - name: "Check out repository code" uses: "actions/checkout@v4" @@ -58,7 +58,7 @@ jobs: poetry: runs-on: "ubuntu-latest" env: - INVOKE_SCHEMA-ENFORCER_LOCAL: "True" + INVOKE_SCHEMA_ENFORCER_LOCAL: "True" steps: - name: "Check out repository code" uses: "actions/checkout@v4" @@ -71,7 +71,7 @@ jobs: yamllint: runs-on: "ubuntu-latest" env: - INVOKE_SCHEMA-ENFORCER_LOCAL: "True" + INVOKE_SCHEMA_ENFORCER_LOCAL: "True" steps: - name: "Check out repository code" uses: "actions/checkout@v4" @@ -93,7 +93,7 @@ jobs: matrix: python-version: ["3.10", "3.13"] env: - INVOKE_SCHEMA-ENFORCER_PYTHON_VER: "${{ matrix.python-version }}" + INVOKE_SCHEMA_ENFORCER_PYTHON_VER: "${{ matrix.python-version }}" steps: - name: "Check out repository code" uses: "actions/checkout@v4" @@ -102,7 +102,7 @@ jobs: with: poetry-version: "2.1.3" - name: "Get image version" - run: "echo INVOKE_SCHEMA-ENFORCER_IMAGE_VER=`poetry version -s`-py${{ matrix.python-version }} >> $GITHUB_ENV" + run: "echo INVOKE_SCHEMA_ENFORCER_IMAGE_VER=`poetry version -s`-py${{ matrix.python-version }} >> $GITHUB_ENV" - name: "Set up Docker Buildx" id: "buildx" uses: "docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2" # v3.10.0 @@ -113,10 +113,10 @@ jobs: context: "./" push: false load: true - tags: "${{ env.INVOKE_SCHEMA-ENFORCER_IMAGE_NAME }}:${{ env.INVOKE_SCHEMA-ENFORCER_IMAGE_VER }}" + tags: "${{ env.INVOKE_SCHEMA_ENFORCER_IMAGE_NAME }}:${{ env.INVOKE_SCHEMA_ENFORCER_IMAGE_VER }}" file: "./Dockerfile" - cache-from: "type=gha,scope=${{ env.INVOKE_SCHEMA-ENFORCER_IMAGE_NAME }}-${{ env.INVOKE_SCHEMA-ENFORCER_IMAGE_VER }}-py${{ matrix.python-version }}" - cache-to: "type=gha,scope=${{ env.INVOKE_SCHEMA-ENFORCER_IMAGE_NAME }}-${{ env.INVOKE_SCHEMA-ENFORCER_IMAGE_VER }}-py${{ matrix.python-version }}" + cache-from: "type=gha,scope=${{ env.INVOKE_SCHEMA_ENFORCER_IMAGE_NAME }}-${{ env.INVOKE_SCHEMA_ENFORCER_IMAGE_VER }}-py${{ matrix.python-version }}" + cache-to: "type=gha,scope=${{ env.INVOKE_SCHEMA_ENFORCER_IMAGE_NAME }}-${{ env.INVOKE_SCHEMA_ENFORCER_IMAGE_VER }}-py${{ matrix.python-version }}" build-args: | PYTHON_VER=${{ matrix.python-version }} - name: "Linting: Pylint" @@ -130,7 +130,7 @@ jobs: python-version: ["3.10", "3.11", "3.12", "3.13"] runs-on: "ubuntu-latest" env: - INVOKE_SCHEMA-ENFORCER_PYTHON_VER: "${{ matrix.python-version }}" + INVOKE_SCHEMA_ENFORCER_PYTHON_VER: "${{ matrix.python-version }}" steps: - name: "Check out repository code" uses: "actions/checkout@v4" @@ -139,7 +139,7 @@ jobs: with: poetry-version: "2.1.3" - name: "Get image version" - run: "echo INVOKE_SCHEMA-ENFORCER_IMAGE_VER=`poetry version -s`-py${{ matrix.python-version }} >> $GITHUB_ENV" + run: "echo INVOKE_SCHEMA_ENFORCER_IMAGE_VER=`poetry version -s`-py${{ matrix.python-version }} >> $GITHUB_ENV" - name: "Set up Docker Buildx" id: "buildx" uses: "docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2" # v3.10.0 @@ -150,10 +150,10 @@ jobs: context: "./" push: false load: true - tags: "${{ env.INVOKE_SCHEMA-ENFORCER_IMAGE_NAME }}:${{ env.INVOKE_SCHEMA-ENFORCER_IMAGE_VER }}" + tags: "${{ env.INVOKE_SCHEMA_ENFORCER_IMAGE_NAME }}:${{ env.INVOKE_SCHEMA_ENFORCER_IMAGE_VER }}" file: "./Dockerfile" - cache-from: "type=gha,scope=${{ env.INVOKE_SCHEMA-ENFORCER_IMAGE_NAME }}-${{ env.INVOKE_SCHEMA-ENFORCER_IMAGE_VER }}-py${{ matrix.python-version }}" - cache-to: "type=gha,scope=${{ env.INVOKE_SCHEMA-ENFORCER_IMAGE_NAME }}-${{ env.INVOKE_SCHEMA-ENFORCER_IMAGE_VER }}-py${{ matrix.python-version }}" + cache-from: "type=gha,scope=${{ env.INVOKE_SCHEMA_ENFORCER_IMAGE_NAME }}-${{ env.INVOKE_SCHEMA_ENFORCER_IMAGE_VER }}-py${{ matrix.python-version }}" + cache-to: "type=gha,scope=${{ env.INVOKE_SCHEMA_ENFORCER_IMAGE_NAME }}-${{ env.INVOKE_SCHEMA_ENFORCER_IMAGE_VER }}-py${{ matrix.python-version }}" build-args: | PYTHON_VER=${{ matrix.python-version }} - name: "Run Tests" diff --git a/schema_enforcer/cli.py b/schema_enforcer/cli.py index 09a9ae3..e6cb366 100644 --- a/schema_enforcer/cli.py +++ b/schema_enforcer/cli.py @@ -1,4 +1,3 @@ -<<<<<<< HEAD """main cli commands.""" import sys @@ -6,12 +5,11 @@ import click from termcolor import colored -from schema_enforcer.utils import MutuallyExclusiveOption from schema_enforcer import config -from schema_enforcer.schemas.manager import SchemaManager -from schema_enforcer.instances.file import InstanceFileManager -from schema_enforcer.utils import error from schema_enforcer.exceptions import InvalidJSONSchema +from schema_enforcer.instances.file import InstanceFileManager +from schema_enforcer.schemas.manager import SchemaManager +from schema_enforcer.utils import MutuallyExclusiveOption, error @click.group() @@ -47,7 +45,7 @@ def main(): show_default=True, ) @main.command() -def validate(show_pass, show_checks, strict): # noqa D205 +def validate(show_pass, show_checks, strict): """Validates instance files against defined schema. \f @@ -150,7 +148,7 @@ def validate(show_pass, show_checks, strict): # noqa D205 help="The name of a schema.", ) @main.command() -def schema(check, generate_invalid, list_schemas, schema_id, dump_schemas): # noqa: D417,D301,D205 +def schema(check, generate_invalid, list_schemas, schema_id, dump_schemas): """Manage your schemas. \f @@ -162,7 +160,13 @@ def schema(check, generate_invalid, list_schemas, schema_id, dump_schemas): # n schema_id (str): Name of schema to evaluate dump_schemas (bool): Dump all schema data or a single schema if schema_id is provided """ - if not check and not generate_invalid and not list_schemas and not schema_id and not dump_schemas: + if ( + not check + and not generate_invalid + and not list_schemas + and not schema_id + and not dump_schemas + ): error( "The 'schema' command requires one or more arguments. You can run the command 'schema-enforcer schema --help' to see the arguments available." ) @@ -193,7 +197,9 @@ def schema(check, generate_invalid, list_schemas, schema_id, dump_schemas): # n if generate_invalid: if not schema_id: - sys.exit("Please indicate the schema you'd like to generate invalid data for using the --schema-id flag") + sys.exit( + "Please indicate the schema you'd like to generate invalid data for using the --schema-id flag" + ) smgr.generate_invalid_tests_expected(schema_id=schema_id) sys.exit(0) @@ -225,9 +231,7 @@ def schema(check, generate_invalid, list_schemas, schema_id, dump_schemas): # n is_flag=True, show_default=True, ) -def ansible( - inventory, limit, show_pass, show_checks -): # pylint: disable=too-many-branches,too-many-locals,too-many-locals,too-many-statements # noqa: D417,D301 +def ansible(inventory, limit, show_pass, show_checks): # pylint: disable=too-many-branches,too-many-locals,too-many-locals,too-many-statements """Validate the hostvars for all hosts within an Ansible inventory. The hostvars are dynamically rendered based on groups to which each host belongs. @@ -269,7 +273,9 @@ def ansible( # This has been left in the code until such a time as we implement the change to two packages so code will not need # to be re-written/ try: - from schema_enforcer.ansible_inventory import AnsibleInventory # pylint: disable=import-outside-toplevel + from schema_enforcer.ansible_inventory import ( + AnsibleInventory, # pylint: disable=import-outside-toplevel + ) except ModuleNotFoundError: error( "ansible package not found, you can run the command 'pip install schema-enforcer[ansible]' to install the latest schema-enforcer sanctioned version." @@ -326,7 +332,9 @@ def ansible( smgr.validate_schemas_exist(declared_schema_ids) # Acquire schemas applicable to the given host - applicable_schemas = inv.get_applicable_schemas(hostvars, smgr, declared_schema_ids, automap) + applicable_schemas = inv.get_applicable_schemas( + hostvars, smgr, declared_schema_ids, automap + ) for schema_obj in applicable_schemas.values(): # Combine host attributes into a single data structure matching to properties defined at the top level of the schema definition if not strict: @@ -359,32 +367,3 @@ def ansible( print(colored("ALL SCHEMA VALIDATION CHECKS PASSED", "green")) else: sys.exit(1) -======= -"""Example cli using click.""" - -import logging - -import click - -from schema_enforcer.log import initialize_logging - -# Import necessary project related things to use in CLI - -log = logging.getLogger(__name__) - - -@click.command() -@click.option("--test", default="Test Output", help="Test argument") -@click.option( - "--log-level", - default="INFO", - type=click.Choice(["NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]), - help="Logging level", -) -@click.option("--log-file", default=None, help="Log file to output to debug logs to.") -def main(test, log_level, log_file): - """Entrypoint into CLI app.""" - initialize_logging(level=log_level, filename=log_file) - log.info("Entrypoint of the CLI app.") - print(test) ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) From 5c3524a1511a4d7fb42abe3625c2b8c27037fbba Mon Sep 17 00:00:00 2001 From: Jeff Kala Date: Fri, 7 Aug 2026 11:24:34 -0600 Subject: [PATCH 4/6] fix docs, pylint, ruff --- .github/workflows/release.yml | 2 +- .gitignore | 1 + docs/admin/release_notes/version_1.1.md | 4 +- docs/images/schema_list.png | Bin 0 -> 15009 bytes docs/user/custom_validators.md | 4 + docs/user/lib_getting_started.md | 2 +- docs/user/lib_overview.md | 2 +- docs/user/schema_command.md | 6 +- docs/user/validate_command.md | 4 +- mkdocs.yml | 11 + poetry.lock | 976 +++++++++++++----- pyproject.toml | 7 - schema_enforcer/ansible_inventory.py | 2 +- schema_enforcer/cli.py | 25 +- schema_enforcer/config.py | 1 + schema_enforcer/instances/file.py | 4 +- schema_enforcer/schemas/jsonschema.py | 3 +- schema_enforcer/schemas/manager.py | 18 +- schema_enforcer/schemas/validator.py | 9 +- schema_enforcer/utils.py | 27 +- schema_enforcer/validation.py | 5 +- tasks.py | 302 +----- .../validators/check_interfaces_ipv4.py | 1 + .../pydantic_validators/models/__init__.py | 6 +- .../pydantic_validators/models/dns.py | 1 + .../pydantic_validators/models/interfaces.py | 9 +- tests/unit/conftest.py | 3 +- tests/unit/test_config_settings.py | 3 +- tests/unit/test_instances_instance_file.py | 4 +- .../test_instances_instance_file_manager.py | 2 +- tests/unit/test_jsonschema.py | 3 +- .../unit/test_schemas_pydantic_validators.py | 6 +- tests/unit/test_schemas_schema_manager.py | 4 +- tests/unit/test_schemas_validator.py | 4 +- tests/unit/test_utils.py | 2 +- tests/unit/test_validator.py | 3 +- 36 files changed, 838 insertions(+), 628 deletions(-) create mode 100644 docs/images/schema_list.png diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 75d603f..c935159 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -69,7 +69,7 @@ jobs: with: user: "__token__" password: "${{ secrets.PYPI_API_TOKEN }}" - # End publish to PyPI job. + # End publish to PyPI job. slack-notify: needs: diff --git a/.gitignore b/.gitignore index 080cc65..0be01db 100644 --- a/.gitignore +++ b/.gitignore @@ -305,3 +305,4 @@ docs/CHANGELOG.md public /compose.yaml /dump.sql +/schema-enforcer/static/* diff --git a/docs/admin/release_notes/version_1.1.md b/docs/admin/release_notes/version_1.1.md index 3dd8338..097b20a 100644 --- a/docs/admin/release_notes/version_1.1.md +++ b/docs/admin/release_notes/version_1.1.md @@ -53,8 +53,8 @@ This document describes all new features and changes in the release `1.1`. The f ### Adds -- [Custom Validators](docs/custom_validators.md) -- [Automatic mapping of schemas to data files](docs/mapping_data_files_to_schemas.md) +- [Custom Validators](../../user/custom_validators.md) +- [Automatic mapping of schemas to data files](../../user/mapping_data_files_to_schemas.md) - Automatic implementation of draft7 format checker to support [IPv4 and IPv6 format declarations](https://json-schema.org/understanding-json-schema/reference/string.html#id12) in a JSON Schema definition [#94](https://github.com/networktocode/schema-enforcer/issues/94) ### Changes diff --git a/docs/images/schema_list.png b/docs/images/schema_list.png new file mode 100644 index 0000000000000000000000000000000000000000..fd87b2fb32dfa76198c0d0d9709e2941f9f767f3 GIT binary patch literal 15009 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 Note: The line `# jsonschema: schemas/dns_servers` tells `schema-enforcer` the ID of the schema which the structured data defined in the file should be validated against. The schema ID is defined by the `$id` top level key in a schema definition. More information on how the structured data is mapped to a schema ID to which it should adhere can be found in the [mapping_schemas README](./docs/mapping_schemas.md) +> Note: The line `# jsonschema: schemas/dns_servers` tells `schema-enforcer` the ID of the schema which the structured data defined in the file should be validated against. The schema ID is defined by the `$id` top level key in a schema definition. More information on how the structured data is mapped to a schema ID to which it should adhere can be found in the [mapping_schemas README](../user/mapping_data_files_to_schemas.md) The file `schema/schemas/dns.yml` is a schema definition file. It contains a schema definition for ntp servers written in JSONSchema. The data in `chi-beijing-rt1/dns.yml` and `eng-london-rt1/dns.yml` should adhere to the schema defined in this schema definition file. diff --git a/docs/user/lib_overview.md b/docs/user/lib_overview.md index 232fadd..01a2d96 100644 --- a/docs/user/lib_overview.md +++ b/docs/user/lib_overview.md @@ -9,7 +9,7 @@ Schema Enforcer requires that two different elements be defined by the user: - Schema Definition Files: These are files which define the schema to which a given set of data should adhere. - Structured Data Files: These are files which contain data that should adhere to the schema defined in one (or multiple) of the schema definition files. -> Note: Data which needs to be validated against a schema definition can come in the form of Structured Data Files or Ansible host vars. Ansible is not installed by default when schema-enforcer is installed. In order to use Ansible features, ansible must already be available or must be declared as an optional dependency when schema-enforcer upon installation. In the interest of brevity and simplicity, this README.md contains discussion only of Structured Data Files -- for more information on how to use `schema-enforcer` with ansible host vars, see [the ansible_command README](docs/ansible_command.md) +> Note: Data which needs to be validated against a schema definition can come in the form of Structured Data Files or Ansible host vars. Ansible is not installed by default when schema-enforcer is installed. In order to use Ansible features, ansible must already be available or must be declared as an optional dependency when schema-enforcer upon installation. In the interest of brevity and simplicity, this README.md contains discussion only of Structured Data Files -- for more information on how to use `schema-enforcer` with ansible host vars, see [the ansible_command README](../user/ansible_command.md) ## Audience (User Personas) - Who should use this Library? diff --git a/docs/user/schema_command.md b/docs/user/schema_command.md index feafe48..0eade8e 100644 --- a/docs/user/schema_command.md +++ b/docs/user/schema_command.md @@ -8,13 +8,13 @@ The `schema-enforcer schema` command is used to manage schemas. It can: ## Listing defined schemas -The `schema enforcer schema --list` command can be used to print out a table of defined schemas. These schemas are loaded based on the directory sctructure elucidated in the [README.md](../README.md) file at the root of the repository in the overview section. +The `schema enforcer schema --list` command can be used to print out a table of defined schemas. These schemas are loaded based on the directory structure elucidated in the [Getting Started Guide](../user/lib_getting_started.md) file at the root of the repository in the overview section. ```cli bash$ cd examples/example3 bash$ schema-enforcer schema --list ``` -![Schema List Command](assets/images/schema_list.png) +![Schema List Command](../images/schema_list.png) ## Checking defined schemas @@ -50,7 +50,7 @@ schema 9 directories, 3 files ``` -> Note: The names of the main_directory and test_directory can be configured in a pyproject.toml file if you want to override the defaults. See [configuration.md](./configuration.md) for more information on how to do so. +> Note: The names of the main_directory and test_directory can be configured in a pyproject.toml file if you want to override the defaults. See [configuration.md](../admin/configuration.md) for more information on how to do so. When putting tests into a directory for a given schema ID, the short form of the schema ID is used as the directory name. The short form of the schema ID is generated by removing `/` from the schema ID and anything proceeding it. For example, the name of the test directory for the schema ID `schemas/ntp` is named `ntp` in the example above. diff --git a/docs/user/validate_command.md b/docs/user/validate_command.md index 48fa386..3b06091 100644 --- a/docs/user/validate_command.md +++ b/docs/user/validate_command.md @@ -43,7 +43,7 @@ Structured Data File Schema ID ./inventory/group_vars/nyc.yml [] ``` -> The structured data file can be mapped to schema definitions in one of a few ways. See the [README in docs/mapping_schemas.md](./mapping_schemas.md) for more information. The [README.md in examples/example2](../examples/example2) also contains detailed examples of schema mappings. +> The structured data file can be mapped to schema definitions in one of a few ways. See the [README in docs/mapping_schemas.md](../user/mapping_data_files_to_schemas.md) for more information. The examples contain detailed examples of schema mappings. #### The `--show-pass` flag @@ -79,4 +79,4 @@ FAIL | [ERROR] Additional properties are not allowed ('test_extra_property' was FAIL | [ERROR] Additional properties are not allowed ('test_extra_property' was unexpected) [FILE] ./hostvars/fail-tests/dns.yml [PROPERTY] dns_servers:1 ``` -> Note: The schema definition `additionalProperties` attribute is part of JSONSchema standard definitions. More information on how to construct these definitions can be found [here](https://json-schema.org/understanding-json-schema/reference/object.html) \ No newline at end of file +> Note: The schema definition `additionalProperties` attribute is part of JSONSchema standard definitions. More information on how to construct these definitions can be found [here](https://json-schema.org/understanding-json-schema/reference/object.html) diff --git a/mkdocs.yml b/mkdocs.yml index 91af916..6e6800d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -124,14 +124,25 @@ nav: - Library Overview: "user/lib_overview.md" - Getting Started: "user/lib_getting_started.md" - Using the Library: "user/lib_use_cases.md" + - Using Ansible Command: "user/ansible_command.md" + - Using Schema Command: "user/schema_command.md" + - Using Validate Command: "user/validate_command.md" + - Using Custom Validators: "user/custom_validators.md" + - Mapping Data Files to Schemas: "user/mapping_data_files_to_schemas.md" - Frequently Asked Questions: "user/faq.md" - Administrator Guide: - Install and Configure: "admin/install.md" + - Configuration Details: "admin/configuration.md" - Upgrade: "admin/upgrade.md" - Uninstall: "admin/uninstall.md" - Release Notes: - "admin/release_notes/index.md" - v1.0: "admin/release_notes/version_1.0.md" + - v1.1: "admin/release_notes/version_1.1.md" + - v1.2: "admin/release_notes/version_1.2.md" + - v1.3: "admin/release_notes/version_1.3.md" + - v1.4: "admin/release_notes/version_1.4.md" + - v1.5: "admin/release_notes/version_1.5.md" - Developer Guide: - Extending the Library: "dev/extending.md" - Contributing to the Library: "dev/contributing.md" diff --git a/poetry.lock b/poetry.lock index 9d7412e..ac735e1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "annotated-types" @@ -142,14 +142,14 @@ test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock [[package]] name = "astroid" -version = "4.0.4" +version = "3.3.11" description = "An abstract syntax tree for Python with inference support." optional = false -python-versions = ">=3.10.0" +python-versions = ">=3.9.0" groups = ["dev"] files = [ - {file = "astroid-4.0.4-py3-none-any.whl", hash = "sha256:52f39653876c7dec3e3afd4c2696920e05c83832b9737afc21928f2d2eb7a753"}, - {file = "astroid-4.0.4.tar.gz", hash = "sha256:986fed8bcf79fb82c78b18a53352a0b287a73817d6dbcfba3162da36667c49a0"}, + {file = "astroid-3.3.11-py3-none-any.whl", hash = "sha256:54c760ae8322ece1abd213057c4b5bba7c49818853fc901ef09719a60dbf9dec"}, + {file = "astroid-3.3.11.tar.gz", hash = "sha256:1e5a5011af2920c7c67a53f65d536d65bfa7116feeaf2354d8b94f29573bb0ce"}, ] [package.dependencies] @@ -157,93 +157,58 @@ typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} [[package]] name = "attrs" -version = "26.1.0" +version = "23.2.0" description = "Classes Without Boilerplate" optional = false -python-versions = ">=3.9" -groups = ["main"] +python-versions = ">=3.7" +groups = ["main", "dev"] files = [ - {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, - {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, ] +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6) ; platform_python_implementation == \"CPython\" and python_version >= \"3.8\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.8\""] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] + [[package]] -name = "bandit" -version = "1.9.4" -description = "Security oriented static analyser for python code." +name = "babel" +version = "2.18.0" +description = "Internationalization utilities" optional = false -python-versions = ">=3.10" -groups = ["dev"] +python-versions = ">=3.8" +groups = ["docs"] files = [ - {file = "bandit-1.9.4-py3-none-any.whl", hash = "sha256:f89ffa663767f5a0585ea075f01020207e966a9c0f2b9ef56a57c7963a3f6f8e"}, - {file = "bandit-1.9.4.tar.gz", hash = "sha256:b589e5de2afe70bd4d53fa0c1da6199f4085af666fde00e8a034f152a52cd628"}, + {file = "babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35"}, + {file = "babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d"}, ] -[package.dependencies] -colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} -PyYAML = ">=5.3.1" -rich = "*" -stevedore = ">=1.20.0" - [package.extras] -baseline = ["GitPython (>=3.1.30)"] -sarif = ["jschema-to-python (>=1.2.3)", "sarif-om (>=1.0.4)"] -test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)"] -toml = ["tomli (>=1.1.0) ; python_version < \"3.11\""] -yaml = ["PyYAML"] +dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] [[package]] -name = "black" -version = "26.3.1" -description = "The uncompromising code formatter." +name = "backrefs" +version = "5.9" +description = "A wrapper around re and regex that adds additional back references." optional = false -python-versions = ">=3.10" -groups = ["dev"] +python-versions = ">=3.9" +groups = ["docs"] files = [ - {file = "black-26.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:86a8b5035fce64f5dcd1b794cf8ec4d31fe458cf6ce3986a30deb434df82a1d2"}, - {file = "black-26.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5602bdb96d52d2d0672f24f6ffe5218795736dd34807fd0fd55ccd6bf206168b"}, - {file = "black-26.3.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c54a4a82e291a1fee5137371ab488866b7c86a3305af4026bdd4dc78642e1ac"}, - {file = "black-26.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:6e131579c243c98f35bce64a7e08e87fb2d610544754675d4a0e73a070a5aa3a"}, - {file = "black-26.3.1-cp310-cp310-win_arm64.whl", hash = "sha256:5ed0ca58586c8d9a487352a96b15272b7fa55d139fc8496b519e78023a8dab0a"}, - {file = "black-26.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:28ef38aee69e4b12fda8dba75e21f9b4f979b490c8ac0baa7cb505369ac9e1ff"}, - {file = "black-26.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bf162ed91a26f1adba8efda0b573bc6924ec1408a52cc6f82cb73ec2b142c"}, - {file = "black-26.3.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:474c27574d6d7037c1bc875a81d9be0a9a4f9ee95e62800dab3cfaadbf75acd5"}, - {file = "black-26.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:5e9d0d86df21f2e1677cc4bd090cd0e446278bcbbe49bf3659c308c3e402843e"}, - {file = "black-26.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:9a5e9f45e5d5e1c5b5c29b3bd4265dcc90e8b92cf4534520896ed77f791f4da5"}, - {file = "black-26.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e6f89631eb88a7302d416594a32faeee9fb8fb848290da9d0a5f2903519fc1"}, - {file = "black-26.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cd2012d35b47d589cb8a16faf8a32ef7a336f56356babd9fcf70939ad1897f"}, - {file = "black-26.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f76ff19ec5297dd8e66eb64deda23631e642c9393ab592826fd4bdc97a4bce7"}, - {file = "black-26.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ddb113db38838eb9f043623ba274cfaf7d51d5b0c22ecb30afe58b1bb8322983"}, - {file = "black-26.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:dfdd51fc3e64ea4f35873d1b3fb25326773d55d2329ff8449139ebaad7357efb"}, - {file = "black-26.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:855822d90f884905362f602880ed8b5df1b7e3ee7d0db2502d4388a954cc8c54"}, - {file = "black-26.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8a33d657f3276328ce00e4d37fe70361e1ec7614da5d7b6e78de5426cb56332f"}, - {file = "black-26.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1cd08e99d2f9317292a311dfe578fd2a24b15dbce97792f9c4d752275c1fa56"}, - {file = "black-26.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:c7e72339f841b5a237ff14f7d3880ddd0fc7f98a1199e8c4327f9a4f478c1839"}, - {file = "black-26.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc622538b430aa4c8c853f7f63bc582b3b8030fd8c80b70fb5fa5b834e575c2"}, - {file = "black-26.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2d6bfaf7fd0993b420bed691f20f9492d53ce9a2bcccea4b797d34e947318a78"}, - {file = "black-26.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f89f2ab047c76a9c03f78d0d66ca519e389519902fa27e7a91117ef7611c0568"}, - {file = "black-26.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b07fc0dab849d24a80a29cfab8d8a19187d1c4685d8a5e6385a5ce323c1f015f"}, - {file = "black-26.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:0126ae5b7c09957da2bdbd91a9ba1207453feada9e9fe51992848658c6c8e01c"}, - {file = "black-26.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:92c0ec1f2cc149551a2b7b47efc32c866406b6891b0ee4625e95967c8f4acfb1"}, - {file = "black-26.3.1-py3-none-any.whl", hash = "sha256:2bd5aa94fc267d38bb21a70d7410a89f1a1d318841855f698746f8e7f51acd1b"}, - {file = "black-26.3.1.tar.gz", hash = "sha256:2c50f5063a9641c7eed7795014ba37b0f5fa227f3d408b968936e24bc0566b07"}, + {file = "backrefs-5.9-py310-none-any.whl", hash = "sha256:db8e8ba0e9de81fcd635f440deab5ae5f2591b54ac1ebe0550a2ca063488cd9f"}, + {file = "backrefs-5.9-py311-none-any.whl", hash = "sha256:6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf"}, + {file = "backrefs-5.9-py312-none-any.whl", hash = "sha256:7fdf9771f63e6028d7fee7e0c497c81abda597ea45d6b8f89e8ad76994f5befa"}, + {file = "backrefs-5.9-py313-none-any.whl", hash = "sha256:cc37b19fa219e93ff825ed1fed8879e47b4d89aa7a1884860e2db64ccd7c676b"}, + {file = "backrefs-5.9-py314-none-any.whl", hash = "sha256:df5e169836cc8acb5e440ebae9aad4bf9d15e226d3bad049cf3f6a5c20cc8dc9"}, + {file = "backrefs-5.9-py39-none-any.whl", hash = "sha256:f48ee18f6252b8f5777a22a00a09a85de0ca931658f1dd96d4406a34f3748c60"}, + {file = "backrefs-5.9.tar.gz", hash = "sha256:808548cb708d66b82ee231f962cb36faaf4f2baab032f2fbb783e9c2fdddaa59"}, ] -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=1.0.0" -platformdirs = ">=2" -pytokens = ">=0.4.0,<0.5.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - [package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.10)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2) ; sys_platform != \"win32\"", "winloop (>=0.5.0) ; sys_platform == \"win32\""] +extras = ["regex"] [[package]] name = "certifi" @@ -251,7 +216,7 @@ version = "2026.2.25" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" -groups = ["dev"] +groups = ["dev", "docs"] files = [ {file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"}, {file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"}, @@ -361,7 +326,7 @@ version = "3.4.7" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["dev"] +groups = ["dev", "docs"] files = [ {file = "charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d"}, {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8"}, @@ -500,7 +465,7 @@ version = "8.3.2" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" -groups = ["main", "dev"] +groups = ["main", "dev", "docs"] files = [ {file = "click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d"}, {file = "click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5"}, @@ -515,13 +480,147 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] +groups = ["main", "dev", "docs"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} +[[package]] +name = "coverage" +version = "7.15.4" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "coverage-7.15.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d0be6daac4cce6b8c8dc65886bae1b082ddbca4da8e5cbb5e15166acf253e264"}, + {file = "coverage-7.15.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b24e078eabcd6a9caa8b0713f9bc1eeb310bcc960a29d45a3b4fcd4b16d5b11d"}, + {file = "coverage-7.15.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cfe20cc8cf8821d4fe54f89106cbf06aa27f37b5bbe3535568065a81539b4150"}, + {file = "coverage-7.15.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:83cf06cdd687677742caff1a9134833b7a8b75f111519d2cb0e0ba1b9a851e15"}, + {file = "coverage-7.15.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8fa4de68e2a752468ff14b4e15db7def689a71be759e826a31ccecbef69c5fd0"}, + {file = "coverage-7.15.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4dff9daa47d83120c3ec38ce921214242944a832aa04e903e50b5b7ebac8972d"}, + {file = "coverage-7.15.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a093fd37229918976f602aa07aa59e0973cde82186f220c8e197f721f5be0ce4"}, + {file = "coverage-7.15.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:317db01a2cb02552fd67e2b1cca77a4b528a2a277176c5e0bf2cecbb639d3f54"}, + {file = "coverage-7.15.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8ee3838dcb656602c3b51e16aed9bfb0822f8d8d6d1c5966d32ec8c104be8e20"}, + {file = "coverage-7.15.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:425920379052ff1fe465268f3361d35804a241bbdd5a1b592c8cb60df4c52325"}, + {file = "coverage-7.15.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:69bb2400abef928e365ea7d4d9925169ada78ed2295546780002d4b65de3df88"}, + {file = "coverage-7.15.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:81661f82d302484e3119e7c80c519c02fa9bcc2a6b339baf67d67bc89c580f04"}, + {file = "coverage-7.15.4-cp310-cp310-win32.whl", hash = "sha256:cb476b2e828ecb71cb6b6a928d23fd20a7ddb501188022dae1c37499149cc338"}, + {file = "coverage-7.15.4-cp310-cp310-win_amd64.whl", hash = "sha256:3fc2130bf37df31852a8384f12601563a45a0024bccc6624f38355cba7a8d360"}, + {file = "coverage-7.15.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bbac5abad70df71019988f83f26ac7092ff2642975def4429e98dc7585ef3490"}, + {file = "coverage-7.15.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:357a173465c7ce028d07a95cc2b63b5bf59f50ecdd5ad75c5cbb78ada984048e"}, + {file = "coverage-7.15.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:21b803935e2efc3acebe9697197a294fccf5dc4e5382bd6369542ff7a7d2a1d7"}, + {file = "coverage-7.15.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7a2b580774a4786c1053157c0165e04476e03ff293993d7c148eee784a94bae6"}, + {file = "coverage-7.15.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9464451c4efffe8d47ace5a540b10b0dc10e879066290f8600872b7f54a419d"}, + {file = "coverage-7.15.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de602f34123c2f4af1c1869c6dbbbd60da6d5983bf01937367295d135cccbfce"}, + {file = "coverage-7.15.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6879ded16a27f3eeca19b900c147e81616e7054db451471a611b2755ee5249f7"}, + {file = "coverage-7.15.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:986be58c3ab54aae8d3496a6225eea74f760fdbe739b38bd442c7e8d133aa53b"}, + {file = "coverage-7.15.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c6103639613fe6c1e989082948419bc77a2d26b6c825c99d7fad25f7d3d87afc"}, + {file = "coverage-7.15.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d3af93dddb5659276c63bc16ac6466ac2033a70ca816097bbc06345b8ccdf571"}, + {file = "coverage-7.15.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b10075e5421d04265766a6d1dac809bbeb8a946fbb23c8f82c227409b2190719"}, + {file = "coverage-7.15.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a67a9f78b2942d87ba8ce3059c642164d2aedd65337377fb52fe9803656bc5c7"}, + {file = "coverage-7.15.4-cp311-cp311-win32.whl", hash = "sha256:69484d1aca26e322e1c3ce03f09341e84524ababad2d7202161738d83cc9f82e"}, + {file = "coverage-7.15.4-cp311-cp311-win_amd64.whl", hash = "sha256:63fd6fcd1dd6e158f7eb78606e72933b3f6d01e7b747f99c6c12d764307a0fdc"}, + {file = "coverage-7.15.4-cp311-cp311-win_arm64.whl", hash = "sha256:ea82116c9893fa89e929b7f197ee5a1950a76e91cc5c85ba503fc02379d04890"}, + {file = "coverage-7.15.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4fedd1f7f428f9fe83b1ead5e7cc87a43427be31aadafbac3ac0636dc7abb22"}, + {file = "coverage-7.15.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37e2f0cdf58e2e1fed4e4d5a8f8786ae2f7eb80b478016876667dc4a01d60a97"}, + {file = "coverage-7.15.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fb55d0e70bb15f2e81477613627286581414693d74ac7963c93a790dd453ca9d"}, + {file = "coverage-7.15.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:899b9da30f3c6c336566e3707495bb23e8302d39d862f01fa78c48b99b9437e2"}, + {file = "coverage-7.15.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d15715e8c46552827e5e4f30a35575a2dbcad14454cf3284c54483946bd16931"}, + {file = "coverage-7.15.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:002a438859f7b430bc99afeaf01a6d187dad1d0dc907b64cdeffc632a5db8fd8"}, + {file = "coverage-7.15.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e4193a04b518f7968f3099755f5509ee7cccc6dc2b92a6b14841934d22e222c9"}, + {file = "coverage-7.15.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e98dcc55d572b38e69d117da7e8e8efb8500f1f5eaf81ecd460a63220790b839"}, + {file = "coverage-7.15.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:af6c538498ce66c10d3fd541c2a8d5b03da5850355add34e6cba564210cb9e72"}, + {file = "coverage-7.15.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1d10025d96ea89fc2f73714dbc4cbd433fe012c1ac9e23f895d7728b238b6e52"}, + {file = "coverage-7.15.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d802e1947603162ded419bff83ac7489820355d2b856dfb09206574e3a37ac0c"}, + {file = "coverage-7.15.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c2de40895718f91951b86712b4c5b694acaf9a0a49be13874896f599a1eed3f4"}, + {file = "coverage-7.15.4-cp312-cp312-win32.whl", hash = "sha256:5c3431b2161279b7db5c2a1aa58ae02e5cb8c3c42d93a5094be3f5537bd5b11b"}, + {file = "coverage-7.15.4-cp312-cp312-win_amd64.whl", hash = "sha256:6befeab5fb2b51c958ca4ac6c5d141a1e8240f4f76e46350f1911963deda49cd"}, + {file = "coverage-7.15.4-cp312-cp312-win_arm64.whl", hash = "sha256:67bc345491ab55b837277d76f5775d057e8c7f1ac44d890d8c2c82adde258c6f"}, + {file = "coverage-7.15.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c705b28feb2775dc82a25f1d473a370bc37ff93f5177f4e29ce2425f560f6921"}, + {file = "coverage-7.15.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ff205ab5e3ecc670f6a4dd19d9cbf12ede53dd41cfc1e15716ec961ea6d314e"}, + {file = "coverage-7.15.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5172326e861a38b48b48befca15e0f477a26b283337a33a739c8fed229934e36"}, + {file = "coverage-7.15.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:12b59c90084e3234fb11184886bf4a40f4f16a8c8f867be2e087b81f8e8868d4"}, + {file = "coverage-7.15.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349062d66f00b40fa2c1c222438bad25fabf755631b5d82937fe985c8008615c"}, + {file = "coverage-7.15.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4256ced708e598e05209bc1a8ab4074e04a51dba4c62fb45926a229af675ace7"}, + {file = "coverage-7.15.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d80f974b20782d9612c8b4c9beeca867074c7cf4079d1419843fa25a26428b25"}, + {file = "coverage-7.15.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e179f19bfe1d31f8eeeaa12990194d761c4f62f0759661000bca6cd8729f40b"}, + {file = "coverage-7.15.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8bc16bb47b7679670eceff71d78bfb7d6e5b143f6c2cd117487ec7c75e0d4b78"}, + {file = "coverage-7.15.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1cd685005cd2c4200adfc14cf39a603b9320efab3f18a8f7f156d20c9cc3345f"}, + {file = "coverage-7.15.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:337399ad2c93b3acd2a937627dae8b3e86b66707cd3d3e856347999aadf1ef8d"}, + {file = "coverage-7.15.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:96e257121228ec5cd2bb919276e94ac11074471bc37d68dbae0e8308cce15fff"}, + {file = "coverage-7.15.4-cp313-cp313-win32.whl", hash = "sha256:c65a9e0dfc6143491879da4e13b5e30f8be192055de508d737fb14601edbd22c"}, + {file = "coverage-7.15.4-cp313-cp313-win_amd64.whl", hash = "sha256:2ff8f5e9b8f7a94f0c11c45631eee103dbcb7d63274edd12c56efe1be690b3b4"}, + {file = "coverage-7.15.4-cp313-cp313-win_arm64.whl", hash = "sha256:6e0a8a5083b096487d6cfced94cdd514d8f5db6f113610fb36c0620edb1028cf"}, + {file = "coverage-7.15.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:770e9325ab5ea6d56f77e59b29ecfe0ac20b57a82a601876f90494a4dda0386f"}, + {file = "coverage-7.15.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d12b33a3a50a1676b7784dc8d00a0c6d66a9f2add4b85a041c19b6a7e53ef23c"}, + {file = "coverage-7.15.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5669c8378ebde86f5def7a25d29586631b58acc27ffde04399f678f3dfc6e082"}, + {file = "coverage-7.15.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff97a14362eef486483ed44042ca2027ea257df6ff768e62358ee0c9776925ac"}, + {file = "coverage-7.15.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a325e815318638aed1655d9c06e6d7c2d3d46c09231ce988070428a8762d734"}, + {file = "coverage-7.15.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:474223409d88eb20d2d6a0d37ea60e8647a65a90cc008dc1f0410af5f64f1e0d"}, + {file = "coverage-7.15.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7f2f62ae3cd189dd2e13aece758c57b3eecbd27be070dbd4cbd10936049e5dbf"}, + {file = "coverage-7.15.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:39ece820e29e0a2ba34b3ecb3be83c27e997eed8926f2ba6fe7ce7a0bda5843b"}, + {file = "coverage-7.15.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f21b56dcace11dfe013014201f577dcd592b2a9b72182d930361b47cf6f73f25"}, + {file = "coverage-7.15.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:93a3a0b662abcc10c73a47cbc72cd60f63618d6989fb2d1286e50eacd974f303"}, + {file = "coverage-7.15.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:141fae2cabf5569b782c10afc4c850ce10f618c13f8db54765cba99cc839da1f"}, + {file = "coverage-7.15.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:81294c7e6ab30c5f74c0353b11b2fd6320e72d9bee6ac73b357caa8b916323a5"}, + {file = "coverage-7.15.4-cp314-cp314-win32.whl", hash = "sha256:7bbd7d6418e0dab31a206af5203bd43ae36edb8e7fba1940b055d3e9249290d7"}, + {file = "coverage-7.15.4-cp314-cp314-win_amd64.whl", hash = "sha256:f0204ed122758782970526057093f448051a39db9d810d4e344bb87a3546f425"}, + {file = "coverage-7.15.4-cp314-cp314-win_arm64.whl", hash = "sha256:9e71e7bc71c686a123347ae47a0de33a175e797a85bb57b791492adf4eec8ed8"}, + {file = "coverage-7.15.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7c922735321eef3f87c280a3d39afff6b646723a2880b862cda4ac7a093b8aa8"}, + {file = "coverage-7.15.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f41c17c4668a655ce96d090d8d5ffdc24ef64b5a02f9753884d08483e8a4a41a"}, + {file = "coverage-7.15.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:46822e9b6ff1c6a72b518c162c44a8f45a61a1d609c51084bf5b16c023c5037b"}, + {file = "coverage-7.15.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3d6f4955b73b5445271379a59e3792b0d978f42d4a01e0cf7a67d9c33a3bb0a5"}, + {file = "coverage-7.15.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3fc9e047706fb4a9abb54f719d3aa643e80e5bb3818182c40aee01ac0f0247ba"}, + {file = "coverage-7.15.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05e491d4f3165d62d4f5c8fd48dfeabf2ae8f42cbbd484319af33ea851b78982"}, + {file = "coverage-7.15.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:226c66e80ec0598d3b9b4874123df167ccca342aca8714f77cac6829688ee09c"}, + {file = "coverage-7.15.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac41cc14bebda0dbfb0628036b7f75706935c95bcc07fefe9a0f93614aa60a57"}, + {file = "coverage-7.15.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8af623e5cd92080acddd02b38f2f406a2c3a0893c38950b211890361448fbf26"}, + {file = "coverage-7.15.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07545711d4f0f32852a18f18ad11f76f0109909d09e78b9008b4cfc67e829429"}, + {file = "coverage-7.15.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a0865421cfdc53654b342d515e5a233187590882d20b95752150e53f65460017"}, + {file = "coverage-7.15.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:460115e32ee40566476db5048f9bec1e842c127ad8e6f8be745aad3ac9cbc839"}, + {file = "coverage-7.15.4-cp314-cp314t-win32.whl", hash = "sha256:cbde877ef9dd7baf272b9bfef2b8a25edd45d9170fc326951dd20eb480335e85"}, + {file = "coverage-7.15.4-cp314-cp314t-win_amd64.whl", hash = "sha256:3da9e92d1c551fd7563833e9ade686efb0c4b7363ab7681a94283958c950bf5e"}, + {file = "coverage-7.15.4-cp314-cp314t-win_arm64.whl", hash = "sha256:3a54f5a0d85050c73a38f6793090ee83974531e67fe5e57a1da9bee11398aa5e"}, + {file = "coverage-7.15.4-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:2c9872e4d9dc5d3cf616bf4b382f5a00359305a5be666a3dd0b5cdb4e49597f9"}, + {file = "coverage-7.15.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:e101dbb4b9b72f0cddd8cdc8c9c5b47f456766f5e0ac82dbfb75e5c55409b78a"}, + {file = "coverage-7.15.4-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7d1abebdb047729e852b9c77a00497dfbeb11eb3a117e037d7dbc3ac8e5f5c54"}, + {file = "coverage-7.15.4-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d28a4a899354d0ea6214cc59b4fa19eefbce1b9ff1688ab579acf49e894bd3fb"}, + {file = "coverage-7.15.4-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffb3c2aacea411cc7e1d27712490c11108e2de1d39019ae32915493a59a8b9ed"}, + {file = "coverage-7.15.4-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9447978a92f405d301123cfd39ff49895490efb769a758fe2734c7f631bf8ce"}, + {file = "coverage-7.15.4-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:050467a7983b8e2fe7dd41a78bb30c3e7f8c0b8cafda14b1c46f8b5e3cf2dd3c"}, + {file = "coverage-7.15.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:d003b7a5708ddad5c206c79607a6b92abb6fc13c57d99d8a4468cc03a2941ced"}, + {file = "coverage-7.15.4-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c38efe30fd74e5c19e9433f11fb1f5dc9c6522770971b7c6145bbaa413dc8800"}, + {file = "coverage-7.15.4-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:1f4f826d70f772ab8b0c052329580d7fe8b8abd191e4ce0c8f81aec6614665d3"}, + {file = "coverage-7.15.4-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:4a4bf917c9953f57c957be31c1cd504e3bd2f34d4a352b9d391a3025336f6768"}, + {file = "coverage-7.15.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:1c9bf40ebef178a45192c75c4964760bb261b0e6ad725da5fc4c93f674f19753"}, + {file = "coverage-7.15.4-cp315-cp315-win32.whl", hash = "sha256:43619d04c3671792d2c4706ae8bf45e265dc87bbd4078189ef8b847ea1e74be2"}, + {file = "coverage-7.15.4-cp315-cp315-win_amd64.whl", hash = "sha256:be619439dbcd31a2eab10b32de9fff62c26ed4bab69dc32b8363fdaaa0882809"}, + {file = "coverage-7.15.4-cp315-cp315-win_arm64.whl", hash = "sha256:def597967dafc2e8d97c9097ea453c464e0bb8ed38f193a43070f10dc623bb6d"}, + {file = "coverage-7.15.4-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c7dbc748ac8a1e3e59a2b28bea47675e6e778081dbbf081bde0d75def2fcbe1d"}, + {file = "coverage-7.15.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:2413074a5ecbb61a01a7888fc72db0ca324d13588c5b38bc0dd8564cdcdfea26"}, + {file = "coverage-7.15.4-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4e6f6f632b7b2f714bf7a1346e8f97b650ee71f3c298aaad42a2ab60f0f07645"}, + {file = "coverage-7.15.4-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8df457da2249d3c75ca2e5e835d59c725abfe92d27fdff6cd99eed85b51d5e9a"}, + {file = "coverage-7.15.4-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:050f66a08805acb5b8a23c6d4a517b1ecf82c08e81ed0e4bd727df065e5c6624"}, + {file = "coverage-7.15.4-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1587fb771d1ccceef708fdde1e5af8c7ed24b486b61d13a321acb7d8145390aa"}, + {file = "coverage-7.15.4-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8b4f1c3a69ca580f3fbd6b2046915f536d7f586874f25c1bb23add2a3c88d50f"}, + {file = "coverage-7.15.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:ffb58d7eff5b7f6ecc6fa21d6288ab7f968a212cb67d682c269c09b9eba3b66f"}, + {file = "coverage-7.15.4-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:d9df165544774574ee004b953023d1bebada1894a80b1052a43d798b0f676e67"}, + {file = "coverage-7.15.4-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:f9de0a24a4079b53e523b5c5e2c5945ec251ab486652659955187cf255a259bc"}, + {file = "coverage-7.15.4-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:150089274bdc9f940628552cb92844e0223c987f1902ab8efe9f45a2ec758d88"}, + {file = "coverage-7.15.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:a58a94fed5da6997d258e8f7668c1e195fbd04a691d781b7558f1e468f9e68bc"}, + {file = "coverage-7.15.4-cp315-cp315t-win32.whl", hash = "sha256:ebd5a6d8466ff30836572f3ba2cae8a5e8f85029b1c6d5e2ed338dc472a5166a"}, + {file = "coverage-7.15.4-cp315-cp315t-win_amd64.whl", hash = "sha256:288bde2a2d7ab6b6c2d7252fcde8b524387f2d970bdba9658fc6f8bbcaef0f9b"}, + {file = "coverage-7.15.4-cp315-cp315t-win_arm64.whl", hash = "sha256:68be5e1de60ff13c9095bbec0e5a7fa45b33b101752215b91345ea1f61c4a278"}, + {file = "coverage-7.15.4-py3-none-any.whl", hash = "sha256:964730a1e9de9c0cf11be6a1a3c79ce419c34882842abd256086ba4698705e84"}, + {file = "coverage-7.15.4.tar.gz", hash = "sha256:0548198fff07ccf4faf469520bce1c2eceb1ce3e62891921138dec10907f9d00"}, +] + +[package.extras] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] + [[package]] name = "cryptography" version = "46.0.7" @@ -631,23 +730,6 @@ typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} [package.extras] test = ["pytest (>=6)"] -[[package]] -name = "flake8" -version = "7.3.0" -description = "the modular source code checker: pep8 pyflakes and co" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"}, - {file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"}, -] - -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.14.0,<2.15.0" -pyflakes = ">=3.4.0,<3.5.0" - [[package]] name = "fqdn" version = "1.5.1" @@ -660,13 +742,58 @@ files = [ {file = "fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f"}, ] +[[package]] +name = "ghp-import" +version = "2.1.0" +description = "Copy your docs directly to the gh-pages branch." +optional = false +python-versions = "*" +groups = ["docs"] +files = [ + {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, + {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, +] + +[package.dependencies] +python-dateutil = ">=2.8.1" + +[package.extras] +dev = ["flake8", "markdown", "twine", "wheel"] + +[[package]] +name = "griffe" +version = "1.1.1" +description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." +optional = false +python-versions = ">=3.8" +groups = ["docs"] +files = [ + {file = "griffe-1.1.1-py3-none-any.whl", hash = "sha256:0c469411e8d671a545725f5c0851a746da8bd99d354a79fdc4abd45219252efb"}, + {file = "griffe-1.1.1.tar.gz", hash = "sha256:faeb78764c0b2bd010719d6e015d07709b0f260258b5d4dd6c88343d9702aa30"}, +] + +[package.dependencies] +colorama = ">=0.4" + +[[package]] +name = "hjson" +version = "3.1.0" +description = "Hjson, a user interface for JSON." +optional = false +python-versions = "*" +groups = ["docs"] +files = [ + {file = "hjson-3.1.0-py3-none-any.whl", hash = "sha256:65713cdcf13214fb554eb8b4ef803419733f4f5e551047c9b711098ab7186b89"}, + {file = "hjson-3.1.0.tar.gz", hash = "sha256:55af475a27cf83a7969c808399d7bccdec8fb836a07ddbd574587593b9cdcf75"}, +] + [[package]] name = "idna" version = "3.11" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "docs"] files = [ {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, @@ -689,14 +816,14 @@ files = [ [[package]] name = "invoke" -version = "3.0.3" +version = "2.2.1" description = "Pythonic task execution" optional = false -python-versions = ">=3.9" +python-versions = ">=3.6" groups = ["dev"] files = [ - {file = "invoke-3.0.3-py3-none-any.whl", hash = "sha256:f11327165e5cbb89b2ad1d88d3292b5113332c43b8553b494da435d6ec6f5053"}, - {file = "invoke-3.0.3.tar.gz", hash = "sha256:437b6a622223824380bfb4e64f612711a6b648c795f565efc8625af66fb57f0c"}, + {file = "invoke-2.2.1-py3-none-any.whl", hash = "sha256:2413bc441b376e5cd3f55bb5d364f973ad8bdd7bf87e53c79de3c11bf3feecc8"}, + {file = "invoke-2.2.1.tar.gz", hash = "sha256:515bf49b4a48932b79b024590348da22f39c4942dff991ad1fb8b8baea1be707"}, ] [[package]] @@ -716,18 +843,19 @@ arrow = ">=0.15.0" [[package]] name = "isort" -version = "8.0.1" +version = "6.1.0" description = "A Python utility / library to sort Python imports." optional = false -python-versions = ">=3.10.0" +python-versions = ">=3.9.0" groups = ["dev"] files = [ - {file = "isort-8.0.1-py3-none-any.whl", hash = "sha256:28b89bc70f751b559aeca209e6120393d43fbe2490de0559662be7a9787e3d75"}, - {file = "isort-8.0.1.tar.gz", hash = "sha256:171ac4ff559cdc060bcfff550bc8404a486fee0caab245679c2abe7cb253c78d"}, + {file = "isort-6.1.0-py3-none-any.whl", hash = "sha256:58d8927ecce74e5087aef019f778d4081a3b6c98f15a80ba35782ca8a2097784"}, + {file = "isort-6.1.0.tar.gz", hash = "sha256:9b8f96a14cfee0677e78e941ff62f03769a06d412aabb9e2a90487b3b7e8d481"}, ] [package.extras] colors = ["colorama"] +plugins = ["setuptools"] [[package]] name = "jinja2" @@ -735,7 +863,7 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "dev", "docs"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -847,13 +975,46 @@ interegular = ["interegular (>=0.3.1,<0.4.0)"] nearley = ["js2py"] regex = ["regex"] +[[package]] +name = "markdown" +version = "3.10.3" +description = "Python implementation of John Gruber's Markdown." +optional = false +python-versions = ">=3.10" +groups = ["dev", "docs"] +files = [ + {file = "markdown-3.10.3-py3-none-any.whl", hash = "sha256:fa6c92a00a4a3c98b22728c64a935ae1928250ae65058a6ded814d2cc29a4cea"}, + {file = "markdown-3.10.3.tar.gz", hash = "sha256:3589362618f743188b4d955b874402bc814f4f83f544dc207719f4baa7d9c45f"}, +] + +[package.extras] +docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python] (>=0.28.3)"] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "markdown-data-tables" +version = "1.0.0" +description = "Embed data files such as YAML as tables in a Markdown document" +optional = false +python-versions = ">=3.8,<4.0" +groups = ["docs"] +files = [ + {file = "markdown_data_tables-1.0.0-py3-none-any.whl", hash = "sha256:a59c6743685691ced4341bdb01024b7a863a1adaa3a2ef92fa068a7e90227d9a"}, + {file = "markdown_data_tables-1.0.0.tar.gz", hash = "sha256:ac1b07c58bb66e9f060ba81cdd63070ec94deb21f0147e519c77c8475ba696ea"}, +] + +[package.dependencies] +markdown = ">=3.3.7,<4.0.0" +pyyaml = ">=6.0,<7.0" +tabulate = ">=0.9.0,<0.10.0" + [[package]] name = "markdown-it-py" version = "4.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.10" -groups = ["main", "dev"] +groups = ["main"] files = [ {file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"}, {file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"}, @@ -871,13 +1032,28 @@ profiling = ["gprof2dot"] rtd = ["ipykernel", "jupyter_sphinx", "mdit-py-plugins (>=0.5.0)", "myst-parser", "pyyaml", "sphinx", "sphinx-book-theme (>=1.0,<2.0)", "sphinx-copybutton", "sphinx-design"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions", "requests"] +[[package]] +name = "markdown-version-annotations" +version = "1.0.1" +description = "Markdown plugin to add custom admonitions for documenting version differences" +optional = false +python-versions = "<4.0,>=3.7" +groups = ["docs"] +files = [ + {file = "markdown_version_annotations-1.0.1-py3-none-any.whl", hash = "sha256:6df0b2ac08bab906c8baa425f59fc0fe342fbe8b3917c144fb75914266b33200"}, + {file = "markdown_version_annotations-1.0.1.tar.gz", hash = "sha256:620aade507ef175ccfb2059db152a34c6a1d2add28c2be16ea4de38d742e6132"}, +] + +[package.dependencies] +markdown = ">=3.3.7,<4.0.0" + [[package]] name = "markupsafe" version = "3.0.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev", "docs"] files = [ {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, @@ -988,44 +1164,311 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] +groups = ["main"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] [[package]] -name = "mypy-extensions" -version = "1.1.0" -description = "Type system extensions for programs checked with the mypy type checker." +name = "mergedeep" +version = "1.3.4" +description = "A deep merge function for 🐍." +optional = false +python-versions = ">=3.6" +groups = ["docs"] +files = [ + {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, + {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +description = "Project documentation with Markdown." +optional = false +python-versions = ">=3.8" +groups = ["docs"] +files = [ + {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, + {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} +ghp-import = ">=1.0" +jinja2 = ">=2.11.1" +markdown = ">=3.3.6" +markupsafe = ">=2.0.1" +mergedeep = ">=1.3.4" +mkdocs-get-deps = ">=0.2.0" +packaging = ">=20.5" +pathspec = ">=0.11.1" +pyyaml = ">=5.1" +pyyaml-env-tag = ">=0.1" +watchdog = ">=2.0" + +[package.extras] +i18n = ["babel (>=2.9.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] + +[[package]] +name = "mkdocs-autorefs" +version = "1.4.4" +description = "Automatically link across pages in MkDocs." +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "mkdocs_autorefs-1.4.4-py3-none-any.whl", hash = "sha256:834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089"}, + {file = "mkdocs_autorefs-1.4.4.tar.gz", hash = "sha256:d54a284f27a7346b9c38f1f852177940c222da508e66edc816a0fa55fc6da197"}, +] + +[package.dependencies] +Markdown = ">=3.3" +markupsafe = ">=2.0.1" +mkdocs = ">=1.1" + +[[package]] +name = "mkdocs-gen-files" +version = "0.5.0" +description = "MkDocs plugin to programmatically generate documentation pages during the build" +optional = false +python-versions = ">=3.7" +groups = ["docs"] +files = [ + {file = "mkdocs_gen_files-0.5.0-py3-none-any.whl", hash = "sha256:7ac060096f3f40bd19039e7277dd3050be9a453c8ac578645844d4d91d7978ea"}, + {file = "mkdocs_gen_files-0.5.0.tar.gz", hash = "sha256:4c7cf256b5d67062a788f6b1d035e157fc1a9498c2399be9af5257d4ff4d19bc"}, +] + +[package.dependencies] +mkdocs = ">=1.0.3" + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.2" +description = "An extra command for MkDocs that infers required PyPI packages from `plugins` in mkdocs.yml" +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650"}, + {file = "mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1"}, +] + +[package.dependencies] +mergedeep = ">=1.3.4" +platformdirs = ">=2.2.0" +pyyaml = ">=5.1" + +[[package]] +name = "mkdocs-glightbox" +version = "0.4.0" +description = "MkDocs plugin supports image lightbox with GLightbox." +optional = false +python-versions = "*" +groups = ["docs"] +files = [ + {file = "mkdocs-glightbox-0.4.0.tar.gz", hash = "sha256:392b34207bf95991071a16d5f8916d1d2f2cd5d5bb59ae2997485ccd778c70d9"}, + {file = "mkdocs_glightbox-0.4.0-py3-none-any.whl", hash = "sha256:e0107beee75d3eb7380ac06ea2d6eac94c999eaa49f8c3cbab0e7be2ac006ccf"}, +] + +[[package]] +name = "mkdocs-macros-plugin" +version = "1.3.7" +description = "Unleash the power of MkDocs with macros and variables" optional = false python-versions = ">=3.8" +groups = ["docs"] +files = [ + {file = "mkdocs_macros_plugin-1.3.7-py3-none-any.whl", hash = "sha256:02432033a5b77fb247d6ec7924e72fc4ceec264165b1644ab8d0dc159c22ce59"}, + {file = "mkdocs_macros_plugin-1.3.7.tar.gz", hash = "sha256:17c7fd1a49b94defcdb502fd453d17a1e730f8836523379d21292eb2be4cb523"}, +] + +[package.dependencies] +hjson = "*" +jinja2 = "*" +mkdocs = ">=0.17" +packaging = "*" +pathspec = "*" +python-dateutil = "*" +pyyaml = "*" +super-collections = "*" +termcolor = "*" + +[package.extras] +test = ["mkdocs-d2-plugin", "mkdocs-include-markdown-plugin", "mkdocs-macros-test", "mkdocs-material (>=6.2)", "mkdocs-test"] + +[[package]] +name = "mkdocs-material" +version = "9.6.15" +description = "Documentation that simply works" +optional = false +python-versions = ">=3.8" +groups = ["docs"] +files = [ + {file = "mkdocs_material-9.6.15-py3-none-any.whl", hash = "sha256:ac969c94d4fe5eb7c924b6d2f43d7db41159ea91553d18a9afc4780c34f2717a"}, + {file = "mkdocs_material-9.6.15.tar.gz", hash = "sha256:64adf8fa8dba1a17905b6aee1894a5aafd966d4aeb44a11088519b0f5ca4f1b5"}, +] + +[package.dependencies] +babel = ">=2.10,<3.0" +backrefs = ">=5.7.post1,<6.0" +colorama = ">=0.4,<1.0" +jinja2 = ">=3.1,<4.0" +markdown = ">=3.2,<4.0" +mkdocs = ">=1.6,<2.0" +mkdocs-material-extensions = ">=1.3,<2.0" +paginate = ">=0.5,<1.0" +pygments = ">=2.16,<3.0" +pymdown-extensions = ">=10.2,<11.0" +requests = ">=2.26,<3.0" + +[package.extras] +git = ["mkdocs-git-committers-plugin-2 (>=1.1,<3)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] +imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"] +recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +description = "Extension pack for Python Markdown and MkDocs Material." +optional = false +python-versions = ">=3.8" +groups = ["docs"] +files = [ + {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, + {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, +] + +[[package]] +name = "mkdocs-redirects" +version = "1.2.2" +description = "A MkDocs plugin for dynamic page redirects to prevent broken links" +optional = false +python-versions = ">=3.8" +groups = ["docs"] +files = [ + {file = "mkdocs_redirects-1.2.2-py3-none-any.whl", hash = "sha256:7dbfa5647b79a3589da4401403d69494bd1f4ad03b9c15136720367e1f340ed5"}, + {file = "mkdocs_redirects-1.2.2.tar.gz", hash = "sha256:3094981b42ffab29313c2c1b8ac3969861109f58b2dd58c45fc81cd44bfa0095"}, +] + +[package.dependencies] +mkdocs = ">=1.1.1" + +[[package]] +name = "mkdocs-section-index" +version = "0.3.10" +description = "MkDocs plugin to allow clickable sections that lead to an index page" +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "mkdocs_section_index-0.3.10-py3-none-any.whl", hash = "sha256:bc27c0d0dc497c0ebaee1fc72839362aed77be7318b5ec0c30628f65918e4776"}, + {file = "mkdocs_section_index-0.3.10.tar.gz", hash = "sha256:a82afbda633c82c5568f0e3b008176b9b365bf4bd8b6f919d6eff09ee146b9f8"}, +] + +[package.dependencies] +mkdocs = ">=1.2" + +[[package]] +name = "mkdocstrings" +version = "0.27.0" +description = "Automatic documentation from sources, for MkDocs." +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "mkdocstrings-0.27.0-py3-none-any.whl", hash = "sha256:6ceaa7ea830770959b55a16203ac63da24badd71325b96af950e59fd37366332"}, + {file = "mkdocstrings-0.27.0.tar.gz", hash = "sha256:16adca6d6b0a1f9e0c07ff0b02ced8e16f228a9d65a37c063ec4c14d7b76a657"}, +] + +[package.dependencies] +click = ">=7.0" +Jinja2 = ">=2.11.1" +Markdown = ">=3.6" +MarkupSafe = ">=1.1" +mkdocs = ">=1.4" +mkdocs-autorefs = ">=1.2" +platformdirs = ">=2.2" +pymdown-extensions = ">=6.3" + +[package.extras] +crystal = ["mkdocstrings-crystal (>=0.3.4)"] +python = ["mkdocstrings-python (>=0.5.2)"] +python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] + +[[package]] +name = "mkdocstrings-python" +version = "1.13.0" +description = "A Python handler for mkdocstrings." +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "mkdocstrings_python-1.13.0-py3-none-any.whl", hash = "sha256:b88bbb207bab4086434743849f8e796788b373bd32e7bfefbf8560ac45d88f97"}, + {file = "mkdocstrings_python-1.13.0.tar.gz", hash = "sha256:2dbd5757e8375b9720e81db16f52f1856bf59905428fd7ef88005d1370e2f64c"}, +] + +[package.dependencies] +griffe = ">=0.49" +mkdocs-autorefs = ">=1.2" +mkdocstrings = ">=0.26" + +[[package]] +name = "mock" +version = "5.2.0" +description = "Rolling backport of unittest.mock for all Pythons" +optional = false +python-versions = ">=3.6" groups = ["dev"] files = [ - {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, - {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, + {file = "mock-5.2.0-py3-none-any.whl", hash = "sha256:7ba87f72ca0e915175596069dbbcc7c75af7b5e9b9bc107ad6349ede0819982f"}, + {file = "mock-5.2.0.tar.gz", hash = "sha256:4e460e818629b4b173f32d08bf30d3af8123afbb8e04bb5707a1fd4799e503f0"}, ] +[package.extras] +build = ["blurb", "twine", "wheel"] +docs = ["sphinx"] +test = ["pytest", "pytest-cov"] + [[package]] name = "packaging" version = "26.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "docs"] files = [ {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, ] markers = {main = "extra == \"ansible-core\" or extra == \"ansible\""} +[[package]] +name = "paginate" +version = "0.5.7" +description = "Divides large result sets into pages for easier browsing" +optional = false +python-versions = "*" +groups = ["docs"] +files = [ + {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, + {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, +] + +[package.extras] +dev = ["pytest", "tox"] +lint = ["black"] + [[package]] name = "pathspec" version = "1.0.4" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["dev", "docs"] files = [ {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"}, {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"}, @@ -1043,7 +1486,7 @@ version = "4.9.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.10" -groups = ["dev"] +groups = ["dev", "docs"] files = [ {file = "platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917"}, {file = "platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a"}, @@ -1065,18 +1508,6 @@ files = [ dev = ["pre-commit", "tox"] testing = ["coverage", "pytest", "pytest-benchmark"] -[[package]] -name = "pycodestyle" -version = "2.14.0" -description = "Python style guide checker" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"}, - {file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"}, -] - [[package]] name = "pycparser" version = "3.0" @@ -1270,43 +1701,13 @@ gcp-secret-manager = ["google-cloud-secret-manager (>=2.23.1)"] toml = ["tomli (>=2.0.1)"] yaml = ["pyyaml (>=6.0.1)"] -[[package]] -name = "pydocstyle" -version = "6.3.0" -description = "Python docstring style checker" -optional = false -python-versions = ">=3.6" -groups = ["dev"] -files = [ - {file = "pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019"}, - {file = "pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"}, -] - -[package.dependencies] -snowballstemmer = ">=2.2.0" - -[package.extras] -toml = ["tomli (>=1.2.3) ; python_version < \"3.11\""] - -[[package]] -name = "pyflakes" -version = "3.4.0" -description = "passive checker of Python programs" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"}, - {file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"}, -] - [[package]] name = "pygments" version = "2.20.0" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "docs"] files = [ {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, @@ -1317,25 +1718,25 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pylint" -version = "4.0.5" +version = "3.3.9" description = "python code static checker" optional = false -python-versions = ">=3.10.0" +python-versions = ">=3.9.0" groups = ["dev"] files = [ - {file = "pylint-4.0.5-py3-none-any.whl", hash = "sha256:00f51c9b14a3b3ae08cff6b2cdd43f28165c78b165b628692e428fb1f8dc2cf2"}, - {file = "pylint-4.0.5.tar.gz", hash = "sha256:8cd6a618df75deb013bd7eb98327a95f02a6fb839205a6bbf5456ef96afb317c"}, + {file = "pylint-3.3.9-py3-none-any.whl", hash = "sha256:01f9b0462c7730f94786c283f3e52a1fbdf0494bbe0971a78d7277ef46a751e7"}, + {file = "pylint-3.3.9.tar.gz", hash = "sha256:d312737d7b25ccf6b01cc4ac629b5dcd14a0fcf3ec392735ac70f137a9d5f83a"}, ] [package.dependencies] -astroid = ">=4.0.2,<=4.1.dev0" +astroid = ">=3.3.8,<=3.4.0.dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, {version = ">=0.3.6", markers = "python_version == \"3.11\""}, ] -isort = ">=5,<5.13 || >5.13,<9" +isort = ">=4.2.5,<5.13 || >5.13,<7" mccabe = ">=0.6,<0.8" platformdirs = ">=2.2" tomli = {version = ">=1.1", markers = "python_version < \"3.11\""} @@ -1345,6 +1746,25 @@ tomlkit = ">=0.10.1" spelling = ["pyenchant (>=3.2,<4.0)"] testutils = ["gitpython (>3)"] +[[package]] +name = "pymdown-extensions" +version = "10.21.3" +description = "Extension pack for Python Markdown." +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "pymdown_extensions-10.21.3-py3-none-any.whl", hash = "sha256:d7a5d08014fc571e80ca21dd6f854e31f94c489800350564d55d15b3c41e76b6"}, + {file = "pymdown_extensions-10.21.3.tar.gz", hash = "sha256:72cfcf55f07aea0d4af2c4f11dd4e52466ddfb1bb819673146398e0bd3a77354"}, +] + +[package.dependencies] +markdown = ">=3.6" +pyyaml = "*" + +[package.extras] +extra = ["pygments (>=2.19.1)"] + [[package]] name = "pytest" version = "9.0.3" @@ -1375,7 +1795,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] +groups = ["main", "docs"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -1399,68 +1819,13 @@ files = [ [package.extras] cli = ["click (>=5.0)"] -[[package]] -name = "pytokens" -version = "0.4.1" -description = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older Pythons." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5"}, - {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe"}, - {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c"}, - {file = "pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7"}, - {file = "pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2"}, - {file = "pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440"}, - {file = "pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc"}, - {file = "pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d"}, - {file = "pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16"}, - {file = "pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6"}, - {file = "pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083"}, - {file = "pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1"}, - {file = "pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1"}, - {file = "pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9"}, - {file = "pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68"}, - {file = "pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b"}, - {file = "pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f"}, - {file = "pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1"}, - {file = "pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4"}, - {file = "pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78"}, - {file = "pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321"}, - {file = "pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa"}, - {file = "pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d"}, - {file = "pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324"}, - {file = "pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9"}, - {file = "pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb"}, - {file = "pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3"}, - {file = "pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975"}, - {file = "pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a"}, - {file = "pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918"}, - {file = "pytokens-0.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:da5baeaf7116dced9c6bb76dc31ba04a2dc3695f3d9f74741d7910122b456edc"}, - {file = "pytokens-0.4.1-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11edda0942da80ff58c4408407616a310adecae1ddd22eef8c692fe266fa5009"}, - {file = "pytokens-0.4.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0fc71786e629cef478cbf29d7ea1923299181d0699dbe7c3c0f4a583811d9fc1"}, - {file = "pytokens-0.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dcafc12c30dbaf1e2af0490978352e0c4041a7cde31f4f81435c2a5e8b9cabb6"}, - {file = "pytokens-0.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:42f144f3aafa5d92bad964d471a581651e28b24434d184871bd02e3a0d956037"}, - {file = "pytokens-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:34bcc734bd2f2d5fe3b34e7b3c0116bfb2397f2d9666139988e7a3eb5f7400e3"}, - {file = "pytokens-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:941d4343bf27b605e9213b26bfa1c4bf197c9c599a9627eb7305b0defcfe40c1"}, - {file = "pytokens-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3ad72b851e781478366288743198101e5eb34a414f1d5627cdd585ca3b25f1db"}, - {file = "pytokens-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:682fa37ff4d8e95f7df6fe6fe6a431e8ed8e788023c6bcc0f0880a12eab80ad1"}, - {file = "pytokens-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:30f51edd9bb7f85c748979384165601d028b84f7bd13fe14d3e065304093916a"}, - {file = "pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de"}, - {file = "pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a"}, -] - -[package.extras] -dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] - [[package]] name = "pyyaml" version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "docs"] files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -1537,6 +1902,21 @@ files = [ {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, ] +[[package]] +name = "pyyaml-env-tag" +version = "1.1" +description = "A custom YAML tag for referencing environment variables in YAML files." +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04"}, + {file = "pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff"}, +] + +[package.dependencies] +pyyaml = "*" + [[package]] name = "referencing" version = "0.37.0" @@ -1560,7 +1940,7 @@ version = "2.33.1" description = "Python HTTP for Humans." optional = false python-versions = ">=3.10" -groups = ["dev"] +groups = ["dev", "docs"] files = [ {file = "requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a"}, {file = "requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517"}, @@ -1682,7 +2062,7 @@ version = "14.3.3" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" -groups = ["main", "dev"] +groups = ["main"] files = [ {file = "rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d"}, {file = "rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b"}, @@ -1911,49 +2291,86 @@ files = [ {file = "ruamel_yaml_clib-0.2.15.tar.gz", hash = "sha256:46e4cc8c43ef6a94885f72512094e482114a8a706d3c555a34ed4b0d20200600"}, ] +[[package]] +name = "ruff" +version = "0.16.2" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "ruff-0.16.2-py3-none-linux_armv6l.whl", hash = "sha256:3c8de4cf2181f01d57946d87d777aa52916976fc09942aed89938fab5e013318"}, + {file = "ruff-0.16.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a48cc05c6fbc811ca81b5d7ba95375affea6582d1b8024e455e41afbbf55344"}, + {file = "ruff-0.16.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a2c0d14fcbb26c91f0f867a6dc9bd71bbc30b1b6151829c884f23faeab2e5700"}, + {file = "ruff-0.16.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:335c621622c4650330be50842561c6586ac6971bb8ab5407fe34dcc9efb16bbe"}, + {file = "ruff-0.16.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20e66910f2c37cc753f9ef6580c914a621b80c4fa3549d3e3521e29d0f5bfc3f"}, + {file = "ruff-0.16.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7e36fbfba65510548156902bcf1350a979a958ce0347ce0f90d73894036b39f"}, + {file = "ruff-0.16.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f0eab35f80df8f134aae5d1630e751901321d317cc8e50dc39e36fa3ed34cd12"}, + {file = "ruff-0.16.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ea8c0594feb894e89c8c61ab9c103d38b0ea72dfde6c594107147ca31b1140"}, + {file = "ruff-0.16.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab3d62dde0b19facdd632008cc4827fc28ada7736c6bd35ab6f1050f0bfed53f"}, + {file = "ruff-0.16.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e43e1f5b8388da9eca1b9e88328d47a5cec794633ccf6f7484ac2dd15eee92c0"}, + {file = "ruff-0.16.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c24788a980581e1d7ea3a0cbe4344c4fbeb0a6a9b1f4713aa46bb104f8294690"}, + {file = "ruff-0.16.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:81806b08329130005dd4a8a8394a0c9da8c6f4cafb16ba438d2a2ee6a18bedf1"}, + {file = "ruff-0.16.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4ce4e02bad779bef557f541a1b31f20d6abeae1cc05ed1b1ac019d4ffd1044c8"}, + {file = "ruff-0.16.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e0422abdf70070255fc4073ce9dfc814cc03db577013761ddd09bc1e4a9a4fbd"}, + {file = "ruff-0.16.2-py3-none-win32.whl", hash = "sha256:bf3a63d78fb39f4bf5ac8ae52051c5520505301abe19ba4e204c453b3f09bb0b"}, + {file = "ruff-0.16.2-py3-none-win_amd64.whl", hash = "sha256:bcabe2f6d0fc7819f1431793005af4e4de7371927d037345bf941252b195b9fa"}, + {file = "ruff-0.16.2-py3-none-win_arm64.whl", hash = "sha256:d614e95cedf38a2053fd351c55b103ba30d017d61688fdbfd40ee0412852a99f"}, + {file = "ruff-0.16.2.tar.gz", hash = "sha256:c3d7828d12e8927a6fc65fe38e2c2541b9e762d360a1786d752cb1b8883b3c9c"}, +] + [[package]] name = "six" version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] +groups = ["main", "docs"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] [[package]] -name = "snowballstemmer" -version = "3.0.1" -description = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms." +name = "super-collections" +version = "0.6.2" +description = "file: README.md" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*" -groups = ["dev"] +python-versions = ">=3.8" +groups = ["docs"] files = [ - {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"}, - {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}, + {file = "super_collections-0.6.2-py3-none-any.whl", hash = "sha256:291b74d26299e9051d69ad9d89e61b07b6646f86a57a2f5ab3063d206eee9c56"}, + {file = "super_collections-0.6.2.tar.gz", hash = "sha256:0c8d8abacd9fad2c7c1c715f036c29f5db213f8cac65f24d45ecba12b4da187a"}, ] +[package.dependencies] +hjson = "*" + +[package.extras] +test = ["pytest (>=7.0)", "pyyaml", "rich"] + [[package]] -name = "stevedore" -version = "5.7.0" -description = "Manage dynamic plugins for Python applications" +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" optional = false -python-versions = ">=3.10" -groups = ["dev"] +python-versions = ">=3.7" +groups = ["docs"] files = [ - {file = "stevedore-5.7.0-py3-none-any.whl", hash = "sha256:fd25efbb32f1abb4c9e502f385f0018632baac11f9ee5d1b70f88cc5e22ad4ed"}, - {file = "stevedore-5.7.0.tar.gz", hash = "sha256:31dd6fe6b3cbe921e21dcefabc9a5f1cf848cf538a1f27543721b8ca09948aa3"}, + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, ] +[package.extras] +widechars = ["wcwidth"] + [[package]] name = "termcolor" version = "3.3.0" description = "ANSI color formatting for output in terminal" optional = false python-versions = ">=3.10" -groups = ["main"] +groups = ["main", "docs"] files = [ {file = "termcolor-3.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5"}, {file = "termcolor-3.3.0.tar.gz", hash = "sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5"}, @@ -1962,6 +2379,18 @@ files = [ [package.extras] tests = ["pytest", "pytest-cov"] +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["dev"] +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + [[package]] name = "tomli" version = "2.4.1" @@ -2032,6 +2461,26 @@ files = [ {file = "tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064"}, ] +[[package]] +name = "towncrier" +version = "24.8.0" +description = "Building newsfiles for your project." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "towncrier-24.8.0-py3-none-any.whl", hash = "sha256:9343209592b839209cdf28c339ba45792fbfe9775b5f9c177462fd693e127d8d"}, + {file = "towncrier-24.8.0.tar.gz", hash = "sha256:013423ee7eed102b2f393c287d22d95f66f1a3ea10a4baa82d298001a7f18af3"}, +] + +[package.dependencies] +click = "*" +jinja2 = "*" +tomli = {version = "*", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["furo (>=2024.05.06)", "nox", "packaging", "sphinx (>=5)", "twisted"] + [[package]] name = "typing-extensions" version = "4.15.0" @@ -2093,7 +2542,7 @@ version = "2.6.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["dev", "docs"] files = [ {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, @@ -2105,6 +2554,49 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] +[[package]] +name = "watchdog" +version = "6.0.0" +description = "Filesystem events monitoring" +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, + {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, + {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, + {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, + {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + [[package]] name = "webcolors" version = "25.10.0" @@ -2143,4 +2635,4 @@ ansible-core = ["ansible-core"] [metadata] lock-version = "2.1" python-versions = ">=3.10,<3.15" -content-hash = "a12524a999231fc170a242f3223c6745249fdf7a8f2a49d13c7c80daaab66b01" +content-hash = "756cdc9a29c06803a1dec93f63eed482645aa71bd2d370679bf0f265779df620" diff --git a/pyproject.toml b/pyproject.toml index 7f3c731..bce0d86 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -134,7 +134,6 @@ convention = "google" "S" ] - [tool.pylint.master] ignore=[".venv", "tests"] @@ -160,12 +159,6 @@ notes = """, [tool.pylint.SIMILARITIES] min-similarity-lines = 15 -[tool.pytest.ini_options] -testpaths = [ - "tests" -] -addopts = "-vv --doctest-modules" - [build-system] requires = ["poetry-core>=2.0.0,<3.0.0"] build-backend = "poetry.core.masonry.api" diff --git a/schema_enforcer/ansible_inventory.py b/schema_enforcer/ansible_inventory.py index b59b56c..8bfc49d 100644 --- a/schema_enforcer/ansible_inventory.py +++ b/schema_enforcer/ansible_inventory.py @@ -2,8 +2,8 @@ from ansible.inventory.manager import InventoryManager # pylint: disable=import-error from ansible.parsing.dataloader import DataLoader # pylint: disable=import-error -from ansible.vars.manager import VariableManager # pylint: disable=import-error from ansible.template import Templar # pylint: disable=import-error +from ansible.vars.manager import VariableManager # pylint: disable=import-error # Referenced https://github.com/fgiorgetti/qpid-dispatch-tests/ for the below class diff --git a/schema_enforcer/cli.py b/schema_enforcer/cli.py index e6cb366..00eabcf 100644 --- a/schema_enforcer/cli.py +++ b/schema_enforcer/cli.py @@ -48,8 +48,6 @@ def main(): def validate(show_pass, show_checks, strict): """Validates instance files against defined schema. - \f - Args: show_pass (bool): show successful schema validations show_checks (bool): show schemas which will be validated against each instance file @@ -151,8 +149,6 @@ def validate(show_pass, show_checks, strict): def schema(check, generate_invalid, list_schemas, schema_id, dump_schemas): """Manage your schemas. - \f - Args: check (bool): Validates that all schemas are valid (spec and unit tests) generate_invalid (bool): Generates expected invalid data from a given schema @@ -160,13 +156,7 @@ def schema(check, generate_invalid, list_schemas, schema_id, dump_schemas): schema_id (str): Name of schema to evaluate dump_schemas (bool): Dump all schema data or a single schema if schema_id is provided """ - if ( - not check - and not generate_invalid - and not list_schemas - and not schema_id - and not dump_schemas - ): + if not check and not generate_invalid and not list_schemas and not schema_id and not dump_schemas: error( "The 'schema' command requires one or more arguments. You can run the command 'schema-enforcer schema --help' to see the arguments available." ) @@ -197,9 +187,7 @@ def schema(check, generate_invalid, list_schemas, schema_id, dump_schemas): if generate_invalid: if not schema_id: - sys.exit( - "Please indicate the schema you'd like to generate invalid data for using the --schema-id flag" - ) + sys.exit("Please indicate the schema you'd like to generate invalid data for using the --schema-id flag") smgr.generate_invalid_tests_expected(schema_id=schema_id) sys.exit(0) @@ -240,7 +228,6 @@ def ansible(inventory, limit, show_pass, show_checks): # pylint: disable=too-ma not defined, the hostvars top level keys will be automatically mapped to a schema definition's top level properties to automatically infer which schema should be used to validate which hostvar. - \f Args: inventory (string): The name of the file used to construct an ansible inventory. @@ -273,8 +260,8 @@ def ansible(inventory, limit, show_pass, show_checks): # pylint: disable=too-ma # This has been left in the code until such a time as we implement the change to two packages so code will not need # to be re-written/ try: - from schema_enforcer.ansible_inventory import ( - AnsibleInventory, # pylint: disable=import-outside-toplevel + from schema_enforcer.ansible_inventory import ( # pylint: disable=import-outside-toplevel + AnsibleInventory, ) except ModuleNotFoundError: error( @@ -332,9 +319,7 @@ def ansible(inventory, limit, show_pass, show_checks): # pylint: disable=too-ma smgr.validate_schemas_exist(declared_schema_ids) # Acquire schemas applicable to the given host - applicable_schemas = inv.get_applicable_schemas( - hostvars, smgr, declared_schema_ids, automap - ) + applicable_schemas = inv.get_applicable_schemas(hostvars, smgr, declared_schema_ids, automap) for schema_obj in applicable_schemas.values(): # Combine host attributes into a single data structure matching to properties defined at the top level of the schema definition if not strict: diff --git a/schema_enforcer/config.py b/schema_enforcer/config.py index 9ed8e4f..7193b06 100644 --- a/schema_enforcer/config.py +++ b/schema_enforcer/config.py @@ -5,6 +5,7 @@ import sys from pathlib import Path from typing import Dict, List, Optional + from typing_extensions import Annotated try: diff --git a/schema_enforcer/instances/file.py b/schema_enforcer/instances/file.py index 484152c..c707290 100644 --- a/schema_enforcer/instances/file.py +++ b/schema_enforcer/instances/file.py @@ -1,10 +1,12 @@ """InstanceFile and InstanceFileManager.""" +import itertools import os import re -import itertools from pathlib import Path + from ruamel.yaml.comments import CommentedMap + from schema_enforcer.utils import find_files, load_file SCHEMA_TAG = "jsonschema" diff --git a/schema_enforcer/schemas/jsonschema.py b/schema_enforcer/schemas/jsonschema.py index c0a2e2f..7fe25d3 100644 --- a/schema_enforcer/schemas/jsonschema.py +++ b/schema_enforcer/schemas/jsonschema.py @@ -6,8 +6,9 @@ from functools import cached_property from jsonschema import Draft7Validator # pylint: disable=import-self + from schema_enforcer.schemas.validator import BaseValidation -from schema_enforcer.validation import ValidationResult, RESULT_FAIL, RESULT_PASS +from schema_enforcer.validation import RESULT_FAIL, RESULT_PASS, ValidationResult class JsonSchema(BaseValidation): # pylint: disable=too-many-instance-attributes diff --git a/schema_enforcer/schemas/manager.py b/schema_enforcer/schemas/manager.py index 247ef6d..c63060b 100644 --- a/schema_enforcer/schemas/manager.py +++ b/schema_enforcer/schemas/manager.py @@ -1,23 +1,21 @@ """Schema manager.""" +import json import os import sys -import json +from typing import List, Optional, Type import jsonref -from termcolor import colored +from pydantic import BaseModel from rich.console import Console from rich.table import Table -from typing import List, Optional, Type - -from pydantic import BaseModel +from termcolor import colored -from schema_enforcer.utils import load_file, find_file, find_files, dump_data_to_yaml -from schema_enforcer.validation import ValidationResult, RESULT_PASS, RESULT_FAIL -from schema_enforcer.exceptions import SchemaNotDefined, InvalidJSONSchema -from schema_enforcer.utils import error, warn +from schema_enforcer.exceptions import InvalidJSONSchema, SchemaNotDefined from schema_enforcer.schemas.jsonschema import JsonSchema from schema_enforcer.schemas.validator import load_validators +from schema_enforcer.utils import dump_data_to_yaml, error, find_file, find_files, load_file, warn +from schema_enforcer.validation import RESULT_FAIL, RESULT_PASS, ValidationResult class SchemaManager: @@ -60,6 +58,7 @@ def create_schema_from_file(self, root, filename, config): Args: root (string): Absolute location of the file in the filesystem. filename (string): Name of the file. + config (string): The Schema Enforcer Config. Returns: JsonSchema: JsonSchema object newly created. @@ -154,6 +153,7 @@ def test_schema_valid(self, schema_id, strict=False): Args: schema_id (str): The unique identifier of a schema. + strict (bool): Whether to fail in strict mode. Returns: list of ValidationResult. diff --git a/schema_enforcer/schemas/validator.py b/schema_enforcer/schemas/validator.py index 2024e74..1b17424 100644 --- a/schema_enforcer/schemas/validator.py +++ b/schema_enforcer/schemas/validator.py @@ -3,12 +3,15 @@ # pylint: disable=no-member, too-few-public-methods # See PEP585 (https://www.python.org/dev/peps/pep-0585/) from __future__ import annotations -from typing import List, Union -import pkgutil + import importlib import inspect +import pkgutil +from typing import List, Union + import jmespath from pydantic import BaseModel, ValidationError + from schema_enforcer.validation import ValidationResult @@ -131,7 +134,7 @@ def pydantic_validation_factory(orig_model) -> PydanticValidation: (PydanticValidation,), { "id": f"{orig_model.id}", - "top_level_properties": set([property for property in orig_model.model_fields]), + "top_level_properties": set([property for property in orig_model.model_fields]), # pylint: disable=consider-using-set-comprehension,unnecessary-comprehension "model": orig_model, }, ) diff --git a/schema_enforcer/utils.py b/schema_enforcer/utils.py index 9078e16..152f4ce 100755 --- a/schema_enforcer/utils.py +++ b/schema_enforcer/utils.py @@ -1,24 +1,21 @@ """Library of utility functions.""" -import os -import json import glob -from collections.abc import Mapping, Sequence import importlib +import json +import os +from collections.abc import Mapping, Sequence from urllib import parse as urlparse -from ruamel.yaml import YAML -from ruamel.yaml.scalarstring import DoubleQuotedScalarString as DQ +from click import Option, UsageError from jsonschema import ( # pylint: disable=no-name-in-module - RefResolver, Draft7Validator, + RefResolver, ) - +from ruamel.yaml import YAML +from ruamel.yaml.scalarstring import DoubleQuotedScalarString as DQ from termcolor import colored - -from click import Option, UsageError - YAML_HANDLER = YAML() YAML_HANDLER.indent(sequence=4, offset=2) YAML_HANDLER.explicit_start = True @@ -147,7 +144,7 @@ def load_schema_from_json_file(schema_root_dir, schema_filepath): Args: schema_root_dir (str): The full path to root directory of schema files. - schema_file_path (str): The path to a schema definition file. + schema_filepath (str): The path to a schema definition file. Returns: jsonschema.Validator: A Validator instance with schema loaded with a RefResolver and format_checker. @@ -293,9 +290,7 @@ def dump_schema_vars(output_dir, schema_properties, variables): dump_data_to_yaml(schema_data, yaml_file) -def find_files( - file_extensions, search_directories, excluded_filenames, excluded_directories=[], return_dir=False -): # pylint: disable=dangerous-default-value +def find_files(file_extensions, search_directories, excluded_filenames, excluded_directories=[], return_dir=False): # pylint: disable=dangerous-default-value """Walk provided search directories and return the full filename for all files matching file_extensions except the excluded_filenames. Args: @@ -412,7 +407,7 @@ def find_file(filename, extensions=("yml", "yaml", "json")): Args: filename (str): Full filename of the file to search for, without the extension. - formats(Tuple[str]): Tuple of formats (file extensions) appended to the file to search for it's existence. + extensions(Tuple[str]): Tuple of formats (file extensions) appended to the file to search for it's existence. Returns: str or None: string of the filename found @@ -470,7 +465,7 @@ def __init__(self, *args, **kwargs): help = kwargs.get("help", "") # pylint: disable=redefined-builtin if self.mutually_exclusive: ex_str = ", ".join(self.mutually_exclusive) - kwargs["help"] = help + (" NOTE: This argument is mutually exclusive with " " arguments: [" + ex_str + "].") + kwargs["help"] = help + (" NOTE: This argument is mutually exclusive with arguments: [" + ex_str + "].") super().__init__(*args, **kwargs) def handle_parse_result(self, ctx, opts, args): diff --git a/schema_enforcer/validation.py b/schema_enforcer/validation.py index 3509dc9..ddc9a0e 100644 --- a/schema_enforcer/validation.py +++ b/schema_enforcer/validation.py @@ -1,10 +1,11 @@ """Validation related classes.""" -from typing import List, Optional, Any +from typing import Any, List, Optional + from pydantic import BaseModel, ConfigDict, field_validator # pylint: disable=no-name-in-module from termcolor import colored -RESULT_PASS = "PASS" # nosec +RESULT_PASS = "PASS" # noqa: S105 RESULT_FAIL = "FAIL" diff --git a/tasks.py b/tasks.py index ca03061..86e70be 100644 --- a/tasks.py +++ b/tasks.py @@ -1,30 +1,11 @@ """Tasks for use with Invoke.""" import os -<<<<<<< HEAD -import sys -from invoke import task - -try: - import tomllib -except ImportError: - try: - import tomli as tomllib - except ImportError: - sys.exit("Please make sure to `pip install tomli` or enable the Poetry shell and run `poetry install`.") - - -def project_ver(): - """Find version from pyproject.toml to use for docker image tagging.""" - with open("pyproject.toml", "rb") as config_file: - return tomllib.load(config_file)["tool"]["poetry"].get("version", "latest") -======= import re from pathlib import Path from invoke import Collection, Exit from invoke import task as invoke_task ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) def is_truthy(arg): @@ -33,10 +14,6 @@ def is_truthy(arg): Examples: >>> is_truthy('yes') True -<<<<<<< HEAD - -======= ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) Args: arg (str): Truthy string (True values are y, yes, t, true, on and 1; false values are n, no, f, false, off and 0. Raises ValueError if val is anything else. @@ -47,73 +24,22 @@ def is_truthy(arg): val = str(arg).lower() if val in ("y", "yes", "t", "true", "on", "1"): return True -<<<<<<< HEAD - elif val in ("n", "no", "f", "false", "off", "0"): - return False - else: - raise ValueError(f"Invalid truthy value: `{arg}`") - - -with open("pyproject.toml", "rb") as config_file: - PYPROJECT_CONFIG = tomllib.load(config_file) -TOOL_CONFIG = PYPROJECT_CONFIG["tool"]["poetry"] - -# Can be set to a separate Python version to be used for launching or building image -PYTHON_VER = os.getenv("PYTHON_VER", "3.10") -# Can be set to a separate ANsible version to be used for launching or building image -ANSIBLE_VER = os.getenv("ANSIBLE_VER", "2.16.14") -ANSIBLE_PACKAGE = os.getenv("ANSIBLE_PACKAGE", "ansible-core") -# Name of the docker image/image -IMAGE_NAME = os.getenv("IMAGE_NAME", TOOL_CONFIG["name"]) -# Tag for the image -IMAGE_VER = os.getenv("IMAGE_VER", f"{TOOL_CONFIG['version']}-py{PYTHON_VER}") -# Gather current working directory for Docker commands -PWD = os.getcwd() -# Local or Docker execution provide "local" to run locally without docker execution -INVOKE_LOCAL = is_truthy(os.getenv("INVOKE_LOCAL", False)) # pylint: disable=W1508 - - -def _get_image_name(with_ansible=False): - """Gets the name of the container image to use. - - Args: - with_ansible (bool): Get name of container image with Ansible installed. - - Returns: - str: Name of container image. Includes tag. - """ - if with_ansible and not os.getenv("GITHUB_ACTION", None): - name = f"{IMAGE_NAME}:{IMAGE_VER}-{ANSIBLE_PACKAGE}{ANSIBLE_VER}" - else: - name = f"{IMAGE_NAME}:{IMAGE_VER}" - - return name - - -def run_cmd(context, exec_cmd, with_ansible=False): - """Wrapper to run the invoke task commands. - - Args: - context (invoke.task): Invoke task object. - exec_cmd (str): Command to run. - with_ansible (bool): Whether to run the command in a container that has ansible installed -======= if val in ("n", "no", "f", "false", "off", "0"): return False raise ValueError(f"Invalid truthy value: `{arg}`") # Use pyinvoke configuration for default values, see http://docs.pyinvoke.org/en/stable/concepts/configuration.html -# Variables may be overwritten in invoke.yml or by the environment variables INVOKE_SCHEMA-ENFORCER_xxx +# Variables may be overwritten in invoke.yml or by the environment variables INVOKE_SCHEMA_ENFORCER_xxx namespace = Collection("schema_enforcer") namespace.configure( { "schema_enforcer": { "project_name": "schema_enforcer", "python_ver": "3.10", - "local": is_truthy(os.getenv("INVOKE_SCHEMA-ENFORCER_LOCAL", "false")), + "local": is_truthy(os.getenv("INVOKE_SCHEMA_ENFORCER_LOCAL", "false")), "image_name": "schema_enforcer", - "image_ver": os.getenv("INVOKE_SCHEMA-ENFORCER_IMAGE_VER", "latest"), + "image_ver": os.getenv("INVOKE_SCHEMA_ENFORCER_IMAGE_VER", "latest"), "pwd": Path(__file__).parent, } } @@ -148,21 +74,10 @@ def run_command(context, exec_cmd, port=None, rm=True): exec_cmd ([str]): Command to run. port (int): Used to serve local docs. rm (bool): Whether to remove the container after running the command. ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) Returns: result (obj): Contains Invoke result from running task. """ -<<<<<<< HEAD - name = _get_image_name(with_ansible) - - if INVOKE_LOCAL: - print(f"LOCAL - Running command {exec_cmd}") - result = context.run(exec_cmd, pty=True) - else: - print(f"DOCKER - Running command: {exec_cmd} container: {name}") - result = context.run(f"docker run -it -v {PWD}:/local {name} sh -c '{exec_cmd}'", pty=True) -======= if is_truthy(context.schema_enforcer.local): print(f"LOCAL - Running command {exec_cmd}") result = context.run(exec_cmd, pty=True) @@ -180,68 +95,13 @@ def run_command(context, exec_cmd, port=None, rm=True): f"docker run -it {'--rm' if rm else ''} -v {context.schema_enforcer.pwd}:/local {context.schema_enforcer.image_name}:{context.schema_enforcer.image_ver} sh -c '{exec_cmd}'", pty=True, ) ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) return result -<<<<<<< HEAD -@task -def build_image( - context, cache=True, force_rm=False, hide=False, with_ansible=False -): # pylint: disable=too-many-arguments - """Builds a container with schema-enforcer installed. - - Args: - context (invoke.task): Invoke task object - cache (bool): Do not use cache when building the image - force_rm (bool): Always remove intermediate containers - hide: (bool): Suppress output from docker build - with_ansible (bool): Build a container with Ansible installed - """ - name = _get_image_name(with_ansible) - env = {"PYTHON_VER": PYTHON_VER} - - if with_ansible: - env["ANSIBLE_VER"] = ANSIBLE_VER - env["ANSIBLE_PACKAGE"] = ANSIBLE_PACKAGE - command = f"docker build --tag {name} --target with_ansible" - command += f" --build-arg ANSIBLE_VER={ANSIBLE_VER} --build-arg ANSIBLE_PACKAGE={ANSIBLE_PACKAGE}" - - else: - command = command = f"docker build --tag {name} --target base" - - command += f" --build-arg PYTHON_VER={PYTHON_VER} -f Dockerfile ." - if not cache: - command += " --no-cache" - if force_rm: - command += " --force-rm" - - print(f"Building image {name}") - result = context.run(command, hide=hide, env=env) - - if result.exited != 0: - print(f"Failed to build image {name}\nError: {result.stderr}") - - -@task -def clean_image(context, with_ansible=False): - """Remove the schema-enforcer container. - - Args: - context (obj): Used to run specific commands - with_ansible (bool): Remove schema-enforcer container with ansible installed - """ - name = _get_image_name(with_ansible) - print(f"Attempting to forcefully remove image {name}") - context.run(f"docker rmi {name} --force") - - -======= # ------------------------------------------------------------------------------ # BUILD # ------------------------------------------------------------------------------ ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) @task( help={ "cache": "Whether to use Docker's cache when building images (default enabled)", @@ -250,18 +110,6 @@ def clean_image(context, with_ansible=False): } ) def build(context, cache=True, force_rm=False, hide=False): -<<<<<<< HEAD - """This will build an image with the provided name and python version. - - Args: - context (obj): Used to run specific commands - cache (bool): Do not use cache when building the image - force_rm (bool): Always remove intermediate containers - hide (bool): Suppress output from docker build - """ - build_image(context, cache, force_rm, hide=hide) - build_image(context, cache, force_rm, hide=hide, with_ansible=True) -======= """Build a Docker image.""" print(f"Building image {context.schema_enforcer.image_name}:{context.schema_enforcer.image_ver}") command = f"docker build --tag {context.schema_enforcer.image_name}:{context.schema_enforcer.image_ver} --build-arg PYTHON_VER={context.schema_enforcer.python_ver} -f Dockerfile ." @@ -296,80 +144,10 @@ def generate_packages(context): def lock(context, check=False): """Generate poetry.lock inside the library container.""" run_command(context, f"poetry {'check' if check else 'lock --no-update'}") ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) @task def clean(context): -<<<<<<< HEAD - """This will remove a specific image. - - Args: - context (obj): Used to run specific commands - """ - clean_image(context) - clean_image(context, with_ansible=True) - - -@task -def rebuild(context, cache=True, force_rm=False): - """This will clean the image and then rebuild image without using cache. - - Args: - context (obj): Used to run specific commands - cache (bool): Use cache for rebuild - force_rm (bool): Always remove intermediate containers - """ - clean(context) - build(context, cache=cache, force_rm=force_rm) - - -@task -def pytest(context): - """This will run pytest for the specified name and Python version. - - Args: - context (obj): Used to run specific commands - """ - exec_cmd = 'find tests/ -name "test_*.py" -a -not -name "test_cli_ansible_not_exists.py" | xargs pytest -vv' - run_cmd(context, exec_cmd, with_ansible=True) - - -@task -def pytest_without_ansible(context): - """This will run pytest only to assert the correct errors are raised when pytest is not installed. - - This must be run inside of a container or environment in which ansible is not installed, otherwise the test case - assertion will fail. - - Args: - context (obj): Used to run specific commands - """ - exec_cmd = 'find tests/ -name "test_cli_ansible_not_exists.py" | xargs pytest -vv' - run_cmd(context, exec_cmd) - - -@task -def black(context): - """This will run black to check that Python files adherence to black standards. - - Args: - context (obj): Used to run specific commands - """ - exec_cmd = "black --check --diff ." - run_cmd(context, exec_cmd, with_ansible=True) - - -@task -def flake8(context): - """This will run flake8 for the specified name and Python version. - - Args: - context (obj): Used to run specific commands - """ - exec_cmd = "flake8 ." - run_cmd(context, exec_cmd, with_ansible=True) -======= """Remove the project specific image.""" print( f"Attempting to forcefully remove image {context.schema_enforcer.image_name}:{context.schema_enforcer.image_ver}" @@ -460,48 +238,21 @@ def ruff(context, action=None, target=None, fix=False, output_format="concise"): if exit_code != 0: raise Exit(code=exit_code) ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) @task def pylint(context): -<<<<<<< HEAD - """This will run pylint for the specified name and Python version. -======= """Run pylint for the specified name and Python version. ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) Args: context (obj): Used to run specific commands """ -<<<<<<< HEAD - exec_cmd = "pylint **/*.py" - run_cmd(context, exec_cmd, with_ansible=True) -======= exec_cmd = 'find . -name "*.py" | grep -vE "tests/unit" | xargs pylint' run_command(context, exec_cmd) ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) @task def yamllint(context): -<<<<<<< HEAD - """This will run yamllint to validate formatting adheres to NTC defined YAML standards. - - Args: - context (obj): Used to run specific commands - name (str): Used to name the docker image - image_ver (str): Define image version - local (bool): Define as `True` to execute locally - """ - exec_cmd = "yamllint ." - run_cmd(context, exec_cmd, with_ansible=True) - - -@task -def pydocstyle(context): - """This will run pydocstyle to validate docstring formatting adheres to NTC defined standards. -======= """Run yamllint to validate formatting adheres to NTC defined YAML standards. Args: @@ -514,43 +265,10 @@ def pydocstyle(context): @task def cli(context): """Enter the image to perform troubleshooting or dev work. ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) - - Args: - context (obj): Used to run specific commands - """ -<<<<<<< HEAD - exec_cmd = "pydocstyle ." - run_cmd(context, exec_cmd, with_ansible=True) - - -@task -def bandit(context): - """This will run bandit to validate basic static code security analysis. - - Args: - context (obj): Used to run specific commands - """ - exec_cmd = "bandit --recursive ./ --configfile .bandit.yml" - run_cmd(context, exec_cmd, with_ansible=True) - - -@task -def tests(context): - """This will run all tests for the specified name and Python version. Args: context (obj): Used to run specific commands """ - black(context) - flake8(context) - pylint(context) - yamllint(context) - pydocstyle(context) - bandit(context) - pytest(context) - pytest_without_ansible(context) -======= dev = f"docker run -it -v {context.schema_enforcer.pwd}:/local {context.schema_enforcer.image_name}:{context.schema_enforcer.image_ver} /bin/bash" context.run(f"{dev}", pty=True) @@ -582,23 +300,10 @@ def tests(context, lint_only=False): if not lint_only: print("Running unit tests...") pytest(context) ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) print("All tests have passed!") @task -<<<<<<< HEAD -def cli(context, with_ansible=False): - """This will enter the image to perform troubleshooting or dev work. - - Args: - context (obj): Used to run specific commands - with_ansible (str): Attach to container with ansible version specified by the 'ANSIBLE_VER' env var - """ - name = _get_image_name(with_ansible) - dev = f"docker run -it -v {PWD}:/local {name} /bin/bash" - context.run(f"{dev}", pty=True) -======= def build_and_check_docs(context): """Build documentation and test the configuration.""" command = "mkdocs build --no-directory-urls --strict" @@ -642,4 +347,3 @@ def generate_release_notes(context, version="", date=""): command += f" --date {date}" # Due to issues with git repo ownership in the containers, this must always run locally. context.run(command) ->>>>>>> 31cc3ca (Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool) diff --git a/tests/fixtures/test_validators/validators/check_interfaces_ipv4.py b/tests/fixtures/test_validators/validators/check_interfaces_ipv4.py index cdfcd58..0fd0a20 100644 --- a/tests/fixtures/test_validators/validators/check_interfaces_ipv4.py +++ b/tests/fixtures/test_validators/validators/check_interfaces_ipv4.py @@ -1,6 +1,7 @@ """Test validator for JmesPathModelValidation class""" import jmespath + from schema_enforcer.schemas.validator import JmesPathModelValidation diff --git a/tests/fixtures/test_validators_pydantic/pydantic_validators/models/__init__.py b/tests/fixtures/test_validators_pydantic/pydantic_validators/models/__init__.py index 5cf670e..f8e32c2 100644 --- a/tests/fixtures/test_validators_pydantic/pydantic_validators/models/__init__.py +++ b/tests/fixtures/test_validators_pydantic/pydantic_validators/models/__init__.py @@ -1,8 +1,10 @@ +"""Init Models.""" + +from schema_enforcer.schemas.manager import PydanticManager + from .dns import Dns from .hostname import Hostname from .interfaces import Interfaces # , Interface, InterfaceTypes -from schema_enforcer.schemas.manager import PydanticManager - manager1 = PydanticManager(models=[Hostname, Interfaces]) manager2 = PydanticManager(prefix="pydantic", models=[Hostname, Interfaces, Dns]) diff --git a/tests/fixtures/test_validators_pydantic/pydantic_validators/models/dns.py b/tests/fixtures/test_validators_pydantic/pydantic_validators/models/dns.py index 07ed5cb..a435843 100644 --- a/tests/fixtures/test_validators_pydantic/pydantic_validators/models/dns.py +++ b/tests/fixtures/test_validators_pydantic/pydantic_validators/models/dns.py @@ -1,6 +1,7 @@ """Validate DNS servers is valid.""" from typing import List + from pydantic import BaseModel, Field from pydantic.networks import IPvAnyAddress diff --git a/tests/fixtures/test_validators_pydantic/pydantic_validators/models/interfaces.py b/tests/fixtures/test_validators_pydantic/pydantic_validators/models/interfaces.py index 01332d4..ed0f9eb 100644 --- a/tests/fixtures/test_validators_pydantic/pydantic_validators/models/interfaces.py +++ b/tests/fixtures/test_validators_pydantic/pydantic_validators/models/interfaces.py @@ -1,19 +1,22 @@ """Validate interfaces are valid.""" from enum import Enum -from typing import Dict, Optional from ipaddress import IPv4Address, IPv6Address +from typing import Dict, Optional + from pydantic import BaseModel class InterfaceTypes(str, Enum): """Interface types.""" - access = "access" - core = "core" + access = "access" # pylint: disable=invalid-name + core = "core" # pylint: disable=invalid-name class Interface(BaseModel): + """Interface Pydantic Model.""" + ipv4: Optional[IPv4Address] = None ipv6: Optional[IPv6Address] = None peer: Optional[str] = None diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index ef7024c..0972c7c 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -2,8 +2,9 @@ import glob import os -from schema_enforcer.utils import load_file + from schema_enforcer.schemas.jsonschema import JsonSchema +from schema_enforcer.utils import load_file FIXTURES_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fixtures", "test_jsonschema") FORMAT_CHECK_ERROR_MESSAGE_MAPPING = { diff --git a/tests/unit/test_config_settings.py b/tests/unit/test_config_settings.py index 334b5cb..9669242 100644 --- a/tests/unit/test_config_settings.py +++ b/tests/unit/test_config_settings.py @@ -1,9 +1,10 @@ """Test Setting Configuration Parameters""" -from unittest import mock import os +from unittest import mock import pytest + from schema_enforcer import config FIXTURES_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fixtures", "test_config") diff --git a/tests/unit/test_instances_instance_file.py b/tests/unit/test_instances_instance_file.py index af026af..816bfe8 100644 --- a/tests/unit/test_instances_instance_file.py +++ b/tests/unit/test_instances_instance_file.py @@ -5,10 +5,10 @@ import pytest -from schema_enforcer.schemas.manager import SchemaManager +from schema_enforcer.config import Settings from schema_enforcer.instances.file import InstanceFile, InstanceFileManager +from schema_enforcer.schemas.manager import SchemaManager from schema_enforcer.validation import ValidationResult -from schema_enforcer.config import Settings FIXTURES_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fixtures", "test_instances") diff --git a/tests/unit/test_instances_instance_file_manager.py b/tests/unit/test_instances_instance_file_manager.py index 32ae97b..bf747f3 100644 --- a/tests/unit/test_instances_instance_file_manager.py +++ b/tests/unit/test_instances_instance_file_manager.py @@ -8,8 +8,8 @@ import pytest -from schema_enforcer.instances.file import InstanceFileManager from schema_enforcer.config import Settings +from schema_enforcer.instances.file import InstanceFileManager FIXTURES_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fixtures", "test_instances") diff --git a/tests/unit/test_jsonschema.py b/tests/unit/test_jsonschema.py index 2d27805..992cea6 100644 --- a/tests/unit/test_jsonschema.py +++ b/tests/unit/test_jsonschema.py @@ -2,11 +2,12 @@ """Tests to validate functions defined in jsonschema.py""" import os + import pytest from schema_enforcer.schemas.jsonschema import JsonSchema -from schema_enforcer.validation import RESULT_PASS, RESULT_FAIL from schema_enforcer.utils import load_file +from schema_enforcer.validation import RESULT_FAIL, RESULT_PASS FIXTURES_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fixtures", "test_jsonschema") LOADED_SCHEMA_DATA = load_file(os.path.join(FIXTURES_DIR, "schema", "schemas", "dns.yml")) diff --git a/tests/unit/test_schemas_pydantic_validators.py b/tests/unit/test_schemas_pydantic_validators.py index 3fbd61b..10c441c 100644 --- a/tests/unit/test_schemas_pydantic_validators.py +++ b/tests/unit/test_schemas_pydantic_validators.py @@ -4,12 +4,14 @@ import os import sys from unittest import mock + import pytest from click.testing import CliRunner -from schema_enforcer.schemas.manager import SchemaManager + +from schema_enforcer import cli from schema_enforcer.config import Settings from schema_enforcer.instances.file import InstanceFileManager -from schema_enforcer import cli +from schema_enforcer.schemas.manager import SchemaManager FIXTURE_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fixtures") diff --git a/tests/unit/test_schemas_schema_manager.py b/tests/unit/test_schemas_schema_manager.py index 0081637..c26c394 100644 --- a/tests/unit/test_schemas_schema_manager.py +++ b/tests/unit/test_schemas_schema_manager.py @@ -2,10 +2,12 @@ """Test manager.py SchemaManager class""" import os + import pytest -from schema_enforcer.schemas.manager import SchemaManager + from schema_enforcer.config import Settings from schema_enforcer.exceptions import InvalidJSONSchema +from schema_enforcer.schemas.manager import SchemaManager FIXTURE_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fixtures") diff --git a/tests/unit/test_schemas_validator.py b/tests/unit/test_schemas_validator.py index 0aa0345..d734599 100644 --- a/tests/unit/test_schemas_validator.py +++ b/tests/unit/test_schemas_validator.py @@ -2,9 +2,11 @@ # pylint: disable=redefined-outer-name import os + import pytest -from schema_enforcer.ansible_inventory import AnsibleInventory + import schema_enforcer.schemas.validator as v +from schema_enforcer.ansible_inventory import AnsibleInventory FIXTURE_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fixtures", "test_validators") diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index f55d8eb..9b0d22d 100755 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -1,7 +1,7 @@ """Tests to validate functions defined in utils.py""" -import os import json +import os import shutil from schema_enforcer import utils diff --git a/tests/unit/test_validator.py b/tests/unit/test_validator.py index 70b250a..6ff3b7e 100644 --- a/tests/unit/test_validator.py +++ b/tests/unit/test_validator.py @@ -1,12 +1,13 @@ """Test validator functions.""" import pytest + from schema_enforcer.schemas.validator import ( BaseModel, BaseValidation, - is_validator, JmesPathModelValidation, PydanticValidation, + is_validator, pydantic_validation_factory, ) From 96dfbf3d3dffa482b907b7c5dd4fbcf80d6c7326 Mon Sep 17 00:00:00 2001 From: Jeff Kala Date: Fri, 7 Aug 2026 11:36:52 -0600 Subject: [PATCH 5/6] first pass at fixing pytest not running properly --- schema_enforcer/instances/__init__.py | 1 + schema_enforcer/schemas/__init__.py | 1 + tests/{ => unit}/fixtures/test_config/pyproject.toml | 0 tests/{ => unit}/fixtures/test_config/pyproject2.toml | 0 .../{ => unit}/fixtures/test_config/pyproject_invalid_attr.toml | 0 .../fixtures/test_instances/hostvars/chi-beijing-rt1/dns.yml | 0 .../fixtures/test_instances/hostvars/chi-beijing-rt1/syslog.yml | 0 .../fixtures/test_instances/hostvars/eng-london-rt1/dns.yaml | 0 .../fixtures/test_instances/hostvars/eng-london-rt1/ntp.yaml | 0 tests/{ => unit}/fixtures/test_instances/pyproject.toml | 0 .../fixtures/test_instances/schema/definitions/arrays/ip.yml | 0 .../fixtures/test_instances/schema/definitions/objects/ip.yml | 0 .../fixtures/test_instances/schema/definitions/properties/ip.yml | 0 tests/{ => unit}/fixtures/test_instances/schema/schemas/dns.yml | 0 tests/{ => unit}/fixtures/test_instances/schema/schemas/ntp.yml | 0 .../{ => unit}/fixtures/test_instances/schema/schemas/syslog.yml | 0 .../fixtures/test_jsonschema/hostvars/can-vancouver-rt1/dns.yml | 0 .../fixtures/test_jsonschema/hostvars/chi-beijing-rt1/dns.yml | 0 .../fixtures/test_jsonschema/hostvars/eng-london-rt1/dns.yml | 0 .../fixtures/test_jsonschema/hostvars/spa-madrid-rt1/dns.yml | 0 .../hostvars/spa-madrid-rt1/incorrect_date_format.yml | 0 .../hostvars/spa-madrid-rt1/incorrect_datetime_format.yml | 0 .../hostvars/spa-madrid-rt1/incorrect_email_format.yml | 0 .../hostvars/spa-madrid-rt1/incorrect_hostname_format.yml | 0 .../hostvars/spa-madrid-rt1/incorrect_ipv4_format.yml | 0 .../hostvars/spa-madrid-rt1/incorrect_ipv6_format.yml | 0 .../hostvars/spa-madrid-rt1/incorrect_jsonptr_format.yml | 0 .../hostvars/spa-madrid-rt1/incorrect_regex_format.yml | 0 .../hostvars/spa-madrid-rt1/incorrect_time_format.yml | 0 .../hostvars/spa-madrid-rt1/incorrect_uri_format.yml | 0 .../fixtures/test_jsonschema/schema/definitions/arrays/ip.yml | 0 .../fixtures/test_jsonschema/schema/definitions/objects/ip.yml | 0 .../test_jsonschema/schema/definitions/properties/ip.yml | 0 tests/{ => unit}/fixtures/test_jsonschema/schema/schemas/dns.yml | 0 .../test_jsonschema/schema/schemas/incorrect_date_format.yml | 0 .../test_jsonschema/schema/schemas/incorrect_datetime_format.yml | 0 .../test_jsonschema/schema/schemas/incorrect_email_format.yml | 0 .../test_jsonschema/schema/schemas/incorrect_hostname_format.yml | 0 .../test_jsonschema/schema/schemas/incorrect_ipv4_format.yml | 0 .../test_jsonschema/schema/schemas/incorrect_ipv6_format.yml | 0 .../test_jsonschema/schema/schemas/incorrect_jsonptr_format.yml | 0 .../test_jsonschema/schema/schemas/incorrect_regex_format.yml | 0 .../test_jsonschema/schema/schemas/incorrect_time_format.yml | 0 .../test_jsonschema/schema/schemas/incorrect_uri_format.yml | 0 .../fixtures/test_jsonschema/schema/schemas/invalid.yml | 0 tests/{ => unit}/fixtures/test_manager/dump/all.txt | 0 tests/{ => unit}/fixtures/test_manager/dump/byid.txt | 0 tests/{ => unit}/fixtures/test_manager/invalid/pyproject.toml | 0 .../fixtures/test_manager/invalid/schema/schemas/invalid.yml | 0 .../test_manager/invalid_generate/schema/schemas/test.yml | 0 .../schema/tests/test/invalid/invalid_type1/data.json | 0 .../schema/tests/test/invalid/invalid_type1/exp_results.yml | 0 .../schema/tests/test/invalid/invalid_type1/results.yml | 0 .../schema/tests/test/invalid/invalid_type2/data.json | 0 .../schema/tests/test/invalid/invalid_type2/exp_results.yml | 0 .../schema/tests/test/invalid/invalid_type2/results.yml | 0 .../invalid_generate/schema/tests/test/valid/test.json | 0 .../test_validators/inventory/host_vars/az_phx_pe01/base.yml | 0 .../test_validators/inventory/host_vars/az_phx_pe02/base.yml | 0 .../test_validators/inventory/host_vars/co_den_p01/base.yml | 0 .../{ => unit}/fixtures/test_validators/inventory/inventory.yml | 0 .../fixtures/test_validators/validators/check_hostname.py | 0 .../fixtures/test_validators/validators/check_interfaces.py | 0 .../fixtures/test_validators/validators/check_interfaces_ipv4.py | 0 .../fixtures/test_validators/validators/check_peers.py | 0 tests/{ => unit}/fixtures/test_validators_pydantic/__init__.py | 0 .../inventory/host_vars/az_phx_pe01/base.yml | 0 .../inventory/host_vars/az_phx_pe01/dns.yml | 0 .../inventory/host_vars/az_phx_pe02/base.yml | 0 .../inventory/host_vars/co_den_p01/base.yml | 0 .../inventory/host_vars/co_den_p01/dns.yml | 0 .../fixtures/test_validators_pydantic/inventory/inventory.yml | 0 .../inventory_fail/host_vars/az_phx_pe01/base.yml | 0 .../inventory_fail/host_vars/az_phx_pe01/dns.yml | 0 .../inventory_fail/host_vars/az_phx_pe02/base.yml | 0 .../inventory_fail/host_vars/co_den_p01/base.yml | 0 .../inventory_fail/host_vars/co_den_p01/dns.yml | 0 .../test_validators_pydantic/inventory_fail/inventory.yml | 0 .../test_validators_pydantic/pydantic_validators/__init__.py | 0 .../pydantic_validators/models/__init__.py | 0 .../test_validators_pydantic/pydantic_validators/models/dns.py | 0 .../pydantic_validators/models/hostname.py | 0 .../pydantic_validators/models/interfaces.py | 0 tests/{ => unit}/mocks/dns/invalid/invalid_format.json | 0 tests/{ => unit}/mocks/dns/invalid/invalid_format.yml | 0 tests/{ => unit}/mocks/dns/invalid/invalid_ip.json | 0 tests/{ => unit}/mocks/dns/invalid/invalid_ip.yml | 0 tests/{ => unit}/mocks/dns/invalid/missing_required.json | 0 tests/{ => unit}/mocks/dns/invalid/missing_required.yml | 0 tests/{ => unit}/mocks/dns/valid/full_implementation.json | 0 tests/{ => unit}/mocks/dns/valid/partial_implementation.json | 0 tests/{ => unit}/mocks/inventory/group_vars/all.yml | 0 tests/{ => unit}/mocks/inventory/group_vars/emea.yml | 0 tests/{ => unit}/mocks/inventory/group_vars/ios.yml | 0 tests/{ => unit}/mocks/inventory/group_vars/na.yml | 0 tests/{ => unit}/mocks/inventory/hosts | 0 tests/{ => unit}/mocks/ntp/invalid/invalid_format.json | 0 tests/{ => unit}/mocks/ntp/invalid/invalid_format.yml | 0 tests/{ => unit}/mocks/ntp/invalid/invalid_ip.json | 0 tests/{ => unit}/mocks/ntp/invalid/invalid_ip.yml | 0 tests/{ => unit}/mocks/ntp/invalid/missing_required.json | 0 tests/{ => unit}/mocks/ntp/invalid/missing_required.yml | 0 tests/{ => unit}/mocks/ntp/valid/full_implementation.json | 0 tests/{ => unit}/mocks/ntp/valid/partial_implementation.json | 0 tests/{ => unit}/mocks/schema/json/definitions/arrays/ip.json | 0 tests/{ => unit}/mocks/schema/json/definitions/objects/ip.json | 0 .../{ => unit}/mocks/schema/json/definitions/properties/ip.json | 0 tests/{ => unit}/mocks/schema/json/full_schemas/ntp.json | 0 tests/{ => unit}/mocks/schema/json/schemas/dns.json | 0 tests/{ => unit}/mocks/schema/json/schemas/ntp.json | 0 tests/{ => unit}/mocks/schema/yaml/definitions/arrays/ip.yml | 0 tests/{ => unit}/mocks/schema/yaml/definitions/objects/ip.yml | 0 tests/{ => unit}/mocks/schema/yaml/definitions/properties/ip.yml | 0 tests/{ => unit}/mocks/schema/yaml/schemas/dns.yml | 0 tests/{ => unit}/mocks/schema/yaml/schemas/ntp.yml | 0 tests/{ => unit}/mocks/syslog/invalid/invalid_format.json | 0 tests/{ => unit}/mocks/syslog/invalid/invalid_format.yml | 0 tests/{ => unit}/mocks/syslog/invalid/invalid_ip.json | 0 tests/{ => unit}/mocks/syslog/invalid/invalid_ip.yml | 0 tests/{ => unit}/mocks/syslog/invalid/missing_required.json | 0 tests/{ => unit}/mocks/syslog/invalid/missing_required.yml | 0 tests/{ => unit}/mocks/syslog/valid/full_implementation.json | 0 tests/{ => unit}/mocks/syslog/valid/partial_implementation.json | 0 tests/{ => unit}/mocks/utils/formatted.json | 0 tests/{ => unit}/mocks/utils/formatted.yml | 0 tests/{ => unit}/mocks/utils/host1/dns.yml | 0 tests/{ => unit}/mocks/utils/host1/ntp.yml | 0 tests/{ => unit}/mocks/utils/host2/dns.yml | 0 tests/{ => unit}/mocks/utils/host2/ntp.yml | 0 tests/{ => unit}/mocks/utils/host3/dns.yml | 0 tests/{ => unit}/mocks/utils/host3/ntp.yml | 0 tests/{ => unit}/mocks/utils/host4/dns.yml | 0 tests/{ => unit}/mocks/utils/host4/ntp.yml | 0 tests/{ => unit}/mocks/utils/ntp_schema.json | 0 134 files changed, 2 insertions(+) create mode 100644 schema_enforcer/instances/__init__.py create mode 100644 schema_enforcer/schemas/__init__.py rename tests/{ => unit}/fixtures/test_config/pyproject.toml (100%) rename tests/{ => unit}/fixtures/test_config/pyproject2.toml (100%) rename tests/{ => unit}/fixtures/test_config/pyproject_invalid_attr.toml (100%) rename tests/{ => unit}/fixtures/test_instances/hostvars/chi-beijing-rt1/dns.yml (100%) rename tests/{ => unit}/fixtures/test_instances/hostvars/chi-beijing-rt1/syslog.yml (100%) rename tests/{ => unit}/fixtures/test_instances/hostvars/eng-london-rt1/dns.yaml (100%) rename tests/{ => unit}/fixtures/test_instances/hostvars/eng-london-rt1/ntp.yaml (100%) rename tests/{ => unit}/fixtures/test_instances/pyproject.toml (100%) rename tests/{ => unit}/fixtures/test_instances/schema/definitions/arrays/ip.yml (100%) rename tests/{ => unit}/fixtures/test_instances/schema/definitions/objects/ip.yml (100%) rename tests/{ => unit}/fixtures/test_instances/schema/definitions/properties/ip.yml (100%) rename tests/{ => unit}/fixtures/test_instances/schema/schemas/dns.yml (100%) rename tests/{ => unit}/fixtures/test_instances/schema/schemas/ntp.yml (100%) rename tests/{ => unit}/fixtures/test_instances/schema/schemas/syslog.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/hostvars/can-vancouver-rt1/dns.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/hostvars/chi-beijing-rt1/dns.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/hostvars/eng-london-rt1/dns.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/dns.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_date_format.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_datetime_format.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_email_format.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_hostname_format.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_ipv4_format.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_ipv6_format.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_jsonptr_format.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_regex_format.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_time_format.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_uri_format.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/schema/definitions/arrays/ip.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/schema/definitions/objects/ip.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/schema/definitions/properties/ip.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/schema/schemas/dns.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/schema/schemas/incorrect_date_format.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/schema/schemas/incorrect_datetime_format.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/schema/schemas/incorrect_email_format.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/schema/schemas/incorrect_hostname_format.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/schema/schemas/incorrect_ipv4_format.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/schema/schemas/incorrect_ipv6_format.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/schema/schemas/incorrect_jsonptr_format.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/schema/schemas/incorrect_regex_format.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/schema/schemas/incorrect_time_format.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/schema/schemas/incorrect_uri_format.yml (100%) rename tests/{ => unit}/fixtures/test_jsonschema/schema/schemas/invalid.yml (100%) rename tests/{ => unit}/fixtures/test_manager/dump/all.txt (100%) rename tests/{ => unit}/fixtures/test_manager/dump/byid.txt (100%) rename tests/{ => unit}/fixtures/test_manager/invalid/pyproject.toml (100%) rename tests/{ => unit}/fixtures/test_manager/invalid/schema/schemas/invalid.yml (100%) rename tests/{ => unit}/fixtures/test_manager/invalid_generate/schema/schemas/test.yml (100%) rename tests/{ => unit}/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type1/data.json (100%) rename tests/{ => unit}/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type1/exp_results.yml (100%) rename tests/{ => unit}/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type1/results.yml (100%) rename tests/{ => unit}/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type2/data.json (100%) rename tests/{ => unit}/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type2/exp_results.yml (100%) rename tests/{ => unit}/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type2/results.yml (100%) rename tests/{ => unit}/fixtures/test_manager/invalid_generate/schema/tests/test/valid/test.json (100%) rename tests/{ => unit}/fixtures/test_validators/inventory/host_vars/az_phx_pe01/base.yml (100%) rename tests/{ => unit}/fixtures/test_validators/inventory/host_vars/az_phx_pe02/base.yml (100%) rename tests/{ => unit}/fixtures/test_validators/inventory/host_vars/co_den_p01/base.yml (100%) rename tests/{ => unit}/fixtures/test_validators/inventory/inventory.yml (100%) rename tests/{ => unit}/fixtures/test_validators/validators/check_hostname.py (100%) rename tests/{ => unit}/fixtures/test_validators/validators/check_interfaces.py (100%) rename tests/{ => unit}/fixtures/test_validators/validators/check_interfaces_ipv4.py (100%) rename tests/{ => unit}/fixtures/test_validators/validators/check_peers.py (100%) rename tests/{ => unit}/fixtures/test_validators_pydantic/__init__.py (100%) rename tests/{ => unit}/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/base.yml (100%) rename tests/{ => unit}/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/dns.yml (100%) rename tests/{ => unit}/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe02/base.yml (100%) rename tests/{ => unit}/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/base.yml (100%) rename tests/{ => unit}/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/dns.yml (100%) rename tests/{ => unit}/fixtures/test_validators_pydantic/inventory/inventory.yml (100%) rename tests/{ => unit}/fixtures/test_validators_pydantic/inventory_fail/host_vars/az_phx_pe01/base.yml (100%) rename tests/{ => unit}/fixtures/test_validators_pydantic/inventory_fail/host_vars/az_phx_pe01/dns.yml (100%) rename tests/{ => unit}/fixtures/test_validators_pydantic/inventory_fail/host_vars/az_phx_pe02/base.yml (100%) rename tests/{ => unit}/fixtures/test_validators_pydantic/inventory_fail/host_vars/co_den_p01/base.yml (100%) rename tests/{ => unit}/fixtures/test_validators_pydantic/inventory_fail/host_vars/co_den_p01/dns.yml (100%) rename tests/{ => unit}/fixtures/test_validators_pydantic/inventory_fail/inventory.yml (100%) rename tests/{ => unit}/fixtures/test_validators_pydantic/pydantic_validators/__init__.py (100%) rename tests/{ => unit}/fixtures/test_validators_pydantic/pydantic_validators/models/__init__.py (100%) rename tests/{ => unit}/fixtures/test_validators_pydantic/pydantic_validators/models/dns.py (100%) rename tests/{ => unit}/fixtures/test_validators_pydantic/pydantic_validators/models/hostname.py (100%) rename tests/{ => unit}/fixtures/test_validators_pydantic/pydantic_validators/models/interfaces.py (100%) rename tests/{ => unit}/mocks/dns/invalid/invalid_format.json (100%) rename tests/{ => unit}/mocks/dns/invalid/invalid_format.yml (100%) rename tests/{ => unit}/mocks/dns/invalid/invalid_ip.json (100%) rename tests/{ => unit}/mocks/dns/invalid/invalid_ip.yml (100%) rename tests/{ => unit}/mocks/dns/invalid/missing_required.json (100%) rename tests/{ => unit}/mocks/dns/invalid/missing_required.yml (100%) rename tests/{ => unit}/mocks/dns/valid/full_implementation.json (100%) rename tests/{ => unit}/mocks/dns/valid/partial_implementation.json (100%) rename tests/{ => unit}/mocks/inventory/group_vars/all.yml (100%) rename tests/{ => unit}/mocks/inventory/group_vars/emea.yml (100%) rename tests/{ => unit}/mocks/inventory/group_vars/ios.yml (100%) rename tests/{ => unit}/mocks/inventory/group_vars/na.yml (100%) rename tests/{ => unit}/mocks/inventory/hosts (100%) rename tests/{ => unit}/mocks/ntp/invalid/invalid_format.json (100%) rename tests/{ => unit}/mocks/ntp/invalid/invalid_format.yml (100%) rename tests/{ => unit}/mocks/ntp/invalid/invalid_ip.json (100%) rename tests/{ => unit}/mocks/ntp/invalid/invalid_ip.yml (100%) rename tests/{ => unit}/mocks/ntp/invalid/missing_required.json (100%) rename tests/{ => unit}/mocks/ntp/invalid/missing_required.yml (100%) rename tests/{ => unit}/mocks/ntp/valid/full_implementation.json (100%) rename tests/{ => unit}/mocks/ntp/valid/partial_implementation.json (100%) rename tests/{ => unit}/mocks/schema/json/definitions/arrays/ip.json (100%) rename tests/{ => unit}/mocks/schema/json/definitions/objects/ip.json (100%) rename tests/{ => unit}/mocks/schema/json/definitions/properties/ip.json (100%) rename tests/{ => unit}/mocks/schema/json/full_schemas/ntp.json (100%) rename tests/{ => unit}/mocks/schema/json/schemas/dns.json (100%) rename tests/{ => unit}/mocks/schema/json/schemas/ntp.json (100%) rename tests/{ => unit}/mocks/schema/yaml/definitions/arrays/ip.yml (100%) rename tests/{ => unit}/mocks/schema/yaml/definitions/objects/ip.yml (100%) rename tests/{ => unit}/mocks/schema/yaml/definitions/properties/ip.yml (100%) rename tests/{ => unit}/mocks/schema/yaml/schemas/dns.yml (100%) rename tests/{ => unit}/mocks/schema/yaml/schemas/ntp.yml (100%) rename tests/{ => unit}/mocks/syslog/invalid/invalid_format.json (100%) rename tests/{ => unit}/mocks/syslog/invalid/invalid_format.yml (100%) rename tests/{ => unit}/mocks/syslog/invalid/invalid_ip.json (100%) rename tests/{ => unit}/mocks/syslog/invalid/invalid_ip.yml (100%) rename tests/{ => unit}/mocks/syslog/invalid/missing_required.json (100%) rename tests/{ => unit}/mocks/syslog/invalid/missing_required.yml (100%) rename tests/{ => unit}/mocks/syslog/valid/full_implementation.json (100%) rename tests/{ => unit}/mocks/syslog/valid/partial_implementation.json (100%) rename tests/{ => unit}/mocks/utils/formatted.json (100%) rename tests/{ => unit}/mocks/utils/formatted.yml (100%) rename tests/{ => unit}/mocks/utils/host1/dns.yml (100%) rename tests/{ => unit}/mocks/utils/host1/ntp.yml (100%) rename tests/{ => unit}/mocks/utils/host2/dns.yml (100%) rename tests/{ => unit}/mocks/utils/host2/ntp.yml (100%) rename tests/{ => unit}/mocks/utils/host3/dns.yml (100%) rename tests/{ => unit}/mocks/utils/host3/ntp.yml (100%) rename tests/{ => unit}/mocks/utils/host4/dns.yml (100%) rename tests/{ => unit}/mocks/utils/host4/ntp.yml (100%) rename tests/{ => unit}/mocks/utils/ntp_schema.json (100%) diff --git a/schema_enforcer/instances/__init__.py b/schema_enforcer/instances/__init__.py new file mode 100644 index 0000000..bf2754f --- /dev/null +++ b/schema_enforcer/instances/__init__.py @@ -0,0 +1 @@ +"""Instance file definitions and management for schema enforcer.""" diff --git a/schema_enforcer/schemas/__init__.py b/schema_enforcer/schemas/__init__.py new file mode 100644 index 0000000..dea54f5 --- /dev/null +++ b/schema_enforcer/schemas/__init__.py @@ -0,0 +1 @@ +"""Schema class definitions and management for schema enforcer.""" diff --git a/tests/fixtures/test_config/pyproject.toml b/tests/unit/fixtures/test_config/pyproject.toml similarity index 100% rename from tests/fixtures/test_config/pyproject.toml rename to tests/unit/fixtures/test_config/pyproject.toml diff --git a/tests/fixtures/test_config/pyproject2.toml b/tests/unit/fixtures/test_config/pyproject2.toml similarity index 100% rename from tests/fixtures/test_config/pyproject2.toml rename to tests/unit/fixtures/test_config/pyproject2.toml diff --git a/tests/fixtures/test_config/pyproject_invalid_attr.toml b/tests/unit/fixtures/test_config/pyproject_invalid_attr.toml similarity index 100% rename from tests/fixtures/test_config/pyproject_invalid_attr.toml rename to tests/unit/fixtures/test_config/pyproject_invalid_attr.toml diff --git a/tests/fixtures/test_instances/hostvars/chi-beijing-rt1/dns.yml b/tests/unit/fixtures/test_instances/hostvars/chi-beijing-rt1/dns.yml similarity index 100% rename from tests/fixtures/test_instances/hostvars/chi-beijing-rt1/dns.yml rename to tests/unit/fixtures/test_instances/hostvars/chi-beijing-rt1/dns.yml diff --git a/tests/fixtures/test_instances/hostvars/chi-beijing-rt1/syslog.yml b/tests/unit/fixtures/test_instances/hostvars/chi-beijing-rt1/syslog.yml similarity index 100% rename from tests/fixtures/test_instances/hostvars/chi-beijing-rt1/syslog.yml rename to tests/unit/fixtures/test_instances/hostvars/chi-beijing-rt1/syslog.yml diff --git a/tests/fixtures/test_instances/hostvars/eng-london-rt1/dns.yaml b/tests/unit/fixtures/test_instances/hostvars/eng-london-rt1/dns.yaml similarity index 100% rename from tests/fixtures/test_instances/hostvars/eng-london-rt1/dns.yaml rename to tests/unit/fixtures/test_instances/hostvars/eng-london-rt1/dns.yaml diff --git a/tests/fixtures/test_instances/hostvars/eng-london-rt1/ntp.yaml b/tests/unit/fixtures/test_instances/hostvars/eng-london-rt1/ntp.yaml similarity index 100% rename from tests/fixtures/test_instances/hostvars/eng-london-rt1/ntp.yaml rename to tests/unit/fixtures/test_instances/hostvars/eng-london-rt1/ntp.yaml diff --git a/tests/fixtures/test_instances/pyproject.toml b/tests/unit/fixtures/test_instances/pyproject.toml similarity index 100% rename from tests/fixtures/test_instances/pyproject.toml rename to tests/unit/fixtures/test_instances/pyproject.toml diff --git a/tests/fixtures/test_instances/schema/definitions/arrays/ip.yml b/tests/unit/fixtures/test_instances/schema/definitions/arrays/ip.yml similarity index 100% rename from tests/fixtures/test_instances/schema/definitions/arrays/ip.yml rename to tests/unit/fixtures/test_instances/schema/definitions/arrays/ip.yml diff --git a/tests/fixtures/test_instances/schema/definitions/objects/ip.yml b/tests/unit/fixtures/test_instances/schema/definitions/objects/ip.yml similarity index 100% rename from tests/fixtures/test_instances/schema/definitions/objects/ip.yml rename to tests/unit/fixtures/test_instances/schema/definitions/objects/ip.yml diff --git a/tests/fixtures/test_instances/schema/definitions/properties/ip.yml b/tests/unit/fixtures/test_instances/schema/definitions/properties/ip.yml similarity index 100% rename from tests/fixtures/test_instances/schema/definitions/properties/ip.yml rename to tests/unit/fixtures/test_instances/schema/definitions/properties/ip.yml diff --git a/tests/fixtures/test_instances/schema/schemas/dns.yml b/tests/unit/fixtures/test_instances/schema/schemas/dns.yml similarity index 100% rename from tests/fixtures/test_instances/schema/schemas/dns.yml rename to tests/unit/fixtures/test_instances/schema/schemas/dns.yml diff --git a/tests/fixtures/test_instances/schema/schemas/ntp.yml b/tests/unit/fixtures/test_instances/schema/schemas/ntp.yml similarity index 100% rename from tests/fixtures/test_instances/schema/schemas/ntp.yml rename to tests/unit/fixtures/test_instances/schema/schemas/ntp.yml diff --git a/tests/fixtures/test_instances/schema/schemas/syslog.yml b/tests/unit/fixtures/test_instances/schema/schemas/syslog.yml similarity index 100% rename from tests/fixtures/test_instances/schema/schemas/syslog.yml rename to tests/unit/fixtures/test_instances/schema/schemas/syslog.yml diff --git a/tests/fixtures/test_jsonschema/hostvars/can-vancouver-rt1/dns.yml b/tests/unit/fixtures/test_jsonschema/hostvars/can-vancouver-rt1/dns.yml similarity index 100% rename from tests/fixtures/test_jsonschema/hostvars/can-vancouver-rt1/dns.yml rename to tests/unit/fixtures/test_jsonschema/hostvars/can-vancouver-rt1/dns.yml diff --git a/tests/fixtures/test_jsonschema/hostvars/chi-beijing-rt1/dns.yml b/tests/unit/fixtures/test_jsonschema/hostvars/chi-beijing-rt1/dns.yml similarity index 100% rename from tests/fixtures/test_jsonschema/hostvars/chi-beijing-rt1/dns.yml rename to tests/unit/fixtures/test_jsonschema/hostvars/chi-beijing-rt1/dns.yml diff --git a/tests/fixtures/test_jsonschema/hostvars/eng-london-rt1/dns.yml b/tests/unit/fixtures/test_jsonschema/hostvars/eng-london-rt1/dns.yml similarity index 100% rename from tests/fixtures/test_jsonschema/hostvars/eng-london-rt1/dns.yml rename to tests/unit/fixtures/test_jsonschema/hostvars/eng-london-rt1/dns.yml diff --git a/tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/dns.yml b/tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/dns.yml similarity index 100% rename from tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/dns.yml rename to tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/dns.yml diff --git a/tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_date_format.yml b/tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_date_format.yml similarity index 100% rename from tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_date_format.yml rename to tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_date_format.yml diff --git a/tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_datetime_format.yml b/tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_datetime_format.yml similarity index 100% rename from tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_datetime_format.yml rename to tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_datetime_format.yml diff --git a/tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_email_format.yml b/tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_email_format.yml similarity index 100% rename from tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_email_format.yml rename to tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_email_format.yml diff --git a/tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_hostname_format.yml b/tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_hostname_format.yml similarity index 100% rename from tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_hostname_format.yml rename to tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_hostname_format.yml diff --git a/tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_ipv4_format.yml b/tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_ipv4_format.yml similarity index 100% rename from tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_ipv4_format.yml rename to tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_ipv4_format.yml diff --git a/tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_ipv6_format.yml b/tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_ipv6_format.yml similarity index 100% rename from tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_ipv6_format.yml rename to tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_ipv6_format.yml diff --git a/tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_jsonptr_format.yml b/tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_jsonptr_format.yml similarity index 100% rename from tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_jsonptr_format.yml rename to tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_jsonptr_format.yml diff --git a/tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_regex_format.yml b/tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_regex_format.yml similarity index 100% rename from tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_regex_format.yml rename to tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_regex_format.yml diff --git a/tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_time_format.yml b/tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_time_format.yml similarity index 100% rename from tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_time_format.yml rename to tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_time_format.yml diff --git a/tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_uri_format.yml b/tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_uri_format.yml similarity index 100% rename from tests/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_uri_format.yml rename to tests/unit/fixtures/test_jsonschema/hostvars/spa-madrid-rt1/incorrect_uri_format.yml diff --git a/tests/fixtures/test_jsonschema/schema/definitions/arrays/ip.yml b/tests/unit/fixtures/test_jsonschema/schema/definitions/arrays/ip.yml similarity index 100% rename from tests/fixtures/test_jsonschema/schema/definitions/arrays/ip.yml rename to tests/unit/fixtures/test_jsonschema/schema/definitions/arrays/ip.yml diff --git a/tests/fixtures/test_jsonschema/schema/definitions/objects/ip.yml b/tests/unit/fixtures/test_jsonschema/schema/definitions/objects/ip.yml similarity index 100% rename from tests/fixtures/test_jsonschema/schema/definitions/objects/ip.yml rename to tests/unit/fixtures/test_jsonschema/schema/definitions/objects/ip.yml diff --git a/tests/fixtures/test_jsonschema/schema/definitions/properties/ip.yml b/tests/unit/fixtures/test_jsonschema/schema/definitions/properties/ip.yml similarity index 100% rename from tests/fixtures/test_jsonschema/schema/definitions/properties/ip.yml rename to tests/unit/fixtures/test_jsonschema/schema/definitions/properties/ip.yml diff --git a/tests/fixtures/test_jsonschema/schema/schemas/dns.yml b/tests/unit/fixtures/test_jsonschema/schema/schemas/dns.yml similarity index 100% rename from tests/fixtures/test_jsonschema/schema/schemas/dns.yml rename to tests/unit/fixtures/test_jsonschema/schema/schemas/dns.yml diff --git a/tests/fixtures/test_jsonschema/schema/schemas/incorrect_date_format.yml b/tests/unit/fixtures/test_jsonschema/schema/schemas/incorrect_date_format.yml similarity index 100% rename from tests/fixtures/test_jsonschema/schema/schemas/incorrect_date_format.yml rename to tests/unit/fixtures/test_jsonschema/schema/schemas/incorrect_date_format.yml diff --git a/tests/fixtures/test_jsonschema/schema/schemas/incorrect_datetime_format.yml b/tests/unit/fixtures/test_jsonschema/schema/schemas/incorrect_datetime_format.yml similarity index 100% rename from tests/fixtures/test_jsonschema/schema/schemas/incorrect_datetime_format.yml rename to tests/unit/fixtures/test_jsonschema/schema/schemas/incorrect_datetime_format.yml diff --git a/tests/fixtures/test_jsonschema/schema/schemas/incorrect_email_format.yml b/tests/unit/fixtures/test_jsonschema/schema/schemas/incorrect_email_format.yml similarity index 100% rename from tests/fixtures/test_jsonschema/schema/schemas/incorrect_email_format.yml rename to tests/unit/fixtures/test_jsonschema/schema/schemas/incorrect_email_format.yml diff --git a/tests/fixtures/test_jsonschema/schema/schemas/incorrect_hostname_format.yml b/tests/unit/fixtures/test_jsonschema/schema/schemas/incorrect_hostname_format.yml similarity index 100% rename from tests/fixtures/test_jsonschema/schema/schemas/incorrect_hostname_format.yml rename to tests/unit/fixtures/test_jsonschema/schema/schemas/incorrect_hostname_format.yml diff --git a/tests/fixtures/test_jsonschema/schema/schemas/incorrect_ipv4_format.yml b/tests/unit/fixtures/test_jsonschema/schema/schemas/incorrect_ipv4_format.yml similarity index 100% rename from tests/fixtures/test_jsonschema/schema/schemas/incorrect_ipv4_format.yml rename to tests/unit/fixtures/test_jsonschema/schema/schemas/incorrect_ipv4_format.yml diff --git a/tests/fixtures/test_jsonschema/schema/schemas/incorrect_ipv6_format.yml b/tests/unit/fixtures/test_jsonschema/schema/schemas/incorrect_ipv6_format.yml similarity index 100% rename from tests/fixtures/test_jsonschema/schema/schemas/incorrect_ipv6_format.yml rename to tests/unit/fixtures/test_jsonschema/schema/schemas/incorrect_ipv6_format.yml diff --git a/tests/fixtures/test_jsonschema/schema/schemas/incorrect_jsonptr_format.yml b/tests/unit/fixtures/test_jsonschema/schema/schemas/incorrect_jsonptr_format.yml similarity index 100% rename from tests/fixtures/test_jsonschema/schema/schemas/incorrect_jsonptr_format.yml rename to tests/unit/fixtures/test_jsonschema/schema/schemas/incorrect_jsonptr_format.yml diff --git a/tests/fixtures/test_jsonschema/schema/schemas/incorrect_regex_format.yml b/tests/unit/fixtures/test_jsonschema/schema/schemas/incorrect_regex_format.yml similarity index 100% rename from tests/fixtures/test_jsonschema/schema/schemas/incorrect_regex_format.yml rename to tests/unit/fixtures/test_jsonschema/schema/schemas/incorrect_regex_format.yml diff --git a/tests/fixtures/test_jsonschema/schema/schemas/incorrect_time_format.yml b/tests/unit/fixtures/test_jsonschema/schema/schemas/incorrect_time_format.yml similarity index 100% rename from tests/fixtures/test_jsonschema/schema/schemas/incorrect_time_format.yml rename to tests/unit/fixtures/test_jsonschema/schema/schemas/incorrect_time_format.yml diff --git a/tests/fixtures/test_jsonschema/schema/schemas/incorrect_uri_format.yml b/tests/unit/fixtures/test_jsonschema/schema/schemas/incorrect_uri_format.yml similarity index 100% rename from tests/fixtures/test_jsonschema/schema/schemas/incorrect_uri_format.yml rename to tests/unit/fixtures/test_jsonschema/schema/schemas/incorrect_uri_format.yml diff --git a/tests/fixtures/test_jsonschema/schema/schemas/invalid.yml b/tests/unit/fixtures/test_jsonschema/schema/schemas/invalid.yml similarity index 100% rename from tests/fixtures/test_jsonschema/schema/schemas/invalid.yml rename to tests/unit/fixtures/test_jsonschema/schema/schemas/invalid.yml diff --git a/tests/fixtures/test_manager/dump/all.txt b/tests/unit/fixtures/test_manager/dump/all.txt similarity index 100% rename from tests/fixtures/test_manager/dump/all.txt rename to tests/unit/fixtures/test_manager/dump/all.txt diff --git a/tests/fixtures/test_manager/dump/byid.txt b/tests/unit/fixtures/test_manager/dump/byid.txt similarity index 100% rename from tests/fixtures/test_manager/dump/byid.txt rename to tests/unit/fixtures/test_manager/dump/byid.txt diff --git a/tests/fixtures/test_manager/invalid/pyproject.toml b/tests/unit/fixtures/test_manager/invalid/pyproject.toml similarity index 100% rename from tests/fixtures/test_manager/invalid/pyproject.toml rename to tests/unit/fixtures/test_manager/invalid/pyproject.toml diff --git a/tests/fixtures/test_manager/invalid/schema/schemas/invalid.yml b/tests/unit/fixtures/test_manager/invalid/schema/schemas/invalid.yml similarity index 100% rename from tests/fixtures/test_manager/invalid/schema/schemas/invalid.yml rename to tests/unit/fixtures/test_manager/invalid/schema/schemas/invalid.yml diff --git a/tests/fixtures/test_manager/invalid_generate/schema/schemas/test.yml b/tests/unit/fixtures/test_manager/invalid_generate/schema/schemas/test.yml similarity index 100% rename from tests/fixtures/test_manager/invalid_generate/schema/schemas/test.yml rename to tests/unit/fixtures/test_manager/invalid_generate/schema/schemas/test.yml diff --git a/tests/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type1/data.json b/tests/unit/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type1/data.json similarity index 100% rename from tests/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type1/data.json rename to tests/unit/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type1/data.json diff --git a/tests/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type1/exp_results.yml b/tests/unit/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type1/exp_results.yml similarity index 100% rename from tests/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type1/exp_results.yml rename to tests/unit/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type1/exp_results.yml diff --git a/tests/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type1/results.yml b/tests/unit/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type1/results.yml similarity index 100% rename from tests/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type1/results.yml rename to tests/unit/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type1/results.yml diff --git a/tests/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type2/data.json b/tests/unit/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type2/data.json similarity index 100% rename from tests/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type2/data.json rename to tests/unit/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type2/data.json diff --git a/tests/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type2/exp_results.yml b/tests/unit/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type2/exp_results.yml similarity index 100% rename from tests/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type2/exp_results.yml rename to tests/unit/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type2/exp_results.yml diff --git a/tests/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type2/results.yml b/tests/unit/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type2/results.yml similarity index 100% rename from tests/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type2/results.yml rename to tests/unit/fixtures/test_manager/invalid_generate/schema/tests/test/invalid/invalid_type2/results.yml diff --git a/tests/fixtures/test_manager/invalid_generate/schema/tests/test/valid/test.json b/tests/unit/fixtures/test_manager/invalid_generate/schema/tests/test/valid/test.json similarity index 100% rename from tests/fixtures/test_manager/invalid_generate/schema/tests/test/valid/test.json rename to tests/unit/fixtures/test_manager/invalid_generate/schema/tests/test/valid/test.json diff --git a/tests/fixtures/test_validators/inventory/host_vars/az_phx_pe01/base.yml b/tests/unit/fixtures/test_validators/inventory/host_vars/az_phx_pe01/base.yml similarity index 100% rename from tests/fixtures/test_validators/inventory/host_vars/az_phx_pe01/base.yml rename to tests/unit/fixtures/test_validators/inventory/host_vars/az_phx_pe01/base.yml diff --git a/tests/fixtures/test_validators/inventory/host_vars/az_phx_pe02/base.yml b/tests/unit/fixtures/test_validators/inventory/host_vars/az_phx_pe02/base.yml similarity index 100% rename from tests/fixtures/test_validators/inventory/host_vars/az_phx_pe02/base.yml rename to tests/unit/fixtures/test_validators/inventory/host_vars/az_phx_pe02/base.yml diff --git a/tests/fixtures/test_validators/inventory/host_vars/co_den_p01/base.yml b/tests/unit/fixtures/test_validators/inventory/host_vars/co_den_p01/base.yml similarity index 100% rename from tests/fixtures/test_validators/inventory/host_vars/co_den_p01/base.yml rename to tests/unit/fixtures/test_validators/inventory/host_vars/co_den_p01/base.yml diff --git a/tests/fixtures/test_validators/inventory/inventory.yml b/tests/unit/fixtures/test_validators/inventory/inventory.yml similarity index 100% rename from tests/fixtures/test_validators/inventory/inventory.yml rename to tests/unit/fixtures/test_validators/inventory/inventory.yml diff --git a/tests/fixtures/test_validators/validators/check_hostname.py b/tests/unit/fixtures/test_validators/validators/check_hostname.py similarity index 100% rename from tests/fixtures/test_validators/validators/check_hostname.py rename to tests/unit/fixtures/test_validators/validators/check_hostname.py diff --git a/tests/fixtures/test_validators/validators/check_interfaces.py b/tests/unit/fixtures/test_validators/validators/check_interfaces.py similarity index 100% rename from tests/fixtures/test_validators/validators/check_interfaces.py rename to tests/unit/fixtures/test_validators/validators/check_interfaces.py diff --git a/tests/fixtures/test_validators/validators/check_interfaces_ipv4.py b/tests/unit/fixtures/test_validators/validators/check_interfaces_ipv4.py similarity index 100% rename from tests/fixtures/test_validators/validators/check_interfaces_ipv4.py rename to tests/unit/fixtures/test_validators/validators/check_interfaces_ipv4.py diff --git a/tests/fixtures/test_validators/validators/check_peers.py b/tests/unit/fixtures/test_validators/validators/check_peers.py similarity index 100% rename from tests/fixtures/test_validators/validators/check_peers.py rename to tests/unit/fixtures/test_validators/validators/check_peers.py diff --git a/tests/fixtures/test_validators_pydantic/__init__.py b/tests/unit/fixtures/test_validators_pydantic/__init__.py similarity index 100% rename from tests/fixtures/test_validators_pydantic/__init__.py rename to tests/unit/fixtures/test_validators_pydantic/__init__.py diff --git a/tests/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/base.yml b/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/base.yml similarity index 100% rename from tests/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/base.yml rename to tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/base.yml diff --git a/tests/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/dns.yml b/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/dns.yml similarity index 100% rename from tests/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/dns.yml rename to tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/dns.yml diff --git a/tests/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe02/base.yml b/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe02/base.yml similarity index 100% rename from tests/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe02/base.yml rename to tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe02/base.yml diff --git a/tests/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/base.yml b/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/base.yml similarity index 100% rename from tests/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/base.yml rename to tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/base.yml diff --git a/tests/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/dns.yml b/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/dns.yml similarity index 100% rename from tests/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/dns.yml rename to tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/dns.yml diff --git a/tests/fixtures/test_validators_pydantic/inventory/inventory.yml b/tests/unit/fixtures/test_validators_pydantic/inventory/inventory.yml similarity index 100% rename from tests/fixtures/test_validators_pydantic/inventory/inventory.yml rename to tests/unit/fixtures/test_validators_pydantic/inventory/inventory.yml diff --git a/tests/fixtures/test_validators_pydantic/inventory_fail/host_vars/az_phx_pe01/base.yml b/tests/unit/fixtures/test_validators_pydantic/inventory_fail/host_vars/az_phx_pe01/base.yml similarity index 100% rename from tests/fixtures/test_validators_pydantic/inventory_fail/host_vars/az_phx_pe01/base.yml rename to tests/unit/fixtures/test_validators_pydantic/inventory_fail/host_vars/az_phx_pe01/base.yml diff --git a/tests/fixtures/test_validators_pydantic/inventory_fail/host_vars/az_phx_pe01/dns.yml b/tests/unit/fixtures/test_validators_pydantic/inventory_fail/host_vars/az_phx_pe01/dns.yml similarity index 100% rename from tests/fixtures/test_validators_pydantic/inventory_fail/host_vars/az_phx_pe01/dns.yml rename to tests/unit/fixtures/test_validators_pydantic/inventory_fail/host_vars/az_phx_pe01/dns.yml diff --git a/tests/fixtures/test_validators_pydantic/inventory_fail/host_vars/az_phx_pe02/base.yml b/tests/unit/fixtures/test_validators_pydantic/inventory_fail/host_vars/az_phx_pe02/base.yml similarity index 100% rename from tests/fixtures/test_validators_pydantic/inventory_fail/host_vars/az_phx_pe02/base.yml rename to tests/unit/fixtures/test_validators_pydantic/inventory_fail/host_vars/az_phx_pe02/base.yml diff --git a/tests/fixtures/test_validators_pydantic/inventory_fail/host_vars/co_den_p01/base.yml b/tests/unit/fixtures/test_validators_pydantic/inventory_fail/host_vars/co_den_p01/base.yml similarity index 100% rename from tests/fixtures/test_validators_pydantic/inventory_fail/host_vars/co_den_p01/base.yml rename to tests/unit/fixtures/test_validators_pydantic/inventory_fail/host_vars/co_den_p01/base.yml diff --git a/tests/fixtures/test_validators_pydantic/inventory_fail/host_vars/co_den_p01/dns.yml b/tests/unit/fixtures/test_validators_pydantic/inventory_fail/host_vars/co_den_p01/dns.yml similarity index 100% rename from tests/fixtures/test_validators_pydantic/inventory_fail/host_vars/co_den_p01/dns.yml rename to tests/unit/fixtures/test_validators_pydantic/inventory_fail/host_vars/co_den_p01/dns.yml diff --git a/tests/fixtures/test_validators_pydantic/inventory_fail/inventory.yml b/tests/unit/fixtures/test_validators_pydantic/inventory_fail/inventory.yml similarity index 100% rename from tests/fixtures/test_validators_pydantic/inventory_fail/inventory.yml rename to tests/unit/fixtures/test_validators_pydantic/inventory_fail/inventory.yml diff --git a/tests/fixtures/test_validators_pydantic/pydantic_validators/__init__.py b/tests/unit/fixtures/test_validators_pydantic/pydantic_validators/__init__.py similarity index 100% rename from tests/fixtures/test_validators_pydantic/pydantic_validators/__init__.py rename to tests/unit/fixtures/test_validators_pydantic/pydantic_validators/__init__.py diff --git a/tests/fixtures/test_validators_pydantic/pydantic_validators/models/__init__.py b/tests/unit/fixtures/test_validators_pydantic/pydantic_validators/models/__init__.py similarity index 100% rename from tests/fixtures/test_validators_pydantic/pydantic_validators/models/__init__.py rename to tests/unit/fixtures/test_validators_pydantic/pydantic_validators/models/__init__.py diff --git a/tests/fixtures/test_validators_pydantic/pydantic_validators/models/dns.py b/tests/unit/fixtures/test_validators_pydantic/pydantic_validators/models/dns.py similarity index 100% rename from tests/fixtures/test_validators_pydantic/pydantic_validators/models/dns.py rename to tests/unit/fixtures/test_validators_pydantic/pydantic_validators/models/dns.py diff --git a/tests/fixtures/test_validators_pydantic/pydantic_validators/models/hostname.py b/tests/unit/fixtures/test_validators_pydantic/pydantic_validators/models/hostname.py similarity index 100% rename from tests/fixtures/test_validators_pydantic/pydantic_validators/models/hostname.py rename to tests/unit/fixtures/test_validators_pydantic/pydantic_validators/models/hostname.py diff --git a/tests/fixtures/test_validators_pydantic/pydantic_validators/models/interfaces.py b/tests/unit/fixtures/test_validators_pydantic/pydantic_validators/models/interfaces.py similarity index 100% rename from tests/fixtures/test_validators_pydantic/pydantic_validators/models/interfaces.py rename to tests/unit/fixtures/test_validators_pydantic/pydantic_validators/models/interfaces.py diff --git a/tests/mocks/dns/invalid/invalid_format.json b/tests/unit/mocks/dns/invalid/invalid_format.json similarity index 100% rename from tests/mocks/dns/invalid/invalid_format.json rename to tests/unit/mocks/dns/invalid/invalid_format.json diff --git a/tests/mocks/dns/invalid/invalid_format.yml b/tests/unit/mocks/dns/invalid/invalid_format.yml similarity index 100% rename from tests/mocks/dns/invalid/invalid_format.yml rename to tests/unit/mocks/dns/invalid/invalid_format.yml diff --git a/tests/mocks/dns/invalid/invalid_ip.json b/tests/unit/mocks/dns/invalid/invalid_ip.json similarity index 100% rename from tests/mocks/dns/invalid/invalid_ip.json rename to tests/unit/mocks/dns/invalid/invalid_ip.json diff --git a/tests/mocks/dns/invalid/invalid_ip.yml b/tests/unit/mocks/dns/invalid/invalid_ip.yml similarity index 100% rename from tests/mocks/dns/invalid/invalid_ip.yml rename to tests/unit/mocks/dns/invalid/invalid_ip.yml diff --git a/tests/mocks/dns/invalid/missing_required.json b/tests/unit/mocks/dns/invalid/missing_required.json similarity index 100% rename from tests/mocks/dns/invalid/missing_required.json rename to tests/unit/mocks/dns/invalid/missing_required.json diff --git a/tests/mocks/dns/invalid/missing_required.yml b/tests/unit/mocks/dns/invalid/missing_required.yml similarity index 100% rename from tests/mocks/dns/invalid/missing_required.yml rename to tests/unit/mocks/dns/invalid/missing_required.yml diff --git a/tests/mocks/dns/valid/full_implementation.json b/tests/unit/mocks/dns/valid/full_implementation.json similarity index 100% rename from tests/mocks/dns/valid/full_implementation.json rename to tests/unit/mocks/dns/valid/full_implementation.json diff --git a/tests/mocks/dns/valid/partial_implementation.json b/tests/unit/mocks/dns/valid/partial_implementation.json similarity index 100% rename from tests/mocks/dns/valid/partial_implementation.json rename to tests/unit/mocks/dns/valid/partial_implementation.json diff --git a/tests/mocks/inventory/group_vars/all.yml b/tests/unit/mocks/inventory/group_vars/all.yml similarity index 100% rename from tests/mocks/inventory/group_vars/all.yml rename to tests/unit/mocks/inventory/group_vars/all.yml diff --git a/tests/mocks/inventory/group_vars/emea.yml b/tests/unit/mocks/inventory/group_vars/emea.yml similarity index 100% rename from tests/mocks/inventory/group_vars/emea.yml rename to tests/unit/mocks/inventory/group_vars/emea.yml diff --git a/tests/mocks/inventory/group_vars/ios.yml b/tests/unit/mocks/inventory/group_vars/ios.yml similarity index 100% rename from tests/mocks/inventory/group_vars/ios.yml rename to tests/unit/mocks/inventory/group_vars/ios.yml diff --git a/tests/mocks/inventory/group_vars/na.yml b/tests/unit/mocks/inventory/group_vars/na.yml similarity index 100% rename from tests/mocks/inventory/group_vars/na.yml rename to tests/unit/mocks/inventory/group_vars/na.yml diff --git a/tests/mocks/inventory/hosts b/tests/unit/mocks/inventory/hosts similarity index 100% rename from tests/mocks/inventory/hosts rename to tests/unit/mocks/inventory/hosts diff --git a/tests/mocks/ntp/invalid/invalid_format.json b/tests/unit/mocks/ntp/invalid/invalid_format.json similarity index 100% rename from tests/mocks/ntp/invalid/invalid_format.json rename to tests/unit/mocks/ntp/invalid/invalid_format.json diff --git a/tests/mocks/ntp/invalid/invalid_format.yml b/tests/unit/mocks/ntp/invalid/invalid_format.yml similarity index 100% rename from tests/mocks/ntp/invalid/invalid_format.yml rename to tests/unit/mocks/ntp/invalid/invalid_format.yml diff --git a/tests/mocks/ntp/invalid/invalid_ip.json b/tests/unit/mocks/ntp/invalid/invalid_ip.json similarity index 100% rename from tests/mocks/ntp/invalid/invalid_ip.json rename to tests/unit/mocks/ntp/invalid/invalid_ip.json diff --git a/tests/mocks/ntp/invalid/invalid_ip.yml b/tests/unit/mocks/ntp/invalid/invalid_ip.yml similarity index 100% rename from tests/mocks/ntp/invalid/invalid_ip.yml rename to tests/unit/mocks/ntp/invalid/invalid_ip.yml diff --git a/tests/mocks/ntp/invalid/missing_required.json b/tests/unit/mocks/ntp/invalid/missing_required.json similarity index 100% rename from tests/mocks/ntp/invalid/missing_required.json rename to tests/unit/mocks/ntp/invalid/missing_required.json diff --git a/tests/mocks/ntp/invalid/missing_required.yml b/tests/unit/mocks/ntp/invalid/missing_required.yml similarity index 100% rename from tests/mocks/ntp/invalid/missing_required.yml rename to tests/unit/mocks/ntp/invalid/missing_required.yml diff --git a/tests/mocks/ntp/valid/full_implementation.json b/tests/unit/mocks/ntp/valid/full_implementation.json similarity index 100% rename from tests/mocks/ntp/valid/full_implementation.json rename to tests/unit/mocks/ntp/valid/full_implementation.json diff --git a/tests/mocks/ntp/valid/partial_implementation.json b/tests/unit/mocks/ntp/valid/partial_implementation.json similarity index 100% rename from tests/mocks/ntp/valid/partial_implementation.json rename to tests/unit/mocks/ntp/valid/partial_implementation.json diff --git a/tests/mocks/schema/json/definitions/arrays/ip.json b/tests/unit/mocks/schema/json/definitions/arrays/ip.json similarity index 100% rename from tests/mocks/schema/json/definitions/arrays/ip.json rename to tests/unit/mocks/schema/json/definitions/arrays/ip.json diff --git a/tests/mocks/schema/json/definitions/objects/ip.json b/tests/unit/mocks/schema/json/definitions/objects/ip.json similarity index 100% rename from tests/mocks/schema/json/definitions/objects/ip.json rename to tests/unit/mocks/schema/json/definitions/objects/ip.json diff --git a/tests/mocks/schema/json/definitions/properties/ip.json b/tests/unit/mocks/schema/json/definitions/properties/ip.json similarity index 100% rename from tests/mocks/schema/json/definitions/properties/ip.json rename to tests/unit/mocks/schema/json/definitions/properties/ip.json diff --git a/tests/mocks/schema/json/full_schemas/ntp.json b/tests/unit/mocks/schema/json/full_schemas/ntp.json similarity index 100% rename from tests/mocks/schema/json/full_schemas/ntp.json rename to tests/unit/mocks/schema/json/full_schemas/ntp.json diff --git a/tests/mocks/schema/json/schemas/dns.json b/tests/unit/mocks/schema/json/schemas/dns.json similarity index 100% rename from tests/mocks/schema/json/schemas/dns.json rename to tests/unit/mocks/schema/json/schemas/dns.json diff --git a/tests/mocks/schema/json/schemas/ntp.json b/tests/unit/mocks/schema/json/schemas/ntp.json similarity index 100% rename from tests/mocks/schema/json/schemas/ntp.json rename to tests/unit/mocks/schema/json/schemas/ntp.json diff --git a/tests/mocks/schema/yaml/definitions/arrays/ip.yml b/tests/unit/mocks/schema/yaml/definitions/arrays/ip.yml similarity index 100% rename from tests/mocks/schema/yaml/definitions/arrays/ip.yml rename to tests/unit/mocks/schema/yaml/definitions/arrays/ip.yml diff --git a/tests/mocks/schema/yaml/definitions/objects/ip.yml b/tests/unit/mocks/schema/yaml/definitions/objects/ip.yml similarity index 100% rename from tests/mocks/schema/yaml/definitions/objects/ip.yml rename to tests/unit/mocks/schema/yaml/definitions/objects/ip.yml diff --git a/tests/mocks/schema/yaml/definitions/properties/ip.yml b/tests/unit/mocks/schema/yaml/definitions/properties/ip.yml similarity index 100% rename from tests/mocks/schema/yaml/definitions/properties/ip.yml rename to tests/unit/mocks/schema/yaml/definitions/properties/ip.yml diff --git a/tests/mocks/schema/yaml/schemas/dns.yml b/tests/unit/mocks/schema/yaml/schemas/dns.yml similarity index 100% rename from tests/mocks/schema/yaml/schemas/dns.yml rename to tests/unit/mocks/schema/yaml/schemas/dns.yml diff --git a/tests/mocks/schema/yaml/schemas/ntp.yml b/tests/unit/mocks/schema/yaml/schemas/ntp.yml similarity index 100% rename from tests/mocks/schema/yaml/schemas/ntp.yml rename to tests/unit/mocks/schema/yaml/schemas/ntp.yml diff --git a/tests/mocks/syslog/invalid/invalid_format.json b/tests/unit/mocks/syslog/invalid/invalid_format.json similarity index 100% rename from tests/mocks/syslog/invalid/invalid_format.json rename to tests/unit/mocks/syslog/invalid/invalid_format.json diff --git a/tests/mocks/syslog/invalid/invalid_format.yml b/tests/unit/mocks/syslog/invalid/invalid_format.yml similarity index 100% rename from tests/mocks/syslog/invalid/invalid_format.yml rename to tests/unit/mocks/syslog/invalid/invalid_format.yml diff --git a/tests/mocks/syslog/invalid/invalid_ip.json b/tests/unit/mocks/syslog/invalid/invalid_ip.json similarity index 100% rename from tests/mocks/syslog/invalid/invalid_ip.json rename to tests/unit/mocks/syslog/invalid/invalid_ip.json diff --git a/tests/mocks/syslog/invalid/invalid_ip.yml b/tests/unit/mocks/syslog/invalid/invalid_ip.yml similarity index 100% rename from tests/mocks/syslog/invalid/invalid_ip.yml rename to tests/unit/mocks/syslog/invalid/invalid_ip.yml diff --git a/tests/mocks/syslog/invalid/missing_required.json b/tests/unit/mocks/syslog/invalid/missing_required.json similarity index 100% rename from tests/mocks/syslog/invalid/missing_required.json rename to tests/unit/mocks/syslog/invalid/missing_required.json diff --git a/tests/mocks/syslog/invalid/missing_required.yml b/tests/unit/mocks/syslog/invalid/missing_required.yml similarity index 100% rename from tests/mocks/syslog/invalid/missing_required.yml rename to tests/unit/mocks/syslog/invalid/missing_required.yml diff --git a/tests/mocks/syslog/valid/full_implementation.json b/tests/unit/mocks/syslog/valid/full_implementation.json similarity index 100% rename from tests/mocks/syslog/valid/full_implementation.json rename to tests/unit/mocks/syslog/valid/full_implementation.json diff --git a/tests/mocks/syslog/valid/partial_implementation.json b/tests/unit/mocks/syslog/valid/partial_implementation.json similarity index 100% rename from tests/mocks/syslog/valid/partial_implementation.json rename to tests/unit/mocks/syslog/valid/partial_implementation.json diff --git a/tests/mocks/utils/formatted.json b/tests/unit/mocks/utils/formatted.json similarity index 100% rename from tests/mocks/utils/formatted.json rename to tests/unit/mocks/utils/formatted.json diff --git a/tests/mocks/utils/formatted.yml b/tests/unit/mocks/utils/formatted.yml similarity index 100% rename from tests/mocks/utils/formatted.yml rename to tests/unit/mocks/utils/formatted.yml diff --git a/tests/mocks/utils/host1/dns.yml b/tests/unit/mocks/utils/host1/dns.yml similarity index 100% rename from tests/mocks/utils/host1/dns.yml rename to tests/unit/mocks/utils/host1/dns.yml diff --git a/tests/mocks/utils/host1/ntp.yml b/tests/unit/mocks/utils/host1/ntp.yml similarity index 100% rename from tests/mocks/utils/host1/ntp.yml rename to tests/unit/mocks/utils/host1/ntp.yml diff --git a/tests/mocks/utils/host2/dns.yml b/tests/unit/mocks/utils/host2/dns.yml similarity index 100% rename from tests/mocks/utils/host2/dns.yml rename to tests/unit/mocks/utils/host2/dns.yml diff --git a/tests/mocks/utils/host2/ntp.yml b/tests/unit/mocks/utils/host2/ntp.yml similarity index 100% rename from tests/mocks/utils/host2/ntp.yml rename to tests/unit/mocks/utils/host2/ntp.yml diff --git a/tests/mocks/utils/host3/dns.yml b/tests/unit/mocks/utils/host3/dns.yml similarity index 100% rename from tests/mocks/utils/host3/dns.yml rename to tests/unit/mocks/utils/host3/dns.yml diff --git a/tests/mocks/utils/host3/ntp.yml b/tests/unit/mocks/utils/host3/ntp.yml similarity index 100% rename from tests/mocks/utils/host3/ntp.yml rename to tests/unit/mocks/utils/host3/ntp.yml diff --git a/tests/mocks/utils/host4/dns.yml b/tests/unit/mocks/utils/host4/dns.yml similarity index 100% rename from tests/mocks/utils/host4/dns.yml rename to tests/unit/mocks/utils/host4/dns.yml diff --git a/tests/mocks/utils/host4/ntp.yml b/tests/unit/mocks/utils/host4/ntp.yml similarity index 100% rename from tests/mocks/utils/host4/ntp.yml rename to tests/unit/mocks/utils/host4/ntp.yml diff --git a/tests/mocks/utils/ntp_schema.json b/tests/unit/mocks/utils/ntp_schema.json similarity index 100% rename from tests/mocks/utils/ntp_schema.json rename to tests/unit/mocks/utils/ntp_schema.json From 90934edb6d3cf874fb1346fb327653758de1b06e Mon Sep 17 00:00:00 2001 From: Jeff Kala Date: Fri, 7 Aug 2026 14:01:20 -0600 Subject: [PATCH 6/6] fixes to revert pytest failures --- pyproject.toml | 2 +- schema_enforcer/utils.py | 69 +------------------ tasks.py | 3 - tests/unit/mocks/utils/host2/dns.yml | 4 -- tests/unit/mocks/utils/host2/ntp.yml | 4 -- tests/unit/mocks/utils/host3/dns.yml | 5 -- tests/unit/mocks/utils/host3/ntp.yml | 3 - tests/unit/mocks/utils/host4/dns.yml | 5 -- tests/unit/mocks/utils/host4/ntp.yml | 3 - tests/unit/mocks/utils/ntp_schema.json | 20 ------ tests/unit/test_ansible_inventory.py | 2 +- tests/unit/test_cli_ansible_not_exists.py | 12 +++- .../test_instances_instance_file_manager.py | 8 +-- .../unit/test_schemas_pydantic_validators.py | 40 +++++------ tests/unit/test_utils.py | 24 +++---- 15 files changed, 51 insertions(+), 153 deletions(-) delete mode 100755 tests/unit/mocks/utils/host2/dns.yml delete mode 100755 tests/unit/mocks/utils/host2/ntp.yml delete mode 100644 tests/unit/mocks/utils/host3/dns.yml delete mode 100644 tests/unit/mocks/utils/host3/ntp.yml delete mode 100644 tests/unit/mocks/utils/host4/dns.yml delete mode 100644 tests/unit/mocks/utils/host4/ntp.yml delete mode 100755 tests/unit/mocks/utils/ntp_schema.json diff --git a/pyproject.toml b/pyproject.toml index bce0d86..4c5a8e1 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -171,7 +171,7 @@ testpaths = [ addopts = "-vv --doctest-modules -p no:warnings --ignore-glob='*mock*'" [tool.towncrier] -package = "schema-enforcer" +package = "schema_enforcer" directory = "changes" filename = "docs/admin/release_notes/version_X.Y.md" template = "towncrier_template.j2" diff --git a/schema_enforcer/utils.py b/schema_enforcer/utils.py index 152f4ce..d216c47 100755 --- a/schema_enforcer/utils.py +++ b/schema_enforcer/utils.py @@ -32,7 +32,7 @@ def error(msg): def get_path_and_filename(filepath): - """Splits ``filepath`` into the directory path and filename w/o extesion. + """Splits ``filepath`` into the directory path and filename w/o extension. Args: filepath (str): The path to a file. @@ -43,9 +43,9 @@ def get_path_and_filename(filepath): Example: >>> path, filename = get_path_and_filename("schema/json/schemas/ntp.json") >>> print(path) - 'schema/json/schemas' + schema/json/schemas >>> print(filename) - 'ntp' + ntp >>> """ file, _ = os.path.splitext(filepath) @@ -107,19 +107,6 @@ def get_conversion_filepaths(original_path, original_extension, conversion_path, Returns: list: A tuple of paths to the original and the conversion files. - - Example: - >>> os.listdir("schema/yaml/schemas/") - ["ntp.yml", "snmp.yml"] - >>> conversion_paths = get_conversion_filepaths( - ... "schema/yaml/", "yml", "schema/json", "json" - ... ) - >>> for yaml_path, json_path in conversion_paths: - ... print(f"Original: {yaml_path} -> Conversion: {json_path}") - ... - Original: schema/yaml/schemas/ntp.yml -> Conversion: schema/json/schemas/ntp.json - Original: schema/yaml/schemas/snmp.yml -> Conversion: schema/json/schemas/snmp.json - >>> """ original_path = os.path.normpath(original_path) conversion_path = os.path.normpath(conversion_path) @@ -148,14 +135,6 @@ def load_schema_from_json_file(schema_root_dir, schema_filepath): Returns: jsonschema.Validator: A Validator instance with schema loaded with a RefResolver and format_checker. - - Example: - >>> schema_root_dir = "/home/users/admin/network-schema/schema/json" - >>> schema_filepath = "schema/json/schemas/ntp.json" - >>> validator = load_schema_from_json_file(schema_root_dir, schema_filepath) - >>> validator.schema - >>> {...} - >>> """ base_uri = f"file:{schema_root_dir}/".replace("\\", "/") with open(os.path.join(schema_root_dir, schema_filepath), encoding="utf-8") as fileh: @@ -180,15 +159,6 @@ def dump_data_to_yaml(data, yaml_path): Returns: None: Data is written to a file. - - Example: - >>> os.listdir("hostvars/sw01/") - ["dns.yml", "snmp.yml"] - >>> data = {"ntp": {"servers": [{"address": "10.1.1.1", "vrf": "mgmt"}]}} - >>> yaml_file = "hostvars/sw01/ntp.yml" - >>> os.listdir("hostvars/sw01/") - ["dns.yml", "ntp.yml", "snmp.yml"] - >>> """ data_formatted = ensure_strings_have_quotes_mapping(data) with open(yaml_path, "w", encoding="utf-8") as fileh: @@ -204,15 +174,6 @@ def dump_data_to_json(data, json_path): Returns: None: Data is written to a file. - - Example: - >>> os.listdir("hostvars/sw01/") - ["dns.json", "snmp.json"] - >>> data = {"ntp": {"servers": [{"address": "10.1.1.1", "vrf": "mgmt"}]}} - >>> json_file = "hostvars/sw01/json.yml" - >>> os.listdir("hostvars/sw01/") - ["dns.json", "ntp.json", "snmp.json"] - >>> """ with open(json_path, "w", encoding="utf-8") as fileh: json.dump(data, fileh, indent=4) @@ -227,18 +188,6 @@ def get_schema_properties(schema_files): Returns: dict: Schema filenames are the keys, and the values are list of property names. - - Example: - >>> schema_files = [ - ... 'schema/json/schemas/ntp.json', 'schema/json/schemas/snmp.json' - ... ] - >>> schema_property_map = get_schema_properties(schema_files) - >>> print(schema_property_map) - { - 'ntp': ['ntp_servers', 'ntp_authentication'], - 'snmp': ['snmp_servers'] - } - >>> """ schema_property_map = {} for schema_file in schema_files: @@ -261,18 +210,6 @@ def dump_schema_vars(output_dir, schema_properties, variables): Returns: None: Files are written for each schema definition. - - Example: - >>> output_dir = "inventory/hostvars/host1" - >>> schema_files = glob.glob("schema/json/schemas/*.json") - >>> schema_properties = get_schema_properties(schema_files) - >>> host_variables = magic_hostvar_generator() - >>> os.isdir(output_dir) - False - >>> dump_schema_vars(output_dir, schema_properties, host_variables) - >>> os.listdir(output_dir) - ['ntp.yml', 'snmp.yml'] - >>> """ os.makedirs(output_dir, exist_ok=True) # Somewhat of a hack to remove non basic object types from data structure diff --git a/tasks.py b/tasks.py index 86e70be..f3feb8f 100644 --- a/tasks.py +++ b/tasks.py @@ -181,9 +181,6 @@ def coverage(context): ) def pytest(context, pattern=None, label=None): """Run pytest test cases.""" - exec_cmd = "pytest -vv --doctest-modules schema_enforcer/ && coverage run --source=schema_enforcer -m pytest && coverage report" - run_command(context, exec_cmd) - doc_test_cmd = "pytest -vv --doctest-modules schema_enforcer/" pytest_cmd = "coverage run --source=schema_enforcer -m pytest" if pattern: diff --git a/tests/unit/mocks/utils/host2/dns.yml b/tests/unit/mocks/utils/host2/dns.yml deleted file mode 100755 index 4919e4b..0000000 --- a/tests/unit/mocks/utils/host2/dns.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -dns_servers: - - address: "10.2.1.1" - vrf: "mgmt" diff --git a/tests/unit/mocks/utils/host2/ntp.yml b/tests/unit/mocks/utils/host2/ntp.yml deleted file mode 100755 index 64e4773..0000000 --- a/tests/unit/mocks/utils/host2/ntp.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -ntp_servers: - - address: "10.2.1.1" - vrf: "mgmt" diff --git a/tests/unit/mocks/utils/host3/dns.yml b/tests/unit/mocks/utils/host3/dns.yml deleted file mode 100644 index 7c7d63c..0000000 --- a/tests/unit/mocks/utils/host3/dns.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -dns_servers: - - address: "10.7.7.7" - vrf: "mgmt" - - address: "10.8.8.8" diff --git a/tests/unit/mocks/utils/host3/ntp.yml b/tests/unit/mocks/utils/host3/ntp.yml deleted file mode 100644 index 64da98f..0000000 --- a/tests/unit/mocks/utils/host3/ntp.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -ntp_servers: - - address: "10.3.3.3" diff --git a/tests/unit/mocks/utils/host4/dns.yml b/tests/unit/mocks/utils/host4/dns.yml deleted file mode 100644 index 3c7baae..0000000 --- a/tests/unit/mocks/utils/host4/dns.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -dns_servers: - - address: "10.4.4.4" - vrf: "mgmt" - - address: "10.5.5.5" diff --git a/tests/unit/mocks/utils/host4/ntp.yml b/tests/unit/mocks/utils/host4/ntp.yml deleted file mode 100644 index bfeae75..0000000 --- a/tests/unit/mocks/utils/host4/ntp.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -ntp_servers: - - address: "10.6.6.6" diff --git a/tests/unit/mocks/utils/ntp_schema.json b/tests/unit/mocks/utils/ntp_schema.json deleted file mode 100755 index c3bda4a..0000000 --- a/tests/unit/mocks/utils/ntp_schema.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "schemas/ntp", - "description": "NTP Configuration schema.", - "type": "object", - "properties": { - "ntp_servers": { - "$ref": "../definitions/arrays/ip.json#ipv4_hosts" - }, - "ntp_authentication": { - "type": "boolean" - }, - "ntp_logging": { - "type": "boolean" - } - }, - "required": [ - "ntp_servers" - ] -} diff --git a/tests/unit/test_ansible_inventory.py b/tests/unit/test_ansible_inventory.py index 6262ac6..1852e88 100644 --- a/tests/unit/test_ansible_inventory.py +++ b/tests/unit/test_ansible_inventory.py @@ -5,7 +5,7 @@ from schema_enforcer.ansible_inventory import AnsibleInventory -INVENTORY_DIR = "tests/mocks/inventory" +INVENTORY_DIR = "tests/unit/mocks/inventory" @pytest.fixture(scope="module") diff --git a/tests/unit/test_cli_ansible_not_exists.py b/tests/unit/test_cli_ansible_not_exists.py index ec9a116..28be8f7 100644 --- a/tests/unit/test_cli_ansible_not_exists.py +++ b/tests/unit/test_cli_ansible_not_exists.py @@ -1,14 +1,22 @@ """Unit tests for cli.py ansible when ansible is not installed""" +import sys +from unittest import mock + from click.testing import CliRunner from schema_enforcer import cli def test_ansible_import_when_not_exists(): - """Tests ansible command exits when ansible is not installed on the host system and message indicates the exit is because the ansible command is not found.""" + """Tests ansible command exits when ansible is not installed on the host system and message indicates the exit is because the ansible command is not found. + + The test environment installs ansible, so the import of `schema_enforcer.ansible_inventory` is forced to raise + ModuleNotFoundError by nulling its entry in `sys.modules`, simulating an environment without ansible installed. + """ runner = CliRunner() - raised_error = runner.invoke(cli.ansible, ["--show-checks"]) + with mock.patch.dict(sys.modules, {"schema_enforcer.ansible_inventory": None}): + raised_error = runner.invoke(cli.ansible, ["--show-checks"]) # For whatever reason, the raised error does not exactly match SystemExit(1). The diff output by pylint shows no # differences between the objects name or type, so the assertion converts to string before matching as this # effectively accomplishes the same thing. diff --git a/tests/unit/test_instances_instance_file_manager.py b/tests/unit/test_instances_instance_file_manager.py index bf747f3..8a6233e 100644 --- a/tests/unit/test_instances_instance_file_manager.py +++ b/tests/unit/test_instances_instance_file_manager.py @@ -47,10 +47,10 @@ def test_print_instances_schema_mapping(ifm, capsys): print_string = ( "Structured Data File Schema ID\n" "--------------------------------------------------------------------------------\n" - "/local/tests/fixtures/test_instances/hostvars/chi-beijing-rt1/dns.yml ['schemas/dns_servers']\n" - "/local/tests/fixtures/test_instances/hostvars/chi-beijing-rt1/syslog.yml []\n" - "/local/tests/fixtures/test_instances/hostvars/eng-london-rt1/dns.yaml []\n" - "/local/tests/fixtures/test_instances/hostvars/eng-london-rt1/ntp.yaml ['schemas/ntp']\n" + "/local/tests/unit/fixtures/test_instances/hostvars/chi-beijing-rt1/dns.yml ['schemas/dns_servers']\n" + "/local/tests/unit/fixtures/test_instances/hostvars/chi-beijing-rt1/syslog.yml []\n" + "/local/tests/unit/fixtures/test_instances/hostvars/eng-london-rt1/dns.yaml []\n" + "/local/tests/unit/fixtures/test_instances/hostvars/eng-london-rt1/ntp.yaml ['schemas/ntp']\n" ) ifm.print_schema_mapping() captured = capsys.readouterr() diff --git a/tests/unit/test_schemas_pydantic_validators.py b/tests/unit/test_schemas_pydantic_validators.py index 10c441c..dde9544 100644 --- a/tests/unit/test_schemas_pydantic_validators.py +++ b/tests/unit/test_schemas_pydantic_validators.py @@ -103,12 +103,12 @@ def test_pydantic_manager_validate_correct_checks_mapping_cli_success(_load): assert result.exit_code == 0 expected = """Structured Data File Schema ID -------------------------------------------------------------------------------- -/local/tests/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/base.yml ['Hostname', 'Interfaces', 'pydantic/Hostname', 'pydantic/Interfaces'] -/local/tests/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/dns.yml ['pydantic/Dns'] -/local/tests/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe02/base.yml ['Hostname', 'Interfaces', 'pydantic/Hostname', 'pydantic/Interfaces'] -/local/tests/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/base.yml ['Hostname', 'Interfaces', 'pydantic/Hostname', 'pydantic/Interfaces'] -/local/tests/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/dns.yml ['pydantic/Dns'] -/local/tests/fixtures/test_validators_pydantic/inventory/inventory.yml [] +/local/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/base.yml ['Hostname', 'Interfaces', 'pydantic/Hostname', 'pydantic/Interfaces'] +/local/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/dns.yml ['pydantic/Dns'] +/local/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe02/base.yml ['Hostname', 'Interfaces', 'pydantic/Hostname', 'pydantic/Interfaces'] +/local/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/base.yml ['Hostname', 'Interfaces', 'pydantic/Hostname', 'pydantic/Interfaces'] +/local/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/dns.yml ['pydantic/Dns'] +/local/tests/unit/fixtures/test_validators_pydantic/inventory/inventory.yml [] """ assert expected == result.output @@ -121,32 +121,32 @@ def test_pydantic_manager_validate_show_pass_cli(_load): _load.assert_called_once() assert result.exit_code == 0 assert ( - "PASS | [FILE] /local/tests/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/base.yml\n" - "PASS | [FILE] /local/tests/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/base.yml\n" - "PASS | [FILE] /local/tests/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/base.yml\n" - "PASS | [FILE] /local/tests/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/base.yml\n" + "PASS | [FILE] /local/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/base.yml\n" + "PASS | [FILE] /local/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/base.yml\n" + "PASS | [FILE] /local/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/base.yml\n" + "PASS | [FILE] /local/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/base.yml\n" in result.output ) assert ( - "PASS | [FILE] /local/tests/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/dns.yml" + "PASS | [FILE] /local/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe01/dns.yml" in result.output ) assert ( - "PASS | [FILE] /local/tests/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/base.yml\n" - "PASS | [FILE] /local/tests/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/base.yml\n" - "PASS | [FILE] /local/tests/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/base.yml\n" - "PASS | [FILE] /local/tests/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/base.yml\n" + "PASS | [FILE] /local/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/base.yml\n" + "PASS | [FILE] /local/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/base.yml\n" + "PASS | [FILE] /local/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/base.yml\n" + "PASS | [FILE] /local/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/base.yml\n" in result.output ) assert ( - "PASS | [FILE] /local/tests/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/dns.yml" + "PASS | [FILE] /local/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/co_den_p01/dns.yml" in result.output ) assert ( - "PASS | [FILE] /local/tests/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe02/base.yml\n" - "PASS | [FILE] /local/tests/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe02/base.yml\n" - "PASS | [FILE] /local/tests/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe02/base.yml\n" - "PASS | [FILE] /local/tests/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe02/base.yml\n" + "PASS | [FILE] /local/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe02/base.yml\n" + "PASS | [FILE] /local/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe02/base.yml\n" + "PASS | [FILE] /local/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe02/base.yml\n" + "PASS | [FILE] /local/tests/unit/fixtures/test_validators_pydantic/inventory/host_vars/az_phx_pe02/base.yml\n" in result.output ) assert "ALL SCHEMA VALIDATION CHECKS PASSED" in result.output diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 9b0d22d..204c366 100755 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -62,14 +62,14 @@ def test_get_path_and_filename(): def test_ensure_yaml_output_format(): data_formatted = utils.ensure_strings_have_quotes_mapping(TEST_DATA) - yaml_path = "tests/mocks/utils/.formatted.yml" + yaml_path = "tests/unit/mocks/utils/.formatted.yml" with open(yaml_path, "w", encoding="utf-8") as fileh: utils.YAML_HANDLER.dump(data_formatted, fileh) with open(yaml_path, encoding="utf-8") as fileh: actual = fileh.read() - with open("tests/mocks/utils/formatted.yml", encoding="utf-8") as fileh: + with open("tests/unit/mocks/utils/formatted.yml", encoding="utf-8") as fileh: mock = fileh.read() mock = remove_comments_from_yaml_string(mock) @@ -80,7 +80,7 @@ def test_ensure_yaml_output_format(): def test_get_conversion_filepaths(): - yaml_path = "tests/mocks/schema/yaml" + yaml_path = "tests/unit/mocks/schema/yaml" json_path = yaml_path.replace("yaml", "json") actual = utils.get_conversion_filepaths(yaml_path, "yml", json_path, "json") expected_defs = [ @@ -99,16 +99,16 @@ def test_get_conversion_filepaths(): def test_load_schema_from_json_file(): - schema_root_dir = os.path.realpath("tests/mocks/schema/json") + schema_root_dir = os.path.realpath("tests/unit/mocks/schema/json") schema_filepath = f"{schema_root_dir}/schemas/ntp.json" validator = utils.load_schema_from_json_file(schema_root_dir, schema_filepath) - with open("tests/mocks/ntp/valid/full_implementation.json", encoding="utf-8") as fileh: + with open("tests/unit/mocks/ntp/valid/full_implementation.json", encoding="utf-8") as fileh: # testing validation tests that the RefResolver works as expected validator.validate(json.load(fileh)) def test_dump_data_to_yaml(): - test_file = "tests/mocks/utils/.test_data.yml" + test_file = "tests/unit/mocks/utils/.test_data.yml" if os.path.isfile(test_file): os.remove(test_file) @@ -116,7 +116,7 @@ def test_dump_data_to_yaml(): utils.dump_data_to_yaml(TEST_DATA, test_file) with open(test_file, encoding="utf-8") as fileh: actual = fileh.read() - with open("tests/mocks/utils/formatted.yml", encoding="utf-8") as fileh: + with open("tests/unit/mocks/utils/formatted.yml", encoding="utf-8") as fileh: mock = fileh.read() mock = remove_comments_from_yaml_string(mock) @@ -127,12 +127,12 @@ def test_dump_data_to_yaml(): def test_dump_data_json(): - test_file = "tests/mocks/utils/.test_data.json" + test_file = "tests/unit/mocks/utils/.test_data.json" assert not os.path.isfile(test_file) utils.dump_data_to_json(TEST_DATA, test_file) with open(test_file, encoding="utf-8") as fileh: actual = fileh.read() - with open("tests/mocks/utils/formatted.json", encoding="utf-8") as fileh: + with open("tests/unit/mocks/utils/formatted.json", encoding="utf-8") as fileh: mock = fileh.read() assert actual == mock os.remove(test_file) @@ -140,7 +140,7 @@ def test_dump_data_json(): def test_get_schema_properties(): - schema_files = [f"tests/mocks/schema/json/schemas/{schema}.json" for schema in ("dns", "ntp")] + schema_files = [f"tests/unit/mocks/schema/json/schemas/{schema}.json" for schema in ("dns", "ntp")] actual = utils.get_schema_properties(schema_files) mock = { "dns": ["dns_servers"], @@ -150,7 +150,7 @@ def test_get_schema_properties(): def test_dump_schema_vars(): - output_dir = "tests/mocks/utils/hostvar" + output_dir = "tests/unit/mocks/utils/hostvar" assert not os.path.isdir(output_dir) schema_properties = { "dns": ["dns_servers"], @@ -161,7 +161,7 @@ def test_dump_schema_vars(): for file in ("dns.yml", "ntp.yml"): with open(f"{output_dir}/{file}", encoding="utf-8") as fileh: actual = fileh.read() - with open(f"tests/mocks/utils/host1/{file}", encoding="utf-8") as fileh: + with open(f"tests/unit/mocks/utils/host1/{file}", encoding="utf-8") as fileh: mock = fileh.read() assert actual == mock