orb-cloud-client 1.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- orb_cloud_client/__init__.py +15 -0
- orb_cloud_client/client.py +107 -0
- orb_cloud_client/example.py +183 -0
- orb_cloud_client/example_async.py +47 -0
- orb_cloud_client/models.py +245 -0
- orb_cloud_client-1.2.1.dist-info/METADATA +426 -0
- orb_cloud_client-1.2.1.dist-info/RECORD +11 -0
- orb_cloud_client-1.2.1.dist-info/WHEEL +5 -0
- orb_cloud_client-1.2.1.dist-info/entry_points.txt +2 -0
- orb_cloud_client-1.2.1.dist-info/licenses/LICENSE +21 -0
- orb_cloud_client-1.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Orb Cloud API Client
|
|
2
|
+
|
|
3
|
+
A Python client library for interacting with the Orb Cloud API.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from .client import OrbCloudClient
|
|
8
|
+
from .models import *
|
|
9
|
+
except ImportError:
|
|
10
|
+
# Handle case where package is not installed
|
|
11
|
+
from orb_cloud_client.client import OrbCloudClient
|
|
12
|
+
from orb_cloud_client.models import *
|
|
13
|
+
|
|
14
|
+
__version__ = "1.0.0"
|
|
15
|
+
__all__ = ["OrbCloudClient"]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Simple HTTP client for Orb Cloud API.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from typing import Dict, Any, Optional, List
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from .models import Device, TempDatasetsRequest, Organization
|
|
10
|
+
except ImportError:
|
|
11
|
+
# Handle direct execution
|
|
12
|
+
from models import Device, TempDatasetsRequest, Organization
|
|
13
|
+
|
|
14
|
+
class OrbCloudClientBase(object):
|
|
15
|
+
def __init__(self, base_url: str = "https://panel.orb.net", token: Optional[str] = None):
|
|
16
|
+
self.base_url = base_url.rstrip('/')
|
|
17
|
+
self.headers = {"Content-Type": "application/json"}
|
|
18
|
+
if token:
|
|
19
|
+
self.headers["Authorization"] = f"Bearer {token}"
|
|
20
|
+
|
|
21
|
+
def _get_organization_devices(self, response: httpx.Response) -> List[Device]:
|
|
22
|
+
"""Get list of devices for the organization from a response."""
|
|
23
|
+
response.raise_for_status()
|
|
24
|
+
return [Device(**device) for device in response.json()]
|
|
25
|
+
|
|
26
|
+
def _configure_temporary_datasets(self, response: httpx.Response) -> Dict[str, Any]:
|
|
27
|
+
"""Process the response for configuring temporary datasets."""
|
|
28
|
+
response.raise_for_status()
|
|
29
|
+
return response.json()
|
|
30
|
+
|
|
31
|
+
def _get_organizations(self, response: httpx.Response) -> List[Organization]:
|
|
32
|
+
"""Get list of organizations accessible with the provided token."""
|
|
33
|
+
response.raise_for_status()
|
|
34
|
+
return [Organization(**org) for org in response.json()]
|
|
35
|
+
|
|
36
|
+
class OrbCloudClient(OrbCloudClientBase):
|
|
37
|
+
"""Simple HTTP client for Orb Cloud API."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, base_url: str = "https://panel.orb.net", token: Optional[str] = None):
|
|
40
|
+
super().__init__(base_url, token)
|
|
41
|
+
self.client = httpx.Client(base_url=self.base_url, headers=self.headers)
|
|
42
|
+
|
|
43
|
+
def __enter__(self):
|
|
44
|
+
return self
|
|
45
|
+
|
|
46
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
47
|
+
self.client.close()
|
|
48
|
+
|
|
49
|
+
def close(self):
|
|
50
|
+
"""Close the HTTP client."""
|
|
51
|
+
self.client.close()
|
|
52
|
+
|
|
53
|
+
def get_organization_devices(self, organization_id: str) -> List[Device]:
|
|
54
|
+
"""Get list of devices for the organization."""
|
|
55
|
+
response = self.client.get(f"/api/v2/organization/{organization_id}/devices")
|
|
56
|
+
return self._get_organization_devices(response)
|
|
57
|
+
|
|
58
|
+
def configure_temporary_datasets(self, device_id: str, temp_datasets_request: TempDatasetsRequest) -> Dict[str, Any]:
|
|
59
|
+
"""Enable temporary data reporting to a custom endpoint with a given config for a specified duration."""
|
|
60
|
+
response = self.client.post(f"/api/v1/device/{device_id}/temp-datasets", json=temp_datasets_request.model_dump())
|
|
61
|
+
return self._configure_temporary_datasets(response)
|
|
62
|
+
|
|
63
|
+
def get_organizations(self) -> List[Organization]:
|
|
64
|
+
"""Get list of organizations accessible with the provided token."""
|
|
65
|
+
response = self.client.get("/api/v2/organizations")
|
|
66
|
+
return self._get_organizations(response)
|
|
67
|
+
|
|
68
|
+
def request(self, method: str, endpoint: str, **kwargs) -> httpx.Response:
|
|
69
|
+
"""Make a raw HTTP request for any other endpoints."""
|
|
70
|
+
return self.client.request(method, endpoint, **kwargs)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class OrbCloudClientAsync(OrbCloudClientBase):
|
|
74
|
+
"""Simple async HTTP client for Orb Cloud API."""
|
|
75
|
+
|
|
76
|
+
def __init__(self, base_url: str = "https://panel.orb.net", token: Optional[str] = None):
|
|
77
|
+
super().__init__(base_url, token)
|
|
78
|
+
self.client = httpx.AsyncClient(base_url=self.base_url, headers=self.headers)
|
|
79
|
+
|
|
80
|
+
async def __aenter__(self):
|
|
81
|
+
return self
|
|
82
|
+
|
|
83
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
84
|
+
await self.client.aclose()
|
|
85
|
+
|
|
86
|
+
async def close(self):
|
|
87
|
+
"""Close the HTTP client."""
|
|
88
|
+
await self.client.aclose()
|
|
89
|
+
|
|
90
|
+
async def get_organization_devices(self, organization_id: str) -> List[Device]:
|
|
91
|
+
"""Get list of devices for the organization."""
|
|
92
|
+
response = await self.client.get(f"/api/v2/organization/{organization_id}/devices")
|
|
93
|
+
return self._get_organization_devices(response)
|
|
94
|
+
|
|
95
|
+
async def configure_temporary_datasets(self, device_id: str, temp_datasets_request: TempDatasetsRequest) -> Dict[str, Any]:
|
|
96
|
+
"""Enable temporary data reporting to a custom endpoint with a given config for a specified duration."""
|
|
97
|
+
response = await self.client.post(f"/api/v1/device/{device_id}/temp-datasets", json=temp_datasets_request.model_dump())
|
|
98
|
+
return self._configure_temporary_datasets(response)
|
|
99
|
+
|
|
100
|
+
async def get_organizations(self) -> List[Organization]:
|
|
101
|
+
"""Get list of organizations accessible with the provided token."""
|
|
102
|
+
response = await self.client.get("/api/v2/organizations")
|
|
103
|
+
return self._get_organizations(response)
|
|
104
|
+
|
|
105
|
+
async def request(self, method: str, endpoint: str, **kwargs) -> httpx.Response:
|
|
106
|
+
"""Make a raw async HTTP request for any other endpoints."""
|
|
107
|
+
return await self.client.request(method, endpoint, **kwargs)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Example usage of the Orb Cloud API Client.
|
|
4
|
+
|
|
5
|
+
This example demonstrates:
|
|
6
|
+
1. Getting a list of devices for an organization
|
|
7
|
+
2. Displaying the devices and letting the user select one
|
|
8
|
+
3. Asking the user for a URL to push data to
|
|
9
|
+
4. Configuring temporary datasets for the selected device with data push
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
from typing import Optional, List
|
|
15
|
+
|
|
16
|
+
# Add the client to the path (for running this example directly)
|
|
17
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
18
|
+
|
|
19
|
+
from client import OrbCloudClient
|
|
20
|
+
from models import TempDatasetsRequest, Datasets, DataPush, Device
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_and_display_devices(client: OrbCloudClient, organization_id: str) -> List[Device]:
|
|
24
|
+
"""Get and display all devices for the organization."""
|
|
25
|
+
print(f"Getting devices for organization: {organization_id}")
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
devices = client.get_organization_devices(organization_id)
|
|
29
|
+
print(f"Found {len(devices)} devices:\n")
|
|
30
|
+
|
|
31
|
+
for i, device in enumerate(devices, 1):
|
|
32
|
+
connection_status = "Connected" if device.is_connected.value == 1 else "Disconnected"
|
|
33
|
+
print(f"{i:2}. {device.name}")
|
|
34
|
+
print(f" orb_id: {device.orb_id}")
|
|
35
|
+
print(f" status: {connection_status}")
|
|
36
|
+
print()
|
|
37
|
+
|
|
38
|
+
return devices
|
|
39
|
+
|
|
40
|
+
except Exception as e:
|
|
41
|
+
print(f"Error getting devices: {e}")
|
|
42
|
+
return []
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def select_device(devices: List[Device]) -> Optional[Device]:
|
|
46
|
+
"""Allow user to select a device from the list."""
|
|
47
|
+
if not devices:
|
|
48
|
+
print("No devices available to select from.")
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
while True:
|
|
52
|
+
try:
|
|
53
|
+
choice = input(f"Select a device (1-{len(devices)}) or 'q' to quit: ").strip()
|
|
54
|
+
|
|
55
|
+
if choice.lower() == 'q':
|
|
56
|
+
print("Exiting...")
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
device_index = int(choice) - 1
|
|
60
|
+
|
|
61
|
+
if 0 <= device_index < len(devices):
|
|
62
|
+
selected_device = devices[device_index]
|
|
63
|
+
print(f"Selected: {selected_device.name}")
|
|
64
|
+
return selected_device
|
|
65
|
+
else:
|
|
66
|
+
print(f"Please enter a number between 1 and {len(devices)}")
|
|
67
|
+
|
|
68
|
+
except ValueError:
|
|
69
|
+
print("Please enter a valid number or 'q' to quit")
|
|
70
|
+
except KeyboardInterrupt:
|
|
71
|
+
print("\nExiting...")
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_push_url() -> Optional[str]:
|
|
76
|
+
"""Get the push URL from the user."""
|
|
77
|
+
while True:
|
|
78
|
+
try:
|
|
79
|
+
url = input("\nEnter the URL to push data to (or 'q' to quit): ").strip()
|
|
80
|
+
|
|
81
|
+
if url.lower() == 'q':
|
|
82
|
+
print("Exiting...")
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
if not url:
|
|
86
|
+
print("Please enter a valid URL")
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
# Basic URL validation
|
|
90
|
+
if not (url.startswith('http://') or url.startswith('https://')):
|
|
91
|
+
print("URL should start with http:// or https://")
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
print(f"Will push data to: {url}")
|
|
95
|
+
return url
|
|
96
|
+
|
|
97
|
+
except KeyboardInterrupt:
|
|
98
|
+
print("\nExiting...")
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def configure_temp_datasets(client: OrbCloudClient, device_id: str, push_url: str) -> bool:
|
|
103
|
+
"""Configure temporary datasets for the specified device."""
|
|
104
|
+
print(f"Configuring temporary datasets for device: {device_id}")
|
|
105
|
+
|
|
106
|
+
# Create the temporary datasets configuration
|
|
107
|
+
temp_config = TempDatasetsRequest(
|
|
108
|
+
duration="5m", # Run for 5 minutes
|
|
109
|
+
datasets_config=Datasets(
|
|
110
|
+
enabled=True,
|
|
111
|
+
datasets=["responsiveness_1s", "scores_1s"], # Collect these datasets
|
|
112
|
+
push=DataPush(
|
|
113
|
+
enabled=True,
|
|
114
|
+
url=push_url, # Use the user-provided URL
|
|
115
|
+
datasets=["responsiveness_1s", "scores_1s"],
|
|
116
|
+
format="json",
|
|
117
|
+
interval_ms=500 # Push new data every 500ms (when available)
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
result = client.configure_temporary_datasets(device_id, temp_config)
|
|
124
|
+
print(f"Temporary datasets configured successfully: {result}")
|
|
125
|
+
return True
|
|
126
|
+
|
|
127
|
+
except Exception as e:
|
|
128
|
+
print(f"Error configuring temporary datasets: {e}")
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def main():
|
|
133
|
+
"""Main example function."""
|
|
134
|
+
# Get configuration from environment variables
|
|
135
|
+
api_token = os.getenv("ORB_API_TOKEN")
|
|
136
|
+
organization_id = os.getenv("ORB_ORGANIZATION_ID")
|
|
137
|
+
|
|
138
|
+
if not api_token:
|
|
139
|
+
print("Error: Please set ORB_API_TOKEN environment variable")
|
|
140
|
+
sys.exit(1)
|
|
141
|
+
|
|
142
|
+
if not organization_id:
|
|
143
|
+
print("Error: Please set ORB_ORGANIZATION_ID environment variable")
|
|
144
|
+
sys.exit(1)
|
|
145
|
+
|
|
146
|
+
print("Orb Cloud API Client Example")
|
|
147
|
+
print("=" * 30)
|
|
148
|
+
|
|
149
|
+
# Create the client
|
|
150
|
+
with OrbCloudClient(token=api_token) as client:
|
|
151
|
+
# Step 1: Get and display all devices
|
|
152
|
+
devices = get_and_display_devices(client, organization_id)
|
|
153
|
+
|
|
154
|
+
if not devices:
|
|
155
|
+
print("No devices found. Exiting.")
|
|
156
|
+
sys.exit(1)
|
|
157
|
+
|
|
158
|
+
# Step 2: Let user select a device
|
|
159
|
+
selected_device = select_device(devices)
|
|
160
|
+
|
|
161
|
+
if not selected_device:
|
|
162
|
+
sys.exit(0)
|
|
163
|
+
|
|
164
|
+
# Step 3: Get the push URL from user
|
|
165
|
+
push_url = get_push_url()
|
|
166
|
+
|
|
167
|
+
if not push_url:
|
|
168
|
+
sys.exit(0)
|
|
169
|
+
|
|
170
|
+
# Step 4: Configure temporary datasets for the selected device
|
|
171
|
+
success = configure_temp_datasets(client, selected_device.orb_id, push_url)
|
|
172
|
+
|
|
173
|
+
if success:
|
|
174
|
+
print("\nExample completed successfully!")
|
|
175
|
+
print(f"Device '{selected_device.name}' is now configured to collect temporary datasets.")
|
|
176
|
+
print(f"Data will be pushed to: {push_url}")
|
|
177
|
+
else:
|
|
178
|
+
print("\nExample failed to configure temporary datasets.")
|
|
179
|
+
sys.exit(1)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
if __name__ == "__main__":
|
|
183
|
+
main()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from client import OrbCloudClientAsync
|
|
2
|
+
import asyncio
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def main():
|
|
7
|
+
"""
|
|
8
|
+
Example of using the OrbCloudClientAsync to fetch organization devices.
|
|
9
|
+
|
|
10
|
+
Requires ORB_CLOUD_TOKEN and ORB_CLOUD_ORGANIZATION_ID environment variables to be set.
|
|
11
|
+
"""
|
|
12
|
+
token = os.environ.get("ORB_CLOUD_TOKEN")
|
|
13
|
+
|
|
14
|
+
if not token:
|
|
15
|
+
print("Please set ORB_CLOUD_TOKEN environment variables.")
|
|
16
|
+
return
|
|
17
|
+
|
|
18
|
+
print("Getting organization...")
|
|
19
|
+
async with OrbCloudClientAsync(token=token) as client:
|
|
20
|
+
organizations = await client.get_organizations()
|
|
21
|
+
if not organizations:
|
|
22
|
+
print("No organizations found for this token.")
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
print("Organizations found:")
|
|
26
|
+
for org in organizations:
|
|
27
|
+
print(f" - ID: {org.organization_id}, Name: {org.name}")
|
|
28
|
+
|
|
29
|
+
print(f"Fetching devices for organization: {org.organization_id}")
|
|
30
|
+
try:
|
|
31
|
+
devices = await client.get_organization_devices(org.organization_id)
|
|
32
|
+
if not devices:
|
|
33
|
+
print("No devices found for this organization.")
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
print("Devices found:")
|
|
37
|
+
for device in devices:
|
|
38
|
+
print(f" - ID: {device.orb_id}, Name: {device.name}")
|
|
39
|
+
except Exception as e:
|
|
40
|
+
print(f"An error occurred: {e}")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if __name__ == "__main__":
|
|
44
|
+
asyncio.run(main())
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# generated by datamodel-codegen:
|
|
2
|
+
# filename: openapi.yaml
|
|
3
|
+
# timestamp: 2025-10-16T14:05:24+00:00
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from typing import Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class IsConnected(Enum):
|
|
14
|
+
integer_0 = 0
|
|
15
|
+
integer_1 = 1
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class GeoIPInfo(BaseModel):
|
|
19
|
+
city: Optional[str] = Field(None, examples=['Kirkland'])
|
|
20
|
+
state: Optional[str] = Field(None, examples=['Washington'])
|
|
21
|
+
country: Optional[str] = Field(None, examples=['United States'])
|
|
22
|
+
isp_name: Optional[str] = Field(None, examples=['Comcast Cable'])
|
|
23
|
+
latitude: Optional[float] = Field(None, examples=[47.6784])
|
|
24
|
+
longitude: Optional[float] = Field(None, examples=[-122.1857])
|
|
25
|
+
country_code: Optional[str] = Field(None, examples=['US'])
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DeviceInfo(BaseModel):
|
|
29
|
+
name: Optional[str] = Field(None, examples=['hardy-nca1515'])
|
|
30
|
+
version: Optional[str] = Field(None, examples=['v1.2.3'])
|
|
31
|
+
cpu_count: Optional[int] = Field(None, examples=[4])
|
|
32
|
+
full_name: Optional[str] = Field(
|
|
33
|
+
None, description='device full hostname', examples=['hardy-nca1515']
|
|
34
|
+
)
|
|
35
|
+
operating_system: Optional[str] = Field(None, examples=['linux'])
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class NetworkInterface(BaseModel):
|
|
39
|
+
name: Optional[str] = Field(None, examples=[''])
|
|
40
|
+
type: Optional[str] = Field(None, examples=['Ethernet'])
|
|
41
|
+
local_ip: Optional[str] = Field(
|
|
42
|
+
None,
|
|
43
|
+
description='Local IP address of the network interface',
|
|
44
|
+
examples=['192.168.207.172'],
|
|
45
|
+
)
|
|
46
|
+
mac_address: Optional[str] = Field(
|
|
47
|
+
None,
|
|
48
|
+
description='MAC address of the network interface',
|
|
49
|
+
examples=['00:90:0b:a5:de:2a'],
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class OrbScore(BaseModel):
|
|
54
|
+
score: Optional[float] = Field(
|
|
55
|
+
None, description='Raw score value', examples=[0.8283297399160232]
|
|
56
|
+
)
|
|
57
|
+
display: Optional[int] = Field(
|
|
58
|
+
None, description='Display score (0-100)', examples=[91]
|
|
59
|
+
)
|
|
60
|
+
included: Optional[bool] = Field(
|
|
61
|
+
None,
|
|
62
|
+
description='Whether there are enough measurements on the subject score that it is conclusive',
|
|
63
|
+
examples=[True],
|
|
64
|
+
)
|
|
65
|
+
value: Optional[float] = Field(
|
|
66
|
+
None,
|
|
67
|
+
description='Raw measurement value (present in leaf components)',
|
|
68
|
+
examples=[38965.44888204175],
|
|
69
|
+
)
|
|
70
|
+
components: Optional[Dict[str, OrbScore]] = Field(
|
|
71
|
+
None, description='Score components breakdown (nested OrbScore objects)'
|
|
72
|
+
)
|
|
73
|
+
duration_ms: Optional[int] = Field(
|
|
74
|
+
None, description='Duration of measurement in milliseconds', examples=[60000]
|
|
75
|
+
)
|
|
76
|
+
score_version: Optional[str] = Field(
|
|
77
|
+
None, description='Version of scoring algorithm', examples=['1.2.0']
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class DataPush(BaseModel):
|
|
82
|
+
enabled: bool = Field(
|
|
83
|
+
..., description='Whether data push is enabled', examples=[True]
|
|
84
|
+
)
|
|
85
|
+
identifiable: Optional[bool] = Field(
|
|
86
|
+
None, description='Whether data should be identifiable', examples=[True]
|
|
87
|
+
)
|
|
88
|
+
datasets: Optional[List[str]] = Field(
|
|
89
|
+
None,
|
|
90
|
+
description='List of datasets to push',
|
|
91
|
+
examples=[['responsiveness_1s', 'speed_results']],
|
|
92
|
+
)
|
|
93
|
+
url: Optional[str] = Field(
|
|
94
|
+
None,
|
|
95
|
+
description='URL to push data to',
|
|
96
|
+
examples=['https://api.example.com/data'],
|
|
97
|
+
)
|
|
98
|
+
format: Optional[str] = Field(
|
|
99
|
+
None, description='Data format for pushing', examples=['json']
|
|
100
|
+
)
|
|
101
|
+
buffer_kb: Optional[int] = Field(
|
|
102
|
+
None, description='Buffer size in KB', examples=[64]
|
|
103
|
+
)
|
|
104
|
+
interval_ms: Optional[int] = Field(
|
|
105
|
+
None, description='Push interval in milliseconds', examples=[5000]
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class DataAPI(BaseModel):
|
|
110
|
+
enabled: bool = Field(
|
|
111
|
+
..., description='Whether data API is enabled', examples=[True]
|
|
112
|
+
)
|
|
113
|
+
identifiable: Optional[bool] = Field(
|
|
114
|
+
None, description='Whether data should be identifiable', examples=[True]
|
|
115
|
+
)
|
|
116
|
+
datasets: Optional[List[str]] = Field(
|
|
117
|
+
None,
|
|
118
|
+
description='List of datasets to expose via API',
|
|
119
|
+
examples=[['responsiveness_1s', 'speed_results']],
|
|
120
|
+
)
|
|
121
|
+
api_key: Optional[str] = Field(
|
|
122
|
+
None,
|
|
123
|
+
description='API key for accessing the data API',
|
|
124
|
+
examples=['your-api-key-here'],
|
|
125
|
+
)
|
|
126
|
+
port: Optional[int] = Field(
|
|
127
|
+
None, description='Port for the data API', examples=[8080]
|
|
128
|
+
)
|
|
129
|
+
buffer: Optional[int] = Field(None, description='Buffer size', examples=[1000])
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class Error(BaseModel):
|
|
133
|
+
message: str = Field(
|
|
134
|
+
..., description='Error message', examples=['Invalid organization ID']
|
|
135
|
+
)
|
|
136
|
+
code: Optional[str] = Field(
|
|
137
|
+
None, description='Error code', examples=['INVALID_ORG_ID']
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class AccountConfigEntitlements(BaseModel):
|
|
142
|
+
entitlements: Optional[List[str]] = None
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class AccountPlanLimits(BaseModel):
|
|
146
|
+
deployment_tokens: Optional[int] = None
|
|
147
|
+
devices: Optional[int] = None
|
|
148
|
+
users: Optional[int] = None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class SummaryTags(BaseModel):
|
|
152
|
+
geoip: Optional[GeoIPInfo] = None
|
|
153
|
+
device_info: Optional[DeviceInfo] = None
|
|
154
|
+
network_interface: Optional[NetworkInterface] = None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class Datasets(BaseModel):
|
|
158
|
+
enabled: bool = Field(
|
|
159
|
+
..., description='Whether datasets are enabled', examples=[True]
|
|
160
|
+
)
|
|
161
|
+
datasets: Optional[List[str]] = Field(
|
|
162
|
+
None,
|
|
163
|
+
description='List of datasets to collect',
|
|
164
|
+
examples=[['responsiveness_1s', 'speed_results']],
|
|
165
|
+
)
|
|
166
|
+
cloud_push: Optional[DataPush] = None
|
|
167
|
+
push: Optional[DataPush] = None
|
|
168
|
+
api: Optional[DataAPI] = None
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class AccountPlan(BaseModel):
|
|
172
|
+
config: Optional[AccountConfigEntitlements] = None
|
|
173
|
+
features: Optional[List[str]] = None
|
|
174
|
+
limits: Optional[AccountPlanLimits] = None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class DeviceSummary(BaseModel):
|
|
178
|
+
tags: Optional[SummaryTags] = None
|
|
179
|
+
version: Optional[str] = Field(
|
|
180
|
+
None, description='Summary version', examples=['0.1.0']
|
|
181
|
+
)
|
|
182
|
+
orb_score: Optional[OrbScore] = None
|
|
183
|
+
created_ts: Optional[int] = Field(
|
|
184
|
+
None, description='Timestamp when summary was created', examples=[1757391432134]
|
|
185
|
+
)
|
|
186
|
+
orb_scores: Optional[List[OrbScore]] = Field(
|
|
187
|
+
None, description='Array of Orb scores for different time windows'
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class TempDatasetsRequest(BaseModel):
|
|
192
|
+
duration: str = Field(
|
|
193
|
+
..., description='Duration for temporary dataset configuration', examples=['1h']
|
|
194
|
+
)
|
|
195
|
+
datasets_config: Datasets
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class Organization(BaseModel):
|
|
199
|
+
customer_id: Optional[str] = None
|
|
200
|
+
name: Optional[str] = None
|
|
201
|
+
organization_id: Optional[str] = None
|
|
202
|
+
plan: Optional[AccountPlan] = None
|
|
203
|
+
subscription_id: Optional[str] = None
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class Device(BaseModel):
|
|
207
|
+
orb_id: str = Field(
|
|
208
|
+
...,
|
|
209
|
+
description='Unique Orb device identifier',
|
|
210
|
+
examples=['cjwbntmuy97ta4rx3mbrjf2gj8j7'],
|
|
211
|
+
)
|
|
212
|
+
name: str = Field(..., description='Device name', examples=['hardy-house'])
|
|
213
|
+
is_connected: IsConnected = Field(
|
|
214
|
+
...,
|
|
215
|
+
description='Connection status (0 = disconnected, 1 = connected)',
|
|
216
|
+
examples=[1],
|
|
217
|
+
)
|
|
218
|
+
is_connected_updated_at: Optional[int] = Field(
|
|
219
|
+
None,
|
|
220
|
+
description='Timestamp when connection status was last updated',
|
|
221
|
+
examples=[1757368500851],
|
|
222
|
+
)
|
|
223
|
+
can_notify: Optional[bool] = Field(
|
|
224
|
+
None, description='Whether the device can send notifications', examples=[False]
|
|
225
|
+
)
|
|
226
|
+
summary: DeviceSummary
|
|
227
|
+
created_ts: Optional[int] = Field(
|
|
228
|
+
None, description='Timestamp when device was created', examples=[1757368560803]
|
|
229
|
+
)
|
|
230
|
+
tags: Optional[List[str]] = Field(
|
|
231
|
+
None, description='Device tags', examples=[['dt=Default Configuration']]
|
|
232
|
+
)
|
|
233
|
+
config: Optional[Dict[str, List[str]]] = Field(
|
|
234
|
+
None,
|
|
235
|
+
description='Device configuration user overrides (map of strings to arrays of strings)',
|
|
236
|
+
examples=[
|
|
237
|
+
{
|
|
238
|
+
'datasets.datasets': ['responsiveness_1s', 'speed_results'],
|
|
239
|
+
'datasets.cloud_push': ['identifiable=true', 'responsiveness_1s'],
|
|
240
|
+
}
|
|
241
|
+
],
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
OrbScore.model_rebuild()
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: orb-cloud-client
|
|
3
|
+
Version: 1.2.1
|
|
4
|
+
Summary: Python client library for Orb Cloud API
|
|
5
|
+
Author-email: Orb Networks <support@orb.net>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Documentation, https://api.orb.net/docs
|
|
8
|
+
Project-URL: Homepage, https://github.com/orbforge/orb-cloud
|
|
9
|
+
Project-URL: Repository, https://github.com/orbforge/orb-cloud
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: build>=1.2.2.post1
|
|
22
|
+
Requires-Dist: httpx>=0.24.0
|
|
23
|
+
Requires-Dist: pydantic>=2.0.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: build; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# Orb Cloud API Client
|
|
30
|
+
|
|
31
|
+
A Python client library for interacting with the Orb Cloud API. This package provides a simple interface to manage devices, configure data collection, and interact with Orb Cloud services.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
Install the package from the wheel file:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install orb-cloud-client
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The package will automatically install its dependencies:
|
|
42
|
+
- `httpx>=0.24.0` (for HTTP requests)
|
|
43
|
+
- `pydantic>=2.0.0` (for data models)
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from orb_cloud_client import OrbCloudClient
|
|
49
|
+
|
|
50
|
+
# Create client
|
|
51
|
+
client = OrbCloudClient(token="your-api-token")
|
|
52
|
+
|
|
53
|
+
# Get organization devices
|
|
54
|
+
devices = client.get_organization_devices("your-org-id")
|
|
55
|
+
print(f"Found {len(devices)} devices")
|
|
56
|
+
|
|
57
|
+
# Use as context manager (recommended)
|
|
58
|
+
with OrbCloudClient(token="your-api-token") as client:
|
|
59
|
+
devices = client.get_organization_devices("your-org-id")
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## API Reference
|
|
63
|
+
|
|
64
|
+
### OrbCloudClient
|
|
65
|
+
|
|
66
|
+
#### Constructor
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
OrbCloudClient(base_url="https://panel.orb.net", token=None)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Parameters:**
|
|
73
|
+
- `base_url` (str, optional): API base URL. Defaults to production environment.
|
|
74
|
+
- `token` (str, optional): Bearer authentication token.
|
|
75
|
+
|
|
76
|
+
#### Methods
|
|
77
|
+
|
|
78
|
+
##### get_organization_devices(organization_id)
|
|
79
|
+
|
|
80
|
+
Get a list of all devices for an organization.
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
devices = client.get_organization_devices("org-123")
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Parameters:**
|
|
87
|
+
- `organization_id` (str): The organization ID
|
|
88
|
+
|
|
89
|
+
**Returns:**
|
|
90
|
+
- `List[Device]`: List of Device objects
|
|
91
|
+
|
|
92
|
+
**Example:**
|
|
93
|
+
```python
|
|
94
|
+
devices = client.get_organization_devices("org-123")
|
|
95
|
+
for device in devices:
|
|
96
|
+
print(f"Device: {device.name} (ID: {device.id})")
|
|
97
|
+
print(f" Status: {'Connected' if device.is_connected else 'Disconnected'}")
|
|
98
|
+
print(f" Location: {device.geo_ip_info.city}, {device.geo_ip_info.state}")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
##### configure_temporary_datasets(device_id, temp_datasets_request)
|
|
102
|
+
|
|
103
|
+
Configure temporary data collection for a device with custom data push endpoint.
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from orb_cloud_client.models import TempDatasetsRequest, Datasets, DataPush
|
|
107
|
+
|
|
108
|
+
temp_config = TempDatasetsRequest(
|
|
109
|
+
duration="5m", # Run for 5 minutes
|
|
110
|
+
datasets_config=Datasets(
|
|
111
|
+
enabled=True,
|
|
112
|
+
datasets=["responsiveness_1s", "scores_1s"], # Collect these datasets
|
|
113
|
+
push=DataPush(
|
|
114
|
+
enabled=True,
|
|
115
|
+
url="https://your-endpoint.com/data", # Your webhook URL
|
|
116
|
+
datasets=["responsiveness_1s", "scores_1s"],
|
|
117
|
+
format="json",
|
|
118
|
+
interval_ms=500 # Push new data every 500ms (when available)
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
result = client.configure_temporary_datasets("device-456", temp_config)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Parameters:**
|
|
127
|
+
- `device_id` (str): The device ID
|
|
128
|
+
- `temp_datasets_request` (TempDatasetsRequest): Configuration object
|
|
129
|
+
|
|
130
|
+
**Returns:**
|
|
131
|
+
- `Dict[str, Any]`: API response
|
|
132
|
+
|
|
133
|
+
##### request(method, endpoint, **kwargs)
|
|
134
|
+
|
|
135
|
+
Make a custom HTTP request to any API endpoint.
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
response = client.request("GET", "/api/v2/custom-endpoint")
|
|
139
|
+
data = response.json()
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Parameters:**
|
|
143
|
+
- `method` (str): HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
144
|
+
- `endpoint` (str): API endpoint path
|
|
145
|
+
- `**kwargs`: Additional arguments passed to httpx
|
|
146
|
+
|
|
147
|
+
**Returns:**
|
|
148
|
+
- `httpx.Response`: Raw HTTP response
|
|
149
|
+
|
|
150
|
+
##### close()
|
|
151
|
+
|
|
152
|
+
Close the HTTP client connection.
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
client.close()
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Data Models
|
|
159
|
+
|
|
160
|
+
All API models are Pydantic models with full type validation and serialization.
|
|
161
|
+
|
|
162
|
+
#### Device
|
|
163
|
+
|
|
164
|
+
Represents an Orb device with all its properties.
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from orb_cloud_client.models import Device
|
|
168
|
+
|
|
169
|
+
# Device properties
|
|
170
|
+
device.id # str: Device UUID
|
|
171
|
+
device.name # str: Device name
|
|
172
|
+
device.is_connected # IsConnected: Connection status (0 or 1)
|
|
173
|
+
device.last_seen # Optional[str]: Last seen timestamp
|
|
174
|
+
device.geo_ip_info # Optional[GeoIPInfo]: Geographic information
|
|
175
|
+
device.device_info # Optional[DeviceInfo]: Hardware/software info
|
|
176
|
+
device.network_interfaces # Optional[List[NetworkInterface]]: Network info
|
|
177
|
+
device.orb_score # Optional[OrbScore]: Performance metrics
|
|
178
|
+
device.summary # Optional[DeviceSummary]: Device summary
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
#### TempDatasetsRequest
|
|
182
|
+
|
|
183
|
+
Configuration for temporary data collection.
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from orb_cloud_client.models import TempDatasetsRequest, Datasets, DataPush
|
|
187
|
+
|
|
188
|
+
temp_request = TempDatasetsRequest(
|
|
189
|
+
duration="5m", # Duration (e.g., "5m", "1h", "30m", "2h")
|
|
190
|
+
datasets_config=Datasets(
|
|
191
|
+
enabled=True,
|
|
192
|
+
datasets=["responsiveness_1s", "scores_1s"], # Dataset types to collect
|
|
193
|
+
push=DataPush( # Data push configuration
|
|
194
|
+
enabled=True,
|
|
195
|
+
url="https://your-server.com/webhook",
|
|
196
|
+
datasets=["responsiveness_1s", "scores_1s"],
|
|
197
|
+
format="json",
|
|
198
|
+
interval_ms=500 # Push interval in milliseconds
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
#### Datasets
|
|
205
|
+
|
|
206
|
+
Data collection configuration with push settings.
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
datasets = Datasets(
|
|
210
|
+
enabled=True, # bool: Enable data collection
|
|
211
|
+
datasets=[ # List[str]: Dataset types to collect
|
|
212
|
+
"responsiveness_1s",
|
|
213
|
+
"scores_1s",
|
|
214
|
+
"latency_1s",
|
|
215
|
+
"throughput_1s"
|
|
216
|
+
],
|
|
217
|
+
push=DataPush( # Optional: Push configuration
|
|
218
|
+
enabled=True,
|
|
219
|
+
url="https://api.example.com/webhook",
|
|
220
|
+
datasets=["responsiveness_1s", "scores_1s"],
|
|
221
|
+
format="json", # Data format
|
|
222
|
+
interval_ms=500 # Push interval in milliseconds
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
#### DataPush
|
|
228
|
+
|
|
229
|
+
Configuration for pushing data to external endpoints.
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
data_push = DataPush(
|
|
233
|
+
enabled=True, # bool: Enable data pushing
|
|
234
|
+
url="https://api.example.com/webhook", # str: Webhook URL
|
|
235
|
+
datasets=["responsiveness_1s", "scores_1s"], # List[str]: Datasets to push
|
|
236
|
+
format="json", # str: Data format
|
|
237
|
+
interval_ms=500 # int: Push interval in milliseconds
|
|
238
|
+
)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
#### GeoIPInfo
|
|
242
|
+
|
|
243
|
+
Geographic information from device IP address.
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
geo_info.city # Optional[str]: City name
|
|
247
|
+
geo_info.state # Optional[str]: State/region
|
|
248
|
+
geo_info.country # Optional[str]: Country name
|
|
249
|
+
geo_info.country_code # Optional[str]: Country code (e.g., "US")
|
|
250
|
+
geo_info.isp_name # Optional[str]: Internet service provider
|
|
251
|
+
geo_info.latitude # Optional[float]: Latitude coordinate
|
|
252
|
+
geo_info.longitude # Optional[float]: Longitude coordinate
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### DeviceInfo
|
|
256
|
+
|
|
257
|
+
Device hardware and software information.
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
device_info.name # Optional[str]: Device hostname
|
|
261
|
+
device_info.version # Optional[str]: Software version
|
|
262
|
+
device_info.cpu_count # Optional[int]: Number of CPU cores
|
|
263
|
+
device_info.full_name # Optional[str]: Full hostname
|
|
264
|
+
device_info.operating_system # Optional[str]: OS name
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
#### NetworkInterface
|
|
268
|
+
|
|
269
|
+
Network interface information.
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
interface.name # Optional[str]: Interface name
|
|
273
|
+
interface.type # Optional[str]: Interface type (e.g., "Ethernet")
|
|
274
|
+
interface.local_ip # Optional[str]: Local IP address
|
|
275
|
+
interface.mac_address # Optional[str]: MAC address
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Usage Examples
|
|
279
|
+
|
|
280
|
+
### Basic Device Management
|
|
281
|
+
|
|
282
|
+
```python
|
|
283
|
+
from orb_cloud_client import OrbCloudClient
|
|
284
|
+
|
|
285
|
+
with OrbCloudClient(token="your-token") as client:
|
|
286
|
+
# Get all devices
|
|
287
|
+
devices = client.get_organization_devices("org-123")
|
|
288
|
+
|
|
289
|
+
# Filter connected devices
|
|
290
|
+
connected_devices = [d for d in devices if d.is_connected == 1]
|
|
291
|
+
|
|
292
|
+
# Find specific device
|
|
293
|
+
target_device = next((d for d in devices if "hardy" in d.name.lower()), None)
|
|
294
|
+
if target_device:
|
|
295
|
+
print(f"Found device: {target_device.name}")
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Configure Data Collection
|
|
299
|
+
|
|
300
|
+
```python
|
|
301
|
+
from orb_cloud_client import OrbCloudClient
|
|
302
|
+
from orb_cloud_client.models import TempDatasetsRequest, Datasets, DataPush
|
|
303
|
+
|
|
304
|
+
with OrbCloudClient(token="your-token") as client:
|
|
305
|
+
# Configure temporary datasets with custom endpoint
|
|
306
|
+
config = TempDatasetsRequest(
|
|
307
|
+
duration="5m", # Run for 5 minutes
|
|
308
|
+
datasets_config=Datasets(
|
|
309
|
+
enabled=True,
|
|
310
|
+
datasets=["responsiveness_1s", "scores_1s"],
|
|
311
|
+
push=DataPush(
|
|
312
|
+
enabled=True,
|
|
313
|
+
url="https://your-analytics.com/orb-data",
|
|
314
|
+
datasets=["responsiveness_1s", "scores_1s"],
|
|
315
|
+
format="json",
|
|
316
|
+
interval_ms=500 # Push every 500ms when data available
|
|
317
|
+
)
|
|
318
|
+
)
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
result = client.configure_temporary_datasets("device-456", config)
|
|
322
|
+
print("Configuration successful:", result)
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Error Handling
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
from orb_cloud_client import OrbCloudClient
|
|
329
|
+
import httpx
|
|
330
|
+
|
|
331
|
+
with OrbCloudClient(token="your-token") as client:
|
|
332
|
+
try:
|
|
333
|
+
devices = client.get_organization_devices("org-123")
|
|
334
|
+
except httpx.HTTPStatusError as e:
|
|
335
|
+
print(f"HTTP error {e.response.status_code}: {e.response.text}")
|
|
336
|
+
except httpx.RequestError as e:
|
|
337
|
+
print(f"Request error: {e}")
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Working with Device Data
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
from orb_cloud_client import OrbCloudClient
|
|
344
|
+
|
|
345
|
+
with OrbCloudClient(token="your-token") as client:
|
|
346
|
+
devices = client.get_organization_devices("org-123")
|
|
347
|
+
|
|
348
|
+
for device in devices:
|
|
349
|
+
print(f"\n--- {device.name} ---")
|
|
350
|
+
print(f"ID: {device.id}")
|
|
351
|
+
print(f"Connected: {'Yes' if device.is_connected else 'No'}")
|
|
352
|
+
|
|
353
|
+
if device.geo_ip_info:
|
|
354
|
+
print(f"Location: {device.geo_ip_info.city}, {device.geo_ip_info.state}")
|
|
355
|
+
print(f"ISP: {device.geo_ip_info.isp_name}")
|
|
356
|
+
|
|
357
|
+
if device.device_info:
|
|
358
|
+
print(f"OS: {device.device_info.operating_system}")
|
|
359
|
+
print(f"Version: {device.device_info.version}")
|
|
360
|
+
|
|
361
|
+
if device.network_interfaces:
|
|
362
|
+
for interface in device.network_interfaces:
|
|
363
|
+
print(f"Interface: {interface.name} ({interface.local_ip})")
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Interactive Example
|
|
367
|
+
|
|
368
|
+
The package includes an interactive example that demonstrates the complete workflow:
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
export ORB_API_TOKEN="your-api-token"
|
|
372
|
+
export ORB_ORGANIZATION_ID="your-org-id"
|
|
373
|
+
python3 -m orb_cloud_client.example
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
The example will:
|
|
377
|
+
1. Fetch and display all devices
|
|
378
|
+
2. Configure temporary datasets for the selected device
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
See `example.py` for example code.
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
## Available Dataset Types
|
|
385
|
+
|
|
386
|
+
Common dataset types you can collect:
|
|
387
|
+
- `responsiveness_{timeframe}` - time-binned responsiveness (example `responiveness_1s`, `responsiveness_15s`)
|
|
388
|
+
- `scores_{timeframe}` - time-binned scores (example `scores_1s`, `scores_1m`)
|
|
389
|
+
- `speed_results` - Result-level speed measurements
|
|
390
|
+
- `web_responsiveness_results` - Result-level DNS resolve and TTFB measurements
|
|
391
|
+
|
|
392
|
+
## Authentication
|
|
393
|
+
|
|
394
|
+
The client uses Bearer token authentication. Get your API token from the Orb Cloud dashboard and pass it to the client constructor:
|
|
395
|
+
|
|
396
|
+
```python
|
|
397
|
+
client = OrbCloudClient(token="your-bearer-token")
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Environment Support
|
|
401
|
+
|
|
402
|
+
- **Staging**: `https://api.staging.orb.net` (default)
|
|
403
|
+
- **Production**: `https://api.orb.net`
|
|
404
|
+
|
|
405
|
+
```python
|
|
406
|
+
# Production client
|
|
407
|
+
client = OrbCloudClient(
|
|
408
|
+
base_url="https://api.orb.net",
|
|
409
|
+
token="your-token"
|
|
410
|
+
)
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## Requirements
|
|
414
|
+
|
|
415
|
+
- Python 3.8+
|
|
416
|
+
- httpx >= 0.24.0
|
|
417
|
+
- pydantic >= 2.0.0
|
|
418
|
+
|
|
419
|
+
## Error Handling
|
|
420
|
+
|
|
421
|
+
The client raises standard httpx exceptions:
|
|
422
|
+
- `httpx.HTTPStatusError` - For HTTP error responses (4xx, 5xx)
|
|
423
|
+
- `httpx.RequestError` - For network/connection errors
|
|
424
|
+
- `httpx.TimeoutException` - For request timeouts
|
|
425
|
+
|
|
426
|
+
Always wrap API calls in try-catch blocks for production use.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
orb_cloud_client/__init__.py,sha256=zLbPCBXomXt5df4vyQVNVVRyRcM9ic4Fv93Vc9dOxN4,382
|
|
2
|
+
orb_cloud_client/client.py,sha256=z2LWiKEakOP-kC4ElXmSPs-VVoWmPSz8WMVAVnWSIn8,4781
|
|
3
|
+
orb_cloud_client/example.py,sha256=2izr6hF7gJyV_Gjqa3v0j9rkDwCjCYJsLXRgJTI_pIU,6046
|
|
4
|
+
orb_cloud_client/example_async.py,sha256=OZb55zsYL_-Lo2TEgbBLVW1rdKldeWOmWI_HwPV1LMg,1427
|
|
5
|
+
orb_cloud_client/models.py,sha256=5TTJBKunvLnvGBNAGmUFXepTi6K44AFzrSOXpDcoQKg,7955
|
|
6
|
+
orb_cloud_client-1.2.1.dist-info/licenses/LICENSE,sha256=eUe4cBliuPkBMknHKwcgVbFlu53hoolIo-YrsynYrug,1069
|
|
7
|
+
orb_cloud_client-1.2.1.dist-info/METADATA,sha256=uHkzxpK5pq-KF-mNyBqJjNNTcAI-btjpnoQdZ1omc1s,12482
|
|
8
|
+
orb_cloud_client-1.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
orb_cloud_client-1.2.1.dist-info/entry_points.txt,sha256=3i38Vp8ALWRx-BCL66C8m5BXCIF3vAJq8VMpOu_wT8Q,62
|
|
10
|
+
orb_cloud_client-1.2.1.dist-info/top_level.txt,sha256=WMPN64HVd0BGfTV4BnMef1lroq7nUv_ZVf8wjYH8DdE,17
|
|
11
|
+
orb_cloud_client-1.2.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Orb Networks
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
orb_cloud_client
|