from __future__ import annotations from typing import Any, Literal from urllib.parse import urlencode import requests from requests import Session from todoist_api_python._core.endpoints import ( ACCESS_TOKEN_PATH, ACCESS_TOKENS_PATH, AUTHORIZE_PATH, get_api_url, get_oauth_url, ) from todoist_api_python._core.http_requests import delete, post from todoist_api_python._core.utils import run_async from todoist_api_python.models import AuthResult """ Possible permission scopes: - `data:read`: Read-only access - `data:read_write`: Read and write access - `data:delete`: Full access including delete - `task:add`: Can create new tasks - `project:delete`: Can delete projects - `backups:read`: Can access user backups without MFA """ Scope = Literal[ "task:add", "data:read", "data:read_write", "data:delete", "project:delete", "backups:read", ] def get_authentication_url(client_id: str, scopes: list[Scope], state: str) -> str: """Get authorization URL to initiate OAuth flow.""" if len(scopes) == 0: raise ValueError("At least one authorization scope should be requested.") endpoint = get_oauth_url(AUTHORIZE_PATH) query = { "client_id": client_id, "scope": ",".join(scopes), "state": state, } return f"{endpoint}?{urlencode(query)}" def get_auth_token( client_id: str, client_secret: str, code: str, session: Session | None = None ) -> AuthResult: """Get access token using provided client ID, client secret, and auth code.""" endpoint = get_oauth_url(ACCESS_TOKEN_PATH) session = session or requests.Session() data = { "client_id": client_id, "client_secret": client_secret, "code": code, } response: dict[str, Any] = post(session=session, url=endpoint, data=data) return AuthResult.from_dict(response) async def get_auth_token_async( client_id: str, client_secret: str, code: str ) -> AuthResult: return await run_async(lambda: get_auth_token(client_id, client_secret, code)) def revoke_auth_token( client_id: str, client_secret: str, token: str, session: Session | None = None ) -> bool: """Revoke an access token.""" # `get_api_url` is not a typo. Deleting access tokens is done using the regular API. endpoint = get_api_url(ACCESS_TOKENS_PATH) session = session or requests.Session() params = { "client_id": client_id, "client_secret": client_secret, "access_token": token, } return delete(session=session, url=endpoint, params=params) async def revoke_auth_token_async( client_id: str, client_secret: str, token: str ) -> bool: return await run_async(lambda: revoke_auth_token(client_id, client_secret, token))