arga-py-sdk 0.1.1__tar.gz

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.
@@ -0,0 +1,45 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - name: Set up Python
15
+ uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.12"
18
+
19
+ - name: Install build dependencies
20
+ run: pip install hatchling build
21
+
22
+ - name: Build package
23
+ run: python -m build
24
+
25
+ - name: Upload dist artifacts
26
+ uses: actions/upload-artifact@v4
27
+ with:
28
+ name: dist
29
+ path: dist/
30
+
31
+ publish:
32
+ needs: build
33
+ runs-on: ubuntu-latest
34
+ environment: pypi
35
+ permissions:
36
+ id-token: write
37
+ steps:
38
+ - name: Download dist artifacts
39
+ uses: actions/download-artifact@v4
40
+ with:
41
+ name: dist
42
+ path: dist/
43
+
44
+ - name: Publish to PyPI
45
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,22 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.so
5
+ dist/
6
+ build/
7
+ *.egg-info/
8
+ *.egg
9
+ .eggs/
10
+ *.whl
11
+ .venv/
12
+ venv/
13
+ env/
14
+ .env
15
+ .pytest_cache/
16
+ .mypy_cache/
17
+ .ruff_cache/
18
+ *.swp
19
+ *.swo
20
+ *~
21
+ .DS_Store
22
+ Thumbs.db
@@ -0,0 +1,149 @@
1
+ Metadata-Version: 2.4
2
+ Name: arga-py-sdk
3
+ Version: 0.1.1
4
+ Summary: Python SDK for the Arga API
5
+ Project-URL: Documentation, https://docs.argalabs.com
6
+ Project-URL: Repository, https://github.com/ArgaLabs/arga-python-sdk
7
+ Author-email: Arga Labs <support@argalabs.com>
8
+ License-Expression: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Typing :: Typed
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: httpx-sse>=0.4.0
20
+ Requires-Dist: httpx>=0.25.0
21
+ Requires-Dist: pydantic>=2.0.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest; extra == 'dev'
24
+ Requires-Dist: pytest-asyncio; extra == 'dev'
25
+ Requires-Dist: respx; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # Arga Python SDK
29
+
30
+ Typed Python client for the [Arga](https://argalabs.com) API. Supports both synchronous and asynchronous usage.
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install arga-sdk
36
+ ```
37
+
38
+ ## Quick Start
39
+
40
+ ```python
41
+ from arga_sdk import Arga
42
+
43
+ client = Arga(api_key="arga_...")
44
+
45
+ # Create a URL run with service twins
46
+ run = client.runs.create_url_run(
47
+ url="https://staging.myapp.com",
48
+ twins=["stripe", "slack"],
49
+ )
50
+ print(run.run_id, run.status)
51
+
52
+ # Poll until the run completes
53
+ detail = client.runs.wait(run.run_id, timeout=300)
54
+ print(detail.status, detail.results_json)
55
+
56
+ # Stream live results
57
+ for event in client.runs.stream_results(run.run_id):
58
+ print(event)
59
+
60
+ # Cancel a run
61
+ client.runs.cancel(run.run_id)
62
+ ```
63
+
64
+ ## Async Usage
65
+
66
+ ```python
67
+ import asyncio
68
+ from arga_sdk import AsyncArga
69
+
70
+ async def main():
71
+ client = AsyncArga(api_key="arga_...")
72
+
73
+ run = await client.runs.create_url_run(url="https://staging.myapp.com")
74
+
75
+ # Stream results
76
+ async for event in client.runs.stream_results(run.run_id):
77
+ print(event)
78
+
79
+ # Wait for completion
80
+ detail = await client.runs.wait(run.run_id)
81
+ print(detail.status)
82
+
83
+ await client.close()
84
+
85
+ asyncio.run(main())
86
+ ```
87
+
88
+ ## Context Manager
89
+
90
+ Both clients support context managers to ensure proper cleanup:
91
+
92
+ ```python
93
+ with Arga(api_key="arga_...") as client:
94
+ run = client.runs.create_url_run(url="https://staging.myapp.com")
95
+
96
+ async with AsyncArga(api_key="arga_...") as client:
97
+ run = await client.runs.create_url_run(url="https://staging.myapp.com")
98
+ ```
99
+
100
+ ## Available Methods
101
+
102
+ ### Runs
103
+
104
+ | Method | Description |
105
+ |--------|-------------|
106
+ | `client.runs.create_url_run(url, ...)` | Test a live URL with browser validation |
107
+ | `client.runs.create_pr_run(repo, ...)` | Validate code changes from a PR or branch |
108
+ | `client.runs.create_agent_run(...)` | Deploy an autonomous agent for exploration |
109
+ | `client.runs.get(run_id)` | Get full run details |
110
+ | `client.runs.stream_results(run_id)` | Stream SSE result events |
111
+ | `client.runs.cancel(run_id)` | Cancel a running run |
112
+ | `client.runs.wait(run_id, ...)` | Poll until terminal status |
113
+
114
+ ### Twins
115
+
116
+ | Method | Description |
117
+ |--------|-------------|
118
+ | `client.twins.list()` | List available service twins |
119
+ | `client.twins.provision(twins, ...)` | Provision twin instances |
120
+ | `client.twins.get_status(run_id)` | Get provisioning status |
121
+ | `client.twins.extend(run_id, ...)` | Extend twin TTL |
122
+
123
+ ### Scenarios
124
+
125
+ | Method | Description |
126
+ |--------|-------------|
127
+ | `client.scenarios.create(name, ...)` | Create a test scenario |
128
+ | `client.scenarios.list(...)` | List scenarios (with optional filters) |
129
+ | `client.scenarios.get(scenario_id)` | Get a scenario by ID |
130
+
131
+ ## Error Handling
132
+
133
+ ```python
134
+ from arga_sdk import Arga, ArgaAPIError, ArgaError
135
+
136
+ client = Arga(api_key="arga_...")
137
+
138
+ try:
139
+ run = client.runs.get("nonexistent-id")
140
+ except ArgaAPIError as e:
141
+ print(e.status_code) # e.g. 404
142
+ print(e.message)
143
+ except ArgaError as e:
144
+ print(e.message)
145
+ ```
146
+
147
+ ## Documentation
148
+
149
+ Full API documentation is available at [docs.argalabs.com](https://docs.argalabs.com).
@@ -0,0 +1,122 @@
1
+ # Arga Python SDK
2
+
3
+ Typed Python client for the [Arga](https://argalabs.com) API. Supports both synchronous and asynchronous usage.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install arga-sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ from arga_sdk import Arga
15
+
16
+ client = Arga(api_key="arga_...")
17
+
18
+ # Create a URL run with service twins
19
+ run = client.runs.create_url_run(
20
+ url="https://staging.myapp.com",
21
+ twins=["stripe", "slack"],
22
+ )
23
+ print(run.run_id, run.status)
24
+
25
+ # Poll until the run completes
26
+ detail = client.runs.wait(run.run_id, timeout=300)
27
+ print(detail.status, detail.results_json)
28
+
29
+ # Stream live results
30
+ for event in client.runs.stream_results(run.run_id):
31
+ print(event)
32
+
33
+ # Cancel a run
34
+ client.runs.cancel(run.run_id)
35
+ ```
36
+
37
+ ## Async Usage
38
+
39
+ ```python
40
+ import asyncio
41
+ from arga_sdk import AsyncArga
42
+
43
+ async def main():
44
+ client = AsyncArga(api_key="arga_...")
45
+
46
+ run = await client.runs.create_url_run(url="https://staging.myapp.com")
47
+
48
+ # Stream results
49
+ async for event in client.runs.stream_results(run.run_id):
50
+ print(event)
51
+
52
+ # Wait for completion
53
+ detail = await client.runs.wait(run.run_id)
54
+ print(detail.status)
55
+
56
+ await client.close()
57
+
58
+ asyncio.run(main())
59
+ ```
60
+
61
+ ## Context Manager
62
+
63
+ Both clients support context managers to ensure proper cleanup:
64
+
65
+ ```python
66
+ with Arga(api_key="arga_...") as client:
67
+ run = client.runs.create_url_run(url="https://staging.myapp.com")
68
+
69
+ async with AsyncArga(api_key="arga_...") as client:
70
+ run = await client.runs.create_url_run(url="https://staging.myapp.com")
71
+ ```
72
+
73
+ ## Available Methods
74
+
75
+ ### Runs
76
+
77
+ | Method | Description |
78
+ |--------|-------------|
79
+ | `client.runs.create_url_run(url, ...)` | Test a live URL with browser validation |
80
+ | `client.runs.create_pr_run(repo, ...)` | Validate code changes from a PR or branch |
81
+ | `client.runs.create_agent_run(...)` | Deploy an autonomous agent for exploration |
82
+ | `client.runs.get(run_id)` | Get full run details |
83
+ | `client.runs.stream_results(run_id)` | Stream SSE result events |
84
+ | `client.runs.cancel(run_id)` | Cancel a running run |
85
+ | `client.runs.wait(run_id, ...)` | Poll until terminal status |
86
+
87
+ ### Twins
88
+
89
+ | Method | Description |
90
+ |--------|-------------|
91
+ | `client.twins.list()` | List available service twins |
92
+ | `client.twins.provision(twins, ...)` | Provision twin instances |
93
+ | `client.twins.get_status(run_id)` | Get provisioning status |
94
+ | `client.twins.extend(run_id, ...)` | Extend twin TTL |
95
+
96
+ ### Scenarios
97
+
98
+ | Method | Description |
99
+ |--------|-------------|
100
+ | `client.scenarios.create(name, ...)` | Create a test scenario |
101
+ | `client.scenarios.list(...)` | List scenarios (with optional filters) |
102
+ | `client.scenarios.get(scenario_id)` | Get a scenario by ID |
103
+
104
+ ## Error Handling
105
+
106
+ ```python
107
+ from arga_sdk import Arga, ArgaAPIError, ArgaError
108
+
109
+ client = Arga(api_key="arga_...")
110
+
111
+ try:
112
+ run = client.runs.get("nonexistent-id")
113
+ except ArgaAPIError as e:
114
+ print(e.status_code) # e.g. 404
115
+ print(e.message)
116
+ except ArgaError as e:
117
+ print(e.message)
118
+ ```
119
+
120
+ ## Documentation
121
+
122
+ Full API documentation is available at [docs.argalabs.com](https://docs.argalabs.com).
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "arga-py-sdk"
7
+ version = "0.1.1"
8
+ description = "Python SDK for the Arga API"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ { name = "Arga Labs", email = "support@argalabs.com" },
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Typing :: Typed",
25
+ ]
26
+ dependencies = [
27
+ "httpx>=0.25.0",
28
+ "httpx-sse>=0.4.0",
29
+ "pydantic>=2.0.0",
30
+ ]
31
+
32
+ [project.optional-dependencies]
33
+ dev = ["pytest", "pytest-asyncio", "respx"]
34
+
35
+ [project.urls]
36
+ Documentation = "https://docs.argalabs.com"
37
+ Repository = "https://github.com/ArgaLabs/arga-python-sdk"
38
+
39
+ [tool.hatch.build.targets.wheel]
40
+ packages = ["src/arga_sdk"]
@@ -0,0 +1,25 @@
1
+ """Arga Python SDK — typed client for the Arga API."""
2
+
3
+ from arga_sdk.client import Arga, AsyncArga
4
+ from arga_sdk.exceptions import ArgaAPIError, ArgaError
5
+ from arga_sdk.types import (
6
+ Run,
7
+ RunDetail,
8
+ Scenario,
9
+ Twin,
10
+ TwinInstance,
11
+ TwinProvisionStatus,
12
+ )
13
+
14
+ __all__ = [
15
+ "Arga",
16
+ "AsyncArga",
17
+ "ArgaAPIError",
18
+ "ArgaError",
19
+ "Run",
20
+ "RunDetail",
21
+ "Scenario",
22
+ "Twin",
23
+ "TwinInstance",
24
+ "TwinProvisionStatus",
25
+ ]
@@ -0,0 +1,116 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Iterator, AsyncIterator
4
+
5
+ import httpx
6
+ import httpx_sse
7
+
8
+ from arga_sdk.exceptions import ArgaAPIError
9
+
10
+
11
+ def _raise_for_status(response: httpx.Response) -> None:
12
+ """Raise ArgaAPIError for non-2xx responses."""
13
+ if response.is_success:
14
+ return
15
+ try:
16
+ body = response.json()
17
+ message = body.get("detail", body.get("message", response.text))
18
+ except Exception:
19
+ message = response.text
20
+ raise ArgaAPIError(
21
+ status_code=response.status_code,
22
+ message=str(message),
23
+ response=response,
24
+ )
25
+
26
+
27
+ class SyncHTTPClient:
28
+ """Synchronous HTTP client wrapper around httpx."""
29
+
30
+ def __init__(self, base_url: str, api_key: str, timeout: float = 60.0) -> None:
31
+ self._client = httpx.Client(
32
+ base_url=base_url,
33
+ headers={
34
+ "Authorization": f"Bearer {api_key}",
35
+ "Content-Type": "application/json",
36
+ },
37
+ timeout=timeout,
38
+ )
39
+
40
+ def get(self, path: str, params: dict[str, Any] | None = None) -> Any:
41
+ response = self._client.get(path, params=params)
42
+ _raise_for_status(response)
43
+ return response.json()
44
+
45
+ def post(self, path: str, json: dict[str, Any] | None = None) -> Any:
46
+ response = self._client.post(path, json=json)
47
+ _raise_for_status(response)
48
+ return response.json()
49
+
50
+ def stream_sse(self, path: str) -> Iterator[dict[str, Any]]:
51
+ """Stream SSE events from a GET endpoint."""
52
+ with httpx_sse.connect_sse(
53
+ self._client, "GET", path
54
+ ) as event_source:
55
+ for sse in event_source.iter_sse():
56
+ import json as _json
57
+
58
+ try:
59
+ data = _json.loads(sse.data)
60
+ except (ValueError, TypeError):
61
+ data = sse.data
62
+ yield {
63
+ "event": sse.event,
64
+ "data": data,
65
+ "id": sse.id,
66
+ "retry": sse.retry,
67
+ }
68
+
69
+ def close(self) -> None:
70
+ self._client.close()
71
+
72
+
73
+ class AsyncHTTPClient:
74
+ """Asynchronous HTTP client wrapper around httpx."""
75
+
76
+ def __init__(self, base_url: str, api_key: str, timeout: float = 60.0) -> None:
77
+ self._client = httpx.AsyncClient(
78
+ base_url=base_url,
79
+ headers={
80
+ "Authorization": f"Bearer {api_key}",
81
+ "Content-Type": "application/json",
82
+ },
83
+ timeout=timeout,
84
+ )
85
+
86
+ async def get(self, path: str, params: dict[str, Any] | None = None) -> Any:
87
+ response = await self._client.get(path, params=params)
88
+ _raise_for_status(response)
89
+ return response.json()
90
+
91
+ async def post(self, path: str, json: dict[str, Any] | None = None) -> Any:
92
+ response = await self._client.post(path, json=json)
93
+ _raise_for_status(response)
94
+ return response.json()
95
+
96
+ async def stream_sse(self, path: str) -> AsyncIterator[dict[str, Any]]:
97
+ """Stream SSE events from a GET endpoint."""
98
+ async with httpx_sse.aconnect_sse(
99
+ self._client, "GET", path
100
+ ) as event_source:
101
+ async for sse in event_source.aiter_sse():
102
+ import json as _json
103
+
104
+ try:
105
+ data = _json.loads(sse.data)
106
+ except (ValueError, TypeError):
107
+ data = sse.data
108
+ yield {
109
+ "event": sse.event,
110
+ "data": data,
111
+ "id": sse.id,
112
+ "retry": sse.retry,
113
+ }
114
+
115
+ async def close(self) -> None:
116
+ await self._client.aclose()