diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..b00bc956 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,70 @@ +## Contributing + +### Format and Styling + +``` +make format +make lint +``` + +### Running tests +``` +make test +``` + +### Run with poetry +``` +poetry run trestle-bot +``` + +### Local testing + +For this guide, we will be using `podman` to test trestlebot in a running container. + +1. Build the image + +```bash +podman build -f Dockerfile -t localhost:5000/trestlebot:latest +``` + +2. Create an environment variables file if testing with the entrypoint script. + +> The entrypoint script is where the logic for GitHub specific integrations should be. The environment variables file will contain variables set by GitHub Actions. + +Example file named `envfile` + +``` +cat envfile +... + +GITHUB_OUTPUT= +INPUT_SKIP_ITEMS= +INPUT_CHECK_ONLY=true +INPUT_SKIP_ASSEMBLE=false +INPUT_SKIP_REGENERATE=false +INPUT_REPOSITORY=. +INPUT_BRANCH=test +INPUT_MARKDOWN_PATH=markdown/profiles +INPUT_OSCAL_MODEL=profile +INPUT_SSP_INDEX_PATH= +INPUT_COMMIT_MESSAGE= +INPUT_COMMIT_USER_NAME=testuser +INPUT_COMMIT_USER_EMAIL=test@example.com +INPUT_FILE_PATTERN=*.md,*.json +INPUT_COMMIT_AUTHOR_NAME= +INPUT_COMMIT_AUTHOR_EMAIL= +INPUT_TARGET_BRANCH= +GITHUB_ACTIONS=true + +``` +3. Use `podman secret` to store sensitive information like API tokens + +```bash +cat my-token.txt | podman secret create repo-secret - +``` + +4. Run the container + +```bash +podman run --entrypoint /entrypoint.sh --secret repo-secret,type=env,target=GITHUB_TOKEN --env-file=envfile -v my-trestle-space:/data -w /data localhost:5000/trestlebot:latest +``` \ No newline at end of file diff --git a/README.md b/README.md index f1fd2cf8..7ca387e2 100644 --- a/README.md +++ b/README.md @@ -41,24 +41,4 @@ Checkout [`action.yml`](./action.yml) for a full list of supported inputs and ou "comp2" ] }, -``` - - -## Contributing - -### Format and Styling - -``` -make format -make lint -``` - -### Running tests -``` -make test -``` - -### Run with poetry -``` -poetry run trestle-bot ``` \ No newline at end of file diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 1be640bb..c679e686 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -5,7 +5,7 @@ Verify the trigger you are using. The default branch is set to `github.ref_name`. If triggered on a pull request, you may notice this set to `pr-number/merge`. Set the branch field to `github.heaf_ref` which is set during pull request triggered workflows. -## Action does not have permission to commit +## Action does not have permission to commit/pull_request If your workflow requires that this action make changes to your branch, ensure the token being used has `content: write` permissions and the token is being set. @@ -16,4 +16,17 @@ If your workflow requires that this action make changes to your branch, ensure t token: ${{ secrets.TOKEN }} ``` +If your workflow requires that this action create a pull request (`target_branch` is set), ensure the token being used has `pull_request: write` permissions and the token is being set. + +```yaml +# github_token has no default. +# To use default token use ${{ secrets.GITHUB_TOKEN }} +- uses: RedHatProductSecurity/trestle-bot@main + with: + markdown_path: "markdown/profiles" + assemble_model: "profile" + target_branch: "main" + github_token: ${{ secrets.TOKEN }} +``` + > Note: Using the GitHub token provided with GitHub Actions to commit to a branch will [NOT trigger additional workflows](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow). \ No newline at end of file diff --git a/action.yml b/action.yml index 9a003e82..5741ec77 100644 --- a/action.yml +++ b/action.yml @@ -13,6 +13,9 @@ inputs: description: "Runs tasks and exits with an error if there is a diff. Defaults to false" required: false default: false + github_token: + description: "GitHub token used to make authenticated API requests" + required: false skip_assemble: description: "Skip assembly task. Defaults to false" required: false @@ -24,7 +27,6 @@ inputs: skip_items: description: "Comma-separated list of content by Trestle name to skip during task execution. For example `profile_x,profile_y`." required: false - default: "" ssp_index_path: description: Path relative to the repository path where the ssp index is located. See project README.md for information about the ssp index. required: false @@ -34,9 +36,12 @@ inputs: required: false default: "Sync automatic updates" branch: - description: Git branch name, where changes should be pushed too. Required if Action is used on the `pull_request` event + description: Name of the Git branch to which modifications should be pushed. Required if Action is used on the `pull_request` event. required: false default: ${{ github.ref_name }} + target_branch: + description: Target branch (or base branch) to create a pull request against. If unset, no pull request will be created. If set, a pull request will be created using the `branch` field as the head branch. + required: false file_pattern: description: Comma separated file pattern list used for `git add`. For example `component-definitions/*,*json`. Defaults to (`.`) required: false @@ -72,6 +77,8 @@ runs: using: "docker" image: "Dockerfile" entrypoint: "/entrypoint.sh" + env: + GITHUB_TOKEN: ${{ inputs.github_token }} branding: icon: "check" diff --git a/entrypoint.sh b/entrypoint.sh index ad5700e7..66a16dc5 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -32,6 +32,7 @@ command="python3.8 -m trestlebot \ --author-name=\"${INPUT_COMMIT_AUTHOR_NAME}\" \ --author-email=\"${INPUT_COMMIT_AUTHOR_EMAIL}\" \ --working-dir=\"${INPUT_REPOSITORY}\" \ + --target-branch=\"${INPUT_TARGET_BRANCH}\" \ --skip-items=\"${INPUT_SKIP_ITEMS}\"" # Conditionally include flags @@ -47,6 +48,16 @@ if [[ ${INPUT_CHECK_ONLY} == true ]]; then command+=" --check-only" fi +# Only set the token value when is a target branch so pull requests can be created +if [[ -n ${INPUT_TARGET_BRANCH} ]]; then + if [[ -z ${GITHUB_TOKEN} ]]; then + echo "Set the GITHUB_TOKEN env variable." + exit 1 + fi + + command+=" --with-token - <<<\"${GITHUB_TOKEN}\"" +fi + exec 3>&1 output=$(eval "$command" > >(tee /dev/fd/3) 2>&1) diff --git a/poetry.lock b/poetry.lock index 40872633..c85697c0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,13 +2,13 @@ [[package]] name = "anyio" -version = "3.7.0" +version = "3.7.1" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.7" files = [ - {file = "anyio-3.7.0-py3-none-any.whl", hash = "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0"}, - {file = "anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce"}, + {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, ] [package.dependencies] @@ -17,7 +17,7 @@ idna = ">=2.8" sniffio = ">=1.1" [package.extras] -doc = ["Sphinx (>=6.1.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme", "sphinxcontrib-jquery"] +doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (<0.22)"] @@ -320,13 +320,13 @@ files = [ [[package]] name = "click" -version = "8.1.3" +version = "8.1.4" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, + {file = "click-8.1.4-py3-none-any.whl", hash = "sha256:2739815aaa5d2c986a88f1e9230c55e17f0caad3d958a5e13ad0797c166db9e3"}, + {file = "click-8.1.4.tar.gz", hash = "sha256:b97d0c74955da062a7d4ef92fadb583806a585b2ea81958a81bd72726cbb8e37"}, ] [package.dependencies] @@ -407,13 +407,13 @@ files = [ [[package]] name = "compliance-trestle" -version = "2.1.1" +version = "2.2.1" description = "Tools to manage & autogenerate python objects representing the OSCAL layers/models" optional = false python-versions = "*" files = [ - {file = "compliance-trestle-2.1.1.tar.gz", hash = "sha256:78c657dd157bba375ab7cbcb9654e8efbf10d51201518061e67a1c2f32329275"}, - {file = "compliance_trestle-2.1.1-py2.py3-none-any.whl", hash = "sha256:dd3dc11df625cc9374cf0797a77751b0ff2ed4b9b5e98a2336836915d54438d7"}, + {file = "compliance-trestle-2.2.1.tar.gz", hash = "sha256:d9da50ea5da3f2df60ddaa83af52c73c37b8b48cf91e28eaac6df0af6dbad73b"}, + {file = "compliance_trestle-2.2.1-py2.py3-none-any.whl", hash = "sha256:ef964173a9f0841565e705334386717d6869c38747f9af2bcfc4956a684285df"}, ] [package.dependencies] @@ -428,7 +428,7 @@ Jinja2 = ">=3.0.1" openpyxl = ">=3.0,<4.0" orjson = "*" paramiko = "*" -pydantic = {version = ">=1.8.2", extras = ["email"]} +pydantic = {version = ">=1.8.2,<2.0.0", extras = ["email"]} python-dotenv = ">=0.10.4" python-frontmatter = "*" pywin32 = {version = ">=1.0", markers = "platform_system == \"Windows\""} @@ -555,13 +555,13 @@ test-randomorder = ["pytest-randomly"] [[package]] name = "datamodel-code-generator" -version = "0.20.0" +version = "0.21.1" description = "Datamodel Code Generator" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "datamodel_code_generator-0.20.0-py3-none-any.whl", hash = "sha256:fdb8dc18fd3a5d2c92e5e3ac473c5a48f3540066941e56fd947d2975cfebd281"}, - {file = "datamodel_code_generator-0.20.0.tar.gz", hash = "sha256:84dc7d6ae64ca67834b414c107adf3510f46a474ac21467a683aca0a4a8f0806"}, + {file = "datamodel_code_generator-0.21.1-py3-none-any.whl", hash = "sha256:9898d4a39a54638022850b0df85fab0ef20394382e88482b8bf8925d4817c493"}, + {file = "datamodel_code_generator-0.21.1.tar.gz", hash = "sha256:feab1c222c5bc3b7c53e1dc8b299bbc261bb17e9dbc22fdd60fc3a2806694ecc"}, ] [package.dependencies] @@ -574,11 +574,11 @@ isort = ">=4.3.21,<6.0" jinja2 = ">=2.10.1,<4.0" openapi-spec-validator = ">=0.2.8,<=0.5.2" packaging = "*" -prance = ">=0.18.2,<1.0" +prance = ">=0.18.2" pydantic = [ - {version = ">=1.10.0,<2.0.0", extras = ["email"], markers = "python_version >= \"3.11\" and python_version < \"4.0\""}, - {version = ">=1.5.1,<2.0.0", extras = ["email"], markers = "python_version < \"3.10\""}, - {version = ">=1.9.0,<2.0.0", extras = ["email"], markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, + {version = ">=1.10.0,<3.0", extras = ["email"], markers = "python_version >= \"3.11\" and python_version < \"4.0\""}, + {version = ">=1.5.1,<3.0", extras = ["email"], markers = "python_version < \"3.10\""}, + {version = ">=1.9.0,<3.0", extras = ["email"], markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, ] PySnooper = ">=0.4.1,<2.0.0" toml = ">=0.10.0,<1.0.0" @@ -645,13 +645,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.1.1" +version = "1.1.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, - {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, + {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, + {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, ] [package.extras] @@ -712,6 +712,27 @@ files = [ [package.dependencies] smmap = ">=3.0.1,<6" +[[package]] +name = "github3-py" +version = "4.0.1" +description = "Python wrapper for the GitHub API(http://developer.github.com/v3)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "github3.py-4.0.1-py3-none-any.whl", hash = "sha256:a89af7de25650612d1da2f0609622bcdeb07ee8a45a1c06b2d16a05e4234e753"}, + {file = "github3.py-4.0.1.tar.gz", hash = "sha256:30d571076753efc389edc7f9aaef338a4fcb24b54d8968d5f39b1342f45ddd36"}, +] + +[package.dependencies] +pyjwt = {version = ">=2.3.0", extras = ["crypto"]} +python-dateutil = ">=2.6.0" +requests = ">=2.18" +uritemplate = ">=3.0.0" + +[package.extras] +dev = ["build", "github3-py[test]", "tox (>=3.1.3)", "twine", "wheel"] +test = ["betamax (>=0.5.1)", "betamax-matchers (>=0.3.0)", "pytest (>=7.0)", "pytest-xdist[psutil]"] + [[package]] name = "gitpython" version = "3.1.31" @@ -739,13 +760,13 @@ files = [ [[package]] name = "httpcore" -version = "0.17.2" +version = "0.17.3" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.7" files = [ - {file = "httpcore-0.17.2-py3-none-any.whl", hash = "sha256:5581b9c12379c4288fe70f43c710d16060c10080617001e6b22a3b6dbcbefd36"}, - {file = "httpcore-0.17.2.tar.gz", hash = "sha256:125f8375ab60036db632f34f4b627a9ad085048eef7cb7d2616fea0f739f98af"}, + {file = "httpcore-0.17.3-py3-none-any.whl", hash = "sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87"}, + {file = "httpcore-0.17.3.tar.gz", hash = "sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888"}, ] [package.dependencies] @@ -1039,43 +1060,43 @@ files = [ [[package]] name = "mypy" -version = "1.4.0" +version = "1.4.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.7" files = [ - {file = "mypy-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3af348e0925a59213244f28c7c0c3a2c2088b4ba2fe9d6c8d4fbb0aba0b7d05"}, - {file = "mypy-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0b2e0da7ff9dd8d2066d093d35a169305fc4e38db378281fce096768a3dbdbf"}, - {file = "mypy-1.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210fe0f39ec5be45dd9d0de253cb79245f0a6f27631d62e0c9c7988be7152965"}, - {file = "mypy-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f7a5971490fd4a5a436e143105a1f78fa8b3fe95b30fff2a77542b4f3227a01f"}, - {file = "mypy-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:50f65f0e9985f1e50040e603baebab83efed9eb37e15a22a4246fa7cd660f981"}, - {file = "mypy-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b1b5c875fcf3e7217a3de7f708166f641ca154b589664c44a6fd6d9f17d9e7e"}, - {file = "mypy-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b4c734d947e761c7ceb1f09a98359dd5666460acbc39f7d0a6b6beec373c5840"}, - {file = "mypy-1.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5984a8d13d35624e3b235a793c814433d810acba9eeefe665cdfed3d08bc3af"}, - {file = "mypy-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0f98973e39e4a98709546a9afd82e1ffcc50c6ec9ce6f7870f33ebbf0bd4f26d"}, - {file = "mypy-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:19d42b08c7532d736a7e0fb29525855e355fa51fd6aef4f9bbc80749ff64b1a2"}, - {file = "mypy-1.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ba9a69172abaa73910643744d3848877d6aac4a20c41742027dcfd8d78f05d9"}, - {file = "mypy-1.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a34eed094c16cad0f6b0d889811592c7a9b7acf10d10a7356349e325d8704b4f"}, - {file = "mypy-1.4.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:53c2a1fed81e05ded10a4557fe12bae05b9ecf9153f162c662a71d924d504135"}, - {file = "mypy-1.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bba57b4d2328740749f676807fcf3036e9de723530781405cc5a5e41fc6e20de"}, - {file = "mypy-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:653863c75f0dbb687d92eb0d4bd9fe7047d096987ecac93bb7b1bc336de48ebd"}, - {file = "mypy-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7461469e163f87a087a5e7aa224102a30f037c11a096a0ceeb721cb0dce274c8"}, - {file = "mypy-1.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cf0ca95e4b8adeaf07815a78b4096b65adf64ea7871b39a2116c19497fcd0dd"}, - {file = "mypy-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:94a81b9354545123feb1a99b960faeff9e1fa204fce47e0042335b473d71530d"}, - {file = "mypy-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:67242d5b28ed0fa88edd8f880aed24da481929467fdbca6487167cb5e3fd31ff"}, - {file = "mypy-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3f2b353eebef669529d9bd5ae3566905a685ae98b3af3aad7476d0d519714758"}, - {file = "mypy-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:62bf18d97c6b089f77f0067b4e321db089d8520cdeefc6ae3ec0f873621c22e5"}, - {file = "mypy-1.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca33ab70a4aaa75bb01086a0b04f0ba8441e51e06fc57e28585176b08cad533b"}, - {file = "mypy-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5a0ee54c2cb0f957f8a6f41794d68f1a7e32b9968675ade5846f538504856d42"}, - {file = "mypy-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6c34d43e3d54ad05024576aef28081d9d0580f6fa7f131255f54020eb12f5352"}, - {file = "mypy-1.4.0-py3-none-any.whl", hash = "sha256:f051ca656be0c179c735a4c3193f307d34c92fdc4908d44fd4516fbe8b10567d"}, - {file = "mypy-1.4.0.tar.gz", hash = "sha256:de1e7e68148a213036276d1f5303b3836ad9a774188961eb2684eddff593b042"}, + {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, + {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, + {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, + {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, + {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, + {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, + {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, + {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, + {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, + {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, + {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, + {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, + {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, + {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, + {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, + {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, + {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, + {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, + {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, + {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=3.10" +typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] @@ -1283,13 +1304,13 @@ files = [ [[package]] name = "platformdirs" -version = "3.7.0" +version = "3.8.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.7.0-py3-none-any.whl", hash = "sha256:cfd065ba43133ff103ab3bd10aecb095c2a0035fcd1f07217c9376900d94ba07"}, - {file = "platformdirs-3.7.0.tar.gz", hash = "sha256:87fbf6473e87c078d536980ba970a472422e94f17b752cfad17024c18876d481"}, + {file = "platformdirs-3.8.0-py3-none-any.whl", hash = "sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e"}, + {file = "platformdirs-3.8.0.tar.gz", hash = "sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc"}, ] [package.extras] @@ -1313,13 +1334,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "prance" -version = "0.22.2.22.0" +version = "23.6.21.0" description = "Resolving Swagger/OpenAPI 2.0 and 3.0.0 Parser" optional = false python-versions = ">=3.8" files = [ - {file = "prance-0.22.2.22.0-py3-none-any.whl", hash = "sha256:57deeb67b7e93ef27c1c17845bf3ccb4af288ccfb5748c7e01779c01a8507f27"}, - {file = "prance-0.22.2.22.0.tar.gz", hash = "sha256:9a83f8a4f5fe0f2d896d238d4bec6b5788b10b94155414b3d88c21c1579b85bf"}, + {file = "prance-23.6.21.0-py3-none-any.whl", hash = "sha256:6a4276fa07ed9f22feda4331097d7503c4adc3097e46ffae97425f2c1026bd9f"}, + {file = "prance-23.6.21.0.tar.gz", hash = "sha256:d8c15f8ac34019751cc4945f866d8d964d7888016d10de3592e339567177cabe"}, ] [package.dependencies] @@ -1361,47 +1382,47 @@ files = [ [[package]] name = "pydantic" -version = "1.10.9" +version = "1.10.11" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e692dec4a40bfb40ca530e07805b1208c1de071a18d26af4a2a0d79015b352ca"}, - {file = "pydantic-1.10.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c52eb595db83e189419bf337b59154bdcca642ee4b2a09e5d7797e41ace783f"}, - {file = "pydantic-1.10.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:939328fd539b8d0edf244327398a667b6b140afd3bf7e347cf9813c736211896"}, - {file = "pydantic-1.10.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b48d3d634bca23b172f47f2335c617d3fcb4b3ba18481c96b7943a4c634f5c8d"}, - {file = "pydantic-1.10.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f0b7628fb8efe60fe66fd4adadd7ad2304014770cdc1f4934db41fe46cc8825f"}, - {file = "pydantic-1.10.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e1aa5c2410769ca28aa9a7841b80d9d9a1c5f223928ca8bec7e7c9a34d26b1d4"}, - {file = "pydantic-1.10.9-cp310-cp310-win_amd64.whl", hash = "sha256:eec39224b2b2e861259d6f3c8b6290d4e0fbdce147adb797484a42278a1a486f"}, - {file = "pydantic-1.10.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d111a21bbbfd85c17248130deac02bbd9b5e20b303338e0dbe0faa78330e37e0"}, - {file = "pydantic-1.10.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e9aec8627a1a6823fc62fb96480abe3eb10168fd0d859ee3d3b395105ae19a7"}, - {file = "pydantic-1.10.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07293ab08e7b4d3c9d7de4949a0ea571f11e4557d19ea24dd3ae0c524c0c334d"}, - {file = "pydantic-1.10.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ee829b86ce984261d99ff2fd6e88f2230068d96c2a582f29583ed602ef3fc2c"}, - {file = "pydantic-1.10.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4b466a23009ff5cdd7076eb56aca537c745ca491293cc38e72bf1e0e00de5b91"}, - {file = "pydantic-1.10.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7847ca62e581e6088d9000f3c497267868ca2fa89432714e21a4fb33a04d52e8"}, - {file = "pydantic-1.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:7845b31959468bc5b78d7b95ec52fe5be32b55d0d09983a877cca6aedc51068f"}, - {file = "pydantic-1.10.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:517a681919bf880ce1dac7e5bc0c3af1e58ba118fd774da2ffcd93c5f96eaece"}, - {file = "pydantic-1.10.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67195274fd27780f15c4c372f4ba9a5c02dad6d50647b917b6a92bf00b3d301a"}, - {file = "pydantic-1.10.9-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2196c06484da2b3fded1ab6dbe182bdabeb09f6318b7fdc412609ee2b564c49a"}, - {file = "pydantic-1.10.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6257bb45ad78abacda13f15bde5886efd6bf549dd71085e64b8dcf9919c38b60"}, - {file = "pydantic-1.10.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3283b574b01e8dbc982080d8287c968489d25329a463b29a90d4157de4f2baaf"}, - {file = "pydantic-1.10.9-cp37-cp37m-win_amd64.whl", hash = "sha256:5f8bbaf4013b9a50e8100333cc4e3fa2f81214033e05ac5aa44fa24a98670a29"}, - {file = "pydantic-1.10.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9cd67fb763248cbe38f0593cd8611bfe4b8ad82acb3bdf2b0898c23415a1f82"}, - {file = "pydantic-1.10.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f50e1764ce9353be67267e7fd0da08349397c7db17a562ad036aa7c8f4adfdb6"}, - {file = "pydantic-1.10.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73ef93e5e1d3c8e83f1ff2e7fdd026d9e063c7e089394869a6e2985696693766"}, - {file = "pydantic-1.10.9-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:128d9453d92e6e81e881dd7e2484e08d8b164da5507f62d06ceecf84bf2e21d3"}, - {file = "pydantic-1.10.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ad428e92ab68798d9326bb3e5515bc927444a3d71a93b4a2ca02a8a5d795c572"}, - {file = "pydantic-1.10.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fab81a92f42d6d525dd47ced310b0c3e10c416bbfae5d59523e63ea22f82b31e"}, - {file = "pydantic-1.10.9-cp38-cp38-win_amd64.whl", hash = "sha256:963671eda0b6ba6926d8fc759e3e10335e1dc1b71ff2a43ed2efd6996634dafb"}, - {file = "pydantic-1.10.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:970b1bdc6243ef663ba5c7e36ac9ab1f2bfecb8ad297c9824b542d41a750b298"}, - {file = "pydantic-1.10.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7e1d5290044f620f80cf1c969c542a5468f3656de47b41aa78100c5baa2b8276"}, - {file = "pydantic-1.10.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83fcff3c7df7adff880622a98022626f4f6dbce6639a88a15a3ce0f96466cb60"}, - {file = "pydantic-1.10.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0da48717dc9495d3a8f215e0d012599db6b8092db02acac5e0d58a65248ec5bc"}, - {file = "pydantic-1.10.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0a2aabdc73c2a5960e87c3ffebca6ccde88665616d1fd6d3db3178ef427b267a"}, - {file = "pydantic-1.10.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9863b9420d99dfa9c064042304868e8ba08e89081428a1c471858aa2af6f57c4"}, - {file = "pydantic-1.10.9-cp39-cp39-win_amd64.whl", hash = "sha256:e7c9900b43ac14110efa977be3da28931ffc74c27e96ee89fbcaaf0b0fe338e1"}, - {file = "pydantic-1.10.9-py3-none-any.whl", hash = "sha256:6cafde02f6699ce4ff643417d1a9223716ec25e228ddc3b436fe7e2d25a1f305"}, - {file = "pydantic-1.10.9.tar.gz", hash = "sha256:95c70da2cd3b6ddf3b9645ecaa8d98f3d80c606624b6d245558d202cd23ea3be"}, + {file = "pydantic-1.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ff44c5e89315b15ff1f7fdaf9853770b810936d6b01a7bcecaa227d2f8fe444f"}, + {file = "pydantic-1.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6c098d4ab5e2d5b3984d3cb2527e2d6099d3de85630c8934efcfdc348a9760e"}, + {file = "pydantic-1.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16928fdc9cb273c6af00d9d5045434c39afba5f42325fb990add2c241402d151"}, + {file = "pydantic-1.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0588788a9a85f3e5e9ebca14211a496409cb3deca5b6971ff37c556d581854e7"}, + {file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9baf78b31da2dc3d3f346ef18e58ec5f12f5aaa17ac517e2ffd026a92a87588"}, + {file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:373c0840f5c2b5b1ccadd9286782852b901055998136287828731868027a724f"}, + {file = "pydantic-1.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:c3339a46bbe6013ef7bdd2844679bfe500347ac5742cd4019a88312aa58a9847"}, + {file = "pydantic-1.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08a6c32e1c3809fbc49debb96bf833164f3438b3696abf0fbeceb417d123e6eb"}, + {file = "pydantic-1.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a451ccab49971af043ec4e0d207cbc8cbe53dbf148ef9f19599024076fe9c25b"}, + {file = "pydantic-1.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b02d24f7b2b365fed586ed73582c20f353a4c50e4be9ba2c57ab96f8091ddae"}, + {file = "pydantic-1.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f34739a89260dfa420aa3cbd069fbcc794b25bbe5c0a214f8fb29e363484b66"}, + {file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e297897eb4bebde985f72a46a7552a7556a3dd11e7f76acda0c1093e3dbcf216"}, + {file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d185819a7a059550ecb85d5134e7d40f2565f3dd94cfd870132c5f91a89cf58c"}, + {file = "pydantic-1.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:4400015f15c9b464c9db2d5d951b6a780102cfa5870f2c036d37c23b56f7fc1b"}, + {file = "pydantic-1.10.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2417de68290434461a266271fc57274a138510dca19982336639484c73a07af6"}, + {file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:331c031ba1554b974c98679bd0780d89670d6fd6f53f5d70b10bdc9addee1713"}, + {file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8268a735a14c308923e8958363e3a3404f6834bb98c11f5ab43251a4e410170c"}, + {file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:44e51ba599c3ef227e168424e220cd3e544288c57829520dc90ea9cb190c3248"}, + {file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d7781f1d13b19700b7949c5a639c764a077cbbdd4322ed505b449d3ca8edcb36"}, + {file = "pydantic-1.10.11-cp37-cp37m-win_amd64.whl", hash = "sha256:7522a7666157aa22b812ce14c827574ddccc94f361237ca6ea8bb0d5c38f1629"}, + {file = "pydantic-1.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc64eab9b19cd794a380179ac0e6752335e9555d214cfcb755820333c0784cb3"}, + {file = "pydantic-1.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8dc77064471780262b6a68fe67e013298d130414d5aaf9b562c33987dbd2cf4f"}, + {file = "pydantic-1.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe429898f2c9dd209bd0632a606bddc06f8bce081bbd03d1c775a45886e2c1cb"}, + {file = "pydantic-1.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:192c608ad002a748e4a0bed2ddbcd98f9b56df50a7c24d9a931a8c5dd053bd3d"}, + {file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ef55392ec4bb5721f4ded1096241e4b7151ba6d50a50a80a2526c854f42e6a2f"}, + {file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:41e0bb6efe86281623abbeeb0be64eab740c865388ee934cd3e6a358784aca6e"}, + {file = "pydantic-1.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:265a60da42f9f27e0b1014eab8acd3e53bd0bad5c5b4884e98a55f8f596b2c19"}, + {file = "pydantic-1.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:469adf96c8e2c2bbfa655fc7735a2a82f4c543d9fee97bd113a7fb509bf5e622"}, + {file = "pydantic-1.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e6cbfbd010b14c8a905a7b10f9fe090068d1744d46f9e0c021db28daeb8b6de1"}, + {file = "pydantic-1.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abade85268cc92dff86d6effcd917893130f0ff516f3d637f50dadc22ae93999"}, + {file = "pydantic-1.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9738b0f2e6c70f44ee0de53f2089d6002b10c33264abee07bdb5c7f03038303"}, + {file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:787cf23e5a0cde753f2eabac1b2e73ae3844eb873fd1f5bdbff3048d8dbb7604"}, + {file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:174899023337b9fc685ac8adaa7b047050616136ccd30e9070627c1aaab53a13"}, + {file = "pydantic-1.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:1954f8778489a04b245a1e7b8b22a9d3ea8ef49337285693cf6959e4b757535e"}, + {file = "pydantic-1.10.11-py3-none-any.whl", hash = "sha256:008c5e266c8aada206d0627a011504e14268a62091450210eda7c07fabe6963e"}, + {file = "pydantic-1.10.11.tar.gz", hash = "sha256:f66d479cf7eb331372c470614be6511eae96f1f120344c25f3f9bb59fb1b5528"}, ] [package.dependencies] @@ -1423,6 +1444,26 @@ files = [ {file = "pyflakes-3.0.1.tar.gz", hash = "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"}, ] +[[package]] +name = "pyjwt" +version = "2.7.0" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyJWT-2.7.0-py3-none-any.whl", hash = "sha256:ba2b425b15ad5ef12f200dc67dd56af4e26de2331f965c5439994dad075876e1"}, + {file = "PyJWT-2.7.0.tar.gz", hash = "sha256:bd6ca4a3c4285c1a2d4349e5a035fdf8fb94e04ccd0fcbe6ba289dae9cc3e074"}, +] + +[package.dependencies] +cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""} + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + [[package]] name = "pynacl" version = "1.5.0" @@ -1501,13 +1542,13 @@ tests = ["pytest"] [[package]] name = "pytest" -version = "7.3.2" +version = "7.4.0" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.3.2-py3-none-any.whl", hash = "sha256:cdcbd012c9312258922f8cd3f1b62a6580fdced17db6014896053d47cddf9295"}, - {file = "pytest-7.3.2.tar.gz", hash = "sha256:ee990a3cc55ba808b80795a79944756f315c67c12b56abd3ac993a7b8c17030b"}, + {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, + {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, ] [package.dependencies] @@ -1539,6 +1580,20 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + [[package]] name = "python-dotenv" version = "1.0.0" @@ -1799,13 +1854,24 @@ files = [ [[package]] name = "typing-extensions" -version = "4.6.3" +version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" optional = false python-versions = ">=3.7" files = [ - {file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"}, - {file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"}, + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] + +[[package]] +name = "uritemplate" +version = "4.1.1" +description = "Implementation of RFC 6570 URI Templates" +optional = false +python-versions = ">=3.6" +files = [ + {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, + {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, ] [[package]] @@ -1843,4 +1909,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = "^3.8.1" -content-hash = "7b26b9e0067e2558ba8512a6aa6707dfd45a5f4d3a957148626d044a1c59e8c8" +content-hash = "7f14774bf8e4f8873e17d21846a0554b2f0f435e4aab0469e0efe43cbd9b286b" diff --git a/pyproject.toml b/pyproject.toml index 84feea3b..c749049e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ trestle-bot = "trestlebot.cli:run" python = '^3.8.1' gitpython = "^3.1.31" compliance-trestle = "^2.1.1" +github3-py = "^4.0.1" [tool.poetry.group.dev.dependencies] flake8 = "^6.0.0" @@ -53,3 +54,13 @@ addopts = """ testpaths = [ 'tests', ] + +[tool.mypy] + +[[tool.mypy.overrides]] +module = "github3.*" +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = "ruamel" +ignore_missing_imports = true diff --git a/tests/trestlebot/test_bot.py b/tests/trestlebot/test_bot.py index 5c7e35a3..27abb1ef 100644 --- a/tests/trestlebot/test_bot.py +++ b/tests/trestlebot/test_bot.py @@ -16,14 +16,17 @@ """Test for top-level Trestle Bot logic.""" +import json import os from typing import Tuple +from unittest.mock import Mock, patch import pytest from git.repo import Repo import trestlebot.bot as bot from tests.testutils import clean +from trestlebot.provider import GitProvider def test_stage_files(tmp_repo: Tuple[str, Repo]) -> None: @@ -148,6 +151,47 @@ def test_local_commit_with_author(tmp_repo: Tuple[str, Repo]) -> None: clean(repo_path, repo) +def test_run(tmp_repo: Tuple[str, Repo]) -> None: + """Test bot run with mocked push""" + repo_path, repo = tmp_repo + + # Create a test file + test_file_path = os.path.join(repo_path, "test.txt") + with open(test_file_path, "w") as f: + f.write("Test content") + + repo.create_remote("origin", url="git.test.com/test/repo.git") + + with patch("git.remote.Remote.push") as mock_push: + mock_push.return_value = "Mocked result" + + # Test running the bot + commit_sha = bot.run( + working_dir=repo_path, + branch="main", + commit_name="Test User", + commit_email="test@example.com", + commit_message="Test commit message", + author_name="The Author", + author_email="author@test.com", + patterns=["*.txt"], + dry_run=False, + ) + assert commit_sha != "" + + # Verify that the commit is made + commit = next(repo.iter_commits()) + assert commit.message.strip() == "Test commit message" + assert commit.author.name == "The Author" + assert commit.author.email == "author@test.com" + mock_push.assert_called_once_with(refspec="HEAD:main") + + # Verify that the file is tracked by the commit + assert os.path.basename(test_file_path) in commit.stats.files + + clean(repo_path, repo) + + def test_run_dry_run(tmp_repo: Tuple[str, Repo]) -> None: """Test bot run with dry run""" repo_path, repo = tmp_repo @@ -204,6 +248,48 @@ def test_empty_commit(tmp_repo: Tuple[str, Repo]) -> None: clean(repo_path, repo) +def test_non_matching_files(tmp_repo: Tuple[str, Repo]) -> None: + """Test that non-matching files are ignored""" + repo_path, repo = tmp_repo + + # Create a test file + test_file_path = os.path.join(repo_path, "test.txt") + with open(test_file_path, "w") as f: + f.write("Test content") + + # Create a test file + data = {"test": "file"} + test_json_path = os.path.join(repo_path, "test.json") + with open(test_json_path, "w") as f: + json.dump(data, f, indent=4) + + # Test running the bot + commit_sha = bot.run( + working_dir=repo_path, + branch="main", + commit_name="Test User", + commit_email="test@example.com", + commit_message="Test commit message", + author_name="The Author", + author_email="author@test.com", + patterns=["*.json"], + dry_run=True, + ) + assert commit_sha != "" + + # Verify that the commit is made + commit = next(repo.iter_commits()) + assert commit.message.strip() == "Test commit message" + assert commit.author.name == "The Author" + assert commit.author.email == "author@test.com" + + # Verify that only the JSON file is tracked in the commits + assert os.path.basename(test_file_path) not in commit.stats.files + assert os.path.basename(test_json_path) in commit.stats.files + + clean(repo_path, repo) + + def test_run_check_only(tmp_repo: Tuple[str, Repo]) -> None: """Test bot run with check_only""" repo_path, repo = tmp_repo @@ -229,3 +315,62 @@ def test_run_check_only(tmp_repo: Tuple[str, Repo]) -> None: dry_run=True, check_only=True, ) + + clean(repo_path, repo) + + +def test_run_with_provider(tmp_repo: Tuple[str, Repo]) -> None: + """Test bot run with mock git provider""" + repo_path, repo = tmp_repo + + # Create a test file + test_file_path = os.path.join(repo_path, "test.txt") + with open(test_file_path, "w") as f: + f.write("Test content") + + mock = Mock(spec=GitProvider) + mock.create_pull_request.return_value = "10" + mock.parse_repository.return_value = ("ns", "repo") + + repo.create_remote("origin", url="git.test.com/test/repo.git") + + with patch("git.remote.Remote.push") as mock_push: + mock_push.return_value = "Mocked result" + + # Test running the bot + commit_sha = bot.run( + working_dir=repo_path, + branch="test", + commit_name="Test User", + commit_email="test@example.com", + commit_message="Test commit message", + author_name="The Author", + author_email="author@test.com", + patterns=["*.txt"], + git_provider=mock, + target_branch="main", + dry_run=False, + ) + assert commit_sha != "" + + # Verify that the commit is made + commit = next(repo.iter_commits()) + assert commit.message.strip() == "Test commit message" + assert commit.author.name == "The Author" + assert commit.author.email == "author@test.com" + + # Verify that the file is tracked by the commit + assert os.path.basename(test_file_path) in commit.stats.files + + # Verify that the method was called with the expected arguments + mock.create_pull_request.assert_called_once_with( + ns="ns", + repo_name="repo", + head_branch="test", + base_branch="main", + title="Automatic updates from trestlebot", + body="", + ) + mock_push.assert_called_once_with(refspec="HEAD:test") + + clean(repo_path, repo) diff --git a/tests/trestlebot/test_cli.py b/tests/trestlebot/test_cli.py index aabbdea8..eb994756 100644 --- a/tests/trestlebot/test_cli.py +++ b/tests/trestlebot/test_cli.py @@ -18,6 +18,7 @@ import sys from typing import List +from unittest.mock import patch import pytest @@ -75,3 +76,40 @@ def test_no_ssp_index(monkeypatch, valid_args_dict, capsys): captured = capsys.readouterr() assert "Must set ssp_index_path when using SSP as oscal model." in captured.err + + +def test_no_markdown_path(monkeypatch, valid_args_dict, capsys): + """Test without a markdown file passed as a flag""" + args_dict = valid_args_dict + args_dict["markdown-path"] = "" + monkeypatch.setattr(sys, "argv", ["trestlebot", *args_dict_to_list(args_dict)]) + + with pytest.raises(SystemExit): + cli_main() + + captured = capsys.readouterr() + + assert "Must set markdown path with oscal model." in captured.err + + +def test_with_target_branch(monkeypatch, valid_args_dict, capsys): + """Test with target branch set an an unsupported Git provider""" + args_dict = valid_args_dict + args_dict["target-branch"] = "main" + monkeypatch.setattr(sys, "argv", ["trestlebot", *args_dict_to_list(args_dict)]) + + with patch("trestlebot.cli.is_github_actions") as mock_check: + mock_check.return_value = False + + with pytest.raises(SystemExit): + cli_main() + + captured = capsys.readouterr() + + expected_string = ( + "target-branch flag is set with an unsupported git provider. " + "If testing locally with the GitHub API, " + "set the GITHUB_ACTIONS environment variable to true." + ) + + assert expected_string in captured.err diff --git a/tests/trestlebot/test_github.py b/tests/trestlebot/test_github.py new file mode 100644 index 00000000..b0c523ab --- /dev/null +++ b/tests/trestlebot/test_github.py @@ -0,0 +1,72 @@ +#!/usr/bin/python + +# Copyright 2023 Red Hat, Inc. +# +# 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. + +"""Test for GitHub provider logic""" + +from typing import Tuple + +import pytest +from git.repo import Repo + +from tests.testutils import clean +from trestlebot.github import GitHub +from trestlebot.provider import GitProviderException + + +@pytest.mark.parametrize( + "repo_url", + [ + "https://github.com/owner/repo", + "https://github.com/owner/repo.git", + "github.com/owner/repo.git", + ], +) +def test_parse_repository(repo_url: str) -> None: + """Tests parsing valid GitHub repo urls""" + gh = GitHub("fake") + + owner, repo_name = gh.parse_repository(repo_url) + + assert owner == "owner" + assert repo_name == "repo" + + +def test_parse_repository_integration(tmp_repo: Tuple[str, Repo]) -> None: + """Tests integration with git remote get-url""" + repo_path, repo = tmp_repo + + repo.create_remote("origin", url="github.com/test/repo.git") + + remote = repo.remote() + + gh = GitHub("fake") + + owner, repo_name = gh.parse_repository(remote.url) + + assert owner == "test" + assert repo_name == "repo" + + clean(repo_path, repo) + + +def test_parse_repository_with_incorrect_name() -> None: + """Test an invalid url input""" + gh = GitHub("fake") + with pytest.raises( + GitProviderException, + match="https://notgithub.com/owner/repo.git is an invalid GitHub repo URL", + ): + gh.parse_repository("https://notgithub.com/owner/repo.git") diff --git a/trestlebot/bot.py b/trestlebot/bot.py index 002db4f3..b7c802dd 100644 --- a/trestlebot/bot.py +++ b/trestlebot/bot.py @@ -23,6 +23,7 @@ from git.repo import Repo from git.util import Actor +from trestlebot.provider import GitProvider, GitProviderException from trestlebot.tasks.base_task import TaskBase, TaskException @@ -87,7 +88,9 @@ def run( author_name: str, author_email: str, patterns: List[str], + git_provider: Optional[GitProvider] = None, pre_tasks: Optional[List[TaskBase]] = None, + target_branch: str = "", check_only: bool = False, dry_run: bool = False, ) -> str: @@ -101,6 +104,7 @@ def run( author_name: Name of the commit author author_email: Email of the commit author patterns: List of file patterns for `git add` + git_provider: Optional configured git provider for interacting with the API pre_tasks: Optional task list to executing before updating the workspace dry_run: Only complete local work. Do not push. @@ -153,10 +157,32 @@ def run( remote.push(refspec=f"HEAD:{branch}") logger.info(f"Changes pushed to {branch} successfully.") + + # Only create a pull request if a GitProvider is configured and + # a target branch is set. + if git_provider is not None and target_branch: + logger.info( + f"Git provider detected, submitting pull request to {target_branch}" + ) + # Parse remote url to get repository information for pull request + namespace, repo_name = git_provider.parse_repository(remote.url) + logger.debug("Detected namespace {namespace} and {repo_name}") + + git_provider.create_pull_request( + ns=namespace, + repo_name=repo_name, + head_branch=branch, + base_branch=target_branch, + title="Automatic updates from trestlebot", + body="", + ) + return commit_sha except GitCommandError as e: - raise RepoException(f"Git push to {branch} failed: {e}") from e + raise RepoException(f"Git push to {branch} failed: {e}") + except GitProviderException as e: + raise RepoException(f"Git pull request to {target_branch} failed: {e}") else: logger.info("Nothing to commit") return commit_sha diff --git a/trestlebot/cli.py b/trestlebot/cli.py index 086e91e0..f019d533 100644 --- a/trestlebot/cli.py +++ b/trestlebot/cli.py @@ -19,10 +19,13 @@ import argparse import logging +import os import sys -from typing import List +from typing import List, Optional -from trestlebot import bot, log +from trestlebot import bot, const, log +from trestlebot.github import GitHub +from trestlebot.provider import GitProvider from trestlebot.tasks.assemble_task import AssembleTask from trestlebot.tasks.authored import types from trestlebot.tasks.base_task import TaskBase @@ -64,7 +67,6 @@ def _parse_cli_arguments() -> argparse.Namespace: "--skip-items", type=str, required=False, - default="", help="Comma-separated list of items of the chosen model type to skip when running tasks", ) parser.add_argument( @@ -115,14 +117,12 @@ def _parse_cli_arguments() -> argparse.Namespace: "--author-name", required=False, type=str, - default="", help="Name for commit author if differs from committer", ) parser.add_argument( "--author-email", required=False, type=str, - default="", help="Email for commit author if differs from committer", ) parser.add_argument( @@ -138,6 +138,22 @@ def _parse_cli_arguments() -> argparse.Namespace: action="store_true", help="Run in verbose mode", ) + parser.add_argument( + "--target-branch", + type=str, + required=False, + help="Target branch or base branch to create a pull request against. \ + No pull request is created if unset", + ) + parser.add_argument( + "--with-token", + nargs="?", + type=argparse.FileType("r"), + required=False, + default=sys.stdin, + help="Read token from standard input for authenticated requests with \ + Git provider (e.g. create pull requests)", + ) return parser.parse_args() @@ -147,7 +163,7 @@ def handle_exception( """Log the exception and return the exit code""" logger.error(msg + f": {exception}", exc_info=True) - return 1 + return const.ERROR_EXIT_CODE def run() -> None: @@ -157,6 +173,7 @@ def run() -> None: log.set_log_level_from_args(args=args) pre_tasks: List[TaskBase] = [] + git_provider: Optional[GitProvider] = None authored_list: List[str] = [model.value for model in types.AuthoredType] @@ -168,15 +185,15 @@ def run() -> None: f"Invalid value {args.oscal_model} for oscal model. " f"Please use catalog, profile, compdef, or ssp." ) - sys.exit(1) + sys.exit(const.ERROR_EXIT_CODE) if not args.markdown_path: - logger.error("Must set markdown path with assemble model.") - sys.exit(1) + logger.error("Must set markdown path with oscal model.") + sys.exit(const.ERROR_EXIT_CODE) if args.oscal_model == "ssp" and args.ssp_index_path == "": logger.error("Must set ssp_index_path when using SSP as oscal model.") - sys.exit(1) + sys.exit(const.ERROR_EXIT_CODE) # Assuming an edit has occurred assemble would be run before regenerate. # Adding this to the list first @@ -204,7 +221,22 @@ def run() -> None: else: logger.info("Regeneration task skipped") - exit_code: int = 0 + if args.target_branch: + if not is_github_actions(): + logger.error( + "target-branch flag is set with an unsupported git provider. " + "If testing locally with the GitHub API, set " + "the GITHUB_ACTIONS environment variable to true." + ) + sys.exit(const.ERROR_EXIT_CODE) + + if not args.with_token: + logger.error("with-token value cannot be empty") + sys.exit(const.ERROR_EXIT_CODE) + + git_provider = GitHub(access_token=args.with_token.read().strip()) + + exit_code: int = const.SUCCESS_EXIT_CODE # Assume it is a successful run, if the bot # throws an exception update the exit code accordingly @@ -219,6 +251,8 @@ def run() -> None: author_email=args.author_email, pre_tasks=pre_tasks, patterns=comma_sep_to_list(args.file_patterns), + git_provider=git_provider, + target_branch=args.target_branch, check_only=args.check_only, ) @@ -236,3 +270,12 @@ def comma_sep_to_list(string: str) -> List[str]: """Convert comma-sep string to list of strings and strip.""" string = string.strip() if string else "" return list(map(str.strip, string.split(","))) if string else [] + + +# GitHub ref: +# https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables +def is_github_actions() -> bool: + var_value = os.getenv("GITHUB_ACTIONS") + if var_value and var_value.lower() in ["true", "1"]: + return True + return False diff --git a/trestlebot/const.py b/trestlebot/const.py new file mode 100644 index 00000000..b471cdd7 --- /dev/null +++ b/trestlebot/const.py @@ -0,0 +1,21 @@ +#!/usr/bin/python + +# Copyright 2023 Red Hat, Inc. +# +# 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. + +"""Global constants""" + +# Common exit codes +SUCCESS_EXIT_CODE = 0 +ERROR_EXIT_CODE = 1 diff --git a/trestlebot/github.py b/trestlebot/github.py new file mode 100644 index 00000000..a0a7f741 --- /dev/null +++ b/trestlebot/github.py @@ -0,0 +1,104 @@ +#!/usr/bin/python + +# Copyright 2023 Red Hat, Inc. +# +# 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. + +"""GitHub related functions for the Trestle Bot.""" + +import re +from typing import Optional, Tuple + +import github3 +from github3.repos.repo import Repository + +from trestlebot.provider import GitProvider, GitProviderException + + +class GitHub(GitProvider): + """Create GitHub object to interact with the GitHub API""" + + def __init__(self, access_token: str): + """ + Initialize GitHub Object + + Args: + access_token: Access token to make authenticated API requests. + """ + session: github3.GitHub = github3.GitHub() + session.login(token=access_token) + + self.session = session + self.pattern = r"^(?:https?://)?github\.com/([^/]+)/([^/.]+)" + + def parse_repository(self, repo_url: str) -> Tuple[str, str]: + """ + Parse repository url + + Args: + repo_url: Valid url for GitHub repo + + Returns: + Owner and repo name in a tuple, respectively + """ + + match = re.match(self.pattern, repo_url) + + if not match: + raise GitProviderException(f"{repo_url} is an invalid GitHub repo URL") + + owner = match.group(1) + repo = match.group(2) + return (owner, repo) + + def create_pull_request( + self, + ns: str, + repo_name: str, + base_branch: str, + head_branch: str, + title: str, + body: str, + ) -> int: + """ + Create a pull request in the repository + + Args: + ns: Namespace or owner of the repository + repo_name: Name of the repository + base_branch: Branch that changes need to be merged into + head_branch: Branch with changes + title: Text for the title of the pull_request + body: Text for the body of the pull request + + Returns: + Pull request number + """ + repository: Optional[Repository] = self.session.repository( + owner=ns, repository=repo_name + ) + if repository is None: + raise GitProviderException( + f"Repository for {ns}/{repo_name} cannot be None" + ) + + pull_request = repository.create_pull( + title=title, body=body, base=base_branch, head=head_branch + ) + + if pull_request: + return pull_request.number + else: + raise GitProviderException( + "Failed to create pull request in {ns}/{repo_name} for {head_branch} to {base_branch}" + ) diff --git a/trestlebot/provider.py b/trestlebot/provider.py new file mode 100644 index 00000000..040626c5 --- /dev/null +++ b/trestlebot/provider.py @@ -0,0 +1,46 @@ +#!/usr/bin/python + +# Copyright 2023 Red Hat, Inc. +# +# 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. + +"""Base Git Provider class for the Trestle Bot.""" + +from abc import ABC, abstractmethod +from typing import Tuple + + +class GitProviderException(Exception): + """An error when interacting with a Git provider""" + + +class GitProvider(ABC): + """ + Abstract base class for Git provider types + """ + + @abstractmethod + def parse_repository(self, repository_url: str) -> Tuple[str, str]: + """Parse repository information into namespace and repo, respectively""" + + @abstractmethod + def create_pull_request( + self, + ns: str, + repo_name: str, + base_branch: str, + head_branch: str, + title: str, + body: str, + ) -> int: + """Create a pull request for a specified branch and return the request number""" diff --git a/trestlebot/tasks/assemble_task.py b/trestlebot/tasks/assemble_task.py index 900c7450..dc01ebf0 100644 --- a/trestlebot/tasks/assemble_task.py +++ b/trestlebot/tasks/assemble_task.py @@ -19,6 +19,7 @@ import os from typing import List +from trestlebot import const from trestlebot.tasks.authored import types from trestlebot.tasks.authored.base_authored import ( AuthoredObjectException, @@ -62,7 +63,12 @@ def execute(self) -> int: return self._assemble() def _assemble(self) -> int: - """Assemble all objects in markdown directory""" + """ + Assemble all objects in markdown directory + + Returns: + 0 on success, raises an exception if not successful + """ authored_object: AuthorObjectBase = types.get_authored_object( self._authored_model, self.get_working_dir(), self._ssp_index_path ) @@ -79,4 +85,4 @@ def _assemble(self) -> int: except AuthoredObjectException as e: raise TaskException(f"Assemble task failed for model {model_path}: {e}") - return 0 + return const.SUCCESS_EXIT_CODE diff --git a/trestlebot/tasks/regenerate_task.py b/trestlebot/tasks/regenerate_task.py index 9477d05b..3e0fb272 100644 --- a/trestlebot/tasks/regenerate_task.py +++ b/trestlebot/tasks/regenerate_task.py @@ -19,6 +19,7 @@ import os from typing import List +from trestlebot import const from trestlebot.tasks.authored import types from trestlebot.tasks.authored.base_authored import ( AuthoredObjectException, @@ -62,7 +63,12 @@ def execute(self) -> int: return self._regenerate() def _regenerate(self) -> int: - """Regenerate all objects in model JSON directory""" + """ + Regenerate all objects in model JSON directory + + Returns: + 0 on success, raises an exception if not successful + """ authored_object: AuthorObjectBase = types.get_authored_object( self._authored_model, self.get_working_dir(), self._ssp_index_path ) @@ -82,4 +88,4 @@ def _regenerate(self) -> int: except AuthoredObjectException as e: raise TaskException(f"Regenerate task failed for model {model}: {e}") - return 0 + return const.SUCCESS_EXIT_CODE