From 49b2e417732d5f4ee0e9085eca57f480a610919b Mon Sep 17 00:00:00 2001 From: bakebot Date: Tue, 9 Jan 2024 12:08:58 +0000 Subject: [PATCH 01/15] Cookie initialy baked by NetworkToCode Cookie Drift Manager Tool Template: ``` { "template": "https://github.com/nautobot/cookiecutter-nautobot-app.git", "dir": "nautobot-app", "ref": "nautobot-app-v1.2", "path": null } ``` Cookie: ``` { "remote": "https://github.com/nautobot/nautobot-app-design-builder.git", "path": "/opt/ntc/drift-manager/outputs/nautobot-app-design-builder", "repository_path": "/opt/ntc/drift-manager/outputs/nautobot-app-design-builder", "dir": "", "branch_prefix": "drift-manager", "context": { "codeowner_github_usernames": "@abates @mzbroch", "full_name": "Network to Code, LLC", "email": "opensource@networktocode.com", "github_org": "nautobot", "plugin_name": "nautobot_design_builder", "verbose_name": "Nautobot Design Builder", "plugin_slug": "nautobot-design-builder", "project_slug": "nautobot-app-design-builder", "repo_url": "https://github.com/nautobot/nautobot-app-design-builder", "base_url": "design-builder", "min_nautobot_version": "1.6.8", "max_nautobot_version": "2.9999", "camel_name": "NautobotDesignBuilder", "project_short_description": "Nautobot app that uses design templates to easily create data objects in Nautobot with minimal input from a user.", "model_class_name": "None", "open_source_license": "Apache-2.0", "docs_base_url": "https://docs.nautobot.com", "docs_app_url": "https://docs.nautobot.com/projects/nautobot-design-builder/en/latest", "_template": "https://github.com/nautobot/cookiecutter-nautobot-app.git", "_output_dir": "/opt/ntc/drift-manager/outputs", "_repo_dir": "/opt/ntc/drift-manager/outputs/.cookiecutters/cookiecutter-nautobot-app/nautobot-app", "_checkout": "nautobot-app-v1.2" }, "base_branch": "develop", "remote_name": "origin", "pull_request_strategy": "PullRequestStrategy.CREATE", "post_actions": [ "PostAction.BLACK" ], "baked_commit_ref": "", "draft": false } ``` CLI Arguments: ``` { "cookie_dir": "", "input": false, "json_filename": "design-builder.json", "output_dir": "./outputs", "push": true, "template": "https://github.com/nautobot/cookiecutter-nautobot-app.git", "template_dir": "nautobot-app", "template_ref": "nautobot-app-v1.2", "pull_request": null, "post_action": [ "black" ], "disable_post_actions": false, "draft": false } ``` --- .cookiecutter.json | 53 ++- .flake8 | 6 +- .github/ISSUE_TEMPLATE/bug_report.md | 8 +- .github/ISSUE_TEMPLATE/feature_request.md | 4 +- .../pull_request_template.md | 39 +- .github/workflows/ci.yml | 138 +++++-- .github/workflows/rebake.yml | 118 ++++++ .github/workflows/upstream_testing.yml | 13 + .gitignore | 7 +- .yamllint.yml | 1 + LICENSE | 15 + README.md | 54 ++- development/Dockerfile | 107 ++++-- development/development.env | 3 - development/docker-compose.base.yml | 28 +- development/docker-compose.dev.yml | 10 +- development/docker-compose.mysql.yml | 8 +- development/docker-compose.postgres.yml | 4 +- development/nautobot_config.py | 101 +++-- docs/admin/compatibility_matrix.md | 7 +- docs/admin/install.md | 25 +- docs/admin/release_notes/version_1.0.md | 43 ++- docs/admin/uninstall.md | 15 +- docs/admin/upgrade.md | 7 +- docs/assets/extra.css | 9 + docs/assets/overrides/partials/copyright.html | 2 +- docs/dev/arch_decision.md | 7 + docs/dev/code_reference/api.md | 5 + docs/dev/code_reference/index.md | 3 + docs/dev/code_reference/package.md | 1 + docs/dev/contributing.md | 19 +- docs/dev/dev_environment.md | 119 +++--- docs/dev/extending.md | 39 +- docs/images/icon-nautobot-design-builder.png | Bin 0 -> 74601 bytes docs/requirements.txt | 2 +- docs/user/app_getting_started.md | 26 +- docs/user/app_overview.md | 18 +- docs/user/app_use_cases.md | 12 + docs/user/external_interactions.md | 17 + docs/user/faq.md | 4 - invoke.example.yml | 8 +- invoke.mysql.yml | 8 +- mkdocs.yml | 35 +- nautobot_design_builder/__init__.py | 33 +- nautobot_design_builder/api/__init__.py | 1 + .../migrations/__init__.py | 0 nautobot_design_builder/tests/__init__.py | 80 +--- nautobot_design_builder/tests/test_api.py | 28 ++ nautobot_design_builder/tests/test_basic.py | 34 ++ pyproject.toml | 56 +-- tasks.py | 348 +++++++++++++++--- 51 files changed, 1159 insertions(+), 569 deletions(-) create mode 100644 .github/workflows/rebake.yml create mode 100644 .github/workflows/upstream_testing.yml create mode 100644 LICENSE create mode 100644 docs/dev/arch_decision.md create mode 100644 docs/dev/code_reference/api.md create mode 100644 docs/dev/code_reference/package.md create mode 100644 docs/images/icon-nautobot-design-builder.png create mode 100644 docs/user/app_use_cases.md create mode 100644 docs/user/external_interactions.md create mode 100644 nautobot_design_builder/api/__init__.py create mode 100644 nautobot_design_builder/migrations/__init__.py create mode 100644 nautobot_design_builder/tests/test_api.py create mode 100644 nautobot_design_builder/tests/test_basic.py diff --git a/.cookiecutter.json b/.cookiecutter.json index c842e4ed..ffc1cb8e 100644 --- a/.cookiecutter.json +++ b/.cookiecutter.json @@ -1,22 +1,35 @@ { - "cookiecutter": { - "codeowner_github_usernames": "@abates @mzbroch", - "full_name": "Network to Code, LLC", - "email": "info@networktocode.com", - "github_org": "nautobot", - "plugin_name": "nautobot_design_builder", - "verbose_name": "Nautobot Design Builder", - "plugin_slug": "nautobot-design-builder", - "project_slug": "nautobot-plugin-design-builder", - "repo_url": "https://github.com/nautobot/nautobot-plugin-design-builder", - "base_url": "design-builder", - "min_nautobot_version": "1.6.0", - "max_nautobot_version": "1.9999", - "camel_name": "NautobotDesignBuilder", - "project_short_description": "A Nautobot App that uses design templates to easily create data objects in Nautobot with minimal input from a user.", - "model_class_name": "None", - "open_source_license": "Apache-2.0", - "docs_base_url": "https://docs.nautobot.com", - "docs_app_url": "https://docs.nautobot.com/projects/design-builder/en/latest" - } + "cookiecutter": { + "codeowner_github_usernames": "@abates @mzbroch", + "full_name": "Network to Code, LLC", + "email": "opensource@networktocode.com", + "github_org": "nautobot", + "plugin_name": "nautobot_design_builder", + "verbose_name": "Nautobot Design Builder", + "plugin_slug": "nautobot-design-builder", + "project_slug": "nautobot-app-design-builder", + "repo_url": "https://github.com/nautobot/nautobot-app-design-builder", + "base_url": "design-builder", + "min_nautobot_version": "1.6.8", + "max_nautobot_version": "2.9999", + "camel_name": "NautobotDesignBuilder", + "project_short_description": "Nautobot app that uses design templates to easily create data objects in Nautobot with minimal input from a user.", + "model_class_name": "None", + "open_source_license": "Apache-2.0", + "docs_base_url": "https://docs.nautobot.com", + "docs_app_url": "https://docs.nautobot.com/projects/nautobot-design-builder/en/latest", + "_drift_manager": { + "template": "https://github.com/nautobot/cookiecutter-nautobot-app.git", + "template_dir": "nautobot-app", + "template_ref": "nautobot-app-v1.2", + "cookie_dir": "", + "branch_prefix": "drift-manager", + "pull_request_strategy": "create", + "post_actions": [ + "black" + ], + "draft": false, + "baked_commit_ref": "b205e8e892aa9fa8dd665963cfdc2f30410d8695" + } + } } diff --git a/.flake8 b/.flake8 index 888023fd..c9f5e84d 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,7 @@ [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 +ignore = + 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 exclude = migrations, __pycache__, diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 0c10ac35..70e517a8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,12 +1,12 @@ --- name: 🐛 Bug Report -about: Report a reproducible bug in the current release of design-builder +about: Report a reproducible bug in the current release of nautobot-design-builder --- ### Environment -* Python version: -* Nautobot version: -* design-builder version: +* Python version: +* Nautobot version: +* nautobot-design-builder version: ### Expected Behavior diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 28b626df..2501eed7 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -5,8 +5,8 @@ about: Propose a new feature or enhancement --- ### Environment -* Nautobot version: -* design-builder version: +* Nautobot version: +* nautobot-design-builder version: -## Change Notes +# Closes: # -## Justification +## What's Changed + + + +## To Do + + +- [ ] Explanation of Change(s) +- [ ] Added change log fragment(s) (for more information see [the documentation](https://docs.nautobot.com/projects/core/en/stable/development/#creating-changelog-fragments)) +- [ ] Attached Screenshots, Payload Example +- [ ] Unit, Integration Tests +- [ ] Documentation Updates (when adding/changing features) +- [ ] Example Plugin Updates (when adding/changing features) +- [ ] Outline Remaining Work, Constraints from Design diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3db1abc9..a163c061 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ on: # yamllint disable-line rule:truthy rule:comments pull_request: ~ env: - PLUGIN_NAME: "nautobot-plugin-design-builder" + PLUGIN_NAME: "nautobot-app-design-builder" jobs: black: @@ -22,7 +22,7 @@ jobs: INVOKE_NAUTOBOT_DESIGN_BUILDER_LOCAL: "True" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Linting: black" @@ -33,7 +33,7 @@ jobs: INVOKE_NAUTOBOT_DESIGN_BUILDER_LOCAL: "True" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Linting: bandit" @@ -44,12 +44,12 @@ jobs: INVOKE_NAUTOBOT_DESIGN_BUILDER_LOCAL: "True" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Linting: pydocstyle" run: "poetry run invoke pydocstyle" - check-docs-build: + flake8: runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_DESIGN_BUILDER_LOCAL: "True" @@ -58,26 +58,26 @@ jobs: uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v4" - - name: "Check Docs Build" - run: "poetry run invoke build-and-check-docs" - flake8: + - name: "Linting: flake8" + run: "poetry run invoke flake8" + poetry: runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_DESIGN_BUILDER_LOCAL: "True" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v4" - - name: "Linting: flake8" - run: "poetry run invoke flake8" + - name: "Checking: poetry lock file" + run: "poetry run invoke lock --check" yamllint: runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_DESIGN_BUILDER_LOCAL: "True" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Linting: yamllint" @@ -87,6 +87,7 @@ jobs: - "bandit" - "pydocstyle" - "flake8" + - "poetry" - "yamllint" - "black" runs-on: "ubuntu-22.04" @@ -94,44 +95,123 @@ jobs: fail-fast: true matrix: python-version: ["3.11"] - nautobot-version: ["1.6"] + nautobot-version: ["1.6.8"] env: - INVOKE_DESIGN_BUILDER_PYTHON_VER: "${{ matrix.python-version }}" - INVOKE_DESIGN_BUILDER_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" + INVOKE_NAUTOBOT_DESIGN_BUILDER_PYTHON_VER: "${{ matrix.python-version }}" + INVOKE_NAUTOBOT_DESIGN_BUILDER_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Set up Docker Buildx" id: "buildx" - uses: "docker/setup-buildx-action@v1" + uses: "docker/setup-buildx-action@v3" + - name: "Build" + uses: "docker/build-push-action@v5" + with: + builder: "${{ steps.buildx.outputs.name }}" + context: "./" + push: false + load: true + tags: "${{ env.PLUGIN_NAME }}/nautobot:${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" + file: "./development/Dockerfile" + cache-from: "type=gha,scope=${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" + cache-to: "type=gha,scope=${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" + build-args: | + NAUTOBOT_VER=${{ matrix.nautobot-version }} + PYTHON_VER=${{ matrix.python-version }} - name: "Copy credentials" run: "cp development/creds.example.env development/creds.env" - name: "Linting: pylint" run: "poetry run invoke pylint" + check-migrations: + needs: + - "bandit" + - "pydocstyle" + - "flake8" + - "poetry" + - "yamllint" + - "black" + runs-on: "ubuntu-22.04" + strategy: + fail-fast: true + matrix: + python-version: ["3.11"] + nautobot-version: ["1.6.8"] + env: + INVOKE_NAUTOBOT_DESIGN_BUILDER_PYTHON_VER: "${{ matrix.python-version }}" + INVOKE_NAUTOBOT_DESIGN_BUILDER_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" + steps: + - name: "Check out repository code" + uses: "actions/checkout@v4" + - name: "Setup environment" + uses: "networktocode/gh-action-setup-poetry-environment@v4" + - name: "Set up Docker Buildx" + id: "buildx" + uses: "docker/setup-buildx-action@v3" + - name: "Build" + uses: "docker/build-push-action@v5" + with: + builder: "${{ steps.buildx.outputs.name }}" + context: "./" + push: false + load: true + tags: "${{ env.PLUGIN_NAME }}/nautobot:${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" + file: "./development/Dockerfile" + cache-from: "type=gha,scope=${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" + cache-to: "type=gha,scope=${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" + build-args: | + NAUTOBOT_VER=${{ matrix.nautobot-version }} + PYTHON_VER=${{ matrix.python-version }} + - name: "Copy credentials" + run: "cp development/creds.example.env development/creds.env" + - name: "Checking: migrations" + run: "poetry run invoke check-migrations" unittest: needs: - "pylint" + - "check-migrations" strategy: fail-fast: true matrix: + python-version: ["3.8", "3.11"] python-version: ["3.8", "3.11"] db-backend: ["postgresql"] nautobot-version: ["1.6", "stable"] include: + - python-version: "3.11" + db-backend: "postgresql" + nautobot-version: "1.6.8" - python-version: "3.11" db-backend: "mysql" - nautobot-version: "1.6" + nautobot-version: "stable" runs-on: "ubuntu-22.04" env: - INVOKE_DESIGN_BUILDER_PYTHON_VER: "${{ matrix.python-version }}" - INVOKE_DESIGN_BUILDER_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" + INVOKE_NAUTOBOT_DESIGN_BUILDER_PYTHON_VER: "${{ matrix.python-version }}" + INVOKE_NAUTOBOT_DESIGN_BUILDER_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v4" + - name: "Set up Docker Buildx" + id: "buildx" + uses: "docker/setup-buildx-action@v3" + - name: "Build" + uses: "docker/build-push-action@v5" + with: + builder: "${{ steps.buildx.outputs.name }}" + context: "./" + push: false + load: true + tags: "${{ env.PLUGIN_NAME }}/nautobot:${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" + file: "./development/Dockerfile" + cache-from: "type=gha,scope=${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" + cache-to: "type=gha,scope=${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" + build-args: | + NAUTOBOT_VER=${{ matrix.nautobot-version }} + PYTHON_VER=${{ matrix.python-version }} - name: "Copy credentials" run: "cp development/creds.example.env development/creds.env" - name: "Use Mysql invoke settings when needed" @@ -145,8 +225,6 @@ jobs: name: "Publish to GitHub" runs-on: "ubuntu-22.04" if: "startsWith(github.ref, 'refs/tags/v')" - env: - INVOKE_NAUTOBOT_DESIGN_BUILDER_LOCAL: "True" steps: - name: "Check out repository code" uses: "actions/checkout@v4" @@ -160,16 +238,12 @@ jobs: run: "echo RELEASE_VERSION=${GITHUB_REF:10} >> $GITHUB_ENV" - name: "Run Poetry Version" run: "poetry version $RELEASE_VERSION" - - name: "Install Dependencies (needed for mkdocs)" - run: "poetry install --no-root" - - name: "Build Documentation" - run: "poetry run invoke build-and-check-docs" - name: "Run Poetry Build" run: "poetry build" - name: "Upload binaries to release" uses: "svenstaro/upload-release-action@v2" with: - repo_token: "${{ secrets.GH_NAUTOBOT_BOT_TOKEN }}" + repo_token: "${{ secrets.NTC_GITHUB_TOKEN }}" # use GH_NAUTOBOT_BOT_TOKEN for Nautobot Org repos. file: "dist/*" tag: "${{ github.ref }}" overwrite: true @@ -180,8 +254,6 @@ jobs: name: "Push Package to PyPI" runs-on: "ubuntu-22.04" if: "startsWith(github.ref, 'refs/tags/v')" - env: - INVOKE_NAUTOBOT_DESIGN_BUILDER_LOCAL: "True" steps: - name: "Check out repository code" uses: "actions/checkout@v4" @@ -195,10 +267,6 @@ jobs: run: "echo RELEASE_VERSION=${GITHUB_REF:10} >> $GITHUB_ENV" - name: "Run Poetry Version" run: "poetry version $RELEASE_VERSION" - - name: "Install Dependencies (needed for mkdocs)" - run: "poetry install --no-root" - - name: "Build Documentation" - run: "poetry run invoke build-and-check-docs" - name: "Run Poetry Build" run: "poetry build" - name: "Push to PyPI" @@ -223,7 +291,7 @@ jobs: # ENVs cannot be used directly in job.if. This is a workaround to check # if SLACK_WEBHOOK_URL is present. if: "env.SLACK_WEBHOOK_URL != ''" - uses: "slackapi/slack-github-action@v1.17.0" + uses: "slackapi/slack-github-action@v1" with: payload: | { diff --git a/.github/workflows/rebake.yml b/.github/workflows/rebake.yml new file mode 100644 index 00000000..13d1e3a0 --- /dev/null +++ b/.github/workflows/rebake.yml @@ -0,0 +1,118 @@ +--- +name: "Rebake Cookie" +on: # yamllint disable-line rule:truthy + workflow_call: + inputs: + cookie: + description: "The cookie to rebake" + type: "string" + default: "" + draft: + description: "Whether to create the pull request as a draft" + type: "string" + default: "" + pull-request: + description: "The pull request strategy" + type: "string" + default: "" + template: + description: "The template repository URL" + type: "string" + default: "" + template-dir: + description: "The directory within the template repository to use as the template" + type: "string" + default: "" + template-ref: + description: "The branch or tag to use for the template" + type: "string" + default: "" + drift-manager-tag: + description: "The drift manager Docker image tag to use" + type: "string" + default: "latest" + workflow_dispatch: + inputs: + cookie: + description: "The cookie to rebake" + type: "string" + default: "" + draft: + description: "Whether to create the pull request as a draft" + type: "string" + default: "" + pull-request: + description: "The pull request strategy" + type: "string" + default: "" + template: + description: "The template repository URL" + type: "string" + default: "" + template-dir: + description: "The directory within the template repository to use as the template" + type: "string" + default: "" + template-ref: + description: "The branch or tag to use for the template" + type: "string" + default: "" + drift-manager-tag: + description: "The drift manager Docker image tag to use" + type: "string" + default: "latest" +jobs: + rebake: + runs-on: "ubuntu-22.04" + permissions: + actions: "write" + contents: "write" + packages: "read" + pull-requests: "write" + container: "ghcr.io/nautobot/cookiecutter-nautobot-app-drift-manager/prod:${{ github.event.inputs.drift-manager-tag }}" + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + steps: + - name: "Configure Rebake Arguments" + id: "config" + shell: "bash" + run: | + ARGS='--push' + + if [[ '${{ github.event.inputs.draft }}' == 'true' ]]; then + ARGS="$ARGS --draft" + elif [[ '${{ github.event.inputs.draft }}' == 'false' ]]; then + ARGS="$ARGS --no-draft" + elif [[ '${{ github.event.inputs.draft }}' == '' ]]; then + echo "Using repo default value for --draft" + else + echo "ERROR: Invalid value for draft: '${{ github.event.inputs.draft }}'" + exit 1 + fi + + if [[ '${{ github.event.inputs.pull-request }}' != '' ]]; then + ARGS="$ARGS --pull-request='${{ github.event.inputs.pull-request }}'" + fi + + if [[ '${{ github.event.inputs.template }}' != '' ]]; then + ARGS="$ARGS --template='${{ github.event.inputs.template }}'" + fi + + if [[ '${{ github.event.inputs.template-dir }}' != '' ]]; then + ARGS="$ARGS --template-dir='${{ github.event.inputs.template-dir }}'" + fi + + if [[ '${{ github.event.inputs.template-ref }}' != '' ]]; then + ARGS="$ARGS --template-ref='${{ github.event.inputs.template-ref }}'" + fi + + if [[ '${{ github.event.inputs.cookie }}' == '' ]]; then + ARGS="$ARGS '${{ github.repositoryUrl }}'" + else + ARGS="$ARGS '${{ github.event.inputs.cookie }}'" + fi + + echo "args=$ARGS" >> $GITHUB_OUTPUT + - name: "Rebake" + run: | + python -m ntc_cookie_drift_manager rebake ${{ steps.config.outputs.args }} diff --git a/.github/workflows/upstream_testing.yml b/.github/workflows/upstream_testing.yml new file mode 100644 index 00000000..fc1361ed --- /dev/null +++ b/.github/workflows/upstream_testing.yml @@ -0,0 +1,13 @@ +--- +name: "Nautobot Upstream Monitor" + +on: # yamllint disable-line rule:truthy rule:comments + schedule: + - cron: "0 4 */2 * *" # every other day at midnight + +jobs: + upstream-test: + uses: "nautobot/nautobot/.github/workflows/plugin_upstream_testing_base.yml@develop" + with: # Below could potentially be collapsed into a single argument if a concrete relationship between both is enforced + invoke_context_name: "NAUTOBOT_DESIGN_BUILDER" + plugin_name: "nautobot-app-design-builder" diff --git a/.gitignore b/.gitignore index 4a639353..fa6224d1 100644 --- a/.gitignore +++ b/.gitignore @@ -304,6 +304,7 @@ development/*.txt invoke.yml # Docs -docs/README.md -docs/CHANGELOG.md -public \ No newline at end of file +public +/compose.yaml +/dump.sql +/nautobot_design_builder/static/nautobot_design_builder/docs diff --git a/.yamllint.yml b/.yamllint.yml index b49e490c..8cc3e9a9 100644 --- a/.yamllint.yml +++ b/.yamllint.yml @@ -10,3 +10,4 @@ rules: quote-type: "double" ignore: | .venv/ + compose.yaml diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..bf295f49 --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +Apache Software License 2.0 + +Copyright (c) 2024, Network to Code, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md index ae2a3aeb..f0f50847 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,66 @@ -# Design Builder +# Nautobot Design Builder + +

- +
- - - + + +
An App for Nautobot.

## Overview -Design Builder is a Nautobot application for easily populating data within Nautobot using standardized design files. These design files are just Jinja templates that describe the Nautobot objects to be created or updated. +> Developer Note: Add a long (2-3 paragraphs) description of what the App does, what problems it solves, what functionality it adds to Nautobot, what external systems it works with etc. + +### Screenshots + +> Developer Note: Add any representative screenshots of the App in action. These images should also be added to the `docs/user/app_use_cases.md` section. + +> Developer Note: Place the files in the `docs/images/` folder and link them using only full URLs from GitHub, for example: `![Overview](https://raw.githubusercontent.com/nautobot/nautobot-app-design-builder/develop/docs/images/plugin-overview.png)`. This absolute static linking is required to ensure the README renders properly in GitHub, the docs site, and any other external sites like PyPI. + +More screenshots can be found in the [Using the App](https://docs.nautobot.com/projects/nautobot-design-builder/en/latest/user/app_use_cases/) page in the documentation. Here's a quick overview of some of the plugin's added functionality: + +![](https://raw.githubusercontent.com/nautobot/nautobot-app-design-builder/develop/docs/images/placeholder.png) + +## Try it out! + +> Developer Note: Only keep this section if appropriate. Update link to correct sandbox. + +This App is installed in the Nautobot Community Sandbox found over at [demo.nautobot.com](https://demo.nautobot.com/)! + +> For a full list of all the available always-on sandbox environments, head over to the main page on [networktocode.com](https://www.networktocode.com/nautobot/sandbox-environments/). ## Documentation Full documentation for this App can be found over on the [Nautobot Docs](https://docs.nautobot.com) website: -- [User Guide](user/app_overview.md) - Overview, Using the App, Getting Started. -- [Administrator Guide](admin/install.md) - How to Install, Configure, Upgrade, or Uninstall the App. -- [Developer Guide](dev/contributing.md) - Extending the App, Code Reference, Contribution Guide. -- [Release Notes / Changelog](admin/release_notes/). -- [Frequently Asked Questions](user/faq.md). +- [User Guide](https://docs.nautobot.com/projects/nautobot-design-builder/en/latest/user/app_overview/) - Overview, Using the App, Getting Started. +- [Administrator Guide](https://docs.nautobot.com/projects/nautobot-design-builder/en/latest/admin/install/) - How to Install, Configure, Upgrade, or Uninstall the App. +- [Developer Guide](https://docs.nautobot.com/projects/nautobot-design-builder/en/latest/dev/contributing/) - Extending the App, Code Reference, Contribution Guide. +- [Release Notes / Changelog](https://docs.nautobot.com/projects/nautobot-design-builder/en/latest/admin/release_notes/). +- [Frequently Asked Questions](https://docs.nautobot.com/projects/nautobot-design-builder/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/nautobot/nautobot-app-design-builder/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://docs.nautobot.com/projects/design-builder/en/latest/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. +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://docs.nautobot.com/projects/nautobot-design-builder/en/latest/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://docs.nautobot.com/projects/design-builder/en/latest/user/faq/) first. Feel free to also swing by the [Network to Code Slack](https://networktocode.slack.com/) (channel `#nautobot`), sign up [here](http://slack.networktocode.com/) if you don't have an account. +For any questions or comments, please check the [FAQ](https://docs.nautobot.com/projects/nautobot-design-builder/en/latest/user/faq/) first. Feel free to also swing by the [Network to Code Slack](https://networktocode.slack.com/) (channel `#nautobot`), sign up [here](http://slack.networktocode.com/) if you don't have an account. diff --git a/development/Dockerfile b/development/Dockerfile index 16241eba..ee399e3c 100644 --- a/development/Dockerfile +++ b/development/Dockerfile @@ -1,52 +1,81 @@ -ARG NAUTOBOT_VER="1.6" -ARG PYTHON_VER=3.8 -FROM ghcr.io/nautobot/nautobot-dev:${NAUTOBOT_VER}-py${PYTHON_VER} +# ------------------------------------------------------------------------------------- +# Nautobot App Developement Dockerfile Template +# Version: 1.1.0 +# +# Apps that need to add additional steps or packages can do in the section below. +# ------------------------------------------------------------------------------------- +# !!! USE CAUTION WHEN MODIFYING LINES BELOW -# Make the value available after the FROM directive -ARG NAUTOBOT_VER -ENV prometheus_multiproc_dir=/prom_cache +# Accepts a desired Nautobot version as build argument, default to 1.6.8 +ARG NAUTOBOT_VER="1.6.8" +# Accepts a desired Python version as build argument, default to 3.11 +ARG PYTHON_VER="3.11" + +# Retrieve published development image of Nautobot base which should include most CI dependencies +FROM ghcr.io/nautobot/nautobot-dev:${NAUTOBOT_VER}-py${PYTHON_VER} + +# Runtime argument and environment setup ARG NAUTOBOT_ROOT=/opt/nautobot -ENV NAUTOBOT_ROOT ${NAUTOBOT_ROOT} +ENV prometheus_multiproc_dir=/prom_cache +ENV NAUTOBOT_ROOT=${NAUTOBOT_ROOT} +ENV INVOKE_NAUTOBOT_DESIGN_BUILDER_LOCAL=true + +# Install Poetry manually via its installer script; +# We might be using an older version of Nautobot that includes an older version of Poetry +# and CI and local development may have a newer version of Poetry +# Since this is only used for development and we don't ship this container, pinning Poetry back is not expressly necessary +# We also don't need virtual environments in container +RUN which poetry || curl -sSL https://install.python-poetry.org | python3 - && \ + poetry config virtualenvs.create false -WORKDIR $NAUTOBOT_ROOT +# !!! USE CAUTION WHEN MODIFYING LINES ABOVE +# ------------------------------------------------------------------------------------- +# App-specifc system build/test dependencies. +# +# Example: LDAP requires `libldap2-dev` to be apt-installed before the Python package. +# ------------------------------------------------------------------------------------- +# --> Start safe to modify section -# Configure poetry -RUN poetry config virtualenvs.create false \ - && poetry config installer.parallel false +# Uncomment the lines below if you are apt-installing any package. +# RUN apt-get -y update && apt-get -y install \ +# libldap2-dev \ +# && rm -rf /var/lib/apt/lists/* +# --> Stop safe to modify section # ------------------------------------------------------------------------------------- -# Install Nautobot Plugin +# Install Nautobot App # ------------------------------------------------------------------------------------- -# The temp directory is used to prepare the Poetry files. -# We need to update files to use the Nautobot version as specified -# with the NAUTOBOT_VER argument and not the version used in the lock file. -# We will use this temp directory for the process. Later, we will copy -# these files to the /source directory to override Poetry files from -# the project. -WORKDIR /tmp/install - -# Copy in only pyproject.toml/poetry.lock to help with caching this layer if no updates to dependencies -COPY poetry.lock pyproject.toml /tmp/install/ - -# Add the requested Nautobot version to pyproject -# to install the correct version based on the NAUTOBOT_VER argument -# Otherwise Poetry will override the version in this container -# with the one in the poetry.lock -RUN poetry add nautobot=${NAUTOBOT_VER} - -# --no-root declares not to install the project package since we're wanting to -# take advantage of caching dependency installation -# and the project is copied in and installed after this step -RUN poetry install --no-interaction --no-ansi --no-root - -# Copy in the rest of the source code and install local Nautobot plugin +# !!! USE CAUTION WHEN MODIFYING LINES BELOW + +# Copy in the source code WORKDIR /source COPY . /source -# Copy updated Poetry files to override the Poetry files from the project. -# This will make sure that the correct Nautobot version is used. -RUN cp /tmp/install/* /source/ -RUN poetry install --no-interaction --no-ansi + +# Get container's installed Nautobot version as a forced constraint +# NAUTOBOT_VER may be a branch name and not a published release therefor we need to get the installed version +# so pip can use it to recognize local constraints. +RUN pip show nautobot | grep "^Version: " | sed -e 's/Version: /nautobot==/' > constraints.txt + +# Use Poetry to grab dev dependencies from the lock file +# Can be improved in Poetry 1.2 which allows `poetry install --only dev` +# +# We can't use the entire freeze as it takes forever to resolve with rigidly fixed non-direct dependencies, +# especially those that are only direct to Nautobot but the container included versions slightly mismatch +RUN poetry export -f requirements.txt --without-hashes --output poetry_freeze_base.txt +RUN poetry export -f requirements.txt --with dev --without-hashes --output poetry_freeze_all.txt +RUN sort poetry_freeze_base.txt poetry_freeze_all.txt | uniq -u > poetry_freeze_dev.txt + +# Install all local project as editable, constrained on Nautobot version, to get any additional +# direct dependencies of the app +RUN --mount=type=cache,target="/root/.cache/pip",sharing=locked \ + pip install -c constraints.txt -e .[all] + +# Install any dev dependencies frozen from Poetry +# Can be improved in Poetry 1.2 which allows `poetry install --only dev` +RUN --mount=type=cache,target="/root/.cache/pip",sharing=locked \ + pip install -c constraints.txt -r poetry_freeze_dev.txt COPY development/nautobot_config.py ${NAUTOBOT_ROOT}/nautobot_config.py +# !!! USE CAUTION WHEN MODIFYING LINES ABOVE diff --git a/development/development.env b/development/development.env index a82d7672..54f0b870 100644 --- a/development/development.env +++ b/development/development.env @@ -7,13 +7,10 @@ NAUTOBOT_BANNER_TOP="Local" NAUTOBOT_CHANGELOG_RETENTION=0 NAUTOBOT_DEBUG=True -NAUTOBOT_DJANGO_EXTENSIONS_ENABLED=True -NAUTOBOT_DJANGO_TOOLBAR_ENABLED=True NAUTOBOT_LOG_LEVEL=DEBUG NAUTOBOT_METRICS_ENABLED=True NAUTOBOT_NAPALM_TIMEOUT=5 NAUTOBOT_MAX_PAGE_SIZE=0 -NAUTOBOT_INSTALLATION_METRICS_ENABLED = False # Redis Configuration Environment Variables NAUTOBOT_REDIS_HOST=redis diff --git a/development/docker-compose.base.yml b/development/docker-compose.base.yml index 973f8276..ff38d9c6 100644 --- a/development/docker-compose.base.yml +++ b/development/docker-compose.base.yml @@ -7,7 +7,7 @@ x-nautobot-build: &nautobot-build context: "../" dockerfile: "development/Dockerfile" x-nautobot-base: &nautobot-base - image: "design-builder/nautobot:${NAUTOBOT_VER}-py${PYTHON_VER}" + image: "nautobot-design-builder/nautobot:${NAUTOBOT_VER}-py${PYTHON_VER}" env_file: - "development.env" - "creds.env" @@ -21,12 +21,9 @@ services: condition: "service_started" db: condition: "service_healthy" - healthcheck: - interval: "30s" - timeout: "10s" - start_period: "120s" - retries: 3 - <<: [*nautobot-build, *nautobot-base] + <<: + - *nautobot-base + - *nautobot-build worker: entrypoint: - "sh" @@ -39,10 +36,15 @@ services: timeout: "10s" start_period: "30s" retries: 3 - test: [ - "CMD", - "bash", - "-c", - "nautobot-server celery inspect ping --destination celery@$$HOSTNAME", - ] ## $$ because of docker-compose + test: ["CMD", "bash", "-c", "nautobot-server celery inspect ping --destination celery@$$HOSTNAME"] ## $$ because of docker-compose + <<: *nautobot-base + beat: + entrypoint: + - "sh" + - "-c" # this is to evaluate the $NAUTOBOT_LOG_LEVEL from the env + - "nautobot-server celery beat -l $$NAUTOBOT_LOG_LEVEL" ## $$ because of docker-compose + depends_on: + - "nautobot" + healthcheck: + disable: true <<: *nautobot-base diff --git a/development/docker-compose.dev.yml b/development/docker-compose.dev.yml index 45e392b3..2201007b 100644 --- a/development/docker-compose.dev.yml +++ b/development/docker-compose.dev.yml @@ -12,15 +12,15 @@ services: volumes: - "./nautobot_config.py:/opt/nautobot/nautobot_config.py" - "../:/source" - - "../examples/backbone_design/designs:/opt/nautobot/designs:cached" - - "../examples/backbone_design/jobs:/opt/nautobot/jobs:cached" + healthcheck: + test: ["CMD", "true"] # Due to layering, disable: true won't work. Instead, change the test docs: entrypoint: "mkdocs serve -v -a 0.0.0.0:8080" ports: - "8001:8080" volumes: - "../:/source" - image: "design-builder/nautobot:${NAUTOBOT_VER}-py${PYTHON_VER}" + image: "nautobot-design-builder/nautobot:${NAUTOBOT_VER}-py${PYTHON_VER}" healthcheck: disable: true tty: true @@ -32,8 +32,8 @@ services: volumes: - "./nautobot_config.py:/opt/nautobot/nautobot_config.py" - "../:/source" - - "../examples/backbone_design/designs:/opt/nautobot/designs:cached" - - "../examples/backbone_design/jobs:/opt/nautobot/jobs:cached" + healthcheck: + test: ["CMD", "true"] # Due to layering, disable: true won't work. Instead, change the test # To expose postgres or redis to the host uncomment the following # postgres: # ports: diff --git a/development/docker-compose.mysql.yml b/development/docker-compose.mysql.yml index c7fa6a1f..062ada94 100644 --- a/development/docker-compose.mysql.yml +++ b/development/docker-compose.mysql.yml @@ -20,6 +20,7 @@ services: image: "mysql:8" command: - "--default-authentication-plugin=mysql_native_password" + - "--max_connections=1000" env_file: - "development.env" - "creds.env" @@ -27,7 +28,12 @@ services: volumes: - "mysql_data:/var/lib/mysql" healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + test: + - "CMD" + - "mysqladmin" + - "ping" + - "-h" + - "localhost" timeout: "20s" retries: 10 volumes: diff --git a/development/docker-compose.postgres.yml b/development/docker-compose.postgres.yml index 55afdb70..12d1de31 100644 --- a/development/docker-compose.postgres.yml +++ b/development/docker-compose.postgres.yml @@ -7,11 +7,13 @@ services: - "NAUTOBOT_DB_ENGINE=django.db.backends.postgresql" db: image: "postgres:13-alpine" + command: + - "-c" + - "max_connections=200" env_file: - "development.env" - "creds.env" volumes: - # - "./nautobot.sql:/tmp/nautobot.sql" - "postgres_data:/var/lib/postgresql/data" healthcheck: test: "pg_isready --username=$$POSTGRES_USER --dbname=$$POSTGRES_DB" diff --git a/development/nautobot_config.py b/development/nautobot_config.py index cb3da0f0..d09d2fea 100644 --- a/development/nautobot_config.py +++ b/development/nautobot_config.py @@ -1,13 +1,24 @@ """Nautobot development configuration file.""" -# pylint: disable=invalid-envvar-default import os import sys -from nautobot.core.settings import * # noqa: F403 -from nautobot.core.settings_funcs import parse_redis_connection -from importlib import metadata -from packaging.version import Version +from nautobot.core.settings import * # noqa: F403 # pylint: disable=wildcard-import,unused-wildcard-import +from nautobot.core.settings_funcs import is_truthy, parse_redis_connection +# +# Debug +# + +DEBUG = is_truthy(os.getenv("NAUTOBOT_DEBUG", False)) +_TESTING = len(sys.argv) > 1 and sys.argv[1] == "test" + +if DEBUG and not _TESTING: + DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda _request: True} + + if "debug_toolbar" not in INSTALLED_APPS: # noqa: F405 + INSTALLED_APPS.append("debug_toolbar") # noqa: F405 + if "debug_toolbar.middleware.DebugToolbarMiddleware" not in MIDDLEWARE: # noqa: F405 + MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware") # noqa: F405 # # Misc. settings @@ -16,6 +27,9 @@ ALLOWED_HOSTS = os.getenv("NAUTOBOT_ALLOWED_HOSTS", "").split(" ") SECRET_KEY = os.getenv("NAUTOBOT_SECRET_KEY", "") +# +# Database +# nautobot_db_engine = os.getenv("NAUTOBOT_DB_ENGINE", "django.db.backends.postgresql") default_db_settings = { @@ -45,18 +59,28 @@ DATABASES["default"]["OPTIONS"] = {"charset": "utf8mb4"} # -# Debug +# Redis # -DEBUG = True +# The django-redis cache is used to establish concurrent locks using Redis. +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": parse_redis_connection(redis_database=0), + "TIMEOUT": 300, + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + }, + } +} -# Django Debug Toolbar -DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda _request: DEBUG and not TESTING} +# Redis Cacheops +CACHEOPS_REDIS = parse_redis_connection(redis_database=1) -if DEBUG and "debug_toolbar" not in INSTALLED_APPS: # noqa: F405 - INSTALLED_APPS.append("debug_toolbar") # noqa: F405 -if DEBUG and "debug_toolbar.middleware.DebugToolbarMiddleware" not in MIDDLEWARE: # noqa: F405 - MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware") # noqa: F405 +# +# Celery settings are not defined here because they can be overloaded with +# environment variables. By default they use `CACHES["default"]["LOCATION"]`. +# # # Logging @@ -64,10 +88,8 @@ LOG_LEVEL = "DEBUG" if DEBUG else "INFO" -TESTING = len(sys.argv) > 1 and sys.argv[1] == "test" - # Verbose logging during normal development operation, but quiet logging during unit test execution -if not TESTING: +if not _TESTING: LOGGING = { "version": 1, "disable_existing_loggers": False, @@ -103,44 +125,17 @@ } # -# Redis +# Apps # -# The django-redis cache is used to establish concurrent locks using Redis. The -# django-rq settings will use the same instance/database by default. -# -# This "default" server is now used by RQ_QUEUES. -# >> See: nautobot.core.settings.RQ_QUEUES -CACHES = { - "default": { - "BACKEND": "django_redis.cache.RedisCache", - "LOCATION": parse_redis_connection(redis_database=0), - "TIMEOUT": 300, - "OPTIONS": { - "CLIENT_CLASS": "django_redis.client.DefaultClient", - }, - } -} - -# RQ_QUEUES is not set here because it just uses the default that gets imported -# up top via `from nautobot.core.settings import *`. - -# Redis Cacheops -CACHEOPS_REDIS = parse_redis_connection(redis_database=1) - -# -# Celery settings are not defined here because they can be overloaded with -# environment variables. By default they use `CACHES["default"]["LOCATION"]`. -# - -# Enable installed plugins. Add the name of each plugin to the list. +# Enable installed Apps. Add the name of each App to the list. PLUGINS = ["nautobot_design_builder"] -# TODO: The following is necessary only until BGP models plugin -# is officially supported in 2.0 -nautobot_version = Version(Version(metadata.version("nautobot")).base_version) - -if nautobot_version < Version("2.0"): - PLUGINS.append("nautobot_bgp_models") - -PLUGINS_CONFIG = {"design_builder": {"context_repository": os.getenv("DESIGN_BUILDER_CONTEXT_REPO_SLUG", None)}} +# Apps configuration settings. These settings are used by various Apps that the user may have installed. +# Each key in the dictionary is the name of an installed App and its value is a dictionary of settings. +# PLUGINS_CONFIG = { +# 'nautobot_design_builder': { +# 'foo': 'bar', +# 'buzz': 'bazz' +# } +# } diff --git a/docs/admin/compatibility_matrix.md b/docs/admin/compatibility_matrix.md index cf37119b..697069a1 100644 --- a/docs/admin/compatibility_matrix.md +++ b/docs/admin/compatibility_matrix.md @@ -1,5 +1,8 @@ # Compatibility Matrix -| Design Builder Version | Nautobot First Support Version | Nautobot Last Support Version | +!!! warning "Developer Note - Remove Me!" + Explain how the release models of the plugin and of Nautobot work together, how releases are supported, how features and older releases are deprecated etc. + +| Nautobot Design Builder Version | Nautobot First Support Version | Nautobot Last Support Version | | ------------- | -------------------- | ------------- | -| 1.0.X | 1.6.0 | 2.0.X | +| 1.0.X | 1.6.8 | 1.99.99 | diff --git a/docs/admin/install.md b/docs/admin/install.md index b77212d5..4695cdd2 100644 --- a/docs/admin/install.md +++ b/docs/admin/install.md @@ -2,9 +2,12 @@ Here you will find detailed instructions on how to **install** and **configure** the App within your Nautobot environment. +!!! warning "Developer Note - Remove Me!" + Detailed instructions on installing the App. You will need to update this section based on any additional dependencies or prerequisites. + ## Prerequisites -- The plugin is compatible with Nautobot 1.6.0 and higher. +- The plugin is compatible with Nautobot 1.6.8 and higher. - Databases supported: PostgreSQL, MySQL !!! note @@ -12,12 +15,13 @@ Here you will find detailed instructions on how to **install** and **configure** ### Access Requirements -Design Builder does not necessarily require any external system access. However, if design jobs will be loaded from a git repository, then the Nautobot instances will need access to the git repo. +!!! warning "Developer Note - Remove Me!" + What external systems (if any) it needs access to in order to work. ## Install Guide !!! note - Plugins can be installed manually or using Python's `pip`. See the [nautobot documentation](https://nautobot.readthedocs.io/en/latest/plugins/#install-the-package) for more details. The pip package name for this plugin is [`nautobot-design-builder`](https://pypi.org/project/nautobot/design-builder/). + Plugins can be installed manually or using Python's `pip`. See the [nautobot documentation](https://nautobot.readthedocs.io/en/latest/plugins/#install-the-package) for more details. The pip package name for this plugin is [`nautobot-design-builder`](https://pypi.org/project/nautobot-design-builder/). The plugin is available as a Python package via PyPI and can be installed with `pip`: @@ -25,7 +29,7 @@ The plugin is available as a Python package via PyPI and can be installed with ` pip install nautobot-design-builder ``` -To ensure Design Builder is automatically re-installed during future upgrades, create a file named `local_requirements.txt` (if not already existing) in the Nautobot root directory (alongside `requirements.txt`) and list the `nautobot-design-builder` package: +To ensure Nautobot Design Builder is automatically re-installed during future upgrades, create a file named `local_requirements.txt` (if not already existing) in the Nautobot root directory (alongside `requirements.txt`) and list the `nautobot-design-builder` package: ```shell echo nautobot-design-builder >> local_requirements.txt @@ -62,3 +66,16 @@ Then restart (if necessary) the Nautobot services which may include: ```shell sudo systemctl restart nautobot nautobot-worker nautobot-scheduler ``` + +## App Configuration + +!!! warning "Developer Note - Remove Me!" + Any configuration required to get the App set up. Edit the table below as per the examples provided. + +The plugin behavior can be controlled with the following list of settings: + +| Key | Example | Default | Description | +| ------- | ------ | -------- | ------------------------------------- | +| `enable_backup` | `True` | `True` | A boolean to represent whether or not to run backup configurations within the plugin. | +| `platform_slug_map` | `{"cisco_wlc": "cisco_aireos"}` | `None` | A dictionary in which the key is the platform slug and the value is what netutils uses in any "network_os" parameter. | +| `per_feature_bar_width` | `0.15` | `0.15` | The width of the table bar within the overview report | diff --git a/docs/admin/release_notes/version_1.0.md b/docs/admin/release_notes/version_1.0.md index 9076e6ae..e2342da4 100644 --- a/docs/admin/release_notes/version_1.0.md +++ b/docs/admin/release_notes/version_1.0.md @@ -1,11 +1,48 @@ # 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 -Initial Public Release +- Major features or milestones +- Achieved in this `x.y` release +- Changes to compatibility with Nautobot and/or other plugins, libraries etc. + +## [v1.0.1] - 2021-09-08 + +### Added + +### Changed + +### Fixed + +- [#123](https://github.com/nautobot/nautobot-app-design-builder/issues/123) Fixed Tag filtering not working in job launch form + +## [v1.0.0] - 2021-08-03 + +### Added -## [v1.0.0] - 2023-11-01 +### Changed -Initial Public Release +### Fixed diff --git a/docs/admin/uninstall.md b/docs/admin/uninstall.md index 63a452ba..3481dce0 100644 --- a/docs/admin/uninstall.md +++ b/docs/admin/uninstall.md @@ -2,10 +2,17 @@ Here you will find any steps necessary to cleanly remove the App from your Nautobot environment. -## Uninstall Guide +## Database Cleanup -Remove the `DESIN_BUILDER` section that was added to `nautobot_config.py` `PLUGINS` & `PLUGINS_CONFIG`. +Prior to removing the plugin from the `nautobot_config.py`, run the following command to roll back any migration specific to this plugin. -## Database Cleanup +```shell +nautobot-server migrate nautobot_app_design_builder zero +``` + +!!! warning "Developer Note - Remove Me!" + Any other cleanup operations to ensure the database is clean after the app is removed. Is there anything else that needs cleaning up, such as CFs, relationships, etc. if they're no longer desired? + +## Remove App configuration -The current version of Design Builder does not include any database models, so no database cleanup is necessary. +Remove the configuration you added in `nautobot_config.py` from `PLUGINS` & `PLUGINS_CONFIG`. diff --git a/docs/admin/upgrade.md b/docs/admin/upgrade.md index 49614d8c..a9ba697a 100644 --- a/docs/admin/upgrade.md +++ b/docs/admin/upgrade.md @@ -4,8 +4,7 @@ Here you will find any steps necessary to upgrade the App in your Nautobot envir ## Upgrade Guide -Since Design Builder does not currently include any custom data models the only requirement for updating is to update the `nautobot-design-builder` package using the `pip` command: +!!! warning "Developer Note - Remove Me!" + Add more detailed steps on how the app is upgraded in an existing Nautobot setup and any version specifics (such as upgrading between major versions with breaking changes). -```python -pip install --upgrade nautobot-design-builder -``` +When a new release comes out it may be necessary to run a migration of the database to account for any changes in the data models used by this plugin. Execute the command `nautobot-server post-upgrade` within the runtime environment of your Nautobot installation after updating the `nautobot-design-builder` package via `pip`. diff --git a/docs/assets/extra.css b/docs/assets/extra.css index a51ccd3e..dfe2e4b1 100644 --- a/docs/assets/extra.css +++ b/docs/assets/extra.css @@ -18,6 +18,15 @@ font-size: 0.7rem; } +/* +* The default max-width is 61rem which does not provide nearly enough space to present code examples or larger tables +*/ +.md-grid { + margin-left: auto; + margin-right: auto; + max-width: 95%; +} + .md-tabs__link { font-size: 0.8rem; } diff --git a/docs/assets/overrides/partials/copyright.html b/docs/assets/overrides/partials/copyright.html index 77aa5078..b92cf5e3 100644 --- a/docs/assets/overrides/partials/copyright.html +++ b/docs/assets/overrides/partials/copyright.html @@ -4,7 +4,7 @@ - Not open source LICENSE + Apache-2.0 LICENSE {% endif %} diff --git a/docs/dev/arch_decision.md b/docs/dev/arch_decision.md new file mode 100644 index 00000000..e7bcbbe4 --- /dev/null +++ b/docs/dev/arch_decision.md @@ -0,0 +1,7 @@ +# Architecture Decision Records + +The intention is to document deviations from a standard Model View Controller (MVC) design. + +!!! warning "Developer Note - Remove Me!" + Optional page, remove if not applicable. + For examples see [Golden Config](https://github.com/nautobot/nautobot-plugin-golden-config/tree/develop/docs/dev/dev_adr.md) and [nautobot-plugin-reservation](https://github.com/networktocode/nautobot-plugin-reservation/blob/develop/docs/dev/dev_adr.md). diff --git a/docs/dev/code_reference/api.md b/docs/dev/code_reference/api.md new file mode 100644 index 00000000..462c3467 --- /dev/null +++ b/docs/dev/code_reference/api.md @@ -0,0 +1,5 @@ +# Nautobot Design Builder API Package + +::: nautobot_design_builder.api + options: + show_submodules: True diff --git a/docs/dev/code_reference/index.md b/docs/dev/code_reference/index.md index 473f2c40..ebe9ff7d 100644 --- a/docs/dev/code_reference/index.md +++ b/docs/dev/code_reference/index.md @@ -1,3 +1,6 @@ # Code Reference Auto-generated code reference documentation from docstrings. + +!!! warning "Developer Note - Remove Me!" + Uses [mkdocstrings](https://mkdocstrings.github.io/) syntax to auto-generate code documentation from docstrings. Two example pages are provided ([api](api.md) and [package](package.md)), add new stubs for each module or package that you think has relevant documentation. diff --git a/docs/dev/code_reference/package.md b/docs/dev/code_reference/package.md new file mode 100644 index 00000000..5cbc0eb0 --- /dev/null +++ b/docs/dev/code_reference/package.md @@ -0,0 +1 @@ +::: nautobot_design_builder diff --git a/docs/dev/contributing.md b/docs/dev/contributing.md index 2d239fb3..2337f740 100644 --- a/docs/dev/contributing.md +++ b/docs/dev/contributing.md @@ -1,8 +1,7 @@ # Contributing to the App -Contributions are encouraged and we are always delighted in any form of work. We are always looking for feedback both in the development of code as well as documentation, use cases, and examples. To contribute to this project, please use the following guidlines: - -## Code Development +!!! warning "Developer Note - Remove Me!" + Information on how to contribute fixes, functionality, or documentation changes back to the project. The project is packaged with a light [development environment](dev_environment.md) based on `docker-compose` to help with the local development of the project and to run tests. @@ -14,18 +13,12 @@ The project is following Network to Code software development guidelines and is Documentation is built using [mkdocs](https://www.mkdocs.org/). The [Docker based development environment](dev_environment.md#docker-development-environment) automatically starts a container hosting a live version of the documentation website on [http://localhost:8001](http://localhost:8001) that auto-refreshes when you make any changes to your local files. -## Documentation - -Code documentation follows the [Google docstring](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) style. Where possible, include a description, argument documentation and examples. - -The user and developer documentation is located in the top level `docs/` directory. The documenation is written in markdown format and is rendered using MkDocs. - -Example designs should be placed in the top level `examples/` directory, as appropriate. - ## Branching Policy -The active branch in Design Builder is the `develop` branch. However, commits are not allowed directly to this branch. Instead, fork the code and open a pull request to `develop`. +!!! warning "Developer Note - Remove Me!" + What branching policy is used for this project and where contributions should be made. ## Release Policy -There is no set release schedule for this App. New releases will be published as appropriate when new features and/or bug fixes are ready. +!!! warning "Developer Note - Remove Me!" + How new versions are released. diff --git a/docs/dev/dev_environment.md b/docs/dev/dev_environment.md index a1463c6c..30393c27 100644 --- a/docs/dev/dev_environment.md +++ b/docs/dev/dev_environment.md @@ -13,14 +13,14 @@ This is a quick reference guide if you're already familiar with the development 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: -- `nautobot_ver`: the version of Nautobot to use as a base for any built docker containers (default: latest) -- `project_name`: the default docker compose project name (default: `nautobot_design_builder`) -- `python_ver`: the version of Python to use as a base for any built docker containers (default: 3.8) +- `nautobot_ver`: the version of Nautobot to use as a base for any built docker containers (default: 1.6.8) +- `project_name`: the default docker compose project name (default: `nautobot-design-builder`) +- `python_ver`: the version of Python to use as a base for any built docker containers (default: 3.11) - `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) - `compose_dir`: the full path to a directory containing the project compose files - `compose_files`: a list of compose files applied in order (see [Multiple Compose files](https://docs.docker.com/compose/extends/#multiple-compose-files) for more information) -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_DESIGN_BUILDER_VARIABLE_NAME` where `VARIABLE_NAME` is the variable you are trying to override. The only exception is `compose_files`, because it is a list it must be overridden in a YAML file. There is an example `invoke.yml` (`invoke.example.yml`) in this directory which can be used as a starting point. +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_NAUTOBOT_DESIGN_BUILDER_VARIABLE_NAME` where `VARIABLE_NAME` is the variable you are trying to override. The only exception is `compose_files`, because it is a list it must be overridden in a YAML file. There is an example `invoke.yml` (`invoke.example.yml`) in this directory which can be used as a starting point. ### Docker Development Environment @@ -55,10 +55,8 @@ To either stop or destroy the development environment use the following options. ```yaml --- -design_builder: +nautobot_design_builder: local: true - compose_files: - - "docker-compose.requirements.yml" ``` Run the following commands: @@ -66,7 +64,7 @@ Run the following commands: ```shell poetry shell poetry install --extras nautobot -export $(cat development/dev.env | xargs) +export $(cat development/development.env | xargs) export $(cat development/creds.env | xargs) invoke start && sleep 5 nautobot-server migrate @@ -101,9 +99,6 @@ The project features a CLI helper based on [Invoke](https://www.pyinvoke.org/) t Each command can be executed with `invoke `. All commands support the arguments `--nautobot-ver` and `--python-ver` if you want to manually define the version of Python and Nautobot to use. Each command also has its own help `invoke --help` -!!! note - To run the mysql (mariadb) development environment, set the environment variable as such `export NAUTOBOT_USE_MYSQL=1`. - #### Local Development Environment ``` @@ -136,7 +131,6 @@ Each command can be executed with `invoke `. All commands support the a unittest Run Django unit tests for the plugin. ``` - ## Project Overview This project provides the ability to develop and manage the Nautobot server locally (with supporting services being *Dockerized*) or by using only Docker containers to manage Nautobot. The main difference between the two environments is the ability to debug and use **pdb** when developing locally. Debugging with **pdb** within the Docker container is more complicated, but can still be accomplished by either entering into the container (via `docker exec`) or attaching your IDE to the container and running the Nautobot service manually within the container. @@ -155,7 +149,7 @@ Poetry is used in lieu of the "virtualenv" commands and is leveraged in both env The `pyproject.toml` file outlines all of the relevant dependencies for the project: - `tool.poetry.dependencies` - the main list of dependencies. -- `tool.poetry.dev-dependencies` - development dependencies, to facilitate linting, testing, and documentation building. +- `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. @@ -185,7 +179,7 @@ The first thing you need to do is build the necessary Docker image for Nautobot #14 exporting layers #14 exporting layers 1.2s done #14 writing image sha256:2d524bc1665327faa0d34001b0a9d2ccf450612bf8feeb969312e96a2d3e3503 done -#14 naming to docker.io/design-builder/nautobot:latest-py3.7 done +#14 naming to docker.io/nautobot-design-builder/nautobot:1.6.8-py3.11 done ``` ### Invoke - Starting the Development Environment @@ -196,18 +190,18 @@ Next, you need to start up your Docker containers. ➜ invoke start Starting Nautobot in detached mode... Running docker-compose command "up --detach" -Creating network "design_builder_default" with the default driver -Creating volume "design_builder_postgres_data" with default driver -Creating design_builder_redis_1 ... -Creating design_builder_docs_1 ... -Creating design_builder_postgres_1 ... -Creating design_builder_postgres_1 ... done -Creating design_builder_redis_1 ... done -Creating design_builder_nautobot_1 ... -Creating design_builder_docs_1 ... done -Creating design_builder_nautobot_1 ... done -Creating design_builder_worker_1 ... -Creating design_builder_worker_1 ... done +Creating network "nautobot_design_builder_default" with the default driver +Creating volume "nautobot_design_builder_postgres_data" with default driver +Creating nautobot_design_builder_redis_1 ... +Creating nautobot_design_builder_docs_1 ... +Creating nautobot_design_builder_postgres_1 ... +Creating nautobot_design_builder_postgres_1 ... done +Creating nautobot_design_builder_redis_1 ... done +Creating nautobot_design_builder_nautobot_1 ... +Creating nautobot_design_builder_docs_1 ... done +Creating nautobot_design_builder_nautobot_1 ... done +Creating nautobot_design_builder_worker_1 ... +Creating nautobot_design_builder_worker_1 ... done Docker Compose is now in the Docker CLI, try `docker compose up` ``` @@ -216,11 +210,11 @@ This will start all of the Docker containers used for hosting Nautobot. You shou ```bash ➜ docker ps ****CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -ee90fbfabd77 design-builder/nautobot:latest-py3.7 "nautobot-server rqw…" 16 seconds ago Up 13 seconds design_builder_worker_1 -b8adb781d013 design-builder/nautobot:latest-py3.7 "/docker-entrypoint.…" 20 seconds ago Up 15 seconds 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp design_builder_nautobot_1 -d64ebd60675d design-builder/nautobot:latest-py3.7 "mkdocs serve -v -a …" 25 seconds ago Up 18 seconds 0.0.0.0:8001->8080/tcp, :::8001->8080/tcp design_builder_docs_1 -e72d63129b36 postgres:13-alpine "docker-entrypoint.s…" 25 seconds ago Up 19 seconds 0.0.0.0:5432->5432/tcp, :::5432->5432/tcp design_builder_postgres_1 -96c6ff66997c redis:6-alpine "docker-entrypoint.s…" 25 seconds ago Up 21 seconds 0.0.0.0:6379->6379/tcp, :::6379->6379/tcp design_builder_redis_1 +ee90fbfabd77 nautobot-design-builder/nautobot:1.6.8-py3.11 "nautobot-server rqw…" 16 seconds ago Up 13 seconds nautobot_design_builder_worker_1 +b8adb781d013 nautobot-design-builder/nautobot:1.6.8-py3.11 "/docker-entrypoint.…" 20 seconds ago Up 15 seconds 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp nautobot_design_builder_nautobot_1 +d64ebd60675d nautobot-design-builder/nautobot:1.6.8-py3.11 "mkdocs serve -v -a …" 25 seconds ago Up 18 seconds 0.0.0.0:8001->8080/tcp, :::8001->8080/tcp nautobot_design_builder_docs_1 +e72d63129b36 postgres:13-alpine "docker-entrypoint.s…" 25 seconds ago Up 19 seconds 0.0.0.0:5432->5432/tcp, :::5432->5432/tcp nautobot_design_builder_postgres_1 +96c6ff66997c redis:6-alpine "docker-entrypoint.s…" 25 seconds ago Up 21 seconds 0.0.0.0:6379->6379/tcp, :::6379->6379/tcp nautobot_design_builder_redis_1 ``` Once the containers are fully up, you should be able to open up a web browser, and view: @@ -264,27 +258,27 @@ The last command to know for now is `invoke stop`. ➜ invoke stop Stopping Nautobot... Running docker-compose command "down" -Stopping design_builder_worker_1 ... -Stopping design_builder_nautobot_1 ... -Stopping design_builder_docs_1 ... -Stopping design_builder_redis_1 ... -Stopping design_builder_postgres_1 ... -Stopping design_builder_worker_1 ... done -Stopping design_builder_nautobot_1 ... done -Stopping design_builder_postgres_1 ... done -Stopping design_builder_redis_1 ... done -Stopping design_builder_docs_1 ... done -Removing design_builder_worker_1 ... -Removing design_builder_nautobot_1 ... -Removing design_builder_docs_1 ... -Removing design_builder_redis_1 ... -Removing design_builder_postgres_1 ... -Removing design_builder_postgres_1 ... done -Removing design_builder_docs_1 ... done -Removing design_builder_worker_1 ... done -Removing design_builder_redis_1 ... done -Removing design_builder_nautobot_1 ... done -Removing network design_builder_default +Stopping nautobot_design_builder_worker_1 ... +Stopping nautobot_design_builder_nautobot_1 ... +Stopping nautobot_design_builder_docs_1 ... +Stopping nautobot_design_builder_redis_1 ... +Stopping nautobot_design_builder_postgres_1 ... +Stopping nautobot_design_builder_worker_1 ... done +Stopping nautobot_design_builder_nautobot_1 ... done +Stopping nautobot_design_builder_postgres_1 ... done +Stopping nautobot_design_builder_redis_1 ... done +Stopping nautobot_design_builder_docs_1 ... done +Removing nautobot_design_builder_worker_1 ... +Removing nautobot_design_builder_nautobot_1 ... +Removing nautobot_design_builder_docs_1 ... +Removing nautobot_design_builder_redis_1 ... +Removing nautobot_design_builder_postgres_1 ... +Removing nautobot_design_builder_postgres_1 ... done +Removing nautobot_design_builder_docs_1 ... done +Removing nautobot_design_builder_worker_1 ... done +Removing nautobot_design_builder_redis_1 ... done +Removing nautobot_design_builder_nautobot_1 ... done +Removing network nautobot_design_builder_default ``` This will safely shut down all of your running Docker containers for this project. When you are ready to spin containers back up, it is as simple as running `invoke start` again [as seen previously](#invoke-starting-the-development-environment). @@ -319,7 +313,10 @@ When trying to debug an issue, one helpful thing you can look at are the logs wi !!! note The `-f` tag will keep the logs open, and output them in realtime as they are generated. -So for example, our plugin is named `design-builder`, the command would most likely be `docker logs design_builder_nautobot_1 -f`. You can find the name of all running containers via `docker ps`. +!!! info + Want to limit the log output even further? Use the `--tail <#>` command line argument in conjunction with `-f`. + +So for example, our plugin is named `nautobot-design-builder`, the command would most likely be `docker logs nautobot_design_builder_nautobot_1 -f`. You can find the name of all running containers via `docker ps`. If you want to view the logs specific to the worker container, simply use the name of that container instead. @@ -389,38 +386,38 @@ Once the containers are up and running, you should now see the new plugin instal To update the Python version, you can update it within `tasks.py`. ```python -namespace = Collection("design_builder") +namespace = Collection("nautobot_design_builder") namespace.configure( { - "design_builder": { + "nautobot_design_builder": { ... - "python_ver": "3.7", + "python_ver": "3.11", ... } } ) ``` -Or set the `INVOKE_NAUTOBOT_GOLDEN_CONFIG_PYTHON_VER` variable. +Or set the `INVOKE_NAUTOBOT_DESIGN_BUILDER_PYTHON_VER` variable. ### Updating Nautobot Version To update the Nautobot version, you can update it within `tasks.py`. ```python -namespace = Collection("design_builder") +namespace = Collection("nautobot_design_builder") namespace.configure( { - "design_builder": { + "nautobot_design_builder": { ... - "nautobot_ver": "1.0.2", + "nautobot_ver": "1.6.8", ... } } ) ``` -Or set the `INVOKE_DESIGN_BUILDER_NAUTOBOT_VER` variable. +Or set the `INVOKE_NAUTOBOT_DESIGN_BUILDER_NAUTOBOT_VER` variable. ## Other Miscellaneous Commands To Know diff --git a/docs/dev/extending.md b/docs/dev/extending.md index a9952735..49b89f46 100644 --- a/docs/dev/extending.md +++ b/docs/dev/extending.md @@ -1,39 +1,6 @@ # Extending the App -Design builder is primarily extended by creating new action tags. These action tags can be provided by a design repository or they can be contributed to the upstream Design Builder project for consumption by the community. Upstreaming these extensions 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. +!!! warning "Developer Note - Remove Me!" + Information on how to extend the App functionality. -## Action Tag Extensions - -The action tags in Design Builder are provided by `design.Builder`. This component reads a design and then executes instructions that are specified in the design. Basic functions, provided out of the box, are -`create`, `create_or_update` and `update`. These actions are self explanatory (for details on syntax see [this document](../user//design_development.md#special-syntax)). Two additional actions are provided, these are the `ref` and `git_context` actions. These two actions are provided as extensions to the builder. - -Extensions specify attribute and/or value actions to the object creator. Within a design template, these extensions can be used by specifying an exclamation point (!) followed by the extensions attribute or value tag. For instance, the `ref` extension implements both an attribute and a value extension. This extension can be used by specifying `!ref`. Extensions can add behavior to the object creator that is not supplied by the standard create and update actions. - -### Attribute Extensions - -Attribute extensions provide some functionality when specified as a YAMl attribute. For instance: - -```yaml -devices: - name: My New Device - "!my_attribute_extension": "some data passed to the extensions" -``` - -In this case, when the object creator encountered `!my_attribute_extension` it will look for an extension that specifies an attribute_tag `my_attribute_extension` and will call the associated `attribute` method on that extension. The `attribute` method will be given the object that is being worked on (the device "My New Device" in this case) as well as the value assigned to the attribute (the string "some data ..." in this case). Values can be any supported YAML type including strings, dictionaries and lists. It is up to the extension to determine if the provided value is valid or not. - -### Value Extensions - -Value extensions can be used to assign a value to an attribute. For instance: - -```yaml -device: - name: "!device_name" -``` - -In this case, when `!device_name` is encountered the object creator will look for an extension that implements the `device_name` value tag. If found, the corresponding `value` method will be called on the extension. Whatever `value` returns will be assigned to the attribute (`name` in this case). For a concrete example of an extension that implements both `attribute` and `value` see the [API docs](./code_reference/ext.md#design_builder.ext.ReferenceExtension) for the ReferenceExtension. - -### Writing a New Extension - -Adding functionality to `design.Builder` is as simple extending the [Extension](./code_reference/ext.md#design_builder.ext.Extension) class and supplying `attribute_tag` and/or `value_tag` class variables as well as the corresponding `attribute` and `value` instance methods. Extensions are singletons within a Builder instance. When an extension's tag is encountered an instance of the extension is created. Subsequent calls to the extension will use the instance created the first time. - -Each extension may optionally implement `commit` or `roll_back` methods. The `commit` method is called once all of a design's objects have been created and updated in the database. Conversely, `roll_back` is called if any error occurs and the database transaction is aborted. These methods provide a means for an extension to perform additional work, or cleanup, based on the outcome of a design's database actions. +Extending the application 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/images/icon-nautobot-design-builder.png b/docs/images/icon-nautobot-design-builder.png new file mode 100644 index 0000000000000000000000000000000000000000..7e00cf6ae0ee76324adab30d68d64206678a85e1 GIT binary patch literal 74601 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
@5hb{hC=~cCz;d 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 literal 0 HcmV?d00001 diff --git a/docs/requirements.txt b/docs/requirements.txt index 4a079cd3..d168c88f 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ mkdocs==1.5.2 -mkdocs-material==9.2.4 +mkdocs-material==9.1.15 mkdocs-version-annotations==1.0.0 mkdocstrings-python==1.5.2 mkdocstrings==0.22.0 diff --git a/docs/user/app_getting_started.md b/docs/user/app_getting_started.md index 8dc87972..24ea0099 100644 --- a/docs/user/app_getting_started.md +++ b/docs/user/app_getting_started.md @@ -8,28 +8,12 @@ To install the App, please follow the instructions detailed in the [Installation ## First steps with the App -The easiest way to experience Design Builder is to run it in a local environment. To start a local environment, clone the design builder git repository and start the application stack. The only requirements for starting a local environment are `docker`, `docker-compose` and [invoke](https://www.pyinvoke.org/installing.html). Once the dependent tools have been installed you'll need to build the docker image by running `invoke build`. At that point, simply run the command `invoke start`. This will start the entire application stack using docker compose. Once the application stack is up and running, navigate to and login. +!!! warning "Developer Note - Remove Me!" + What (with screenshots preferably) does it look like to perform the simplest workflow within the App once installed? ## What are the next steps? -The Design Builder application ships with some sample designs to demonstrate capabilities. Once the application stack is ready, you should have two designs listed under the "Jobs" -> "Jobs" menu item. +!!! warning "Developer Note - Remove Me!" + After taking the first steps, what else could the users look at doing. -![Jobs list](../images/screenshots/sample-design-jobs-list.png) - -Note that both jobs are disabled. Nautobot automatically marks jobs as disabled when they are first loaded. In order to run these jobs, click the edit button ![edit button](../images/screenshots/edit-button.png) and check the "enabled" checkbox: - -![enabled checkbox](../images/screenshots/job-enabled-checkbox.png) - -Once you click `save`, the jobs should be runnable. - -To implement any design, click the run button [run button](../images/screenshots/run-button.png). For example, run the "Initial Data" job, which will add a manufacturer, a device type, a device role, several regions and several sites. Additionally, each site will have two devices. Here is the design template for this design: - -```jinja ---8<-- "examples/backbone_design/designs/core_site/designs/0001_design.yaml.j2" -``` - -If you run the job you should see output in the job result that shows the various objects being created: - -![design job result](../images/screenshots/design-job-result.png) - -Once the initial data job has been run, try enabling and running the "Backbone Site Design" job to create a new site with racks and routers. +You can check out the [Use Cases](app_use_cases.md) section for more examples. diff --git a/docs/user/app_overview.md b/docs/user/app_overview.md index d7f79768..06ff5d32 100644 --- a/docs/user/app_overview.md +++ b/docs/user/app_overview.md @@ -7,18 +7,24 @@ This document provides an overview of the App including critical information and ## Description -Design Builder provides a system where standardized network designs can be developed to produce collections of objects within Nautobot. These designs are text based templates that can create and update hierarchical data structures within Nautobot. ## Audience (User Personas) - Who should use this App? -- Network engineers who want to have reproducible sets of Nautobot objects based on some standard design. -- Automation engineers who want to be able to automate the creation of Nautobot objects based on a set of standard designs. +!!! warning "Developer Note - Remove Me!" + Who is this meant for/ who is the common user of this app? ## Authors and Maintainers -- Andrew Bates (@abates) -- Mzb (@mzbroch) +!!! warning "Developer Note - Remove Me!" + Add the team and/or the main individuals maintaining this project. Include historical maintainers as well. ## Nautobot Features Used -This application interacts directly with Nautobot's Object Relational Mapping (ORM) system. +!!! warning "Developer Note - Remove Me!" + What is shown today in the Installed Plugins page in Nautobot. What parts of Nautobot does it interact with, what does it add etc. ? + +### Extras + +!!! warning "Developer Note - Remove Me!" + Custom Fields - things like which CFs are created by this app? + Jobs - are jobs, if so, which ones, installed by this app? diff --git a/docs/user/app_use_cases.md b/docs/user/app_use_cases.md new file mode 100644 index 00000000..dc06944f --- /dev/null +++ b/docs/user/app_use_cases.md @@ -0,0 +1,12 @@ +# Using the App + +This document describes common use-cases and scenarios for this App. + +## General Usage + +## Use-cases and common workflows + +## Screenshots + +!!! warning "Developer Note - Remove Me!" + Ideally captures every view exposed by the App. Should include a relevant dataset. diff --git a/docs/user/external_interactions.md b/docs/user/external_interactions.md new file mode 100644 index 00000000..eaba5b56 --- /dev/null +++ b/docs/user/external_interactions.md @@ -0,0 +1,17 @@ +# External Interactions + +This document describes external dependencies and prerequisites for this App to operate, including system requirements, API endpoints, interconnection or integrations to other applications or services, and similar topics. + +!!! warning "Developer Note - Remove Me!" + Optional page, remove if not applicable. + +## External System Integrations + +### From the App to Other Systems + +### From Other Systems to the App + +## Nautobot REST API endpoints + +!!! warning "Developer Note - Remove Me!" + API documentation in this doc - including python request examples, curl examples, postman collections referred etc. diff --git a/docs/user/faq.md b/docs/user/faq.md index 346f565b..318b08dc 100644 --- a/docs/user/faq.md +++ b/docs/user/faq.md @@ -1,5 +1 @@ # Frequently Asked Questions - -## When importing designs from git using the Nautobot Git Repositories feature, what should I select for the `Provides` field? - -Design builder design's are an extension of the existing Nautobot Job's functionality. Therefore, any repository containing design jobs should select the `jobs` option in the `Provides` field. diff --git a/invoke.example.yml b/invoke.example.yml index ff6e7ff6..9a4fa928 100644 --- a/invoke.example.yml +++ b/invoke.example.yml @@ -1,9 +1,9 @@ --- -design_builder: - project_name: "design-builder" - nautobot_ver: "latest" +nautobot_design_builder: + project_name: "nautobot-design-builder" + nautobot_ver: "1.6.8" local: false - python_ver: "3.8" + python_ver: "3.11" compose_dir: "development" compose_files: - "docker-compose.base.yml" diff --git a/invoke.mysql.yml b/invoke.mysql.yml index b66d6eac..62ffa9f2 100644 --- a/invoke.mysql.yml +++ b/invoke.mysql.yml @@ -1,9 +1,9 @@ --- -design_builder: - project_name: "design-builder" - nautobot_ver: "latest" +nautobot_design_builder: + project_name: "nautobot-design-builder" + nautobot_ver: "1.6.8" local: false - python_ver: "3.8" + python_ver: "3.11" compose_dir: "development" compose_files: - "docker-compose.base.yml" diff --git a/mkdocs.yml b/mkdocs.yml index 30865e5a..45425f61 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,8 +1,8 @@ --- dev_addr: "127.0.0.1:8001" -edit_uri: "edit/develop/docs" +edit_uri: "edit/main/nautobot-app-design-builder/docs" site_dir: "nautobot_design_builder/static/nautobot_design_builder/docs" -site_name: "Design Builder Documentation" +site_name: "Nautobot Design Builder Documentation" site_url: "https://docs.nautobot.com/projects/nautobot-design-builder/en/latest/" repo_url: "https://github.com/nautobot/nautobot-app-design-builder" copyright: "Copyright © The Authors" @@ -14,14 +14,17 @@ theme: - "django" - "yaml" features: - - "navigation.tracking" + - "content.action.edit" + - "content.action.view" + - "content.code.copy" + - "navigation.footer" + - "navigation.indexes" - "navigation.tabs" - "navigation.tabs.sticky" - - "search.suggest" + - "navigation.tracking" - "search.highlight" - "search.share" - - "navigation.indexes" - - "content.tooltips" + - "search.suggest" favicon: "assets/favicon.ico" logo: "assets/nautobot_logo.svg" palette: @@ -69,7 +72,6 @@ extra: link: "https://twitter.com/networktocode" name: "Network to Code Twitter" markdown_extensions: - - "abbr" - "admonition" - "toc": permalink: true @@ -78,9 +80,7 @@ markdown_extensions: - "pymdownx.highlight": anchor_linenums: true - "pymdownx.inlinehilite" - - "pymdownx.snippets": - auto_append: - - "docs/assets/abbreviations.md" + - "pymdownx.snippets" - "pymdownx.superfences" - "footnotes" plugins: @@ -90,7 +90,7 @@ plugins: default_handler: "python" handlers: python: - paths: ["nautobot_design_builder"] + paths: ["."] options: show_root_heading: true watch: @@ -101,9 +101,9 @@ nav: - User Guide: - App Overview: "user/app_overview.md" - Getting Started: "user/app_getting_started.md" - - Design Quick Start: "user/design_quickstart.md" - - Design Development: "user/design_development.md" + - Using the App: "user/app_use_cases.md" - Frequently Asked Questions: "user/faq.md" + - External Interactions: "user/external_interactions.md" - Administrator Guide: - Install and Configure: "admin/install.md" - Upgrade: "admin/upgrade.md" @@ -116,12 +116,9 @@ nav: - Extending the App: "dev/extending.md" - Contributing to the App: "dev/contributing.md" - Development Environment: "dev/dev_environment.md" + - Architecture Decision Records: "dev/arch_decision.md" - Code Reference: - "dev/code_reference/index.md" - - Design Job: "dev/code_reference/design_job.md" - - Context: "dev/code_reference/context.md" - - Design Builder: "dev/code_reference/design.md" - - Jinja Rendering: "dev/code_reference/jinja2.md" - - Template Extensions: "dev/code_reference/ext.md" - - Util: "dev/code_reference/util.md" + - Package: "dev/code_reference/package.md" + - API: "dev/code_reference/api.md" - Nautobot Docs Home ↗︎: "https://docs.nautobot.com" diff --git a/nautobot_design_builder/__init__.py b/nautobot_design_builder/__init__.py index 21450d85..79d1d6c2 100644 --- a/nautobot_design_builder/__init__.py +++ b/nautobot_design_builder/__init__.py @@ -1,39 +1,26 @@ -"""App declaration for Nautobot Design Builder.""" -from django.conf import settings -from django.utils.functional import classproperty - -from nautobot.apps import NautobotAppConfig - +"""Plugin declaration for nautobot_design_builder.""" # Metadata is inherited from Nautobot. If not including Nautobot in the environment, this should be added -try: - from importlib import metadata -except ImportError: - # Python version < 3.8 - import importlib_metadata as metadata +from importlib import metadata __version__ = metadata.version(__name__) +from nautobot.extras.plugins import NautobotAppConfig -class DesignBuilderConfig(NautobotAppConfig): - """App configuration for the nautobot_design_builder app.""" + +class NautobotDesignBuilderConfig(NautobotAppConfig): + """Plugin configuration for the nautobot_design_builder plugin.""" name = "nautobot_design_builder" - verbose_name = "Design Builder" + verbose_name = "Nautobot Design Builder" version = __version__ author = "Network to Code, LLC" - description = "Design Builder." + description = "Nautobot app that uses design templates to easily create data objects in Nautobot with minimal input from a user.." base_url = "design-builder" required_settings = [] - min_version = "1.6.0" + min_version = "1.6.8" max_version = "2.9999" default_settings = {} caching_config = {} - # pylint: disable=no-self-argument - @classproperty - def context_repository(cls): - """Retrieve the Git Repository slug that has been configured for the Design Builder.""" - return settings.PLUGINS_CONFIG[cls.name]["context_repository"] - -config = DesignBuilderConfig # pylint:disable=invalid-name +config = NautobotDesignBuilderConfig # pylint:disable=invalid-name diff --git a/nautobot_design_builder/api/__init__.py b/nautobot_design_builder/api/__init__.py new file mode 100644 index 00000000..e61c518c --- /dev/null +++ b/nautobot_design_builder/api/__init__.py @@ -0,0 +1 @@ +"""REST API module for nautobot_design_builder plugin.""" diff --git a/nautobot_design_builder/migrations/__init__.py b/nautobot_design_builder/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/nautobot_design_builder/tests/__init__.py b/nautobot_design_builder/tests/__init__.py index 0a946241..78db10c4 100644 --- a/nautobot_design_builder/tests/__init__.py +++ b/nautobot_design_builder/tests/__init__.py @@ -1,79 +1 @@ -"""Unit tests for design_builder app.""" - -import logging -import shutil -import tempfile -from os import path -from typing import Type -from unittest import mock -from unittest.mock import PropertyMock, patch - -from django.test import TestCase - -from nautobot_design_builder.design_job import DesignJob -from nautobot_design_builder.util import nautobot_version - -logging.disable(logging.CRITICAL) - - -class DesignTestCase(TestCase): - """DesignTestCase aides in creating unit tests for design jobs and templates.""" - - def setUp(self): - """Setup a mock git repo to watch for config context creation.""" - super().setUp() - self.logged_messages = [] - self.git_patcher = patch("nautobot_design_builder.ext.GitRepo") - self.git_mock = self.git_patcher.start() - - self.git_path = tempfile.mkdtemp() - git_instance_mock = PropertyMock() - git_instance_mock.return_value.path = self.git_path - self.git_mock.side_effect = git_instance_mock - - def get_mocked_job(self, design_class: Type[DesignJob]): - """Create an instance of design_class and properly mock request and job_result for testing.""" - job = design_class() - job.job_result = mock.Mock() - if nautobot_version < "2.0.0": - job.request = mock.Mock() - else: - job.job_result.data = {} - old_run = job.run - - def new_run(data, commit): - kwargs = {**data} - kwargs["dryrun"] = not commit - old_run(**kwargs) - - job.run = new_run - self.logged_messages = [] - - def record_log(message, obj, level_choice, grouping=None, logger=None): # pylint: disable=unused-argument - self.logged_messages.append( - { - "message": message, - "obj": obj, - "level_choice": level_choice, - "grouping": grouping, - } - ) - - job.job_result.log.side_effect = record_log - return job - - def assert_context_files_created(self, *filenames): - """Confirm that the list of filenames were created as part of the design implementation.""" - for filename in filenames: - self.assertTrue(path.exists(path.join(self.git_path, filename)), f"{filename} was not created") - - def assertJobSuccess(self, job): # pylint: disable=invalid-name - """Assert that a mocked job has completed successfully.""" - if job.failed: - self.fail(f"Job failed with {self.logged_messages[-1]}") - - def tearDown(self): - """Remove temporary files.""" - self.git_patcher.stop() - shutil.rmtree(self.git_path) - super().tearDown() +"""Unit tests for nautobot_design_builder plugin.""" diff --git a/nautobot_design_builder/tests/test_api.py b/nautobot_design_builder/tests/test_api.py new file mode 100644 index 00000000..ff93d198 --- /dev/null +++ b/nautobot_design_builder/tests/test_api.py @@ -0,0 +1,28 @@ +"""Unit tests for nautobot_design_builder.""" +from django.contrib.auth import get_user_model +from django.test import TestCase +from django.urls import reverse +from rest_framework import status +from rest_framework.test import APIClient + +from nautobot.users.models import Token + +User = get_user_model() + + +class PlaceholderAPITest(TestCase): + """Test the NautobotDesignBuilder API.""" + + def setUp(self): + """Create a superuser and token for API calls.""" + self.user = User.objects.create(username="testuser", is_superuser=True) + self.token = Token.objects.create(user=self.user) + self.client = APIClient() + self.client.credentials(HTTP_AUTHORIZATION=f"Token {self.token.key}") + + def test_placeholder(self): + """Verify that devices can be listed.""" + url = reverse("dcim-api:device-list") + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["count"], 0) diff --git a/nautobot_design_builder/tests/test_basic.py b/nautobot_design_builder/tests/test_basic.py new file mode 100644 index 00000000..9b52639c --- /dev/null +++ b/nautobot_design_builder/tests/test_basic.py @@ -0,0 +1,34 @@ +"""Basic tests that do not require Django.""" +import unittest +import os +import toml + +from nautobot_design_builder import __version__ as project_version + + +class TestVersion(unittest.TestCase): + """Test Version is the same.""" + + def test_version(self): + """Verify that pyproject.toml version is same as version specified in the package.""" + parent_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) + poetry_version = toml.load(os.path.join(parent_path, "pyproject.toml"))["tool"]["poetry"]["version"] + self.assertEqual(project_version, poetry_version) + + +class TestDocsPackaging(unittest.TestCase): + """Test Version in doc requirements is the same pyproject.""" + + def test_version(self): + """Verify that pyproject.toml dev dependencies have the same versions as in the docs requirements.txt.""" + parent_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) + poetry_path = os.path.join(parent_path, "pyproject.toml") + poetry_details = toml.load(poetry_path)["tool"]["poetry"]["group"]["dev"]["dependencies"] + with open(f"{parent_path}/docs/requirements.txt", "r", encoding="utf-8") as file: + requirements = [line for line in file.read().splitlines() if (len(line) > 0 and not line.startswith("#"))] + for pkg in requirements: + if len(pkg.split("==")) == 2: + pkg, version = pkg.split("==") + else: + version = "*" + self.assertEqual(poetry_details[pkg], version) diff --git a/pyproject.toml b/pyproject.toml index 659b29c3..0ddb3455 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,24 @@ [tool.poetry] name = "nautobot-design-builder" -version = "0.4.4" +version = "0.1.0" description = "Nautobot app that uses design templates to easily create data objects in Nautobot with minimal input from a user." -authors = ["Network to Code, LLC "] +authors = ["Network to Code, LLC "] +license = "Apache-2.0" readme = "README.md" homepage = "https://github.com/nautobot/nautobot-app-design-builder" repository = "https://github.com/nautobot/nautobot-app-design-builder" keywords = ["nautobot", "nautobot-plugin"] +classifiers = [ + "Intended Audience :: Developers", + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", +] include = [ + "LICENSE", "README.md", ] packages = [ @@ -19,21 +30,20 @@ python = ">=3.8,<3.12" # Used for local development nautobot = ">=1.6.0,<=2.9999" -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] bandit = "*" black = "*" coverage = "*" django-debug-toolbar = "*" -# we need to pin flake8 because of package dependencies that cause it to downgrade and -# therefore cause issues with linting since older versions do not take .flake8 as config -flake8 = "^3.9.2" +flake8 = "*" invoke = "*" ipython = "*" pydocstyle = "*" pylint = "*" pylint-django = "*" -pytest = "*" +pylint-nautobot = "*" yamllint = "*" +toml = "*" Markdown = "*" toml = "*" @@ -42,21 +52,16 @@ nautobot-bgp-models = "*" # Rendering docs to HTML mkdocs = "1.5.2" # Material for MkDocs theme -mkdocs-material = "9.2.4" +mkdocs-material = "9.1.15" # Render custom markdown for version added/changed/remove notes mkdocs-version-annotations = "1.0.0" # Automatic documentation from sources, for MkDocs mkdocstrings = "0.22.0" mkdocstrings-python = "1.5.2" - -[tool.poetry.extras] -nautobot = ["nautobot"] -# bgp_models = ["nautobot-bgp-models"] - [tool.black] line-length = 120 -target-version = ['py37'] +target-version = ['py38', 'py39', 'py310', 'py311'] include = '\.pyi?$' exclude = ''' ( @@ -79,23 +84,19 @@ exclude = ''' [tool.pylint.master] # Include the pylint_django plugin to avoid spurious warnings about Django patterns -load-plugins = "pylint_django" -ignore = ".venv" +load-plugins="pylint_django, pylint_nautobot" +ignore=".venv" [tool.pylint.basic] # No docstrings required for private methods (Pylint default), or for test_ functions, or for inner Meta classes. -no-docstring-rgx = "^(_|test_|Test|Meta$)" +no-docstring-rgx="^(_|test_|Meta$)" [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, - duplicate-code, - too-many-lines, - too-many-ancestors, - raise-missing-from, -""" + line-too-long + """ [tool.pylint.miscellaneous] # Don't flag TODO as a failure, let us commit with things that still need to be done in the code @@ -104,6 +105,11 @@ notes = """, XXX, """ +[tool.pylint-nautobot] +supported_nautobot_versions = [ + "1.6.8" +] + [tool.pydocstyle] convention = "google" inherit = false @@ -119,7 +125,3 @@ add_ignore = "D212" [build-system] requires = ["poetry_core>=1.0.0"] build-backend = "poetry.core.masonry.api" - -[tool.pytest.ini_options] -testpaths = ["tests"] -addopts = "-vv --doctest-modules" diff --git a/tasks.py b/tasks.py index 45fcc144..cdafbe3b 100644 --- a/tasks.py +++ b/tasks.py @@ -1,6 +1,6 @@ """Tasks for use with Invoke. -(c) 2020-2021 Network To Code +Copyright (c) 2023, Network to Code, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -12,10 +12,11 @@ limitations under the License. """ -from distutils.util import strtobool -from invoke import Collection, task as invoke_task import os +from invoke.collection import Collection +from invoke.tasks import task as invoke_task + def is_truthy(arg): """Convert "truthy" strings into Booleans. @@ -29,7 +30,14 @@ def is_truthy(arg): """ if isinstance(arg, bool): return arg - return bool(strtobool(arg)) + + val = str(arg).lower() + if val in ("y", "yes", "t", "true", "on", "1"): + return True + elif val in ("n", "no", "f", "false", "off", "0"): + return False + else: + raise ValueError(f"Invalid truthy value: `{arg}`") # Use pyinvoke configuration for default values, see http://docs.pyinvoke.org/en/stable/concepts/configuration.html @@ -38,9 +46,9 @@ def is_truthy(arg): namespace.configure( { "nautobot_design_builder": { - "nautobot_ver": "1.6", - "project_name": "nautobot_design_builder", - "python_ver": "3.8", + "nautobot_ver": "1.6.8", + "project_name": "nautobot-design-builder", + "python_ver": "3.11", "local": False, "compose_dir": os.path.join(os.path.dirname(__file__), "development"), "compose_files": [ @@ -55,6 +63,10 @@ def is_truthy(arg): ) +def _is_compose_included(context, name): + return f"docker-compose.{name}.yml" in context.nautobot_design_builder.compose_files + + def task(function=None, *args, **kwargs): """Task decorator to override the default Invoke task decorator and add each task to the invoke namespace.""" @@ -88,13 +100,28 @@ def docker_compose(context, command, **kwargs): "COMPOSE_HTTP_TIMEOUT": context.nautobot_design_builder.compose_http_timeout, "NAUTOBOT_VER": context.nautobot_design_builder.nautobot_ver, "PYTHON_VER": context.nautobot_design_builder.python_ver, + **kwargs.pop("env", {}), } - compose_command = f'docker compose --project-name {context.nautobot_design_builder.project_name} --project-directory "{context.nautobot_design_builder.compose_dir}"' + compose_command_tokens = [ + "docker compose", + f"--project-name {context.nautobot_design_builder.project_name}", + f'--project-directory "{context.nautobot_design_builder.compose_dir}"', + ] + for compose_file in context.nautobot_design_builder.compose_files: compose_file_path = os.path.join(context.nautobot_design_builder.compose_dir, compose_file) - compose_command += f' -f "{compose_file_path}"' - compose_command += f" {command}" + compose_command_tokens.append(f' -f "{compose_file_path}"') + + compose_command_tokens.append(command) + + # If `service` was passed as a kwarg, add it to the end. + service = kwargs.pop("service", None) + if service is not None: + compose_command_tokens.append(service) + print(f'Running docker compose command "{command}"') + compose_command = " ".join(compose_command_tokens) + return context.run(compose_command, env=build_env, **kwargs) @@ -109,9 +136,11 @@ def run_command(context, command, **kwargs): if "nautobot" in results.stdout: compose_command = f"exec nautobot {command}" else: - compose_command = f"run --entrypoint '{command}' nautobot" + compose_command = f"run --rm --entrypoint '{command}' nautobot" + + pty = kwargs.pop("pty", True) - docker_compose(context, compose_command, pty=kwargs.pop("pty", True), **kwargs) + docker_compose(context, compose_command, pty=pty, **kwargs) # ------------------------------------------------------------------------------ @@ -143,42 +172,73 @@ def generate_packages(context): 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 Nautobot container.""" + run_command(context, f"poetry {'check' if check else 'lock --no-update'}") + + # ------------------------------------------------------------------------------ # START / STOP / DEBUG # ------------------------------------------------------------------------------ -@task -def debug(context): - """Start Nautobot and its dependencies in debug mode.""" - print("Starting Nautobot in debug mode...") - docker_compose(context, "up") +@task(help={"service": "If specified, only affect this service."}) +def debug(context, service=""): + """Start specified or all services and its dependencies in debug mode.""" + print(f"Starting {service} in debug mode...") + docker_compose(context, "up", service=service) -@task -def start(context): - """Start Nautobot and its dependencies in detached mode.""" +@task(help={"service": "If specified, only affect this service."}) +def start(context, service=""): + """Start specified or all services and its dependencies in detached mode.""" print("Starting Nautobot in detached mode...") - docker_compose(context, "up --detach") + docker_compose(context, "up --detach", service=service) -@task -def restart(context): - """Gracefully restart all containers.""" +@task(help={"service": "If specified, only affect this service."}) +def restart(context, service=""): + """Gracefully restart specified or all services.""" print("Restarting Nautobot...") - docker_compose(context, "restart") + docker_compose(context, "restart", service=service) -@task -def stop(context): - """Stop Nautobot and its dependencies.""" +@task(help={"service": "If specified, only affect this service."}) +def stop(context, service=""): + """Stop specified or all services, if service is not specified, remove all containers.""" print("Stopping Nautobot...") - docker_compose(context, "down") + docker_compose(context, "stop" if service else "down --remove-orphans", service=service) @task def destroy(context): """Destroy all containers and volumes.""" print("Destroying Nautobot...") - docker_compose(context, "down --volumes") + docker_compose(context, "down --remove-orphans --volumes") + + +@task +def export(context): + """Export docker compose configuration to `compose.yaml` file. + + Useful to: + + - Debug docker compose configuration. + - Allow using `docker compose` command directly without invoke. + """ + docker_compose(context, "convert > compose.yaml") + + +@task(name="ps", help={"all": "Show all, including stopped containers"}) +def ps_task(context, all=False): + """List containers.""" + docker_compose(context, f"ps {'--all' if all else ''}") @task @@ -191,12 +251,12 @@ def vscode(context): @task( help={ - "service": "docker compose service name to view (default: nautobot)", - "follow": "Follow logs", - "tail": "Tail N number of lines or 'all'", + "service": "If specified, only display logs for this service (default: all)", + "follow": "Flag to follow logs (default: False)", + "tail": "Tail N number of lines (default: all)", } ) -def logs(context, service="nautobot", follow=False, tail=None): +def logs(context, service="", follow=False, tail=0): """View the logs of a docker compose service.""" command = "logs " @@ -205,18 +265,21 @@ def logs(context, service="nautobot", follow=False, tail=None): if tail: command += f"--tail={tail} " - command += service - docker_compose(context, command) + docker_compose(context, command, service=service) # ------------------------------------------------------------------------------ # ACTIONS # ------------------------------------------------------------------------------ -@task -def nbshell(context): +@task(help={"file": "Python file to execute"}) +def nbshell(context, file=""): """Launch an interactive nbshell session.""" - command = "nautobot-server nbshell" - run_command(context, command) + command = [ + "nautobot-server", + "nbshell", + f"< '{file}'" if file else "", + ] + run_command(context, " ".join(command), pty=not bool(file)) @task @@ -228,7 +291,7 @@ def shell_plus(context): @task def cli(context): - """Launch a bash shell inside the running Nautobot container.""" + """Launch a bash shell inside the Nautobot container.""" run_command(context, "bash") @@ -286,6 +349,165 @@ def post_upgrade(context): run_command(context, command) +@task( + help={ + "service": "Docker compose service name to run command in (default: nautobot).", + "command": "Command to run (default: bash).", + "file": "File to run command with (default: empty)", + }, +) +def exec(context, service="nautobot", command="bash", file=""): + """Launch a command inside the running container (defaults to bash shell inside nautobot container).""" + command = [ + "exec", + "--", + service, + command, + f"< '{file}'" if file else "", + ] + docker_compose(context, " ".join(command), pty=not bool(file)) + + +@task( + help={ + "db-name": "Database name (default: Nautobot database)", + "input-file": "SQL file to execute and quit (default: empty, start interactive CLI)", + "output-file": "Ouput file, overwrite if exists (default: empty, output to stdout)", + "query": "SQL command to execute and quit (default: empty)", + } +) +def dbshell(context, db_name="", input_file="", output_file="", query=""): + """Start database CLI inside the running `db` container. + + Doesn't use `nautobot-server dbshell`, using started `db` service container only. + """ + if input_file and query: + raise ValueError("Cannot specify both, `input_file` and `query` arguments") + if output_file and not (input_file or query): + raise ValueError("`output_file` argument requires `input_file` or `query` argument") + + env = {} + if query: + env["_SQL_QUERY"] = query + + command = [ + "exec", + "--env=_SQL_QUERY" if query else "", + "-- db sh -c '", + ] + + if _is_compose_included(context, "mysql"): + command += [ + "mysql", + "--user=$MYSQL_USER", + "--password=$MYSQL_PASSWORD", + f"--database={db_name or '$MYSQL_DATABASE'}", + ] + elif _is_compose_included(context, "postgres"): + command += [ + "psql", + "--username=$POSTGRES_USER", + f"--dbname={db_name or '$POSTGRES_DB'}", + ] + else: + raise ValueError("Unsupported database backend.") + + command += [ + "'", + '<<<"$_SQL_QUERY"' if query else "", + f"< '{input_file}'" if input_file else "", + f"> '{output_file}'" if output_file else "", + ] + + docker_compose(context, " ".join(command), env=env, pty=not (input_file or output_file or query)) + + +@task( + help={ + "input-file": "SQL dump file to replace the existing database with. This can be generated using `invoke backup-db` (default: `dump.sql`).", + } +) +def import_db(context, input_file="dump.sql"): + """Stop Nautobot containers and replace the current database with the dump into the running `db` container.""" + docker_compose(context, "stop -- nautobot worker") + + command = ["exec -- db sh -c '"] + + if _is_compose_included(context, "mysql"): + command += [ + "mysql", + "--database=$MYSQL_DATABASE", + "--user=$MYSQL_USER", + "--password=$MYSQL_PASSWORD", + ] + elif _is_compose_included(context, "postgres"): + command += [ + "psql", + "--username=$POSTGRES_USER", + "postgres", + ] + else: + raise ValueError("Unsupported database backend.") + + command += [ + "'", + f"< '{input_file}'", + ] + + docker_compose(context, " ".join(command), pty=False) + + print("Database import complete, you can start Nautobot now: `invoke start`") + + +@task( + help={ + "db-name": "Database name to backup (default: Nautobot database)", + "output-file": "Ouput file, overwrite if exists (default: `dump.sql`)", + "readable": "Flag to dump database data in more readable format (default: `True`)", + } +) +def backup_db(context, db_name="", output_file="dump.sql", readable=True): + """Dump database into `output_file` file from running `db` container.""" + command = ["exec -- db sh -c '"] + + if _is_compose_included(context, "mysql"): + command += [ + "mysqldump", + "--user=root", + "--password=$MYSQL_ROOT_PASSWORD", + "--add-drop-database", + "--skip-extended-insert" if readable else "", + "--databases", + db_name if db_name else "$MYSQL_DATABASE", + ] + elif _is_compose_included(context, "postgres"): + command += [ + "pg_dump", + "--clean", + "--create", + "--if-exists", + "--username=$POSTGRES_USER", + f"--dbname={db_name or '$POSTGRES_DB'}", + "--inserts" if readable else "", + ] + else: + raise ValueError("Unsupported database backend.") + + command += [ + "'", + f"> '{output_file}'", + ] + + docker_compose(context, " ".join(command), pty=False) + + print(50 * "=") + print("The database backup has been successfully completed and saved to the following file:") + print(output_file) + print("You can import this database backup with the following command:") + print(f"invoke import-db --input-file '{output_file}'") + print(50 * "=") + + # ------------------------------------------------------------------------------ # DOCS # ------------------------------------------------------------------------------ @@ -295,10 +517,10 @@ def docs(context): command = "mkdocs serve -v" if is_truthy(context.nautobot_design_builder.local): - print("Serving Documentation...") + print(">>> Serving Documentation at http://localhost:8001") run_command(context, command) else: - print("Only used when developing locally (i.e. context.nautobot_design_builder.local=True)!") + start(context, service="docs") @task @@ -378,7 +600,7 @@ def bandit(context): @task def yamllint(context): - """Run yamllint to validate formating adheres to NTC defined YAML standards. + """Run yamllint to validate formatting adheres to NTC defined YAML standards. Args: context (obj): Used to run specific commands @@ -390,7 +612,7 @@ def yamllint(context): @task def check_migrations(context): """Check for missing migrations.""" - command = "nautobot-server --config=nautobot/core/tests/nautobot_config.py makemigrations --dry-run --check" + command = "nautobot-server makemigrations --dry-run --check" run_command(context, command) @@ -401,9 +623,19 @@ def check_migrations(context): "label": "specify a directory or module to test instead of running all Nautobot tests", "failfast": "fail as soon as a single test fails don't run the entire test suite", "buffer": "Discard output from passing tests", + "pattern": "Run specific test methods, classes, or modules instead of all tests", + "verbose": "Enable verbose test output.", } ) -def unittest(context, keepdb=False, label="nautobot_design_builder", failfast=False, buffer=True): +def unittest( + context, + keepdb=False, + label="nautobot_design_builder", + failfast=False, + buffer=True, + pattern="", + verbose=False, +): """Run Nautobot unit tests.""" command = f"coverage run --module nautobot.core.cli test {label}" @@ -413,6 +645,11 @@ def unittest(context, keepdb=False, label="nautobot_design_builder", failfast=Fa command += " --failfast" if buffer: command += " --buffer" + if pattern: + command += f" -k='{pattern}'" + if verbose: + command += " --verbosity 2" + run_command(context, command) @@ -426,10 +663,12 @@ def unittest_coverage(context): @task( help={ - "failfast": "fail as soon as a single test fails don't run the entire test suite", + "failfast": "fail as soon as a single test fails don't run the entire test suite. (default: False)", + "keepdb": "Save and re-use test database between test runs for faster re-testing. (default: False)", + "lint-only": "Only run linters; unit tests will be excluded. (default: False)", } ) -def tests(context, failfast=False): +def tests(context, failfast=False, keepdb=False, lint_only=False): """Run all tests for this plugin.""" # If we are not running locally, start the docker containers so we don't have to for each test if not is_truthy(context.nautobot_design_builder.local): @@ -446,9 +685,16 @@ def tests(context, failfast=False): pydocstyle(context) print("Running yamllint...") yamllint(context) + print("Running poetry check...") + lock(context, check=True) + print("Running migrations check...") + check_migrations(context) print("Running pylint...") pylint(context) - print("Running unit tests...") - unittest(context, failfast=failfast) + print("Running mkdocs...") + build_and_check_docs(context) + if not lint_only: + print("Running unit tests...") + unittest(context, failfast=failfast, keepdb=keepdb) + unittest_coverage(context) print("All tests have passed!") - unittest_coverage(context) From 321eb817552207dcd956cf139db14544d27a6259 Mon Sep 17 00:00:00 2001 From: Jan Snasel Date: Tue, 9 Jan 2024 12:42:54 +0000 Subject: [PATCH 02/15] chore: Manual fixes --- .github/workflows/ci.yml | 2 +- README.md | 30 +------ development/development.env | 1 + development/docker-compose.dev.yml | 4 + development/nautobot_config.py | 17 ++-- docs/admin/compatibility_matrix.md | 2 +- docs/admin/install.md | 19 +---- docs/admin/release_notes/version_1.0.md | 43 +--------- docs/admin/uninstall.md | 3 - docs/admin/upgrade.md | 7 +- docs/dev/arch_decision.md | 7 -- docs/dev/code_reference/index.md | 3 - docs/dev/contributing.md | 19 +++-- docs/dev/extending.md | 39 ++++++++- docs/images/icon-design-builder.png | Bin 74601 -> 0 bytes docs/requirements.txt | 2 +- docs/user/app_getting_started.md | 26 ++++-- docs/user/app_overview.md | 18 ++-- docs/user/app_use_cases.md | 12 --- docs/user/external_interactions.md | 17 ---- docs/user/faq.md | 4 + mkdocs.yml | 11 ++- nautobot_design_builder/__init__.py | 8 ++ nautobot_design_builder/api/__init__.py | 1 - .../migrations/__init__.py | 0 nautobot_design_builder/tests/__init__.py | 78 ++++++++++++++++++ nautobot_design_builder/tests/test_api.py | 28 ------- pyproject.toml | 8 +- 28 files changed, 209 insertions(+), 200 deletions(-) delete mode 100644 docs/dev/arch_decision.md delete mode 100644 docs/images/icon-design-builder.png delete mode 100644 docs/user/app_use_cases.md delete mode 100644 docs/user/external_interactions.md delete mode 100644 nautobot_design_builder/api/__init__.py delete mode 100644 nautobot_design_builder/migrations/__init__.py delete mode 100644 nautobot_design_builder/tests/test_api.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a163c061..8bb24e72 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -243,7 +243,7 @@ jobs: - name: "Upload binaries to release" uses: "svenstaro/upload-release-action@v2" with: - repo_token: "${{ secrets.NTC_GITHUB_TOKEN }}" # use GH_NAUTOBOT_BOT_TOKEN for Nautobot Org repos. + repo_token: "${{ secrets.GH_NAUTOBOT_BOT_TOKEN }}" file: "dist/*" tag: "${{ github.ref }}" overwrite: true diff --git a/README.md b/README.md index f0f50847..e77e3149 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,5 @@ # Nautobot Design Builder - -


@@ -23,25 +13,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 App does, what problems it solves, what functionality it adds to Nautobot, what external systems it works with etc. - -### Screenshots - -> Developer Note: Add any representative screenshots of the App in action. These images should also be added to the `docs/user/app_use_cases.md` section. - -> Developer Note: Place the files in the `docs/images/` folder and link them using only full URLs from GitHub, for example: `![Overview](https://raw.githubusercontent.com/nautobot/nautobot-app-design-builder/develop/docs/images/plugin-overview.png)`. This absolute static linking is required to ensure the README renders properly in GitHub, the docs site, and any other external sites like PyPI. - -More screenshots can be found in the [Using the App](https://docs.nautobot.com/projects/nautobot-design-builder/en/latest/user/app_use_cases/) page in the documentation. Here's a quick overview of some of the plugin's added functionality: - -![](https://raw.githubusercontent.com/nautobot/nautobot-app-design-builder/develop/docs/images/placeholder.png) - -## Try it out! - -> Developer Note: Only keep this section if appropriate. Update link to correct sandbox. - -This App is installed in the Nautobot Community Sandbox found over at [demo.nautobot.com](https://demo.nautobot.com/)! - -> For a full list of all the available always-on sandbox environments, head over to the main page on [networktocode.com](https://www.networktocode.com/nautobot/sandbox-environments/). +Design Builder is a Nautobot application for easily populating data within Nautobot using standardized design files. These design files are just Jinja templates that describe the Nautobot objects to be created or updated. ## Documentation diff --git a/development/development.env b/development/development.env index 54f0b870..7613c1e2 100644 --- a/development/development.env +++ b/development/development.env @@ -11,6 +11,7 @@ NAUTOBOT_LOG_LEVEL=DEBUG NAUTOBOT_METRICS_ENABLED=True NAUTOBOT_NAPALM_TIMEOUT=5 NAUTOBOT_MAX_PAGE_SIZE=0 +NAUTOBOT_INSTALLATION_METRICS_ENABLED = False # Redis Configuration Environment Variables NAUTOBOT_REDIS_HOST=redis diff --git a/development/docker-compose.dev.yml b/development/docker-compose.dev.yml index 2201007b..28916504 100644 --- a/development/docker-compose.dev.yml +++ b/development/docker-compose.dev.yml @@ -12,6 +12,8 @@ services: volumes: - "./nautobot_config.py:/opt/nautobot/nautobot_config.py" - "../:/source" + - "../examples/backbone_design/designs:/opt/nautobot/designs:cached" + - "../examples/backbone_design/jobs:/opt/nautobot/jobs:cached" healthcheck: test: ["CMD", "true"] # Due to layering, disable: true won't work. Instead, change the test docs: @@ -32,6 +34,8 @@ services: volumes: - "./nautobot_config.py:/opt/nautobot/nautobot_config.py" - "../:/source" + - "../examples/backbone_design/designs:/opt/nautobot/designs:cached" + - "../examples/backbone_design/jobs:/opt/nautobot/jobs:cached" healthcheck: test: ["CMD", "true"] # Due to layering, disable: true won't work. Instead, change the test # To expose postgres or redis to the host uncomment the following diff --git a/development/nautobot_config.py b/development/nautobot_config.py index d09d2fea..e9959dc2 100644 --- a/development/nautobot_config.py +++ b/development/nautobot_config.py @@ -4,6 +4,8 @@ from nautobot.core.settings import * # noqa: F403 # pylint: disable=wildcard-import,unused-wildcard-import from nautobot.core.settings_funcs import is_truthy, parse_redis_connection +from importlib import metadata +from packaging.version import Version # # Debug @@ -133,9 +135,12 @@ # Apps configuration settings. These settings are used by various Apps that the user may have installed. # Each key in the dictionary is the name of an installed App and its value is a dictionary of settings. -# PLUGINS_CONFIG = { -# 'nautobot_design_builder': { -# 'foo': 'bar', -# 'buzz': 'bazz' -# } -# } + +# TODO: The following is necessary only until BGP models plugin +# is officially supported in 2.0 +nautobot_version = Version(Version(metadata.version("nautobot")).base_version) + +if nautobot_version < Version("2.0"): + PLUGINS.append("nautobot_bgp_models") + +PLUGINS_CONFIG = {"design_builder": {"context_repository": os.getenv("DESIGN_BUILDER_CONTEXT_REPO_SLUG", None)}} diff --git a/docs/admin/compatibility_matrix.md b/docs/admin/compatibility_matrix.md index 697069a1..4a032595 100644 --- a/docs/admin/compatibility_matrix.md +++ b/docs/admin/compatibility_matrix.md @@ -5,4 +5,4 @@ | Nautobot Design Builder Version | Nautobot First Support Version | Nautobot Last Support Version | | ------------- | -------------------- | ------------- | -| 1.0.X | 1.6.8 | 1.99.99 | +| 1.0.X | 1.6.8 | 2.0.X | diff --git a/docs/admin/install.md b/docs/admin/install.md index 4695cdd2..f9b94932 100644 --- a/docs/admin/install.md +++ b/docs/admin/install.md @@ -2,9 +2,6 @@ Here you will find detailed instructions on how to **install** and **configure** the App within your Nautobot environment. -!!! warning "Developer Note - Remove Me!" - Detailed instructions on installing the App. You will need to update this section based on any additional dependencies or prerequisites. - ## Prerequisites - The plugin is compatible with Nautobot 1.6.8 and higher. @@ -15,8 +12,7 @@ Here you will find detailed instructions on how to **install** and **configure** ### Access Requirements -!!! warning "Developer Note - Remove Me!" - What external systems (if any) it needs access to in order to work. +Design Builder does not necessarily require any external system access. However, if design jobs will be loaded from a git repository, then the Nautobot instances will need access to the git repo. ## Install Guide @@ -66,16 +62,3 @@ Then restart (if necessary) the Nautobot services which may include: ```shell sudo systemctl restart nautobot nautobot-worker nautobot-scheduler ``` - -## App Configuration - -!!! warning "Developer Note - Remove Me!" - Any configuration required to get the App set up. Edit the table below as per the examples provided. - -The plugin behavior can be controlled with the following list of settings: - -| Key | Example | Default | Description | -| ------- | ------ | -------- | ------------------------------------- | -| `enable_backup` | `True` | `True` | A boolean to represent whether or not to run backup configurations within the plugin. | -| `platform_slug_map` | `{"cisco_wlc": "cisco_aireos"}` | `None` | A dictionary in which the key is the platform slug and the value is what netutils uses in any "network_os" parameter. | -| `per_feature_bar_width` | `0.15` | `0.15` | The width of the table bar within the overview report | diff --git a/docs/admin/release_notes/version_1.0.md b/docs/admin/release_notes/version_1.0.md index e2342da4..9076e6ae 100644 --- a/docs/admin/release_notes/version_1.0.md +++ b/docs/admin/release_notes/version_1.0.md @@ -1,48 +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 plugins, libraries etc. - -## [v1.0.1] - 2021-09-08 - -### Added - -### Changed - -### Fixed - -- [#123](https://github.com/nautobot/nautobot-app-design-builder/issues/123) Fixed Tag filtering not working in job launch form - -## [v1.0.0] - 2021-08-03 - -### Added +Initial Public Release -### Changed +## [v1.0.0] - 2023-11-01 -### Fixed +Initial Public Release diff --git a/docs/admin/uninstall.md b/docs/admin/uninstall.md index 3481dce0..63cda675 100644 --- a/docs/admin/uninstall.md +++ b/docs/admin/uninstall.md @@ -10,9 +10,6 @@ Prior to removing the plugin from the `nautobot_config.py`, run the following co nautobot-server migrate nautobot_app_design_builder zero ``` -!!! warning "Developer Note - Remove Me!" - Any other cleanup operations to ensure the database is clean after the app is removed. Is there anything else that needs cleaning up, such as CFs, relationships, etc. if they're no longer desired? - ## Remove App configuration Remove the configuration you added in `nautobot_config.py` from `PLUGINS` & `PLUGINS_CONFIG`. diff --git a/docs/admin/upgrade.md b/docs/admin/upgrade.md index a9ba697a..73ca07d2 100644 --- a/docs/admin/upgrade.md +++ b/docs/admin/upgrade.md @@ -4,7 +4,10 @@ Here you will find any steps necessary to upgrade the App in your Nautobot envir ## Upgrade Guide -!!! warning "Developer Note - Remove Me!" - Add more detailed steps on how the app is upgraded in an existing Nautobot setup and any version specifics (such as upgrading between major versions with breaking changes). +Since Design Builder does not currently include any custom data models the only requirement for updating is to update the `nautobot-design-builder` package using the `pip` command: + +```python +pip install --upgrade nautobot-design-builder +``` When a new release comes out it may be necessary to run a migration of the database to account for any changes in the data models used by this plugin. Execute the command `nautobot-server post-upgrade` within the runtime environment of your Nautobot installation after updating the `nautobot-design-builder` package via `pip`. diff --git a/docs/dev/arch_decision.md b/docs/dev/arch_decision.md deleted file mode 100644 index e7bcbbe4..00000000 --- a/docs/dev/arch_decision.md +++ /dev/null @@ -1,7 +0,0 @@ -# Architecture Decision Records - -The intention is to document deviations from a standard Model View Controller (MVC) design. - -!!! warning "Developer Note - Remove Me!" - Optional page, remove if not applicable. - For examples see [Golden Config](https://github.com/nautobot/nautobot-plugin-golden-config/tree/develop/docs/dev/dev_adr.md) and [nautobot-plugin-reservation](https://github.com/networktocode/nautobot-plugin-reservation/blob/develop/docs/dev/dev_adr.md). diff --git a/docs/dev/code_reference/index.md b/docs/dev/code_reference/index.md index ebe9ff7d..473f2c40 100644 --- a/docs/dev/code_reference/index.md +++ b/docs/dev/code_reference/index.md @@ -1,6 +1,3 @@ # Code Reference Auto-generated code reference documentation from docstrings. - -!!! warning "Developer Note - Remove Me!" - Uses [mkdocstrings](https://mkdocstrings.github.io/) syntax to auto-generate code documentation from docstrings. Two example pages are provided ([api](api.md) and [package](package.md)), add new stubs for each module or package that you think has relevant documentation. diff --git a/docs/dev/contributing.md b/docs/dev/contributing.md index 2337f740..2d239fb3 100644 --- a/docs/dev/contributing.md +++ b/docs/dev/contributing.md @@ -1,7 +1,8 @@ # Contributing to the App -!!! warning "Developer Note - Remove Me!" - Information on how to contribute fixes, functionality, or documentation changes back to the project. +Contributions are encouraged and we are always delighted in any form of work. We are always looking for feedback both in the development of code as well as documentation, use cases, and examples. To contribute to this project, please use the following guidlines: + +## Code Development The project is packaged with a light [development environment](dev_environment.md) based on `docker-compose` to help with the local development of the project and to run tests. @@ -13,12 +14,18 @@ The project is following Network to Code software development guidelines and is Documentation is built using [mkdocs](https://www.mkdocs.org/). The [Docker based development environment](dev_environment.md#docker-development-environment) automatically starts a container hosting a live version of the documentation website on [http://localhost:8001](http://localhost:8001) that auto-refreshes when you make any changes to your local files. +## Documentation + +Code documentation follows the [Google docstring](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) style. Where possible, include a description, argument documentation and examples. + +The user and developer documentation is located in the top level `docs/` directory. The documenation is written in markdown format and is rendered using MkDocs. + +Example designs should be placed in the top level `examples/` directory, as appropriate. + ## Branching Policy -!!! warning "Developer Note - Remove Me!" - What branching policy is used for this project and where contributions should be made. +The active branch in Design Builder is the `develop` branch. However, commits are not allowed directly to this branch. Instead, fork the code and open a pull request to `develop`. ## Release Policy -!!! warning "Developer Note - Remove Me!" - How new versions are released. +There is no set release schedule for this App. New releases will be published as appropriate when new features and/or bug fixes are ready. diff --git a/docs/dev/extending.md b/docs/dev/extending.md index 49b89f46..a9952735 100644 --- a/docs/dev/extending.md +++ b/docs/dev/extending.md @@ -1,6 +1,39 @@ # Extending the App -!!! warning "Developer Note - Remove Me!" - Information on how to extend the App functionality. +Design builder is primarily extended by creating new action tags. These action tags can be provided by a design repository or they can be contributed to the upstream Design Builder project for consumption by the community. Upstreaming these extensions 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. -Extending the application 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. +## Action Tag Extensions + +The action tags in Design Builder are provided by `design.Builder`. This component reads a design and then executes instructions that are specified in the design. Basic functions, provided out of the box, are +`create`, `create_or_update` and `update`. These actions are self explanatory (for details on syntax see [this document](../user//design_development.md#special-syntax)). Two additional actions are provided, these are the `ref` and `git_context` actions. These two actions are provided as extensions to the builder. + +Extensions specify attribute and/or value actions to the object creator. Within a design template, these extensions can be used by specifying an exclamation point (!) followed by the extensions attribute or value tag. For instance, the `ref` extension implements both an attribute and a value extension. This extension can be used by specifying `!ref`. Extensions can add behavior to the object creator that is not supplied by the standard create and update actions. + +### Attribute Extensions + +Attribute extensions provide some functionality when specified as a YAMl attribute. For instance: + +```yaml +devices: + name: My New Device + "!my_attribute_extension": "some data passed to the extensions" +``` + +In this case, when the object creator encountered `!my_attribute_extension` it will look for an extension that specifies an attribute_tag `my_attribute_extension` and will call the associated `attribute` method on that extension. The `attribute` method will be given the object that is being worked on (the device "My New Device" in this case) as well as the value assigned to the attribute (the string "some data ..." in this case). Values can be any supported YAML type including strings, dictionaries and lists. It is up to the extension to determine if the provided value is valid or not. + +### Value Extensions + +Value extensions can be used to assign a value to an attribute. For instance: + +```yaml +device: + name: "!device_name" +``` + +In this case, when `!device_name` is encountered the object creator will look for an extension that implements the `device_name` value tag. If found, the corresponding `value` method will be called on the extension. Whatever `value` returns will be assigned to the attribute (`name` in this case). For a concrete example of an extension that implements both `attribute` and `value` see the [API docs](./code_reference/ext.md#design_builder.ext.ReferenceExtension) for the ReferenceExtension. + +### Writing a New Extension + +Adding functionality to `design.Builder` is as simple extending the [Extension](./code_reference/ext.md#design_builder.ext.Extension) class and supplying `attribute_tag` and/or `value_tag` class variables as well as the corresponding `attribute` and `value` instance methods. Extensions are singletons within a Builder instance. When an extension's tag is encountered an instance of the extension is created. Subsequent calls to the extension will use the instance created the first time. + +Each extension may optionally implement `commit` or `roll_back` methods. The `commit` method is called once all of a design's objects have been created and updated in the database. Conversely, `roll_back` is called if any error occurs and the database transaction is aborted. These methods provide a means for an extension to perform additional work, or cleanup, based on the outcome of a design's database actions. diff --git a/docs/images/icon-design-builder.png b/docs/images/icon-design-builder.png deleted file mode 100644 index 7e00cf6ae0ee76324adab30d68d64206678a85e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74601 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

@5hb{hC=~cCz;d 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 diff --git a/docs/requirements.txt b/docs/requirements.txt index d168c88f..4a079cd3 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ mkdocs==1.5.2 -mkdocs-material==9.1.15 +mkdocs-material==9.2.4 mkdocs-version-annotations==1.0.0 mkdocstrings-python==1.5.2 mkdocstrings==0.22.0 diff --git a/docs/user/app_getting_started.md b/docs/user/app_getting_started.md index 24ea0099..8dc87972 100644 --- a/docs/user/app_getting_started.md +++ b/docs/user/app_getting_started.md @@ -8,12 +8,28 @@ To install the App, please follow the instructions detailed in the [Installation ## First steps with the App -!!! warning "Developer Note - Remove Me!" - What (with screenshots preferably) does it look like to perform the simplest workflow within the App once installed? +The easiest way to experience Design Builder is to run it in a local environment. To start a local environment, clone the design builder git repository and start the application stack. The only requirements for starting a local environment are `docker`, `docker-compose` and [invoke](https://www.pyinvoke.org/installing.html). Once the dependent tools have been installed you'll need to build the docker image by running `invoke build`. At that point, simply run the command `invoke start`. This will start the entire application stack using docker compose. Once the application stack is up and running, navigate to and login. ## What are the next steps? -!!! warning "Developer Note - Remove Me!" - After taking the first steps, what else could the users look at doing. +The Design Builder application ships with some sample designs to demonstrate capabilities. Once the application stack is ready, you should have two designs listed under the "Jobs" -> "Jobs" menu item. -You can check out the [Use Cases](app_use_cases.md) section for more examples. +![Jobs list](../images/screenshots/sample-design-jobs-list.png) + +Note that both jobs are disabled. Nautobot automatically marks jobs as disabled when they are first loaded. In order to run these jobs, click the edit button ![edit button](../images/screenshots/edit-button.png) and check the "enabled" checkbox: + +![enabled checkbox](../images/screenshots/job-enabled-checkbox.png) + +Once you click `save`, the jobs should be runnable. + +To implement any design, click the run button [run button](../images/screenshots/run-button.png). For example, run the "Initial Data" job, which will add a manufacturer, a device type, a device role, several regions and several sites. Additionally, each site will have two devices. Here is the design template for this design: + +```jinja +--8<-- "examples/backbone_design/designs/core_site/designs/0001_design.yaml.j2" +``` + +If you run the job you should see output in the job result that shows the various objects being created: + +![design job result](../images/screenshots/design-job-result.png) + +Once the initial data job has been run, try enabling and running the "Backbone Site Design" job to create a new site with racks and routers. diff --git a/docs/user/app_overview.md b/docs/user/app_overview.md index 06ff5d32..d7f79768 100644 --- a/docs/user/app_overview.md +++ b/docs/user/app_overview.md @@ -7,24 +7,18 @@ This document provides an overview of the App including critical information and ## Description +Design Builder provides a system where standardized network designs can be developed to produce collections of objects within Nautobot. These designs are text based templates that can create and update hierarchical data structures within Nautobot. ## Audience (User Personas) - Who should use this App? -!!! warning "Developer Note - Remove Me!" - Who is this meant for/ who is the common user of this app? +- Network engineers who want to have reproducible sets of Nautobot objects based on some standard design. +- Automation engineers who want to be able to automate the creation of Nautobot objects based on a set of standard designs. ## Authors and Maintainers -!!! warning "Developer Note - Remove Me!" - Add the team and/or the main individuals maintaining this project. Include historical maintainers as well. +- Andrew Bates (@abates) +- Mzb (@mzbroch) ## Nautobot Features Used -!!! warning "Developer Note - Remove Me!" - What is shown today in the Installed Plugins page in Nautobot. What parts of Nautobot does it interact with, what does it add etc. ? - -### Extras - -!!! warning "Developer Note - Remove Me!" - Custom Fields - things like which CFs are created by this app? - Jobs - are jobs, if so, which ones, installed by this app? +This application interacts directly with Nautobot's Object Relational Mapping (ORM) system. diff --git a/docs/user/app_use_cases.md b/docs/user/app_use_cases.md deleted file mode 100644 index dc06944f..00000000 --- a/docs/user/app_use_cases.md +++ /dev/null @@ -1,12 +0,0 @@ -# Using the App - -This document describes common use-cases and scenarios for this App. - -## General Usage - -## Use-cases and common workflows - -## Screenshots - -!!! warning "Developer Note - Remove Me!" - Ideally captures every view exposed by the App. Should include a relevant dataset. diff --git a/docs/user/external_interactions.md b/docs/user/external_interactions.md deleted file mode 100644 index eaba5b56..00000000 --- a/docs/user/external_interactions.md +++ /dev/null @@ -1,17 +0,0 @@ -# External Interactions - -This document describes external dependencies and prerequisites for this App to operate, including system requirements, API endpoints, interconnection or integrations to other applications or services, and similar topics. - -!!! warning "Developer Note - Remove Me!" - Optional page, remove if not applicable. - -## External System Integrations - -### From the App to Other Systems - -### From Other Systems to the App - -## Nautobot REST API endpoints - -!!! warning "Developer Note - Remove Me!" - API documentation in this doc - including python request examples, curl examples, postman collections referred etc. diff --git a/docs/user/faq.md b/docs/user/faq.md index 318b08dc..346f565b 100644 --- a/docs/user/faq.md +++ b/docs/user/faq.md @@ -1 +1,5 @@ # Frequently Asked Questions + +## When importing designs from git using the Nautobot Git Repositories feature, what should I select for the `Provides` field? + +Design builder design's are an extension of the existing Nautobot Job's functionality. Therefore, any repository containing design jobs should select the `jobs` option in the `Provides` field. diff --git a/mkdocs.yml b/mkdocs.yml index 45425f61..92e79e9a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -101,9 +101,9 @@ nav: - User Guide: - App Overview: "user/app_overview.md" - Getting Started: "user/app_getting_started.md" - - Using the App: "user/app_use_cases.md" + - Design Quick Start: "user/design_quickstart.md" + - Design Development: "user/design_development.md" - Frequently Asked Questions: "user/faq.md" - - External Interactions: "user/external_interactions.md" - Administrator Guide: - Install and Configure: "admin/install.md" - Upgrade: "admin/upgrade.md" @@ -116,9 +116,14 @@ nav: - Extending the App: "dev/extending.md" - Contributing to the App: "dev/contributing.md" - Development Environment: "dev/dev_environment.md" - - Architecture Decision Records: "dev/arch_decision.md" - Code Reference: - "dev/code_reference/index.md" - Package: "dev/code_reference/package.md" - API: "dev/code_reference/api.md" + - Design Job: "dev/code_reference/design_job.md" + - Context: "dev/code_reference/context.md" + - Design Builder: "dev/code_reference/design.md" + - Jinja Rendering: "dev/code_reference/jinja2.md" + - Template Extensions: "dev/code_reference/ext.md" + - Util: "dev/code_reference/util.md" - Nautobot Docs Home ↗︎: "https://docs.nautobot.com" diff --git a/nautobot_design_builder/__init__.py b/nautobot_design_builder/__init__.py index 79d1d6c2..e6c1dd23 100644 --- a/nautobot_design_builder/__init__.py +++ b/nautobot_design_builder/__init__.py @@ -1,4 +1,6 @@ """Plugin declaration for nautobot_design_builder.""" +from django.conf import settings +from django.utils.functional import classproperty # Metadata is inherited from Nautobot. If not including Nautobot in the environment, this should be added from importlib import metadata @@ -22,5 +24,11 @@ class NautobotDesignBuilderConfig(NautobotAppConfig): default_settings = {} caching_config = {} + # pylint: disable=no-self-argument + @classproperty + def context_repository(cls): + """Retrieve the Git Repository slug that has been configured for the Design Builder.""" + return settings.PLUGINS_CONFIG[cls.name]["context_repository"] + config = NautobotDesignBuilderConfig # pylint:disable=invalid-name diff --git a/nautobot_design_builder/api/__init__.py b/nautobot_design_builder/api/__init__.py deleted file mode 100644 index e61c518c..00000000 --- a/nautobot_design_builder/api/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""REST API module for nautobot_design_builder plugin.""" diff --git a/nautobot_design_builder/migrations/__init__.py b/nautobot_design_builder/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/nautobot_design_builder/tests/__init__.py b/nautobot_design_builder/tests/__init__.py index 78db10c4..0b0a2d30 100644 --- a/nautobot_design_builder/tests/__init__.py +++ b/nautobot_design_builder/tests/__init__.py @@ -1 +1,79 @@ """Unit tests for nautobot_design_builder plugin.""" + +import logging +import shutil +import tempfile +from os import path +from typing import Type +from unittest import mock +from unittest.mock import PropertyMock, patch + +from django.test import TestCase + +from nautobot_design_builder.design_job import DesignJob +from nautobot_design_builder.util import nautobot_version + +logging.disable(logging.CRITICAL) + + +class DesignTestCase(TestCase): + """DesignTestCase aides in creating unit tests for design jobs and templates.""" + + def setUp(self): + """Setup a mock git repo to watch for config context creation.""" + super().setUp() + self.logged_messages = [] + self.git_patcher = patch("nautobot_design_builder.ext.GitRepo") + self.git_mock = self.git_patcher.start() + + self.git_path = tempfile.mkdtemp() + git_instance_mock = PropertyMock() + git_instance_mock.return_value.path = self.git_path + self.git_mock.side_effect = git_instance_mock + + def get_mocked_job(self, design_class: Type[DesignJob]): + """Create an instance of design_class and properly mock request and job_result for testing.""" + job = design_class() + job.job_result = mock.Mock() + if nautobot_version < "2.0.0": + job.request = mock.Mock() + else: + job.job_result.data = {} + old_run = job.run + + def new_run(data, commit): + kwargs = {**data} + kwargs["dryrun"] = not commit + old_run(**kwargs) + + job.run = new_run + self.logged_messages = [] + + def record_log(message, obj, level_choice, grouping=None, logger=None): # pylint: disable=unused-argument + self.logged_messages.append( + { + "message": message, + "obj": obj, + "level_choice": level_choice, + "grouping": grouping, + } + ) + + job.job_result.log.side_effect = record_log + return job + + def assert_context_files_created(self, *filenames): + """Confirm that the list of filenames were created as part of the design implementation.""" + for filename in filenames: + self.assertTrue(path.exists(path.join(self.git_path, filename)), f"{filename} was not created") + + def assertJobSuccess(self, job): # pylint: disable=invalid-name + """Assert that a mocked job has completed successfully.""" + if job.failed: + self.fail(f"Job failed with {self.logged_messages[-1]}") + + def tearDown(self): + """Remove temporary files.""" + self.git_patcher.stop() + shutil.rmtree(self.git_path) + super().tearDown() diff --git a/nautobot_design_builder/tests/test_api.py b/nautobot_design_builder/tests/test_api.py deleted file mode 100644 index ff93d198..00000000 --- a/nautobot_design_builder/tests/test_api.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Unit tests for nautobot_design_builder.""" -from django.contrib.auth import get_user_model -from django.test import TestCase -from django.urls import reverse -from rest_framework import status -from rest_framework.test import APIClient - -from nautobot.users.models import Token - -User = get_user_model() - - -class PlaceholderAPITest(TestCase): - """Test the NautobotDesignBuilder API.""" - - def setUp(self): - """Create a superuser and token for API calls.""" - self.user = User.objects.create(username="testuser", is_superuser=True) - self.token = Token.objects.create(user=self.user) - self.client = APIClient() - self.client.credentials(HTTP_AUTHORIZATION=f"Token {self.token.key}") - - def test_placeholder(self): - """Verify that devices can be listed.""" - url = reverse("dcim-api:device-list") - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data["count"], 0) diff --git a/pyproject.toml b/pyproject.toml index 0ddb3455..6ff8caf8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "nautobot-design-builder" -version = "0.1.0" +version = "0.4.4" description = "Nautobot app that uses design templates to easily create data objects in Nautobot with minimal input from a user." authors = ["Network to Code, LLC "] license = "Apache-2.0" @@ -52,13 +52,17 @@ nautobot-bgp-models = "*" # Rendering docs to HTML mkdocs = "1.5.2" # Material for MkDocs theme -mkdocs-material = "9.1.15" +mkdocs-material = "9.2.4" # Render custom markdown for version added/changed/remove notes mkdocs-version-annotations = "1.0.0" # Automatic documentation from sources, for MkDocs mkdocstrings = "0.22.0" mkdocstrings-python = "1.5.2" +[tool.poetry.extras] +nautobot = ["nautobot"] +# bgp_models = ["nautobot-bgp-models"] + [tool.black] line-length = 120 target-version = ['py38', 'py39', 'py310', 'py311'] From 4dd1e20af3aff71293be5e4bd1f7774ccb1ed7c4 Mon Sep 17 00:00:00 2001 From: Jan Snasel Date: Tue, 9 Jan 2024 12:43:29 +0000 Subject: [PATCH 03/15] chore: Poetry lock --- poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 2f5a6f2b..2b2bfb9c 100755 --- a/poetry.lock +++ b/poetry.lock @@ -3712,4 +3712,4 @@ nautobot = ["nautobot"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.12" -content-hash = "8ec4ecf63625568afcbfb1ff021f998969cf00efa320323c380084253a5323f3" +content-hash = "8ec4ecf63625568afcbfb1ff021f998969cf00efa320323c380084253a5323f3" \ No newline at end of file From 5ed10a1a2de592e40ccf7eb64dc5f8b69d394d5f Mon Sep 17 00:00:00 2001 From: Jan Snasel Date: Tue, 9 Jan 2024 13:02:53 +0000 Subject: [PATCH 04/15] fix: Docs --- docs/dev/code_reference/api.md | 5 ----- mkdocs.yml | 7 +++++-- 2 files changed, 5 insertions(+), 7 deletions(-) delete mode 100644 docs/dev/code_reference/api.md diff --git a/docs/dev/code_reference/api.md b/docs/dev/code_reference/api.md deleted file mode 100644 index 462c3467..00000000 --- a/docs/dev/code_reference/api.md +++ /dev/null @@ -1,5 +0,0 @@ -# Nautobot Design Builder API Package - -::: nautobot_design_builder.api - options: - show_submodules: True diff --git a/mkdocs.yml b/mkdocs.yml index 92e79e9a..67ff2df1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -72,6 +72,7 @@ extra: link: "https://twitter.com/networktocode" name: "Network to Code Twitter" markdown_extensions: + - "abbr" - "admonition" - "toc": permalink: true @@ -80,7 +81,9 @@ markdown_extensions: - "pymdownx.highlight": anchor_linenums: true - "pymdownx.inlinehilite" - - "pymdownx.snippets" + - "pymdownx.snippets": + auto_append: + - "docs/assets/abbreviations.md" - "pymdownx.superfences" - "footnotes" plugins: @@ -104,6 +107,7 @@ nav: - Design Quick Start: "user/design_quickstart.md" - Design Development: "user/design_development.md" - Frequently Asked Questions: "user/faq.md" + - Git-based Config Context: "user/git_config_context.md" - Administrator Guide: - Install and Configure: "admin/install.md" - Upgrade: "admin/upgrade.md" @@ -119,7 +123,6 @@ nav: - Code Reference: - "dev/code_reference/index.md" - Package: "dev/code_reference/package.md" - - API: "dev/code_reference/api.md" - Design Job: "dev/code_reference/design_job.md" - Context: "dev/code_reference/context.md" - Design Builder: "dev/code_reference/design.md" From f4c86de7ed8aa99dbb8a84553940a155fe98cefa Mon Sep 17 00:00:00 2001 From: Jan Snasel Date: Tue, 9 Jan 2024 13:03:21 +0000 Subject: [PATCH 05/15] fix: pylint --- nautobot_design_builder/__init__.py | 5 +++-- nautobot_design_builder/contrib/ext.py | 5 +++++ nautobot_design_builder/contrib/tests/test_ext.py | 10 ++++++++++ nautobot_design_builder/design.py | 1 + nautobot_design_builder/ext.py | 5 +++-- nautobot_design_builder/fields.py | 2 ++ nautobot_design_builder/jinja2.py | 3 +++ .../management/commands/build_design.py | 1 + nautobot_design_builder/tests/test_context.py | 8 ++++++++ nautobot_design_builder/tests/test_data_sources.py | 6 ++++++ nautobot_design_builder/tests/test_design_job.py | 4 ++++ nautobot_design_builder/tests/test_errors.py | 6 ++++++ nautobot_design_builder/tests/test_ext.py | 6 ++++++ nautobot_design_builder/util.py | 1 + 14 files changed, 59 insertions(+), 4 deletions(-) diff --git a/nautobot_design_builder/__init__.py b/nautobot_design_builder/__init__.py index e6c1dd23..0a908ca3 100644 --- a/nautobot_design_builder/__init__.py +++ b/nautobot_design_builder/__init__.py @@ -1,9 +1,10 @@ """Plugin declaration for nautobot_design_builder.""" -from django.conf import settings -from django.utils.functional import classproperty # Metadata is inherited from Nautobot. If not including Nautobot in the environment, this should be added from importlib import metadata +from django.conf import settings +from django.utils.functional import classproperty + __version__ = metadata.version(__name__) from nautobot.extras.plugins import NautobotAppConfig diff --git a/nautobot_design_builder/contrib/ext.py b/nautobot_design_builder/contrib/ext.py index 72898db9..d025d171 100644 --- a/nautobot_design_builder/contrib/ext.py +++ b/nautobot_design_builder/contrib/ext.py @@ -45,6 +45,7 @@ def lookup_by_content_type(self, app_label, model_name, query): model_class = content_type.model_class() queryset = model_class.objects except ContentType.DoesNotExist: + # pylint: disable=raise-missing-from raise DesignImplementationError(f"Could not find model class for {model_class}") return self.lookup(queryset, query) @@ -117,8 +118,10 @@ def lookup(self, queryset, query, parent=None): try: return queryset.get(**query) except ObjectDoesNotExist: + # pylint: disable=raise-missing-from raise DoesNotExistError(queryset.model, query_filter=query, parent=parent) except MultipleObjectsReturned: + # pylint: disable=raise-missing-from raise MultipleObjectsReturnedError(queryset.model, query=query, parent=parent) @@ -274,6 +277,7 @@ def attribute(self, value, model_instance) -> None: remote_instance = self.lookup(query_managers.pop(0), termination_query) except (DoesNotExistError, FieldError): if not query_managers: + # pylint:disable=raise-missing-from raise DoesNotExistError(model_instance.model_class, query_filter=termination_query) cable_attributes.update( @@ -467,6 +471,7 @@ def __init__(self, builder: Builder): self.PeerEndpoint = PeerEndpoint # pylint:disable=invalid-name self.Peering = Peering # pylint:disable=invalid-name except ModuleNotFoundError: + # pylint:disable=raise-missing-from raise DesignImplementationError( "the `bgp_peering` tag can only be used when the bgp models app is installed." ) diff --git a/nautobot_design_builder/contrib/tests/test_ext.py b/nautobot_design_builder/contrib/tests/test_ext.py index 4fab2c46..f156d2b7 100644 --- a/nautobot_design_builder/contrib/tests/test_ext.py +++ b/nautobot_design_builder/contrib/tests/test_ext.py @@ -21,6 +21,8 @@ class TestLookupExtension(TestCase): + """Test Lookup Extension.""" + def test_lookup_by_dict(self): design_template = """ manufacturers: @@ -54,6 +56,8 @@ def test_lookup_by_single_attribute(self): class TestCableConnectionExtension(TestCase): + """Test Cable Connection Extension.""" + def test_connect_cable(self): design_template_v1 = """ sites: @@ -191,6 +195,8 @@ def setUp(self) -> None: class TestNextPrefixExtension(PrefixExtensionTests): + """Test Next Prefix Extension.""" + def test_next_prefix_lookup(self): extension = NextPrefixExtension(None) want = "10.0.4.0/24" @@ -263,6 +269,8 @@ def test_lookup_by_role_and_tenant(self): class TestChildPrefixExtension(PrefixExtensionTests): + """Test Child Prefix Extension.""" + def test_creation(self): design_template = """ prefixes: @@ -290,6 +298,8 @@ def test_creation(self): class TestBGPExtension(TestCase): + """Test BGP extension.""" + def setUp(self): # TODO: Remove this when BGP models is migrated to 2.0 if nautobot_version >= "2.0.0": diff --git a/nautobot_design_builder/design.py b/nautobot_design_builder/design.py index 1ae187cc..fae663dd 100644 --- a/nautobot_design_builder/design.py +++ b/nautobot_design_builder/design.py @@ -277,6 +277,7 @@ def _load_instance(self): return except ObjectDoesNotExist: if self.action == "update": + # pylint: disable=raise-missing-from raise errors.DesignImplementationError(f"No match with {query_filter}", self.model_class) self.created = True # since the object was not found, we need to diff --git a/nautobot_design_builder/ext.py b/nautobot_design_builder/ext.py index b03d52b7..147cb3a3 100644 --- a/nautobot_design_builder/ext.py +++ b/nautobot_design_builder/ext.py @@ -9,7 +9,7 @@ from types import ModuleType import yaml -from nautobot_design_builder import DesignBuilderConfig +from nautobot_design_builder import NautobotDesignBuilderConfig from nautobot_design_builder.errors import DesignImplementationError from nautobot_design_builder.git import GitRepo @@ -190,6 +190,7 @@ def value(self, key) -> "ModelInstance": try: model_instance = self._env[key] except KeyError: + # pylint: disable=raise-missing-from raise DesignImplementationError(f"No ref named {key} has been saved in the design.") if model_instance.instance and not model_instance.instance._state.adding: # pylint: disable=protected-access model_instance.instance.refresh_from_db() @@ -225,7 +226,7 @@ class GitContextExtension(AttributeExtension): def __init__(self, builder: "Builder"): # noqa: D107 super().__init__(builder) - slug = DesignBuilderConfig.context_repository + slug = NautobotDesignBuilderConfig.context_repository self.context_repo = GitRepo(slug, builder.job_result) self._env = {} self._reset() diff --git a/nautobot_design_builder/fields.py b/nautobot_design_builder/fields.py index b81c8b39..74e733c2 100644 --- a/nautobot_design_builder/fields.py +++ b/nautobot_design_builder/fields.py @@ -153,11 +153,13 @@ def set_value(self, value): # noqa:D102 value.save() value = value.instance.pk except MultipleObjectsReturned: + # pylint: disable=raise-missing-from raise DesignImplementationError( f"Expected exactly 1 object for {self.model.__name__}({value}) but got more than one" ) except ObjectDoesNotExist: query = ",".join([f'{k}="{v}"' for k, v in value.items()]) + # pylint: disable=raise-missing-from raise DesignImplementationError(f"Could not find {self.model.__name__}: {query}") elif hasattr(value, "instance"): value = value.instance.pk diff --git a/nautobot_design_builder/jinja2.py b/nautobot_design_builder/jinja2.py index ad897b6a..73722f18 100644 --- a/nautobot_design_builder/jinja2.py +++ b/nautobot_design_builder/jinja2.py @@ -77,11 +77,13 @@ def network_offset(prefix: str, offset: str) -> IPNetwork: try: prefix = IPNetwork(prefix) except AddrFormatError: + # pylint: disable=raise-missing-from raise AddrFormatError(f"Invalid prefix {prefix}") try: offset = IPNetwork(offset) except AddrFormatError: + # pylint: disable=raise-missing-from raise AddrFormatError(f"Invalid offset {offset}") # netaddr overloads the + operator to sum @@ -100,6 +102,7 @@ def _json_default(value): try: return value.data except AttributeError: + # pylint: disable=raise-missing-from raise TypeError(f"Object of type {value.__class__.__name__} is not JSON serializable") diff --git a/nautobot_design_builder/management/commands/build_design.py b/nautobot_design_builder/management/commands/build_design.py index 24937ca9..8f0c6b58 100644 --- a/nautobot_design_builder/management/commands/build_design.py +++ b/nautobot_design_builder/management/commands/build_design.py @@ -14,6 +14,7 @@ def _load_file(filename): with open(filename) as file: # pylint: disable=unspecified-encoding return yaml.safe_load(file) except FileNotFoundError as ex: + # pylint: disable=raise-missing-from raise CommandError(str(ex)) diff --git a/nautobot_design_builder/tests/test_context.py b/nautobot_design_builder/tests/test_context.py index dcc3d3b7..565b98b7 100644 --- a/nautobot_design_builder/tests/test_context.py +++ b/nautobot_design_builder/tests/test_context.py @@ -7,6 +7,8 @@ class TestContext(unittest.TestCase): + """Test context.""" + def test_load(self): data = {"var1": "val1", "var2": "val2"} context = Context.load(data) @@ -55,6 +57,8 @@ def test_nested_list(self): class TestUpdateDictNode(unittest.TestCase): + """Test dict node.""" + def test_simple_update(self): data1 = {"var1": "val1"} data2 = {"var1": "val2"} @@ -112,6 +116,8 @@ def test_nested_update(self): class TestRootNode(unittest.TestCase): + """Test root node.""" + def test_simple_struct(self): data = {"var1": "val1"} want = {"var1": "val1"} @@ -222,6 +228,8 @@ def test_something_other_than_a_string(self): class TestContextDecorator(unittest.TestCase): + """Test context decorator.""" + def test_context_file(self): base_files = [ (BaseContext, "base_context_file"), diff --git a/nautobot_design_builder/tests/test_data_sources.py b/nautobot_design_builder/tests/test_data_sources.py index ebf70ccf..9c335ed2 100644 --- a/nautobot_design_builder/tests/test_data_sources.py +++ b/nautobot_design_builder/tests/test_data_sources.py @@ -60,6 +60,8 @@ def _create_module(path, content=""): class TestBase(TestCase): + """Base class for tests.""" + def setUp(self) -> None: super().setUp() self.repo_class = namedtuple("GitRepository", "provided_contents current_head filesystem_path slug") @@ -95,6 +97,8 @@ def get_repo( class TestModuleLoading(TestBase): + """Test that designs are loaded correctly.""" + def test_load_design_package(self): package_name = "design_builder_designs.module_loading" repo = self.get_repo(DATASOURCE_IDENTIFIER, "module-loading") @@ -149,6 +153,8 @@ def test_module_not_found(self): class TestDesignDiscovery(TestBase): + """Test that designs are discovered correctly.""" + def test_single_design_in_one_file(self): repo = self.get_repo(DATASOURCE_IDENTIFIER, "single-design-one-file") _create_file(os.path.join(repo.filesystem_path, "designs", "single_design_one_file.py"), DESIGN_FILE_1) diff --git a/nautobot_design_builder/tests/test_design_job.py b/nautobot_design_builder/tests/test_design_job.py index 97c5b594..bca2fdd6 100644 --- a/nautobot_design_builder/tests/test_design_job.py +++ b/nautobot_design_builder/tests/test_design_job.py @@ -11,6 +11,8 @@ class TestDesignJob(DesignTestCase): + """Test running design jobs.""" + @patch("nautobot_design_builder.design_job.Builder") def test_simple_design_commit(self, object_creator: Mock): job = self.get_mocked_job(test_designs.SimpleDesign) @@ -59,6 +61,8 @@ def test_custom_extensions(self, builder_patch: Mock): class TestDesignJobLogging(DesignTestCase): + """Test that the design job logs errors correctly.""" + @patch("nautobot_design_builder.design_job.Builder") def test_simple_design_implementation_error(self, object_creator: Mock): object_creator.return_value.implement_design.side_effect = DesignImplementationError("Broken") diff --git a/nautobot_design_builder/tests/test_errors.py b/nautobot_design_builder/tests/test_errors.py index 8eb4a318..cf78994b 100644 --- a/nautobot_design_builder/tests/test_errors.py +++ b/nautobot_design_builder/tests/test_errors.py @@ -7,7 +7,11 @@ class TestDesignModelError(unittest.TestCase): + """Test DesignModelError.""" + class TestModel: # pylint:disable=too-few-public-methods + """A test model.""" + def __init__(self, title="", parent=None): self.title = title self.instance = self @@ -64,6 +68,8 @@ def test_explicit_parent(self): class TestDesignValidationError(unittest.TestCase): + """Test DesignValidationError.""" + def test_single_string(self): want = "Error Message failed validation" got = str(DesignValidationError("Error Message")) diff --git a/nautobot_design_builder/tests/test_ext.py b/nautobot_design_builder/tests/test_ext.py index 31a1f47a..f8b5dd3c 100644 --- a/nautobot_design_builder/tests/test_ext.py +++ b/nautobot_design_builder/tests/test_ext.py @@ -22,6 +22,8 @@ class NotExtension: # pylint: disable=too-few-public-methods class TestExtensionDiscovery(TestCase): + """Test that extensions are discovered correctly.""" + def test_is_extension(self): self.assertTrue(ext.is_extension(Extension)) self.assertFalse(ext.is_extension(NotExtension)) @@ -39,6 +41,8 @@ def test_extensions(self): class TestCustomExtensions(TestCase): + """Test that custom extensions are loaded correctly.""" + def test_builder_called_with_custom_extensions(self): builder = Builder(extensions=[Extension]) self.assertEqual( @@ -51,6 +55,8 @@ def test_builder_called_with_invalid_extensions(self): class TestExtensionCommitRollback(TestCase): + """Test that extensions are called correctly.""" + @staticmethod def run_test(design, commit): """Implement a design and return wether or not `commit` and `roll_back` were called.""" diff --git a/nautobot_design_builder/util.py b/nautobot_design_builder/util.py index 42f367ce..9bdd8e1c 100644 --- a/nautobot_design_builder/util.py +++ b/nautobot_design_builder/util.py @@ -91,6 +91,7 @@ def load_design_package(path: str, package_name: str) -> Type[ModuleType]: package_spec.loader.exec_module(package) return package except FileNotFoundError: + # pylint: disable=raise-missing-from raise ModuleNotFoundError(f"no module named '{package_name}' at {path}") From 43cd74654aa38e67f4f0971b2302947df4147997 Mon Sep 17 00:00:00 2001 From: Jeremy White Date: Tue, 9 Jan 2024 11:08:07 -0500 Subject: [PATCH 06/15] Update docs/admin/compatibility_matrix.md --- docs/admin/compatibility_matrix.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/admin/compatibility_matrix.md b/docs/admin/compatibility_matrix.md index 4a032595..2a57658e 100644 --- a/docs/admin/compatibility_matrix.md +++ b/docs/admin/compatibility_matrix.md @@ -5,4 +5,4 @@ | Nautobot Design Builder Version | Nautobot First Support Version | Nautobot Last Support Version | | ------------- | -------------------- | ------------- | -| 1.0.X | 1.6.8 | 2.0.X | +| 1.0.X | 1.6.8 | 2.9999 | From 64d7580830cd6aab42e3095fab87edb534ed1a9d Mon Sep 17 00:00:00 2001 From: Jeremy White Date: Tue, 9 Jan 2024 11:08:12 -0500 Subject: [PATCH 07/15] Update docs/admin/compatibility_matrix.md --- docs/admin/compatibility_matrix.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/admin/compatibility_matrix.md b/docs/admin/compatibility_matrix.md index 2a57658e..bb8c8c81 100644 --- a/docs/admin/compatibility_matrix.md +++ b/docs/admin/compatibility_matrix.md @@ -1,7 +1,5 @@ # Compatibility Matrix -!!! warning "Developer Note - Remove Me!" - Explain how the release models of the plugin and of Nautobot work together, how releases are supported, how features and older releases are deprecated etc. | Nautobot Design Builder Version | Nautobot First Support Version | Nautobot Last Support Version | | ------------- | -------------------- | ------------- | From 39d4fe9ca3fa420cb6f907be4634f6608f2373b9 Mon Sep 17 00:00:00 2001 From: itdependsnetworks Date: Sat, 20 Jan 2024 20:30:37 -0500 Subject: [PATCH 08/15] Update drift manager for min version and uninstall update --- .github/workflows/ci.yml | 7 +- development/Dockerfile | 4 +- docs/admin/compatibility_matrix.md | 2 +- docs/admin/install.md | 2 +- docs/admin/uninstall.md | 8 - docs/dev/dev_environment.md | 12 +- invoke.example.yml | 2 +- invoke.mysql.yml | 2 +- mkdocs.yml | 2 +- nautobot_design_builder/__init__.py | 2 +- nautobot_design_builder/tests/test_basic.py | 34 -- poetry.lock | 557 ++++++++++++-------- pyproject.toml | 2 +- tasks.py | 2 +- 14 files changed, 355 insertions(+), 283 deletions(-) delete mode 100644 nautobot_design_builder/tests/test_basic.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8bb24e72..bb02bcb1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,7 +95,7 @@ jobs: fail-fast: true matrix: python-version: ["3.11"] - nautobot-version: ["1.6.8"] + nautobot-version: ["1.6.0"] env: INVOKE_NAUTOBOT_DESIGN_BUILDER_PYTHON_VER: "${{ matrix.python-version }}" INVOKE_NAUTOBOT_DESIGN_BUILDER_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" @@ -138,7 +138,7 @@ jobs: fail-fast: true matrix: python-version: ["3.11"] - nautobot-version: ["1.6.8"] + nautobot-version: ["1.6.0"] env: INVOKE_NAUTOBOT_DESIGN_BUILDER_PYTHON_VER: "${{ matrix.python-version }}" INVOKE_NAUTOBOT_DESIGN_BUILDER_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" @@ -175,14 +175,13 @@ jobs: strategy: fail-fast: true matrix: - python-version: ["3.8", "3.11"] python-version: ["3.8", "3.11"] db-backend: ["postgresql"] nautobot-version: ["1.6", "stable"] include: - python-version: "3.11" db-backend: "postgresql" - nautobot-version: "1.6.8" + nautobot-version: "1.6.0" - python-version: "3.11" db-backend: "mysql" nautobot-version: "stable" diff --git a/development/Dockerfile b/development/Dockerfile index ee399e3c..b7b58482 100644 --- a/development/Dockerfile +++ b/development/Dockerfile @@ -6,8 +6,8 @@ # ------------------------------------------------------------------------------------- # !!! USE CAUTION WHEN MODIFYING LINES BELOW -# Accepts a desired Nautobot version as build argument, default to 1.6.8 -ARG NAUTOBOT_VER="1.6.8" +# Accepts a desired Nautobot version as build argument, default to 1.6.0 +ARG NAUTOBOT_VER="1.6.0" # Accepts a desired Python version as build argument, default to 3.11 ARG PYTHON_VER="3.11" diff --git a/docs/admin/compatibility_matrix.md b/docs/admin/compatibility_matrix.md index bb8c8c81..1d00ccf9 100644 --- a/docs/admin/compatibility_matrix.md +++ b/docs/admin/compatibility_matrix.md @@ -3,4 +3,4 @@ | Nautobot Design Builder Version | Nautobot First Support Version | Nautobot Last Support Version | | ------------- | -------------------- | ------------- | -| 1.0.X | 1.6.8 | 2.9999 | +| 1.0.X | 1.6.0 | 2.9999 | diff --git a/docs/admin/install.md b/docs/admin/install.md index f9b94932..5d99d843 100644 --- a/docs/admin/install.md +++ b/docs/admin/install.md @@ -4,7 +4,7 @@ Here you will find detailed instructions on how to **install** and **configure** ## Prerequisites -- The plugin is compatible with Nautobot 1.6.8 and higher. +- The plugin is compatible with Nautobot 1.6.0 and higher. - Databases supported: PostgreSQL, MySQL !!! note diff --git a/docs/admin/uninstall.md b/docs/admin/uninstall.md index 63cda675..db13946e 100644 --- a/docs/admin/uninstall.md +++ b/docs/admin/uninstall.md @@ -2,14 +2,6 @@ Here you will find any steps necessary to cleanly remove the App from your Nautobot environment. -## Database Cleanup - -Prior to removing the plugin from the `nautobot_config.py`, run the following command to roll back any migration specific to this plugin. - -```shell -nautobot-server migrate nautobot_app_design_builder zero -``` - ## Remove App configuration Remove the configuration you added in `nautobot_config.py` from `PLUGINS` & `PLUGINS_CONFIG`. diff --git a/docs/dev/dev_environment.md b/docs/dev/dev_environment.md index 30393c27..7be6e05c 100644 --- a/docs/dev/dev_environment.md +++ b/docs/dev/dev_environment.md @@ -13,7 +13,7 @@ This is a quick reference guide if you're already familiar with the development 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: -- `nautobot_ver`: the version of Nautobot to use as a base for any built docker containers (default: 1.6.8) +- `nautobot_ver`: the version of Nautobot to use as a base for any built docker containers (default: 1.6.0) - `project_name`: the default docker compose project name (default: `nautobot-design-builder`) - `python_ver`: the version of Python to use as a base for any built docker containers (default: 3.11) - `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) @@ -179,7 +179,7 @@ The first thing you need to do is build the necessary Docker image for Nautobot #14 exporting layers #14 exporting layers 1.2s done #14 writing image sha256:2d524bc1665327faa0d34001b0a9d2ccf450612bf8feeb969312e96a2d3e3503 done -#14 naming to docker.io/nautobot-design-builder/nautobot:1.6.8-py3.11 done +#14 naming to docker.io/nautobot-design-builder/nautobot:1.6.0-py3.11 done ``` ### Invoke - Starting the Development Environment @@ -210,9 +210,9 @@ This will start all of the Docker containers used for hosting Nautobot. You shou ```bash ➜ docker ps ****CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -ee90fbfabd77 nautobot-design-builder/nautobot:1.6.8-py3.11 "nautobot-server rqw…" 16 seconds ago Up 13 seconds nautobot_design_builder_worker_1 -b8adb781d013 nautobot-design-builder/nautobot:1.6.8-py3.11 "/docker-entrypoint.…" 20 seconds ago Up 15 seconds 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp nautobot_design_builder_nautobot_1 -d64ebd60675d nautobot-design-builder/nautobot:1.6.8-py3.11 "mkdocs serve -v -a …" 25 seconds ago Up 18 seconds 0.0.0.0:8001->8080/tcp, :::8001->8080/tcp nautobot_design_builder_docs_1 +ee90fbfabd77 nautobot-design-builder/nautobot:1.6.0-py3.11 "nautobot-server rqw…" 16 seconds ago Up 13 seconds nautobot_design_builder_worker_1 +b8adb781d013 nautobot-design-builder/nautobot:1.6.0-py3.11 "/docker-entrypoint.…" 20 seconds ago Up 15 seconds 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp nautobot_design_builder_nautobot_1 +d64ebd60675d nautobot-design-builder/nautobot:1.6.0-py3.11 "mkdocs serve -v -a …" 25 seconds ago Up 18 seconds 0.0.0.0:8001->8080/tcp, :::8001->8080/tcp nautobot_design_builder_docs_1 e72d63129b36 postgres:13-alpine "docker-entrypoint.s…" 25 seconds ago Up 19 seconds 0.0.0.0:5432->5432/tcp, :::5432->5432/tcp nautobot_design_builder_postgres_1 96c6ff66997c redis:6-alpine "docker-entrypoint.s…" 25 seconds ago Up 21 seconds 0.0.0.0:6379->6379/tcp, :::6379->6379/tcp nautobot_design_builder_redis_1 ``` @@ -410,7 +410,7 @@ namespace.configure( { "nautobot_design_builder": { ... - "nautobot_ver": "1.6.8", + "nautobot_ver": "1.6.0", ... } } diff --git a/invoke.example.yml b/invoke.example.yml index 9a4fa928..9132ee94 100644 --- a/invoke.example.yml +++ b/invoke.example.yml @@ -1,7 +1,7 @@ --- nautobot_design_builder: project_name: "nautobot-design-builder" - nautobot_ver: "1.6.8" + nautobot_ver: "1.6.0" local: false python_ver: "3.11" compose_dir: "development" diff --git a/invoke.mysql.yml b/invoke.mysql.yml index 62ffa9f2..dd1881cc 100644 --- a/invoke.mysql.yml +++ b/invoke.mysql.yml @@ -1,7 +1,7 @@ --- nautobot_design_builder: project_name: "nautobot-design-builder" - nautobot_ver: "1.6.8" + nautobot_ver: "1.6.0" local: false python_ver: "3.11" compose_dir: "development" diff --git a/mkdocs.yml b/mkdocs.yml index 67ff2df1..14bf4cd5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -123,9 +123,9 @@ nav: - Code Reference: - "dev/code_reference/index.md" - Package: "dev/code_reference/package.md" - - Design Job: "dev/code_reference/design_job.md" - Context: "dev/code_reference/context.md" - Design Builder: "dev/code_reference/design.md" + - Design Job: "dev/code_reference/design_job.md" - Jinja Rendering: "dev/code_reference/jinja2.md" - Template Extensions: "dev/code_reference/ext.md" - Util: "dev/code_reference/util.md" diff --git a/nautobot_design_builder/__init__.py b/nautobot_design_builder/__init__.py index 0a908ca3..a45a8c71 100644 --- a/nautobot_design_builder/__init__.py +++ b/nautobot_design_builder/__init__.py @@ -20,7 +20,7 @@ class NautobotDesignBuilderConfig(NautobotAppConfig): description = "Nautobot app that uses design templates to easily create data objects in Nautobot with minimal input from a user.." base_url = "design-builder" required_settings = [] - min_version = "1.6.8" + min_version = "1.6.0" max_version = "2.9999" default_settings = {} caching_config = {} diff --git a/nautobot_design_builder/tests/test_basic.py b/nautobot_design_builder/tests/test_basic.py deleted file mode 100644 index 9b52639c..00000000 --- a/nautobot_design_builder/tests/test_basic.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Basic tests that do not require Django.""" -import unittest -import os -import toml - -from nautobot_design_builder import __version__ as project_version - - -class TestVersion(unittest.TestCase): - """Test Version is the same.""" - - def test_version(self): - """Verify that pyproject.toml version is same as version specified in the package.""" - parent_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) - poetry_version = toml.load(os.path.join(parent_path, "pyproject.toml"))["tool"]["poetry"]["version"] - self.assertEqual(project_version, poetry_version) - - -class TestDocsPackaging(unittest.TestCase): - """Test Version in doc requirements is the same pyproject.""" - - def test_version(self): - """Verify that pyproject.toml dev dependencies have the same versions as in the docs requirements.txt.""" - parent_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) - poetry_path = os.path.join(parent_path, "pyproject.toml") - poetry_details = toml.load(poetry_path)["tool"]["poetry"]["group"]["dev"]["dependencies"] - with open(f"{parent_path}/docs/requirements.txt", "r", encoding="utf-8") as file: - requirements = [line for line in file.read().splitlines() if (len(line) > 0 and not line.startswith("#"))] - for pkg in requirements: - if len(pkg.split("==")) == 2: - pkg, version = pkg.split("==") - else: - version = "*" - self.assertEqual(poetry_details[pkg], version) diff --git a/poetry.lock b/poetry.lock index 2b2bfb9c..58a9b455 100755 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +# This file is automatically @generated by Poetry and should not be changed by hand. [[package]] name = "amqp" @@ -59,18 +59,23 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] [[package]] name = "astroid" -version = "3.0.2" +version = "2.15.8" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false -python-versions = ">=3.8.0" +python-versions = ">=3.7.2" files = [ - {file = "astroid-3.0.2-py3-none-any.whl", hash = "sha256:d6e62862355f60e716164082d6b4b041d38e2a8cf1c7cd953ded5108bac8ff5c"}, - {file = "astroid-3.0.2.tar.gz", hash = "sha256:4a61cf0a59097c7bb52689b0fd63717cd2a8a14dc9f1eee97b82d814881c8c91"}, + {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, + {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, ] [package.dependencies] +lazy-object-proxy = ">=1.4.0" typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} +wrapt = [ + {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, + {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, +] [[package]] name = "asttokens" @@ -211,20 +216,23 @@ yaml = ["PyYAML"] [[package]] name = "beautifulsoup4" -version = "4.12.2" +version = "4.12.3" description = "Screen-scraping library" category = "dev" optional = false python-versions = ">=3.6.0" files = [ - {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, - {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, ] [package.dependencies] soupsieve = ">1.2" [package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] html5lib = ["html5lib"] lxml = ["lxml"] @@ -751,14 +759,14 @@ files = [ [[package]] name = "defusedxml" -version = "0.7.1" +version = "0.8.0rc2" description = "XML bomb protection for Python stdlib modules" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" files = [ - {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, - {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, + {file = "defusedxml-0.8.0rc2-py2.py3-none-any.whl", hash = "sha256:1c812964311154c3bf4aaf3bc1443b31ee13530b7f255eaaa062c0553c76103d"}, + {file = "defusedxml-0.8.0rc2.tar.gz", hash = "sha256:138c7d540a78775182206c7c97fe65b246a2f40b29471e1a2f1b0da76e7a3942"}, ] [[package]] @@ -1285,21 +1293,6 @@ uritemplate = ">=3.0.0" coreapi = ["coreapi (>=2.3.3)", "coreschema (>=0.0.4)"] validation = ["swagger-spec-validator (>=2.1.0)"] -[[package]] -name = "exceptiongroup" -version = "1.2.0" -description = "Backport of PEP 654 (exception groups)" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, -] - -[package.extras] -test = ["pytest (>=6)"] - [[package]] name = "executing" version = "2.0.1" @@ -1317,20 +1310,20 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "flake8" -version = "3.9.2" +version = "5.0.4" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.6.1" files = [ - {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, - {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, + {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, + {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, ] [package.dependencies] -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.7.0,<2.8.0" -pyflakes = ">=2.3.0,<2.4.0" +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.9.0,<2.10.0" +pyflakes = ">=2.5.0,<2.6.0" [[package]] name = "funcy" @@ -1379,21 +1372,21 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.40" +version = "3.1.41" description = "GitPython is a Python library used to interact with Git repositories" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "GitPython-3.1.40-py3-none-any.whl", hash = "sha256:cf14627d5a8049ffbf49915732e5eddbe8134c3bdb9d476e6182b676fc573f8a"}, - {file = "GitPython-3.1.40.tar.gz", hash = "sha256:22b126e9ffb671fdd0c129796343a02bf67bf2994b35449ffc9321aa755e18a4"}, + {file = "GitPython-3.1.41-py3-none-any.whl", hash = "sha256:c36b6634d069b3f719610175020a9aed919421c87552185b085e04fbbdb10b7c"}, + {file = "GitPython-3.1.41.tar.gz", hash = "sha256:ed66e624884f76df22c8e16066d567aaa5a37d5b5fa19db2c6df6f7156db9048"}, ] [package.dependencies] gitdb = ">=4.0.1,<5" [package.extras] -test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest", "pytest-cov", "pytest-instafail", "pytest-subtests", "pytest-sugar"] +test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "sumtypes"] [[package]] name = "graphene" @@ -1494,14 +1487,14 @@ six = ">=1.12" [[package]] name = "griffe" -version = "0.38.1" +version = "0.39.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." category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "griffe-0.38.1-py3-none-any.whl", hash = "sha256:334c79d3b5964ade65c05dfcaf53518c576dedd387aaba5c9fd71212f34f1483"}, - {file = "griffe-0.38.1.tar.gz", hash = "sha256:bd68d7da7f3d87bc57eb9962b250db123efd9bbcc06c11c1a91b6e583b2a9361"}, + {file = "griffe-0.39.1-py3-none-any.whl", hash = "sha256:6ce4ecffcf0d2f96362c5974b3f7df812da8f8d4cfcc5ebc8202ef72656fc087"}, + {file = "griffe-0.39.1.tar.gz", hash = "sha256:ead8dfede6e6531cce6bf69090a4f3c6d36fdf923c43f8e85aa530552cef0c09"}, ] [package.dependencies] @@ -1541,22 +1534,22 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs [[package]] name = "importlib-resources" -version = "6.1.1" +version = "5.13.0" description = "Read resources from Python packages" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_resources-6.1.1-py3-none-any.whl", hash = "sha256:e8bf90d8213b486f428c9c39714b920041cb02c184686a3dee24905aaa8105d6"}, - {file = "importlib_resources-6.1.1.tar.gz", hash = "sha256:3893a00122eafde6894c59914446a512f728a0c1a45f9bb9b63721b6bacf0b4a"}, + {file = "importlib_resources-5.13.0-py3-none-any.whl", hash = "sha256:9f7bd0c97b79972a6cce36a366356d16d5e13b09679c11a58f1014bfdf8e64b2"}, + {file = "importlib_resources-5.13.0.tar.gz", hash = "sha256:82d5c6cca930697dbbd86c93333bb2c2e72861d4789a11c2662b933e5ad2b528"}, ] [package.dependencies] zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [[package]] name = "inflection" @@ -1570,18 +1563,6 @@ files = [ {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"}, ] -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - [[package]] name = "invoke" version = "2.2.0" @@ -1671,14 +1652,14 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] [[package]] name = "jinja2" -version = "3.1.2" +version = "3.1.3" description = "A very fast and expressive template engine." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, ] [package.dependencies] @@ -1711,14 +1692,14 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- [[package]] name = "kombu" -version = "5.3.4" +version = "5.3.5" description = "Messaging library for Python." category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "kombu-5.3.4-py3-none-any.whl", hash = "sha256:63bb093fc9bb80cfb3a0972336a5cec1fa7ac5f9ef7e8237c6bf8dda9469313e"}, - {file = "kombu-5.3.4.tar.gz", hash = "sha256:0bb2e278644d11dea6272c17974a3dbb9688a949f3bb60aeb5b791329c44fadc"}, + {file = "kombu-5.3.5-py3-none-any.whl", hash = "sha256:0eac1bbb464afe6fb0924b21bf79460416d25d8abc52546d4f16cad94f789488"}, + {file = "kombu-5.3.5.tar.gz", hash = "sha256:30e470f1a6b49c70dc6f6d13c3e4cc4e178aa6c469ceb6bcd55645385fc84b93"}, ] [package.dependencies] @@ -1744,6 +1725,53 @@ sqs = ["boto3 (>=1.26.143)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"] yaml = ["PyYAML (>=3.10)"] zookeeper = ["kazoo (>=2.8.0)"] +[[package]] +name = "lazy-object-proxy" +version = "1.10.0" +description = "A fast and thorough lazy object proxy." +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab7004cf2e59f7c2e4345604a3e6ea0d92ac44e1c2375527d56492014e690c3"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc0d2fc424e54c70c4bc06787e4072c4f3b1aa2f897dfdc34ce1013cf3ceef05"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e2adb09778797da09d2b5ebdbceebf7dd32e2c96f79da9052b2e87b6ea495895"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1f711e2c6dcd4edd372cf5dec5c5a30d23bba06ee012093267b3376c079ec83"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-win32.whl", hash = "sha256:76a095cfe6045c7d0ca77db9934e8f7b71b14645f0094ffcd842349ada5c5fb9"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4f87d4ed9064b2628da63830986c3d2dca7501e6018347798313fcf028e2fd4"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fec03caabbc6b59ea4a638bee5fce7117be8e99a4103d9d5ad77f15d6f81020c"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c83f957782cbbe8136bee26416686a6ae998c7b6191711a04da776dc9e47d4"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009e6bb1f1935a62889ddc8541514b6a9e1fcf302667dcb049a0be5c8f613e56"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75fc59fc450050b1b3c203c35020bc41bd2695ed692a392924c6ce180c6f1dc9"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:782e2c9b2aab1708ffb07d4bf377d12901d7a1d99e5e410d648d892f8967ab1f"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-win32.whl", hash = "sha256:edb45bb8278574710e68a6b021599a10ce730d156e5b254941754a9cc0b17d03"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:e271058822765ad5e3bca7f05f2ace0de58a3f4e62045a8c90a0dfd2f8ad8cc6"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e98c8af98d5707dcdecc9ab0863c0ea6e88545d42ca7c3feffb6b4d1e370c7ba"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:952c81d415b9b80ea261d2372d2a4a2332a3890c2b83e0535f263ddfe43f0d43"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b39d3a151309efc8cc48675918891b865bdf742a8616a337cb0090791a0de9"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e221060b701e2aa2ea991542900dd13907a5c90fa80e199dbf5a03359019e7a3"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92f09ff65ecff3108e56526f9e2481b8116c0b9e1425325e13245abfd79bdb1b"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-win32.whl", hash = "sha256:3ad54b9ddbe20ae9f7c1b29e52f123120772b06dbb18ec6be9101369d63a4074"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:127a789c75151db6af398b8972178afe6bda7d6f68730c057fbbc2e96b08d282"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4ed0518a14dd26092614412936920ad081a424bdcb54cc13349a8e2c6d106a"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ad9e6ed739285919aa9661a5bbed0aaf410aa60231373c5579c6b4801bd883c"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc0a92c02fa1ca1e84fc60fa258458e5bf89d90a1ddaeb8ed9cc3147f417255"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0aefc7591920bbd360d57ea03c995cebc204b424524a5bd78406f6e1b8b2a5d8"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5faf03a7d8942bb4476e3b62fd0f4cf94eaf4618e304a19865abf89a35c0bbee"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-win32.whl", hash = "sha256:e333e2324307a7b5d86adfa835bb500ee70bfcd1447384a822e96495796b0ca4"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:cb73507defd385b7705c599a94474b1d5222a508e502553ef94114a143ec6696"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366c32fe5355ef5fc8a232c5436f4cc66e9d3e8967c01fb2e6302fd6627e3d94"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2297f08f08a2bb0d32a4265e98a006643cd7233fb7983032bd61ac7a02956b3b"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18dd842b49456aaa9a7cf535b04ca4571a302ff72ed8740d06b5adcd41fe0757"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:217138197c170a2a74ca0e05bddcd5f1796c735c37d0eee33e43259b192aa424"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a3a87cf1e133e5b1994144c12ca4aa3d9698517fe1e2ca82977781b16955658"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-win32.whl", hash = "sha256:30b339b2a743c5288405aa79a69e706a06e02958eab31859f7f3c04980853b70"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:a899b10e17743683b293a729d3a11f2f399e8a90c73b089e29f5d0fe3509f0dd"}, + {file = "lazy_object_proxy-1.10.0-pp310.pp311.pp312.pp38.pp39-none-any.whl", hash = "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d"}, +] + [[package]] name = "lxml" version = "5.1.0" @@ -1752,6 +1780,7 @@ category = "dev" optional = false python-versions = ">=3.6" files = [ + {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:704f5572ff473a5f897745abebc6df40f22d4133c1e0a1f124e4f2bd3330ff7e"}, {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d3c0f8567ffe7502d969c2c1b809892dc793b5d0665f602aad19895f8d508da"}, {file = "lxml-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5fcfbebdb0c5d8d18b84118842f31965d59ee3e66996ac842e21f957eb76138c"}, {file = "lxml-5.1.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f37c6d7106a9d6f0708d4e164b707037b7380fcd0b04c5bd9cae1fb46a856fb"}, @@ -1761,6 +1790,7 @@ files = [ {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82bddf0e72cb2af3cbba7cec1d2fd11fda0de6be8f4492223d4a268713ef2147"}, {file = "lxml-5.1.0-cp310-cp310-win32.whl", hash = "sha256:b66aa6357b265670bb574f050ffceefb98549c721cf28351b748be1ef9577d93"}, {file = "lxml-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:4946e7f59b7b6a9e27bef34422f645e9a368cb2be11bf1ef3cafc39a1f6ba68d"}, + {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:14deca1460b4b0f6b01f1ddc9557704e8b365f55c63070463f6c18619ebf964f"}, {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed8c3d2cd329bf779b7ed38db176738f3f8be637bb395ce9629fc76f78afe3d4"}, {file = "lxml-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:436a943c2900bb98123b06437cdd30580a61340fbdb7b28aaf345a459c19046a"}, {file = "lxml-5.1.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acb6b2f96f60f70e7f34efe0c3ea34ca63f19ca63ce90019c6cbca6b676e81fa"}, @@ -1770,6 +1800,7 @@ files = [ {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4c9bda132ad108b387c33fabfea47866af87f4ea6ffb79418004f0521e63204"}, {file = "lxml-5.1.0-cp311-cp311-win32.whl", hash = "sha256:bc64d1b1dab08f679fb89c368f4c05693f58a9faf744c4d390d7ed1d8223869b"}, {file = "lxml-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5ab722ae5a873d8dcee1f5f45ddd93c34210aed44ff2dc643b5025981908cda"}, + {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9aa543980ab1fbf1720969af1d99095a548ea42e00361e727c58a40832439114"}, {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6f11b77ec0979f7e4dc5ae081325a2946f1fe424148d3945f943ceaede98adb8"}, {file = "lxml-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a36c506e5f8aeb40680491d39ed94670487ce6614b9d27cabe45d94cd5d63e1e"}, {file = "lxml-5.1.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f643ffd2669ffd4b5a3e9b41c909b72b2a1d5e4915da90a77e119b8d48ce867a"}, @@ -1795,8 +1826,8 @@ files = [ {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8f52fe6859b9db71ee609b0c0a70fea5f1e71c3462ecf144ca800d3f434f0764"}, {file = "lxml-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:d42e3a3fc18acc88b838efded0e6ec3edf3e328a58c68fbd36a7263a874906c8"}, {file = "lxml-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:eac68f96539b32fce2c9b47eb7c25bb2582bdaf1bbb360d25f564ee9e04c542b"}, + {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ae15347a88cf8af0949a9872b57a320d2605ae069bcdf047677318bc0bba45b1"}, {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c26aab6ea9c54d3bed716b8851c8bfc40cb249b8e9880e250d1eddde9f709bf5"}, - {file = "lxml-5.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cfbac9f6149174f76df7e08c2e28b19d74aed90cad60383ad8671d3af7d0502f"}, {file = "lxml-5.1.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:342e95bddec3a698ac24378d61996b3ee5ba9acfeb253986002ac53c9a5f6f84"}, {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:725e171e0b99a66ec8605ac77fa12239dbe061482ac854d25720e2294652eeaa"}, {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d184e0d5c918cff04cdde9dbdf9600e960161d773666958c9d7b565ccc60c45"}, @@ -1804,6 +1835,7 @@ files = [ {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d48fc57e7c1e3df57be5ae8614bab6d4e7b60f65c5457915c26892c41afc59e"}, {file = "lxml-5.1.0-cp38-cp38-win32.whl", hash = "sha256:7ec465e6549ed97e9f1e5ed51c657c9ede767bc1c11552f7f4d022c4df4a977a"}, {file = "lxml-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:b21b4031b53d25b0858d4e124f2f9131ffc1530431c6d1321805c90da78388d1"}, + {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:52427a7eadc98f9e62cb1368a5079ae826f94f05755d2d567d93ee1bc3ceb354"}, {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6a2a2c724d97c1eb8cf966b16ca2915566a4904b9aad2ed9a09c748ffe14f969"}, {file = "lxml-5.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843b9c835580d52828d8f69ea4302537337a21e6b4f1ec711a52241ba4a824f3"}, {file = "lxml-5.1.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b99f564659cfa704a2dd82d0684207b1aadf7d02d33e54845f9fc78e06b7581"}, @@ -1896,62 +1928,72 @@ wavedrom = ["wavedrom"] [[package]] name = "markupsafe" -version = "2.1.3" +version = "2.1.4" description = "Safely add untrusted strings to HTML/XML markup." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a07f40ef8f0fbc5ef1000d0c78771f4d5ca03b4953fc162749772916b298fc4"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d18b66fe626ac412d96c2ab536306c736c66cf2a31c243a45025156cc190dc8a"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698e84142f3f884114ea8cf83e7a67ca8f4ace8454e78fe960646c6c91c63bfa"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a3b78a5af63ec10d8604180380c13dcd870aba7928c1fe04e881d5c792dc4e"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:15866d7f2dc60cfdde12ebb4e75e41be862348b4728300c36cdf405e258415ec"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6aa5e2e7fc9bc042ae82d8b79d795b9a62bd8f15ba1e7594e3db243f158b5565"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:54635102ba3cf5da26eb6f96c4b8c53af8a9c0d97b64bdcb592596a6255d8518"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-win32.whl", hash = "sha256:3583a3a3ab7958e354dc1d25be74aee6228938312ee875a22330c4dc2e41beb0"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-win_amd64.whl", hash = "sha256:d6e427c7378c7f1b2bef6a344c925b8b63623d3321c09a237b7cc0e77dd98ceb"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bf1196dcc239e608605b716e7b166eb5faf4bc192f8a44b81e85251e62584bd2"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df98d4a9cd6a88d6a585852f56f2155c9cdb6aec78361a19f938810aa020954"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b835aba863195269ea358cecc21b400276747cc977492319fd7682b8cd2c253d"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23984d1bdae01bee794267424af55eef4dfc038dc5d1272860669b2aa025c9e3"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c98c33ffe20e9a489145d97070a435ea0679fddaabcafe19982fe9c971987d5"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9896fca4a8eb246defc8b2a7ac77ef7553b638e04fbf170bff78a40fa8a91474"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b0fe73bac2fed83839dbdbe6da84ae2a31c11cfc1c777a40dbd8ac8a6ed1560f"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c7556bafeaa0a50e2fe7dc86e0382dea349ebcad8f010d5a7dc6ba568eaaa789"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-win32.whl", hash = "sha256:fc1a75aa8f11b87910ffd98de62b29d6520b6d6e8a3de69a70ca34dea85d2a8a"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:3a66c36a3864df95e4f62f9167c734b3b1192cb0851b43d7cc08040c074c6279"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:765f036a3d00395a326df2835d8f86b637dbaf9832f90f5d196c3b8a7a5080cb"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21e7af8091007bf4bebf4521184f4880a6acab8df0df52ef9e513d8e5db23411"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c31fe855c77cad679b302aabc42d724ed87c043b1432d457f4976add1c2c3e"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653fa39578957bc42e5ebc15cf4361d9e0ee4b702d7d5ec96cdac860953c5b4"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47bb5f0142b8b64ed1399b6b60f700a580335c8e1c57f2f15587bd072012decc"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fe8512ed897d5daf089e5bd010c3dc03bb1bdae00b35588c49b98268d4a01e00"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:36d7626a8cca4d34216875aee5a1d3d654bb3dac201c1c003d182283e3205949"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6f14a9cd50c3cb100eb94b3273131c80d102e19bb20253ac7bd7336118a673a"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-win32.whl", hash = "sha256:c8f253a84dbd2c63c19590fa86a032ef3d8cc18923b8049d91bcdeeb2581fbf6"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:8b570a1537367b52396e53325769608f2a687ec9a4363647af1cded8928af959"}, + {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"}, ] [[package]] @@ -1971,14 +2013,14 @@ traitlets = "*" [[package]] name = "mccabe" -version = "0.6.1" +version = "0.7.0" description = "McCabe checker, plugin for flake8" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] [[package]] @@ -2161,14 +2203,14 @@ files = [ [[package]] name = "nautobot" -version = "1.6.8" +version = "1.6.9" description = "Source of truth and network automation platform." category = "main" optional = false python-versions = ">=3.8,<3.12" files = [ - {file = "nautobot-1.6.8-py3-none-any.whl", hash = "sha256:6e8b350d01021a88f58bec971093f57b1ed793f74e3a139cd6b20e04d11afc42"}, - {file = "nautobot-1.6.8.tar.gz", hash = "sha256:664d1f2bc1ff466007f047e89c82659b6b7e9d1d1a63758de73cfcd3a0c791e7"}, + {file = "nautobot-1.6.9-py3-none-any.whl", hash = "sha256:f011bb7d91f4aff24b8bf8903b37eea8b4826ae1b34e41a75397601b5d917831"}, + {file = "nautobot-1.6.9.tar.gz", hash = "sha256:08943c35036448b32653b054ff5adeea217a48ada645229b53d6608eb5b17c36"}, ] [package.dependencies] @@ -2469,22 +2511,6 @@ files = [ docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] -[[package]] -name = "pluggy" -version = "1.3.0" -description = "plugin and hook calling mechanisms for python" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - [[package]] name = "prometheus-client" version = "0.17.1" @@ -2566,6 +2592,7 @@ files = [ {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, @@ -2574,6 +2601,8 @@ files = [ {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"}, {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, @@ -2640,14 +2669,14 @@ tests = ["pytest"] [[package]] name = "pycodestyle" -version = "2.7.0" +version = "2.9.1" description = "Python style guide checker" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" files = [ - {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, - {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, + {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, + {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, ] [[package]] @@ -2682,14 +2711,14 @@ toml = ["tomli (>=1.2.3)"] [[package]] name = "pyflakes" -version = "2.3.1" +version = "2.5.0" description = "passive checker of Python programs" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" files = [ - {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, - {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, + {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, + {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, ] [[package]] @@ -2728,24 +2757,24 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pylint" -version = "3.0.3" +version = "2.17.7" description = "python code static checker" category = "dev" optional = false -python-versions = ">=3.8.0" +python-versions = ">=3.7.2" files = [ - {file = "pylint-3.0.3-py3-none-any.whl", hash = "sha256:7a1585285aefc5165db81083c3e06363a27448f6b467b3b0f30dbd0ac1f73810"}, - {file = "pylint-3.0.3.tar.gz", hash = "sha256:58c2398b0301e049609a8429789ec6edf3aabe9b6c5fec916acd18639c16de8b"}, + {file = "pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87"}, + {file = "pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad"}, ] [package.dependencies] -astroid = ">=3.0.1,<=3.1.0-dev0" +astroid = ">=2.15.8,<=2.17.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, ] -isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" +isort = ">=4.2.5,<6" mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} @@ -2775,6 +2804,24 @@ pylint-plugin-utils = ">=0.8" [package.extras] with-django = ["Django (>=2.2)"] +[[package]] +name = "pylint-nautobot" +version = "0.2.1" +description = "Custom Pylint Rules for Nautobot" +category = "dev" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "pylint_nautobot-0.2.1-py3-none-any.whl", hash = "sha256:6656cd571d6e997e6d7e37631308f1de25949a596a8309ab6d47a2e387c892c6"}, + {file = "pylint_nautobot-0.2.1.tar.gz", hash = "sha256:2872106a29236b0e31293efe4a2d02a66527c67f33437f3e2345251c4cf71b4d"}, +] + +[package.dependencies] +importlib-resources = ">=5.12.0,<6.0.0" +pylint = ">=2.13,<3.0" +pyyaml = ">=6.0,<7.0" +tomli = ">=2.0.1,<3.0.0" + [[package]] name = "pylint-plugin-utils" version = "0.8.2" @@ -2870,29 +2917,6 @@ files = [ {file = "pyrsistent-0.20.0.tar.gz", hash = "sha256:4c48f78f62ab596c679086084d0dd13254ae4f3d6c72a83ffdf5ebdef8f265a4"}, ] -[[package]] -name = "pytest" -version = "7.4.4" -description = "pytest: simple powerful testing with Python" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - [[package]] name = "python-crontab" version = "3.0.0" @@ -2960,55 +2984,55 @@ files = [ [[package]] name = "pyuwsgi" -version = "2.0.23" +version = "2.0.23.post0" description = "The uWSGI server" category = "main" optional = false python-versions = "*" files = [ - {file = "pyuwsgi-2.0.23-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0bb538ef57960389d67bcd4a9e7ebb562ed13a4556a5596305ce5361e121fc4e"}, - {file = "pyuwsgi-2.0.23-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd9689290c3b4afec7d28f1c43ec60f9ee905abf66a501584454cbf6b620678"}, - {file = "pyuwsgi-2.0.23-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80e6fd3a9f49fad9404dd2622116db16990dd9c5061461fd700a82b429f0ee2b"}, - {file = "pyuwsgi-2.0.23-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4796cb1d35eff2cdae6ea01ffb26d2ec0ddf5c692d9f4bf5a28cab61baf78f4"}, - {file = "pyuwsgi-2.0.23-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:366dbc57eaee7b37f3e1c4039fcd7ba2a5693579e17ba07704038ffa28a8be57"}, - {file = "pyuwsgi-2.0.23-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:40ddfcb7d972cac169e62253027f932bb047a995cfbe98398c1451b137e3cf8d"}, - {file = "pyuwsgi-2.0.23-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f4dc785d94878088fd2b4b6da7a630b5538d461b92b6a767cb56401dac1373b9"}, - {file = "pyuwsgi-2.0.23-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbde1da759d1486d6b20938b8f03b84b4dfe4a1b7ba111c586b1eaed6cd85cdc"}, - {file = "pyuwsgi-2.0.23-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12568dbacacd02b22791b352c3e93a9307d565512a851b36483ffe4db69b711e"}, - {file = "pyuwsgi-2.0.23-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9db7b77bf6ee429da0583f36f168bcf1294195d7a4ac53b53d1f5d8ac8c2717"}, - {file = "pyuwsgi-2.0.23-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1de2f99dc4642aea7226889c76083884260920adc14a4a533660479941c6e6f2"}, - {file = "pyuwsgi-2.0.23-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fbad05b405630ddaaf8010822fc8bc553551bcf691df2d1ffbfd4d2204f9973f"}, - {file = "pyuwsgi-2.0.23-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:39107b8abaf488e890d53372bef7b80fdf350b703bbfa2f4ded1002eea31b198"}, - {file = "pyuwsgi-2.0.23-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:feb783ef451dc09cd37b2376ccc9e8ff28d3296542df0351e0a4502c8fac765c"}, - {file = "pyuwsgi-2.0.23-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ea11e270161e5cc8f6935778841f30e3226b0ee3b70185d88d8fa2bf0317bdc9"}, - {file = "pyuwsgi-2.0.23-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3608203a37ebf5580f3fc4901ae1295fd181caa7ec49d29b7dcc1864725049e"}, - {file = "pyuwsgi-2.0.23-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bf9d22dd5397a80cf91242f173c4bab0104c7c8b17d286b289a9582a30643cac"}, - {file = "pyuwsgi-2.0.23-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6505cb52b25eecf81338b9f17f4b47ec6288f3911eb65a5a9f3be03ed2ba0b97"}, - {file = "pyuwsgi-2.0.23-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:563270210d79a9e1a76ead34dec40b0ddf1491ac44e02e9d9fd41f8e08938f07"}, - {file = "pyuwsgi-2.0.23-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1883c08aa902dbeb7bd70c5ea319452ecbce49adc715ece4c4bef8c0acfb8523"}, - {file = "pyuwsgi-2.0.23-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:79641c8fccc507288b58805c0edb0540713b9fb65d445d703329606a3fbc2fab"}, - {file = "pyuwsgi-2.0.23-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:02a21ce1175599d0e9d63dc3bb576f7662e1ba3412b746bd9780708f55b35587"}, - {file = "pyuwsgi-2.0.23-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d72e622517522df0e8e04fc1f2aff0d1cafeececc44eecf6f83646f405ef474f"}, - {file = "pyuwsgi-2.0.23-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58cb2c48bfb34b73f5a7586c55d2e29e927a7ca6ca45153e9d860d380f4d6ef1"}, - {file = "pyuwsgi-2.0.23-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7684b4c97bb0d52f3e53f5f67a39241ed1ea234e4a8c351a7ea4a4cfd397909"}, - {file = "pyuwsgi-2.0.23-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4983d2f201d14bf7ed6ec2f6e9449e046440476877e55b1cf6f165d2eb6d3cf4"}, - {file = "pyuwsgi-2.0.23-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:462dccd00ad01a33744a7c061fa2080b58e6b4c0f25cb95e8f9628a42d10f04f"}, - {file = "pyuwsgi-2.0.23-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:764e833b890a82cf94f60087147bd98d8d8769e133e1c1289cd7b8af4d4e19ee"}, - {file = "pyuwsgi-2.0.23-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:120ae908df0b006d1e88b43a3dfbb2f02212ac768d75baefc2a20cdf1b279b11"}, - {file = "pyuwsgi-2.0.23-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78d8ab2ac544a80bfb57a3019f1768e2ca327993f3a2e39aee92b0a70746f0bb"}, - {file = "pyuwsgi-2.0.23-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1e3151b5495e3b1882b07a72e040e7a0422e8e5e58ceafc4cc046428c781f86"}, - {file = "pyuwsgi-2.0.23-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56ba238ccf4e12de0bba0ee7d92e316e3acda22419e3403dc0d474420baf3d71"}, - {file = "pyuwsgi-2.0.23-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:173709af71a86d9efee16a702933fee2ee3e6ac6b7f80eee86414bab0c80838a"}, - {file = "pyuwsgi-2.0.23-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a35ab28beba766f89c7a0db6a6a0fcedb72d7c9ff3262f3f27418bf5b757602e"}, - {file = "pyuwsgi-2.0.23-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0fd1f679c4597641bb30887e9180c42dfabf4b3e7e2747425f4468fe93a17e51"}, - {file = "pyuwsgi-2.0.23-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7c8eca007320f91f4009eca578e3014a443e7f7b33dabb2454754971fd5df4c0"}, - {file = "pyuwsgi-2.0.23-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cebebc9a322f3d5caf19938114d66ff341852756511f99f1892fbc684120501"}, - {file = "pyuwsgi-2.0.23-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8f2311699e2562670e3ce979bbb566302e7951e758ee80f77a42f1e13a2e221"}, - {file = "pyuwsgi-2.0.23-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf687febfb7f1cfcbedb07762f39279df8725e9e681a859448ee1c1e79a39180"}, - {file = "pyuwsgi-2.0.23-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b27a7dd26e134c134ba0ed17bc28209eb459709480bdc773ce7da5ecc016c81a"}, - {file = "pyuwsgi-2.0.23-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:447a2a72e4285a1617154c496005fbaf1fbf5b3cf6e81186a13e3627ed7b0994"}, - {file = "pyuwsgi-2.0.23-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a13932ba8079b627d6233c8a04c1544bbe2a9007ddeed7f68f46401b1d0c5d5d"}, - {file = "pyuwsgi-2.0.23.tar.gz", hash = "sha256:74ac3e9c641969a3073c67793773a73bd7968ddcc3fa810c5396415e80cc0df1"}, + {file = "pyuwsgi-2.0.23.post0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:49dfe43726f4a71d3440f7a36eb3ba5b361e04807164d34ececda138e2dc2375"}, + {file = "pyuwsgi-2.0.23.post0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65420b185003dd5b66f41a6d1aa03d63d953a18e818bd4a013fc8e9d580f11cb"}, + {file = "pyuwsgi-2.0.23.post0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7bc7c60d8e1242b3a638754d2487c505112c642010c460442993be85f3ca9ec7"}, + {file = "pyuwsgi-2.0.23.post0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ae2abaa47cb9c0018c790935897aec8001fb709dfac54286a37ab2e0b88dca"}, + {file = "pyuwsgi-2.0.23.post0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:af376cafca1501b2d4b8184c427c55b32c1a3dcb6070dc27115ca552898c7ff8"}, + {file = "pyuwsgi-2.0.23.post0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f56a729808ed7aa1d7973d6f900a75bc36b976b7ab6c8867064f36e34cdafd4e"}, + {file = "pyuwsgi-2.0.23.post0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4270e68bb2633b0fc132aad6d415e4e0cde67093a97e64dd84bd186264a8c083"}, + {file = "pyuwsgi-2.0.23.post0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:97c940a69242dc45658dba3330e64d809f34e33d9631547b6928fd20075b4bb9"}, + {file = "pyuwsgi-2.0.23.post0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cac396c2e8e0d199bde9bb8fc90538c82207d0c3d722d08b9a63619b41945d6"}, + {file = "pyuwsgi-2.0.23.post0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59d6a718ad42be54b2b80c8c236b728b8b83fb93438786e95f63fc259229ccd7"}, + {file = "pyuwsgi-2.0.23.post0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c38b5bb59e1bf59030f2d43a3e67aa18e6089c8e7f43e9c5f2099567466d35f4"}, + {file = "pyuwsgi-2.0.23.post0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7199009447770812056a5b417c4847bd44db1b0230d4bb64c48a4ffacd4e96f0"}, + {file = "pyuwsgi-2.0.23.post0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f361d168cf175796fe36ab6a88dee079245a2f08e587e8190a38bd1b33238fa8"}, + {file = "pyuwsgi-2.0.23.post0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:52a45e98fe746ae9c9437c5b6f0cdb6117f979c8800f09c8e4dae2997786affd"}, + {file = "pyuwsgi-2.0.23.post0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7455976abfa1dd43b5f3376f7f04a925c16babba1c3fc6edcdd81f5c0f24383"}, + {file = "pyuwsgi-2.0.23.post0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:508f5d84cd677cecc640d0e321badc61080c40c61843cd130b32f356729a599f"}, + {file = "pyuwsgi-2.0.23.post0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcf93afec49f5cf29b0a68f4d2fb3e44a3ad1f205704ab2f41f9db47dacb8e13"}, + {file = "pyuwsgi-2.0.23.post0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a19ab0d5c43bc179a70cb079feb7804e39be6326bf98ec38808fcea5e7d44bd0"}, + {file = "pyuwsgi-2.0.23.post0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8c5283e38c4fd3130cd7384d57535d60435c63b81a41a6463f26f340efeda9de"}, + {file = "pyuwsgi-2.0.23.post0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0d9dfb79bffa552e5985385bc114ecec1d4079b95ce24796f577ef0df727da06"}, + {file = "pyuwsgi-2.0.23.post0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b531ac80155b6c839215d05f95569b34e614e97aab055072c74112b1d2a45546"}, + {file = "pyuwsgi-2.0.23.post0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:eae183104f3fa26f3d9c28fe75f2ad914e3a365103a6a66e329c0f59f9e461d4"}, + {file = "pyuwsgi-2.0.23.post0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a34ab2863ff0120c6e0e75c63c9ced462bfb4777e6b8237e4e1df60fb34af51"}, + {file = "pyuwsgi-2.0.23.post0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc18481f336be63e80fc983aaa1a040e7c69c25c3145edcf93f0e6de2f1ad0d6"}, + {file = "pyuwsgi-2.0.23.post0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:245da016b424c261d148bbb83d2407aac77e6d5793cbd4e23a17f7e3a8aa061f"}, + {file = "pyuwsgi-2.0.23.post0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8de1d975be958cff9122ecc82bf393bf7f41fff6f1047e76ed972047763bbd31"}, + {file = "pyuwsgi-2.0.23.post0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d75859311605a510a6050ec622ec4beb9f2f8cce5f090e5cea70a1ff74133f8b"}, + {file = "pyuwsgi-2.0.23.post0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d3ad00212ffbb208b7146744ad3710b908734f844b5e2bf533fb09fc44726f37"}, + {file = "pyuwsgi-2.0.23.post0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:374142b106de187c4572b4441a367fa3466d9ea5aaabe475da42bb9f2202a690"}, + {file = "pyuwsgi-2.0.23.post0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:137db348bd5f585e8e5a609046d3ac9ef58483bba93de1e3c568c1a860c31b9c"}, + {file = "pyuwsgi-2.0.23.post0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52b7a837dbc8702b245481514a32c88418a42df7b5ee68d45695eba457abd3ee"}, + {file = "pyuwsgi-2.0.23.post0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcfeb1eaca5f4dd0e6ed9194e7ec98dcb3a8ac108e8f0414ed7c28d608517ef"}, + {file = "pyuwsgi-2.0.23.post0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7887c2acc8262223ff9cdce974851da0917818c12ef3ec0f49ec11a9943731fe"}, + {file = "pyuwsgi-2.0.23.post0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bae72689ddf8e0bdd1a974a364ed052dd19d7897f1d5c3efcf8d9010c60f56ef"}, + {file = "pyuwsgi-2.0.23.post0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9565569474f9e9f02f6fa490d96d8c5c7e3004829c01c0446cdb74c618b6a433"}, + {file = "pyuwsgi-2.0.23.post0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6ba86c6aa815635eefe7728b9b219af281a4e956bab240c5871db6c151c300a8"}, + {file = "pyuwsgi-2.0.23.post0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29ab8a02e812fbc34026ddb79f274a574c96fc488f384f320d3af37bd7edf932"}, + {file = "pyuwsgi-2.0.23.post0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4f9c0694a11d8dfbbe2814b8b242a7c4dfa143b63e01447fabce9966a90fa60"}, + {file = "pyuwsgi-2.0.23.post0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f75e45e14462cbb94fc32242378eef7bda97173de57a68a5d46e4053677a7547"}, + {file = "pyuwsgi-2.0.23.post0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e7140fc3548cd9d0f02c4511b679ba47d26593d2cceb249d2d147c9901d90022"}, + {file = "pyuwsgi-2.0.23.post0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ed348cc4c5a4964c8e8fa61ab0ef50c00f7676179a6c0cb0f55f0122db1db1c2"}, + {file = "pyuwsgi-2.0.23.post0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17a8818ec98f92e7935cf0ff56ed4f02a069362e10554df969f70fcdf78d9199"}, + {file = "pyuwsgi-2.0.23.post0.tar.gz", hash = "sha256:04ec79c4a3acad21002ebf1479050c3208605d27cc6659008df51092951eeb8e"}, ] [[package]] @@ -3024,6 +3048,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -3031,8 +3056,16 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -3049,6 +3082,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -3056,6 +3090,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -3671,6 +3706,86 @@ files = [ {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, ] +[[package]] +name = "wrapt" +version = "1.16.0" +description = "Module for decorators, wrappers and monkey patching." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, + {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, + {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, + {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, + {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, + {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, + {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, + {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, + {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, + {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, + {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, + {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, + {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, + {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, + {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, + {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, + {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, +] + [[package]] name = "yamllint" version = "1.33.0" @@ -3712,4 +3827,4 @@ nautobot = ["nautobot"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.12" -content-hash = "8ec4ecf63625568afcbfb1ff021f998969cf00efa320323c380084253a5323f3" \ No newline at end of file +content-hash = "72179cef06ccc57bdf0ca30e13bce1b29d71e245aabd605b319c2e6e423f1a48" diff --git a/pyproject.toml b/pyproject.toml index 6ff8caf8..74f1a861 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,7 +111,7 @@ notes = """, [tool.pylint-nautobot] supported_nautobot_versions = [ - "1.6.8" + "1.6.0" ] [tool.pydocstyle] diff --git a/tasks.py b/tasks.py index cdafbe3b..f4b98cbf 100644 --- a/tasks.py +++ b/tasks.py @@ -46,7 +46,7 @@ def is_truthy(arg): namespace.configure( { "nautobot_design_builder": { - "nautobot_ver": "1.6.8", + "nautobot_ver": "1.6.0", "project_name": "nautobot-design-builder", "python_ver": "3.11", "local": False, From 9973deb29b3a94339eafb864a1280307769f8822 Mon Sep 17 00:00:00 2001 From: Andrew Bates Date: Tue, 23 Jan 2024 08:53:38 -0500 Subject: [PATCH 09/15] docs: New logo --- README.md | 2 +- docs/admin/compatibility_matrix.md | 1 - docs/images/icon-nautobot-design-builder.png | Bin 74601 -> 138203 bytes 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index e77e3149..6279bb05 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Nautobot Design Builder

- +
diff --git a/docs/admin/compatibility_matrix.md b/docs/admin/compatibility_matrix.md index 1d00ccf9..2b638b5f 100644 --- a/docs/admin/compatibility_matrix.md +++ b/docs/admin/compatibility_matrix.md @@ -1,6 +1,5 @@ # Compatibility Matrix - | Nautobot Design Builder Version | Nautobot First Support Version | Nautobot Last Support Version | | ------------- | -------------------- | ------------- | | 1.0.X | 1.6.0 | 2.9999 | diff --git a/docs/images/icon-nautobot-design-builder.png b/docs/images/icon-nautobot-design-builder.png index 7e00cf6ae0ee76324adab30d68d64206678a85e1..9d620744b93801ffe3643a5b08951565150db0ad 100644 GIT binary patch literal 138203 zcmeFYhgXx^^F17jpjV3MReF&kS9+6PR8T-E3Is&DG^MxDn~fq(66qv}Qly0@APH3z z2vunzLMYM`Na!t;-xKfsy#K`eeb!&N<}>n_ZB

1Nx0KK5&vo7eG)z}x^ZvXWHqr7jr?0tq# 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 literal 74601 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

@5hb{hC=~cCz;d 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 From 7813ed6f968b6174aee9328c08ad07fc729bf689 Mon Sep 17 00:00:00 2001 From: Andrew Bates Date: Tue, 23 Jan 2024 11:05:53 -0500 Subject: [PATCH 10/15] refactor: Cruft --- .flake8 | 1 + .github/ISSUE_TEMPLATE/feature_request.md | 1 - development/docker-compose.mysql.yml | 36 ++++++++++++++++++----- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/.flake8 b/.flake8 index c9f5e84d..9875fe18 100644 --- a/.flake8 +++ b/.flake8 @@ -8,3 +8,4 @@ exclude = manage.py, settings.py, .venv + \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 2501eed7..a33f065e 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -19,4 +19,3 @@ about: Propose a new feature or enhancement functionality and describe how. ---> ### Use Case - diff --git a/development/docker-compose.mysql.yml b/development/docker-compose.mysql.yml index 062ada94..bfd1ddcb 100644 --- a/development/docker-compose.mysql.yml +++ b/development/docker-compose.mysql.yml @@ -1,14 +1,31 @@ +# We can't remove volumes in a compose override, for the test configuration using the final containers +# we don't want the volumes so this is the default override file to add the volumes in the dev case +# any override will need to include these volumes to use them. +# see: https://github.com/docker/compose/issues/3729 --- version: "3.8" - services: nautobot: - environment: - - "NAUTOBOT_DB_ENGINE=django.db.backends.mysql" - env_file: - - "development.env" - - "creds.env" - - "development_mysql.env" + command: "nautobot-server runserver 0.0.0.0:8080" + ports: + - "8080:8080" + volumes: + - "./nautobot_config.py:/opt/nautobot/nautobot_config.py" + - "../:/source" + - "../examples/backbone_design/designs:/opt/nautobot/designs:cached" + - "../examples/backbone_design/jobs:/opt/nautobot/jobs:cached" + healthcheck: + test: ["CMD", "true"] # Due to layering, disable: true won't work. Instead, change the test + docs: + entrypoint: "mkdocs serve -v -a 0.0.0.0:8080" + ports: + - "8001:8080" + volumes: + - "../:/source" + image: "nautobot-design-builder/nautobot:${NAUTOBOT_VER}-py${PYTHON_VER}" + healthcheck: + disable: true + tty: true worker: environment: - "NAUTOBOT_DB_ENGINE=django.db.backends.mysql" @@ -26,7 +43,10 @@ services: - "creds.env" - "development_mysql.env" volumes: - - "mysql_data:/var/lib/mysql" + - "./nautobot_config.py:/opt/nautobot/nautobot_config.py" + - "../:/source" + - "../examples/backbone_design/designs:/opt/nautobot/designs:cached" + - "../examples/backbone_design/jobs:/opt/nautobot/jobs:cached" healthcheck: test: - "CMD" From 70e65d8dbca24bf29c9232783650a7d0fcac1100 Mon Sep 17 00:00:00 2001 From: Andrew Bates Date: Tue, 23 Jan 2024 11:32:07 -0500 Subject: [PATCH 11/15] fix: poetry dependency fixes --- .github/workflows/ci.yml | 2 +- poetry.lock | 760 +++++++++++++++------------------------ pyproject.toml | 2 - 3 files changed, 298 insertions(+), 466 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb02bcb1..ec96d1e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -138,7 +138,7 @@ jobs: fail-fast: true matrix: python-version: ["3.11"] - nautobot-version: ["1.6.0"] + nautobot-version: ["1.6.8"] env: INVOKE_NAUTOBOT_DESIGN_BUILDER_PYTHON_VER: "${{ matrix.python-version }}" INVOKE_NAUTOBOT_DESIGN_BUILDER_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" diff --git a/poetry.lock b/poetry.lock index 58a9b455..e197905c 100755 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "amqp" version = "5.2.0" description = "Low-level AMQP client for Python (fork of amqplib)." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -19,7 +18,6 @@ vine = ">=5.0.0,<6.0.0" name = "aniso8601" version = "7.0.0" description = "A library for parsing ISO 8601 strings." -category = "main" optional = false python-versions = "*" files = [ @@ -31,7 +29,6 @@ files = [ name = "appnope" version = "0.1.3" description = "Disable App Nap on macOS >= 10.9" -category = "dev" optional = false python-versions = "*" files = [ @@ -43,7 +40,6 @@ files = [ name = "asgiref" version = "3.7.2" description = "ASGI specs, helper code, and adapters" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -61,7 +57,6 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] name = "astroid" version = "2.15.8" description = "An abstract syntax tree for Python with inference support." -category = "dev" optional = false python-versions = ">=3.7.2" files = [ @@ -81,7 +76,6 @@ wrapt = [ name = "asttokens" version = "2.4.1" description = "Annotate AST trees with source code positions" -category = "dev" optional = false python-versions = "*" files = [ @@ -100,7 +94,6 @@ test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] name = "async-timeout" version = "4.0.3" description = "Timeout context manager for asyncio programs" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -112,7 +105,6 @@ files = [ name = "attrs" version = "23.2.0" description = "Classes Without Boilerplate" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -132,7 +124,6 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p name = "babel" version = "2.14.0" description = "Internationalization utilities" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -150,7 +141,6 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] name = "backcall" version = "0.2.0" description = "Specifications for callback functions passed in to an API" -category = "dev" optional = false python-versions = "*" files = [ @@ -162,7 +152,6 @@ files = [ name = "backports-zoneinfo" version = "0.2.1" description = "Backport of the standard library zoneinfo module" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -194,7 +183,6 @@ tzdata = ["tzdata"] name = "bandit" version = "1.7.6" description = "Security oriented static analyser for python code." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -218,7 +206,6 @@ yaml = ["PyYAML"] name = "beautifulsoup4" version = "4.12.3" description = "Screen-scraping library" -category = "dev" optional = false python-versions = ">=3.6.0" files = [ @@ -240,7 +227,6 @@ lxml = ["lxml"] name = "billiard" version = "4.2.0" description = "Python multiprocessing fork with improvements and bugfixes" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -252,7 +238,6 @@ files = [ name = "black" version = "23.12.1" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -299,7 +284,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "celery" version = "5.3.6" description = "Distributed Task Queue." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -356,7 +340,6 @@ zstd = ["zstandard (==0.22.0)"] name = "certifi" version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -368,7 +351,6 @@ files = [ name = "cffi" version = "1.16.0" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -433,7 +415,6 @@ pycparser = "*" name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -533,7 +514,6 @@ files = [ name = "click" version = "8.1.7" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -548,7 +528,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "click-didyoumean" version = "0.3.0" description = "Enables git-like *did-you-mean* feature in click" -category = "main" optional = false python-versions = ">=3.6.2,<4.0.0" files = [ @@ -563,7 +542,6 @@ click = ">=7" name = "click-plugins" version = "1.1.1" description = "An extension module for click to enable registering CLI commands via setuptools entry-points." -category = "main" optional = false python-versions = "*" files = [ @@ -581,7 +559,6 @@ dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"] name = "click-repl" version = "0.3.0" description = "REPL plugin for Click" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -600,7 +577,6 @@ testing = ["pytest (>=7.2.1)", "pytest-cov (>=4.0.0)", "tox (>=4.4.3)"] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -612,7 +588,6 @@ files = [ name = "coverage" version = "7.4.0" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -677,7 +652,6 @@ toml = ["tomli"] name = "cron-descriptor" version = "1.4.0" description = "A Python library that converts cron expressions into human readable strings." -category = "main" optional = false python-versions = "*" files = [ @@ -689,55 +663,62 @@ dev = ["polib"] [[package]] name = "cryptography" -version = "41.0.7" +version = "42.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, - {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, - {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, - {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, - {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, - {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, - {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, -] - -[package.dependencies] -cffi = ">=1.12" + {file = "cryptography-42.0.0-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:c640b0ef54138fde761ec99a6c7dc4ce05e80420262c20fa239e694ca371d434"}, + {file = "cryptography-42.0.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:678cfa0d1e72ef41d48993a7be75a76b0725d29b820ff3cfd606a5b2b33fda01"}, + {file = "cryptography-42.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:146e971e92a6dd042214b537a726c9750496128453146ab0ee8971a0299dc9bd"}, + {file = "cryptography-42.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87086eae86a700307b544625e3ba11cc600c3c0ef8ab97b0fda0705d6db3d4e3"}, + {file = "cryptography-42.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0a68bfcf57a6887818307600c3c0ebc3f62fbb6ccad2240aa21887cda1f8df1b"}, + {file = "cryptography-42.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5a217bca51f3b91971400890905a9323ad805838ca3fa1e202a01844f485ee87"}, + {file = "cryptography-42.0.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ca20550bb590db16223eb9ccc5852335b48b8f597e2f6f0878bbfd9e7314eb17"}, + {file = "cryptography-42.0.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:33588310b5c886dfb87dba5f013b8d27df7ffd31dc753775342a1e5ab139e59d"}, + {file = "cryptography-42.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9515ea7f596c8092fdc9902627e51b23a75daa2c7815ed5aa8cf4f07469212ec"}, + {file = "cryptography-42.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:35cf6ed4c38f054478a9df14f03c1169bb14bd98f0b1705751079b25e1cb58bc"}, + {file = "cryptography-42.0.0-cp37-abi3-win32.whl", hash = "sha256:8814722cffcfd1fbd91edd9f3451b88a8f26a5fd41b28c1c9193949d1c689dc4"}, + {file = "cryptography-42.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:a2a8d873667e4fd2f34aedab02ba500b824692c6542e017075a2efc38f60a4c0"}, + {file = "cryptography-42.0.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:8fedec73d590fd30c4e3f0d0f4bc961aeca8390c72f3eaa1a0874d180e868ddf"}, + {file = "cryptography-42.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be41b0c7366e5549265adf2145135dca107718fa44b6e418dc7499cfff6b4689"}, + {file = "cryptography-42.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca482ea80626048975360c8e62be3ceb0f11803180b73163acd24bf014133a0"}, + {file = "cryptography-42.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c58115384bdcfe9c7f644c72f10f6f42bed7cf59f7b52fe1bf7ae0a622b3a139"}, + {file = "cryptography-42.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:56ce0c106d5c3fec1038c3cca3d55ac320a5be1b44bf15116732d0bc716979a2"}, + {file = "cryptography-42.0.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:324721d93b998cb7367f1e6897370644751e5580ff9b370c0a50dc60a2003513"}, + {file = "cryptography-42.0.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:d97aae66b7de41cdf5b12087b5509e4e9805ed6f562406dfcf60e8481a9a28f8"}, + {file = "cryptography-42.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:85f759ed59ffd1d0baad296e72780aa62ff8a71f94dc1ab340386a1207d0ea81"}, + {file = "cryptography-42.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:206aaf42e031b93f86ad60f9f5d9da1b09164f25488238ac1dc488334eb5e221"}, + {file = "cryptography-42.0.0-cp39-abi3-win32.whl", hash = "sha256:74f18a4c8ca04134d2052a140322002fef535c99cdbc2a6afc18a8024d5c9d5b"}, + {file = "cryptography-42.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:14e4b909373bc5bf1095311fa0f7fcabf2d1a160ca13f1e9e467be1ac4cbdf94"}, + {file = "cryptography-42.0.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3005166a39b70c8b94455fdbe78d87a444da31ff70de3331cdec2c568cf25b7e"}, + {file = "cryptography-42.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:be14b31eb3a293fc6e6aa2807c8a3224c71426f7c4e3639ccf1a2f3ffd6df8c3"}, + {file = "cryptography-42.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:bd7cf7a8d9f34cc67220f1195884151426ce616fdc8285df9054bfa10135925f"}, + {file = "cryptography-42.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c310767268d88803b653fffe6d6f2f17bb9d49ffceb8d70aed50ad45ea49ab08"}, + {file = "cryptography-42.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bdce70e562c69bb089523e75ef1d9625b7417c6297a76ac27b1b8b1eb51b7d0f"}, + {file = "cryptography-42.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e9326ca78111e4c645f7e49cbce4ed2f3f85e17b61a563328c85a5208cf34440"}, + {file = "cryptography-42.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:69fd009a325cad6fbfd5b04c711a4da563c6c4854fc4c9544bff3088387c77c0"}, + {file = "cryptography-42.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:988b738f56c665366b1e4bfd9045c3efae89ee366ca3839cd5af53eaa1401bce"}, + {file = "cryptography-42.0.0.tar.gz", hash = "sha256:6cf9b76d6e93c62114bd19485e5cb003115c134cf9ce91f8ac924c44f8c8c3f4"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] nox = ["nox"] -pep8test = ["black", "check-sdist", "mypy", "ruff"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] name = "cssselect" version = "1.2.0" description = "cssselect parses CSS3 Selectors and translates them to XPath 1.0" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -749,7 +730,6 @@ files = [ name = "decorator" version = "5.1.1" description = "Decorators for Humans" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -759,21 +739,19 @@ files = [ [[package]] name = "defusedxml" -version = "0.8.0rc2" +version = "0.7.1" description = "XML bomb protection for Python stdlib modules" -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "defusedxml-0.8.0rc2-py2.py3-none-any.whl", hash = "sha256:1c812964311154c3bf4aaf3bc1443b31ee13530b7f255eaaa062c0553c76103d"}, - {file = "defusedxml-0.8.0rc2.tar.gz", hash = "sha256:138c7d540a78775182206c7c97fe65b246a2f40b29471e1a2f1b0da76e7a3942"}, + {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, + {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, ] [[package]] name = "dill" version = "0.3.7" description = "serialize all of Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -788,7 +766,6 @@ graph = ["objgraph (>=1.7.2)"] name = "django" version = "3.2.23" description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -809,7 +786,6 @@ bcrypt = ["bcrypt"] name = "django-ajax-tables" version = "1.1.1" description = "Django tag for ajax-enabled tables" -category = "main" optional = false python-versions = "*" files = [ @@ -817,43 +793,10 @@ files = [ {file = "django_ajax_tables-1.1.1.tar.gz", hash = "sha256:5a7e7bc7940aa6332a564916cde22010a858a3d29fc1090ce8061010ec76337c"}, ] -[[package]] -name = "django-appconf" -version = "1.0.6" -description = "A helper class for handling configuration defaults of packaged apps gracefully." -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "django-appconf-1.0.6.tar.gz", hash = "sha256:cfe87ea827c4ee04b9a70fab90b86d704cb02f2981f89da8423cb0fabf88efbf"}, - {file = "django_appconf-1.0.6-py3-none-any.whl", hash = "sha256:c3ae442fba1ff7ec830412c5184b17169a7a1e71cf0864a4c3f93cf4c98a1993"}, -] - -[package.dependencies] -django = "*" - -[[package]] -name = "django-cacheops" -version = "6.2" -description = "A slick ORM cache with automatic granular event-driven invalidation for Django." -category = "main" -optional = false -python-versions = ">=3.5" -files = [ - {file = "django-cacheops-6.2.tar.gz", hash = "sha256:cc73fd0a1c14799253ff20a8a45791a3c8d2802217b301e70cfa08ae819e438f"}, -] - -[package.dependencies] -django = ">=2.1" -funcy = ">=1.8,<2.0" -redis = ">=3.0.0" -six = ">=1.4.0" - [[package]] name = "django-celery-beat" version = "2.5.0" description = "Database-backed Periodic Tasks." -category = "main" optional = false python-versions = "*" files = [ @@ -870,11 +813,24 @@ django-timezone-field = ">=5.0" python-crontab = ">=2.3.4" tzdata = "*" +[[package]] +name = "django-celery-results" +version = "2.4.0" +description = "Celery result backends for Django." +optional = false +python-versions = "*" +files = [ + {file = "django_celery_results-2.4.0-py3-none-any.whl", hash = "sha256:be91307c02fbbf0dda21993c3001c60edb74595444ccd6ad696552fe3689e85b"}, + {file = "django_celery_results-2.4.0.tar.gz", hash = "sha256:75aa51970db5691cbf242c6a0ff50c8cdf419e265cd0e9b772335d06436c4b99"}, +] + +[package.dependencies] +celery = ">=5.2.3,<6.0" + [[package]] name = "django-constance" version = "2.9.1" description = "Django live settings with pluggable backends, including Redis." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -893,7 +849,6 @@ redis = ["redis"] name = "django-cors-headers" version = "4.2.0" description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -904,27 +859,10 @@ files = [ [package.dependencies] Django = ">=3.2" -[[package]] -name = "django-cryptography" -version = "1.1" -description = "Easily encrypt data in Django" -category = "main" -optional = false -python-versions = ">=3.6" -files = [ - {file = "django_cryptography-1.1-py2.py3-none-any.whl", hash = "sha256:93702fcf0d75865d55362f20ecd95274c4eef60ccdce46cbdade0420acee07cb"}, -] - -[package.dependencies] -cryptography = "*" -Django = "*" -django-appconf = "*" - [[package]] name = "django-db-file-storage" version = "0.5.5" description = "Custom FILE_STORAGE for Django. Saves files in your database instead of your file system." -category = "main" optional = false python-versions = "*" files = [ @@ -938,7 +876,6 @@ Django = "*" name = "django-debug-toolbar" version = "4.2.0" description = "A configurable set of panels that display various debug information about the current request/response." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -954,7 +891,6 @@ sqlparse = ">=0.2" name = "django-extensions" version = "3.2.3" description = "Extensions for Django" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -969,7 +905,6 @@ Django = ">=3.2" name = "django-filter" version = "23.1" description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -984,7 +919,6 @@ Django = ">=3.2" name = "django-health-check" version = "3.17.0" description = "Run checks on services like databases, queue servers, celery processes, etc." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1003,7 +937,6 @@ test = ["celery", "pytest", "pytest-cov", "pytest-django", "redis"] name = "django-jinja" version = "2.10.2" description = "Jinja2 templating language integrated in Django." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1015,47 +948,10 @@ files = [ django = ">=2.2" jinja2 = ">=3" -[[package]] -name = "django-js-asset" -version = "2.2.0" -description = "script tag with additional attributes for django.forms.Media" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "django_js_asset-2.2.0-py3-none-any.whl", hash = "sha256:7ef3e858e13d06f10799b56eea62b1e76706f42cf4e709be4e13356bc0ae30d8"}, - {file = "django_js_asset-2.2.0.tar.gz", hash = "sha256:0c57a82cae2317e83951d956110ce847f58ff0cdc24e314dbc18b35033917e94"}, -] - -[package.dependencies] -django = ">=3.2" - -[package.extras] -tests = ["coverage"] - -[[package]] -name = "django-mptt" -version = "0.14.0" -description = "Utilities for implementing Modified Preorder Tree Traversal with your Django Models and working with trees of Model instances." -category = "main" -optional = false -python-versions = ">=3.6" -files = [ - {file = "django-mptt-0.14.0.tar.gz", hash = "sha256:2c92a2b1614c53086278795ccf50580cf1f9b8564f3ff03055dd62bab5987711"}, - {file = "django_mptt-0.14.0-py3-none-any.whl", hash = "sha256:d9a87433ab0e4f35247c6f6d5a93ace6990860a4ba8796f815d185f773b9acfc"}, -] - -[package.dependencies] -django-js-asset = "*" - -[package.extras] -tests = ["coverage", "mock-django"] - [[package]] name = "django-picklefield" version = "3.1" description = "Pickled object field for Django" -category = "main" optional = false python-versions = ">=3" files = [ @@ -1073,7 +969,6 @@ tests = ["tox"] name = "django-prometheus" version = "2.3.1" description = "Django middlewares to monitor your application with Prometheus.io." -category = "main" optional = false python-versions = "*" files = [ @@ -1088,7 +983,6 @@ prometheus-client = ">=0.7" name = "django-redis" version = "5.3.0" description = "Full featured redis cache backend for Django." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1103,32 +997,10 @@ redis = ">=3,<4.0.0 || >4.0.0,<4.0.1 || >4.0.1" [package.extras] hiredis = ["redis[hiredis] (>=3,!=4.0.0,!=4.0.1)"] -[[package]] -name = "django-rq" -version = "2.8.1" -description = "An app that provides django integration for RQ (Redis Queue)" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "django-rq-2.8.1.tar.gz", hash = "sha256:ff053aa4d1b1e1acc47c99b4a21b514de8745894c00d1e6f4abc8b37d35d66d6"}, - {file = "django_rq-2.8.1-py2.py3-none-any.whl", hash = "sha256:f5d649dc57b5564011460b2b69c8a60a4f5f10ee8692b51d1dfc17035b1039b8"}, -] - -[package.dependencies] -django = ">=2.0" -redis = ">=3" -rq = ">=1.14" - -[package.extras] -sentry = ["raven (>=6.1.0)"] -testing = ["mock (>=2.0.0)"] - [[package]] name = "django-tables2" version = "2.6.0" description = "Table/data-grid framework for Django" -category = "main" optional = false python-versions = "*" files = [ @@ -1146,7 +1018,6 @@ tablib = ["tablib"] name = "django-taggit" version = "4.0.0" description = "django-taggit is a reusable Django application for simple tagging." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1161,7 +1032,6 @@ Django = ">=3.2" name = "django-timezone-field" version = "5.1" description = "A Django app providing DB, form, and REST framework fields for zoneinfo and pytz timezone objects." -category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -1171,19 +1041,18 @@ files = [ [package.dependencies] "backports.zoneinfo" = {version = ">=0.2.1,<0.3.0", markers = "python_version < \"3.9\""} -Django = ">=2.2,<3.0.0 || >=3.2.0,<5.0" +Django = ">=2.2,<3.0.dev0 || >=3.2.dev0,<5.0" pytz = "*" [[package]] name = "django-tree-queries" -version = "0.15.0" +version = "0.16.1" description = "Tree queries with explicit opt-in, without configurability" -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "django_tree_queries-0.15.0-py3-none-any.whl", hash = "sha256:cf11340de59d3122919fde46e99966bad40ff942df768d683383b111554134a1"}, - {file = "django_tree_queries-0.15.0.tar.gz", hash = "sha256:0e994c2a4601c021a115a397ec8d0ff7d5e614fae95947f72126e6a419c60f08"}, + {file = "django_tree_queries-0.16.1-py3-none-any.whl", hash = "sha256:b57cebd85136897dc2d7d1da50f3944b13d4713009af655ae221c8202146c2f5"}, + {file = "django_tree_queries-0.16.1.tar.gz", hash = "sha256:5a7765bdbc78742ae7b206348aa674a7e39ef38069ac3854a51b330d25081c43"}, ] [package.extras] @@ -1193,7 +1062,6 @@ tests = ["coverage"] name = "django-webserver" version = "1.2.0" description = "Django management commands for production webservers" -category = "main" optional = false python-versions = "*" files = [ @@ -1215,7 +1083,6 @@ waitress = ["waitress"] name = "djangorestframework" version = "3.14.0" description = "Web APIs for Django, made easy." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1227,16 +1094,29 @@ files = [ django = ">=3.0" pytz = "*" +[[package]] +name = "drf-react-template-framework" +version = "0.0.17" +description = "Django REST Framework plugin that creates form schemas for react-jsonschema-form" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "drf-react-template-framework-0.0.17.tar.gz", hash = "sha256:25b115981528977fa703fb2a9b354f3874fff82830b56fc4c7269b287a0a9580"}, + {file = "drf_react_template_framework-0.0.17-py3-none-any.whl", hash = "sha256:d8116b0c03459574a3b0f2885ce80702127f49fc66960d50deef1c7a35151593"}, +] + +[package.dependencies] +djangorestframework = ">=3.12.0,<4.0.0" + [[package]] name = "drf-spectacular" -version = "0.26.5" +version = "0.26.3" description = "Sane and flexible OpenAPI 3 schema generation for Django REST framework" -category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "drf-spectacular-0.26.5.tar.gz", hash = "sha256:aee55330a774ba8a9cbdb125714d1c9ee05a8aafd3ce3be8bfd26527649aeb44"}, - {file = "drf_spectacular-0.26.5-py3-none-any.whl", hash = "sha256:c0002a820b11771fdbf37853deb371947caf0159d1afeeffe7598e964bc1db94"}, + {file = "drf-spectacular-0.26.3.tar.gz", hash = "sha256:b907a72a0244e5dcfeca625e9632cd8ebccdbe2cb528b7c1de1191708be6f31e"}, + {file = "drf_spectacular-0.26.3-py3-none-any.whl", hash = "sha256:1d84ac70522baaadd6d84a25ce5fe5ea50cfcba0387856689f98ac536f14aa32"}, ] [package.dependencies] @@ -1256,7 +1136,6 @@ sidecar = ["drf-spectacular-sidecar"] name = "drf-spectacular-sidecar" version = "2024.1.1" description = "Serve self-contained distribution builds of Swagger UI and Redoc with Django" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1268,36 +1147,23 @@ files = [ Django = ">=2.2" [[package]] -name = "drf-yasg" -version = "1.21.7" -description = "Automated generation of real Swagger/OpenAPI 2.0 schemas from Django Rest Framework code." -category = "main" +name = "emoji" +version = "2.8.0" +description = "Emoji for Python" optional = false -python-versions = ">=3.6" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "drf-yasg-1.21.7.tar.gz", hash = "sha256:4c3b93068b3dfca6969ab111155e4dd6f7b2d680b98778de8fd460b7837bdb0d"}, - {file = "drf_yasg-1.21.7-py3-none-any.whl", hash = "sha256:f85642072c35e684356475781b7ecf5d218fff2c6185c040664dd49f0a4be181"}, + {file = "emoji-2.8.0-py2.py3-none-any.whl", hash = "sha256:a8468fd836b7ecb6d1eac054c9a591701ce0ccd6c6f7779ad71b66f76664df90"}, + {file = "emoji-2.8.0.tar.gz", hash = "sha256:8d8b5dec3c507444b58890e598fc895fcec022b3f5acb49497c6ccc5208b8b00"}, ] -[package.dependencies] -django = ">=2.2.16" -djangorestframework = ">=3.10.3" -inflection = ">=0.3.1" -packaging = ">=21.0" -pytz = ">=2021.1" -pyyaml = ">=5.1" -swagger-spec-validator = {version = ">=2.1.0", optional = true, markers = "extra == \"validation\""} -uritemplate = ">=3.0.0" - [package.extras] -coreapi = ["coreapi (>=2.3.3)", "coreschema (>=0.0.4)"] -validation = ["swagger-spec-validator (>=2.1.0)"] +dev = ["coverage", "coveralls", "pytest"] [[package]] name = "executing" version = "2.0.1" description = "Get the currently executing AST node of a frame, and other information" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1312,7 +1178,6 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth name = "flake8" version = "5.0.4" description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" optional = false python-versions = ">=3.6.1" files = [ @@ -1325,23 +1190,10 @@ mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.9.0,<2.10.0" pyflakes = ">=2.5.0,<2.6.0" -[[package]] -name = "funcy" -version = "1.18" -description = "A fancy and practical functional tools" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "funcy-1.18-py2.py3-none-any.whl", hash = "sha256:00ce91afc850357a131dc54f0db2ad8a1110d5087f1fa4480d7ea3ba0249f89d"}, - {file = "funcy-1.18.tar.gz", hash = "sha256:15448d19a8ebcc7a585afe7a384a19186d0bd67cbf56fb42cd1fd0f76313f9b2"}, -] - [[package]] name = "ghp-import" version = "2.1.0" description = "Copy your docs directly to the gh-pages branch." -category = "dev" optional = false python-versions = "*" files = [ @@ -1359,7 +1211,6 @@ dev = ["flake8", "markdown", "twine", "wheel"] name = "gitdb" version = "4.0.11" description = "Git Object Database" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1374,7 +1225,6 @@ smmap = ">=3.0.1,<6" name = "gitpython" version = "3.1.41" description = "GitPython is a Python library used to interact with Git repositories" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1392,7 +1242,6 @@ test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre name = "graphene" version = "2.1.9" description = "GraphQL Framework for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -1415,7 +1264,6 @@ test = ["coveralls", "fastdiff (==0.2.0)", "iso8601", "mock", "promise", "pytest name = "graphene-django" version = "2.16.0" description = "Graphene Django integration" -category = "main" optional = false python-versions = "*" files = [ @@ -1440,7 +1288,6 @@ test = ["coveralls", "django-filter (>=2)", "djangorestframework (>=3.6.3)", "mo name = "graphene-django-optimizer" version = "0.8.0" description = "Optimize database access inside graphene queries." -category = "main" optional = false python-versions = "*" files = [ @@ -1451,7 +1298,6 @@ files = [ name = "graphql-core" version = "2.3.2" description = "GraphQL implementation for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -1472,7 +1318,6 @@ test = ["coveralls (==1.11.1)", "cython (==0.29.17)", "gevent (==1.5.0)", "pyann name = "graphql-relay" version = "2.0.1" description = "Relay implementation for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -1489,7 +1334,6 @@ six = ">=1.12" name = "griffe" version = "0.39.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." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1504,7 +1348,6 @@ colorama = ">=0.4" name = "idna" version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1516,7 +1359,6 @@ files = [ name = "importlib-metadata" version = "7.0.1" description = "Read metadata from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1536,7 +1378,6 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "importlib-resources" version = "5.13.0" description = "Read resources from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1555,7 +1396,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "inflection" version = "0.5.1" description = "A port of Ruby on Rails inflector to Python" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1567,7 +1407,6 @@ files = [ name = "invoke" version = "2.2.0" description = "Pythonic task execution" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1579,7 +1418,6 @@ files = [ name = "ipython" version = "8.12.3" description = "IPython: Productive Interactive Computing" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1619,7 +1457,6 @@ test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pa name = "isort" version = "5.13.2" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -1634,7 +1471,6 @@ colors = ["colorama (>=0.4.6)"] name = "jedi" version = "0.19.1" description = "An autocompletion tool for Python that can be used for text editors." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1654,7 +1490,6 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] name = "jinja2" version = "3.1.3" description = "A very fast and expressive template engine." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1670,31 +1505,46 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "jsonschema" -version = "4.17.3" +version = "4.18.6" description = "An implementation of JSON Schema validation for Python" -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "jsonschema-4.17.3-py3-none-any.whl", hash = "sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6"}, - {file = "jsonschema-4.17.3.tar.gz", hash = "sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d"}, + {file = "jsonschema-4.18.6-py3-none-any.whl", hash = "sha256:dc274409c36175aad949c68e5ead0853aaffbe8e88c830ae66bb3c7a1728ad2d"}, + {file = "jsonschema-4.18.6.tar.gz", hash = "sha256:ce71d2f8c7983ef75a756e568317bf54bc531dc3ad7e66a128eae0d51623d8a3"}, ] [package.dependencies] -attrs = ">=17.4.0" +attrs = ">=22.2.0" importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} +jsonschema-specifications = ">=2023.03.6" pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""} -pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2" +referencing = ">=0.28.4" +rpds-py = ">=0.7.1" [package.extras] format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] +[[package]] +name = "jsonschema-specifications" +version = "2023.12.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, + {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, +] + +[package.dependencies] +importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} +referencing = ">=0.31.0" + [[package]] name = "kombu" version = "5.3.5" description = "Messaging library for Python." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1729,7 +1579,6 @@ zookeeper = ["kazoo (>=2.8.0)"] name = "lazy-object-proxy" version = "1.10.0" description = "A fast and thorough lazy object proxy." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1776,7 +1625,6 @@ files = [ name = "lxml" version = "5.1.0" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1870,7 +1718,6 @@ source = ["Cython (>=3.0.7)"] name = "markdown" version = "3.3.7" description = "Python implementation of Markdown." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1888,7 +1735,6 @@ testing = ["coverage", "pyyaml"] name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1913,7 +1759,6 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "markdown2" version = "2.4.12" description = "A fast and complete Python implementation of Markdown" -category = "dev" optional = false python-versions = ">=3.5, <4" files = [ @@ -1930,7 +1775,6 @@ wavedrom = ["wavedrom"] name = "markupsafe" version = "2.1.4" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2000,7 +1844,6 @@ files = [ name = "matplotlib-inline" version = "0.1.6" description = "Inline Matplotlib backend for Jupyter" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -2015,7 +1858,6 @@ traitlets = "*" name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2027,7 +1869,6 @@ files = [ name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2039,7 +1880,6 @@ files = [ name = "mergedeep" version = "1.3.4" description = "A deep merge function for 🐍." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2051,7 +1891,6 @@ files = [ name = "mkdocs" version = "1.5.2" description = "Project documentation with Markdown." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2083,7 +1922,6 @@ min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-imp name = "mkdocs-autorefs" version = "0.5.0" description = "Automatically link across pages in MkDocs." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2099,7 +1937,6 @@ mkdocs = ">=1.1" name = "mkdocs-material" version = "9.2.4" description = "Documentation that simply works" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2126,7 +1963,6 @@ requests = ">=2.26" name = "mkdocs-material-extensions" version = "1.3.1" description = "Extension pack for Python Markdown and MkDocs Material." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2138,7 +1974,6 @@ files = [ name = "mkdocs-version-annotations" version = "1.0.0" description = "MkDocs plugin to add custom admonitions for documenting version differences" -category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -2150,7 +1985,6 @@ files = [ name = "mkdocstrings" version = "0.22.0" description = "Automatic documentation from sources, for MkDocs." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2177,7 +2011,6 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] name = "mkdocstrings-python" version = "1.5.2" description = "A Python handler for mkdocstrings." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2193,7 +2026,6 @@ mkdocstrings = ">=0.20" name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -2203,60 +2035,58 @@ files = [ [[package]] name = "nautobot" -version = "1.6.9" +version = "2.1.2" description = "Source of truth and network automation platform." -category = "main" optional = false python-versions = ">=3.8,<3.12" files = [ - {file = "nautobot-1.6.9-py3-none-any.whl", hash = "sha256:f011bb7d91f4aff24b8bf8903b37eea8b4826ae1b34e41a75397601b5d917831"}, - {file = "nautobot-1.6.9.tar.gz", hash = "sha256:08943c35036448b32653b054ff5adeea217a48ada645229b53d6608eb5b17c36"}, + {file = "nautobot-2.1.2-py3-none-any.whl", hash = "sha256:13fffb9ff7bf6dbee0df492256bc37060bea4229d71461b0b7447839bc35873a"}, + {file = "nautobot-2.1.2.tar.gz", hash = "sha256:185c1a1556c77f6ed5f2c9ed82aeea1f2b385b0ea2ceb480c78a6dbec8ef07d1"}, ] [package.dependencies] celery = ">=5.3.1,<5.4.0" Django = ">=3.2.23,<3.3.0" django-ajax-tables = ">=1.1.1,<1.2.0" -django-cacheops = ">=6.2,<6.3" django-celery-beat = ">=2.5.0,<2.6.0" +django-celery-results = ">=2.4.0,<2.5.0" django-constance = {version = ">=2.9.1,<2.10.0", extras = ["database"]} django-cors-headers = ">=4.2.0,<4.3.0" -django-cryptography = ">=1.1,<1.2" django-db-file-storage = ">=0.5.5,<0.6.0" django-extensions = ">=3.2.3,<3.3.0" django-filter = ">=23.1,<23.2" django-health-check = ">=3.17.0,<3.18.0" django-jinja = ">=2.10.2,<2.11.0" -django-mptt = ">=0.14.0,<0.15.0" django-prometheus = ">=2.3.1,<2.4.0" django-redis = ">=5.3.0,<5.4.0" -django-rq = ">=2.8.1,<2.9.0" django-tables2 = ">=2.6.0,<2.7.0" django-taggit = ">=4.0.0,<4.1.0" django-timezone-field = ">=5.1,<5.2" -django-tree-queries = ">=0.15.0,<0.16.0" +django-tree-queries = ">=0.16.1,<0.17.0" django-webserver = ">=1.2.0,<1.3.0" djangorestframework = ">=3.14.0,<3.15.0" -drf-spectacular = {version = ">=0.26.4,<0.27.0", extras = ["sidecar"]} -drf-yasg = {version = ">=1.20.0,<2.0.0", extras = ["validation"]} -GitPython = ">=3.1.36,<3.2.0" +drf-react-template-framework = ">=0.0.17,<0.0.18" +drf-spectacular = {version = "0.26.3", extras = ["sidecar"]} +emoji = ">=2.8.0,<2.9.0" +GitPython = ">=3.1.41,<3.2.0" graphene-django = ">=2.16.0,<2.17.0" graphene-django-optimizer = ">=0.8.0,<0.9.0" -Jinja2 = ">=3.1.2,<3.2.0" -jsonschema = ">=4.7.0,<4.18.0" +Jinja2 = ">=3.1.3,<3.2.0" +jsonschema = ">=4.7.0,<4.19.0" Markdown = ">=3.3.7,<3.4.0" MarkupSafe = ">=2.1.3,<2.2.0" netaddr = ">=0.8.0,<0.9.0" -netutils = ">=1.5.0,<2.0.0" -packaging = ">=23.0,<23.2" +netutils = ">=1.6.0,<2.0.0" +nh3 = ">=0.2.15,<0.3.0" +packaging = ">=23.1" Pillow = ">=10.0.0,<10.1.0" -prometheus-client = ">=0.14.1,<0.18" -psycopg2-binary = ">=2.9.6,<2.10.0" +prometheus-client = ">=0.17.1,<0.18.0" +psycopg2-binary = ">=2.9.9,<2.10.0" +python-slugify = ">=8.0.1,<8.1.0" pyuwsgi = ">=2.0.21,<2.1.0" PyYAML = ">=6.0,<6.1" social-auth-app-django = ">=5.2.0,<5.3.0" svgwrite = ">=1.4.2,<1.5.0" -toml = ">=0.10.2,<0.11.0" [package.extras] all = ["django-auth-ldap (>=4.3.0,<4.4.0)", "django-storages (>=1.13.2,<1.14.0)", "mysqlclient (>=2.2.0,<2.3.0)", "napalm (>=4.1.0,<4.2.0)", "social-auth-core[openidconnect,saml] (>=4.4.2,<4.5.0)"] @@ -2268,24 +2098,22 @@ sso = ["social-auth-core[openidconnect,saml] (>=4.4.2,<4.5.0)"] [[package]] name = "nautobot-bgp-models" -version = "0.7.1" +version = "0.20.1" description = "Nautobot BGP Models Plugin" -category = "dev" optional = false -python-versions = ">=3.7,<4.0" +python-versions = ">=3.8,<3.12" files = [ - {file = "nautobot_bgp_models-0.7.1-py3-none-any.whl", hash = "sha256:6a480b64ed1fc565f889bc4baf4f4c176b370b83b8bc34ecc53e3100cc14e19f"}, - {file = "nautobot_bgp_models-0.7.1.tar.gz", hash = "sha256:3fa1e6ab2eefa66807d04306181ace74567096b473fee6fd9452d6d997470814"}, + {file = "nautobot_bgp_models-0.20.1-py3-none-any.whl", hash = "sha256:d670a80aa5073cb11a7d560d9282ffa1e7cc2a1810702514793ce846225fafdd"}, + {file = "nautobot_bgp_models-0.20.1.tar.gz", hash = "sha256:ca78171f6e91a946f9ba075a87704494ddbd4d65c386d7db2f841628b29c3552"}, ] [package.dependencies] -nautobot = ">=1.5.4,<2.0.0" +nautobot = ">=2.0.0,<3.0.0" [[package]] name = "netaddr" version = "0.8.0" description = "A network address manipulation library for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -2297,7 +2125,6 @@ files = [ name = "netutils" version = "1.6.0" description = "Common helper functions useful in network automation." -category = "main" optional = false python-versions = ">=3.8,<4.0" files = [ @@ -2308,11 +2135,35 @@ files = [ [package.extras] optionals = ["jsonschema (>=4.17.3,<5.0.0)", "napalm (>=4.0.0,<5.0.0)"] +[[package]] +name = "nh3" +version = "0.2.15" +description = "Python bindings to the ammonia HTML sanitization library." +optional = false +python-versions = "*" +files = [ + {file = "nh3-0.2.15-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:9c0d415f6b7f2338f93035bba5c0d8c1b464e538bfbb1d598acd47d7969284f0"}, + {file = "nh3-0.2.15-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6f42f99f0cf6312e470b6c09e04da31f9abaadcd3eb591d7d1a88ea931dca7f3"}, + {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac19c0d68cd42ecd7ead91a3a032fdfff23d29302dbb1311e641a130dfefba97"}, + {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0d77272ce6d34db6c87b4f894f037d55183d9518f948bba236fe81e2bb4e28"}, + {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8d595df02413aa38586c24811237e95937ef18304e108b7e92c890a06793e3bf"}, + {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86e447a63ca0b16318deb62498db4f76fc60699ce0a1231262880b38b6cff911"}, + {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3277481293b868b2715907310c7be0f1b9d10491d5adf9fce11756a97e97eddf"}, + {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60684857cfa8fdbb74daa867e5cad3f0c9789415aba660614fe16cd66cbb9ec7"}, + {file = "nh3-0.2.15-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3b803a5875e7234907f7d64777dfde2b93db992376f3d6d7af7f3bc347deb305"}, + {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0d02d0ff79dfd8208ed25a39c12cbda092388fff7f1662466e27d97ad011b770"}, + {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f3b53ba93bb7725acab1e030bc2ecd012a817040fd7851b332f86e2f9bb98dc6"}, + {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:b1e97221cedaf15a54f5243f2c5894bb12ca951ae4ddfd02a9d4ea9df9e1a29d"}, + {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5167a6403d19c515217b6bcaaa9be420974a6ac30e0da9e84d4fc67a5d474c5"}, + {file = "nh3-0.2.15-cp37-abi3-win32.whl", hash = "sha256:427fecbb1031db085eaac9931362adf4a796428ef0163070c484b5a768e71601"}, + {file = "nh3-0.2.15-cp37-abi3-win_amd64.whl", hash = "sha256:bc2d086fb540d0fa52ce35afaded4ea526b8fc4d3339f783db55c95de40ef02e"}, + {file = "nh3-0.2.15.tar.gz", hash = "sha256:d1e30ff2d8d58fb2a14961f7aac1bbb1c51f9bdd7da727be35c63826060b0bf3"}, +] + [[package]] name = "oauthlib" version = "3.2.2" description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2327,21 +2178,19 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] [[package]] name = "packaging" -version = "23.1" +version = "23.2" description = "Core utilities for Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] name = "paginate" version = "0.5.6" description = "Divides large result sets into pages for easier browsing" -category = "dev" optional = false python-versions = "*" files = [ @@ -2352,7 +2201,6 @@ files = [ name = "parso" version = "0.8.3" description = "A Python Parser" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2368,7 +2216,6 @@ testing = ["docopt", "pytest (<6.0.0)"] name = "pathspec" version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2380,7 +2227,6 @@ files = [ name = "pbr" version = "6.0.0" description = "Python Build Reasonableness" -category = "dev" optional = false python-versions = ">=2.6" files = [ @@ -2392,7 +2238,6 @@ files = [ name = "pexpect" version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." -category = "dev" optional = false python-versions = "*" files = [ @@ -2407,7 +2252,6 @@ ptyprocess = ">=0.5" name = "pickleshare" version = "0.7.5" description = "Tiny 'shelve'-like database with concurrency support" -category = "dev" optional = false python-versions = "*" files = [ @@ -2419,7 +2263,6 @@ files = [ name = "pillow" version = "10.0.1" description = "Python Imaging Library (Fork)" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2487,7 +2330,6 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa name = "pkgutil-resolve-name" version = "1.3.10" description = "Resolve a name to an object." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2499,7 +2341,6 @@ files = [ name = "platformdirs" version = "4.1.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2515,7 +2356,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co name = "prometheus-client" version = "0.17.1" description = "Python client for the Prometheus monitoring system." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2530,7 +2370,6 @@ twisted = ["twisted"] name = "promise" version = "2.3" description = "Promises/A+ implementation for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -2547,7 +2386,6 @@ test = ["coveralls", "futures", "mock", "pytest (>=2.7.3)", "pytest-benchmark", name = "prompt-toolkit" version = "3.0.43" description = "Library for building powerful interactive command lines in Python" -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -2562,7 +2400,6 @@ wcwidth = "*" name = "psycopg2-binary" version = "2.9.9" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2592,7 +2429,6 @@ files = [ {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, @@ -2601,8 +2437,6 @@ files = [ {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"}, {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, @@ -2644,7 +2478,6 @@ files = [ name = "ptyprocess" version = "0.7.0" description = "Run a subprocess in a pseudo terminal" -category = "dev" optional = false python-versions = "*" files = [ @@ -2656,7 +2489,6 @@ files = [ name = "pure-eval" version = "0.2.2" description = "Safely evaluate AST nodes without side effects" -category = "dev" optional = false python-versions = "*" files = [ @@ -2671,7 +2503,6 @@ tests = ["pytest"] name = "pycodestyle" version = "2.9.1" description = "Python style guide checker" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2683,7 +2514,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2695,7 +2525,6 @@ files = [ name = "pydocstyle" version = "6.3.0" description = "Python docstring style checker" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2713,7 +2542,6 @@ toml = ["tomli (>=1.2.3)"] name = "pyflakes" version = "2.5.0" description = "passive checker of Python programs" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2725,7 +2553,6 @@ files = [ name = "pygments" version = "2.17.2" description = "Pygments is a syntax highlighting package written in Python." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2741,7 +2568,6 @@ windows-terminal = ["colorama (>=0.4.6)"] name = "pyjwt" version = "2.8.0" description = "JSON Web Token implementation in Python" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2759,7 +2585,6 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] name = "pylint" version = "2.17.7" description = "python code static checker" -category = "dev" optional = false python-versions = ">=3.7.2" files = [ @@ -2789,7 +2614,6 @@ testutils = ["gitpython (>3)"] name = "pylint-django" version = "2.5.5" description = "A Pylint plugin to help Pylint understand the Django web framework" -category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -2808,7 +2632,6 @@ with-django = ["Django (>=2.2)"] name = "pylint-nautobot" version = "0.2.1" description = "Custom Pylint Rules for Nautobot" -category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -2826,7 +2649,6 @@ tomli = ">=2.0.1,<3.0.0" name = "pylint-plugin-utils" version = "0.8.2" description = "Utilities and helpers for writing Pylint plugins" -category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -2841,7 +2663,6 @@ pylint = ">=1.7" name = "pymdown-extensions" version = "10.4" description = "Extension pack for Python Markdown." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2860,7 +2681,6 @@ extra = ["pygments (>=2.12)"] name = "pyquery" version = "2.0.0" description = "A jquery-like library for python" -category = "dev" optional = false python-versions = "*" files = [ @@ -2875,53 +2695,10 @@ lxml = ">=2.1" [package.extras] test = ["pytest", "pytest-cov", "requests", "webob", "webtest"] -[[package]] -name = "pyrsistent" -version = "0.20.0" -description = "Persistent/Functional/Immutable data structures" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyrsistent-0.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c3aba3e01235221e5b229a6c05f585f344734bd1ad42a8ac51493d74722bbce"}, - {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1beb78af5423b879edaf23c5591ff292cf7c33979734c99aa66d5914ead880f"}, - {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21cc459636983764e692b9eba7144cdd54fdec23ccdb1e8ba392a63666c60c34"}, - {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5ac696f02b3fc01a710427585c855f65cd9c640e14f52abe52020722bb4906b"}, - {file = "pyrsistent-0.20.0-cp310-cp310-win32.whl", hash = "sha256:0724c506cd8b63c69c7f883cc233aac948c1ea946ea95996ad8b1380c25e1d3f"}, - {file = "pyrsistent-0.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:8441cf9616d642c475684d6cf2520dd24812e996ba9af15e606df5f6fd9d04a7"}, - {file = "pyrsistent-0.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0f3b1bcaa1f0629c978b355a7c37acd58907390149b7311b5db1b37648eb6958"}, - {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cdd7ef1ea7a491ae70d826b6cc64868de09a1d5ff9ef8d574250d0940e275b8"}, - {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cae40a9e3ce178415040a0383f00e8d68b569e97f31928a3a8ad37e3fde6df6a"}, - {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6288b3fa6622ad8a91e6eb759cfc48ff3089e7c17fb1d4c59a919769314af224"}, - {file = "pyrsistent-0.20.0-cp311-cp311-win32.whl", hash = "sha256:7d29c23bdf6e5438c755b941cef867ec2a4a172ceb9f50553b6ed70d50dfd656"}, - {file = "pyrsistent-0.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:59a89bccd615551391f3237e00006a26bcf98a4d18623a19909a2c48b8e986ee"}, - {file = "pyrsistent-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:09848306523a3aba463c4b49493a760e7a6ca52e4826aa100ee99d8d39b7ad1e"}, - {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a14798c3005ec892bbada26485c2eea3b54109cb2533713e355c806891f63c5e"}, - {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b14decb628fac50db5e02ee5a35a9c0772d20277824cfe845c8a8b717c15daa3"}, - {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e2c116cc804d9b09ce9814d17df5edf1df0c624aba3b43bc1ad90411487036d"}, - {file = "pyrsistent-0.20.0-cp312-cp312-win32.whl", hash = "sha256:e78d0c7c1e99a4a45c99143900ea0546025e41bb59ebc10182e947cf1ece9174"}, - {file = "pyrsistent-0.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:4021a7f963d88ccd15b523787d18ed5e5269ce57aa4037146a2377ff607ae87d"}, - {file = "pyrsistent-0.20.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:79ed12ba79935adaac1664fd7e0e585a22caa539dfc9b7c7c6d5ebf91fb89054"}, - {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f920385a11207dc372a028b3f1e1038bb244b3ec38d448e6d8e43c6b3ba20e98"}, - {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f5c2d012671b7391803263419e31b5c7c21e7c95c8760d7fc35602353dee714"}, - {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef3992833fbd686ee783590639f4b8343a57f1f75de8633749d984dc0eb16c86"}, - {file = "pyrsistent-0.20.0-cp38-cp38-win32.whl", hash = "sha256:881bbea27bbd32d37eb24dd320a5e745a2a5b092a17f6debc1349252fac85423"}, - {file = "pyrsistent-0.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:6d270ec9dd33cdb13f4d62c95c1a5a50e6b7cdd86302b494217137f760495b9d"}, - {file = "pyrsistent-0.20.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ca52d1ceae015859d16aded12584c59eb3825f7b50c6cfd621d4231a6cc624ce"}, - {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b318ca24db0f0518630e8b6f3831e9cba78f099ed5c1d65ffe3e023003043ba0"}, - {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed2c3216a605dc9a6ea50c7e84c82906e3684c4e80d2908208f662a6cbf9022"}, - {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e14c95c16211d166f59c6611533d0dacce2e25de0f76e4c140fde250997b3ca"}, - {file = "pyrsistent-0.20.0-cp39-cp39-win32.whl", hash = "sha256:f058a615031eea4ef94ead6456f5ec2026c19fb5bd6bfe86e9665c4158cf802f"}, - {file = "pyrsistent-0.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:58b8f6366e152092194ae68fefe18b9f0b4f89227dfd86a07770c3d86097aebf"}, - {file = "pyrsistent-0.20.0-py3-none-any.whl", hash = "sha256:c55acc4733aad6560a7f5f818466631f07efc001fd023f34a6c203f8b6df0f0b"}, - {file = "pyrsistent-0.20.0.tar.gz", hash = "sha256:4c48f78f62ab596c679086084d0dd13254ae4f3d6c72a83ffdf5ebdef8f265a4"}, -] - [[package]] name = "python-crontab" version = "3.0.0" description = "Python Crontab API" -category = "main" optional = false python-versions = "*" files = [ @@ -2940,7 +2717,6 @@ cron-schedule = ["croniter"] name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -2951,11 +2727,27 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "python-slugify" +version = "8.0.1" +description = "A Python slugify application that also handles Unicode" +optional = false +python-versions = ">=3.7" +files = [ + {file = "python-slugify-8.0.1.tar.gz", hash = "sha256:ce0d46ddb668b3be82f4ed5e503dbc33dd815d83e2eb6824211310d3fb172a27"}, + {file = "python_slugify-8.0.1-py2.py3-none-any.whl", hash = "sha256:70ca6ea68fe63ecc8fa4fcf00ae651fc8a5d02d93dcd12ae6d4fc7ca46c4d395"}, +] + +[package.dependencies] +text-unidecode = ">=1.3" + +[package.extras] +unidecode = ["Unidecode (>=1.1.1)"] + [[package]] name = "python3-openid" version = "3.2.0" description = "OpenID support for modern servers and consumers." -category = "main" optional = false python-versions = "*" files = [ @@ -2974,7 +2766,6 @@ postgresql = ["psycopg2"] name = "pytz" version = "2023.3.post1" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -2986,7 +2777,6 @@ files = [ name = "pyuwsgi" version = "2.0.23.post0" description = "The uWSGI server" -category = "main" optional = false python-versions = "*" files = [ @@ -3039,7 +2829,6 @@ files = [ name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3048,7 +2837,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -3056,16 +2844,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -3082,7 +2862,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -3090,7 +2869,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -3100,7 +2878,6 @@ files = [ name = "pyyaml-env-tag" version = "0.1" description = "A custom YAML tag for referencing environment variables in YAML files. " -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3115,7 +2892,6 @@ pyyaml = "*" name = "readtime" version = "3.0.0" description = "Calculates the time some text takes the average human to read, based on Medium's read time forumula" -category = "dev" optional = false python-versions = "*" files = [ @@ -3131,7 +2907,6 @@ pyquery = ">=1.2" name = "redis" version = "5.0.1" description = "Python client for Redis database and key-value store" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3146,11 +2921,25 @@ async-timeout = {version = ">=4.0.2", markers = "python_full_version <= \"3.11.2 hiredis = ["hiredis (>=1.0.0)"] ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] +[[package]] +name = "referencing" +version = "0.32.1" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "referencing-0.32.1-py3-none-any.whl", hash = "sha256:7e4dc12271d8e15612bfe35792f5ea1c40970dadf8624602e33db2758f7ee554"}, + {file = "referencing-0.32.1.tar.gz", hash = "sha256:3c57da0513e9563eb7e203ebe9bb3a1b509b042016433bd1e45a2853466c3dd3"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" + [[package]] name = "regex" version = "2023.12.25" description = "Alternative regular expression module, to replace re." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3253,7 +3042,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3275,7 +3063,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "requests-oauthlib" version = "1.3.1" description = "OAuthlib authentication support for Requests." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -3294,7 +3081,6 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"] name = "rich" version = "13.7.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -3311,26 +3097,117 @@ typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9 jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] -name = "rq" -version = "1.15.1" -description = "RQ is a simple, lightweight, library for creating background jobs, and processing them." -category = "main" +name = "rpds-py" +version = "0.17.1" +description = "Python bindings to Rust's persistent data structures (rpds)" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "rq-1.15.1-py2.py3-none-any.whl", hash = "sha256:6e243d8d9c4af4686ded4b01b25ea1ff4bac4fc260b02638fbe9c8c17b004bd1"}, - {file = "rq-1.15.1.tar.gz", hash = "sha256:1f49f4ac1a084044bb8e95b3f305c0bf17e55618b08c18e0b60c080f12d6f008"}, + {file = "rpds_py-0.17.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4128980a14ed805e1b91a7ed551250282a8ddf8201a4e9f8f5b7e6225f54170d"}, + {file = "rpds_py-0.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ff1dcb8e8bc2261a088821b2595ef031c91d499a0c1b031c152d43fe0a6ecec8"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d65e6b4f1443048eb7e833c2accb4fa7ee67cc7d54f31b4f0555b474758bee55"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a71169d505af63bb4d20d23a8fbd4c6ce272e7bce6cc31f617152aa784436f29"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:436474f17733c7dca0fbf096d36ae65277e8645039df12a0fa52445ca494729d"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10162fe3f5f47c37ebf6d8ff5a2368508fe22007e3077bf25b9c7d803454d921"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:720215373a280f78a1814becb1312d4e4d1077b1202a56d2b0815e95ccb99ce9"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:70fcc6c2906cfa5c6a552ba7ae2ce64b6c32f437d8f3f8eea49925b278a61453"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:91e5a8200e65aaac342a791272c564dffcf1281abd635d304d6c4e6b495f29dc"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:99f567dae93e10be2daaa896e07513dd4bf9c2ecf0576e0533ac36ba3b1d5394"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24e4900a6643f87058a27320f81336d527ccfe503984528edde4bb660c8c8d59"}, + {file = "rpds_py-0.17.1-cp310-none-win32.whl", hash = "sha256:0bfb09bf41fe7c51413f563373e5f537eaa653d7adc4830399d4e9bdc199959d"}, + {file = "rpds_py-0.17.1-cp310-none-win_amd64.whl", hash = "sha256:20de7b7179e2031a04042e85dc463a93a82bc177eeba5ddd13ff746325558aa6"}, + {file = "rpds_py-0.17.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:65dcf105c1943cba45d19207ef51b8bc46d232a381e94dd38719d52d3980015b"}, + {file = "rpds_py-0.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:01f58a7306b64e0a4fe042047dd2b7d411ee82e54240284bab63e325762c1147"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:071bc28c589b86bc6351a339114fb7a029f5cddbaca34103aa573eba7b482382"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae35e8e6801c5ab071b992cb2da958eee76340e6926ec693b5ff7d6381441745"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149c5cd24f729e3567b56e1795f74577aa3126c14c11e457bec1b1c90d212e38"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e796051f2070f47230c745d0a77a91088fbee2cc0502e9b796b9c6471983718c"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e820ee1004327609b28db8307acc27f5f2e9a0b185b2064c5f23e815f248f8"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1957a2ab607f9added64478a6982742eb29f109d89d065fa44e01691a20fc20a"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8587fd64c2a91c33cdc39d0cebdaf30e79491cc029a37fcd458ba863f8815383"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4dc889a9d8a34758d0fcc9ac86adb97bab3fb7f0c4d29794357eb147536483fd"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2953937f83820376b5979318840f3ee47477d94c17b940fe31d9458d79ae7eea"}, + {file = "rpds_py-0.17.1-cp311-none-win32.whl", hash = "sha256:1bfcad3109c1e5ba3cbe2f421614e70439f72897515a96c462ea657261b96518"}, + {file = "rpds_py-0.17.1-cp311-none-win_amd64.whl", hash = "sha256:99da0a4686ada4ed0f778120a0ea8d066de1a0a92ab0d13ae68492a437db78bf"}, + {file = "rpds_py-0.17.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1dc29db3900cb1bb40353772417800f29c3d078dbc8024fd64655a04ee3c4bdf"}, + {file = "rpds_py-0.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82ada4a8ed9e82e443fcef87e22a3eed3654dd3adf6e3b3a0deb70f03e86142a"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d36b2b59e8cc6e576f8f7b671e32f2ff43153f0ad6d0201250a7c07f25d570e"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3677fcca7fb728c86a78660c7fb1b07b69b281964673f486ae72860e13f512ad"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:516fb8c77805159e97a689e2f1c80655c7658f5af601c34ffdb916605598cda2"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df3b6f45ba4515632c5064e35ca7f31d51d13d1479673185ba8f9fefbbed58b9"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a967dd6afda7715d911c25a6ba1517975acd8d1092b2f326718725461a3d33f9"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dbbb95e6fc91ea3102505d111b327004d1c4ce98d56a4a02e82cd451f9f57140"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02866e060219514940342a1f84303a1ef7a1dad0ac311792fbbe19b521b489d2"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2528ff96d09f12e638695f3a2e0c609c7b84c6df7c5ae9bfeb9252b6fa686253"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd345a13ce06e94c753dab52f8e71e5252aec1e4f8022d24d56decd31e1b9b23"}, + {file = "rpds_py-0.17.1-cp312-none-win32.whl", hash = "sha256:2a792b2e1d3038daa83fa474d559acfd6dc1e3650ee93b2662ddc17dbff20ad1"}, + {file = "rpds_py-0.17.1-cp312-none-win_amd64.whl", hash = "sha256:292f7344a3301802e7c25c53792fae7d1593cb0e50964e7bcdcc5cf533d634e3"}, + {file = "rpds_py-0.17.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:8ffe53e1d8ef2520ebcf0c9fec15bb721da59e8ef283b6ff3079613b1e30513d"}, + {file = "rpds_py-0.17.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4341bd7579611cf50e7b20bb8c2e23512a3dc79de987a1f411cb458ab670eb90"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4eb548daf4836e3b2c662033bfbfc551db58d30fd8fe660314f86bf8510b93"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b686f25377f9c006acbac63f61614416a6317133ab7fafe5de5f7dc8a06d42eb"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e21b76075c01d65d0f0f34302b5a7457d95721d5e0667aea65e5bb3ab415c25"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b86b21b348f7e5485fae740d845c65a880f5d1eda1e063bc59bef92d1f7d0c55"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f175e95a197f6a4059b50757a3dca33b32b61691bdbd22c29e8a8d21d3914cae"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1701fc54460ae2e5efc1dd6350eafd7a760f516df8dbe51d4a1c79d69472fbd4"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9051e3d2af8f55b42061603e29e744724cb5f65b128a491446cc029b3e2ea896"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:7450dbd659fed6dd41d1a7d47ed767e893ba402af8ae664c157c255ec6067fde"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5a024fa96d541fd7edaa0e9d904601c6445e95a729a2900c5aec6555fe921ed6"}, + {file = "rpds_py-0.17.1-cp38-none-win32.whl", hash = "sha256:da1ead63368c04a9bded7904757dfcae01eba0e0f9bc41d3d7f57ebf1c04015a"}, + {file = "rpds_py-0.17.1-cp38-none-win_amd64.whl", hash = "sha256:841320e1841bb53fada91c9725e766bb25009cfd4144e92298db296fb6c894fb"}, + {file = "rpds_py-0.17.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:f6c43b6f97209e370124baf2bf40bb1e8edc25311a158867eb1c3a5d449ebc7a"}, + {file = "rpds_py-0.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7d63ec01fe7c76c2dbb7e972fece45acbb8836e72682bde138e7e039906e2c"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81038ff87a4e04c22e1d81f947c6ac46f122e0c80460b9006e6517c4d842a6ec"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:810685321f4a304b2b55577c915bece4c4a06dfe38f6e62d9cc1d6ca8ee86b99"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25f071737dae674ca8937a73d0f43f5a52e92c2d178330b4c0bb6ab05586ffa6"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa5bfb13f1e89151ade0eb812f7b0d7a4d643406caaad65ce1cbabe0a66d695f"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfe07308b311a8293a0d5ef4e61411c5c20f682db6b5e73de6c7c8824272c256"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a000133a90eea274a6f28adc3084643263b1e7c1a5a66eb0a0a7a36aa757ed74"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d0e8a6434a3fbf77d11448c9c25b2f25244226cfbec1a5159947cac5b8c5fa4"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:efa767c220d94aa4ac3a6dd3aeb986e9f229eaf5bce92d8b1b3018d06bed3772"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:dbc56680ecf585a384fbd93cd42bc82668b77cb525343170a2d86dafaed2a84b"}, + {file = "rpds_py-0.17.1-cp39-none-win32.whl", hash = "sha256:270987bc22e7e5a962b1094953ae901395e8c1e1e83ad016c5cfcfff75a15a3f"}, + {file = "rpds_py-0.17.1-cp39-none-win_amd64.whl", hash = "sha256:2a7b2f2f56a16a6d62e55354dd329d929560442bd92e87397b7a9586a32e3e76"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a3264e3e858de4fc601741498215835ff324ff2482fd4e4af61b46512dd7fc83"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f2f3b28b40fddcb6c1f1f6c88c6f3769cd933fa493ceb79da45968a21dccc920"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9584f8f52010295a4a417221861df9bea4c72d9632562b6e59b3c7b87a1522b7"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c64602e8be701c6cfe42064b71c84ce62ce66ddc6422c15463fd8127db3d8066"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:060f412230d5f19fc8c8b75f315931b408d8ebf56aec33ef4168d1b9e54200b1"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9412abdf0ba70faa6e2ee6c0cc62a8defb772e78860cef419865917d86c7342"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9737bdaa0ad33d34c0efc718741abaafce62fadae72c8b251df9b0c823c63b22"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9f0e4dc0f17dcea4ab9d13ac5c666b6b5337042b4d8f27e01b70fae41dd65c57"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1db228102ab9d1ff4c64148c96320d0be7044fa28bd865a9ce628ce98da5973d"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:d8bbd8e56f3ba25a7d0cf980fc42b34028848a53a0e36c9918550e0280b9d0b6"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:be22ae34d68544df293152b7e50895ba70d2a833ad9566932d750d3625918b82"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bf046179d011e6114daf12a534d874958b039342b347348a78b7cdf0dd9d6041"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:1a746a6d49665058a5896000e8d9d2f1a6acba8a03b389c1e4c06e11e0b7f40d"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0b8bf5b8db49d8fd40f54772a1dcf262e8be0ad2ab0206b5a2ec109c176c0a4"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f7f4cb1f173385e8a39c29510dd11a78bf44e360fb75610594973f5ea141028b"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7fbd70cb8b54fe745301921b0816c08b6d917593429dfc437fd024b5ba713c58"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bdf1303df671179eaf2cb41e8515a07fc78d9d00f111eadbe3e14262f59c3d0"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad059a4bd14c45776600d223ec194e77db6c20255578bb5bcdd7c18fd169361"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3664d126d3388a887db44c2e293f87d500c4184ec43d5d14d2d2babdb4c64cad"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:698ea95a60c8b16b58be9d854c9f993c639f5c214cf9ba782eca53a8789d6b19"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:c3d2010656999b63e628a3c694f23020322b4178c450dc478558a2b6ef3cb9bb"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:938eab7323a736533f015e6069a7d53ef2dcc841e4e533b782c2bfb9fb12d84b"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e626b365293a2142a62b9a614e1f8e331b28f3ca57b9f05ebbf4cf2a0f0bdc5"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:380e0df2e9d5d5d339803cfc6d183a5442ad7ab3c63c2a0982e8c824566c5ccc"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b760a56e080a826c2e5af09002c1a037382ed21d03134eb6294812dda268c811"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5576ee2f3a309d2bb403ec292d5958ce03953b0e57a11d224c1f134feaf8c40f"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3c3461ebb4c4f1bbc70b15d20b565759f97a5aaf13af811fcefc892e9197ba"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:637b802f3f069a64436d432117a7e58fab414b4e27a7e81049817ae94de45d8d"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffee088ea9b593cc6160518ba9bd319b5475e5f3e578e4552d63818773c6f56a"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ac732390d529d8469b831949c78085b034bff67f584559340008d0f6041a049"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:93432e747fb07fa567ad9cc7aaadd6e29710e515aabf939dfbed8046041346c6"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7b7d9ca34542099b4e185b3c2a2b2eda2e318a7dbde0b0d83357a6d4421b5296"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:0387ce69ba06e43df54e43968090f3626e231e4bc9150e4c3246947567695f68"}, + {file = "rpds_py-0.17.1.tar.gz", hash = "sha256:0210b2668f24c078307260bf88bdac9d6f1093635df5123789bfee4d8d7fc8e7"}, ] -[package.dependencies] -click = ">=5.0.0" -redis = ">=4.0.0" - [[package]] name = "rx" version = "1.6.3" description = "Reactive Extensions (Rx) for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -3341,7 +3218,6 @@ files = [ name = "singledispatch" version = "4.1.0" description = "Backport functools.singledispatch to older Pythons." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3357,7 +3233,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -3369,7 +3244,6 @@ files = [ name = "smmap" version = "5.0.1" description = "A pure Python implementation of a sliding window memory map manager" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3381,7 +3255,6 @@ files = [ name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" optional = false python-versions = "*" files = [ @@ -3393,7 +3266,6 @@ files = [ name = "social-auth-app-django" version = "5.2.0" description = "Python Social Authentication, Django integration." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3409,7 +3281,6 @@ social-auth-core = ">=4.4.1" name = "social-auth-core" version = "4.5.1" description = "Python social authentication made simple." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3436,7 +3307,6 @@ saml = ["python3-saml (>=1.5.0)"] name = "soupsieve" version = "2.5" description = "A modern CSS selector implementation for Beautiful Soup." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3448,7 +3318,6 @@ files = [ name = "sqlparse" version = "0.4.4" description = "A non-validating SQL parser." -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -3465,7 +3334,6 @@ test = ["pytest", "pytest-cov"] name = "stack-data" version = "0.6.3" description = "Extract data from python stack frames and tracebacks for informative displays" -category = "dev" optional = false python-versions = "*" files = [ @@ -3485,7 +3353,6 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] name = "stevedore" version = "5.1.0" description = "Manage dynamic plugins for Python applications" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3500,7 +3367,6 @@ pbr = ">=2.0.0,<2.1.0 || >2.1.0" name = "svgwrite" version = "1.4.3" description = "A Python library to create SVG drawings." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3508,28 +3374,10 @@ files = [ {file = "svgwrite-1.4.3.zip", hash = "sha256:a8fbdfd4443302a6619a7f76bc937fc683daf2628d9b737c891ec08b8ce524c3"}, ] -[[package]] -name = "swagger-spec-validator" -version = "3.0.3" -description = "Validation of Swagger specifications" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "swagger-spec-validator-3.0.3.tar.gz", hash = "sha256:16a5ce08c772824a77b1a4a05efc047d72eef1ed53fb969dfe0a18f437ac30a8"}, - {file = "swagger_spec_validator-3.0.3-py2.py3-none-any.whl", hash = "sha256:174b5de4ab0899df9a57d35c880aaa515511c4b8b578d9d519b09a9596537055"}, -] - -[package.dependencies] -jsonschema = "*" -pyyaml = "*" -typing-extensions = "*" - [[package]] name = "text-unidecode" version = "1.3" description = "The most basic Text::Unidecode port" -category = "main" optional = false python-versions = "*" files = [ @@ -3541,7 +3389,6 @@ files = [ name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -3553,7 +3400,6 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3565,7 +3411,6 @@ files = [ name = "tomlkit" version = "0.12.3" description = "Style preserving TOML library" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3577,7 +3422,6 @@ files = [ name = "traitlets" version = "5.14.1" description = "Traitlets Python configuration system" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3593,7 +3437,6 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0, name = "typing-extensions" version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3605,7 +3448,6 @@ files = [ name = "tzdata" version = "2023.4" description = "Provider of IANA time zone data" -category = "main" optional = false python-versions = ">=2" files = [ @@ -3617,7 +3459,6 @@ files = [ name = "uritemplate" version = "4.1.1" description = "Implementation of RFC 6570 URI Templates" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3629,7 +3470,6 @@ files = [ name = "urllib3" version = "2.1.0" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3646,7 +3486,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "vine" version = "5.1.0" description = "Python promises." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3658,7 +3497,6 @@ files = [ name = "watchdog" version = "3.0.0" description = "Filesystem events monitoring" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3698,7 +3536,6 @@ watchmedo = ["PyYAML (>=3.10)"] name = "wcwidth" version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" -category = "main" optional = false python-versions = "*" files = [ @@ -3710,7 +3547,6 @@ files = [ name = "wrapt" version = "1.16.0" description = "Module for decorators, wrappers and monkey patching." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3790,7 +3626,6 @@ files = [ name = "yamllint" version = "1.33.0" description = "A linter for YAML files." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3809,7 +3644,6 @@ dev = ["doc8", "flake8", "flake8-import-order", "rstcheck[sphinx]", "sphinx"] name = "zipp" version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3827,4 +3661,4 @@ nautobot = ["nautobot"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.12" -content-hash = "72179cef06ccc57bdf0ca30e13bce1b29d71e245aabd605b319c2e6e423f1a48" +content-hash = "9e48488bcea20a1dcdb94088bdc3f229b0ecde221962f1dbf8cdd64645b86151" diff --git a/pyproject.toml b/pyproject.toml index 74f1a861..d7c9b1d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,6 @@ pylint-nautobot = "*" yamllint = "*" toml = "*" Markdown = "*" -toml = "*" nautobot-bgp-models = "*" @@ -61,7 +60,6 @@ mkdocstrings-python = "1.5.2" [tool.poetry.extras] nautobot = ["nautobot"] -# bgp_models = ["nautobot-bgp-models"] [tool.black] line-length = 120 From 1bf513a8e9d54d5744bab8a34bc8a74881c5939b Mon Sep 17 00:00:00 2001 From: Andrew Bates Date: Tue, 23 Jan 2024 14:05:53 -0500 Subject: [PATCH 12/15] ci: trying to fix the CI pipeline --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec96d1e7..89708216 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,7 +95,7 @@ jobs: fail-fast: true matrix: python-version: ["3.11"] - nautobot-version: ["1.6.0"] + nautobot-version: ["1.6"] env: INVOKE_NAUTOBOT_DESIGN_BUILDER_PYTHON_VER: "${{ matrix.python-version }}" INVOKE_NAUTOBOT_DESIGN_BUILDER_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" @@ -138,7 +138,7 @@ jobs: fail-fast: true matrix: python-version: ["3.11"] - nautobot-version: ["1.6.8"] + nautobot-version: ["1.6"] env: INVOKE_NAUTOBOT_DESIGN_BUILDER_PYTHON_VER: "${{ matrix.python-version }}" INVOKE_NAUTOBOT_DESIGN_BUILDER_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" From 2dfd1bbcaaae8bc8e1b06af0acb7684c8d6336a5 Mon Sep 17 00:00:00 2001 From: Andrew Bates Date: Wed, 24 Jan 2024 09:42:20 -0500 Subject: [PATCH 13/15] ci: Changing pylint and migration tests to use Nautobot 2.1 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 89708216..21d662a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,7 +95,7 @@ jobs: fail-fast: true matrix: python-version: ["3.11"] - nautobot-version: ["1.6"] + nautobot-version: ["2.1"] env: INVOKE_NAUTOBOT_DESIGN_BUILDER_PYTHON_VER: "${{ matrix.python-version }}" INVOKE_NAUTOBOT_DESIGN_BUILDER_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" @@ -138,7 +138,7 @@ jobs: fail-fast: true matrix: python-version: ["3.11"] - nautobot-version: ["1.6"] + nautobot-version: ["2.1"] env: INVOKE_NAUTOBOT_DESIGN_BUILDER_PYTHON_VER: "${{ matrix.python-version }}" INVOKE_NAUTOBOT_DESIGN_BUILDER_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" From ab99edabe6e617b76df24c23a8ccc302a7b64501 Mon Sep 17 00:00:00 2001 From: Andrew Bates Date: Wed, 24 Jan 2024 10:33:36 -0500 Subject: [PATCH 14/15] ci: More ci fixes --- nautobot_design_builder/contrib/tests/test_ext.py | 7 ++++--- poetry.lock | 15 ++++++++------- pyproject.toml | 4 ++-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/nautobot_design_builder/contrib/tests/test_ext.py b/nautobot_design_builder/contrib/tests/test_ext.py index f156d2b7..095bd1b1 100644 --- a/nautobot_design_builder/contrib/tests/test_ext.py +++ b/nautobot_design_builder/contrib/tests/test_ext.py @@ -301,9 +301,10 @@ class TestBGPExtension(TestCase): """Test BGP extension.""" def setUp(self): - # TODO: Remove this when BGP models is migrated to 2.0 - if nautobot_version >= "2.0.0": - self.skipTest("BGP Models is not supported in Nautobot 2.x") + try: + import nautobot_bgp_models.models # pylint:disable=import-outside-toplevel,unused-import,import-error # noqa: F401 + except ModuleNotFoundError: + self.skipTest("BGP Models app is not installed, skipping associated tests") super().setUp() def test_creation(self): diff --git a/poetry.lock b/poetry.lock index e197905c..e690484c 100755 --- a/poetry.lock +++ b/poetry.lock @@ -181,24 +181,24 @@ tzdata = ["tzdata"] [[package]] name = "bandit" -version = "1.7.6" +version = "1.7.7" description = "Security oriented static analyser for python code." optional = false python-versions = ">=3.8" files = [ - {file = "bandit-1.7.6-py3-none-any.whl", hash = "sha256:36da17c67fc87579a5d20c323c8d0b1643a890a2b93f00b3d1229966624694ff"}, - {file = "bandit-1.7.6.tar.gz", hash = "sha256:72ce7bc9741374d96fb2f1c9a8960829885f1243ffde743de70a19cee353e8f3"}, + {file = "bandit-1.7.7-py3-none-any.whl", hash = "sha256:17e60786a7ea3c9ec84569fd5aee09936d116cb0cb43151023258340dbffb7ed"}, + {file = "bandit-1.7.7.tar.gz", hash = "sha256:527906bec6088cb499aae31bc962864b4e77569e9d529ee51df3a93b4b8ab28a"}, ] [package.dependencies] colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} -GitPython = ">=3.1.30" PyYAML = ">=5.3.1" rich = "*" stevedore = ">=1.20.0" [package.extras] -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)", "tomli (>=1.1.0)"] +baseline = ["GitPython (>=3.1.30)"] +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)"] yaml = ["PyYAML"] @@ -2100,7 +2100,7 @@ sso = ["social-auth-core[openidconnect,saml] (>=4.4.2,<4.5.0)"] name = "nautobot-bgp-models" version = "0.20.1" description = "Nautobot BGP Models Plugin" -optional = false +optional = true python-versions = ">=3.8,<3.12" files = [ {file = "nautobot_bgp_models-0.20.1-py3-none-any.whl", hash = "sha256:d670a80aa5073cb11a7d560d9282ffa1e7cc2a1810702514793ce846225fafdd"}, @@ -3656,9 +3656,10 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.link testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [extras] +contrib = ["nautobot-bgp-models"] nautobot = ["nautobot"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.12" -content-hash = "9e48488bcea20a1dcdb94088bdc3f229b0ecde221962f1dbf8cdd64645b86151" +content-hash = "1d57b02542b672d4ca50f193c021ca5df7080976694344c3906b5acc18c1e9c3" diff --git a/pyproject.toml b/pyproject.toml index d7c9b1d4..72eede19 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ packages = [ python = ">=3.8,<3.12" # Used for local development nautobot = ">=1.6.0,<=2.9999" +nautobot-bgp-models = { version = "*", optional = true } [tool.poetry.group.dev.dependencies] bandit = "*" @@ -46,8 +47,6 @@ yamllint = "*" toml = "*" Markdown = "*" -nautobot-bgp-models = "*" - # Rendering docs to HTML mkdocs = "1.5.2" # Material for MkDocs theme @@ -60,6 +59,7 @@ mkdocstrings-python = "1.5.2" [tool.poetry.extras] nautobot = ["nautobot"] +contrib = ["nautobot-bgp-models"] [tool.black] line-length = 120 From 9dc8b165820c7ac5991eadea70fc99c6c56b8bd8 Mon Sep 17 00:00:00 2001 From: Andrew Bates Date: Fri, 26 Jan 2024 08:16:37 -0500 Subject: [PATCH 15/15] refactor: Refactored v1/v2 contrib tests --- development/development.env | 2 + development/docker-compose.mysql.yml | 51 +-- development/nautobot_config.py | 34 +- .../contrib/tests/test_ext.py | 400 +----------------- .../tests/testdata/lookup_extension.yaml | 28 ++ .../testdata/nautobot_v1/bgp_extension.yaml | 78 ++++ .../nautobot_v1/cable_connections.yaml | 59 +++ .../testdata/nautobot_v1/child_prefix.yaml | 43 ++ .../testdata/nautobot_v1/next_prefix.yaml | 55 +++ .../next_prefix_by_role_and_tenant.yaml | 27 ++ .../testdata/nautobot_v2/bgp_extension.yaml | 87 ++++ .../nautobot_v2/cable_connections.yaml | 68 +++ .../testdata/nautobot_v2/child_prefix.yaml | 49 +++ .../testdata/nautobot_v2/next_prefix.yaml | 61 +++ .../next_prefix_by_role_and_tenant.yaml | 33 ++ nautobot_design_builder/tests/test_builder.py | 6 +- 16 files changed, 653 insertions(+), 428 deletions(-) create mode 100644 nautobot_design_builder/contrib/tests/testdata/lookup_extension.yaml create mode 100644 nautobot_design_builder/contrib/tests/testdata/nautobot_v1/bgp_extension.yaml create mode 100644 nautobot_design_builder/contrib/tests/testdata/nautobot_v1/cable_connections.yaml create mode 100644 nautobot_design_builder/contrib/tests/testdata/nautobot_v1/child_prefix.yaml create mode 100644 nautobot_design_builder/contrib/tests/testdata/nautobot_v1/next_prefix.yaml create mode 100644 nautobot_design_builder/contrib/tests/testdata/nautobot_v1/next_prefix_by_role_and_tenant.yaml create mode 100644 nautobot_design_builder/contrib/tests/testdata/nautobot_v2/bgp_extension.yaml create mode 100644 nautobot_design_builder/contrib/tests/testdata/nautobot_v2/cable_connections.yaml create mode 100644 nautobot_design_builder/contrib/tests/testdata/nautobot_v2/child_prefix.yaml create mode 100644 nautobot_design_builder/contrib/tests/testdata/nautobot_v2/next_prefix.yaml create mode 100644 nautobot_design_builder/contrib/tests/testdata/nautobot_v2/next_prefix_by_role_and_tenant.yaml diff --git a/development/development.env b/development/development.env index 7613c1e2..c81fe526 100644 --- a/development/development.env +++ b/development/development.env @@ -37,3 +37,5 @@ POSTGRES_DB=${NAUTOBOT_DB_NAME} MYSQL_USER=${NAUTOBOT_DB_USER} MYSQL_DATABASE=${NAUTOBOT_DB_NAME} MYSQL_ROOT_HOST=% + +DESIGN_BUILDER_ENABLE_BGP=True diff --git a/development/docker-compose.mysql.yml b/development/docker-compose.mysql.yml index bfd1ddcb..dc2641a2 100644 --- a/development/docker-compose.mysql.yml +++ b/development/docker-compose.mysql.yml @@ -1,31 +1,14 @@ -# We can't remove volumes in a compose override, for the test configuration using the final containers -# we don't want the volumes so this is the default override file to add the volumes in the dev case -# any override will need to include these volumes to use them. -# see: https://github.com/docker/compose/issues/3729 --- version: "3.8" + services: nautobot: - command: "nautobot-server runserver 0.0.0.0:8080" - ports: - - "8080:8080" - volumes: - - "./nautobot_config.py:/opt/nautobot/nautobot_config.py" - - "../:/source" - - "../examples/backbone_design/designs:/opt/nautobot/designs:cached" - - "../examples/backbone_design/jobs:/opt/nautobot/jobs:cached" - healthcheck: - test: ["CMD", "true"] # Due to layering, disable: true won't work. Instead, change the test - docs: - entrypoint: "mkdocs serve -v -a 0.0.0.0:8080" - ports: - - "8001:8080" - volumes: - - "../:/source" - image: "nautobot-design-builder/nautobot:${NAUTOBOT_VER}-py${PYTHON_VER}" - healthcheck: - disable: true - tty: true + environment: + - "NAUTOBOT_DB_ENGINE=django.db.backends.mysql" + env_file: + - "development.env" + - "creds.env" + - "development_mysql.env" worker: environment: - "NAUTOBOT_DB_ENGINE=django.db.backends.mysql" @@ -33,27 +16,25 @@ services: - "development.env" - "creds.env" - "development_mysql.env" + beat: + environment: + - "NAUTOBOT_DB_ENGINE=django.db.backends.mysql" + env_file: + - "development.env" + - "creds.env" + - "development_mysql.env" db: image: "mysql:8" command: - "--default-authentication-plugin=mysql_native_password" - - "--max_connections=1000" env_file: - "development.env" - "creds.env" - "development_mysql.env" volumes: - - "./nautobot_config.py:/opt/nautobot/nautobot_config.py" - - "../:/source" - - "../examples/backbone_design/designs:/opt/nautobot/designs:cached" - - "../examples/backbone_design/jobs:/opt/nautobot/jobs:cached" + - "mysql_data:/var/lib/mysql" healthcheck: - test: - - "CMD" - - "mysqladmin" - - "ping" - - "-h" - - "localhost" + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] timeout: "20s" retries: 10 volumes: diff --git a/development/nautobot_config.py b/development/nautobot_config.py index e9959dc2..43d06a57 100644 --- a/development/nautobot_config.py +++ b/development/nautobot_config.py @@ -1,11 +1,13 @@ """Nautobot development configuration file.""" +from importlib import metadata +from importlib.util import find_spec import os import sys +from packaging.version import Version + from nautobot.core.settings import * # noqa: F403 # pylint: disable=wildcard-import,unused-wildcard-import from nautobot.core.settings_funcs import is_truthy, parse_redis_connection -from importlib import metadata -from packaging.version import Version # # Debug @@ -136,11 +138,29 @@ # Apps configuration settings. These settings are used by various Apps that the user may have installed. # Each key in the dictionary is the name of an installed App and its value is a dictionary of settings. -# TODO: The following is necessary only until BGP models plugin -# is officially supported in 2.0 -nautobot_version = Version(Version(metadata.version("nautobot")).base_version) - -if nautobot_version < Version("2.0"): +if is_truthy(os.getenv("DESIGN_BUILDER_ENABLE_BGP", "False")): + if find_spec("nautobot_bgp_models") is None: + nautobot_version = Version(Version(metadata.version("nautobot")).base_version) + import subprocess # nosec + + package = "nautobot-bgp-models" + if nautobot_version < Version("2.0"): + # pip doesn't have the capability to automatically pick + # a version compatible with the current Nautobot, so we + # do that manually. This is only for development environments + # so that we can run unit tests on Nautobot 1.6 and 2.x + package = "nautobot-bgp-models==0.9.1" + command = [ + sys.executable, + "-m", + "pip", + "install", + package, + ] + # Since we aren't evaluating any input variables, *and* we + # are not using a shell, this command should be considered + # save, therefore the `nosec` annotation. + subprocess.check_call(command, shell=False) # nosec PLUGINS.append("nautobot_bgp_models") PLUGINS_CONFIG = {"design_builder": {"context_repository": os.getenv("DESIGN_BUILDER_CONTEXT_REPO_SLUG", None)}} diff --git a/nautobot_design_builder/contrib/tests/test_ext.py b/nautobot_design_builder/contrib/tests/test_ext.py index 095bd1b1..617be7fb 100644 --- a/nautobot_design_builder/contrib/tests/test_ext.py +++ b/nautobot_design_builder/contrib/tests/test_ext.py @@ -1,398 +1,32 @@ """Unit tests related to template extensions.""" -import yaml +import os -from django.db.models import Q from django.test import TestCase -from nautobot.extras.models import Status -from nautobot.dcim.models import Interface, Device, DeviceType -from nautobot.tenancy.models import Tenant -from nautobot.ipam.models import Prefix - -from nautobot_design_builder.contrib.ext import ( - BGPPeeringExtension, - ChildPrefixExtension, - LookupExtension, - CableConnectionExtension, - NextPrefixExtension, -) -from nautobot_design_builder.design import Builder +from nautobot_design_builder.tests.test_builder import builder_test_case from nautobot_design_builder.util import nautobot_version -class TestLookupExtension(TestCase): - """Test Lookup Extension.""" - - def test_lookup_by_dict(self): - design_template = """ - manufacturers: - - name: "Manufacturer" - - device_types: - - "!lookup:manufacturer": - name: "Manufacturer" - model: "model" - """ - design = yaml.safe_load(design_template) - builder = Builder(extensions=[LookupExtension]) - builder.implement_design(design, commit=True) - device_type = DeviceType.objects.get(model="model") - self.assertEqual("Manufacturer", device_type.manufacturer.name) - - def test_lookup_by_single_attribute(self): - design_template = """ - manufacturers: - - name: "Manufacturer" - - device_types: - - "!lookup:manufacturer:name": "Manufacturer" - model: "model" - """ - design = yaml.safe_load(design_template) - builder = Builder(extensions=[LookupExtension]) - builder.implement_design(design, commit=True) - device_type = DeviceType.objects.get(model="model") - self.assertEqual("Manufacturer", device_type.manufacturer.name) - - -class TestCableConnectionExtension(TestCase): - """Test Cable Connection Extension.""" - - def test_connect_cable(self): - design_template_v1 = """ - sites: - - "!create_or_update:name": "Site" - status__name: "Active" - device_roles: - - "!create_or_update:name": "test-role" - manufacturers: - - "!create_or_update:name": "test-manufacturer" - device_types: - - manufacturer__name: "test-manufacturer" - "!create_or_update:model": "test-type" - devices: - - "!create_or_update:name": "Device 1" - "!ref": "device1" - site__name: "Site" - status__name: "Active" - device_role__name: "test-role" - device_type__model: "test-type" - interfaces: - - "!create_or_update:name": "GigabitEthernet1" - type: "1000base-t" - status__name: "Active" - - "!create_or_update:name": "Device 2" - site__name: "Site" - status__name: "Active" - device_role__name: "test-role" - device_type__model: "test-type" - interfaces: - - "!create_or_update:name": "GigabitEthernet1" - type: "1000base-t" - status__name: "Active" - "!connect_cable": - status__name: "Planned" - to: - device: "!ref:device1" - name: "GigabitEthernet1" - """ - - design_template_v2 = """ - location_types: - - "!create_or_update:name": "Site" - content_types: - - "!get:app_label": "dcim" - "!get:model": "device" - locations: - - location_type__name: "Site" - "!create_or_update:name": "Site" - status__name: "Active" - roles: - - "!create_or_update:name": "test-role" - content_types: - - "!get:app_label": "dcim" - "!get:model": "device" - manufacturers: - - "!create_or_update:name": "test-manufacturer" - device_types: - - manufacturer__name: "test-manufacturer" - "!create_or_update:model": "test-type" - devices: - - "!create_or_update:name": "Device 1" - "!ref": "device1" - location__name: "Site" - status__name: "Active" - role__name: "test-role" - device_type__model: "test-type" - interfaces: - - "!create_or_update:name": "GigabitEthernet1" - type: "1000base-t" - status__name: "Active" - - "!create_or_update:name": "Device 2" - location__name: "Site" - status__name: "Active" - role__name: "test-role" - device_type__model: "test-type" - interfaces: - - "!create_or_update:name": "GigabitEthernet1" - type: "1000base-t" - status__name: "Active" - "!connect_cable": - status__name: "Planned" - to: - device: "!ref:device1" - name: "GigabitEthernet1" - """ - - if nautobot_version < "2.0.0": - design = yaml.safe_load(design_template_v1) - else: - design = yaml.safe_load(design_template_v2) - - # test idempotence by running it twice: - for _ in range(2): - builder = Builder(extensions=[CableConnectionExtension]) - builder.implement_design(design, commit=True) - interfaces = Interface.objects.all() - self.assertEqual(2, len(interfaces)) - self.assertEqual(interfaces[0].connected_endpoint, interfaces[1]) - self.assertIsNotNone(interfaces[0]._path_id) # pylint: disable=protected-access - self.assertIsNotNone(interfaces[1]._path_id) # pylint: disable=protected-access - - -class PrefixExtensionTests(TestCase): - """Base class for testing prefix based extensions.""" - - @staticmethod - def create_prefix(prefix, **kwargs): - """Affirm the existence of a prefix and return it.""" - prefix, _ = Prefix.objects.get_or_create( - prefix=prefix, - defaults={ - "status": Status.objects.get(name="Active"), - **kwargs, - }, - ) - return prefix - - def setUp(self) -> None: - self.tenant = Tenant.objects.create(name="Nautobot Airports") - if nautobot_version < "2.0.0": - from nautobot.ipam.models import Role # pylint: disable=no-name-in-module,import-outside-toplevel - else: - from nautobot.extras.models import Role # pylint: disable=no-name-in-module,import-outside-toplevel - - self.server_role = Role.objects.create(name="servers") - self.video_role = Role.objects.create(name="video") - self.prefixes = [] - self.prefixes.append(PrefixExtensionTests.create_prefix("10.0.0.0/8", tenant=self.tenant)) - self.prefixes.append( - PrefixExtensionTests.create_prefix("10.0.0.0/23", tenant=self.tenant, role=self.server_role) - ) - self.prefixes.append( - PrefixExtensionTests.create_prefix("10.0.2.0/23", tenant=self.tenant, role=self.video_role) - ) - - -class TestNextPrefixExtension(PrefixExtensionTests): - """Test Next Prefix Extension.""" +@builder_test_case(os.path.join(os.path.dirname(__file__), "testdata")) +class TestAgnosticExtensions(TestCase): + """Test contrib extensions against any version of Nautobot.""" - def test_next_prefix_lookup(self): - extension = NextPrefixExtension(None) - want = "10.0.4.0/24" - got = extension._get_next([self.prefixes[0]], "24") # pylint:disable=protected-access - self.assertEqual(want, got) - def test_next_prefix_lookup_from_full_prefix(self): - for prefix in ["10.0.0.0/24", "10.0.1.0/24"]: - PrefixExtensionTests.create_prefix(prefix) - - prefixes = Prefix.objects.filter( - Q(network="10.0.0.0", prefix_length=23) | Q(network="10.0.2.0", prefix_length=23) - ) - - extension = NextPrefixExtension(None) - want = "10.0.2.0/24" - got = extension._get_next(prefixes, "24") # pylint:disable=protected-access - self.assertEqual(want, got) - - def test_creation(self): - design_template = """ - prefixes: - - "!next_prefix": - prefix: - - "10.0.0.0/23" - - "10.0.2.0/23" - length: 24 - status__name: "Active" - - "!next_prefix": - prefix: - - "10.0.0.0/23" - - "10.0.2.0/23" - length: 24 - status__name: "Active" - - "!next_prefix": - prefix: - - "10.0.0.0/23" - - "10.0.2.0/23" - length: 24 - status__name: "Active" - - "!next_prefix": - prefix: - - "10.0.0.0/23" - - "10.0.2.0/23" - length: 24 - status__name: "Active" - """ - design = yaml.safe_load(design_template) - object_creator = Builder(extensions=[NextPrefixExtension]) - object_creator.implement_design(design, commit=True) - self.assertTrue(Prefix.objects.filter(prefix="10.0.0.0/24").exists()) - self.assertTrue(Prefix.objects.filter(prefix="10.0.1.0/24").exists()) - self.assertTrue(Prefix.objects.filter(prefix="10.0.2.0/24").exists()) - self.assertTrue(Prefix.objects.filter(prefix="10.0.3.0/24").exists()) - - def test_lookup_by_role_and_tenant(self): - design_template = """ - prefixes: - - "!next_prefix": - role__name: "video" - tenant__name: "Nautobot Airports" - length: 24 - status__name: "Active" - """ - self.assertFalse(Prefix.objects.filter(prefix="10.0.2.0/24").exists()) - design = yaml.safe_load(design_template) - object_creator = Builder(extensions=[NextPrefixExtension]) - object_creator.implement_design(design, commit=True) - self.assertTrue(Prefix.objects.filter(prefix="10.0.2.0/24").exists()) - - -class TestChildPrefixExtension(PrefixExtensionTests): - """Test Child Prefix Extension.""" - - def test_creation(self): - design_template = """ - prefixes: - - "!next_prefix": - prefix: - - "10.0.0.0/23" - length: 24 - status__name: "Active" - "!ref": "parent_prefix" - - "!child_prefix": - parent: "!ref:parent_prefix" - offset: "0.0.0.0/25" - status__name: "Active" - - "!child_prefix": - parent: "!ref:parent_prefix" - offset: "0.0.0.128/25" - status__name: "Active" - """ - design = yaml.safe_load(design_template) - object_creator = Builder(extensions=[NextPrefixExtension, ChildPrefixExtension]) - object_creator.implement_design(design, commit=True) - self.assertTrue(Prefix.objects.filter(prefix="10.0.0.0/24").exists()) - self.assertTrue(Prefix.objects.filter(prefix="10.0.0.0/25").exists()) - self.assertTrue(Prefix.objects.filter(prefix="10.0.0.128/25").exists()) - - -class TestBGPExtension(TestCase): - """Test BGP extension.""" +@builder_test_case(os.path.join(os.path.dirname(__file__), "testdata", "nautobot_v1")) +class TestV1Extensions(TestCase): + """Test contrib extensions against Nautobot V1.""" def setUp(self): - try: - import nautobot_bgp_models.models # pylint:disable=import-outside-toplevel,unused-import,import-error # noqa: F401 - except ModuleNotFoundError: - self.skipTest("BGP Models app is not installed, skipping associated tests") + if nautobot_version >= "2.0.0": + self.skipTest("These tests are only supported in Nautobot 1.x") super().setUp() - def test_creation(self): - design_template = """ - sites: - - "!create_or_update:name": "Site" - status__name: "Active" - - device_roles: - - "!create_or_update:name": "test-role" - manufacturers: - - "!create_or_update:name": "test-manufacturer" +@builder_test_case(os.path.join(os.path.dirname(__file__), "testdata", "nautobot_v2")) +class TestV2Extensions(TestCase): + """Test contrib extensions against Nautobot V2.""" - device_types: - - manufacturer__name: "test-manufacturer" - "!create_or_update:model": "test-type" - - autonomous_systems: - - "!create_or_update:asn": 64500 - status__name: "Active" - - devices: - - "!create_or_update:name": "device1" - status__name: "Active" - site__name: "Site" - device_role__name: "test-role" - device_type__model: "test-type" - interfaces: - - "!create_or_update:name": "Ethernet1/1" - type: "virtual" - status__name: "Active" - ip_addresses: - - "!create_or_update:address": "192.168.1.1/24" - status__name: "Active" - bgp_routing_instances: - - "!create_or_update:autonomous_system__asn": 64500 - "!ref": "device1-instance" - - - "!create_or_update:name": "device2" - status__name: "Active" - site__name: "Site" - device_role__name: "test-role" - device_type__model: "test-type" - interfaces: - - "!create_or_update:name": "Ethernet1/1" - type: "virtual" - status__name: "Active" - ip_addresses: - - "!create_or_update:address": "192.168.1.2/24" - status__name: "Active" - bgp_routing_instances: - - "!create_or_update:autonomous_system__asn": 64500 - "!ref": "device2-instance" - - bgp_peerings: - - "!bgp_peering": - endpoint_a: - "!create_or_update:routing_instance__device__name": "device1" - "!create_or_update:source_ip": - "!get:interface__device__name": "device1" - "!get:interface__name": "Ethernet1/1" - endpoint_z: - "!create_or_update:routing_instance__device__name": "device2" - "!create_or_update:source_ip": - "!get:interface__device__name": "device2" - "!get:interface__name": "Ethernet1/1" - status__name: "Active" - """ - from nautobot_bgp_models.models import Peering # pylint: disable=import-outside-toplevel - - design = yaml.safe_load(design_template) - object_creator = Builder(extensions=[BGPPeeringExtension]) - object_creator.implement_design(design, commit=True) - device1 = Device.objects.get(name="device1") - device2 = Device.objects.get(name="device2") - - endpoint1 = device1.bgp_routing_instances.first().endpoints.first() - endpoint2 = device2.bgp_routing_instances.first().endpoints.first() - self.assertEqual(endpoint1.peering, endpoint2.peering) - peering_pk = endpoint1.peering.pk - self.assertEqual(1, Peering.objects.all().count()) - self.assertEqual(endpoint2.peer, endpoint1) - self.assertEqual(endpoint1.peer, endpoint2) - - # confirm idempotence - object_creator.implement_design(design, commit=True) - self.assertEqual(1, Peering.objects.all().count()) - self.assertEqual(peering_pk, Peering.objects.first().pk) - self.assertEqual(endpoint2.peer, endpoint1) - self.assertEqual(endpoint1.peer, endpoint2) + def setUp(self): + if nautobot_version < "2.0.0": + self.skipTest("These tests are only supported in Nautobot 2.x") + super().setUp() diff --git a/nautobot_design_builder/contrib/tests/testdata/lookup_extension.yaml b/nautobot_design_builder/contrib/tests/testdata/lookup_extension.yaml new file mode 100644 index 00000000..a96dae99 --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/lookup_extension.yaml @@ -0,0 +1,28 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.LookupExtension" + +designs: + - manufacturers: + - name: "Manufacturer1" + - name: "Manufacturer2" + device_types: + - "!lookup:manufacturer": + name: "Manufacturer1" + model: "model1" + - "!lookup:manufacturer:name": "Manufacturer2" + model: "model2" +checks: + - equal: + - model: "nautobot.dcim.models.DeviceType" + query: {model: "model1"} + attribute: "manufacturer" + - model: "nautobot.dcim.models.Manufacturer" + query: {name: "Manufacturer1"} + + - equal: + - model: "nautobot.dcim.models.DeviceType" + query: {model: "model2"} + attribute: "manufacturer" + - model: "nautobot.dcim.models.Manufacturer" + query: {name: "Manufacturer2"} diff --git a/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/bgp_extension.yaml b/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/bgp_extension.yaml new file mode 100644 index 00000000..ea436b56 --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/bgp_extension.yaml @@ -0,0 +1,78 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.BGPPeeringExtension" +designs: + - sites: + - "!create_or_update:name": "Site" + status__name: "Active" + + device_roles: + - "!create_or_update:name": "test-role" + + manufacturers: + - "!create_or_update:name": "test-manufacturer" + + device_types: + - manufacturer__name: "test-manufacturer" + "!create_or_update:model": "test-type" + + autonomous_systems: + - "!create_or_update:asn": 64500 + status__name: "Active" + + devices: + - "!create_or_update:name": "device1" + status__name: "Active" + site__name: "Site" + device_role__name: "test-role" + device_type__model: "test-type" + interfaces: + - "!create_or_update:name": "Ethernet1/1" + type: "virtual" + status__name: "Active" + ip_addresses: + - "!create_or_update:address": "192.168.1.1/24" + status__name: "Active" + bgp_routing_instances: + - "!create_or_update:autonomous_system__asn": 64500 + "!ref": "device1-instance" + status__name: "Active" + + - "!create_or_update:name": "device2" + status__name: "Active" + site__name: "Site" + device_role__name: "test-role" + device_type__model: "test-type" + interfaces: + - "!create_or_update:name": "Ethernet1/1" + type: "virtual" + status__name: "Active" + ip_addresses: + - "!create_or_update:address": "192.168.1.2/24" + status__name: "Active" + bgp_routing_instances: + - "!create_or_update:autonomous_system__asn": 64500 + "!ref": "device2-instance" + status__name: "Active" + + bgp_peerings: + - "!bgp_peering": + endpoint_a: + "!create_or_update:routing_instance__device__name": "device1" + "!create_or_update:source_ip": + "!get:interface__device__name": "device1" + "!get:interface__name": "Ethernet1/1" + endpoint_z: + "!create_or_update:routing_instance__device__name": "device2" + "!create_or_update:source_ip": + "!get:interface__device__name": "device2" + "!get:interface__name": "Ethernet1/1" + status__name: "Active" +checks: + - equal: + - model: "nautobot_bgp_models.models.PeerEndpoint" + query: {routing_instance__device__name: "device1"} + attribute: "peering" + - model: "nautobot_bgp_models.models.PeerEndpoint" + query: {routing_instance__device__name: "device2"} + attribute: "peering" diff --git a/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/cable_connections.yaml b/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/cable_connections.yaml new file mode 100644 index 00000000..bd80c907 --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/cable_connections.yaml @@ -0,0 +1,59 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.CableConnectionExtension" +designs: + - sites: + - "!create_or_update:name": "Site" + status__name: "Active" + device_roles: + - "!create_or_update:name": "test-role" + manufacturers: + - "!create_or_update:name": "test-manufacturer" + device_types: + - manufacturer__name: "test-manufacturer" + "!create_or_update:model": "test-type" + devices: + - "!create_or_update:name": "Device 1" + "!ref": "device1" + site__name: "Site" + status__name: "Active" + device_role__name: "test-role" + device_type__model: "test-type" + interfaces: + - "!create_or_update:name": "GigabitEthernet1" + type: "1000base-t" + status__name: "Active" + - "!create_or_update:name": "Device 2" + site__name: "Site" + status__name: "Active" + device_role__name: "test-role" + device_type__model: "test-type" + interfaces: + - "!create_or_update:name": "GigabitEthernet1" + type: "1000base-t" + status__name: "Active" + "!connect_cable": + status__name: "Planned" + to: + device: "!ref:device1" + name: "GigabitEthernet1" + # Second design, same as the first, checks for + # cable connection idempotence + - devices: + - "!create_or_update:name": "Device 1" + "!ref": "device1" + - "!create_or_update:name": "Device 2" + interfaces: + - "!create_or_update:name": "GigabitEthernet1" + "!connect_cable": + status__name: "Planned" + to: + device: "!ref:device1" + name: "GigabitEthernet1" + +checks: + - connected: + - model: "nautobot.dcim.models.Interface" + query: {device__name: "Device 1", name: "GigabitEthernet1"} + - model: "nautobot.dcim.models.Interface" + query: {device__name: "Device 2", name: "GigabitEthernet1"} diff --git a/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/child_prefix.yaml b/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/child_prefix.yaml new file mode 100644 index 00000000..0f07d112 --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/child_prefix.yaml @@ -0,0 +1,43 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.NextPrefixExtension" + - "nautobot_design_builder.contrib.ext.ChildPrefixExtension" +designs: + - tenants: + - name: "Nautobot Airports" + roles: + - name: "Video" + - name: "Servers" + prefixes: + - prefix: "10.0.0.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Servers" + - prefix: "10.0.2.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Video" + - "!next_prefix": + prefix: + - "10.0.0.0/23" + length: 24 + status__name: "Active" + "!ref": "parent_prefix" + - "!child_prefix": + parent: "!ref:parent_prefix" + offset: "0.0.0.0/25" + status__name: "Active" + - "!child_prefix": + parent: "!ref:parent_prefix" + offset: "0.0.0.128/25" + status__name: "Active" +checks: + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.0.0/24"} + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.0.0/25"} + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.0.128/25"} diff --git a/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/next_prefix.yaml b/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/next_prefix.yaml new file mode 100644 index 00000000..937c9bc1 --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/next_prefix.yaml @@ -0,0 +1,55 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.NextPrefixExtension" +designs: + - tenants: + - name: "Nautobot Airports" + roles: + - name: "Video" + - name: "Servers" + prefixes: + - prefix: "10.0.0.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Servers" + - prefix: "10.0.2.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Video" + - "!next_prefix": + prefix: + - "10.0.0.0/23" + - "10.0.2.0/23" + length: 24 + status__name: "Active" + - "!next_prefix": + prefix: + - "10.0.0.0/23" + - "10.0.2.0/23" + length: 24 + status__name: "Active" + - "!next_prefix": + prefix: + - "10.0.0.0/23" + - "10.0.2.0/23" + length: 24 + status__name: "Active" + - "!next_prefix": + prefix: + - "10.0.0.0/23" + - "10.0.2.0/23" + length: 24 + status__name: "Active" +checks: + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.0.0/24"} + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.1.0/24"} + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.2.0/24"} + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.3.0/24"} diff --git a/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/next_prefix_by_role_and_tenant.yaml b/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/next_prefix_by_role_and_tenant.yaml new file mode 100644 index 00000000..2a4023c2 --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/next_prefix_by_role_and_tenant.yaml @@ -0,0 +1,27 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.NextPrefixExtension" +designs: + - tenants: + - name: "Nautobot Airports" + roles: + - name: "Video" + - name: "Servers" + prefixes: + - prefix: "10.0.0.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Servers" + - prefix: "10.0.2.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Video" + - "!next_prefix": + role__name: "Video" + tenant__name: "Nautobot Airports" + length: 24 + status__name: "Active" +checks: + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.2.0/24"} diff --git a/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/bgp_extension.yaml b/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/bgp_extension.yaml new file mode 100644 index 00000000..627df53e --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/bgp_extension.yaml @@ -0,0 +1,87 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.BGPPeeringExtension" +designs: + - roles: + - name: "test-role" + content_types: + - "!get:app_label": "dcim" + "!get:model": "device" + prefixes: + - status__name: "Active" + prefix: "192.168.1.0/24" + + location_types: + - name: "Site" + content_types: + - "!get:app_label": "dcim" + "!get:model": "device" + locations: + name: "Site" + status__name: "Active" + + manufacturers: + - "!create_or_update:name": "test-manufacturer" + + device_types: + - manufacturer__name: "test-manufacturer" + "!create_or_update:model": "test-type" + + autonomous_systems: + - "!create_or_update:asn": 64500 + status__name: "Active" + + devices: + - "!create_or_update:name": "device1" + status__name: "Active" + location__name: "Site" + role__name: "test-role" + device_type__model: "test-type" + interfaces: + - "!create_or_update:name": "Ethernet1/1" + type: "virtual" + status__name: "Active" + ip_address_assignments: + - ip_address: + "!create_or_update:address": "192.168.1.1/24" + status__name: "Active" + bgp_routing_instances: + - "!create_or_update:autonomous_system__asn": 64500 + "!ref": "device1-instance" + status__name: "Active" + + - "!create_or_update:name": "device2" + status__name: "Active" + location__name: "Site" + role__name: "test-role" + device_type__model: "test-type" + interfaces: + - "!create_or_update:name": "Ethernet1/1" + type: "virtual" + status__name: "Active" + ip_address_assignments: + - ip_address: + "!create_or_update:address": "192.168.1.2/24" + status__name: "Active" + bgp_routing_instances: + - "!create_or_update:autonomous_system__asn": 64500 + "!ref": "device2-instance" + status__name: "Active" + + bgp_peerings: + - "!bgp_peering": + endpoint_a: + "!create_or_update:routing_instance__device__name": "device1" + "!create_or_update:source_ip__address": "192.168.1.1/24" + endpoint_z: + "!create_or_update:routing_instance__device__name": "device2" + "!create_or_update:source_ip__address": "192.168.1.2/24" + status__name: "Active" +checks: + - equal: + - model: "nautobot_bgp_models.models.PeerEndpoint" + query: {routing_instance__device__name: "device1"} + attribute: "peering" + - model: "nautobot_bgp_models.models.PeerEndpoint" + query: {routing_instance__device__name: "device2"} + attribute: "peering" diff --git a/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/cable_connections.yaml b/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/cable_connections.yaml new file mode 100644 index 00000000..f7aef4ed --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/cable_connections.yaml @@ -0,0 +1,68 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.CableConnectionExtension" +designs: + - location_types: + - "!create_or_update:name": "Site" + content_types: + - "!get:app_label": "dcim" + "!get:model": "device" + locations: + - location_type__name: "Site" + "!create_or_update:name": "Site" + status__name: "Active" + roles: + - "!create_or_update:name": "test-role" + content_types: + - "!get:app_label": "dcim" + "!get:model": "device" + manufacturers: + - "!create_or_update:name": "test-manufacturer" + device_types: + - manufacturer__name: "test-manufacturer" + "!create_or_update:model": "test-type" + devices: + - "!create_or_update:name": "Device 1" + "!ref": "device1" + location__name: "Site" + status__name: "Active" + role__name: "test-role" + device_type__model: "test-type" + interfaces: + - "!create_or_update:name": "GigabitEthernet1" + type: "1000base-t" + status__name: "Active" + - "!create_or_update:name": "Device 2" + location__name: "Site" + status__name: "Active" + role__name: "test-role" + device_type__model: "test-type" + interfaces: + - "!create_or_update:name": "GigabitEthernet1" + type: "1000base-t" + status__name: "Active" + "!connect_cable": + status__name: "Planned" + to: + device: "!ref:device1" + name: "GigabitEthernet1" + # Second design, same as the first, checks for + # cable connection idempotence + - devices: + - "!create_or_update:name": "Device 1" + "!ref": "device1" + - "!create_or_update:name": "Device 2" + interfaces: + - "!create_or_update:name": "GigabitEthernet1" + "!connect_cable": + status__name: "Planned" + to: + device: "!ref:device1" + name: "GigabitEthernet1" + +checks: + - connected: + - model: "nautobot.dcim.models.Interface" + query: {device__name: "Device 1", name: "GigabitEthernet1"} + - model: "nautobot.dcim.models.Interface" + query: {device__name: "Device 2", name: "GigabitEthernet1"} diff --git a/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/child_prefix.yaml b/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/child_prefix.yaml new file mode 100644 index 00000000..d4ed1d17 --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/child_prefix.yaml @@ -0,0 +1,49 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.NextPrefixExtension" + - "nautobot_design_builder.contrib.ext.ChildPrefixExtension" +designs: + - tenants: + - name: "Nautobot Airports" + roles: + - name: "Video" + content_types: + - "!get:app_label": "ipam" + "!get:model": "prefix" + - name: "Servers" + content_types: + - "!get:app_label": "ipam" + "!get:model": "prefix" + - prefixes: + - prefix: "10.0.0.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Servers" + - prefix: "10.0.2.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Video" + - "!next_prefix": + prefix: + - "10.0.0.0/23" + length: 24 + status__name: "Active" + "!ref": "parent_prefix" + - "!child_prefix": + parent: "!ref:parent_prefix" + offset: "0.0.0.0/25" + status__name: "Active" + - "!child_prefix": + parent: "!ref:parent_prefix" + offset: "0.0.0.128/25" + status__name: "Active" +checks: + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.0.0/24"} + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.0.0/25"} + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.0.128/25"} diff --git a/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/next_prefix.yaml b/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/next_prefix.yaml new file mode 100644 index 00000000..bceb228c --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/next_prefix.yaml @@ -0,0 +1,61 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.NextPrefixExtension" +designs: + - tenants: + - name: "Nautobot Airports" + roles: + - name: "Video" + content_types: + - "!get:app_label": "ipam" + "!get:model": "prefix" + - name: "Servers" + content_types: + - "!get:app_label": "ipam" + "!get:model": "prefix" + - prefixes: + - prefix: "10.0.0.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Servers" + - prefix: "10.0.2.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Video" + - "!next_prefix": + prefix: + - "10.0.0.0/23" + - "10.0.2.0/23" + length: 24 + status__name: "Active" + - "!next_prefix": + prefix: + - "10.0.0.0/23" + - "10.0.2.0/23" + length: 24 + status__name: "Active" + - "!next_prefix": + prefix: + - "10.0.0.0/23" + - "10.0.2.0/23" + length: 24 + status__name: "Active" + - "!next_prefix": + prefix: + - "10.0.0.0/23" + - "10.0.2.0/23" + length: 24 + status__name: "Active" +checks: + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.0.0/24"} + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.1.0/24"} + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.2.0/24"} + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.3.0/24"} diff --git a/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/next_prefix_by_role_and_tenant.yaml b/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/next_prefix_by_role_and_tenant.yaml new file mode 100644 index 00000000..40faf41f --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/next_prefix_by_role_and_tenant.yaml @@ -0,0 +1,33 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.NextPrefixExtension" +designs: + - tenants: + - name: "Nautobot Airports" + roles: + - name: "Video" + content_types: + - "!get:app_label": "ipam" + "!get:model": "prefix" + - name: "Servers" + content_types: + - "!get:app_label": "ipam" + "!get:model": "prefix" + - prefixes: + - prefix: "10.0.0.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Servers" + - prefix: "10.0.2.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Video" + - "!next_prefix": + role__name: "Video" + tenant__name: "Nautobot Airports" + length: 24 + status__name: "Active" +checks: + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.2.0/24"} diff --git a/nautobot_design_builder/tests/test_builder.py b/nautobot_design_builder/tests/test_builder.py index 8a26cafd..207dc5d5 100644 --- a/nautobot_design_builder/tests/test_builder.py +++ b/nautobot_design_builder/tests/test_builder.py @@ -138,12 +138,12 @@ def test_runner(self, roll_back: Mock): @builder_test_case(os.path.join(os.path.dirname(__file__), "testdata")) -class TestGeneralDesigns(TestCase): # pylint:disable=too-many-public-methods +class TestGeneralDesigns(TestCase): """Designs that should work with all versions of Nautobot.""" @builder_test_case(os.path.join(os.path.dirname(__file__), "testdata", "nautobot_v1")) -class TestV1Designs(TestCase): # pylint:disable=too-many-public-methods +class TestV1Designs(TestCase): """Designs that only work in Nautobot 1.x""" def setUp(self): @@ -153,7 +153,7 @@ def setUp(self): @builder_test_case(os.path.join(os.path.dirname(__file__), "testdata", "nautobot_v2")) -class TestV2Designs(TestCase): # pylint:disable=too-many-public-methods +class TestV2Designs(TestCase): """Designs that only work in Nautobot 1.x""" def setUp(self):