├── .codecov.yml ├── .devcontainer.json ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug.md │ ├── config.yml │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── release-drafter.yml └── workflows │ ├── actions.yml │ ├── release-drafter.yml │ └── release.yml ├── .gitignore ├── LICENSE.md ├── Makefile ├── README.md ├── aiogithubapi ├── __init__.py ├── client.py ├── common │ ├── __init__.py │ ├── const.py │ └── exceptions.py ├── const.py ├── device.py ├── exceptions.py ├── github.py ├── graphql_examples │ ├── __init__.py │ └── repository_tags.py ├── helpers.py ├── legacy │ ├── __init__.py │ ├── client.py │ ├── device.py │ ├── github.py │ └── helpers.py ├── models │ ├── __init__.py │ ├── base.py │ ├── clones.py │ ├── commit.py │ ├── contents.py │ ├── device_login.py │ ├── events.py │ ├── git_tree.py │ ├── issue.py │ ├── issue_comment.py │ ├── label.py │ ├── license.py │ ├── login_oauth.py │ ├── meta.py │ ├── milestone.py │ ├── organization.py │ ├── owner.py │ ├── permissions.py │ ├── projects.py │ ├── pull_request.py │ ├── rate_limit.py │ ├── reaction.py │ ├── release.py │ ├── repository.py │ ├── request_data.py │ ├── response.py │ ├── tag.py │ ├── user.py │ └── views.py ├── namespaces │ ├── __init__.py │ ├── base.py │ ├── contents.py │ ├── events.py │ ├── git.py │ ├── issues.py │ ├── orgs.py │ ├── projects.py │ ├── pulls.py │ ├── releases.py │ ├── repos.py │ ├── traffic.py │ ├── user.py │ └── users.py └── objects │ ├── __init__.py │ ├── base.py │ ├── content.py │ ├── login │ ├── __init__.py │ ├── device.py │ └── oauth.py │ ├── orgs │ ├── __init__.py │ └── team.py │ ├── ratelimit.py │ ├── repos │ ├── __init__.py │ ├── branch.py │ ├── commit.py │ ├── fork.py │ ├── label.py │ └── traffic │ │ ├── __init__.py │ │ ├── clones.py │ │ └── pageviews.py │ ├── repository │ ├── __init__.py │ ├── collaborator.py │ ├── content.py │ ├── issue │ │ ├── __init__.py │ │ └── comment.py │ ├── pullrequest.py │ ├── release.py │ └── traffic.py │ └── users │ ├── __init__.py │ └── user.py ├── documentation.md ├── netlify.toml ├── poetry.lock ├── pyproject.toml ├── runtime.txt └── tests ├── __init__.py ├── client ├── test_construction.py └── test_operation.py ├── common.py ├── conftest.py ├── device ├── __init__.py ├── test_construction.py └── test_device_flow.py ├── fixtures ├── emojis.json ├── generic.json ├── generic.txt ├── graphql.json ├── headers.json ├── login_device_code.json ├── login_oauth_access_token.json ├── markdown.txt ├── meta.json ├── octocat.txt ├── organizations.json ├── orgs_octocat.json ├── orgs_octocat_projects.json ├── projects_1.json ├── rate_limit.json ├── repos_octocat_hello-world.json ├── repos_octocat_hello-world_commits.json ├── repos_octocat_hello-world_contents.json ├── repos_octocat_hello-world_contents_readme.json ├── repos_octocat_hello-world_events.json ├── repos_octocat_hello-world_git_trees_master.json ├── repos_octocat_hello-world_issues.json ├── repos_octocat_hello-world_issues_1_comments.json ├── repos_octocat_hello-world_projects.json ├── repos_octocat_hello-world_pulls.json ├── repos_octocat_hello-world_readme_.txt ├── repos_octocat_hello-world_readme_test.txt ├── repos_octocat_hello-world_releases.json ├── repos_octocat_hello-world_releases_latest.json ├── repos_octocat_hello-world_tags.json ├── repos_octocat_hello-world_traffic_clones.json ├── repos_octocat_hello-world_traffic_views.json ├── user.json ├── user_orgs.json ├── user_repos.json ├── user_starred.json ├── users_octocat.json ├── users_octocat_orgs.json ├── users_octocat_projects.json ├── users_octocat_repos.json ├── users_octocat_starred.json ├── versions.json └── zen.txt ├── github ├── __init__.py ├── test_construction.py ├── test_emojis.py ├── test_generic.py ├── test_graphql.py ├── test_markdown.py ├── test_meta.py ├── test_octocat.py ├── test_rate_limit.py ├── test_versions.py └── test_zen.py ├── legacy ├── client │ └── test_client.py ├── device │ └── test_device_login.py ├── fixtures │ ├── base │ │ ├── bad_authentication.json │ │ └── bad_response.json │ ├── base_response.json │ ├── oauth_access_token_error.json │ └── oauth_access_token_pending.json ├── github │ ├── test_get_org_repos.py │ ├── test_get_rate_limit.py │ ├── test_get_repo.py │ ├── test_graphql.py │ └── test_render_markdown.py ├── objects │ ├── login │ │ ├── test_device.py │ │ └── test_oauth.py │ ├── orgs │ │ └── test_team.py │ ├── repos │ │ ├── test_branch.py │ │ ├── test_commit.py │ │ ├── test_fork.py │ │ ├── test_label.py │ │ └── traffic │ │ │ ├── test_clones.py │ │ │ └── test_pageviews.py │ ├── repository │ │ ├── test_collaborator.py │ │ ├── test_content.py │ │ ├── test_issue.py │ │ ├── test_pullrequest.py │ │ ├── test_release.py │ │ ├── test_repository.py │ │ └── test_traffic.py │ ├── test_ratelimit.py │ └── users │ │ └── test_user.py ├── responses │ ├── __init__.py │ ├── base.py │ ├── branch.py │ ├── commit.py │ ├── contents.py │ ├── issue_comments.py │ ├── issue_fixture.py │ ├── login │ │ ├── device_fixtrue.py │ │ └── oauth_fixtrue.py │ ├── oauth │ │ └── device │ │ │ └── flow │ │ │ └── login_fixtrue.py │ ├── org_repositories.py │ ├── orgs │ │ └── team_fixtrue.py │ ├── rate_limit.py │ ├── releases.py │ ├── repos │ │ ├── branch_fixtrue.py │ │ ├── commit_fixtrue.py │ │ ├── fork_fixtrue.py │ │ ├── label_fixtrue.py │ │ └── traffic │ │ │ ├── clones_fixtrue.py │ │ │ └── pageviews_fixtrue.py │ ├── repository │ │ ├── collaborator_fixtrue.py │ │ ├── commit_fixtrue.py │ │ └── pullrequest_fixtrue.py │ ├── repository_fixture.py │ ├── tree.py │ └── users │ │ └── user_fixtrue.py └── test_helpers.py ├── namespaces ├── __init__.py ├── test_contents.py ├── test_events.py ├── test_git.py ├── test_issues.py ├── test_orgs.py ├── test_projects.py ├── test_pulls.py ├── test_releases.py ├── test_repos.py ├── test_traffic.py ├── test_user.py └── test_users.py ├── test_const.py └── test_helper.py /.codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | codecov: 3 | branch: main 4 | 5 | coverage: 6 | precision: 2 7 | status: 8 | patch: off 9 | project: 10 | default: 11 | target: 100% 12 | 13 | parsers: 14 | gcov: 15 | branch_detection: 16 | conditional: yes 17 | loop: yes 18 | method: no 19 | macro: no 20 | 21 | ignore: 22 | - "tests" -------------------------------------------------------------------------------- /.devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ludeeus/aiogithubapi", 3 | "image": "mcr.microsoft.com/vscode/devcontainers/python:0-3.11-bullseye", 4 | "postCreateCommand": "make requirements", 5 | "customizations": { 6 | "extensions": [ 7 | "ms-python.python", 8 | "github.vscode-pull-request-github", 9 | "ryanluker.vscode-coverage-gutters", 10 | "ms-python.vscode-pylance" 11 | ], 12 | "settings": { 13 | "files.eol": "\n", 14 | "editor.tabSize": 4, 15 | "terminal.integrated.shell.linux": "/bin/bash", 16 | "python.pythonPath": "/usr/local/python/bin/python", 17 | "python.analysis.autoSearchPaths": false, 18 | "python.linting.pylintEnabled": true, 19 | "python.linting.enabled": true, 20 | "python.formatting.provider": "black", 21 | "editor.formatOnPaste": false, 22 | "editor.formatOnSave": true, 23 | "editor.formatOnType": true, 24 | "files.trimTrailingWhitespace": true 25 | } 26 | }, 27 | "remoteUser": "vscode" 28 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [ludeeus] -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Report a bug with aiogithubapi 3 | about: Report an issue with aiogithubapi 4 | labels: bug 5 | --- 6 | ## The problem 7 | 11 | 12 | 13 | ## Environment 14 | 15 | - Operating system: 16 | - Python version: 17 | 18 | ## Problem-relevant code 19 | 20 | 21 | ```python 22 | 23 | ``` 24 | 25 | ## Traceback/Error logs 26 | 29 | 30 | ```txt 31 | 32 | ``` 33 | 34 | ## Additional information 35 | 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request for aiogithubapi 3 | about: Suggest a feature/change to be added to aiogithubapi 4 | labels: enhancement 5 | --- 6 | ## The idea 7 | 8 | 9 | 10 | 11 | ## Implementation 12 | 13 | 14 | 15 | ## Alternatives 16 | 17 | 18 | 19 | ## Additional context 20 | 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ## Proposed change 7 | 8 | 14 | 15 | ## Type of change 16 | 17 | 23 | 24 | - [ ] Dependency upgrade 25 | - [ ] Bugfix (non-breaking change which fixes an issue) 26 | - [ ] New feature (which adds functionalit) 27 | - [ ] Breaking change (fix/feature causing existing functionality to break) 28 | - [ ] Code quality improvements to existing code or addition of tests 29 | 30 | ## Additional information 31 | 32 | 36 | 37 | - This PR fixes or closes issue: fixes # 38 | - This PR is related to issue: 39 | 40 | ## Checklist 41 | 42 | 48 | 49 | - [ ] The code change is tested and works locally. 50 | - [ ] Local tests pass. 51 | - [ ] There is no commented out code in this PR. 52 | - [ ] The code has been formatted using Black (`make lint`) 53 | - [ ] Tests have been added to verify that the new code works. 54 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "06:00" 8 | open-pull-requests-limit: 10 9 | versioning-strategy: increase-if-necessary 10 | - package-ecosystem: "github-actions" 11 | directory: "/" 12 | schedule: 13 | interval: daily 14 | time: "06:00" 15 | open-pull-requests-limit: 10 -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | change-template: '- #$NUMBER $TITLE @$AUTHOR' 2 | sort-direction: ascending 3 | exclude-labels: 4 | - "release-drafter-ignore" 5 | categories: 6 | - title: "Dependency Updates" 7 | label: "dependencies" 8 | collapse-after: 1 9 | 10 | template: | 11 | ## What’s Changed 12 | $CHANGES -------------------------------------------------------------------------------- /.github/workflows/actions.yml: -------------------------------------------------------------------------------- 1 | name: Actions 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | 11 | concurrency: 12 | group: '${{ github.workflow }}-${{ github.ref }}' 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | lint: 17 | name: Lint 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: 📥 Checkout the repository 21 | uses: actions/checkout@v4.2.2 22 | 23 | - name: 🛠 Set up Python 3 24 | uses: actions/setup-python@v5.6.0 25 | id: python 26 | with: 27 | python-version: "3.11" 28 | 29 | - name: 📦 Install dependencies 30 | run: make requirements 31 | 32 | - name: 🖤 Lint with Black 33 | run: make black-check 34 | 35 | - name: 🔤 Lint with isort 36 | run: make isort-check 37 | 38 | test: 39 | name: Test with Python ${{ matrix.python.version }} 40 | needs: lint 41 | runs-on: ubuntu-latest 42 | strategy: 43 | matrix: 44 | python: 45 | - version: "3.9" 46 | - version: "3.10" 47 | - version: "3.11" 48 | - version: "3.12" 49 | steps: 50 | - name: 📥 Checkout the repository 51 | uses: actions/checkout@v4.2.2 52 | with: 53 | fetch-depth: 0 54 | 55 | - name: 🛠️ Set up Python ${{ matrix.python.version }} 56 | uses: actions/setup-python@v5.6.0 57 | with: 58 | python-version: ${{ matrix.python.version }} 59 | allow-prereleases: ${{ matrix.python.prereleases || false }} 60 | 61 | - name: 📦 Install dependencies 62 | run: make requirements 63 | 64 | - name: 🏃 Run tests 65 | run: make test 66 | 67 | - name: 🛠 Build 68 | run: make build 69 | 70 | coverage: 71 | name: Coverage 72 | needs: test 73 | runs-on: ubuntu-latest 74 | steps: 75 | - name: 📥 Checkout the repository 76 | uses: actions/checkout@v4.2.2 77 | with: 78 | fetch-depth: 0 79 | 80 | - name: 🛠 Set up Python 3.10 81 | uses: actions/setup-python@v5.6.0 82 | id: python 83 | with: 84 | python-version: "3.10" 85 | 86 | - name: 📦 Install dependencies 87 | run: make requirements 88 | 89 | - name: 📤 Upload coverage to Codecov 90 | run: | 91 | make coverage 92 | curl -sfSL https://codecov.io/bash | bash - -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release-drafter: 10 | name: Release Drafter 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: 📥 Checkout the repository 14 | uses: actions/checkout@v4.2.2 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: ⏭️ Get next version 19 | id: version 20 | run: | 21 | declare -i newpost 22 | latest=$(git describe --tags $(git rev-list --tags --max-count=1)) 23 | latestpre=$(echo "$latest" | awk '{split($0,a,"."); print a[1] "." a[2]}') 24 | datepre=$(date --utc '+%y.%-m') 25 | if [[ "$latestpre" == "$datepre" ]]; then 26 | latestpost=$(echo "$latest" | awk '{split($0,a,"."); print a[3]}') 27 | newpost=$latestpost+1 28 | else 29 | newpost=0 30 | fi 31 | echo Current version: $latest 32 | echo New target version: $datepre.$newpost 33 | echo "::set-output name=version::$datepre.$newpost" 34 | 35 | - name: 🏃 Run Release Drafter 36 | uses: release-drafter/release-drafter@v6.1.0 37 | with: 38 | tag: ${{ steps.version.outputs.version }} 39 | name: ${{ steps.version.outputs.version }} 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Release 3 | 4 | on: 5 | release: 6 | types: 7 | - published 8 | 9 | permissions: {} 10 | 11 | jobs: 12 | deploy: 13 | runs-on: ubuntu-latest 14 | name: Deploy to PyPi 15 | environment: 16 | name: release 17 | url: https://pypi.org/project/aiogithubapi 18 | permissions: 19 | # Used to authenticate to PyPI via OIDC. 20 | id-token: write 21 | steps: 22 | - name: 📥 Checkout the repository 23 | uses: actions/checkout@v4.2.2 24 | 25 | - name: 🛠 Set up Python 26 | uses: actions/setup-python@v5.6.0 27 | id: python 28 | with: 29 | python-version: "3.11" 30 | 31 | - name: 📦 Install poetry 32 | run: make install-poetry 33 | 34 | - name: 🔢 Set version number 35 | run: poetry version "${{ github.event.release.tag_name }}" 36 | 37 | - name: 🛠 Build 38 | run: poetry build --no-interaction 39 | 40 | - name: 🚀 Publish to PyPi 41 | uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *egg* 2 | __pycache__ 3 | __target__ 4 | .coverage 5 | coverage.xml 6 | .vscode 7 | htmlcov 8 | build 9 | dist 10 | docs 11 | example.py -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2022 Joakim Sørensen @ludeeus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := help 2 | 3 | help: ## Shows this help message 4 | @printf "\033[1m%s\033[36m %s\033[32m %s\033[0m \n\n" "Development environment for" "ludeeus/aiogithubapi" ""; 5 | @awk 'BEGIN {FS = ":.*##";} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m make %-25s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST); 6 | @echo 7 | 8 | requirements: install-poetry ## Install requirements 9 | @poetry install --extras "deprecated-verify" 10 | @poetry check 11 | 12 | install: ## Install aiogithubapi 13 | @poetry install --extras "deprecated-verify" 14 | 15 | install-poetry: 16 | @curl -sSL https://install.python-poetry.org | python3 - 17 | 18 | build: ## Build the package 19 | @poetry build 20 | 21 | test: ## Run all tests 22 | @poetry run pytest tests -rxf -x -v -l --cov=./ --cov-report=xml 23 | 24 | lint: isort black ## Lint all files 25 | 26 | coverage: ## Check the coverage of the package 27 | @poetry run pytest tests -rxf -x -v -l --cov=./ --cov-report=xml > /dev/null 28 | @poetry run coverage report --skip-covered 29 | 30 | isort: 31 | @poetry run isort aiogithubapi 32 | 33 | isort-check: 34 | @poetry run isort aiogithubapi --check-only 35 | 36 | black: 37 | @poetry run black --fast aiogithubapi 38 | 39 | example: 40 | @poetry run python example.py 41 | 42 | black-check: 43 | @poetry run black --check --fast aiogithubapi 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aiogithubapi 2 | 3 | [![codecov](https://codecov.io/gh/ludeeus/aiogithubapi/branch/main/graph/badge.svg)](https://codecov.io/gh/ludeeus/aiogithubapi) 4 | ![python version](https://img.shields.io/badge/Python-3.9=><=3.12-blue.svg) 5 | [![PyPI](https://img.shields.io/pypi/v/aiogithubapi)](https://pypi.org/project/aiogithubapi) 6 | ![Actions](https://github.com/ludeeus/aiogithubapi/workflows/Actions/badge.svg?branch=main) 7 | 8 | _Asynchronous Python client for the GitHub API_ 9 | 10 | This is not a full client for the API (Have you seen it, it's huge), and will probably never be. 11 | Things are added when needed or requested. 12 | 13 | If something you need is missing please raise [a feature request to have it added](https://github.com/ludeeus/aiogithubapi/issues/new?assignees=&labels=enhancement&template=feature_request.md) or [create a PR 🎉](#contribute). 14 | 15 | For examples on how to use it see the [tests directory](./tests). 16 | 17 | ## Install 18 | 19 | ```bash 20 | python3 -m pip install aiogithubapi 21 | ``` 22 | 23 | ## Project transition 24 | 25 | **Note: This project is currently in a transition phase.** 26 | 27 | In august 2021 a new API interface was introduced (in [#42](https://github.com/ludeeus/aiogithubapi/pull/42)). With that addition, all parts of the old interface is now considered deprecated. 28 | Which includes: 29 | 30 | - The [`aiogithubapi.common`](./aiogithubapi/common) module 31 | - The [`aiogithubapi.legacy`](./aiogithubapi/legacy) module 32 | - The [`aiogithubapi.objects`](./aiogithubapi/objects) module 33 | - All classes starting with `AIOGitHub` 34 | - The `async_call_api` function in the [`aiogithubapi/helpers.py`](./aiogithubapi/helpers.py) file 35 | - The `sigstore_verify_release_asset` function in the [`aiogithubapi/helpers.py`](./aiogithubapi/helpers.py) file 36 | - The `GitHubDevice` class in `aiogithubapi`, replaced with `GitHubDeviceAPI` 37 | - The `GitHub` class in `aiogithubapi`, replaced with `GitHubAPI` 38 | 39 | Later this year (2024), warning logs will start to be emitted for deprecated code. 40 | 41 | Early next year (2025), the old code will be removed. 42 | 43 | ## Contribute 44 | 45 | **All** contributions are welcome! 46 | 47 | 1. Fork the repository 48 | 2. Clone the repository locally and open the devcontainer or use GitHub codespaces 49 | 3. Do your changes 50 | 4. Lint the files with `make lint` 51 | 5. Ensure all tests passes with `make test` 52 | 6. Ensure 100% coverage with `make coverage` 53 | 7. Commit your work, and push it to GitHub 54 | 8. Create a PR against the `main` branch 55 | -------------------------------------------------------------------------------- /aiogithubapi/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Asynchronous Python client for the GitHub API https://github.com/ludeeus/aiogithubapi 3 | 4 | .. include:: ../documentation.md 5 | """ 6 | 7 | from .common.exceptions import ( 8 | AIOGitHubAPIAuthenticationException, 9 | AIOGitHubAPIException, 10 | AIOGitHubAPINotModifiedException, 11 | AIOGitHubAPIRatelimitException, 12 | ) 13 | from .const import ( 14 | DeviceFlowError, 15 | GitHubClientKwarg, 16 | GitHubIssueLockReason, 17 | GitHubRequestKwarg, 18 | HttpStatusCode, 19 | Repository, 20 | ) 21 | from .device import GitHubDeviceAPI 22 | from .exceptions import ( 23 | GitHubAuthenticationException, 24 | GitHubConnectionException, 25 | GitHubException, 26 | GitHubGraphQLException, 27 | GitHubNotFoundException, 28 | GitHubNotModifiedException, 29 | GitHubPayloadException, 30 | GitHubPermissionException, 31 | GitHubRatelimitException, 32 | ) 33 | from .github import GitHub as GitHubAPI 34 | from .legacy.device import AIOGitHubAPIDeviceLogin as GitHubDevice 35 | from .legacy.github import AIOGitHubAPI as GitHub 36 | from .models.base import GitHubBase 37 | from .models.clones import GitHubClonesModel 38 | from .models.commit import GitHubCommitModel 39 | from .models.contents import GitHubContentsModel 40 | from .models.device_login import GitHubLoginDeviceModel 41 | from .models.events import GitHubEventModel 42 | from .models.git_tree import GitHubGitTreeModel 43 | from .models.issue import GitHubIssueModel 44 | from .models.issue_comment import GitHubIssueCommentModel 45 | from .models.label import GitHubLabelModel 46 | from .models.license import GitHubLicenseModel 47 | from .models.login_oauth import GitHubLoginOauthModel 48 | from .models.meta import GitHubMetaModel 49 | from .models.milestone import GitHubMilestoneModel 50 | from .models.organization import GitHubOrganizationMinimalModel, GitHubOrganizationModel 51 | from .models.owner import GitHubOwnerModel 52 | from .models.permissions import GitHubPermissionsModel 53 | from .models.projects import GitHubProjectModel 54 | from .models.pull_request import GitHubPullRequestModel 55 | from .models.rate_limit import ( 56 | GitHubRateLimitModel, 57 | GitHubRateLimitResourceModel, 58 | GitHubRateLimitResourcesModel, 59 | ) 60 | from .models.reaction import GitHubReactionModel 61 | from .models.release import GitHubReleaseAssetModel, GitHubReleaseModel 62 | from .models.repository import GitHubRepositoryModel 63 | from .models.request_data import GitHubBaseRequestDataModel 64 | from .models.response import GitHubResponseHeadersModel, GitHubResponseModel 65 | from .models.tag import GitHubTagModel 66 | from .models.user import ( 67 | GitHubAuthenticatedUserModel, 68 | GitHubBaseUserModel, 69 | GitHubUserModel, 70 | GitHubUserPlanModel, 71 | ) 72 | from .models.views import GitHubViewsModel 73 | -------------------------------------------------------------------------------- /aiogithubapi/common/__init__.py: -------------------------------------------------------------------------------- 1 | """Common elements.""" 2 | -------------------------------------------------------------------------------- /aiogithubapi/common/const.py: -------------------------------------------------------------------------------- 1 | """AIOGitHubAPI: Constants""" 2 | 3 | from ..const import * 4 | 5 | OAUTH_DEVICE_LOGIN = "https://github.com/login/device/code" 6 | OAUTH_ACCESS_TOKEN = "https://github.com/login/oauth/access_token" 7 | -------------------------------------------------------------------------------- /aiogithubapi/common/exceptions.py: -------------------------------------------------------------------------------- 1 | """AIOGitHubAPI: Exceptions""" 2 | 3 | from ..exceptions import ( 4 | GitHubAuthenticationException, 5 | GitHubException, 6 | GitHubNotModifiedException, 7 | GitHubRatelimitException, 8 | ) 9 | 10 | 11 | class AIOGitHubAPIException(GitHubException): 12 | """ 13 | Raise this when something is off. 14 | 15 | Deprecated: use `aiogithubapi.exceptions.GitHubException` instead 16 | """ 17 | 18 | 19 | class AIOGitHubAPIRatelimitException(GitHubRatelimitException, AIOGitHubAPIException): 20 | """ 21 | Raise this when we hit the ratelimit. 22 | 23 | Deprecated: use `aiogithubapi.exceptions.GitHubRatelimitException` instead 24 | """ 25 | 26 | 27 | class AIOGitHubAPINotModifiedException(GitHubNotModifiedException, AIOGitHubAPIException): 28 | """ 29 | Raise this when we the content was not modified. 30 | 31 | Deprecated: use `aiogithubapi.exceptions.GitHubNotModifiedException` instead 32 | """ 33 | 34 | 35 | class AIOGitHubAPIAuthenticationException(GitHubAuthenticationException, AIOGitHubAPIException): 36 | """ 37 | Raise this when there is an authentication issue. 38 | 39 | Deprecated: use `aiogithubapi.exceptions.GitHubAuthenticationException` instead 40 | """ 41 | -------------------------------------------------------------------------------- /aiogithubapi/exceptions.py: -------------------------------------------------------------------------------- 1 | """Custom exceptions for aiogithubapi.""" 2 | 3 | 4 | class GitHubException(Exception): 5 | """ 6 | This is raised when unknown exceptions occur. 7 | 8 | And it's used as a base for all other exceptions 9 | so if you want to catch all GitHub related errors 10 | you should catch this base exception. 11 | """ 12 | 13 | 14 | class GitHubConnectionException(GitHubException): 15 | """This is raised when there is a connection issue with GitHub.""" 16 | 17 | 18 | class GitHubRatelimitException(GitHubException): 19 | """This is raised when the ratelimit is reached.""" 20 | 21 | 22 | class GitHubNotFoundException(GitHubException): 23 | """This is raised when the requested resource is not found.""" 24 | 25 | 26 | class GitHubPayloadException(GitHubException): 27 | """This is raised when the payload is invalid.""" 28 | 29 | 30 | class GitHubGraphQLException(GitHubException): 31 | """This is raised when the response from GraphQL calls have errors.""" 32 | 33 | 34 | class GitHubPermissionException(GitHubException): 35 | """This is raised when the user has no permission to do the requested resource.""" 36 | 37 | 38 | class GitHubNotModifiedException(GitHubException): 39 | """This is raised when the providede ETag matches and the content has not been modified.""" 40 | 41 | 42 | class GitHubAuthenticationException(GitHubException): 43 | """This is raised when we receive an authentication issue.""" 44 | -------------------------------------------------------------------------------- /aiogithubapi/graphql_examples/__init__.py: -------------------------------------------------------------------------------- 1 | """Example GraphQL queries""" 2 | -------------------------------------------------------------------------------- /aiogithubapi/graphql_examples/repository_tags.py: -------------------------------------------------------------------------------- 1 | """Example GrapQL Query to get repository tags.""" 2 | 3 | EXAMPLE_QUERY = """ 4 | query ($owner: String!, $repository: String!, $number_of_tags: Int = 1) { 5 | repository(owner: $owner, name: $repository) { 6 | refs( 7 | refPrefix: "refs/tags/" 8 | first: $number_of_tags 9 | orderBy: {field: TAG_COMMIT_DATE, direction: DESC} 10 | ) { 11 | tags: nodes { 12 | name 13 | } 14 | } 15 | } 16 | } 17 | """ 18 | -------------------------------------------------------------------------------- /aiogithubapi/helpers.py: -------------------------------------------------------------------------------- 1 | """Helpers for AIOGitHubAPI.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING, Optional 6 | 7 | import aiohttp 8 | 9 | from .const import HttpMethod, Repository, RepositoryType 10 | from .legacy.helpers import ( 11 | async_call_api as legacy_async_call_api, 12 | short_message, 13 | short_sha, 14 | ) 15 | from .objects.base import AIOGitHubAPIResponse 16 | 17 | if TYPE_CHECKING: 18 | from sigstore.verify import VerificationResult 19 | 20 | 21 | def repository_full_name(repository: RepositoryType) -> str: 22 | """Return the repository name.""" 23 | if isinstance(repository, str): 24 | return repository 25 | if isinstance(repository, Repository): 26 | return repository.full_name 27 | return f"{repository['owner']}/{repository['repo']}" 28 | 29 | 30 | async def async_call_api( 31 | session: aiohttp.ClientSession, 32 | method: HttpMethod, 33 | url: str, 34 | headers: dict, 35 | params: Optional[dict] = None, 36 | data: dict or str or None = None, 37 | jsondata: bool = True, 38 | returnjson: bool = True, 39 | ) -> AIOGitHubAPIResponse: 40 | """Deprecated: Execute the API call.""" 41 | return await legacy_async_call_api( 42 | session, method, url, headers, params, data, jsondata, returnjson 43 | ) 44 | 45 | 46 | def sigstore_verify_release_asset( 47 | asset: bytes, 48 | signature_bundle: bytes, 49 | repository: str, 50 | workflow: str, 51 | tag: str, 52 | *, 53 | workflow_name: str | None = None, 54 | workflow_trigger: str | None = None, 55 | offline_verification: bool = False, 56 | **kwargs, 57 | ) -> VerificationResult: 58 | """Verify release asset. 59 | 60 | Deprecated function, a replacement will not be added. 61 | """ 62 | from warnings import warn # noqa 63 | 64 | warn( 65 | "The 'sigstore_verify_release_asset' function is deprecated and will be removed in a future version.", 66 | stacklevel=2, 67 | ) 68 | from io import BytesIO # noqa 69 | 70 | from sigstore.verify import VerificationMaterials, Verifier, models, policy # noqa 71 | 72 | verifier = Verifier.production() 73 | policies = [ 74 | policy.Identity( 75 | identity=f"https://github.com/{repository}/.github/workflows/{workflow}@refs/tags/{tag}", 76 | issuer="https://token.actions.githubusercontent.com", 77 | ), 78 | policy.GitHubWorkflowRepository(repository), 79 | policy.GitHubWorkflowRef(f"refs/tags/{tag}"), 80 | ] 81 | if workflow_trigger: 82 | policies.append(policy.GitHubWorkflowTrigger(workflow_trigger)) 83 | if workflow_name: 84 | policies.append(policy.GitHubWorkflowName(workflow_name)) 85 | 86 | return verifier.verify( 87 | VerificationMaterials.from_bundle( 88 | input_=BytesIO(asset), 89 | bundle=models.Bundle().from_json(signature_bundle), 90 | offline=offline_verification, 91 | ), 92 | policy=policy.AllOf(policies), 93 | ) 94 | -------------------------------------------------------------------------------- /aiogithubapi/legacy/__init__.py: -------------------------------------------------------------------------------- 1 | """Initialise the legacy module, everything here is considered deprecated.""" 2 | -------------------------------------------------------------------------------- /aiogithubapi/legacy/client.py: -------------------------------------------------------------------------------- 1 | """ 2 | AIOGitHubAPI: AioGitHubClient 3 | 4 | This is the class that do the requests against the API 5 | It also keeps track of ratelimits 6 | """ 7 | 8 | # pylint: disable=redefined-builtin, too-many-arguments 9 | from __future__ import annotations 10 | 11 | from typing import Optional 12 | 13 | import aiohttp 14 | 15 | from ..common.const import BASE_API_HEADERS, BASE_API_URL 16 | from ..helpers import async_call_api 17 | from ..objects.base import AIOGitHubAPIBase, AIOGitHubAPIResponse 18 | from ..objects.ratelimit import AIOGitHubAPIRateLimit 19 | 20 | 21 | class AIOGitHubAPIClient(AIOGitHubAPIBase): 22 | """ 23 | Client to handle API calls. 24 | 25 | Deprecated: Used by old versions of the library. 26 | """ 27 | 28 | def __init__( 29 | self, 30 | session: aiohttp.ClientSession, 31 | token: str, 32 | headers: Optional[dict] = None, 33 | base_url: Optional[str] = None, 34 | ) -> None: 35 | """Initialize the API client.""" 36 | self.session = session 37 | self.base_url = base_url or BASE_API_URL 38 | self.last_response: Optional[AIOGitHubAPIResponse] = None 39 | self.token = token 40 | self.ratelimits = AIOGitHubAPIRateLimit() 41 | self.headers = {} 42 | self.headers.update(BASE_API_HEADERS) 43 | self.headers.update(headers or {}) 44 | 45 | if token is not None: 46 | self.headers["Authorization"] = "token {}".format(token) 47 | 48 | async def get( 49 | self, 50 | endpoint: str, 51 | returnjson: bool = True, 52 | headers: dict or None = None, 53 | params: dict or None = None, 54 | ) -> AIOGitHubAPIResponse: 55 | """Execute a GET request.""" 56 | url = f"{self.base_url}{endpoint}" 57 | _headers = {} 58 | _headers.update(self.headers) 59 | _headers.update(headers or {}) 60 | 61 | response = await async_call_api( 62 | session=self.session, 63 | method="GET", 64 | url=url, 65 | returnjson=returnjson, 66 | headers=_headers, 67 | params=params, 68 | ) 69 | self.ratelimits.load_from_response_headers(response.headers) 70 | self.last_response = response 71 | return response 72 | 73 | async def post( 74 | self, 75 | endpoint: str, 76 | returnjson: bool = False, 77 | headers: dict or None = None, 78 | params: dict or None = None, 79 | data: dict or str or None = None, 80 | jsondata: bool = False, 81 | ) -> AIOGitHubAPIResponse: 82 | """Execute a POST request.""" 83 | url = f"{self.base_url}{endpoint}" 84 | _headers = {} 85 | _headers.update(self.headers) 86 | _headers.update(headers or {}) 87 | 88 | response = await async_call_api( 89 | session=self.session, 90 | method="POST", 91 | url=url, 92 | returnjson=returnjson, 93 | headers=_headers, 94 | params=params, 95 | data=data, 96 | jsondata=jsondata, 97 | ) 98 | self.ratelimits.load_from_response_headers(response.headers) 99 | self.last_response = response 100 | return response 101 | -------------------------------------------------------------------------------- /aiogithubapi/legacy/helpers.py: -------------------------------------------------------------------------------- 1 | """Helpers for AIOGitHubAPI.""" 2 | 3 | from asyncio import CancelledError, TimeoutError 4 | from typing import Optional 5 | 6 | import aiohttp 7 | from aiohttp.client_exceptions import ClientError 8 | import async_timeout 9 | import backoff 10 | 11 | from ..common.const import HTTP_STATUS_CODE_GOOD_LIST, HttpMethod, HttpStatusCode 12 | from ..common.exceptions import ( 13 | AIOGitHubAPIAuthenticationException, 14 | AIOGitHubAPIException, 15 | AIOGitHubAPINotModifiedException, 16 | AIOGitHubAPIRatelimitException, 17 | ) 18 | from ..objects.base import AIOGitHubAPIResponse 19 | 20 | 21 | def short_sha(sha: str) -> str: 22 | """Return the first 7 characters of the sha.""" 23 | return sha[0:7] 24 | 25 | 26 | def short_message(message: str) -> str: 27 | """Return the first line of a message""" 28 | return message.split("\n")[0] 29 | 30 | 31 | @backoff.on_exception( 32 | backoff.expo, 33 | (ClientError, CancelledError, TimeoutError, KeyError), 34 | max_tries=5, 35 | logger=None, 36 | ) 37 | async def async_call_api( 38 | session: aiohttp.ClientSession, 39 | method: HttpMethod, 40 | url: str, 41 | headers: dict, 42 | params: Optional[dict] = None, 43 | data: dict or str or None = None, 44 | jsondata: bool = True, 45 | returnjson: bool = True, 46 | ) -> AIOGitHubAPIResponse: 47 | """Execute the API call.""" 48 | response = AIOGitHubAPIResponse() 49 | 50 | async with async_timeout.timeout(20): 51 | if method == HttpMethod.GET: 52 | result = await session.get(url, params=params or {}, headers=headers) 53 | else: 54 | if jsondata: 55 | result = await session.post( 56 | url, 57 | params=params or {}, 58 | headers=headers, 59 | json=data or {}, 60 | ) 61 | else: 62 | result = await session.post( 63 | url, 64 | params=params or {}, 65 | headers=headers, 66 | data=data or "", 67 | ) 68 | 69 | response.status = result.status 70 | response.headers = result.headers 71 | 72 | if response.status == HttpStatusCode.RATELIMIT: 73 | raise AIOGitHubAPIRatelimitException("GitHub Ratelimit error") 74 | 75 | if response.status == HttpStatusCode.UNAUTHORIZED: 76 | raise AIOGitHubAPIAuthenticationException(HttpStatusCode.UNAUTHORIZED) 77 | 78 | if response.status == HttpStatusCode.NOT_MODIFIED: 79 | raise AIOGitHubAPINotModifiedException(f"Response from {url} was not modified.") 80 | 81 | if response.status not in HTTP_STATUS_CODE_GOOD_LIST: 82 | raise AIOGitHubAPIException( 83 | f"GitHub returned {HttpStatusCode(response.status)} for {url}" 84 | ) 85 | 86 | if returnjson: 87 | response.data = await result.json() 88 | else: 89 | response.data = await result.text() 90 | 91 | if isinstance(response.data, dict): 92 | if response.data.get("message"): 93 | if response.data["message"] == "Bad credentials": 94 | raise AIOGitHubAPIAuthenticationException("Access token is not valid!") 95 | raise AIOGitHubAPIException(response.data) 96 | 97 | return response 98 | -------------------------------------------------------------------------------- /aiogithubapi/models/__init__.py: -------------------------------------------------------------------------------- 1 | """Initialise aiogithubapi models.""" 2 | -------------------------------------------------------------------------------- /aiogithubapi/models/base.py: -------------------------------------------------------------------------------- 1 | """Base class for all GitHub objects.""" 2 | 3 | from __future__ import annotations 4 | 5 | from logging import Logger 6 | from typing import Any, Dict 7 | 8 | from ..const import LOGGER 9 | 10 | IGNORE_KEYS = ("node_id", "performed_via_github_app", "_links") 11 | 12 | 13 | class GitHubBase: 14 | """Base class for all GitHub objects.""" 15 | 16 | logger: Logger = LOGGER 17 | 18 | @staticmethod 19 | def slugify(value: str) -> str: 20 | """Slugify.""" 21 | return str(value).replace("-", "_").lower() 22 | 23 | 24 | class GitHubDataModelBase(GitHubBase): 25 | """Base class for all GitHub data objects.""" 26 | 27 | _raw_data: Any | None = None 28 | _log_missing: bool = True 29 | _process_data: bool = True 30 | _slugify_keys: bool = True 31 | 32 | def __init__(self, data: Dict[str, Any]) -> None: 33 | """Init.""" 34 | self._raw_data = data 35 | if self._process_data: 36 | for key, value in self._raw_data.items(): 37 | if self._slugify_keys: 38 | key = self.slugify(key) 39 | if hasattr(self, key): 40 | if handler := getattr(self, f"_generate_{key}", None): 41 | value = handler(value) 42 | self.__setattr__(key, value) 43 | elif self._log_missing and key not in IGNORE_KEYS: 44 | self.logger.debug( 45 | "'%s' is missing key '%s' for %s", 46 | self.__class__.__name__, 47 | key, 48 | type(value), 49 | ) 50 | self.__post_init__() 51 | 52 | def __post_init__(self): 53 | """Post init.""" 54 | 55 | @property 56 | def as_dict(self) -> Dict[str, Any]: 57 | """Return attributes as a dict.""" 58 | 59 | def expand_value_if_needed(value: Any) -> Any: 60 | if isinstance(value, GitHubDataModelBase): 61 | return value.as_dict 62 | if isinstance(value, list): 63 | return [expand_value_if_needed(v) for v in value] 64 | return value 65 | 66 | return { 67 | key: expand_value_if_needed(value) 68 | for key, value in self.__dict__.items() 69 | if not key.startswith("_") 70 | } 71 | -------------------------------------------------------------------------------- /aiogithubapi/models/clones.py: -------------------------------------------------------------------------------- 1 | """GitHub clones data class.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import Any, Dict 6 | 7 | from .base import GitHubDataModelBase 8 | 9 | 10 | class _Clone(GitHubDataModelBase): 11 | """GitHub clone data.""" 12 | 13 | timestamp: str | None = None 14 | count: int | None = None 15 | uniques: int | None = None 16 | 17 | 18 | class GitHubClonesModel(GitHubDataModelBase): 19 | """GitHub clones data class.""" 20 | 21 | count: int | None = None 22 | uniques: int | None = None 23 | clones: list[_Clone] | None = None 24 | 25 | def _generate_clones(self, data: list[Dict[str, Any]]) -> list[_Clone]: 26 | """Generate GitHubClonesModel from list of dicts.""" 27 | return [_Clone(clone) for clone in data or []] 28 | -------------------------------------------------------------------------------- /aiogithubapi/models/commit.py: -------------------------------------------------------------------------------- 1 | """GitHub commit data class.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import Any, Dict 6 | 7 | from .base import GitHubDataModelBase 8 | from .user import GitHubUserModel 9 | 10 | 11 | class _Author(GitHubDataModelBase): 12 | """Internal entry.""" 13 | 14 | name: str | None = None 15 | email: str | None = None 16 | date: str | None = None 17 | 18 | 19 | class _Committer(_Author): 20 | """Internal entry.""" 21 | 22 | 23 | class _Tree(GitHubDataModelBase): 24 | """Internal entry.""" 25 | 26 | sha: str | None = None 27 | url: str | None = None 28 | 29 | 30 | class _Verification(GitHubDataModelBase): 31 | """Internal entry.""" 32 | 33 | verified: bool | None = None 34 | reason: str | None = None 35 | signature: str | None = None 36 | payload: str | None = None 37 | 38 | 39 | class _Parents(GitHubDataModelBase): 40 | """Internal entry.""" 41 | 42 | sha: str | None = None 43 | url: str | None = None 44 | html_url: str | None = None 45 | 46 | 47 | class _Commit(GitHubDataModelBase): 48 | """Internal entry.""" 49 | 50 | author: _Author | None = None 51 | committer: _Committer | None = None 52 | message: str | None = None 53 | tree: _Tree | None = None 54 | url: str | None = None 55 | comment_count: int | None = None 56 | verification: _Verification | None = None 57 | 58 | def _generate_author(self, data: Dict[str, Any] | None) -> _Author: 59 | """Generate author data.""" 60 | return _Author(data) if data else None 61 | 62 | def _generate_committer(self, data: Dict[str, Any] | None) -> _Committer: 63 | """Generate committer data.""" 64 | return _Committer(data) if data else None 65 | 66 | def _generate_tree(self, data: Dict[str, Any] | None) -> _Tree: 67 | """Generate tree data.""" 68 | return _Tree(data) if data else None 69 | 70 | def _generate_verification(self, data: Dict[str, Any] | None) -> _Verification: 71 | """Generate verification data.""" 72 | return _Verification(data) if data else None 73 | 74 | 75 | class GitHubCommitModel(GitHubDataModelBase): 76 | """GitHub commit data class.""" 77 | 78 | sha: str | None = None 79 | commit: _Commit | None = None 80 | url: str | None = None 81 | html_url: str | None = None 82 | comments_url: str | None = None 83 | author: GitHubUserModel | None = None 84 | committer: GitHubUserModel | None = None 85 | parents: list[_Parents] | None = None 86 | 87 | def _generate_commit(self, data: Dict[str, Any] | None) -> _Commit: 88 | """Generate commit data.""" 89 | return _Commit(data) if data else None 90 | 91 | def _generate_author(self, data: Dict[str, Any] | None) -> GitHubUserModel: 92 | """Generate author data.""" 93 | return GitHubUserModel(data) if data else None 94 | 95 | def _generate_committer(self, data: Dict[str, Any] | None) -> GitHubUserModel: 96 | """Generate committer data.""" 97 | return GitHubUserModel(data) if data else None 98 | -------------------------------------------------------------------------------- /aiogithubapi/models/contents.py: -------------------------------------------------------------------------------- 1 | """GitHub contents data class.""" 2 | 3 | from __future__ import annotations 4 | 5 | from .base import GitHubDataModelBase 6 | 7 | 8 | class GitHubContentsModel(GitHubDataModelBase): 9 | """GitHub contents data class.""" 10 | 11 | type: str | None = None 12 | encoding: str | None = None 13 | size: int | None = None 14 | name: str | None = None 15 | path: str | None = None 16 | content: str | None = None 17 | sha: str | None = None 18 | url: str | None = None 19 | git_url: str | None = None 20 | html_url: str | None = None 21 | download_url: str | None = None 22 | -------------------------------------------------------------------------------- /aiogithubapi/models/device_login.py: -------------------------------------------------------------------------------- 1 | """GitHub device login data class.""" 2 | 3 | from __future__ import annotations 4 | 5 | from .base import GitHubDataModelBase 6 | 7 | 8 | class GitHubLoginDeviceModel(GitHubDataModelBase): 9 | """GitHub device login data class.""" 10 | 11 | device_code: str | None = None 12 | user_code: str | None = None 13 | verification_uri: str | None = None 14 | expires_in: int | None = None 15 | interval: int | None = None 16 | -------------------------------------------------------------------------------- /aiogithubapi/models/events.py: -------------------------------------------------------------------------------- 1 | """GitHub Activity event models""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import Any, Dict 6 | 7 | from .base import GitHubDataModelBase 8 | 9 | 10 | class _Actor(GitHubDataModelBase): 11 | """Internal entry.""" 12 | 13 | id: str | None = None 14 | login: str | None = None 15 | display_login: str | None = None 16 | gravatar_id: str | None = None 17 | url: str | None = None 18 | avatar_url: str | None = None 19 | 20 | 21 | class _Repo(GitHubDataModelBase): 22 | """Internal entry.""" 23 | 24 | id: str | None = None 25 | name: str | None = None 26 | url: str | None = None 27 | 28 | 29 | class GitHubEventModel(GitHubDataModelBase): 30 | """ 31 | GitHub base event data class. 32 | 33 | The type is PascalCase 34 | 35 | https://docs.github.com/en/developers/webhooks-and-events/events/github-event-types 36 | """ 37 | 38 | id: str | None = None 39 | type: str | None = None 40 | actor: _Actor | None = None 41 | org: _Actor | None = None 42 | repo: _Repo | None = None 43 | payload: Dict[str, Any] | None = None 44 | public: bool | None = None 45 | created_at: str | None = None 46 | 47 | def _generate_actor(self, data: Dict[str, Any] | None) -> _Actor: 48 | """Generate actor data.""" 49 | return _Actor(data) if data else None 50 | 51 | def _generate_repo(self, data: Dict[str, Any] | None) -> _Repo: 52 | """Generate repo data.""" 53 | return _Repo(data) if data else None 54 | -------------------------------------------------------------------------------- /aiogithubapi/models/git_tree.py: -------------------------------------------------------------------------------- 1 | """GitHub git data class.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import Any, Dict 6 | 7 | from .base import GitHubDataModelBase 8 | 9 | 10 | class GitHubGitTreeEntryModel(GitHubDataModelBase): 11 | """GitHub git tree entry model.""" 12 | 13 | mode: str | None = None 14 | path: str | None = None 15 | sha: str | None = None 16 | size: int | None = None 17 | type: str | None = None 18 | url: str | None = None 19 | 20 | 21 | class GitHubGitTreeModel(GitHubDataModelBase): 22 | """GitHub git data class.""" 23 | 24 | sha: str | None = None 25 | tree: list[GitHubGitTreeEntryModel] | None = None 26 | truncated: bool | None = None 27 | url: str | None = None 28 | 29 | def _generate_tree(self, data: list[Dict[str, Any]]) -> list: 30 | """Generate tree entries.""" 31 | return [GitHubGitTreeEntryModel(entry) for entry in data or []] 32 | -------------------------------------------------------------------------------- /aiogithubapi/models/issue.py: -------------------------------------------------------------------------------- 1 | """GitHub issue data class.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import Any, Dict 6 | 7 | from .base import GitHubDataModelBase 8 | from .label import GitHubLabelModel 9 | from .milestone import GitHubMilestoneModel 10 | from .user import GitHubUserModel 11 | 12 | 13 | class _PullRequest(GitHubDataModelBase): 14 | """GitHub pull_request data model.""" 15 | 16 | url: str | None = None 17 | html_url: str | None = None 18 | diff_url: str | None = None 19 | patch_url: str | None = None 20 | 21 | 22 | class GitHubIssueModel(GitHubDataModelBase): 23 | """GitHub issue data class.""" 24 | 25 | active_lock_reason: str | None = None 26 | author_association: str | None = None 27 | body: str | None = None 28 | closed_at: str | None = None 29 | closed_by: GitHubUserModel | None = None 30 | comments_url: str | None = None 31 | comments: int | None = None 32 | created_at: str | None = None 33 | events_url: str | None = None 34 | html_url: str | None = None 35 | id: int | None = None 36 | labels_url: str | None = None 37 | locked: bool | None = None 38 | milestone: GitHubMilestoneModel | None = None 39 | number: int | None = None 40 | repository_url: str | None = None 41 | state: str | None = None 42 | title: str | None = None 43 | updated_at: str | None = None 44 | url: str | None = None 45 | pull_request: _PullRequest | None = None 46 | assignees: list[GitHubUserModel] | None = None 47 | assignee: GitHubUserModel | None = None 48 | user: GitHubUserModel | None = None 49 | labels: list[GitHubLabelModel] | None = None 50 | 51 | @property 52 | def is_pull_request(self) -> bool: 53 | """Return True if issue is a pull request.""" 54 | return self.pull_request is not None 55 | 56 | def _generate_labels(self, data: list[Dict[str, Any]]) -> list[GitHubLabelModel]: 57 | """Generate GitHubLabelModel list from data.""" 58 | return [GitHubLabelModel(label) for label in data or []] 59 | 60 | def _generate_assignees(self, data: list[Dict[str, Any]]) -> list[GitHubUserModel]: 61 | """Generate GitHubUserModel list from data.""" 62 | return [GitHubUserModel(user) for user in data or []] 63 | 64 | def _generate_assignee(self, data: Dict[str, Any] | None) -> GitHubUserModel: 65 | """Generate GitHubUserModel from data.""" 66 | return GitHubUserModel(data) if data else None 67 | 68 | def _generate_milestone(self, data: Dict[str, Any] | None) -> "GitHubMilestoneModel": 69 | """Generate GitHubMilestoneModel from data.""" 70 | return GitHubMilestoneModel(data) if data else None 71 | 72 | def _generate_closed_by(self, data: Dict[str, Any] | None) -> GitHubUserModel: 73 | """Generate GitHubUserModel from data.""" 74 | return GitHubUserModel(data) if data else None 75 | 76 | def _generate_pull_request(self, data: Dict[str, Any] | None) -> _PullRequest: 77 | """Generate GitHubPullRequestModel from data.""" 78 | return _PullRequest(data) if data else None 79 | -------------------------------------------------------------------------------- /aiogithubapi/models/issue_comment.py: -------------------------------------------------------------------------------- 1 | """GitHub issue_comment data class.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import Any, Dict 6 | 7 | from .base import GitHubDataModelBase 8 | from .user import GitHubUserModel 9 | 10 | 11 | class GitHubIssueCommentModel(GitHubDataModelBase): 12 | """GitHub issue_comment data class.""" 13 | 14 | author_association: str | None = None 15 | body: str | None = None 16 | created_at: str | None = None 17 | html_url: str | None = None 18 | id: int | None = None 19 | issue_url: str | None = None 20 | updated_at: str | None = None 21 | url: str | None = None 22 | user: GitHubUserModel | None = None 23 | 24 | def _generate_user(self, data: Dict[str, Any] | None) -> GitHubUserModel: 25 | """Generate GitHubUserModel.""" 26 | return GitHubUserModel(data) if data else None 27 | -------------------------------------------------------------------------------- /aiogithubapi/models/label.py: -------------------------------------------------------------------------------- 1 | """GitHub label data class.""" 2 | 3 | from __future__ import annotations 4 | 5 | from .base import GitHubDataModelBase 6 | 7 | 8 | class GitHubLabelModel(GitHubDataModelBase): 9 | """GitHub label data class.""" 10 | 11 | color: str | None = None 12 | default: bool | None = None 13 | description: str | None = None 14 | id: int | None = None 15 | name: str | None = None 16 | url: str | None = None 17 | -------------------------------------------------------------------------------- /aiogithubapi/models/license.py: -------------------------------------------------------------------------------- 1 | """GitHub license data class.""" 2 | 3 | from __future__ import annotations 4 | 5 | from .base import GitHubDataModelBase 6 | 7 | 8 | class GitHubLicenseModel(GitHubDataModelBase): 9 | """GitHub license data class.""" 10 | 11 | key: str | None = None 12 | name: str | None = None 13 | spdx_id: str | None = None 14 | url: str | None = None 15 | -------------------------------------------------------------------------------- /aiogithubapi/models/login_oauth.py: -------------------------------------------------------------------------------- 1 | """GitHub login oauth data class.""" 2 | 3 | from __future__ import annotations 4 | 5 | from .base import GitHubDataModelBase 6 | 7 | 8 | class GitHubLoginOauthModel(GitHubDataModelBase): 9 | """GitHub login oauth data class.""" 10 | 11 | access_token: str | None = None 12 | token_type: str | None = None 13 | scope: str | None = None 14 | -------------------------------------------------------------------------------- /aiogithubapi/models/meta.py: -------------------------------------------------------------------------------- 1 | """GitHub meta data class.""" 2 | 3 | from __future__ import annotations 4 | 5 | from .base import GitHubDataModelBase 6 | 7 | 8 | class _FingerPrints(GitHubDataModelBase): 9 | """Representation of a GitHub tag commit.""" 10 | 11 | sha256_rsa: str | None = None 12 | sha256_dsa: str | None = None 13 | sha256_ecdsa: str | None = None 14 | sha256_ed25519: str | None = None 15 | 16 | 17 | class GitHubMetaModel(GitHubDataModelBase): 18 | """GitHub meta data class.""" 19 | 20 | verifiable_password_authentication: bool | None = None 21 | ssh_key_fingerprints: _FingerPrints | None = None 22 | ssh_keys: list[str] | None = None 23 | hooks: list[str] | None = None 24 | web: list[str] | None = None 25 | api: list[str] | None = None 26 | git: list[str] | None = None 27 | packages: list[str] | None = None 28 | pages: list[str] | None = None 29 | importer: list[str] | None = None 30 | actions: list[str] | None = None 31 | dependabot: list[str] | None = None 32 | 33 | def _generate_ssh_key_fingerprints(self, data: dict) -> _FingerPrints: 34 | """Generate commit data.""" 35 | return _FingerPrints(data) 36 | -------------------------------------------------------------------------------- /aiogithubapi/models/milestone.py: -------------------------------------------------------------------------------- 1 | """GitHub milestone data class.""" 2 | 3 | from __future__ import annotations 4 | 5 | from .base import GitHubDataModelBase 6 | from .user import GitHubUserModel 7 | 8 | 9 | class GitHubMilestoneModel(GitHubDataModelBase): 10 | """GitHub milestone data class.""" 11 | 12 | closed_at: str | None = None 13 | closed_issues: int | None = None 14 | created_at: str | None = None 15 | description: str | None = None 16 | due_on: str | None = None 17 | html_url: str | None = None 18 | id: int | None = None 19 | labels_url: str | None = None 20 | number: int | None = None 21 | open_issues: int | None = None 22 | state: str | None = None 23 | title: str | None = None 24 | updated_at: str | None = None 25 | url: str | None = None 26 | creator: GitHubUserModel | None = None 27 | 28 | def _generate_creator(self, data: dict) -> GitHubUserModel: 29 | """Generate the creator.""" 30 | return GitHubUserModel(data) 31 | -------------------------------------------------------------------------------- /aiogithubapi/models/organization.py: -------------------------------------------------------------------------------- 1 | """GitHub organization model.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import Any, Dict 6 | 7 | from .base import GitHubDataModelBase 8 | 9 | 10 | class _Plan(GitHubDataModelBase): 11 | """GitHub organization plan data model.""" 12 | 13 | name: str | None = None 14 | space: int | None = None 15 | private_repos: int | None = None 16 | filled_seats: int | None = None 17 | seats: int | None = None 18 | 19 | 20 | class GitHubOrganizationModel(GitHubDataModelBase): 21 | """GitHub organization model.""" 22 | 23 | login: str | None = None 24 | id: int | None = None 25 | node_id: str | None = None 26 | url: str | None = None 27 | repos_url: str | None = None 28 | events_url: str | None = None 29 | hooks_url: str | None = None 30 | issues_url: str | None = None 31 | members_url: str | None = None 32 | public_members_url: str | None = None 33 | avatar_url: str | None = None 34 | description: str | None = None 35 | name: str | None = None 36 | company: str | None = None 37 | blog: str | None = None 38 | location: str | None = None 39 | email: str | None = None 40 | twitter_username: str | None = None 41 | is_verified: bool | None = None 42 | has_organization_projects: bool | None = None 43 | has_repository_projects: bool | None = None 44 | public_repos: int | None = None 45 | public_gists: int | None = None 46 | followers: int | None = None 47 | following: int | None = None 48 | html_url: str | None = None 49 | created_at: str | None = None 50 | updated_at: str | None = None 51 | type: str | None = None 52 | total_private_repos: int | None = None 53 | owned_private_repos: int | None = None 54 | private_gists: int | None = None 55 | disk_usage: int | None = None 56 | collaborators: int | None = None 57 | billing_email: str | None = None 58 | plan: _Plan | None = None 59 | default_repository_permission: str | None = None 60 | members_can_create_repositories: bool | None = None 61 | two_factor_requirement_enabled: bool | None = None 62 | members_allowed_repository_creation_type: str | None = None 63 | members_can_create_public_repositories: bool | None = None 64 | members_can_create_private_repositories: bool | None = None 65 | members_can_create_internal_repositories: bool | None = None 66 | members_can_create_pages: bool | None = None 67 | members_can_fork_private_repositories: bool | None = None 68 | 69 | def _generate_plan(self, data: Dict[str, Any]) -> _Plan: 70 | """Generate GitHubLabelModel list from data.""" 71 | return _Plan(data) if data else None 72 | 73 | 74 | class GitHubOrganizationMinimalModel(GitHubDataModelBase): 75 | """GitHub organization minimal model.""" 76 | 77 | login: str | None = None 78 | id: int | None = None 79 | node_id: str | None = None 80 | url: str | None = None 81 | repos_url: str | None = None 82 | events_url: str | None = None 83 | hooks_url: str | None = None 84 | issues_url: str | None = None 85 | members_url: str | None = None 86 | public_members_url: str | None = None 87 | avatar_url: str | None = None 88 | description: str | None = None 89 | -------------------------------------------------------------------------------- /aiogithubapi/models/owner.py: -------------------------------------------------------------------------------- 1 | """GitHub owner model.""" 2 | 3 | from .user import GitHubUserModel 4 | 5 | 6 | class GitHubOwnerModel(GitHubUserModel): 7 | """GitHub owner model.""" 8 | -------------------------------------------------------------------------------- /aiogithubapi/models/permissions.py: -------------------------------------------------------------------------------- 1 | """GitHub permissions data class.""" 2 | 3 | from __future__ import annotations 4 | 5 | from .base import GitHubDataModelBase 6 | 7 | 8 | class GitHubPermissionsModel(GitHubDataModelBase): 9 | """GitHub permissions data class.""" 10 | 11 | admin: bool | None = None 12 | maintain: bool | None = None 13 | push: bool | None = None 14 | triage: bool | None = None 15 | pull: bool | None = None 16 | -------------------------------------------------------------------------------- /aiogithubapi/models/projects.py: -------------------------------------------------------------------------------- 1 | """GitHub projects model.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import Any, Dict 6 | 7 | from .base import GitHubDataModelBase 8 | from .user import GitHubBaseUserModel 9 | 10 | 11 | class GitHubProjectModel(GitHubDataModelBase): 12 | """GitHub project model.""" 13 | 14 | owner_url: str | None = None 15 | url: str | None = None 16 | html_url: str | None = None 17 | columns_url: str | None = None 18 | id: int | None = None 19 | node_id: str | None = None 20 | name: str | None = None 21 | body: str | None = None 22 | number: int | None = None 23 | state: str | None = None 24 | creator: GitHubBaseUserModel | None = None 25 | created_at: str | None = None 26 | updated_at: str | None = None 27 | 28 | def _generate_creator(self, data: Dict[str, Any] | None) -> GitHubBaseUserModel: 29 | """Generate GitHub user creator model.""" 30 | return GitHubBaseUserModel(data) if data else None 31 | -------------------------------------------------------------------------------- /aiogithubapi/models/pull_request.py: -------------------------------------------------------------------------------- 1 | """GitHub pull_request data model.""" 2 | 3 | from __future__ import annotations 4 | 5 | from .issue import GitHubIssueModel 6 | 7 | 8 | class GitHubPullRequestModel(GitHubIssueModel): 9 | """GitHub pull_request data model.""" 10 | 11 | diff_url: str | None = None 12 | patch_url: str | None = None 13 | issue_url: str | None = None 14 | merged_at: str | None = None 15 | merge_commit_sha: str | None = None 16 | requested_reviewers: list[dict] | None = None 17 | requested_teams: list[dict] | None = None 18 | draft: bool | None = None 19 | head: dict | None = None 20 | base: dict | None = None 21 | auto_merge: bool | None = None 22 | commits_url: str | None = None 23 | review_comments_url: str | None = None 24 | review_comment_url: str | None = None 25 | statuses_url: str | None = None 26 | -------------------------------------------------------------------------------- /aiogithubapi/models/rate_limit.py: -------------------------------------------------------------------------------- 1 | """GitHub rate limit data class.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import Any, Dict 6 | 7 | from .base import GitHubDataModelBase 8 | 9 | 10 | class GitHubRateLimitResourceModel(GitHubDataModelBase): 11 | """GitHub rate limit resource data class.""" 12 | 13 | limit: int | None = None 14 | used: int | None = None 15 | remaining: int | None = None 16 | reset: int | None = None 17 | 18 | 19 | class GitHubRateLimitResourcesModel(GitHubDataModelBase): 20 | """GitHub rate limit resources data class.""" 21 | 22 | core: GitHubRateLimitResourceModel | None = None 23 | search: GitHubRateLimitResourceModel | None = None 24 | graphql: GitHubRateLimitResourceModel | None = None 25 | dependency_snapshots: GitHubRateLimitResourceModel | None = None 26 | integration_manifest: GitHubRateLimitResourceModel | None = None 27 | source_import: GitHubRateLimitResourceModel | None = None 28 | code_scanning_upload: GitHubRateLimitResourceModel | None = None 29 | actions_runner_registration: GitHubRateLimitResourceModel | None = None 30 | scim: GitHubRateLimitResourceModel | None = None 31 | 32 | def _generate_core(self, data: Any) -> GitHubRateLimitResourceModel: 33 | return GitHubRateLimitResourceModel(data) if data else None 34 | 35 | def _generate_search(self, data: Any) -> GitHubRateLimitResourceModel: 36 | return GitHubRateLimitResourceModel(data) if data else None 37 | 38 | def _generate_graphql(self, data: Any) -> GitHubRateLimitResourceModel: 39 | return GitHubRateLimitResourceModel(data) if data else None 40 | 41 | def _generate_integration_manifest(self, data: Any) -> GitHubRateLimitResourceModel: 42 | return GitHubRateLimitResourceModel(data) if data else None 43 | 44 | def _generate_source_import(self, data: Any) -> GitHubRateLimitResourceModel: 45 | return GitHubRateLimitResourceModel(data) if data else None 46 | 47 | def _generate_code_scanning_upload(self, data: Any) -> GitHubRateLimitResourceModel: 48 | return GitHubRateLimitResourceModel(data) if data else None 49 | 50 | def _generate_dependency_snapshots(self, data: Any) -> GitHubRateLimitResourceModel: 51 | return GitHubRateLimitResourceModel(data) if data else None 52 | 53 | 54 | class GitHubRateLimitModel(GitHubDataModelBase): 55 | """GitHub rate limit data class.""" 56 | 57 | rate: GitHubRateLimitResourceModel | None = None 58 | resources: GitHubRateLimitResourcesModel | None = None 59 | 60 | def _generate_rate(self, data: Dict[str, Any]) -> GitHubRateLimitResourceModel: 61 | """Generate rate limit resource model.""" 62 | return GitHubRateLimitResourceModel(data) if data else None 63 | 64 | def _generate_resources(self, data: Dict[str, Any]) -> GitHubRateLimitResourcesModel: 65 | """Generate rate limit resources model.""" 66 | return GitHubRateLimitResourcesModel(data) if data else None 67 | -------------------------------------------------------------------------------- /aiogithubapi/models/reaction.py: -------------------------------------------------------------------------------- 1 | """GitHub reaction data class.""" 2 | 3 | from __future__ import annotations 4 | 5 | from .base import GitHubDataModelBase 6 | 7 | 8 | class GitHubReactionModel(GitHubDataModelBase): 9 | """GitHub reaction data class.""" 10 | 11 | _log_missing: bool = False 12 | 13 | url: str | None = None 14 | total_count: int | None = None 15 | thumbs_up: int | None = None 16 | thumbs_down: int | None = None 17 | laugh: int | None = None 18 | hooray: int | None = None 19 | confused: int | None = None 20 | heart: int | None = None 21 | rocket: int | None = None 22 | eyes: int | None = None 23 | 24 | def __post_init__(self): 25 | """Initialize attributes.""" 26 | self.thumbs_up = self._raw_data.get("+1", 0) 27 | self.thumbs_down = self._raw_data.get("-1", 0) 28 | -------------------------------------------------------------------------------- /aiogithubapi/models/release.py: -------------------------------------------------------------------------------- 1 | """GitHub release data class.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import Any, Dict 6 | 7 | from .base import GitHubDataModelBase 8 | from .reaction import GitHubReactionModel 9 | from .user import GitHubBaseUserModel 10 | 11 | 12 | class GitHubReleaseAssetModel(GitHubDataModelBase): 13 | """Representation of a GitHub release asset.""" 14 | 15 | url: str | None = None 16 | id: int | None = None 17 | name: str | None = None 18 | label: str | None = None 19 | uploader: GitHubBaseUserModel | None = None 20 | content_type: str | None = None 21 | state: str | None = None 22 | size: int | None = None 23 | download_count: int = None 24 | created_at: str = None 25 | updated_at: str = None 26 | browser_download_url: str = None 27 | 28 | def _generate_uploader(self, data: Dict[str, Any] | None) -> GitHubBaseUserModel: 29 | """Generate uploader data.""" 30 | return GitHubBaseUserModel(data) if data else None 31 | 32 | 33 | class GitHubReleaseModel(GitHubDataModelBase): 34 | """GitHub release data class.""" 35 | 36 | url: str | None = None 37 | assets_url: str | None = None 38 | upload_url: str | None = None 39 | html_url: str | None = None 40 | id: int | None = None 41 | discussion_url: str | None = None 42 | author: GitHubBaseUserModel | None = None 43 | tag_name: str | None = None 44 | target_commitish: str | None = None 45 | name: str | None = None 46 | draft: bool | None = None 47 | prerelease: bool | None = None 48 | created_at: str | None = None 49 | published_at: str | None = None 50 | assets: list[dict] | None = None 51 | tarball_url: str | None = None 52 | zipball_url: str | None = None 53 | body: str | None = None 54 | reactions: GitHubReactionModel | None = None 55 | mentions_count: int | None = None 56 | 57 | def _generate_author(self, data: dict) -> GitHubBaseUserModel: 58 | """Generate author data.""" 59 | return GitHubBaseUserModel(data) 60 | 61 | def _generate_assets(self, data: list[Dict[str, Any]]) -> dict: 62 | """Generate assets data.""" 63 | return [GitHubReleaseAssetModel(asset) for asset in data or []] 64 | 65 | def _generate_reactions(self, data: dict) -> GitHubReactionModel: 66 | """Generate reactions data.""" 67 | return GitHubReactionModel(data) 68 | -------------------------------------------------------------------------------- /aiogithubapi/models/request_data.py: -------------------------------------------------------------------------------- 1 | """Dataclass to hold base request details.""" 2 | 3 | from __future__ import annotations 4 | 5 | from dataclasses import dataclass 6 | from typing import Any, Dict 7 | 8 | from aiohttp.hdrs import AUTHORIZATION, USER_AGENT 9 | 10 | from ..const import ( 11 | BASE_API_HEADERS, 12 | BASE_API_URL, 13 | DEFAULT_USER_AGENT, 14 | HEADER_GITHUB_API_VERSION, 15 | GitHubClientKwarg, 16 | ) 17 | from .base import GitHubBase 18 | 19 | 20 | @dataclass 21 | class GitHubBaseRequestDataModel(GitHubBase): 22 | """Dataclass to hold base request details.""" 23 | 24 | kwargs: Dict[GitHubClientKwarg, Any] 25 | token: str | None = None 26 | api_version: str | None = None 27 | 28 | def __post_init__(self): 29 | """Check user agent.""" 30 | if self.headers[USER_AGENT] == DEFAULT_USER_AGENT: 31 | self.logger.debug( 32 | "User-Agent not set. Set this with passing a user-agent header or the client_name argument." 33 | ) 34 | 35 | def request_url(self, endpoint: str) -> str: 36 | """Generate full request url.""" 37 | return f"{self.base_url}{endpoint}" 38 | 39 | @property 40 | def timeout(self) -> int: 41 | """Return timeout.""" 42 | return self.kwargs.get(GitHubClientKwarg.TIMEOUT) or 20 43 | 44 | @property 45 | def base_url(self) -> str: 46 | """Return the base url.""" 47 | return self.kwargs.get(GitHubClientKwarg.BASE_URL) or BASE_API_URL 48 | 49 | @property 50 | def headers(self) -> Dict[str, str]: 51 | """Return base request headers.""" 52 | headers = BASE_API_HEADERS.copy() 53 | if self.token: 54 | headers[AUTHORIZATION] = f"token {self.token}" 55 | if kwarg_headers := self.kwargs.get(GitHubClientKwarg.HEADERS): 56 | headers.update(kwarg_headers) 57 | if client_name := self.kwargs.get(GitHubClientKwarg.CLIENT_NAME): 58 | headers[USER_AGENT] = client_name 59 | if self.api_version is not None: 60 | headers[HEADER_GITHUB_API_VERSION] = self.api_version 61 | return headers 62 | -------------------------------------------------------------------------------- /aiogithubapi/models/tag.py: -------------------------------------------------------------------------------- 1 | """GitHub tag data class.""" 2 | 3 | from __future__ import annotations 4 | 5 | from .base import GitHubDataModelBase 6 | 7 | 8 | class _Commit(GitHubDataModelBase): 9 | """Representation of a GitHub tag commit.""" 10 | 11 | sha: str | None = None 12 | url: str | None = None 13 | 14 | 15 | class GitHubTagModel(GitHubDataModelBase): 16 | """GitHub tag data class.""" 17 | 18 | name: str | None = None 19 | commit: _Commit | None = None 20 | zipball_url: str | None = None 21 | tarball_url: str | None = None 22 | 23 | def _generate_commit(self, data: dict) -> _Commit: 24 | """Generate commit data.""" 25 | return _Commit(data) 26 | -------------------------------------------------------------------------------- /aiogithubapi/models/user.py: -------------------------------------------------------------------------------- 1 | """GitHub user models data class.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import Any, Dict 6 | 7 | from .base import GitHubDataModelBase 8 | 9 | 10 | class GitHubBaseUserModel(GitHubDataModelBase): 11 | """GitHub base user data class.""" 12 | 13 | avatar_url: str | None = None 14 | events_url: str | None = None 15 | followers_url: str | None = None 16 | following_url: str | None = None 17 | gists_url: str | None = None 18 | gravatar_id: str | None = None 19 | html_url: str | None = None 20 | id: int | None = None 21 | login: str | None = None 22 | organizations_url: str | None = None 23 | received_events_url: str | None = None 24 | repos_url: str | None = None 25 | site_admin: bool | None = None 26 | starred_url: str | None = None 27 | subscriptions_url: str | None = None 28 | type: str | None = None 29 | url: str | None = None 30 | user_view_type: str | None = None 31 | 32 | 33 | class GitHubUserModel(GitHubBaseUserModel): 34 | """GitHub user data class.""" 35 | 36 | bio: str | None = None 37 | blog: str | None = None 38 | company: str | None = None 39 | created_at: str | None = None 40 | email: str | None = None 41 | followers: int | None = None 42 | following: int | None = None 43 | hireable: bool | None = None 44 | location: str | None = None 45 | name: str | None = None 46 | public_gists: int | None = None 47 | public_repos: int | None = None 48 | twitter_username: str | None = None 49 | updated_at: str | None = None 50 | 51 | 52 | class GitHubAuthenticatedUserModel(GitHubUserModel): 53 | """GitHub authenticated user data class.""" 54 | 55 | collaborators: int | None = None 56 | disk_usage: int | None = None 57 | owned_private_repos: int | None = None 58 | plan: GitHubUserPlanModel | None = None 59 | private_gists: int | None = None 60 | total_private_repos: int | None = None 61 | two_factor_authentication: bool | None = None 62 | 63 | def _generate_plan(self, data: Dict[str, Any] | None) -> GitHubUserPlanModel: 64 | """Generate GitHub user plan model.""" 65 | return GitHubUserPlanModel(data) if data else None 66 | 67 | 68 | class GitHubUserPlanModel(GitHubDataModelBase): 69 | """GitHub user plan data class.""" 70 | 71 | collaborators: int | None = None 72 | name: str | None = None 73 | private_repos: int | None = None 74 | space: int | None = None 75 | -------------------------------------------------------------------------------- /aiogithubapi/models/views.py: -------------------------------------------------------------------------------- 1 | """GitHub views data class.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import Any, Dict 6 | 7 | from .base import GitHubDataModelBase 8 | 9 | 10 | class _View(GitHubDataModelBase): 11 | """GitHub view data.""" 12 | 13 | timestamp: str | None = None 14 | count: int | None = None 15 | uniques: int | None = None 16 | 17 | 18 | class GitHubViewsModel(GitHubDataModelBase): 19 | """GitHub views data class.""" 20 | 21 | count: int | None = None 22 | uniques: int | None = None 23 | views: list[_View] | None = None 24 | 25 | def _generate_views(self, data: list[Dict[str, Any]]) -> list[_View]: 26 | """Generate views from list of dicts.""" 27 | return [_View(view) for view in data or []] 28 | -------------------------------------------------------------------------------- /aiogithubapi/namespaces/__init__.py: -------------------------------------------------------------------------------- 1 | """Initialise API namespaces.""" 2 | -------------------------------------------------------------------------------- /aiogithubapi/namespaces/base.py: -------------------------------------------------------------------------------- 1 | """Used for the GitHub API namespace.""" 2 | 3 | from ..client import GitHubClient 4 | 5 | 6 | class BaseNamespace: 7 | """Used for the GitHub API namespace.""" 8 | 9 | def __init__(self, client: GitHubClient) -> None: 10 | """Initialise the namespace.""" 11 | self._client = client 12 | self.__post_init__() 13 | 14 | def __post_init__(self) -> None: 15 | """Post initialisation.""" 16 | -------------------------------------------------------------------------------- /aiogithubapi/namespaces/contents.py: -------------------------------------------------------------------------------- 1 | """ 2 | Methods for the contents namespace 3 | 4 | https://docs.github.com/en/rest/reference/repos#contents 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from typing import Any, Dict 10 | 11 | from aiohttp.hdrs import ACCEPT 12 | 13 | from ..const import GitHubRequestAcceptHeader, GitHubRequestKwarg, RepositoryType 14 | from ..helpers import repository_full_name 15 | from ..models.contents import GitHubContentsModel 16 | from ..models.response import GitHubResponseModel 17 | from .base import BaseNamespace 18 | 19 | 20 | class GitHubContentsNamespace(BaseNamespace): 21 | """Methods for the contents namespace""" 22 | 23 | async def get( 24 | self, 25 | repository: RepositoryType, 26 | path: str | None = None, 27 | **kwargs: Dict[GitHubRequestKwarg, Any], 28 | ) -> GitHubResponseModel[GitHubContentsModel | list[GitHubContentsModel]]: 29 | """ 30 | Get repository content 31 | 32 | Gets the contents of a file or directory in a repository. 33 | 34 | Files and symlinks support a custom media type for retrieving the raw content 35 | or rendered HTML (when supported). All content types support a custom media 36 | type to ensure the content is returned in a consistent object format. 37 | 38 | **Arguments**: 39 | 40 | `repository` 41 | 42 | The repository to return contents from, example "octocat/hello-world" 43 | 44 | `path` 45 | 46 | Specify the file path or directory. If you omit this, you will 47 | receive the contents of the repository's root directory. 48 | 49 | https://docs.github.com/en/rest/reference/repos#get-repository-content 50 | """ 51 | response = await self._client.async_call_api( 52 | endpoint=f"/repos/{repository_full_name(repository)}" 53 | f"/contents{f'/{path}' if path else ''}", 54 | **{ 55 | GitHubRequestKwarg.HEADERS: {ACCEPT: GitHubRequestAcceptHeader.BASE_JSON.value}, 56 | **kwargs, 57 | }, 58 | ) 59 | 60 | if isinstance(response.data, list): 61 | response.data = [GitHubContentsModel(data) for data in response.data] 62 | else: 63 | response.data = GitHubContentsModel(response.data) 64 | return response 65 | -------------------------------------------------------------------------------- /aiogithubapi/namespaces/git.py: -------------------------------------------------------------------------------- 1 | """ 2 | Methods for the git namespace 3 | 4 | https://docs.github.com/en/rest/reference/git 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from typing import Any, Dict 10 | 11 | from ..const import GitHubRequestKwarg, RepositoryType 12 | from ..models.git_tree import GitHubGitTreeModel 13 | from ..models.response import GitHubResponseModel 14 | from .base import BaseNamespace 15 | 16 | 17 | class GitHubGitNamespace(BaseNamespace): 18 | """Methods for the git namespace""" 19 | 20 | async def get_tree( 21 | self, 22 | repository: RepositoryType, 23 | tree_sha: str, 24 | **kwargs: Dict[GitHubRequestKwarg, Any], 25 | ) -> GitHubResponseModel[GitHubGitTreeModel]: 26 | """ 27 | Get a tree 28 | Returns a single tree using the SHA1 value for that tree. 29 | 30 | If truncated is true in the response then the number of items in 31 | the tree array exceeded our maximum limit. 32 | If you need to fetch more items, use the 33 | non-recursive method of fetching trees, and fetch one sub-tree at a time. 34 | 35 | **Arguments**: 36 | 37 | `repository` 38 | 39 | The repository to return the tree for, example "octocat/hello-world" 40 | 41 | `tree_sha` 42 | 43 | The tree SHA or ref to return 44 | 45 | https://docs.github.com/en/rest/reference/git#get-a-tree 46 | """ 47 | response = await self._client.async_call_api( 48 | endpoint=f"/repos/{repository}/git/trees/{tree_sha}", 49 | **kwargs, 50 | ) 51 | response.data = GitHubGitTreeModel(response.data) 52 | return response 53 | -------------------------------------------------------------------------------- /aiogithubapi/namespaces/orgs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Methods for the orgs namespace 3 | 4 | https://docs.github.com/en/rest/reference/orgs 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from typing import Any, Dict 10 | 11 | from ..const import GitHubRequestKwarg, HttpMethod 12 | from ..models.organization import ( 13 | GitHubOrganizationMinimalModel, 14 | GitHubOrganizationModel, 15 | ) 16 | from ..models.response import GitHubResponseModel 17 | from .base import BaseNamespace 18 | from .projects import GitHubOrganizationProjectsNamespace 19 | 20 | 21 | class GitHubOrgsNamespace(BaseNamespace): 22 | """Methods for the orgs namespace""" 23 | 24 | def __post_init__(self) -> None: 25 | self._projects = GitHubOrganizationProjectsNamespace(self._client) 26 | 27 | @property 28 | def projects(self) -> GitHubOrganizationProjectsNamespace: 29 | """Property to access the users projects namespace""" 30 | return self._projects 31 | 32 | async def list( 33 | self, 34 | **kwargs: Dict[GitHubRequestKwarg, Any], 35 | ) -> GitHubResponseModel[list[GitHubOrganizationMinimalModel]]: 36 | """ 37 | List organizations 38 | 39 | 40 | https://docs.github.com/en/rest/reference/orgs#list-organizations 41 | """ 42 | response = await self._client.async_call_api( 43 | endpoint="/organizations", 44 | **kwargs, 45 | ) 46 | response.data = [GitHubOrganizationMinimalModel(org) for org in response.data or []] 47 | return response 48 | 49 | async def get( 50 | self, 51 | org: str, 52 | **kwargs: Dict[GitHubRequestKwarg, Any], 53 | ) -> GitHubResponseModel[GitHubOrganizationModel]: 54 | """ 55 | Get a organization 56 | 57 | **Arguments**: 58 | 59 | `org` 60 | 61 | The organization to return, example "octocat" 62 | 63 | https://docs.github.com/en/rest/reference/orgs#get-an-organization 64 | """ 65 | response = await self._client.async_call_api( 66 | endpoint=f"/orgs/{org}", 67 | **kwargs, 68 | ) 69 | response.data = GitHubOrganizationModel(response.data) 70 | return response 71 | 72 | async def update( 73 | self, 74 | org: str, 75 | data: dict[str, Any], 76 | **kwargs: Dict[GitHubRequestKwarg, Any], 77 | ) -> GitHubResponseModel[GitHubOrganizationModel]: 78 | """ 79 | Update an organization 80 | 81 | **Arguments**: 82 | 83 | `org` 84 | 85 | The organization to update, example "octocat" 86 | 87 | `data` 88 | 89 | A dictionary of data to update. 90 | 91 | https://docs.github.com/en/rest/reference/orgs#update-an-organization 92 | """ 93 | response = await self._client.async_call_api( 94 | endpoint=f"/orgs/{org}", 95 | data=data, 96 | method=HttpMethod.PATCH, 97 | **kwargs, 98 | ) 99 | response.data = GitHubOrganizationModel(response.data) 100 | return response 101 | -------------------------------------------------------------------------------- /aiogithubapi/namespaces/pulls.py: -------------------------------------------------------------------------------- 1 | """ 2 | Methods for the issues namespace 3 | 4 | https://docs.github.com/en/rest/reference/pulls 5 | """ 6 | 7 | from typing import Any, Dict, List 8 | 9 | from ..const import GitHubRequestKwarg, RepositoryType 10 | from ..helpers import repository_full_name 11 | from ..models.pull_request import GitHubPullRequestModel 12 | from ..models.response import GitHubResponseModel 13 | from .base import BaseNamespace 14 | 15 | 16 | class GitHubPullsNamespace(BaseNamespace): 17 | """ 18 | Methods for the pull requests namespace 19 | 20 | The Pull Request API allows you to list, view, edit, create, 21 | and even merge pull requests. Comments on pull requests 22 | can be managed via the issues namespace. 23 | """ 24 | 25 | async def list( 26 | self, 27 | repository: RepositoryType, 28 | **kwargs: Dict[GitHubRequestKwarg, Any], 29 | ) -> GitHubResponseModel[List[GitHubPullRequestModel]]: 30 | """ 31 | List pull requests 32 | 33 | **Arguments**: 34 | 35 | `repository` 36 | 37 | The repository to return pull requests from, example "octocat/hello-world" 38 | 39 | https://docs.github.com/en/rest/reference/pulls#list-pull-requests 40 | """ 41 | response = await self._client.async_call_api( 42 | endpoint=f"/repos/{repository_full_name(repository)}/pulls", 43 | **kwargs, 44 | ) 45 | response.data = [GitHubPullRequestModel(data) for data in response.data] 46 | return response 47 | -------------------------------------------------------------------------------- /aiogithubapi/namespaces/releases.py: -------------------------------------------------------------------------------- 1 | """ 2 | Methods for the releases namespace 3 | 4 | https://docs.github.com/en/rest/reference/repos#releases 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from typing import Any, Dict, List 10 | 11 | from ..const import GitHubRequestKwarg, RepositoryType 12 | from ..models.release import GitHubReleaseModel 13 | from ..models.response import GitHubResponseModel 14 | from .base import BaseNamespace 15 | 16 | 17 | class GitHubReleasesNamespace(BaseNamespace): 18 | """Methods for the releases namespace""" 19 | 20 | async def list( 21 | self, 22 | repository: RepositoryType, 23 | **kwargs: Dict[GitHubRequestKwarg, Any], 24 | ) -> GitHubResponseModel[List[GitHubReleaseModel]]: 25 | """ 26 | List releases 27 | 28 | This returns a list of releases, which does not include 29 | regular Git tags that have not been associated with a release. 30 | 31 | **Arguments**: 32 | 33 | `repository` 34 | 35 | The repository to return the tree for, example "octocat/hello-world" 36 | 37 | https://docs.github.com/en/rest/reference/repos#list-releases 38 | """ 39 | response = await self._client.async_call_api( 40 | endpoint=f"/repos/{repository}/releases", 41 | **kwargs, 42 | ) 43 | response.data = [GitHubReleaseModel(data) for data in response.data] 44 | return response 45 | 46 | async def latest( 47 | self, 48 | repository: RepositoryType, 49 | **kwargs: Dict[GitHubRequestKwarg, Any], 50 | ) -> GitHubResponseModel[GitHubReleaseModel]: 51 | """ 52 | Get the latest release 53 | 54 | This returns the latest release for the specified repository. 55 | 56 | **Arguments**: 57 | 58 | `repository` 59 | 60 | The repository to return the latest release for, example "octocat/hello-world" 61 | 62 | https://docs.github.com/en/rest/reference/repos#get-the-latest-release 63 | """ 64 | response = await self._client.async_call_api( 65 | endpoint=f"/repos/{repository}/releases/latest", 66 | **kwargs, 67 | ) 68 | response.data = GitHubReleaseModel(response.data) 69 | return response 70 | -------------------------------------------------------------------------------- /aiogithubapi/namespaces/traffic.py: -------------------------------------------------------------------------------- 1 | """ 2 | Methods for the traffic namespace 3 | 4 | https://docs.github.com/en/rest/reference/repos#traffic 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from typing import Any, Dict 10 | 11 | from ..const import GitHubRequestKwarg, RepositoryType 12 | from ..models.clones import GitHubClonesModel 13 | from ..models.response import GitHubResponseModel 14 | from ..models.views import GitHubViewsModel 15 | from .base import BaseNamespace 16 | 17 | 18 | class GitHubTrafficNamespace(BaseNamespace): 19 | """Methods for the traffic namespace""" 20 | 21 | async def clones( 22 | self, 23 | repository: RepositoryType, 24 | **kwargs: Dict[GitHubRequestKwarg, Any], 25 | ) -> GitHubResponseModel[GitHubClonesModel]: 26 | """ 27 | Get repository clones 28 | 29 | Get the total number of clones and breakdown per day or week for the last 14 days. 30 | Timestamps are aligned to UTC midnight of the beginning of the day or week. 31 | Week begins on Monday. 32 | 33 | **Arguments**: 34 | 35 | `repository` 36 | 37 | The repository to return clones for, example "octocat/hello-world" 38 | 39 | https://docs.github.com/en/rest/reference/git#get-a-tree 40 | """ 41 | response = await self._client.async_call_api( 42 | endpoint=f"/repos/{repository}/traffic/clones", 43 | **kwargs, 44 | ) 45 | response.data = GitHubClonesModel(response.data) 46 | return response 47 | 48 | async def views( 49 | self, 50 | repository: RepositoryType, 51 | **kwargs: Dict[GitHubRequestKwarg, Any], 52 | ) -> GitHubResponseModel[GitHubViewsModel]: 53 | """ 54 | Get page views 55 | 56 | Get the total number of views and breakdown per day or week for the last 14 days. 57 | Timestamps are aligned to UTC midnight of the beginning of the day or week. 58 | Week begins on Monday. 59 | 60 | **Arguments**: 61 | 62 | `repository` 63 | 64 | The repository to return views for, example "octocat/hello-world" 65 | 66 | https://docs.github.com/en/rest/reference/git#get-a-tree 67 | """ 68 | response = await self._client.async_call_api( 69 | endpoint=f"/repos/{repository}/traffic/views", 70 | **kwargs, 71 | ) 72 | response.data = GitHubViewsModel(response.data) 73 | return response 74 | -------------------------------------------------------------------------------- /aiogithubapi/objects/__init__.py: -------------------------------------------------------------------------------- 1 | """Deprecated""" 2 | -------------------------------------------------------------------------------- /aiogithubapi/objects/base.py: -------------------------------------------------------------------------------- 1 | """AIOGitHubAPI: objects.base""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from aiohttp.hdrs import ETAG 8 | 9 | from ..common.const import HttpStatusCode 10 | 11 | if TYPE_CHECKING: 12 | from ..client import AIOGitHubAPIClient 13 | 14 | 15 | class AIOGitHubAPIBase: 16 | """Base class for AIOGitHubAPI.""" 17 | 18 | def __init__(self, attributes) -> None: 19 | """Initialize.""" 20 | self.attributes = attributes 21 | 22 | 23 | class AIOGitHubAPIBaseClient(AIOGitHubAPIBase): 24 | """Base class for AIOGitHubAPI.""" 25 | 26 | def __init__(self, client: AIOGitHubAPIClient, attributes: dict) -> None: 27 | """Initialise.""" 28 | super().__init__(attributes) 29 | self.client = client 30 | 31 | 32 | class AIOGitHubAPIResponse: 33 | """Response object for AIOGitHub.""" 34 | 35 | def __init__(self) -> None: 36 | """initialise.""" 37 | self.headers: dict = {} 38 | self.data = {} 39 | self.status: HttpStatusCode = HttpStatusCode.OK 40 | 41 | def as_dict(self): 42 | """Return attributes as a dict.""" 43 | return { 44 | "headers": self.headers, 45 | "status": self.status, 46 | "data": self.data, 47 | "etag": self.etag, 48 | } 49 | 50 | @property 51 | def etag(self): 52 | """Return the ETag for this response.""" 53 | return self.headers.get(ETAG) 54 | -------------------------------------------------------------------------------- /aiogithubapi/objects/content.py: -------------------------------------------------------------------------------- 1 | """AIOGitHubAPI: objects.content""" 2 | 3 | from .base import AIOGitHubAPIBase 4 | 5 | 6 | class AIOGitHubAPIContentBase(AIOGitHubAPIBase): 7 | """Base Content class for AIOGitHubAPI.""" 8 | -------------------------------------------------------------------------------- /aiogithubapi/objects/login/__init__.py: -------------------------------------------------------------------------------- 1 | """Class objects for login.""" 2 | -------------------------------------------------------------------------------- /aiogithubapi/objects/login/device.py: -------------------------------------------------------------------------------- 1 | """ 2 | Class object for AIOGitHubAPILoginDevice 3 | Documentation: https://docs.github.com/en/developers/apps/authorizing-oauth-apps#device-flow 4 | 5 | Generated by generate/generate.py - 2020-09-08 17:49:51.525706 6 | """ 7 | 8 | from ..base import AIOGitHubAPIBase 9 | 10 | 11 | class AIOGitHubAPILoginDevice(AIOGitHubAPIBase): 12 | @property 13 | def device_code(self): 14 | return self.attributes.get("device_code", "") 15 | 16 | @property 17 | def user_code(self): 18 | return self.attributes.get("user_code", "") 19 | 20 | @property 21 | def verification_uri(self): 22 | return self.attributes.get("verification_uri", "") 23 | 24 | @property 25 | def expires_in(self): 26 | return self.attributes.get("expires_in", None) 27 | 28 | @property 29 | def interval(self): 30 | return self.attributes.get("interval", None) 31 | -------------------------------------------------------------------------------- /aiogithubapi/objects/login/oauth.py: -------------------------------------------------------------------------------- 1 | """ 2 | Class object for AIOGitHubAPILoginOauth 3 | Documentation: https://docs.github.com/en/developers/apps/authorizing-oauth-apps#device-flow 4 | 5 | Generated by generate/generate.py - 2020-09-08 18:45:20.581868 6 | """ 7 | 8 | from ..base import AIOGitHubAPIBase 9 | 10 | 11 | class AIOGitHubAPILoginOauth(AIOGitHubAPIBase): 12 | @property 13 | def access_token(self): 14 | return self.attributes.get("access_token", "") 15 | 16 | @property 17 | def token_type(self): 18 | return self.attributes.get("token_type", "") 19 | 20 | @property 21 | def scope(self): 22 | return self.attributes.get("scope", "") 23 | -------------------------------------------------------------------------------- /aiogithubapi/objects/orgs/__init__.py: -------------------------------------------------------------------------------- 1 | """AIOGitHubAPI""" 2 | -------------------------------------------------------------------------------- /aiogithubapi/objects/ratelimit.py: -------------------------------------------------------------------------------- 1 | """ 2 | AIOGitHubAPI: objects.ratelimit 3 | 4 | https://developer.github.com/v3/rate_limit/ 5 | """ 6 | 7 | # pylint: disable=missing-docstring 8 | from datetime import datetime, timezone 9 | 10 | from .base import AIOGitHubAPIBase 11 | 12 | 13 | class AIOGitHubAPIRateLimit(AIOGitHubAPIBase): 14 | """ 15 | AIOGitHubAPIRateLimit 16 | 17 | Holds information about the current reatelimit status. 18 | """ 19 | 20 | def __init__(self) -> None: 21 | """Initialize.""" 22 | self.limit = None 23 | self.remaining = None 24 | self.reset = None 25 | 26 | @property 27 | def reset_utc(self) -> None: 28 | """Return naïve date + time in UTC for next reset.""" 29 | if self.reset is None: 30 | return None 31 | return datetime.fromtimestamp(int(self.reset), tz=timezone.utc).replace(tzinfo=None) 32 | 33 | def load_from_response_headers(self, headers: dict) -> None: 34 | """ 35 | Load from response headers. 36 | 37 | :param headers: A dctionary with the returned headers 38 | """ 39 | self.limit = headers.get("X-RateLimit-Limit", "0") 40 | self.remaining = headers.get("X-RateLimit-Remaining", "0") 41 | self.reset = headers.get("X-RateLimit-Reset", "0") 42 | -------------------------------------------------------------------------------- /aiogithubapi/objects/repos/__init__.py: -------------------------------------------------------------------------------- 1 | """AIOGitHubAPI""" 2 | -------------------------------------------------------------------------------- /aiogithubapi/objects/repos/label.py: -------------------------------------------------------------------------------- 1 | """ 2 | Class object for AIOGitHubAPIReposLabel 3 | Documentation: https://docs.github.com/en/rest/reference/issues#get-a-label 4 | 5 | Generated by generate/generate.py - 2020-08-02 10:32:07.005845 6 | """ 7 | 8 | from ..base import AIOGitHubAPIBase 9 | 10 | 11 | class AIOGitHubAPIReposLabel(AIOGitHubAPIBase): 12 | @property 13 | def id(self): 14 | return self.attributes.get("id", None) 15 | 16 | @property 17 | def node_id(self): 18 | return self.attributes.get("node_id", "") 19 | 20 | @property 21 | def url(self): 22 | return self.attributes.get("url", "") 23 | 24 | @property 25 | def name(self): 26 | return self.attributes.get("name", "") 27 | 28 | @property 29 | def description(self): 30 | return self.attributes.get("description", "") 31 | 32 | @property 33 | def color(self): 34 | return self.attributes.get("color", "") 35 | 36 | @property 37 | def default(self): 38 | return self.attributes.get("default", True) 39 | -------------------------------------------------------------------------------- /aiogithubapi/objects/repos/traffic/__init__.py: -------------------------------------------------------------------------------- 1 | """AIOGitHubAPI""" 2 | -------------------------------------------------------------------------------- /aiogithubapi/objects/repos/traffic/clones.py: -------------------------------------------------------------------------------- 1 | """ 2 | Class object for AIOGitHubAPIReposTrafficClones 3 | Documentation: https://docs.github.com/en/rest/reference/repos#get-repository-clones 4 | 5 | Generated by generate/generate.py - 2020-08-02 12:24:23.865974 6 | """ 7 | 8 | from ...base import AIOGitHubAPIBase 9 | 10 | 11 | class Clones(AIOGitHubAPIBase): 12 | @property 13 | def timestamp(self): 14 | return self.attributes.get("timestamp", "") 15 | 16 | @property 17 | def count(self): 18 | return self.attributes.get("count", None) 19 | 20 | @property 21 | def uniques(self): 22 | return self.attributes.get("uniques", None) 23 | 24 | 25 | class AIOGitHubAPIReposTrafficClones(AIOGitHubAPIBase): 26 | @property 27 | def count(self): 28 | return self.attributes.get("count", None) 29 | 30 | @property 31 | def uniques(self): 32 | return self.attributes.get("uniques", None) 33 | 34 | @property 35 | def clones(self): 36 | return [Clones(x) for x in self.attributes.get("clones", [])] 37 | -------------------------------------------------------------------------------- /aiogithubapi/objects/repos/traffic/pageviews.py: -------------------------------------------------------------------------------- 1 | """ 2 | Class object for AIOGitHubAPIReposTrafficPageviews 3 | Documentation: https://docs.github.com/en/rest/reference/repos#get-page-views 4 | 5 | Generated by generate/generate.py - 2020-08-02 12:25:07.157407 6 | """ 7 | 8 | from ...base import AIOGitHubAPIBase 9 | 10 | 11 | class Views(AIOGitHubAPIBase): 12 | @property 13 | def timestamp(self): 14 | return self.attributes.get("timestamp", "") 15 | 16 | @property 17 | def count(self): 18 | return self.attributes.get("count", None) 19 | 20 | @property 21 | def uniques(self): 22 | return self.attributes.get("uniques", None) 23 | 24 | 25 | class AIOGitHubAPIReposTrafficPageviews(AIOGitHubAPIBase): 26 | @property 27 | def count(self): 28 | return self.attributes.get("count", None) 29 | 30 | @property 31 | def uniques(self): 32 | return self.attributes.get("uniques", None) 33 | 34 | @property 35 | def views(self): 36 | return [Views(x) for x in self.attributes.get("views", [])] 37 | -------------------------------------------------------------------------------- /aiogithubapi/objects/repository/collaborator.py: -------------------------------------------------------------------------------- 1 | """ 2 | Class object for AIOGitHubAPIRepositoryCollaborator 3 | Documentation: https://docs.github.com/en/rest/reference/repos#list-repository-collaborators 4 | 5 | Generated by generate/generate.py - 2020-08-02 10:09:26.842173 6 | """ 7 | 8 | from ..base import AIOGitHubAPIBase 9 | 10 | 11 | class Permissions(AIOGitHubAPIBase): 12 | @property 13 | def pull(self): 14 | return self.attributes.get("pull", True) 15 | 16 | @property 17 | def push(self): 18 | return self.attributes.get("push", True) 19 | 20 | @property 21 | def admin(self): 22 | return self.attributes.get("admin", False) 23 | 24 | 25 | class AIOGitHubAPIRepositoryCollaborator(AIOGitHubAPIBase): 26 | @property 27 | def login(self): 28 | return self.attributes.get("login", "") 29 | 30 | @property 31 | def id(self): 32 | return self.attributes.get("id", None) 33 | 34 | @property 35 | def node_id(self): 36 | return self.attributes.get("node_id", "") 37 | 38 | @property 39 | def avatar_url(self): 40 | return self.attributes.get("avatar_url", "") 41 | 42 | @property 43 | def gravatar_id(self): 44 | return self.attributes.get("gravatar_id", "") 45 | 46 | @property 47 | def url(self): 48 | return self.attributes.get("url", "") 49 | 50 | @property 51 | def html_url(self): 52 | return self.attributes.get("html_url", "") 53 | 54 | @property 55 | def followers_url(self): 56 | return self.attributes.get("followers_url", "") 57 | 58 | @property 59 | def following_url(self): 60 | return self.attributes.get("following_url", "") 61 | 62 | @property 63 | def gists_url(self): 64 | return self.attributes.get("gists_url", "") 65 | 66 | @property 67 | def starred_url(self): 68 | return self.attributes.get("starred_url", "") 69 | 70 | @property 71 | def subscriptions_url(self): 72 | return self.attributes.get("subscriptions_url", "") 73 | 74 | @property 75 | def organizations_url(self): 76 | return self.attributes.get("organizations_url", "") 77 | 78 | @property 79 | def repos_url(self): 80 | return self.attributes.get("repos_url", "") 81 | 82 | @property 83 | def events_url(self): 84 | return self.attributes.get("events_url", "") 85 | 86 | @property 87 | def received_events_url(self): 88 | return self.attributes.get("received_events_url", "") 89 | 90 | @property 91 | def type(self): 92 | return self.attributes.get("type", "") 93 | 94 | @property 95 | def site_admin(self): 96 | return self.attributes.get("site_admin", False) 97 | 98 | @property 99 | def permissions(self): 100 | return Permissions(self.attributes.get("permissions", {})) 101 | -------------------------------------------------------------------------------- /aiogithubapi/objects/repository/content.py: -------------------------------------------------------------------------------- 1 | """ 2 | AIOGitHubAPI: Repository Content 3 | 4 | https://developer.github.com/v3/repos/contents/ 5 | """ 6 | 7 | # pylint: disable=missing-docstring 8 | import base64 9 | 10 | from ..content import AIOGitHubAPIContentBase 11 | 12 | 13 | class AIOGitHubAPIRepositoryContent(AIOGitHubAPIContentBase): 14 | """Repository Conetent GitHub API implementation.""" 15 | 16 | @property 17 | def type(self): 18 | return self.attributes.get("type", "file") 19 | 20 | @property 21 | def encoding(self): 22 | return self.attributes.get("encoding") 23 | 24 | @property 25 | def name(self): 26 | return self.attributes.get("name") 27 | 28 | @property 29 | def path(self): 30 | return self.attributes.get("path") 31 | 32 | @property 33 | def content(self): 34 | return base64.b64decode(bytearray(self.attributes.get("content"), "utf-8")).decode() 35 | 36 | @property 37 | def download_url(self): 38 | return self.attributes.get("download_url") or self.attributes.get("browser_download_url") 39 | 40 | 41 | class AIOGitHubAPIRepositoryTreeContent(AIOGitHubAPIContentBase): 42 | """Repository Conetent GitHub API implementation.""" 43 | 44 | def __init__(self, attributes, repository, ref): 45 | """Initialize.""" 46 | self.attributes = attributes 47 | self.repository = repository 48 | self.ref = ref 49 | 50 | @property 51 | def full_path(self): 52 | return self.attributes.get("path") 53 | 54 | @property 55 | def is_directory(self): 56 | if self.attributes.get("type") == "tree": 57 | return True 58 | return False 59 | 60 | @property 61 | def path(self): 62 | path = "" 63 | if "/" in self.attributes.get("path"): 64 | path = self.attributes.get("path").split( 65 | f"/{self.attributes.get('path').split('/')[-1]}" 66 | )[0] 67 | return path 68 | 69 | @property 70 | def filename(self): 71 | filename = self.attributes.get("path") 72 | if "/" in self.attributes.get("path"): 73 | filename = self.attributes.get("path").split("/")[-1] 74 | return filename 75 | 76 | @property 77 | def url(self): 78 | return self.attributes.get("url") 79 | 80 | @property 81 | def download_url(self): 82 | return f"https://raw.githubusercontent.com/{self.repository}/{self.ref}/{self.attributes.get('path')}" 83 | -------------------------------------------------------------------------------- /aiogithubapi/objects/repository/issue/comment.py: -------------------------------------------------------------------------------- 1 | """ 2 | AIOGitHubAPI: Issue Comment 3 | 4 | https://developer.github.com/v3/issues/comments/ 5 | """ 6 | 7 | from ...base import AIOGitHubAPIBaseClient 8 | 9 | # pylint: disable=missing-docstring 10 | from ...content import AIOGitHubAPIContentBase 11 | from ...users.user import AIOGitHubAPIUsersUser 12 | 13 | 14 | class AIOGitHubAPIRepositoryIssueCommentUser(AIOGitHubAPIUsersUser): 15 | """Issue comment user GitHub API implementation.""" 16 | 17 | @property 18 | def login(self): 19 | return self.attributes.get("login") 20 | 21 | @property 22 | def id(self): 23 | return self.attributes.get("id") 24 | 25 | @property 26 | def avatar_url(self): 27 | return self.attributes.get("avatar_url") 28 | 29 | @property 30 | def html_url(self): 31 | return self.attributes.get("html_url") 32 | 33 | @property 34 | def type(self): 35 | return self.attributes.get("type") 36 | 37 | @property 38 | def site_admin(self): 39 | return self.attributes.get("site_admin") 40 | 41 | 42 | class AIOGitHubAPIRepositoryIssueComment(AIOGitHubAPIBaseClient): 43 | """Issue comment GitHub API implementation.""" 44 | 45 | def __init__(self, client: AIOGitHubAPIContentBase, attributes: dict, repository: str): 46 | """Initialize.""" 47 | super().__init__(client, attributes) 48 | self.repository = repository 49 | 50 | @property 51 | def html_url(self): 52 | return self.attributes.get("html_url") 53 | 54 | @property 55 | def id(self): 56 | return self.attributes.get("id") 57 | 58 | @property 59 | def created_at(self): 60 | return self.attributes.get("created_at") 61 | 62 | @property 63 | def updated_at(self): 64 | return self.attributes.get("updated_at") 65 | 66 | @property 67 | def body(self): 68 | return self.attributes.get("body") 69 | 70 | @property 71 | def user(self): 72 | return AIOGitHubAPIRepositoryIssueCommentUser(self.attributes.get("user", {})) 73 | 74 | async def update(self, body: str) -> None: 75 | """Updates an issue comment.""" 76 | _endpoint = f"/repos/{self.repository}/issues/comments/{self.id}" 77 | 78 | await self.client.post(endpoint=_endpoint, data={"body": body}, jsondata=True) 79 | -------------------------------------------------------------------------------- /aiogithubapi/objects/repository/release.py: -------------------------------------------------------------------------------- 1 | """ 2 | AIOGitHubAPI: Repository Release 3 | 4 | https://developer.github.com/v3/repos/releases/ 5 | """ 6 | 7 | # pylint: disable=missing-docstring 8 | from datetime import datetime 9 | 10 | from ...objects.base import AIOGitHubAPIBase 11 | from ...objects.repository.content import AIOGitHubAPIRepositoryContent 12 | 13 | 14 | class AIOGitHubAPIRepositoryRelease(AIOGitHubAPIBase): 15 | """Repository Release GitHub API implementation.""" 16 | 17 | @property 18 | def tag_name(self): 19 | return self.attributes.get("tag_name") 20 | 21 | @property 22 | def name(self): 23 | return self.attributes.get("name") 24 | 25 | @property 26 | def published_at(self): 27 | return datetime.strptime(self.attributes.get("published_at"), "%Y-%m-%dT%H:%M:%SZ") 28 | 29 | @property 30 | def draft(self): 31 | return self.attributes.get("draft") 32 | 33 | @property 34 | def prerelease(self): 35 | return self.attributes.get("prerelease") 36 | 37 | @property 38 | def assets(self): 39 | return [AIOGitHubAPIRepositoryContent(x) for x in self.attributes.get("assets", [])] 40 | -------------------------------------------------------------------------------- /aiogithubapi/objects/repository/traffic.py: -------------------------------------------------------------------------------- 1 | """ 2 | AIOGitHubAPI: Repository Traffic 3 | 4 | https://docs.github.com/en/rest/reference/repos#traffic 5 | """ 6 | 7 | from typing import Optional 8 | 9 | from aiohttp.hdrs import IF_NONE_MATCH 10 | 11 | from ..base import AIOGitHubAPIBaseClient 12 | from ..repos.traffic.clones import AIOGitHubAPIReposTrafficClones 13 | from ..repos.traffic.pageviews import AIOGitHubAPIReposTrafficPageviews 14 | 15 | 16 | class AIOGitHubAPIRepositoryTraffic(AIOGitHubAPIBaseClient): 17 | """Repository Release GitHub API implementation.""" 18 | 19 | @property 20 | def full_name(self) -> None: 21 | return self.attributes.get("full_name") 22 | 23 | async def get_views(self, etag: Optional[str] = None): 24 | _endpoint = f"/repos/{self.full_name}/traffic/views" 25 | _headers = {} 26 | if etag: 27 | _headers[IF_NONE_MATCH] = etag 28 | response = await self.client.get(endpoint=_endpoint, headers=_headers) 29 | return AIOGitHubAPIReposTrafficPageviews(response.data) 30 | 31 | async def get_clones(self, etag: Optional[str] = None) -> None: 32 | _endpoint = f"/repos/{self.full_name}/traffic/clones" 33 | _headers = {} 34 | if etag: 35 | _headers[IF_NONE_MATCH] = etag 36 | response = await self.client.get(endpoint=_endpoint, headers=_headers) 37 | return AIOGitHubAPIReposTrafficClones(response.data) 38 | -------------------------------------------------------------------------------- /aiogithubapi/objects/users/__init__.py: -------------------------------------------------------------------------------- 1 | """Deprecated""" 2 | -------------------------------------------------------------------------------- /documentation.md: -------------------------------------------------------------------------------- 1 | Note: When importing from this module, use \`from aiogithubapi import\` 2 | Anything not available directly in aiogithubapi (defined in \`\_\_init\_\_.py\`) 3 | is considered internal use only, and they can be removed without warning. 4 | 5 | ## Example usage of aiogithubapi 6 | 7 | ```python 8 | """Example usage of aiogithubapi.""" 9 | import asyncio 10 | from aiogithubapi import GitHubAPI, GitHubDeviceAPI 11 | 12 | CLIENT_ID = "" 13 | TOKEN = "" 14 | 15 | 16 | async def with_device_flow(): 17 | """Example usage of aiogithubapi with Device OAuth flow.""" 18 | async with GitHubDeviceAPI(client_id=CLIENT_ID) as device_login: 19 | registration = await device_login.register() 20 | print( 21 | f"Open https://github.com/login/device and enter: {registration.data.user_code}" 22 | ) 23 | activation = await device_login.activation(device_code=registration.data.device_code) 24 | 25 | async with GitHubAPI(token=activation.data.access_token, **{"client_name": "MyClient/1.2.3"}) as github: 26 | repository = await github.async_get_repository("ludeeus/aiogithubapi") 27 | print("Repository name:", repository.data.name) 28 | print("Repository description:", repository.data.description) 29 | 30 | 31 | async def with_token(): 32 | """Example usage of aiogithubapi with PAT.""" 33 | async with GitHubAPI(token=TOKEN, **{"client_name": "MyClient/1.2.3"}) as github: 34 | repository = await github.async_get_repository("ludeeus/aiogithubapi") 35 | print("Repository name:", repository.data.name) 36 | print("Repository description:", repository.data.description) 37 | 38 | 39 | asyncio.get_event_loop().run_until_complete(with_token()) 40 | ``` 41 | 42 | ## Usage notes 43 | 44 | - When constructing the client, you should pass a `client_name` parameter, or a user agent string. 45 | - Each response object has an `etag` attribute, which can be used to make subsequent requests. 46 | - If you pass an `etag` parameter, and the API returns a 304 Not Modified, the client will raise `GitHubNotModifiedException` 47 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "docs/aiogithubapi/" 3 | command = """ 4 | pip3 install -q poetry && 5 | poetry install && 6 | poetry run pdoc --html --output-dir docs aiogithubapi 7 | """ -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = "poetry.core.masonry.api" 3 | requires = ["poetry-core>=1.0.0"] 4 | 5 | [project] 6 | name = "aiogithubapi" 7 | readme = "README.md" 8 | license = { text = "MIT" } 9 | dynamic = ["readme", "classifiers", "dependencies", "optional-dependencies"] 10 | requires-python = ">=3.9" 11 | maintainers = [ 12 | { name = "Ludeeus", email = "joasoe@proton.me" }, 13 | ] 14 | [project.optional-dependencies] 15 | "deprecated-verify" = [ 16 | "sigstore <2", 17 | "securesystemslib <1", 18 | "setuptools >=60.0.0" 19 | ] 20 | 21 | [project.urls] 22 | "Repository" = "https://github.com/ludeeus/aiogithubapi" 23 | "Bug tracker" = "https://github.com/ludeeus/aiogithubapi/issues" 24 | 25 | [tool.poetry] 26 | name = "aiogithubapi" 27 | version = "0" 28 | description = "Asynchronous Python client for the GitHub API" 29 | authors = ["Ludeeus "] 30 | classifiers = [ 31 | "Intended Audience :: Developers", 32 | "Natural Language :: English", 33 | "Topic :: Software Development :: Libraries :: Python Modules", 34 | ] 35 | 36 | exclude = ['**/__pycache__'] 37 | include = ["aiogithubapi", "aiogithubapi.*", "LICENCE.md"] 38 | 39 | [tool.poetry.dependencies] 40 | python = "^3.9" 41 | aiohttp = "^3.8" 42 | async-timeout = "^5" 43 | backoff = "<3" 44 | 45 | 46 | [tool.poetry.group.dev.dependencies] 47 | aresponses = "^3.0.0" 48 | black = "^25.1.0" 49 | isort = "^6.0.1" 50 | mypy = "^1.13" 51 | pdoc3 = "^0.11.0" 52 | pylint = "^3.2.7" 53 | pytest = "^8.3.4" 54 | pytest-asyncio = "^0.26.0" 55 | pytest-cov = "^6.1.1" 56 | 57 | 58 | [tool.black] 59 | line-length = 100 60 | target-version = ['py38'] 61 | exclude = 'generated' 62 | 63 | [tool.isort] 64 | combine_as_imports = true 65 | force_sort_within_sections = true 66 | profile = "black" 67 | 68 | [tool.pylint.'MESSAGES CONTROL'] 69 | disable = "unsubscriptable-object,duplicate-code" 70 | 71 | [tool.coverage.run] 72 | source = ["aiogithubapi"] 73 | omit = ["setup.py", "example.py", "tests/*"] 74 | 75 | [tool.coverage.report] 76 | exclude_lines = ["if TYPE_CHECKING:", "if sys.version_info.major == "] -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | 3.9 -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Tests.""" 2 | -------------------------------------------------------------------------------- /tests/device/__init__.py: -------------------------------------------------------------------------------- 1 | """Tests for the device object.""" 2 | -------------------------------------------------------------------------------- /tests/device/test_construction.py: -------------------------------------------------------------------------------- 1 | """Construction tests for the device object.""" 2 | # pylint: disable=protected-access,missing-function-docstring 3 | from __future__ import annotations 4 | 5 | import aiohttp 6 | import pytest 7 | 8 | from aiogithubapi import GitHubClientKwarg, GitHubDeviceAPI 9 | 10 | from tests.common import CLIENT_ID 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_session_creation(): 15 | device = GitHubDeviceAPI(client_id=CLIENT_ID) 16 | assert device._session 17 | assert isinstance(device._session, aiohttp.ClientSession) 18 | 19 | assert not device._session.closed 20 | await device.close_session() 21 | assert device._session.closed 22 | 23 | 24 | @pytest.mark.asyncio 25 | async def test_session_pass(): 26 | async with aiohttp.ClientSession() as session: 27 | device = GitHubDeviceAPI(client_id=CLIENT_ID, session=session) 28 | assert device._session is session 29 | 30 | 31 | @pytest.mark.asyncio 32 | async def test_session_creation_with_enter(): 33 | async with GitHubDeviceAPI(client_id=CLIENT_ID) as device: 34 | assert device._session 35 | assert isinstance(device._session, aiohttp.ClientSession) 36 | assert not device._session.closed 37 | 38 | assert device._session.closed 39 | 40 | 41 | @pytest.mark.asyncio 42 | async def test_base_url(): 43 | async with GitHubDeviceAPI(client_id=CLIENT_ID) as device: 44 | assert device._client._base_request_data.base_url == "https://github.com" 45 | 46 | async with GitHubDeviceAPI( 47 | client_id=CLIENT_ID, **{GitHubClientKwarg.BASE_URL: "https://example.com"} 48 | ) as device: 49 | assert device._client._base_request_data.base_url == "https://example.com" 50 | -------------------------------------------------------------------------------- /tests/fixtures/emojis.json: -------------------------------------------------------------------------------- 1 | { 2 | "+1": "https://github.githubassets.com/images/icons/emoji/unicode/1f44d.png?v8", 3 | "beers": "https://github.githubassets.com/images/icons/emoji/unicode/1f37b.png?v8" 4 | } -------------------------------------------------------------------------------- /tests/fixtures/generic.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /tests/fixtures/generic.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ludeeus/aiogithubapi/484f1f4213778e71bbeb1352986d14b6e8bb0146/tests/fixtures/generic.txt -------------------------------------------------------------------------------- /tests/fixtures/graphql.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "repository": { 4 | "description": "This your first repo!" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /tests/fixtures/headers.json: -------------------------------------------------------------------------------- 1 | { 2 | "Server": "GitHub.com", 3 | "Date": "Mon, 1 Jan 1970 00:00:00 GMT", 4 | "Content-Type": "application/json; charset=utf-8", 5 | "Transfer-Encoding": "chunked", 6 | "Cache-Control": "private, max-age=60, s-maxage=60", 7 | "Vary": "Accept, Authorization, Cookie, X-GitHub-OTP", 8 | "Etag": "W/\"1234567890abcdefghijklmnopqrstuvwxyz\"", 9 | "X-OAuth-Scopes": "repo", 10 | "X-Accepted-OAuth-Scopes": "", 11 | "github-authentication-token-expiration": "1970-01-01 01:00:00 UTC", 12 | "X-GitHub-Media-Type": "github.v3; param=raw; format=json", 13 | "Link": "; rel=\"prev\", ; rel=\"next\", ; rel=\"last\"", 14 | "X-GitHub-Api-Version-Selected": "2022-11-28", 15 | "X-RateLimit-Limit": "5000", 16 | "X-RateLimit-Remaining": "4999", 17 | "X-RateLimit-Reset": "1", 18 | "X-RateLimit-Used": "1", 19 | "X-RateLimit-Resource": "core", 20 | "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset", 21 | "Access-Control-Allow-Origin": "*", 22 | "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", 23 | "X-Frame-Options": "deny", 24 | "X-Content-Type-Options": "nosniff", 25 | "X-XSS-Protection": "0", 26 | "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", 27 | "Content-Security-Policy": "default-src 'none'", 28 | "Content-Encoding": "gzip", 29 | "Permissions-Policy": "", 30 | "X-GitHub-Request-Id": "12A3:45BC:6D7890:12EF34:5678G901" 31 | } -------------------------------------------------------------------------------- /tests/fixtures/login_device_code.json: -------------------------------------------------------------------------------- 1 | { 2 | "device_code": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 3 | "user_code": "WDJB-MJHT", 4 | "verification_uri": "https://github.com/login/device", 5 | "expires_in": 900, 6 | "interval": 1 7 | } -------------------------------------------------------------------------------- /tests/fixtures/login_oauth_access_token.json: -------------------------------------------------------------------------------- 1 | { 2 | "access_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 3 | "token_type": "bearer", 4 | "scope": "user" 5 | } -------------------------------------------------------------------------------- /tests/fixtures/markdown.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum... -------------------------------------------------------------------------------- /tests/fixtures/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "verifiable_password_authentication": true, 3 | "ssh_key_fingerprints": { 4 | "SHA256_RSA": "nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8", 5 | "SHA256_DSA": "br9IjFspm1vxR3iA35FWE+4VTyz1hYVLIE2t1/CeyWQ", 6 | "SHA256_ECDSA": "p2QAMXNIC1TJYWeIOttrVc98/R1BUFWu3/LiyKgUfQM", 7 | "SHA256_ED25519": "+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU" 8 | }, 9 | "ssh_keys": [ 10 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl", 11 | "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=", 12 | "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==" 13 | ], 14 | "hooks": [ 15 | "192.30.252.0/22" 16 | ], 17 | "web": [ 18 | "192.30.252.0/22", 19 | "185.199.108.0/22" 20 | ], 21 | "api": [ 22 | "192.30.252.0/22", 23 | "185.199.108.0/22" 24 | ], 25 | "git": [ 26 | "192.30.252.0/22" 27 | ], 28 | "packages": [ 29 | "192.30.252.0/22" 30 | ], 31 | "pages": [ 32 | "192.30.252.153/32", 33 | "192.30.252.154/32" 34 | ], 35 | "importer": [ 36 | "54.158.161.132", 37 | "54.226.70.38" 38 | ], 39 | "actions": [ 40 | "13.64.0.0/16", 41 | "13.65.0.0/16" 42 | ], 43 | "dependabot": [ 44 | "54.158.161.132" 45 | ] 46 | } -------------------------------------------------------------------------------- /tests/fixtures/octocat.txt: -------------------------------------------------------------------------------- 1 | MMM. .MMM 2 | MMMMMMMMMMMMMMMMMMM 3 | MMMMMMMMMMMMMMMMMMM _________________ 4 | MMMMMMMMMMMMMMMMMMMMM | | 5 | MMMMMMMMMMMMMMMMMMMMMMM | Encourage flow. | 6 | MMMMMMMMMMMMMMMMMMMMMMMM |_ _____________| 7 | MMMM::- -:::::::- -::MMMM |/ 8 | MM~:~ 00~:::::~ 00~:~MM 9 | .. MMMMM::.00:::+:::.00::MMMMM .. 10 | .MM::::: ._. :::::MM. 11 | MMMM;:::::;MMMM 12 | -MM MMMMMMM 13 | ^ M+ MMMMMMMMM 14 | MMMMMMM MM MM MM 15 | MM MM MM MM 16 | MM MM MM MM 17 | .~~MM~MM~MM~MM~~. 18 | ~~~~MM:~MM~~~MM~:MM~~~~ 19 | ~~~~~~==~==~~~==~==~~~~~~ 20 | ~~~~~~==~==~==~==~~~~~~ 21 | :~==~==~==~==~~ -------------------------------------------------------------------------------- /tests/fixtures/organizations.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "login": "github", 4 | "id": 1, 5 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", 6 | "url": "https://api.github.com/orgs/github", 7 | "repos_url": "https://api.github.com/orgs/github/repos", 8 | "events_url": "https://api.github.com/orgs/github/events", 9 | "hooks_url": "https://api.github.com/orgs/github/hooks", 10 | "issues_url": "https://api.github.com/orgs/github/issues", 11 | "members_url": "https://api.github.com/orgs/github/members{/member}", 12 | "public_members_url": "https://api.github.com/orgs/github/public_members{/member}", 13 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 14 | "description": "A great organization" 15 | } 16 | ] -------------------------------------------------------------------------------- /tests/fixtures/orgs_octocat.json: -------------------------------------------------------------------------------- 1 | { 2 | "login": "github", 3 | "id": 1, 4 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", 5 | "url": "https://api.github.com/orgs/github", 6 | "repos_url": "https://api.github.com/orgs/github/repos", 7 | "events_url": "https://api.github.com/orgs/github/events", 8 | "hooks_url": "https://api.github.com/orgs/github/hooks", 9 | "issues_url": "https://api.github.com/orgs/github/issues", 10 | "members_url": "https://api.github.com/orgs/github/members{/member}", 11 | "public_members_url": "https://api.github.com/orgs/github/public_members{/member}", 12 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 13 | "description": "A great organization", 14 | "name": "github", 15 | "company": "GitHub", 16 | "blog": "https://github.com/blog", 17 | "location": "San Francisco", 18 | "email": "octocat@github.com", 19 | "twitter_username": "github", 20 | "is_verified": true, 21 | "has_organization_projects": true, 22 | "has_repository_projects": true, 23 | "public_repos": 2, 24 | "public_gists": 1, 25 | "followers": 20, 26 | "following": 0, 27 | "html_url": "https://github.com/octocat", 28 | "created_at": "2008-01-14T04:33:35Z", 29 | "updated_at": "2014-03-03T18:58:10Z", 30 | "type": "Organization", 31 | "total_private_repos": 100, 32 | "owned_private_repos": 100, 33 | "private_gists": 81, 34 | "disk_usage": 10000, 35 | "collaborators": 8, 36 | "billing_email": "mona@github.com", 37 | "plan": { 38 | "name": "Medium", 39 | "space": 400, 40 | "private_repos": 20, 41 | "filled_seats": 4, 42 | "seats": 5 43 | }, 44 | "default_repository_permission": "read", 45 | "members_can_create_repositories": true, 46 | "two_factor_requirement_enabled": true, 47 | "members_allowed_repository_creation_type": "all", 48 | "members_can_create_public_repositories": false, 49 | "members_can_create_private_repositories": false, 50 | "members_can_create_internal_repositories": false, 51 | "members_can_create_pages": true, 52 | "members_can_fork_private_repositories": false 53 | } -------------------------------------------------------------------------------- /tests/fixtures/orgs_octocat_projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "owner_url": "https://api.github.com/orgs/octocat", 4 | "url": "https://api.github.com/projects/1002605", 5 | "html_url": "https://github.com/orgs/api-playground/projects/1", 6 | "columns_url": "https://api.github.com/projects/1002605/columns", 7 | "id": 1002605, 8 | "node_id": "MDc6UHJvamVjdDEwMDI2MDU=", 9 | "name": "Organization Roadmap", 10 | "body": "High-level roadmap for the upcoming year.", 11 | "number": 1, 12 | "state": "open", 13 | "creator": { 14 | "login": "octocat", 15 | "id": 1, 16 | "node_id": "MDQ6VXNlcjE=", 17 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 18 | "gravatar_id": "", 19 | "url": "https://api.github.com/users/octocat", 20 | "html_url": "https://github.com/octocat", 21 | "followers_url": "https://api.github.com/users/octocat/followers", 22 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 23 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 24 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 25 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 26 | "organizations_url": "https://api.github.com/users/octocat/orgs", 27 | "repos_url": "https://api.github.com/users/octocat/repos", 28 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 29 | "received_events_url": "https://api.github.com/users/octocat/received_events", 30 | "type": "User", 31 | "site_admin": false 32 | }, 33 | "created_at": "2011-04-11T20:09:31Z", 34 | "updated_at": "2014-03-04T18:58:10Z" 35 | } 36 | ] -------------------------------------------------------------------------------- /tests/fixtures/projects_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "owner_url": "https://api.github.com/repos/api-playground/projects-test", 3 | "url": "https://api.github.com/projects/1002604", 4 | "html_url": "https://github.com/api-playground/projects-test/projects/1", 5 | "columns_url": "https://api.github.com/projects/1002604/columns", 6 | "id": 1002604, 7 | "node_id": "MDc6UHJvamVjdDEwMDI2MDQ=", 8 | "name": "Projects Documentation", 9 | "body": "Developer documentation project for the developer site.", 10 | "number": 1, 11 | "state": "open", 12 | "creator": { 13 | "login": "octocat", 14 | "id": 1, 15 | "node_id": "MDQ6VXNlcjE=", 16 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 17 | "gravatar_id": "", 18 | "url": "https://api.github.com/users/octocat", 19 | "html_url": "https://github.com/octocat", 20 | "followers_url": "https://api.github.com/users/octocat/followers", 21 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 22 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 23 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 24 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 25 | "organizations_url": "https://api.github.com/users/octocat/orgs", 26 | "repos_url": "https://api.github.com/users/octocat/repos", 27 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 28 | "received_events_url": "https://api.github.com/users/octocat/received_events", 29 | "type": "User", 30 | "site_admin": false 31 | }, 32 | "created_at": "2011-04-10T20:09:31Z", 33 | "updated_at": "2014-03-03T18:58:10Z" 34 | } -------------------------------------------------------------------------------- /tests/fixtures/rate_limit.json: -------------------------------------------------------------------------------- 1 | { 2 | "resources": { 3 | "core": { 4 | "limit": 5000, 5 | "used": 175, 6 | "remaining": 4825, 7 | "reset": 1642154468 8 | }, 9 | "search": { 10 | "limit": 30, 11 | "used": 0, 12 | "remaining": 30, 13 | "reset": 1642151840 14 | }, 15 | "graphql": { 16 | "limit": 5000, 17 | "used": 137, 18 | "remaining": 4863, 19 | "reset": 1642154460 20 | }, 21 | "integration_manifest": { 22 | "limit": 5000, 23 | "used": 0, 24 | "remaining": 5000, 25 | "reset": 1642155380 26 | }, 27 | "dependency_snapshots": { 28 | "limit": 5000, 29 | "used": 0, 30 | "remaining": 5000, 31 | "reset": 1642155380 32 | }, 33 | "source_import": { 34 | "limit": 100, 35 | "used": 0, 36 | "remaining": 100, 37 | "reset": 1642151840 38 | }, 39 | "code_scanning_upload": { 40 | "limit": 500, 41 | "used": 0, 42 | "remaining": 500, 43 | "reset": 1642155380 44 | }, 45 | "actions_runner_registration": { 46 | "limit": 10000, 47 | "used": 0, 48 | "remaining": 10000, 49 | "reset": 1642155380 50 | }, 51 | "scim": { 52 | "limit": 15000, 53 | "used": 0, 54 | "remaining": 15000, 55 | "reset": 1642155380 56 | } 57 | }, 58 | "rate": { 59 | "limit": 5000, 60 | "used": 175, 61 | "remaining": 4825, 62 | "reset": 1642154468 63 | } 64 | } -------------------------------------------------------------------------------- /tests/fixtures/repos_octocat_hello-world_contents.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "README", 4 | "path": "README", 5 | "sha": "980a0d5f19a64b4b30a87d4206aade58726b60e3", 6 | "size": 13, 7 | "url": "https://api.github.com/repos/octocat/Hello-World/contents/README?ref=master", 8 | "html_url": "https://github.com/octocat/Hello-World/blob/master/README", 9 | "git_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs/980a0d5f19a64b4b30a87d4206aade58726b60e3", 10 | "download_url": "https://raw.githubusercontent.com/octocat/Hello-World/master/README", 11 | "type": "file", 12 | "_links": { 13 | "self": "https://api.github.com/repos/octocat/Hello-World/contents/README?ref=master", 14 | "git": "https://api.github.com/repos/octocat/Hello-World/git/blobs/980a0d5f19a64b4b30a87d4206aade58726b60e3", 15 | "html": "https://github.com/octocat/Hello-World/blob/master/README" 16 | } 17 | } 18 | ] -------------------------------------------------------------------------------- /tests/fixtures/repos_octocat_hello-world_contents_readme.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "README", 3 | "path": "README", 4 | "sha": "980a0d5f19a64b4b30a87d4206aade58726b60e3", 5 | "size": 13, 6 | "url": "https://api.github.com/repos/octocat/Hello-World/contents/README?ref=master", 7 | "html_url": "https://github.com/octocat/Hello-World/blob/master/README", 8 | "git_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs/980a0d5f19a64b4b30a87d4206aade58726b60e3", 9 | "download_url": "https://raw.githubusercontent.com/octocat/Hello-World/master/README", 10 | "type": "file", 11 | "_links": { 12 | "self": "https://api.github.com/repos/octocat/Hello-World/contents/README?ref=master", 13 | "git": "https://api.github.com/repos/octocat/Hello-World/git/blobs/980a0d5f19a64b4b30a87d4206aade58726b60e3", 14 | "html": "https://github.com/octocat/Hello-World/blob/master/README" 15 | } 16 | } -------------------------------------------------------------------------------- /tests/fixtures/repos_octocat_hello-world_events.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "2", 4 | "type": "PushEvent", 5 | "actor": { 6 | "id": 1, 7 | "login": "ludeeus", 8 | "display_login": "ludeeus", 9 | "gravatar_id": "", 10 | "url": "https://api.github.com/users/ludeeus", 11 | "avatar_url": "https://avatars.githubusercontent.com/u/15093472?" 12 | }, 13 | "repo": { 14 | "id": 198505021, 15 | "name": "ludeeus/aiogithubapi", 16 | "url": "https://api.github.com/repos/ludeeus/aiogithubapi" 17 | }, 18 | "payload": { 19 | "push_id": 1, 20 | "size": 1, 21 | "distinct_size": 1, 22 | "ref": "refs/heads/main", 23 | "head": "c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc", 24 | "before": "c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc", 25 | "commits": [ 26 | { 27 | "sha": "c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc", 28 | "author": { 29 | "email": "ludeeus@ludeeus.dev", 30 | "name": "Ludeeus" 31 | }, 32 | "message": "Do awesome stuff", 33 | "distinct": true, 34 | "url": "https://api.github.com/repos/ludeeus/aiogithubapi/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc" 35 | } 36 | ] 37 | }, 38 | "public": true, 39 | "created_at": "9998-01-01T00:00:00Z" 40 | }, 41 | { 42 | "id": "1", 43 | "type": "PushEvent", 44 | "actor": { 45 | "id": 1, 46 | "login": "ludeeus", 47 | "display_login": "ludeeus", 48 | "gravatar_id": "", 49 | "url": "https://api.github.com/users/ludeeus", 50 | "avatar_url": "https://avatars.githubusercontent.com/u/15093472?" 51 | }, 52 | "repo": { 53 | "id": 198505021, 54 | "name": "ludeeus/aiogithubapi", 55 | "url": "https://api.github.com/repos/ludeeus/aiogithubapi" 56 | }, 57 | "payload": { 58 | "push_id": 1, 59 | "size": 1, 60 | "distinct_size": 1, 61 | "ref": "refs/heads/main", 62 | "head": "c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc", 63 | "before": "c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc", 64 | "commits": [ 65 | { 66 | "sha": "c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc", 67 | "author": { 68 | "email": "ludeeus@ludeeus.dev", 69 | "name": "Ludeeus" 70 | }, 71 | "message": "Do awesome stuff", 72 | "distinct": true, 73 | "url": "https://api.github.com/repos/ludeeus/aiogithubapi/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc" 74 | } 75 | ] 76 | }, 77 | "public": true, 78 | "created_at": "9999-01-01T00:00:00Z" 79 | } 80 | ] -------------------------------------------------------------------------------- /tests/fixtures/repos_octocat_hello-world_git_trees_master.json: -------------------------------------------------------------------------------- 1 | { 2 | "sha": "7fd1a60b01f91b314f59955a4e4d4e80d8edf11d", 3 | "url": "https://api.github.com/repos/octocat/Hello-World/git/trees/7fd1a60b01f91b314f59955a4e4d4e80d8edf11d", 4 | "tree": [ 5 | { 6 | "path": "README", 7 | "mode": "100644", 8 | "type": "blob", 9 | "sha": "980a0d5f19a64b4b30a87d4206aade58726b60e3", 10 | "size": 13, 11 | "url": "https://api.github.com/repos/octocat/Hello-World/git/blobs/980a0d5f19a64b4b30a87d4206aade58726b60e3" 12 | } 13 | ], 14 | "truncated": false 15 | } -------------------------------------------------------------------------------- /tests/fixtures/repos_octocat_hello-world_issues_1_comments.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "url": "https://api.github.com/repos/ludeeus/aiogithubapi/issues/comments/567236951", 4 | "html_url": "https://github.com/ludeeus/aiogithubapi/issues/1#issuecomment-567236951", 5 | "issue_url": "https://api.github.com/repos/ludeeus/aiogithubapi/issues/1", 6 | "id": 567236951, 7 | "node_id": "MDEyOklzc3VlQ29tbWVudDU2NzIzNjk1MQ==", 8 | "user": { 9 | "login": "ludeeus", 10 | "id": 15093472, 11 | "node_id": "MDQ6VXNlcjE1MDkzNDcy", 12 | "avatar_url": "https://avatars.githubusercontent.com/u/15093472?v=4", 13 | "gravatar_id": "", 14 | "url": "https://api.github.com/users/ludeeus", 15 | "html_url": "https://github.com/ludeeus", 16 | "followers_url": "https://api.github.com/users/ludeeus/followers", 17 | "following_url": "https://api.github.com/users/ludeeus/following{/other_user}", 18 | "gists_url": "https://api.github.com/users/ludeeus/gists{/gist_id}", 19 | "starred_url": "https://api.github.com/users/ludeeus/starred{/owner}{/repo}", 20 | "subscriptions_url": "https://api.github.com/users/ludeeus/subscriptions", 21 | "organizations_url": "https://api.github.com/users/ludeeus/orgs", 22 | "repos_url": "https://api.github.com/users/ludeeus/repos", 23 | "events_url": "https://api.github.com/users/ludeeus/events{/privacy}", 24 | "received_events_url": "https://api.github.com/users/ludeeus/received_events", 25 | "type": "User", 26 | "site_admin": false 27 | }, 28 | "created_at": "2019-12-18T22:15:37Z", 29 | "updated_at": "2019-12-18T22:15:37Z", 30 | "author_association": "OWNER", 31 | "body": "No logs, no issue!", 32 | "performed_via_github_app": null 33 | } 34 | ] -------------------------------------------------------------------------------- /tests/fixtures/repos_octocat_hello-world_projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "owner_url": "https://api.github.com/repos/api-playground/projects-test", 4 | "url": "https://api.github.com/projects/1002604", 5 | "html_url": "https://github.com/api-playground/projects-test/projects/1", 6 | "columns_url": "https://api.github.com/projects/1002604/columns", 7 | "id": 1002604, 8 | "node_id": "MDc6UHJvamVjdDEwMDI2MDQ=", 9 | "name": "Projects Documentation", 10 | "body": "Developer documentation project for the developer site.", 11 | "number": 1, 12 | "state": "open", 13 | "creator": { 14 | "login": "octocat", 15 | "id": 1, 16 | "node_id": "MDQ6VXNlcjE=", 17 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 18 | "gravatar_id": "", 19 | "url": "https://api.github.com/users/octocat", 20 | "html_url": "https://github.com/octocat", 21 | "followers_url": "https://api.github.com/users/octocat/followers", 22 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 23 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 24 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 25 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 26 | "organizations_url": "https://api.github.com/users/octocat/orgs", 27 | "repos_url": "https://api.github.com/users/octocat/repos", 28 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 29 | "received_events_url": "https://api.github.com/users/octocat/received_events", 30 | "type": "User", 31 | "site_admin": false 32 | }, 33 | "created_at": "2011-04-10T20:09:31Z", 34 | "updated_at": "2014-03-03T18:58:10Z" 35 | } 36 | ] -------------------------------------------------------------------------------- /tests/fixtures/repos_octocat_hello-world_readme_.txt: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /tests/fixtures/repos_octocat_hello-world_readme_test.txt: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /tests/fixtures/repos_octocat_hello-world_releases.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "url": "https://api.github.com/repos/octocat/hello-world/releases/48037472", 4 | "assets_url": "https://api.github.com/repos/octocat/hello-world/releases/48037472/assets", 5 | "upload_url": "https://uploads.github.com/repos/octocat/hello-world/releases/48037472/assets{?name,label}", 6 | "html_url": "https://github.com/octocat/hello-world/releases/tag/untagged-ccd1f5c2718d4956471f", 7 | "id": 48037472, 8 | "author": { 9 | "login": "github-actions[bot]", 10 | "id": 41898282, 11 | "node_id": "MDM6Qm90NDE4OTgyODI=", 12 | "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", 13 | "gravatar_id": "", 14 | "url": "https://api.github.com/users/github-actions%5Bbot%5D", 15 | "html_url": "https://github.com/apps/github-actions", 16 | "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", 17 | "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", 18 | "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", 19 | "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", 20 | "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", 21 | "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", 22 | "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", 23 | "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", 24 | "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", 25 | "type": "Bot", 26 | "site_admin": false 27 | }, 28 | "reactions": {}, 29 | "node_id": "MDc6UmVsZWFzZTQ4MDM3NDcy", 30 | "tag_name": "1", 31 | "target_commitish": "main", 32 | "name": "First release", 33 | "draft": true, 34 | "prerelease": false, 35 | "created_at": "2021-08-18T11:28:23Z", 36 | "published_at": null, 37 | "assets": [ 38 | { 39 | "uploader": {} 40 | } 41 | ], 42 | "tarball_url": null, 43 | "zipball_url": null, 44 | "body": "Changes", 45 | "mentions_count": 2 46 | } 47 | ] -------------------------------------------------------------------------------- /tests/fixtures/repos_octocat_hello-world_releases_latest.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://api.github.com/repos/octocat/hello-world/releases/48037472", 3 | "assets_url": "https://api.github.com/repos/octocat/hello-world/releases/48037472/assets", 4 | "upload_url": "https://uploads.github.com/repos/octocat/hello-world/releases/48037472/assets{?name,label}", 5 | "html_url": "https://github.com/octocat/hello-world/releases/tag/untagged-ccd1f5c2718d4956471f", 6 | "id": 48037472, 7 | "author": { 8 | "login": "github-actions[bot]", 9 | "id": 41898282, 10 | "node_id": "MDM6Qm90NDE4OTgyODI=", 11 | "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", 12 | "gravatar_id": "", 13 | "url": "https://api.github.com/users/github-actions%5Bbot%5D", 14 | "html_url": "https://github.com/apps/github-actions", 15 | "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", 16 | "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", 17 | "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", 18 | "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", 19 | "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", 20 | "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", 21 | "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", 22 | "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", 23 | "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", 24 | "type": "Bot", 25 | "site_admin": false 26 | }, 27 | "reactions": {}, 28 | "node_id": "MDc6UmVsZWFzZTQ4MDM3NDcy", 29 | "tag_name": "1", 30 | "target_commitish": "main", 31 | "name": "First release", 32 | "draft": true, 33 | "prerelease": false, 34 | "created_at": "2021-08-18T11:28:23Z", 35 | "published_at": null, 36 | "assets": [ 37 | { 38 | "uploader": {} 39 | } 40 | ], 41 | "tarball_url": null, 42 | "zipball_url": null, 43 | "body": "Changes", 44 | "mentions_count": 2 45 | } -------------------------------------------------------------------------------- /tests/fixtures/repos_octocat_hello-world_tags.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "v0.1", 4 | "commit": { 5 | "sha": "c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc", 6 | "url": "https://api.github.com/repos/octocat/Hello-World/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc" 7 | }, 8 | "zipball_url": "https://github.com/octocat/Hello-World/zipball/v0.1", 9 | "tarball_url": "https://github.com/octocat/Hello-World/tarball/v0.1", 10 | "node_id": "MDQ6VXNlcjE=" 11 | } 12 | ] -------------------------------------------------------------------------------- /tests/fixtures/repos_octocat_hello-world_traffic_clones.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "uniques": 1, 4 | "clones": [ 5 | { 6 | "timestamp": "1970-01-01T00:00:00Z", 7 | "count": 1, 8 | "uniques": 1 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /tests/fixtures/repos_octocat_hello-world_traffic_views.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "uniques": 1, 4 | "views": [ 5 | { 6 | "timestamp": "1970-01-01T00:00:00Z", 7 | "count": 1, 8 | "uniques": 1 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /tests/fixtures/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "login": "octocat", 3 | "id": 583231, 4 | "node_id": "MDQ6VXNlcjU4MzIzMQ==", 5 | "avatar_url": "https://avatars.githubusercontent.com/u/583231?v=4", 6 | "gravatar_id": "", 7 | "url": "https://api.github.com/users/octocat", 8 | "html_url": "https://github.com/octocat", 9 | "followers_url": "https://api.github.com/users/octocat/followers", 10 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 11 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 12 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 13 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 14 | "organizations_url": "https://api.github.com/users/octocat/orgs", 15 | "repos_url": "https://api.github.com/users/octocat/repos", 16 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 17 | "received_events_url": "https://api.github.com/users/octocat/received_events", 18 | "type": "User", 19 | "site_admin": false, 20 | "name": "The Octocat", 21 | "company": "@github", 22 | "blog": "https://github.blog", 23 | "location": "San Francisco", 24 | "email": "octocat@github.com", 25 | "hireable": null, 26 | "bio": null, 27 | "twitter_username": null, 28 | "public_repos": 8, 29 | "public_gists": 8, 30 | "plan": { 31 | "private_repos": 0 32 | }, 33 | "followers": 3928, 34 | "following": 9, 35 | "created_at": "2011-01-25T18:44:36Z", 36 | "updated_at": "2021-07-22T14:27:29Z" 37 | } -------------------------------------------------------------------------------- /tests/fixtures/user_orgs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "login": "github", 4 | "id": 1, 5 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", 6 | "url": "https://api.github.com/orgs/github", 7 | "repos_url": "https://api.github.com/orgs/github/repos", 8 | "events_url": "https://api.github.com/orgs/github/events", 9 | "hooks_url": "https://api.github.com/orgs/github/hooks", 10 | "issues_url": "https://api.github.com/orgs/github/issues", 11 | "members_url": "https://api.github.com/orgs/github/members{/member}", 12 | "public_members_url": "https://api.github.com/orgs/github/public_members{/member}", 13 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 14 | "description": "A great organization" 15 | } 16 | ] -------------------------------------------------------------------------------- /tests/fixtures/users_octocat.json: -------------------------------------------------------------------------------- 1 | { 2 | "login": "octocat", 3 | "id": 583231, 4 | "node_id": "MDQ6VXNlcjU4MzIzMQ==", 5 | "avatar_url": "https://avatars.githubusercontent.com/u/583231?v=4", 6 | "gravatar_id": "", 7 | "url": "https://api.github.com/users/octocat", 8 | "html_url": "https://github.com/octocat", 9 | "followers_url": "https://api.github.com/users/octocat/followers", 10 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 11 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 12 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 13 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 14 | "organizations_url": "https://api.github.com/users/octocat/orgs", 15 | "repos_url": "https://api.github.com/users/octocat/repos", 16 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 17 | "received_events_url": "https://api.github.com/users/octocat/received_events", 18 | "type": "User", 19 | "site_admin": false, 20 | "name": "The Octocat", 21 | "company": "@github", 22 | "blog": "https://github.blog", 23 | "location": "San Francisco", 24 | "email": "octocat@github.com", 25 | "hireable": null, 26 | "bio": null, 27 | "twitter_username": null, 28 | "public_repos": 8, 29 | "public_gists": 8, 30 | "followers": 3928, 31 | "following": 9, 32 | "created_at": "2011-01-25T18:44:36Z", 33 | "updated_at": "2021-07-22T14:27:29Z" 34 | } -------------------------------------------------------------------------------- /tests/fixtures/users_octocat_orgs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "login": "github", 4 | "id": 1, 5 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", 6 | "url": "https://api.github.com/orgs/github", 7 | "repos_url": "https://api.github.com/orgs/github/repos", 8 | "events_url": "https://api.github.com/orgs/github/events", 9 | "hooks_url": "https://api.github.com/orgs/github/hooks", 10 | "issues_url": "https://api.github.com/orgs/github/issues", 11 | "members_url": "https://api.github.com/orgs/github/members{/member}", 12 | "public_members_url": "https://api.github.com/orgs/github/public_members{/member}", 13 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 14 | "description": "A great organization" 15 | } 16 | ] -------------------------------------------------------------------------------- /tests/fixtures/users_octocat_projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "owner_url": "https://api.github.com/users/octocat", 4 | "url": "https://api.github.com/projects/1002603", 5 | "html_url": "https://github.com/users/octocat/projects/1", 6 | "columns_url": "https://api.github.com/projects/1002603/columns", 7 | "id": 1002603, 8 | "node_id": "MDc6UHJvamVjdDEwMDI2MDM=", 9 | "name": "My Projects", 10 | "body": "A board to manage my personal projects.", 11 | "number": 1, 12 | "state": "open", 13 | "creator": { 14 | "login": "octocat", 15 | "id": 1, 16 | "node_id": "MDQ6VXNlcjE=", 17 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 18 | "gravatar_id": "", 19 | "url": "https://api.github.com/users/octocat", 20 | "html_url": "https://github.com/octocat", 21 | "followers_url": "https://api.github.com/users/octocat/followers", 22 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 23 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 24 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 25 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 26 | "organizations_url": "https://api.github.com/users/octocat/orgs", 27 | "repos_url": "https://api.github.com/users/octocat/repos", 28 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 29 | "received_events_url": "https://api.github.com/users/octocat/received_events", 30 | "type": "User", 31 | "site_admin": false 32 | }, 33 | "created_at": "2011-04-10T20:09:31Z", 34 | "updated_at": "2014-03-03T18:58:10Z" 35 | } 36 | ] -------------------------------------------------------------------------------- /tests/fixtures/versions.json: -------------------------------------------------------------------------------- 1 | [ 2 | "2022-11-28" 3 | ] -------------------------------------------------------------------------------- /tests/fixtures/zen.txt: -------------------------------------------------------------------------------- 1 | Beautiful is better than ugly. -------------------------------------------------------------------------------- /tests/github/__init__.py: -------------------------------------------------------------------------------- 1 | """Tests for the base class.""" 2 | -------------------------------------------------------------------------------- /tests/github/test_construction.py: -------------------------------------------------------------------------------- 1 | """Construction tests for the main class.""" 2 | # pylint: disable=protected-access,missing-function-docstring 3 | from __future__ import annotations 4 | 5 | from unittest.mock import patch 6 | 7 | import aiohttp 8 | import pytest 9 | 10 | from aiogithubapi import GitHubAPI 11 | 12 | from tests.common import TOKEN 13 | 14 | 15 | @pytest.mark.asyncio 16 | async def test_session_creation(): 17 | github = GitHubAPI() 18 | assert github._session 19 | assert isinstance(github._session, aiohttp.ClientSession) 20 | 21 | assert not github._session.closed 22 | await github.close_session() 23 | assert github._session.closed 24 | 25 | 26 | @pytest.mark.asyncio 27 | async def test_session_pass(): 28 | async with aiohttp.ClientSession() as session: 29 | github = GitHubAPI(session=session) 30 | assert github._session is session 31 | 32 | 33 | @pytest.mark.asyncio 34 | async def test_session_creation_with_enter(): 35 | async with GitHubAPI() as github: 36 | assert github._session 37 | assert isinstance(github._session, aiohttp.ClientSession) 38 | assert not github._session.closed 39 | 40 | assert github._session.closed 41 | 42 | 43 | @pytest.mark.asyncio 44 | async def test_token(): 45 | assert GitHubAPI()._client._base_request_data.token is None 46 | assert GitHubAPI(token=TOKEN)._client._base_request_data.token == TOKEN 47 | with patch("os.environ", {"GITHUB_TOKEN": TOKEN}): 48 | assert GitHubAPI()._client._base_request_data.token == TOKEN 49 | 50 | 51 | @pytest.mark.asyncio 52 | async def test_api_version(): 53 | assert GitHubAPI()._client._base_request_data.token is None 54 | assert GitHubAPI(token=TOKEN, api_version="3000-01-01")._client._base_request_data.api_version == "3000-01-01" 55 | -------------------------------------------------------------------------------- /tests/github/test_emojis.py: -------------------------------------------------------------------------------- 1 | """Test emojis.""" 2 | # pylint: disable=missing-docstring 3 | import pytest 4 | 5 | from aiogithubapi import GitHubAPI 6 | 7 | from tests.common import MockedRequests 8 | 9 | 10 | @pytest.mark.asyncio 11 | async def test_emojis(github_api: GitHubAPI, mock_requests: MockedRequests): 12 | response = await github_api.emojis() 13 | assert response.status == 200 14 | assert isinstance(response.data, dict) 15 | assert "beers" in response.data 16 | assert mock_requests.called == 1 17 | assert mock_requests.last_request["url"] == "https://api.github.com/emojis" 18 | -------------------------------------------------------------------------------- /tests/github/test_generic.py: -------------------------------------------------------------------------------- 1 | """Test generic api call.""" 2 | # pylint: disable=missing-docstring 3 | import pytest 4 | 5 | from aiogithubapi import GitHubAPI, GitHubRequestKwarg 6 | 7 | from tests.common import MockedRequests 8 | 9 | 10 | @pytest.mark.asyncio 11 | async def test_generic_get(github_api: GitHubAPI, mock_requests: MockedRequests): 12 | response = await github_api.generic("/generic") 13 | assert response.status == 200 14 | assert response.data == {} 15 | assert mock_requests.called == 1 16 | assert mock_requests.last_request["method"] == "get" 17 | assert mock_requests.last_request["url"] == "https://api.github.com/generic" 18 | 19 | 20 | @pytest.mark.asyncio 21 | async def test_generic_post(github_api: GitHubAPI, mock_requests: MockedRequests): 22 | response = await github_api.generic("/generic", **{GitHubRequestKwarg.METHOD: "POST"}) 23 | assert response.status == 200 24 | assert response.data == {} 25 | assert mock_requests.called == 1 26 | assert mock_requests.last_request["method"] == "post" 27 | assert mock_requests.last_request["url"] == "https://api.github.com/generic" 28 | assert mock_requests.last_request["data"] is None 29 | assert "json" not in mock_requests.last_request 30 | 31 | 32 | @pytest.mark.asyncio 33 | async def test_generic_post_with_data(github_api: GitHubAPI, mock_requests: MockedRequests): 34 | response = await github_api.generic( 35 | "/generic", {"test": "data"}, **{GitHubRequestKwarg.METHOD: "POST"} 36 | ) 37 | assert response.status == 200 38 | assert response.data == {} 39 | assert mock_requests.called == 1 40 | assert mock_requests.last_request["method"] == "post" 41 | assert mock_requests.last_request["url"] == "https://api.github.com/generic" 42 | assert mock_requests.last_request["json"] == {"test": "data"} 43 | -------------------------------------------------------------------------------- /tests/github/test_graphql.py: -------------------------------------------------------------------------------- 1 | """Test graphql.""" 2 | # pylint: disable=missing-docstring 3 | import pytest 4 | 5 | from aiogithubapi import GitHubAPI, GitHubGraphQLException 6 | from aiogithubapi.graphql_examples.repository_tags import EXAMPLE_QUERY 7 | 8 | from tests.common import MockedRequests, MockResponse 9 | 10 | 11 | @pytest.mark.asyncio 12 | async def test_graphql(github_api: GitHubAPI, mock_requests: MockedRequests): 13 | response = await github_api.graphql(EXAMPLE_QUERY) 14 | assert response.status == 200 15 | assert isinstance(response.data, dict) 16 | assert mock_requests.called == 1 17 | assert mock_requests.last_request["url"] == "https://api.github.com/graphql" 18 | 19 | 20 | @pytest.mark.asyncio 21 | async def test_graphql_error( 22 | github_api: GitHubAPI, 23 | mock_requests: MockedRequests, 24 | mock_response: MockResponse, 25 | ): 26 | mock_response.mock_data = {"errors": [{"message": "test"}]} 27 | with pytest.raises(GitHubGraphQLException, match="test"): 28 | await github_api.graphql(EXAMPLE_QUERY) 29 | 30 | assert mock_requests.called == 1 31 | assert mock_requests.last_request["url"] == "https://api.github.com/graphql" 32 | -------------------------------------------------------------------------------- /tests/github/test_markdown.py: -------------------------------------------------------------------------------- 1 | """Test generic api call.""" 2 | # pylint: disable=missing-docstring 3 | import pytest 4 | 5 | from aiogithubapi import GitHubAPI 6 | 7 | from tests.common import ( 8 | HEADERS_TEXT, 9 | TEST_REPOSITORY_NAME, 10 | MockedRequests, 11 | MockResponse, 12 | ) 13 | 14 | 15 | @pytest.mark.asyncio 16 | async def test_markdown_base( 17 | github_api: GitHubAPI, 18 | mock_requests: MockedRequests, 19 | mock_response: MockResponse, 20 | ): 21 | mock_response.mock_headers = HEADERS_TEXT 22 | response = await github_api.markdown("Lorem ipsum...") 23 | assert response.status == 200 24 | assert response.data == "Lorem ipsum..." 25 | assert mock_requests.called == 1 26 | assert mock_requests.last_request["method"] == "post" 27 | assert mock_requests.last_request["url"] == "https://api.github.com/markdown" 28 | assert mock_requests.last_request["json"] == { 29 | "context": None, 30 | "mode": None, 31 | "text": "Lorem ipsum...", 32 | } 33 | 34 | 35 | @pytest.mark.asyncio 36 | async def test_markdown_mode( 37 | github_api: GitHubAPI, 38 | mock_requests: MockedRequests, 39 | mock_response: MockResponse, 40 | ): 41 | mock_response.mock_headers = HEADERS_TEXT 42 | response = await github_api.markdown("Lorem ipsum...", mode="gfm") 43 | assert response.status == 200 44 | assert response.data == "Lorem ipsum..." 45 | assert mock_requests.called == 1 46 | assert mock_requests.last_request["method"] == "post" 47 | assert mock_requests.last_request["url"] == "https://api.github.com/markdown" 48 | assert mock_requests.last_request["json"] == { 49 | "context": None, 50 | "mode": "gfm", 51 | "text": "Lorem ipsum...", 52 | } 53 | 54 | 55 | @pytest.mark.asyncio 56 | async def test_markdown_context( 57 | github_api: GitHubAPI, 58 | mock_requests: MockedRequests, 59 | mock_response: MockResponse, 60 | ): 61 | mock_response.mock_headers = HEADERS_TEXT 62 | response = await github_api.markdown("Lorem ipsum...", context=TEST_REPOSITORY_NAME) 63 | assert response.status == 200 64 | assert response.data == "Lorem ipsum..." 65 | assert mock_requests.called == 1 66 | assert mock_requests.last_request["method"] == "post" 67 | assert mock_requests.last_request["url"] == "https://api.github.com/markdown" 68 | assert mock_requests.last_request["json"] == { 69 | "context": TEST_REPOSITORY_NAME, 70 | "mode": "gfm", 71 | "text": "Lorem ipsum...", 72 | } 73 | -------------------------------------------------------------------------------- /tests/github/test_meta.py: -------------------------------------------------------------------------------- 1 | """Test meta.""" 2 | # pylint: disable=missing-docstring 3 | import pytest 4 | 5 | from aiogithubapi import GitHubAPI 6 | 7 | from tests.common import MockedRequests 8 | 9 | 10 | @pytest.mark.asyncio 11 | async def test_meta(github_api: GitHubAPI, mock_requests: MockedRequests): 12 | response = await github_api.meta() 13 | assert response.status == 200 14 | assert response.data.verifiable_password_authentication 15 | assert ( 16 | response.data.ssh_key_fingerprints.sha256_ed25519 17 | == "+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU" 18 | ) 19 | assert mock_requests.called == 1 20 | assert mock_requests.last_request["url"] == "https://api.github.com/meta" 21 | -------------------------------------------------------------------------------- /tests/github/test_octocat.py: -------------------------------------------------------------------------------- 1 | """Test octocat.""" 2 | # pylint: disable=missing-docstring 3 | from aiohttp.hdrs import CONTENT_TYPE 4 | import pytest 5 | 6 | from aiogithubapi import GitHubAPI 7 | from aiogithubapi.const import HttpContentType 8 | 9 | from tests.common import MockedRequests, MockResponse 10 | 11 | 12 | @pytest.mark.asyncio 13 | async def test_octocat( 14 | github_api: GitHubAPI, 15 | mock_response: MockResponse, 16 | mock_requests: MockedRequests, 17 | ): 18 | mock_response.mock_headers = {CONTENT_TYPE: HttpContentType.TEXT_PLAIN} 19 | response = await github_api.octocat() 20 | assert response.status == 200 21 | assert "Encourage flow." in response.data 22 | assert mock_requests.called == 1 23 | assert mock_requests.last_request["url"] == "https://api.github.com/octocat" 24 | -------------------------------------------------------------------------------- /tests/github/test_rate_limit.py: -------------------------------------------------------------------------------- 1 | """Test rate_limit.""" 2 | # pylint: disable=missing-docstring 3 | import pytest 4 | 5 | from aiogithubapi import GitHubAPI 6 | 7 | from tests.common import MockedRequests 8 | 9 | 10 | @pytest.mark.asyncio 11 | async def test_rate_limit(github_api: GitHubAPI, mock_requests: MockedRequests): 12 | response = await github_api.rate_limit() 13 | assert response.status == 200 14 | assert response.data.resources.core.limit == 5000 15 | assert mock_requests.called == 1 16 | assert mock_requests.last_request["url"] == "https://api.github.com/rate_limit" 17 | -------------------------------------------------------------------------------- /tests/github/test_versions.py: -------------------------------------------------------------------------------- 1 | """Test zen.""" 2 | # pylint: disable=missing-docstring 3 | from aiohttp.hdrs import CONTENT_TYPE 4 | import pytest 5 | 6 | from aiogithubapi import GitHubAPI 7 | from aiogithubapi.const import HttpContentType 8 | 9 | from tests.common import MockedRequests, MockResponse 10 | 11 | 12 | @pytest.mark.asyncio 13 | async def test_versions( 14 | github_api: GitHubAPI, 15 | mock_response: MockResponse, 16 | mock_requests: MockedRequests, 17 | ): 18 | response = await github_api.versions() 19 | assert response.status == 200 20 | assert response.data[0] == "2022-11-28" 21 | assert response.headers.x_github_api_version_selected == "2022-11-28" 22 | assert mock_requests.called == 1 23 | assert mock_requests.last_request["url"] == "https://api.github.com/versions" 24 | -------------------------------------------------------------------------------- /tests/github/test_zen.py: -------------------------------------------------------------------------------- 1 | """Test zen.""" 2 | # pylint: disable=missing-docstring 3 | from aiohttp.hdrs import CONTENT_TYPE 4 | import pytest 5 | 6 | from aiogithubapi import GitHubAPI 7 | from aiogithubapi.const import HttpContentType 8 | 9 | from tests.common import MockedRequests, MockResponse 10 | 11 | 12 | @pytest.mark.asyncio 13 | async def test_zen( 14 | github_api: GitHubAPI, 15 | mock_response: MockResponse, 16 | mock_requests: MockedRequests, 17 | ): 18 | mock_response.mock_headers = {CONTENT_TYPE: HttpContentType.TEXT_PLAIN} 19 | response = await github_api.zen() 20 | assert response.status == 200 21 | assert response.data == "Beautiful is better than ugly." 22 | assert mock_requests.called == 1 23 | assert mock_requests.last_request["url"] == "https://api.github.com/zen" 24 | -------------------------------------------------------------------------------- /tests/legacy/device/test_device_login.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from unittest.mock import AsyncMock 3 | 4 | import aiohttp 5 | import pytest 6 | 7 | from aiogithubapi import AIOGitHubAPIException, GitHubDevice, GitHubDeviceAPI 8 | 9 | from tests.common import CLIENT_ID, load_fixture 10 | from tests.conftest import client_session 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_session(): 15 | async with aiohttp.ClientSession() as session: 16 | device = GitHubDevice("xxxxx", "", session) 17 | assert device.session == session 18 | 19 | async with GitHubDevice("xxxxx") as github: 20 | assert github.session is not None 21 | 22 | 23 | @pytest.mark.asyncio 24 | async def test_device(mock_response, github_device: GitHubDevice, asyncio_sleep: AsyncMock): 25 | device = await github_device.async_register_device() 26 | assert device.user_code == "WDJB-MJHT" 27 | 28 | mock_response.mock_data_list = [ 29 | { 30 | "error": "authorization_pending", 31 | "error_description": "Pending user authorization", 32 | }, 33 | { 34 | "access_token": "e72e16c7e42f292c6912e7710c838347ae178b4a", 35 | "token_type": "bearer", 36 | "scope": "user", 37 | }, 38 | ] 39 | 40 | activation = await github_device.async_device_activation() 41 | assert asyncio_sleep.call_count == 1 42 | assert asyncio_sleep.call_args[0][0] == 1 43 | assert activation.access_token == "e72e16c7e42f292c6912e7710c838347ae178b4a" 44 | 45 | 46 | @pytest.mark.asyncio 47 | async def test_no_user_response(github_device: GitHubDevice): 48 | device = await github_device.async_register_device() 49 | assert device.user_code == "WDJB-MJHT" 50 | 51 | with pytest.raises(AIOGitHubAPIException, match="User took too long to enter key"): 52 | github_device._expires = datetime.timestamp(datetime.now()) 53 | await github_device.async_device_activation() 54 | 55 | 56 | @pytest.mark.asyncio 57 | async def test_device_error(mock_response, github_device: GitHubDevice): 58 | device = await github_device.async_register_device() 59 | assert device.user_code == "WDJB-MJHT" 60 | 61 | mock_response.mock_data = load_fixture("oauth_access_token_error.json", True) 62 | 63 | with pytest.raises(AIOGitHubAPIException, match="Unsupported grant type"): 64 | await github_device.async_device_activation() 65 | 66 | 67 | @pytest.mark.asyncio 68 | async def test_no_device_code(github_device: GitHubDevice, asyncio_sleep: AsyncMock): 69 | with pytest.raises(AIOGitHubAPIException, match="User took too long to enter key"): 70 | github_device._expires = datetime.timestamp(datetime.now()) 71 | await github_device.async_device_activation() 72 | assert asyncio_sleep.call_count == 1 73 | assert asyncio_sleep.call_args[0][0] == 5 74 | -------------------------------------------------------------------------------- /tests/legacy/fixtures/base/bad_authentication.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Bad credentials" 3 | } -------------------------------------------------------------------------------- /tests/legacy/fixtures/base/bad_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "I'm a tea pot", 3 | "code": 418 4 | } -------------------------------------------------------------------------------- /tests/legacy/fixtures/base_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "value" 3 | } -------------------------------------------------------------------------------- /tests/legacy/fixtures/oauth_access_token_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "unsupported_grant_type", 3 | "error_description": "Unsupported grant type" 4 | } -------------------------------------------------------------------------------- /tests/legacy/fixtures/oauth_access_token_pending.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "authorization_pending", 3 | "error_description": "Pending user authorization" 4 | } -------------------------------------------------------------------------------- /tests/legacy/github/test_get_org_repos.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-docstring, redefined-outer-name, unused-import 2 | import json 3 | 4 | import pytest 5 | 6 | from aiogithubapi import GitHub 7 | from aiogithubapi.common.exceptions import ( 8 | AIOGitHubAPINotModifiedException, 9 | AIOGitHubAPIRatelimitException, 10 | ) 11 | 12 | from tests.common import TOKEN 13 | from tests.legacy.responses.base import base_response 14 | from tests.legacy.responses.org_repositories import org_repositories_response 15 | 16 | 17 | @pytest.mark.asyncio 18 | async def test_get_org_repos(mock_response, org_repositories_response, client_session): 19 | mock_response.mock_data = org_repositories_response 20 | 21 | async with GitHub(TOKEN, session=client_session) as github: 22 | org = await github.get_org_repos("octocat") 23 | first_repo = org[0] 24 | assert first_repo.description == "This your first repo!" 25 | assert github.client.ratelimits.remaining == "4999" 26 | 27 | mock_response.clear() 28 | mock_response.mock_status = 304 29 | 30 | with pytest.raises(AIOGitHubAPINotModifiedException): 31 | await github.get_org_repos("octocat", etag=github.client.last_response.etag) 32 | 33 | 34 | @pytest.mark.asyncio 35 | async def test_get_org_repos_ratelimited(github, mock_response): 36 | mock_response.mock_status = 403 37 | 38 | with pytest.raises(AIOGitHubAPIRatelimitException): 39 | await github.get_org_repos("octocat") 40 | -------------------------------------------------------------------------------- /tests/legacy/github/test_get_rate_limit.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-docstring, redefined-outer-name, unused-import 2 | import pytest 3 | 4 | from aiogithubapi import GitHub 5 | 6 | from tests.legacy.responses.base import base_response 7 | 8 | 9 | @pytest.mark.asyncio 10 | async def test_get_rate_limit(mock_response, base_response, github: GitHub): 11 | mock_response.mock_data = base_response 12 | 13 | rate_limit = await github.get_rate_limit() 14 | assert rate_limit["remaining"] == "4999" 15 | -------------------------------------------------------------------------------- /tests/legacy/github/test_get_repo.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-docstring, redefined-outer-name, unused-import 2 | import json 3 | 4 | import pytest 5 | 6 | from aiogithubapi import ( 7 | AIOGitHubAPINotModifiedException, 8 | AIOGitHubAPIRatelimitException, 9 | GitHub, 10 | GitHubAPI, 11 | GitHubNotModifiedException, 12 | ) 13 | from aiogithubapi.const import GitHubRequestKwarg 14 | 15 | from tests.common import EXPECTED_ETAG, TOKEN 16 | from tests.legacy.responses.base import base_response 17 | from tests.legacy.responses.repository_fixture import repository_response 18 | 19 | 20 | @pytest.mark.asyncio 21 | async def test_get_repo(mock_response, event_loop, repository_response, client_session): 22 | mock_response.mock_data = repository_response 23 | 24 | async with GitHub(TOKEN, session=client_session) as github: 25 | repository = await github.get_repo("octocat/Hello-World") 26 | assert repository.description == "This your first repo!" 27 | assert github.client.last_response.etag == EXPECTED_ETAG 28 | assert github.client.ratelimits.remaining == "4999" 29 | 30 | mock_response.clear() 31 | mock_response.mock_status = 304 32 | 33 | with pytest.raises(AIOGitHubAPINotModifiedException): 34 | await github.get_repo("octocat/Hello-World", github.client.last_response.etag) 35 | 36 | 37 | @pytest.mark.asyncio 38 | async def test_get_repo_ratelimited(mock_response, github): 39 | mock_response.mock_status = 403 40 | 41 | with pytest.raises(AIOGitHubAPIRatelimitException): 42 | await github.get_repo("octocat/Hello-World") 43 | -------------------------------------------------------------------------------- /tests/legacy/github/test_graphql.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-docstring, redefined-outer-name, unused-import 2 | import pytest 3 | 4 | from aiogithubapi import GitHub 5 | 6 | from tests.common import TOKEN, load_fixture 7 | 8 | 9 | @pytest.mark.asyncio 10 | async def test_graphql(client_session): 11 | async with GitHub(TOKEN, session=client_session) as github: 12 | repository = await github.graphql( 13 | query='query{repository(owner: "awesome", name: "repo"){description}}' 14 | ) 15 | assert repository["repository"]["description"] == "This your first repo!" 16 | -------------------------------------------------------------------------------- /tests/legacy/github/test_render_markdown.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-docstring, redefined-outer-name, unused-import 2 | import json 3 | 4 | import aiohttp 5 | import pytest 6 | 7 | from aiogithubapi import AIOGitHubAPIException, AIOGitHubAPINotModifiedException, GitHub 8 | from aiogithubapi.common.exceptions import AIOGitHubAPIRatelimitException 9 | 10 | from tests.common import TOKEN 11 | from tests.legacy.responses.base import base_response 12 | 13 | 14 | @pytest.mark.asyncio 15 | async def test_render_markdown(mock_response, base_response, client_session): 16 | mock_response.mock_data = json.dumps(base_response) 17 | 18 | async with GitHub(TOKEN, session=client_session) as github: 19 | render = await github.render_markdown("test") 20 | assert github.client.ratelimits.remaining == "4999" 21 | assert isinstance(render, str) 22 | 23 | mock_response.clear() 24 | mock_response.mock_status = 304 25 | 26 | with pytest.raises(AIOGitHubAPINotModifiedException): 27 | await github.render_markdown("test", etag=github.client.last_response.etag) 28 | 29 | 30 | @pytest.mark.asyncio 31 | async def test_render_markdown_rate_limited(mock_response, github): 32 | mock_response.mock_status = 403 33 | with pytest.raises(AIOGitHubAPIRatelimitException): 34 | await github.render_markdown("test") 35 | 36 | 37 | @pytest.mark.asyncio 38 | async def test_render_markdown_error(mock_response, client_session): 39 | mock_response.mock_status = 500 40 | 41 | async with GitHub(TOKEN, session=client_session) as github: 42 | with pytest.raises(AIOGitHubAPIException): 43 | await github.render_markdown("test") 44 | -------------------------------------------------------------------------------- /tests/legacy/objects/login/test_device.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generated by generate/generate.py - 2020-09-08 17:49:51.525706 3 | """ 4 | from aiogithubapi.objects.login.device import AIOGitHubAPILoginDevice 5 | 6 | from tests.legacy.responses.login.device_fixtrue import device_fixtrue_response 7 | 8 | 9 | def test_device(device_fixtrue_response): 10 | obj = AIOGitHubAPILoginDevice(device_fixtrue_response) 11 | assert obj.device_code == device_fixtrue_response["device_code"] 12 | assert obj.user_code == device_fixtrue_response["user_code"] 13 | assert obj.verification_uri == device_fixtrue_response["verification_uri"] 14 | assert obj.expires_in == device_fixtrue_response["expires_in"] 15 | assert obj.interval == device_fixtrue_response["interval"] 16 | -------------------------------------------------------------------------------- /tests/legacy/objects/login/test_oauth.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generated by generate/generate.py - 2020-09-08 18:45:20.581868 3 | """ 4 | from aiogithubapi.objects.login.oauth import AIOGitHubAPILoginOauth 5 | 6 | from tests.legacy.responses.login.oauth_fixtrue import oauth_fixtrue_response 7 | 8 | 9 | def test_oauth(oauth_fixtrue_response): 10 | obj = AIOGitHubAPILoginOauth(oauth_fixtrue_response) 11 | assert obj.access_token == oauth_fixtrue_response["access_token"] 12 | assert obj.token_type == oauth_fixtrue_response["token_type"] 13 | assert obj.scope == oauth_fixtrue_response["scope"] 14 | -------------------------------------------------------------------------------- /tests/legacy/objects/repos/test_label.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generated by generate/generate.py - 2020-08-02 10:32:07.005845 3 | """ 4 | from aiogithubapi.objects.repos.label import AIOGitHubAPIReposLabel 5 | 6 | from tests.legacy.responses.repos.label_fixtrue import label_fixtrue_response 7 | 8 | 9 | def test_label(label_fixtrue_response): 10 | obj = AIOGitHubAPIReposLabel(label_fixtrue_response) 11 | assert obj.id == label_fixtrue_response["id"] 12 | assert obj.node_id == label_fixtrue_response["node_id"] 13 | assert obj.url == label_fixtrue_response["url"] 14 | assert obj.name == label_fixtrue_response["name"] 15 | assert obj.description == label_fixtrue_response["description"] 16 | assert obj.color == label_fixtrue_response["color"] 17 | assert obj.default == label_fixtrue_response["default"] 18 | -------------------------------------------------------------------------------- /tests/legacy/objects/repos/traffic/test_clones.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generated by generate/generate.py - 2020-08-02 12:24:23.865974 3 | """ 4 | from aiogithubapi.objects.repos.traffic.clones import AIOGitHubAPIReposTrafficClones 5 | 6 | from tests.legacy.responses.repos.traffic.clones_fixtrue import clones_fixtrue_response 7 | 8 | 9 | def test_clones(clones_fixtrue_response): 10 | obj = AIOGitHubAPIReposTrafficClones(clones_fixtrue_response) 11 | assert obj.count == clones_fixtrue_response["count"] 12 | assert obj.uniques == clones_fixtrue_response["uniques"] 13 | assert obj.clones[0].timestamp == clones_fixtrue_response["clones"][0]["timestamp"] 14 | assert obj.clones[0].count == clones_fixtrue_response["clones"][0]["count"] 15 | assert obj.clones[0].uniques == clones_fixtrue_response["clones"][0]["uniques"] 16 | -------------------------------------------------------------------------------- /tests/legacy/objects/repos/traffic/test_pageviews.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generated by generate/generate.py - 2020-08-02 12:25:07.157407 3 | """ 4 | from aiogithubapi.objects.repos.traffic.pageviews import ( 5 | AIOGitHubAPIReposTrafficPageviews, 6 | ) 7 | 8 | from tests.legacy.responses.repos.traffic.pageviews_fixtrue import ( 9 | pageviews_fixtrue_response, 10 | ) 11 | 12 | 13 | def test_pageviews(pageviews_fixtrue_response): 14 | obj = AIOGitHubAPIReposTrafficPageviews(pageviews_fixtrue_response) 15 | assert obj.count == pageviews_fixtrue_response["count"] 16 | assert obj.uniques == pageviews_fixtrue_response["uniques"] 17 | assert obj.views[0].timestamp == pageviews_fixtrue_response["views"][0]["timestamp"] 18 | assert obj.views[0].count == pageviews_fixtrue_response["views"][0]["count"] 19 | assert obj.views[0].uniques == pageviews_fixtrue_response["views"][0]["uniques"] 20 | -------------------------------------------------------------------------------- /tests/legacy/objects/repository/test_collaborator.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generated by generate/generate.py - 2020-08-02 10:09:26.842173 3 | """ 4 | from aiogithubapi.objects.repository.collaborator import ( 5 | AIOGitHubAPIRepositoryCollaborator, 6 | ) 7 | 8 | from tests.legacy.responses.repository.collaborator_fixtrue import ( 9 | collaborator_fixtrue_response, 10 | ) 11 | 12 | 13 | def test_collaborator(collaborator_fixtrue_response): 14 | obj = AIOGitHubAPIRepositoryCollaborator(collaborator_fixtrue_response) 15 | assert obj.login == collaborator_fixtrue_response["login"] 16 | assert obj.id == collaborator_fixtrue_response["id"] 17 | assert obj.node_id == collaborator_fixtrue_response["node_id"] 18 | assert obj.avatar_url == collaborator_fixtrue_response["avatar_url"] 19 | assert obj.gravatar_id == collaborator_fixtrue_response["gravatar_id"] 20 | assert obj.url == collaborator_fixtrue_response["url"] 21 | assert obj.html_url == collaborator_fixtrue_response["html_url"] 22 | assert obj.followers_url == collaborator_fixtrue_response["followers_url"] 23 | assert obj.following_url == collaborator_fixtrue_response["following_url"] 24 | assert obj.gists_url == collaborator_fixtrue_response["gists_url"] 25 | assert obj.starred_url == collaborator_fixtrue_response["starred_url"] 26 | assert obj.subscriptions_url == collaborator_fixtrue_response["subscriptions_url"] 27 | assert obj.organizations_url == collaborator_fixtrue_response["organizations_url"] 28 | assert obj.repos_url == collaborator_fixtrue_response["repos_url"] 29 | assert obj.events_url == collaborator_fixtrue_response["events_url"] 30 | assert obj.received_events_url == collaborator_fixtrue_response["received_events_url"] 31 | assert obj.type == collaborator_fixtrue_response["type"] 32 | assert obj.site_admin == collaborator_fixtrue_response["site_admin"] 33 | assert obj.permissions.pull == collaborator_fixtrue_response["permissions"]["pull"] 34 | assert obj.permissions.push == collaborator_fixtrue_response["permissions"]["push"] 35 | assert obj.permissions.admin == collaborator_fixtrue_response["permissions"]["admin"] 36 | -------------------------------------------------------------------------------- /tests/legacy/objects/repository/test_release.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-docstring, redefined-outer-name, unused-import 2 | import datetime 3 | import json 4 | 5 | import pytest 6 | 7 | from aiogithubapi import GitHub 8 | 9 | from tests.common import TOKEN 10 | from tests.legacy.responses.releases import releases_response 11 | from tests.legacy.responses.repository_fixture import repository_response 12 | 13 | 14 | @pytest.mark.asyncio 15 | async def test_get_releases(aresponses, repository_response, releases_response): 16 | aresponses.add( 17 | "api.github.com", 18 | "/repos/octocat/Hello-World", 19 | "get", 20 | aresponses.Response( 21 | text=json.dumps(repository_response), 22 | status=200, 23 | headers={ 24 | "X-RateLimit-Remaining": "1337", 25 | "Content-Type": "application/json", 26 | "Etag": "xyz..zyx", 27 | }, 28 | ), 29 | ) 30 | aresponses.add( 31 | "api.github.com", 32 | "/repos/octocat/Hello-World/releases", 33 | "get", 34 | aresponses.Response( 35 | text=json.dumps(releases_response), 36 | status=200, 37 | headers={ 38 | "X-RateLimit-Remaining": "1337", 39 | "Content-Type": "application/json", 40 | "Etag": "xyz..zyx", 41 | }, 42 | ), 43 | ) 44 | aresponses.add( 45 | "api.github.com", 46 | "/repos/octocat/Hello-World/releases", 47 | "get", 48 | aresponses.Response( 49 | text=json.dumps(releases_response), 50 | status=200, 51 | headers={ 52 | "X-RateLimit-Remaining": "1337", 53 | "Content-Type": "application/json", 54 | "Etag": "xyz..zyx", 55 | }, 56 | ), 57 | ) 58 | aresponses.add( 59 | "api.github.com", 60 | "/repos/octocat/Hello-World/releases", 61 | "get", 62 | aresponses.Response( 63 | text=json.dumps(releases_response), 64 | status=200, 65 | headers={ 66 | "X-RateLimit-Remaining": "1337", 67 | "Content-Type": "application/json", 68 | "Etag": "xyz..zyx", 69 | }, 70 | ), 71 | ) 72 | async with GitHub(TOKEN) as github: 73 | repository = await github.get_repo("octocat/Hello-World") 74 | releases = await repository.get_releases() 75 | first = releases[0] 76 | assert first.tag_name == "v1.0.0" 77 | assert first.name == "v1.0.0" 78 | assert first.published_at == datetime.datetime(2013, 2, 27, 19, 35, 32) 79 | assert not first.draft 80 | assert not first.prerelease 81 | assert len(first.assets) == 1 82 | -------------------------------------------------------------------------------- /tests/legacy/objects/test_ratelimit.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-docstring, redefined-outer-name, unused-import 2 | from aiogithubapi.objects.ratelimit import AIOGitHubAPIRateLimit 3 | 4 | 5 | def test_ratelimit(): 6 | ratelimit = AIOGitHubAPIRateLimit() 7 | assert ratelimit.reset_utc is None 8 | ratelimit.load_from_response_headers( 9 | { 10 | "X-RateLimit-Remaining": "4999", 11 | "X-RateLimit-Limit": "5000", 12 | "X-RateLimit-Reset": "4999", 13 | } 14 | ) 15 | assert ratelimit.reset_utc.year == 1970 16 | assert ratelimit.reset_utc.month == 1 17 | assert ratelimit.reset_utc.day == 1 18 | assert ratelimit.reset_utc.hour == 1 19 | assert ratelimit.reset_utc.minute == 23 20 | assert ratelimit.reset_utc.second == 19 21 | -------------------------------------------------------------------------------- /tests/legacy/objects/users/test_user.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generated by generate/generate.py - 2020-08-02 10:35:28.920747 3 | """ 4 | from aiogithubapi.objects.users.user import AIOGitHubAPIUsersUser 5 | 6 | from tests.legacy.responses.users.user_fixtrue import user_fixtrue_response 7 | 8 | 9 | def test_user(user_fixtrue_response): 10 | obj = AIOGitHubAPIUsersUser(user_fixtrue_response) 11 | assert obj.login == user_fixtrue_response["login"] 12 | assert obj.id == user_fixtrue_response["id"] 13 | assert obj.node_id == user_fixtrue_response["node_id"] 14 | assert obj.avatar_url == user_fixtrue_response["avatar_url"] 15 | assert obj.gravatar_id == user_fixtrue_response["gravatar_id"] 16 | assert obj.url == user_fixtrue_response["url"] 17 | assert obj.html_url == user_fixtrue_response["html_url"] 18 | assert obj.followers_url == user_fixtrue_response["followers_url"] 19 | assert obj.following_url == user_fixtrue_response["following_url"] 20 | assert obj.gists_url == user_fixtrue_response["gists_url"] 21 | assert obj.starred_url == user_fixtrue_response["starred_url"] 22 | assert obj.subscriptions_url == user_fixtrue_response["subscriptions_url"] 23 | assert obj.organizations_url == user_fixtrue_response["organizations_url"] 24 | assert obj.repos_url == user_fixtrue_response["repos_url"] 25 | assert obj.events_url == user_fixtrue_response["events_url"] 26 | assert obj.received_events_url == user_fixtrue_response["received_events_url"] 27 | assert obj.type == user_fixtrue_response["type"] 28 | assert obj.site_admin == user_fixtrue_response["site_admin"] 29 | assert obj.name == user_fixtrue_response["name"] 30 | assert obj.company == user_fixtrue_response["company"] 31 | assert obj.blog == user_fixtrue_response["blog"] 32 | assert obj.location == user_fixtrue_response["location"] 33 | assert obj.email == user_fixtrue_response["email"] 34 | assert obj.hireable == user_fixtrue_response["hireable"] 35 | assert obj.bio == user_fixtrue_response["bio"] 36 | assert obj.twitter_username == user_fixtrue_response["twitter_username"] 37 | assert obj.public_repos == user_fixtrue_response["public_repos"] 38 | assert obj.public_gists == user_fixtrue_response["public_gists"] 39 | assert obj.followers == user_fixtrue_response["followers"] 40 | assert obj.following == user_fixtrue_response["following"] 41 | assert obj.created_at == user_fixtrue_response["created_at"] 42 | assert obj.updated_at == user_fixtrue_response["updated_at"] 43 | -------------------------------------------------------------------------------- /tests/legacy/responses/__init__.py: -------------------------------------------------------------------------------- 1 | """Test responses.""" 2 | -------------------------------------------------------------------------------- /tests/legacy/responses/base.py: -------------------------------------------------------------------------------- 1 | """Fixtures.""" 2 | import pytest 3 | 4 | 5 | @pytest.fixture() 6 | def base_response(): 7 | return {"key": "value"} 8 | 9 | 10 | @pytest.fixture() 11 | def bad_auth_response(): 12 | return {"message": "Bad credentials"} 13 | 14 | 15 | @pytest.fixture() 16 | def bad_response(): 17 | return {"message": "I'm a tea pot", "code": 418} 18 | -------------------------------------------------------------------------------- /tests/legacy/responses/contents.py: -------------------------------------------------------------------------------- 1 | """Fixtures.""" 2 | # pylint: disable=missing-docstring 3 | import pytest 4 | 5 | 6 | @pytest.fixture() 7 | def contents_list_response(): 8 | return [ 9 | { 10 | "type": "file", 11 | "size": 625, 12 | "name": "octokit.rb", 13 | "path": "lib/octokit.rb", 14 | "sha": "fff6fe3a23bf1c8ea0692b4a883af99bee26fd3b", 15 | "url": "https://api.github.com/repos/octokit/octokit.rb/contents/lib/octokit.rb", 16 | "git_url": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/fff6fe3a23bf1c8ea0692b4a883af99bee26fd3b", 17 | "html_url": "https://github.com/octokit/octokit.rb/blob/main/lib/octokit.rb", 18 | "download_url": "https://raw.githubusercontent.com/octokit/octokit.rb/main/lib/octokit.rb", 19 | "_links": { 20 | "self": "https://api.github.com/repos/octokit/octokit.rb/contents/lib/octokit.rb", 21 | "git": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/fff6fe3a23bf1c8ea0692b4a883af99bee26fd3b", 22 | "html": "https://github.com/octokit/octokit.rb/blob/main/lib/octokit.rb", 23 | }, 24 | }, 25 | { 26 | "type": "dir", 27 | "size": 0, 28 | "name": "octokit", 29 | "path": "lib/octokit", 30 | "sha": "a84d88e7554fc1fa21bcbc4efae3c782a70d2b9d", 31 | "url": "https://api.github.com/repos/octokit/octokit.rb/contents/lib/octokit", 32 | "git_url": "https://api.github.com/repos/octokit/octokit.rb/git/trees/a84d88e7554fc1fa21bcbc4efae3c782a70d2b9d", 33 | "html_url": "https://github.com/octokit/octokit.rb/tree/main/lib/octokit", 34 | "download_url": None, 35 | "_links": { 36 | "self": "https://api.github.com/repos/octokit/octokit.rb/contents/lib/octokit", 37 | "git": "https://api.github.com/repos/octokit/octokit.rb/git/trees/a84d88e7554fc1fa21bcbc4efae3c782a70d2b9d", 38 | "html": "https://github.com/octokit/octokit.rb/tree/main/lib/octokit", 39 | }, 40 | }, 41 | ] 42 | 43 | 44 | @pytest.fixture() 45 | def contents_file_response(): 46 | return { 47 | "type": "file", 48 | "encoding": "base64", 49 | "size": 5362, 50 | "name": "README.md", 51 | "path": "README.md", 52 | "content": "", 53 | "sha": "3d21ec53a331a6f037a91c368710b99387d012c1", 54 | "url": "https://api.github.com/repos/octokit/octokit.rb/contents/README.md", 55 | "git_url": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1", 56 | "html_url": "https://github.com/octokit/octokit.rb/blob/main/README.md", 57 | "download_url": "https://raw.githubusercontent.com/octokit/octokit.rb/main/README.md", 58 | "_links": { 59 | "git": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1", 60 | "self": "https://api.github.com/repos/octokit/octokit.rb/contents/README.md", 61 | "html": "https://github.com/octokit/octokit.rb/blob/main/README.md", 62 | }, 63 | } 64 | -------------------------------------------------------------------------------- /tests/legacy/responses/issue_comments.py: -------------------------------------------------------------------------------- 1 | """Fixtures.""" 2 | # pylint: disable=missing-docstring 3 | import pytest 4 | 5 | 6 | @pytest.fixture() 7 | def issue_comments_response(): 8 | return [ 9 | { 10 | "id": 1, 11 | "node_id": "MDEyOklzc3VlQ29tbWVudDE=", 12 | "url": "https://api.github.com/repos/octocat/Hello-World/issues/comments/1", 13 | "html_url": "https://github.com/octocat/Hello-World/issues/1347#issuecomment-1", 14 | "body": "Me too", 15 | "user": { 16 | "login": "octocat", 17 | "id": 1, 18 | "node_id": "MDQ6VXNlcjE=", 19 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 20 | "gravatar_id": "", 21 | "url": "https://api.github.com/users/octocat", 22 | "html_url": "https://github.com/octocat", 23 | "followers_url": "https://api.github.com/users/octocat/followers", 24 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 25 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 26 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 27 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 28 | "organizations_url": "https://api.github.com/users/octocat/orgs", 29 | "repos_url": "https://api.github.com/users/octocat/repos", 30 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 31 | "received_events_url": "https://api.github.com/users/octocat/received_events", 32 | "type": "User", 33 | "site_admin": False, 34 | }, 35 | "created_at": "2011-04-14T16:00:49Z", 36 | "updated_at": "2011-04-14T16:00:49Z", 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /tests/legacy/responses/login/device_fixtrue.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generated by generate/generate.py - 2020-09-08 17:49:51.525706 3 | """ 4 | import pytest 5 | 6 | 7 | @pytest.fixture() 8 | def device_fixtrue_response(): 9 | return { 10 | "device_code": "3584d83530557fdd1f46af8289938c8ef79f9dc5", 11 | "user_code": "WDJB-MJHT", 12 | "verification_uri": "https://github.com/login/device", 13 | "expires_in": 900, 14 | "interval": 5, 15 | } 16 | -------------------------------------------------------------------------------- /tests/legacy/responses/login/oauth_fixtrue.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generated by generate/generate.py - 2020-09-08 18:45:20.581868 3 | """ 4 | import pytest 5 | 6 | 7 | @pytest.fixture() 8 | def oauth_fixtrue_response(): 9 | return { 10 | "access_token": "e72e16c7e42f292c6912e7710c838347ae178b4a", 11 | "token_type": "bearer", 12 | "scope": "user", 13 | } 14 | -------------------------------------------------------------------------------- /tests/legacy/responses/oauth/device/flow/login_fixtrue.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generated by generate/generate.py - 2020-09-08 17:46:00.893961 3 | """ 4 | import pytest 5 | 6 | 7 | @pytest.fixture() 8 | def login_fixtrue_response(): 9 | return { 10 | "device_code": "3584d83530557fdd1f46af8289938c8ef79f9dc5", 11 | "user_code": "WDJB-MJHT", 12 | "verification_uri": "https://github.com/login/device", 13 | "expires_in": 900, 14 | "interval": 5, 15 | } 16 | -------------------------------------------------------------------------------- /tests/legacy/responses/orgs/team_fixtrue.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generated by generate/generate.py - 2020-08-02 10:33:09.248066 3 | """ 4 | import pytest 5 | 6 | 7 | @pytest.fixture() 8 | def team_fixtrue_response(): 9 | return { 10 | "id": 1, 11 | "node_id": "MDQ6VGVhbTE=", 12 | "url": "https://api.github.com/teams/1", 13 | "html_url": "https://api.github.com/teams/justice-league", 14 | "name": "Justice League", 15 | "slug": "justice-league", 16 | "description": "A great team.", 17 | "privacy": "closed", 18 | "permission": "admin", 19 | "members_url": "https://api.github.com/teams/1/members{/member}", 20 | "repositories_url": "https://api.github.com/teams/1/repos", 21 | "parent": None, 22 | "members_count": 3, 23 | "repos_count": 10, 24 | "created_at": "2017-07-14T16:53:42Z", 25 | "updated_at": "2017-08-17T12:37:15Z", 26 | "organization": { 27 | "login": "github", 28 | "id": 1, 29 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", 30 | "url": "https://api.github.com/orgs/github", 31 | "repos_url": "https://api.github.com/orgs/github/repos", 32 | "events_url": "https://api.github.com/orgs/github/events", 33 | "hooks_url": "https://api.github.com/orgs/github/hooks", 34 | "issues_url": "https://api.github.com/orgs/github/issues", 35 | "members_url": "https://api.github.com/orgs/github/members{/member}", 36 | "public_members_url": "https://api.github.com/orgs/github/public_members{/member}", 37 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 38 | "description": "A great organization", 39 | "name": "github", 40 | "company": "GitHub", 41 | "blog": "https://github.com/blog", 42 | "location": "San Francisco", 43 | "email": "octocat@github.com", 44 | "twitter_username": "github", 45 | "is_verified": True, 46 | "has_organization_projects": True, 47 | "has_repository_projects": True, 48 | "public_repos": 2, 49 | "public_gists": 1, 50 | "followers": 20, 51 | "following": 0, 52 | "html_url": "https://github.com/octocat", 53 | "created_at": "2008-01-14T04:33:35Z", 54 | "type": "Organization", 55 | }, 56 | } 57 | -------------------------------------------------------------------------------- /tests/legacy/responses/rate_limit.py: -------------------------------------------------------------------------------- 1 | """Fixtures.""" 2 | # pylint: disable=missing-docstring 3 | import pytest 4 | 5 | 6 | @pytest.fixture() 7 | def rate_limit_response(): 8 | return { 9 | "resources": { 10 | "core": {"limit": 5000, "remaining": 4999, "reset": 1372700873}, 11 | "search": {"limit": 30, "remaining": 18, "reset": 1372697452}, 12 | "graphql": {"limit": 5000, "remaining": 4993, "reset": 1372700389}, 13 | "integration_manifest": { 14 | "limit": 5000, 15 | "remaining": 4999, 16 | "reset": 1551806725, 17 | }, 18 | }, 19 | "rate": {"limit": 5000, "remaining": 4999, "reset": 1372700873}, 20 | } 21 | -------------------------------------------------------------------------------- /tests/legacy/responses/repos/label_fixtrue.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generated by generate/generate.py - 2020-08-02 10:32:07.005845 3 | """ 4 | import pytest 5 | 6 | 7 | @pytest.fixture() 8 | def label_fixtrue_response(): 9 | return { 10 | "id": 208045946, 11 | "node_id": "MDU6TGFiZWwyMDgwNDU5NDY=", 12 | "url": "https://api.github.com/repos/octocat/Hello-World/labels/bug", 13 | "name": "bug", 14 | "description": "Something isn't working", 15 | "color": "f29513", 16 | "default": True, 17 | } 18 | -------------------------------------------------------------------------------- /tests/legacy/responses/repos/traffic/clones_fixtrue.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generated by generate/generate.py - 2020-08-02 12:24:23.865974 3 | """ 4 | import pytest 5 | 6 | 7 | @pytest.fixture() 8 | def clones_fixtrue_response(): 9 | return { 10 | "count": 173, 11 | "uniques": 128, 12 | "clones": [ 13 | {"timestamp": "2016-10-10T00:00:00Z", "count": 2, "uniques": 1}, 14 | {"timestamp": "2016-10-11T00:00:00Z", "count": 17, "uniques": 16}, 15 | {"timestamp": "2016-10-12T00:00:00Z", "count": 21, "uniques": 15}, 16 | {"timestamp": "2016-10-13T00:00:00Z", "count": 8, "uniques": 7}, 17 | {"timestamp": "2016-10-14T00:00:00Z", "count": 5, "uniques": 5}, 18 | {"timestamp": "2016-10-15T00:00:00Z", "count": 2, "uniques": 2}, 19 | {"timestamp": "2016-10-16T00:00:00Z", "count": 8, "uniques": 7}, 20 | {"timestamp": "2016-10-17T00:00:00Z", "count": 26, "uniques": 15}, 21 | {"timestamp": "2016-10-18T00:00:00Z", "count": 19, "uniques": 17}, 22 | {"timestamp": "2016-10-19T00:00:00Z", "count": 19, "uniques": 14}, 23 | {"timestamp": "2016-10-20T00:00:00Z", "count": 19, "uniques": 15}, 24 | {"timestamp": "2016-10-21T00:00:00Z", "count": 9, "uniques": 7}, 25 | {"timestamp": "2016-10-22T00:00:00Z", "count": 5, "uniques": 5}, 26 | {"timestamp": "2016-10-23T00:00:00Z", "count": 6, "uniques": 5}, 27 | {"timestamp": "2016-10-24T00:00:00Z", "count": 7, "uniques": 5}, 28 | ], 29 | } 30 | -------------------------------------------------------------------------------- /tests/legacy/responses/repos/traffic/pageviews_fixtrue.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generated by generate/generate.py - 2020-08-02 12:25:07.157407 3 | """ 4 | import pytest 5 | 6 | 7 | @pytest.fixture() 8 | def pageviews_fixtrue_response(): 9 | return { 10 | "count": 14850, 11 | "uniques": 3782, 12 | "views": [ 13 | {"timestamp": "2016-10-10T00:00:00Z", "count": 440, "uniques": 143}, 14 | {"timestamp": "2016-10-11T00:00:00Z", "count": 1308, "uniques": 414}, 15 | {"timestamp": "2016-10-12T00:00:00Z", "count": 1486, "uniques": 452}, 16 | {"timestamp": "2016-10-13T00:00:00Z", "count": 1170, "uniques": 401}, 17 | {"timestamp": "2016-10-14T00:00:00Z", "count": 868, "uniques": 266}, 18 | {"timestamp": "2016-10-15T00:00:00Z", "count": 495, "uniques": 157}, 19 | {"timestamp": "2016-10-16T00:00:00Z", "count": 524, "uniques": 175}, 20 | {"timestamp": "2016-10-17T00:00:00Z", "count": 1263, "uniques": 412}, 21 | {"timestamp": "2016-10-18T00:00:00Z", "count": 1402, "uniques": 417}, 22 | {"timestamp": "2016-10-19T00:00:00Z", "count": 1394, "uniques": 424}, 23 | {"timestamp": "2016-10-20T00:00:00Z", "count": 1492, "uniques": 448}, 24 | {"timestamp": "2016-10-21T00:00:00Z", "count": 1153, "uniques": 332}, 25 | {"timestamp": "2016-10-22T00:00:00Z", "count": 566, "uniques": 168}, 26 | {"timestamp": "2016-10-23T00:00:00Z", "count": 675, "uniques": 184}, 27 | {"timestamp": "2016-10-24T00:00:00Z", "count": 614, "uniques": 237}, 28 | ], 29 | } 30 | -------------------------------------------------------------------------------- /tests/legacy/responses/repository/collaborator_fixtrue.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generated by generate/generate.py - 2020-08-02 10:09:26.842173 3 | """ 4 | import pytest 5 | 6 | 7 | @pytest.fixture() 8 | def collaborator_fixtrue_response(): 9 | return { 10 | "login": "octocat", 11 | "id": 1, 12 | "node_id": "MDQ6VXNlcjE=", 13 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 14 | "gravatar_id": "", 15 | "url": "https://api.github.com/users/octocat", 16 | "html_url": "https://github.com/octocat", 17 | "followers_url": "https://api.github.com/users/octocat/followers", 18 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 19 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 20 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 21 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 22 | "organizations_url": "https://api.github.com/users/octocat/orgs", 23 | "repos_url": "https://api.github.com/users/octocat/repos", 24 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 25 | "received_events_url": "https://api.github.com/users/octocat/received_events", 26 | "type": "User", 27 | "site_admin": False, 28 | "permissions": {"pull": True, "push": True, "admin": False}, 29 | } 30 | -------------------------------------------------------------------------------- /tests/legacy/responses/tree.py: -------------------------------------------------------------------------------- 1 | """Fixtures.""" 2 | # pylint: disable=missing-docstring 3 | import pytest 4 | 5 | 6 | @pytest.fixture() 7 | def tree_response(): 8 | return { 9 | "sha": "fc6274d15fa3ae2ab983129fb037999f264ba9a7", 10 | "url": "https://api.github.com/repos/octocat/Hello-World/trees/fc6274d15fa3ae2ab983129fb037999f264ba9a7", 11 | "tree": [ 12 | { 13 | "path": "subdir/file.txt", 14 | "mode": "100644", 15 | "type": "blob", 16 | "size": 132, 17 | "sha": "7c258a9869f33c1e1e1f74fbb32f07c86cb5a75b", 18 | "url": "https://api.github.com/repos/octocat/Hello-World/git/7c258a9869f33c1e1e1f74fbb32f07c86cb5a75b", 19 | }, 20 | { 21 | "path": "subdir", 22 | "mode": "100644", 23 | "type": "tree", 24 | "size": 132, 25 | "sha": "7c258a9869f33c1e1e1f74fbb32f07c86cb5a75b", 26 | "url": "https://api.github.com/repos/octocat/Hello-World/git/7c258a9869f33c1e1e1f74fbb32f07c86cb5a75b", 27 | }, 28 | ], 29 | "truncated": False, 30 | } 31 | -------------------------------------------------------------------------------- /tests/legacy/responses/users/user_fixtrue.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generated by generate/generate.py - 2020-08-02 10:35:28.920747 3 | """ 4 | import pytest 5 | 6 | 7 | @pytest.fixture() 8 | def user_fixtrue_response(): 9 | return { 10 | "login": "octocat", 11 | "id": 1, 12 | "node_id": "MDQ6VXNlcjE=", 13 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 14 | "gravatar_id": "", 15 | "url": "https://api.github.com/users/octocat", 16 | "html_url": "https://github.com/octocat", 17 | "followers_url": "https://api.github.com/users/octocat/followers", 18 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 19 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 20 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 21 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 22 | "organizations_url": "https://api.github.com/users/octocat/orgs", 23 | "repos_url": "https://api.github.com/users/octocat/repos", 24 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 25 | "received_events_url": "https://api.github.com/users/octocat/received_events", 26 | "type": "User", 27 | "site_admin": False, 28 | "name": "monalisa octocat", 29 | "company": "GitHub", 30 | "blog": "https://github.com/blog", 31 | "location": "San Francisco", 32 | "email": "octocat@github.com", 33 | "hireable": False, 34 | "bio": "There once was...", 35 | "twitter_username": "monatheoctocat", 36 | "public_repos": 2, 37 | "public_gists": 1, 38 | "followers": 20, 39 | "following": 0, 40 | "created_at": "2008-01-14T04:33:35Z", 41 | "updated_at": "2008-01-14T04:33:35Z", 42 | } 43 | -------------------------------------------------------------------------------- /tests/legacy/test_helpers.py: -------------------------------------------------------------------------------- 1 | from aiogithubapi.helpers import short_message, short_sha 2 | 3 | 4 | def test_short_sha(): 5 | assert short_sha("123456789") == "1234567" 6 | 7 | 8 | def test_short_message(): 9 | assert short_message("show\nno show") == "show" 10 | -------------------------------------------------------------------------------- /tests/namespaces/__init__.py: -------------------------------------------------------------------------------- 1 | """Init namespaces testing""" 2 | -------------------------------------------------------------------------------- /tests/namespaces/test_contents.py: -------------------------------------------------------------------------------- 1 | """Test contents namespace.""" 2 | # pylint: disable=missing-docstring 3 | import pytest 4 | 5 | from aiogithubapi import GitHubAPI, GitHubContentsModel 6 | 7 | from tests.common import TEST_REPOSITORY_NAME, MockedRequests 8 | 9 | 10 | @pytest.mark.asyncio 11 | async def test_get_root(github_api: GitHubAPI, mock_requests: MockedRequests): 12 | response = await github_api.repos.contents.get(TEST_REPOSITORY_NAME) 13 | assert response.status == 200 14 | assert isinstance(response.data, list) 15 | assert len(response.data) == 1 16 | assert response.data[0].name == "README" 17 | assert mock_requests.called == 1 18 | assert ( 19 | mock_requests.last_request["url"] 20 | == "https://api.github.com/repos/octocat/hello-world/contents" 21 | ) 22 | 23 | 24 | @pytest.mark.asyncio 25 | async def test_get_readme(github_api: GitHubAPI, mock_requests: MockedRequests): 26 | response = await github_api.repos.contents.get(TEST_REPOSITORY_NAME, "README") 27 | assert response.status == 200 28 | assert isinstance(response.data, GitHubContentsModel) 29 | assert response.data.name == "README" 30 | assert mock_requests.called == 1 31 | assert ( 32 | mock_requests.last_request["url"] 33 | == "https://api.github.com/repos/octocat/hello-world/contents/README" 34 | ) 35 | -------------------------------------------------------------------------------- /tests/namespaces/test_git.py: -------------------------------------------------------------------------------- 1 | """Test git namespace.""" 2 | # pylint: disable=missing-docstring 3 | import pytest 4 | 5 | from aiogithubapi import GitHubAPI 6 | 7 | from tests.common import TEST_REPOSITORY_NAME, MockedRequests 8 | 9 | 10 | @pytest.mark.asyncio 11 | async def test_get_tree(github_api: GitHubAPI, mock_requests: MockedRequests): 12 | response = await github_api.repos.git.get_tree(TEST_REPOSITORY_NAME, "master") 13 | assert response.status == 200 14 | assert response.data.tree[0].path == "README" 15 | assert mock_requests.called == 1 16 | assert ( 17 | mock_requests.last_request["url"] 18 | == "https://api.github.com/repos/octocat/hello-world/git/trees/master" 19 | ) 20 | -------------------------------------------------------------------------------- /tests/namespaces/test_orgs.py: -------------------------------------------------------------------------------- 1 | """Test orgs namespace.""" 2 | # pylint: disable=missing-docstring 3 | 4 | import pytest 5 | 6 | from aiogithubapi import ( 7 | GitHubAPI, 8 | GitHubOrganizationMinimalModel, 9 | GitHubOrganizationModel, 10 | ) 11 | 12 | from tests.common import TEST_ORGANIZATION, MockedRequests 13 | 14 | 15 | @pytest.mark.asyncio 16 | async def test_list_organizations(github_api: GitHubAPI, mock_requests: MockedRequests): 17 | response = await github_api.orgs.list() 18 | assert response.status == 200 19 | assert isinstance(response.data, list) 20 | fist = response.data[0] 21 | assert isinstance(fist, GitHubOrganizationMinimalModel) 22 | assert fist.login == "github" 23 | assert mock_requests.called == 1 24 | assert mock_requests.last_request["url"] == "https://api.github.com/organizations" 25 | 26 | 27 | @pytest.mark.asyncio 28 | async def test_get_organization(github_api: GitHubAPI, mock_requests: MockedRequests): 29 | response = await github_api.orgs.get(TEST_ORGANIZATION) 30 | assert response.status == 200 31 | assert isinstance(response.data, GitHubOrganizationModel) 32 | assert response.data.name == "github" 33 | assert mock_requests.called == 1 34 | assert mock_requests.last_request["url"] == "https://api.github.com/orgs/octocat" 35 | 36 | 37 | @pytest.mark.asyncio 38 | async def test_update_organization(github_api: GitHubAPI, mock_requests: MockedRequests): 39 | response = await github_api.orgs.update(TEST_ORGANIZATION, {"test": "data"}) 40 | assert response.status == 200 41 | assert isinstance(response.data, GitHubOrganizationModel) 42 | assert response.data.name == "github" 43 | assert mock_requests.called == 1 44 | assert mock_requests.last_request["url"] == "https://api.github.com/orgs/octocat" 45 | -------------------------------------------------------------------------------- /tests/namespaces/test_pulls.py: -------------------------------------------------------------------------------- 1 | """Test contents namespace.""" 2 | # pylint: disable=missing-docstring 3 | import pytest 4 | 5 | from aiogithubapi import GitHubAPI 6 | 7 | from tests.common import TEST_REPOSITORY_NAME, MockedRequests, MockResponse 8 | 9 | 10 | @pytest.mark.asyncio 11 | async def test_list(github_api: GitHubAPI, mock_requests: MockedRequests): 12 | response = await github_api.repos.pulls.list(TEST_REPOSITORY_NAME) 13 | assert response.status == 200 14 | assert isinstance(response.data, list) 15 | assert len(response.data) == 1 16 | assert response.data[0].title == "Awesome title" 17 | assert mock_requests.called == 1 18 | assert ( 19 | mock_requests.last_request["url"] 20 | == "https://api.github.com/repos/octocat/hello-world/pulls" 21 | ) 22 | 23 | 24 | @pytest.mark.asyncio 25 | async def test_list_empty( 26 | github_api: GitHubAPI, 27 | mock_requests: MockedRequests, 28 | mock_response: MockResponse, 29 | ): 30 | mock_response.mock_data = [] 31 | response = await github_api.repos.pulls.list(TEST_REPOSITORY_NAME) 32 | assert response.status == 200 33 | assert isinstance(response.data, list) 34 | assert len(response.data) == 0 35 | assert mock_requests.called == 1 36 | assert ( 37 | mock_requests.last_request["url"] 38 | == "https://api.github.com/repos/octocat/hello-world/pulls" 39 | ) 40 | -------------------------------------------------------------------------------- /tests/namespaces/test_releases.py: -------------------------------------------------------------------------------- 1 | """Test releases namespace.""" 2 | # pylint: disable=missing-docstring 3 | import pytest 4 | 5 | from aiogithubapi import GitHubAPI, GitHubReleaseModel 6 | 7 | from tests.common import TEST_REPOSITORY_NAME, MockedRequests 8 | 9 | 10 | @pytest.mark.asyncio 11 | async def test_list_releases(github_api: GitHubAPI, mock_requests: MockedRequests): 12 | response = await github_api.repos.releases.list(TEST_REPOSITORY_NAME) 13 | assert response.status == 200 14 | assert isinstance(response.data, list) 15 | assert isinstance(response.data[0], GitHubReleaseModel) 16 | assert response.data[0].name == "First release" 17 | assert mock_requests.called == 1 18 | assert ( 19 | mock_requests.last_request["url"] 20 | == "https://api.github.com/repos/octocat/hello-world/releases" 21 | ) 22 | 23 | @pytest.mark.asyncio 24 | async def test_latest(github_api: GitHubAPI, mock_requests: MockedRequests): 25 | response = await github_api.repos.releases.latest(TEST_REPOSITORY_NAME) 26 | assert response.status == 200 27 | assert isinstance(response.data, GitHubReleaseModel) 28 | assert response.data.name == "First release" 29 | assert mock_requests.called == 1 30 | assert ( 31 | mock_requests.last_request["url"] 32 | == "https://api.github.com/repos/octocat/hello-world/releases/latest" 33 | ) -------------------------------------------------------------------------------- /tests/namespaces/test_traffic.py: -------------------------------------------------------------------------------- 1 | """Test traffic namespace.""" 2 | # pylint: disable=missing-docstring 3 | import pytest 4 | 5 | from aiogithubapi import GitHubAPI 6 | 7 | from tests.common import TEST_REPOSITORY_NAME, MockedRequests 8 | 9 | 10 | @pytest.mark.asyncio 11 | async def test_clones(github_api: GitHubAPI, mock_requests: MockedRequests): 12 | response = await github_api.repos.traffic.clones(TEST_REPOSITORY_NAME) 13 | assert response.status == 200 14 | assert len(response.data.clones) == 1 15 | assert response.data.clones[0].count == 1 16 | assert mock_requests.called == 1 17 | assert ( 18 | mock_requests.last_request["url"] 19 | == "https://api.github.com/repos/octocat/hello-world/traffic/clones" 20 | ) 21 | 22 | 23 | @pytest.mark.asyncio 24 | async def test_views(github_api: GitHubAPI, mock_requests: MockedRequests): 25 | response = await github_api.repos.traffic.views(TEST_REPOSITORY_NAME) 26 | assert response.status == 200 27 | assert len(response.data.views) == 1 28 | assert response.data.views[0].count == 1 29 | assert mock_requests.called == 1 30 | assert ( 31 | mock_requests.last_request["url"] 32 | == "https://api.github.com/repos/octocat/hello-world/traffic/views" 33 | ) 34 | -------------------------------------------------------------------------------- /tests/namespaces/test_user.py: -------------------------------------------------------------------------------- 1 | """Test users namespace.""" 2 | # pylint: disable=missing-docstring 3 | import pytest 4 | 5 | from aiogithubapi import GitHubAPI 6 | 7 | from tests.common import TEST_USER_NAME, MockedRequests 8 | 9 | 10 | @pytest.mark.asyncio 11 | async def test_get_authenticated_user(github_api: GitHubAPI, mock_requests: MockedRequests): 12 | response = await github_api.user.get() 13 | assert response.status == 200 14 | assert response.data.name == "The Octocat" 15 | assert mock_requests.called == 1 16 | assert mock_requests.last_request["url"] == "https://api.github.com/user" 17 | 18 | 19 | @pytest.mark.asyncio 20 | async def test_get_authenticated_user_starred(github_api: GitHubAPI, mock_requests: MockedRequests): 21 | response = await github_api.user.starred() 22 | assert response.status == 200 23 | first_repo = response.data[0] 24 | assert first_repo.name == "Hello-World" 25 | assert mock_requests.called == 1 26 | assert mock_requests.last_request["url"] == "https://api.github.com/user/starred" 27 | 28 | 29 | @pytest.mark.asyncio 30 | async def test_get_authenticated_user_repos(github_api: GitHubAPI, mock_requests: MockedRequests): 31 | response = await github_api.user.repos() 32 | assert response.status == 200 33 | first_repo = response.data[0] 34 | assert first_repo.name == "Hello-World" 35 | assert mock_requests.called == 1 36 | assert mock_requests.last_request["url"] == "https://api.github.com/user/repos" 37 | 38 | 39 | @pytest.mark.asyncio 40 | async def test_get_authenticated_user_orgs(github_api: GitHubAPI, mock_requests: MockedRequests): 41 | response = await github_api.user.orgs() 42 | assert response.status == 200 43 | first_repo = response.data[0] 44 | assert first_repo.login == "github" 45 | assert mock_requests.called == 1 46 | assert mock_requests.last_request["url"] == "https://api.github.com/user/orgs" 47 | -------------------------------------------------------------------------------- /tests/namespaces/test_users.py: -------------------------------------------------------------------------------- 1 | """Test users namespace.""" 2 | # pylint: disable=missing-docstring 3 | import pytest 4 | 5 | from aiogithubapi import GitHubAPI 6 | 7 | from tests.common import TEST_USER_NAME, MockedRequests 8 | 9 | 10 | @pytest.mark.asyncio 11 | async def test_get_user(github_api: GitHubAPI, mock_requests: MockedRequests): 12 | response = await github_api.users.get(TEST_USER_NAME) 13 | assert response.status == 200 14 | assert response.data.name == "The Octocat" 15 | assert mock_requests.called == 1 16 | assert mock_requests.last_request["url"] == "https://api.github.com/users/octocat" 17 | 18 | 19 | @pytest.mark.asyncio 20 | async def test_get_user_starred(github_api: GitHubAPI, mock_requests: MockedRequests): 21 | response = await github_api.users.starred(TEST_USER_NAME) 22 | assert response.status == 200 23 | first_repo = response.data[0] 24 | assert first_repo.name == "Hello-World" 25 | assert mock_requests.called == 1 26 | assert mock_requests.last_request["url"] == "https://api.github.com/users/octocat/starred" 27 | 28 | 29 | @pytest.mark.asyncio 30 | async def test_get_user_repos(github_api: GitHubAPI, mock_requests: MockedRequests): 31 | response = await github_api.users.repos(TEST_USER_NAME) 32 | assert response.status == 200 33 | first_repo = response.data[0] 34 | assert first_repo.name == "Hello-World" 35 | assert mock_requests.called == 1 36 | assert mock_requests.last_request["url"] == "https://api.github.com/users/octocat/repos" 37 | 38 | 39 | @pytest.mark.asyncio 40 | async def test_get_user_orgs(github_api: GitHubAPI, mock_requests: MockedRequests): 41 | response = await github_api.users.orgs(TEST_USER_NAME) 42 | assert response.status == 200 43 | first_repo = response.data[0] 44 | assert first_repo.login == "github" 45 | assert mock_requests.called == 1 46 | assert mock_requests.last_request["url"] == "https://api.github.com/users/octocat/orgs" 47 | -------------------------------------------------------------------------------- /tests/test_const.py: -------------------------------------------------------------------------------- 1 | """Test const.""" 2 | 3 | from aiogithubapi.const import StrEnum, IntEnum 4 | 5 | 6 | def test_enum_value(): 7 | """Test enum value.""" 8 | 9 | class TestStrEnum(StrEnum): 10 | """Test StrEnum class""" 11 | 12 | TEST = "test" 13 | 14 | class TestIntEnum(IntEnum): 15 | """Test StrEnum class""" 16 | 17 | TEST = 4 18 | 19 | assert TestStrEnum.TEST == "test" 20 | assert TestStrEnum.TEST.value == "test" 21 | assert str(TestStrEnum.TEST) == "test" 22 | assert f"{TestStrEnum.TEST}" == "test" 23 | assert "{}".format(TestStrEnum.TEST) == "test" 24 | 25 | assert TestIntEnum.TEST == 4 26 | assert TestIntEnum.TEST.value == 4 27 | assert int(TestIntEnum.TEST) == 4 28 | -------------------------------------------------------------------------------- /tests/test_helper.py: -------------------------------------------------------------------------------- 1 | """Test repository dataclass.""" 2 | from io import BytesIO 3 | import json 4 | from unittest.mock import patch 5 | from aiogithubapi import Repository 6 | from aiogithubapi.helpers import repository_full_name, sigstore_verify_release_asset 7 | from sigstore.verify import VerificationSuccess, VerificationFailure 8 | 9 | 10 | def test_repository(): 11 | """Test repository dataclass.""" 12 | assert ( 13 | repository_full_name(Repository(owner="octocat", repo="Hello-World")) 14 | == "octocat/Hello-World" 15 | ) 16 | assert repository_full_name("octocat/Hello-World") == "octocat/Hello-World" 17 | assert ( 18 | repository_full_name({"owner": "octocat", "repo": "Hello-World"}) == "octocat/Hello-World" 19 | ) 20 | 21 | 22 | def test_sigstore_success(): 23 | with patch("sigstore.verify.Verifier.verify", return_value=VerificationSuccess()), patch( 24 | "sigstore.verify.VerificationMaterials.from_bundle" 25 | ): 26 | verification = sigstore_verify_release_asset( 27 | asset=b"test", 28 | signature_bundle=bytes(json.dumps({"critical": {"identity": "test"}}), "utf-8"), 29 | repository="test", 30 | workflow="test", 31 | tag="test", 32 | workflow_name="test", 33 | workflow_trigger="release", 34 | ) 35 | assert verification.success 36 | assert getattr(verification, "reason", None) is None 37 | assert verification.success is True 38 | 39 | 40 | def test_sigstore_failure(): 41 | with patch( 42 | "sigstore.verify.Verifier.verify", 43 | return_value=VerificationFailure(reason="Some reason"), 44 | ), patch("sigstore.verify.VerificationMaterials.from_bundle"): 45 | verification = sigstore_verify_release_asset( 46 | asset=b"test", 47 | signature_bundle=bytes(json.dumps({"critical": {"identity": "test"}}), "utf-8"), 48 | repository="test", 49 | workflow="test", 50 | tag="test", 51 | workflow_name="test", 52 | workflow_trigger="release", 53 | ) 54 | assert getattr(verification, "reason", None) is not None 55 | assert verification.reason == "Some reason" 56 | assert verification.success is False 57 | --------------------------------------------------------------------------------