diff --git a/pyproject.toml b/pyproject.toml index 2af2770..0f4523b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "pipedream" [tool.poetry] name = "pipedream" -version = "1.0.12" +version = "1.0.13" description = "" readme = "README.md" authors = [] diff --git a/src/pipedream/actions/__init__.py b/src/pipedream/actions/__init__.py index 5cde020..cb410bc 100644 --- a/src/pipedream/actions/__init__.py +++ b/src/pipedream/actions/__init__.py @@ -2,3 +2,33 @@ # isort: skip_file +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ListActionsRequestRegistry +_dynamic_imports: typing.Dict[str, str] = {"ListActionsRequestRegistry": ".types"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListActionsRequestRegistry"] diff --git a/src/pipedream/actions/client.py b/src/pipedream/actions/client.py index 5f86f30..e37449a 100644 --- a/src/pipedream/actions/client.py +++ b/src/pipedream/actions/client.py @@ -12,6 +12,7 @@ from ..types.run_action_opts_stash_id import RunActionOptsStashId from ..types.run_action_response import RunActionResponse from .raw_client import AsyncRawActionsClient, RawActionsClient +from .types.list_actions_request_registry import ListActionsRequestRegistry # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) @@ -40,6 +41,7 @@ def list( limit: typing.Optional[int] = None, q: typing.Optional[str] = None, app: typing.Optional[str] = None, + registry: typing.Optional[ListActionsRequestRegistry] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Component]: """ @@ -62,13 +64,16 @@ def list( app : typing.Optional[str] The ID or name slug of the app to filter the actions + registry : typing.Optional[ListActionsRequestRegistry] + The registry to retrieve actions from. Defaults to 'all' ('public', 'private', or 'all') + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- SyncPager[Component] - actions listed + behaves like registry=all Examples -------- @@ -88,7 +93,7 @@ def list( yield page """ return self._raw_client.list( - after=after, before=before, limit=limit, q=q, app=app, request_options=request_options + after=after, before=before, limit=limit, q=q, app=app, registry=registry, request_options=request_options ) def retrieve( @@ -380,6 +385,7 @@ async def list( limit: typing.Optional[int] = None, q: typing.Optional[str] = None, app: typing.Optional[str] = None, + registry: typing.Optional[ListActionsRequestRegistry] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Component]: """ @@ -402,13 +408,16 @@ async def list( app : typing.Optional[str] The ID or name slug of the app to filter the actions + registry : typing.Optional[ListActionsRequestRegistry] + The registry to retrieve actions from. Defaults to 'all' ('public', 'private', or 'all') + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- AsyncPager[Component] - actions listed + behaves like registry=all Examples -------- @@ -437,7 +446,7 @@ async def main() -> None: asyncio.run(main()) """ return await self._raw_client.list( - after=after, before=before, limit=limit, q=q, app=app, request_options=request_options + after=after, before=before, limit=limit, q=q, app=app, registry=registry, request_options=request_options ) async def retrieve( diff --git a/src/pipedream/actions/raw_client.py b/src/pipedream/actions/raw_client.py index 47bb1ed..1bde948 100644 --- a/src/pipedream/actions/raw_client.py +++ b/src/pipedream/actions/raw_client.py @@ -11,6 +11,7 @@ from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..core.serialization import convert_and_respect_annotation_metadata +from ..errors.bad_request_error import BadRequestError from ..errors.too_many_requests_error import TooManyRequestsError from ..types.component import Component from ..types.configure_prop_response import ConfigurePropResponse @@ -20,6 +21,7 @@ from ..types.reload_props_response import ReloadPropsResponse from ..types.run_action_opts_stash_id import RunActionOptsStashId from ..types.run_action_response import RunActionResponse +from .types.list_actions_request_registry import ListActionsRequestRegistry # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) @@ -37,6 +39,7 @@ def list( limit: typing.Optional[int] = None, q: typing.Optional[str] = None, app: typing.Optional[str] = None, + registry: typing.Optional[ListActionsRequestRegistry] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Component]: """ @@ -59,13 +62,16 @@ def list( app : typing.Optional[str] The ID or name slug of the app to filter the actions + registry : typing.Optional[ListActionsRequestRegistry] + The registry to retrieve actions from. Defaults to 'all' ('public', 'private', or 'all') + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- SyncPager[Component] - actions listed + behaves like registry=all """ _response = self._client_wrapper.httpx_client.request( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/actions", @@ -76,6 +82,7 @@ def list( "limit": limit, "q": q, "app": app, + "registry": registry, }, request_options=request_options, ) @@ -100,11 +107,23 @@ def list( limit=limit, q=q, app=app, + registry=registry, request_options=request_options, ) return SyncPager( has_next=_has_next, items=_items, get_next=_get_next, response=BaseHttpResponse(response=_response) ) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 429: raise TooManyRequestsError( headers=dict(_response.headers), @@ -474,6 +493,7 @@ async def list( limit: typing.Optional[int] = None, q: typing.Optional[str] = None, app: typing.Optional[str] = None, + registry: typing.Optional[ListActionsRequestRegistry] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Component]: """ @@ -496,13 +516,16 @@ async def list( app : typing.Optional[str] The ID or name slug of the app to filter the actions + registry : typing.Optional[ListActionsRequestRegistry] + The registry to retrieve actions from. Defaults to 'all' ('public', 'private', or 'all') + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- AsyncPager[Component] - actions listed + behaves like registry=all """ _response = await self._client_wrapper.httpx_client.request( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/actions", @@ -513,6 +536,7 @@ async def list( "limit": limit, "q": q, "app": app, + "registry": registry, }, request_options=request_options, ) @@ -539,12 +563,24 @@ async def _get_next(): limit=limit, q=q, app=app, + registry=registry, request_options=request_options, ) return AsyncPager( has_next=_has_next, items=_items, get_next=_get_next, response=BaseHttpResponse(response=_response) ) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 429: raise TooManyRequestsError( headers=dict(_response.headers), diff --git a/src/pipedream/actions/types/__init__.py b/src/pipedream/actions/types/__init__.py new file mode 100644 index 0000000..87d460d --- /dev/null +++ b/src/pipedream/actions/types/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .list_actions_request_registry import ListActionsRequestRegistry +_dynamic_imports: typing.Dict[str, str] = {"ListActionsRequestRegistry": ".list_actions_request_registry"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListActionsRequestRegistry"] diff --git a/src/pipedream/actions/types/list_actions_request_registry.py b/src/pipedream/actions/types/list_actions_request_registry.py new file mode 100644 index 0000000..6a8673d --- /dev/null +++ b/src/pipedream/actions/types/list_actions_request_registry.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListActionsRequestRegistry = typing.Union[typing.Literal["public", "private", "all"], typing.Any] diff --git a/src/pipedream/apps/__init__.py b/src/pipedream/apps/__init__.py index 3a7655e..4074914 100644 --- a/src/pipedream/apps/__init__.py +++ b/src/pipedream/apps/__init__.py @@ -6,8 +6,8 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import AppsListRequestSortDirection, AppsListRequestSortKey -_dynamic_imports: typing.Dict[str, str] = {"AppsListRequestSortDirection": ".types", "AppsListRequestSortKey": ".types"} + from .types import ListAppsRequestSortDirection, ListAppsRequestSortKey +_dynamic_imports: typing.Dict[str, str] = {"ListAppsRequestSortDirection": ".types", "ListAppsRequestSortKey": ".types"} def __getattr__(attr_name: str) -> typing.Any: @@ -31,4 +31,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["AppsListRequestSortDirection", "AppsListRequestSortKey"] +__all__ = ["ListAppsRequestSortDirection", "ListAppsRequestSortKey"] diff --git a/src/pipedream/apps/client.py b/src/pipedream/apps/client.py index 5d44a36..28ab702 100644 --- a/src/pipedream/apps/client.py +++ b/src/pipedream/apps/client.py @@ -8,8 +8,8 @@ from ..types.app import App from ..types.get_app_response import GetAppResponse from .raw_client import AsyncRawAppsClient, RawAppsClient -from .types.apps_list_request_sort_direction import AppsListRequestSortDirection -from .types.apps_list_request_sort_key import AppsListRequestSortKey +from .types.list_apps_request_sort_direction import ListAppsRequestSortDirection +from .types.list_apps_request_sort_key import ListAppsRequestSortKey class AppsClient: @@ -34,8 +34,8 @@ def list( before: typing.Optional[str] = None, limit: typing.Optional[int] = None, q: typing.Optional[str] = None, - sort_key: typing.Optional[AppsListRequestSortKey] = None, - sort_direction: typing.Optional[AppsListRequestSortDirection] = None, + sort_key: typing.Optional[ListAppsRequestSortKey] = None, + sort_direction: typing.Optional[ListAppsRequestSortDirection] = None, category_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[App]: @@ -56,10 +56,10 @@ def list( q : typing.Optional[str] A search query to filter the apps - sort_key : typing.Optional[AppsListRequestSortKey] + sort_key : typing.Optional[ListAppsRequestSortKey] The key to sort the apps by - sort_direction : typing.Optional[AppsListRequestSortDirection] + sort_direction : typing.Optional[ListAppsRequestSortDirection] The direction to sort the apps category_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]] @@ -158,8 +158,8 @@ async def list( before: typing.Optional[str] = None, limit: typing.Optional[int] = None, q: typing.Optional[str] = None, - sort_key: typing.Optional[AppsListRequestSortKey] = None, - sort_direction: typing.Optional[AppsListRequestSortDirection] = None, + sort_key: typing.Optional[ListAppsRequestSortKey] = None, + sort_direction: typing.Optional[ListAppsRequestSortDirection] = None, category_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[App]: @@ -180,10 +180,10 @@ async def list( q : typing.Optional[str] A search query to filter the apps - sort_key : typing.Optional[AppsListRequestSortKey] + sort_key : typing.Optional[ListAppsRequestSortKey] The key to sort the apps by - sort_direction : typing.Optional[AppsListRequestSortDirection] + sort_direction : typing.Optional[ListAppsRequestSortDirection] The direction to sort the apps category_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]] diff --git a/src/pipedream/apps/raw_client.py b/src/pipedream/apps/raw_client.py index 3bf84a7..c127e01 100644 --- a/src/pipedream/apps/raw_client.py +++ b/src/pipedream/apps/raw_client.py @@ -13,8 +13,8 @@ from ..types.app import App from ..types.get_app_response import GetAppResponse from ..types.list_apps_response import ListAppsResponse -from .types.apps_list_request_sort_direction import AppsListRequestSortDirection -from .types.apps_list_request_sort_key import AppsListRequestSortKey +from .types.list_apps_request_sort_direction import ListAppsRequestSortDirection +from .types.list_apps_request_sort_key import ListAppsRequestSortKey class RawAppsClient: @@ -28,8 +28,8 @@ def list( before: typing.Optional[str] = None, limit: typing.Optional[int] = None, q: typing.Optional[str] = None, - sort_key: typing.Optional[AppsListRequestSortKey] = None, - sort_direction: typing.Optional[AppsListRequestSortDirection] = None, + sort_key: typing.Optional[ListAppsRequestSortKey] = None, + sort_direction: typing.Optional[ListAppsRequestSortDirection] = None, category_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[App]: @@ -50,10 +50,10 @@ def list( q : typing.Optional[str] A search query to filter the apps - sort_key : typing.Optional[AppsListRequestSortKey] + sort_key : typing.Optional[ListAppsRequestSortKey] The key to sort the apps by - sort_direction : typing.Optional[AppsListRequestSortDirection] + sort_direction : typing.Optional[ListAppsRequestSortDirection] The direction to sort the apps category_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]] @@ -165,8 +165,8 @@ async def list( before: typing.Optional[str] = None, limit: typing.Optional[int] = None, q: typing.Optional[str] = None, - sort_key: typing.Optional[AppsListRequestSortKey] = None, - sort_direction: typing.Optional[AppsListRequestSortDirection] = None, + sort_key: typing.Optional[ListAppsRequestSortKey] = None, + sort_direction: typing.Optional[ListAppsRequestSortDirection] = None, category_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[App]: @@ -187,10 +187,10 @@ async def list( q : typing.Optional[str] A search query to filter the apps - sort_key : typing.Optional[AppsListRequestSortKey] + sort_key : typing.Optional[ListAppsRequestSortKey] The key to sort the apps by - sort_direction : typing.Optional[AppsListRequestSortDirection] + sort_direction : typing.Optional[ListAppsRequestSortDirection] The direction to sort the apps category_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]] diff --git a/src/pipedream/apps/types/__init__.py b/src/pipedream/apps/types/__init__.py index 08c3a02..526f29a 100644 --- a/src/pipedream/apps/types/__init__.py +++ b/src/pipedream/apps/types/__init__.py @@ -6,11 +6,11 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .apps_list_request_sort_direction import AppsListRequestSortDirection - from .apps_list_request_sort_key import AppsListRequestSortKey + from .list_apps_request_sort_direction import ListAppsRequestSortDirection + from .list_apps_request_sort_key import ListAppsRequestSortKey _dynamic_imports: typing.Dict[str, str] = { - "AppsListRequestSortDirection": ".apps_list_request_sort_direction", - "AppsListRequestSortKey": ".apps_list_request_sort_key", + "ListAppsRequestSortDirection": ".list_apps_request_sort_direction", + "ListAppsRequestSortKey": ".list_apps_request_sort_key", } @@ -35,4 +35,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["AppsListRequestSortDirection", "AppsListRequestSortKey"] +__all__ = ["ListAppsRequestSortDirection", "ListAppsRequestSortKey"] diff --git a/src/pipedream/apps/types/apps_list_request_sort_direction.py b/src/pipedream/apps/types/list_apps_request_sort_direction.py similarity index 61% rename from src/pipedream/apps/types/apps_list_request_sort_direction.py rename to src/pipedream/apps/types/list_apps_request_sort_direction.py index d841cc9..4d86906 100644 --- a/src/pipedream/apps/types/apps_list_request_sort_direction.py +++ b/src/pipedream/apps/types/list_apps_request_sort_direction.py @@ -2,4 +2,4 @@ import typing -AppsListRequestSortDirection = typing.Union[typing.Literal["asc", "desc"], typing.Any] +ListAppsRequestSortDirection = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/pipedream/apps/types/apps_list_request_sort_key.py b/src/pipedream/apps/types/list_apps_request_sort_key.py similarity index 65% rename from src/pipedream/apps/types/apps_list_request_sort_key.py rename to src/pipedream/apps/types/list_apps_request_sort_key.py index ab4065d..6e9b8e2 100644 --- a/src/pipedream/apps/types/apps_list_request_sort_key.py +++ b/src/pipedream/apps/types/list_apps_request_sort_key.py @@ -2,4 +2,4 @@ import typing -AppsListRequestSortKey = typing.Union[typing.Literal["name", "name_slug", "featured_weight"], typing.Any] +ListAppsRequestSortKey = typing.Union[typing.Literal["name", "name_slug", "featured_weight"], typing.Any] diff --git a/src/pipedream/client.py b/src/pipedream/client.py index 514ef44..cfb7782 100644 --- a/src/pipedream/client.py +++ b/src/pipedream/client.py @@ -6,11 +6,11 @@ import typing import httpx +from ._.types.project_environment import ProjectEnvironment from .core.api_error import ApiError from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from .core.oauth_token_provider import OAuthTokenProvider from .environment import PipedreamEnvironment -from .types.project_environment import ProjectEnvironment if typing.TYPE_CHECKING: from .accounts.client import AccountsClient, AsyncAccountsClient diff --git a/src/pipedream/components/__init__.py b/src/pipedream/components/__init__.py index 5cde020..609af6c 100644 --- a/src/pipedream/components/__init__.py +++ b/src/pipedream/components/__init__.py @@ -2,3 +2,33 @@ # isort: skip_file +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ListComponentsRequestRegistry +_dynamic_imports: typing.Dict[str, str] = {"ListComponentsRequestRegistry": ".types"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListComponentsRequestRegistry"] diff --git a/src/pipedream/components/client.py b/src/pipedream/components/client.py index 2004396..accfced 100644 --- a/src/pipedream/components/client.py +++ b/src/pipedream/components/client.py @@ -11,6 +11,7 @@ from ..types.configured_props import ConfiguredProps from ..types.reload_props_response import ReloadPropsResponse from .raw_client import AsyncRawComponentsClient, RawComponentsClient +from .types.list_components_request_registry import ListComponentsRequestRegistry # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) @@ -39,6 +40,7 @@ def list( limit: typing.Optional[int] = None, q: typing.Optional[str] = None, app: typing.Optional[str] = None, + registry: typing.Optional[ListComponentsRequestRegistry] = None, component_type: typing.Optional[ComponentType] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Component]: @@ -62,6 +64,9 @@ def list( app : typing.Optional[str] The ID or name slug of the app to filter the components + registry : typing.Optional[ListComponentsRequestRegistry] + The registry to retrieve components from. Defaults to 'all' ('public', 'private', or 'all') + component_type : typing.Optional[ComponentType] The type of the component to filter the components @@ -71,7 +76,7 @@ def list( Returns ------- SyncPager[Component] - components listed + behaves like registry=all Examples -------- @@ -96,6 +101,7 @@ def list( limit=limit, q=q, app=app, + registry=registry, component_type=component_type, request_options=request_options, ) @@ -323,6 +329,7 @@ async def list( limit: typing.Optional[int] = None, q: typing.Optional[str] = None, app: typing.Optional[str] = None, + registry: typing.Optional[ListComponentsRequestRegistry] = None, component_type: typing.Optional[ComponentType] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Component]: @@ -346,6 +353,9 @@ async def list( app : typing.Optional[str] The ID or name slug of the app to filter the components + registry : typing.Optional[ListComponentsRequestRegistry] + The registry to retrieve components from. Defaults to 'all' ('public', 'private', or 'all') + component_type : typing.Optional[ComponentType] The type of the component to filter the components @@ -355,7 +365,7 @@ async def list( Returns ------- AsyncPager[Component] - components listed + behaves like registry=all Examples -------- @@ -389,6 +399,7 @@ async def main() -> None: limit=limit, q=q, app=app, + registry=registry, component_type=component_type, request_options=request_options, ) diff --git a/src/pipedream/components/raw_client.py b/src/pipedream/components/raw_client.py index 3453921..ea1e193 100644 --- a/src/pipedream/components/raw_client.py +++ b/src/pipedream/components/raw_client.py @@ -11,6 +11,7 @@ from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..core.serialization import convert_and_respect_annotation_metadata +from ..errors.bad_request_error import BadRequestError from ..errors.too_many_requests_error import TooManyRequestsError from ..types.component import Component from ..types.component_type import ComponentType @@ -19,6 +20,7 @@ from ..types.get_component_response import GetComponentResponse from ..types.get_components_response import GetComponentsResponse from ..types.reload_props_response import ReloadPropsResponse +from .types.list_components_request_registry import ListComponentsRequestRegistry # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) @@ -36,6 +38,7 @@ def list( limit: typing.Optional[int] = None, q: typing.Optional[str] = None, app: typing.Optional[str] = None, + registry: typing.Optional[ListComponentsRequestRegistry] = None, component_type: typing.Optional[ComponentType] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Component]: @@ -59,6 +62,9 @@ def list( app : typing.Optional[str] The ID or name slug of the app to filter the components + registry : typing.Optional[ListComponentsRequestRegistry] + The registry to retrieve components from. Defaults to 'all' ('public', 'private', or 'all') + component_type : typing.Optional[ComponentType] The type of the component to filter the components @@ -68,7 +74,7 @@ def list( Returns ------- SyncPager[Component] - components listed + behaves like registry=all """ _response = self._client_wrapper.httpx_client.request( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/components", @@ -79,6 +85,7 @@ def list( "limit": limit, "q": q, "app": app, + "registry": registry, "component_type": component_type, }, request_options=request_options, @@ -104,12 +111,24 @@ def list( limit=limit, q=q, app=app, + registry=registry, component_type=component_type, request_options=request_options, ) return SyncPager( has_next=_has_next, items=_items, get_next=_get_next, response=BaseHttpResponse(response=_response) ) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 429: raise TooManyRequestsError( headers=dict(_response.headers), @@ -392,6 +411,7 @@ async def list( limit: typing.Optional[int] = None, q: typing.Optional[str] = None, app: typing.Optional[str] = None, + registry: typing.Optional[ListComponentsRequestRegistry] = None, component_type: typing.Optional[ComponentType] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Component]: @@ -415,6 +435,9 @@ async def list( app : typing.Optional[str] The ID or name slug of the app to filter the components + registry : typing.Optional[ListComponentsRequestRegistry] + The registry to retrieve components from. Defaults to 'all' ('public', 'private', or 'all') + component_type : typing.Optional[ComponentType] The type of the component to filter the components @@ -424,7 +447,7 @@ async def list( Returns ------- AsyncPager[Component] - components listed + behaves like registry=all """ _response = await self._client_wrapper.httpx_client.request( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/components", @@ -435,6 +458,7 @@ async def list( "limit": limit, "q": q, "app": app, + "registry": registry, "component_type": component_type, }, request_options=request_options, @@ -462,6 +486,7 @@ async def _get_next(): limit=limit, q=q, app=app, + registry=registry, component_type=component_type, request_options=request_options, ) @@ -469,6 +494,17 @@ async def _get_next(): return AsyncPager( has_next=_has_next, items=_items, get_next=_get_next, response=BaseHttpResponse(response=_response) ) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 429: raise TooManyRequestsError( headers=dict(_response.headers), diff --git a/src/pipedream/components/types/__init__.py b/src/pipedream/components/types/__init__.py new file mode 100644 index 0000000..8af9c8e --- /dev/null +++ b/src/pipedream/components/types/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .list_components_request_registry import ListComponentsRequestRegistry +_dynamic_imports: typing.Dict[str, str] = {"ListComponentsRequestRegistry": ".list_components_request_registry"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListComponentsRequestRegistry"] diff --git a/src/pipedream/components/types/list_components_request_registry.py b/src/pipedream/components/types/list_components_request_registry.py new file mode 100644 index 0000000..0a536ba --- /dev/null +++ b/src/pipedream/components/types/list_components_request_registry.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListComponentsRequestRegistry = typing.Union[typing.Literal["public", "private", "all"], typing.Any] diff --git a/src/pipedream/core/client_wrapper.py b/src/pipedream/core/client_wrapper.py index b75073a..8f5fb02 100644 --- a/src/pipedream/core/client_wrapper.py +++ b/src/pipedream/core/client_wrapper.py @@ -3,7 +3,7 @@ import typing import httpx -from ..types.project_environment import ProjectEnvironment +from .._.types.project_environment import ProjectEnvironment from .http_client import AsyncHttpClient, HttpClient @@ -27,10 +27,10 @@ def __init__( def get_headers(self) -> typing.Dict[str, str]: headers: typing.Dict[str, str] = { - "User-Agent": "pipedream/1.0.12", + "User-Agent": "pipedream/1.0.13", "X-Fern-Language": "Python", "X-Fern-SDK-Name": "pipedream", - "X-Fern-SDK-Version": "1.0.12", + "X-Fern-SDK-Version": "1.0.13", **(self.get_custom_headers() or {}), } if self._project_environment is not None: diff --git a/src/pipedream/errors/__init__.py b/src/pipedream/errors/__init__.py index a6d06ac..2717162 100644 --- a/src/pipedream/errors/__init__.py +++ b/src/pipedream/errors/__init__.py @@ -6,8 +6,14 @@ from importlib import import_module if typing.TYPE_CHECKING: + from .bad_request_error import BadRequestError + from .not_found_error import NotFoundError from .too_many_requests_error import TooManyRequestsError -_dynamic_imports: typing.Dict[str, str] = {"TooManyRequestsError": ".too_many_requests_error"} +_dynamic_imports: typing.Dict[str, str] = { + "BadRequestError": ".bad_request_error", + "NotFoundError": ".not_found_error", + "TooManyRequestsError": ".too_many_requests_error", +} def __getattr__(attr_name: str) -> typing.Any: @@ -31,4 +37,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["TooManyRequestsError"] +__all__ = ["BadRequestError", "NotFoundError", "TooManyRequestsError"] diff --git a/src/pipedream/errors/bad_request_error.py b/src/pipedream/errors/bad_request_error.py new file mode 100644 index 0000000..baf5be4 --- /dev/null +++ b/src/pipedream/errors/bad_request_error.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.api_error import ApiError + + +class BadRequestError(ApiError): + def __init__(self, body: typing.Optional[typing.Any], headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=400, headers=headers, body=body) diff --git a/src/pipedream/errors/not_found_error.py b/src/pipedream/errors/not_found_error.py new file mode 100644 index 0000000..dcd60e3 --- /dev/null +++ b/src/pipedream/errors/not_found_error.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.api_error import ApiError + + +class NotFoundError(ApiError): + def __init__(self, body: typing.Optional[typing.Any], headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=404, headers=headers, body=body) diff --git a/src/pipedream/projects/client.py b/src/pipedream/projects/client.py index e8ec738..5a9a508 100644 --- a/src/pipedream/projects/client.py +++ b/src/pipedream/projects/client.py @@ -3,10 +3,15 @@ import typing from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.pagination import AsyncPager, SyncPager from ..core.request_options import RequestOptions +from ..types.project import Project from ..types.project_info_response import ProjectInfoResponse from .raw_client import AsyncRawProjectsClient, RawProjectsClient +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + class ProjectsClient: def __init__(self, *, client_wrapper: SyncClientWrapper): @@ -23,6 +28,206 @@ def with_raw_response(self) -> RawProjectsClient: """ return self._raw_client + def list( + self, + *, + after: typing.Optional[str] = None, + before: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + q: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[Project]: + """ + List the projects that are available to the authenticated Connect client + + Parameters + ---------- + after : typing.Optional[str] + The cursor to start from for pagination + + before : typing.Optional[str] + The cursor to end before for pagination + + limit : typing.Optional[int] + The maximum number of results to return + + q : typing.Optional[str] + A search query to filter the projects + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[Project] + projects listed + + Examples + -------- + from pipedream import Pipedream + + client = Pipedream( + project_id="YOUR_PROJECT_ID", + project_environment="YOUR_PROJECT_ENVIRONMENT", + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + ) + response = client.projects.list() + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.list(after=after, before=before, limit=limit, q=q, request_options=request_options) + + def create( + self, + *, + name: str, + app_name: typing.Optional[str] = OMIT, + support_email: typing.Optional[str] = OMIT, + external_url: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> Project: + """ + Create a new project for the authenticated workspace + + Parameters + ---------- + name : str + Name of the project + + app_name : typing.Optional[str] + Display name for the Connect application + + support_email : typing.Optional[str] + Support email displayed to end users + + external_url : typing.Optional[str] + External URL for the project, if configured + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Project + project created + + Examples + -------- + from pipedream import Pipedream + + client = Pipedream( + project_id="YOUR_PROJECT_ID", + project_environment="YOUR_PROJECT_ENVIRONMENT", + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + ) + client.projects.create( + name="name", + ) + """ + _response = self._raw_client.create( + name=name, + app_name=app_name, + support_email=support_email, + external_url=external_url, + request_options=request_options, + ) + return _response.data + + def retrieve(self, *, request_options: typing.Optional[RequestOptions] = None) -> Project: + """ + Get the project details for a specific project + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Project + project retrieved + + Examples + -------- + from pipedream import Pipedream + + client = Pipedream( + project_id="YOUR_PROJECT_ID", + project_environment="YOUR_PROJECT_ENVIRONMENT", + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + ) + client.projects.retrieve() + """ + _response = self._raw_client.retrieve(request_options=request_options) + return _response.data + + def delete(self, *, request_options: typing.Optional[RequestOptions] = None) -> None: + """ + Delete a project owned by the authenticated workspace + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + None + + Examples + -------- + from pipedream import Pipedream + + client = Pipedream( + project_id="YOUR_PROJECT_ID", + project_environment="YOUR_PROJECT_ENVIRONMENT", + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + ) + client.projects.delete() + """ + _response = self._raw_client.delete(request_options=request_options) + return _response.data + + def update(self, *, logo: str, request_options: typing.Optional[RequestOptions] = None) -> None: + """ + Upload or replace the project logo + + Parameters + ---------- + logo : str + Data URI containing the new Base64 encoded image + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + None + + Examples + -------- + from pipedream import Pipedream + + client = Pipedream( + project_id="YOUR_PROJECT_ID", + project_environment="YOUR_PROJECT_ENVIRONMENT", + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + ) + client.projects.update( + logo="data:image/png;base64,AAAAAA...", + ) + """ + _response = self._raw_client.update(logo=logo, request_options=request_options) + return _response.data + def retrieve_info(self, *, request_options: typing.Optional[RequestOptions] = None) -> ProjectInfoResponse: """ Retrieve project configuration and environment details @@ -68,6 +273,249 @@ def with_raw_response(self) -> AsyncRawProjectsClient: """ return self._raw_client + async def list( + self, + *, + after: typing.Optional[str] = None, + before: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + q: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[Project]: + """ + List the projects that are available to the authenticated Connect client + + Parameters + ---------- + after : typing.Optional[str] + The cursor to start from for pagination + + before : typing.Optional[str] + The cursor to end before for pagination + + limit : typing.Optional[int] + The maximum number of results to return + + q : typing.Optional[str] + A search query to filter the projects + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[Project] + projects listed + + Examples + -------- + import asyncio + + from pipedream import AsyncPipedream + + client = AsyncPipedream( + project_id="YOUR_PROJECT_ID", + project_environment="YOUR_PROJECT_ENVIRONMENT", + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + ) + + + async def main() -> None: + response = await client.projects.list() + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.list( + after=after, before=before, limit=limit, q=q, request_options=request_options + ) + + async def create( + self, + *, + name: str, + app_name: typing.Optional[str] = OMIT, + support_email: typing.Optional[str] = OMIT, + external_url: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> Project: + """ + Create a new project for the authenticated workspace + + Parameters + ---------- + name : str + Name of the project + + app_name : typing.Optional[str] + Display name for the Connect application + + support_email : typing.Optional[str] + Support email displayed to end users + + external_url : typing.Optional[str] + External URL for the project, if configured + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Project + project created + + Examples + -------- + import asyncio + + from pipedream import AsyncPipedream + + client = AsyncPipedream( + project_id="YOUR_PROJECT_ID", + project_environment="YOUR_PROJECT_ENVIRONMENT", + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + ) + + + async def main() -> None: + await client.projects.create( + name="name", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + name=name, + app_name=app_name, + support_email=support_email, + external_url=external_url, + request_options=request_options, + ) + return _response.data + + async def retrieve(self, *, request_options: typing.Optional[RequestOptions] = None) -> Project: + """ + Get the project details for a specific project + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Project + project retrieved + + Examples + -------- + import asyncio + + from pipedream import AsyncPipedream + + client = AsyncPipedream( + project_id="YOUR_PROJECT_ID", + project_environment="YOUR_PROJECT_ENVIRONMENT", + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + ) + + + async def main() -> None: + await client.projects.retrieve() + + + asyncio.run(main()) + """ + _response = await self._raw_client.retrieve(request_options=request_options) + return _response.data + + async def delete(self, *, request_options: typing.Optional[RequestOptions] = None) -> None: + """ + Delete a project owned by the authenticated workspace + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + None + + Examples + -------- + import asyncio + + from pipedream import AsyncPipedream + + client = AsyncPipedream( + project_id="YOUR_PROJECT_ID", + project_environment="YOUR_PROJECT_ENVIRONMENT", + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + ) + + + async def main() -> None: + await client.projects.delete() + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete(request_options=request_options) + return _response.data + + async def update(self, *, logo: str, request_options: typing.Optional[RequestOptions] = None) -> None: + """ + Upload or replace the project logo + + Parameters + ---------- + logo : str + Data URI containing the new Base64 encoded image + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + None + + Examples + -------- + import asyncio + + from pipedream import AsyncPipedream + + client = AsyncPipedream( + project_id="YOUR_PROJECT_ID", + project_environment="YOUR_PROJECT_ENVIRONMENT", + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + ) + + + async def main() -> None: + await client.projects.update( + logo="data:image/png;base64,AAAAAA...", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.update(logo=logo, request_options=request_options) + return _response.data + async def retrieve_info(self, *, request_options: typing.Optional[RequestOptions] = None) -> ProjectInfoResponse: """ Retrieve project configuration and environment details diff --git a/src/pipedream/projects/raw_client.py b/src/pipedream/projects/raw_client.py index 54d4b1c..bca4ba9 100644 --- a/src/pipedream/projects/raw_client.py +++ b/src/pipedream/projects/raw_client.py @@ -7,16 +7,336 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.http_response import AsyncHttpResponse, HttpResponse from ..core.jsonable_encoder import jsonable_encoder +from ..core.pagination import AsyncPager, BaseHttpResponse, SyncPager from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions +from ..errors.bad_request_error import BadRequestError +from ..errors.not_found_error import NotFoundError from ..errors.too_many_requests_error import TooManyRequestsError +from ..types.list_projects_response import ListProjectsResponse +from ..types.project import Project from ..types.project_info_response import ProjectInfoResponse +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + class RawProjectsClient: def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper + def list( + self, + *, + after: typing.Optional[str] = None, + before: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + q: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[Project]: + """ + List the projects that are available to the authenticated Connect client + + Parameters + ---------- + after : typing.Optional[str] + The cursor to start from for pagination + + before : typing.Optional[str] + The cursor to end before for pagination + + limit : typing.Optional[int] + The maximum number of results to return + + q : typing.Optional[str] + A search query to filter the projects + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[Project] + projects listed + """ + _response = self._client_wrapper.httpx_client.request( + "v1/connect/projects", + method="GET", + params={ + "after": after, + "before": before, + "limit": limit, + "q": q, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + ListProjectsResponse, + parse_obj_as( + type_=ListProjectsResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.data + _has_next = False + _get_next = None + if _parsed_response.page_info is not None: + _parsed_next = _parsed_response.page_info.end_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.list( + after=_parsed_next, + before=before, + limit=limit, + q=q, + request_options=request_options, + ) + return SyncPager( + has_next=_has_next, items=_items, get_next=_get_next, response=BaseHttpResponse(response=_response) + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def create( + self, + *, + name: str, + app_name: typing.Optional[str] = OMIT, + support_email: typing.Optional[str] = OMIT, + external_url: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[Project]: + """ + Create a new project for the authenticated workspace + + Parameters + ---------- + name : str + Name of the project + + app_name : typing.Optional[str] + Display name for the Connect application + + support_email : typing.Optional[str] + Support email displayed to end users + + external_url : typing.Optional[str] + External URL for the project, if configured + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Project] + project created + """ + _response = self._client_wrapper.httpx_client.request( + "v1/connect/projects", + method="POST", + json={ + "name": name, + "app_name": app_name, + "support_email": support_email, + "external_url": external_url, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Project, + parse_obj_as( + type_=Project, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def retrieve(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[Project]: + """ + Get the project details for a specific project + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Project] + project retrieved + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/connect/projects/{jsonable_encoder(self._client_wrapper._project_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Project, + parse_obj_as( + type_=Project, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def delete(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[None]: + """ + Delete a project owned by the authenticated workspace + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[None] + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/connect/projects/{jsonable_encoder(self._client_wrapper._project_id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return HttpResponse(response=_response, data=None) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def update(self, *, logo: str, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[None]: + """ + Upload or replace the project logo + + Parameters + ---------- + logo : str + Data URI containing the new Base64 encoded image + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[None] + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/connect/projects/{jsonable_encoder(self._client_wrapper._project_id)}/logo", + method="POST", + json={ + "logo": logo, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return HttpResponse(response=_response, data=None) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + def retrieve_info( self, *, request_options: typing.Optional[RequestOptions] = None ) -> HttpResponse[ProjectInfoResponse]: @@ -69,6 +389,323 @@ class AsyncRawProjectsClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper + async def list( + self, + *, + after: typing.Optional[str] = None, + before: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + q: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[Project]: + """ + List the projects that are available to the authenticated Connect client + + Parameters + ---------- + after : typing.Optional[str] + The cursor to start from for pagination + + before : typing.Optional[str] + The cursor to end before for pagination + + limit : typing.Optional[int] + The maximum number of results to return + + q : typing.Optional[str] + A search query to filter the projects + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[Project] + projects listed + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/connect/projects", + method="GET", + params={ + "after": after, + "before": before, + "limit": limit, + "q": q, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + ListProjectsResponse, + parse_obj_as( + type_=ListProjectsResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.data + _has_next = False + _get_next = None + if _parsed_response.page_info is not None: + _parsed_next = _parsed_response.page_info.end_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.list( + after=_parsed_next, + before=before, + limit=limit, + q=q, + request_options=request_options, + ) + + return AsyncPager( + has_next=_has_next, items=_items, get_next=_get_next, response=BaseHttpResponse(response=_response) + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def create( + self, + *, + name: str, + app_name: typing.Optional[str] = OMIT, + support_email: typing.Optional[str] = OMIT, + external_url: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[Project]: + """ + Create a new project for the authenticated workspace + + Parameters + ---------- + name : str + Name of the project + + app_name : typing.Optional[str] + Display name for the Connect application + + support_email : typing.Optional[str] + Support email displayed to end users + + external_url : typing.Optional[str] + External URL for the project, if configured + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Project] + project created + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/connect/projects", + method="POST", + json={ + "name": name, + "app_name": app_name, + "support_email": support_email, + "external_url": external_url, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Project, + parse_obj_as( + type_=Project, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def retrieve(self, *, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[Project]: + """ + Get the project details for a specific project + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Project] + project retrieved + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/connect/projects/{jsonable_encoder(self._client_wrapper._project_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Project, + parse_obj_as( + type_=Project, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def delete(self, *, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[None]: + """ + Delete a project owned by the authenticated workspace + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[None] + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/connect/projects/{jsonable_encoder(self._client_wrapper._project_id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return AsyncHttpResponse(response=_response, data=None) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def update( + self, *, logo: str, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[None]: + """ + Upload or replace the project logo + + Parameters + ---------- + logo : str + Data URI containing the new Base64 encoded image + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[None] + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/connect/projects/{jsonable_encoder(self._client_wrapper._project_id)}/logo", + method="POST", + json={ + "logo": logo, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return AsyncHttpResponse(response=_response, data=None) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + async def retrieve_info( self, *, request_options: typing.Optional[RequestOptions] = None ) -> AsyncHttpResponse[ProjectInfoResponse]: diff --git a/src/pipedream/triggers/__init__.py b/src/pipedream/triggers/__init__.py index 5cde020..ae3a5f2 100644 --- a/src/pipedream/triggers/__init__.py +++ b/src/pipedream/triggers/__init__.py @@ -2,3 +2,33 @@ # isort: skip_file +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ListTriggersRequestRegistry +_dynamic_imports: typing.Dict[str, str] = {"ListTriggersRequestRegistry": ".types"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListTriggersRequestRegistry"] diff --git a/src/pipedream/triggers/client.py b/src/pipedream/triggers/client.py index 17e518e..c6f4524 100644 --- a/src/pipedream/triggers/client.py +++ b/src/pipedream/triggers/client.py @@ -11,6 +11,7 @@ from ..types.emitter import Emitter from ..types.reload_props_response import ReloadPropsResponse from .raw_client import AsyncRawTriggersClient, RawTriggersClient +from .types.list_triggers_request_registry import ListTriggersRequestRegistry # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) @@ -39,6 +40,7 @@ def list( limit: typing.Optional[int] = None, q: typing.Optional[str] = None, app: typing.Optional[str] = None, + registry: typing.Optional[ListTriggersRequestRegistry] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Component]: """ @@ -61,13 +63,16 @@ def list( app : typing.Optional[str] The ID or name slug of the app to filter the triggers + registry : typing.Optional[ListTriggersRequestRegistry] + The registry to retrieve triggers from. Defaults to 'all' ('public', 'private', or 'all') + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- SyncPager[Component] - triggers listed + behaves like registry=all Examples -------- @@ -87,7 +92,7 @@ def list( yield page """ return self._raw_client.list( - after=after, before=before, limit=limit, q=q, app=app, request_options=request_options + after=after, before=before, limit=limit, q=q, app=app, registry=registry, request_options=request_options ) def retrieve( @@ -390,6 +395,7 @@ async def list( limit: typing.Optional[int] = None, q: typing.Optional[str] = None, app: typing.Optional[str] = None, + registry: typing.Optional[ListTriggersRequestRegistry] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Component]: """ @@ -412,13 +418,16 @@ async def list( app : typing.Optional[str] The ID or name slug of the app to filter the triggers + registry : typing.Optional[ListTriggersRequestRegistry] + The registry to retrieve triggers from. Defaults to 'all' ('public', 'private', or 'all') + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- AsyncPager[Component] - triggers listed + behaves like registry=all Examples -------- @@ -447,7 +456,7 @@ async def main() -> None: asyncio.run(main()) """ return await self._raw_client.list( - after=after, before=before, limit=limit, q=q, app=app, request_options=request_options + after=after, before=before, limit=limit, q=q, app=app, registry=registry, request_options=request_options ) async def retrieve( diff --git a/src/pipedream/triggers/raw_client.py b/src/pipedream/triggers/raw_client.py index 93ac6c6..75f6c70 100644 --- a/src/pipedream/triggers/raw_client.py +++ b/src/pipedream/triggers/raw_client.py @@ -11,6 +11,7 @@ from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..core.serialization import convert_and_respect_annotation_metadata +from ..errors.bad_request_error import BadRequestError from ..errors.too_many_requests_error import TooManyRequestsError from ..types.component import Component from ..types.configure_prop_response import ConfigurePropResponse @@ -20,6 +21,7 @@ from ..types.get_component_response import GetComponentResponse from ..types.get_components_response import GetComponentsResponse from ..types.reload_props_response import ReloadPropsResponse +from .types.list_triggers_request_registry import ListTriggersRequestRegistry # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) @@ -37,6 +39,7 @@ def list( limit: typing.Optional[int] = None, q: typing.Optional[str] = None, app: typing.Optional[str] = None, + registry: typing.Optional[ListTriggersRequestRegistry] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Component]: """ @@ -59,13 +62,16 @@ def list( app : typing.Optional[str] The ID or name slug of the app to filter the triggers + registry : typing.Optional[ListTriggersRequestRegistry] + The registry to retrieve triggers from. Defaults to 'all' ('public', 'private', or 'all') + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- SyncPager[Component] - triggers listed + behaves like registry=all """ _response = self._client_wrapper.httpx_client.request( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/triggers", @@ -76,6 +82,7 @@ def list( "limit": limit, "q": q, "app": app, + "registry": registry, }, request_options=request_options, ) @@ -100,11 +107,23 @@ def list( limit=limit, q=q, app=app, + registry=registry, request_options=request_options, ) return SyncPager( has_next=_has_next, items=_items, get_next=_get_next, response=BaseHttpResponse(response=_response) ) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 429: raise TooManyRequestsError( headers=dict(_response.headers), @@ -484,6 +503,7 @@ async def list( limit: typing.Optional[int] = None, q: typing.Optional[str] = None, app: typing.Optional[str] = None, + registry: typing.Optional[ListTriggersRequestRegistry] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Component]: """ @@ -506,13 +526,16 @@ async def list( app : typing.Optional[str] The ID or name slug of the app to filter the triggers + registry : typing.Optional[ListTriggersRequestRegistry] + The registry to retrieve triggers from. Defaults to 'all' ('public', 'private', or 'all') + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- AsyncPager[Component] - triggers listed + behaves like registry=all """ _response = await self._client_wrapper.httpx_client.request( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/triggers", @@ -523,6 +546,7 @@ async def list( "limit": limit, "q": q, "app": app, + "registry": registry, }, request_options=request_options, ) @@ -549,12 +573,24 @@ async def _get_next(): limit=limit, q=q, app=app, + registry=registry, request_options=request_options, ) return AsyncPager( has_next=_has_next, items=_items, get_next=_get_next, response=BaseHttpResponse(response=_response) ) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 429: raise TooManyRequestsError( headers=dict(_response.headers), diff --git a/src/pipedream/triggers/types/__init__.py b/src/pipedream/triggers/types/__init__.py new file mode 100644 index 0000000..1024e89 --- /dev/null +++ b/src/pipedream/triggers/types/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .list_triggers_request_registry import ListTriggersRequestRegistry +_dynamic_imports: typing.Dict[str, str] = {"ListTriggersRequestRegistry": ".list_triggers_request_registry"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListTriggersRequestRegistry"] diff --git a/src/pipedream/triggers/types/list_triggers_request_registry.py b/src/pipedream/triggers/types/list_triggers_request_registry.py new file mode 100644 index 0000000..e6f9d13 --- /dev/null +++ b/src/pipedream/triggers/types/list_triggers_request_registry.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListTriggersRequestRegistry = typing.Union[typing.Literal["public", "private", "all"], typing.Any] diff --git a/src/pipedream/types/__init__.py b/src/pipedream/types/__init__.py index 6063ee7..09107fb 100644 --- a/src/pipedream/types/__init__.py +++ b/src/pipedream/types/__init__.py @@ -92,9 +92,11 @@ from .list_accounts_response import ListAccountsResponse from .list_app_categories_response import ListAppCategoriesResponse from .list_apps_response import ListAppsResponse + from .list_projects_response import ListProjectsResponse from .observation import Observation from .observation_error import ObservationError from .page_info import PageInfo + from .project import Project from .project_environment import ProjectEnvironment from .project_info_response import ProjectInfoResponse from .project_info_response_app import ProjectInfoResponseApp @@ -205,9 +207,11 @@ "ListAccountsResponse": ".list_accounts_response", "ListAppCategoriesResponse": ".list_app_categories_response", "ListAppsResponse": ".list_apps_response", + "ListProjectsResponse": ".list_projects_response", "Observation": ".observation", "ObservationError": ".observation_error", "PageInfo": ".page_info", + "Project": ".project", "ProjectEnvironment": ".project_environment", "ProjectInfoResponse": ".project_info_response", "ProjectInfoResponseApp": ".project_info_response_app", @@ -342,9 +346,11 @@ def __dir__(): "ListAccountsResponse", "ListAppCategoriesResponse", "ListAppsResponse", + "ListProjectsResponse", "Observation", "ObservationError", "PageInfo", + "Project", "ProjectEnvironment", "ProjectInfoResponse", "ProjectInfoResponseApp", diff --git a/src/pipedream/types/list_projects_response.py b/src/pipedream/types/list_projects_response.py new file mode 100644 index 0000000..4094ff3 --- /dev/null +++ b/src/pipedream/types/list_projects_response.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .page_info import PageInfo +from .project import Project + + +class ListProjectsResponse(UniversalBaseModel): + """ + Response received when listing Connect projects + """ + + data: typing.List[Project] + page_info: PageInfo + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/pipedream/types/project.py b/src/pipedream/types/project.py new file mode 100644 index 0000000..020e95e --- /dev/null +++ b/src/pipedream/types/project.py @@ -0,0 +1,46 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class Project(UniversalBaseModel): + """ + Project that can be accessed via the Connect API + """ + + id: str = pydantic.Field() + """ + Hash ID for the project + """ + + name: str = pydantic.Field() + """ + Display name of the project + """ + + app_name: typing.Optional[str] = pydantic.Field(default=None) + """ + App name shown to Connect users + """ + + support_email: typing.Optional[str] = pydantic.Field(default=None) + """ + Support email configured for the project + """ + + external_url: typing.Optional[str] = pydantic.Field(default=None) + """ + External URL for the project, if configured + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow