container-pool 0.1.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.
- container_pool-0.1.0/.github/workflows/ci.yml +22 -0
- container_pool-0.1.0/.github/workflows/claude.yml +45 -0
- container_pool-0.1.0/.github/workflows/publish.yml +42 -0
- container_pool-0.1.0/.gitignore +207 -0
- container_pool-0.1.0/LICENSE +21 -0
- container_pool-0.1.0/PKG-INFO +212 -0
- container_pool-0.1.0/README.md +163 -0
- container_pool-0.1.0/pyproject.toml +45 -0
- container_pool-0.1.0/src/container_pool/__init__.py +51 -0
- container_pool-0.1.0/src/container_pool/_backend.py +92 -0
- container_pool-0.1.0/src/container_pool/_container.py +87 -0
- container_pool-0.1.0/src/container_pool/_exceptions.py +46 -0
- container_pool-0.1.0/src/container_pool/_pool.py +220 -0
- container_pool-0.1.0/src/container_pool/_retry.py +53 -0
- container_pool-0.1.0/src/container_pool/_tracker.py +56 -0
- container_pool-0.1.0/src/container_pool/_types.py +36 -0
- container_pool-0.1.0/src/container_pool/backends/__init__.py +4 -0
- container_pool-0.1.0/src/container_pool/backends/openai.py +158 -0
- container_pool-0.1.0/tests/__init__.py +0 -0
- container_pool-0.1.0/tests/backends/__init__.py +0 -0
- container_pool-0.1.0/tests/backends/test_openai_backend.py +176 -0
- container_pool-0.1.0/tests/conftest.py +43 -0
- container_pool-0.1.0/tests/test_container.py +60 -0
- container_pool-0.1.0/tests/test_pool.py +256 -0
- container_pool-0.1.0/tests/test_retry.py +71 -0
- container_pool-0.1.0/tests/test_tracker.py +74 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.13"
|
|
17
|
+
|
|
18
|
+
- name: Install dependencies
|
|
19
|
+
run: pip install -e ".[openai,dev]"
|
|
20
|
+
|
|
21
|
+
- name: Run tests
|
|
22
|
+
run: pytest tests/ -v
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: Claude Code
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issue_comment:
|
|
5
|
+
types: [created]
|
|
6
|
+
pull_request_review_comment:
|
|
7
|
+
types: [created]
|
|
8
|
+
pull_request:
|
|
9
|
+
types: [opened, synchronize]
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write
|
|
13
|
+
pull-requests: write
|
|
14
|
+
issues: write
|
|
15
|
+
id-token: write
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
# Auto-review every PR when opened or updated
|
|
19
|
+
review:
|
|
20
|
+
if: github.event_name == 'pull_request'
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: anthropics/claude-code-action@v1
|
|
24
|
+
with:
|
|
25
|
+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
26
|
+
prompt: |
|
|
27
|
+
Review this pull request. Check for:
|
|
28
|
+
- Correctness and edge cases
|
|
29
|
+
- Consistency with the existing codebase patterns (provider-agnostic backend interface, asyncio pool mechanics)
|
|
30
|
+
- Missing or inadequate test coverage
|
|
31
|
+
- Any issues with the public API surface in __init__.py
|
|
32
|
+
Be concise. Summarize findings as inline comments and a short summary.
|
|
33
|
+
claude_args: "--max-turns 5"
|
|
34
|
+
|
|
35
|
+
# Respond to @claude mentions in issue/PR comments
|
|
36
|
+
assist:
|
|
37
|
+
if: |
|
|
38
|
+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
|
39
|
+
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude'))
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
steps:
|
|
42
|
+
- uses: anthropics/claude-code-action@v1
|
|
43
|
+
with:
|
|
44
|
+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
45
|
+
claude_args: "--max-turns 10"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
|
|
13
|
+
- uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: "3.13"
|
|
16
|
+
|
|
17
|
+
- name: Install dependencies
|
|
18
|
+
run: pip install -e ".[openai,dev]"
|
|
19
|
+
|
|
20
|
+
- name: Run tests
|
|
21
|
+
run: pytest tests/ -v
|
|
22
|
+
|
|
23
|
+
publish:
|
|
24
|
+
needs: test
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
environment: pypi
|
|
27
|
+
permissions:
|
|
28
|
+
id-token: write # for trusted publishing (no token needed if configured on PyPI)
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v4
|
|
31
|
+
|
|
32
|
+
- uses: actions/setup-python@v5
|
|
33
|
+
with:
|
|
34
|
+
python-version: "3.13"
|
|
35
|
+
|
|
36
|
+
- name: Build package
|
|
37
|
+
run: |
|
|
38
|
+
pip install build
|
|
39
|
+
python -m build
|
|
40
|
+
|
|
41
|
+
- name: Publish to PyPI
|
|
42
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
#poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
#pdm.lock
|
|
116
|
+
#pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
#pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# SageMath parsed files
|
|
135
|
+
*.sage.py
|
|
136
|
+
|
|
137
|
+
# Environments
|
|
138
|
+
.env
|
|
139
|
+
.envrc
|
|
140
|
+
.venv
|
|
141
|
+
env/
|
|
142
|
+
venv/
|
|
143
|
+
ENV/
|
|
144
|
+
env.bak/
|
|
145
|
+
venv.bak/
|
|
146
|
+
|
|
147
|
+
# Spyder project settings
|
|
148
|
+
.spyderproject
|
|
149
|
+
.spyproject
|
|
150
|
+
|
|
151
|
+
# Rope project settings
|
|
152
|
+
.ropeproject
|
|
153
|
+
|
|
154
|
+
# mkdocs documentation
|
|
155
|
+
/site
|
|
156
|
+
|
|
157
|
+
# mypy
|
|
158
|
+
.mypy_cache/
|
|
159
|
+
.dmypy.json
|
|
160
|
+
dmypy.json
|
|
161
|
+
|
|
162
|
+
# Pyre type checker
|
|
163
|
+
.pyre/
|
|
164
|
+
|
|
165
|
+
# pytype static type analyzer
|
|
166
|
+
.pytype/
|
|
167
|
+
|
|
168
|
+
# Cython debug symbols
|
|
169
|
+
cython_debug/
|
|
170
|
+
|
|
171
|
+
# PyCharm
|
|
172
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
173
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
174
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
175
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
176
|
+
#.idea/
|
|
177
|
+
|
|
178
|
+
# Abstra
|
|
179
|
+
# Abstra is an AI-powered process automation framework.
|
|
180
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
181
|
+
# Learn more at https://abstra.io/docs
|
|
182
|
+
.abstra/
|
|
183
|
+
|
|
184
|
+
# Visual Studio Code
|
|
185
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
186
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
188
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
189
|
+
# .vscode/
|
|
190
|
+
|
|
191
|
+
# Ruff stuff:
|
|
192
|
+
.ruff_cache/
|
|
193
|
+
|
|
194
|
+
# PyPI configuration file
|
|
195
|
+
.pypirc
|
|
196
|
+
|
|
197
|
+
# Cursor
|
|
198
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
199
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
200
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
201
|
+
.cursorignore
|
|
202
|
+
.cursorindexingignore
|
|
203
|
+
|
|
204
|
+
# Marimo
|
|
205
|
+
marimo/_static/
|
|
206
|
+
marimo/_lsp/
|
|
207
|
+
__marimo__/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 aayushdwids
|
|
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,212 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: container-pool
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Provider-agnostic async container pool with expiry recovery and per-request file tracking
|
|
5
|
+
Project-URL: Repository, https://github.com/aayushgzip/container-pool
|
|
6
|
+
Project-URL: Issues, https://github.com/aayushgzip/container-pool/issues
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2026 aayushdwids
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Keywords: async,asyncio,code-interpreter,container,openai,pool
|
|
30
|
+
Classifier: Development Status :: 4 - Beta
|
|
31
|
+
Classifier: Framework :: AsyncIO
|
|
32
|
+
Classifier: Intended Audience :: Developers
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
38
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
39
|
+
Requires-Python: >=3.11
|
|
40
|
+
Provides-Extra: dev
|
|
41
|
+
Requires-Dist: build; extra == 'dev'
|
|
42
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
43
|
+
Requires-Dist: pytest-mock>=3.12; extra == 'dev'
|
|
44
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
45
|
+
Requires-Dist: twine; extra == 'dev'
|
|
46
|
+
Provides-Extra: openai
|
|
47
|
+
Requires-Dist: openai>=1.0.0; extra == 'openai'
|
|
48
|
+
Description-Content-Type: text/markdown
|
|
49
|
+
|
|
50
|
+
# container-pool
|
|
51
|
+
|
|
52
|
+
Production-grade async container pool for Python. Handles container lifecycle, reuse, concurrency, and automatic recovery from expiry — so you don't have to.
|
|
53
|
+
|
|
54
|
+
Built for OpenAI's Code Interpreter, but designed to work with **any sandboxed container runtime** via a pluggable backend interface.
|
|
55
|
+
|
|
56
|
+
## The Problem
|
|
57
|
+
|
|
58
|
+
When running sandboxed containers behind a multi-user backend, you hit problems no provider solves for you:
|
|
59
|
+
|
|
60
|
+
- **Containers expire silently** after 20 minutes of inactivity. Your next request fails with a 404.
|
|
61
|
+
- **No built-in pooling.** Every request creates a new container (~2-3s overhead).
|
|
62
|
+
- **No concurrency management.** Two users hitting your API simultaneously? You're on your own.
|
|
63
|
+
- **File cleanup is your problem.** Leaked files accumulate and you eat the storage cost.
|
|
64
|
+
|
|
65
|
+
`container-pool` is the infrastructure layer that handles all of this.
|
|
66
|
+
|
|
67
|
+
## What This Does
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
Request A ──→ acquire() ──→ [Container 1] ──→ release() ──→ back to pool
|
|
71
|
+
Request B ──→ acquire() ──→ [Container 2] ──→ release() ──→ back to pool
|
|
72
|
+
Request C ──→ acquire() ──→ (pool full, blocks until release) ──→ ...
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- **FIFO pool** with configurable size, blocking acquisition with timeout when exhausted
|
|
76
|
+
- **Automatic expiry recovery** — detects expired containers (404, status=expired) and transparently recreates them
|
|
77
|
+
- **Per-request file tracking** with cleanup, so containers stay clean between users
|
|
78
|
+
- **Retry with exponential backoff** on container creation failures
|
|
79
|
+
- **Graceful shutdown** that destroys all containers on exit
|
|
80
|
+
- **Provider-agnostic** — implement `BaseContainerBackend` to support any runtime
|
|
81
|
+
|
|
82
|
+
## Installation
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install container-pool # core only
|
|
86
|
+
pip install "container-pool[openai]" # with OpenAI backend
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Usage
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from openai import AsyncOpenAI
|
|
93
|
+
from container_pool import ContainerPool, RequestFileTracker
|
|
94
|
+
from container_pool.backends.openai import OpenAIContainerBackend
|
|
95
|
+
|
|
96
|
+
client = AsyncOpenAI()
|
|
97
|
+
backend = OpenAIContainerBackend(client)
|
|
98
|
+
|
|
99
|
+
pool = ContainerPool(
|
|
100
|
+
backend,
|
|
101
|
+
max_pool_size=5,
|
|
102
|
+
acquire_timeout=30.0,
|
|
103
|
+
container_name="my-pool",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Acquire, use, release
|
|
107
|
+
container = await pool.acquire()
|
|
108
|
+
try:
|
|
109
|
+
tracker = RequestFileTracker(container)
|
|
110
|
+
uploaded = await tracker.upload_file("/tmp/data.csv")
|
|
111
|
+
# ... run code interpreter with container.container_id ...
|
|
112
|
+
files = await container.list_output_files("/mnt/data/")
|
|
113
|
+
results = await container.download_files(files, "/tmp/output")
|
|
114
|
+
finally:
|
|
115
|
+
await tracker.cleanup() # delete uploaded files
|
|
116
|
+
await pool.release(container) # return to pool
|
|
117
|
+
|
|
118
|
+
# On app shutdown
|
|
119
|
+
await pool.shutdown()
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Custom Backends
|
|
123
|
+
|
|
124
|
+
Implement `BaseContainerBackend` to plug in any container runtime:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from container_pool import BaseContainerBackend, ContainerInfo, UploadedFile
|
|
128
|
+
|
|
129
|
+
class MyBackend(BaseContainerBackend):
|
|
130
|
+
async def create_container(self, name: str) -> ContainerInfo: ...
|
|
131
|
+
async def get_container(self, container_id: str) -> ContainerInfo: ...
|
|
132
|
+
async def destroy_container(self, container_id: str) -> None: ...
|
|
133
|
+
async def upload_file(self, container_id: str, local_path: str) -> UploadedFile: ...
|
|
134
|
+
async def download_file_content(self, container_id: str, file_id: str) -> bytes: ...
|
|
135
|
+
async def download_file_to_disk(self, container_id: str, file_id: str, local_path: str) -> int: ...
|
|
136
|
+
async def delete_file(self, container_id: str, file_id: str) -> None: ...
|
|
137
|
+
async def list_files(self, container_id: str, path_prefix: str = "") -> dict[str, str]: ...
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## How It Works
|
|
141
|
+
|
|
142
|
+
### Acquire Flow
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
acquire()
|
|
146
|
+
├─ Queue has available container? → validate it's alive → return
|
|
147
|
+
├─ Pool below max size? → create new container → return
|
|
148
|
+
└─ Pool exhausted? → block until someone calls release() (with timeout)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Expiry Recovery
|
|
152
|
+
|
|
153
|
+
`container-pool` handles silent expiry transparently — callers always get a live container:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
validate_or_recreate(container)
|
|
157
|
+
├─ active status → use it
|
|
158
|
+
├─ expired status → recreate
|
|
159
|
+
├─ 404 → recreate
|
|
160
|
+
└─ connection error → recreate
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Performance
|
|
164
|
+
|
|
165
|
+
| Operation | Latency |
|
|
166
|
+
|---|---|
|
|
167
|
+
| Warm acquire | <100ms |
|
|
168
|
+
| Cold acquire | ~2-3s (container creation) |
|
|
169
|
+
| Pool exhausted | Blocks up to `acquire_timeout` |
|
|
170
|
+
| Expiry recovery | ~2-3s (transparent recreation) |
|
|
171
|
+
|
|
172
|
+
## Configuration
|
|
173
|
+
|
|
174
|
+
| Parameter | Description |
|
|
175
|
+
|---|---|
|
|
176
|
+
| `max_pool_size` | Max containers in pool (1–50) |
|
|
177
|
+
| `acquire_timeout` | Seconds to wait when pool is exhausted |
|
|
178
|
+
| `container_name` | Name prefix for created containers |
|
|
179
|
+
| `creation_max_attempts` | Retry attempts on creation failure (default: 3) |
|
|
180
|
+
| `creation_base_delay` | Base delay for exponential backoff in seconds (default: 1.0) |
|
|
181
|
+
|
|
182
|
+
## Roadmap
|
|
183
|
+
|
|
184
|
+
### v1 (current)
|
|
185
|
+
- [x] FIFO pool with `asyncio.Queue`
|
|
186
|
+
- [x] Automatic expiry detection and recovery
|
|
187
|
+
- [x] Per-request file tracking and cleanup
|
|
188
|
+
- [x] Retry with exponential backoff
|
|
189
|
+
- [x] Graceful shutdown
|
|
190
|
+
- [x] Pluggable backend interface
|
|
191
|
+
- [x] OpenAI Code Interpreter backend
|
|
192
|
+
|
|
193
|
+
### v2
|
|
194
|
+
- [ ] **Pool pre-warming** — create containers at startup to eliminate cold-start latency
|
|
195
|
+
- [ ] **Background keep-alive** — periodic pings to prevent idle expiry
|
|
196
|
+
- [ ] **Distributed state** — Redis/PostgreSQL backend for multi-node deployments
|
|
197
|
+
- [ ] **Observability** — metrics for pool utilization, acquire wait times, expiry rate
|
|
198
|
+
- [ ] **Pool strategies** — LRU, priority-based in addition to FIFO
|
|
199
|
+
|
|
200
|
+
## Contributing
|
|
201
|
+
|
|
202
|
+
Contributions welcome. Please open an issue first to discuss what you'd like to change.
|
|
203
|
+
|
|
204
|
+
## Why This Exists
|
|
205
|
+
|
|
206
|
+
Built after hitting every one of these problems while running Code Interpreter in a multi-user production backend. OpenAI's docs hand you a container ID and say good luck — this is the "good luck" part.
|
|
207
|
+
|
|
208
|
+
— [@aayushgzip](https://github.com/aayushgzip)
|
|
209
|
+
|
|
210
|
+
## License
|
|
211
|
+
|
|
212
|
+
[MIT](LICENSE)
|