Module aiogithubapi.device

Class for OAuth device flow authentication.

https://docs.github.com/en/developers/apps/authorizing-oauth-apps#device-flow

Expand source code
"""
Class for OAuth device flow authentication.

https://docs.github.com/en/developers/apps/authorizing-oauth-apps#device-flow
"""
from __future__ import annotations

import asyncio
from datetime import datetime
from typing import Any, Dict

import aiohttp

from .client import GitHubClient
from .const import (
    BASE_GITHUB_URL,
    OAUTH_ACCESS_TOKEN_PATH,
    OAUTH_DEVICE_LOGIN_PATH,
    DeviceFlowError,
    GitHubClientKwarg,
    GitHubRequestKwarg,
    HttpMethod,
)
from .exceptions import GitHubException
from .legacy.device import AIOGitHubAPIDeviceLogin as LegacyAIOGitHubAPIDeviceLogin
from .models.base import GitHubBase
from .models.device_login import GitHubLoginDeviceModel
from .models.login_oauth import GitHubLoginOauthModel
from .models.response import GitHubResponseModel


class AIOGitHubAPIDeviceLogin(LegacyAIOGitHubAPIDeviceLogin):
    """Dummy class to not break existing code."""


class GitHubDeviceAPI(GitHubBase):
    """GitHub API OAuth device flow"""

    _close_session = False

    def __init__(
        self,
        client_id: str,
        session: aiohttp.ClientSession | None = None,
        **kwargs: Dict[GitHubClientKwarg, Any],
    ):
        """
        Initialises a GitHub API OAuth device flow.

        **Arguments**:

        `client_id` (Optional)

        The client ID of your OAuth app.


        `session` (Optional)

        `aiohttp.ClientSession` to be used by this package.
        If you do not pass one, one will be created for you.

        `**kwargs` (Optional)

        Pass additional arguments.
        See the `aiogithubapi.const.GitHubClientKwarg` enum for valid options.

        https://docs.github.com/en/developers/apps/authorizing-oauth-apps#device-flow
        """
        self.client_id = client_id
        self._interval = 5
        self._expires = None

        if session is None:
            session = aiohttp.ClientSession()
            self._close_session = True

        self._session = session

        if GitHubClientKwarg.BASE_URL not in kwargs:
            kwargs[GitHubClientKwarg.BASE_URL] = BASE_GITHUB_URL

        self._client = GitHubClient(session=session, **kwargs)

    async def __aenter__(self) -> GitHubDeviceAPI:
        """Async enter."""
        return self

    async def __aexit__(self, *exc_info) -> None:
        """Async exit."""
        await self.close_session()

    async def close_session(self) -> None:
        """Close open client session."""
        if self._session and self._close_session:
            await self._session.close()

    async def register(
        self,
        **kwargs: Dict[GitHubRequestKwarg, Any],
    ) -> GitHubResponseModel[GitHubLoginDeviceModel]:
        """Register the device and return a object that contains the user code for authorization."""
        response = await self._client.async_call_api(
            endpoint=OAUTH_DEVICE_LOGIN_PATH,
            **{
                **kwargs,
                GitHubRequestKwarg.METHOD: HttpMethod.POST,
                GitHubRequestKwarg.PARAMS: {
                    "client_id": self.client_id,
                    "scope": kwargs.get(GitHubRequestKwarg.SCOPE, ""),
                },
            },
        )
        response.data = GitHubLoginDeviceModel(response.data)
        self._interval = response.data.interval
        self._expires = datetime.timestamp(datetime.now()) + response.data.expires_in
        return response

    async def activation(
        self,
        device_code: str,
        **kwargs: Dict[GitHubRequestKwarg, Any],
    ) -> GitHubResponseModel[GitHubLoginOauthModel]:
        """
        Wait for the user to enter the code and activate the device.

        **Arguments**:

        `device_code`

        The device_code that was returned when registering the device.

        """
        if self._expires is None:
            raise GitHubException("Expiration has passed, re-run the registration")

        _user_confirmed = None
        while _user_confirmed is None:

            if self._expires < datetime.timestamp(datetime.now()):
                raise GitHubException("User took too long to enter key")

            response = await self._client.async_call_api(
                endpoint=OAUTH_ACCESS_TOKEN_PATH,
                **{
                    **kwargs,
                    GitHubRequestKwarg.METHOD: HttpMethod.POST,
                    GitHubRequestKwarg.PARAMS: {
                        "client_id": self.client_id,
                        "device_code": device_code,
                        "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
                    },
                },
            )

            if error := response.data.get("error"):
                if error == DeviceFlowError.AUTHORIZATION_PENDING:
                    self.logger.debug(response.data.get("error_description"))
                    await asyncio.sleep(self._interval)
                else:
                    raise GitHubException(response.data.get("error_description"))
            else:
                response.data = GitHubLoginOauthModel(response.data)
                break

        return response

Classes

class AIOGitHubAPIDeviceLogin (client_id: str, scope: str = '', session: aiohttp.ClientSession = None)

Dummy class to not break existing code.

Initialises a GitHub API OAuth device flow.

param required description
client_id True The client ID of your OAuth app.
scope False Scope(s) that will be requested.
session False aiohttp.ClientSession to be used by this package.
Expand source code
class AIOGitHubAPIDeviceLogin(LegacyAIOGitHubAPIDeviceLogin):
    """Dummy class to not break existing code."""

Ancestors

Inherited members

class GitHubDeviceAPI (client_id: str, session: aiohttp.ClientSession | None = None, **kwargs: Dict[GitHubClientKwarg, Any])

GitHub API OAuth device flow

Initialises a GitHub API OAuth device flow.

Arguments:

client_id (Optional)

The client ID of your OAuth app.

session (Optional)

aiohttp.ClientSession to be used by this package. If you do not pass one, one will be created for you.

**kwargs (Optional)

Pass additional arguments. See the GitHubClientKwarg enum for valid options.

https://docs.github.com/en/developers/apps/authorizing-oauth-apps#device-flow

Expand source code
class GitHubDeviceAPI(GitHubBase):
    """GitHub API OAuth device flow"""

    _close_session = False

    def __init__(
        self,
        client_id: str,
        session: aiohttp.ClientSession | None = None,
        **kwargs: Dict[GitHubClientKwarg, Any],
    ):
        """
        Initialises a GitHub API OAuth device flow.

        **Arguments**:

        `client_id` (Optional)

        The client ID of your OAuth app.


        `session` (Optional)

        `aiohttp.ClientSession` to be used by this package.
        If you do not pass one, one will be created for you.

        `**kwargs` (Optional)

        Pass additional arguments.
        See the `aiogithubapi.const.GitHubClientKwarg` enum for valid options.

        https://docs.github.com/en/developers/apps/authorizing-oauth-apps#device-flow
        """
        self.client_id = client_id
        self._interval = 5
        self._expires = None

        if session is None:
            session = aiohttp.ClientSession()
            self._close_session = True

        self._session = session

        if GitHubClientKwarg.BASE_URL not in kwargs:
            kwargs[GitHubClientKwarg.BASE_URL] = BASE_GITHUB_URL

        self._client = GitHubClient(session=session, **kwargs)

    async def __aenter__(self) -> GitHubDeviceAPI:
        """Async enter."""
        return self

    async def __aexit__(self, *exc_info) -> None:
        """Async exit."""
        await self.close_session()

    async def close_session(self) -> None:
        """Close open client session."""
        if self._session and self._close_session:
            await self._session.close()

    async def register(
        self,
        **kwargs: Dict[GitHubRequestKwarg, Any],
    ) -> GitHubResponseModel[GitHubLoginDeviceModel]:
        """Register the device and return a object that contains the user code for authorization."""
        response = await self._client.async_call_api(
            endpoint=OAUTH_DEVICE_LOGIN_PATH,
            **{
                **kwargs,
                GitHubRequestKwarg.METHOD: HttpMethod.POST,
                GitHubRequestKwarg.PARAMS: {
                    "client_id": self.client_id,
                    "scope": kwargs.get(GitHubRequestKwarg.SCOPE, ""),
                },
            },
        )
        response.data = GitHubLoginDeviceModel(response.data)
        self._interval = response.data.interval
        self._expires = datetime.timestamp(datetime.now()) + response.data.expires_in
        return response

    async def activation(
        self,
        device_code: str,
        **kwargs: Dict[GitHubRequestKwarg, Any],
    ) -> GitHubResponseModel[GitHubLoginOauthModel]:
        """
        Wait for the user to enter the code and activate the device.

        **Arguments**:

        `device_code`

        The device_code that was returned when registering the device.

        """
        if self._expires is None:
            raise GitHubException("Expiration has passed, re-run the registration")

        _user_confirmed = None
        while _user_confirmed is None:

            if self._expires < datetime.timestamp(datetime.now()):
                raise GitHubException("User took too long to enter key")

            response = await self._client.async_call_api(
                endpoint=OAUTH_ACCESS_TOKEN_PATH,
                **{
                    **kwargs,
                    GitHubRequestKwarg.METHOD: HttpMethod.POST,
                    GitHubRequestKwarg.PARAMS: {
                        "client_id": self.client_id,
                        "device_code": device_code,
                        "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
                    },
                },
            )

            if error := response.data.get("error"):
                if error == DeviceFlowError.AUTHORIZATION_PENDING:
                    self.logger.debug(response.data.get("error_description"))
                    await asyncio.sleep(self._interval)
                else:
                    raise GitHubException(response.data.get("error_description"))
            else:
                response.data = GitHubLoginOauthModel(response.data)
                break

        return response

Ancestors

Class variables

var logger : logging.Logger

Methods

async def activation(self, device_code: str, **kwargs: Dict[GitHubRequestKwarg, Any]) ‑> GitHubResponseModel[GitHubLoginOauthModel]

Wait for the user to enter the code and activate the device.

Arguments:

device_code

The device_code that was returned when registering the device.

Expand source code
async def activation(
    self,
    device_code: str,
    **kwargs: Dict[GitHubRequestKwarg, Any],
) -> GitHubResponseModel[GitHubLoginOauthModel]:
    """
    Wait for the user to enter the code and activate the device.

    **Arguments**:

    `device_code`

    The device_code that was returned when registering the device.

    """
    if self._expires is None:
        raise GitHubException("Expiration has passed, re-run the registration")

    _user_confirmed = None
    while _user_confirmed is None:

        if self._expires < datetime.timestamp(datetime.now()):
            raise GitHubException("User took too long to enter key")

        response = await self._client.async_call_api(
            endpoint=OAUTH_ACCESS_TOKEN_PATH,
            **{
                **kwargs,
                GitHubRequestKwarg.METHOD: HttpMethod.POST,
                GitHubRequestKwarg.PARAMS: {
                    "client_id": self.client_id,
                    "device_code": device_code,
                    "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
                },
            },
        )

        if error := response.data.get("error"):
            if error == DeviceFlowError.AUTHORIZATION_PENDING:
                self.logger.debug(response.data.get("error_description"))
                await asyncio.sleep(self._interval)
            else:
                raise GitHubException(response.data.get("error_description"))
        else:
            response.data = GitHubLoginOauthModel(response.data)
            break

    return response
async def close_session(self) ‑> None

Close open client session.

Expand source code
async def close_session(self) -> None:
    """Close open client session."""
    if self._session and self._close_session:
        await self._session.close()
async def register(self, **kwargs: Dict[GitHubRequestKwarg, Any]) ‑> GitHubResponseModel[GitHubLoginDeviceModel]

Register the device and return a object that contains the user code for authorization.

Expand source code
async def register(
    self,
    **kwargs: Dict[GitHubRequestKwarg, Any],
) -> GitHubResponseModel[GitHubLoginDeviceModel]:
    """Register the device and return a object that contains the user code for authorization."""
    response = await self._client.async_call_api(
        endpoint=OAUTH_DEVICE_LOGIN_PATH,
        **{
            **kwargs,
            GitHubRequestKwarg.METHOD: HttpMethod.POST,
            GitHubRequestKwarg.PARAMS: {
                "client_id": self.client_id,
                "scope": kwargs.get(GitHubRequestKwarg.SCOPE, ""),
            },
        },
    )
    response.data = GitHubLoginDeviceModel(response.data)
    self._interval = response.data.interval
    self._expires = datetime.timestamp(datetime.now()) + response.data.expires_in
    return response

Inherited members