From 8eb048617cef30c4d282db3a702b6e28ead9a4f9 Mon Sep 17 00:00:00 2001 From: tdstein Date: Mon, 16 Dec 2024 14:46:12 -0500 Subject: [PATCH 1/4] add base protocols with correct method signatures --- src/posit/connect/bundles.py | 4 +-- src/posit/connect/content.py | 10 +++--- src/posit/connect/environments.py | 19 +++-------- src/posit/connect/groups.py | 4 +-- src/posit/connect/jobs.py | 19 +++-------- src/posit/connect/metrics/shiny_usage.py | 4 +-- src/posit/connect/metrics/usage.py | 2 +- src/posit/connect/metrics/visits.py | 4 +-- src/posit/connect/oauth/associations.py | 4 +-- src/posit/connect/oauth/integrations.py | 4 +-- src/posit/connect/oauth/sessions.py | 4 +-- src/posit/connect/packages.py | 27 ++++------------ src/posit/connect/permissions.py | 4 +-- src/posit/connect/resources.py | 40 +++++++++++++++++++++--- src/posit/connect/tasks.py | 2 +- src/posit/connect/users.py | 4 +-- src/posit/connect/vanities.py | 6 ++-- src/posit/connect/variants.py | 4 +-- tests/posit/connect/test_resources.py | 4 +-- 19 files changed, 82 insertions(+), 87 deletions(-) diff --git a/src/posit/connect/bundles.py b/src/posit/connect/bundles.py index 515751fa..3363e3bb 100644 --- a/src/posit/connect/bundles.py +++ b/src/posit/connect/bundles.py @@ -11,11 +11,11 @@ from .context import Context -class BundleMetadata(resources.Resource): +class BundleMetadata(resources.BaseResource): pass -class Bundle(resources.Resource): +class Bundle(resources.BaseResource): @property def metadata(self) -> BundleMetadata: return BundleMetadata(self._ctx, **self.get("metadata", {})) diff --git a/src/posit/connect/content.py b/src/posit/connect/content.py index a98e9a64..dd900f6d 100644 --- a/src/posit/connect/content.py +++ b/src/posit/connect/content.py @@ -24,7 +24,7 @@ from .errors import ClientError from .oauth.associations import ContentItemAssociations from .permissions import Permissions -from .resources import Active, Resource, Resources, _ResourceSequence +from .resources import Active, BaseResource, Resources, _ResourceSequence from .tags import ContentItemTags from .vanities import VanityMixin from .variants import Variants @@ -160,7 +160,7 @@ def update( ) -class ContentItemOAuth(Resource): +class ContentItemOAuth(BaseResource): def __init__(self, ctx: Context, content_guid: str) -> None: super().__init__(ctx) self["content_guid"] = content_guid @@ -170,11 +170,11 @@ def associations(self) -> ContentItemAssociations: return ContentItemAssociations(self._ctx, content_guid=self["content_guid"]) -class ContentItemOwner(Resource): +class ContentItemOwner(BaseResource): pass -class ContentItem(Active, VanityMixin, Resource): +class ContentItem(Active, VanityMixin, BaseResource): class _AttrsBase(TypedDict, total=False): # # `name` will be set by other _Attrs classes # name: str @@ -376,7 +376,7 @@ def restart(self) -> None: f"Restart not supported for this application mode: {self['app_mode']}. Did you need to use the 'render()' method instead? Note that some application modes do not support 'render()' or 'restart()'.", ) - def update( + def update( # type: ignore[reportIncompatibleMethodOverride] self, **attrs: Unpack[ContentItem._Attrs], ) -> None: diff --git a/src/posit/connect/environments.py b/src/posit/connect/environments.py index 4b229fa9..d2cc85e5 100644 --- a/src/posit/connect/environments.py +++ b/src/posit/connect/environments.py @@ -1,19 +1,17 @@ from __future__ import annotations from abc import abstractmethod -from collections.abc import Mapping, Sized +from typing import Protocol from typing_extensions import ( - Any, List, Literal, - Protocol, - SupportsIndex, TypedDict, - overload, runtime_checkable, ) +from .resources import Resource, ResourceSequence + MatchingType = Literal["any", "exact", "none"] """Directions for how environments are considered for selection. @@ -40,7 +38,7 @@ class Installations(TypedDict): """Interpreter installations in an execution environment.""" -class Environment(Mapping[str, Any]): +class Environment(Resource): @abstractmethod def destroy(self) -> None: """Destroy the environment. @@ -95,13 +93,7 @@ def update( @runtime_checkable -class Environments(Sized, Protocol): - @overload - def __getitem__(self, index: SupportsIndex) -> Environment: ... - - @overload - def __getitem__(self, index: slice) -> List[Environment]: ... - +class Environments(ResourceSequence[Environment], Protocol): def create( self, *, @@ -217,4 +209,3 @@ def find_by( ---- This action requires administrator or publisher privileges. """ - ... diff --git a/src/posit/connect/groups.py b/src/posit/connect/groups.py index 717b18d9..fa5cf9dd 100644 --- a/src/posit/connect/groups.py +++ b/src/posit/connect/groups.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, List, Optional, overload from .paginator import Paginator -from .resources import Resource, Resources +from .resources import BaseResource, Resources if TYPE_CHECKING: import requests @@ -14,7 +14,7 @@ from .users import User -class Group(Resource): +class Group(BaseResource): def __init__(self, ctx: Context, **kwargs) -> None: super().__init__(ctx, **kwargs) self._ctx: Context = ctx diff --git a/src/posit/connect/jobs.py b/src/posit/connect/jobs.py index d9124f82..8797686b 100644 --- a/src/posit/connect/jobs.py +++ b/src/posit/connect/jobs.py @@ -1,18 +1,14 @@ from __future__ import annotations -from abc import abstractmethod -from collections.abc import Mapping, Sized from typing import ( - Any, Iterable, - List, Literal, Protocol, - SupportsIndex, - overload, runtime_checkable, ) +from .resources import Resource, ResourceSequence + JobTag = Literal[ "unknown", "build_report", @@ -43,8 +39,7 @@ StatusCode = Literal[0, 1, 2] -class Job(Mapping[str, Any]): - @abstractmethod +class Job(Resource, Protocol): def destroy(self) -> None: """Destroy the job. @@ -59,13 +54,7 @@ def destroy(self) -> None: @runtime_checkable -class Jobs(Sized, Protocol): - @overload - def __getitem__(self, index: SupportsIndex) -> Job: ... - - @overload - def __getitem__(self, index: slice) -> List[Job]: ... - +class Jobs(ResourceSequence[Job], Protocol): def fetch(self) -> Iterable[Job]: """Fetch all jobs. diff --git a/src/posit/connect/metrics/shiny_usage.py b/src/posit/connect/metrics/shiny_usage.py index 68a391be..a5240f4f 100644 --- a/src/posit/connect/metrics/shiny_usage.py +++ b/src/posit/connect/metrics/shiny_usage.py @@ -3,10 +3,10 @@ from typing import List, overload from ..cursors import CursorPaginator -from ..resources import Resource, Resources +from ..resources import BaseResource, Resources -class ShinyUsageEvent(Resource): +class ShinyUsageEvent(BaseResource): @property def content_guid(self) -> str: """The associated unique content identifier. diff --git a/src/posit/connect/metrics/usage.py b/src/posit/connect/metrics/usage.py index 6714cca7..b9eac649 100644 --- a/src/posit/connect/metrics/usage.py +++ b/src/posit/connect/metrics/usage.py @@ -10,7 +10,7 @@ from . import shiny_usage, visits -class UsageEvent(resources.Resource): +class UsageEvent(resources.BaseResource): @staticmethod def from_event( event: visits.VisitEvent | shiny_usage.ShinyUsageEvent, diff --git a/src/posit/connect/metrics/visits.py b/src/posit/connect/metrics/visits.py index 393aae36..ea88dfb6 100644 --- a/src/posit/connect/metrics/visits.py +++ b/src/posit/connect/metrics/visits.py @@ -3,10 +3,10 @@ from typing import List, overload from ..cursors import CursorPaginator -from ..resources import Resource, Resources +from ..resources import BaseResource, Resources -class VisitEvent(Resource): +class VisitEvent(BaseResource): @property def content_guid(self) -> str: """The associated unique content identifier. diff --git a/src/posit/connect/oauth/associations.py b/src/posit/connect/oauth/associations.py index 9fc999e6..44723dfc 100644 --- a/src/posit/connect/oauth/associations.py +++ b/src/posit/connect/oauth/associations.py @@ -3,10 +3,10 @@ from typing import List from ..context import Context -from ..resources import Resource, Resources +from ..resources import BaseResource, Resources -class Association(Resource): +class Association(BaseResource): pass diff --git a/src/posit/connect/oauth/integrations.py b/src/posit/connect/oauth/integrations.py index 540cd6f0..3e3259e9 100644 --- a/src/posit/connect/oauth/integrations.py +++ b/src/posit/connect/oauth/integrations.py @@ -2,11 +2,11 @@ from typing import List, Optional, overload -from ..resources import Resource, Resources +from ..resources import BaseResource, Resources from .associations import IntegrationAssociations -class Integration(Resource): +class Integration(BaseResource): """OAuth integration resource.""" @property diff --git a/src/posit/connect/oauth/sessions.py b/src/posit/connect/oauth/sessions.py index 503ea254..fae3981b 100644 --- a/src/posit/connect/oauth/sessions.py +++ b/src/posit/connect/oauth/sessions.py @@ -2,10 +2,10 @@ from typing import List, Optional, overload -from ..resources import Resource, Resources +from ..resources import BaseResource, Resources -class Session(Resource): +class Session(BaseResource): """OAuth session resource.""" def delete(self) -> None: diff --git a/src/posit/connect/packages.py b/src/posit/connect/packages.py index 63289c4f..4983eaff 100644 --- a/src/posit/connect/packages.py +++ b/src/posit/connect/packages.py @@ -1,28 +1,19 @@ from __future__ import annotations from typing_extensions import ( - Any, Iterable, Literal, - Mapping, Protocol, - Sized, - SupportsIndex, - overload, ) - -class ContentPackage(Mapping[str, Any]): - pass +from .resources import Resource, ResourceSequence -class ContentPackages(Sized, Protocol): - @overload - def __getitem__(self, index: SupportsIndex) -> ContentPackage: ... +class ContentPackage(Resource, Protocol): + pass - @overload - def __getitem__(self, index: slice) -> ContentPackage: ... +class ContentPackages(ResourceSequence[ContentPackage], Protocol): def fetch( self, *, @@ -84,17 +75,11 @@ def find_by( ... -class Package(Mapping[str, Any]): +class Package(Resource, Protocol): pass -class Packages(Sized, Protocol): - @overload - def __getitem__(self, index: SupportsIndex) -> ContentPackage: ... - - @overload - def __getitem__(self, index: slice) -> ContentPackage: ... - +class Packages(ResourceSequence[Package], Protocol): def fetch( self, *, diff --git a/src/posit/connect/permissions.py b/src/posit/connect/permissions.py index e8afd9c1..211decc2 100644 --- a/src/posit/connect/permissions.py +++ b/src/posit/connect/permissions.py @@ -6,7 +6,7 @@ from requests.sessions import Session as Session -from .resources import Resource, Resources +from .resources import BaseResource, Resources if TYPE_CHECKING: from .context import Context @@ -14,7 +14,7 @@ from .users import User -class Permission(Resource): +class Permission(BaseResource): def destroy(self) -> None: """Destroy the permission.""" path = f"v1/content/{self['content_guid']}/permissions/{self['id']}" diff --git a/src/posit/connect/resources.py b/src/posit/connect/resources.py index 66e6058f..70cffc53 100644 --- a/src/posit/connect/resources.py +++ b/src/posit/connect/resources.py @@ -6,8 +6,15 @@ from typing import ( TYPE_CHECKING, Any, + Hashable, Iterable, + Iterator, + List, + Protocol, Sequence, + SupportsIndex, + TypeVar, + overload, ) from .context import Context @@ -17,7 +24,7 @@ from .context import Context -class Resource(dict): +class BaseResource(dict): def __init__(self, ctx: Context, /, **kwargs): super().__init__(**kwargs) self._ctx = ctx @@ -42,7 +49,7 @@ def __init__(self, ctx: Context) -> None: self._ctx = ctx -class Active(ABC, Resource): +class Active(ABC, BaseResource): def __init__(self, ctx: Context, path: str, /, **attributes): """A dict abstraction for any HTTP endpoint that returns a singular resource. @@ -62,7 +69,11 @@ def __init__(self, ctx: Context, path: str, /, **attributes): self._path = path -class _Resource(dict): +class Resource(Protocol): + def __getitem__(self, key: Hashable) -> Any: ... + + +class _Resource(dict, Resource): def __init__(self, ctx: Context, path: str, **attributes): self._ctx = ctx self._path = path @@ -77,7 +88,26 @@ def update(self, **attributes): # type: ignore[reportIncompatibleMethodOverride super().update(**result) -class _ResourceSequence(Sequence): +T = TypeVar("T", bound=Resource) + + +class ResourceSequence(Protocol[T]): + @overload + def __getitem__(self, index: SupportsIndex, /) -> T: ... + + @overload + def __getitem__(self, index: slice, /) -> List[T]: ... + + def __len__(self) -> int: ... + + def __iter__(self) -> Iterator[T]: ... + + def __str__(self) -> str: ... + + def __repr__(self) -> str: ... + + +class _ResourceSequence(Sequence[T], ResourceSequence[T]): def __init__(self, ctx: Context, path: str, *, uid: str = "guid"): self._ctx = ctx self._path = path @@ -89,7 +119,7 @@ def __getitem__(self, index): def __len__(self) -> int: return len(list(self.fetch())) - def __iter__(self): + def __iter__(self) -> Iterator[T]: return iter(self.fetch()) def __str__(self) -> str: diff --git a/src/posit/connect/tasks.py b/src/posit/connect/tasks.py index 907a458f..8360304d 100644 --- a/src/posit/connect/tasks.py +++ b/src/posit/connect/tasks.py @@ -7,7 +7,7 @@ from . import resources -class Task(resources.Resource): +class Task(resources.BaseResource): @property def is_finished(self) -> bool: """The task state. diff --git a/src/posit/connect/users.py b/src/posit/connect/users.py index f002cf88..334617a5 100644 --- a/src/posit/connect/users.py +++ b/src/posit/connect/users.py @@ -9,14 +9,14 @@ from . import me from .content import Content from .paginator import Paginator -from .resources import Resource, Resources +from .resources import BaseResource, Resources if TYPE_CHECKING: from .context import Context from .groups import Group -class User(Resource): +class User(BaseResource): @property def content(self) -> Content: return Content(self._ctx, owner_guid=self["guid"]) diff --git a/src/posit/connect/vanities.py b/src/posit/connect/vanities.py index 75becc6f..5a483054 100644 --- a/src/posit/connect/vanities.py +++ b/src/posit/connect/vanities.py @@ -4,10 +4,10 @@ from .context import Context from .errors import ClientError -from .resources import Resource, Resources +from .resources import BaseResource, Resources -class Vanity(Resource): +class Vanity(BaseResource): """A vanity resource. Vanities maintain custom URL paths assigned to content. @@ -115,7 +115,7 @@ def all(self) -> List[Vanity]: return [Vanity(self._ctx, **result) for result in results] -class VanityMixin(Resource): +class VanityMixin(BaseResource): """Mixin class to add a vanity attribute to a resource.""" class HasGuid(TypedDict): diff --git a/src/posit/connect/variants.py b/src/posit/connect/variants.py index ba8597c1..9970c70e 100644 --- a/src/posit/connect/variants.py +++ b/src/posit/connect/variants.py @@ -1,11 +1,11 @@ from typing import List from .context import Context -from .resources import Resource, Resources +from .resources import BaseResource, Resources from .tasks import Task -class Variant(Resource): +class Variant(BaseResource): def render(self) -> Task: path = f"variants/{self['id']}/render" response = self._ctx.client.post(path) diff --git a/tests/posit/connect/test_resources.py b/tests/posit/connect/test_resources.py index 28cf6e0e..a17d252b 100644 --- a/tests/posit/connect/test_resources.py +++ b/tests/posit/connect/test_resources.py @@ -3,13 +3,13 @@ from unittest import mock from unittest.mock import Mock -from posit.connect.resources import Resource +from posit.connect.resources import BaseResource config = Mock() session = Mock() -class FakeResource(Resource): +class FakeResource(BaseResource): @property def foo(self) -> Optional[str]: return self.get("foo") From 986e745c21f60e80cd5116269884b02d1de03873 Mon Sep 17 00:00:00 2001 From: tdstein Date: Mon, 16 Dec 2024 15:19:37 -0500 Subject: [PATCH 2/4] fix: import from typing_extensions instead of typing --- examples/connect/databricks/fastapi/app.py | 2 +- .../posit/connect/test_content_item_permissions.py | 3 +-- src/posit/connect/_api.py | 3 ++- src/posit/connect/_api_call.py | 3 ++- src/posit/connect/_json.py | 2 +- src/posit/connect/_utils.py | 2 +- src/posit/connect/bundles.py | 3 ++- src/posit/connect/client.py | 3 +-- src/posit/connect/config.py | 3 ++- src/posit/connect/content.py | 9 ++++++--- src/posit/connect/context.py | 2 +- src/posit/connect/cursors.py | 3 ++- src/posit/connect/env.py | 2 +- src/posit/connect/environments.py | 2 +- src/posit/connect/errors.py | 3 ++- src/posit/connect/external/databricks.py | 2 +- src/posit/connect/external/snowflake.py | 2 +- src/posit/connect/groups.py | 2 +- src/posit/connect/jobs.py | 2 +- src/posit/connect/metrics/shiny_usage.py | 2 +- src/posit/connect/metrics/usage.py | 3 +-- src/posit/connect/metrics/visits.py | 2 +- src/posit/connect/oauth/associations.py | 2 +- src/posit/connect/oauth/integrations.py | 2 +- src/posit/connect/oauth/oauth.py | 3 +-- src/posit/connect/oauth/sessions.py | 2 +- src/posit/connect/paginator.py | 3 ++- src/posit/connect/permissions.py | 3 +-- src/posit/connect/resources.py | 3 ++- src/posit/connect/system.py | 4 +--- src/posit/connect/tags.py | 3 +-- src/posit/connect/tasks.py | 2 +- src/posit/connect/users.py | 12 +++++++++--- src/posit/connect/vanities.py | 4 +--- src/posit/connect/variants.py | 2 +- tests/posit/connect/external/test_databricks.py | 2 +- tests/posit/connect/test_jobs.py | 3 +-- tests/posit/connect/test_resources.py | 3 ++- 38 files changed, 60 insertions(+), 53 deletions(-) diff --git a/examples/connect/databricks/fastapi/app.py b/examples/connect/databricks/fastapi/app.py index ed01f41c..a3e49e6a 100644 --- a/examples/connect/databricks/fastapi/app.py +++ b/examples/connect/databricks/fastapi/app.py @@ -3,11 +3,11 @@ from __future__ import annotations import os -from typing import TYPE_CHECKING, Annotated from databricks import sql from databricks.sdk.core import Config, databricks_cli from fastapi import FastAPI, Header +from typing_extensions import TYPE_CHECKING, Annotated from posit.connect.external.databricks import PositCredentialsStrategy diff --git a/integration/tests/posit/connect/test_content_item_permissions.py b/integration/tests/posit/connect/test_content_item_permissions.py index 518178d5..9f3ee04b 100644 --- a/integration/tests/posit/connect/test_content_item_permissions.py +++ b/integration/tests/posit/connect/test_content_item_permissions.py @@ -1,8 +1,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING - import pytest +from typing_extensions import TYPE_CHECKING from posit import connect diff --git a/src/posit/connect/_api.py b/src/posit/connect/_api.py index 29325158..f45dc0ee 100644 --- a/src/posit/connect/_api.py +++ b/src/posit/connect/_api.py @@ -4,7 +4,8 @@ from __future__ import annotations from collections.abc import Mapping -from typing import TYPE_CHECKING, Any, Optional, cast + +from typing_extensions import TYPE_CHECKING, Any, Optional, cast from ._api_call import ApiCallMixin, get_api from ._json import Jsonifiable, JsonifiableDict, ResponseAttrs diff --git a/src/posit/connect/_api_call.py b/src/posit/connect/_api_call.py index 56ce70f6..0de5f0ba 100644 --- a/src/posit/connect/_api_call.py +++ b/src/posit/connect/_api_call.py @@ -1,7 +1,8 @@ from __future__ import annotations import posixpath -from typing import TYPE_CHECKING, Protocol + +from typing_extensions import TYPE_CHECKING, Protocol if TYPE_CHECKING: from ._json import Jsonifiable diff --git a/src/posit/connect/_json.py b/src/posit/connect/_json.py index 62f28f82..85f331fa 100644 --- a/src/posit/connect/_json.py +++ b/src/posit/connect/_json.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Tuple, TypeVar, Union +from typing_extensions import Dict, List, Tuple, TypeVar, Union # Implemented in https://github.com/posit-dev/py-shiny/blob/415ced034e6c500adda524abb7579731c32088b5/shiny/types.py#L357-L386 # Table from: https://github.com/python/cpython/blob/df1eec3dae3b1eddff819fd70f58b03b3fbd0eda/Lib/json/encoder.py#L77-L95 diff --git a/src/posit/connect/_utils.py b/src/posit/connect/_utils.py index 26842bbc..e279f3b7 100644 --- a/src/posit/connect/_utils.py +++ b/src/posit/connect/_utils.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any +from typing_extensions import Any def drop_none(x: dict[str, Any]) -> dict[str, Any]: diff --git a/src/posit/connect/bundles.py b/src/posit/connect/bundles.py index 3363e3bb..5dba4ee4 100644 --- a/src/posit/connect/bundles.py +++ b/src/posit/connect/bundles.py @@ -3,7 +3,8 @@ from __future__ import annotations import io -from typing import TYPE_CHECKING, List + +from typing_extensions import TYPE_CHECKING, List from . import resources, tasks diff --git a/src/posit/connect/client.py b/src/posit/connect/client.py index f0f5c3a1..6ef22c21 100644 --- a/src/posit/connect/client.py +++ b/src/posit/connect/client.py @@ -2,9 +2,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, overload - from requests import Response, Session +from typing_extensions import TYPE_CHECKING, overload from . import hooks, me from .auth import Auth diff --git a/src/posit/connect/config.py b/src/posit/connect/config.py index 59d03c87..500bda73 100644 --- a/src/posit/connect/config.py +++ b/src/posit/connect/config.py @@ -1,7 +1,8 @@ """Client configuration.""" import os -from typing import Optional + +from typing_extensions import Optional from . import urls diff --git a/src/posit/connect/content.py b/src/posit/connect/content.py index dd900f6d..f7a3fdb8 100644 --- a/src/posit/connect/content.py +++ b/src/posit/connect/content.py @@ -4,18 +4,21 @@ import posixpath import time -from typing import ( + +from typing_extensions import ( TYPE_CHECKING, Any, List, Literal, + NotRequired, Optional, + Required, + TypedDict, + Unpack, cast, overload, ) -from typing_extensions import NotRequired, Required, TypedDict, Unpack - from . import tasks from ._api import ApiDictEndpoint, JsonifiableDict from .bundles import Bundles diff --git a/src/posit/connect/context.py b/src/posit/connect/context.py index ec7ff55a..dbc456b7 100644 --- a/src/posit/connect/context.py +++ b/src/posit/connect/context.py @@ -2,9 +2,9 @@ import functools import weakref -from typing import TYPE_CHECKING, Protocol from packaging.version import Version +from typing_extensions import TYPE_CHECKING, Protocol if TYPE_CHECKING: from .client import Client diff --git a/src/posit/connect/cursors.py b/src/posit/connect/cursors.py index 22f406e1..414d965d 100644 --- a/src/posit/connect/cursors.py +++ b/src/posit/connect/cursors.py @@ -1,7 +1,8 @@ from __future__ import annotations from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, Generator, List + +from typing_extensions import TYPE_CHECKING, Any, Generator, List if TYPE_CHECKING: from .context import Context diff --git a/src/posit/connect/env.py b/src/posit/connect/env.py index 5ca26cd2..f801dc00 100644 --- a/src/posit/connect/env.py +++ b/src/posit/connect/env.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Iterator, List, Mapping, MutableMapping, Optional +from typing_extensions import TYPE_CHECKING, Any, Iterator, List, Mapping, MutableMapping, Optional from .resources import Resources diff --git a/src/posit/connect/environments.py b/src/posit/connect/environments.py index d2cc85e5..1fb78b59 100644 --- a/src/posit/connect/environments.py +++ b/src/posit/connect/environments.py @@ -1,11 +1,11 @@ from __future__ import annotations from abc import abstractmethod -from typing import Protocol from typing_extensions import ( List, Literal, + Protocol, TypedDict, runtime_checkable, ) diff --git a/src/posit/connect/errors.py b/src/posit/connect/errors.py index 8e8bf8db..e7ef7997 100644 --- a/src/posit/connect/errors.py +++ b/src/posit/connect/errors.py @@ -1,5 +1,6 @@ import json -from typing import Any + +from typing_extensions import Any class ClientError(Exception): diff --git a/src/posit/connect/external/databricks.py b/src/posit/connect/external/databricks.py index e73c5f71..0c9ad886 100644 --- a/src/posit/connect/external/databricks.py +++ b/src/posit/connect/external/databricks.py @@ -6,9 +6,9 @@ """ import abc -from typing import Callable, Dict, Optional import requests +from typing_extensions import Callable, Dict, Optional from ..client import Client from ..oauth import Credentials diff --git a/src/posit/connect/external/snowflake.py b/src/posit/connect/external/snowflake.py index 7f5ec923..1bc5dd74 100644 --- a/src/posit/connect/external/snowflake.py +++ b/src/posit/connect/external/snowflake.py @@ -3,7 +3,7 @@ NOTE: The APIs in this module are provided as a convenience and are subject to breaking changes. """ -from typing import Optional +from typing_extensions import Optional from ..client import Client from .external import is_local diff --git a/src/posit/connect/groups.py b/src/posit/connect/groups.py index fa5cf9dd..36f530e4 100644 --- a/src/posit/connect/groups.py +++ b/src/posit/connect/groups.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List, Optional, overload +from typing_extensions import TYPE_CHECKING, List, Optional, overload from .paginator import Paginator from .resources import BaseResource, Resources diff --git a/src/posit/connect/jobs.py b/src/posit/connect/jobs.py index 8797686b..959c1dab 100644 --- a/src/posit/connect/jobs.py +++ b/src/posit/connect/jobs.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import ( +from typing_extensions import ( Iterable, Literal, Protocol, diff --git a/src/posit/connect/metrics/shiny_usage.py b/src/posit/connect/metrics/shiny_usage.py index a5240f4f..d9657d9f 100644 --- a/src/posit/connect/metrics/shiny_usage.py +++ b/src/posit/connect/metrics/shiny_usage.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import List, overload +from typing_extensions import List, overload from ..cursors import CursorPaginator from ..resources import BaseResource, Resources diff --git a/src/posit/connect/metrics/usage.py b/src/posit/connect/metrics/usage.py index b9eac649..b513a52f 100644 --- a/src/posit/connect/metrics/usage.py +++ b/src/posit/connect/metrics/usage.py @@ -2,9 +2,8 @@ from __future__ import annotations -from typing import List, overload - from requests.sessions import Session as Session +from typing_extensions import List, overload from .. import resources from . import shiny_usage, visits diff --git a/src/posit/connect/metrics/visits.py b/src/posit/connect/metrics/visits.py index ea88dfb6..503d14af 100644 --- a/src/posit/connect/metrics/visits.py +++ b/src/posit/connect/metrics/visits.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import List, overload +from typing_extensions import List, overload from ..cursors import CursorPaginator from ..resources import BaseResource, Resources diff --git a/src/posit/connect/oauth/associations.py b/src/posit/connect/oauth/associations.py index 44723dfc..5d20db02 100644 --- a/src/posit/connect/oauth/associations.py +++ b/src/posit/connect/oauth/associations.py @@ -1,6 +1,6 @@ """OAuth association resources.""" -from typing import List +from typing_extensions import List from ..context import Context from ..resources import BaseResource, Resources diff --git a/src/posit/connect/oauth/integrations.py b/src/posit/connect/oauth/integrations.py index 3e3259e9..7dca39eb 100644 --- a/src/posit/connect/oauth/integrations.py +++ b/src/posit/connect/oauth/integrations.py @@ -1,6 +1,6 @@ """OAuth integration resources.""" -from typing import List, Optional, overload +from typing_extensions import List, Optional, overload from ..resources import BaseResource, Resources from .associations import IntegrationAssociations diff --git a/src/posit/connect/oauth/oauth.py b/src/posit/connect/oauth/oauth.py index 90df7270..efec4395 100644 --- a/src/posit/connect/oauth/oauth.py +++ b/src/posit/connect/oauth/oauth.py @@ -1,9 +1,8 @@ from __future__ import annotations import os -from typing import TYPE_CHECKING, Optional -from typing_extensions import TypedDict +from typing_extensions import TYPE_CHECKING, Optional, TypedDict from ..resources import Resources from .integrations import Integrations diff --git a/src/posit/connect/oauth/sessions.py b/src/posit/connect/oauth/sessions.py index fae3981b..e0db5829 100644 --- a/src/posit/connect/oauth/sessions.py +++ b/src/posit/connect/oauth/sessions.py @@ -1,6 +1,6 @@ """OAuth session resources.""" -from typing import List, Optional, overload +from typing_extensions import List, Optional, overload from ..resources import BaseResource, Resources diff --git a/src/posit/connect/paginator.py b/src/posit/connect/paginator.py index 75b224d3..0252e66f 100644 --- a/src/posit/connect/paginator.py +++ b/src/posit/connect/paginator.py @@ -1,7 +1,8 @@ from __future__ import annotations from dataclasses import dataclass -from typing import TYPE_CHECKING, Generator, List + +from typing_extensions import TYPE_CHECKING, Generator, List if TYPE_CHECKING: from .context import Context diff --git a/src/posit/connect/permissions.py b/src/posit/connect/permissions.py index 211decc2..4b029cb5 100644 --- a/src/posit/connect/permissions.py +++ b/src/posit/connect/permissions.py @@ -2,9 +2,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List, Optional, overload - from requests.sessions import Session as Session +from typing_extensions import TYPE_CHECKING, List, Optional, overload from .resources import BaseResource, Resources diff --git a/src/posit/connect/resources.py b/src/posit/connect/resources.py index 70cffc53..7095c8fa 100644 --- a/src/posit/connect/resources.py +++ b/src/posit/connect/resources.py @@ -3,7 +3,8 @@ import posixpath import warnings from abc import ABC -from typing import ( + +from typing_extensions import ( TYPE_CHECKING, Any, Hashable, diff --git a/src/posit/connect/system.py b/src/posit/connect/system.py index 446eff4f..46353fd5 100644 --- a/src/posit/connect/system.py +++ b/src/posit/connect/system.py @@ -2,9 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List, Literal, overload - -from typing_extensions import TypedDict, Unpack +from typing_extensions import TYPE_CHECKING, List, Literal, TypedDict, Unpack, overload from .context import ContextManager from .resources import Active diff --git a/src/posit/connect/tags.py b/src/posit/connect/tags.py index 502a7b49..0ff303ae 100644 --- a/src/posit/connect/tags.py +++ b/src/posit/connect/tags.py @@ -1,9 +1,8 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Optional, overload -from typing_extensions import NotRequired, TypedDict, Unpack +from typing_extensions import TYPE_CHECKING, NotRequired, Optional, TypedDict, Unpack, overload from .context import Context, ContextManager from .resources import Active diff --git a/src/posit/connect/tasks.py b/src/posit/connect/tasks.py index 8360304d..ac6b2931 100644 --- a/src/posit/connect/tasks.py +++ b/src/posit/connect/tasks.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import overload +from typing_extensions import overload from . import resources diff --git a/src/posit/connect/users.py b/src/posit/connect/users.py index 334617a5..a925df90 100644 --- a/src/posit/connect/users.py +++ b/src/posit/connect/users.py @@ -2,9 +2,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List, Literal - -from typing_extensions import NotRequired, Required, TypedDict, Unpack +from typing_extensions import ( + TYPE_CHECKING, + List, + Literal, + NotRequired, + Required, + TypedDict, + Unpack, +) from . import me from .content import Content diff --git a/src/posit/connect/vanities.py b/src/posit/connect/vanities.py index 5a483054..3ab9f4f2 100644 --- a/src/posit/connect/vanities.py +++ b/src/posit/connect/vanities.py @@ -1,6 +1,4 @@ -from typing import Callable, List, Optional - -from typing_extensions import NotRequired, Required, TypedDict, Unpack +from typing_extensions import Callable, List, NotRequired, Optional, Required, TypedDict, Unpack from .context import Context from .errors import ClientError diff --git a/src/posit/connect/variants.py b/src/posit/connect/variants.py index 9970c70e..2e518fb0 100644 --- a/src/posit/connect/variants.py +++ b/src/posit/connect/variants.py @@ -1,4 +1,4 @@ -from typing import List +from typing_extensions import List from .context import Context from .resources import BaseResource, Resources diff --git a/tests/posit/connect/external/test_databricks.py b/tests/posit/connect/external/test_databricks.py index 9861b907..134911b1 100644 --- a/tests/posit/connect/external/test_databricks.py +++ b/tests/posit/connect/external/test_databricks.py @@ -1,9 +1,9 @@ import base64 -from typing import Dict from unittest.mock import patch import pytest import responses +from typing_extensions import Dict from posit.connect import Client from posit.connect.external.databricks import ( diff --git a/tests/posit/connect/test_jobs.py b/tests/posit/connect/test_jobs.py index b2463d98..802dd850 100644 --- a/tests/posit/connect/test_jobs.py +++ b/tests/posit/connect/test_jobs.py @@ -1,8 +1,7 @@ -from typing import TYPE_CHECKING - import pytest import responses from requests.exceptions import HTTPError +from typing_extensions import TYPE_CHECKING from posit.connect.client import Client diff --git a/tests/posit/connect/test_resources.py b/tests/posit/connect/test_resources.py index a17d252b..52536b96 100644 --- a/tests/posit/connect/test_resources.py +++ b/tests/posit/connect/test_resources.py @@ -1,8 +1,9 @@ import warnings -from typing import Optional from unittest import mock from unittest.mock import Mock +from typing_extensions import Optional + from posit.connect.resources import BaseResource config = Mock() From de6755279e44237daf9d8ee94e2e13d9ef6a2457 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Tue, 17 Dec 2024 12:14:47 -0500 Subject: [PATCH 3/4] Add test to verify no `typing` imports are ever used --- tests/posit/connect/test_internal_code.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/posit/connect/test_internal_code.py diff --git a/tests/posit/connect/test_internal_code.py b/tests/posit/connect/test_internal_code.py new file mode 100644 index 00000000..6672911f --- /dev/null +++ b/tests/posit/connect/test_internal_code.py @@ -0,0 +1,20 @@ +from pathlib import Path + +import pytest + +here = Path(__file__).resolve().parent +root_dir = here.parent.parent.parent + +tests_dir = root_dir / "tests" +src_dir = root_dir / "src" +integration_tests_dir = tests_dir / "integration" / "tests" + + +@pytest.mark.parametrize("path", [tests_dir, src_dir, integration_tests_dir]) +def test_no_from_typing_imports(path: Path): + for python_file in path.rglob("*.py"): + file_txt = python_file.read_text() + if "\nfrom typing import" in file_txt: + raise ValueError( + f"Found `from typing import` in {python_file.relative_to(root_dir)}. Please replace the import with `typing_extensions`." + ) From d75c45edb41352911289f53407e96c19ebf802a8 Mon Sep 17 00:00:00 2001 From: tdstein Date: Fri, 20 Dec 2024 11:29:38 -0500 Subject: [PATCH 4/4] docs: update api documentation to show all resources (#370) --- docs/Makefile | 2 +- docs/_quarto.yml | 21 ++- src/posit/connect/client.py | 156 ++++++++++++----------- src/posit/connect/env.py | 2 + src/posit/connect/environments.py | 2 + src/posit/connect/external/__init__.py | 7 +- src/posit/connect/external/databricks.py | 7 +- src/posit/connect/external/snowflake.py | 8 +- src/posit/connect/jobs.py | 2 + src/posit/connect/metrics/__init__.py | 2 + src/posit/connect/oauth/__init__.py | 2 + src/posit/connect/packages.py | 2 + src/posit/connect/repository.py | 2 +- src/posit/connect/system.py | 2 +- src/posit/connect/tags.py | 2 + src/posit/connect/vanities.py | 2 + 16 files changed, 135 insertions(+), 86 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 51a41792..3c4bae0e 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -7,7 +7,7 @@ CURRENT_YEAR ?= $(shell date +%Y) # Quarto settings QUARTO ?= quarto # quartodoc doesn't like py3.8; Run using `--with` as it can conflict with the project's dependencies -QUARTODOC ?= --with "quartodoc==0.8.1" quartodoc +QUARTODOC ?= --no-cache --with "quartodoc==0.8.1" quartodoc # Netlify settings NETLIFY_SITE_ID ?= 5cea1f56-7935-4387-975a-18a7905d15ee diff --git a/docs/_quarto.yml b/docs/_quarto.yml index 2de729b1..f3a85eb0 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -94,20 +94,29 @@ quartodoc: - put - patch - delete - - title: Posit Connect Resources + - title: Resources contents: - connect.bundles - connect.content + - connect.env + - connect.environments - connect.groups + - connect.jobs + - connect.metrics + - connect.metrics.usage + - connect.oauth + - connect.oauth.associations + - connect.oauth.integrations + - connect.oauth.sessions + - connect.packages - connect.permissions + - connect.repository + - connect.system + - connect.tags - connect.tasks - connect.users - connect.vanities - - title: Posit Connect Metrics - contents: - - connect.metrics - - connect.metrics.usage - - title: External Integrations + - title: Third-Party Integrations contents: - connect.external.databricks - connect.external.snowflake diff --git a/src/posit/connect/client.py b/src/posit/connect/client.py index 6ef22c21..a9248d3a 100644 --- a/src/posit/connect/client.py +++ b/src/posit/connect/client.py @@ -11,8 +11,8 @@ from .content import Content from .context import Context, ContextManager, requires from .groups import Groups -from .metrics import Metrics -from .oauth import OAuth +from .metrics.metrics import Metrics +from .oauth.oauth import OAuth from .resources import _PaginatedResourceSequence, _ResourceSequence from .system import System from .tags import Tags @@ -43,18 +43,30 @@ class Client(ContextManager): ---------- content: Content Content resource. + environments: Environments + Environments resource. + groups: Groups + Groups resource. me: User - Connect user resource. + Current user resource. metrics: Metrics Metrics resource. oauth: OAuth OAuth resource. + packages: Packages + Packages resource. + system: System + System resource. + tags: Tags + Tags resource. tasks: Tasks Tasks resource. users: Users Users resource. + vanities: Vanities + Vanities resource. version: str - Server version. + The server version. """ @overload @@ -162,28 +174,21 @@ def __init__(self, *args, **kwargs) -> None: self._ctx = Context(self) @property - def version(self) -> str | None: + def content(self) -> Content: """ - The server version. + The content resource interface. Returns ------- - str - The version of the Posit Connect server. + Content + The content resource instance. """ - return self._ctx.version + return Content(self._ctx) @property - def me(self) -> User: - """ - The connected user. - - Returns - ------- - User - The currently authenticated user. - """ - return me.get(self._ctx) + @requires(version="2023.05.0") + def environments(self) -> Environments: + return _ResourceSequence(self._ctx, "v1/environments") @property def groups(self) -> Groups: @@ -197,40 +202,66 @@ def groups(self) -> Groups: return Groups(self._ctx) @property - def tasks(self) -> Tasks: + def me(self) -> User: """ - The tasks resource interface. + The connected user. Returns ------- - tasks.Tasks - The tasks resource instance. + User + The currently authenticated user. """ - return Tasks(self._ctx) + return me.get(self._ctx) @property - def users(self) -> Users: + def metrics(self) -> Metrics: """ - The users resource interface. + The Metrics API interface. + + The Metrics API is designed for capturing, retrieving, and managing + quantitative measurements of Connect interactions. It is commonly used + for monitoring and analyzing system performance, user behavior, and + business processes. This API facilitates real-time data collection and + accessibility, enabling organizations to make informed decisions based + on key performance indicators (KPIs). Returns ------- - Users - The users resource instance. + Metrics + The metrics API instance. + + Examples + -------- + >>> from posit import connect + >>> client = connect.Client() + >>> content_guid = "2243770d-ace0-4782-87f9-fe2aeca14fc8" + >>> events = client.metrics.usage.find(content_guid=content_guid) + >>> len(events) + 24 """ - return Users(self._ctx) + return Metrics(self._ctx) @property - def content(self) -> Content: + @requires(version="2024.08.0") + def oauth(self) -> OAuth: """ - The content resource interface. + The OAuth API interface. Returns ------- - Content - The content resource instance. + OAuth + The oauth API instance. """ - return Content(self._ctx) + return OAuth(self._ctx, self.cfg.api_key) + + @property + @requires(version="2024.11.0") + def packages(self) -> Packages: + return _PaginatedResourceSequence(self._ctx, "v1/packages", uid="name") + + @property + def system(self) -> System: + return System(self._ctx, "v1/system") @property def tags(self) -> Tags: @@ -255,63 +286,44 @@ def tags(self) -> Tags: return Tags(self._ctx, "v1/tags") @property - def metrics(self) -> Metrics: + def tasks(self) -> Tasks: """ - The Metrics API interface. - - The Metrics API is designed for capturing, retrieving, and managing - quantitative measurements of Connect interactions. It is commonly used - for monitoring and analyzing system performance, user behavior, and - business processes. This API facilitates real-time data collection and - accessibility, enabling organizations to make informed decisions based - on key performance indicators (KPIs). + The tasks resource interface. Returns ------- - Metrics - The metrics API instance. - - Examples - -------- - >>> from posit import connect - >>> client = connect.Client() - >>> content_guid = "2243770d-ace0-4782-87f9-fe2aeca14fc8" - >>> events = client.metrics.usage.find(content_guid=content_guid) - >>> len(events) - 24 + tasks.Tasks + The tasks resource instance. """ - return Metrics(self._ctx) + return Tasks(self._ctx) @property - @requires(version="2024.08.0") - def oauth(self) -> OAuth: + def users(self) -> Users: """ - The OAuth API interface. + The users resource interface. Returns ------- - OAuth - The oauth API instance. + Users + The users resource instance. """ - return OAuth(self._ctx, self.cfg.api_key) - - @property - @requires(version="2024.11.0") - def packages(self) -> Packages: - return _PaginatedResourceSequence(self._ctx, "v1/packages", uid="name") + return Users(self._ctx) @property def vanities(self) -> Vanities: return Vanities(self._ctx) @property - def system(self) -> System: - return System(self._ctx, "v1/system") + def version(self) -> str | None: + """ + The server version. - @property - @requires(version="2023.05.0") - def environments(self) -> Environments: - return _ResourceSequence(self._ctx, "v1/environments") + Returns + ------- + str + The version of the Posit Connect server. + """ + return self._ctx.version def __del__(self): """Close the session when the Client instance is deleted.""" diff --git a/src/posit/connect/env.py b/src/posit/connect/env.py index a04a2ec9..8ce42e2e 100644 --- a/src/posit/connect/env.py +++ b/src/posit/connect/env.py @@ -1,3 +1,5 @@ +"""Environment variable resources.""" + from __future__ import annotations from typing_extensions import TYPE_CHECKING, Any, Iterator, List, Mapping, MutableMapping, Optional diff --git a/src/posit/connect/environments.py b/src/posit/connect/environments.py index 1fb78b59..f235dbbd 100644 --- a/src/posit/connect/environments.py +++ b/src/posit/connect/environments.py @@ -1,3 +1,5 @@ +"""Environment resources.""" + from __future__ import annotations from abc import abstractmethod diff --git a/src/posit/connect/external/__init__.py b/src/posit/connect/external/__init__.py index 1f263905..efc68376 100644 --- a/src/posit/connect/external/__init__.py +++ b/src/posit/connect/external/__init__.py @@ -1 +1,6 @@ -# NOTE: The APIs in this module are provided as a convenience and are subject to breaking changes. +"""External integrations. + +Notes +----- +The APIs in this module are provided as a convenience and are subject to breaking changes. +""" diff --git a/src/posit/connect/external/databricks.py b/src/posit/connect/external/databricks.py index 0c9ad886..6578d659 100644 --- a/src/posit/connect/external/databricks.py +++ b/src/posit/connect/external/databricks.py @@ -1,7 +1,10 @@ -""" +"""Databricks SDK integration. + Databricks SDK credentials implementations which support interacting with Posit OAuth integrations on Connect. -NOTE: These APIs are provided as a convenience and are subject to breaking changes: +Notes +----- +These APIs are provided as a convenience and are subject to breaking changes: https://github.com/databricks/databricks-sdk-py#interface-stability """ diff --git a/src/posit/connect/external/snowflake.py b/src/posit/connect/external/snowflake.py index 1bc5dd74..54789c9b 100644 --- a/src/posit/connect/external/snowflake.py +++ b/src/posit/connect/external/snowflake.py @@ -1,6 +1,10 @@ -"""Snowflake SDK credentials implementations which support interacting with Posit OAuth integrations on Connect. +"""Snowflake SDK integration. -NOTE: The APIs in this module are provided as a convenience and are subject to breaking changes. +Snowflake SDK credentials implementations which support interacting with Posit OAuth integrations on Connect. + +Notes +----- +The APIs in this module are provided as a convenience and are subject to breaking changes. """ from typing_extensions import Optional diff --git a/src/posit/connect/jobs.py b/src/posit/connect/jobs.py index 959c1dab..69fb38af 100644 --- a/src/posit/connect/jobs.py +++ b/src/posit/connect/jobs.py @@ -1,3 +1,5 @@ +"""Job resources.""" + from __future__ import annotations from typing_extensions import ( diff --git a/src/posit/connect/metrics/__init__.py b/src/posit/connect/metrics/__init__.py index b285b30e..c5c6a6c3 100644 --- a/src/posit/connect/metrics/__init__.py +++ b/src/posit/connect/metrics/__init__.py @@ -1 +1,3 @@ +"""Metric resources.""" + from .metrics import Metrics as Metrics diff --git a/src/posit/connect/oauth/__init__.py b/src/posit/connect/oauth/__init__.py index 453aafb7..45a216ba 100644 --- a/src/posit/connect/oauth/__init__.py +++ b/src/posit/connect/oauth/__init__.py @@ -1,2 +1,4 @@ +"""OAuth resources.""" + from .oauth import Credentials as Credentials from .oauth import OAuth as OAuth diff --git a/src/posit/connect/packages.py b/src/posit/connect/packages.py index 4983eaff..af108e84 100644 --- a/src/posit/connect/packages.py +++ b/src/posit/connect/packages.py @@ -1,3 +1,5 @@ +"""Package resources.""" + from __future__ import annotations from typing_extensions import ( diff --git a/src/posit/connect/repository.py b/src/posit/connect/repository.py index 2b81e554..63ad56ad 100644 --- a/src/posit/connect/repository.py +++ b/src/posit/connect/repository.py @@ -1,4 +1,4 @@ -"""Content item repository.""" +"""Repository resources.""" from __future__ import annotations diff --git a/src/posit/connect/system.py b/src/posit/connect/system.py index 46353fd5..60b46ab8 100644 --- a/src/posit/connect/system.py +++ b/src/posit/connect/system.py @@ -1,4 +1,4 @@ -"""System Information.""" +"""System resources.""" from __future__ import annotations diff --git a/src/posit/connect/tags.py b/src/posit/connect/tags.py index 47f8f809..4f03a36d 100644 --- a/src/posit/connect/tags.py +++ b/src/posit/connect/tags.py @@ -1,3 +1,5 @@ +"""Tag resources.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/src/posit/connect/vanities.py b/src/posit/connect/vanities.py index 3ab9f4f2..1f105f21 100644 --- a/src/posit/connect/vanities.py +++ b/src/posit/connect/vanities.py @@ -1,3 +1,5 @@ +"""Vanity URL resources.""" + from typing_extensions import Callable, List, NotRequired, Optional, Required, TypedDict, Unpack from .context import Context