diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e2e170..347cfcb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/droidrun-cloud-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Rye run: | @@ -46,7 +46,7 @@ jobs: id-token: write runs-on: ${{ github.repository == 'stainless-sdks/droidrun-cloud-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Rye run: | @@ -67,7 +67,7 @@ jobs: github.repository == 'stainless-sdks/droidrun-cloud-python' && !startsWith(github.ref, 'refs/heads/stl/') id: github-oidc - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: core.setOutput('github_token', await core.getIDToken()); @@ -87,7 +87,7 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/droidrun-cloud-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Rye run: | diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 965bfe5..112bd43 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Rye run: | diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 9f5c3c4..ba61562 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'droidrun/mobilerun-sdk-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Check release environment run: | diff --git a/.release-please-manifest.json b/.release-please-manifest.json index e0dc500..1f73031 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.1.0" + ".": "3.2.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 1578830..20e38d9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 89 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/droidrun%2Fdroidrun-cloud-5354e1e393f7a2c470fba288fd927027d7f8ab0f76350be330392a32d61321d7.yml -openapi_spec_hash: 61d176c5697051a52251e67cc2a143b7 -config_hash: 2e5f796057a879ad2efd58b3ec8289cf +configured_endpoints: 104 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/droidrun/droidrun-cloud-9c5542353ace880640fb7e8946c5c8d008480abb8d1952595ba2ee9323aed724.yml +openapi_spec_hash: afa528f0921063291f66c56c6093f2ab +config_hash: 27cfddf59d8227e89667fa012952335c diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b66e4c..91e1a63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,43 @@ # Changelog +## 3.2.0 (2026-05-21) + +Full Changelog: [v3.1.0...v3.2.0](https://github.com/droidrun/mobilerun-sdk-python/compare/v3.1.0...v3.2.0) + +### Features + +* **api:** add back devices.state.time ([d6f0634](https://github.com/droidrun/mobilerun-sdk-python/commit/d6f0634aa89816215469ab0140d06256e32e3874)) +* **api:** add missing device tools ([b11b8a9](https://github.com/droidrun/mobilerun-sdk-python/commit/b11b8a95ca84a2cc657f63b6e0a97182a698bfbb)) +* **api:** api update ([2791087](https://github.com/droidrun/mobilerun-sdk-python/commit/279108770b917a87a17b62a9855233133b7d651c)) +* **api:** api update ([c77e4cb](https://github.com/droidrun/mobilerun-sdk-python/commit/c77e4cbf5e12b8d69f71a64e0e1b6e5e2570b027)) +* **api:** api update ([713c27d](https://github.com/droidrun/mobilerun-sdk-python/commit/713c27da8f2a496f57b68bb2465f9e8ebd13ede0)) +* **api:** api update ([1e39fcb](https://github.com/droidrun/mobilerun-sdk-python/commit/1e39fcbdc514e9ef20feda52cd0232c8f6b568aa)) +* **api:** api update ([8b8b8b1](https://github.com/droidrun/mobilerun-sdk-python/commit/8b8b8b1d56db10de229123323ccdcb28b3903086)) +* **api:** api update ([75bbc88](https://github.com/droidrun/mobilerun-sdk-python/commit/75bbc88550c6f53e16c25c692ea0febc2ed07acd)) +* **api:** device timezone api like location ([2dc5cdd](https://github.com/droidrun/mobilerun-sdk-python/commit/2dc5cdd19fe9f7c40a66c0e39d4c2e015e982c4d)) +* **api:** labels for objects ([0b8ee8c](https://github.com/droidrun/mobilerun-sdk-python/commit/0b8ee8c2cd690761a249fb97ac22c393a574f8f3)) +* **api:** manual updates ([7861088](https://github.com/droidrun/mobilerun-sdk-python/commit/786108846d965ae14d619615b1d991d0cf937cb1)) +* **api:** manual updates ([4579552](https://github.com/droidrun/mobilerun-sdk-python/commit/4579552f608c8de6aeab1731f512509ff049b6a4)) +* **internal/types:** support eagerly validating pydantic iterators ([8c7fc10](https://github.com/droidrun/mobilerun-sdk-python/commit/8c7fc10dae7a32f182a614733b3ea7a79455b288)) +* support setting headers via env ([273abbc](https://github.com/droidrun/mobilerun-sdk-python/commit/273abbc5339e39f57f59d885a9bfdf5df5d2e4fe)) + + +### Bug Fixes + +* **client:** add missing f-string prefix in file type error message ([8d073c2](https://github.com/droidrun/mobilerun-sdk-python/commit/8d073c20fc48f1ae04718591bcd2b7f8805c9c3c)) +* use correct field name format for multipart file arrays ([7ad94ce](https://github.com/droidrun/mobilerun-sdk-python/commit/7ad94ce83e73be25a9dc55c91aabe36065594480)) + + +### Performance Improvements + +* **client:** optimize file structure copying in multipart requests ([b6562d3](https://github.com/droidrun/mobilerun-sdk-python/commit/b6562d3c3c02a73d9e7e36f26bfd6d86728993d2)) + + +### Chores + +* **internal:** more robust bootstrap script ([d8ed809](https://github.com/droidrun/mobilerun-sdk-python/commit/d8ed809f6aec7556e96cd697a8b517120cd4ebff)) +* **internal:** reformat pyproject.toml ([d108c45](https://github.com/droidrun/mobilerun-sdk-python/commit/d108c45430e484b935c7ee5bd17138c2bc998d0f)) + ## 3.1.0 (2026-04-16) Full Changelog: [v3.0.0...v3.1.0](https://github.com/droidrun/mobilerun-sdk-python/compare/v3.0.0...v3.1.0) diff --git a/README.md b/README.md index 748a763..73b8b1d 100644 --- a/README.md +++ b/README.md @@ -124,11 +124,17 @@ from mobilerun_sdk import Mobilerun client = Mobilerun() -profile = client.profiles.create( - name="x", - spec={}, +device = client.devices.create( + carrier={ + "gsm_operator_alpha": "GsmOperatorAlpha", + "gsm_operator_numeric": 0, + "gsm_sim_operator_alpha": "GsmSimOperatorAlpha", + "gsm_sim_operator_iso_country": "GsmSimOperatorIsoCountry", + "gsm_sim_operator_numeric": 0, + "persist_sys_timezone": "PersistSysTimezone", + }, ) -print(profile.spec) +print(device.carrier) ``` ## File uploads diff --git a/api.md b/api.md index dfe1856..ccda2c7 100644 --- a/api.md +++ b/api.md @@ -5,145 +5,148 @@ from mobilerun_sdk.types import ( DeviceCarrier, DeviceIdentifiers, DeviceSpec, + Location, Meta, Pagination, PaginationMeta, PermissionSet, + Socks5, ) ``` -# Tasks +# Agents Types: ```python -from mobilerun_sdk.types import ( - PackageCredentials, - Task, - TaskStatus, - UsageResult, - TaskRetrieveResponse, - TaskListResponse, - TaskGetStatusResponse, - TaskGetTrajectoryResponse, - TaskRunResponse, - TaskSendMessageResponse, - TaskStopResponse, -) +from mobilerun_sdk.types import AgentListResponse ``` Methods: -- client.tasks.retrieve(task_id) -> TaskRetrieveResponse -- client.tasks.list(\*\*params) -> TaskListResponse -- client.tasks.attach(task_id) -> None -- client.tasks.get_status(task_id) -> TaskGetStatusResponse -- client.tasks.get_trajectory(task_id) -> TaskGetTrajectoryResponse -- client.tasks.run(\*\*params) -> TaskRunResponse -- client.tasks.run_streamed(\*\*params) -> object -- client.tasks.send_message(task_id, \*\*params) -> TaskSendMessageResponse -- client.tasks.stop(task_id) -> TaskStopResponse +- client.agents.list() -> AgentListResponse -## Screenshots +# Apps Types: ```python -from mobilerun_sdk.types.tasks import MediaResponse, ScreenshotListResponse +from mobilerun_sdk.types import ( + AppRetrieveResponse, + AppListResponse, + AppDeleteResponse, + AppConfirmUploadResponse, + AppCreateSignedUploadURLResponse, + AppMarkFailedResponse, +) ``` Methods: -- client.tasks.screenshots.retrieve(index, \*, task_id) -> MediaResponse -- client.tasks.screenshots.list(task_id) -> ScreenshotListResponse +- client.apps.retrieve(id) -> AppRetrieveResponse +- client.apps.list(\*\*params) -> AppListResponse +- client.apps.delete(id) -> AppDeleteResponse +- client.apps.confirm_upload(id) -> AppConfirmUploadResponse +- client.apps.create_signed_upload_url(\*\*params) -> AppCreateSignedUploadURLResponse +- client.apps.mark_failed(id) -> AppMarkFailedResponse -## UiStates +# Carriers Types: ```python -from mobilerun_sdk.types.tasks import UiStateListResponse +from mobilerun_sdk.types import ( + CarrierCreateResponse, + CarrierRetrieveResponse, + CarrierUpdateResponse, + CarrierListResponse, + CarrierDeleteResponse, + CarrierLookupResponse, +) ``` Methods: -- client.tasks.ui_states.retrieve(index, \*, task_id) -> MediaResponse -- client.tasks.ui_states.list(task_id) -> UiStateListResponse +- client.carriers.create(\*\*params) -> CarrierCreateResponse +- client.carriers.retrieve(carrier_id) -> CarrierRetrieveResponse +- client.carriers.update(carrier_id, \*\*params) -> CarrierUpdateResponse +- client.carriers.list(\*\*params) -> CarrierListResponse +- client.carriers.delete(carrier_id) -> CarrierDeleteResponse +- client.carriers.lookup(\*\*params) -> CarrierLookupResponse -# Agents +# Credentials Types: ```python -from mobilerun_sdk.types import AgentListResponse +from mobilerun_sdk.types import CredentialListResponse ``` Methods: -- client.agents.list() -> AgentListResponse +- client.credentials.list(\*\*params) -> CredentialListResponse -# Proxies +## Packages Types: ```python -from mobilerun_sdk.types import ( - ProxyConfig, - ProxyCreateResponse, - ProxyRetrieveResponse, - ProxyUpdateResponse, - ProxyListResponse, - ProxyDeleteResponse, -) +from mobilerun_sdk.types.credentials import PackageCreateResponse, PackageListResponse ``` Methods: -- client.proxies.create(\*\*params) -> ProxyCreateResponse -- client.proxies.retrieve(proxy_id) -> ProxyRetrieveResponse -- client.proxies.update(proxy_id, \*\*params) -> ProxyUpdateResponse -- client.proxies.list(\*\*params) -> ProxyListResponse -- client.proxies.delete(proxy_id) -> ProxyDeleteResponse +- client.credentials.packages.create(\*\*params) -> PackageCreateResponse +- client.credentials.packages.list(package_name) -> PackageListResponse -# Carriers +### Credentials Types: ```python -from mobilerun_sdk.types import Carrier, CarrierListResponse, CarrierDeleteResponse +from mobilerun_sdk.types.credentials.packages import ( + Credential, + CredentialCreateResponse, + CredentialRetrieveResponse, + CredentialDeleteResponse, +) ``` Methods: -- client.carriers.create(\*\*params) -> Carrier -- client.carriers.retrieve(carrier_id) -> Carrier -- client.carriers.update(carrier_id, \*\*params) -> Carrier -- client.carriers.list(\*\*params) -> CarrierListResponse -- client.carriers.delete(carrier_id) -> CarrierDeleteResponse -- client.carriers.lookup(\*\*params) -> Carrier +- client.credentials.packages.credentials.create(package_name, \*\*params) -> CredentialCreateResponse +- client.credentials.packages.credentials.retrieve(credential_name, \*, package_name) -> CredentialRetrieveResponse +- client.credentials.packages.credentials.delete(credential_name, \*, package_name) -> CredentialDeleteResponse -# Profiles +#### Fields Types: ```python -from mobilerun_sdk.types import Profile, ProfileListResponse, ProfileDeleteResponse +from mobilerun_sdk.types.credentials.packages.credentials import ( + FieldCreateResponse, + FieldUpdateResponse, + FieldDeleteResponse, +) ``` Methods: -- client.profiles.create(\*\*params) -> Profile -- client.profiles.retrieve(profile_id) -> Profile -- client.profiles.update(profile_id, \*\*params) -> Profile -- client.profiles.list(\*\*params) -> ProfileListResponse -- client.profiles.delete(profile_id) -> ProfileDeleteResponse +- client.credentials.packages.credentials.fields.create(credential_name, \*, package_name, \*\*params) -> FieldCreateResponse +- client.credentials.packages.credentials.fields.update(field_type, \*, package_name, credential_name, \*\*params) -> FieldUpdateResponse +- client.credentials.packages.credentials.fields.delete(field_type, \*, package_name, credential_name) -> FieldDeleteResponse # Devices Types: ```python -from mobilerun_sdk.types import Device, DeviceListResponse, DeviceCountResponse +from mobilerun_sdk.types import ( + Device, + DeviceListResponse, + DeviceCountResponse, + DeviceFingerprintResponse, +) ``` Methods: @@ -152,136 +155,156 @@ Methods: - client.devices.retrieve(device_id) -> Device - client.devices.list(\*\*params) -> DeviceListResponse - client.devices.count() -> DeviceCountResponse +- client.devices.fingerprint(device_id) -> DeviceFingerprintResponse +- client.devices.reboot(device_id) -> None +- client.devices.reset(device_id) -> None - client.devices.set_name(device_id, \*\*params) -> Device - client.devices.terminate(device_id, \*\*params) -> None - client.devices.wait_ready(device_id) -> Device -## Time +## Actions Types: ```python -from mobilerun_sdk.types.devices import TimeTimeResponse, TimeTimezoneResponse +from mobilerun_sdk.types.devices import ActionOverlayVisibleResponse ``` Methods: -- client.devices.time.set_timezone(device_id, \*\*params) -> None -- client.devices.time.time(device_id) -> str -- client.devices.time.timezone(device_id) -> TimeTimezoneResponse - -## Profile - -Methods: - -- client.devices.profile.update(device_id, \*\*params) -> None +- client.devices.actions.global\_(device_id, \*\*params) -> None +- client.devices.actions.overlay_visible(device_id) -> ActionOverlayVisibleResponse +- client.devices.actions.set_overlay_visible(device_id, \*\*params) -> None +- client.devices.actions.swipe(device_id, \*\*params) -> None +- client.devices.actions.tap(device_id, \*\*params) -> None -## Files +## Apps Types: ```python -from mobilerun_sdk.types.devices import FileInfo, FileListResponse, FileDownloadResponse +from mobilerun_sdk.types.devices import AppListResponse ``` Methods: -- client.devices.files.list(device_id, \*\*params) -> FileListResponse -- client.devices.files.delete(device_id, \*\*params) -> None -- client.devices.files.download(device_id, \*\*params) -> str -- client.devices.files.upload(device_id, \*\*params) -> None +- client.devices.apps.update(package_name, \*, device_id) -> None +- client.devices.apps.list(device_id, \*\*params) -> Optional[AppListResponse] +- client.devices.apps.delete(package_name, \*, device_id) -> None +- client.devices.apps.install(device_id, \*\*params) -> None +- client.devices.apps.start(package_name, \*, device_id, \*\*params) -> None -## Proxy +## Esim Types: ```python -from mobilerun_sdk.types.devices import ProxyStatusResponse +from mobilerun_sdk.types.devices import EsimListResponse, EsimActivateResponse, EsimStatusResponse ``` Methods: -- client.devices.proxy.connect(device_id, \*\*params) -> None -- client.devices.proxy.disconnect(device_id) -> None -- client.devices.proxy.status(device_id) -> ProxyStatusResponse +- client.devices.esim.list(device_id) -> Optional[EsimListResponse] +- client.devices.esim.activate(device_id, \*\*params) -> EsimActivateResponse +- client.devices.esim.enable(device_id, \*\*params) -> None +- client.devices.esim.remove(device_id, \*\*params) -> None +- client.devices.esim.set_roaming(device_id, \*\*params) -> None +- client.devices.esim.status(device_id) -> Optional[EsimStatusResponse] -## Location +### Apn Types: ```python -from mobilerun_sdk.types.devices import LocationGetResponse +from mobilerun_sdk.types.devices.esim import ApnListResponse ``` Methods: -- client.devices.location.get(device_id) -> LocationGetResponse -- client.devices.location.set(device_id, \*\*params) -> None +- client.devices.esim.apn.create(device_id, \*\*params) -> None +- client.devices.esim.apn.list(device_id) -> Optional[ApnListResponse] +- client.devices.esim.apn.select(device_id, \*\*params) -> None -## Actions +## Files Types: ```python -from mobilerun_sdk.types.devices import ActionOverlayVisibleResponse +from mobilerun_sdk.types.devices import FileInfo, FileListResponse, FileDownloadResponse ``` Methods: -- client.devices.actions.global\_(device_id, \*\*params) -> None -- client.devices.actions.overlay_visible(device_id) -> ActionOverlayVisibleResponse -- client.devices.actions.set_overlay_visible(device_id, \*\*params) -> None -- client.devices.actions.swipe(device_id, \*\*params) -> None -- client.devices.actions.tap(device_id, \*\*params) -> None +- client.devices.files.list(device_id, \*\*params) -> FileListResponse +- client.devices.files.delete(device_id, \*\*params) -> None +- client.devices.files.download(device_id, \*\*params) -> str +- client.devices.files.upload(device_id, \*\*params) -> None -## State +## Keyboard -Types: +Methods: -```python -from mobilerun_sdk.types.devices import Rect, StateScreenshotResponse, StateUiResponse -``` +- client.devices.keyboard.clear(device_id) -> None +- client.devices.keyboard.key(device_id, \*\*params) -> None +- client.devices.keyboard.write(device_id, \*\*params) -> None + +## Location Methods: -- client.devices.state.screenshot(device_id, \*\*params) -> str -- client.devices.state.ui(device_id, \*\*params) -> StateUiResponse +- client.devices.location.get(device_id) -> Location +- client.devices.location.set(device_id, \*\*params) -> None -## Apps +## Packages Types: ```python -from mobilerun_sdk.types.devices import AppListResponse +from mobilerun_sdk.types.devices import PackageListResponse ``` Methods: -- client.devices.apps.update(package_name, \*, device_id) -> None -- client.devices.apps.list(device_id, \*\*params) -> Optional[AppListResponse] -- client.devices.apps.delete(package_name, \*, device_id) -> None -- client.devices.apps.install(device_id, \*\*params) -> None -- client.devices.apps.start(package_name, \*, device_id, \*\*params) -> None +- client.devices.packages.list(device_id, \*\*params) -> Optional[PackageListResponse] -## Packages +## Profile + +Methods: + +- client.devices.profile.update(device_id, \*\*params) -> None + +## Proxy Types: ```python -from mobilerun_sdk.types.devices import PackageListResponse +from mobilerun_sdk.types.devices import ProxyStatusResponse ``` Methods: -- client.devices.packages.list(device_id, \*\*params) -> Optional[PackageListResponse] +- client.devices.proxy.connect(device_id, \*\*params) -> None +- client.devices.proxy.disconnect(device_id) -> None +- client.devices.proxy.status(device_id) -> ProxyStatusResponse -## Keyboard +## State + +Types: + +```python +from mobilerun_sdk.types.devices import ( + Rect, + StateScreenshotResponse, + StateTimeResponse, + StateUiResponse, +) +``` Methods: -- client.devices.keyboard.clear(device_id) -> None -- client.devices.keyboard.key(device_id, \*\*params) -> None -- client.devices.keyboard.write(device_id, \*\*params) -> None +- client.devices.state.screenshot(device_id, \*\*params) -> str +- client.devices.state.time(device_id) -> str +- client.devices.state.ui(device_id, \*\*params) -> StateUiResponse ## Tasks @@ -295,129 +318,163 @@ Methods: - client.devices.tasks.list(device_id, \*\*params) -> TaskListResponse -## Esim +## Timezone Types: ```python -from mobilerun_sdk.types.devices import EsimListResponse, EsimActivateResponse +from mobilerun_sdk.types.devices import TimezoneGetResponse ``` Methods: -- client.devices.esim.list(device_id) -> Optional[EsimListResponse] -- client.devices.esim.activate(device_id, \*\*params) -> EsimActivateResponse -- client.devices.esim.enable(device_id, \*\*params) -> None -- client.devices.esim.remove(device_id, \*\*params) -> None +- client.devices.timezone.get(device_id) -> TimezoneGetResponse +- client.devices.timezone.set(device_id, \*\*params) -> None -# Apps +## Language Types: ```python -from mobilerun_sdk.types import AppListResponse +from mobilerun_sdk.types.devices import LanguageGetResponse ``` Methods: -- client.apps.list(\*\*params) -> AppListResponse +- client.devices.language.get(device_id) -> LanguageGetResponse +- client.devices.language.set(device_id, \*\*params) -> None -# Credentials +# Hooks Types: ```python -from mobilerun_sdk.types import CredentialListResponse +from mobilerun_sdk.types import ( + HookRetrieveResponse, + HookUpdateResponse, + HookListResponse, + HookGetSampleDataResponse, + HookPerformResponse, + HookSubscribeResponse, + HookUnsubscribeResponse, +) ``` Methods: -- client.credentials.list(\*\*params) -> CredentialListResponse +- client.hooks.retrieve(hook_id) -> HookRetrieveResponse +- client.hooks.update(hook_id, \*\*params) -> HookUpdateResponse +- client.hooks.list(\*\*params) -> HookListResponse +- client.hooks.get_sample_data() -> HookGetSampleDataResponse +- client.hooks.perform(\*\*params) -> HookPerformResponse +- client.hooks.subscribe(\*\*params) -> HookSubscribeResponse +- client.hooks.unsubscribe(hook_id) -> HookUnsubscribeResponse -## Packages +# Models Types: ```python -from mobilerun_sdk.types.credentials import PackageCreateResponse, PackageListResponse +from mobilerun_sdk.types import ModelListResponse ``` Methods: -- client.credentials.packages.create(\*\*params) -> PackageCreateResponse -- client.credentials.packages.list(package_name) -> PackageListResponse +- client.models.list() -> ModelListResponse -### Credentials +# Profiles Types: ```python -from mobilerun_sdk.types.credentials.packages import ( - Credential, - CredentialCreateResponse, - CredentialRetrieveResponse, - CredentialDeleteResponse, -) +from mobilerun_sdk.types import Profile, ProfileListResponse, ProfileDeleteResponse ``` Methods: -- client.credentials.packages.credentials.create(package_name, \*\*params) -> CredentialCreateResponse -- client.credentials.packages.credentials.retrieve(credential_name, \*, package_name) -> CredentialRetrieveResponse -- client.credentials.packages.credentials.delete(credential_name, \*, package_name) -> CredentialDeleteResponse +- client.profiles.create(\*\*params) -> Profile +- client.profiles.retrieve(profile_id) -> Profile +- client.profiles.update(profile_id, \*\*params) -> Profile +- client.profiles.list(\*\*params) -> ProfileListResponse +- client.profiles.delete(profile_id) -> ProfileDeleteResponse -#### Fields +# Proxies Types: ```python -from mobilerun_sdk.types.credentials.packages.credentials import ( - FieldCreateResponse, - FieldUpdateResponse, - FieldDeleteResponse, +from mobilerun_sdk.types import ( + ProxyConfig, + ProxyCreateResponse, + ProxyRetrieveResponse, + ProxyUpdateResponse, + ProxyListResponse, + ProxyDeleteResponse, ) ``` Methods: -- client.credentials.packages.credentials.fields.create(credential_name, \*, package_name, \*\*params) -> FieldCreateResponse -- client.credentials.packages.credentials.fields.update(field_type, \*, package_name, credential_name, \*\*params) -> FieldUpdateResponse -- client.credentials.packages.credentials.fields.delete(field_type, \*, package_name, credential_name) -> FieldDeleteResponse +- client.proxies.create(\*\*params) -> ProxyCreateResponse +- client.proxies.retrieve(proxy_id) -> ProxyRetrieveResponse +- client.proxies.update(proxy_id, \*\*params) -> ProxyUpdateResponse +- client.proxies.list(\*\*params) -> ProxyListResponse +- client.proxies.delete(proxy_id) -> ProxyDeleteResponse -# Hooks +# Tasks Types: ```python from mobilerun_sdk.types import ( - HookRetrieveResponse, - HookUpdateResponse, - HookListResponse, - HookGetSampleDataResponse, - HookPerformResponse, - HookSubscribeResponse, - HookUnsubscribeResponse, + PackageCredentials, + Task, + TaskStatus, + UsageResult, + TaskRetrieveResponse, + TaskListResponse, + TaskGetStatusResponse, + TaskGetTrajectoryResponse, + TaskRunResponse, + TaskSendMessageResponse, + TaskStopResponse, ) ``` Methods: -- client.hooks.retrieve(hook_id) -> HookRetrieveResponse -- client.hooks.update(hook_id, \*\*params) -> HookUpdateResponse -- client.hooks.list(\*\*params) -> HookListResponse -- client.hooks.get_sample_data() -> HookGetSampleDataResponse -- client.hooks.perform(\*\*params) -> HookPerformResponse -- client.hooks.subscribe(\*\*params) -> HookSubscribeResponse -- client.hooks.unsubscribe(hook_id) -> HookUnsubscribeResponse +- client.tasks.retrieve(task_id) -> TaskRetrieveResponse +- client.tasks.list(\*\*params) -> TaskListResponse +- client.tasks.attach(task_id) -> None +- client.tasks.get_status(task_id) -> TaskGetStatusResponse +- client.tasks.get_trajectory(task_id) -> TaskGetTrajectoryResponse +- client.tasks.run(\*\*params) -> TaskRunResponse +- client.tasks.run_streamed(\*\*params) -> object +- client.tasks.send_message(task_id, \*\*params) -> TaskSendMessageResponse +- client.tasks.stop(task_id) -> TaskStopResponse -# Models +## Screenshots Types: ```python -from mobilerun_sdk.types import ModelListResponse +from mobilerun_sdk.types.tasks import MediaResponse, ScreenshotListResponse ``` Methods: -- client.models.list() -> ModelListResponse +- client.tasks.screenshots.retrieve(index, \*, task_id) -> MediaResponse +- client.tasks.screenshots.list(task_id) -> ScreenshotListResponse + +## UiStates + +Types: + +```python +from mobilerun_sdk.types.tasks import UiStateListResponse +``` + +Methods: + +- client.tasks.ui_states.retrieve(index, \*, task_id) -> MediaResponse +- client.tasks.ui_states.list(task_id) -> UiStateListResponse diff --git a/pyproject.toml b/pyproject.toml index 4a1ba0a..b097c23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mobilerun-sdk" -version = "3.1.0" +version = "3.2.0" description = "The official Python library for the mobilerun API" dynamic = ["readme"] license = "Apache-2.0" @@ -168,7 +168,7 @@ show_error_codes = true # # We also exclude our `tests` as mypy doesn't always infer # types correctly and Pyright will still catch any type errors. -exclude = ['src/mobilerun_sdk/_files.py', '_dev/.*.py', 'tests/.*'] +exclude = ["src/mobilerun_sdk/_files.py", "_dev/.*.py", "tests/.*"] strict_equality = true implicit_reexport = true diff --git a/scripts/bootstrap b/scripts/bootstrap index b430fee..fe8451e 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,7 +4,7 @@ set -e cd "$(dirname "$0")/.." -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "${SKIP_BREW:-}" != "1" ] && [ -t 0 ]; then brew bundle check >/dev/null 2>&1 || { echo -n "==> Install Homebrew dependencies? (y/N): " read -r response diff --git a/src/mobilerun_sdk/_client.py b/src/mobilerun_sdk/_client.py index 09cae30..28b10f0 100644 --- a/src/mobilerun_sdk/_client.py +++ b/src/mobilerun_sdk/_client.py @@ -20,7 +20,11 @@ RequestOptions, not_given, ) -from ._utils import is_given, get_async_library +from ._utils import ( + is_given, + is_mapping_t, + get_async_library, +) from ._compat import cached_property from ._version import __version__ from ._streaming import Stream as Stream, AsyncStream as AsyncStream @@ -96,6 +100,15 @@ def __init__( if base_url is None: base_url = f"https://api.mobilerun.ai/v1" + custom_headers_env = os.environ.get("MOBILERUN_CUSTOM_HEADERS") + if custom_headers_env is not None: + parsed: dict[str, str] = {} + for line in custom_headers_env.split("\n"): + colon = line.find(":") + if colon >= 0: + parsed[line[:colon].strip()] = line[colon + 1 :].strip() + default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})} + super().__init__( version=__version__, base_url=base_url, @@ -107,13 +120,6 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - @cached_property - def tasks(self) -> TasksResource: - """Tasks API""" - from .resources.tasks import TasksResource - - return TasksResource(self) - @cached_property def agents(self) -> AgentsResource: """Agents API""" @@ -122,22 +128,25 @@ def agents(self) -> AgentsResource: return AgentsResource(self) @cached_property - def proxies(self) -> ProxiesResource: - from .resources.proxies import ProxiesResource + def apps(self) -> AppsResource: + """App Management""" + from .resources.apps import AppsResource - return ProxiesResource(self) + return AppsResource(self) @cached_property def carriers(self) -> CarriersResource: + """Mobile Carriers""" from .resources.carriers import CarriersResource return CarriersResource(self) @cached_property - def profiles(self) -> ProfilesResource: - from .resources.profiles import ProfilesResource + def credentials(self) -> CredentialsResource: + """Vault & Secrets""" + from .resources.credentials import CredentialsResource - return ProfilesResource(self) + return CredentialsResource(self) @cached_property def devices(self) -> DevicesResource: @@ -145,31 +154,39 @@ def devices(self) -> DevicesResource: return DevicesResource(self) - @cached_property - def apps(self) -> AppsResource: - from .resources.apps import AppsResource - - return AppsResource(self) - - @cached_property - def credentials(self) -> CredentialsResource: - from .resources.credentials import CredentialsResource - - return CredentialsResource(self) - @cached_property def hooks(self) -> HooksResource: - """Webhooks API""" from .resources.hooks import HooksResource return HooksResource(self) @cached_property def models(self) -> ModelsResource: + """LLM Models""" from .resources.models import ModelsResource return ModelsResource(self) + @cached_property + def profiles(self) -> ProfilesResource: + from .resources.profiles import ProfilesResource + + return ProfilesResource(self) + + @cached_property + def proxies(self) -> ProxiesResource: + """Network Proxies""" + from .resources.proxies import ProxiesResource + + return ProxiesResource(self) + + @cached_property + def tasks(self) -> TasksResource: + """Tasks API""" + from .resources.tasks import TasksResource + + return TasksResource(self) + @cached_property def with_raw_response(self) -> MobilerunWithRawResponse: return MobilerunWithRawResponse(self) @@ -334,6 +351,15 @@ def __init__( if base_url is None: base_url = f"https://api.mobilerun.ai/v1" + custom_headers_env = os.environ.get("MOBILERUN_CUSTOM_HEADERS") + if custom_headers_env is not None: + parsed: dict[str, str] = {} + for line in custom_headers_env.split("\n"): + colon = line.find(":") + if colon >= 0: + parsed[line[:colon].strip()] = line[colon + 1 :].strip() + default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})} + super().__init__( version=__version__, base_url=base_url, @@ -345,13 +371,6 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - @cached_property - def tasks(self) -> AsyncTasksResource: - """Tasks API""" - from .resources.tasks import AsyncTasksResource - - return AsyncTasksResource(self) - @cached_property def agents(self) -> AsyncAgentsResource: """Agents API""" @@ -360,22 +379,25 @@ def agents(self) -> AsyncAgentsResource: return AsyncAgentsResource(self) @cached_property - def proxies(self) -> AsyncProxiesResource: - from .resources.proxies import AsyncProxiesResource + def apps(self) -> AsyncAppsResource: + """App Management""" + from .resources.apps import AsyncAppsResource - return AsyncProxiesResource(self) + return AsyncAppsResource(self) @cached_property def carriers(self) -> AsyncCarriersResource: + """Mobile Carriers""" from .resources.carriers import AsyncCarriersResource return AsyncCarriersResource(self) @cached_property - def profiles(self) -> AsyncProfilesResource: - from .resources.profiles import AsyncProfilesResource + def credentials(self) -> AsyncCredentialsResource: + """Vault & Secrets""" + from .resources.credentials import AsyncCredentialsResource - return AsyncProfilesResource(self) + return AsyncCredentialsResource(self) @cached_property def devices(self) -> AsyncDevicesResource: @@ -383,31 +405,39 @@ def devices(self) -> AsyncDevicesResource: return AsyncDevicesResource(self) - @cached_property - def apps(self) -> AsyncAppsResource: - from .resources.apps import AsyncAppsResource - - return AsyncAppsResource(self) - - @cached_property - def credentials(self) -> AsyncCredentialsResource: - from .resources.credentials import AsyncCredentialsResource - - return AsyncCredentialsResource(self) - @cached_property def hooks(self) -> AsyncHooksResource: - """Webhooks API""" from .resources.hooks import AsyncHooksResource return AsyncHooksResource(self) @cached_property def models(self) -> AsyncModelsResource: + """LLM Models""" from .resources.models import AsyncModelsResource return AsyncModelsResource(self) + @cached_property + def profiles(self) -> AsyncProfilesResource: + from .resources.profiles import AsyncProfilesResource + + return AsyncProfilesResource(self) + + @cached_property + def proxies(self) -> AsyncProxiesResource: + """Network Proxies""" + from .resources.proxies import AsyncProxiesResource + + return AsyncProxiesResource(self) + + @cached_property + def tasks(self) -> AsyncTasksResource: + """Tasks API""" + from .resources.tasks import AsyncTasksResource + + return AsyncTasksResource(self) + @cached_property def with_raw_response(self) -> AsyncMobilerunWithRawResponse: return AsyncMobilerunWithRawResponse(self) @@ -538,13 +568,6 @@ class MobilerunWithRawResponse: def __init__(self, client: Mobilerun) -> None: self._client = client - @cached_property - def tasks(self) -> tasks.TasksResourceWithRawResponse: - """Tasks API""" - from .resources.tasks import TasksResourceWithRawResponse - - return TasksResourceWithRawResponse(self._client.tasks) - @cached_property def agents(self) -> agents.AgentsResourceWithRawResponse: """Agents API""" @@ -553,22 +576,25 @@ def agents(self) -> agents.AgentsResourceWithRawResponse: return AgentsResourceWithRawResponse(self._client.agents) @cached_property - def proxies(self) -> proxies.ProxiesResourceWithRawResponse: - from .resources.proxies import ProxiesResourceWithRawResponse + def apps(self) -> apps.AppsResourceWithRawResponse: + """App Management""" + from .resources.apps import AppsResourceWithRawResponse - return ProxiesResourceWithRawResponse(self._client.proxies) + return AppsResourceWithRawResponse(self._client.apps) @cached_property def carriers(self) -> carriers.CarriersResourceWithRawResponse: + """Mobile Carriers""" from .resources.carriers import CarriersResourceWithRawResponse return CarriersResourceWithRawResponse(self._client.carriers) @cached_property - def profiles(self) -> profiles.ProfilesResourceWithRawResponse: - from .resources.profiles import ProfilesResourceWithRawResponse + def credentials(self) -> credentials.CredentialsResourceWithRawResponse: + """Vault & Secrets""" + from .resources.credentials import CredentialsResourceWithRawResponse - return ProfilesResourceWithRawResponse(self._client.profiles) + return CredentialsResourceWithRawResponse(self._client.credentials) @cached_property def devices(self) -> devices.DevicesResourceWithRawResponse: @@ -576,44 +602,45 @@ def devices(self) -> devices.DevicesResourceWithRawResponse: return DevicesResourceWithRawResponse(self._client.devices) - @cached_property - def apps(self) -> apps.AppsResourceWithRawResponse: - from .resources.apps import AppsResourceWithRawResponse - - return AppsResourceWithRawResponse(self._client.apps) - - @cached_property - def credentials(self) -> credentials.CredentialsResourceWithRawResponse: - from .resources.credentials import CredentialsResourceWithRawResponse - - return CredentialsResourceWithRawResponse(self._client.credentials) - @cached_property def hooks(self) -> hooks.HooksResourceWithRawResponse: - """Webhooks API""" from .resources.hooks import HooksResourceWithRawResponse return HooksResourceWithRawResponse(self._client.hooks) @cached_property def models(self) -> models.ModelsResourceWithRawResponse: + """LLM Models""" from .resources.models import ModelsResourceWithRawResponse return ModelsResourceWithRawResponse(self._client.models) + @cached_property + def profiles(self) -> profiles.ProfilesResourceWithRawResponse: + from .resources.profiles import ProfilesResourceWithRawResponse -class AsyncMobilerunWithRawResponse: - _client: AsyncMobilerun + return ProfilesResourceWithRawResponse(self._client.profiles) - def __init__(self, client: AsyncMobilerun) -> None: - self._client = client + @cached_property + def proxies(self) -> proxies.ProxiesResourceWithRawResponse: + """Network Proxies""" + from .resources.proxies import ProxiesResourceWithRawResponse + + return ProxiesResourceWithRawResponse(self._client.proxies) @cached_property - def tasks(self) -> tasks.AsyncTasksResourceWithRawResponse: + def tasks(self) -> tasks.TasksResourceWithRawResponse: """Tasks API""" - from .resources.tasks import AsyncTasksResourceWithRawResponse + from .resources.tasks import TasksResourceWithRawResponse - return AsyncTasksResourceWithRawResponse(self._client.tasks) + return TasksResourceWithRawResponse(self._client.tasks) + + +class AsyncMobilerunWithRawResponse: + _client: AsyncMobilerun + + def __init__(self, client: AsyncMobilerun) -> None: + self._client = client @cached_property def agents(self) -> agents.AsyncAgentsResourceWithRawResponse: @@ -623,22 +650,25 @@ def agents(self) -> agents.AsyncAgentsResourceWithRawResponse: return AsyncAgentsResourceWithRawResponse(self._client.agents) @cached_property - def proxies(self) -> proxies.AsyncProxiesResourceWithRawResponse: - from .resources.proxies import AsyncProxiesResourceWithRawResponse + def apps(self) -> apps.AsyncAppsResourceWithRawResponse: + """App Management""" + from .resources.apps import AsyncAppsResourceWithRawResponse - return AsyncProxiesResourceWithRawResponse(self._client.proxies) + return AsyncAppsResourceWithRawResponse(self._client.apps) @cached_property def carriers(self) -> carriers.AsyncCarriersResourceWithRawResponse: + """Mobile Carriers""" from .resources.carriers import AsyncCarriersResourceWithRawResponse return AsyncCarriersResourceWithRawResponse(self._client.carriers) @cached_property - def profiles(self) -> profiles.AsyncProfilesResourceWithRawResponse: - from .resources.profiles import AsyncProfilesResourceWithRawResponse + def credentials(self) -> credentials.AsyncCredentialsResourceWithRawResponse: + """Vault & Secrets""" + from .resources.credentials import AsyncCredentialsResourceWithRawResponse - return AsyncProfilesResourceWithRawResponse(self._client.profiles) + return AsyncCredentialsResourceWithRawResponse(self._client.credentials) @cached_property def devices(self) -> devices.AsyncDevicesResourceWithRawResponse: @@ -646,44 +676,45 @@ def devices(self) -> devices.AsyncDevicesResourceWithRawResponse: return AsyncDevicesResourceWithRawResponse(self._client.devices) - @cached_property - def apps(self) -> apps.AsyncAppsResourceWithRawResponse: - from .resources.apps import AsyncAppsResourceWithRawResponse - - return AsyncAppsResourceWithRawResponse(self._client.apps) - - @cached_property - def credentials(self) -> credentials.AsyncCredentialsResourceWithRawResponse: - from .resources.credentials import AsyncCredentialsResourceWithRawResponse - - return AsyncCredentialsResourceWithRawResponse(self._client.credentials) - @cached_property def hooks(self) -> hooks.AsyncHooksResourceWithRawResponse: - """Webhooks API""" from .resources.hooks import AsyncHooksResourceWithRawResponse return AsyncHooksResourceWithRawResponse(self._client.hooks) @cached_property def models(self) -> models.AsyncModelsResourceWithRawResponse: + """LLM Models""" from .resources.models import AsyncModelsResourceWithRawResponse return AsyncModelsResourceWithRawResponse(self._client.models) + @cached_property + def profiles(self) -> profiles.AsyncProfilesResourceWithRawResponse: + from .resources.profiles import AsyncProfilesResourceWithRawResponse -class MobilerunWithStreamedResponse: - _client: Mobilerun + return AsyncProfilesResourceWithRawResponse(self._client.profiles) - def __init__(self, client: Mobilerun) -> None: - self._client = client + @cached_property + def proxies(self) -> proxies.AsyncProxiesResourceWithRawResponse: + """Network Proxies""" + from .resources.proxies import AsyncProxiesResourceWithRawResponse + + return AsyncProxiesResourceWithRawResponse(self._client.proxies) @cached_property - def tasks(self) -> tasks.TasksResourceWithStreamingResponse: + def tasks(self) -> tasks.AsyncTasksResourceWithRawResponse: """Tasks API""" - from .resources.tasks import TasksResourceWithStreamingResponse + from .resources.tasks import AsyncTasksResourceWithRawResponse - return TasksResourceWithStreamingResponse(self._client.tasks) + return AsyncTasksResourceWithRawResponse(self._client.tasks) + + +class MobilerunWithStreamedResponse: + _client: Mobilerun + + def __init__(self, client: Mobilerun) -> None: + self._client = client @cached_property def agents(self) -> agents.AgentsResourceWithStreamingResponse: @@ -693,22 +724,25 @@ def agents(self) -> agents.AgentsResourceWithStreamingResponse: return AgentsResourceWithStreamingResponse(self._client.agents) @cached_property - def proxies(self) -> proxies.ProxiesResourceWithStreamingResponse: - from .resources.proxies import ProxiesResourceWithStreamingResponse + def apps(self) -> apps.AppsResourceWithStreamingResponse: + """App Management""" + from .resources.apps import AppsResourceWithStreamingResponse - return ProxiesResourceWithStreamingResponse(self._client.proxies) + return AppsResourceWithStreamingResponse(self._client.apps) @cached_property def carriers(self) -> carriers.CarriersResourceWithStreamingResponse: + """Mobile Carriers""" from .resources.carriers import CarriersResourceWithStreamingResponse return CarriersResourceWithStreamingResponse(self._client.carriers) @cached_property - def profiles(self) -> profiles.ProfilesResourceWithStreamingResponse: - from .resources.profiles import ProfilesResourceWithStreamingResponse + def credentials(self) -> credentials.CredentialsResourceWithStreamingResponse: + """Vault & Secrets""" + from .resources.credentials import CredentialsResourceWithStreamingResponse - return ProfilesResourceWithStreamingResponse(self._client.profiles) + return CredentialsResourceWithStreamingResponse(self._client.credentials) @cached_property def devices(self) -> devices.DevicesResourceWithStreamingResponse: @@ -716,44 +750,45 @@ def devices(self) -> devices.DevicesResourceWithStreamingResponse: return DevicesResourceWithStreamingResponse(self._client.devices) - @cached_property - def apps(self) -> apps.AppsResourceWithStreamingResponse: - from .resources.apps import AppsResourceWithStreamingResponse - - return AppsResourceWithStreamingResponse(self._client.apps) - - @cached_property - def credentials(self) -> credentials.CredentialsResourceWithStreamingResponse: - from .resources.credentials import CredentialsResourceWithStreamingResponse - - return CredentialsResourceWithStreamingResponse(self._client.credentials) - @cached_property def hooks(self) -> hooks.HooksResourceWithStreamingResponse: - """Webhooks API""" from .resources.hooks import HooksResourceWithStreamingResponse return HooksResourceWithStreamingResponse(self._client.hooks) @cached_property def models(self) -> models.ModelsResourceWithStreamingResponse: + """LLM Models""" from .resources.models import ModelsResourceWithStreamingResponse return ModelsResourceWithStreamingResponse(self._client.models) + @cached_property + def profiles(self) -> profiles.ProfilesResourceWithStreamingResponse: + from .resources.profiles import ProfilesResourceWithStreamingResponse -class AsyncMobilerunWithStreamedResponse: - _client: AsyncMobilerun + return ProfilesResourceWithStreamingResponse(self._client.profiles) - def __init__(self, client: AsyncMobilerun) -> None: - self._client = client + @cached_property + def proxies(self) -> proxies.ProxiesResourceWithStreamingResponse: + """Network Proxies""" + from .resources.proxies import ProxiesResourceWithStreamingResponse + + return ProxiesResourceWithStreamingResponse(self._client.proxies) @cached_property - def tasks(self) -> tasks.AsyncTasksResourceWithStreamingResponse: + def tasks(self) -> tasks.TasksResourceWithStreamingResponse: """Tasks API""" - from .resources.tasks import AsyncTasksResourceWithStreamingResponse + from .resources.tasks import TasksResourceWithStreamingResponse - return AsyncTasksResourceWithStreamingResponse(self._client.tasks) + return TasksResourceWithStreamingResponse(self._client.tasks) + + +class AsyncMobilerunWithStreamedResponse: + _client: AsyncMobilerun + + def __init__(self, client: AsyncMobilerun) -> None: + self._client = client @cached_property def agents(self) -> agents.AsyncAgentsResourceWithStreamingResponse: @@ -763,22 +798,25 @@ def agents(self) -> agents.AsyncAgentsResourceWithStreamingResponse: return AsyncAgentsResourceWithStreamingResponse(self._client.agents) @cached_property - def proxies(self) -> proxies.AsyncProxiesResourceWithStreamingResponse: - from .resources.proxies import AsyncProxiesResourceWithStreamingResponse + def apps(self) -> apps.AsyncAppsResourceWithStreamingResponse: + """App Management""" + from .resources.apps import AsyncAppsResourceWithStreamingResponse - return AsyncProxiesResourceWithStreamingResponse(self._client.proxies) + return AsyncAppsResourceWithStreamingResponse(self._client.apps) @cached_property def carriers(self) -> carriers.AsyncCarriersResourceWithStreamingResponse: + """Mobile Carriers""" from .resources.carriers import AsyncCarriersResourceWithStreamingResponse return AsyncCarriersResourceWithStreamingResponse(self._client.carriers) @cached_property - def profiles(self) -> profiles.AsyncProfilesResourceWithStreamingResponse: - from .resources.profiles import AsyncProfilesResourceWithStreamingResponse + def credentials(self) -> credentials.AsyncCredentialsResourceWithStreamingResponse: + """Vault & Secrets""" + from .resources.credentials import AsyncCredentialsResourceWithStreamingResponse - return AsyncProfilesResourceWithStreamingResponse(self._client.profiles) + return AsyncCredentialsResourceWithStreamingResponse(self._client.credentials) @cached_property def devices(self) -> devices.AsyncDevicesResourceWithStreamingResponse: @@ -786,31 +824,39 @@ def devices(self) -> devices.AsyncDevicesResourceWithStreamingResponse: return AsyncDevicesResourceWithStreamingResponse(self._client.devices) - @cached_property - def apps(self) -> apps.AsyncAppsResourceWithStreamingResponse: - from .resources.apps import AsyncAppsResourceWithStreamingResponse - - return AsyncAppsResourceWithStreamingResponse(self._client.apps) - - @cached_property - def credentials(self) -> credentials.AsyncCredentialsResourceWithStreamingResponse: - from .resources.credentials import AsyncCredentialsResourceWithStreamingResponse - - return AsyncCredentialsResourceWithStreamingResponse(self._client.credentials) - @cached_property def hooks(self) -> hooks.AsyncHooksResourceWithStreamingResponse: - """Webhooks API""" from .resources.hooks import AsyncHooksResourceWithStreamingResponse return AsyncHooksResourceWithStreamingResponse(self._client.hooks) @cached_property def models(self) -> models.AsyncModelsResourceWithStreamingResponse: + """LLM Models""" from .resources.models import AsyncModelsResourceWithStreamingResponse return AsyncModelsResourceWithStreamingResponse(self._client.models) + @cached_property + def profiles(self) -> profiles.AsyncProfilesResourceWithStreamingResponse: + from .resources.profiles import AsyncProfilesResourceWithStreamingResponse + + return AsyncProfilesResourceWithStreamingResponse(self._client.profiles) + + @cached_property + def proxies(self) -> proxies.AsyncProxiesResourceWithStreamingResponse: + """Network Proxies""" + from .resources.proxies import AsyncProxiesResourceWithStreamingResponse + + return AsyncProxiesResourceWithStreamingResponse(self._client.proxies) + + @cached_property + def tasks(self) -> tasks.AsyncTasksResourceWithStreamingResponse: + """Tasks API""" + from .resources.tasks import AsyncTasksResourceWithStreamingResponse + + return AsyncTasksResourceWithStreamingResponse(self._client.tasks) + Client = Mobilerun diff --git a/src/mobilerun_sdk/_files.py b/src/mobilerun_sdk/_files.py index 0f0f150..22855ba 100644 --- a/src/mobilerun_sdk/_files.py +++ b/src/mobilerun_sdk/_files.py @@ -3,8 +3,8 @@ import io import os import pathlib -from typing import overload -from typing_extensions import TypeGuard +from typing import Sequence, cast, overload +from typing_extensions import TypeVar, TypeGuard import anyio @@ -17,7 +17,9 @@ HttpxFileContent, HttpxRequestFiles, ) -from ._utils import is_tuple_t, is_mapping_t, is_sequence_t +from ._utils import is_list, is_mapping, is_tuple_t, is_mapping_t, is_sequence_t + +_T = TypeVar("_T") def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]: @@ -97,7 +99,7 @@ async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles elif is_sequence_t(files): files = [(key, await _async_transform_file(file)) for key, file in files] else: - raise TypeError("Unexpected file type input {type(files)}, expected mapping or sequence") + raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence") return files @@ -121,3 +123,51 @@ async def async_read_file_content(file: FileContent) -> HttpxFileContent: return await anyio.Path(file).read_bytes() return file + + +def deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]]) -> _T: + """Copy only the containers along the given paths. + + Used to guard against mutation by extract_files without copying the entire structure. + Only dicts and lists that lie on a path are copied; everything else + is returned by reference. + + For example, given paths=[["foo", "files", "file"]] and the structure: + { + "foo": { + "bar": {"baz": {}}, + "files": {"file": } + } + } + The root dict, "foo", and "files" are copied (they lie on the path). + "bar" and "baz" are returned by reference (off the path). + """ + return _deepcopy_with_paths(item, paths, 0) + + +def _deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]], index: int) -> _T: + if not paths: + return item + if is_mapping(item): + key_to_paths: dict[str, list[Sequence[str]]] = {} + for path in paths: + if index < len(path): + key_to_paths.setdefault(path[index], []).append(path) + + # if no path continues through this mapping, it won't be mutated and copying it is redundant + if not key_to_paths: + return item + + result = dict(item) + for key, subpaths in key_to_paths.items(): + if key in result: + result[key] = _deepcopy_with_paths(result[key], subpaths, index + 1) + return cast(_T, result) + if is_list(item): + array_paths = [path for path in paths if index < len(path) and path[index] == ""] + + # if no path expects a list here, nothing will be mutated inside it - return by reference + if not array_paths: + return cast(_T, item) + return cast(_T, [_deepcopy_with_paths(entry, array_paths, index + 1) for entry in item]) + return item diff --git a/src/mobilerun_sdk/_models.py b/src/mobilerun_sdk/_models.py index 29070e0..8c5ab26 100644 --- a/src/mobilerun_sdk/_models.py +++ b/src/mobilerun_sdk/_models.py @@ -25,7 +25,9 @@ ClassVar, Protocol, Required, + Annotated, ParamSpec, + TypeAlias, TypedDict, TypeGuard, final, @@ -79,7 +81,15 @@ from ._constants import RAW_RESPONSE_HEADER if TYPE_CHECKING: + from pydantic import GetCoreSchemaHandler, ValidatorFunctionWrapHandler + from pydantic_core import CoreSchema, core_schema from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema +else: + try: + from pydantic_core import CoreSchema, core_schema + except ImportError: + CoreSchema = None + core_schema = None __all__ = ["BaseModel", "GenericModel"] @@ -396,6 +406,76 @@ def model_dump_json( ) +class _EagerIterable(list[_T], Generic[_T]): + """ + Accepts any Iterable[T] input (including generators), consumes it + eagerly, and validates all items upfront. + + Validation preserves the original container type where possible + (e.g. a set[T] stays a set[T]). Serialization (model_dump / JSON) + always emits a list — round-tripping through model_dump() will not + restore the original container type. + """ + + @classmethod + def __get_pydantic_core_schema__( + cls, + source_type: Any, + handler: GetCoreSchemaHandler, + ) -> CoreSchema: + (item_type,) = get_args(source_type) or (Any,) + item_schema: CoreSchema = handler.generate_schema(item_type) + list_of_items_schema: CoreSchema = core_schema.list_schema(item_schema) + + return core_schema.no_info_wrap_validator_function( + cls._validate, + list_of_items_schema, + serialization=core_schema.plain_serializer_function_ser_schema( + cls._serialize, + info_arg=False, + ), + ) + + @staticmethod + def _validate(v: Iterable[_T], handler: "ValidatorFunctionWrapHandler") -> Any: + original_type: type[Any] = type(v) + + # Normalize to list so list_schema can validate each item + if isinstance(v, list): + items: list[_T] = v + else: + try: + items = list(v) + except TypeError as e: + raise TypeError("Value is not iterable") from e + + # Validate items against the inner schema + validated: list[_T] = handler(items) + + # Reconstruct original container type + if original_type is list: + return validated + # str(list) produces the list's repr, not a string built from items, + # so skip reconstruction for str and its subclasses. + if issubclass(original_type, str): + return validated + try: + return original_type(validated) + except (TypeError, ValueError): + # If the type cannot be reconstructed, just return the validated list + return validated + + @staticmethod + def _serialize(v: Iterable[_T]) -> list[_T]: + """Always serialize as a list so Pydantic's JSON encoder is happy.""" + if isinstance(v, list): + return v + return list(v) + + +EagerIterable: TypeAlias = Annotated[Iterable[_T], _EagerIterable] + + def _construct_field(value: object, field: FieldInfo, key: str) -> object: if value is None: return field_get_default(field) diff --git a/src/mobilerun_sdk/_qs.py b/src/mobilerun_sdk/_qs.py index de8c99b..4127c19 100644 --- a/src/mobilerun_sdk/_qs.py +++ b/src/mobilerun_sdk/_qs.py @@ -2,17 +2,13 @@ from typing import Any, List, Tuple, Union, Mapping, TypeVar from urllib.parse import parse_qs, urlencode -from typing_extensions import Literal, get_args +from typing_extensions import get_args -from ._types import NotGiven, not_given +from ._types import NotGiven, ArrayFormat, NestedFormat, not_given from ._utils import flatten _T = TypeVar("_T") - -ArrayFormat = Literal["comma", "repeat", "indices", "brackets"] -NestedFormat = Literal["dots", "brackets"] - PrimitiveData = Union[str, int, float, bool, None] # this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"] # https://github.com/microsoft/pyright/issues/3555 diff --git a/src/mobilerun_sdk/_types.py b/src/mobilerun_sdk/_types.py index 0f64915..c41c84a 100644 --- a/src/mobilerun_sdk/_types.py +++ b/src/mobilerun_sdk/_types.py @@ -47,6 +47,9 @@ ModelT = TypeVar("ModelT", bound=pydantic.BaseModel) _T = TypeVar("_T") +ArrayFormat = Literal["comma", "repeat", "indices", "brackets"] +NestedFormat = Literal["dots", "brackets"] + # Approximates httpx internal ProxiesTypes and RequestFiles types # while adding support for `PathLike` instances diff --git a/src/mobilerun_sdk/_utils/__init__.py b/src/mobilerun_sdk/_utils/__init__.py index 10cb66d..1c090e5 100644 --- a/src/mobilerun_sdk/_utils/__init__.py +++ b/src/mobilerun_sdk/_utils/__init__.py @@ -24,7 +24,6 @@ coerce_integer as coerce_integer, file_from_path as file_from_path, strip_not_given as strip_not_given, - deepcopy_minimal as deepcopy_minimal, get_async_library as get_async_library, maybe_coerce_float as maybe_coerce_float, get_required_header as get_required_header, diff --git a/src/mobilerun_sdk/_utils/_utils.py b/src/mobilerun_sdk/_utils/_utils.py index 63b8cd6..199cd23 100644 --- a/src/mobilerun_sdk/_utils/_utils.py +++ b/src/mobilerun_sdk/_utils/_utils.py @@ -17,11 +17,11 @@ ) from pathlib import Path from datetime import date, datetime -from typing_extensions import TypeGuard +from typing_extensions import TypeGuard, get_args import sniffio -from .._types import Omit, NotGiven, FileTypes, HeadersLike +from .._types import Omit, NotGiven, FileTypes, ArrayFormat, HeadersLike _T = TypeVar("_T") _TupleT = TypeVar("_TupleT", bound=Tuple[object, ...]) @@ -40,25 +40,45 @@ def extract_files( query: Mapping[str, object], *, paths: Sequence[Sequence[str]], + array_format: ArrayFormat = "brackets", ) -> list[tuple[str, FileTypes]]: """Recursively extract files from the given dictionary based on specified paths. A path may look like this ['foo', 'files', '', 'data']. + ``array_format`` controls how ```` segments contribute to the emitted + field name. Supported values: ``"brackets"`` (``foo[]``), ``"repeat"`` and + ``"comma"`` (``foo``), ``"indices"`` (``foo[0]``, ``foo[1]``). + Note: this mutates the given dictionary. """ files: list[tuple[str, FileTypes]] = [] for path in paths: - files.extend(_extract_items(query, path, index=0, flattened_key=None)) + files.extend(_extract_items(query, path, index=0, flattened_key=None, array_format=array_format)) return files +def _array_suffix(array_format: ArrayFormat, array_index: int) -> str: + if array_format == "brackets": + return "[]" + if array_format == "indices": + return f"[{array_index}]" + if array_format == "repeat" or array_format == "comma": + # Both repeat the bare field name for each file part; there is no + # meaningful way to comma-join binary parts. + return "" + raise NotImplementedError( + f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}" + ) + + def _extract_items( obj: object, path: Sequence[str], *, index: int, flattened_key: str | None, + array_format: ArrayFormat, ) -> list[tuple[str, FileTypes]]: try: key = path[index] @@ -75,9 +95,11 @@ def _extract_items( if is_list(obj): files: list[tuple[str, FileTypes]] = [] - for entry in obj: - assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "") - files.append((flattened_key + "[]", cast(FileTypes, entry))) + for array_index, entry in enumerate(obj): + suffix = _array_suffix(array_format, array_index) + emitted_key = (flattened_key + suffix) if flattened_key else suffix + assert_is_file_content(entry, key=emitted_key) + files.append((emitted_key, cast(FileTypes, entry))) return files assert_is_file_content(obj, key=flattened_key) @@ -106,6 +128,7 @@ def _extract_items( path, index=index, flattened_key=flattened_key, + array_format=array_format, ) elif is_list(obj): if key != "": @@ -117,9 +140,12 @@ def _extract_items( item, path, index=index, - flattened_key=flattened_key + "[]" if flattened_key is not None else "[]", + flattened_key=( + (flattened_key if flattened_key is not None else "") + _array_suffix(array_format, array_index) + ), + array_format=array_format, ) - for item in obj + for array_index, item in enumerate(obj) ] ) @@ -177,21 +203,6 @@ def is_iterable(obj: object) -> TypeGuard[Iterable[object]]: return isinstance(obj, Iterable) -def deepcopy_minimal(item: _T) -> _T: - """Minimal reimplementation of copy.deepcopy() that will only copy certain object types: - - - mappings, e.g. `dict` - - list - - This is done for performance reasons. - """ - if is_mapping(item): - return cast(_T, {k: deepcopy_minimal(v) for k, v in item.items()}) - if is_list(item): - return cast(_T, [deepcopy_minimal(entry) for entry in item]) - return item - - # copied from https://github.com/Rapptz/RoboDanny def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str: size = len(seq) diff --git a/src/mobilerun_sdk/_version.py b/src/mobilerun_sdk/_version.py index f3f6069..a3beadb 100644 --- a/src/mobilerun_sdk/_version.py +++ b/src/mobilerun_sdk/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "mobilerun_sdk" -__version__ = "3.1.0" # x-release-please-version +__version__ = "3.2.0" # x-release-please-version diff --git a/src/mobilerun_sdk/resources/__init__.py b/src/mobilerun_sdk/resources/__init__.py index 2ded3d8..21f473c 100644 --- a/src/mobilerun_sdk/resources/__init__.py +++ b/src/mobilerun_sdk/resources/__init__.py @@ -82,54 +82,36 @@ ) __all__ = [ - "TasksResource", - "AsyncTasksResource", - "TasksResourceWithRawResponse", - "AsyncTasksResourceWithRawResponse", - "TasksResourceWithStreamingResponse", - "AsyncTasksResourceWithStreamingResponse", "AgentsResource", "AsyncAgentsResource", "AgentsResourceWithRawResponse", "AsyncAgentsResourceWithRawResponse", "AgentsResourceWithStreamingResponse", "AsyncAgentsResourceWithStreamingResponse", - "ProxiesResource", - "AsyncProxiesResource", - "ProxiesResourceWithRawResponse", - "AsyncProxiesResourceWithRawResponse", - "ProxiesResourceWithStreamingResponse", - "AsyncProxiesResourceWithStreamingResponse", - "CarriersResource", - "AsyncCarriersResource", - "CarriersResourceWithRawResponse", - "AsyncCarriersResourceWithRawResponse", - "CarriersResourceWithStreamingResponse", - "AsyncCarriersResourceWithStreamingResponse", - "ProfilesResource", - "AsyncProfilesResource", - "ProfilesResourceWithRawResponse", - "AsyncProfilesResourceWithRawResponse", - "ProfilesResourceWithStreamingResponse", - "AsyncProfilesResourceWithStreamingResponse", - "DevicesResource", - "AsyncDevicesResource", - "DevicesResourceWithRawResponse", - "AsyncDevicesResourceWithRawResponse", - "DevicesResourceWithStreamingResponse", - "AsyncDevicesResourceWithStreamingResponse", "AppsResource", "AsyncAppsResource", "AppsResourceWithRawResponse", "AsyncAppsResourceWithRawResponse", "AppsResourceWithStreamingResponse", "AsyncAppsResourceWithStreamingResponse", + "CarriersResource", + "AsyncCarriersResource", + "CarriersResourceWithRawResponse", + "AsyncCarriersResourceWithRawResponse", + "CarriersResourceWithStreamingResponse", + "AsyncCarriersResourceWithStreamingResponse", "CredentialsResource", "AsyncCredentialsResource", "CredentialsResourceWithRawResponse", "AsyncCredentialsResourceWithRawResponse", "CredentialsResourceWithStreamingResponse", "AsyncCredentialsResourceWithStreamingResponse", + "DevicesResource", + "AsyncDevicesResource", + "DevicesResourceWithRawResponse", + "AsyncDevicesResourceWithRawResponse", + "DevicesResourceWithStreamingResponse", + "AsyncDevicesResourceWithStreamingResponse", "HooksResource", "AsyncHooksResource", "HooksResourceWithRawResponse", @@ -142,4 +124,22 @@ "AsyncModelsResourceWithRawResponse", "ModelsResourceWithStreamingResponse", "AsyncModelsResourceWithStreamingResponse", + "ProfilesResource", + "AsyncProfilesResource", + "ProfilesResourceWithRawResponse", + "AsyncProfilesResourceWithRawResponse", + "ProfilesResourceWithStreamingResponse", + "AsyncProfilesResourceWithStreamingResponse", + "ProxiesResource", + "AsyncProxiesResource", + "ProxiesResourceWithRawResponse", + "AsyncProxiesResourceWithRawResponse", + "ProxiesResourceWithStreamingResponse", + "AsyncProxiesResourceWithStreamingResponse", + "TasksResource", + "AsyncTasksResource", + "TasksResourceWithRawResponse", + "AsyncTasksResourceWithRawResponse", + "TasksResourceWithStreamingResponse", + "AsyncTasksResourceWithStreamingResponse", ] diff --git a/src/mobilerun_sdk/resources/apps.py b/src/mobilerun_sdk/resources/apps.py index 1b80d91..2587290 100644 --- a/src/mobilerun_sdk/resources/apps.py +++ b/src/mobilerun_sdk/resources/apps.py @@ -2,13 +2,14 @@ from __future__ import annotations +from typing import Iterable from typing_extensions import Literal import httpx -from ..types import app_list_params +from ..types import app_list_params, app_create_signed_upload_url_params from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import maybe_transform, async_maybe_transform +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -19,11 +20,18 @@ ) from .._base_client import make_request_options from ..types.app_list_response import AppListResponse +from ..types.app_delete_response import AppDeleteResponse +from ..types.app_retrieve_response import AppRetrieveResponse +from ..types.app_mark_failed_response import AppMarkFailedResponse +from ..types.app_confirm_upload_response import AppConfirmUploadResponse +from ..types.app_create_signed_upload_url_response import AppCreateSignedUploadURLResponse __all__ = ["AppsResource", "AsyncAppsResource"] class AppsResource(SyncAPIResource): + """App Management""" + @cached_property def with_raw_response(self) -> AppsResourceWithRawResponse: """ @@ -43,15 +51,49 @@ def with_streaming_response(self) -> AppsResourceWithStreamingResponse: """ return AppsResourceWithStreamingResponse(self) + def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AppRetrieveResponse: + """ + Retrieves an app by its ID + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + path_template("/apps/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AppRetrieveResponse, + ) + def list( self, *, order: Literal["asc", "desc"] | Omit = omit, page: int | Omit = omit, page_size: int | Omit = omit, + platform: Literal["all", "android", "ios"] | Omit = omit, query: str | Omit = omit, sort_by: Literal["createdAt", "name"] | Omit = omit, - source: Literal["all", "uploaded", "store", "queued"] | Omit = omit, + status: Literal["all", "queued", "available", "failed"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -83,9 +125,10 @@ def list( "order": order, "page": page, "page_size": page_size, + "platform": platform, "query": query, "sort_by": sort_by, - "source": source, + "status": status, }, app_list_params.AppListParams, ), @@ -93,8 +136,173 @@ def list( cast_to=AppListResponse, ) + def delete( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AppDeleteResponse: + """Deletes an uploaded app by ID. + + Removes files from R2 storage and the database + entry. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._delete( + path_template("/apps/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AppDeleteResponse, + ) + + def confirm_upload( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AppConfirmUploadResponse: + """ + Verifies the APK file exists in R2 and sets the app status to available. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._post( + path_template("/apps/{id}/confirm-upload", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AppConfirmUploadResponse, + ) + + def create_signed_upload_url( + self, + *, + bundle_id: str, + display_name: str, + files: Iterable[app_create_signed_upload_url_params.File], + size_bytes: float, + version_code: float, + version_name: str, + country: str | Omit = omit, + description: str | Omit = omit, + developer_name: str | Omit = omit, + icon_url: str | Omit = omit, + platform: Literal["android", "ios"] | Omit = omit, + target_sdk: float | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AppCreateSignedUploadURLResponse: + """ + Creates or updates an app and returns pre-signed Cloudflare R2 upload URLs for + each file + + Args: + country: Country code for Search Results + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/apps/create-signed-upload-url", + body=maybe_transform( + { + "bundle_id": bundle_id, + "display_name": display_name, + "files": files, + "size_bytes": size_bytes, + "version_code": version_code, + "version_name": version_name, + "country": country, + "description": description, + "developer_name": developer_name, + "icon_url": icon_url, + "platform": platform, + "target_sdk": target_sdk, + }, + app_create_signed_upload_url_params.AppCreateSignedUploadURLParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AppCreateSignedUploadURLResponse, + ) + + def mark_failed( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AppMarkFailedResponse: + """ + Sets the app status to failed. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._post( + path_template("/apps/{id}/mark-failed", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AppMarkFailedResponse, + ) + class AsyncAppsResource(AsyncAPIResource): + """App Management""" + @cached_property def with_raw_response(self) -> AsyncAppsResourceWithRawResponse: """ @@ -114,15 +322,49 @@ def with_streaming_response(self) -> AsyncAppsResourceWithStreamingResponse: """ return AsyncAppsResourceWithStreamingResponse(self) + async def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AppRetrieveResponse: + """ + Retrieves an app by its ID + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + path_template("/apps/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AppRetrieveResponse, + ) + async def list( self, *, order: Literal["asc", "desc"] | Omit = omit, page: int | Omit = omit, page_size: int | Omit = omit, + platform: Literal["all", "android", "ios"] | Omit = omit, query: str | Omit = omit, sort_by: Literal["createdAt", "name"] | Omit = omit, - source: Literal["all", "uploaded", "store", "queued"] | Omit = omit, + status: Literal["all", "queued", "available", "failed"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -154,9 +396,10 @@ async def list( "order": order, "page": page, "page_size": page_size, + "platform": platform, "query": query, "sort_by": sort_by, - "source": source, + "status": status, }, app_list_params.AppListParams, ), @@ -164,38 +407,261 @@ async def list( cast_to=AppListResponse, ) + async def delete( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AppDeleteResponse: + """Deletes an uploaded app by ID. + + Removes files from R2 storage and the database + entry. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._delete( + path_template("/apps/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AppDeleteResponse, + ) + + async def confirm_upload( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AppConfirmUploadResponse: + """ + Verifies the APK file exists in R2 and sets the app status to available. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._post( + path_template("/apps/{id}/confirm-upload", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AppConfirmUploadResponse, + ) + + async def create_signed_upload_url( + self, + *, + bundle_id: str, + display_name: str, + files: Iterable[app_create_signed_upload_url_params.File], + size_bytes: float, + version_code: float, + version_name: str, + country: str | Omit = omit, + description: str | Omit = omit, + developer_name: str | Omit = omit, + icon_url: str | Omit = omit, + platform: Literal["android", "ios"] | Omit = omit, + target_sdk: float | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AppCreateSignedUploadURLResponse: + """ + Creates or updates an app and returns pre-signed Cloudflare R2 upload URLs for + each file + + Args: + country: Country code for Search Results + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/apps/create-signed-upload-url", + body=await async_maybe_transform( + { + "bundle_id": bundle_id, + "display_name": display_name, + "files": files, + "size_bytes": size_bytes, + "version_code": version_code, + "version_name": version_name, + "country": country, + "description": description, + "developer_name": developer_name, + "icon_url": icon_url, + "platform": platform, + "target_sdk": target_sdk, + }, + app_create_signed_upload_url_params.AppCreateSignedUploadURLParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AppCreateSignedUploadURLResponse, + ) + + async def mark_failed( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AppMarkFailedResponse: + """ + Sets the app status to failed. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._post( + path_template("/apps/{id}/mark-failed", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AppMarkFailedResponse, + ) + class AppsResourceWithRawResponse: def __init__(self, apps: AppsResource) -> None: self._apps = apps + self.retrieve = to_raw_response_wrapper( + apps.retrieve, + ) self.list = to_raw_response_wrapper( apps.list, ) + self.delete = to_raw_response_wrapper( + apps.delete, + ) + self.confirm_upload = to_raw_response_wrapper( + apps.confirm_upload, + ) + self.create_signed_upload_url = to_raw_response_wrapper( + apps.create_signed_upload_url, + ) + self.mark_failed = to_raw_response_wrapper( + apps.mark_failed, + ) class AsyncAppsResourceWithRawResponse: def __init__(self, apps: AsyncAppsResource) -> None: self._apps = apps + self.retrieve = async_to_raw_response_wrapper( + apps.retrieve, + ) self.list = async_to_raw_response_wrapper( apps.list, ) + self.delete = async_to_raw_response_wrapper( + apps.delete, + ) + self.confirm_upload = async_to_raw_response_wrapper( + apps.confirm_upload, + ) + self.create_signed_upload_url = async_to_raw_response_wrapper( + apps.create_signed_upload_url, + ) + self.mark_failed = async_to_raw_response_wrapper( + apps.mark_failed, + ) class AppsResourceWithStreamingResponse: def __init__(self, apps: AppsResource) -> None: self._apps = apps + self.retrieve = to_streamed_response_wrapper( + apps.retrieve, + ) self.list = to_streamed_response_wrapper( apps.list, ) + self.delete = to_streamed_response_wrapper( + apps.delete, + ) + self.confirm_upload = to_streamed_response_wrapper( + apps.confirm_upload, + ) + self.create_signed_upload_url = to_streamed_response_wrapper( + apps.create_signed_upload_url, + ) + self.mark_failed = to_streamed_response_wrapper( + apps.mark_failed, + ) class AsyncAppsResourceWithStreamingResponse: def __init__(self, apps: AsyncAppsResource) -> None: self._apps = apps + self.retrieve = async_to_streamed_response_wrapper( + apps.retrieve, + ) self.list = async_to_streamed_response_wrapper( apps.list, ) + self.delete = async_to_streamed_response_wrapper( + apps.delete, + ) + self.confirm_upload = async_to_streamed_response_wrapper( + apps.confirm_upload, + ) + self.create_signed_upload_url = async_to_streamed_response_wrapper( + apps.create_signed_upload_url, + ) + self.mark_failed = async_to_streamed_response_wrapper( + apps.mark_failed, + ) diff --git a/src/mobilerun_sdk/resources/carriers.py b/src/mobilerun_sdk/resources/carriers.py index 642493e..d627159 100644 --- a/src/mobilerun_sdk/resources/carriers.py +++ b/src/mobilerun_sdk/resources/carriers.py @@ -18,14 +18,19 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.carrier import Carrier from ..types.carrier_list_response import CarrierListResponse +from ..types.carrier_create_response import CarrierCreateResponse from ..types.carrier_delete_response import CarrierDeleteResponse +from ..types.carrier_lookup_response import CarrierLookupResponse +from ..types.carrier_update_response import CarrierUpdateResponse +from ..types.carrier_retrieve_response import CarrierRetrieveResponse __all__ = ["CarriersResource", "AsyncCarriersResource"] class CarriersResource(SyncAPIResource): + """Mobile Carriers""" + @cached_property def with_raw_response(self) -> CarriersResourceWithRawResponse: """ @@ -70,7 +75,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Carrier: + ) -> CarrierCreateResponse: """ Create a new carrier @@ -141,7 +146,7 @@ def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Carrier, + cast_to=CarrierCreateResponse, ) def retrieve( @@ -154,7 +159,7 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Carrier: + ) -> CarrierRetrieveResponse: """ Get carrier by ID @@ -174,7 +179,7 @@ def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Carrier, + cast_to=CarrierRetrieveResponse, ) def update( @@ -201,7 +206,7 @@ def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Carrier: + ) -> CarrierUpdateResponse: """ Update a carrier @@ -268,7 +273,7 @@ def update( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Carrier, + cast_to=CarrierUpdateResponse, ) def list( @@ -377,7 +382,7 @@ def lookup( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Carrier: + ) -> CarrierLookupResponse: """ Get carrier by MCC and MNC @@ -409,11 +414,13 @@ def lookup( carrier_lookup_params.CarrierLookupParams, ), ), - cast_to=Carrier, + cast_to=CarrierLookupResponse, ) class AsyncCarriersResource(AsyncAPIResource): + """Mobile Carriers""" + @cached_property def with_raw_response(self) -> AsyncCarriersResourceWithRawResponse: """ @@ -458,7 +465,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Carrier: + ) -> CarrierCreateResponse: """ Create a new carrier @@ -529,7 +536,7 @@ async def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Carrier, + cast_to=CarrierCreateResponse, ) async def retrieve( @@ -542,7 +549,7 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Carrier: + ) -> CarrierRetrieveResponse: """ Get carrier by ID @@ -562,7 +569,7 @@ async def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Carrier, + cast_to=CarrierRetrieveResponse, ) async def update( @@ -589,7 +596,7 @@ async def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Carrier: + ) -> CarrierUpdateResponse: """ Update a carrier @@ -656,7 +663,7 @@ async def update( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Carrier, + cast_to=CarrierUpdateResponse, ) async def list( @@ -765,7 +772,7 @@ async def lookup( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Carrier: + ) -> CarrierLookupResponse: """ Get carrier by MCC and MNC @@ -797,7 +804,7 @@ async def lookup( carrier_lookup_params.CarrierLookupParams, ), ), - cast_to=Carrier, + cast_to=CarrierLookupResponse, ) diff --git a/src/mobilerun_sdk/resources/credentials/credentials.py b/src/mobilerun_sdk/resources/credentials/credentials.py index 43d30f4..9276d9f 100644 --- a/src/mobilerun_sdk/resources/credentials/credentials.py +++ b/src/mobilerun_sdk/resources/credentials/credentials.py @@ -30,8 +30,11 @@ class CredentialsResource(SyncAPIResource): + """Vault & Secrets""" + @cached_property def packages(self) -> PackagesResource: + """Vault & Secrets""" return PackagesResource(self._client) @cached_property @@ -97,8 +100,11 @@ def list( class AsyncCredentialsResource(AsyncAPIResource): + """Vault & Secrets""" + @cached_property def packages(self) -> AsyncPackagesResource: + """Vault & Secrets""" return AsyncPackagesResource(self._client) @cached_property @@ -173,6 +179,7 @@ def __init__(self, credentials: CredentialsResource) -> None: @cached_property def packages(self) -> PackagesResourceWithRawResponse: + """Vault & Secrets""" return PackagesResourceWithRawResponse(self._credentials.packages) @@ -186,6 +193,7 @@ def __init__(self, credentials: AsyncCredentialsResource) -> None: @cached_property def packages(self) -> AsyncPackagesResourceWithRawResponse: + """Vault & Secrets""" return AsyncPackagesResourceWithRawResponse(self._credentials.packages) @@ -199,6 +207,7 @@ def __init__(self, credentials: CredentialsResource) -> None: @cached_property def packages(self) -> PackagesResourceWithStreamingResponse: + """Vault & Secrets""" return PackagesResourceWithStreamingResponse(self._credentials.packages) @@ -212,4 +221,5 @@ def __init__(self, credentials: AsyncCredentialsResource) -> None: @cached_property def packages(self) -> AsyncPackagesResourceWithStreamingResponse: + """Vault & Secrets""" return AsyncPackagesResourceWithStreamingResponse(self._credentials.packages) diff --git a/src/mobilerun_sdk/resources/credentials/packages/credentials/credentials.py b/src/mobilerun_sdk/resources/credentials/packages/credentials/credentials.py index 1802563..df12c15 100644 --- a/src/mobilerun_sdk/resources/credentials/packages/credentials/credentials.py +++ b/src/mobilerun_sdk/resources/credentials/packages/credentials/credentials.py @@ -34,8 +34,11 @@ class CredentialsResource(SyncAPIResource): + """Vault & Secrets""" + @cached_property def fields(self) -> FieldsResource: + """Vault & Secrets""" return FieldsResource(self._client) @cached_property @@ -181,8 +184,11 @@ def delete( class AsyncCredentialsResource(AsyncAPIResource): + """Vault & Secrets""" + @cached_property def fields(self) -> AsyncFieldsResource: + """Vault & Secrets""" return AsyncFieldsResource(self._client) @cached_property @@ -343,6 +349,7 @@ def __init__(self, credentials: CredentialsResource) -> None: @cached_property def fields(self) -> FieldsResourceWithRawResponse: + """Vault & Secrets""" return FieldsResourceWithRawResponse(self._credentials.fields) @@ -362,6 +369,7 @@ def __init__(self, credentials: AsyncCredentialsResource) -> None: @cached_property def fields(self) -> AsyncFieldsResourceWithRawResponse: + """Vault & Secrets""" return AsyncFieldsResourceWithRawResponse(self._credentials.fields) @@ -381,6 +389,7 @@ def __init__(self, credentials: CredentialsResource) -> None: @cached_property def fields(self) -> FieldsResourceWithStreamingResponse: + """Vault & Secrets""" return FieldsResourceWithStreamingResponse(self._credentials.fields) @@ -400,4 +409,5 @@ def __init__(self, credentials: AsyncCredentialsResource) -> None: @cached_property def fields(self) -> AsyncFieldsResourceWithStreamingResponse: + """Vault & Secrets""" return AsyncFieldsResourceWithStreamingResponse(self._credentials.fields) diff --git a/src/mobilerun_sdk/resources/credentials/packages/credentials/fields.py b/src/mobilerun_sdk/resources/credentials/packages/credentials/fields.py index 77940d6..38ebfd9 100644 --- a/src/mobilerun_sdk/resources/credentials/packages/credentials/fields.py +++ b/src/mobilerun_sdk/resources/credentials/packages/credentials/fields.py @@ -26,6 +26,8 @@ class FieldsResource(SyncAPIResource): + """Vault & Secrets""" + @cached_property def with_raw_response(self) -> FieldsResourceWithRawResponse: """ @@ -192,6 +194,8 @@ def delete( class AsyncFieldsResource(AsyncAPIResource): + """Vault & Secrets""" + @cached_property def with_raw_response(self) -> AsyncFieldsResourceWithRawResponse: """ diff --git a/src/mobilerun_sdk/resources/credentials/packages/packages.py b/src/mobilerun_sdk/resources/credentials/packages/packages.py index f871c41..135ccdd 100644 --- a/src/mobilerun_sdk/resources/credentials/packages/packages.py +++ b/src/mobilerun_sdk/resources/credentials/packages/packages.py @@ -31,8 +31,11 @@ class PackagesResource(SyncAPIResource): + """Vault & Secrets""" + @cached_property def credentials(self) -> CredentialsResource: + """Vault & Secrets""" return CredentialsResource(self._client) @cached_property @@ -121,8 +124,11 @@ def list( class AsyncPackagesResource(AsyncAPIResource): + """Vault & Secrets""" + @cached_property def credentials(self) -> AsyncCredentialsResource: + """Vault & Secrets""" return AsyncCredentialsResource(self._client) @cached_property @@ -223,6 +229,7 @@ def __init__(self, packages: PackagesResource) -> None: @cached_property def credentials(self) -> CredentialsResourceWithRawResponse: + """Vault & Secrets""" return CredentialsResourceWithRawResponse(self._packages.credentials) @@ -239,6 +246,7 @@ def __init__(self, packages: AsyncPackagesResource) -> None: @cached_property def credentials(self) -> AsyncCredentialsResourceWithRawResponse: + """Vault & Secrets""" return AsyncCredentialsResourceWithRawResponse(self._packages.credentials) @@ -255,6 +263,7 @@ def __init__(self, packages: PackagesResource) -> None: @cached_property def credentials(self) -> CredentialsResourceWithStreamingResponse: + """Vault & Secrets""" return CredentialsResourceWithStreamingResponse(self._packages.credentials) @@ -271,4 +280,5 @@ def __init__(self, packages: AsyncPackagesResource) -> None: @cached_property def credentials(self) -> AsyncCredentialsResourceWithStreamingResponse: + """Vault & Secrets""" return AsyncCredentialsResourceWithStreamingResponse(self._packages.credentials) diff --git a/src/mobilerun_sdk/resources/devices/__init__.py b/src/mobilerun_sdk/resources/devices/__init__.py index e361ca9..77ef45e 100644 --- a/src/mobilerun_sdk/resources/devices/__init__.py +++ b/src/mobilerun_sdk/resources/devices/__init__.py @@ -16,14 +16,6 @@ EsimResourceWithStreamingResponse, AsyncEsimResourceWithStreamingResponse, ) -from .time import ( - TimeResource, - AsyncTimeResource, - TimeResourceWithRawResponse, - AsyncTimeResourceWithRawResponse, - TimeResourceWithStreamingResponse, - AsyncTimeResourceWithStreamingResponse, -) from .files import ( FilesResource, AsyncFilesResource, @@ -88,6 +80,14 @@ KeyboardResourceWithStreamingResponse, AsyncKeyboardResourceWithStreamingResponse, ) +from .language import ( + LanguageResource, + AsyncLanguageResource, + LanguageResourceWithRawResponse, + AsyncLanguageResourceWithRawResponse, + LanguageResourceWithStreamingResponse, + AsyncLanguageResourceWithStreamingResponse, +) from .location import ( LocationResource, AsyncLocationResource, @@ -104,80 +104,94 @@ PackagesResourceWithStreamingResponse, AsyncPackagesResourceWithStreamingResponse, ) +from .timezone import ( + TimezoneResource, + AsyncTimezoneResource, + TimezoneResourceWithRawResponse, + AsyncTimezoneResourceWithRawResponse, + TimezoneResourceWithStreamingResponse, + AsyncTimezoneResourceWithStreamingResponse, +) __all__ = [ - "TimeResource", - "AsyncTimeResource", - "TimeResourceWithRawResponse", - "AsyncTimeResourceWithRawResponse", - "TimeResourceWithStreamingResponse", - "AsyncTimeResourceWithStreamingResponse", - "ProfileResource", - "AsyncProfileResource", - "ProfileResourceWithRawResponse", - "AsyncProfileResourceWithRawResponse", - "ProfileResourceWithStreamingResponse", - "AsyncProfileResourceWithStreamingResponse", - "FilesResource", - "AsyncFilesResource", - "FilesResourceWithRawResponse", - "AsyncFilesResourceWithRawResponse", - "FilesResourceWithStreamingResponse", - "AsyncFilesResourceWithStreamingResponse", - "ProxyResource", - "AsyncProxyResource", - "ProxyResourceWithRawResponse", - "AsyncProxyResourceWithRawResponse", - "ProxyResourceWithStreamingResponse", - "AsyncProxyResourceWithStreamingResponse", - "LocationResource", - "AsyncLocationResource", - "LocationResourceWithRawResponse", - "AsyncLocationResourceWithRawResponse", - "LocationResourceWithStreamingResponse", - "AsyncLocationResourceWithStreamingResponse", "ActionsResource", "AsyncActionsResource", "ActionsResourceWithRawResponse", "AsyncActionsResourceWithRawResponse", "ActionsResourceWithStreamingResponse", "AsyncActionsResourceWithStreamingResponse", - "StateResource", - "AsyncStateResource", - "StateResourceWithRawResponse", - "AsyncStateResourceWithRawResponse", - "StateResourceWithStreamingResponse", - "AsyncStateResourceWithStreamingResponse", "AppsResource", "AsyncAppsResource", "AppsResourceWithRawResponse", "AsyncAppsResourceWithRawResponse", "AppsResourceWithStreamingResponse", "AsyncAppsResourceWithStreamingResponse", - "PackagesResource", - "AsyncPackagesResource", - "PackagesResourceWithRawResponse", - "AsyncPackagesResourceWithRawResponse", - "PackagesResourceWithStreamingResponse", - "AsyncPackagesResourceWithStreamingResponse", + "EsimResource", + "AsyncEsimResource", + "EsimResourceWithRawResponse", + "AsyncEsimResourceWithRawResponse", + "EsimResourceWithStreamingResponse", + "AsyncEsimResourceWithStreamingResponse", + "FilesResource", + "AsyncFilesResource", + "FilesResourceWithRawResponse", + "AsyncFilesResourceWithRawResponse", + "FilesResourceWithStreamingResponse", + "AsyncFilesResourceWithStreamingResponse", "KeyboardResource", "AsyncKeyboardResource", "KeyboardResourceWithRawResponse", "AsyncKeyboardResourceWithRawResponse", "KeyboardResourceWithStreamingResponse", "AsyncKeyboardResourceWithStreamingResponse", + "LocationResource", + "AsyncLocationResource", + "LocationResourceWithRawResponse", + "AsyncLocationResourceWithRawResponse", + "LocationResourceWithStreamingResponse", + "AsyncLocationResourceWithStreamingResponse", + "PackagesResource", + "AsyncPackagesResource", + "PackagesResourceWithRawResponse", + "AsyncPackagesResourceWithRawResponse", + "PackagesResourceWithStreamingResponse", + "AsyncPackagesResourceWithStreamingResponse", + "ProfileResource", + "AsyncProfileResource", + "ProfileResourceWithRawResponse", + "AsyncProfileResourceWithRawResponse", + "ProfileResourceWithStreamingResponse", + "AsyncProfileResourceWithStreamingResponse", + "ProxyResource", + "AsyncProxyResource", + "ProxyResourceWithRawResponse", + "AsyncProxyResourceWithRawResponse", + "ProxyResourceWithStreamingResponse", + "AsyncProxyResourceWithStreamingResponse", + "StateResource", + "AsyncStateResource", + "StateResourceWithRawResponse", + "AsyncStateResourceWithRawResponse", + "StateResourceWithStreamingResponse", + "AsyncStateResourceWithStreamingResponse", "TasksResource", "AsyncTasksResource", "TasksResourceWithRawResponse", "AsyncTasksResourceWithRawResponse", "TasksResourceWithStreamingResponse", "AsyncTasksResourceWithStreamingResponse", - "EsimResource", - "AsyncEsimResource", - "EsimResourceWithRawResponse", - "AsyncEsimResourceWithRawResponse", - "EsimResourceWithStreamingResponse", - "AsyncEsimResourceWithStreamingResponse", + "TimezoneResource", + "AsyncTimezoneResource", + "TimezoneResourceWithRawResponse", + "AsyncTimezoneResourceWithRawResponse", + "TimezoneResourceWithStreamingResponse", + "AsyncTimezoneResourceWithStreamingResponse", + "LanguageResource", + "AsyncLanguageResource", + "LanguageResourceWithRawResponse", + "AsyncLanguageResourceWithRawResponse", + "LanguageResourceWithStreamingResponse", + "AsyncLanguageResourceWithStreamingResponse", "DevicesResource", "AsyncDevicesResource", "DevicesResourceWithRawResponse", diff --git a/src/mobilerun_sdk/resources/devices/apps.py b/src/mobilerun_sdk/resources/devices/apps.py index 4ffb89d..4541bb5 100644 --- a/src/mobilerun_sdk/resources/devices/apps.py +++ b/src/mobilerun_sdk/resources/devices/apps.py @@ -3,11 +3,12 @@ from __future__ import annotations from typing import Optional +from typing_extensions import overload import httpx from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given -from ..._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform +from ..._utils import is_given, path_template, required_args, maybe_transform, strip_not_given, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -183,11 +184,13 @@ def delete( cast_to=NoneType, ) + @overload def install( self, device_id: str, *, - package_name: str, + bundle_id: str, + package_name: str | Omit = omit, x_device_display_id: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -196,10 +199,49 @@ def install( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: + """Install app + + Args: + bundle_id: iOS bundle identifier (e.g. + + com.example.app) + + package_name: Android package name (e.g. com.example.app) + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds """ - Install app + ... + + @overload + def install( + self, + device_id: str, + *, + package_name: str, + bundle_id: str | Omit = omit, + x_device_display_id: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """Install app Args: + package_name: Android package name (e.g. + + com.example.app) + + bundle_id: iOS bundle identifier (e.g. com.example.app) + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -208,6 +250,23 @@ def install( timeout: Override the client-level default timeout for this request, in seconds """ + ... + + @required_args(["bundle_id"], ["package_name"]) + def install( + self, + device_id: str, + *, + bundle_id: str | Omit = omit, + package_name: str | Omit = omit, + x_device_display_id: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} @@ -219,7 +278,13 @@ def install( } return self._post( path_template("/devices/{device_id}/apps", device_id=device_id), - body=maybe_transform({"package_name": package_name}, app_install_params.AppInstallParams), + body=maybe_transform( + { + "bundle_id": bundle_id, + "package_name": package_name, + }, + app_install_params.AppInstallParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -433,11 +498,13 @@ async def delete( cast_to=NoneType, ) + @overload async def install( self, device_id: str, *, - package_name: str, + bundle_id: str, + package_name: str | Omit = omit, x_device_display_id: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -446,10 +513,49 @@ async def install( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: + """Install app + + Args: + bundle_id: iOS bundle identifier (e.g. + + com.example.app) + + package_name: Android package name (e.g. com.example.app) + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds """ - Install app + ... + + @overload + async def install( + self, + device_id: str, + *, + package_name: str, + bundle_id: str | Omit = omit, + x_device_display_id: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """Install app Args: + package_name: Android package name (e.g. + + com.example.app) + + bundle_id: iOS bundle identifier (e.g. com.example.app) + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -458,6 +564,23 @@ async def install( timeout: Override the client-level default timeout for this request, in seconds """ + ... + + @required_args(["bundle_id"], ["package_name"]) + async def install( + self, + device_id: str, + *, + bundle_id: str | Omit = omit, + package_name: str | Omit = omit, + x_device_display_id: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} @@ -469,7 +592,13 @@ async def install( } return await self._post( path_template("/devices/{device_id}/apps", device_id=device_id), - body=await async_maybe_transform({"package_name": package_name}, app_install_params.AppInstallParams), + body=await async_maybe_transform( + { + "bundle_id": bundle_id, + "package_name": package_name, + }, + app_install_params.AppInstallParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/mobilerun_sdk/resources/devices/devices.py b/src/mobilerun_sdk/resources/devices/devices.py index b6743a9..31c41ed 100644 --- a/src/mobilerun_sdk/resources/devices/devices.py +++ b/src/mobilerun_sdk/resources/devices/devices.py @@ -16,22 +16,6 @@ AppsResourceWithStreamingResponse, AsyncAppsResourceWithStreamingResponse, ) -from .esim import ( - EsimResource, - AsyncEsimResource, - EsimResourceWithRawResponse, - AsyncEsimResourceWithRawResponse, - EsimResourceWithStreamingResponse, - AsyncEsimResourceWithStreamingResponse, -) -from .time import ( - TimeResource, - AsyncTimeResource, - TimeResourceWithRawResponse, - AsyncTimeResourceWithRawResponse, - TimeResourceWithStreamingResponse, - AsyncTimeResourceWithStreamingResponse, -) from .files import ( FilesResource, AsyncFilesResource, @@ -82,7 +66,7 @@ AsyncProfileResourceWithStreamingResponse, ) from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given -from ..._utils import path_template, maybe_transform, async_maybe_transform +from ..._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform from .keyboard import ( KeyboardResource, AsyncKeyboardResource, @@ -91,6 +75,14 @@ KeyboardResourceWithStreamingResponse, AsyncKeyboardResourceWithStreamingResponse, ) +from .language import ( + LanguageResource, + AsyncLanguageResource, + LanguageResourceWithRawResponse, + AsyncLanguageResourceWithRawResponse, + LanguageResourceWithStreamingResponse, + AsyncLanguageResourceWithStreamingResponse, +) from .location import ( LocationResource, AsyncLocationResource, @@ -107,7 +99,23 @@ PackagesResourceWithStreamingResponse, AsyncPackagesResourceWithStreamingResponse, ) +from .timezone import ( + TimezoneResource, + AsyncTimezoneResource, + TimezoneResourceWithRawResponse, + AsyncTimezoneResourceWithRawResponse, + TimezoneResourceWithStreamingResponse, + AsyncTimezoneResourceWithStreamingResponse, +) from ..._compat import cached_property +from .esim.esim import ( + EsimResource, + AsyncEsimResource, + EsimResourceWithRawResponse, + AsyncEsimResourceWithRawResponse, + EsimResourceWithStreamingResponse, + AsyncEsimResourceWithStreamingResponse, +) from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( to_raw_response_wrapper, @@ -119,6 +127,8 @@ from ...types.device import Device from ...types.device_list_response import DeviceListResponse from ...types.device_count_response import DeviceCountResponse +from ...types.shared_params.location import Location +from ...types.device_fingerprint_response import DeviceFingerprintResponse from ...types.shared_params.device_carrier import DeviceCarrier from ...types.shared_params.device_identifiers import DeviceIdentifiers @@ -127,52 +137,57 @@ class DevicesResource(SyncAPIResource): @cached_property - def time(self) -> TimeResource: - return TimeResource(self._client) + def actions(self) -> ActionsResource: + return ActionsResource(self._client) @cached_property - def profile(self) -> ProfileResource: - return ProfileResource(self._client) + def apps(self) -> AppsResource: + return AppsResource(self._client) + + @cached_property + def esim(self) -> EsimResource: + return EsimResource(self._client) @cached_property def files(self) -> FilesResource: return FilesResource(self._client) @cached_property - def proxy(self) -> ProxyResource: - return ProxyResource(self._client) + def keyboard(self) -> KeyboardResource: + return KeyboardResource(self._client) @cached_property def location(self) -> LocationResource: return LocationResource(self._client) @cached_property - def actions(self) -> ActionsResource: - return ActionsResource(self._client) - - @cached_property - def state(self) -> StateResource: - return StateResource(self._client) + def packages(self) -> PackagesResource: + return PackagesResource(self._client) @cached_property - def apps(self) -> AppsResource: - return AppsResource(self._client) + def profile(self) -> ProfileResource: + return ProfileResource(self._client) @cached_property - def packages(self) -> PackagesResource: - return PackagesResource(self._client) + def proxy(self) -> ProxyResource: + return ProxyResource(self._client) @cached_property - def keyboard(self) -> KeyboardResource: - return KeyboardResource(self._client) + def state(self) -> StateResource: + return StateResource(self._client) @cached_property def tasks(self) -> TasksResource: + """Device Management""" return TasksResource(self._client) @cached_property - def esim(self) -> EsimResource: - return EsimResource(self._client) + def timezone(self) -> TimezoneResource: + return TimezoneResource(self._client) + + @cached_property + def language(self) -> LanguageResource: + return LanguageResource(self._client) @cached_property def with_raw_response(self) -> DevicesResourceWithRawResponse: @@ -196,16 +211,21 @@ def with_streaming_response(self) -> DevicesResourceWithStreamingResponse: def create( self, *, - device_type: Literal[ - "dedicated_physical_device", "dedicated_premium_device", "dedicated_emulated_device", "dedicated_ios_device" - ] + query_country: str | Omit = omit, + device_type: Literal["dedicated_physical_device", "dedicated_premium_device", "dedicated_ios_device"] | Omit = omit, + profile_id: str | Omit = omit, + android_version: int | Omit = omit, apps: Optional[SequenceNotStr[str]] | Omit = omit, carrier: DeviceCarrier | Omit = omit, + body_country: str | Omit = omit, files: Optional[SequenceNotStr[str]] | Omit = omit, identifiers: DeviceIdentifiers | Omit = omit, + locale: str | Omit = omit, + location: Location | Omit = omit, name: str | Omit = omit, proxy: device_create_params.Proxy | Omit = omit, + timezone: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -213,10 +233,16 @@ def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Device: - """ - Provision a new device + """Provision a new device Args: + query_country: ISO 3166-1 alpha-2 country code. + + If omitted the system picks the country with + the most availability. + + profile_id: Profile ID to use as device spec + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -229,12 +255,17 @@ def create( "/devices", body=maybe_transform( { + "android_version": android_version, "apps": apps, "carrier": carrier, + "body_country": body_country, "files": files, "identifiers": identifiers, + "locale": locale, + "location": location, "name": name, "proxy": proxy, + "timezone": timezone, }, device_create_params.DeviceCreateParams, ), @@ -243,7 +274,14 @@ def create( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"device_type": device_type}, device_create_params.DeviceCreateParams), + query=maybe_transform( + { + "query_country": query_country, + "device_type": device_type, + "profile_id": profile_id, + }, + device_create_params.DeviceCreateParams, + ), ), cast_to=Device, ) @@ -294,15 +332,20 @@ def list( state: Optional[ List[ Literal[ - "creating", "assigned", "ready", "rebooting", "migrating", "terminated", "maintenance", "unknown" + "creating", + "assigned", + "ready", + "rebooting", + "migrating", + "resetting", + "terminated", + "maintenance", + "unknown", ] ] ] | Omit = omit, - type: Literal[ - "dedicated_physical_device", "dedicated_premium_device", "dedicated_emulated_device", "dedicated_ios_device" - ] - | Omit = omit, + type: Literal["dedicated_physical_device", "dedicated_premium_device", "dedicated_ios_device"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -366,6 +409,115 @@ def count( cast_to=DeviceCountResponse, ) + def fingerprint( + self, + device_id: str, + *, + x_device_display_id: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> DeviceFingerprintResponse: + """ + Device fingerprint snapshot + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not device_id: + raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = { + **strip_not_given( + {"X-Device-Display-ID": str(x_device_display_id) if is_given(x_device_display_id) else not_given} + ), + **(extra_headers or {}), + } + return self._get( + path_template("/devices/{device_id}/fingerprint", device_id=device_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DeviceFingerprintResponse, + ) + + def reboot( + self, + device_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Reboot a device + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not device_id: + raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._post( + path_template("/devices/{device_id}/reboot", device_id=device_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def reset( + self, + device_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Reset a device to a fresh state (VMOS one-click new device; non-VMOS providers + return 404) + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not device_id: + raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._post( + path_template("/devices/{device_id}/reset", device_id=device_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + def set_name( self, device_id: str, @@ -480,52 +632,57 @@ def wait_ready( class AsyncDevicesResource(AsyncAPIResource): @cached_property - def time(self) -> AsyncTimeResource: - return AsyncTimeResource(self._client) + def actions(self) -> AsyncActionsResource: + return AsyncActionsResource(self._client) @cached_property - def profile(self) -> AsyncProfileResource: - return AsyncProfileResource(self._client) + def apps(self) -> AsyncAppsResource: + return AsyncAppsResource(self._client) + + @cached_property + def esim(self) -> AsyncEsimResource: + return AsyncEsimResource(self._client) @cached_property def files(self) -> AsyncFilesResource: return AsyncFilesResource(self._client) @cached_property - def proxy(self) -> AsyncProxyResource: - return AsyncProxyResource(self._client) + def keyboard(self) -> AsyncKeyboardResource: + return AsyncKeyboardResource(self._client) @cached_property def location(self) -> AsyncLocationResource: return AsyncLocationResource(self._client) @cached_property - def actions(self) -> AsyncActionsResource: - return AsyncActionsResource(self._client) - - @cached_property - def state(self) -> AsyncStateResource: - return AsyncStateResource(self._client) + def packages(self) -> AsyncPackagesResource: + return AsyncPackagesResource(self._client) @cached_property - def apps(self) -> AsyncAppsResource: - return AsyncAppsResource(self._client) + def profile(self) -> AsyncProfileResource: + return AsyncProfileResource(self._client) @cached_property - def packages(self) -> AsyncPackagesResource: - return AsyncPackagesResource(self._client) + def proxy(self) -> AsyncProxyResource: + return AsyncProxyResource(self._client) @cached_property - def keyboard(self) -> AsyncKeyboardResource: - return AsyncKeyboardResource(self._client) + def state(self) -> AsyncStateResource: + return AsyncStateResource(self._client) @cached_property def tasks(self) -> AsyncTasksResource: + """Device Management""" return AsyncTasksResource(self._client) @cached_property - def esim(self) -> AsyncEsimResource: - return AsyncEsimResource(self._client) + def timezone(self) -> AsyncTimezoneResource: + return AsyncTimezoneResource(self._client) + + @cached_property + def language(self) -> AsyncLanguageResource: + return AsyncLanguageResource(self._client) @cached_property def with_raw_response(self) -> AsyncDevicesResourceWithRawResponse: @@ -549,16 +706,21 @@ def with_streaming_response(self) -> AsyncDevicesResourceWithStreamingResponse: async def create( self, *, - device_type: Literal[ - "dedicated_physical_device", "dedicated_premium_device", "dedicated_emulated_device", "dedicated_ios_device" - ] + query_country: str | Omit = omit, + device_type: Literal["dedicated_physical_device", "dedicated_premium_device", "dedicated_ios_device"] | Omit = omit, + profile_id: str | Omit = omit, + android_version: int | Omit = omit, apps: Optional[SequenceNotStr[str]] | Omit = omit, carrier: DeviceCarrier | Omit = omit, + body_country: str | Omit = omit, files: Optional[SequenceNotStr[str]] | Omit = omit, identifiers: DeviceIdentifiers | Omit = omit, + locale: str | Omit = omit, + location: Location | Omit = omit, name: str | Omit = omit, proxy: device_create_params.Proxy | Omit = omit, + timezone: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -566,10 +728,16 @@ async def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Device: - """ - Provision a new device + """Provision a new device Args: + query_country: ISO 3166-1 alpha-2 country code. + + If omitted the system picks the country with + the most availability. + + profile_id: Profile ID to use as device spec + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -582,12 +750,17 @@ async def create( "/devices", body=await async_maybe_transform( { + "android_version": android_version, "apps": apps, "carrier": carrier, + "body_country": body_country, "files": files, "identifiers": identifiers, + "locale": locale, + "location": location, "name": name, "proxy": proxy, + "timezone": timezone, }, device_create_params.DeviceCreateParams, ), @@ -597,7 +770,12 @@ async def create( extra_body=extra_body, timeout=timeout, query=await async_maybe_transform( - {"device_type": device_type}, device_create_params.DeviceCreateParams + { + "query_country": query_country, + "device_type": device_type, + "profile_id": profile_id, + }, + device_create_params.DeviceCreateParams, ), ), cast_to=Device, @@ -649,15 +827,20 @@ async def list( state: Optional[ List[ Literal[ - "creating", "assigned", "ready", "rebooting", "migrating", "terminated", "maintenance", "unknown" + "creating", + "assigned", + "ready", + "rebooting", + "migrating", + "resetting", + "terminated", + "maintenance", + "unknown", ] ] ] | Omit = omit, - type: Literal[ - "dedicated_physical_device", "dedicated_premium_device", "dedicated_emulated_device", "dedicated_ios_device" - ] - | Omit = omit, + type: Literal["dedicated_physical_device", "dedicated_premium_device", "dedicated_ios_device"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -721,6 +904,115 @@ async def count( cast_to=DeviceCountResponse, ) + async def fingerprint( + self, + device_id: str, + *, + x_device_display_id: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> DeviceFingerprintResponse: + """ + Device fingerprint snapshot + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not device_id: + raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = { + **strip_not_given( + {"X-Device-Display-ID": str(x_device_display_id) if is_given(x_device_display_id) else not_given} + ), + **(extra_headers or {}), + } + return await self._get( + path_template("/devices/{device_id}/fingerprint", device_id=device_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DeviceFingerprintResponse, + ) + + async def reboot( + self, + device_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Reboot a device + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not device_id: + raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._post( + path_template("/devices/{device_id}/reboot", device_id=device_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def reset( + self, + device_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Reset a device to a fresh state (VMOS one-click new device; non-VMOS providers + return 404) + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not device_id: + raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._post( + path_template("/devices/{device_id}/reset", device_id=device_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + async def set_name( self, device_id: str, @@ -849,6 +1141,15 @@ def __init__(self, devices: DevicesResource) -> None: self.count = to_raw_response_wrapper( devices.count, ) + self.fingerprint = to_raw_response_wrapper( + devices.fingerprint, + ) + self.reboot = to_raw_response_wrapper( + devices.reboot, + ) + self.reset = to_raw_response_wrapper( + devices.reset, + ) self.set_name = to_raw_response_wrapper( devices.set_name, ) @@ -860,52 +1161,57 @@ def __init__(self, devices: DevicesResource) -> None: ) @cached_property - def time(self) -> TimeResourceWithRawResponse: - return TimeResourceWithRawResponse(self._devices.time) + def actions(self) -> ActionsResourceWithRawResponse: + return ActionsResourceWithRawResponse(self._devices.actions) @cached_property - def profile(self) -> ProfileResourceWithRawResponse: - return ProfileResourceWithRawResponse(self._devices.profile) + def apps(self) -> AppsResourceWithRawResponse: + return AppsResourceWithRawResponse(self._devices.apps) + + @cached_property + def esim(self) -> EsimResourceWithRawResponse: + return EsimResourceWithRawResponse(self._devices.esim) @cached_property def files(self) -> FilesResourceWithRawResponse: return FilesResourceWithRawResponse(self._devices.files) @cached_property - def proxy(self) -> ProxyResourceWithRawResponse: - return ProxyResourceWithRawResponse(self._devices.proxy) + def keyboard(self) -> KeyboardResourceWithRawResponse: + return KeyboardResourceWithRawResponse(self._devices.keyboard) @cached_property def location(self) -> LocationResourceWithRawResponse: return LocationResourceWithRawResponse(self._devices.location) @cached_property - def actions(self) -> ActionsResourceWithRawResponse: - return ActionsResourceWithRawResponse(self._devices.actions) - - @cached_property - def state(self) -> StateResourceWithRawResponse: - return StateResourceWithRawResponse(self._devices.state) + def packages(self) -> PackagesResourceWithRawResponse: + return PackagesResourceWithRawResponse(self._devices.packages) @cached_property - def apps(self) -> AppsResourceWithRawResponse: - return AppsResourceWithRawResponse(self._devices.apps) + def profile(self) -> ProfileResourceWithRawResponse: + return ProfileResourceWithRawResponse(self._devices.profile) @cached_property - def packages(self) -> PackagesResourceWithRawResponse: - return PackagesResourceWithRawResponse(self._devices.packages) + def proxy(self) -> ProxyResourceWithRawResponse: + return ProxyResourceWithRawResponse(self._devices.proxy) @cached_property - def keyboard(self) -> KeyboardResourceWithRawResponse: - return KeyboardResourceWithRawResponse(self._devices.keyboard) + def state(self) -> StateResourceWithRawResponse: + return StateResourceWithRawResponse(self._devices.state) @cached_property def tasks(self) -> TasksResourceWithRawResponse: + """Device Management""" return TasksResourceWithRawResponse(self._devices.tasks) @cached_property - def esim(self) -> EsimResourceWithRawResponse: - return EsimResourceWithRawResponse(self._devices.esim) + def timezone(self) -> TimezoneResourceWithRawResponse: + return TimezoneResourceWithRawResponse(self._devices.timezone) + + @cached_property + def language(self) -> LanguageResourceWithRawResponse: + return LanguageResourceWithRawResponse(self._devices.language) class AsyncDevicesResourceWithRawResponse: @@ -924,6 +1230,15 @@ def __init__(self, devices: AsyncDevicesResource) -> None: self.count = async_to_raw_response_wrapper( devices.count, ) + self.fingerprint = async_to_raw_response_wrapper( + devices.fingerprint, + ) + self.reboot = async_to_raw_response_wrapper( + devices.reboot, + ) + self.reset = async_to_raw_response_wrapper( + devices.reset, + ) self.set_name = async_to_raw_response_wrapper( devices.set_name, ) @@ -935,52 +1250,57 @@ def __init__(self, devices: AsyncDevicesResource) -> None: ) @cached_property - def time(self) -> AsyncTimeResourceWithRawResponse: - return AsyncTimeResourceWithRawResponse(self._devices.time) + def actions(self) -> AsyncActionsResourceWithRawResponse: + return AsyncActionsResourceWithRawResponse(self._devices.actions) @cached_property - def profile(self) -> AsyncProfileResourceWithRawResponse: - return AsyncProfileResourceWithRawResponse(self._devices.profile) + def apps(self) -> AsyncAppsResourceWithRawResponse: + return AsyncAppsResourceWithRawResponse(self._devices.apps) + + @cached_property + def esim(self) -> AsyncEsimResourceWithRawResponse: + return AsyncEsimResourceWithRawResponse(self._devices.esim) @cached_property def files(self) -> AsyncFilesResourceWithRawResponse: return AsyncFilesResourceWithRawResponse(self._devices.files) @cached_property - def proxy(self) -> AsyncProxyResourceWithRawResponse: - return AsyncProxyResourceWithRawResponse(self._devices.proxy) + def keyboard(self) -> AsyncKeyboardResourceWithRawResponse: + return AsyncKeyboardResourceWithRawResponse(self._devices.keyboard) @cached_property def location(self) -> AsyncLocationResourceWithRawResponse: return AsyncLocationResourceWithRawResponse(self._devices.location) @cached_property - def actions(self) -> AsyncActionsResourceWithRawResponse: - return AsyncActionsResourceWithRawResponse(self._devices.actions) - - @cached_property - def state(self) -> AsyncStateResourceWithRawResponse: - return AsyncStateResourceWithRawResponse(self._devices.state) + def packages(self) -> AsyncPackagesResourceWithRawResponse: + return AsyncPackagesResourceWithRawResponse(self._devices.packages) @cached_property - def apps(self) -> AsyncAppsResourceWithRawResponse: - return AsyncAppsResourceWithRawResponse(self._devices.apps) + def profile(self) -> AsyncProfileResourceWithRawResponse: + return AsyncProfileResourceWithRawResponse(self._devices.profile) @cached_property - def packages(self) -> AsyncPackagesResourceWithRawResponse: - return AsyncPackagesResourceWithRawResponse(self._devices.packages) + def proxy(self) -> AsyncProxyResourceWithRawResponse: + return AsyncProxyResourceWithRawResponse(self._devices.proxy) @cached_property - def keyboard(self) -> AsyncKeyboardResourceWithRawResponse: - return AsyncKeyboardResourceWithRawResponse(self._devices.keyboard) + def state(self) -> AsyncStateResourceWithRawResponse: + return AsyncStateResourceWithRawResponse(self._devices.state) @cached_property def tasks(self) -> AsyncTasksResourceWithRawResponse: + """Device Management""" return AsyncTasksResourceWithRawResponse(self._devices.tasks) @cached_property - def esim(self) -> AsyncEsimResourceWithRawResponse: - return AsyncEsimResourceWithRawResponse(self._devices.esim) + def timezone(self) -> AsyncTimezoneResourceWithRawResponse: + return AsyncTimezoneResourceWithRawResponse(self._devices.timezone) + + @cached_property + def language(self) -> AsyncLanguageResourceWithRawResponse: + return AsyncLanguageResourceWithRawResponse(self._devices.language) class DevicesResourceWithStreamingResponse: @@ -999,6 +1319,15 @@ def __init__(self, devices: DevicesResource) -> None: self.count = to_streamed_response_wrapper( devices.count, ) + self.fingerprint = to_streamed_response_wrapper( + devices.fingerprint, + ) + self.reboot = to_streamed_response_wrapper( + devices.reboot, + ) + self.reset = to_streamed_response_wrapper( + devices.reset, + ) self.set_name = to_streamed_response_wrapper( devices.set_name, ) @@ -1010,52 +1339,57 @@ def __init__(self, devices: DevicesResource) -> None: ) @cached_property - def time(self) -> TimeResourceWithStreamingResponse: - return TimeResourceWithStreamingResponse(self._devices.time) + def actions(self) -> ActionsResourceWithStreamingResponse: + return ActionsResourceWithStreamingResponse(self._devices.actions) + + @cached_property + def apps(self) -> AppsResourceWithStreamingResponse: + return AppsResourceWithStreamingResponse(self._devices.apps) @cached_property - def profile(self) -> ProfileResourceWithStreamingResponse: - return ProfileResourceWithStreamingResponse(self._devices.profile) + def esim(self) -> EsimResourceWithStreamingResponse: + return EsimResourceWithStreamingResponse(self._devices.esim) @cached_property def files(self) -> FilesResourceWithStreamingResponse: return FilesResourceWithStreamingResponse(self._devices.files) @cached_property - def proxy(self) -> ProxyResourceWithStreamingResponse: - return ProxyResourceWithStreamingResponse(self._devices.proxy) + def keyboard(self) -> KeyboardResourceWithStreamingResponse: + return KeyboardResourceWithStreamingResponse(self._devices.keyboard) @cached_property def location(self) -> LocationResourceWithStreamingResponse: return LocationResourceWithStreamingResponse(self._devices.location) @cached_property - def actions(self) -> ActionsResourceWithStreamingResponse: - return ActionsResourceWithStreamingResponse(self._devices.actions) - - @cached_property - def state(self) -> StateResourceWithStreamingResponse: - return StateResourceWithStreamingResponse(self._devices.state) + def packages(self) -> PackagesResourceWithStreamingResponse: + return PackagesResourceWithStreamingResponse(self._devices.packages) @cached_property - def apps(self) -> AppsResourceWithStreamingResponse: - return AppsResourceWithStreamingResponse(self._devices.apps) + def profile(self) -> ProfileResourceWithStreamingResponse: + return ProfileResourceWithStreamingResponse(self._devices.profile) @cached_property - def packages(self) -> PackagesResourceWithStreamingResponse: - return PackagesResourceWithStreamingResponse(self._devices.packages) + def proxy(self) -> ProxyResourceWithStreamingResponse: + return ProxyResourceWithStreamingResponse(self._devices.proxy) @cached_property - def keyboard(self) -> KeyboardResourceWithStreamingResponse: - return KeyboardResourceWithStreamingResponse(self._devices.keyboard) + def state(self) -> StateResourceWithStreamingResponse: + return StateResourceWithStreamingResponse(self._devices.state) @cached_property def tasks(self) -> TasksResourceWithStreamingResponse: + """Device Management""" return TasksResourceWithStreamingResponse(self._devices.tasks) @cached_property - def esim(self) -> EsimResourceWithStreamingResponse: - return EsimResourceWithStreamingResponse(self._devices.esim) + def timezone(self) -> TimezoneResourceWithStreamingResponse: + return TimezoneResourceWithStreamingResponse(self._devices.timezone) + + @cached_property + def language(self) -> LanguageResourceWithStreamingResponse: + return LanguageResourceWithStreamingResponse(self._devices.language) class AsyncDevicesResourceWithStreamingResponse: @@ -1074,6 +1408,15 @@ def __init__(self, devices: AsyncDevicesResource) -> None: self.count = async_to_streamed_response_wrapper( devices.count, ) + self.fingerprint = async_to_streamed_response_wrapper( + devices.fingerprint, + ) + self.reboot = async_to_streamed_response_wrapper( + devices.reboot, + ) + self.reset = async_to_streamed_response_wrapper( + devices.reset, + ) self.set_name = async_to_streamed_response_wrapper( devices.set_name, ) @@ -1085,49 +1428,54 @@ def __init__(self, devices: AsyncDevicesResource) -> None: ) @cached_property - def time(self) -> AsyncTimeResourceWithStreamingResponse: - return AsyncTimeResourceWithStreamingResponse(self._devices.time) + def actions(self) -> AsyncActionsResourceWithStreamingResponse: + return AsyncActionsResourceWithStreamingResponse(self._devices.actions) @cached_property - def profile(self) -> AsyncProfileResourceWithStreamingResponse: - return AsyncProfileResourceWithStreamingResponse(self._devices.profile) + def apps(self) -> AsyncAppsResourceWithStreamingResponse: + return AsyncAppsResourceWithStreamingResponse(self._devices.apps) + + @cached_property + def esim(self) -> AsyncEsimResourceWithStreamingResponse: + return AsyncEsimResourceWithStreamingResponse(self._devices.esim) @cached_property def files(self) -> AsyncFilesResourceWithStreamingResponse: return AsyncFilesResourceWithStreamingResponse(self._devices.files) @cached_property - def proxy(self) -> AsyncProxyResourceWithStreamingResponse: - return AsyncProxyResourceWithStreamingResponse(self._devices.proxy) + def keyboard(self) -> AsyncKeyboardResourceWithStreamingResponse: + return AsyncKeyboardResourceWithStreamingResponse(self._devices.keyboard) @cached_property def location(self) -> AsyncLocationResourceWithStreamingResponse: return AsyncLocationResourceWithStreamingResponse(self._devices.location) @cached_property - def actions(self) -> AsyncActionsResourceWithStreamingResponse: - return AsyncActionsResourceWithStreamingResponse(self._devices.actions) - - @cached_property - def state(self) -> AsyncStateResourceWithStreamingResponse: - return AsyncStateResourceWithStreamingResponse(self._devices.state) + def packages(self) -> AsyncPackagesResourceWithStreamingResponse: + return AsyncPackagesResourceWithStreamingResponse(self._devices.packages) @cached_property - def apps(self) -> AsyncAppsResourceWithStreamingResponse: - return AsyncAppsResourceWithStreamingResponse(self._devices.apps) + def profile(self) -> AsyncProfileResourceWithStreamingResponse: + return AsyncProfileResourceWithStreamingResponse(self._devices.profile) @cached_property - def packages(self) -> AsyncPackagesResourceWithStreamingResponse: - return AsyncPackagesResourceWithStreamingResponse(self._devices.packages) + def proxy(self) -> AsyncProxyResourceWithStreamingResponse: + return AsyncProxyResourceWithStreamingResponse(self._devices.proxy) @cached_property - def keyboard(self) -> AsyncKeyboardResourceWithStreamingResponse: - return AsyncKeyboardResourceWithStreamingResponse(self._devices.keyboard) + def state(self) -> AsyncStateResourceWithStreamingResponse: + return AsyncStateResourceWithStreamingResponse(self._devices.state) @cached_property def tasks(self) -> AsyncTasksResourceWithStreamingResponse: + """Device Management""" return AsyncTasksResourceWithStreamingResponse(self._devices.tasks) @cached_property - def esim(self) -> AsyncEsimResourceWithStreamingResponse: - return AsyncEsimResourceWithStreamingResponse(self._devices.esim) + def timezone(self) -> AsyncTimezoneResourceWithStreamingResponse: + return AsyncTimezoneResourceWithStreamingResponse(self._devices.timezone) + + @cached_property + def language(self) -> AsyncLanguageResourceWithStreamingResponse: + return AsyncLanguageResourceWithStreamingResponse(self._devices.language) diff --git a/src/mobilerun_sdk/resources/devices/esim/__init__.py b/src/mobilerun_sdk/resources/devices/esim/__init__.py new file mode 100644 index 0000000..12056c7 --- /dev/null +++ b/src/mobilerun_sdk/resources/devices/esim/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .apn import ( + ApnResource, + AsyncApnResource, + ApnResourceWithRawResponse, + AsyncApnResourceWithRawResponse, + ApnResourceWithStreamingResponse, + AsyncApnResourceWithStreamingResponse, +) +from .esim import ( + EsimResource, + AsyncEsimResource, + EsimResourceWithRawResponse, + AsyncEsimResourceWithRawResponse, + EsimResourceWithStreamingResponse, + AsyncEsimResourceWithStreamingResponse, +) + +__all__ = [ + "ApnResource", + "AsyncApnResource", + "ApnResourceWithRawResponse", + "AsyncApnResourceWithRawResponse", + "ApnResourceWithStreamingResponse", + "AsyncApnResourceWithStreamingResponse", + "EsimResource", + "AsyncEsimResource", + "EsimResourceWithRawResponse", + "AsyncEsimResourceWithRawResponse", + "EsimResourceWithStreamingResponse", + "AsyncEsimResourceWithStreamingResponse", +] diff --git a/src/mobilerun_sdk/resources/devices/time.py b/src/mobilerun_sdk/resources/devices/esim/apn.py similarity index 65% rename from src/mobilerun_sdk/resources/devices/time.py rename to src/mobilerun_sdk/resources/devices/esim/apn.py index daab6c6..c732e52 100644 --- a/src/mobilerun_sdk/resources/devices/time.py +++ b/src/mobilerun_sdk/resources/devices/esim/apn.py @@ -2,50 +2,59 @@ from __future__ import annotations +from typing import Optional + import httpx -from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given -from ..._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import ( +from ...._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from ...._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import ( to_raw_response_wrapper, to_streamed_response_wrapper, async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ..._base_client import make_request_options -from ...types.devices import time_set_timezone_params -from ...types.devices.time_timezone_response import TimeTimezoneResponse +from ...._base_client import make_request_options +from ....types.devices.esim import apn_create_params, apn_select_params +from ....types.devices.esim.apn_list_response import ApnListResponse -__all__ = ["TimeResource", "AsyncTimeResource"] +__all__ = ["ApnResource", "AsyncApnResource"] -class TimeResource(SyncAPIResource): +class ApnResource(SyncAPIResource): @cached_property - def with_raw_response(self) -> TimeResourceWithRawResponse: + def with_raw_response(self) -> ApnResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#accessing-raw-response-data-eg-headers """ - return TimeResourceWithRawResponse(self) + return ApnResourceWithRawResponse(self) @cached_property - def with_streaming_response(self) -> TimeResourceWithStreamingResponse: + def with_streaming_response(self) -> ApnResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#with_streaming_response """ - return TimeResourceWithStreamingResponse(self) + return ApnResourceWithStreamingResponse(self) - def set_timezone( + def create( self, device_id: str, *, - timezone: str, + apn: str, + mcc: str, + mnc: str, + name: str, + protocol: str, + roaming_protocol: str, + sub_id: int, + type: str, x_device_display_id: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -55,7 +64,7 @@ def set_timezone( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ - Set device timezone + Create and set an APN for an eSIM subscription Args: extra_headers: Send extra headers @@ -76,15 +85,27 @@ def set_timezone( **(extra_headers or {}), } return self._post( - path_template("/devices/{device_id}/timezone", device_id=device_id), - body=maybe_transform({"timezone": timezone}, time_set_timezone_params.TimeSetTimezoneParams), + path_template("/devices/{device_id}/esim/apn", device_id=device_id), + body=maybe_transform( + { + "apn": apn, + "mcc": mcc, + "mnc": mnc, + "name": name, + "protocol": protocol, + "roaming_protocol": roaming_protocol, + "sub_id": sub_id, + "type": type, + }, + apn_create_params.ApnCreateParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=NoneType, ) - def time( + def list( self, device_id: str, *, @@ -95,9 +116,9 @@ def time( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> str: + ) -> Optional[ApnListResponse]: """ - Device time + List APNs for active subscriptions Args: extra_headers: Send extra headers @@ -117,17 +138,19 @@ def time( **(extra_headers or {}), } return self._get( - path_template("/devices/{device_id}/time", device_id=device_id), + path_template("/devices/{device_id}/esim/apn", device_id=device_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=str, + cast_to=ApnListResponse, ) - def timezone( + def select( self, device_id: str, *, + apn_id: int, + sub_id: int, x_device_display_id: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -135,9 +158,9 @@ def timezone( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> TimeTimezoneResponse: + ) -> None: """ - Get device timezone + Select an existing APN as preferred Args: extra_headers: Send extra headers @@ -150,46 +173,61 @@ def timezone( """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} extra_headers = { **strip_not_given( {"X-Device-Display-ID": str(x_device_display_id) if is_given(x_device_display_id) else not_given} ), **(extra_headers or {}), } - return self._get( - path_template("/devices/{device_id}/timezone", device_id=device_id), + return self._put( + path_template("/devices/{device_id}/esim/apn", device_id=device_id), + body=maybe_transform( + { + "apn_id": apn_id, + "sub_id": sub_id, + }, + apn_select_params.ApnSelectParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=TimeTimezoneResponse, + cast_to=NoneType, ) -class AsyncTimeResource(AsyncAPIResource): +class AsyncApnResource(AsyncAPIResource): @cached_property - def with_raw_response(self) -> AsyncTimeResourceWithRawResponse: + def with_raw_response(self) -> AsyncApnResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#accessing-raw-response-data-eg-headers """ - return AsyncTimeResourceWithRawResponse(self) + return AsyncApnResourceWithRawResponse(self) @cached_property - def with_streaming_response(self) -> AsyncTimeResourceWithStreamingResponse: + def with_streaming_response(self) -> AsyncApnResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#with_streaming_response """ - return AsyncTimeResourceWithStreamingResponse(self) + return AsyncApnResourceWithStreamingResponse(self) - async def set_timezone( + async def create( self, device_id: str, *, - timezone: str, + apn: str, + mcc: str, + mnc: str, + name: str, + protocol: str, + roaming_protocol: str, + sub_id: int, + type: str, x_device_display_id: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -199,7 +237,7 @@ async def set_timezone( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ - Set device timezone + Create and set an APN for an eSIM subscription Args: extra_headers: Send extra headers @@ -220,15 +258,27 @@ async def set_timezone( **(extra_headers or {}), } return await self._post( - path_template("/devices/{device_id}/timezone", device_id=device_id), - body=await async_maybe_transform({"timezone": timezone}, time_set_timezone_params.TimeSetTimezoneParams), + path_template("/devices/{device_id}/esim/apn", device_id=device_id), + body=await async_maybe_transform( + { + "apn": apn, + "mcc": mcc, + "mnc": mnc, + "name": name, + "protocol": protocol, + "roaming_protocol": roaming_protocol, + "sub_id": sub_id, + "type": type, + }, + apn_create_params.ApnCreateParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=NoneType, ) - async def time( + async def list( self, device_id: str, *, @@ -239,9 +289,9 @@ async def time( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> str: + ) -> Optional[ApnListResponse]: """ - Device time + List APNs for active subscriptions Args: extra_headers: Send extra headers @@ -261,17 +311,19 @@ async def time( **(extra_headers or {}), } return await self._get( - path_template("/devices/{device_id}/time", device_id=device_id), + path_template("/devices/{device_id}/esim/apn", device_id=device_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=str, + cast_to=ApnListResponse, ) - async def timezone( + async def select( self, device_id: str, *, + apn_id: int, + sub_id: int, x_device_display_id: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -279,9 +331,9 @@ async def timezone( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> TimeTimezoneResponse: + ) -> None: """ - Get device timezone + Select an existing APN as preferred Args: extra_headers: Send extra headers @@ -294,76 +346,84 @@ async def timezone( """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} extra_headers = { **strip_not_given( {"X-Device-Display-ID": str(x_device_display_id) if is_given(x_device_display_id) else not_given} ), **(extra_headers or {}), } - return await self._get( - path_template("/devices/{device_id}/timezone", device_id=device_id), + return await self._put( + path_template("/devices/{device_id}/esim/apn", device_id=device_id), + body=await async_maybe_transform( + { + "apn_id": apn_id, + "sub_id": sub_id, + }, + apn_select_params.ApnSelectParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=TimeTimezoneResponse, + cast_to=NoneType, ) -class TimeResourceWithRawResponse: - def __init__(self, time: TimeResource) -> None: - self._time = time +class ApnResourceWithRawResponse: + def __init__(self, apn: ApnResource) -> None: + self._apn = apn - self.set_timezone = to_raw_response_wrapper( - time.set_timezone, + self.create = to_raw_response_wrapper( + apn.create, ) - self.time = to_raw_response_wrapper( - time.time, + self.list = to_raw_response_wrapper( + apn.list, ) - self.timezone = to_raw_response_wrapper( - time.timezone, + self.select = to_raw_response_wrapper( + apn.select, ) -class AsyncTimeResourceWithRawResponse: - def __init__(self, time: AsyncTimeResource) -> None: - self._time = time +class AsyncApnResourceWithRawResponse: + def __init__(self, apn: AsyncApnResource) -> None: + self._apn = apn - self.set_timezone = async_to_raw_response_wrapper( - time.set_timezone, + self.create = async_to_raw_response_wrapper( + apn.create, ) - self.time = async_to_raw_response_wrapper( - time.time, + self.list = async_to_raw_response_wrapper( + apn.list, ) - self.timezone = async_to_raw_response_wrapper( - time.timezone, + self.select = async_to_raw_response_wrapper( + apn.select, ) -class TimeResourceWithStreamingResponse: - def __init__(self, time: TimeResource) -> None: - self._time = time +class ApnResourceWithStreamingResponse: + def __init__(self, apn: ApnResource) -> None: + self._apn = apn - self.set_timezone = to_streamed_response_wrapper( - time.set_timezone, + self.create = to_streamed_response_wrapper( + apn.create, ) - self.time = to_streamed_response_wrapper( - time.time, + self.list = to_streamed_response_wrapper( + apn.list, ) - self.timezone = to_streamed_response_wrapper( - time.timezone, + self.select = to_streamed_response_wrapper( + apn.select, ) -class AsyncTimeResourceWithStreamingResponse: - def __init__(self, time: AsyncTimeResource) -> None: - self._time = time +class AsyncApnResourceWithStreamingResponse: + def __init__(self, apn: AsyncApnResource) -> None: + self._apn = apn - self.set_timezone = async_to_streamed_response_wrapper( - time.set_timezone, + self.create = async_to_streamed_response_wrapper( + apn.create, ) - self.time = async_to_streamed_response_wrapper( - time.time, + self.list = async_to_streamed_response_wrapper( + apn.list, ) - self.timezone = async_to_streamed_response_wrapper( - time.timezone, + self.select = async_to_streamed_response_wrapper( + apn.select, ) diff --git a/src/mobilerun_sdk/resources/devices/esim.py b/src/mobilerun_sdk/resources/devices/esim/esim.py similarity index 64% rename from src/mobilerun_sdk/resources/devices/esim.py rename to src/mobilerun_sdk/resources/devices/esim/esim.py index 3cf9c6e..9693e2e 100644 --- a/src/mobilerun_sdk/resources/devices/esim.py +++ b/src/mobilerun_sdk/resources/devices/esim/esim.py @@ -6,25 +6,38 @@ import httpx -from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given -from ..._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import ( +from .apn import ( + ApnResource, + AsyncApnResource, + ApnResourceWithRawResponse, + AsyncApnResourceWithRawResponse, + ApnResourceWithStreamingResponse, + AsyncApnResourceWithStreamingResponse, +) +from ...._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from ...._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import ( to_raw_response_wrapper, to_streamed_response_wrapper, async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ..._base_client import make_request_options -from ...types.devices import esim_enable_params, esim_remove_params, esim_activate_params -from ...types.devices.esim_list_response import EsimListResponse -from ...types.devices.esim_activate_response import EsimActivateResponse +from ...._base_client import make_request_options +from ....types.devices import esim_enable_params, esim_remove_params, esim_activate_params, esim_set_roaming_params +from ....types.devices.esim_list_response import EsimListResponse +from ....types.devices.esim_status_response import EsimStatusResponse +from ....types.devices.esim_activate_response import EsimActivateResponse __all__ = ["EsimResource", "AsyncEsimResource"] class EsimResource(SyncAPIResource): + @cached_property + def apn(self) -> ApnResource: + return ApnResource(self._client) + @cached_property def with_raw_response(self) -> EsimResourceWithRawResponse: """ @@ -89,8 +102,9 @@ def activate( device_id: str, *, enable: bool, - matching_id: str, sm_dp_addr: str, + confirmation_code: str | Omit = omit, + matching_id: str | Omit = omit, x_device_display_id: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -103,6 +117,10 @@ def activate( Configure eSIM (download profile and/or enable subscription) Args: + confirmation_code: Optional carrier-issued confirmation code (the 4th LPA segment). Required only + for plans whose SM-DP+ challenges the device for one. Requires matchingId — the + LPA spec only interprets segment 4 when segment 3 is present. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -124,8 +142,9 @@ def activate( body=maybe_transform( { "enable": enable, - "matching_id": matching_id, "sm_dp_addr": sm_dp_addr, + "confirmation_code": confirmation_code, + "matching_id": matching_id, }, esim_activate_params.EsimActivateParams, ), @@ -224,8 +243,95 @@ def remove( cast_to=NoneType, ) + def set_roaming( + self, + device_id: str, + *, + enabled: bool, + x_device_display_id: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Toggle eSIM data roaming + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not device_id: + raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + extra_headers = { + **strip_not_given( + {"X-Device-Display-ID": str(x_device_display_id) if is_given(x_device_display_id) else not_given} + ), + **(extra_headers or {}), + } + return self._put( + path_template("/devices/{device_id}/esim/roaming", device_id=device_id), + body=maybe_transform({"enabled": enabled}, esim_set_roaming_params.EsimSetRoamingParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def status( + self, + device_id: str, + *, + x_device_display_id: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Optional[EsimStatusResponse]: + """ + Get eSIM connectivity status + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not device_id: + raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = { + **strip_not_given( + {"X-Device-Display-ID": str(x_device_display_id) if is_given(x_device_display_id) else not_given} + ), + **(extra_headers or {}), + } + return self._get( + path_template("/devices/{device_id}/esim/status", device_id=device_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EsimStatusResponse, + ) + class AsyncEsimResource(AsyncAPIResource): + @cached_property + def apn(self) -> AsyncApnResource: + return AsyncApnResource(self._client) + @cached_property def with_raw_response(self) -> AsyncEsimResourceWithRawResponse: """ @@ -290,8 +396,9 @@ async def activate( device_id: str, *, enable: bool, - matching_id: str, sm_dp_addr: str, + confirmation_code: str | Omit = omit, + matching_id: str | Omit = omit, x_device_display_id: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -304,6 +411,10 @@ async def activate( Configure eSIM (download profile and/or enable subscription) Args: + confirmation_code: Optional carrier-issued confirmation code (the 4th LPA segment). Required only + for plans whose SM-DP+ challenges the device for one. Requires matchingId — the + LPA spec only interprets segment 4 when segment 3 is present. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -325,8 +436,9 @@ async def activate( body=await async_maybe_transform( { "enable": enable, - "matching_id": matching_id, "sm_dp_addr": sm_dp_addr, + "confirmation_code": confirmation_code, + "matching_id": matching_id, }, esim_activate_params.EsimActivateParams, ), @@ -425,6 +537,89 @@ async def remove( cast_to=NoneType, ) + async def set_roaming( + self, + device_id: str, + *, + enabled: bool, + x_device_display_id: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Toggle eSIM data roaming + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not device_id: + raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + extra_headers = { + **strip_not_given( + {"X-Device-Display-ID": str(x_device_display_id) if is_given(x_device_display_id) else not_given} + ), + **(extra_headers or {}), + } + return await self._put( + path_template("/devices/{device_id}/esim/roaming", device_id=device_id), + body=await async_maybe_transform({"enabled": enabled}, esim_set_roaming_params.EsimSetRoamingParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def status( + self, + device_id: str, + *, + x_device_display_id: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Optional[EsimStatusResponse]: + """ + Get eSIM connectivity status + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not device_id: + raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = { + **strip_not_given( + {"X-Device-Display-ID": str(x_device_display_id) if is_given(x_device_display_id) else not_given} + ), + **(extra_headers or {}), + } + return await self._get( + path_template("/devices/{device_id}/esim/status", device_id=device_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EsimStatusResponse, + ) + class EsimResourceWithRawResponse: def __init__(self, esim: EsimResource) -> None: @@ -442,6 +637,16 @@ def __init__(self, esim: EsimResource) -> None: self.remove = to_raw_response_wrapper( esim.remove, ) + self.set_roaming = to_raw_response_wrapper( + esim.set_roaming, + ) + self.status = to_raw_response_wrapper( + esim.status, + ) + + @cached_property + def apn(self) -> ApnResourceWithRawResponse: + return ApnResourceWithRawResponse(self._esim.apn) class AsyncEsimResourceWithRawResponse: @@ -460,6 +665,16 @@ def __init__(self, esim: AsyncEsimResource) -> None: self.remove = async_to_raw_response_wrapper( esim.remove, ) + self.set_roaming = async_to_raw_response_wrapper( + esim.set_roaming, + ) + self.status = async_to_raw_response_wrapper( + esim.status, + ) + + @cached_property + def apn(self) -> AsyncApnResourceWithRawResponse: + return AsyncApnResourceWithRawResponse(self._esim.apn) class EsimResourceWithStreamingResponse: @@ -478,6 +693,16 @@ def __init__(self, esim: EsimResource) -> None: self.remove = to_streamed_response_wrapper( esim.remove, ) + self.set_roaming = to_streamed_response_wrapper( + esim.set_roaming, + ) + self.status = to_streamed_response_wrapper( + esim.status, + ) + + @cached_property + def apn(self) -> ApnResourceWithStreamingResponse: + return ApnResourceWithStreamingResponse(self._esim.apn) class AsyncEsimResourceWithStreamingResponse: @@ -496,3 +721,13 @@ def __init__(self, esim: AsyncEsimResource) -> None: self.remove = async_to_streamed_response_wrapper( esim.remove, ) + self.set_roaming = async_to_streamed_response_wrapper( + esim.set_roaming, + ) + self.status = async_to_streamed_response_wrapper( + esim.status, + ) + + @cached_property + def apn(self) -> AsyncApnResourceWithStreamingResponse: + return AsyncApnResourceWithStreamingResponse(self._esim.apn) diff --git a/src/mobilerun_sdk/resources/devices/files.py b/src/mobilerun_sdk/resources/devices/files.py index 0b20330..ffaba39 100644 --- a/src/mobilerun_sdk/resources/devices/files.py +++ b/src/mobilerun_sdk/resources/devices/files.py @@ -6,16 +6,9 @@ import httpx +from ..._files import deepcopy_with_paths from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, FileTypes, omit, not_given -from ..._utils import ( - is_given, - extract_files, - path_template, - maybe_transform, - strip_not_given, - deepcopy_minimal, - async_maybe_transform, -) +from ..._utils import is_given, extract_files, path_template, maybe_transform, strip_not_given, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -222,7 +215,7 @@ def upload( ), **(extra_headers or {}), } - body = deepcopy_minimal({"file": file}) + body = deepcopy_with_paths({"file": file}, [["file"]]) files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) # It should be noted that the actual Content-Type header that will be # sent to the server will contain a `boundary` parameter, e.g. @@ -434,7 +427,7 @@ async def upload( ), **(extra_headers or {}), } - body = deepcopy_minimal({"file": file}) + body = deepcopy_with_paths({"file": file}, [["file"]]) files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) # It should be noted that the actual Content-Type header that will be # sent to the server will contain a `boundary` parameter, e.g. diff --git a/src/mobilerun_sdk/resources/devices/keyboard.py b/src/mobilerun_sdk/resources/devices/keyboard.py index 66c14c6..97e2695 100644 --- a/src/mobilerun_sdk/resources/devices/keyboard.py +++ b/src/mobilerun_sdk/resources/devices/keyboard.py @@ -130,6 +130,7 @@ def write( *, text: str, clear: bool | Omit = omit, + error_rate: float | Omit = omit, stealth: bool | Omit = omit, wpm: int | Omit = omit, x_device_display_id: int | Omit = omit, @@ -143,9 +144,11 @@ def write( """Input text Args: - wpm: Words per minute for stealth typing. + error_rate: Per-character mistake rate for humantouch typing. - 0 uses portal default. + -1 uses server default. + + wpm: Words per minute for stealth typing. 0 uses portal default. extra_headers: Send extra headers @@ -170,6 +173,7 @@ def write( { "text": text, "clear": clear, + "error_rate": error_rate, "stealth": stealth, "wpm": wpm, }, @@ -292,6 +296,7 @@ async def write( *, text: str, clear: bool | Omit = omit, + error_rate: float | Omit = omit, stealth: bool | Omit = omit, wpm: int | Omit = omit, x_device_display_id: int | Omit = omit, @@ -305,9 +310,11 @@ async def write( """Input text Args: - wpm: Words per minute for stealth typing. + error_rate: Per-character mistake rate for humantouch typing. + + -1 uses server default. - 0 uses portal default. + wpm: Words per minute for stealth typing. 0 uses portal default. extra_headers: Send extra headers @@ -332,6 +339,7 @@ async def write( { "text": text, "clear": clear, + "error_rate": error_rate, "stealth": stealth, "wpm": wpm, }, diff --git a/src/mobilerun_sdk/resources/devices/language.py b/src/mobilerun_sdk/resources/devices/language.py new file mode 100644 index 0000000..498c78f --- /dev/null +++ b/src/mobilerun_sdk/resources/devices/language.py @@ -0,0 +1,303 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from ..._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from ...types.devices import language_set_params +from ...types.devices.language_get_response import LanguageGetResponse + +__all__ = ["LanguageResource", "AsyncLanguageResource"] + + +class LanguageResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> LanguageResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#accessing-raw-response-data-eg-headers + """ + return LanguageResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> LanguageResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#with_streaming_response + """ + return LanguageResourceWithStreamingResponse(self) + + def get( + self, + device_id: str, + *, + x_device_display_id: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> LanguageGetResponse: + """ + Get device language/locale + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not device_id: + raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = { + **strip_not_given( + {"X-Device-Display-ID": str(x_device_display_id) if is_given(x_device_display_id) else not_given} + ), + **(extra_headers or {}), + } + return self._get( + path_template("/devices/{device_id}/language", device_id=device_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LanguageGetResponse, + ) + + def set( + self, + device_id: str, + *, + locale: str, + restart: bool | Omit = omit, + x_device_display_id: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Set device language/locale + + Args: + locale: BCP-47 locale: a 2–3 letter language tag, optionally followed by a 4-letter + script and/or a 2-letter region (e.g. en-US, de-DE, ja-JP, zh-Hans-CN). + + restart: Restart zygote so the locale change takes full effect immediately. Without it, + the locale is written but won't fully apply until the next reboot. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not device_id: + raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + extra_headers = { + **strip_not_given( + {"X-Device-Display-ID": str(x_device_display_id) if is_given(x_device_display_id) else not_given} + ), + **(extra_headers or {}), + } + return self._post( + path_template("/devices/{device_id}/language", device_id=device_id), + body=maybe_transform( + { + "locale": locale, + "restart": restart, + }, + language_set_params.LanguageSetParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class AsyncLanguageResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncLanguageResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncLanguageResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncLanguageResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#with_streaming_response + """ + return AsyncLanguageResourceWithStreamingResponse(self) + + async def get( + self, + device_id: str, + *, + x_device_display_id: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> LanguageGetResponse: + """ + Get device language/locale + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not device_id: + raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = { + **strip_not_given( + {"X-Device-Display-ID": str(x_device_display_id) if is_given(x_device_display_id) else not_given} + ), + **(extra_headers or {}), + } + return await self._get( + path_template("/devices/{device_id}/language", device_id=device_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LanguageGetResponse, + ) + + async def set( + self, + device_id: str, + *, + locale: str, + restart: bool | Omit = omit, + x_device_display_id: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Set device language/locale + + Args: + locale: BCP-47 locale: a 2–3 letter language tag, optionally followed by a 4-letter + script and/or a 2-letter region (e.g. en-US, de-DE, ja-JP, zh-Hans-CN). + + restart: Restart zygote so the locale change takes full effect immediately. Without it, + the locale is written but won't fully apply until the next reboot. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not device_id: + raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + extra_headers = { + **strip_not_given( + {"X-Device-Display-ID": str(x_device_display_id) if is_given(x_device_display_id) else not_given} + ), + **(extra_headers or {}), + } + return await self._post( + path_template("/devices/{device_id}/language", device_id=device_id), + body=await async_maybe_transform( + { + "locale": locale, + "restart": restart, + }, + language_set_params.LanguageSetParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class LanguageResourceWithRawResponse: + def __init__(self, language: LanguageResource) -> None: + self._language = language + + self.get = to_raw_response_wrapper( + language.get, + ) + self.set = to_raw_response_wrapper( + language.set, + ) + + +class AsyncLanguageResourceWithRawResponse: + def __init__(self, language: AsyncLanguageResource) -> None: + self._language = language + + self.get = async_to_raw_response_wrapper( + language.get, + ) + self.set = async_to_raw_response_wrapper( + language.set, + ) + + +class LanguageResourceWithStreamingResponse: + def __init__(self, language: LanguageResource) -> None: + self._language = language + + self.get = to_streamed_response_wrapper( + language.get, + ) + self.set = to_streamed_response_wrapper( + language.set, + ) + + +class AsyncLanguageResourceWithStreamingResponse: + def __init__(self, language: AsyncLanguageResource) -> None: + self._language = language + + self.get = async_to_streamed_response_wrapper( + language.get, + ) + self.set = async_to_streamed_response_wrapper( + language.set, + ) diff --git a/src/mobilerun_sdk/resources/devices/location.py b/src/mobilerun_sdk/resources/devices/location.py index 799ac2c..f9469fc 100644 --- a/src/mobilerun_sdk/resources/devices/location.py +++ b/src/mobilerun_sdk/resources/devices/location.py @@ -16,7 +16,7 @@ ) from ..._base_client import make_request_options from ...types.devices import location_set_params -from ...types.devices.location_get_response import LocationGetResponse +from ...types.shared.location import Location __all__ = ["LocationResource", "AsyncLocationResource"] @@ -52,7 +52,7 @@ def get( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> LocationGetResponse: + ) -> Location: """ Get device location @@ -78,7 +78,7 @@ def get( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=LocationGetResponse, + cast_to=Location, ) def set( @@ -163,7 +163,7 @@ async def get( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> LocationGetResponse: + ) -> Location: """ Get device location @@ -189,7 +189,7 @@ async def get( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=LocationGetResponse, + cast_to=Location, ) async def set( diff --git a/src/mobilerun_sdk/resources/devices/proxy.py b/src/mobilerun_sdk/resources/devices/proxy.py index 5cf46cb..08e0c04 100644 --- a/src/mobilerun_sdk/resources/devices/proxy.py +++ b/src/mobilerun_sdk/resources/devices/proxy.py @@ -52,7 +52,6 @@ def connect( smart_ip: bool | Omit = omit, socks5: proxy_connect_params.Socks5 | Omit = omit, user: str | Omit = omit, - wireguard: str | Omit = omit, x_device_display_id: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -65,12 +64,10 @@ def connect( Connect proxy Args: - name: Proxy name (used for wireguard tunnel name) + name: Proxy name socks5: SOCKS5 proxy configuration (required for socks5). - wireguard: WireGuard tunnel configuration file content (required for wireguard). - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -99,7 +96,6 @@ def connect( "smart_ip": smart_ip, "socks5": socks5, "user": user, - "wireguard": wireguard, }, proxy_connect_params.ProxyConnectParams, ), @@ -222,7 +218,6 @@ async def connect( smart_ip: bool | Omit = omit, socks5: proxy_connect_params.Socks5 | Omit = omit, user: str | Omit = omit, - wireguard: str | Omit = omit, x_device_display_id: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -235,12 +230,10 @@ async def connect( Connect proxy Args: - name: Proxy name (used for wireguard tunnel name) + name: Proxy name socks5: SOCKS5 proxy configuration (required for socks5). - wireguard: WireGuard tunnel configuration file content (required for wireguard). - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -269,7 +262,6 @@ async def connect( "smart_ip": smart_ip, "socks5": socks5, "user": user, - "wireguard": wireguard, }, proxy_connect_params.ProxyConnectParams, ), diff --git a/src/mobilerun_sdk/resources/devices/state.py b/src/mobilerun_sdk/resources/devices/state.py index 032c8c4..789665e 100644 --- a/src/mobilerun_sdk/resources/devices/state.py +++ b/src/mobilerun_sdk/resources/devices/state.py @@ -86,6 +86,46 @@ def screenshot( cast_to=str, ) + def time( + self, + device_id: str, + *, + x_device_display_id: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> str: + """ + Device time + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not device_id: + raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = { + **strip_not_given( + {"X-Device-Display-ID": str(x_device_display_id) if is_given(x_device_display_id) else not_given} + ), + **(extra_headers or {}), + } + return self._get( + path_template("/devices/{device_id}/time", device_id=device_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=str, + ) + def ui( self, device_id: str, @@ -199,6 +239,46 @@ async def screenshot( cast_to=str, ) + async def time( + self, + device_id: str, + *, + x_device_display_id: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> str: + """ + Device time + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not device_id: + raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = { + **strip_not_given( + {"X-Device-Display-ID": str(x_device_display_id) if is_given(x_device_display_id) else not_given} + ), + **(extra_headers or {}), + } + return await self._get( + path_template("/devices/{device_id}/time", device_id=device_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=str, + ) + async def ui( self, device_id: str, @@ -252,6 +332,9 @@ def __init__(self, state: StateResource) -> None: self.screenshot = to_raw_response_wrapper( state.screenshot, ) + self.time = to_raw_response_wrapper( + state.time, + ) self.ui = to_raw_response_wrapper( state.ui, ) @@ -264,6 +347,9 @@ def __init__(self, state: AsyncStateResource) -> None: self.screenshot = async_to_raw_response_wrapper( state.screenshot, ) + self.time = async_to_raw_response_wrapper( + state.time, + ) self.ui = async_to_raw_response_wrapper( state.ui, ) @@ -276,6 +362,9 @@ def __init__(self, state: StateResource) -> None: self.screenshot = to_streamed_response_wrapper( state.screenshot, ) + self.time = to_streamed_response_wrapper( + state.time, + ) self.ui = to_streamed_response_wrapper( state.ui, ) @@ -288,6 +377,9 @@ def __init__(self, state: AsyncStateResource) -> None: self.screenshot = async_to_streamed_response_wrapper( state.screenshot, ) + self.time = async_to_streamed_response_wrapper( + state.time, + ) self.ui = async_to_streamed_response_wrapper( state.ui, ) diff --git a/src/mobilerun_sdk/resources/devices/tasks.py b/src/mobilerun_sdk/resources/devices/tasks.py index 62c8d6f..061d2ca 100644 --- a/src/mobilerun_sdk/resources/devices/tasks.py +++ b/src/mobilerun_sdk/resources/devices/tasks.py @@ -24,6 +24,8 @@ class TasksResource(SyncAPIResource): + """Device Management""" + @cached_property def with_raw_response(self) -> TasksResourceWithRawResponse: """ @@ -94,6 +96,8 @@ def list( class AsyncTasksResource(AsyncAPIResource): + """Device Management""" + @cached_property def with_raw_response(self) -> AsyncTasksResourceWithRawResponse: """ diff --git a/src/mobilerun_sdk/resources/devices/timezone.py b/src/mobilerun_sdk/resources/devices/timezone.py new file mode 100644 index 0000000..a136b2f --- /dev/null +++ b/src/mobilerun_sdk/resources/devices/timezone.py @@ -0,0 +1,277 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from ..._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from ...types.devices import timezone_set_params +from ...types.devices.timezone_get_response import TimezoneGetResponse + +__all__ = ["TimezoneResource", "AsyncTimezoneResource"] + + +class TimezoneResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> TimezoneResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#accessing-raw-response-data-eg-headers + """ + return TimezoneResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> TimezoneResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#with_streaming_response + """ + return TimezoneResourceWithStreamingResponse(self) + + def get( + self, + device_id: str, + *, + x_device_display_id: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TimezoneGetResponse: + """ + Get device timezone + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not device_id: + raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = { + **strip_not_given( + {"X-Device-Display-ID": str(x_device_display_id) if is_given(x_device_display_id) else not_given} + ), + **(extra_headers or {}), + } + return self._get( + path_template("/devices/{device_id}/timezone", device_id=device_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TimezoneGetResponse, + ) + + def set( + self, + device_id: str, + *, + timezone: str, + x_device_display_id: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Set device timezone + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not device_id: + raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + extra_headers = { + **strip_not_given( + {"X-Device-Display-ID": str(x_device_display_id) if is_given(x_device_display_id) else not_given} + ), + **(extra_headers or {}), + } + return self._post( + path_template("/devices/{device_id}/timezone", device_id=device_id), + body=maybe_transform({"timezone": timezone}, timezone_set_params.TimezoneSetParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class AsyncTimezoneResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncTimezoneResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncTimezoneResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncTimezoneResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#with_streaming_response + """ + return AsyncTimezoneResourceWithStreamingResponse(self) + + async def get( + self, + device_id: str, + *, + x_device_display_id: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TimezoneGetResponse: + """ + Get device timezone + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not device_id: + raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = { + **strip_not_given( + {"X-Device-Display-ID": str(x_device_display_id) if is_given(x_device_display_id) else not_given} + ), + **(extra_headers or {}), + } + return await self._get( + path_template("/devices/{device_id}/timezone", device_id=device_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TimezoneGetResponse, + ) + + async def set( + self, + device_id: str, + *, + timezone: str, + x_device_display_id: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Set device timezone + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not device_id: + raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + extra_headers = { + **strip_not_given( + {"X-Device-Display-ID": str(x_device_display_id) if is_given(x_device_display_id) else not_given} + ), + **(extra_headers or {}), + } + return await self._post( + path_template("/devices/{device_id}/timezone", device_id=device_id), + body=await async_maybe_transform({"timezone": timezone}, timezone_set_params.TimezoneSetParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class TimezoneResourceWithRawResponse: + def __init__(self, timezone: TimezoneResource) -> None: + self._timezone = timezone + + self.get = to_raw_response_wrapper( + timezone.get, + ) + self.set = to_raw_response_wrapper( + timezone.set, + ) + + +class AsyncTimezoneResourceWithRawResponse: + def __init__(self, timezone: AsyncTimezoneResource) -> None: + self._timezone = timezone + + self.get = async_to_raw_response_wrapper( + timezone.get, + ) + self.set = async_to_raw_response_wrapper( + timezone.set, + ) + + +class TimezoneResourceWithStreamingResponse: + def __init__(self, timezone: TimezoneResource) -> None: + self._timezone = timezone + + self.get = to_streamed_response_wrapper( + timezone.get, + ) + self.set = to_streamed_response_wrapper( + timezone.set, + ) + + +class AsyncTimezoneResourceWithStreamingResponse: + def __init__(self, timezone: AsyncTimezoneResource) -> None: + self._timezone = timezone + + self.get = async_to_streamed_response_wrapper( + timezone.get, + ) + self.set = async_to_streamed_response_wrapper( + timezone.set, + ) diff --git a/src/mobilerun_sdk/resources/hooks.py b/src/mobilerun_sdk/resources/hooks.py index c86af7d..185b05e 100644 --- a/src/mobilerun_sdk/resources/hooks.py +++ b/src/mobilerun_sdk/resources/hooks.py @@ -31,8 +31,6 @@ class HooksResource(SyncAPIResource): - """Webhooks API""" - @cached_property def with_raw_response(self) -> HooksResourceWithRawResponse: """ @@ -316,8 +314,6 @@ def unsubscribe( class AsyncHooksResource(AsyncAPIResource): - """Webhooks API""" - @cached_property def with_raw_response(self) -> AsyncHooksResourceWithRawResponse: """ diff --git a/src/mobilerun_sdk/resources/models.py b/src/mobilerun_sdk/resources/models.py index d1edbdd..4bb2764 100644 --- a/src/mobilerun_sdk/resources/models.py +++ b/src/mobilerun_sdk/resources/models.py @@ -20,6 +20,8 @@ class ModelsResource(SyncAPIResource): + """LLM Models""" + @cached_property def with_raw_response(self) -> ModelsResourceWithRawResponse: """ @@ -60,6 +62,8 @@ def list( class AsyncModelsResource(AsyncAPIResource): + """LLM Models""" + @cached_property def with_raw_response(self) -> AsyncModelsResourceWithRawResponse: """ diff --git a/src/mobilerun_sdk/resources/proxies.py b/src/mobilerun_sdk/resources/proxies.py index b3cf10c..cd4235f 100644 --- a/src/mobilerun_sdk/resources/proxies.py +++ b/src/mobilerun_sdk/resources/proxies.py @@ -28,6 +28,8 @@ class ProxiesResource(SyncAPIResource): + """Network Proxies""" + @cached_property def with_raw_response(self) -> ProxiesResourceWithRawResponse: """ @@ -349,6 +351,8 @@ def delete( class AsyncProxiesResource(AsyncAPIResource): + """Network Proxies""" + @cached_property def with_raw_response(self) -> AsyncProxiesResourceWithRawResponse: """ diff --git a/src/mobilerun_sdk/types/__init__.py b/src/mobilerun_sdk/types/__init__.py index 6a946aa..ba767ee 100644 --- a/src/mobilerun_sdk/types/__init__.py +++ b/src/mobilerun_sdk/types/__init__.py @@ -6,6 +6,8 @@ from .device import Device as Device from .shared import ( Meta as Meta, + Socks5 as Socks5, + Location as Location, DeviceSpec as DeviceSpec, Pagination as Pagination, DeviceCarrier as DeviceCarrier, @@ -13,7 +15,6 @@ PaginationMeta as PaginationMeta, DeviceIdentifiers as DeviceIdentifiers, ) -from .carrier import Carrier as Carrier from .profile import Profile as Profile from .task_status import TaskStatus as TaskStatus from .proxy_config import ProxyConfig as ProxyConfig @@ -31,6 +32,7 @@ from .task_list_response import TaskListResponse as TaskListResponse from .task_stop_response import TaskStopResponse as TaskStopResponse from .agent_list_response import AgentListResponse as AgentListResponse +from .app_delete_response import AppDeleteResponse as AppDeleteResponse from .carrier_list_params import CarrierListParams as CarrierListParams from .hook_perform_params import HookPerformParams as HookPerformParams from .model_list_response import ModelListResponse as ModelListResponse @@ -42,6 +44,7 @@ from .device_create_params import DeviceCreateParams as DeviceCreateParams from .device_list_response import DeviceListResponse as DeviceListResponse from .hook_update_response import HookUpdateResponse as HookUpdateResponse +from .app_retrieve_response import AppRetrieveResponse as AppRetrieveResponse from .carrier_create_params import CarrierCreateParams as CarrierCreateParams from .carrier_list_response import CarrierListResponse as CarrierListResponse from .carrier_lookup_params import CarrierLookupParams as CarrierLookupParams @@ -59,17 +62,26 @@ from .device_set_name_params import DeviceSetNameParams as DeviceSetNameParams from .hook_retrieve_response import HookRetrieveResponse as HookRetrieveResponse from .task_retrieve_response import TaskRetrieveResponse as TaskRetrieveResponse +from .carrier_create_response import CarrierCreateResponse as CarrierCreateResponse from .carrier_delete_response import CarrierDeleteResponse as CarrierDeleteResponse +from .carrier_lookup_response import CarrierLookupResponse as CarrierLookupResponse +from .carrier_update_response import CarrierUpdateResponse as CarrierUpdateResponse from .device_terminate_params import DeviceTerminateParams as DeviceTerminateParams from .hook_subscribe_response import HookSubscribeResponse as HookSubscribeResponse from .profile_delete_response import ProfileDeleteResponse as ProfileDeleteResponse from .proxy_retrieve_response import ProxyRetrieveResponse as ProxyRetrieveResponse +from .app_mark_failed_response import AppMarkFailedResponse as AppMarkFailedResponse from .credential_list_response import CredentialListResponse as CredentialListResponse from .task_get_status_response import TaskGetStatusResponse as TaskGetStatusResponse from .task_run_streamed_params import TaskRunStreamedParams as TaskRunStreamedParams from .task_send_message_params import TaskSendMessageParams as TaskSendMessageParams +from .carrier_retrieve_response import CarrierRetrieveResponse as CarrierRetrieveResponse from .hook_unsubscribe_response import HookUnsubscribeResponse as HookUnsubscribeResponse from .package_credentials_param import PackageCredentialsParam as PackageCredentialsParam from .task_send_message_response import TaskSendMessageResponse as TaskSendMessageResponse +from .app_confirm_upload_response import AppConfirmUploadResponse as AppConfirmUploadResponse +from .device_fingerprint_response import DeviceFingerprintResponse as DeviceFingerprintResponse from .task_get_trajectory_response import TaskGetTrajectoryResponse as TaskGetTrajectoryResponse from .hook_get_sample_data_response import HookGetSampleDataResponse as HookGetSampleDataResponse +from .app_create_signed_upload_url_params import AppCreateSignedUploadURLParams as AppCreateSignedUploadURLParams +from .app_create_signed_upload_url_response import AppCreateSignedUploadURLResponse as AppCreateSignedUploadURLResponse diff --git a/src/mobilerun_sdk/types/app_confirm_upload_response.py b/src/mobilerun_sdk/types/app_confirm_upload_response.py new file mode 100644 index 0000000..da579dc --- /dev/null +++ b/src/mobilerun_sdk/types/app_confirm_upload_response.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["AppConfirmUploadResponse"] + + +class AppConfirmUploadResponse(BaseModel): + message: str + + success: Literal[True] diff --git a/src/mobilerun_sdk/types/app_create_signed_upload_url_params.py b/src/mobilerun_sdk/types/app_create_signed_upload_url_params.py new file mode 100644 index 0000000..a374c4b --- /dev/null +++ b/src/mobilerun_sdk/types/app_create_signed_upload_url_params.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["AppCreateSignedUploadURLParams", "File"] + + +class AppCreateSignedUploadURLParams(TypedDict, total=False): + bundle_id: Required[Annotated[str, PropertyInfo(alias="bundleId")]] + + display_name: Required[Annotated[str, PropertyInfo(alias="displayName")]] + + files: Required[Iterable[File]] + + size_bytes: Required[Annotated[float, PropertyInfo(alias="sizeBytes")]] + + version_code: Required[Annotated[float, PropertyInfo(alias="versionCode")]] + + version_name: Required[Annotated[str, PropertyInfo(alias="versionName")]] + + country: str + """Country code for Search Results""" + + description: str + + developer_name: Annotated[str, PropertyInfo(alias="developerName")] + + icon_url: Annotated[str, PropertyInfo(alias="iconURL")] + + platform: Literal["android", "ios"] + + target_sdk: Annotated[float, PropertyInfo(alias="targetSdk")] + + +class File(TypedDict, total=False): + content_type: Required[Annotated[str, PropertyInfo(alias="contentType")]] + + file_name: Required[Annotated[str, PropertyInfo(alias="fileName")]] diff --git a/src/mobilerun_sdk/types/app_create_signed_upload_url_response.py b/src/mobilerun_sdk/types/app_create_signed_upload_url_response.py new file mode 100644 index 0000000..8d15581 --- /dev/null +++ b/src/mobilerun_sdk/types/app_create_signed_upload_url_response.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["AppCreateSignedUploadURLResponse", "R2UploadURL"] + + +class R2UploadURL(BaseModel): + file_name: str = FieldInfo(alias="fileName") + + r2_upload_url: str = FieldInfo(alias="r2UploadUrl") + + +class AppCreateSignedUploadURLResponse(BaseModel): + app_id: str = FieldInfo(alias="appId") + """App ID in the database""" + + r2_upload_urls: List[R2UploadURL] = FieldInfo(alias="r2UploadUrls") + """Pre-signed Cloudflare R2 URLs for uploading app files""" + + version_id: str = FieldInfo(alias="versionId") + """App version ID in the database""" diff --git a/src/mobilerun_sdk/types/app_delete_response.py b/src/mobilerun_sdk/types/app_delete_response.py new file mode 100644 index 0000000..5dce5fa --- /dev/null +++ b/src/mobilerun_sdk/types/app_delete_response.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["AppDeleteResponse"] + + +class AppDeleteResponse(BaseModel): + message: str + + success: Literal[True] diff --git a/src/mobilerun_sdk/types/app_list_params.py b/src/mobilerun_sdk/types/app_list_params.py index 01fe905..319c937 100644 --- a/src/mobilerun_sdk/types/app_list_params.py +++ b/src/mobilerun_sdk/types/app_list_params.py @@ -16,8 +16,10 @@ class AppListParams(TypedDict, total=False): page_size: Annotated[int, PropertyInfo(alias="pageSize")] + platform: Literal["all", "android", "ios"] + query: str sort_by: Annotated[Literal["createdAt", "name"], PropertyInfo(alias="sortBy")] - source: Literal["all", "uploaded", "store", "queued"] + status: Literal["all", "queued", "available", "failed"] diff --git a/src/mobilerun_sdk/types/app_list_response.py b/src/mobilerun_sdk/types/app_list_response.py index e211771..f7e2394 100644 --- a/src/mobilerun_sdk/types/app_list_response.py +++ b/src/mobilerun_sdk/types/app_list_response.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, List, Union, Optional +from typing import List, Optional from datetime import datetime from typing_extensions import Literal @@ -9,25 +9,23 @@ from .._models import BaseModel from .shared.pagination import Pagination -__all__ = ["AppListResponse", "Count", "Item"] +__all__ = ["AppListResponse", "Count", "Item", "ItemVersion"] class Count(BaseModel): available_count: float = FieldInfo(alias="availableCount") - queued_count: float = FieldInfo(alias="queuedCount") + failed_count: float = FieldInfo(alias="failedCount") - store_count: float = FieldInfo(alias="storeCount") + queued_count: float = FieldInfo(alias="queuedCount") total_count: float = FieldInfo(alias="totalCount") - upload_count: float = FieldInfo(alias="uploadCount") - -class Item(BaseModel): +class ItemVersion(BaseModel): id: str - category_name: Optional[str] = FieldInfo(alias="categoryName", default=None) + app_id: str = FieldInfo(alias="appId") country: Literal[ "AF", @@ -286,47 +284,45 @@ class Item(BaseModel): created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) - description: Optional[str] = None + queued_at: Optional[datetime] = FieldInfo(alias="queuedAt", default=None) - developer_name: Optional[str] = FieldInfo(alias="developerName", default=None) + size_bytes: Optional[int] = FieldInfo(alias="sizeBytes", default=None) - display_name: str = FieldInfo(alias="displayName") + source: Literal["user", "system", "portal"] - expected_files: Union[str, float, bool, Dict[str, Optional[object]], List[Optional[object]], None] = FieldInfo( - alias="expectedFiles", default=None - ) + status: Literal["queued", "available", "failed"] - icon_url: str = FieldInfo(alias="iconURL") + target_sdk: Optional[int] = FieldInfo(alias="targetSdk", default=None) - package_name: str = FieldInfo(alias="packageName") + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) - privacy_policy_url: Optional[str] = FieldInfo(alias="privacyPolicyUrl", default=None) + user_id: Optional[str] = FieldInfo(alias="userId", default=None) - queued_at: Optional[datetime] = FieldInfo(alias="queuedAt", default=None) + version_code: int = FieldInfo(alias="versionCode") - rating_count: Optional[int] = FieldInfo(alias="ratingCount", default=None) + version_name: str = FieldInfo(alias="versionName") - rating_score: Optional[str] = FieldInfo(alias="ratingScore", default=None) - size_bytes: Optional[int] = FieldInfo(alias="sizeBytes", default=None) +class Item(BaseModel): + id: str - source: Literal["uploaded", "store"] + bundle_id: str = FieldInfo(alias="bundleId") - status: Literal["queued", "available", "failed"] + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) - stealth_tier: Optional[Literal["tier1", "tier2", "tier3"]] = FieldInfo(alias="stealthTier", default=None) + description: Optional[str] = None - target_sdk: Optional[int] = FieldInfo(alias="targetSdk", default=None) + developer_name: Optional[str] = FieldInfo(alias="developerName", default=None) - type: Literal["android", "ios"] + display_name: str = FieldInfo(alias="displayName") - updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) + icon_url: str = FieldInfo(alias="iconURL") - user_id: Optional[str] = FieldInfo(alias="userId", default=None) + platform: Literal["android", "ios"] - version_code: int = FieldInfo(alias="versionCode") + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) - version_name: str = FieldInfo(alias="versionName") + version: ItemVersion class AppListResponse(BaseModel): diff --git a/src/mobilerun_sdk/types/app_mark_failed_response.py b/src/mobilerun_sdk/types/app_mark_failed_response.py new file mode 100644 index 0000000..4df598b --- /dev/null +++ b/src/mobilerun_sdk/types/app_mark_failed_response.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["AppMarkFailedResponse"] + + +class AppMarkFailedResponse(BaseModel): + message: str + + success: Literal[True] diff --git a/src/mobilerun_sdk/types/app_retrieve_response.py b/src/mobilerun_sdk/types/app_retrieve_response.py new file mode 100644 index 0000000..57fd551 --- /dev/null +++ b/src/mobilerun_sdk/types/app_retrieve_response.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["AppRetrieveResponse", "Data"] + + +class Data(BaseModel): + id: str + + bundle_id: str = FieldInfo(alias="bundleId") + + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) + + description: Optional[str] = None + + developer_name: Optional[str] = FieldInfo(alias="developerName", default=None) + + display_name: str = FieldInfo(alias="displayName") + + icon_url: str = FieldInfo(alias="iconURL") + + platform: Literal["android", "ios"] + + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) + + +class AppRetrieveResponse(BaseModel): + data: Data diff --git a/src/mobilerun_sdk/types/carrier.py b/src/mobilerun_sdk/types/carrier_create_response.py similarity index 90% rename from src/mobilerun_sdk/types/carrier.py rename to src/mobilerun_sdk/types/carrier_create_response.py index 7289eec..643be23 100644 --- a/src/mobilerun_sdk/types/carrier.py +++ b/src/mobilerun_sdk/types/carrier_create_response.py @@ -7,10 +7,10 @@ from .._models import BaseModel -__all__ = ["Carrier"] +__all__ = ["CarrierCreateResponse"] -class Carrier(BaseModel): +class CarrierCreateResponse(BaseModel): id: int company: str diff --git a/src/mobilerun_sdk/types/carrier_list_response.py b/src/mobilerun_sdk/types/carrier_list_response.py index 8d6b97b..098754a 100644 --- a/src/mobilerun_sdk/types/carrier_list_response.py +++ b/src/mobilerun_sdk/types/carrier_list_response.py @@ -1,18 +1,59 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional +from datetime import datetime from pydantic import Field as FieldInfo -from .carrier import Carrier from .._models import BaseModel from .shared.meta import Meta -__all__ = ["CarrierListResponse"] +__all__ = ["CarrierListResponse", "Item"] + + +class Item(BaseModel): + id: int + + company: str + + country: str + + country_code: str + + country_iso: str + + created_at: datetime + + detail_url: str + + gsm_bands: str + + lte_bands: str + + mcc: str + + mnc: str + + mobile_prefix: str + + nsn_size: str + + number_format: str + + operator: str + + protocols: str + + umts_bands: str + + website: str + + schema_: Optional[str] = FieldInfo(alias="$schema", default=None) + """A URL to the JSON Schema for this object.""" class CarrierListResponse(BaseModel): - items: Optional[List[Carrier]] = None + items: Optional[List[Item]] = None pagination: Meta diff --git a/src/mobilerun_sdk/types/carrier_lookup_response.py b/src/mobilerun_sdk/types/carrier_lookup_response.py new file mode 100644 index 0000000..925e0d6 --- /dev/null +++ b/src/mobilerun_sdk/types/carrier_lookup_response.py @@ -0,0 +1,51 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["CarrierLookupResponse"] + + +class CarrierLookupResponse(BaseModel): + id: int + + company: str + + country: str + + country_code: str + + country_iso: str + + created_at: datetime + + detail_url: str + + gsm_bands: str + + lte_bands: str + + mcc: str + + mnc: str + + mobile_prefix: str + + nsn_size: str + + number_format: str + + operator: str + + protocols: str + + umts_bands: str + + website: str + + schema_: Optional[str] = FieldInfo(alias="$schema", default=None) + """A URL to the JSON Schema for this object.""" diff --git a/src/mobilerun_sdk/types/carrier_retrieve_response.py b/src/mobilerun_sdk/types/carrier_retrieve_response.py new file mode 100644 index 0000000..f4bf58c --- /dev/null +++ b/src/mobilerun_sdk/types/carrier_retrieve_response.py @@ -0,0 +1,51 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["CarrierRetrieveResponse"] + + +class CarrierRetrieveResponse(BaseModel): + id: int + + company: str + + country: str + + country_code: str + + country_iso: str + + created_at: datetime + + detail_url: str + + gsm_bands: str + + lte_bands: str + + mcc: str + + mnc: str + + mobile_prefix: str + + nsn_size: str + + number_format: str + + operator: str + + protocols: str + + umts_bands: str + + website: str + + schema_: Optional[str] = FieldInfo(alias="$schema", default=None) + """A URL to the JSON Schema for this object.""" diff --git a/src/mobilerun_sdk/types/carrier_update_response.py b/src/mobilerun_sdk/types/carrier_update_response.py new file mode 100644 index 0000000..ba0d7ef --- /dev/null +++ b/src/mobilerun_sdk/types/carrier_update_response.py @@ -0,0 +1,51 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["CarrierUpdateResponse"] + + +class CarrierUpdateResponse(BaseModel): + id: int + + company: str + + country: str + + country_code: str + + country_iso: str + + created_at: datetime + + detail_url: str + + gsm_bands: str + + lte_bands: str + + mcc: str + + mnc: str + + mobile_prefix: str + + nsn_size: str + + number_format: str + + operator: str + + protocols: str + + umts_bands: str + + website: str + + schema_: Optional[str] = FieldInfo(alias="$schema", default=None) + """A URL to the JSON Schema for this object.""" diff --git a/src/mobilerun_sdk/types/device_create_params.py b/src/mobilerun_sdk/types/device_create_params.py index 751f16e..a631b9c 100644 --- a/src/mobilerun_sdk/types/device_create_params.py +++ b/src/mobilerun_sdk/types/device_create_params.py @@ -3,45 +3,54 @@ from __future__ import annotations from typing import Optional -from typing_extensions import Literal, Required, Annotated, TypedDict +from typing_extensions import Literal, Annotated, TypedDict from .._types import SequenceNotStr from .._utils import PropertyInfo +from .shared_params.socks5 import Socks5 +from .shared_params.location import Location from .shared_params.device_carrier import DeviceCarrier from .shared_params.device_identifiers import DeviceIdentifiers -__all__ = ["DeviceCreateParams", "Proxy", "ProxySocks5"] +__all__ = ["DeviceCreateParams", "Proxy"] class DeviceCreateParams(TypedDict, total=False): + query_country: Annotated[str, PropertyInfo(alias="country")] + """ISO 3166-1 alpha-2 country code. + + If omitted the system picks the country with the most availability. + """ + device_type: Annotated[ - Literal[ - "dedicated_physical_device", "dedicated_premium_device", "dedicated_emulated_device", "dedicated_ios_device" - ], + Literal["dedicated_physical_device", "dedicated_premium_device", "dedicated_ios_device"], PropertyInfo(alias="deviceType"), ] + profile_id: Annotated[str, PropertyInfo(alias="profileId")] + """Profile ID to use as device spec""" + + android_version: Annotated[int, PropertyInfo(alias="androidVersion")] + apps: Optional[SequenceNotStr[str]] carrier: DeviceCarrier + body_country: Annotated[str, PropertyInfo(alias="country")] + files: Optional[SequenceNotStr[str]] identifiers: DeviceIdentifiers - name: str - - proxy: Proxy - + locale: str -class ProxySocks5(TypedDict, total=False): - host: Required[str] + location: Location - password: Required[str] + name: str - port: Required[int] + proxy: Proxy - user: Required[str] + timezone: str class Proxy(TypedDict, total=False): @@ -49,6 +58,4 @@ class Proxy(TypedDict, total=False): smart_ip: Annotated[bool, PropertyInfo(alias="smartIp")] - socks5: ProxySocks5 - - wireguard: str + socks5: Socks5 diff --git a/src/mobilerun_sdk/types/device_fingerprint_response.py b/src/mobilerun_sdk/types/device_fingerprint_response.py new file mode 100644 index 0000000..4f8f410 --- /dev/null +++ b/src/mobilerun_sdk/types/device_fingerprint_response.py @@ -0,0 +1,48 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .shared.device_carrier import DeviceCarrier +from .shared.device_identifiers import DeviceIdentifiers + +__all__ = ["DeviceFingerprintResponse", "Display", "Model"] + + +class Display(BaseModel): + density_dpi: Optional[int] = FieldInfo(alias="densityDpi", default=None) + + height: Optional[int] = None + + width: Optional[int] = None + + +class Model(BaseModel): + aosp_version: Optional[str] = FieldInfo(alias="aospVersion", default=None) + + brand: Optional[str] = None + + device: Optional[str] = None + + hardware: Optional[str] = None + + image_repository: Optional[str] = FieldInfo(alias="imageRepository", default=None) + + manufacturer: Optional[str] = None + + model: Optional[str] = None + + +class DeviceFingerprintResponse(BaseModel): + carrier: DeviceCarrier + + display: Display + + identifiers: DeviceIdentifiers + + model: Model + + schema_: Optional[str] = FieldInfo(alias="$schema", default=None) + """A URL to the JSON Schema for this object.""" diff --git a/src/mobilerun_sdk/types/device_list_params.py b/src/mobilerun_sdk/types/device_list_params.py index 037f565..fcec1bb 100644 --- a/src/mobilerun_sdk/types/device_list_params.py +++ b/src/mobilerun_sdk/types/device_list_params.py @@ -26,9 +26,19 @@ class DeviceListParams(TypedDict, total=False): provider_id: Annotated[str, PropertyInfo(alias="providerId")] state: Optional[ - List[Literal["creating", "assigned", "ready", "rebooting", "migrating", "terminated", "maintenance", "unknown"]] + List[ + Literal[ + "creating", + "assigned", + "ready", + "rebooting", + "migrating", + "resetting", + "terminated", + "maintenance", + "unknown", + ] + ] ] - type: Literal[ - "dedicated_physical_device", "dedicated_premium_device", "dedicated_emulated_device", "dedicated_ios_device" - ] + type: Literal["dedicated_physical_device", "dedicated_premium_device", "dedicated_ios_device"] diff --git a/src/mobilerun_sdk/types/devices/__init__.py b/src/mobilerun_sdk/types/devices/__init__.py index c5f89e4..ea98848 100644 --- a/src/mobilerun_sdk/types/devices/__init__.py +++ b/src/mobilerun_sdk/types/devices/__init__.py @@ -20,25 +20,28 @@ from .file_list_response import FileListResponse as FileListResponse from .file_upload_params import FileUploadParams as FileUploadParams from .task_list_response import TaskListResponse as TaskListResponse -from .time_time_response import TimeTimeResponse as TimeTimeResponse from .action_swipe_params import ActionSwipeParams as ActionSwipeParams from .keyboard_key_params import KeyboardKeyParams as KeyboardKeyParams +from .language_set_params import LanguageSetParams as LanguageSetParams from .location_set_params import LocationSetParams as LocationSetParams from .package_list_params import PackageListParams as PackageListParams +from .state_time_response import StateTimeResponse as StateTimeResponse +from .timezone_set_params import TimezoneSetParams as TimezoneSetParams from .action_global_params import ActionGlobalParams as ActionGlobalParams from .esim_activate_params import EsimActivateParams as EsimActivateParams +from .esim_status_response import EsimStatusResponse as EsimStatusResponse from .file_download_params import FileDownloadParams as FileDownloadParams from .proxy_connect_params import ProxyConnectParams as ProxyConnectParams from .keyboard_write_params import KeyboardWriteParams as KeyboardWriteParams -from .location_get_response import LocationGetResponse as LocationGetResponse +from .language_get_response import LanguageGetResponse as LanguageGetResponse from .package_list_response import PackageListResponse as PackageListResponse from .profile_update_params import ProfileUpdateParams as ProfileUpdateParams from .proxy_status_response import ProxyStatusResponse as ProxyStatusResponse +from .timezone_get_response import TimezoneGetResponse as TimezoneGetResponse from .esim_activate_response import EsimActivateResponse as EsimActivateResponse from .file_download_response import FileDownloadResponse as FileDownloadResponse -from .time_timezone_response import TimeTimezoneResponse as TimeTimezoneResponse +from .esim_set_roaming_params import EsimSetRoamingParams as EsimSetRoamingParams from .state_screenshot_params import StateScreenshotParams as StateScreenshotParams -from .time_set_timezone_params import TimeSetTimezoneParams as TimeSetTimezoneParams from .state_screenshot_response import StateScreenshotResponse as StateScreenshotResponse from .action_overlay_visible_response import ActionOverlayVisibleResponse as ActionOverlayVisibleResponse from .action_set_overlay_visible_params import ActionSetOverlayVisibleParams as ActionSetOverlayVisibleParams diff --git a/src/mobilerun_sdk/types/devices/app_install_params.py b/src/mobilerun_sdk/types/devices/app_install_params.py index 915231c..8db67fe 100644 --- a/src/mobilerun_sdk/types/devices/app_install_params.py +++ b/src/mobilerun_sdk/types/devices/app_install_params.py @@ -2,14 +2,32 @@ from __future__ import annotations -from typing_extensions import Required, Annotated, TypedDict +from typing import Union +from typing_extensions import Required, Annotated, TypeAlias, TypedDict from ..._utils import PropertyInfo -__all__ = ["AppInstallParams"] +__all__ = ["AppInstallParams", "Variant0", "Variant1"] -class AppInstallParams(TypedDict, total=False): +class Variant0(TypedDict, total=False): + bundle_id: Required[Annotated[str, PropertyInfo(alias="bundleId")]] + """iOS bundle identifier (e.g. com.example.app)""" + + package_name: Annotated[str, PropertyInfo(alias="packageName")] + """Android package name (e.g. com.example.app)""" + + x_device_display_id: Annotated[int, PropertyInfo(alias="X-Device-Display-ID")] + + +class Variant1(TypedDict, total=False): package_name: Required[Annotated[str, PropertyInfo(alias="packageName")]] + """Android package name (e.g. com.example.app)""" + + bundle_id: Annotated[str, PropertyInfo(alias="bundleId")] + """iOS bundle identifier (e.g. com.example.app)""" x_device_display_id: Annotated[int, PropertyInfo(alias="X-Device-Display-ID")] + + +AppInstallParams: TypeAlias = Union[Variant0, Variant1] diff --git a/src/mobilerun_sdk/types/devices/esim/__init__.py b/src/mobilerun_sdk/types/devices/esim/__init__.py new file mode 100644 index 0000000..7fb2236 --- /dev/null +++ b/src/mobilerun_sdk/types/devices/esim/__init__.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .apn_create_params import ApnCreateParams as ApnCreateParams +from .apn_list_response import ApnListResponse as ApnListResponse +from .apn_select_params import ApnSelectParams as ApnSelectParams diff --git a/src/mobilerun_sdk/types/devices/esim/apn_create_params.py b/src/mobilerun_sdk/types/devices/esim/apn_create_params.py new file mode 100644 index 0000000..ffb2434 --- /dev/null +++ b/src/mobilerun_sdk/types/devices/esim/apn_create_params.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, Annotated, TypedDict + +from ...._utils import PropertyInfo + +__all__ = ["ApnCreateParams"] + + +class ApnCreateParams(TypedDict, total=False): + apn: Required[str] + + mcc: Required[str] + + mnc: Required[str] + + name: Required[str] + + protocol: Required[str] + + roaming_protocol: Required[Annotated[str, PropertyInfo(alias="roamingProtocol")]] + + sub_id: Required[Annotated[int, PropertyInfo(alias="subId")]] + + type: Required[str] + + x_device_display_id: Annotated[int, PropertyInfo(alias="X-Device-Display-ID")] diff --git a/src/mobilerun_sdk/types/devices/esim/apn_list_response.py b/src/mobilerun_sdk/types/devices/esim/apn_list_response.py new file mode 100644 index 0000000..41bd145 --- /dev/null +++ b/src/mobilerun_sdk/types/devices/esim/apn_list_response.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from typing_extensions import TypeAlias + +from pydantic import Field as FieldInfo + +from ...._models import BaseModel + +__all__ = ["ApnListResponse", "ApnListResponseItem"] + + +class ApnListResponseItem(BaseModel): + id: int + + apn: str + + is_preferred: bool = FieldInfo(alias="isPreferred") + + mcc: str + + mnc: str + + name: str + + protocol: str + + roaming_protocol: str = FieldInfo(alias="roamingProtocol") + + sub_id: int = FieldInfo(alias="subId") + + type: str + + +ApnListResponse: TypeAlias = List[ApnListResponseItem] diff --git a/src/mobilerun_sdk/types/devices/esim/apn_select_params.py b/src/mobilerun_sdk/types/devices/esim/apn_select_params.py new file mode 100644 index 0000000..ca44100 --- /dev/null +++ b/src/mobilerun_sdk/types/devices/esim/apn_select_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, Annotated, TypedDict + +from ...._utils import PropertyInfo + +__all__ = ["ApnSelectParams"] + + +class ApnSelectParams(TypedDict, total=False): + apn_id: Required[Annotated[int, PropertyInfo(alias="apnId")]] + + sub_id: Required[Annotated[int, PropertyInfo(alias="subId")]] + + x_device_display_id: Annotated[int, PropertyInfo(alias="X-Device-Display-ID")] diff --git a/src/mobilerun_sdk/types/devices/esim_activate_params.py b/src/mobilerun_sdk/types/devices/esim_activate_params.py index 643eebc..e5c9e74 100644 --- a/src/mobilerun_sdk/types/devices/esim_activate_params.py +++ b/src/mobilerun_sdk/types/devices/esim_activate_params.py @@ -12,8 +12,15 @@ class EsimActivateParams(TypedDict, total=False): enable: Required[bool] - matching_id: Required[Annotated[str, PropertyInfo(alias="matchingId")]] - sm_dp_addr: Required[Annotated[str, PropertyInfo(alias="smDpAddr")]] + confirmation_code: Annotated[str, PropertyInfo(alias="confirmationCode")] + """Optional carrier-issued confirmation code (the 4th LPA segment). + + Required only for plans whose SM-DP+ challenges the device for one. Requires + matchingId — the LPA spec only interprets segment 4 when segment 3 is present. + """ + + matching_id: Annotated[str, PropertyInfo(alias="matchingId")] + x_device_display_id: Annotated[int, PropertyInfo(alias="X-Device-Display-ID")] diff --git a/src/mobilerun_sdk/types/devices/esim_set_roaming_params.py b/src/mobilerun_sdk/types/devices/esim_set_roaming_params.py new file mode 100644 index 0000000..218544c --- /dev/null +++ b/src/mobilerun_sdk/types/devices/esim_set_roaming_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["EsimSetRoamingParams"] + + +class EsimSetRoamingParams(TypedDict, total=False): + enabled: Required[bool] + + x_device_display_id: Annotated[int, PropertyInfo(alias="X-Device-Display-ID")] diff --git a/src/mobilerun_sdk/types/devices/esim_status_response.py b/src/mobilerun_sdk/types/devices/esim_status_response.py new file mode 100644 index 0000000..9a42b6e --- /dev/null +++ b/src/mobilerun_sdk/types/devices/esim_status_response.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from typing_extensions import TypeAlias + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["EsimStatusResponse", "EsimStatusResponseItem"] + + +class EsimStatusResponseItem(BaseModel): + carrier: str + + data_roaming_enabled: bool = FieldInfo(alias="dataRoamingEnabled") + + data_state: str = FieldInfo(alias="dataState") + + is_roaming: bool = FieldInfo(alias="isRoaming") + + mobile_data_enabled: bool = FieldInfo(alias="mobileDataEnabled") + + network_type: str = FieldInfo(alias="networkType") + + operator: str + + phone_type: str = FieldInfo(alias="phoneType") + + sim_state: str = FieldInfo(alias="simState") + + sub_id: int = FieldInfo(alias="subId") + + +EsimStatusResponse: TypeAlias = List[EsimStatusResponseItem] diff --git a/src/mobilerun_sdk/types/devices/keyboard_write_params.py b/src/mobilerun_sdk/types/devices/keyboard_write_params.py index f0fa72f..c1340d7 100644 --- a/src/mobilerun_sdk/types/devices/keyboard_write_params.py +++ b/src/mobilerun_sdk/types/devices/keyboard_write_params.py @@ -14,6 +14,9 @@ class KeyboardWriteParams(TypedDict, total=False): clear: bool + error_rate: Annotated[float, PropertyInfo(alias="errorRate")] + """Per-character mistake rate for humantouch typing. -1 uses server default.""" + stealth: bool wpm: int diff --git a/src/mobilerun_sdk/types/devices/language_get_response.py b/src/mobilerun_sdk/types/devices/language_get_response.py new file mode 100644 index 0000000..22c2c78 --- /dev/null +++ b/src/mobilerun_sdk/types/devices/language_get_response.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["LanguageGetResponse"] + + +class LanguageGetResponse(BaseModel): + locale: str + + schema_: Optional[str] = FieldInfo(alias="$schema", default=None) + """A URL to the JSON Schema for this object.""" diff --git a/src/mobilerun_sdk/types/devices/language_set_params.py b/src/mobilerun_sdk/types/devices/language_set_params.py new file mode 100644 index 0000000..1cfb643 --- /dev/null +++ b/src/mobilerun_sdk/types/devices/language_set_params.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["LanguageSetParams"] + + +class LanguageSetParams(TypedDict, total=False): + locale: Required[str] + """ + BCP-47 locale: a 2–3 letter language tag, optionally followed by a 4-letter + script and/or a 2-letter region (e.g. en-US, de-DE, ja-JP, zh-Hans-CN). + """ + + restart: bool + """Restart zygote so the locale change takes full effect immediately. + + Without it, the locale is written but won't fully apply until the next reboot. + """ + + x_device_display_id: Annotated[int, PropertyInfo(alias="X-Device-Display-ID")] diff --git a/src/mobilerun_sdk/types/devices/proxy_connect_params.py b/src/mobilerun_sdk/types/devices/proxy_connect_params.py index d177e00..5afc504 100644 --- a/src/mobilerun_sdk/types/devices/proxy_connect_params.py +++ b/src/mobilerun_sdk/types/devices/proxy_connect_params.py @@ -13,7 +13,7 @@ class ProxyConnectParams(TypedDict, total=False): host: str name: str - """Proxy name (used for wireguard tunnel name)""" + """Proxy name""" password: str @@ -26,9 +26,6 @@ class ProxyConnectParams(TypedDict, total=False): user: str - wireguard: str - """WireGuard tunnel configuration file content (required for wireguard).""" - x_device_display_id: Annotated[int, PropertyInfo(alias="X-Device-Display-ID")] diff --git a/src/mobilerun_sdk/types/devices/proxy_status_response.py b/src/mobilerun_sdk/types/devices/proxy_status_response.py index b457c9d..668f612 100644 --- a/src/mobilerun_sdk/types/devices/proxy_status_response.py +++ b/src/mobilerun_sdk/types/devices/proxy_status_response.py @@ -16,7 +16,7 @@ class ProxyStatusResponse(BaseModel): """Active proxy name""" protocol: Optional[str] = None - """Active proxy protocol (socks5 or wireguard).""" + """Active proxy protocol (socks5).""" schema_: Optional[str] = FieldInfo(alias="$schema", default=None) """A URL to the JSON Schema for this object.""" diff --git a/src/mobilerun_sdk/types/devices/time_time_response.py b/src/mobilerun_sdk/types/devices/state_time_response.py similarity index 65% rename from src/mobilerun_sdk/types/devices/time_time_response.py rename to src/mobilerun_sdk/types/devices/state_time_response.py index 3851347..6aed1eb 100644 --- a/src/mobilerun_sdk/types/devices/time_time_response.py +++ b/src/mobilerun_sdk/types/devices/state_time_response.py @@ -2,6 +2,6 @@ from typing_extensions import TypeAlias -__all__ = ["TimeTimeResponse"] +__all__ = ["StateTimeResponse"] -TimeTimeResponse: TypeAlias = str +StateTimeResponse: TypeAlias = str diff --git a/src/mobilerun_sdk/types/devices/state_ui_response.py b/src/mobilerun_sdk/types/devices/state_ui_response.py index 7a695f3..bdddd0c 100644 --- a/src/mobilerun_sdk/types/devices/state_ui_response.py +++ b/src/mobilerun_sdk/types/devices/state_ui_response.py @@ -76,3 +76,5 @@ class StateUiResponse(BaseModel): schema_: Optional[str] = FieldInfo(alias="$schema", default=None) """A URL to the JSON Schema for this object.""" + + ime_tree: Optional[object] = None diff --git a/src/mobilerun_sdk/types/devices/time_timezone_response.py b/src/mobilerun_sdk/types/devices/timezone_get_response.py similarity index 82% rename from src/mobilerun_sdk/types/devices/time_timezone_response.py rename to src/mobilerun_sdk/types/devices/timezone_get_response.py index 7c5340f..2eaa30d 100644 --- a/src/mobilerun_sdk/types/devices/time_timezone_response.py +++ b/src/mobilerun_sdk/types/devices/timezone_get_response.py @@ -6,10 +6,10 @@ from ..._models import BaseModel -__all__ = ["TimeTimezoneResponse"] +__all__ = ["TimezoneGetResponse"] -class TimeTimezoneResponse(BaseModel): +class TimezoneGetResponse(BaseModel): timezone: Optional[str] = None schema_: Optional[str] = FieldInfo(alias="$schema", default=None) diff --git a/src/mobilerun_sdk/types/devices/time_set_timezone_params.py b/src/mobilerun_sdk/types/devices/timezone_set_params.py similarity index 79% rename from src/mobilerun_sdk/types/devices/time_set_timezone_params.py rename to src/mobilerun_sdk/types/devices/timezone_set_params.py index 24cd94c..87172dc 100644 --- a/src/mobilerun_sdk/types/devices/time_set_timezone_params.py +++ b/src/mobilerun_sdk/types/devices/timezone_set_params.py @@ -6,10 +6,10 @@ from ..._utils import PropertyInfo -__all__ = ["TimeSetTimezoneParams"] +__all__ = ["TimezoneSetParams"] -class TimeSetTimezoneParams(TypedDict, total=False): +class TimezoneSetParams(TypedDict, total=False): timezone: Required[str] x_device_display_id: Annotated[int, PropertyInfo(alias="X-Device-Display-ID")] diff --git a/src/mobilerun_sdk/types/shared/__init__.py b/src/mobilerun_sdk/types/shared/__init__.py index 1c5496b..7d0f116 100644 --- a/src/mobilerun_sdk/types/shared/__init__.py +++ b/src/mobilerun_sdk/types/shared/__init__.py @@ -1,6 +1,8 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .meta import Meta as Meta +from .socks5 import Socks5 as Socks5 +from .location import Location as Location from .pagination import Pagination as Pagination from .device_spec import DeviceSpec as DeviceSpec from .device_carrier import DeviceCarrier as DeviceCarrier diff --git a/src/mobilerun_sdk/types/shared/device_spec.py b/src/mobilerun_sdk/types/shared/device_spec.py index 33e54cc..64614c9 100644 --- a/src/mobilerun_sdk/types/shared/device_spec.py +++ b/src/mobilerun_sdk/types/shared/device_spec.py @@ -4,21 +4,13 @@ from pydantic import Field as FieldInfo +from .socks5 import Socks5 +from .location import Location from ..._models import BaseModel from .device_carrier import DeviceCarrier from .device_identifiers import DeviceIdentifiers -__all__ = ["DeviceSpec", "Proxy", "ProxySocks5"] - - -class ProxySocks5(BaseModel): - host: str - - password: str - - port: int - - user: str +__all__ = ["DeviceSpec", "Proxy"] class Proxy(BaseModel): @@ -26,23 +18,31 @@ class Proxy(BaseModel): smart_ip: Optional[bool] = FieldInfo(alias="smartIp", default=None) - socks5: Optional[ProxySocks5] = None - - wireguard: Optional[str] = None + socks5: Optional[Socks5] = None class DeviceSpec(BaseModel): schema_: Optional[str] = FieldInfo(alias="$schema", default=None) """A URL to the JSON Schema for this object.""" + android_version: Optional[int] = FieldInfo(alias="androidVersion", default=None) + apps: Optional[List[str]] = None carrier: Optional[DeviceCarrier] = None + country: Optional[str] = None + files: Optional[List[str]] = None identifiers: Optional[DeviceIdentifiers] = None + locale: Optional[str] = None + + location: Optional[Location] = None + name: Optional[str] = None proxy: Optional[Proxy] = None + + timezone: Optional[str] = None diff --git a/src/mobilerun_sdk/types/devices/location_get_response.py b/src/mobilerun_sdk/types/shared/location.py similarity index 83% rename from src/mobilerun_sdk/types/devices/location_get_response.py rename to src/mobilerun_sdk/types/shared/location.py index 27f711a..bf0a32a 100644 --- a/src/mobilerun_sdk/types/devices/location_get_response.py +++ b/src/mobilerun_sdk/types/shared/location.py @@ -6,10 +6,10 @@ from ..._models import BaseModel -__all__ = ["LocationGetResponse"] +__all__ = ["Location"] -class LocationGetResponse(BaseModel): +class Location(BaseModel): latitude: float longitude: float diff --git a/src/mobilerun_sdk/types/shared/socks5.py b/src/mobilerun_sdk/types/shared/socks5.py new file mode 100644 index 0000000..36791bd --- /dev/null +++ b/src/mobilerun_sdk/types/shared/socks5.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["Socks5"] + + +class Socks5(BaseModel): + host: str + + password: str + + port: int + + user: str diff --git a/src/mobilerun_sdk/types/shared_params/__init__.py b/src/mobilerun_sdk/types/shared_params/__init__.py index 28eabc9..0b4dbfb 100644 --- a/src/mobilerun_sdk/types/shared_params/__init__.py +++ b/src/mobilerun_sdk/types/shared_params/__init__.py @@ -1,5 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from .socks5 import Socks5 as Socks5 +from .location import Location as Location from .device_spec import DeviceSpec as DeviceSpec from .device_carrier import DeviceCarrier as DeviceCarrier from .device_identifiers import DeviceIdentifiers as DeviceIdentifiers diff --git a/src/mobilerun_sdk/types/shared_params/device_spec.py b/src/mobilerun_sdk/types/shared_params/device_spec.py index 60bd226..7d1a43e 100644 --- a/src/mobilerun_sdk/types/shared_params/device_spec.py +++ b/src/mobilerun_sdk/types/shared_params/device_spec.py @@ -3,24 +3,16 @@ from __future__ import annotations from typing import Optional -from typing_extensions import Required, Annotated, TypedDict +from typing_extensions import Annotated, TypedDict +from .socks5 import Socks5 from ..._types import SequenceNotStr from ..._utils import PropertyInfo +from .location import Location from .device_carrier import DeviceCarrier from .device_identifiers import DeviceIdentifiers -__all__ = ["DeviceSpec", "Proxy", "ProxySocks5"] - - -class ProxySocks5(TypedDict, total=False): - host: Required[str] - - password: Required[str] - - port: Required[int] - - user: Required[str] +__all__ = ["DeviceSpec", "Proxy"] class Proxy(TypedDict, total=False): @@ -28,20 +20,28 @@ class Proxy(TypedDict, total=False): smart_ip: Annotated[bool, PropertyInfo(alias="smartIp")] - socks5: ProxySocks5 - - wireguard: str + socks5: Socks5 class DeviceSpec(TypedDict, total=False): + android_version: Annotated[int, PropertyInfo(alias="androidVersion")] + apps: Optional[SequenceNotStr[str]] carrier: DeviceCarrier + country: str + files: Optional[SequenceNotStr[str]] identifiers: DeviceIdentifiers + locale: str + + location: Location + name: str proxy: Proxy + + timezone: str diff --git a/src/mobilerun_sdk/types/shared_params/location.py b/src/mobilerun_sdk/types/shared_params/location.py new file mode 100644 index 0000000..49198dc --- /dev/null +++ b/src/mobilerun_sdk/types/shared_params/location.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["Location"] + + +class Location(TypedDict, total=False): + latitude: Required[float] + + longitude: Required[float] diff --git a/src/mobilerun_sdk/types/shared_params/socks5.py b/src/mobilerun_sdk/types/shared_params/socks5.py new file mode 100644 index 0000000..0e56d16 --- /dev/null +++ b/src/mobilerun_sdk/types/shared_params/socks5.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["Socks5"] + + +class Socks5(TypedDict, total=False): + host: Required[str] + + password: Required[str] + + port: Required[int] + + user: Required[str] diff --git a/src/mobilerun_sdk/types/task.py b/src/mobilerun_sdk/types/task.py index d436f7b..4a0f115 100644 --- a/src/mobilerun_sdk/types/task.py +++ b/src/mobilerun_sdk/types/task.py @@ -39,6 +39,8 @@ class Task(BaseModel): credentials: Optional[List[PackageCredentials]] = None + credits_used: Optional[float] = FieldInfo(alias="creditsUsed", default=None) + dispatched_at: Optional[datetime] = FieldInfo(alias="dispatchedAt", default=None) display_id: Optional[int] = FieldInfo(alias="displayId", default=None) diff --git a/src/mobilerun_sdk/types/task_list_response.py b/src/mobilerun_sdk/types/task_list_response.py index 9dfed40..16c8d1d 100644 --- a/src/mobilerun_sdk/types/task_list_response.py +++ b/src/mobilerun_sdk/types/task_list_response.py @@ -1,16 +1,100 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import Dict, List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo -from .task import Task from .._models import BaseModel +from .task_status import TaskStatus +from .package_credentials import PackageCredentials from .shared.pagination_meta import PaginationMeta -__all__ = ["TaskListResponse"] +__all__ = ["TaskListResponse", "Item"] + + +class Item(BaseModel): + """Task representation for list endpoints — omits the large trajectory field.""" + + id: str + + device_id: str = FieldInfo(alias="deviceId") + + display_id: int = FieldInfo(alias="displayId") + + llm_model: str = FieldInfo(alias="llmModel") + """The LLM model identifier to use for the task (e.g. 'gemini/gemini-2.5-flash')""" + + status: TaskStatus + + task: str + + tmp_device: bool = FieldInfo(alias="tmpDevice") + + user_id: str = FieldInfo(alias="userId") + + agent_id: Optional[int] = FieldInfo(alias="agentId", default=None) + + apps: Optional[List[str]] = None + + cancel_requested_at: Optional[datetime] = FieldInfo(alias="cancelRequestedAt", default=None) + + claimed_at: Optional[datetime] = FieldInfo(alias="claimedAt", default=None) + + continue_on_failure: Optional[bool] = FieldInfo(alias="continueOnFailure", default=None) + + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) + + credentials: Optional[List[PackageCredentials]] = None + + credits_used: Optional[float] = FieldInfo(alias="creditsUsed", default=None) + + dispatched_at: Optional[datetime] = FieldInfo(alias="dispatchedAt", default=None) + + execution_timeout: Optional[int] = FieldInfo(alias="executionTimeout", default=None) + + files: Optional[List[str]] = None + + finished_at: Optional[datetime] = FieldInfo(alias="finishedAt", default=None) + + max_steps: Optional[int] = FieldInfo(alias="maxSteps", default=None) + + memory_namespace: Optional[str] = FieldInfo(alias="memoryNamespace", default=None) + """Memory namespace for cross-task personalization""" + + message: Optional[str] = None + + output: Optional[Dict[str, object]] = None + + output_schema: Optional[Dict[str, object]] = FieldInfo(alias="outputSchema", default=None) + + reasoning: Optional[bool] = None + + stealth: Optional[bool] = None + + steps: Optional[int] = None + + stream_url: Optional[str] = FieldInfo(alias="streamUrl", default=None) + + subagent_model: Optional[str] = FieldInfo(alias="subagentModel", default=None) + """LLM model used by sub-agent roles: executor, app_opener, structured_output""" + + succeeded: Optional[bool] = None + + temperature: Optional[float] = None + + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) + + vision: Optional[bool] = None + + vpn_country: Optional[Literal["US", "BR", "FR", "DE", "IN", "JP", "KR", "ZA"]] = FieldInfo( + alias="vpnCountry", default=None + ) class TaskListResponse(BaseModel): - items: List[Task] + items: List[Item] """The paginated items""" pagination: PaginationMeta diff --git a/tests/api_resources/devices/esim/__init__.py b/tests/api_resources/devices/esim/__init__.py new file mode 100644 index 0000000..fd8019a --- /dev/null +++ b/tests/api_resources/devices/esim/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/devices/esim/test_apn.py b/tests/api_resources/devices/esim/test_apn.py new file mode 100644 index 0000000..d538cf7 --- /dev/null +++ b/tests/api_resources/devices/esim/test_apn.py @@ -0,0 +1,430 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, Optional, cast + +import pytest + +from tests.utils import assert_matches_type +from mobilerun_sdk import Mobilerun, AsyncMobilerun +from mobilerun_sdk.types.devices.esim import ApnListResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestApn: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create(self, client: Mobilerun) -> None: + apn = client.devices.esim.apn.create( + device_id="deviceId", + apn="apn", + mcc="mcc", + mnc="mnc", + name="name", + protocol="protocol", + roaming_protocol="roamingProtocol", + sub_id=0, + type="type", + ) + assert apn is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Mobilerun) -> None: + apn = client.devices.esim.apn.create( + device_id="deviceId", + apn="apn", + mcc="mcc", + mnc="mnc", + name="name", + protocol="protocol", + roaming_protocol="roamingProtocol", + sub_id=0, + type="type", + x_device_display_id=0, + ) + assert apn is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: Mobilerun) -> None: + response = client.devices.esim.apn.with_raw_response.create( + device_id="deviceId", + apn="apn", + mcc="mcc", + mnc="mnc", + name="name", + protocol="protocol", + roaming_protocol="roamingProtocol", + sub_id=0, + type="type", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + apn = response.parse() + assert apn is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Mobilerun) -> None: + with client.devices.esim.apn.with_streaming_response.create( + device_id="deviceId", + apn="apn", + mcc="mcc", + mnc="mnc", + name="name", + protocol="protocol", + roaming_protocol="roamingProtocol", + sub_id=0, + type="type", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + apn = response.parse() + assert apn is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_create(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + client.devices.esim.apn.with_raw_response.create( + device_id="", + apn="apn", + mcc="mcc", + mnc="mnc", + name="name", + protocol="protocol", + roaming_protocol="roamingProtocol", + sub_id=0, + type="type", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Mobilerun) -> None: + apn = client.devices.esim.apn.list( + device_id="deviceId", + ) + assert_matches_type(Optional[ApnListResponse], apn, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Mobilerun) -> None: + apn = client.devices.esim.apn.list( + device_id="deviceId", + x_device_display_id=0, + ) + assert_matches_type(Optional[ApnListResponse], apn, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Mobilerun) -> None: + response = client.devices.esim.apn.with_raw_response.list( + device_id="deviceId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + apn = response.parse() + assert_matches_type(Optional[ApnListResponse], apn, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Mobilerun) -> None: + with client.devices.esim.apn.with_streaming_response.list( + device_id="deviceId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + apn = response.parse() + assert_matches_type(Optional[ApnListResponse], apn, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_list(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + client.devices.esim.apn.with_raw_response.list( + device_id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_select(self, client: Mobilerun) -> None: + apn = client.devices.esim.apn.select( + device_id="deviceId", + apn_id=0, + sub_id=0, + ) + assert apn is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_select_with_all_params(self, client: Mobilerun) -> None: + apn = client.devices.esim.apn.select( + device_id="deviceId", + apn_id=0, + sub_id=0, + x_device_display_id=0, + ) + assert apn is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_select(self, client: Mobilerun) -> None: + response = client.devices.esim.apn.with_raw_response.select( + device_id="deviceId", + apn_id=0, + sub_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + apn = response.parse() + assert apn is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_select(self, client: Mobilerun) -> None: + with client.devices.esim.apn.with_streaming_response.select( + device_id="deviceId", + apn_id=0, + sub_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + apn = response.parse() + assert apn is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_select(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + client.devices.esim.apn.with_raw_response.select( + device_id="", + apn_id=0, + sub_id=0, + ) + + +class TestAsyncApn: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncMobilerun) -> None: + apn = await async_client.devices.esim.apn.create( + device_id="deviceId", + apn="apn", + mcc="mcc", + mnc="mnc", + name="name", + protocol="protocol", + roaming_protocol="roamingProtocol", + sub_id=0, + type="type", + ) + assert apn is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncMobilerun) -> None: + apn = await async_client.devices.esim.apn.create( + device_id="deviceId", + apn="apn", + mcc="mcc", + mnc="mnc", + name="name", + protocol="protocol", + roaming_protocol="roamingProtocol", + sub_id=0, + type="type", + x_device_display_id=0, + ) + assert apn is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncMobilerun) -> None: + response = await async_client.devices.esim.apn.with_raw_response.create( + device_id="deviceId", + apn="apn", + mcc="mcc", + mnc="mnc", + name="name", + protocol="protocol", + roaming_protocol="roamingProtocol", + sub_id=0, + type="type", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + apn = await response.parse() + assert apn is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncMobilerun) -> None: + async with async_client.devices.esim.apn.with_streaming_response.create( + device_id="deviceId", + apn="apn", + mcc="mcc", + mnc="mnc", + name="name", + protocol="protocol", + roaming_protocol="roamingProtocol", + sub_id=0, + type="type", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + apn = await response.parse() + assert apn is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_create(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + await async_client.devices.esim.apn.with_raw_response.create( + device_id="", + apn="apn", + mcc="mcc", + mnc="mnc", + name="name", + protocol="protocol", + roaming_protocol="roamingProtocol", + sub_id=0, + type="type", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncMobilerun) -> None: + apn = await async_client.devices.esim.apn.list( + device_id="deviceId", + ) + assert_matches_type(Optional[ApnListResponse], apn, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncMobilerun) -> None: + apn = await async_client.devices.esim.apn.list( + device_id="deviceId", + x_device_display_id=0, + ) + assert_matches_type(Optional[ApnListResponse], apn, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncMobilerun) -> None: + response = await async_client.devices.esim.apn.with_raw_response.list( + device_id="deviceId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + apn = await response.parse() + assert_matches_type(Optional[ApnListResponse], apn, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncMobilerun) -> None: + async with async_client.devices.esim.apn.with_streaming_response.list( + device_id="deviceId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + apn = await response.parse() + assert_matches_type(Optional[ApnListResponse], apn, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_list(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + await async_client.devices.esim.apn.with_raw_response.list( + device_id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_select(self, async_client: AsyncMobilerun) -> None: + apn = await async_client.devices.esim.apn.select( + device_id="deviceId", + apn_id=0, + sub_id=0, + ) + assert apn is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_select_with_all_params(self, async_client: AsyncMobilerun) -> None: + apn = await async_client.devices.esim.apn.select( + device_id="deviceId", + apn_id=0, + sub_id=0, + x_device_display_id=0, + ) + assert apn is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_select(self, async_client: AsyncMobilerun) -> None: + response = await async_client.devices.esim.apn.with_raw_response.select( + device_id="deviceId", + apn_id=0, + sub_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + apn = await response.parse() + assert apn is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_select(self, async_client: AsyncMobilerun) -> None: + async with async_client.devices.esim.apn.with_streaming_response.select( + device_id="deviceId", + apn_id=0, + sub_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + apn = await response.parse() + assert apn is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_select(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + await async_client.devices.esim.apn.with_raw_response.select( + device_id="", + apn_id=0, + sub_id=0, + ) diff --git a/tests/api_resources/devices/test_apps.py b/tests/api_resources/devices/test_apps.py index 946ac64..87c426d 100644 --- a/tests/api_resources/devices/test_apps.py +++ b/tests/api_resources/devices/test_apps.py @@ -196,29 +196,30 @@ def test_path_params_delete(self, client: Mobilerun) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_install(self, client: Mobilerun) -> None: + def test_method_install_overload_1(self, client: Mobilerun) -> None: app = client.devices.apps.install( device_id="deviceId", - package_name="packageName", + bundle_id="x", ) assert app is None @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_install_with_all_params(self, client: Mobilerun) -> None: + def test_method_install_with_all_params_overload_1(self, client: Mobilerun) -> None: app = client.devices.apps.install( device_id="deviceId", - package_name="packageName", + bundle_id="x", + package_name="x", x_device_display_id=0, ) assert app is None @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_install(self, client: Mobilerun) -> None: + def test_raw_response_install_overload_1(self, client: Mobilerun) -> None: response = client.devices.apps.with_raw_response.install( device_id="deviceId", - package_name="packageName", + bundle_id="x", ) assert response.is_closed is True @@ -228,10 +229,10 @@ def test_raw_response_install(self, client: Mobilerun) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_install(self, client: Mobilerun) -> None: + def test_streaming_response_install_overload_1(self, client: Mobilerun) -> None: with client.devices.apps.with_streaming_response.install( device_id="deviceId", - package_name="packageName", + bundle_id="x", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -243,11 +244,68 @@ def test_streaming_response_install(self, client: Mobilerun) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_path_params_install(self, client: Mobilerun) -> None: + def test_path_params_install_overload_1(self, client: Mobilerun) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): client.devices.apps.with_raw_response.install( device_id="", - package_name="packageName", + bundle_id="x", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_install_overload_2(self, client: Mobilerun) -> None: + app = client.devices.apps.install( + device_id="deviceId", + package_name="x", + ) + assert app is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_install_with_all_params_overload_2(self, client: Mobilerun) -> None: + app = client.devices.apps.install( + device_id="deviceId", + package_name="x", + bundle_id="x", + x_device_display_id=0, + ) + assert app is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_install_overload_2(self, client: Mobilerun) -> None: + response = client.devices.apps.with_raw_response.install( + device_id="deviceId", + package_name="x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + app = response.parse() + assert app is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_install_overload_2(self, client: Mobilerun) -> None: + with client.devices.apps.with_streaming_response.install( + device_id="deviceId", + package_name="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + app = response.parse() + assert app is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_install_overload_2(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + client.devices.apps.with_raw_response.install( + device_id="", + package_name="x", ) @pytest.mark.skip(reason="Mock server tests are disabled") @@ -498,29 +556,30 @@ async def test_path_params_delete(self, async_client: AsyncMobilerun) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_install(self, async_client: AsyncMobilerun) -> None: + async def test_method_install_overload_1(self, async_client: AsyncMobilerun) -> None: app = await async_client.devices.apps.install( device_id="deviceId", - package_name="packageName", + bundle_id="x", ) assert app is None @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_install_with_all_params(self, async_client: AsyncMobilerun) -> None: + async def test_method_install_with_all_params_overload_1(self, async_client: AsyncMobilerun) -> None: app = await async_client.devices.apps.install( device_id="deviceId", - package_name="packageName", + bundle_id="x", + package_name="x", x_device_display_id=0, ) assert app is None @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_raw_response_install(self, async_client: AsyncMobilerun) -> None: + async def test_raw_response_install_overload_1(self, async_client: AsyncMobilerun) -> None: response = await async_client.devices.apps.with_raw_response.install( device_id="deviceId", - package_name="packageName", + bundle_id="x", ) assert response.is_closed is True @@ -530,10 +589,10 @@ async def test_raw_response_install(self, async_client: AsyncMobilerun) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_streaming_response_install(self, async_client: AsyncMobilerun) -> None: + async def test_streaming_response_install_overload_1(self, async_client: AsyncMobilerun) -> None: async with async_client.devices.apps.with_streaming_response.install( device_id="deviceId", - package_name="packageName", + bundle_id="x", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -545,11 +604,68 @@ async def test_streaming_response_install(self, async_client: AsyncMobilerun) -> @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_path_params_install(self, async_client: AsyncMobilerun) -> None: + async def test_path_params_install_overload_1(self, async_client: AsyncMobilerun) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): await async_client.devices.apps.with_raw_response.install( device_id="", - package_name="packageName", + bundle_id="x", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_install_overload_2(self, async_client: AsyncMobilerun) -> None: + app = await async_client.devices.apps.install( + device_id="deviceId", + package_name="x", + ) + assert app is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_install_with_all_params_overload_2(self, async_client: AsyncMobilerun) -> None: + app = await async_client.devices.apps.install( + device_id="deviceId", + package_name="x", + bundle_id="x", + x_device_display_id=0, + ) + assert app is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_install_overload_2(self, async_client: AsyncMobilerun) -> None: + response = await async_client.devices.apps.with_raw_response.install( + device_id="deviceId", + package_name="x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + app = await response.parse() + assert app is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_install_overload_2(self, async_client: AsyncMobilerun) -> None: + async with async_client.devices.apps.with_streaming_response.install( + device_id="deviceId", + package_name="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + app = await response.parse() + assert app is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_install_overload_2(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + await async_client.devices.apps.with_raw_response.install( + device_id="", + package_name="x", ) @pytest.mark.skip(reason="Mock server tests are disabled") diff --git a/tests/api_resources/devices/test_esim.py b/tests/api_resources/devices/test_esim.py index 84cf406..82541c2 100644 --- a/tests/api_resources/devices/test_esim.py +++ b/tests/api_resources/devices/test_esim.py @@ -11,6 +11,7 @@ from mobilerun_sdk import Mobilerun, AsyncMobilerun from mobilerun_sdk.types.devices import ( EsimListResponse, + EsimStatusResponse, EsimActivateResponse, ) @@ -77,7 +78,6 @@ def test_method_activate(self, client: Mobilerun) -> None: esim = client.devices.esim.activate( device_id="deviceId", enable=True, - matching_id="matchingId", sm_dp_addr="smDpAddr", ) assert_matches_type(EsimActivateResponse, esim, path=["response"]) @@ -88,8 +88,9 @@ def test_method_activate_with_all_params(self, client: Mobilerun) -> None: esim = client.devices.esim.activate( device_id="deviceId", enable=True, - matching_id="matchingId", sm_dp_addr="smDpAddr", + confirmation_code="confirmationCode", + matching_id="matchingId", x_device_display_id=0, ) assert_matches_type(EsimActivateResponse, esim, path=["response"]) @@ -100,7 +101,6 @@ def test_raw_response_activate(self, client: Mobilerun) -> None: response = client.devices.esim.with_raw_response.activate( device_id="deviceId", enable=True, - matching_id="matchingId", sm_dp_addr="smDpAddr", ) @@ -115,7 +115,6 @@ def test_streaming_response_activate(self, client: Mobilerun) -> None: with client.devices.esim.with_streaming_response.activate( device_id="deviceId", enable=True, - matching_id="matchingId", sm_dp_addr="smDpAddr", ) as response: assert not response.is_closed @@ -133,7 +132,6 @@ def test_path_params_activate(self, client: Mobilerun) -> None: client.devices.esim.with_raw_response.activate( device_id="", enable=True, - matching_id="matchingId", sm_dp_addr="smDpAddr", ) @@ -249,6 +247,113 @@ def test_path_params_remove(self, client: Mobilerun) -> None: sub_id=0, ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_set_roaming(self, client: Mobilerun) -> None: + esim = client.devices.esim.set_roaming( + device_id="deviceId", + enabled=True, + ) + assert esim is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_set_roaming_with_all_params(self, client: Mobilerun) -> None: + esim = client.devices.esim.set_roaming( + device_id="deviceId", + enabled=True, + x_device_display_id=0, + ) + assert esim is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_set_roaming(self, client: Mobilerun) -> None: + response = client.devices.esim.with_raw_response.set_roaming( + device_id="deviceId", + enabled=True, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + esim = response.parse() + assert esim is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_set_roaming(self, client: Mobilerun) -> None: + with client.devices.esim.with_streaming_response.set_roaming( + device_id="deviceId", + enabled=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + esim = response.parse() + assert esim is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_set_roaming(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + client.devices.esim.with_raw_response.set_roaming( + device_id="", + enabled=True, + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_status(self, client: Mobilerun) -> None: + esim = client.devices.esim.status( + device_id="deviceId", + ) + assert_matches_type(Optional[EsimStatusResponse], esim, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_status_with_all_params(self, client: Mobilerun) -> None: + esim = client.devices.esim.status( + device_id="deviceId", + x_device_display_id=0, + ) + assert_matches_type(Optional[EsimStatusResponse], esim, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_status(self, client: Mobilerun) -> None: + response = client.devices.esim.with_raw_response.status( + device_id="deviceId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + esim = response.parse() + assert_matches_type(Optional[EsimStatusResponse], esim, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_status(self, client: Mobilerun) -> None: + with client.devices.esim.with_streaming_response.status( + device_id="deviceId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + esim = response.parse() + assert_matches_type(Optional[EsimStatusResponse], esim, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_status(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + client.devices.esim.with_raw_response.status( + device_id="", + ) + class TestAsyncEsim: parametrize = pytest.mark.parametrize( @@ -312,7 +417,6 @@ async def test_method_activate(self, async_client: AsyncMobilerun) -> None: esim = await async_client.devices.esim.activate( device_id="deviceId", enable=True, - matching_id="matchingId", sm_dp_addr="smDpAddr", ) assert_matches_type(EsimActivateResponse, esim, path=["response"]) @@ -323,8 +427,9 @@ async def test_method_activate_with_all_params(self, async_client: AsyncMobileru esim = await async_client.devices.esim.activate( device_id="deviceId", enable=True, - matching_id="matchingId", sm_dp_addr="smDpAddr", + confirmation_code="confirmationCode", + matching_id="matchingId", x_device_display_id=0, ) assert_matches_type(EsimActivateResponse, esim, path=["response"]) @@ -335,7 +440,6 @@ async def test_raw_response_activate(self, async_client: AsyncMobilerun) -> None response = await async_client.devices.esim.with_raw_response.activate( device_id="deviceId", enable=True, - matching_id="matchingId", sm_dp_addr="smDpAddr", ) @@ -350,7 +454,6 @@ async def test_streaming_response_activate(self, async_client: AsyncMobilerun) - async with async_client.devices.esim.with_streaming_response.activate( device_id="deviceId", enable=True, - matching_id="matchingId", sm_dp_addr="smDpAddr", ) as response: assert not response.is_closed @@ -368,7 +471,6 @@ async def test_path_params_activate(self, async_client: AsyncMobilerun) -> None: await async_client.devices.esim.with_raw_response.activate( device_id="", enable=True, - matching_id="matchingId", sm_dp_addr="smDpAddr", ) @@ -483,3 +585,110 @@ async def test_path_params_remove(self, async_client: AsyncMobilerun) -> None: device_id="", sub_id=0, ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_set_roaming(self, async_client: AsyncMobilerun) -> None: + esim = await async_client.devices.esim.set_roaming( + device_id="deviceId", + enabled=True, + ) + assert esim is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_set_roaming_with_all_params(self, async_client: AsyncMobilerun) -> None: + esim = await async_client.devices.esim.set_roaming( + device_id="deviceId", + enabled=True, + x_device_display_id=0, + ) + assert esim is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_set_roaming(self, async_client: AsyncMobilerun) -> None: + response = await async_client.devices.esim.with_raw_response.set_roaming( + device_id="deviceId", + enabled=True, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + esim = await response.parse() + assert esim is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_set_roaming(self, async_client: AsyncMobilerun) -> None: + async with async_client.devices.esim.with_streaming_response.set_roaming( + device_id="deviceId", + enabled=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + esim = await response.parse() + assert esim is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_set_roaming(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + await async_client.devices.esim.with_raw_response.set_roaming( + device_id="", + enabled=True, + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_status(self, async_client: AsyncMobilerun) -> None: + esim = await async_client.devices.esim.status( + device_id="deviceId", + ) + assert_matches_type(Optional[EsimStatusResponse], esim, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_status_with_all_params(self, async_client: AsyncMobilerun) -> None: + esim = await async_client.devices.esim.status( + device_id="deviceId", + x_device_display_id=0, + ) + assert_matches_type(Optional[EsimStatusResponse], esim, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_status(self, async_client: AsyncMobilerun) -> None: + response = await async_client.devices.esim.with_raw_response.status( + device_id="deviceId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + esim = await response.parse() + assert_matches_type(Optional[EsimStatusResponse], esim, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_status(self, async_client: AsyncMobilerun) -> None: + async with async_client.devices.esim.with_streaming_response.status( + device_id="deviceId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + esim = await response.parse() + assert_matches_type(Optional[EsimStatusResponse], esim, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_status(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + await async_client.devices.esim.with_raw_response.status( + device_id="", + ) diff --git a/tests/api_resources/devices/test_keyboard.py b/tests/api_resources/devices/test_keyboard.py index ba20e76..766a63b 100644 --- a/tests/api_resources/devices/test_keyboard.py +++ b/tests/api_resources/devices/test_keyboard.py @@ -138,6 +138,7 @@ def test_method_write_with_all_params(self, client: Mobilerun) -> None: device_id="deviceId", text="text", clear=True, + error_rate=0, stealth=True, wpm=0, x_device_display_id=0, @@ -310,6 +311,7 @@ async def test_method_write_with_all_params(self, async_client: AsyncMobilerun) device_id="deviceId", text="text", clear=True, + error_rate=0, stealth=True, wpm=0, x_device_display_id=0, diff --git a/tests/api_resources/devices/test_language.py b/tests/api_resources/devices/test_language.py new file mode 100644 index 0000000..c38f4dd --- /dev/null +++ b/tests/api_resources/devices/test_language.py @@ -0,0 +1,240 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from tests.utils import assert_matches_type +from mobilerun_sdk import Mobilerun, AsyncMobilerun +from mobilerun_sdk.types.devices import LanguageGetResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestLanguage: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_get(self, client: Mobilerun) -> None: + language = client.devices.language.get( + device_id="deviceId", + ) + assert_matches_type(LanguageGetResponse, language, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_get_with_all_params(self, client: Mobilerun) -> None: + language = client.devices.language.get( + device_id="deviceId", + x_device_display_id=0, + ) + assert_matches_type(LanguageGetResponse, language, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_get(self, client: Mobilerun) -> None: + response = client.devices.language.with_raw_response.get( + device_id="deviceId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + language = response.parse() + assert_matches_type(LanguageGetResponse, language, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_get(self, client: Mobilerun) -> None: + with client.devices.language.with_streaming_response.get( + device_id="deviceId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + language = response.parse() + assert_matches_type(LanguageGetResponse, language, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_get(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + client.devices.language.with_raw_response.get( + device_id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_set(self, client: Mobilerun) -> None: + language = client.devices.language.set( + device_id="deviceId", + locale="sqf-Kkif-BB", + ) + assert language is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_set_with_all_params(self, client: Mobilerun) -> None: + language = client.devices.language.set( + device_id="deviceId", + locale="sqf-Kkif-BB", + restart=True, + x_device_display_id=0, + ) + assert language is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_set(self, client: Mobilerun) -> None: + response = client.devices.language.with_raw_response.set( + device_id="deviceId", + locale="sqf-Kkif-BB", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + language = response.parse() + assert language is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_set(self, client: Mobilerun) -> None: + with client.devices.language.with_streaming_response.set( + device_id="deviceId", + locale="sqf-Kkif-BB", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + language = response.parse() + assert language is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_set(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + client.devices.language.with_raw_response.set( + device_id="", + locale="sqf-Kkif-BB", + ) + + +class TestAsyncLanguage: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_get(self, async_client: AsyncMobilerun) -> None: + language = await async_client.devices.language.get( + device_id="deviceId", + ) + assert_matches_type(LanguageGetResponse, language, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_get_with_all_params(self, async_client: AsyncMobilerun) -> None: + language = await async_client.devices.language.get( + device_id="deviceId", + x_device_display_id=0, + ) + assert_matches_type(LanguageGetResponse, language, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_get(self, async_client: AsyncMobilerun) -> None: + response = await async_client.devices.language.with_raw_response.get( + device_id="deviceId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + language = await response.parse() + assert_matches_type(LanguageGetResponse, language, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_get(self, async_client: AsyncMobilerun) -> None: + async with async_client.devices.language.with_streaming_response.get( + device_id="deviceId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + language = await response.parse() + assert_matches_type(LanguageGetResponse, language, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_get(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + await async_client.devices.language.with_raw_response.get( + device_id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_set(self, async_client: AsyncMobilerun) -> None: + language = await async_client.devices.language.set( + device_id="deviceId", + locale="sqf-Kkif-BB", + ) + assert language is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_set_with_all_params(self, async_client: AsyncMobilerun) -> None: + language = await async_client.devices.language.set( + device_id="deviceId", + locale="sqf-Kkif-BB", + restart=True, + x_device_display_id=0, + ) + assert language is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_set(self, async_client: AsyncMobilerun) -> None: + response = await async_client.devices.language.with_raw_response.set( + device_id="deviceId", + locale="sqf-Kkif-BB", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + language = await response.parse() + assert language is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_set(self, async_client: AsyncMobilerun) -> None: + async with async_client.devices.language.with_streaming_response.set( + device_id="deviceId", + locale="sqf-Kkif-BB", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + language = await response.parse() + assert language is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_set(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + await async_client.devices.language.with_raw_response.set( + device_id="", + locale="sqf-Kkif-BB", + ) diff --git a/tests/api_resources/devices/test_location.py b/tests/api_resources/devices/test_location.py index 2d8b007..2ec5cb0 100644 --- a/tests/api_resources/devices/test_location.py +++ b/tests/api_resources/devices/test_location.py @@ -9,7 +9,7 @@ from tests.utils import assert_matches_type from mobilerun_sdk import Mobilerun, AsyncMobilerun -from mobilerun_sdk.types.devices import LocationGetResponse +from mobilerun_sdk.types.shared import Location base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -23,7 +23,7 @@ def test_method_get(self, client: Mobilerun) -> None: location = client.devices.location.get( device_id="deviceId", ) - assert_matches_type(LocationGetResponse, location, path=["response"]) + assert_matches_type(Location, location, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -32,7 +32,7 @@ def test_method_get_with_all_params(self, client: Mobilerun) -> None: device_id="deviceId", x_device_display_id=0, ) - assert_matches_type(LocationGetResponse, location, path=["response"]) + assert_matches_type(Location, location, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -44,7 +44,7 @@ def test_raw_response_get(self, client: Mobilerun) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" location = response.parse() - assert_matches_type(LocationGetResponse, location, path=["response"]) + assert_matches_type(Location, location, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -56,7 +56,7 @@ def test_streaming_response_get(self, client: Mobilerun) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" location = response.parse() - assert_matches_type(LocationGetResponse, location, path=["response"]) + assert_matches_type(Location, location, path=["response"]) assert cast(Any, response.is_closed) is True @@ -141,7 +141,7 @@ async def test_method_get(self, async_client: AsyncMobilerun) -> None: location = await async_client.devices.location.get( device_id="deviceId", ) - assert_matches_type(LocationGetResponse, location, path=["response"]) + assert_matches_type(Location, location, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -150,7 +150,7 @@ async def test_method_get_with_all_params(self, async_client: AsyncMobilerun) -> device_id="deviceId", x_device_display_id=0, ) - assert_matches_type(LocationGetResponse, location, path=["response"]) + assert_matches_type(Location, location, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -162,7 +162,7 @@ async def test_raw_response_get(self, async_client: AsyncMobilerun) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" location = await response.parse() - assert_matches_type(LocationGetResponse, location, path=["response"]) + assert_matches_type(Location, location, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -174,7 +174,7 @@ async def test_streaming_response_get(self, async_client: AsyncMobilerun) -> Non assert response.http_request.headers.get("X-Stainless-Lang") == "python" location = await response.parse() - assert_matches_type(LocationGetResponse, location, path=["response"]) + assert_matches_type(Location, location, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/devices/test_proxy.py b/tests/api_resources/devices/test_proxy.py index eb9d960..914b3e3 100644 --- a/tests/api_resources/devices/test_proxy.py +++ b/tests/api_resources/devices/test_proxy.py @@ -42,7 +42,6 @@ def test_method_connect_with_all_params(self, client: Mobilerun) -> None: "user": "user", }, user="user", - wireguard="wireguard", x_device_display_id=0, ) assert proxy is None @@ -214,7 +213,6 @@ async def test_method_connect_with_all_params(self, async_client: AsyncMobilerun "user": "user", }, user="user", - wireguard="wireguard", x_device_display_id=0, ) assert proxy is None diff --git a/tests/api_resources/devices/test_state.py b/tests/api_resources/devices/test_state.py index 9ef60ae..64d94ec 100644 --- a/tests/api_resources/devices/test_state.py +++ b/tests/api_resources/devices/test_state.py @@ -71,6 +71,57 @@ def test_path_params_screenshot(self, client: Mobilerun) -> None: device_id="", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_time(self, client: Mobilerun) -> None: + state = client.devices.state.time( + device_id="deviceId", + ) + assert_matches_type(str, state, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_time_with_all_params(self, client: Mobilerun) -> None: + state = client.devices.state.time( + device_id="deviceId", + x_device_display_id=0, + ) + assert_matches_type(str, state, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_time(self, client: Mobilerun) -> None: + response = client.devices.state.with_raw_response.time( + device_id="deviceId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + state = response.parse() + assert_matches_type(str, state, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_time(self, client: Mobilerun) -> None: + with client.devices.state.with_streaming_response.time( + device_id="deviceId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + state = response.parse() + assert_matches_type(str, state, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_time(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + client.devices.state.with_raw_response.time( + device_id="", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_ui(self, client: Mobilerun) -> None: @@ -181,6 +232,57 @@ async def test_path_params_screenshot(self, async_client: AsyncMobilerun) -> Non device_id="", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_time(self, async_client: AsyncMobilerun) -> None: + state = await async_client.devices.state.time( + device_id="deviceId", + ) + assert_matches_type(str, state, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_time_with_all_params(self, async_client: AsyncMobilerun) -> None: + state = await async_client.devices.state.time( + device_id="deviceId", + x_device_display_id=0, + ) + assert_matches_type(str, state, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_time(self, async_client: AsyncMobilerun) -> None: + response = await async_client.devices.state.with_raw_response.time( + device_id="deviceId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + state = await response.parse() + assert_matches_type(str, state, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_time(self, async_client: AsyncMobilerun) -> None: + async with async_client.devices.state.with_streaming_response.time( + device_id="deviceId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + state = await response.parse() + assert_matches_type(str, state, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_time(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + await async_client.devices.state.with_raw_response.time( + device_id="", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_ui(self, async_client: AsyncMobilerun) -> None: diff --git a/tests/api_resources/devices/test_time.py b/tests/api_resources/devices/test_time.py deleted file mode 100644 index 6427488..0000000 --- a/tests/api_resources/devices/test_time.py +++ /dev/null @@ -1,340 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from tests.utils import assert_matches_type -from mobilerun_sdk import Mobilerun, AsyncMobilerun -from mobilerun_sdk.types.devices import TimeTimezoneResponse - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestTime: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_set_timezone(self, client: Mobilerun) -> None: - time = client.devices.time.set_timezone( - device_id="deviceId", - timezone="timezone", - ) - assert time is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_set_timezone_with_all_params(self, client: Mobilerun) -> None: - time = client.devices.time.set_timezone( - device_id="deviceId", - timezone="timezone", - x_device_display_id=0, - ) - assert time is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_set_timezone(self, client: Mobilerun) -> None: - response = client.devices.time.with_raw_response.set_timezone( - device_id="deviceId", - timezone="timezone", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - time = response.parse() - assert time is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_set_timezone(self, client: Mobilerun) -> None: - with client.devices.time.with_streaming_response.set_timezone( - device_id="deviceId", - timezone="timezone", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - time = response.parse() - assert time is None - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_set_timezone(self, client: Mobilerun) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): - client.devices.time.with_raw_response.set_timezone( - device_id="", - timezone="timezone", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_time(self, client: Mobilerun) -> None: - time = client.devices.time.time( - device_id="deviceId", - ) - assert_matches_type(str, time, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_time_with_all_params(self, client: Mobilerun) -> None: - time = client.devices.time.time( - device_id="deviceId", - x_device_display_id=0, - ) - assert_matches_type(str, time, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_time(self, client: Mobilerun) -> None: - response = client.devices.time.with_raw_response.time( - device_id="deviceId", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - time = response.parse() - assert_matches_type(str, time, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_time(self, client: Mobilerun) -> None: - with client.devices.time.with_streaming_response.time( - device_id="deviceId", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - time = response.parse() - assert_matches_type(str, time, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_time(self, client: Mobilerun) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): - client.devices.time.with_raw_response.time( - device_id="", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_timezone(self, client: Mobilerun) -> None: - time = client.devices.time.timezone( - device_id="deviceId", - ) - assert_matches_type(TimeTimezoneResponse, time, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_timezone_with_all_params(self, client: Mobilerun) -> None: - time = client.devices.time.timezone( - device_id="deviceId", - x_device_display_id=0, - ) - assert_matches_type(TimeTimezoneResponse, time, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_timezone(self, client: Mobilerun) -> None: - response = client.devices.time.with_raw_response.timezone( - device_id="deviceId", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - time = response.parse() - assert_matches_type(TimeTimezoneResponse, time, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_timezone(self, client: Mobilerun) -> None: - with client.devices.time.with_streaming_response.timezone( - device_id="deviceId", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - time = response.parse() - assert_matches_type(TimeTimezoneResponse, time, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_timezone(self, client: Mobilerun) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): - client.devices.time.with_raw_response.timezone( - device_id="", - ) - - -class TestAsyncTime: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_set_timezone(self, async_client: AsyncMobilerun) -> None: - time = await async_client.devices.time.set_timezone( - device_id="deviceId", - timezone="timezone", - ) - assert time is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_set_timezone_with_all_params(self, async_client: AsyncMobilerun) -> None: - time = await async_client.devices.time.set_timezone( - device_id="deviceId", - timezone="timezone", - x_device_display_id=0, - ) - assert time is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_set_timezone(self, async_client: AsyncMobilerun) -> None: - response = await async_client.devices.time.with_raw_response.set_timezone( - device_id="deviceId", - timezone="timezone", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - time = await response.parse() - assert time is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_set_timezone(self, async_client: AsyncMobilerun) -> None: - async with async_client.devices.time.with_streaming_response.set_timezone( - device_id="deviceId", - timezone="timezone", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - time = await response.parse() - assert time is None - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_set_timezone(self, async_client: AsyncMobilerun) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): - await async_client.devices.time.with_raw_response.set_timezone( - device_id="", - timezone="timezone", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_time(self, async_client: AsyncMobilerun) -> None: - time = await async_client.devices.time.time( - device_id="deviceId", - ) - assert_matches_type(str, time, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_time_with_all_params(self, async_client: AsyncMobilerun) -> None: - time = await async_client.devices.time.time( - device_id="deviceId", - x_device_display_id=0, - ) - assert_matches_type(str, time, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_time(self, async_client: AsyncMobilerun) -> None: - response = await async_client.devices.time.with_raw_response.time( - device_id="deviceId", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - time = await response.parse() - assert_matches_type(str, time, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_time(self, async_client: AsyncMobilerun) -> None: - async with async_client.devices.time.with_streaming_response.time( - device_id="deviceId", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - time = await response.parse() - assert_matches_type(str, time, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_time(self, async_client: AsyncMobilerun) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): - await async_client.devices.time.with_raw_response.time( - device_id="", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_timezone(self, async_client: AsyncMobilerun) -> None: - time = await async_client.devices.time.timezone( - device_id="deviceId", - ) - assert_matches_type(TimeTimezoneResponse, time, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_timezone_with_all_params(self, async_client: AsyncMobilerun) -> None: - time = await async_client.devices.time.timezone( - device_id="deviceId", - x_device_display_id=0, - ) - assert_matches_type(TimeTimezoneResponse, time, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_timezone(self, async_client: AsyncMobilerun) -> None: - response = await async_client.devices.time.with_raw_response.timezone( - device_id="deviceId", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - time = await response.parse() - assert_matches_type(TimeTimezoneResponse, time, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_timezone(self, async_client: AsyncMobilerun) -> None: - async with async_client.devices.time.with_streaming_response.timezone( - device_id="deviceId", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - time = await response.parse() - assert_matches_type(TimeTimezoneResponse, time, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_timezone(self, async_client: AsyncMobilerun) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): - await async_client.devices.time.with_raw_response.timezone( - device_id="", - ) diff --git a/tests/api_resources/devices/test_timezone.py b/tests/api_resources/devices/test_timezone.py new file mode 100644 index 0000000..6902ac4 --- /dev/null +++ b/tests/api_resources/devices/test_timezone.py @@ -0,0 +1,238 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from tests.utils import assert_matches_type +from mobilerun_sdk import Mobilerun, AsyncMobilerun +from mobilerun_sdk.types.devices import TimezoneGetResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestTimezone: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_get(self, client: Mobilerun) -> None: + timezone = client.devices.timezone.get( + device_id="deviceId", + ) + assert_matches_type(TimezoneGetResponse, timezone, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_get_with_all_params(self, client: Mobilerun) -> None: + timezone = client.devices.timezone.get( + device_id="deviceId", + x_device_display_id=0, + ) + assert_matches_type(TimezoneGetResponse, timezone, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_get(self, client: Mobilerun) -> None: + response = client.devices.timezone.with_raw_response.get( + device_id="deviceId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + timezone = response.parse() + assert_matches_type(TimezoneGetResponse, timezone, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_get(self, client: Mobilerun) -> None: + with client.devices.timezone.with_streaming_response.get( + device_id="deviceId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + timezone = response.parse() + assert_matches_type(TimezoneGetResponse, timezone, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_get(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + client.devices.timezone.with_raw_response.get( + device_id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_set(self, client: Mobilerun) -> None: + timezone = client.devices.timezone.set( + device_id="deviceId", + timezone="timezone", + ) + assert timezone is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_set_with_all_params(self, client: Mobilerun) -> None: + timezone = client.devices.timezone.set( + device_id="deviceId", + timezone="timezone", + x_device_display_id=0, + ) + assert timezone is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_set(self, client: Mobilerun) -> None: + response = client.devices.timezone.with_raw_response.set( + device_id="deviceId", + timezone="timezone", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + timezone = response.parse() + assert timezone is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_set(self, client: Mobilerun) -> None: + with client.devices.timezone.with_streaming_response.set( + device_id="deviceId", + timezone="timezone", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + timezone = response.parse() + assert timezone is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_set(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + client.devices.timezone.with_raw_response.set( + device_id="", + timezone="timezone", + ) + + +class TestAsyncTimezone: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_get(self, async_client: AsyncMobilerun) -> None: + timezone = await async_client.devices.timezone.get( + device_id="deviceId", + ) + assert_matches_type(TimezoneGetResponse, timezone, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_get_with_all_params(self, async_client: AsyncMobilerun) -> None: + timezone = await async_client.devices.timezone.get( + device_id="deviceId", + x_device_display_id=0, + ) + assert_matches_type(TimezoneGetResponse, timezone, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_get(self, async_client: AsyncMobilerun) -> None: + response = await async_client.devices.timezone.with_raw_response.get( + device_id="deviceId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + timezone = await response.parse() + assert_matches_type(TimezoneGetResponse, timezone, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_get(self, async_client: AsyncMobilerun) -> None: + async with async_client.devices.timezone.with_streaming_response.get( + device_id="deviceId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + timezone = await response.parse() + assert_matches_type(TimezoneGetResponse, timezone, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_get(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + await async_client.devices.timezone.with_raw_response.get( + device_id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_set(self, async_client: AsyncMobilerun) -> None: + timezone = await async_client.devices.timezone.set( + device_id="deviceId", + timezone="timezone", + ) + assert timezone is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_set_with_all_params(self, async_client: AsyncMobilerun) -> None: + timezone = await async_client.devices.timezone.set( + device_id="deviceId", + timezone="timezone", + x_device_display_id=0, + ) + assert timezone is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_set(self, async_client: AsyncMobilerun) -> None: + response = await async_client.devices.timezone.with_raw_response.set( + device_id="deviceId", + timezone="timezone", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + timezone = await response.parse() + assert timezone is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_set(self, async_client: AsyncMobilerun) -> None: + async with async_client.devices.timezone.with_streaming_response.set( + device_id="deviceId", + timezone="timezone", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + timezone = await response.parse() + assert timezone is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_set(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + await async_client.devices.timezone.with_raw_response.set( + device_id="", + timezone="timezone", + ) diff --git a/tests/api_resources/test_apps.py b/tests/api_resources/test_apps.py index 0015cdc..887a5ee 100644 --- a/tests/api_resources/test_apps.py +++ b/tests/api_resources/test_apps.py @@ -9,7 +9,14 @@ from tests.utils import assert_matches_type from mobilerun_sdk import Mobilerun, AsyncMobilerun -from mobilerun_sdk.types import AppListResponse +from mobilerun_sdk.types import ( + AppListResponse, + AppDeleteResponse, + AppRetrieveResponse, + AppMarkFailedResponse, + AppConfirmUploadResponse, + AppCreateSignedUploadURLResponse, +) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -17,6 +24,48 @@ class TestApps: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: Mobilerun) -> None: + app = client.apps.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AppRetrieveResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Mobilerun) -> None: + response = client.apps.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + app = response.parse() + assert_matches_type(AppRetrieveResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Mobilerun) -> None: + with client.apps.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + app = response.parse() + assert_matches_type(AppRetrieveResponse, app, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.apps.with_raw_response.retrieve( + "", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list(self, client: Mobilerun) -> None: @@ -30,9 +79,10 @@ def test_method_list_with_all_params(self, client: Mobilerun) -> None: order="asc", page=1, page_size=1, + platform="all", query="query", sort_by="createdAt", - source="all", + status="all", ) assert_matches_type(AppListResponse, app, path=["response"]) @@ -58,12 +108,268 @@ def test_streaming_response_list(self, client: Mobilerun) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_delete(self, client: Mobilerun) -> None: + app = client.apps.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AppDeleteResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_delete(self, client: Mobilerun) -> None: + response = client.apps.with_raw_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + app = response.parse() + assert_matches_type(AppDeleteResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: Mobilerun) -> None: + with client.apps.with_streaming_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + app = response.parse() + assert_matches_type(AppDeleteResponse, app, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_delete(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.apps.with_raw_response.delete( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_confirm_upload(self, client: Mobilerun) -> None: + app = client.apps.confirm_upload( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AppConfirmUploadResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_confirm_upload(self, client: Mobilerun) -> None: + response = client.apps.with_raw_response.confirm_upload( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + app = response.parse() + assert_matches_type(AppConfirmUploadResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_confirm_upload(self, client: Mobilerun) -> None: + with client.apps.with_streaming_response.confirm_upload( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + app = response.parse() + assert_matches_type(AppConfirmUploadResponse, app, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_confirm_upload(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.apps.with_raw_response.confirm_upload( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_signed_upload_url(self, client: Mobilerun) -> None: + app = client.apps.create_signed_upload_url( + bundle_id="x", + display_name="x", + files=[ + { + "content_type": "x", + "file_name": "x", + } + ], + size_bytes=0, + version_code=0, + version_name="x", + ) + assert_matches_type(AppCreateSignedUploadURLResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_signed_upload_url_with_all_params(self, client: Mobilerun) -> None: + app = client.apps.create_signed_upload_url( + bundle_id="x", + display_name="x", + files=[ + { + "content_type": "x", + "file_name": "x", + } + ], + size_bytes=0, + version_code=0, + version_name="x", + country="US", + description="description", + developer_name="developerName", + icon_url="iconURL", + platform="android", + target_sdk=0, + ) + assert_matches_type(AppCreateSignedUploadURLResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create_signed_upload_url(self, client: Mobilerun) -> None: + response = client.apps.with_raw_response.create_signed_upload_url( + bundle_id="x", + display_name="x", + files=[ + { + "content_type": "x", + "file_name": "x", + } + ], + size_bytes=0, + version_code=0, + version_name="x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + app = response.parse() + assert_matches_type(AppCreateSignedUploadURLResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create_signed_upload_url(self, client: Mobilerun) -> None: + with client.apps.with_streaming_response.create_signed_upload_url( + bundle_id="x", + display_name="x", + files=[ + { + "content_type": "x", + "file_name": "x", + } + ], + size_bytes=0, + version_code=0, + version_name="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + app = response.parse() + assert_matches_type(AppCreateSignedUploadURLResponse, app, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_mark_failed(self, client: Mobilerun) -> None: + app = client.apps.mark_failed( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AppMarkFailedResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_mark_failed(self, client: Mobilerun) -> None: + response = client.apps.with_raw_response.mark_failed( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + app = response.parse() + assert_matches_type(AppMarkFailedResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_mark_failed(self, client: Mobilerun) -> None: + with client.apps.with_streaming_response.mark_failed( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + app = response.parse() + assert_matches_type(AppMarkFailedResponse, app, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_mark_failed(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.apps.with_raw_response.mark_failed( + "", + ) + class TestAsyncApps: parametrize = pytest.mark.parametrize( "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncMobilerun) -> None: + app = await async_client.apps.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AppRetrieveResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncMobilerun) -> None: + response = await async_client.apps.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + app = await response.parse() + assert_matches_type(AppRetrieveResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncMobilerun) -> None: + async with async_client.apps.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + app = await response.parse() + assert_matches_type(AppRetrieveResponse, app, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.apps.with_raw_response.retrieve( + "", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncMobilerun) -> None: @@ -77,9 +383,10 @@ async def test_method_list_with_all_params(self, async_client: AsyncMobilerun) - order="asc", page=1, page_size=1, + platform="all", query="query", sort_by="createdAt", - source="all", + status="all", ) assert_matches_type(AppListResponse, app, path=["response"]) @@ -104,3 +411,217 @@ async def test_streaming_response_list(self, async_client: AsyncMobilerun) -> No assert_matches_type(AppListResponse, app, path=["response"]) assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_delete(self, async_client: AsyncMobilerun) -> None: + app = await async_client.apps.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AppDeleteResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_delete(self, async_client: AsyncMobilerun) -> None: + response = await async_client.apps.with_raw_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + app = await response.parse() + assert_matches_type(AppDeleteResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncMobilerun) -> None: + async with async_client.apps.with_streaming_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + app = await response.parse() + assert_matches_type(AppDeleteResponse, app, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_delete(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.apps.with_raw_response.delete( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_confirm_upload(self, async_client: AsyncMobilerun) -> None: + app = await async_client.apps.confirm_upload( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AppConfirmUploadResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_confirm_upload(self, async_client: AsyncMobilerun) -> None: + response = await async_client.apps.with_raw_response.confirm_upload( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + app = await response.parse() + assert_matches_type(AppConfirmUploadResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_confirm_upload(self, async_client: AsyncMobilerun) -> None: + async with async_client.apps.with_streaming_response.confirm_upload( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + app = await response.parse() + assert_matches_type(AppConfirmUploadResponse, app, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_confirm_upload(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.apps.with_raw_response.confirm_upload( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_signed_upload_url(self, async_client: AsyncMobilerun) -> None: + app = await async_client.apps.create_signed_upload_url( + bundle_id="x", + display_name="x", + files=[ + { + "content_type": "x", + "file_name": "x", + } + ], + size_bytes=0, + version_code=0, + version_name="x", + ) + assert_matches_type(AppCreateSignedUploadURLResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_signed_upload_url_with_all_params(self, async_client: AsyncMobilerun) -> None: + app = await async_client.apps.create_signed_upload_url( + bundle_id="x", + display_name="x", + files=[ + { + "content_type": "x", + "file_name": "x", + } + ], + size_bytes=0, + version_code=0, + version_name="x", + country="US", + description="description", + developer_name="developerName", + icon_url="iconURL", + platform="android", + target_sdk=0, + ) + assert_matches_type(AppCreateSignedUploadURLResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create_signed_upload_url(self, async_client: AsyncMobilerun) -> None: + response = await async_client.apps.with_raw_response.create_signed_upload_url( + bundle_id="x", + display_name="x", + files=[ + { + "content_type": "x", + "file_name": "x", + } + ], + size_bytes=0, + version_code=0, + version_name="x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + app = await response.parse() + assert_matches_type(AppCreateSignedUploadURLResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create_signed_upload_url(self, async_client: AsyncMobilerun) -> None: + async with async_client.apps.with_streaming_response.create_signed_upload_url( + bundle_id="x", + display_name="x", + files=[ + { + "content_type": "x", + "file_name": "x", + } + ], + size_bytes=0, + version_code=0, + version_name="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + app = await response.parse() + assert_matches_type(AppCreateSignedUploadURLResponse, app, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_mark_failed(self, async_client: AsyncMobilerun) -> None: + app = await async_client.apps.mark_failed( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AppMarkFailedResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_mark_failed(self, async_client: AsyncMobilerun) -> None: + response = await async_client.apps.with_raw_response.mark_failed( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + app = await response.parse() + assert_matches_type(AppMarkFailedResponse, app, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_mark_failed(self, async_client: AsyncMobilerun) -> None: + async with async_client.apps.with_streaming_response.mark_failed( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + app = await response.parse() + assert_matches_type(AppMarkFailedResponse, app, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_mark_failed(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.apps.with_raw_response.mark_failed( + "", + ) diff --git a/tests/api_resources/test_carriers.py b/tests/api_resources/test_carriers.py index 429c77d..537784a 100644 --- a/tests/api_resources/test_carriers.py +++ b/tests/api_resources/test_carriers.py @@ -10,9 +10,12 @@ from tests.utils import assert_matches_type from mobilerun_sdk import Mobilerun, AsyncMobilerun from mobilerun_sdk.types import ( - Carrier, CarrierListResponse, + CarrierCreateResponse, CarrierDeleteResponse, + CarrierLookupResponse, + CarrierUpdateResponse, + CarrierRetrieveResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -30,7 +33,7 @@ def test_method_create(self, client: Mobilerun) -> None: mnc="x", operator="x", ) - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierCreateResponse, carrier, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -53,7 +56,7 @@ def test_method_create_with_all_params(self, client: Mobilerun) -> None: umts_bands="umts_bands", website="website", ) - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierCreateResponse, carrier, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -68,7 +71,7 @@ def test_raw_response_create(self, client: Mobilerun) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" carrier = response.parse() - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierCreateResponse, carrier, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -83,7 +86,7 @@ def test_streaming_response_create(self, client: Mobilerun) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" carrier = response.parse() - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierCreateResponse, carrier, path=["response"]) assert cast(Any, response.is_closed) is True @@ -93,7 +96,7 @@ def test_method_retrieve(self, client: Mobilerun) -> None: carrier = client.carriers.retrieve( 1, ) - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierRetrieveResponse, carrier, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -105,7 +108,7 @@ def test_raw_response_retrieve(self, client: Mobilerun) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" carrier = response.parse() - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierRetrieveResponse, carrier, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -117,7 +120,7 @@ def test_streaming_response_retrieve(self, client: Mobilerun) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" carrier = response.parse() - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierRetrieveResponse, carrier, path=["response"]) assert cast(Any, response.is_closed) is True @@ -127,7 +130,7 @@ def test_method_update(self, client: Mobilerun) -> None: carrier = client.carriers.update( carrier_id=1, ) - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierUpdateResponse, carrier, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -149,7 +152,7 @@ def test_method_update_with_all_params(self, client: Mobilerun) -> None: umts_bands="umts_bands", website="website", ) - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierUpdateResponse, carrier, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -161,7 +164,7 @@ def test_raw_response_update(self, client: Mobilerun) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" carrier = response.parse() - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierUpdateResponse, carrier, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -173,7 +176,7 @@ def test_streaming_response_update(self, client: Mobilerun) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" carrier = response.parse() - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierUpdateResponse, carrier, path=["response"]) assert cast(Any, response.is_closed) is True @@ -259,7 +262,7 @@ def test_method_lookup(self, client: Mobilerun) -> None: mcc="x", mnc="x", ) - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierLookupResponse, carrier, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -272,7 +275,7 @@ def test_raw_response_lookup(self, client: Mobilerun) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" carrier = response.parse() - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierLookupResponse, carrier, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -285,7 +288,7 @@ def test_streaming_response_lookup(self, client: Mobilerun) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" carrier = response.parse() - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierLookupResponse, carrier, path=["response"]) assert cast(Any, response.is_closed) is True @@ -304,7 +307,7 @@ async def test_method_create(self, async_client: AsyncMobilerun) -> None: mnc="x", operator="x", ) - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierCreateResponse, carrier, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -327,7 +330,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncMobilerun) umts_bands="umts_bands", website="website", ) - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierCreateResponse, carrier, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -342,7 +345,7 @@ async def test_raw_response_create(self, async_client: AsyncMobilerun) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" carrier = await response.parse() - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierCreateResponse, carrier, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -357,7 +360,7 @@ async def test_streaming_response_create(self, async_client: AsyncMobilerun) -> assert response.http_request.headers.get("X-Stainless-Lang") == "python" carrier = await response.parse() - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierCreateResponse, carrier, path=["response"]) assert cast(Any, response.is_closed) is True @@ -367,7 +370,7 @@ async def test_method_retrieve(self, async_client: AsyncMobilerun) -> None: carrier = await async_client.carriers.retrieve( 1, ) - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierRetrieveResponse, carrier, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -379,7 +382,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncMobilerun) -> None assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" carrier = await response.parse() - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierRetrieveResponse, carrier, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -391,7 +394,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncMobilerun) - assert response.http_request.headers.get("X-Stainless-Lang") == "python" carrier = await response.parse() - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierRetrieveResponse, carrier, path=["response"]) assert cast(Any, response.is_closed) is True @@ -401,7 +404,7 @@ async def test_method_update(self, async_client: AsyncMobilerun) -> None: carrier = await async_client.carriers.update( carrier_id=1, ) - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierUpdateResponse, carrier, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -423,7 +426,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncMobilerun) umts_bands="umts_bands", website="website", ) - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierUpdateResponse, carrier, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -435,7 +438,7 @@ async def test_raw_response_update(self, async_client: AsyncMobilerun) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" carrier = await response.parse() - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierUpdateResponse, carrier, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -447,7 +450,7 @@ async def test_streaming_response_update(self, async_client: AsyncMobilerun) -> assert response.http_request.headers.get("X-Stainless-Lang") == "python" carrier = await response.parse() - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierUpdateResponse, carrier, path=["response"]) assert cast(Any, response.is_closed) is True @@ -533,7 +536,7 @@ async def test_method_lookup(self, async_client: AsyncMobilerun) -> None: mcc="x", mnc="x", ) - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierLookupResponse, carrier, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -546,7 +549,7 @@ async def test_raw_response_lookup(self, async_client: AsyncMobilerun) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" carrier = await response.parse() - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierLookupResponse, carrier, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -559,6 +562,6 @@ async def test_streaming_response_lookup(self, async_client: AsyncMobilerun) -> assert response.http_request.headers.get("X-Stainless-Lang") == "python" carrier = await response.parse() - assert_matches_type(Carrier, carrier, path=["response"]) + assert_matches_type(CarrierLookupResponse, carrier, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_devices.py b/tests/api_resources/test_devices.py index 975ae92..c70afb3 100644 --- a/tests/api_resources/test_devices.py +++ b/tests/api_resources/test_devices.py @@ -13,6 +13,7 @@ Device, DeviceListResponse, DeviceCountResponse, + DeviceFingerprintResponse, ) from mobilerun_sdk._utils import parse_datetime @@ -32,7 +33,10 @@ def test_method_create(self, client: Mobilerun) -> None: @parametrize def test_method_create_with_all_params(self, client: Mobilerun) -> None: device = client.devices.create( + query_country="country", device_type="dedicated_physical_device", + profile_id="profileId", + android_version=0, apps=["string"], carrier={ "gsm_operator_alpha": "GsmOperatorAlpha", @@ -42,6 +46,7 @@ def test_method_create_with_all_params(self, client: Mobilerun) -> None: "gsm_sim_operator_numeric": 0, "persist_sys_timezone": "PersistSysTimezone", }, + body_country="country", files=["string"], identifiers={ "bootloader_serial_number": "BootloaderSerialNumber", @@ -60,6 +65,11 @@ def test_method_create_with_all_params(self, client: Mobilerun) -> None: "identifier_wifi_mac": "IdentifierWifiMAC", "serial_number": "SerialNumber", }, + locale="locale", + location={ + "latitude": 0, + "longitude": 0, + }, name="name", proxy={ "name": "name", @@ -70,8 +80,8 @@ def test_method_create_with_all_params(self, client: Mobilerun) -> None: "port": 0, "user": "user", }, - "wireguard": "wireguard", }, + timezone="timezone", ) assert_matches_type(Device, device, path=["response"]) @@ -211,6 +221,141 @@ def test_streaming_response_count(self, client: Mobilerun) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_fingerprint(self, client: Mobilerun) -> None: + device = client.devices.fingerprint( + device_id="deviceId", + ) + assert_matches_type(DeviceFingerprintResponse, device, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_fingerprint_with_all_params(self, client: Mobilerun) -> None: + device = client.devices.fingerprint( + device_id="deviceId", + x_device_display_id=0, + ) + assert_matches_type(DeviceFingerprintResponse, device, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_fingerprint(self, client: Mobilerun) -> None: + response = client.devices.with_raw_response.fingerprint( + device_id="deviceId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + device = response.parse() + assert_matches_type(DeviceFingerprintResponse, device, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_fingerprint(self, client: Mobilerun) -> None: + with client.devices.with_streaming_response.fingerprint( + device_id="deviceId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + device = response.parse() + assert_matches_type(DeviceFingerprintResponse, device, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_fingerprint(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + client.devices.with_raw_response.fingerprint( + device_id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_reboot(self, client: Mobilerun) -> None: + device = client.devices.reboot( + "deviceId", + ) + assert device is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_reboot(self, client: Mobilerun) -> None: + response = client.devices.with_raw_response.reboot( + "deviceId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + device = response.parse() + assert device is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_reboot(self, client: Mobilerun) -> None: + with client.devices.with_streaming_response.reboot( + "deviceId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + device = response.parse() + assert device is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_reboot(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + client.devices.with_raw_response.reboot( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_reset(self, client: Mobilerun) -> None: + device = client.devices.reset( + "deviceId", + ) + assert device is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_reset(self, client: Mobilerun) -> None: + response = client.devices.with_raw_response.reset( + "deviceId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + device = response.parse() + assert device is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_reset(self, client: Mobilerun) -> None: + with client.devices.with_streaming_response.reset( + "deviceId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + device = response.parse() + assert device is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_reset(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + client.devices.with_raw_response.reset( + "", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_set_name(self, client: Mobilerun) -> None: @@ -367,7 +512,10 @@ async def test_method_create(self, async_client: AsyncMobilerun) -> None: @parametrize async def test_method_create_with_all_params(self, async_client: AsyncMobilerun) -> None: device = await async_client.devices.create( + query_country="country", device_type="dedicated_physical_device", + profile_id="profileId", + android_version=0, apps=["string"], carrier={ "gsm_operator_alpha": "GsmOperatorAlpha", @@ -377,6 +525,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncMobilerun) "gsm_sim_operator_numeric": 0, "persist_sys_timezone": "PersistSysTimezone", }, + body_country="country", files=["string"], identifiers={ "bootloader_serial_number": "BootloaderSerialNumber", @@ -395,6 +544,11 @@ async def test_method_create_with_all_params(self, async_client: AsyncMobilerun) "identifier_wifi_mac": "IdentifierWifiMAC", "serial_number": "SerialNumber", }, + locale="locale", + location={ + "latitude": 0, + "longitude": 0, + }, name="name", proxy={ "name": "name", @@ -405,8 +559,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncMobilerun) "port": 0, "user": "user", }, - "wireguard": "wireguard", }, + timezone="timezone", ) assert_matches_type(Device, device, path=["response"]) @@ -546,6 +700,141 @@ async def test_streaming_response_count(self, async_client: AsyncMobilerun) -> N assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_fingerprint(self, async_client: AsyncMobilerun) -> None: + device = await async_client.devices.fingerprint( + device_id="deviceId", + ) + assert_matches_type(DeviceFingerprintResponse, device, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_fingerprint_with_all_params(self, async_client: AsyncMobilerun) -> None: + device = await async_client.devices.fingerprint( + device_id="deviceId", + x_device_display_id=0, + ) + assert_matches_type(DeviceFingerprintResponse, device, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_fingerprint(self, async_client: AsyncMobilerun) -> None: + response = await async_client.devices.with_raw_response.fingerprint( + device_id="deviceId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + device = await response.parse() + assert_matches_type(DeviceFingerprintResponse, device, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_fingerprint(self, async_client: AsyncMobilerun) -> None: + async with async_client.devices.with_streaming_response.fingerprint( + device_id="deviceId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + device = await response.parse() + assert_matches_type(DeviceFingerprintResponse, device, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_fingerprint(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + await async_client.devices.with_raw_response.fingerprint( + device_id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_reboot(self, async_client: AsyncMobilerun) -> None: + device = await async_client.devices.reboot( + "deviceId", + ) + assert device is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_reboot(self, async_client: AsyncMobilerun) -> None: + response = await async_client.devices.with_raw_response.reboot( + "deviceId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + device = await response.parse() + assert device is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_reboot(self, async_client: AsyncMobilerun) -> None: + async with async_client.devices.with_streaming_response.reboot( + "deviceId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + device = await response.parse() + assert device is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_reboot(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + await async_client.devices.with_raw_response.reboot( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_reset(self, async_client: AsyncMobilerun) -> None: + device = await async_client.devices.reset( + "deviceId", + ) + assert device is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_reset(self, async_client: AsyncMobilerun) -> None: + response = await async_client.devices.with_raw_response.reset( + "deviceId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + device = await response.parse() + assert device is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_reset(self, async_client: AsyncMobilerun) -> None: + async with async_client.devices.with_streaming_response.reset( + "deviceId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + device = await response.parse() + assert device is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_reset(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): + await async_client.devices.with_raw_response.reset( + "", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_set_name(self, async_client: AsyncMobilerun) -> None: diff --git a/tests/api_resources/test_profiles.py b/tests/api_resources/test_profiles.py index 6c557de..404e8d8 100644 --- a/tests/api_resources/test_profiles.py +++ b/tests/api_resources/test_profiles.py @@ -36,6 +36,7 @@ def test_method_create_with_all_params(self, client: Mobilerun) -> None: profile = client.profiles.create( name="x", spec={ + "android_version": 0, "apps": ["string"], "carrier": { "gsm_operator_alpha": "GsmOperatorAlpha", @@ -45,6 +46,7 @@ def test_method_create_with_all_params(self, client: Mobilerun) -> None: "gsm_sim_operator_numeric": 0, "persist_sys_timezone": "PersistSysTimezone", }, + "country": "country", "files": ["string"], "identifiers": { "bootloader_serial_number": "BootloaderSerialNumber", @@ -63,6 +65,11 @@ def test_method_create_with_all_params(self, client: Mobilerun) -> None: "identifier_wifi_mac": "IdentifierWifiMAC", "serial_number": "SerialNumber", }, + "locale": "locale", + "location": { + "latitude": 0, + "longitude": 0, + }, "name": "name", "proxy": { "name": "name", @@ -73,8 +80,8 @@ def test_method_create_with_all_params(self, client: Mobilerun) -> None: "port": 0, "user": "user", }, - "wireguard": "wireguard", }, + "timezone": "timezone", }, ) assert_matches_type(Profile, profile, path=["response"]) @@ -166,6 +173,7 @@ def test_method_update_with_all_params(self, client: Mobilerun) -> None: profile_id="profileId", name="x", spec={ + "android_version": 0, "apps": ["string"], "carrier": { "gsm_operator_alpha": "GsmOperatorAlpha", @@ -175,6 +183,7 @@ def test_method_update_with_all_params(self, client: Mobilerun) -> None: "gsm_sim_operator_numeric": 0, "persist_sys_timezone": "PersistSysTimezone", }, + "country": "country", "files": ["string"], "identifiers": { "bootloader_serial_number": "BootloaderSerialNumber", @@ -193,6 +202,11 @@ def test_method_update_with_all_params(self, client: Mobilerun) -> None: "identifier_wifi_mac": "IdentifierWifiMAC", "serial_number": "SerialNumber", }, + "locale": "locale", + "location": { + "latitude": 0, + "longitude": 0, + }, "name": "name", "proxy": { "name": "name", @@ -203,8 +217,8 @@ def test_method_update_with_all_params(self, client: Mobilerun) -> None: "port": 0, "user": "user", }, - "wireguard": "wireguard", }, + "timezone": "timezone", }, ) assert_matches_type(Profile, profile, path=["response"]) @@ -352,6 +366,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncMobilerun) profile = await async_client.profiles.create( name="x", spec={ + "android_version": 0, "apps": ["string"], "carrier": { "gsm_operator_alpha": "GsmOperatorAlpha", @@ -361,6 +376,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncMobilerun) "gsm_sim_operator_numeric": 0, "persist_sys_timezone": "PersistSysTimezone", }, + "country": "country", "files": ["string"], "identifiers": { "bootloader_serial_number": "BootloaderSerialNumber", @@ -379,6 +395,11 @@ async def test_method_create_with_all_params(self, async_client: AsyncMobilerun) "identifier_wifi_mac": "IdentifierWifiMAC", "serial_number": "SerialNumber", }, + "locale": "locale", + "location": { + "latitude": 0, + "longitude": 0, + }, "name": "name", "proxy": { "name": "name", @@ -389,8 +410,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncMobilerun) "port": 0, "user": "user", }, - "wireguard": "wireguard", }, + "timezone": "timezone", }, ) assert_matches_type(Profile, profile, path=["response"]) @@ -482,6 +503,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncMobilerun) profile_id="profileId", name="x", spec={ + "android_version": 0, "apps": ["string"], "carrier": { "gsm_operator_alpha": "GsmOperatorAlpha", @@ -491,6 +513,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncMobilerun) "gsm_sim_operator_numeric": 0, "persist_sys_timezone": "PersistSysTimezone", }, + "country": "country", "files": ["string"], "identifiers": { "bootloader_serial_number": "BootloaderSerialNumber", @@ -509,6 +532,11 @@ async def test_method_update_with_all_params(self, async_client: AsyncMobilerun) "identifier_wifi_mac": "IdentifierWifiMAC", "serial_number": "SerialNumber", }, + "locale": "locale", + "location": { + "latitude": 0, + "longitude": 0, + }, "name": "name", "proxy": { "name": "name", @@ -519,8 +547,8 @@ async def test_method_update_with_all_params(self, async_client: AsyncMobilerun) "port": 0, "user": "user", }, - "wireguard": "wireguard", }, + "timezone": "timezone", }, ) assert_matches_type(Profile, profile, path=["response"]) diff --git a/tests/test_deepcopy.py b/tests/test_deepcopy.py deleted file mode 100644 index db8149c..0000000 --- a/tests/test_deepcopy.py +++ /dev/null @@ -1,58 +0,0 @@ -from mobilerun_sdk._utils import deepcopy_minimal - - -def assert_different_identities(obj1: object, obj2: object) -> None: - assert obj1 == obj2 - assert id(obj1) != id(obj2) - - -def test_simple_dict() -> None: - obj1 = {"foo": "bar"} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - - -def test_nested_dict() -> None: - obj1 = {"foo": {"bar": True}} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert_different_identities(obj1["foo"], obj2["foo"]) - - -def test_complex_nested_dict() -> None: - obj1 = {"foo": {"bar": [{"hello": "world"}]}} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert_different_identities(obj1["foo"], obj2["foo"]) - assert_different_identities(obj1["foo"]["bar"], obj2["foo"]["bar"]) - assert_different_identities(obj1["foo"]["bar"][0], obj2["foo"]["bar"][0]) - - -def test_simple_list() -> None: - obj1 = ["a", "b", "c"] - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - - -def test_nested_list() -> None: - obj1 = ["a", [1, 2, 3]] - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert_different_identities(obj1[1], obj2[1]) - - -class MyObject: ... - - -def test_ignores_other_types() -> None: - # custom classes - my_obj = MyObject() - obj1 = {"foo": my_obj} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert obj1["foo"] is my_obj - - # tuples - obj3 = ("a", "b") - obj4 = deepcopy_minimal(obj3) - assert obj3 is obj4 diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py index 5792e00..bd4e31b 100644 --- a/tests/test_extract_files.py +++ b/tests/test_extract_files.py @@ -4,7 +4,7 @@ import pytest -from mobilerun_sdk._types import FileTypes +from mobilerun_sdk._types import FileTypes, ArrayFormat from mobilerun_sdk._utils import extract_files @@ -37,10 +37,7 @@ def test_multiple_files() -> None: def test_top_level_file_array() -> None: query = {"files": [b"file one", b"file two"], "title": "hello"} - assert extract_files(query, paths=[["files", ""]]) == [ - ("files[]", b"file one"), - ("files[]", b"file two"), - ] + assert extract_files(query, paths=[["files", ""]]) == [("files[]", b"file one"), ("files[]", b"file two")] assert query == {"title": "hello"} @@ -71,3 +68,24 @@ def test_ignores_incorrect_paths( expected: list[tuple[str, FileTypes]], ) -> None: assert extract_files(query, paths=paths) == expected + + +@pytest.mark.parametrize( + "array_format,expected_top_level,expected_nested", + [ + ("brackets", [("files[]", b"a"), ("files[]", b"b")], [("items[][file]", b"a"), ("items[][file]", b"b")]), + ("repeat", [("files", b"a"), ("files", b"b")], [("items[file]", b"a"), ("items[file]", b"b")]), + ("comma", [("files", b"a"), ("files", b"b")], [("items[file]", b"a"), ("items[file]", b"b")]), + ("indices", [("files[0]", b"a"), ("files[1]", b"b")], [("items[0][file]", b"a"), ("items[1][file]", b"b")]), + ], +) +def test_array_format_controls_file_field_names( + array_format: ArrayFormat, + expected_top_level: list[tuple[str, FileTypes]], + expected_nested: list[tuple[str, FileTypes]], +) -> None: + top_level = {"files": [b"a", b"b"]} + assert extract_files(top_level, paths=[["files", ""]], array_format=array_format) == expected_top_level + + nested = {"items": [{"file": b"a"}, {"file": b"b"}]} + assert extract_files(nested, paths=[["items", "", "file"]], array_format=array_format) == expected_nested diff --git a/tests/test_files.py b/tests/test_files.py index 7d4e3e7..97fe070 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -4,7 +4,8 @@ import pytest from dirty_equals import IsDict, IsList, IsBytes, IsTuple -from mobilerun_sdk._files import to_httpx_files, async_to_httpx_files +from mobilerun_sdk._files import to_httpx_files, deepcopy_with_paths, async_to_httpx_files +from mobilerun_sdk._utils import extract_files readme_path = Path(__file__).parent.parent.joinpath("README.md") @@ -49,3 +50,99 @@ def test_string_not_allowed() -> None: "file": "foo", # type: ignore } ) + + +def assert_different_identities(obj1: object, obj2: object) -> None: + assert obj1 == obj2 + assert obj1 is not obj2 + + +class TestDeepcopyWithPaths: + def test_copies_top_level_dict(self) -> None: + original = {"file": b"data", "other": "value"} + result = deepcopy_with_paths(original, [["file"]]) + assert_different_identities(result, original) + + def test_file_value_is_same_reference(self) -> None: + file_bytes = b"contents" + original = {"file": file_bytes} + result = deepcopy_with_paths(original, [["file"]]) + assert_different_identities(result, original) + assert result["file"] is file_bytes + + def test_list_popped_wholesale(self) -> None: + files = [b"f1", b"f2"] + original = {"files": files, "title": "t"} + result = deepcopy_with_paths(original, [["files", ""]]) + assert_different_identities(result, original) + result_files = result["files"] + assert isinstance(result_files, list) + assert_different_identities(result_files, files) + + def test_nested_array_path_copies_list_and_elements(self) -> None: + elem1 = {"file": b"f1", "extra": 1} + elem2 = {"file": b"f2", "extra": 2} + original = {"items": [elem1, elem2]} + result = deepcopy_with_paths(original, [["items", "", "file"]]) + assert_different_identities(result, original) + result_items = result["items"] + assert isinstance(result_items, list) + assert_different_identities(result_items, original["items"]) + assert_different_identities(result_items[0], elem1) + assert_different_identities(result_items[1], elem2) + + def test_empty_paths_returns_same_object(self) -> None: + original = {"foo": "bar"} + result = deepcopy_with_paths(original, []) + assert result is original + + def test_multiple_paths(self) -> None: + f1 = b"file1" + f2 = b"file2" + original = {"a": f1, "b": f2, "c": "unchanged"} + result = deepcopy_with_paths(original, [["a"], ["b"]]) + assert_different_identities(result, original) + assert result["a"] is f1 + assert result["b"] is f2 + assert result["c"] is original["c"] + + def test_extract_files_does_not_mutate_original_top_level(self) -> None: + file_bytes = b"contents" + original = {"file": file_bytes, "other": "value"} + + copied = deepcopy_with_paths(original, [["file"]]) + extracted = extract_files(copied, paths=[["file"]]) + + assert extracted == [("file", file_bytes)] + assert original == {"file": file_bytes, "other": "value"} + assert copied == {"other": "value"} + + def test_extract_files_does_not_mutate_original_nested_array_path(self) -> None: + file1 = b"f1" + file2 = b"f2" + original = { + "items": [ + {"file": file1, "extra": 1}, + {"file": file2, "extra": 2}, + ], + "title": "example", + } + + copied = deepcopy_with_paths(original, [["items", "", "file"]]) + extracted = extract_files(copied, paths=[["items", "", "file"]]) + + assert [entry for _, entry in extracted] == [file1, file2] + assert original == { + "items": [ + {"file": file1, "extra": 1}, + {"file": file2, "extra": 2}, + ], + "title": "example", + } + assert copied == { + "items": [ + {"extra": 1}, + {"extra": 2}, + ], + "title": "example", + } diff --git a/tests/test_models.py b/tests/test_models.py index 36abb13..231e44e 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,7 +1,8 @@ import json -from typing import TYPE_CHECKING, Any, Dict, List, Union, Optional, cast +from typing import TYPE_CHECKING, Any, Dict, List, Union, Iterable, Optional, cast from datetime import datetime, timezone -from typing_extensions import Literal, Annotated, TypeAliasType +from collections import deque +from typing_extensions import Literal, Annotated, TypedDict, TypeAliasType import pytest import pydantic @@ -9,7 +10,7 @@ from mobilerun_sdk._utils import PropertyInfo from mobilerun_sdk._compat import PYDANTIC_V1, parse_obj, model_dump, model_json -from mobilerun_sdk._models import DISCRIMINATOR_CACHE, BaseModel, construct_type +from mobilerun_sdk._models import DISCRIMINATOR_CACHE, BaseModel, EagerIterable, construct_type class BasicModel(BaseModel): @@ -961,3 +962,56 @@ def __getattr__(self, attr: str) -> Item: ... assert model.a.prop == 1 assert isinstance(model.a, Item) assert model.other == "foo" + + +# NOTE: Workaround for Pydantic Iterable behavior. +# Iterable fields are replaced with a ValidatorIterator and may be consumed +# during serialization, which can cause subsequent dumps to return empty data. +# See: https://github.com/pydantic/pydantic/issues/9541 +@pytest.mark.parametrize( + "data, expected_validated", + [ + ([1, 2, 3], [1, 2, 3]), + ((1, 2, 3), (1, 2, 3)), + (set([1, 2, 3]), set([1, 2, 3])), + (iter([1, 2, 3]), [1, 2, 3]), + ([], []), + ((x for x in [1, 2, 3]), [1, 2, 3]), + (map(lambda x: x, [1, 2, 3]), [1, 2, 3]), + (frozenset([1, 2, 3]), frozenset([1, 2, 3])), + (deque([1, 2, 3]), deque([1, 2, 3])), + ], + ids=["list", "tuple", "set", "iterator", "empty", "generator", "map", "frozenset", "deque"], +) +@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2") +def test_iterable_construction(data: Iterable[int], expected_validated: Iterable[int]) -> None: + class TypeWithIterable(TypedDict): + items: EagerIterable[int] + + class Model(BaseModel): + data: TypeWithIterable + + m = Model.model_validate({"data": {"items": data}}) + assert m.data["items"] == expected_validated + + # Verify repeated dumps don't lose data (the original bug) + assert m.model_dump()["data"]["items"] == list(expected_validated) + assert m.model_dump()["data"]["items"] == list(expected_validated) + + +@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2") +def test_iterable_construction_str_falls_back_to_list() -> None: + # str is iterable (over chars), but str(list_of_chars) produces the list's repr + # rather than reconstructing a string from items. We special-case str to fall + # back to list instead of attempting reconstruction. + class TypeWithIterable(TypedDict): + items: EagerIterable[str] + + class Model(BaseModel): + data: TypeWithIterable + + m = Model.model_validate({"data": {"items": "hello"}}) + + # falls back to list of chars rather than calling str(["h", "e", "l", "l", "o"]) + assert m.data["items"] == ["h", "e", "l", "l", "o"] + assert m.model_dump()["data"]["items"] == ["h", "e", "l", "l", "o"]