diff --git a/pyproject.toml b/pyproject.toml index 2dab782..6a6900a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "scrapybara" -version = "2.0.6" +version = "2.0.7" description = "" readme = "README.md" authors = [] diff --git a/reference.md b/reference.md index 9b3ce7a..778f83f 100644 --- a/reference.md +++ b/reference.md @@ -113,6 +113,98 @@ client.get( + + + + +
client.get_instances() +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from scrapybara import Scrapybara + +client = Scrapybara( + api_key="YOUR_API_KEY", +) +client.get_instances() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.get_auth_states() +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from scrapybara import Scrapybara + +client = Scrapybara( + api_key="YOUR_API_KEY", +) +client.get_auth_states() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ +
@@ -957,11 +1049,11 @@ client.browser.get_cdp_url( -
client.browser.authenticate(...) +
client.browser.save_auth(...)
-#### 📝 Description +#### 🔌 Usage
@@ -969,12 +1061,62 @@ client.browser.get_cdp_url(
-Authenticate browser with Anon for all available apps +```python +from scrapybara import Scrapybara + +client = Scrapybara( + api_key="YOUR_API_KEY", +) +client.browser.save_auth( + instance_id="instance_id", +) + +```
+#### ⚙️ Parameters + +
+
+ +
+
+ +**instance_id:** `str` + +
+
+ +
+
+ +**name:** `typing.Optional[str]` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.browser.authenticate(...) +
+
+ #### 🔌 Usage
@@ -991,7 +1133,7 @@ client = Scrapybara( ) client.browser.authenticate( instance_id="instance_id", - context_id="context_id", + auth_state_id="auth_state_id", ) ``` @@ -1016,7 +1158,7 @@ client.browser.authenticate(
-**context_id:** `str` +**auth_state_id:** `str`
diff --git a/src/scrapybara/__init__.py b/src/scrapybara/__init__.py index 7322978..91c8291 100644 --- a/src/scrapybara/__init__.py +++ b/src/scrapybara/__init__.py @@ -2,6 +2,7 @@ from .types import ( ActResponse, + AuthStateResponse, BrowserAuthenticateResponse, BrowserGetCdpUrlResponse, CellType, @@ -18,6 +19,7 @@ KernelInfo, Notebook, NotebookCell, + SaveBrowserAuthResponse, ScrapeResponse, StartBrowserResponse, Status, @@ -38,6 +40,7 @@ "ActResponse", "Action", "AsyncScrapybara", + "AuthStateResponse", "BrowserAuthenticateResponse", "BrowserGetCdpUrlResponse", "CellType", @@ -56,6 +59,7 @@ "Model", "Notebook", "NotebookCell", + "SaveBrowserAuthResponse", "ScrapeResponse", "Scrapybara", "ScrapybaraEnvironment", diff --git a/src/scrapybara/base_client.py b/src/scrapybara/base_client.py index e6ee261..c2e37de 100644 --- a/src/scrapybara/base_client.py +++ b/src/scrapybara/base_client.py @@ -21,6 +21,7 @@ from .types.http_validation_error import HttpValidationError from json.decoder import JSONDecodeError from .core.jsonable_encoder import jsonable_encoder +from .types.auth_state_response import AuthStateResponse from .core.client_wrapper import AsyncClientWrapper from .instance.client import AsyncInstanceClient from .agent.client import AsyncAgentClient @@ -226,6 +227,90 @@ def get(self, instance_id: str, *, request_options: typing.Optional[RequestOptio raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) + def get_instances( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[GetInstanceResponse]: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[GetInstanceResponse] + Successful Response + + Examples + -------- + from scrapybara import Scrapybara + + client = Scrapybara( + api_key="YOUR_API_KEY", + ) + client.get_instances() + """ + _response = self._client_wrapper.httpx_client.request( + "v1/instances", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast( + typing.List[GetInstanceResponse], + parse_obj_as( + type_=typing.List[GetInstanceResponse], # type: ignore + object_=_response.json(), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + def get_auth_states( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[AuthStateResponse]: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[AuthStateResponse] + Successful Response + + Examples + -------- + from scrapybara import Scrapybara + + client = Scrapybara( + api_key="YOUR_API_KEY", + ) + client.get_auth_states() + """ + _response = self._client_wrapper.httpx_client.request( + "v1/auth_states", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast( + typing.List[AuthStateResponse], + parse_obj_as( + type_=typing.List[AuthStateResponse], # type: ignore + object_=_response.json(), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + class AsyncBaseClient: """ @@ -437,6 +522,106 @@ async def main() -> None: raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) + async def get_instances( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[GetInstanceResponse]: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[GetInstanceResponse] + Successful Response + + Examples + -------- + import asyncio + + from scrapybara import AsyncScrapybara + + client = AsyncScrapybara( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.get_instances() + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/instances", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast( + typing.List[GetInstanceResponse], + parse_obj_as( + type_=typing.List[GetInstanceResponse], # type: ignore + object_=_response.json(), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + async def get_auth_states( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[AuthStateResponse]: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[AuthStateResponse] + Successful Response + + Examples + -------- + import asyncio + + from scrapybara import AsyncScrapybara + + client = AsyncScrapybara( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.get_auth_states() + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/auth_states", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast( + typing.List[AuthStateResponse], + parse_obj_as( + type_=typing.List[AuthStateResponse], # type: ignore + object_=_response.json(), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + def _get_base_url(*, base_url: typing.Optional[str] = None, environment: ScrapybaraEnvironment) -> str: if base_url is not None: diff --git a/src/scrapybara/browser/client.py b/src/scrapybara/browser/client.py index e0da9e0..a77c77f 100644 --- a/src/scrapybara/browser/client.py +++ b/src/scrapybara/browser/client.py @@ -11,6 +11,7 @@ from json.decoder import JSONDecodeError from ..core.api_error import ApiError from ..types.browser_get_cdp_url_response import BrowserGetCdpUrlResponse +from ..types.save_browser_auth_response import SaveBrowserAuthResponse from ..types.browser_authenticate_response import BrowserAuthenticateResponse from ..types.stop_browser_response import StopBrowserResponse from ..core.client_wrapper import AsyncClientWrapper @@ -132,17 +133,80 @@ def get_cdp_url( raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) + def save_auth( + self, + instance_id: str, + *, + name: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SaveBrowserAuthResponse: + """ + Parameters + ---------- + instance_id : str + + name : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SaveBrowserAuthResponse + Successful Response + + Examples + -------- + from scrapybara import Scrapybara + + client = Scrapybara( + api_key="YOUR_API_KEY", + ) + client.browser.save_auth( + instance_id="instance_id", + ) + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/instance/{jsonable_encoder(instance_id)}/browser/save_auth", + method="POST", + params={ + "name": name, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast( + SaveBrowserAuthResponse, + parse_obj_as( + type_=SaveBrowserAuthResponse, # type: ignore + object_=_response.json(), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + typing.cast( + HttpValidationError, + parse_obj_as( + type_=HttpValidationError, # type: ignore + object_=_response.json(), + ), + ) + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + def authenticate( - self, instance_id: str, *, context_id: str, request_options: typing.Optional[RequestOptions] = None + self, instance_id: str, *, auth_state_id: str, request_options: typing.Optional[RequestOptions] = None ) -> BrowserAuthenticateResponse: """ - Authenticate browser with Anon for all available apps - Parameters ---------- instance_id : str - context_id : str + auth_state_id : str request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -161,14 +225,14 @@ def authenticate( ) client.browser.authenticate( instance_id="instance_id", - context_id="context_id", + auth_state_id="auth_state_id", ) """ _response = self._client_wrapper.httpx_client.request( f"v1/instance/{jsonable_encoder(instance_id)}/browser/authenticate", method="POST", params={ - "context_id": context_id, + "auth_state_id": auth_state_id, }, request_options=request_options, ) @@ -383,17 +447,88 @@ async def main() -> None: raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) + async def save_auth( + self, + instance_id: str, + *, + name: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SaveBrowserAuthResponse: + """ + Parameters + ---------- + instance_id : str + + name : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SaveBrowserAuthResponse + Successful Response + + Examples + -------- + import asyncio + + from scrapybara import AsyncScrapybara + + client = AsyncScrapybara( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.browser.save_auth( + instance_id="instance_id", + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/instance/{jsonable_encoder(instance_id)}/browser/save_auth", + method="POST", + params={ + "name": name, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast( + SaveBrowserAuthResponse, + parse_obj_as( + type_=SaveBrowserAuthResponse, # type: ignore + object_=_response.json(), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + typing.cast( + HttpValidationError, + parse_obj_as( + type_=HttpValidationError, # type: ignore + object_=_response.json(), + ), + ) + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + async def authenticate( - self, instance_id: str, *, context_id: str, request_options: typing.Optional[RequestOptions] = None + self, instance_id: str, *, auth_state_id: str, request_options: typing.Optional[RequestOptions] = None ) -> BrowserAuthenticateResponse: """ - Authenticate browser with Anon for all available apps - Parameters ---------- instance_id : str - context_id : str + auth_state_id : str request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -417,7 +552,7 @@ async def authenticate( async def main() -> None: await client.browser.authenticate( instance_id="instance_id", - context_id="context_id", + auth_state_id="auth_state_id", ) @@ -427,7 +562,7 @@ async def main() -> None: f"v1/instance/{jsonable_encoder(instance_id)}/browser/authenticate", method="POST", params={ - "context_id": context_id, + "auth_state_id": auth_state_id, }, request_options=request_options, ) diff --git a/src/scrapybara/client.py b/src/scrapybara/client.py index 9a750a9..d205c1c 100644 --- a/src/scrapybara/client.py +++ b/src/scrapybara/client.py @@ -4,13 +4,13 @@ import httpx import os -import typing from pydantic import BaseModel, ValidationError from scrapybara.agent.types.model import Model from scrapybara.environment import ScrapybaraEnvironment from .core.request_options import RequestOptions from .types import ( ActResponse, + AuthStateResponse, BrowserAuthenticateResponse, BrowserGetCdpUrlResponse, CellType, @@ -25,6 +25,7 @@ KernelInfo, Notebook as NotebookType, NotebookCell, + SaveBrowserAuthResponse, ScrapeResponse, StartBrowserResponse, StopBrowserResponse, @@ -179,11 +180,23 @@ def get_cdp_url( self.instance_id, request_options=request_options ) + def save_auth( + self, + *, + name: Optional[str] = None, + request_options: Optional[RequestOptions] = None, + ) -> SaveBrowserAuthResponse: + return self._client.browser.save_auth( + self.instance_id, name=name, request_options=request_options + ) + def authenticate( - self, *, context_id: str, request_options: Optional[RequestOptions] = None + self, *, auth_state_id: str, request_options: Optional[RequestOptions] = None ) -> BrowserAuthenticateResponse: return self._client.browser.authenticate( - self.instance_id, context_id=context_id, request_options=request_options + self.instance_id, + auth_state_id=auth_state_id, + request_options=request_options, ) def stop( @@ -213,11 +226,23 @@ async def get_cdp_url( self.instance_id, request_options=request_options ) + async def save_auth( + self, + *, + name: Optional[str] = None, + request_options: Optional[RequestOptions] = None, + ) -> SaveBrowserAuthResponse: + return await self._client.browser.save_auth( + self.instance_id, name=name, request_options=request_options + ) + async def authenticate( - self, *, context_id: str, request_options: Optional[RequestOptions] = None + self, *, auth_state_id: str, request_options: Optional[RequestOptions] = None ) -> BrowserAuthenticateResponse: return await self._client.browser.authenticate( - self.instance_id, context_id=context_id, request_options=request_options + self.instance_id, + auth_state_id=auth_state_id, + request_options=request_options, ) async def stop( @@ -896,6 +921,31 @@ def get( self._base_client, ) + def get_instances( + self, + *, + request_options: Optional[RequestOptions] = None, + ) -> List[Instance]: + response = self._base_client.get_instances(request_options=request_options) + return [ + Instance( + instance.id, + instance.launch_time, + instance.instance_type, + instance.status, + self._base_client, + ) + for instance in response + ] + + def get_auth_states( + self, + *, + request_options: Optional[RequestOptions] = None, + ) -> List[AuthStateResponse]: + response = self._base_client.get_auth_states(request_options=request_options) + return [AuthStateResponse(id=state.id, name=state.name) for state in response] + class AsyncScrapybara: def __init__( @@ -952,3 +1002,32 @@ async def get( response.status, self._base_client, ) + + async def get_instances( + self, + *, + request_options: Optional[RequestOptions] = None, + ) -> List[AsyncInstance]: + response = await self._base_client.get_instances( + request_options=request_options + ) + return [ + AsyncInstance( + instance.id, + instance.launch_time, + instance.instance_type, + instance.status, + self._base_client, + ) + for instance in response + ] + + async def get_auth_states( + self, + *, + request_options: Optional[RequestOptions] = None, + ) -> List[AuthStateResponse]: + response = await self._base_client.get_auth_states( + request_options=request_options + ) + return [AuthStateResponse(id=state.id, name=state.name) for state in response] diff --git a/src/scrapybara/core/client_wrapper.py b/src/scrapybara/core/client_wrapper.py index 583a197..f59b9a3 100644 --- a/src/scrapybara/core/client_wrapper.py +++ b/src/scrapybara/core/client_wrapper.py @@ -16,7 +16,7 @@ def get_headers(self) -> typing.Dict[str, str]: headers: typing.Dict[str, str] = { "X-Fern-Language": "Python", "X-Fern-SDK-Name": "scrapybara", - "X-Fern-SDK-Version": "2.0.6", + "X-Fern-SDK-Version": "2.0.7", } headers["x-api-key"] = self.api_key return headers diff --git a/src/scrapybara/types/__init__.py b/src/scrapybara/types/__init__.py index 884dbe3..b83f133 100644 --- a/src/scrapybara/types/__init__.py +++ b/src/scrapybara/types/__init__.py @@ -1,6 +1,7 @@ # This file was auto-generated by Fern from our API Definition. from .act_response import ActResponse +from .auth_state_response import AuthStateResponse from .browser_authenticate_response import BrowserAuthenticateResponse from .browser_get_cdp_url_response import BrowserGetCdpUrlResponse from .cell_type import CellType @@ -17,6 +18,7 @@ from .kernel_info import KernelInfo from .notebook import Notebook from .notebook_cell import NotebookCell +from .save_browser_auth_response import SaveBrowserAuthResponse from .scrape_response import ScrapeResponse from .start_browser_response import StartBrowserResponse from .status import Status @@ -27,6 +29,7 @@ __all__ = [ "ActResponse", + "AuthStateResponse", "BrowserAuthenticateResponse", "BrowserGetCdpUrlResponse", "CellType", @@ -43,6 +46,7 @@ "KernelInfo", "Notebook", "NotebookCell", + "SaveBrowserAuthResponse", "ScrapeResponse", "StartBrowserResponse", "Status", diff --git a/src/scrapybara/types/auth_state_response.py b/src/scrapybara/types/auth_state_response.py new file mode 100644 index 0000000..89272b7 --- /dev/null +++ b/src/scrapybara/types/auth_state_response.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +from ..core.pydantic_utilities import UniversalBaseModel +import typing +from ..core.pydantic_utilities import IS_PYDANTIC_V2 +import pydantic + + +class AuthStateResponse(UniversalBaseModel): + id: str + name: typing.Optional[str] = None + + 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/scrapybara/types/browser_authenticate_response.py b/src/scrapybara/types/browser_authenticate_response.py index f7ee971..154f087 100644 --- a/src/scrapybara/types/browser_authenticate_response.py +++ b/src/scrapybara/types/browser_authenticate_response.py @@ -1,14 +1,13 @@ # This file was auto-generated by Fern from our API Definition. from ..core.pydantic_utilities import UniversalBaseModel -import typing from ..core.pydantic_utilities import IS_PYDANTIC_V2 +import typing import pydantic class BrowserAuthenticateResponse(UniversalBaseModel): status: str - authenticated_apps: typing.List[str] if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 diff --git a/src/scrapybara/types/save_browser_auth_response.py b/src/scrapybara/types/save_browser_auth_response.py new file mode 100644 index 0000000..e1446ca --- /dev/null +++ b/src/scrapybara/types/save_browser_auth_response.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +from ..core.pydantic_utilities import UniversalBaseModel +import typing +from ..core.pydantic_utilities import IS_PYDANTIC_V2 +import pydantic + + +class SaveBrowserAuthResponse(UniversalBaseModel): + status: str + auth_state_id: str + name: typing.Optional[str] = None + + 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/tests/custom/test_client.py b/tests/custom/test_client.py index a9a60a0..6f81cab 100644 --- a/tests/custom/test_client.py +++ b/tests/custom/test_client.py @@ -8,6 +8,12 @@ def test_client() -> None: client = Scrapybara() instance = client.start() assert instance.id is not None + instances = client.get_instances() + assert len(instances) > 0 screenshot_response = instance.screenshot() assert screenshot_response.base_64_image is not None + instance.browser.start() + cdp_url = instance.browser.get_cdp_url() + assert cdp_url is not None + instance.browser.stop() instance.stop()