bounded_subprocess 2.4.0__tar.gz → 2.6.0__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.
Files changed (40) hide show
  1. bounded_subprocess-2.6.0/.claude/settings.local.json +8 -0
  2. bounded_subprocess-2.6.0/AGENTS.md +10 -0
  3. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/PKG-INFO +1 -1
  4. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/pyproject.toml +1 -1
  5. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/src/bounded_subprocess/bounded_subprocess_async.py +31 -2
  6. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/test/test_async.py +74 -0
  7. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/uv.lock +2 -2
  8. bounded_subprocess-2.4.0/.git +0 -1
  9. bounded_subprocess-2.4.0/AGENTS.md +0 -5
  10. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/.github/workflows/docs.yaml +0 -0
  11. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/.github/workflows/test.yml +0 -0
  12. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/.gitignore +0 -0
  13. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/LICENSE.txt +0 -0
  14. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/Makefile +0 -0
  15. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/README.md +0 -0
  16. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/cspell.config.yaml +0 -0
  17. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/docs/index.md +0 -0
  18. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/mkdocs.yaml +0 -0
  19. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/src/bounded_subprocess/__init__.py +0 -0
  20. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/src/bounded_subprocess/bounded_subprocess.py +0 -0
  21. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/src/bounded_subprocess/interactive.py +0 -0
  22. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/src/bounded_subprocess/interactive_async.py +0 -0
  23. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/src/bounded_subprocess/util.py +0 -0
  24. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/test/__init__.py +0 -0
  25. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/test/evil_programs/block_on_inputs.py +0 -0
  26. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/test/evil_programs/close_outputs.py +0 -0
  27. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/test/evil_programs/dies_shortly_after_launch.py +0 -0
  28. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/test/evil_programs/dies_while_writing.py +0 -0
  29. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/test/evil_programs/does_not_read.py +0 -0
  30. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/test/evil_programs/echo_stdin.py +0 -0
  31. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/test/evil_programs/fork_bomb.py +0 -0
  32. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/test/evil_programs/fork_once.py +0 -0
  33. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/test/evil_programs/long_stdout.py +0 -0
  34. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/test/evil_programs/read_one_line.py +0 -0
  35. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/test/evil_programs/sleep_forever.py +0 -0
  36. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/test/evil_programs/unbounded_output.py +0 -0
  37. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/test/evil_programs/write_forever_but_no_newline.py +0 -0
  38. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/test/module_test.py +0 -0
  39. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/test/test_interactive.py +0 -0
  40. {bounded_subprocess-2.4.0 → bounded_subprocess-2.6.0}/test/test_interactive_async.py +0 -0
@@ -0,0 +1,8 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(python -m pytest:*)",
5
+ "Bash(uv run pytest:*)"
6
+ ]
7
+ }
8
+ }
@@ -0,0 +1,10 @@
1
+ Use `uv run`to run and not `python`.
2
+
3
+ ## Publishing Process
4
+
5
+ Increment the version number as appropriate in pyproject.toml. Delete old
6
+ builds from dist/. Run uv sync to update the lock file. Commit the changes,
7
+ which should have just changes to pyproject.taml and uv.lock. Run `uv build`
8
+ and then `uv publish`. In the interactive prompt, you MUST enter __token__
9
+ for the username. I know it says that, but I don't read what's on the screen.
10
+ The password is in the keychain.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bounded_subprocess
3
- Version: 2.4.0
3
+ Version: 2.6.0
4
4
  Summary: A library to facilitate running subprocesses that may misbehave.
5
5
  Project-URL: Homepage, https://github.com/arjunguha/bounded_subprocess
6
6
  Project-URL: Bug Tracker, https://github.com/arjunguha/bounded_subprocess
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bounded_subprocess"
3
- version = "2.4.0"
3
+ version = "2.6.0"
4
4
  authors = [
5
5
  { name="Arjun Guha" },
6
6
  { name="Ming-Ho Yee" },
@@ -163,6 +163,9 @@ async def podman_run(
163
163
  env=None,
164
164
  stdin_data: Optional[str] = None,
165
165
  stdin_write_timeout: Optional[int] = None,
166
+ volumes: List[str] = [],
167
+ cwd: Optional[str] = None,
168
+ memory_limit_mb: Optional[int] = None,
166
169
  ) -> Result:
167
170
  """
168
171
  Run a subprocess in a podman container asynchronously with bounded stdout/stderr capture.
@@ -172,19 +175,33 @@ async def podman_run(
172
175
  the same as `run`, except for an additional `image` parameter to specify the
173
176
  container image to use.
174
177
 
178
+ Args:
179
+ args: Command arguments to run in the container.
180
+ image: Container image to use.
181
+ timeout_seconds: Maximum time to wait for the process to complete.
182
+ max_output_size: Maximum size in bytes for stdout/stderr capture.
183
+ env: Optional dictionary of environment variables.
184
+ stdin_data: Optional string data to write to stdin.
185
+ stdin_write_timeout: Optional timeout for writing stdin data.
186
+ volumes: Optional list of volume mount specifications (e.g., ["/host/path:/container/path"]).
187
+ cwd: Optional working directory path inside the container.
188
+ memory_limit_mb: Optional memory limit in megabytes for the container.
189
+
175
190
  Example:
176
191
 
177
192
  ```python
178
193
  import asyncio
179
- from bounded_subprocess.bounded_subprocess_async import run_container
194
+ from bounded_subprocess.bounded_subprocess_async import podman_run
180
195
 
181
196
  async def main():
182
- result = await run_container(
197
+ result = await podman_run(
183
198
  ["cat"],
184
199
  image="alpine:latest",
185
200
  timeout_seconds=5,
186
201
  max_output_size=1024,
187
202
  stdin_data="hello\n",
203
+ volumes=["/host/data:/container/data"],
204
+ cwd="/container/data",
188
205
  )
189
206
  print(result.exit_code)
190
207
  print(result.stdout.strip())
@@ -209,6 +226,18 @@ async def podman_run(
209
226
  for key, value in env.items():
210
227
  podman_args.extend(["-e", f"{key}={value}"])
211
228
 
229
+ # Handle volume mounts
230
+ for volume in volumes:
231
+ podman_args.extend(["-v", volume])
232
+
233
+ # Handle memory limit
234
+ if memory_limit_mb is not None:
235
+ podman_args.extend(["--memory", f"{memory_limit_mb}m", "--memory-swap", f"{memory_limit_mb}m"])
236
+
237
+ # Handle working directory
238
+ if cwd is not None:
239
+ podman_args.extend(["-w", cwd])
240
+
212
241
  podman_args.append(image)
213
242
  podman_args.extend(args)
214
243
 
@@ -171,3 +171,77 @@ async def test_podman_run_unbounded_output():
171
171
  assert result.timeout is True
172
172
  # stderr may contain podman pull messages, which is fine
173
173
  assert len(result.stdout) == 1024
174
+
175
+
176
+ @pytest.mark.asyncio
177
+ async def test_podman_run_volumes():
178
+ """Test podman_run with volume mounts."""
179
+ import tempfile
180
+
181
+ # Create a temporary file with test content
182
+ with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as tmp_file:
183
+ tmp_file.write("test content\n")
184
+ tmp_path = tmp_file.name
185
+
186
+ try:
187
+ # Mount the file and read it from the container
188
+ result = await podman_run(
189
+ ["cat", "/mnt/test.txt"],
190
+ image="alpine:latest",
191
+ timeout_seconds=5,
192
+ max_output_size=1024,
193
+ volumes=[f"{tmp_path}:/mnt/test.txt:ro"],
194
+ )
195
+ assert result.exit_code == 0
196
+ assert result.timeout is False
197
+ assert result.stdout == "test content\n"
198
+ finally:
199
+ # Clean up
200
+ try:
201
+ os.unlink(tmp_path)
202
+ except OSError:
203
+ pass
204
+
205
+
206
+ @pytest.mark.asyncio
207
+ async def test_podman_run_cwd():
208
+ """Test podman_run with working directory."""
209
+ result = await podman_run(
210
+ ["pwd"],
211
+ image="alpine:latest",
212
+ timeout_seconds=5,
213
+ max_output_size=1024,
214
+ cwd="/tmp",
215
+ )
216
+ assert result.exit_code == 0
217
+ assert result.timeout is False
218
+ assert result.stdout.strip() == "/tmp"
219
+
220
+
221
+ @pytest.mark.asyncio
222
+ async def test_podman_run_memory_limit_ok():
223
+ """Test podman_run with memory limit that is sufficient."""
224
+ result = await podman_run(
225
+ ["echo", "hello"],
226
+ image="alpine:latest",
227
+ timeout_seconds=10,
228
+ max_output_size=1024,
229
+ memory_limit_mb=64,
230
+ )
231
+ assert result.exit_code == 0
232
+ assert result.timeout is False
233
+ assert result.stdout.strip() == "hello"
234
+
235
+
236
+ @pytest.mark.asyncio
237
+ async def test_podman_run_memory_limit_oom():
238
+ """Test podman_run where the process exceeds the memory limit."""
239
+ # Allocate ~128MB in a container limited to 32MB
240
+ result = await podman_run(
241
+ ["python3", "-c", "x = bytearray(128 * 1024 * 1024)"],
242
+ image="python:3.12-alpine",
243
+ timeout_seconds=30,
244
+ max_output_size=4096,
245
+ memory_limit_mb=32,
246
+ )
247
+ assert result.exit_code != 0
@@ -1,5 +1,5 @@
1
1
  version = 1
2
- revision = 2
2
+ revision = 3
3
3
  requires-python = ">=3.9"
4
4
  resolution-markers = [
5
5
  "python_full_version >= '3.10'",
@@ -31,7 +31,7 @@ wheels = [
31
31
 
32
32
  [[package]]
33
33
  name = "bounded-subprocess"
34
- version = "2.4.0"
34
+ version = "2.6.0"
35
35
  source = { editable = "." }
36
36
  dependencies = [
37
37
  { name = "typeguard" },
@@ -1 +0,0 @@
1
- gitdir: /media/external0/arjun-nosudo/repos/arjunguha/bounded_subprocess/bare/worktrees/main
@@ -1,5 +0,0 @@
1
- ## Publishing Process
2
-
3
- Increment the version number as appropriate in pyproject.toml. Delete old
4
- builds from dist/. Run uv sync to update the lock file. Commit the changes,
5
- which should have just changes to pyproject.taml and uv.lock.