diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..0cd297a0 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,52 @@ +name: pre-release + +on: + push: + tags: [ "[0-9]+.[0-9]+.[0-9]+*" ] + +env: + GIT_CLIFF_VERSION: 2.12.0 + +jobs: + create-pre-release: + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout commit + uses: actions/checkout@v6 + with: + sparse-checkout: | + cliff.toml + sparse-checkout-cone-mode: false + - uses: gardenlinux/python-gardenlinux-lib/.github/actions/setup@main + - name: Use cargo cache + id: cache-cargo + uses: actions/cache@v5 + with: + path: | + ~/.cargo + key: gh-release-${{ runner.os }}-git-cliff-${{ env.GIT_CLIFF_VERSION }}- + restore-keys: gh-release-${{ runner.os }}-git-cliff-${{ env.GIT_CLIFF_VERSION }}-${{ hashFiles('~/.cargo/.crates.toml') }}- + - name: Install git-cliff + if: steps.cache-cargo.outputs.cache-hit != 'true' + run: | + cargo install git-cliff@${GIT_CLIFF_VERSION} + - name: Get the Git tag name + id: get-tag-name + run: echo "tag-name=${GITHUB_REF/refs\/tags\//}" | tee -a "$GITHUB_OUTPUT" + - id: release + name: Create changelog and release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gl-gh-release create \ + --repo "python-gardenlinux-lib" \ + --tag "${{ steps.get-tag-name.outputs.tag-name }}" \ + --commit "${{ github.sha }}" \ + --name 'python-gardenlinux-lib v${{ steps.get-tag-name.outputs.tag-name }}' \ + --latest \ + --body " + $(git-cliff -o - --current) + " diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 00000000..9ef8bdfc --- /dev/null +++ b/cliff.toml @@ -0,0 +1,84 @@ +# git-cliff ~ configuration file +# https://git-cliff.org/docs/configuration + +[changelog] +# A Tera template to be rendered as the changelog's header. +# See https://keats.github.io/tera/docs/#introduction +header = """ +{% if version -%} + # Changelog for {{ version }} }} +{% else -%} + # Changelog +{% endif -%} + +All notable changes since last release will be documented below. +""" +# A Tera template to be rendered for each release in the changelog. +# See https://keats.github.io/tera/docs/#introduction +body = """ +{%- macro remote_url() -%} + https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} +{%- endmacro -%} + +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | upper_first }} + {%- for commit in commits %} + - {{ commit.message | split(pat="\n") | first | upper_first | trim }}\ + {% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif -%} + {% if commit.remote.pr_number %} in \ + [#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) \ + {%- endif -%} + {% endfor %} +{% endfor %} +""" +# A Tera template to be rendered as the changelog's footer. +# See https://keats.github.io/tera/docs/#introduction +footer = """ +{%- macro remote_url() -%} + https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} +{%- endmacro -%} + +{% for release in releases -%} + {% if release.version -%} + {% if release.previous.version -%} + [{{ release.version | trim_start_matches(pat="v") }}]: \ + {{ self::remote_url() }}/compare/{{ release.previous.version }}...{{ release.version }} + {% endif -%} + {% else -%} + [unreleased]: {{ self::remote_url() }}/compare/{{ release.previous.version }}...HEAD + {% endif -%} +{% endfor %} + +""" +# Remove leading and trailing whitespaces from the changelog's body. +trim = true + +[git] +# Parse commits according to the conventional commits specification. +# See https://www.conventionalcommits.org +conventional_commits = true +# Exclude commits that do not match the conventional commits specification. +filter_unconventional = false +# An array of regex based parsers for extracting data from the commit message. +# Assigns commits to groups. +# Optionally sets the commit's scope and can decide to exclude commits from further processing. +commit_parsers = [ + { message = "^[a|A]dd", group = "Added" }, + { message = "^[s|S]upport", group = "Added" }, + { message = "^[r|R]emove", group = "Removed" }, + { message = "^.*: add", group = "Added" }, + { message = "^.*: support", group = "Added" }, + { message = "^.*: remove", group = "Removed" }, + { message = "^.*: delete", group = "Removed" }, + { message = "^test", group = "Fixed" }, + { message = "^fix", group = "Fixed" }, + { message = "^.*: fix", group = "Fixed" }, + { message = "^.*", group = "Changed" }, +] +# Prevent commits that are breaking from being excluded by commit parsers. +filter_commits = false +# Order releases topologically instead of chronologically. +topo_order = true +# Order of commits in each group/release within the changelog. +# Allowed values: newest, oldest +sort_commits = "oldest" diff --git a/docs/release.rst b/docs/release.rst new file mode 100644 index 00000000..da6a50a0 --- /dev/null +++ b/docs/release.rst @@ -0,0 +1,28 @@ +python-gardenlinux-lib release documentation +============================================ + +*python-gardenlinux-lib* strictly follow syntax and intention of `Semantic Versioning `. Each release reflects the intention and expected impact therefore. + +A new release is done by tagging a commit with a valid version. This will create a GitHub pre-release for proof-reading. Once done a new release can be published using GitHub CLI or UI. + +Newly added docstrings should contain the first version supporting the new API / command line. + +Step by step guide +------------------ + +#. *python-gardenlinux-lib* version files: + + *python-gardenlinux-lib* versioning needs to be set in: + + - ``pyproject.toml`` + - ``.github/actions/setup/action.yml`` + + Additionally at the moment (removal pending): + + - ``.github/actions/features_parse/action.yml`` + - ``.github/actions/flavors_parse/action.yml`` + +#. ``git tag `` +#. Review the generated pre-release changelog by visiting the GitHub project release page and publish it if applicable. +#. Projects consuming the *python-gardenlinux-lib* may use the following git URL for dependency definition: + ``gardenlinux @ git+https://github.com/gardenlinux/python-gardenlinux-lib.git@1.0.0`` diff --git a/poetry.lock b/poetry.lock index 7b880e27..f82f3135 100644 --- a/poetry.lock +++ b/poetry.lock @@ -52,14 +52,14 @@ dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)" [[package]] name = "bandit" -version = "1.9.2" +version = "1.9.3" description = "Security oriented static analyser for python code." optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "bandit-1.9.2-py3-none-any.whl", hash = "sha256:bda8d68610fc33a6e10b7a8f1d61d92c8f6c004051d5e946406be1fb1b16a868"}, - {file = "bandit-1.9.2.tar.gz", hash = "sha256:32410415cd93bf9c8b91972159d5cf1e7f063a9146d70345641cd3877de348ce"}, + {file = "bandit-1.9.3-py3-none-any.whl", hash = "sha256:4745917c88d2246def79748bde5e08b9d5e9b92f877863d43fab70cd8814ce6a"}, + {file = "bandit-1.9.3.tar.gz", hash = "sha256:ade4b9b7786f89ef6fc7344a52b34558caec5da74cb90373aed01de88472f774"}, ] [package.dependencies] @@ -97,14 +97,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "boto3-stubs" -version = "1.42.25" -description = "Type annotations for boto3 1.42.25 generated with mypy-boto3-builder 8.12.0" +version = "1.42.30" +description = "Type annotations for boto3 1.42.30 generated with mypy-boto3-builder 8.12.0" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "boto3_stubs-1.42.25-py3-none-any.whl", hash = "sha256:a61a4caaf2199d11510bb18d044254fa0fd1929a6b07817f00faa8e23437adc5"}, - {file = "boto3_stubs-1.42.25.tar.gz", hash = "sha256:fd40c758991ae1bcbd1adbb153d513a028bf525642f193f9a77f71220c493cf6"}, + {file = "boto3_stubs-1.42.30-py3-none-any.whl", hash = "sha256:e1d106cf9c662ecfd6044483e53c6e9584b6da916e753510f51a8bfc8d19016d"}, + {file = "boto3_stubs-1.42.30.tar.gz", hash = "sha256:68a2ca754686c980d79d1c67f2d4d5eb8dc3d89f4ec62d4080b95fbdad3ee01b"}, ] [package.dependencies] @@ -165,7 +165,7 @@ bedrock-data-automation-runtime = ["mypy-boto3-bedrock-data-automation-runtime ( bedrock-runtime = ["mypy-boto3-bedrock-runtime (>=1.42.0,<1.43.0)"] billing = ["mypy-boto3-billing (>=1.42.0,<1.43.0)"] billingconductor = ["mypy-boto3-billingconductor (>=1.42.0,<1.43.0)"] -boto3 = ["boto3 (==1.42.25)"] +boto3 = ["boto3 (==1.42.30)"] braket = ["mypy-boto3-braket (>=1.42.0,<1.43.0)"] budgets = ["mypy-boto3-budgets (>=1.42.0,<1.43.0)"] ce = ["mypy-boto3-ce (>=1.42.0,<1.43.0)"] @@ -554,14 +554,14 @@ crt = ["awscrt (==0.29.2)"] [[package]] name = "botocore-stubs" -version = "1.42.25" +version = "1.42.30" description = "Type annotations and code completion for botocore" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "botocore_stubs-1.42.25-py3-none-any.whl", hash = "sha256:49d15529002bd1099a9a099a77d70b7b52859153783440e96eb55791e8147d1b"}, - {file = "botocore_stubs-1.42.25.tar.gz", hash = "sha256:70a8a53ba2684ff462c44d5996acd85fc5c7eb969e2cf3c25274441269524298"}, + {file = "botocore_stubs-1.42.30-py3-none-any.whl", hash = "sha256:e41f864440d7d84ef514384557454ca094a5ee74e72c4ff907f637bc8a786df4"}, + {file = "botocore_stubs-1.42.30.tar.gz", hash = "sha256:c4d11678eb172263feb1de805452c376d9c11e54f1903a7cfa132ba765d57b7d"}, ] [package.dependencies] @@ -572,14 +572,14 @@ botocore = ["botocore"] [[package]] name = "certifi" -version = "2025.11.12" +version = "2026.1.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev", "docs"] files = [ - {file = "certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b"}, - {file = "certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316"}, + {file = "certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c"}, + {file = "certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120"}, ] [[package]] @@ -845,104 +845,104 @@ markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \" [[package]] name = "coverage" -version = "7.13.0" +version = "7.13.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "coverage-7.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:02d9fb9eccd48f6843c98a37bd6817462f130b86da8660461e8f5e54d4c06070"}, - {file = "coverage-7.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:367449cf07d33dc216c083f2036bb7d976c6e4903ab31be400ad74ad9f85ce98"}, - {file = "coverage-7.13.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cdb3c9f8fef0a954c632f64328a3935988d33a6604ce4bf67ec3e39670f12ae5"}, - {file = "coverage-7.13.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d10fd186aac2316f9bbb46ef91977f9d394ded67050ad6d84d94ed6ea2e8e54e"}, - {file = "coverage-7.13.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f88ae3e69df2ab62fb0bc5219a597cb890ba5c438190ffa87490b315190bb33"}, - {file = "coverage-7.13.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4be718e51e86f553bcf515305a158a1cd180d23b72f07ae76d6017c3cc5d791"}, - {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a00d3a393207ae12f7c49bb1c113190883b500f48979abb118d8b72b8c95c032"}, - {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a7b1cd820e1b6116f92c6128f1188e7afe421c7e1b35fa9836b11444e53ebd9"}, - {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:37eee4e552a65866f15dedd917d5e5f3d59805994260720821e2c1b51ac3248f"}, - {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:62d7c4f13102148c78d7353c6052af6d899a7f6df66a32bddcc0c0eb7c5326f8"}, - {file = "coverage-7.13.0-cp310-cp310-win32.whl", hash = "sha256:24e4e56304fdb56f96f80eabf840eab043b3afea9348b88be680ec5986780a0f"}, - {file = "coverage-7.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:74c136e4093627cf04b26a35dab8cbfc9b37c647f0502fc313376e11726ba303"}, - {file = "coverage-7.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0dfa3855031070058add1a59fdfda0192fd3e8f97e7c81de0596c145dea51820"}, - {file = "coverage-7.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fdb6f54f38e334db97f72fa0c701e66d8479af0bc3f9bfb5b90f1c30f54500f"}, - {file = "coverage-7.13.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7e442c013447d1d8d195be62852270b78b6e255b79b8675bad8479641e21fd96"}, - {file = "coverage-7.13.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ed5630d946859de835a85e9a43b721123a8a44ec26e2830b296d478c7fd4259"}, - {file = "coverage-7.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f15a931a668e58087bc39d05d2b4bf4b14ff2875b49c994bbdb1c2217a8daeb"}, - {file = "coverage-7.13.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30a3a201a127ea57f7e14ba43c93c9c4be8b7d17a26e03bb49e6966d019eede9"}, - {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a485ff48fbd231efa32d58f479befce52dcb6bfb2a88bb7bf9a0b89b1bc8030"}, - {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:22486cdafba4f9e471c816a2a5745337742a617fef68e890d8baf9f3036d7833"}, - {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:263c3dbccc78e2e331e59e90115941b5f53e85cfcc6b3b2fbff1fd4e3d2c6ea8"}, - {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5330fa0cc1f5c3c4c3bb8e101b742025933e7848989370a1d4c8c5e401ea753"}, - {file = "coverage-7.13.0-cp311-cp311-win32.whl", hash = "sha256:0f4872f5d6c54419c94c25dd6ae1d015deeb337d06e448cd890a1e89a8ee7f3b"}, - {file = "coverage-7.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51a202e0f80f241ccb68e3e26e19ab5b3bf0f813314f2c967642f13ebcf1ddfe"}, - {file = "coverage-7.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:d2a9d7f1c11487b1c69367ab3ac2d81b9b3721f097aa409a3191c3e90f8f3dd7"}, - {file = "coverage-7.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0b3d67d31383c4c68e19a88e28fc4c2e29517580f1b0ebec4a069d502ce1e0bf"}, - {file = "coverage-7.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:581f086833d24a22c89ae0fe2142cfaa1c92c930adf637ddf122d55083fb5a0f"}, - {file = "coverage-7.13.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a3a30f0e257df382f5f9534d4ce3d4cf06eafaf5192beb1a7bd066cb10e78fb"}, - {file = "coverage-7.13.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:583221913fbc8f53b88c42e8dbb8fca1d0f2e597cb190ce45916662b8b9d9621"}, - {file = "coverage-7.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f5d9bd30756fff3e7216491a0d6d520c448d5124d3d8e8f56446d6412499e74"}, - {file = "coverage-7.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a23e5a1f8b982d56fa64f8e442e037f6ce29322f1f9e6c2344cd9e9f4407ee57"}, - {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b01c22bc74a7fb44066aaf765224c0d933ddf1f5047d6cdfe4795504a4493f8"}, - {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:898cce66d0836973f48dda4e3514d863d70142bdf6dfab932b9b6a90ea5b222d"}, - {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3ab483ea0e251b5790c2aac03acde31bff0c736bf8a86829b89382b407cd1c3b"}, - {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d84e91521c5e4cb6602fe11ece3e1de03b2760e14ae4fcf1a4b56fa3c801fcd"}, - {file = "coverage-7.13.0-cp312-cp312-win32.whl", hash = "sha256:193c3887285eec1dbdb3f2bd7fbc351d570ca9c02ca756c3afbc71b3c98af6ef"}, - {file = "coverage-7.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:4f3e223b2b2db5e0db0c2b97286aba0036ca000f06aca9b12112eaa9af3d92ae"}, - {file = "coverage-7.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:086cede306d96202e15a4b77ace8472e39d9f4e5f9fd92dd4fecdfb2313b2080"}, - {file = "coverage-7.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:28ee1c96109974af104028a8ef57cec21447d42d0e937c0275329272e370ebcf"}, - {file = "coverage-7.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e97353dcc5587b85986cda4ff3ec98081d7e84dd95e8b2a6d59820f0545f8a"}, - {file = "coverage-7.13.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:99acd4dfdfeb58e1937629eb1ab6ab0899b131f183ee5f23e0b5da5cba2fec74"}, - {file = "coverage-7.13.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff45e0cd8451e293b63ced93161e189780baf444119391b3e7d25315060368a6"}, - {file = "coverage-7.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4f72a85316d8e13234cafe0a9f81b40418ad7a082792fa4165bd7d45d96066b"}, - {file = "coverage-7.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11c21557d0e0a5a38632cbbaca5f008723b26a89d70db6315523df6df77d6232"}, - {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76541dc8d53715fb4f7a3a06b34b0dc6846e3c69bc6204c55653a85dd6220971"}, - {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6e9e451dee940a86789134b6b0ffbe31c454ade3b849bb8a9d2cca2541a8e91d"}, - {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5c67dace46f361125e6b9cace8fe0b729ed8479f47e70c89b838d319375c8137"}, - {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f59883c643cb19630500f57016f76cfdcd6845ca8c5b5ea1f6e17f74c8e5f511"}, - {file = "coverage-7.13.0-cp313-cp313-win32.whl", hash = "sha256:58632b187be6f0be500f553be41e277712baa278147ecb7559983c6d9faf7ae1"}, - {file = "coverage-7.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:73419b89f812f498aca53f757dd834919b48ce4799f9d5cad33ca0ae442bdb1a"}, - {file = "coverage-7.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb76670874fdd6091eedcc856128ee48c41a9bbbb9c3f1c7c3cf169290e3ffd6"}, - {file = "coverage-7.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6e63ccc6e0ad8986386461c3c4b737540f20426e7ec932f42e030320896c311a"}, - {file = "coverage-7.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:494f5459ffa1bd45e18558cd98710c36c0b8fbfa82a5eabcbe671d80ecffbfe8"}, - {file = "coverage-7.13.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:06cac81bf10f74034e055e903f5f946e3e26fc51c09fc9f584e4a1605d977053"}, - {file = "coverage-7.13.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f2ffc92b46ed6e6760f1d47a71e56b5664781bc68986dbd1836b2b70c0ce2071"}, - {file = "coverage-7.13.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0602f701057c6823e5db1b74530ce85f17c3c5be5c85fc042ac939cbd909426e"}, - {file = "coverage-7.13.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:25dc33618d45456ccb1d37bce44bc78cf269909aa14c4db2e03d63146a8a1493"}, - {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71936a8b3b977ddd0b694c28c6a34f4fff2e9dd201969a4ff5d5fc7742d614b0"}, - {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:936bc20503ce24770c71938d1369461f0c5320830800933bc3956e2a4ded930e"}, - {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:af0a583efaacc52ae2521f8d7910aff65cdb093091d76291ac5820d5e947fc1c"}, - {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f1c23e24a7000da892a312fb17e33c5f94f8b001de44b7cf8ba2e36fbd15859e"}, - {file = "coverage-7.13.0-cp313-cp313t-win32.whl", hash = "sha256:5f8a0297355e652001015e93be345ee54393e45dc3050af4a0475c5a2b767d46"}, - {file = "coverage-7.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6abb3a4c52f05e08460bd9acf04fec027f8718ecaa0d09c40ffbc3fbd70ecc39"}, - {file = "coverage-7.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3ad968d1e3aa6ce5be295ab5fe3ae1bf5bb4769d0f98a80a0252d543a2ef2e9e"}, - {file = "coverage-7.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:453b7ec753cf5e4356e14fe858064e5520c460d3bbbcb9c35e55c0d21155c256"}, - {file = "coverage-7.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af827b7cbb303e1befa6c4f94fd2bf72f108089cfa0f8abab8f4ca553cf5ca5a"}, - {file = "coverage-7.13.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9987a9e4f8197a1000280f7cc089e3ea2c8b3c0a64d750537809879a7b4ceaf9"}, - {file = "coverage-7.13.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3188936845cd0cb114fa6a51842a304cdbac2958145d03be2377ec41eb285d19"}, - {file = "coverage-7.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2bdb3babb74079f021696cb46b8bb5f5661165c385d3a238712b031a12355be"}, - {file = "coverage-7.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7464663eaca6adba4175f6c19354feea61ebbdd735563a03d1e472c7072d27bb"}, - {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8069e831f205d2ff1f3d355e82f511eb7c5522d7d413f5db5756b772ec8697f8"}, - {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6fb2d5d272341565f08e962cce14cdf843a08ac43bd621783527adb06b089c4b"}, - {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5e70f92ef89bac1ac8a99b3324923b4749f008fdbd7aa9cb35e01d7a284a04f9"}, - {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b5de7d4583e60d5fd246dd57fcd3a8aa23c6e118a8c72b38adf666ba8e7e927"}, - {file = "coverage-7.13.0-cp314-cp314-win32.whl", hash = "sha256:a6c6e16b663be828a8f0b6c5027d36471d4a9f90d28444aa4ced4d48d7d6ae8f"}, - {file = "coverage-7.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:0900872f2fdb3ee5646b557918d02279dc3af3dfb39029ac4e945458b13f73bc"}, - {file = "coverage-7.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:3a10260e6a152e5f03f26db4a407c4c62d3830b9af9b7c0450b183615f05d43b"}, - {file = "coverage-7.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9097818b6cc1cfb5f174e3263eba4a62a17683bcfe5c4b5d07f4c97fa51fbf28"}, - {file = "coverage-7.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0018f73dfb4301a89292c73be6ba5f58722ff79f51593352759c1790ded1cabe"}, - {file = "coverage-7.13.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:166ad2a22ee770f5656e1257703139d3533b4a0b6909af67c6b4a3adc1c98657"}, - {file = "coverage-7.13.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f6aaef16d65d1787280943f1c8718dc32e9cf141014e4634d64446702d26e0ff"}, - {file = "coverage-7.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e999e2dcc094002d6e2c7bbc1fb85b58ba4f465a760a8014d97619330cdbbbf3"}, - {file = "coverage-7.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:00c3d22cf6fb1cf3bf662aaaa4e563be8243a5ed2630339069799835a9cc7f9b"}, - {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22ccfe8d9bb0d6134892cbe1262493a8c70d736b9df930f3f3afae0fe3ac924d"}, - {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9372dff5ea15930fea0445eaf37bbbafbc771a49e70c0aeed8b4e2c2614cc00e"}, - {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:69ac2c492918c2461bc6ace42d0479638e60719f2a4ef3f0815fa2df88e9f940"}, - {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:739c6c051a7540608d097b8e13c76cfa85263ced467168dc6b477bae3df7d0e2"}, - {file = "coverage-7.13.0-cp314-cp314t-win32.whl", hash = "sha256:fe81055d8c6c9de76d60c94ddea73c290b416e061d40d542b24a5871bad498b7"}, - {file = "coverage-7.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:445badb539005283825959ac9fa4a28f712c214b65af3a2c464f1adc90f5fcbc"}, - {file = "coverage-7.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:de7f6748b890708578fc4b7bb967d810aeb6fcc9bff4bb77dbca77dab2f9df6a"}, - {file = "coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904"}, - {file = "coverage-7.13.0.tar.gz", hash = "sha256:a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936"}, + {file = "coverage-7.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1fa280b3ad78eea5be86f94f461c04943d942697e0dac889fa18fff8f5f9147"}, + {file = "coverage-7.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c3d8c679607220979434f494b139dfb00131ebf70bb406553d69c1ff01a5c33d"}, + {file = "coverage-7.13.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:339dc63b3eba969067b00f41f15ad161bf2946613156fb131266d8debc8e44d0"}, + {file = "coverage-7.13.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db622b999ffe49cb891f2fff3b340cdc2f9797d01a0a202a0973ba2562501d90"}, + {file = "coverage-7.13.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1443ba9acbb593fa7c1c29e011d7c9761545fe35e7652e85ce7f51a16f7e08d"}, + {file = "coverage-7.13.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c832ec92c4499ac463186af72f9ed4d8daec15499b16f0a879b0d1c8e5cf4a3b"}, + {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:562ec27dfa3f311e0db1ba243ec6e5f6ab96b1edfcfc6cf86f28038bc4961ce6"}, + {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4de84e71173d4dada2897e5a0e1b7877e5eefbfe0d6a44edee6ce31d9b8ec09e"}, + {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a5a68357f686f8c4d527a2dc04f52e669c2fc1cbde38f6f7eb6a0e58cbd17cae"}, + {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:77cc258aeb29a3417062758975521eae60af6f79e930d6993555eeac6a8eac29"}, + {file = "coverage-7.13.1-cp310-cp310-win32.whl", hash = "sha256:bb4f8c3c9a9f34423dba193f241f617b08ffc63e27f67159f60ae6baf2dcfe0f"}, + {file = "coverage-7.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:c8e2706ceb622bc63bac98ebb10ef5da80ed70fbd8a7999a5076de3afaef0fb1"}, + {file = "coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88"}, + {file = "coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3"}, + {file = "coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9"}, + {file = "coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee"}, + {file = "coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf"}, + {file = "coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3"}, + {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef"}, + {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851"}, + {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb"}, + {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba"}, + {file = "coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19"}, + {file = "coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a"}, + {file = "coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c"}, + {file = "coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3"}, + {file = "coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e"}, + {file = "coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c"}, + {file = "coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62"}, + {file = "coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968"}, + {file = "coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e"}, + {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f"}, + {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee"}, + {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf"}, + {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c"}, + {file = "coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7"}, + {file = "coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6"}, + {file = "coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c"}, + {file = "coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78"}, + {file = "coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b"}, + {file = "coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd"}, + {file = "coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992"}, + {file = "coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4"}, + {file = "coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a"}, + {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766"}, + {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4"}, + {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398"}, + {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784"}, + {file = "coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461"}, + {file = "coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500"}, + {file = "coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9"}, + {file = "coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc"}, + {file = "coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a"}, + {file = "coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4"}, + {file = "coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6"}, + {file = "coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1"}, + {file = "coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd"}, + {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c"}, + {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0"}, + {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e"}, + {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53"}, + {file = "coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842"}, + {file = "coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2"}, + {file = "coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09"}, + {file = "coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894"}, + {file = "coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a"}, + {file = "coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f"}, + {file = "coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909"}, + {file = "coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4"}, + {file = "coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75"}, + {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9"}, + {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465"}, + {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864"}, + {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9"}, + {file = "coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5"}, + {file = "coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a"}, + {file = "coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0"}, + {file = "coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a"}, + {file = "coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6"}, + {file = "coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673"}, + {file = "coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5"}, + {file = "coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d"}, + {file = "coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8"}, + {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486"}, + {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564"}, + {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7"}, + {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416"}, + {file = "coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f"}, + {file = "coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79"}, + {file = "coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4"}, + {file = "coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573"}, + {file = "coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd"}, ] [package.extras] @@ -1039,26 +1039,26 @@ files = [ [[package]] name = "docutils" -version = "0.21.2" +version = "0.22.4" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=3.9" groups = ["docs"] files = [ - {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, - {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, + {file = "docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de"}, + {file = "docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968"}, ] [[package]] name = "filelock" -version = "3.20.1" +version = "3.20.3" description = "A platform independent file lock." optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a"}, - {file = "filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c"}, + {file = "filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1"}, + {file = "filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1"}, ] [[package]] @@ -1097,14 +1097,14 @@ test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3. [[package]] name = "identify" -version = "2.6.15" +version = "2.6.16" description = "File identification library for Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757"}, - {file = "identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf"}, + {file = "identify-2.6.16-py2.py3-none-any.whl", hash = "sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0"}, + {file = "identify-2.6.16.tar.gz", hash = "sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980"}, ] [package.extras] @@ -1197,21 +1197,21 @@ files = [ [[package]] name = "jsonschema" -version = "4.25.1" +version = "4.26.0" description = "An implementation of JSON Schema validation for Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63"}, - {file = "jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85"}, + {file = "jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce"}, + {file = "jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326"}, ] [package.dependencies] attrs = ">=22.2.0" jsonschema-specifications = ">=2023.03.6" referencing = ">=0.28.4" -rpds-py = ">=0.7.1" +rpds-py = ">=0.25.0" [package.extras] format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] @@ -1522,14 +1522,14 @@ test-extras = ["pytest-mpl", "pytest-randomly"] [[package]] name = "nodeenv" -version = "1.9.1" +version = "1.10.0" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["dev"] files = [ - {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, - {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, + {file = "nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827"}, + {file = "nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb"}, ] [[package]] @@ -1718,6 +1718,25 @@ files = [ [package.dependencies] cffi = ">=2.0" +[[package]] +name = "pygithub" +version = "2.8.1" +description = "Use the full Github API v3" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pygithub-2.8.1-py3-none-any.whl", hash = "sha256:23a0a5bca93baef082e03411bf0ce27204c32be8bfa7abc92fe4a3e132936df0"}, + {file = "pygithub-2.8.1.tar.gz", hash = "sha256:341b7c78521cb07324ff670afd1baa2bf5c286f8d9fd302c1798ba594a5400c9"}, +] + +[package.dependencies] +pyjwt = {version = ">=2.4.0", extras = ["crypto"]} +pynacl = ">=1.4.0" +requests = ">=2.14.0" +typing-extensions = ">=4.5.0" +urllib3 = ">=1.26.0" + [[package]] name = "pygments" version = "2.19.2" @@ -1733,6 +1752,69 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pyjwt" +version = "2.10.1" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, + {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, +] + +[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", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "pynacl" +version = "1.6.2" +description = "Python binding to the Networking and Cryptography (NaCl) library" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pynacl-1.6.2-cp314-cp314t-macosx_10_10_universal2.whl", hash = "sha256:622d7b07cc5c02c666795792931b50c91f3ce3c2649762efb1ef0d5684c81594"}, + {file = "pynacl-1.6.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d071c6a9a4c94d79eb665db4ce5cedc537faf74f2355e4d502591d850d3913c0"}, + {file = "pynacl-1.6.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe9847ca47d287af41e82be1dd5e23023d3c31a951da134121ab02e42ac218c9"}, + {file = "pynacl-1.6.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04316d1fc625d860b6c162fff704eb8426b1a8bcd3abacea11142cbd99a6b574"}, + {file = "pynacl-1.6.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44081faff368d6c5553ccf55322ef2819abb40e25afaec7e740f159f74813634"}, + {file = "pynacl-1.6.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:a9f9932d8d2811ce1a8ffa79dcbdf3970e7355b5c8eb0c1a881a57e7f7d96e88"}, + {file = "pynacl-1.6.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:bc4a36b28dd72fb4845e5d8f9760610588a96d5a51f01d84d8c6ff9849968c14"}, + {file = "pynacl-1.6.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bffb6d0f6becacb6526f8f42adfb5efb26337056ee0831fb9a7044d1a964444"}, + {file = "pynacl-1.6.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fef529ef3ee487ad8113d287a593fa26f48ee3620d92ecc6f1d09ea38e0709b"}, + {file = "pynacl-1.6.2-cp314-cp314t-win32.whl", hash = "sha256:a84bf1c20339d06dc0c85d9aea9637a24f718f375d861b2668b2f9f96fa51145"}, + {file = "pynacl-1.6.2-cp314-cp314t-win_amd64.whl", hash = "sha256:320ef68a41c87547c91a8b58903c9caa641ab01e8512ce291085b5fe2fcb7590"}, + {file = "pynacl-1.6.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d29bfe37e20e015a7d8b23cfc8bd6aa7909c92a1b8f41ee416bbb3e79ef182b2"}, + {file = "pynacl-1.6.2-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465"}, + {file = "pynacl-1.6.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8845c0631c0be43abdd865511c41eab235e0be69c81dc66a50911594198679b0"}, + {file = "pynacl-1.6.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22de65bb9010a725b0dac248f353bb072969c94fa8d6b1f34b87d7953cf7bbe4"}, + {file = "pynacl-1.6.2-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46065496ab748469cdd999246d17e301b2c24ae2fdf739132e580a0e94c94a87"}, + {file = "pynacl-1.6.2-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a66d6fb6ae7661c58995f9c6435bda2b1e68b54b598a6a10247bfcdadac996c"}, + {file = "pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:26bfcd00dcf2cf160f122186af731ae30ab120c18e8375684ec2670dccd28130"}, + {file = "pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c8a231e36ec2cab018c4ad4358c386e36eede0319a0c41fed24f840b1dac59f6"}, + {file = "pynacl-1.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:68be3a09455743ff9505491220b64440ced8973fe930f270c8e07ccfa25b1f9e"}, + {file = "pynacl-1.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8b097553b380236d51ed11356c953bf8ce36a29a3e596e934ecabe76c985a577"}, + {file = "pynacl-1.6.2-cp38-abi3-win32.whl", hash = "sha256:5811c72b473b2f38f7e2a3dc4f8642e3a3e9b5e7317266e4ced1fba85cae41aa"}, + {file = "pynacl-1.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:62985f233210dee6548c223301b6c25440852e13d59a8b81490203c3227c5ba0"}, + {file = "pynacl-1.6.2-cp38-abi3-win_arm64.whl", hash = "sha256:834a43af110f743a754448463e8fd61259cd4ab5bbedcf70f9dabad1d28a394c"}, + {file = "pynacl-1.6.2.tar.gz", hash = "sha256:018494d6d696ae03c7e656e5e74cdfd8ea1326962cc401bcf018f1ed8436811c"}, +] + +[package.dependencies] +cffi = {version = ">=2.0.0", markers = "platform_python_implementation != \"PyPy\" and python_version >= \"3.9\""} + +[package.extras] +docs = ["sphinx (<7)", "sphinx_rtd_theme"] +tests = ["hypothesis (>=3.27.0)", "pytest (>=7.4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] + [[package]] name = "pytest" version = "9.0.2" @@ -1985,31 +2067,16 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "roman-numerals" -version = "4.0.0" +version = "4.1.0" description = "Manipulate well-formed Roman numerals" optional = false python-versions = ">=3.10" groups = ["docs"] files = [ - {file = "roman_numerals-4.0.0-py3-none-any.whl", hash = "sha256:4131feb23ba1a542494873e4cee7844ec8d226a750134efc65ceb20939ed33c9"}, - {file = "roman_numerals-4.0.0.tar.gz", hash = "sha256:231287018a8788bf8c0718482a08c15b90458523ea1d840a18a791a86d4583b3"}, + {file = "roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7"}, + {file = "roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2"}, ] -[[package]] -name = "roman-numerals-py" -version = "4.0.0" -description = "This package is deprecated, switch to roman-numerals." -optional = false -python-versions = ">=3.10" -groups = ["docs"] -files = [ - {file = "roman_numerals_py-4.0.0-py3-none-any.whl", hash = "sha256:dfcecf6e0cddbf2ee1112e7e2ebf58ba771984f075cb57a30e1811cee4f06332"}, - {file = "roman_numerals_py-4.0.0.tar.gz", hash = "sha256:f7fa8dff5b7b7251d3a7586b97c57a0698e2e28898fa42c23bcc0cf51b02aee9"}, -] - -[package.dependencies] -roman-numerals = "4.0.0" - [[package]] name = "rpds-py" version = "0.30.0" @@ -2191,27 +2258,27 @@ files = [ [[package]] name = "sphinx" -version = "8.2.3" +version = "9.1.0" description = "Python documentation generator" optional = false -python-versions = ">=3.11" +python-versions = ">=3.12" groups = ["docs"] files = [ - {file = "sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3"}, - {file = "sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348"}, + {file = "sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978"}, + {file = "sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb"}, ] [package.dependencies] alabaster = ">=0.7.14" babel = ">=2.13" colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} -docutils = ">=0.20,<0.22" +docutils = ">=0.21,<0.23" imagesize = ">=1.3" Jinja2 = ">=3.1" packaging = ">=23.0" Pygments = ">=2.17" requests = ">=2.30.0" -roman-numerals-py = ">=1.0.0" +roman-numerals = ">=1.0.0" snowballstemmer = ">=2.2" sphinxcontrib-applehelp = ">=1.0.7" sphinxcontrib-devhelp = ">=1.0.6" @@ -2220,26 +2287,21 @@ sphinxcontrib-jsmath = ">=1.0.1" sphinxcontrib-qthelp = ">=1.0.6" sphinxcontrib-serializinghtml = ">=1.1.9" -[package.extras] -docs = ["sphinxcontrib-websupport"] -lint = ["betterproto (==2.0.0b6)", "mypy (==1.15.0)", "pypi-attestations (==0.0.21)", "pyright (==1.1.395)", "pytest (>=8.0)", "ruff (==0.9.9)", "sphinx-lint (>=0.9)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.19.0.20250219)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241128)", "types-requests (==2.32.0.20241016)", "types-urllib3 (==1.26.25.14)"] -test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "pytest-xdist[psutil] (>=3.4)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] - [[package]] name = "sphinx-rtd-theme" -version = "3.0.2" +version = "3.1.0" description = "Read the Docs theme for Sphinx" optional = false python-versions = ">=3.8" groups = ["docs"] files = [ - {file = "sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13"}, - {file = "sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85"}, + {file = "sphinx_rtd_theme-3.1.0-py2.py3-none-any.whl", hash = "sha256:1785824ae8e6632060490f67cf3a72d404a85d2d9fc26bce3619944de5682b89"}, + {file = "sphinx_rtd_theme-3.1.0.tar.gz", hash = "sha256:b44276f2c276e909239a4f6c955aa667aaafeb78597923b1c60babc76db78e4c"}, ] [package.dependencies] -docutils = ">0.18,<0.22" -sphinx = ">=6,<9" +docutils = ">0.18,<0.23" +sphinx = ">=6,<10" sphinxcontrib-jquery = ">=4,<5" [package.extras] @@ -2374,14 +2436,14 @@ files = [ [[package]] name = "types-awscrt" -version = "0.31.0" +version = "0.31.1" description = "Type annotations and code completion for awscrt" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "types_awscrt-0.31.0-py3-none-any.whl", hash = "sha256:009cfe5b9af8c75e8304243490e20a5229e7a56203f1d41481f5522233453f51"}, - {file = "types_awscrt-0.31.0.tar.gz", hash = "sha256:aa8b42148af0847be14e2b8ea3637a3518ffab038f8d3be7083950f3ce87d3ff"}, + {file = "types_awscrt-0.31.1-py3-none-any.whl", hash = "sha256:7e4364ac635f72bd57f52b093883640b1448a6eded0ecbac6e900bf4b1e4777b"}, + {file = "types_awscrt-0.31.1.tar.gz", hash = "sha256:08b13494f93f45c1a92eb264755fce50ed0d1dc75059abb5e31670feb9a09724"}, ] [[package]] @@ -2441,7 +2503,7 @@ version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, @@ -2449,14 +2511,14 @@ files = [ [[package]] name = "urllib3" -version = "2.6.2" +version = "2.6.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["main", "dev", "docs"] files = [ - {file = "urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd"}, - {file = "urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797"}, + {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, + {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, ] [package.extras] @@ -2467,19 +2529,19 @@ zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "virtualenv" -version = "20.35.4" +version = "20.36.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b"}, - {file = "virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c"}, + {file = "virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f"}, + {file = "virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba"}, ] [package.dependencies] distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" +filelock = {version = ">=3.20.1,<4", markers = "python_version >= \"3.10\""} platformdirs = ">=3.9.1,<5" [package.extras] @@ -2488,14 +2550,14 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "werkzeug" -version = "3.1.4" +version = "3.1.5" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "werkzeug-3.1.4-py3-none-any.whl", hash = "sha256:2ad50fb9ed09cc3af22c54698351027ace879a0b60a3b5edf5730b2f7d876905"}, - {file = "werkzeug-3.1.4.tar.gz", hash = "sha256:cd3cd98b1b92dc3b7b3995038826c68097dcb16f9baa63abe35f20eafeb9fe5e"}, + {file = "werkzeug-3.1.5-py3-none-any.whl", hash = "sha256:5111e36e91086ece91f93268bb39b4a35c1e6f1feac762c9c822ded0a4e322dc"}, + {file = "werkzeug-3.1.5.tar.gz", hash = "sha256:6a548b0e88955dd07ccb25539d7d0cc97417ee9e179677d22c7041c8f078ce67"}, ] [package.dependencies] @@ -2521,5 +2583,5 @@ test = ["pytest", "pytest-cov"] [metadata] lock-version = "2.1" -python-versions = ">=3.13, !=3.14.1" -content-hash = "cfd1fe0f7ea50e1d87ef53df17ca9b21f1dce9654b6003b08e89eeff3cbcfc6a" +python-versions = ">=3.13,!=3.14.1" +content-hash = "0e92dd06c0f4f5a06988e7218eaed5d650d9a5a24bb2c8e2c6b5cda303f53f07" diff --git a/pyproject.toml b/pyproject.toml index 93f80f6f..18db1b22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,23 +8,24 @@ readme = "README.md" packages = [{ include = "gardenlinux", from = "src" }] [tool.poetry.dependencies] -python = ">=3.13, !=3.14.1" +python = ">=3.13,!=3.14.1" apt-repo = "^0.5" -boto3 = "^1.42.10" +boto3 = "^1.42.30" click = "^8.3.1" cryptography = "^46.0.3" -jsonschema = "^4.25.1" +jsonschema = "^4.26.0" networkx = "^3.6" oras = "^0.2.38" -pygit2 = "^1.19.0" +pygit2 = "^1.19.1" pygments = "^2.19.2" +PyGithub = "^2.8.1" PyYAML = "^6.0.2" gitpython = "^3.1.45" [tool.poetry.group.dev.dependencies] -bandit = "^1.9.2" -moto = "^5.1.16" -pre-commit = "^4.5.0" +bandit = "^1.9.3" +moto = "^5.1.20" +pre-commit = "^4.5.1" python-dotenv = "^1.2.1" pytest = "^9.0.2" pytest-cov = "^7.0.0" @@ -34,7 +35,7 @@ mypy = "1.18.2" types-click = "^7.1.8" types-pyyaml = "^6.0.12.20250915" types-requests = "^2.32.4.20260107" -boto3-stubs = { extras = ["s3"], version = "^1.42.25" } +boto3-stubs = { extras = ["s3"], version = "^1.42.30" } [tool.poetry.group.docs.dependencies] sphinx-rtd-theme = "^3.0.2" @@ -46,7 +47,6 @@ gl-flavors-parse = "gardenlinux.flavors.__main__:main" gl-gh-release = "gardenlinux.github.release.__main__:main" gl-oci = "gardenlinux.oci.__main__:main" gl-s3 = "gardenlinux.s3.__main__:main" -gl-gh = "gardenlinux.github.__main__:main" [tool.pytest.ini_options] pythonpath = ["src"] diff --git a/src/gardenlinux/github/__init__.py b/src/gardenlinux/github/__init__.py index e69de29b..caae6fa6 100644 --- a/src/gardenlinux/github/__init__.py +++ b/src/gardenlinux/github/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- + +""" +GitHub module +""" + +from .client import Client + +__all__ = ["Client"] diff --git a/src/gardenlinux/github/client.py b/src/gardenlinux/github/client.py new file mode 100644 index 00000000..2a3c8f6b --- /dev/null +++ b/src/gardenlinux/github/client.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +""" +GitHub client +""" + +from logging import Logger +from os import environ +from typing import Any, Optional + +from github import Auth, Github + +from ..logger import LoggerSetup + + +class Client(object): + """ + GitHub client instance to provide methods for interaction with GitHub API. + + :author: Garden Linux Maintainers + :copyright: Copyright 2024 SAP SE + :package: gardenlinux + :subpackage: github + :since: 1.0.0 + :license: https://www.apache.org/licenses/LICENSE-2.0 + Apache License, Version 2.0 + """ + + def __init__(self, token: Optional[str] = None, logger: Optional[Logger] = None): + """ + Constructor __init__(Client) + + :param token: GitHub access token + :param logger: Logger instance + + :since: 1.0.0 + """ + + self._client = None + self._token = token + + if self._token is None or self._token.strip() == "": + self._token = environ.get("GITHUB_TOKEN") + + if self._token is None: + raise ValueError("GITHUB_TOKEN environment variable not set") + + if logger is None or not logger.hasHandlers(): + logger = LoggerSetup.get_logger("gardenlinux.github") + + self._logger = logger + + @property + def instance(self) -> Github: + if self._client is None: + self._client = Github(auth=Auth.Token(self._token)) + + return self._client + + def __getattr__(self, name: str) -> Any: + """ + python.org: Called when an attribute lookup has not found the attribute in + the usual places (i.e. it is not an instance attribute nor is it found in the + class tree for self). + + :param name: Attribute name + + :return: (mixed) Attribute + :since: 0.8.0 + """ + + self._logger.debug(f"gardenlinux.github.Client.{name} accessed") + return getattr(self.instance, name) diff --git a/src/gardenlinux/github/release/__init__.py b/src/gardenlinux/github/release/__init__.py index db8549fc..ac7d6fff 100644 --- a/src/gardenlinux/github/release/__init__.py +++ b/src/gardenlinux/github/release/__init__.py @@ -5,8 +5,9 @@ import requests -from gardenlinux.constants import RELEASE_ID_FILE, REQUESTS_TIMEOUTS -from gardenlinux.logger import LoggerSetup +from ...constants import RELEASE_ID_FILE, REQUESTS_TIMEOUTS +from ...logger import LoggerSetup +from .release import Release LOGGER = LoggerSetup.get_logger("gardenlinux.github.release", logging.INFO) @@ -103,3 +104,6 @@ def upload_to_github_release_page( f"Upload failed with status code {response.status_code}: {response.text}" ) response.raise_for_status() + + +__all__ = ["Release", "write_to_release_id_file", "upload_to_github_release_page"] diff --git a/src/gardenlinux/github/release/__main__.py b/src/gardenlinux/github/release/__main__.py index 29c6d2fd..e403893f 100644 --- a/src/gardenlinux/github/release/__main__.py +++ b/src/gardenlinux/github/release/__main__.py @@ -6,10 +6,10 @@ from ..release_notes import create_github_release_notes from . import ( - create_github_release, upload_to_github_release_page, write_to_release_id_file, ) +from .release import Release LOGGER = LoggerSetup.get_logger("gardenlinux.github", logging.INFO) @@ -22,9 +22,19 @@ def main() -> None: create_parser.add_argument("--owner", default="gardenlinux") create_parser.add_argument("--repo", default="gardenlinux") create_parser.add_argument("--tag", required=True) - create_parser.add_argument("--commit", required=True) + create_parser.add_argument("--name") + create_parser.add_argument("--body", required=True) + create_parser.add_argument("--commit") + create_parser.add_argument("--pre-release", action="store_true", default=True) create_parser.add_argument("--latest", action="store_true", default=False) - create_parser.add_argument("--dry-run", action="store_true", default=False) + + create_parser_gl = subparsers.add_parser("create-with-gl-release-notes") + create_parser_gl.add_argument("--owner", default="gardenlinux") + create_parser_gl.add_argument("--repo", default="gardenlinux") + create_parser_gl.add_argument("--tag", required=True) + create_parser_gl.add_argument("--commit", required=True) + create_parser_gl.add_argument("--latest", action="store_true", default=False) + create_parser_gl.add_argument("--dry-run", action="store_true", default=False) upload_parser = subparsers.add_parser("upload") upload_parser.add_argument("--owner", default="gardenlinux") @@ -36,6 +46,15 @@ def main() -> None: args = parser.parse_args() if args.command == "create": + release = Release(args.repo, args.owner) + release.tag = args.tag + release.name = args.name + release.body = args.body + release.commitish = args.commit + release.is_pre_release = args.pre_release + release.is_latest = args.latest + release.create() + elif args.command == "create-with-gl-release-notes": body = create_github_release_notes( args.tag, args.commit, GARDENLINUX_GITHUB_RELEASE_BUCKET_NAME ) @@ -44,9 +63,13 @@ def main() -> None: print("This release would be created:") print(body) else: - release_id = create_github_release( - args.owner, args.repo, args.tag, args.commit, args.latest, body - ) + release = Release(args.repo, args.owner) + release.tag = args.tag + release.body = body + release.commitish = args.commit + release.is_latest = args.latest + + release_id = release.create() write_to_release_id_file(f"{release_id}") LOGGER.info(f"Release created with ID: {release_id}") elif args.command == "upload": diff --git a/src/gardenlinux/github/release/release.py b/src/gardenlinux/github/release/release.py new file mode 100644 index 00000000..d4c5099b --- /dev/null +++ b/src/gardenlinux/github/release/release.py @@ -0,0 +1,231 @@ +# -*- coding: utf-8 -*- + +""" +GitHub release container +""" + +from logging import Logger +from typing import Optional + +from ...logger import LoggerSetup +from ..client import Client + + +class Release(object): + """ + GitHub release instance to provide methods for interaction. + + :author: Garden Linux Maintainers + :copyright: Copyright 2024 SAP SE + :package: gardenlinux + :subpackage: github + :since: 1.0.0 + :license: https://www.apache.org/licenses/LICENSE-2.0 + Apache License, Version 2.0 + """ + + def __init__( + self, + repo: str, + owner: str = "gardenlinux", + token: Optional[str] = None, + logger: Optional[Logger] = None, + ): + """ + Constructor __init__(Release) + + :param repo: GitHub repository containing releases + :param owner: GitHub owner for release data + :param token: GitHub access token + :param logger: Logger instance + + :since: 1.0.0 + """ + + self._owner = owner + self._repo = repo + self._name: Optional[str] = None + self._tag: Optional[str] = None + self._commitish: Optional[str] = None + self._latest = True + self._pre_release = False + self._release_body: Optional[str] = None + + if logger is None or not logger.hasHandlers(): + logger = LoggerSetup.get_logger("gardenlinux.github") + + self._logger = logger + + self._client = Client(token, self._logger) + + @property + def body(self) -> str: + """ + Returns the Git release body set. + + :return: (str) Git release body + :since: 1.0.0 + """ + + if self._release_body is None: + raise ValueError("GitHub release body not set") + + return self._release_body + + @body.setter + def body(self, value: str) -> None: + """ + Sets the Git release body. + + :param value: Git release body + + :since: 1.0.0 + """ + + self._release_body = value + + @property + def commitish(self) -> Optional[str]: + """ + Returns the Git release related commit hash. + + :return: (str) Git release commit hash + :since: 1.0.0 + """ + + return self._commitish + + @commitish.setter + def commitish(self, value: str) -> None: + """ + Sets the Git release related commit hash. + + :param value: Git release commit hash + + :since: 1.0.0 + """ + + self._commitish = value + + @property + def is_latest(self) -> bool: + """ + Returns true if the Git release is marked as "latest". + + :return: (str) Git release latest status + :since: 1.0.0 + """ + + return self._latest + + @is_latest.setter + def is_latest(self, value: bool) -> None: + """ + If set to true the Git release created will be marked as "latest". + + :param value: Git release latest status + + :since: 1.0.0 + """ + + self._latest = bool(value) + + @property + def is_pre_release(self) -> bool: + """ + Returns true if the Git release is marked as pre-release. + + :return: (str) Git release pre-release status + :since: 1.0.0 + """ + + return self._pre_release + + @is_pre_release.setter + def is_pre_release(self, value: bool) -> None: + """ + If set to true the Git release created will be marked as pre-release. + + :param value: Git release pre-release status + + :since: 1.0.0 + """ + + self._pre_release = bool(value) + + @property + def name(self) -> str: + """ + Returns the Git release name set. + + :return: (str) Git release name + :since: 1.0.0 + """ + + if self._name is None: + return self.tag + + return self._name + + @name.setter + def name(self, value: str) -> None: + """ + Sets the Git release name. + + :param value: Git release name + + :since: 1.0.0 + """ + + self._name = value + + @property + def tag(self) -> str: + """ + Returns the Git release tag set. + + :return: (str) Git release tag + :since: 1.0.0 + """ + + if self._tag is None: + raise ValueError("GitHub release tag not set") + + return self._tag + + @tag.setter + def tag(self, value: str) -> None: + """ + Sets the Git release tag. + + :param value: Git release tag + + :since: 1.0.0 + """ + + self._tag = value + + def create(self) -> int: + """ + Creates an GitHub release. + + :return: (str) GitHub release ID created + :since: 1.0.0 + """ + + kwargs = { + "name": self.name, + "message": self.body, + "draft": False, + "prerelease": self.is_pre_release, + "make_latest": "true" if self.is_latest else "false", + } + + if self.commitish is not None: + kwargs["target_commitish"] = self._commitish + + release = self._client.get_repo( + f"{self._owner}/{self._repo}" + ).create_git_release(self.tag, **kwargs) + + return release.id # type: ignore[no-any-return] diff --git a/tests/github/constants.py b/tests/github/constants.py new file mode 100644 index 00000000..91ea3419 --- /dev/null +++ b/tests/github/constants.py @@ -0,0 +1,77 @@ +REPO_JSON = { + "id": 1, + "node_id": "test", + "name": "gardenlinux", + "full_name": "gardenlinux/gardenlinux", + "owner": {}, + "private": False, + "html_url": "https://github.com/gardenlinux/gardenlinux", + "description": "Happily copied from REST API endpoints for repositories @ github.com", + "fork": False, + "url": "https://api.github.com/repos/gardenlinux/gardenlinux", + "archive_url": "https://api.github.com/repos/gardenlinux/gardenlinux/{archive_format}{/ref}", + "assignees_url": "https://api.github.com/repos/gardenlinux/gardenlinux/assignees{/user}", + "blobs_url": "https://api.github.com/repos/gardenlinux/gardenlinux/git/blobs{/sha}", + "branches_url": "https://api.github.com/repos/gardenlinux/gardenlinux/branches{/branch}", + "collaborators_url": "https://api.github.com/repos/gardenlinux/gardenlinux/collaborators{/collaborator}", + "comments_url": "https://api.github.com/repos/gardenlinux/gardenlinux/comments{/number}", + "commits_url": "https://api.github.com/repos/gardenlinux/gardenlinux/commits{/sha}", + "compare_url": "https://api.github.com/repos/gardenlinux/gardenlinux/compare/{base}...{head}", + "contents_url": "https://api.github.com/repos/gardenlinux/gardenlinux/contents/{+path}", + "contributors_url": "https://api.github.com/repos/gardenlinux/gardenlinux/contributors", + "deployments_url": "https://api.github.com/repos/gardenlinux/gardenlinux/deployments", + "downloads_url": "https://api.github.com/repos/gardenlinux/gardenlinux/downloads", + "events_url": "https://api.github.com/repos/gardenlinux/gardenlinux/events", + "forks_url": "https://api.github.com/repos/gardenlinux/gardenlinux/forks", + "git_commits_url": "https://api.github.com/repos/gardenlinux/gardenlinux/git/commits{/sha}", + "git_refs_url": "https://api.github.com/repos/gardenlinux/gardenlinux/git/refs{/sha}", + "git_tags_url": "https://api.github.com/repos/gardenlinux/gardenlinux/git/tags{/sha}", + "git_url": "git:github.com/gardenlinux/gardenlinux.git", + "issue_comment_url": "https://api.github.com/repos/gardenlinux/gardenlinux/issues/comments{/number}", + "issue_events_url": "https://api.github.com/repos/gardenlinux/gardenlinux/issues/events{/number}", + "issues_url": "https://api.github.com/repos/gardenlinux/gardenlinux/issues{/number}", + "keys_url": "https://api.github.com/repos/gardenlinux/gardenlinux/keys{/key_id}", + "labels_url": "https://api.github.com/repos/gardenlinux/gardenlinux/labels{/name}", + "languages_url": "https://api.github.com/repos/gardenlinux/gardenlinux/languages", + "merges_url": "https://api.github.com/repos/gardenlinux/gardenlinux/merges", + "milestones_url": "https://api.github.com/repos/gardenlinux/gardenlinux/milestones{/number}", + "notifications_url": "https://api.github.com/repos/gardenlinux/gardenlinux/notifications{?since,all,participating}", + "pulls_url": "https://api.github.com/repos/gardenlinux/gardenlinux/pulls{/number}", + "releases_url": "https://api.github.com/repos/gardenlinux/gardenlinux/releases{/id}", + "ssh_url": "git@github.com:gardenlinux/gardenlinux.git", + "stargazers_url": "https://api.github.com/repos/gardenlinux/gardenlinux/stargazers", + "statuses_url": "https://api.github.com/repos/gardenlinux/gardenlinux/statuses/{sha}", + "subscribers_url": "https://api.github.com/repos/gardenlinux/gardenlinux/subscribers", + "subscription_url": "https://api.github.com/repos/gardenlinux/gardenlinux/subscription", + "tags_url": "https://api.github.com/repos/gardenlinux/gardenlinux/tags", + "teams_url": "https://api.github.com/repos/gardenlinux/gardenlinux/teams", + "trees_url": "https://api.github.com/repos/gardenlinux/gardenlinux/git/trees{/sha}", + "clone_url": "https://github.com/gardenlinux/gardenlinux.git", + "mirror_url": "git:git.example.com/gardenlinux/gardenlinux", + "hooks_url": "https://api.github.com/repos/gardenlinux/gardenlinux/hooks", + "svn_url": "https://svn.github.com/gardenlinux/gardenlinux", + "homepage": "https://gardenlinux.io", + "language": None, + "forks_count": 0, + "stargazers_count": 0, + "watchers_count": 0, + "size": 1, + "default_branch": "main", + "open_issues_count": 0, + "is_template": False, + "topics": [], + "has_issues": True, + "has_projects": True, + "has_wiki": True, + "has_pages": True, + "has_downloads": True, + "has_discussions": True, + "archived": False, + "disabled": False, + "visibility": "public", + "pushed_at": "2011-01-26T19:06:43Z", + "created_at": "2011-01-26T19:01:12Z", + "updated_at": "2011-01-26T19:14:43Z", + "permissions": {"admin": False, "push": False, "pull": True}, + "security_and_analysis": {}, +} diff --git a/tests/github/test_create_github_release.py b/tests/github/test_create_github_release.py deleted file mode 100644 index b702183d..00000000 --- a/tests/github/test_create_github_release.py +++ /dev/null @@ -1,91 +0,0 @@ -from pathlib import Path - -import pytest -import requests -import requests_mock - -from gardenlinux.github.release import create_github_release, write_to_release_id_file - -from ..constants import TEST_GARDENLINUX_COMMIT, TEST_GARDENLINUX_RELEASE - - -def test_create_github_release_needs_github_token() -> None: - with requests_mock.Mocker(): - with pytest.raises(ValueError) as exn: - create_github_release( - "gardenlinux", - "gardenlinux", - TEST_GARDENLINUX_RELEASE, - TEST_GARDENLINUX_COMMIT, - False, - "", - ) - assert str(exn.value) == "GITHUB_TOKEN environment variable not set", ( - "Expected an exception to be raised on missing GITHUB_TOKEN environment variable" - ) - - -def test_create_github_release_raise_on_failure( - caplog: pytest.LogCaptureFixture, github_token: None -) -> None: - with requests_mock.Mocker() as m: - with pytest.raises(requests.exceptions.HTTPError): - m.post( - "https://api.github.com/repos/gardenlinux/gardenlinux/releases", - text="{}", - status_code=503, - ) - create_github_release( - "gardenlinux", - "gardenlinux", - TEST_GARDENLINUX_RELEASE, - TEST_GARDENLINUX_COMMIT, - False, - "", - ) - assert any( - "Failed to create release" in record.message for record in caplog.records - ), "Expected a failure log record" - - -def test_create_github_release( - caplog: pytest.LogCaptureFixture, github_token: None -) -> None: - with requests_mock.Mocker() as m: - m.post( - "https://api.github.com/repos/gardenlinux/gardenlinux/releases", - text='{"id": 101}', - status_code=201, - ) - assert ( - create_github_release( - "gardenlinux", - "gardenlinux", - TEST_GARDENLINUX_RELEASE, - TEST_GARDENLINUX_COMMIT, - False, - "", - ) - == 101 - ) - assert any( - "Release created successfully" in record.message - for record in caplog.records - ), "Expected a success log record" - - -def test_write_to_release_id_file(release_id_file: Path) -> None: - write_to_release_id_file(TEST_GARDENLINUX_RELEASE) - assert release_id_file.read_text() == TEST_GARDENLINUX_RELEASE - - -def test_write_to_release_id_file_broken_file_permissions( - release_id_file: Path, caplog: pytest.LogCaptureFixture -) -> None: - release_id_file.touch(0) # this will make the file unwritable - - with pytest.raises(SystemExit): - write_to_release_id_file(TEST_GARDENLINUX_RELEASE) - assert any("Could not create" in record.message for record in caplog.records), ( - "Expected a failure log record" - ) diff --git a/tests/github/test_github_script.py b/tests/github/test_github_script.py index cc10b83e..c3ca2ede 100644 --- a/tests/github/test_github_script.py +++ b/tests/github/test_github_script.py @@ -1,11 +1,13 @@ import sys import pytest +import requests_mock import gardenlinux.github.release.__main__ as gh from gardenlinux.constants import GARDENLINUX_GITHUB_RELEASE_BUCKET_NAME from ..constants import TEST_GARDENLINUX_COMMIT, TEST_GARDENLINUX_RELEASE +from .constants import REPO_JSON def test_script_parse_args_wrong_command( @@ -26,7 +28,16 @@ def test_script_parse_args_create_command_required_args( monkeypatch: pytest.MonkeyPatch, capfd: pytest.CaptureFixture[str] ) -> None: monkeypatch.setattr( - sys, "argv", ["gh", "create", "--owner", "gardenlinux", "--repo", "gardenlinux"] + sys, + "argv", + [ + "gh", + "create-with-gl-release-notes", + "--owner", + "gardenlinux", + "--repo", + "gardenlinux", + ], ) with pytest.raises(SystemExit): @@ -63,7 +74,7 @@ def test_script_create_dry_run( "argv", [ "gh", - "create", + "create-with-gl-release-notes", "--owner", "gardenlinux", "--repo", @@ -92,37 +103,47 @@ def test_script_create_dry_run( def test_script_create( monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture ) -> None: - monkeypatch.setattr( - sys, - "argv", - [ - "gh", - "create", - "--owner", - "gardenlinux", - "--repo", - "gardenlinux", - "--tag", - TEST_GARDENLINUX_RELEASE, - "--commit", - TEST_GARDENLINUX_COMMIT, - ], - ) - monkeypatch.setattr( - "gardenlinux.github.release.__main__.create_github_release_notes", - lambda tag, commit, bucket: f"{tag} {commit} {bucket}", - ) - monkeypatch.setattr( - "gardenlinux.github.release.__main__.create_github_release", - lambda a1, a2, a3, a4, a5, a6: TEST_GARDENLINUX_RELEASE, - ) + with requests_mock.Mocker() as m: + monkeypatch.setattr( + sys, + "argv", + [ + "gh", + "create-with-gl-release-notes", + "--owner", + "gardenlinux", + "--repo", + "gardenlinux", + "--tag", + TEST_GARDENLINUX_RELEASE, + "--commit", + TEST_GARDENLINUX_COMMIT, + ], + ) + monkeypatch.setattr( + "gardenlinux.github.release.__main__.create_github_release_notes", + lambda tag, commit, bucket: f"{tag} {commit} {bucket}", + ) + monkeypatch.setenv("GITHUB_TOKEN", "invalid") + + m.get( + "//api.github.com:443/repos/gardenlinux/gardenlinux", + json=REPO_JSON, + status_code=200, + ) + + m.post( + "//api.github.com:443/repos/gardenlinux/gardenlinux/releases", + json={"id": 101}, + status_code=201, + ) - gh.main() + gh.main() - assert any( - f"Release created with ID: {TEST_GARDENLINUX_RELEASE}" in record.message - for record in caplog.records - ), "Expected a release creation confirmation log entry" + assert any( + "Release created with ID: 101" in record.message + for record in caplog.records + ), "Expected a release creation confirmation log entry" def test_script_upload_dry_run( diff --git a/tests/github/test_release.py b/tests/github/test_release.py new file mode 100644 index 00000000..dc74685b --- /dev/null +++ b/tests/github/test_release.py @@ -0,0 +1,89 @@ +from pathlib import Path + +import pytest +import requests_mock +from github import GithubException + +from gardenlinux.github.release import Release, write_to_release_id_file + +from ..constants import ( + TEST_GARDENLINUX_COMMIT, + TEST_GARDENLINUX_RELEASE, +) +from .constants import REPO_JSON + + +def test_Release_create_needs_github_token() -> None: + with ( + requests_mock.Mocker(), + pytest.raises(ValueError, match="GITHUB_TOKEN environment variable not set"), + ): + _ = Release("gardenlinux", "gardenlinux") + + +def test_Release_raise_on_failure( + caplog: pytest.LogCaptureFixture, github_token: str +) -> None: + with requests_mock.Mocker() as m: + release = Release("gardenlinux", "gardenlinux", token="test") + + release.tag = TEST_GARDENLINUX_RELEASE + release.commitish = TEST_GARDENLINUX_COMMIT + release.is_latest = False + release.body = "" + + with pytest.raises(GithubException): + m.get( + "//api.github.com:443/repos/gardenlinux/gardenlinux", + json=REPO_JSON, + status_code=200, + ) + + m.post( + "//api.github.com:443/repos/gardenlinux/gardenlinux/releases", + json={}, + status_code=503, + ) + + release.create() + + +def test_Release(caplog: pytest.LogCaptureFixture, github_token: str) -> None: + with requests_mock.Mocker() as m: + release = Release("gardenlinux", "gardenlinux", token="test") + + release.tag = TEST_GARDENLINUX_RELEASE + release.commitish = TEST_GARDENLINUX_COMMIT + release.is_latest = False + release.body = "" + + m.get( + "//api.github.com:443/repos/gardenlinux/gardenlinux", + json=REPO_JSON, + status_code=200, + ) + + m.post( + "//api.github.com:443/repos/gardenlinux/gardenlinux/releases", + json={"id": 101}, + status_code=201, + ) + + assert release.create() == 101 + + +def test_write_to_release_id_file(release_id_file: Path) -> None: + write_to_release_id_file(TEST_GARDENLINUX_RELEASE) + assert release_id_file.read_text() == TEST_GARDENLINUX_RELEASE + + +def test_write_to_release_id_file_broken_file_permissions( + release_id_file: Path, caplog: pytest.LogCaptureFixture +) -> None: + release_id_file.touch(0) # this will make the file unwritable + + with pytest.raises(SystemExit): + write_to_release_id_file(TEST_GARDENLINUX_RELEASE) + assert any("Could not create" in record.message for record in caplog.records), ( + "Expected a failure log record" + )