Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,42 @@
name: Publish to PyPI
name: CI & Publish

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
# Tests gate everything: no version bump, no publish unless these pass.
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install package + test deps (incl. fastSDK)
run: pip install -e ".[test]"

- name: Run tests
run: pytest -v

# Soft checks: report only, never fail the build.
- name: ruff (soft)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's okay, we can start using soft requirements, we move to strict when we have applied this across the repos.

continue-on-error: true
run: ruff check apipod test

- name: mypy (soft)
continue-on-error: true
run: mypy apipod

# Runs only on push to main, and only after tests pass.
publish:
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -18,7 +49,7 @@ jobs:
with:
python-version: "3.12"

- name: Install dependencies
- name: Install build tooling
run: pip install build twine

- name: Bump patch version
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def hello(name: str):
return f"Hello {name}!"

# Built-in media processing — uploads/URLs/base64 are parsed for you
@app.endpoint("/process_image")
@app.endpoint("/process-image")
def process_image(image: ImageFile):
img_array = image.to_np_array()
# ... run your AI model here ...
Expand Down
10 changes: 10 additions & 0 deletions apipod/engine/backend/fastapi/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,16 @@ def sync_wrapper(*args, **kwargs):
if binding is not None:
result = wrap_schema_response(result, binding)
return JobResultFactory._serialize_result(result)

# Python 3.12+ follows __wrapped__ when evaluating inspect.isgeneratorfunction().
# sync_wrapper is never a generator (it returns a JobResult or StreamProducer).
# Without this, FastAPI would mistake a task endpoint wrapping a generator for a
# streaming route and serve the JobResult as iterated key-value NDJSON chunks.
if hasattr(sync_wrapper, "__wrapped__"):
# Pin the signature explicitly so FastAPI can parse parameters
sync_wrapper.__signature__ = inspect.signature(func)
del sync_wrapper.__wrapped__

return sync_wrapper

def _create_task_endpoint_decorator(self, plan: EndpointExecutionPlan):
Expand Down
87 changes: 87 additions & 0 deletions docs/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Development instructions

## 1. Think Before Coding

**Don't assume. Don't hide confusion. Surface tradeoffs.**

Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them. Don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
- Use thinking methods like Elon Musk's first principle reasoning or design-thinking for complex tasks.

## 2. Simplicity First

**Minimum code that solves the problem. Nothing speculative.**
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
- Don't repeat yourself.

Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.

## 3. Surgical Changes

**Touch only what you must. Clean up only your own mess.**

When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.

When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.

The test: Every changed line should trace directly to the user's request.

## 4. Code quality and style
- Write high-quality, developer-friendly and clean-code, use design-patterns (Refactoring Guru) where helpful.
- Include docstrings and comments to explain the why of a non-obvious method or code. No decorative comments.
- Write memory and compute efficient code.
- Write pythonic code and use builtins when it simplifies (generators, list-comprehensions, functools).
- Reduce complexity and boilerplate. Remove unused code.
- State unclear variable names and rename for better readability and clearity.
- Avoid local imports
- Do not write "deprecated" or backward compability code.
- Update the README.md if there's user related important changes. Update TECHNICAL_README.md for architectural, or project maintainer important changes. Keep the .md update brief use technical correct terms.
- Write flake8 formatted code with "flake8.args": [
"--max-line-length=120",
"--ignore=E203,W503,E501,E261,W293"
]
}
- Avoid using direct cmd python calls and pip installs. Work with the local venv if necessary. Always ask if necessary.

## 5. Reason and thinking style
Do thinking and reasoning like a smart caveman.
- Cut all filler, keep technical substance.
- Drop articles (a, an, the), filler (just, really, basically, actually).
- Drop pleasantries (sure, certainly, happy to).
- No hedging. Fragments fine. Short synonyms.
- Technical terms stay exact. Code blocks unchanged.
- Pattern: [thing] [action] [reason]. [next step]

## 6. Public facing texts: Readmes, prints, logs.
- Write as technically brilliant European expert who gives you straight answers, backs claims with data, and genuinely wants you to succeed.
- Ttone spectrum Formality 35% (lean casual) · Humor 25% (dry, never flippant) · Complexity 40% (lean technical) · Confidence 80% (very confident).
- Direct without being cold. Say what you mean, no corporate padding. Warm without being informal. Complete sentences, measured enthusiasm. Committed, not hedging.
- Committed, not hedging. "This works", not "this might work".
- "We" for the platform, "you" for the reader. Reserve "I" for rare authored guides only.
- Developer-first, technical correct terms.
- No em-dashes. The character `—` (U+2014) must not appear in ANYWHERE. Use a period, comma, parentheses, or colon.
- No marketing adverbs like seamlessly, effortlessly, robust, powerful, cutting-edge, automatically, intelligently, revolutionary, game-changing, world-class, best-in-class. E.g. three steps, ~X minutes" beats "easy onboarding".
- No bullet-everything. Bullets only for true enumerations (statuses, regions, tiers).
- Subtle transmit socaity's vision and use emotional-intelligence and marketing techniques for end-user intent based messages.
- Use google docstring format.
- The four brand hues (each tied to an ICP) with Tonal scales (50/300/500/700/900). Apply where makes sense.
| Name | Hex | Meaning / ICP |
| ---------------------------- | ----------- | ------------------------------------------------------------------------- |
| **Neon Green** | `#5A8F00` | Developer. Active system health, engineering precision, technical truth. |
| **Silicon Blue** | `#0A86BF` | SMB CTO. Enterprise stability, secure infrastructure, professional trust. |
| **Creator Violet** | `#7C3AED` | Creator. Generative potential, creative momentum, warmth. |
| **Signal Pink (Rose)** | `#EC4899` | Alert. Warnings, critical status, high-priority attention. |


32 changes: 0 additions & 32 deletions docs/EXAMPLE_DOCKERFILE_AZURE

This file was deleted.

40 changes: 0 additions & 40 deletions docs/EXAMPLE_DOCKERFILE_RUNPOD

This file was deleted.

24 changes: 24 additions & 0 deletions docs/TECHNICAL_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,28 @@ The **stream store** is the pluggable backend that buffers a job's chunks betwee

A client calls `POST /tts` with a JSON body. FastAPI validates it against the rewritten signature and calls the outermost wrapper. The file-handling layer converts any media inputs into media-toolkit objects. If the endpoint is queued, the queue mixin stores the job and returns `{job_id, status: "queued", links}` — the worker thread later executes the real function, feeding `JobProgress` updates into the store. The client (typically fastSDK) polls `/status/{job_id}` until `finished` and receives the result, with any returned `AudioFile` serialized as a `FileModel`. Without a queue the same conversion happens inline and the response returns directly. On RunPod, the identical user function is reached through `handler → _router(path) → file handling → execute`, proving the core principle: the function is written once, the backend decides how it runs.

## Testing and CI

The suite (`test/`) is built so endpoint definitions live apart from test logic, and one helper boots them under any run intent.

```
test/
├── conftest.py # build_service (in-process TestClient), live_service (real subprocess via CLI), config matrix, file paths
├── services/ # reusable services: register(app) callbacks + a runnable entrypoint
│ ├── core_service.py # scalars, custom model, mixed media, JobProgress, file upload
│ ├── schema_service.py # one endpoint per standardized schema, an extended schema, raw/typed mapping CASES
│ └── exec_service.py # runnable service for fastSDK + streaming (predict, echo_image, chat, text, video)
├── files/ # media assets for upload/download tests
├── test_config.py # intent -> backend resolution + /openapi.json loads for every backend
├── test_core.py # core endpoint plumbing (types, model, files, queue lifecycle)
├── test_schemas.py # standardized schema endpoints + response-model normalization
├── test_cli.py # scan / build / simulate produce the right artifacts
└── test_execution.py # fastSDK end-to-end (subprocess) + SSE streaming (in-process)
```

Run `pytest`. Two primitives keep tests DRY: `build_service(register, **config)` yields a `TestClient` for an app built from a `register(app)` callback under any `APIPod(**config)` intent; `live_service(simulate=...)` boots a service as a real subprocess through `apipod --start` / `--simulate` and yields its URL. Backends are parametrized from `FASTAPI_CONFIGS`, so a single test asserts behaviour across development, serverless, dedicated and runpod.

The fastSDK end-to-end tests skip automatically when the installed fastsdk lacks the `connect` API (e.g. a local in-progress build); CI installs one that has it. Pytest config (`pythonpath` in `pyproject.toml`) resolves `import apipod` to the in-repo source and the `conftest`/`services` helpers by name, so no `sys.path` shims are needed and each file is also runnable directly from an IDE via its `__main__` block.

CI (`.github/workflows/publish.yml`) runs the `test` job on every push and pull request (installing `.[test]`, which includes fastSDK). The `publish` job `needs: test`, so the patch version bump and PyPI upload happen only on a push to `main` after tests pass. flake8 and mypy run as soft checks (`continue-on-error`): they surface warnings without gating the build.

29 changes: 29 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,37 @@ apipod = "apipod.cli:main"
runpod = [
"runpod>=1.7.7"
]
test = [
"pytest",
"httpx",
"ruff",
"mypy",
]


[project.urls]
Repository = "https://github.com/SocAIty/apipod"
Homepage = "https://www.socaity.ai"

[tool.pytest.ini_options]
testpaths = ["test"]
# Make `import apipod` resolve to the in-repo source (not an installed copy) and
# let test modules import the shared `conftest` helpers and `services` package by
# name. The sibling fastsdk entry is used in local dev when the repo is checked
# out next to apipod; it is silently ignored in CI where fastsdk comes from pip.
pythonpath = [".", "test", "../fastsdk"]

[tool.ruff]
line-length = 120
exclude = ["venv", "dist", "build", "*.egg-info"]

[tool.ruff.lint]
# Mirror the previous flake8 config: ignore stylistic rules that conflict with
# black/our formatting conventions.
ignore = ["E203", "E261", "E501", "W293"]

[tool.mypy]
# Soft static typing: a warning signal, never a build gate (see CI).
ignore_missing_imports = true
follow_imports = "silent"
warn_unused_ignores = false
Empty file removed test/__init__.py
Empty file.
Loading