Compare commits
	
		
			80 Commits
		
	
	
		
			v0.38.0
			...
			renovate/p
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e853bf4920 | |||
| 6d0c4ef55a | |||
| 407368dfc5 | |||
| 51cdbeaf19 | |||
| 503d65b56f | |||
| e85eaf3452 | |||
| 5b98b132c6 | |||
| d1f492b218 | |||
| c9ad33e65f | |||
| e41f35ca1f | |||
| 9b4e1d7787 | |||
| 546ef22dd5 | |||
| cdcc0825b4 | |||
| da2158d7ce | |||
| 31e1b064af | |||
| 295e59270d | |||
| fdebc846bb | |||
| 69561748a3 | |||
| 3b5360589e | |||
| a2b44b37e4 | |||
| fbe9cc553b | |||
| 0360d12958 | |||
| 42b523d136 | |||
| b1da842bc8 | |||
| 04ffd2ea29 | |||
| ddea14a553 | |||
| 111b418f58 | |||
| a5905683ee | |||
| 02cbdbed6c | |||
| e5c3db6b56 | |||
| ec8339bcea | |||
| a915815a2b | |||
| f7c411184c | |||
| b94d26a995 | |||
| 2020169e5e | |||
| 7ab6178861 | |||
| 8a54fd2ec0 | |||
| 68395b0a5e | |||
| b847bb2ceb | |||
| 6421a3923f | |||
| 13097b36fb | |||
| 590e0941bf | |||
| 29e597c815 | |||
| 4c51e697d9 | |||
| b8918b3d03 | |||
| 4369470727 | |||
| 52bb626eea | |||
| ed35c1b8e6 | |||
| 0aecbae7d6 | |||
| 0b73c67ef2 | |||
| fa4e530e7e | |||
| e54838e6ac | |||
| 420f790582 | |||
| 5f22912497 | |||
| 246a96f3e9 | |||
| 1e21ac841f | |||
| 8dfa9e9f68 | |||
| 6530b4c620 | |||
| 74c1bea948 | |||
| a5312ba3fb | |||
| 1122726fc9 | |||
| 03bc2cdb4a | |||
| 74c016093a | |||
| 66b488c09f | |||
| 
						
						
							
						
						44d6f9d161
	
				 | 
					
					
						|||
| a9895a9807 | |||
| 554fc3e6b5 | |||
| a7aa213bc4 | |||
| dea03bb39f | |||
| b3244c3836 | |||
| 
						
						
							
						
						66a842ea08
	
				 | 
					
					
						|||
| 
						
						
							
						
						434f05f6a7
	
				 | 
					
					
						|||
| 
						
						
							
						
						7fa0955d23
	
				 | 
					
					
						|||
| 
						
						
							
						
						2a5f074002
	
				 | 
					
					
						|||
| b8ea3d87f5 | |||
| 
						
						
							
						
						070a77c665
	
				 | 
					
					
						|||
| f833c986b9 | |||
| 9cc478c66d | |||
| 
						
						
							
						
						a5949e1efe
	
				 | 
					
					
						|||
| 
						
						
							
						
						1086eb3d18
	
				 | 
					
					
						
							
								
								
									
										1
									
								
								.archive/.python-version.old
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.archive/.python-version.old
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					webexmemebot-3119
 | 
				
			||||||
							
								
								
									
										0
									
								
								poetry.lock → .archive/poetry.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										0
									
								
								poetry.lock → .archive/poetry.lock
									
									
									
										generated
									
									
									
								
							@@ -1,26 +1,23 @@
 | 
				
			|||||||
name: CI
 | 
					name: CI
 | 
				
			||||||
on:
 | 
					on:
 | 
				
			||||||
  pull_request:
 | 
					  pull_request:
 | 
				
			||||||
    types: [opened, synchronize, reopened]
 | 
					    types:
 | 
				
			||||||
    paths-ignore:
 | 
					      - opened
 | 
				
			||||||
      - "README.md"
 | 
					      - edited
 | 
				
			||||||
      - "LICENSE.md"
 | 
					      - synchronize
 | 
				
			||||||
      - ".gitignore"
 | 
					      - reopened
 | 
				
			||||||
      - "renovate.json"
 | 
					 | 
				
			||||||
      - ".gitea/CODEOWNERS"
 | 
					 | 
				
			||||||
      - ".archive"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  ci:
 | 
					  ci:
 | 
				
			||||||
    runs-on: ubuntu-poetry-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Check out repository code
 | 
					      - name: Check out repository code
 | 
				
			||||||
        uses: actions/checkout@v4.2.2
 | 
					        uses: actions/checkout@v5.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          fetch-depth: 0
 | 
					          fetch-depth: 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Run Hadolint
 | 
					      - name: Run Hadolint
 | 
				
			||||||
        uses: hadolint/hadolint-action@v3.1.0
 | 
					        uses: hadolint/hadolint-action@v3.3.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          dockerfile: Dockerfile
 | 
					          dockerfile: Dockerfile
 | 
				
			||||||
          output-file: hadolint.out
 | 
					          output-file: hadolint.out
 | 
				
			||||||
@@ -28,35 +25,71 @@ jobs:
 | 
				
			|||||||
          no-fail: true
 | 
					          no-fail: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Setup Python
 | 
					      - name: Setup Python
 | 
				
			||||||
        uses: actions/setup-python@v5
 | 
					        uses: actions/setup-python@v6
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: "${{ vars.PYTHON_VERSION }}"
 | 
					          python-version: "3.14"
 | 
				
			||||||
          cache: 'poetry'
 | 
					
 | 
				
			||||||
 | 
					      - name: uv cache
 | 
				
			||||||
 | 
					        uses: actions/cache@v4
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          path: /tmp/.uv-cache
 | 
				
			||||||
 | 
					          key: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }}
 | 
				
			||||||
 | 
					          restore-keys: |
 | 
				
			||||||
 | 
					            uv-${{ runner.os }}-${{ hashFiles('uv.lock') }}
 | 
				
			||||||
 | 
					            uv-${{ runner.os }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Install dependencies
 | 
					      - name: Install dependencies
 | 
				
			||||||
        run: poetry install
 | 
					        run: uv sync
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      # - name: Lint
 | 
				
			||||||
 | 
					      #   run: |
 | 
				
			||||||
 | 
					      #     uv run pylint --fail-under=8 --recursive=yes --output-format=parseable --output=lintreport.txt app/ tests/
 | 
				
			||||||
 | 
					      #     cat lintreport.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Lint
 | 
					      - name: Lint
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          poetry run pylint --fail-under=8 --recursive=yes --output-format=parseable --output=lintreport.txt app/ tests/
 | 
					          uv run pylint --fail-under=8 --recursive=yes --output-format=parseable app/ tests/
 | 
				
			||||||
          cat lintreport.txt
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Unit Test
 | 
					      - name: Unit Test
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          poetry run coverage run -m pytest -v --junitxml=testresults.xml
 | 
					          uv run coverage run -m pytest -v --junitxml=testresults.xml
 | 
				
			||||||
          poetry run coverage xml
 | 
					          uv run coverage xml
 | 
				
			||||||
          sed -i 's@${{ gitea.workspace }}@/github/workspace@g' coverage.xml
 | 
					          sed -i 's@${{ gitea.workspace }}@/github/workspace@g' coverage.xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: SonarQube Cloud Scan
 | 
					      - name: Minimize uv cache
 | 
				
			||||||
        uses: SonarSource/sonarqube-scan-action@v4.2.1
 | 
					        run: uv cache prune --ci
 | 
				
			||||||
        env:
 | 
					      
 | 
				
			||||||
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
 | 
					      - name: Set up environment for Snyk
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          uv pip freeze > requirements.txt
 | 
				
			||||||
 | 
					          mv pyproject.toml pyproject.toml.bak
 | 
				
			||||||
 | 
					          mv uv.lock uv.lock.bak
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Snyk Vulnerability Scan
 | 
					      - name: Snyk SAST Scan
 | 
				
			||||||
        uses: snyk/actions/python@master
 | 
					        uses: snyk/actions/python@master
 | 
				
			||||||
        continue-on-error: true  # Sometimes vulns aren't immediately fixable
 | 
					 | 
				
			||||||
        env:
 | 
					        env:
 | 
				
			||||||
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
 | 
					          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          command: snyk
 | 
					          # command: snyk
 | 
				
			||||||
          args: test --all-projects
 | 
					          args: snyk code test #--all-projects --exclude=.archive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # - name: SonarQube Scan
 | 
				
			||||||
 | 
					      #   uses: SonarSource/sonarqube-scan-action@v5.2.0
 | 
				
			||||||
 | 
					      #   env:
 | 
				
			||||||
 | 
					      #     SONAR_HOST_URL: ${{ secrets.SONARQUBE_HOST_URL }}
 | 
				
			||||||
 | 
					      #     SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # - name: Snyk Vulnerability Scan
 | 
				
			||||||
 | 
					      #   uses: snyk/actions/python@master
 | 
				
			||||||
 | 
					      #   continue-on-error: true # Sometimes vulns aren't immediately fixable
 | 
				
			||||||
 | 
					      #   env:
 | 
				
			||||||
 | 
					      #     SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
 | 
				
			||||||
 | 
					      #   with:
 | 
				
			||||||
 | 
					      #     command: snyk
 | 
				
			||||||
 | 
					      #     args: test --all-projects
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Reverse set up environment for Snyk
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          rm -f requirements.txt
 | 
				
			||||||
 | 
					          mv pyproject.toml.bak pyproject.toml
 | 
				
			||||||
 | 
					          mv uv.lock.bak uv.lock
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,28 +7,12 @@ on:
 | 
				
			|||||||
      - edited
 | 
					      - edited
 | 
				
			||||||
      - synchronize
 | 
					      - synchronize
 | 
				
			||||||
      - reopened
 | 
					      - reopened
 | 
				
			||||||
      - labeled
 | 
					 | 
				
			||||||
      - unlabeled
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  validate:
 | 
					  validate:
 | 
				
			||||||
    name: Validate PR Title
 | 
					    name: Validate PR Title
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      # - name: PR Conventional Commit Validation
 | 
					      - uses: https://git.tainton.uk/actions/conventional-commits-check-action@v1.3.0
 | 
				
			||||||
      #   uses: ytanikin/pr-conventional-commits@1.4.0
 | 
					 | 
				
			||||||
      #   with:
 | 
					 | 
				
			||||||
      #     task_types: '["feat","fix","docs","test","ci","refactor","perf","chore","revert"]'
 | 
					 | 
				
			||||||
      #     add_label: 'false'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      # DOES NOT WORK WITH GITEA
 | 
					 | 
				
			||||||
      - uses: amannn/action-semantic-pull-request@v5
 | 
					 | 
				
			||||||
        env:
 | 
					 | 
				
			||||||
          GITHUB_TOKEN: ${{ gitea.token }}
 | 
					 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          requireScope: true
 | 
					          commit-message: ${{ gitea.event.pull_request.title }}
 | 
				
			||||||
          wip: true
 | 
					 | 
				
			||||||
          validateSingleCommit: true
 | 
					 | 
				
			||||||
          validateSingleCommitMatchesPrTitle: true
 | 
					 | 
				
			||||||
          githubBaseUrl: https://git.tainton.uk/api/v1
 | 
					 | 
				
			||||||
          ignoreLabels: ignore/semantic-pr-title
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,104 +1,48 @@
 | 
				
			|||||||
name: Release
 | 
					name: Release
 | 
				
			||||||
on:
 | 
					on:
 | 
				
			||||||
 | 
					  workflow_dispatch:
 | 
				
			||||||
  schedule:
 | 
					  schedule:
 | 
				
			||||||
    - cron: "0 9 * * 0"
 | 
					    - cron: '0 9 * * 0'
 | 
				
			||||||
  issue_comment:
 | 
					 | 
				
			||||||
    types: [created]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  manual_trigger:
 | 
					 | 
				
			||||||
    name: Manual Trigger Cleanup
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    if: ${{ gitea.event_name == 'issue_comment' }}
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - name: Log event metadata
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          echo "Issue: ${{ gitea.event.issue.number }}"
 | 
					 | 
				
			||||||
          echo "Comment: ${{ gitea.event.comment.body }}"
 | 
					 | 
				
			||||||
          echo "User: ${{ gitea.event.comment.user.login }}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Stop workflow if required conditions are not met
 | 
					 | 
				
			||||||
        if: ${{ !contains(gitea.event.issue.number, '436') || !contains(gitea.event.comment.body, '/trigger-release') || !contains(gitea.event.comment.user.login, 'luke') }}
 | 
					 | 
				
			||||||
        run: exit 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Delete issue comment
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          curl -X DELETE \
 | 
					 | 
				
			||||||
            -H "Authorization: token ${{ gitea.token }}" \
 | 
					 | 
				
			||||||
            "${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/issues/comments/${{ gitea.event.comment.id }}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  # test:
 | 
					  # test:
 | 
				
			||||||
  #   name: Unit Test
 | 
					  #   name: Test
 | 
				
			||||||
  #   uses: https://git.tainton.uk/public/webexmemebot/.gitea/workflows/ci.yml@main
 | 
					  #   uses: https://git.tainton.uk/${{ gitea.repository }}/.gitea/workflows/ci.yml@main
 | 
				
			||||||
  #   continue-on-error: true
 | 
					
 | 
				
			||||||
 | 
					  tag:
 | 
				
			||||||
 | 
					    name: Tag release
 | 
				
			||||||
 | 
					    uses: https://git.tainton.uk/actions/gha-workflows/.gitea/workflows/release-with-tag.yaml@main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  create_release:
 | 
					  create_release:
 | 
				
			||||||
    name: Create Release
 | 
					    name: Create Release
 | 
				
			||||||
 | 
					    needs: tag
 | 
				
			||||||
 | 
					    uses: https://git.tainton.uk/actions/gha-workflows/.gitea/workflows/create-release-preexisting-tag.yaml@main
 | 
				
			||||||
 | 
					    with:
 | 
				
			||||||
 | 
					      tag: ${{ needs.tag.outputs.tag_name }}
 | 
				
			||||||
 | 
					      body: ${{ needs.tag.outputs.changelog }}
 | 
				
			||||||
 | 
					    secrets:
 | 
				
			||||||
 | 
					      ACTIONS_TOKEN: ${{ secrets.ACTIONS_TOKEN }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # get_release_id:
 | 
				
			||||||
 | 
					  #   name: Get Release ID
 | 
				
			||||||
 | 
					  #   runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					  #   needs: create_release
 | 
				
			||||||
 | 
					  #   outputs:
 | 
				
			||||||
 | 
					  #     releaseid: ${{ steps.getid.outputs.releaseid }}
 | 
				
			||||||
 | 
					  #   steps:
 | 
				
			||||||
 | 
					  #     - name: Get Release ID
 | 
				
			||||||
 | 
					  #       id: getid
 | 
				
			||||||
 | 
					  #       run: |
 | 
				
			||||||
 | 
					  #         rid=$(curl -s -X 'GET' \
 | 
				
			||||||
 | 
					  #         -H 'accept: application/json' \
 | 
				
			||||||
 | 
					  #         '${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases/latest' | jq -r '.id')
 | 
				
			||||||
 | 
					  #         echo "releaseid=$rid" >> "$GITEA_OUTPUT"
 | 
				
			||||||
 | 
					  #         echo "$rid"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  create_docker:
 | 
				
			||||||
 | 
					    name: Publish Docker Images
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    # needs: test
 | 
					    needs: [tag, create_release]
 | 
				
			||||||
    outputs:
 | 
					 | 
				
			||||||
      release_name: ${{ steps.get_next_version.outputs.tag }}
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - name: Check out repository
 | 
					 | 
				
			||||||
        uses: actions/checkout@v4.2.2
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          fetch-depth: 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Changes since last tag
 | 
					 | 
				
			||||||
        id: changes
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          rm -f .changes
 | 
					 | 
				
			||||||
          git log $(git describe --tags --abbrev=0)..HEAD --no-merges --oneline >> .changes
 | 
					 | 
				
			||||||
          cat .changes
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Check for changes
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          if [[ -z $(grep '[^[:space:]]' .changes) ]] ; then
 | 
					 | 
				
			||||||
            echo "changes=false"
 | 
					 | 
				
			||||||
            echo "changes=false" >> "$GITEA_OUTPUT"
 | 
					 | 
				
			||||||
          else
 | 
					 | 
				
			||||||
            echo "changes=true"
 | 
					 | 
				
			||||||
            echo "changes=true" >> "$GITEA_OUTPUT"
 | 
					 | 
				
			||||||
          fi
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Cancel if no changes
 | 
					 | 
				
			||||||
        if: steps.changes.outputs.changes == 'false'
 | 
					 | 
				
			||||||
        run: exit 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Set server URL
 | 
					 | 
				
			||||||
        id: set_srvurl
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          SRVURL=$(echo "${{ gitea.server_url }}" | sed 's/https:\/\/\(.*\)/\1/')
 | 
					 | 
				
			||||||
          echo "srvurl=$SRVURL" >> "$GITEA_OUTPUT"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Get next version
 | 
					 | 
				
			||||||
        uses: TriPSs/conventional-changelog-action@v6
 | 
					 | 
				
			||||||
        id: get_next_version
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          git-url: ${{ steps.set_srvurl.outputs.srvurl }}
 | 
					 | 
				
			||||||
          github-token: ${{ gitea.token }}
 | 
					 | 
				
			||||||
          preset: "conventionalcommits"
 | 
					 | 
				
			||||||
          # preset: "angular"  # This is the default
 | 
					 | 
				
			||||||
          skip-commit: true
 | 
					 | 
				
			||||||
          release-count: 1
 | 
					 | 
				
			||||||
          output-file: false
 | 
					 | 
				
			||||||
          create-summary: true
 | 
					 | 
				
			||||||
          skip-on-empty: true
 | 
					 | 
				
			||||||
          skip-version-file: true
 | 
					 | 
				
			||||||
          skip-tag: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Create release
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          curl -s -X POST \
 | 
					 | 
				
			||||||
            -H "Authorization: token ${{ secrets.ACTIONS_TOKEN }}" \
 | 
					 | 
				
			||||||
            -H "accept: application/json" \
 | 
					 | 
				
			||||||
            -H "Content-Type: application/json" \
 | 
					 | 
				
			||||||
            -d "{\"tag_name\": \"${{ steps.get_next_version.outputs.tag }}\", \"name\": \"${{ steps.get_next_version.outputs.tag }}\", \"body\": \"${{ steps.get_next_version.outputs.changelog }}\"}" \
 | 
					 | 
				
			||||||
            "${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  build_docker:
 | 
					 | 
				
			||||||
    name: Build Docker Images
 | 
					 | 
				
			||||||
    needs: create_release
 | 
					 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Update Docker configuration
 | 
					      - name: Update Docker configuration
 | 
				
			||||||
        continue-on-error: true
 | 
					        continue-on-error: true
 | 
				
			||||||
@@ -110,11 +54,17 @@ jobs:
 | 
				
			|||||||
          echo "DOCKER_OPTS=\"--insecure-registry ${{ vars.PACKAGES_REGISTRY_URL }}\"" >> /etc/default/docker
 | 
					          echo "DOCKER_OPTS=\"--insecure-registry ${{ vars.PACKAGES_REGISTRY_URL }}\"" >> /etc/default/docker
 | 
				
			||||||
          echo "{\"insecure-registries\": [\"${{ vars.PACKAGES_REGISTRY_URL }}\"]}" > /etc/docker/daemon.json
 | 
					          echo "{\"insecure-registries\": [\"${{ vars.PACKAGES_REGISTRY_URL }}\"]}" > /etc/docker/daemon.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Get repo name
 | 
				
			||||||
 | 
					        id: split
 | 
				
			||||||
 | 
					        run: echo "repo=${REPO##*/}" >> "$GITEA_OUTPUT"
 | 
				
			||||||
 | 
					        env:
 | 
				
			||||||
 | 
					          REPO: ${{ gitea.repository }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Check out repository
 | 
					      - name: Check out repository
 | 
				
			||||||
        uses: actions/checkout@v4
 | 
					        uses: actions/checkout@v5.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          fetch-depth: 0
 | 
					          fetch-depth: 0
 | 
				
			||||||
          ref: ${{ needs.create_release.outputs.release_name }}
 | 
					          ref: ${{ needs.tag.outputs.tag_name }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Set up Docker Buildx
 | 
					      - name: Set up Docker Buildx
 | 
				
			||||||
        uses: docker/setup-buildx-action@v3
 | 
					        uses: docker/setup-buildx-action@v3
 | 
				
			||||||
@@ -137,10 +87,10 @@ jobs:
 | 
				
			|||||||
        id: meta
 | 
					        id: meta
 | 
				
			||||||
        uses: docker/metadata-action@v5
 | 
					        uses: docker/metadata-action@v5
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
 | 
					          tags: type=semver,pattern=v{{version}},value=${{ needs.tag.outputs.tag_name }}
 | 
				
			||||||
          images: |
 | 
					          images: |
 | 
				
			||||||
            ghcr.io/${{ vars.GHCR_USERNAME }}/webexmemebot
 | 
					            ghcr.io/${{ vars.GHCR_USERNAME }}/${{ steps.split.outputs.repo }}
 | 
				
			||||||
            ${{ vars.PACKAGES_REGISTRY_URL }}/${{ gitea.repository }}
 | 
					            ${{ vars.PACKAGES_REGISTRY_URL }}/${{ gitea.repository }}
 | 
				
			||||||
          tags: type=semver,pattern=v{{version}},value=${{ needs.create_release.outputs.release_name }}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Print metadata
 | 
					      - name: Print metadata
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										36
									
								
								.gitea/workflows/security.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								.gitea/workflows/security.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					name: Security
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  workflow_dispatch:
 | 
				
			||||||
 | 
					  push:
 | 
				
			||||||
 | 
					    branches:
 | 
				
			||||||
 | 
					      - main
 | 
				
			||||||
 | 
					  schedule:
 | 
				
			||||||
 | 
					    - cron: "@daily"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  # sonarqube:
 | 
				
			||||||
 | 
					  #   name: SonarQube
 | 
				
			||||||
 | 
					  #   runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					  #   steps:
 | 
				
			||||||
 | 
					  #     - name: Checkout repo
 | 
				
			||||||
 | 
					  #       uses: actions/checkout@v4.2.2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  #     - name: SonarQube Scan
 | 
				
			||||||
 | 
					  #       uses: SonarSource/sonarqube-scan-action@v5.2.0
 | 
				
			||||||
 | 
					  #       env:
 | 
				
			||||||
 | 
					  #         SONAR_HOST_URL: ${{ secrets.SONARQUBE_HOST_URL }}
 | 
				
			||||||
 | 
					  #         SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  snyk:
 | 
				
			||||||
 | 
					    name: Snyk
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: Checkout repo
 | 
				
			||||||
 | 
					        uses: actions/checkout@v5.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Snyk
 | 
				
			||||||
 | 
					        uses: snyk/actions/python@master
 | 
				
			||||||
 | 
					        continue-on-error: true
 | 
				
			||||||
 | 
					        env:
 | 
				
			||||||
 | 
					          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
 | 
				
			||||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -9,6 +9,7 @@ __pycache__/
 | 
				
			|||||||
# Distribution / packaging
 | 
					# Distribution / packaging
 | 
				
			||||||
.Python
 | 
					.Python
 | 
				
			||||||
build/
 | 
					build/
 | 
				
			||||||
 | 
					.pdm-build/
 | 
				
			||||||
develop-eggs/
 | 
					develop-eggs/
 | 
				
			||||||
dist/
 | 
					dist/
 | 
				
			||||||
downloads/
 | 
					downloads/
 | 
				
			||||||
@@ -33,6 +34,9 @@ MANIFEST
 | 
				
			|||||||
*.manifest
 | 
					*.manifest
 | 
				
			||||||
*.spec
 | 
					*.spec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# PyRight
 | 
				
			||||||
 | 
					pyrightconfig.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Installer logs
 | 
					# Installer logs
 | 
				
			||||||
pip-log.txt
 | 
					pip-log.txt
 | 
				
			||||||
pip-delete-this-directory.txt
 | 
					pip-delete-this-directory.txt
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
fail_fast: false
 | 
					fail_fast: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
minimum_pre_commit_version: 3.8.0
 | 
					minimum_pre_commit_version: 4.3.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
default_install_hook_types: [pre-commit, commit-msg]
 | 
					default_install_hook_types: [pre-commit, commit-msg]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										14
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								Dockerfile
									
									
									
									
									
								
							@@ -1,22 +1,24 @@
 | 
				
			|||||||
FROM python:3.13-slim
 | 
					FROM python:3.14.0-slim
 | 
				
			||||||
LABEL maintainer="Luke Tainton <luke@tainton.uk>"
 | 
					LABEL maintainer="Luke Tainton <luke@tainton.uk>"
 | 
				
			||||||
LABEL org.opencontainers.image.source="https://github.com/luketainton/webexmemebot"
 | 
					 | 
				
			||||||
USER root
 | 
					USER root
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENV PYTHONPATH="/run:/usr/local/lib/python3.13/lib-dynload:/usr/local/lib/python3.13/site-packages:/usr/local/lib/python3.13"
 | 
					ENV PYTHONPATH="/run:/usr/local/lib/python3.13/lib-dynload:/usr/local/lib/python3.13/site-packages:/usr/local/lib/python3.13"
 | 
				
			||||||
 | 
					ENV UV_PROJECT_ENVIRONMENT="/usr/local/"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WORKDIR /run
 | 
					WORKDIR /run
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY imp.py /run/imp.py
 | 
					COPY imp.py /run/imp.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN mkdir -p /.local && \
 | 
					RUN mkdir -p /.local && \
 | 
				
			||||||
    chmod -R 777 /.local && \
 | 
					    chmod -R 777 /.local && \
 | 
				
			||||||
    pip install -U pip poetry
 | 
					    pip install -U pip uv==0.5.14
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY pyproject.toml /run/pyproject.toml
 | 
					COPY pyproject.toml /run/pyproject.toml
 | 
				
			||||||
COPY poetry.lock /run/poetry.lock
 | 
					COPY uv.lock /run/uv.lock
 | 
				
			||||||
 | 
					# needed for PDM build
 | 
				
			||||||
 | 
					COPY README.md /run/README.md
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN poetry config virtualenvs.create false && \
 | 
					RUN uv sync --frozen
 | 
				
			||||||
    poetry install --without dev
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENTRYPOINT ["python3", "-B", "-m", "app.main"]
 | 
					ENTRYPOINT ["python3", "-B", "-m", "app.main"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										15
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					# webexmemebot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Description
 | 
				
			||||||
 | 
					Webex-based meme generation bot using memegen.link.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## How to install
 | 
				
			||||||
 | 
					1. Clone the repository
 | 
				
			||||||
 | 
					2. Copy `.env.default` to `.env`
 | 
				
			||||||
 | 
					3. Edit `.env` as required:
 | 
				
			||||||
 | 
					    - `WEBEX_API_KEY` - Webex API key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## How to use
 | 
				
			||||||
 | 
					1. Install Docker and Docker Compose
 | 
				
			||||||
 | 
					2. Run `docker-compose up -d`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										14
									
								
								app/close.py
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								app/close.py
									
									
									
									
									
								
							@@ -1,8 +1,13 @@
 | 
				
			|||||||
 | 
					"""Command module for handling the 'exit' command in the Webex meme bot."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from webex_bot.models.command import Command
 | 
					from webex_bot.models.command import Command
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ExitCommand(Command):
 | 
					class ExitCommand(Command):
 | 
				
			||||||
 | 
					    """Command to handle the 'exit' command in the Webex meme bot."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self) -> None:
 | 
					    def __init__(self) -> None:
 | 
				
			||||||
 | 
					        """Initialize the ExitCommand with command keyword and help message."""
 | 
				
			||||||
        super().__init__(
 | 
					        super().__init__(
 | 
				
			||||||
            command_keyword="exit",
 | 
					            command_keyword="exit",
 | 
				
			||||||
            help_message="Exit",
 | 
					            help_message="Exit",
 | 
				
			||||||
@@ -10,11 +15,14 @@ class ExitCommand(Command):
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
        self.sender: str = ""
 | 
					        self.sender: str = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def pre_execute(self, message, attachment_actions, activity) -> None:
 | 
					    def pre_execute(self, message, attachment_actions, activity) -> None:  # pylint: disable=unused-argument
 | 
				
			||||||
 | 
					        """Pre-execution logic for the exit command."""
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def execute(self, message, attachment_actions, activity) -> None:
 | 
					    def execute(self, message, attachment_actions, activity) -> None:  # pylint: disable=unused-argument
 | 
				
			||||||
 | 
					        """Execute the exit command."""
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def post_execute(self, message, attachment_actions, activity) -> None:
 | 
					    def post_execute(self, message, attachment_actions, activity) -> None:  # pylint: disable=unused-argument
 | 
				
			||||||
 | 
					        """Post-execution logic for the exit command."""
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										29
									
								
								app/img.py
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								app/img.py
									
									
									
									
									
								
							@@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					"""Generates meme images using the memegen.link API."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import requests
 | 
					import requests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CHAR_REPLACEMENTS: list = [
 | 
					CHAR_REPLACEMENTS: list = [
 | 
				
			||||||
@@ -17,6 +19,11 @@ CHAR_REPLACEMENTS: list = [
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_templates() -> list[dict]:
 | 
					def get_templates() -> list[dict]:
 | 
				
			||||||
 | 
					    """Fetches available meme templates from the memegen.link API.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Returns:
 | 
				
			||||||
 | 
					        list[dict]: A list of dictionaries containing meme template information.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    url: str = "https://api.memegen.link/templates"
 | 
					    url: str = "https://api.memegen.link/templates"
 | 
				
			||||||
    req: requests.Response = requests.get(url=url, timeout=10)
 | 
					    req: requests.Response = requests.get(url=url, timeout=10)
 | 
				
			||||||
    req.raise_for_status()
 | 
					    req.raise_for_status()
 | 
				
			||||||
@@ -40,6 +47,14 @@ def get_templates() -> list[dict]:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def format_meme_string(input_string: str) -> str:
 | 
					def format_meme_string(input_string: str) -> str:
 | 
				
			||||||
 | 
					    """Formats a string for use in a meme image URL.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        input_string (str): The string to format.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Returns:
 | 
				
			||||||
 | 
					        str: The formatted string suitable for meme image URLs.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    # https://memegen.link/#special-characters
 | 
					    # https://memegen.link/#special-characters
 | 
				
			||||||
    out_string: str = input_string
 | 
					    out_string: str = input_string
 | 
				
			||||||
    for char_replacement in CHAR_REPLACEMENTS:
 | 
					    for char_replacement in CHAR_REPLACEMENTS:
 | 
				
			||||||
@@ -48,6 +63,16 @@ def format_meme_string(input_string: str) -> str:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def generate_api_url(template: str, top_str: str, btm_str: str) -> str:
 | 
					def generate_api_url(template: str, top_str: str, btm_str: str) -> str:
 | 
				
			||||||
 | 
					    """Generates a meme image URL using the memegen.link API.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        template (str): The template identifier in the format "name.ext".
 | 
				
			||||||
 | 
					        top_str (str): The text for the top line of the meme.
 | 
				
			||||||
 | 
					        btm_str (str): The text for the bottom line of the meme.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Returns:
 | 
				
			||||||
 | 
					        str: The complete URL for the meme image.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    tmpl_name: str
 | 
					    tmpl_name: str
 | 
				
			||||||
    tmpl_ext: str
 | 
					    tmpl_ext: str
 | 
				
			||||||
    tmpl_name, tmpl_ext = template.split(".")
 | 
					    tmpl_name, tmpl_ext = template.split(".")
 | 
				
			||||||
@@ -55,7 +80,5 @@ def generate_api_url(template: str, top_str: str, btm_str: str) -> str:
 | 
				
			|||||||
    top_str = format_meme_string(top_str)
 | 
					    top_str = format_meme_string(top_str)
 | 
				
			||||||
    btm_str = format_meme_string(btm_str)
 | 
					    btm_str = format_meme_string(btm_str)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    url: str = (
 | 
					    url: str = f"https://api.memegen.link/images/{tmpl_name}/{top_str}/{btm_str}.{tmpl_ext}"
 | 
				
			||||||
        f"https://api.memegen.link/images/{tmpl_name}/{top_str}/{btm_str}.{tmpl_ext}"
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    return url
 | 
					    return url
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,7 @@
 | 
				
			|||||||
#!/usr/local/bin/python3
 | 
					#!/usr/local/bin/python3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""Main entry point for the Webex Bot application."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from webex_bot.webex_bot import WebexBot
 | 
					from webex_bot.webex_bot import WebexBot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from app import close, meme
 | 
					from app import close, meme
 | 
				
			||||||
@@ -18,6 +20,7 @@ def create_bot() -> WebexBot:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def main() -> None:
 | 
					def main() -> None:
 | 
				
			||||||
 | 
					    """Main function to run the Webex Bot."""
 | 
				
			||||||
    bot: WebexBot = create_bot()
 | 
					    bot: WebexBot = create_bot()
 | 
				
			||||||
    bot.add_command(meme.MakeMemeCommand())
 | 
					    bot.add_command(meme.MakeMemeCommand())
 | 
				
			||||||
    bot.add_command(close.ExitCommand())
 | 
					    bot.add_command(close.ExitCommand())
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										40
									
								
								app/meme.py
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								app/meme.py
									
									
									
									
									
								
							@@ -1,9 +1,11 @@
 | 
				
			|||||||
 | 
					"""Generates meme images using the memegen.link API."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from webex_bot.models.command import Command
 | 
					from webex_bot.models.command import Command
 | 
				
			||||||
from webex_bot.models.response import Response, response_from_adaptive_card
 | 
					from webex_bot.models.response import Response, response_from_adaptive_card
 | 
				
			||||||
from webexteamssdk.models.cards import (
 | 
					from webexpythonsdk.models.cards import (
 | 
				
			||||||
    AdaptiveCard,
 | 
					    AdaptiveCard,
 | 
				
			||||||
    Choice,
 | 
					    Choice,
 | 
				
			||||||
    Choices,
 | 
					    ChoiceSet,
 | 
				
			||||||
    Column,
 | 
					    Column,
 | 
				
			||||||
    ColumnSet,
 | 
					    ColumnSet,
 | 
				
			||||||
    FontSize,
 | 
					    FontSize,
 | 
				
			||||||
@@ -11,7 +13,7 @@ from webexteamssdk.models.cards import (
 | 
				
			|||||||
    Text,
 | 
					    Text,
 | 
				
			||||||
    TextBlock,
 | 
					    TextBlock,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from webexteamssdk.models.cards.actions import OpenUrl, Submit
 | 
					from webexpythonsdk.models.cards.actions import OpenUrl, Submit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from app import img
 | 
					from app import img
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -22,6 +24,7 @@ class MakeMemeCommand(Command):
 | 
				
			|||||||
    """Class for initial Webex interactive card."""
 | 
					    """Class for initial Webex interactive card."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self) -> None:
 | 
					    def __init__(self) -> None:
 | 
				
			||||||
 | 
					        """Initialize the MakeMemeCommand with command keyword and help message."""
 | 
				
			||||||
        super().__init__(
 | 
					        super().__init__(
 | 
				
			||||||
            command_keyword="/meme",
 | 
					            command_keyword="/meme",
 | 
				
			||||||
            help_message="Make a Meme",
 | 
					            help_message="Make a Meme",
 | 
				
			||||||
@@ -29,10 +32,12 @@ class MakeMemeCommand(Command):
 | 
				
			|||||||
            delete_previous_message=True,
 | 
					            delete_previous_message=True,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def pre_execute(self, message, attachment_actions, activity) -> None:
 | 
					    def pre_execute(self, message, attachment_actions, activity) -> None:  # pylint: disable=unused-argument
 | 
				
			||||||
 | 
					        """Pre-execution logic for the MakeMemeCommand."""
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def execute(self, message, attachment_actions, activity) -> Response:
 | 
					    def execute(self, message, attachment_actions, activity) -> Response:  # pylint: disable=unused-argument
 | 
				
			||||||
 | 
					        """Execute the MakeMemeCommand and return an adaptive card."""
 | 
				
			||||||
        card_body: list = [
 | 
					        card_body: list = [
 | 
				
			||||||
            ColumnSet(
 | 
					            ColumnSet(
 | 
				
			||||||
                columns=[
 | 
					                columns=[
 | 
				
			||||||
@@ -45,13 +50,13 @@ class MakeMemeCommand(Command):
 | 
				
			|||||||
                                size=FontSize.MEDIUM,
 | 
					                                size=FontSize.MEDIUM,
 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                            TextBlock(
 | 
					                            TextBlock(
 | 
				
			||||||
                                "This bot uses memegen.link to generate memes. Click 'View Templates' to view available templates.",
 | 
					                                "This bot uses memegen.link to generate memes. Click 'View Templates' to view available templates.",  # pylint: disable=line-too-long
 | 
				
			||||||
                                weight=FontWeight.LIGHTER,
 | 
					                                weight=FontWeight.LIGHTER,
 | 
				
			||||||
                                size=FontSize.SMALL,
 | 
					                                size=FontSize.SMALL,
 | 
				
			||||||
                                wrap=True,
 | 
					                                wrap=True,
 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                            TextBlock(
 | 
					                            TextBlock(
 | 
				
			||||||
                                "Both fields are required. If you do not want to specify a value, please type a space.",
 | 
					                                "Both fields are required. If you do not want to specify a value, please type a space.",  # pylint: disable=line-too-long
 | 
				
			||||||
                                weight=FontWeight.LIGHTER,
 | 
					                                weight=FontWeight.LIGHTER,
 | 
				
			||||||
                                size=FontSize.SMALL,
 | 
					                                size=FontSize.SMALL,
 | 
				
			||||||
                                wrap=True,
 | 
					                                wrap=True,
 | 
				
			||||||
@@ -65,13 +70,10 @@ class MakeMemeCommand(Command):
 | 
				
			|||||||
                    Column(
 | 
					                    Column(
 | 
				
			||||||
                        width=1,
 | 
					                        width=1,
 | 
				
			||||||
                        items=[
 | 
					                        items=[
 | 
				
			||||||
                            Choices(
 | 
					                            ChoiceSet(
 | 
				
			||||||
                                id="meme_type",
 | 
					                                id="meme_type",
 | 
				
			||||||
                                isMultiSelect=False,
 | 
					                                isMultiSelect=False,
 | 
				
			||||||
                                choices=[
 | 
					                                choices=[Choice(title=x["name"], value=x["choiceval"]) for x in TEMPLATES],
 | 
				
			||||||
                                    Choice(title=x["name"], value=x["choiceval"])
 | 
					 | 
				
			||||||
                                    for x in TEMPLATES
 | 
					 | 
				
			||||||
                                ],
 | 
					 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                            Text(id="text_top", placeholder="Top Text", maxLength=100),
 | 
					                            Text(id="text_top", placeholder="Top Text", maxLength=100),
 | 
				
			||||||
                            Text(
 | 
					                            Text(
 | 
				
			||||||
@@ -103,6 +105,7 @@ class MakeMemeCallback(Command):
 | 
				
			|||||||
    """Class to process user data and return meme."""
 | 
					    """Class to process user data and return meme."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self) -> None:
 | 
					    def __init__(self) -> None:
 | 
				
			||||||
 | 
					        """Initialize the MakeMemeCallback with command keyword and help message."""
 | 
				
			||||||
        super().__init__(
 | 
					        super().__init__(
 | 
				
			||||||
            card_callback_keyword="make_meme_callback_rbamzfyx",
 | 
					            card_callback_keyword="make_meme_callback_rbamzfyx",
 | 
				
			||||||
            delete_previous_message=True,
 | 
					            delete_previous_message=True,
 | 
				
			||||||
@@ -113,7 +116,8 @@ class MakeMemeCallback(Command):
 | 
				
			|||||||
        self.meme: str = ""
 | 
					        self.meme: str = ""
 | 
				
			||||||
        self.meme_filename: str = ""
 | 
					        self.meme_filename: str = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def pre_execute(self, message, attachment_actions, activity) -> str:
 | 
					    def pre_execute(self, message, attachment_actions, activity) -> str:  # pylint: disable=unused-argument
 | 
				
			||||||
 | 
					        """Pre-execution logic for the MakeMemeCallback."""
 | 
				
			||||||
        self.meme: str = attachment_actions.inputs.get("meme_type")
 | 
					        self.meme: str = attachment_actions.inputs.get("meme_type")
 | 
				
			||||||
        self.text_top: str = attachment_actions.inputs.get("text_top")
 | 
					        self.text_top: str = attachment_actions.inputs.get("text_top")
 | 
				
			||||||
        self.text_bottom: str = attachment_actions.inputs.get("text_bottom")
 | 
					        self.text_bottom: str = attachment_actions.inputs.get("text_bottom")
 | 
				
			||||||
@@ -127,13 +131,12 @@ class MakeMemeCallback(Command):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return "Generating your meme..."
 | 
					        return "Generating your meme..."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def execute(self, message, attachment_actions, activity) -> Response | None:
 | 
					    def execute(self, message, attachment_actions, activity) -> Response | None:  # pylint: disable=unused-argument
 | 
				
			||||||
 | 
					        """Execute the MakeMemeCallback and return a response with the meme image."""
 | 
				
			||||||
        if self.error:
 | 
					        if self.error:
 | 
				
			||||||
            return None
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.meme_filename: str = img.generate_api_url(
 | 
					        self.meme_filename: str = img.generate_api_url(self.meme, self.text_top, self.text_bottom)
 | 
				
			||||||
            self.meme, self.text_top, self.text_bottom
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        msg: Response = Response(
 | 
					        msg: Response = Response(
 | 
				
			||||||
            attributes={
 | 
					            attributes={
 | 
				
			||||||
                "roomId": activity["target"]["globalId"],
 | 
					                "roomId": activity["target"]["globalId"],
 | 
				
			||||||
@@ -143,5 +146,6 @@ class MakeMemeCallback(Command):
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
        return msg
 | 
					        return msg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def post_execute(self, message, attachment_actions, activity) -> None:
 | 
					    def post_execute(self, message, attachment_actions, activity) -> None:  # pylint: disable=unused-argument
 | 
				
			||||||
 | 
					        """Post-execution logic for the MakeMemeCallback."""
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,3 @@
 | 
				
			|||||||
---
 | 
					 | 
				
			||||||
version: "3"
 | 
					 | 
				
			||||||
services:
 | 
					services:
 | 
				
			||||||
  app:
 | 
					  app:
 | 
				
			||||||
    build:
 | 
					    build:
 | 
				
			||||||
@@ -7,4 +5,3 @@ services:
 | 
				
			|||||||
      dockerfile: Dockerfile
 | 
					      dockerfile: Dockerfile
 | 
				
			||||||
    restart: unless-stopped
 | 
					    restart: unless-stopped
 | 
				
			||||||
    env_file: .env
 | 
					    env_file: .env
 | 
				
			||||||
...
 | 
					 | 
				
			||||||
@@ -1,29 +1,37 @@
 | 
				
			|||||||
[tool.poetry]
 | 
					[project]
 | 
				
			||||||
name = "webexmemebot"
 | 
					name = "webexmemebot"
 | 
				
			||||||
version = "0.0.0"  # Version is tracked by GitHub Releases
 | 
					version = "0.0.0"
 | 
				
			||||||
description = "Webex-based meme generation bot using memegen.link."
 | 
					description = "Webex-based meme generation bot using memegen.link."
 | 
				
			||||||
authors = ["luketainton"]
 | 
					 | 
				
			||||||
readme = "README.md"
 | 
					readme = "README.md"
 | 
				
			||||||
package-mode = false
 | 
					authors = [
 | 
				
			||||||
 | 
					    {name = "luketainton"},
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					requires-python = "<3.15,>=3.14"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					    "webex-bot<1.1.0,>=1.0.3",
 | 
				
			||||||
 | 
					    "pillow<12.0.1,>=12.0.0",
 | 
				
			||||||
 | 
					    "astroid<=4.0.1",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.poetry.dependencies]
 | 
					[dependency-groups]
 | 
				
			||||||
python = "^3.11.2"
 | 
					dev = [
 | 
				
			||||||
webex-bot = "^0.5.2"
 | 
					    "black<25.9.1,>=25.9.0",
 | 
				
			||||||
pillow = "^11.0.0"
 | 
					    "coverage<8.0.0,>=7.6.10",
 | 
				
			||||||
astroid = "<=3.3.8"
 | 
					    "isort<7.0.1,>=7.0.0",
 | 
				
			||||||
 | 
					    "pylint<4.1.0,>=4.0.0",
 | 
				
			||||||
 | 
					    "pylint-exit<2.0.0,>=1.2.0",
 | 
				
			||||||
 | 
					    "pytest<9.0.0,>=8.3.4",
 | 
				
			||||||
 | 
					    "pre-commit<5.0.0,>=4.0.1",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.poetry.group.dev.dependencies]
 | 
					[project.scripts]
 | 
				
			||||||
black = "^24.10.0"
 | 
					 | 
				
			||||||
coverage = "^7.6.10"
 | 
					 | 
				
			||||||
isort = "^5.13.2"
 | 
					 | 
				
			||||||
pylint = "^3.3.2"
 | 
					 | 
				
			||||||
pylint-exit = "^1.2.0"
 | 
					 | 
				
			||||||
pytest = "^8.3.4"
 | 
					 | 
				
			||||||
pre-commit = "^4.0.1"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[build-system]
 | 
					 | 
				
			||||||
requires = ["poetry-core"]
 | 
					 | 
				
			||||||
build-backend = "poetry.core.masonry.api"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[tool.poetry.scripts]
 | 
					 | 
				
			||||||
meme = "app.main:main"
 | 
					meme = "app.main:main"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[tool.pdm.build]
 | 
				
			||||||
 | 
					includes = []
 | 
				
			||||||
 | 
					[build-system]
 | 
				
			||||||
 | 
					requires = ["pdm-backend"]
 | 
				
			||||||
 | 
					build-backend = "pdm.backend"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[tool.black]
 | 
				
			||||||
 | 
					line-length = 120
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,25 +1,23 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "assignAutomerge": true,
 | 
					  "assignAutomerge": false,
 | 
				
			||||||
  "assigneesFromCodeOwners": true,
 | 
					  "assigneesFromCodeOwners": false,
 | 
				
			||||||
  "dependencyDashboardAutoclose": true,
 | 
					  "dependencyDashboardAutoclose": true,
 | 
				
			||||||
  "extends": [
 | 
					  "extends": ["config:recommended"],
 | 
				
			||||||
    "config:recommended"
 | 
					  "ignorePaths": ["**/.archive/**"],
 | 
				
			||||||
  ],
 | 
					  "labels": ["type/dependencies"],
 | 
				
			||||||
  "ignorePaths": [
 | 
					 | 
				
			||||||
    "**/.archive/**"
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  "labels": [
 | 
					 | 
				
			||||||
    "type/dependencies"
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  "platformCommit": "enabled",
 | 
					  "platformCommit": "enabled",
 | 
				
			||||||
  "rebaseWhen": "behind-base-branch",
 | 
					  "rebaseWhen": "behind-base-branch",
 | 
				
			||||||
  "rollbackPrs": true,
 | 
					  "rollbackPrs": true,
 | 
				
			||||||
 | 
					  "semanticCommits": "enabled",
 | 
				
			||||||
 | 
					  "semanticCommitScope": "deps",
 | 
				
			||||||
 | 
					  "semanticCommitType": "feat",
 | 
				
			||||||
  "vulnerabilityAlerts": {
 | 
					  "vulnerabilityAlerts": {
 | 
				
			||||||
    "commitMessagePrefix": "[SECURITY] ",
 | 
					    "commitMessagePrefix": "[SECURITY] ",
 | 
				
			||||||
    "enabled": true,
 | 
					    "enabled": true,
 | 
				
			||||||
    "labels": [
 | 
					    "labels": ["security"],
 | 
				
			||||||
      "security"
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    "prCreation": "immediate"
 | 
					    "prCreation": "immediate"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "lockFileMaintenance": {
 | 
				
			||||||
 | 
					    "enabled": true
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,19 +2,22 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
vars: dict = {
 | 
					env_vars: dict = {
 | 
				
			||||||
    "APP_VERSION": "dev",
 | 
					    "APP_VERSION": "dev",
 | 
				
			||||||
    "WEBEX_API_KEY": "testing",
 | 
					    "WEBEX_API_KEY": "testing",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
for var, value in vars.items():
 | 
					for var, value in env_vars.items():
 | 
				
			||||||
    os.environ[var] = value
 | 
					    os.environ[var] = value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# needs to be imported AFTER environment variables are set
 | 
					# needs to be imported AFTER environment variables are set
 | 
				
			||||||
from app.config import config  # pragma: no cover  # noqa: E402
 | 
					from app.config import (
 | 
				
			||||||
 | 
					    config,
 | 
				
			||||||
 | 
					)  # pylint: disable=wrong-import-position # pragma: no cover  # noqa: E402
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_config() -> None:
 | 
					def test_config() -> None:
 | 
				
			||||||
    assert config.webex_token == vars["WEBEX_API_KEY"]
 | 
					    """Test the configuration settings."""
 | 
				
			||||||
    assert config.version == vars["APP_VERSION"]
 | 
					    assert config.webex_token == env_vars["WEBEX_API_KEY"]
 | 
				
			||||||
 | 
					    assert config.version == env_vars["APP_VERSION"]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,8 +29,4 @@ def test_error_false() -> None:
 | 
				
			|||||||
    callback.text_top = "TEST"
 | 
					    callback.text_top = "TEST"
 | 
				
			||||||
    callback.text_bottom = "TEST"
 | 
					    callback.text_bottom = "TEST"
 | 
				
			||||||
    result: Response = callback.execute(None, None, {"target": {"globalId": "TEST"}})
 | 
					    result: Response = callback.execute(None, None, {"target": {"globalId": "TEST"}})
 | 
				
			||||||
    assert (
 | 
					    assert isinstance(result, Response) and result.roomId == "TEST" and result.files[0] == callback.meme_filename
 | 
				
			||||||
        isinstance(result, Response)
 | 
					 | 
				
			||||||
        and result.roomId == "TEST"
 | 
					 | 
				
			||||||
        and result.files[0] == callback.meme_filename
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										577
									
								
								uv.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										577
									
								
								uv.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,577 @@
 | 
				
			|||||||
 | 
					version = 1
 | 
				
			||||||
 | 
					revision = 3
 | 
				
			||||||
 | 
					requires-python = "==3.14.*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "astroid"
 | 
				
			||||||
 | 
					version = "4.0.1"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/a7/d1/6eee8726a863f28ff50d26c5eacb1a590f96ccbb273ce0a8c047ffb10f5a/astroid-4.0.1.tar.gz", hash = "sha256:0d778ec0def05b935e198412e62f9bcca8b3b5c39fdbe50b0ba074005e477aab", size = 405414, upload-time = "2025-10-11T15:15:42.6Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/47/f4/034361a9cbd9284ef40c8ad107955ede4efae29cbc17a059f63f6569c06a/astroid-4.0.1-py3-none-any.whl", hash = "sha256:37ab2f107d14dc173412327febf6c78d39590fdafcb44868f03b6c03452e3db0", size = 276268, upload-time = "2025-10-11T15:15:40.585Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "backoff"
 | 
				
			||||||
 | 
					version = "2.2.1"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "black"
 | 
				
			||||||
 | 
					version = "25.9.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					    { name = "click" },
 | 
				
			||||||
 | 
					    { name = "mypy-extensions" },
 | 
				
			||||||
 | 
					    { name = "packaging" },
 | 
				
			||||||
 | 
					    { name = "pathspec" },
 | 
				
			||||||
 | 
					    { name = "platformdirs" },
 | 
				
			||||||
 | 
					    { name = "pytokens" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/4b/43/20b5c90612d7bdb2bdbcceeb53d588acca3bb8f0e4c5d5c751a2c8fdd55a/black-25.9.0.tar.gz", hash = "sha256:0474bca9a0dd1b51791fcc507a4e02078a1c63f6d4e4ae5544b9848c7adfb619", size = 648393, upload-time = "2025-09-19T00:27:37.758Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/1b/46/863c90dcd3f9d41b109b7f19032ae0db021f0b2a81482ba0a1e28c84de86/black-25.9.0-py3-none-any.whl", hash = "sha256:474b34c1342cdc157d307b56c4c65bce916480c4a8f6551fdc6bf9b486a7c4ae", size = 203363, upload-time = "2025-09-19T00:27:35.724Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "certifi"
 | 
				
			||||||
 | 
					version = "2025.10.5"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "cfgv"
 | 
				
			||||||
 | 
					version = "3.4.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "charset-normalizer"
 | 
				
			||||||
 | 
					version = "3.4.4"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "click"
 | 
				
			||||||
 | 
					version = "8.3.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					    { name = "colorama", marker = "sys_platform == 'win32'" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "colorama"
 | 
				
			||||||
 | 
					version = "0.4.6"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "coloredlogs"
 | 
				
			||||||
 | 
					version = "15.0.1"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					    { name = "humanfriendly" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "coverage"
 | 
				
			||||||
 | 
					version = "7.11.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/1c/38/ee22495420457259d2f3390309505ea98f98a5eed40901cf62196abad006/coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050", size = 811905, upload-time = "2025-10-15T15:15:08.542Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/f4/06/e923830c1985ce808e40a3fa3eb46c13350b3224b7da59757d37b6ce12b8/coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497", size = 216110, upload-time = "2025-10-15T15:14:15.157Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/42/82/cdeed03bfead45203fb651ed756dfb5266028f5f939e7f06efac4041dad5/coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e", size = 216395, upload-time = "2025-10-15T15:14:16.863Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/fc/ba/e1c80caffc3199aa699813f73ff097bc2df7b31642bdbc7493600a8f1de5/coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1", size = 247433, upload-time = "2025-10-15T15:14:18.589Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/80/c0/5b259b029694ce0a5bbc1548834c7ba3db41d3efd3474489d7efce4ceb18/coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca", size = 249970, upload-time = "2025-10-15T15:14:20.307Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/8c/86/171b2b5e1aac7e2fd9b43f7158b987dbeb95f06d1fbecad54ad8163ae3e8/coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd", size = 251324, upload-time = "2025-10-15T15:14:22.419Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/1a/7e/7e10414d343385b92024af3932a27a1caf75c6e27ee88ba211221ff1a145/coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43", size = 247445, upload-time = "2025-10-15T15:14:24.205Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/c4/3b/e4f966b21f5be8c4bf86ad75ae94efa0de4c99c7bbb8114476323102e345/coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777", size = 249324, upload-time = "2025-10-15T15:14:26.234Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/00/a2/8479325576dfcd909244d0df215f077f47437ab852ab778cfa2f8bf4d954/coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2", size = 247261, upload-time = "2025-10-15T15:14:28.42Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/7b/d8/3a9e2db19d94d65771d0f2e21a9ea587d11b831332a73622f901157cc24b/coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d", size = 247092, upload-time = "2025-10-15T15:14:30.784Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/b3/b1/bbca3c472544f9e2ad2d5116b2379732957048be4b93a9c543fcd0207e5f/coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4", size = 248755, upload-time = "2025-10-15T15:14:32.585Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/89/49/638d5a45a6a0f00af53d6b637c87007eb2297042186334e9923a61aa8854/coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721", size = 218793, upload-time = "2025-10-15T15:14:34.972Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/30/cc/b675a51f2d068adb3cdf3799212c662239b0ca27f4691d1fff81b92ea850/coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad", size = 219587, upload-time = "2025-10-15T15:14:37.047Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/93/98/5ac886876026de04f00820e5094fe22166b98dcb8b426bf6827aaf67048c/coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479", size = 218168, upload-time = "2025-10-15T15:14:38.861Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/14/d1/b4145d35b3e3ecf4d917e97fc8895bcf027d854879ba401d9ff0f533f997/coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f", size = 216850, upload-time = "2025-10-15T15:14:40.651Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/ca/d1/7f645fc2eccd318369a8a9948acc447bb7c1ade2911e31d3c5620544c22b/coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e", size = 217071, upload-time = "2025-10-15T15:14:42.755Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/54/7d/64d124649db2737ceced1dfcbdcb79898d5868d311730f622f8ecae84250/coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44", size = 258570, upload-time = "2025-10-15T15:14:44.542Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/6c/3f/6f5922f80dc6f2d8b2c6f974835c43f53eb4257a7797727e6ca5b7b2ec1f/coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3", size = 260738, upload-time = "2025-10-15T15:14:46.436Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/0e/5f/9e883523c4647c860b3812b417a2017e361eca5b635ee658387dc11b13c1/coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b", size = 262994, upload-time = "2025-10-15T15:14:48.3Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/07/bb/43b5a8e94c09c8bf51743ffc65c4c841a4ca5d3ed191d0a6919c379a1b83/coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d", size = 257282, upload-time = "2025-10-15T15:14:50.236Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/aa/e5/0ead8af411411330b928733e1d201384b39251a5f043c1612970310e8283/coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2", size = 260430, upload-time = "2025-10-15T15:14:52.413Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/ae/66/03dd8bb0ba5b971620dcaac145461950f6d8204953e535d2b20c6b65d729/coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e", size = 258190, upload-time = "2025-10-15T15:14:54.268Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/45/ae/28a9cce40bf3174426cb2f7e71ee172d98e7f6446dff936a7ccecee34b14/coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996", size = 256658, upload-time = "2025-10-15T15:14:56.436Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/5c/7c/3a44234a8599513684bfc8684878fd7b126c2760f79712bb78c56f19efc4/coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11", size = 259342, upload-time = "2025-10-15T15:14:58.538Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/e1/e6/0108519cba871af0351725ebdb8660fd7a0fe2ba3850d56d32490c7d9b4b/coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73", size = 219568, upload-time = "2025-10-15T15:15:00.382Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/c9/76/44ba876e0942b4e62fdde23ccb029ddb16d19ba1bef081edd00857ba0b16/coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547", size = 220687, upload-time = "2025-10-15T15:15:02.322Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/b9/0c/0df55ecb20d0d0ed5c322e10a441775e1a3a5d78c60f0c4e1abfe6fcf949/coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3", size = 218711, upload-time = "2025-10-15T15:15:04.575Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/5f/04/642c1d8a448ae5ea1369eac8495740a79eb4e581a9fb0cbdce56bbf56da1/coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68", size = 207761, upload-time = "2025-10-15T15:15:06.439Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "dill"
 | 
				
			||||||
 | 
					version = "0.4.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976, upload-time = "2025-04-16T00:41:48.867Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload-time = "2025-04-16T00:41:47.671Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "distlib"
 | 
				
			||||||
 | 
					version = "0.4.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "filelock"
 | 
				
			||||||
 | 
					version = "3.20.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "humanfriendly"
 | 
				
			||||||
 | 
					version = "10.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					    { name = "pyreadline3", marker = "sys_platform == 'win32'" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "identify"
 | 
				
			||||||
 | 
					version = "2.6.15"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "idna"
 | 
				
			||||||
 | 
					version = "3.11"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "iniconfig"
 | 
				
			||||||
 | 
					version = "2.3.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "isort"
 | 
				
			||||||
 | 
					version = "7.0.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/63/53/4f3c058e3bace40282876f9b553343376ee687f3c35a525dc79dbd450f88/isort-7.0.0.tar.gz", hash = "sha256:5513527951aadb3ac4292a41a16cbc50dd1642432f5e8c20057d414bdafb4187", size = 805049, upload-time = "2025-10-11T13:30:59.107Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/7f/ed/e3705d6d02b4f7aea715a353c8ce193efd0b5db13e204df895d38734c244/isort-7.0.0-py3-none-any.whl", hash = "sha256:1bcabac8bc3c36c7fb7b98a76c8abb18e0f841a3ba81decac7691008592499c1", size = 94672, upload-time = "2025-10-11T13:30:57.665Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "mccabe"
 | 
				
			||||||
 | 
					version = "0.7.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "mypy-extensions"
 | 
				
			||||||
 | 
					version = "1.1.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "nodeenv"
 | 
				
			||||||
 | 
					version = "1.9.1"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "packaging"
 | 
				
			||||||
 | 
					version = "25.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "pathspec"
 | 
				
			||||||
 | 
					version = "0.12.1"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "pillow"
 | 
				
			||||||
 | 
					version = "12.0.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/54/2a/9a8c6ba2c2c07b71bec92cf63e03370ca5e5f5c5b119b742bcc0cde3f9c5/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9", size = 4045531, upload-time = "2025-10-15T18:23:10.121Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/84/54/836fdbf1bfb3d66a59f0189ff0b9f5f666cee09c6188309300df04ad71fa/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2", size = 4120554, upload-time = "2025-10-15T18:23:12.14Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/0d/cd/16aec9f0da4793e98e6b54778a5fbce4f375c6646fe662e80600b8797379/pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a", size = 3576812, upload-time = "2025-10-15T18:23:13.962Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/f6/b7/13957fda356dc46339298b351cae0d327704986337c3c69bb54628c88155/pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b", size = 5252689, upload-time = "2025-10-15T18:23:15.562Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/fc/f5/eae31a306341d8f331f43edb2e9122c7661b975433de5e447939ae61c5da/pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad", size = 4650186, upload-time = "2025-10-15T18:23:17.379Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/86/62/2a88339aa40c4c77e79108facbd307d6091e2c0eb5b8d3cf4977cfca2fe6/pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01", size = 6230308, upload-time = "2025-10-15T18:23:18.971Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/c7/33/5425a8992bcb32d1cb9fa3dd39a89e613d09a22f2c8083b7bf43c455f760/pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c", size = 8039222, upload-time = "2025-10-15T18:23:20.909Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/d8/61/3f5d3b35c5728f37953d3eec5b5f3e77111949523bd2dd7f31a851e50690/pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e", size = 6346657, upload-time = "2025-10-15T18:23:23.077Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/3a/be/ee90a3d79271227e0f0a33c453531efd6ed14b2e708596ba5dd9be948da3/pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e", size = 7038482, upload-time = "2025-10-15T18:23:25.005Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/44/34/a16b6a4d1ad727de390e9bd9f19f5f669e079e5826ec0f329010ddea492f/pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9", size = 6461416, upload-time = "2025-10-15T18:23:27.009Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/b6/39/1aa5850d2ade7d7ba9f54e4e4c17077244ff7a2d9e25998c38a29749eb3f/pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab", size = 7131584, upload-time = "2025-10-15T18:23:29.752Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/bf/db/4fae862f8fad0167073a7733973bfa955f47e2cac3dc3e3e6257d10fab4a/pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b", size = 6400621, upload-time = "2025-10-15T18:23:32.06Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/2b/24/b350c31543fb0107ab2599464d7e28e6f856027aadda995022e695313d94/pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b", size = 7142916, upload-time = "2025-10-15T18:23:34.71Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/0f/9b/0ba5a6fd9351793996ef7487c4fdbde8d3f5f75dbedc093bb598648fddf0/pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0", size = 2523836, upload-time = "2025-10-15T18:23:36.967Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/f5/7a/ceee0840aebc579af529b523d530840338ecf63992395842e54edc805987/pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6", size = 5255092, upload-time = "2025-10-15T18:23:38.573Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/44/76/20776057b4bfd1aef4eeca992ebde0f53a4dce874f3ae693d0ec90a4f79b/pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6", size = 4653158, upload-time = "2025-10-15T18:23:40.238Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/82/3f/d9ff92ace07be8836b4e7e87e6a4c7a8318d47c2f1463ffcf121fc57d9cb/pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1", size = 6267882, upload-time = "2025-10-15T18:23:42.434Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/9f/7a/4f7ff87f00d3ad33ba21af78bfcd2f032107710baf8280e3722ceec28cda/pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e", size = 8071001, upload-time = "2025-10-15T18:23:44.29Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/75/87/fcea108944a52dad8cca0715ae6247e271eb80459364a98518f1e4f480c1/pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca", size = 6380146, upload-time = "2025-10-15T18:23:46.065Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/91/52/0d31b5e571ef5fd111d2978b84603fce26aba1b6092f28e941cb46570745/pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925", size = 7067344, upload-time = "2025-10-15T18:23:47.898Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/7b/f4/2dd3d721f875f928d48e83bb30a434dee75a2531bca839bb996bb0aa5a91/pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8", size = 6491864, upload-time = "2025-10-15T18:23:49.607Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/30/4b/667dfcf3d61fc309ba5a15b141845cece5915e39b99c1ceab0f34bf1d124/pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4", size = 7158911, upload-time = "2025-10-15T18:23:51.351Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/a2/2f/16cabcc6426c32218ace36bf0d55955e813f2958afddbf1d391849fee9d1/pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52", size = 6408045, upload-time = "2025-10-15T18:23:53.177Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/35/73/e29aa0c9c666cf787628d3f0dcf379f4791fba79f4936d02f8b37165bdf8/pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a", size = 7148282, upload-time = "2025-10-15T18:23:55.316Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/c1/70/6b41bdcddf541b437bbb9f47f94d2db5d9ddef6c37ccab8c9107743748a4/pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7", size = 2525630, upload-time = "2025-10-15T18:23:57.149Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "platformdirs"
 | 
				
			||||||
 | 
					version = "4.5.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "pluggy"
 | 
				
			||||||
 | 
					version = "1.6.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "pre-commit"
 | 
				
			||||||
 | 
					version = "4.3.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					    { name = "cfgv" },
 | 
				
			||||||
 | 
					    { name = "identify" },
 | 
				
			||||||
 | 
					    { name = "nodeenv" },
 | 
				
			||||||
 | 
					    { name = "pyyaml" },
 | 
				
			||||||
 | 
					    { name = "virtualenv" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "pygments"
 | 
				
			||||||
 | 
					version = "2.19.2"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "pyjwt"
 | 
				
			||||||
 | 
					version = "2.10.1"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "pylint"
 | 
				
			||||||
 | 
					version = "4.0.2"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					    { name = "astroid" },
 | 
				
			||||||
 | 
					    { name = "colorama", marker = "sys_platform == 'win32'" },
 | 
				
			||||||
 | 
					    { name = "dill" },
 | 
				
			||||||
 | 
					    { name = "isort" },
 | 
				
			||||||
 | 
					    { name = "mccabe" },
 | 
				
			||||||
 | 
					    { name = "platformdirs" },
 | 
				
			||||||
 | 
					    { name = "tomlkit" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/b6/f8/2feda2bc72654811f2596e856c33c2a98225fba717df2b55c8d6a1f5cdad/pylint-4.0.2.tar.gz", hash = "sha256:9c22dfa52781d3b79ce86ab2463940f874921a3e5707bcfc98dd0c019945014e", size = 1572401, upload-time = "2025-10-20T13:02:34.975Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/1e/8b/2e814a255436fc6d604a60f1e8b8a186e05082aa3c0cabfd9330192496a2/pylint-4.0.2-py3-none-any.whl", hash = "sha256:9627ccd129893fb8ee8e8010261cb13485daca83e61a6f854a85528ee579502d", size = 536019, upload-time = "2025-10-20T13:02:32.778Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "pylint-exit"
 | 
				
			||||||
 | 
					version = "1.2.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/26/fb/4365157ab89cd442cca4714595466394d5ee328709ca1804a5c85be7ae32/pylint-exit-1.2.0.zip", hash = "sha256:b6ad02884c01c5560a5275079fe5a6c792afff90ecccf0c02513e1547ee280b0", size = 11093, upload-time = "2020-07-15T22:18:01.006Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/94/ed/5d45bbd42d5407250dd46ce1b9c098d612c3a9bb538858d09da2df77c961/pylint_exit-1.2.0-py2.py3-none-any.whl", hash = "sha256:65c9e7856e9058705a92d7c45628d604b2a4b8ee2b3c18a7303be77f9ed87cbe", size = 6340, upload-time = "2020-07-15T22:18:00.11Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "pyreadline3"
 | 
				
			||||||
 | 
					version = "3.5.4"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839, upload-time = "2024-09-19T02:40:10.062Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "pytest"
 | 
				
			||||||
 | 
					version = "8.4.2"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					    { name = "colorama", marker = "sys_platform == 'win32'" },
 | 
				
			||||||
 | 
					    { name = "iniconfig" },
 | 
				
			||||||
 | 
					    { name = "packaging" },
 | 
				
			||||||
 | 
					    { name = "pluggy" },
 | 
				
			||||||
 | 
					    { name = "pygments" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "pytokens"
 | 
				
			||||||
 | 
					version = "0.2.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/d4/c2/dbadcdddb412a267585459142bfd7cc241e6276db69339353ae6e241ab2b/pytokens-0.2.0.tar.gz", hash = "sha256:532d6421364e5869ea57a9523bf385f02586d4662acbcc0342afd69511b4dd43", size = 15368, upload-time = "2025-10-15T08:02:42.738Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/89/5a/c269ea6b348b6f2c32686635df89f32dbe05df1088dd4579302a6f8f99af/pytokens-0.2.0-py3-none-any.whl", hash = "sha256:74d4b318c67f4295c13782ddd9abcb7e297ec5630ad060eb90abf7ebbefe59f8", size = 12038, upload-time = "2025-10-15T08:02:41.694Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "pyyaml"
 | 
				
			||||||
 | 
					version = "6.0.3"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "requests"
 | 
				
			||||||
 | 
					version = "2.32.5"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					    { name = "certifi" },
 | 
				
			||||||
 | 
					    { name = "charset-normalizer" },
 | 
				
			||||||
 | 
					    { name = "idna" },
 | 
				
			||||||
 | 
					    { name = "urllib3" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "requests-toolbelt"
 | 
				
			||||||
 | 
					version = "1.0.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					    { name = "requests" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "tomlkit"
 | 
				
			||||||
 | 
					version = "0.13.3"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "urllib3"
 | 
				
			||||||
 | 
					version = "2.5.0"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "virtualenv"
 | 
				
			||||||
 | 
					version = "20.35.4"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					    { name = "distlib" },
 | 
				
			||||||
 | 
					    { name = "filelock" },
 | 
				
			||||||
 | 
					    { name = "platformdirs" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799, upload-time = "2025-10-29T06:57:40.511Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095, upload-time = "2025-10-29T06:57:37.598Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "webex-bot"
 | 
				
			||||||
 | 
					version = "1.0.8"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					    { name = "backoff" },
 | 
				
			||||||
 | 
					    { name = "coloredlogs" },
 | 
				
			||||||
 | 
					    { name = "webexpythonsdk" },
 | 
				
			||||||
 | 
					    { name = "websockets" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/5a/76/7a0c03bf856abd3294dd145e67e4f5e479ea0a0858ef5d1a1bb64e85e7e9/webex_bot-1.0.8.tar.gz", hash = "sha256:2139c0d011f58f12f9652a191a293148ba1cefe0d55431ccf47849ca1bee7904", size = 30713, upload-time = "2025-09-18T09:37:22.8Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/ac/ab/c028b6f7ce529e6f61550afdaa17525925fecd15f0c8e90d497294379d3c/webex_bot-1.0.8-py2.py3-none-any.whl", hash = "sha256:d0789dec6fbdb31a24b2d3120039329c17dcca7d0278321bf2cf8cacd305bb88", size = 23002, upload-time = "2025-09-18T09:37:21.545Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "webexmemebot"
 | 
				
			||||||
 | 
					version = "0.0.0"
 | 
				
			||||||
 | 
					source = { editable = "." }
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					    { name = "astroid" },
 | 
				
			||||||
 | 
					    { name = "pillow" },
 | 
				
			||||||
 | 
					    { name = "webex-bot" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[package.dev-dependencies]
 | 
				
			||||||
 | 
					dev = [
 | 
				
			||||||
 | 
					    { name = "black" },
 | 
				
			||||||
 | 
					    { name = "coverage" },
 | 
				
			||||||
 | 
					    { name = "isort" },
 | 
				
			||||||
 | 
					    { name = "pre-commit" },
 | 
				
			||||||
 | 
					    { name = "pylint" },
 | 
				
			||||||
 | 
					    { name = "pylint-exit" },
 | 
				
			||||||
 | 
					    { name = "pytest" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[package.metadata]
 | 
				
			||||||
 | 
					requires-dist = [
 | 
				
			||||||
 | 
					    { name = "astroid", specifier = "<=4.0.1" },
 | 
				
			||||||
 | 
					    { name = "pillow", specifier = ">=12.0.0,<12.0.1" },
 | 
				
			||||||
 | 
					    { name = "webex-bot", specifier = ">=1.0.3,<1.1.0" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[package.metadata.requires-dev]
 | 
				
			||||||
 | 
					dev = [
 | 
				
			||||||
 | 
					    { name = "black", specifier = ">=25.9.0,<25.9.1" },
 | 
				
			||||||
 | 
					    { name = "coverage", specifier = ">=7.6.10,<8.0.0" },
 | 
				
			||||||
 | 
					    { name = "isort", specifier = ">=7.0.0,<7.0.1" },
 | 
				
			||||||
 | 
					    { name = "pre-commit", specifier = ">=4.0.1,<5.0.0" },
 | 
				
			||||||
 | 
					    { name = "pylint", specifier = ">=4.0.0,<4.1.0" },
 | 
				
			||||||
 | 
					    { name = "pylint-exit", specifier = ">=1.2.0,<2.0.0" },
 | 
				
			||||||
 | 
					    { name = "pytest", specifier = ">=8.3.4,<9.0.0" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "webexpythonsdk"
 | 
				
			||||||
 | 
					version = "2.0.4"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					    { name = "pyjwt" },
 | 
				
			||||||
 | 
					    { name = "requests" },
 | 
				
			||||||
 | 
					    { name = "requests-toolbelt" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/84/11/1e4e50b36228c6f40d943adc3a46b94f20864a91784e51624ad12880abba/webexpythonsdk-2.0.4.tar.gz", hash = "sha256:8103193460bb9da51b7873654f4591fc265a336751b49f372fb3b584c440c538", size = 66886, upload-time = "2025-01-22T17:12:48.576Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/0f/a2/56c2848eb73965b70472e156650031f84ad8bc7a442b3c0c7a4846c04514/webexpythonsdk-2.0.4-py3-none-any.whl", hash = "sha256:ee8845dc79fc9b296a9e0080d1dffd9565a0116ca82b97796225057a7d22e285", size = 149107, upload-time = "2025-01-22T17:12:45.279Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "websockets"
 | 
				
			||||||
 | 
					version = "11.0.3"
 | 
				
			||||||
 | 
					source = { registry = "https://pypi.org/simple" }
 | 
				
			||||||
 | 
					sdist = { url = "https://files.pythonhosted.org/packages/d8/3b/2ed38e52eed4cf277f9df5f0463a99199a04d9e29c9e227cfafa57bd3993/websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016", size = 104235, upload-time = "2023-05-07T14:25:20.083Z" }
 | 
				
			||||||
 | 
					wheels = [
 | 
				
			||||||
 | 
					    { url = "https://files.pythonhosted.org/packages/47/96/9d5749106ff57629b54360664ae7eb9afd8302fad1680ead385383e33746/websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6", size = 118056, upload-time = "2023-05-07T14:25:18.508Z" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
		Reference in New Issue
	
	Block a user