briq-cloud 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,7 @@
1
+ .venv/
2
+ dist/
3
+ __pycache__/
4
+ *.egg-info/
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ .pytest_cache/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 briq
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,150 @@
1
+ Metadata-Version: 2.5
2
+ Name: briq-cloud
3
+ Version: 0.1.1
4
+ Summary: The Python client for briq: run sandboxes for your agents, run commands and move files.
5
+ Project-URL: Homepage, https://briq.cloud
6
+ Project-URL: Repository, https://github.com/briq-cloud/briq-python
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: agents,api-client,briq,microvm,sandbox
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
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: Programming Language :: Python :: 3.14
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: httpx<1,>=0.27
21
+ Description-Content-Type: text/markdown
22
+
23
+ # briq-cloud
24
+
25
+ Python client for [briq](https://briq.cloud). Create a sandbox, run commands in it, move files in and out.
26
+
27
+ ```python
28
+ from briq_cloud import Briq
29
+
30
+ with Briq() as briq: # reads BRIQ_API_KEY
31
+ with briq.create_sandbox(sleep="30m") as sandbox: # destroyed when the block ends
32
+ sandbox.write_file("/work/hello.py", b"print('hello')")
33
+ result = sandbox.exec("python3 hello.py", cwd="/work")
34
+ print(result.stdout, result.exit_code)
35
+ print(sandbox.read_file("/work/hello.py"))
36
+ print(sandbox.preview_url(8000))
37
+ ```
38
+
39
+ The async client has the same methods, awaited:
40
+
41
+ ```python
42
+ from briq_cloud import AsyncBriq
43
+
44
+ async with AsyncBriq() as briq:
45
+ sandbox = await briq.create_sandbox()
46
+ result = await sandbox.exec(["ls", "-la"])
47
+ await sandbox.destroy()
48
+ ```
49
+
50
+ Install: `pip install briq-cloud`. Python 3.10 or later. The only dependency is `httpx`.
51
+
52
+ ## Configure
53
+
54
+ | Argument | Default | Environment variable |
55
+ | ---------- | ------------------------ | -------------------- |
56
+ | `api_key` | none, required | `BRIQ_API_KEY` |
57
+ | `base_url` | `https://api.briq.cloud` | `BRIQ_API_URL` |
58
+
59
+ `timeout` is the seconds one request may take. The client reads the usual proxy and certificate variables (`HTTPS_PROXY`, `SSL_CERT_FILE`). To set up the connection yourself, pass `http_client=` an `httpx.Client` (`httpx.AsyncClient` for `AsyncBriq`): it is used as it is, `close` leaves it open, and a redirect is never followed, whatever it says. `transport` takes an `httpx` transport, for tests.
60
+
61
+ ## Sandbox
62
+
63
+ | Method | Does |
64
+ | ------------------------------------ | ------------------------------------------------------------- |
65
+ | `briq.create_sandbox(...)` | Makes a sandbox, waits until it runs. |
66
+ | `briq.sandbox(id)` | Reconnects to a sandbox by id. |
67
+ | `sandbox.exec(command, ...)` | Runs a command, returns `ExecResult`. |
68
+ | `sandbox.write_file(path, data)` | Writes bytes, creates missing directories. |
69
+ | `sandbox.read_file(path)` | Returns the bytes of a file. |
70
+ | `sandbox.list_files(path)` | Returns the entries of a directory. Needs `python3` inside. |
71
+ | `sandbox.remove(path, recursive=)` | Removes a file or a directory. |
72
+ | `sandbox.preview_url(port)` | Returns a URL for a port. `public=True` for anyone. |
73
+ | `sandbox.stop()` / `sandbox.start()` | Stops it, keeps the disk, bills nothing. Starts it again. |
74
+ | `sandbox.set_sleep("30m")` | Sets the idle time before it sleeps. `"never"` stays awake. |
75
+ | `sandbox.refresh()` | Reads its state again. |
76
+ | `sandbox.destroy()` | Removes it and its disk. |
77
+
78
+ A sleeping sandbox wakes on the next call.
79
+
80
+ ### Run a command
81
+
82
+ ```python
83
+ sandbox.exec(
84
+ "make test",
85
+ cwd="/work",
86
+ env={"CI": "1"},
87
+ timeout=600,
88
+ on_output=lambda chunk: print(chunk.text, end=""),
89
+ )
90
+ ```
91
+
92
+ - A string runs in a shell. A list is the program and its arguments.
93
+ - `timeout` is in seconds, up to 24 hours. A command that outlives it is stopped: `timed_out` is true and `exit_code` is `None`.
94
+ - Without `on_output`, a command up to 60 seconds runs in one request.
95
+ - With `on_output`, or past 60 seconds, the output reaches `on_output` as it is written. Both streams are read, in order within each stream.
96
+ - `stdout` and `stderr` are text. Read bytes with `read_file`.
97
+
98
+ ### Secrets
99
+
100
+ ```python
101
+ briq.create_sandbox(env={"MODE": "test"}, secret_env={"API_TOKEN": token})
102
+ ```
103
+
104
+ `env` values show in the sandbox's status. `secret_env` values are write-only. If the API cannot keep them so, the call raises `unsupported`.
105
+
106
+ ## Errors
107
+
108
+ Every failure is a `BriqError` with a stable `code`.
109
+
110
+ | `code` | Means | Try again? |
111
+ | ------------------ | -------------------------------------------------------------- | --------------- |
112
+ | `auth` | No key, a wrong key, or a key that may not do this. | No |
113
+ | `not_found` | The sandbox does not exist. | No |
114
+ | `file_not_found` | The file or directory does not exist. | No |
115
+ | `quota` | A limit stopped it: credits, plan, rate limit, size, a file. | Later |
116
+ | `unavailable` | briq could not take the call. Nothing was applied. | Yes |
117
+ | `invalid_argument` | The call is wrong. | No |
118
+ | `provider` | A refusal that fits no other code. | Maybe |
119
+ | `outcome_unknown` | The call was sent. Its result is not known. | Check first |
120
+ | `timeout` | A deadline of the client ended the call. | Maybe |
121
+ | `unsupported` | This API or image cannot do it. | No |
122
+ | `aborted` | The command was stopped by someone else. | No |
123
+
124
+ `quota` also covers HTTP 413 and the API's `plan_limit` refusals: trying again changes nothing until the limit does. A file over 100 MiB is `quota`, from `read_file` and `write_file`.
125
+
126
+ Also on the error: `status`, `api_code`, `request_id` (quote it when you ask for help), `retry_after`, `details`, `idempotency_key` and `sandbox_id`. `details` holds codes, ids and numbers (`{"limit": "size", "plan": "hobby"}`); the text a provider gave is never kept.
127
+
128
+ A command and its directory may total 8,192 bytes. A longer one raises `invalid_argument` before anything is sent: write the script with `write_file` and run that file.
129
+
130
+ The client never repeats a call whose effect it cannot see. When a command's end is not known, or a sandbox's creation got no answer, it raises `outcome_unknown`. To retry a creation safely, pass the same `idempotency_key` again: briq returns the first sandbox. Without a key of your own the client makes one, and `error.idempotency_key` is that key.
131
+
132
+ ```python
133
+ try:
134
+ sandbox = briq.create_sandbox()
135
+ except BriqError as error:
136
+ if error.code == "outcome_unknown" and error.idempotency_key:
137
+ sandbox = briq.create_sandbox(idempotency_key=error.idempotency_key)
138
+ else:
139
+ raise
140
+ ```
141
+
142
+ If creating fails after the sandbox exists (it did not start in time, or the call was cancelled or interrupted), the client destroys it and raises the original error. When it cannot destroy it, the error is `outcome_unknown` with `error.sandbox_id`: call `briq.sandbox(error.sandbox_id).destroy()`. The cause of the failure is `error.__cause__`.
143
+
144
+ ### Cancel a call
145
+
146
+ A cancelled task (`asyncio.wait_for` timing out, `task.cancel()`) or a `KeyboardInterrupt` ends a call cleanly. A sandbox made by the call is destroyed, even though the cancel is running, and a command started with `on_output` is stopped. The cancel or the interrupt is raised again as it was; a cleanup that fails never replaces it. A cancel that arrives while the create request is still in flight cannot be undone, because the id is not known yet: pass your own `idempotency_key` and repeat the call to get the sandbox.
147
+
148
+ ## License
149
+
150
+ MIT
@@ -0,0 +1,128 @@
1
+ # briq-cloud
2
+
3
+ Python client for [briq](https://briq.cloud). Create a sandbox, run commands in it, move files in and out.
4
+
5
+ ```python
6
+ from briq_cloud import Briq
7
+
8
+ with Briq() as briq: # reads BRIQ_API_KEY
9
+ with briq.create_sandbox(sleep="30m") as sandbox: # destroyed when the block ends
10
+ sandbox.write_file("/work/hello.py", b"print('hello')")
11
+ result = sandbox.exec("python3 hello.py", cwd="/work")
12
+ print(result.stdout, result.exit_code)
13
+ print(sandbox.read_file("/work/hello.py"))
14
+ print(sandbox.preview_url(8000))
15
+ ```
16
+
17
+ The async client has the same methods, awaited:
18
+
19
+ ```python
20
+ from briq_cloud import AsyncBriq
21
+
22
+ async with AsyncBriq() as briq:
23
+ sandbox = await briq.create_sandbox()
24
+ result = await sandbox.exec(["ls", "-la"])
25
+ await sandbox.destroy()
26
+ ```
27
+
28
+ Install: `pip install briq-cloud`. Python 3.10 or later. The only dependency is `httpx`.
29
+
30
+ ## Configure
31
+
32
+ | Argument | Default | Environment variable |
33
+ | ---------- | ------------------------ | -------------------- |
34
+ | `api_key` | none, required | `BRIQ_API_KEY` |
35
+ | `base_url` | `https://api.briq.cloud` | `BRIQ_API_URL` |
36
+
37
+ `timeout` is the seconds one request may take. The client reads the usual proxy and certificate variables (`HTTPS_PROXY`, `SSL_CERT_FILE`). To set up the connection yourself, pass `http_client=` an `httpx.Client` (`httpx.AsyncClient` for `AsyncBriq`): it is used as it is, `close` leaves it open, and a redirect is never followed, whatever it says. `transport` takes an `httpx` transport, for tests.
38
+
39
+ ## Sandbox
40
+
41
+ | Method | Does |
42
+ | ------------------------------------ | ------------------------------------------------------------- |
43
+ | `briq.create_sandbox(...)` | Makes a sandbox, waits until it runs. |
44
+ | `briq.sandbox(id)` | Reconnects to a sandbox by id. |
45
+ | `sandbox.exec(command, ...)` | Runs a command, returns `ExecResult`. |
46
+ | `sandbox.write_file(path, data)` | Writes bytes, creates missing directories. |
47
+ | `sandbox.read_file(path)` | Returns the bytes of a file. |
48
+ | `sandbox.list_files(path)` | Returns the entries of a directory. Needs `python3` inside. |
49
+ | `sandbox.remove(path, recursive=)` | Removes a file or a directory. |
50
+ | `sandbox.preview_url(port)` | Returns a URL for a port. `public=True` for anyone. |
51
+ | `sandbox.stop()` / `sandbox.start()` | Stops it, keeps the disk, bills nothing. Starts it again. |
52
+ | `sandbox.set_sleep("30m")` | Sets the idle time before it sleeps. `"never"` stays awake. |
53
+ | `sandbox.refresh()` | Reads its state again. |
54
+ | `sandbox.destroy()` | Removes it and its disk. |
55
+
56
+ A sleeping sandbox wakes on the next call.
57
+
58
+ ### Run a command
59
+
60
+ ```python
61
+ sandbox.exec(
62
+ "make test",
63
+ cwd="/work",
64
+ env={"CI": "1"},
65
+ timeout=600,
66
+ on_output=lambda chunk: print(chunk.text, end=""),
67
+ )
68
+ ```
69
+
70
+ - A string runs in a shell. A list is the program and its arguments.
71
+ - `timeout` is in seconds, up to 24 hours. A command that outlives it is stopped: `timed_out` is true and `exit_code` is `None`.
72
+ - Without `on_output`, a command up to 60 seconds runs in one request.
73
+ - With `on_output`, or past 60 seconds, the output reaches `on_output` as it is written. Both streams are read, in order within each stream.
74
+ - `stdout` and `stderr` are text. Read bytes with `read_file`.
75
+
76
+ ### Secrets
77
+
78
+ ```python
79
+ briq.create_sandbox(env={"MODE": "test"}, secret_env={"API_TOKEN": token})
80
+ ```
81
+
82
+ `env` values show in the sandbox's status. `secret_env` values are write-only. If the API cannot keep them so, the call raises `unsupported`.
83
+
84
+ ## Errors
85
+
86
+ Every failure is a `BriqError` with a stable `code`.
87
+
88
+ | `code` | Means | Try again? |
89
+ | ------------------ | -------------------------------------------------------------- | --------------- |
90
+ | `auth` | No key, a wrong key, or a key that may not do this. | No |
91
+ | `not_found` | The sandbox does not exist. | No |
92
+ | `file_not_found` | The file or directory does not exist. | No |
93
+ | `quota` | A limit stopped it: credits, plan, rate limit, size, a file. | Later |
94
+ | `unavailable` | briq could not take the call. Nothing was applied. | Yes |
95
+ | `invalid_argument` | The call is wrong. | No |
96
+ | `provider` | A refusal that fits no other code. | Maybe |
97
+ | `outcome_unknown` | The call was sent. Its result is not known. | Check first |
98
+ | `timeout` | A deadline of the client ended the call. | Maybe |
99
+ | `unsupported` | This API or image cannot do it. | No |
100
+ | `aborted` | The command was stopped by someone else. | No |
101
+
102
+ `quota` also covers HTTP 413 and the API's `plan_limit` refusals: trying again changes nothing until the limit does. A file over 100 MiB is `quota`, from `read_file` and `write_file`.
103
+
104
+ Also on the error: `status`, `api_code`, `request_id` (quote it when you ask for help), `retry_after`, `details`, `idempotency_key` and `sandbox_id`. `details` holds codes, ids and numbers (`{"limit": "size", "plan": "hobby"}`); the text a provider gave is never kept.
105
+
106
+ A command and its directory may total 8,192 bytes. A longer one raises `invalid_argument` before anything is sent: write the script with `write_file` and run that file.
107
+
108
+ The client never repeats a call whose effect it cannot see. When a command's end is not known, or a sandbox's creation got no answer, it raises `outcome_unknown`. To retry a creation safely, pass the same `idempotency_key` again: briq returns the first sandbox. Without a key of your own the client makes one, and `error.idempotency_key` is that key.
109
+
110
+ ```python
111
+ try:
112
+ sandbox = briq.create_sandbox()
113
+ except BriqError as error:
114
+ if error.code == "outcome_unknown" and error.idempotency_key:
115
+ sandbox = briq.create_sandbox(idempotency_key=error.idempotency_key)
116
+ else:
117
+ raise
118
+ ```
119
+
120
+ If creating fails after the sandbox exists (it did not start in time, or the call was cancelled or interrupted), the client destroys it and raises the original error. When it cannot destroy it, the error is `outcome_unknown` with `error.sandbox_id`: call `briq.sandbox(error.sandbox_id).destroy()`. The cause of the failure is `error.__cause__`.
121
+
122
+ ### Cancel a call
123
+
124
+ A cancelled task (`asyncio.wait_for` timing out, `task.cancel()`) or a `KeyboardInterrupt` ends a call cleanly. A sandbox made by the call is destroyed, even though the cancel is running, and a command started with `on_output` is stopped. The cancel or the interrupt is raised again as it was; a cleanup that fails never replaces it. A cancel that arrives while the create request is still in flight cannot be undone, because the id is not known yet: pass your own `idempotency_key` and repeat the call to get the sandbox.
125
+
126
+ ## License
127
+
128
+ MIT
@@ -0,0 +1,60 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "briq-cloud"
7
+ dynamic = ["version"]
8
+ description = "The Python client for briq: run sandboxes for your agents, run commands and move files."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ keywords = ["briq", "sandbox", "agents", "microvm", "api-client"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Programming Language :: Python :: 3.14",
23
+ "Typing :: Typed",
24
+ ]
25
+ dependencies = ["httpx>=0.27,<1"]
26
+
27
+ [project.urls]
28
+ Homepage = "https://briq.cloud"
29
+ Repository = "https://github.com/briq-cloud/briq-python"
30
+
31
+ [dependency-groups]
32
+ dev = ["pytest>=8", "mypy>=1.11", "ruff>=0.6"]
33
+
34
+ [tool.hatch.version]
35
+ path = "src/briq_cloud/_version.py"
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["src/briq_cloud"]
39
+
40
+ [tool.hatch.build.targets.sdist]
41
+ # What a consumer needs: the tests, their snapshot of the API and the dev scripts stay in the repo.
42
+ include = ["src", "README.md", "LICENSE", "pyproject.toml"]
43
+
44
+ [tool.pytest.ini_options]
45
+ testpaths = ["tests"]
46
+ addopts = "-p no:cacheprovider"
47
+
48
+ [tool.ruff]
49
+ line-length = 100
50
+ target-version = "py310"
51
+ src = ["src", "tests"]
52
+
53
+ [tool.ruff.lint]
54
+ select = ["E", "F", "W", "I", "UP", "B", "SIM", "RUF", "C4", "PT"]
55
+ ignore = ["PT018", "PT012"] # a test may assert two facts at once
56
+
57
+ [tool.mypy]
58
+ strict = true
59
+ python_version = "3.10"
60
+ files = ["src", "tests"]
@@ -0,0 +1,21 @@
1
+ """Python client for briq: sandboxes you create, run commands in, and read files from."""
2
+
3
+ from ._async import AsyncBriq, AsyncSandbox
4
+ from ._errors import BriqError, Code
5
+ from ._models import ExecResult, FileInfo, OutputChunk, SandboxInfo
6
+ from ._sync import Briq, Sandbox
7
+ from ._version import __version__
8
+
9
+ __all__ = [
10
+ "AsyncBriq",
11
+ "AsyncSandbox",
12
+ "Briq",
13
+ "BriqError",
14
+ "Code",
15
+ "ExecResult",
16
+ "FileInfo",
17
+ "OutputChunk",
18
+ "Sandbox",
19
+ "SandboxInfo",
20
+ "__version__",
21
+ ]