Skip to content

Commit 2bc8a7e

Browse files
committed
init
0 parents  commit 2bc8a7e

File tree

9 files changed

+345
-0
lines changed

9 files changed

+345
-0
lines changed

.github/workflows/publish.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Publish to PyPI
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
jobs:
8+
deploy:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v4
12+
- name: Set up Python
13+
uses: actions/setup-python@v4
14+
with:
15+
python-version: '3.x'
16+
- name: Install dependencies
17+
run: |
18+
python -m pip install --upgrade pip
19+
pip install build twine
20+
- name: Build and publish
21+
env:
22+
TWINE_USERNAME: __token__
23+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
24+
run: |
25+
python -m build
26+
twine upload dist/*

.gitignore

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Python
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
*.so
6+
.Python
7+
build/
8+
develop-eggs/
9+
dist/
10+
downloads/
11+
eggs/
12+
.eggs/
13+
lib/
14+
lib64/
15+
parts/
16+
sdist/
17+
var/
18+
wheels/
19+
*.egg-info/
20+
.installed.cfg
21+
*.egg
22+
23+
# Virtual Environment
24+
venv/
25+
env/
26+
ENV/
27+
.env/
28+
.venv/
29+
30+
# IDE
31+
.idea/
32+
.vscode/
33+
*.swp
34+
*.swo
35+
36+
# Test coverage
37+
.coverage
38+
htmlcov/
39+
coverage.xml
40+
.pytest_cache/
41+
42+
# mypy
43+
.mypy_cache/
44+
.dmypy.json
45+
dmypy.json
46+
47+
# Distribution
48+
dist/
49+
build/

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Inference Shell Inc.
4+
5+
This software is licensed for use only in conjunction with inference.sh services and platforms.
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software to use it with inference.sh services, subject to the following conditions:
9+
10+
1. The software may only be used in conjunction with inference.sh services and platforms.
11+
2. Redistribution and modification are permitted only for use with inference.sh services.
12+
3. This copyright notice and permission notice shall be included in all copies or
13+
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.

README.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# inference.sh CLI
2+
3+
Helper package for inference.sh Python applications.
4+
5+
## Installation
6+
7+
```bash
8+
pip install infsh
9+
```
10+
11+
## File Handling
12+
13+
The `File` class provides a standardized way to handle files in the inference.sh ecosystem:
14+
15+
```python
16+
from infsh import File
17+
18+
# Basic file creation
19+
file = File(path="/path/to/file.png")
20+
21+
# File with explicit metadata
22+
file = File(
23+
path="/path/to/file.png",
24+
mime_type="image/png",
25+
filename="custom_name.png",
26+
size=1024 # in bytes
27+
)
28+
29+
# Create from path (automatically populates metadata)
30+
file = File.from_path("/path/to/file.png")
31+
32+
# Check if file exists
33+
exists = file.exists()
34+
35+
# Access file metadata
36+
print(file.mime_type) # automatically detected if not specified
37+
print(file.size) # file size in bytes
38+
print(file.filename) # basename of the file
39+
40+
# Refresh metadata (useful if file has changed)
41+
file.refresh_metadata()
42+
```
43+
44+
The `File` class automatically handles:
45+
- MIME type detection
46+
- File size calculation
47+
- Filename extraction from path
48+
- File existence checking
49+
50+
## Creating an App
51+
52+
To create an inference app, inherit from `BaseApp` and define your input/output types:
53+
54+
```python
55+
from infsh import BaseApp, BaseAppInput, BaseAppOutput, File
56+
57+
class AppInput(BaseAppInput):
58+
image: str # URL or file path to image
59+
mask: str # URL or file path to mask
60+
61+
class AppOutput(BaseAppOutput):
62+
image: File
63+
64+
class MyApp(BaseApp):
65+
async def setup(self):
66+
# Initialize your model here
67+
pass
68+
69+
async def run(self, app_input: AppInput) -> AppOutput:
70+
# Process input and return output
71+
result_path = "/tmp/result.png"
72+
return AppOutput(image=File(path=result_path))
73+
74+
async def unload(self):
75+
# Clean up resources
76+
pass
77+
```
78+
79+
The app lifecycle has three main methods:
80+
- `setup()`: Called when the app starts, use it to initialize models
81+
- `run()`: Called for each inference request
82+
- `unload()`: Called when shutting down, use it to free resources

pyproject.toml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[build-system]
2+
requires = ["setuptools>=61.0"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "inferencesh"
7+
version = "0.1.2"
8+
description = "inference.sh Python SDK"
9+
authors = [
10+
{name = "Inference Shell Inc.", email = "hello@inference.sh"},
11+
]
12+
readme = "README.md"
13+
requires-python = ">=3.7"
14+
classifiers = [
15+
"Programming Language :: Python :: 3",
16+
"License :: OSI Approved :: MIT License",
17+
"Operating System :: OS Independent",
18+
]
19+
dependencies = [
20+
"pydantic>=2.0.0",
21+
]
22+
23+
[project.urls]
24+
"Homepage" = "https://github.com/inference-sh/sdk"
25+
"Bug Tracker" = "https://github.com/inference-sh/sdk/issues"
26+
27+
[project.scripts]
28+
infsh = "infsh.__main__:cli"
29+
30+
[tool.pytest]
31+
testpaths = ["tests"]
32+
python_files = ["test_*.py"]
33+
addopts = "-v"
34+
35+
[tool.flake8]
36+
max-line-length = 100
37+
exclude = [".git", "__pycache__", "build", "dist"]

setup.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from setuptools import setup, find_packages
2+
3+
setup(
4+
name="inferencesh",
5+
version="0.1.2",
6+
description="inference.sh Python SDK",
7+
author="Inference Shell Inc.",
8+
author_email="hello@inference.sh",
9+
packages=find_packages(where="src"),
10+
package_dir={"": "src"},
11+
install_requires=[
12+
"pydantic>=2.0.0",
13+
],
14+
python_requires=">=3.7",
15+
classifiers=[
16+
"Programming Language :: Python :: 3",
17+
"License :: OSI Approved :: MIT License",
18+
"Operating System :: OS Independent",
19+
],
20+
)

src/inferencesh/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""inference.sh Python SDK package."""
2+
3+
__version__ = "0.1.2"
4+
5+
from .sdk import BaseApp, BaseAppInput, BaseAppOutput, File

src/inferencesh/sdk.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from typing import Optional, Union
2+
from pydantic import BaseModel, ConfigDict
3+
import mimetypes
4+
import os
5+
6+
class BaseAppInput(BaseModel):
7+
pass
8+
9+
class BaseAppOutput(BaseModel):
10+
pass
11+
12+
class BaseApp(BaseModel):
13+
model_config = ConfigDict(arbitrary_types_allowed=True)
14+
async def setup(self):
15+
pass
16+
17+
async def run(self, app_input: BaseAppInput) -> BaseAppOutput:
18+
raise NotImplementedError("run method must be implemented")
19+
20+
async def unload(self):
21+
pass
22+
23+
24+
class File(BaseModel):
25+
"""A class representing a file in the inference.sh ecosystem."""
26+
path: str # Absolute path to the file
27+
mime_type: Optional[str] = None # MIME type of the file
28+
size: Optional[int] = None # File size in bytes
29+
filename: Optional[str] = None # Original filename if available
30+
31+
def __init__(self, **data):
32+
super().__init__(**data)
33+
if not os.path.isabs(self.path):
34+
self.path = os.path.abspath(self.path)
35+
self._populate_metadata()
36+
37+
def _populate_metadata(self) -> None:
38+
"""Populate file metadata from the path if it exists."""
39+
if os.path.exists(self.path):
40+
if not self.mime_type:
41+
self.mime_type = self._guess_mime_type()
42+
if not self.size:
43+
self.size = self._get_file_size()
44+
if not self.filename:
45+
self.filename = self._get_filename()
46+
47+
@classmethod
48+
def from_path(cls, path: Union[str, os.PathLike]) -> 'File':
49+
"""Create a File instance from a file path."""
50+
return cls(path=str(path))
51+
52+
def _guess_mime_type(self) -> Optional[str]:
53+
"""Guess the MIME type of the file."""
54+
return mimetypes.guess_type(self.path)[0]
55+
56+
def _get_file_size(self) -> int:
57+
"""Get the size of the file in bytes."""
58+
return os.path.getsize(self.path)
59+
60+
def _get_filename(self) -> str:
61+
"""Get the base filename from the path."""
62+
return os.path.basename(self.path)
63+
64+
def exists(self) -> bool:
65+
"""Check if the file exists."""
66+
return os.path.exists(self.path)
67+
68+
def refresh_metadata(self) -> None:
69+
"""Refresh all metadata from the file."""
70+
self._populate_metadata()
71+
72+
class Config:
73+
"""Pydantic config"""
74+
arbitrary_types_allowed = True

tests/test_sdk.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import os
2+
import pytest
3+
from inferencesh import BaseApp, BaseAppInput, BaseAppOutput, File
4+
5+
def test_file_creation():
6+
# Create a temporary file
7+
with open("test.txt", "w") as f:
8+
f.write("test")
9+
10+
file = File(path="test.txt")
11+
assert file.exists()
12+
assert file.size > 0
13+
assert file.mime_type is not None
14+
assert file.filename == "test.txt"
15+
16+
os.remove("test.txt")
17+
18+
def test_base_app():
19+
class TestInput(BaseAppInput):
20+
text: str
21+
22+
class TestOutput(BaseAppOutput):
23+
result: str
24+
25+
class TestApp(BaseApp):
26+
async def run(self, app_input: TestInput) -> TestOutput:
27+
return TestOutput(result=f"Processed: {app_input.text}")
28+
29+
app = TestApp()
30+
with pytest.raises(NotImplementedError):
31+
app.run(TestInput(text="test"))

0 commit comments

Comments
 (0)