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 515751fa..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 @@ -11,11 +12,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/client.py b/src/posit/connect/client.py index f0f5c3a1..10b02c45 100644 --- a/src/posit/connect/client.py +++ b/src/posit/connect/client.py @@ -2,9 +2,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, overload - from requests import Response, Session +from typing_extensions import TYPE_CHECKING, overload + +from posit.connect.environments import Environment +from posit.connect.packages import Package from . import hooks, me from .auth import Auth @@ -299,7 +301,7 @@ def oauth(self) -> OAuth: @property @requires(version="2024.11.0") def packages(self) -> Packages: - return _PaginatedResourceSequence(self._ctx, "v1/packages", uid="name") + return _PaginatedResourceSequence[Package](self._ctx, "v1/packages", uid="name") @property def vanities(self) -> Vanities: @@ -312,7 +314,7 @@ def system(self) -> System: @property @requires(version="2023.05.0") def environments(self) -> Environments: - return _ResourceSequence(self._ctx, "v1/environments") + return _ResourceSequence[Environment](self._ctx, "v1/environments") def __del__(self): """Close the session when the Client instance is deleted.""" 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 a98e9a64..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 @@ -24,7 +27,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 +163,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 +173,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 +379,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/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 4b229fa9..1fb78b59 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_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/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 717b18d9..36f530e4 100644 --- a/src/posit/connect/groups.py +++ b/src/posit/connect/groups.py @@ -2,10 +2,10 @@ 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 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..2fdf8b4e 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, +from typing_extensions import ( Iterable, - List, Literal, Protocol, - SupportsIndex, - overload, runtime_checkable, ) +from .resources import Resource, ResourceSequence + JobTag = Literal[ "unknown", "build_report", @@ -43,8 +39,8 @@ StatusCode = Literal[0, 1, 2] -class Job(Mapping[str, Any]): - @abstractmethod +@runtime_checkable +class Job(Resource, Protocol): def destroy(self) -> None: """Destroy the job. @@ -59,13 +55,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..d9657d9f 100644 --- a/src/posit/connect/metrics/shiny_usage.py +++ b/src/posit/connect/metrics/shiny_usage.py @@ -1,12 +1,12 @@ from __future__ import annotations -from typing import List, overload +from typing_extensions 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..b513a52f 100644 --- a/src/posit/connect/metrics/usage.py +++ b/src/posit/connect/metrics/usage.py @@ -2,15 +2,14 @@ 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 -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..503d14af 100644 --- a/src/posit/connect/metrics/visits.py +++ b/src/posit/connect/metrics/visits.py @@ -1,12 +1,12 @@ from __future__ import annotations -from typing import List, overload +from typing_extensions 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..5d20db02 100644 --- a/src/posit/connect/oauth/associations.py +++ b/src/posit/connect/oauth/associations.py @@ -1,12 +1,12 @@ """OAuth association resources.""" -from typing import List +from typing_extensions 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..7dca39eb 100644 --- a/src/posit/connect/oauth/integrations.py +++ b/src/posit/connect/oauth/integrations.py @@ -1,12 +1,12 @@ """OAuth integration resources.""" -from typing import List, Optional, overload +from typing_extensions 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/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 503ea254..e0db5829 100644 --- a/src/posit/connect/oauth/sessions.py +++ b/src/posit/connect/oauth/sessions.py @@ -1,11 +1,11 @@ """OAuth session resources.""" -from typing import List, Optional, overload +from typing_extensions 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/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 e8afd9c1..4b029cb5 100644 --- a/src/posit/connect/permissions.py +++ b/src/posit/connect/permissions.py @@ -2,11 +2,10 @@ 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 Resource, Resources +from .resources import BaseResource, Resources if TYPE_CHECKING: from .context import Context @@ -14,7 +13,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..5bd5bd5a 100644 --- a/src/posit/connect/resources.py +++ b/src/posit/connect/resources.py @@ -3,11 +3,21 @@ import posixpath import warnings from abc import ABC -from typing import ( + +from typing_extensions import ( TYPE_CHECKING, Any, + Hashable, + ItemsView, Iterable, + Iterator, + List, + Protocol, Sequence, + SupportsIndex, + TypeVar, + cast, + overload, ) from .context import Context @@ -17,7 +27,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 +52,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 +72,13 @@ def __init__(self, ctx: Context, path: str, /, **attributes): self._path = path -class _Resource(dict): +class Resource(Protocol): + def __getitem__(self, key: Hashable) -> Any: ... + + def items(self) -> ItemsView: ... + + +class _Resource(dict, Resource): def __init__(self, ctx: Context, path: str, **attributes): self._ctx = ctx self._path = path @@ -77,11 +93,42 @@ def update(self, **attributes): # type: ignore[reportIncompatibleMethodOverride super().update(**result) -class _ResourceSequence(Sequence): - def __init__(self, ctx: Context, path: str, *, uid: str = "guid"): +_T = TypeVar("_T", bound=Resource) +_T_co = TypeVar("_T_co", bound=Resource, covariant=True) + + +class ResourceFactory(Protocol[_T_co]): + def __call__(self, ctx: Context, path: str, **attributes: Any) -> _T_co: ... + + +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, + factory: ResourceFactory[_T_co] = _Resource, + uid: str = "guid", + ): self._ctx = ctx self._path = path self._uid = uid + self._factory = factory def __getitem__(self, index): return list(self.fetch())[index] @@ -89,7 +136,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: @@ -98,32 +145,37 @@ def __str__(self) -> str: def __repr__(self) -> str: return repr(self.fetch()) - def create(self, **attributes: Any) -> Any: + def create(self, **attributes: Any) -> _T: response = self._ctx.client.post(self._path, json=attributes) result = response.json() uid = result[self._uid] path = posixpath.join(self._path, uid) - return _Resource(self._ctx, path, **result) + resource = self._factory(self._ctx, path, **result) + resource = cast(_T, resource) + return resource - def fetch(self, **conditions) -> Iterable[Any]: + def fetch(self, **conditions: Any) -> Iterable[_T]: response = self._ctx.client.get(self._path, params=conditions) results = response.json() - resources = [] + resources: List[_T] = [] for result in results: uid = result[self._uid] path = posixpath.join(self._path, uid) - resource = _Resource(self._ctx, path, **result) + resource = self._factory(self._ctx, path, **result) + resource = cast(_T, resource) resources.append(resource) return resources - def find(self, *args: str) -> Any: + def find(self, *args: str) -> _T: path = posixpath.join(self._path, *args) response = self._ctx.client.get(path) result = response.json() - return _Resource(self._ctx, path, **result) + resource = self._factory(self._ctx, path, **result) + resource = cast(_T, resource) + return resource - def find_by(self, **conditions) -> Any | None: + def find_by(self, **conditions: Any) -> _T | None: """ Find the first record matching the specified conditions. @@ -142,8 +194,8 @@ def find_by(self, **conditions) -> Any | None: return next((v for v in collection if v.items() >= conditions.items()), None) -class _PaginatedResourceSequence(_ResourceSequence): - def fetch(self, **conditions): +class _PaginatedResourceSequence(_ResourceSequence[_T]): + def fetch(self, **conditions: Any) -> Iterable[_T]: paginator = Paginator(self._ctx, self._path, dict(**conditions)) for page in paginator.fetch_pages(): resources = [] @@ -151,6 +203,7 @@ def fetch(self, **conditions): for result in results: uid = result[self._uid] path = posixpath.join(self._path, uid) - resource = _Resource(self._ctx, path, **result) + resource = self._factory(self._ctx, path, **result) + resource = cast(_T, resource) resources.append(resource) yield from resources 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 907a458f..ac6b2931 100644 --- a/src/posit/connect/tasks.py +++ b/src/posit/connect/tasks.py @@ -2,12 +2,12 @@ from __future__ import annotations -from typing import overload +from typing_extensions import overload 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..a925df90 100644 --- a/src/posit/connect/users.py +++ b/src/posit/connect/users.py @@ -2,21 +2,27 @@ 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 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..3ab9f4f2 100644 --- a/src/posit/connect/vanities.py +++ b/src/posit/connect/vanities.py @@ -1,13 +1,11 @@ -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 -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 +113,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..2e518fb0 100644 --- a/src/posit/connect/variants.py +++ b/src/posit/connect/variants.py @@ -1,11 +1,11 @@ -from typing import List +from typing_extensions 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/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 28cf6e0e..52536b96 100644 --- a/tests/posit/connect/test_resources.py +++ b/tests/posit/connect/test_resources.py @@ -1,15 +1,16 @@ import warnings -from typing import Optional from unittest import mock from unittest.mock import Mock -from posit.connect.resources import Resource +from typing_extensions import Optional + +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")