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..4d26ea4 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: 94
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/droidrun/droidrun-cloud-c33c250c3235c14a1e5a173b18ef347fe2789e0b21f7e1b0649bd27ae255975e.yml
+openapi_spec_hash: 7710fc1424f709edd45d9dfe90121649
+config_hash: 2202392fa81fb6cad4fcd79e62717a43
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0b66e4c..2dc4853 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,37 @@
# Changelog
+## 3.2.0 (2026-05-13)
+
+Full Changelog: [v3.1.0...v3.2.0](https://github.com/droidrun/mobilerun-sdk-python/compare/v3.1.0...v3.2.0)
+
+### Features
+
+* **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:** 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..f60b9fc 100644
--- a/api.md
+++ b/api.md
@@ -5,138 +5,136 @@ 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
@@ -156,132 +154,127 @@ Methods:
- 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
```
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
-## Location
+## Files
Types:
```python
-from mobilerun_sdk.types.devices import LocationGetResponse
+from mobilerun_sdk.types.devices import FileInfo, FileListResponse, FileDownloadResponse
```
Methods:
-- client.devices.location.get(device_id) -> LocationGetResponse
-- client.devices.location.set(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
-## Actions
+## Keyboard
-Types:
+Methods:
-```python
-from mobilerun_sdk.types.devices import ActionOverlayVisibleResponse
-```
+- 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.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.location.get(device_id) -> Location
+- client.devices.location.set(device_id, \*\*params) -> None
-## State
+## Packages
Types:
```python
-from mobilerun_sdk.types.devices import Rect, StateScreenshotResponse, StateUiResponse
+from mobilerun_sdk.types.devices import PackageListResponse
```
Methods:
-- client.devices.state.screenshot(device_id, \*\*params) -> str
-- client.devices.state.ui(device_id, \*\*params) -> StateUiResponse
+- client.devices.packages.list(device_id, \*\*params) -> Optional[PackageListResponse]
-## Apps
+## Profile
+
+Methods:
+
+- client.devices.profile.update(device_id, \*\*params) -> None
+
+## Proxy
Types:
```python
-from mobilerun_sdk.types.devices import AppListResponse
+from mobilerun_sdk.types.devices import ProxyStatusResponse
```
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.proxy.connect(device_id, \*\*params) -> None
+- client.devices.proxy.disconnect(device_id) -> None
+- client.devices.proxy.status(device_id) -> ProxyStatusResponse
-## Packages
+## State
Types:
```python
-from mobilerun_sdk.types.devices import PackageListResponse
+from mobilerun_sdk.types.devices import Rect, StateScreenshotResponse, StateUiResponse
```
Methods:
-- client.devices.packages.list(device_id, \*\*params) -> Optional[PackageListResponse]
-
-## Keyboard
-
-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.ui(device_id, \*\*params) -> StateUiResponse
## Tasks
@@ -295,129 +288,151 @@ Methods:
- client.devices.tasks.list(device_id, \*\*params) -> TaskListResponse
-## Esim
+## Time
Types:
```python
-from mobilerun_sdk.types.devices import EsimListResponse, EsimActivateResponse
+from mobilerun_sdk.types.devices import TimeTimeResponse, TimeTimezoneResponse
```
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.time.set_timezone(device_id, \*\*params) -> None
+- client.devices.time.time(device_id) -> str
+- client.devices.time.timezone(device_id) -> TimeTimezoneResponse
-# Apps
+# Hooks
Types:
```python
-from mobilerun_sdk.types import AppListResponse
+from mobilerun_sdk.types import (
+ HookRetrieveResponse,
+ HookUpdateResponse,
+ HookListResponse,
+ HookGetSampleDataResponse,
+ HookPerformResponse,
+ HookSubscribeResponse,
+ HookUnsubscribeResponse,
+)
```
Methods:
-- client.apps.list(\*\*params) -> AppListResponse
+- 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
-# Credentials
+# Models
Types:
```python
-from mobilerun_sdk.types import CredentialListResponse
+from mobilerun_sdk.types import ModelListResponse
```
Methods:
-- client.credentials.list(\*\*params) -> CredentialListResponse
+- client.models.list() -> ModelListResponse
-## Packages
+# Profiles
Types:
```python
-from mobilerun_sdk.types.credentials import PackageCreateResponse, PackageListResponse
+from mobilerun_sdk.types import Profile, ProfileListResponse, ProfileDeleteResponse
```
Methods:
-- client.credentials.packages.create(\*\*params) -> PackageCreateResponse
-- client.credentials.packages.list(package_name) -> PackageListResponse
+- 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
-### Credentials
+# Proxies
Types:
```python
-from mobilerun_sdk.types.credentials.packages import (
- Credential,
- CredentialCreateResponse,
- CredentialRetrieveResponse,
- CredentialDeleteResponse,
+from mobilerun_sdk.types import (
+ ProxyConfig,
+ ProxyCreateResponse,
+ ProxyRetrieveResponse,
+ ProxyUpdateResponse,
+ ProxyListResponse,
+ ProxyDeleteResponse,
)
```
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.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
-#### Fields
+# Tasks
Types:
```python
-from mobilerun_sdk.types.credentials.packages.credentials import (
- FieldCreateResponse,
- FieldUpdateResponse,
- FieldDeleteResponse,
+from mobilerun_sdk.types import (
+ PackageCredentials,
+ Task,
+ TaskStatus,
+ UsageResult,
+ TaskRetrieveResponse,
+ TaskListResponse,
+ TaskGetStatusResponse,
+ TaskGetTrajectoryResponse,
+ TaskRunResponse,
+ TaskSendMessageResponse,
+ TaskStopResponse,
)
```
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.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
-# Hooks
+## Screenshots
Types:
```python
-from mobilerun_sdk.types import (
- HookRetrieveResponse,
- HookUpdateResponse,
- HookListResponse,
- HookGetSampleDataResponse,
- HookPerformResponse,
- HookSubscribeResponse,
- HookUnsubscribeResponse,
-)
+from mobilerun_sdk.types.tasks import MediaResponse, ScreenshotListResponse
```
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.screenshots.retrieve(index, \*, task_id) -> MediaResponse
+- client.tasks.screenshots.list(task_id) -> ScreenshotListResponse
-# Models
+## UiStates
Types:
```python
-from mobilerun_sdk.types import ModelListResponse
+from mobilerun_sdk.types.tasks import UiStateListResponse
```
Methods:
-- client.models.list() -> ModelListResponse
+- 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..469c26c 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,54 +128,66 @@ 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:
+ """Device Management"""
from .resources.devices import 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 +352,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 +372,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,54 +380,66 @@ 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:
+ """Device Management"""
from .resources.devices import 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 +570,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,67 +578,72 @@ 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:
+ """Device Management"""
from .resources.devices import 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,67 +653,72 @@ 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:
+ """Device Management"""
from .resources.devices import 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,67 +728,72 @@ 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:
+ """Device Management"""
from .resources.devices import 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,54 +803,66 @@ 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:
+ """Device Management"""
from .resources.devices import 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..ae8f789 100644
--- a/src/mobilerun_sdk/resources/devices/__init__.py
+++ b/src/mobilerun_sdk/resources/devices/__init__.py
@@ -106,78 +106,78 @@
)
__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",
+ "TimeResource",
+ "AsyncTimeResource",
+ "TimeResourceWithRawResponse",
+ "AsyncTimeResourceWithRawResponse",
+ "TimeResourceWithStreamingResponse",
+ "AsyncTimeResourceWithStreamingResponse",
"DevicesResource",
"AsyncDevicesResource",
"DevicesResourceWithRawResponse",
diff --git a/src/mobilerun_sdk/resources/devices/devices.py b/src/mobilerun_sdk/resources/devices/devices.py
index b6743a9..17b6bae 100644
--- a/src/mobilerun_sdk/resources/devices/devices.py
+++ b/src/mobilerun_sdk/resources/devices/devices.py
@@ -119,6 +119,7 @@
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.shared_params.device_carrier import DeviceCarrier
from ...types.shared_params.device_identifiers import DeviceIdentifiers
@@ -126,53 +127,56 @@
class DevicesResource(SyncAPIResource):
+ """Device Management"""
+
@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 time(self) -> TimeResource:
+ return TimeResource(self._client)
@cached_property
def with_raw_response(self) -> DevicesResourceWithRawResponse:
@@ -196,16 +200,23 @@ def with_streaming_response(self) -> DevicesResourceWithStreamingResponse:
def create(
self,
*,
+ query_country: str | Omit = omit,
device_type: Literal[
"dedicated_physical_device", "dedicated_premium_device", "dedicated_emulated_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 +224,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 +246,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 +265,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,
)
@@ -479,53 +508,56 @@ def wait_ready(
class AsyncDevicesResource(AsyncAPIResource):
+ """Device Management"""
+
@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 time(self) -> AsyncTimeResource:
+ return AsyncTimeResource(self._client)
@cached_property
def with_raw_response(self) -> AsyncDevicesResourceWithRawResponse:
@@ -549,16 +581,23 @@ def with_streaming_response(self) -> AsyncDevicesResourceWithStreamingResponse:
async def create(
self,
*,
+ query_country: str | Omit = omit,
device_type: Literal[
"dedicated_physical_device", "dedicated_premium_device", "dedicated_emulated_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 +605,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 +627,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 +647,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,
@@ -860,52 +915,53 @@ 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 time(self) -> TimeResourceWithRawResponse:
+ return TimeResourceWithRawResponse(self._devices.time)
class AsyncDevicesResourceWithRawResponse:
@@ -935,52 +991,53 @@ 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 time(self) -> AsyncTimeResourceWithRawResponse:
+ return AsyncTimeResourceWithRawResponse(self._devices.time)
class DevicesResourceWithStreamingResponse:
@@ -1010,52 +1067,53 @@ 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 profile(self) -> ProfileResourceWithStreamingResponse:
- return ProfileResourceWithStreamingResponse(self._devices.profile)
+ def apps(self) -> AppsResourceWithStreamingResponse:
+ return AppsResourceWithStreamingResponse(self._devices.apps)
+
+ @cached_property
+ 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 time(self) -> TimeResourceWithStreamingResponse:
+ return TimeResourceWithStreamingResponse(self._devices.time)
class AsyncDevicesResourceWithStreamingResponse:
@@ -1085,49 +1143,50 @@ 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 time(self) -> AsyncTimeResourceWithStreamingResponse:
+ return AsyncTimeResourceWithStreamingResponse(self._devices.time)
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/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/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/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..dff397e 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,25 @@
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 .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..0722b7a 100644
--- a/src/mobilerun_sdk/types/device_create_params.py
+++ b/src/mobilerun_sdk/types/device_create_params.py
@@ -3,17 +3,25 @@
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"
@@ -21,27 +29,30 @@ class DeviceCreateParams(TypedDict, total=False):
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 +60,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/devices/__init__.py b/src/mobilerun_sdk/types/devices/__init__.py
index c5f89e4..b9ba498 100644
--- a/src/mobilerun_sdk/types/devices/__init__.py
+++ b/src/mobilerun_sdk/types/devices/__init__.py
@@ -30,7 +30,6 @@
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 .package_list_response import PackageListResponse as PackageListResponse
from .profile_update_params import ProfileUpdateParams as ProfileUpdateParams
from .proxy_status_response import ProxyStatusResponse as ProxyStatusResponse
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/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/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/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/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_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/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..765eca2 100644
--- a/tests/api_resources/test_devices.py
+++ b/tests/api_resources/test_devices.py
@@ -32,7 +32,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 +45,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 +64,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 +79,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"])
@@ -367,7 +376,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 +389,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 +408,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 +423,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"])
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"]