Skip to content

Commit

Permalink
tooling: generate some type stubs
Browse files Browse the repository at this point in the history
Use mypy stubgen tool
(https://mypy.readthedocs.io/en/stable/stubgen.html) to generate a few
stubs for types used in the project in place of "# type: ignore"
overrides.

General approach:
  1. run `stubgen -o stubs -m some.module`
  2. check for errors by running `tox -e type`
  3. curate pyi files to fix errors (delete conflicting types, etc)
  • Loading branch information
dbarnett committed Aug 25, 2024
1 parent 0b88004 commit e20ee37
Show file tree
Hide file tree
Showing 12 changed files with 280 additions and 5 deletions.
2 changes: 1 addition & 1 deletion gcalcli/gcal.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from dateutil.relativedelta import relativedelta
from dateutil.tz import tzlocal
from google_auth_oauthlib.flow import InstalledAppFlow # type: ignore
from googleapiclient.discovery import build # type: ignore
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError # type: ignore
from google.auth.transport.requests import Request # type: ignore

Expand Down
2 changes: 1 addition & 1 deletion gcalcli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from dateutil.parser import parse as dateutil_parse
from dateutil.tz import tzlocal
from parsedatetime.parsedatetime import Calendar # type: ignore
from parsedatetime.parsedatetime import Calendar

locale.setlocale(locale.LC_ALL, '')
fuzzy_date_parse = Calendar().parse
Expand Down
8 changes: 5 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ Repository = "https://github.com/insanum/gcalcli"
[project.optional-dependencies]
vobject = ["vobject"]

[tool.setuptools]
packages = ["gcalcli"]

[tool.setuptools.data-files]
"share/man/man1" = ["docs/man1/gcalcli.1"]

Expand All @@ -59,6 +62,5 @@ float_to_top = true
combine_star = true
py_version = 3

[[tool.mypy.overrides]]
module = ["gcalcli"]
ignore_missing_imports = true
[tool.mypy]
mypy_path = "gcalcli:stubs:tests"
49 changes: 49 additions & 0 deletions stubs/google/auth/credentials.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import abc
from _typeshed import Incomplete

class Credentials(metaclass=abc.ABCMeta):
token: Incomplete
expiry: Incomplete
def __init__(self) -> None: ...
@property
def expired(self): ...
@property
def valid(self): ...
@abc.abstractmethod
def refresh(self, request): ...
def apply(self, headers, token: Incomplete | None = None) -> None: ...
def before_request(self, request, method, url, headers) -> None: ...

class AnonymousCredentials(Credentials):
@property
def expired(self): ...
@property
def valid(self): ...
def refresh(self, request) -> None: ...
def apply(self, headers, token: Incomplete | None = None) -> None: ...
def before_request(self, request, method, url, headers) -> None: ...

class ReadOnlyScoped(metaclass=abc.ABCMeta):
def __init__(self) -> None: ...
@property
def scopes(self): ...
@property
@abc.abstractmethod
def requires_scopes(self): ...
def has_scopes(self, scopes): ...

class Scoped(ReadOnlyScoped, metaclass=abc.ABCMeta):
@abc.abstractmethod
def with_scopes(self, scopes): ...

def with_scopes_if_required(credentials, scopes): ...

class Signing(metaclass=abc.ABCMeta):
@abc.abstractmethod
def sign_bytes(self, message): ...
@property
@abc.abstractmethod
def signer_email(self): ...
@property
@abc.abstractmethod
def signer(self): ...
4 changes: 4 additions & 0 deletions stubs/google/auth/exceptions.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class GoogleAuthError(Exception): ...
class TransportError(GoogleAuthError): ...
class RefreshError(GoogleAuthError): ...
class DefaultCredentialsError(GoogleAuthError): ...
20 changes: 20 additions & 0 deletions stubs/google/auth/transport/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import abc
from _typeshed import Incomplete

DEFAULT_REFRESH_STATUS_CODES: Incomplete
DEFAULT_MAX_REFRESH_ATTEMPTS: int

class Response(metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
def status(self): ...
@property
@abc.abstractmethod
def headers(self): ...
@property
@abc.abstractmethod
def data(self): ...

class Request(metaclass=abc.ABCMeta):
@abc.abstractmethod
def __call__(self, url, method: str = 'GET', body: Incomplete | None = None, headers: Incomplete | None = None, timeout: Incomplete | None = None, **kwargs): ...
21 changes: 21 additions & 0 deletions stubs/google/auth/transport/requests.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import requests
from _typeshed import Incomplete
from google.auth import exceptions as exceptions, transport as transport

class _Response(transport.Response):
def __init__(self, response) -> None: ...
@property
def status(self): ...
@property
def headers(self): ...
@property
def data(self): ...

class Request(transport.Request):
session: Incomplete
def __init__(self, session: Incomplete | None = None) -> None: ...
def __call__(self, url, method: str = 'GET', body: Incomplete | None = None, headers: Incomplete | None = None, timeout: Incomplete | None = None, **kwargs): ...

class AuthorizedSession(requests.Session):
credentials: Incomplete
def __init__(self, credentials, refresh_status_codes=..., max_refresh_attempts=..., refresh_timeout: Incomplete | None = None, **kwargs) -> None: ...
24 changes: 24 additions & 0 deletions stubs/google/oauth2/credentials.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from _typeshed import Incomplete
from google.auth import credentials as credentials, exceptions as exceptions

class Credentials(credentials.ReadOnlyScoped, credentials.Credentials):
token: Incomplete
def __init__(self, token, refresh_token: Incomplete | None = None, id_token: Incomplete | None = None, token_uri: Incomplete | None = None, client_id: Incomplete | None = None, client_secret: Incomplete | None = None, scopes: Incomplete | None = None) -> None: ...
@property
def refresh_token(self): ...
@property
def token_uri(self): ...
@property
def id_token(self): ...
@property
def client_id(self): ...
@property
def client_secret(self): ...
@property
def requires_scopes(self): ...
expiry: Incomplete
def refresh(self, request) -> None: ...
@classmethod
def from_authorized_user_info(cls, info, scopes: Incomplete | None = None): ...
@classmethod
def from_authorized_user_file(cls, filename, scopes: Incomplete | None = None): ...
36 changes: 36 additions & 0 deletions stubs/google_auth_oauthlib/flow.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import wsgiref.simple_server
from google.oauth2.credentials import Credentials
from _typeshed import Incomplete

class Flow:
client_type: Incomplete
client_config: Incomplete
oauth2session: Incomplete
code_verifier: Incomplete
autogenerate_code_verifier: Incomplete
def __init__(self, oauth2session, client_type, client_config, redirect_uri: Incomplete | None = None, code_verifier: Incomplete | None = None, autogenerate_code_verifier: bool = True) -> None: ...
@classmethod
def from_client_config(cls, client_config, scopes, **kwargs): ...
@classmethod
def from_client_secrets_file(cls, client_secrets_file, scopes, **kwargs): ...
@property
def redirect_uri(self): ...
@redirect_uri.setter
def redirect_uri(self, value) -> None: ...
def authorization_url(self, **kwargs): ...
def fetch_token(self, **kwargs): ...
@property
def credentials(self): ...
def authorized_session(self): ...

class InstalledAppFlow(Flow):
redirect_uri: Incomplete
def run_local_server(self, host: str = 'localhost', bind_addr: Incomplete | None = None, port: int = 8080, authorization_prompt_message=..., success_message=..., open_browser: bool = True, redirect_uri_trailing_slash: bool = True, timeout_seconds: Incomplete | None = None, token_audience: Incomplete | None = None, browser: Incomplete | None = None, **kwargs) -> Credentials: ...

class _WSGIRequestHandler(wsgiref.simple_server.WSGIRequestHandler):
def log_message(self, format, *args) -> None: ...

class _RedirectWSGIApp:
last_request_uri: Incomplete
def __init__(self, success_message) -> None: ...
def __call__(self, environ, start_response): ...
26 changes: 26 additions & 0 deletions stubs/googleapiclient/discovery.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from _typeshed import Incomplete
from email.generator import Generator as BytesGenerator

__all__ = ['build', 'build_from_document', 'fix_method_name', 'key2param']

class _BytesGenerator(BytesGenerator): ...

def fix_method_name(name): ...
def key2param(key): ...
def build(serviceName, version, http: Incomplete | None = None, discoveryServiceUrl=..., developerKey: Incomplete | None = None, model: Incomplete | None = None, requestBuilder=..., credentials: Incomplete | None = None, cache_discovery: bool = True, cache: Incomplete | None = None): ...
def build_from_document(service, base: Incomplete | None = None, future: Incomplete | None = None, http: Incomplete | None = None, developerKey: Incomplete | None = None, model: Incomplete | None = None, requestBuilder=..., credentials: Incomplete | None = None): ...

class ResourceMethodParameters:
argmap: Incomplete
required_params: Incomplete
repeated_params: Incomplete
pattern_params: Incomplete
query_params: Incomplete
path_params: Incomplete
param_types: Incomplete
enum_params: Incomplete
def __init__(self, method_desc) -> None: ...
def set_parameters(self, method_desc) -> None: ...

class Resource:
def __init__(self, http, baseUrl, model, requestBuilder, developerKey, resourceDesc, rootDesc, schema) -> None: ...
92 changes: 92 additions & 0 deletions stubs/parsedatetime/parsedatetime/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import datetime
import logging
import time
from _typeshed import Incomplete
from collections.abc import Generator
from typing import Tuple

__version__: str
__url__: str
__download_url__: str
__description__: str

class NullHandler(logging.Handler):
def emit(self, record) -> None: ...

log: Incomplete
debug: bool
pdtLocales: Incomplete
VERSION_FLAG_STYLE: int
VERSION_CONTEXT_STYLE: int

class Calendar:
ptc: Incomplete
version: Incomplete
def __init__(self, constants: Incomplete | None = None, version=...) -> None: ...
def context(self) -> Generator[Incomplete, None, None]: ...
@property
def currentContext(self): ...
def parseDate(self, dateString, sourceTime: Incomplete | None = None): ...
def parseDateText(self, dateString, sourceTime: Incomplete | None = None): ...
def evalRanges(self, datetimeString, sourceTime: Incomplete | None = None): ...
def parseDT(self, datetimeString, sourceTime: Incomplete | None = None, tzinfo: Incomplete | None = None, version: Incomplete | None = None) -> Tuple[datetime.datetime, Incomplete]: ...
def parse(self, datetimeString, sourceTime: Incomplete | None = None, version: Incomplete | None = None) -> Tuple[time.struct_time, Incomplete]: ...
def inc(self, source, month: Incomplete | None = None, year: Incomplete | None = None): ...
def nlp(self, inputString, sourceTime: Incomplete | None = None, version: Incomplete | None = None): ...

class Constants:
localeID: Incomplete
fallbackLocales: Incomplete
locale: Incomplete
usePyICU: Incomplete
Second: int
Minute: int
Hour: int
Day: int
Week: int
Month: int
Year: int
rangeSep: str
BirthdayEpoch: int
StartTimeFromSourceTime: bool
StartHour: int
YearParseStyle: int
DOWParseStyle: int
CurrentDOWParseStyle: bool
RE_DATE4: Incomplete
RE_DATE3: Incomplete
RE_MONTH: Incomplete
RE_WEEKDAY: Incomplete
RE_NUMBER: Incomplete
RE_SPECIAL: Incomplete
RE_UNITS_ONLY: Incomplete
RE_UNITS: Incomplete
RE_QUNITS: Incomplete
RE_MODIFIER: Incomplete
RE_TIMEHMS: Incomplete
RE_TIMEHMS2: Incomplete
RE_NLP_PREFIX: str
RE_DATE: Incomplete
RE_DATE2: Incomplete
RE_DAY: Incomplete
RE_DAY2: Incomplete
RE_TIME: Incomplete
RE_REMAINING: str
RE_RTIMEHMS: Incomplete
RE_RTIMEHMS2: Incomplete
RE_RDATE: Incomplete
RE_RDATE3: Incomplete
DATERNG1: Incomplete
DATERNG2: Incomplete
DATERNG3: Incomplete
TIMERNG1: Incomplete
TIMERNG2: Incomplete
TIMERNG3: Incomplete
TIMERNG4: Incomplete
re_option: Incomplete
cre_source: Incomplete
cre_keys: Incomplete
def __init__(self, localeID: Incomplete | None = None, usePyICU: bool = True, fallbackLocales=['en_US']) -> None: ...
def __getattr__(self, name): ...
def daysInMonth(self, month, year): ...
def getSource(self, sourceKey, sourceTime: Incomplete | None = None): ...
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ skip_install = true
deps =
mypy >= 1.0
types-python-dateutil
types-requests
types-vobject
commands =
mypy {posargs:gcalcli}
Expand Down

0 comments on commit e20ee37

Please sign in to comment.