diff --git a/.gitignore b/.gitignore index a800141bd..cca2cf7b8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,17 +7,17 @@ __pycache__/ # Distribution / packaging .Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ +/env/ +/build/ +/develop-eggs/ +/dist/ +/downloads/ +/eggs/ +/lib/ +/lib64/ +/parts/ +/sdist/ +/var/ *.egg-info/ .installed.cfg *.egg diff --git a/pyrightconfig.stricter.json b/pyrightconfig.stricter.json index 729f56cc7..104695c45 100644 --- a/pyrightconfig.stricter.json +++ b/pyrightconfig.stricter.json @@ -27,6 +27,7 @@ "stubs/backports_abc", "stubs/bleach", "stubs/boto", + "stubs/caldav", "stubs/commonmark", "stubs/cryptography", "stubs/docutils", diff --git a/stubs/caldav/@tests/stubtest_allowlist.txt b/stubs/caldav/@tests/stubtest_allowlist.txt new file mode 100644 index 000000000..452044772 --- /dev/null +++ b/stubs/caldav/@tests/stubtest_allowlist.txt @@ -0,0 +1,13 @@ +# **kwargs replaced with actual arguments in stubs +caldav.DAVClient.calendar +caldav.DAVClient.principal +caldav.davclient.DAVClient.calendar +caldav.davclient.DAVClient.principal + +# Initialized in class, but immediatly overwritten in __init__ +caldav.DAVClient.url +caldav.davclient.DAVClient.url +caldav.davclient.DAVResponse.headers +caldav.elements.base.BaseElement.children + +.*.findprop diff --git a/stubs/caldav/METADATA.toml b/stubs/caldav/METADATA.toml new file mode 100644 index 000000000..d8ced5c4e --- /dev/null +++ b/stubs/caldav/METADATA.toml @@ -0,0 +1,3 @@ +version = "0.8" +# also types-lxml, types-vobject, and types-icalendar when those stubs are added +requires = ["types-requests"] diff --git a/stubs/caldav/caldav/__init__.pyi b/stubs/caldav/caldav/__init__.pyi new file mode 100644 index 000000000..721495fa9 --- /dev/null +++ b/stubs/caldav/caldav/__init__.pyi @@ -0,0 +1,2 @@ +from .davclient import DAVClient as DAVClient +from .objects import * diff --git a/stubs/caldav/caldav/davclient.pyi b/stubs/caldav/caldav/davclient.pyi new file mode 100644 index 000000000..6689f23e3 --- /dev/null +++ b/stubs/caldav/caldav/davclient.pyi @@ -0,0 +1,70 @@ +from collections.abc import Iterable, Mapping +from typing import Any +from urllib.parse import ParseResult, SplitResult + +from requests.auth import AuthBase +from requests.models import Response +from requests.structures import CaseInsensitiveDict + +from .lib.url import URL +from .objects import Calendar, DAVObject, Principal + +_Element = Any # actually lxml.etree._Element + +class DAVResponse: + reason: str + tree: _Element | None + status: int + headers: CaseInsensitiveDict[str] + objects: dict[str, dict[str, str]] # only defined after call to find_objects_and_props() + def __init__(self, response: Response) -> None: ... + @property + def raw(self) -> str: ... + def validate_status(self, status: str) -> None: ... + def find_objects_and_props(self) -> None: ... + def expand_simple_props( + self, props: Iterable[Any] = ..., multi_value_props: Iterable[Any] = ..., xpath: str | None = ... + ) -> dict[str, dict[str, str]]: ... + +class DAVClient: + proxy: str | None + url: URL + headers: dict[str, str] + username: str | None + password: str | None + auth: AuthBase | None + ssl_verify_cert: bool | str + ssl_cert: str | tuple[str, str] | None + def __init__( + self, + url: str, + proxy: str | None = ..., + username: str | None = ..., + password: str | None = ..., + auth: AuthBase | None = ..., + ssl_verify_cert: bool | str = ..., + ssl_cert: str | tuple[str, str] | None = ..., + ) -> None: ... + def principal(self, *, url: str | ParseResult | SplitResult | URL | None = ...) -> Principal: ... + def calendar( + self, + url: str | ParseResult | SplitResult | URL | None = ..., + parent: DAVObject | None = ..., + name: str | None = ..., + id: str | None = ..., + props: Mapping[Any, Any] = ..., + **extra: Any, + ) -> Calendar: ... + def check_dav_support(self) -> str | None: ... + def check_cdav_support(self) -> bool: ... + def check_scheduling_support(self) -> bool: ... + def propfind(self, url: str | None = ..., props: str = ..., depth: int = ...) -> DAVResponse: ... + def proppatch(self, url: str, body: str, dummy: None = ...) -> DAVResponse: ... + def report(self, url: str, query: str = ..., depth: int = ...) -> DAVResponse: ... + def mkcol(self, url: str, body: str, dummy: None = ...) -> DAVResponse: ... + def mkcalendar(self, url: str, body: str = ..., dummy: None = ...) -> DAVResponse: ... + def put(self, url: str, body: str, headers: Mapping[str, str] = ...) -> DAVResponse: ... + def post(self, url: str, body: str, headers: Mapping[str, str] = ...) -> DAVResponse: ... + def delete(self, url: str) -> DAVResponse: ... + def options(self, url: str) -> DAVResponse: ... + def request(self, url: str, method: str = ..., body: str = ..., headers: Mapping[str, str] = ...) -> DAVResponse: ... diff --git a/stubs/caldav/caldav/elements/__init__.pyi b/stubs/caldav/caldav/elements/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/caldav/caldav/elements/base.pyi b/stubs/caldav/caldav/elements/base.pyi new file mode 100644 index 000000000..c418e0cde --- /dev/null +++ b/stubs/caldav/caldav/elements/base.pyi @@ -0,0 +1,23 @@ +from _typeshed import Self +from collections.abc import Iterable +from typing import Any, ClassVar + +_Element = Any # actually lxml.etree._Element + +class BaseElement: + tag: ClassVar[str | None] + children: list[BaseElement] + value: str | None + attributes: Any | None + caldav_class: Any | None + def __init__(self, name: str | None = ..., value: str | bytes | None = ...) -> None: ... + def __add__(self: Self, other: BaseElement) -> Self: ... + def xmlelement(self) -> _Element: ... + def xmlchildren(self, root: _Element) -> None: ... + def append(self: Self, element: BaseElement | Iterable[BaseElement]) -> Self: ... + +class NamedBaseElement(BaseElement): + def __init__(self, name: str | None = ...) -> None: ... + +class ValuedBaseElement(BaseElement): + def __init__(self, value: str | bytes | None = ...) -> None: ... diff --git a/stubs/caldav/caldav/elements/cdav.pyi b/stubs/caldav/caldav/elements/cdav.pyi new file mode 100644 index 000000000..632894a24 --- /dev/null +++ b/stubs/caldav/caldav/elements/cdav.pyi @@ -0,0 +1,100 @@ +import datetime +from typing import ClassVar + +from .base import BaseElement, NamedBaseElement, ValuedBaseElement + +class CalendarQuery(BaseElement): + tag: ClassVar[str] + +class FreeBusyQuery(BaseElement): + tag: ClassVar[str] + +class Mkcalendar(BaseElement): + tag: ClassVar[str] + +class CalendarMultiGet(BaseElement): + tag: ClassVar[str] + +class ScheduleInboxURL(BaseElement): + tag: ClassVar[str] + +class ScheduleOutboxURL(BaseElement): + tag: ClassVar[str] + +class Filter(BaseElement): + tag: ClassVar[str] + +class CompFilter(NamedBaseElement): + tag: ClassVar[str] + +class PropFilter(NamedBaseElement): + tag: ClassVar[str] + +class ParamFilter(NamedBaseElement): + tag: ClassVar[str] + +class TextMatch(ValuedBaseElement): + tag: ClassVar[str] + def __init__(self, value, collation: str = ..., negate: bool = ...) -> None: ... + +class TimeRange(BaseElement): + tag: ClassVar[str] + def __init__(self, start: datetime.datetime | None = ..., end: datetime.datetime | None = ...) -> None: ... + +class NotDefined(BaseElement): + tag: ClassVar[str] + +class CalendarData(BaseElement): + tag: ClassVar[str] + +class Expand(BaseElement): + tag: ClassVar[str] + def __init__(self, start: datetime.datetime | None, end: datetime.datetime | None = ...) -> None: ... + +class Comp(NamedBaseElement): + tag: ClassVar[str] + +class CalendarUserAddressSet(BaseElement): + tag: ClassVar[str] + +class CalendarUserType(BaseElement): + tag: ClassVar[str] + +class CalendarHomeSet(BaseElement): + tag: ClassVar[str] + +class Calendar(BaseElement): + tag: ClassVar[str] + +class CalendarDescription(ValuedBaseElement): + tag: ClassVar[str] + +class CalendarTimeZone(ValuedBaseElement): + tag: ClassVar[str] + +class SupportedCalendarComponentSet(ValuedBaseElement): + tag: ClassVar[str] + +class SupportedCalendarData(ValuedBaseElement): + tag: ClassVar[str] + +class MaxResourceSize(ValuedBaseElement): + tag: ClassVar[str] + +class MinDateTime(ValuedBaseElement): + tag: ClassVar[str] + +class MaxDateTime(ValuedBaseElement): + tag: ClassVar[str] + +class MaxInstances(ValuedBaseElement): + tag: ClassVar[str] + +class MaxAttendeesPerInstance(ValuedBaseElement): + tag: ClassVar[str] + +class Allprop(BaseElement): + tag: ClassVar[str] + +class ScheduleTag(BaseElement): + tag: ClassVar[str] diff --git a/stubs/caldav/caldav/elements/dav.pyi b/stubs/caldav/caldav/elements/dav.pyi new file mode 100644 index 000000000..4f3577e2f --- /dev/null +++ b/stubs/caldav/caldav/elements/dav.pyi @@ -0,0 +1,63 @@ +from typing import ClassVar + +from .base import BaseElement, ValuedBaseElement + +class Propfind(BaseElement): + tag: ClassVar[str] + +class PropertyUpdate(BaseElement): + tag: ClassVar[str] + +class Mkcol(BaseElement): + tag: ClassVar[str] + +class SyncCollection(BaseElement): + tag: ClassVar[str] + +class SyncToken(BaseElement): + tag: ClassVar[str] + +class SyncLevel(BaseElement): + tag: ClassVar[str] + +class Prop(BaseElement): + tag: ClassVar[str] + +class Collection(BaseElement): + tag: ClassVar[str] + +class Set(BaseElement): + tag: ClassVar[str] + +class ResourceType(BaseElement): + tag: ClassVar[str] + +class DisplayName(ValuedBaseElement): + tag: ClassVar[str] + +class GetEtag(ValuedBaseElement): + tag: ClassVar[str] + +class Href(BaseElement): + tag: ClassVar[str] + +class Response(BaseElement): + tag: ClassVar[str] + +class Status(BaseElement): + tag: ClassVar[str] + +class PropStat(BaseElement): + tag: ClassVar[str] + +class MultiStatus(BaseElement): + tag: ClassVar[str] + +class CurrentUserPrincipal(BaseElement): + tag: ClassVar[str] + +class PrincipalCollectionSet(BaseElement): + tag: ClassVar[str] + +class Allprop(BaseElement): + tag: ClassVar[str] diff --git a/stubs/caldav/caldav/elements/ical.pyi b/stubs/caldav/caldav/elements/ical.pyi new file mode 100644 index 000000000..762d6ae1e --- /dev/null +++ b/stubs/caldav/caldav/elements/ical.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar + +from .base import ValuedBaseElement + +class CalendarColor(ValuedBaseElement): + tag: ClassVar[str] + +class CalendarOrder(ValuedBaseElement): + tag: ClassVar[str] diff --git a/stubs/caldav/caldav/lib/__init__.pyi b/stubs/caldav/caldav/lib/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/caldav/caldav/lib/error.pyi b/stubs/caldav/caldav/lib/error.pyi new file mode 100644 index 000000000..7e3ed16e6 --- /dev/null +++ b/stubs/caldav/caldav/lib/error.pyi @@ -0,0 +1,24 @@ +from typing import Any, Type + +def assert_(condition: object) -> None: ... + +ERR_FRAGMENT: str + +class AuthorizationError(Exception): + url: Any + reason: str + +class DAVError(Exception): ... +class PropsetError(DAVError): ... +class ProppatchError(DAVError): ... +class PropfindError(DAVError): ... +class ReportError(DAVError): ... +class MkcolError(DAVError): ... +class MkcalendarError(DAVError): ... +class PutError(DAVError): ... +class DeleteError(DAVError): ... +class NotFoundError(DAVError): ... +class ConsistencyError(DAVError): ... +class ReponseError(DAVError): ... + +exception_by_method: dict[str, Type[DAVError]] diff --git a/stubs/caldav/caldav/lib/namespace.pyi b/stubs/caldav/caldav/lib/namespace.pyi new file mode 100644 index 000000000..a3ad5724e --- /dev/null +++ b/stubs/caldav/caldav/lib/namespace.pyi @@ -0,0 +1,4 @@ +nsmap: dict[str, str] +nsmap2: dict[str, str] + +def ns(prefix: str, tag: str | None = ...) -> str: ... diff --git a/stubs/caldav/caldav/lib/url.pyi b/stubs/caldav/caldav/lib/url.pyi new file mode 100644 index 000000000..a47a19578 --- /dev/null +++ b/stubs/caldav/caldav/lib/url.pyi @@ -0,0 +1,27 @@ +from typing import overload +from urllib.parse import ParseResult, SplitResult + +class URL: + def __init__(self, url: str | ParseResult | SplitResult) -> None: ... + def __bool__(self) -> bool: ... + def __ne__(self, other: object) -> bool: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + @overload + @classmethod + def objectify(cls, url: None) -> None: ... + @overload + @classmethod + def objectify(cls, url: URL | str | ParseResult | SplitResult) -> URL: ... + def __getattr__(self, attr: str): ... + def __unicode__(self) -> str: ... + def strip_trailing_slash(self) -> URL: ... + def is_auth(self) -> bool: ... + def unauth(self) -> URL: ... + def canonical(self) -> URL: ... + def join(self, path: object) -> URL: ... + +@overload +def make(url: None) -> None: ... +@overload +def make(url: URL | str | ParseResult | SplitResult) -> URL: ... diff --git a/stubs/caldav/caldav/lib/vcal.pyi b/stubs/caldav/caldav/lib/vcal.pyi new file mode 100644 index 000000000..7b567428c --- /dev/null +++ b/stubs/caldav/caldav/lib/vcal.pyi @@ -0,0 +1 @@ +def fix(event): ... diff --git a/stubs/caldav/caldav/objects.pyi b/stubs/caldav/caldav/objects.pyi new file mode 100644 index 000000000..f7a62c2c0 --- /dev/null +++ b/stubs/caldav/caldav/objects.pyi @@ -0,0 +1,174 @@ +import datetime +from _typeshed import Self +from collections.abc import Iterable, Iterator, Mapping +from typing import Any, Type, TypeVar, overload +from typing_extensions import Literal +from urllib.parse import ParseResult, SplitResult + +from .davclient import DAVClient +from .elements.cdav import CompFilter, ScheduleInboxURL, ScheduleOutboxURL +from .lib.url import URL + +_CC = TypeVar("_CC", bound=CalendarObjectResource) + +_VBase = Any # actually vobject.base.VBase +_vCalAddress = Any # actually icalendar.vCalAddress + +class DAVObject: + id: str | None + url: URL | None + client: DAVClient | None + parent: DAVObject | None + name: str | None + props: Mapping[Any, Any] + extra_init_options: dict[str, Any] + def __init__( + self, + client: DAVClient | None = ..., + url: str | ParseResult | SplitResult | URL | None = ..., + parent: DAVObject | None = ..., + name: str | None = ..., + id: str | None = ..., + props: Mapping[Any, Any] | None = ..., + **extra: Any, + ) -> None: ... + @property + def canonical_url(self) -> str: ... + def children(self, type: str | None = ...) -> list[tuple[URL, Any, Any]]: ... + def get_property(self, prop, use_cached: bool = ..., **passthrough) -> Any | None: ... + def get_properties( + self, props: Any | None = ..., depth: int = ..., parse_response_xml: bool = ..., parse_props: bool = ... + ): ... + def set_properties(self: Self, props: Any | None = ...) -> Self: ... + def save(self: Self) -> Self: ... + def delete(self) -> None: ... + +class CalendarSet(DAVObject): + def calendars(self) -> list[Calendar]: ... + def make_calendar( + self, name: str | None = ..., cal_id: str | None = ..., supported_calendar_component_set: Any | None = ... + ) -> Calendar: ... + def calendar(self, name: str | None = ..., cal_id: str | None = ...) -> Calendar: ... + +class Principal(DAVObject): + def __init__(self, client: DAVClient | None = ..., url: str | ParseResult | SplitResult | URL | None = ...) -> None: ... + def calendars(self) -> list[Calendar]: ... + def make_calendar( + self, name: str | None = ..., cal_id: str | None = ..., supported_calendar_component_set: Any | None = ... + ) -> Calendar: ... + def calendar(self, name: str | None = ..., cal_id: str | None = ...) -> Calendar: ... + def get_vcal_address(self) -> _vCalAddress: ... + calendar_home_set: CalendarSet # can also be set to anything URL.objectify() accepts + def freebusy_request(self, dtstart, dtend, attendees): ... + def calendar_user_address_set(self) -> list[str]: ... + def schedule_inbox(self) -> ScheduleInbox: ... + def schedule_outbox(self) -> ScheduleOutbox: ... + +class Calendar(DAVObject): + def get_supported_components(self) -> list[Any]: ... + def save_with_invites(self, ical: str, attendees, **attendeeoptions) -> None: ... + def save_event(self, ical: str, no_overwrite: bool = ..., no_create: bool = ...) -> Event: ... + def save_todo(self, ical: str, no_overwrite: bool = ..., no_create: bool = ...) -> Todo: ... + def save_journal(self, ical: str, no_overwrite: bool = ..., no_create: bool = ...) -> Journal: ... + add_event = save_event + add_todo = save_todo + add_journal = save_journal + def calendar_multiget(self, event_urls: Iterable[URL]) -> list[Event]: ... + def build_date_search_query( + self, + start, + end: datetime.datetime | None = ..., + compfilter: Literal["VEVENT"] | None = ..., + expand: bool | Literal["maybe"] = ..., + ): ... + @overload + def date_search( + self, + start: datetime.datetime, + end: datetime.datetime | None = ..., + compfilter: Literal["VEVENT"] = ..., + expand: bool | Literal["maybe"] = ..., + ) -> list[Event]: ... + @overload + def date_search( + self, start: datetime.datetime, *, compfilter: None, expand: bool | Literal["maybe"] = ... + ) -> list[CalendarObjectResource]: ... + @overload + def date_search( + self, start: datetime.datetime, end: datetime.datetime | None, compfilter: None, expand: bool | Literal["maybe"] = ... + ) -> list[CalendarObjectResource]: ... + @overload + def search(self, xml, comp_class: None = ...) -> list[CalendarObjectResource]: ... + @overload + def search(self, xml, comp_class: Type[_CC]) -> list[_CC]: ... + def freebusy_request(self, start: datetime.datetime, end: datetime.datetime) -> FreeBusy: ... + def todos(self, sort_keys: Iterable[str] = ..., include_completed: bool = ..., sort_key: str | None = ...) -> list[Todo]: ... + def event_by_url(self, href, data: Any | None = ...) -> Event: ... + def object_by_uid(self, uid: str, comp_filter: CompFilter | None = ...) -> Event: ... + def todo_by_uid(self, uid: str) -> CalendarObjectResource: ... + def event_by_uid(self, uid: str) -> CalendarObjectResource: ... + def journal_by_uid(self, uid: str) -> CalendarObjectResource: ... + event = event_by_uid + def events(self) -> list[Event]: ... + def objects_by_sync_token( + self, sync_token: Any | None = ..., load_objects: bool = ... + ) -> SynchronizableCalendarObjectCollection: ... + objects = objects_by_sync_token + def journals(self) -> list[Journal]: ... + +class ScheduleMailbox(Calendar): + def __init__( + self, + client: DAVClient | None = ..., + principal: Principal | None = ..., + url: str | ParseResult | SplitResult | URL | None = ..., + ) -> None: ... + def get_items(self): ... + +class ScheduleInbox(ScheduleMailbox): + findprop = ScheduleInboxURL + +class ScheduleOutbox(ScheduleMailbox): + findprop = ScheduleOutboxURL + +class SynchronizableCalendarObjectCollection: + def __init__(self, calendar, objects, sync_token) -> None: ... + def __iter__(self) -> Iterator[Any]: ... + def objects_by_url(self): ... + def sync(self) -> tuple[Any, Any]: ... + +class CalendarObjectResource(DAVObject): + def __init__( + self, + client: DAVClient | None = ..., + url: str | ParseResult | SplitResult | URL | None = ..., + data: Any | None = ..., + parent: Any | None = ..., + id: Any | None = ..., + props: Any | None = ..., + ) -> None: ... + def add_organizer(self) -> None: ... + def add_attendee(self, attendee, no_default_parameters: bool = ..., **parameters) -> None: ... + def is_invite_request(self) -> bool: ... + def accept_invite(self, calendar: Any | None = ...) -> None: ... + def decline_invite(self, calendar: Any | None = ...) -> None: ... + def tentatively_accept_invite(self, calendar: Any | None = ...) -> None: ... + def copy(self: Self, keep_uid: bool = ..., new_parent: Any | None = ...) -> Self: ... + def load(self: Self) -> Self: ... + def change_attendee_status(self, attendee: Any | None = ..., **kwargs) -> None: ... + def save( + self: Self, no_overwrite: bool = ..., no_create: bool = ..., obj_type: Any | None = ..., if_schedule_tag_match: bool = ... + ) -> Self: ... + data: Any + vobject_instance: _VBase + icalendar_instance: Any + instance: _VBase + +class Event(CalendarObjectResource): ... +class Journal(CalendarObjectResource): ... + +class FreeBusy(CalendarObjectResource): + def __init__(self, parent, data, url: str | ParseResult | SplitResult | URL | None = ..., id: Any | None = ...) -> None: ... + +class Todo(CalendarObjectResource): + def complete(self, completion_timestamp: datetime.datetime | None = ...) -> None: ...