[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-79927":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":13,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":14,"stars7d":13,"stars30d":15,"stars90d":14,"forks30d":14,"starsTrendScore":14,"compositeScore":16,"rankGlobal":9,"rankLanguage":9,"license":17,"archived":18,"fork":18,"defaultBranch":19,"hasWiki":20,"hasPages":18,"topics":21,"createdAt":9,"pushedAt":9,"updatedAt":33,"readmeContent":34,"aiSummary":35,"trendingCount":14,"starSnapshotCount":14,"syncStatus":36,"lastSyncTime":37,"discoverSource":38},79927,"stainful","stainlu\u002Fstainful","stainlu","The open-source Stainless — point your existing stainless.yml at it and get the same idiomatic Python SDK. Zero migration. No SaaS.",null,"Python",90,11,1,0,3,3.24,"MIT License",false,"main",true,[22,23,24,25,26,27,28,29,30,31,32],"api-client","codegen","developer-tools","httpx","openapi","openapi-codegen","oss-alternative","pydantic","python","sdk-generator","stainless","2026-06-12 02:03:55","# stainful\n\n**The open-source Stainless.** Generate an idiomatic Python SDK from an OpenAPI\nspec and a `stainless.yml` — open source, runs locally and in CI, no hosted service.\n\n[![License](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Flicense-MIT-3da639?style=flat-square)](LICENSE)\n[![Python](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fpython-3.10%2B-3776ab?style=flat-square&logo=python&logoColor=white)](pyproject.toml)\n[![CI](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Factions\u002Fworkflow\u002Fstatus\u002Fstainlu\u002Fstainful\u002Fci.yml?branch=main&style=flat-square&label=ci)](https:\u002F\u002Fgithub.com\u002Fstainlu\u002Fstainful\u002Factions\u002Fworkflows\u002Fci.yml)\n[![PRs welcome](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FPRs-welcome-7c3aed?style=flat-square)](CONTRIBUTING.md)\n\n\u003Cimg src=\"assets\u002Farchitecture.png\" alt=\"stainful — OpenAPI spec + stainless.yml → resolved IR → idiomatic Python SDK\" width=\"100%\">\n\n---\n\nstainful turns an **OpenAPI 3.x spec** and a **Stainless config** into a Python SDK\nthat reads like it was written by hand — typed models, real error classes,\nretries, auto-pagination, streaming, sync **and** async. It reuses the\n`stainless.yml` format, so if you already have one, you can point stainful at it\nas-is.\n\n> **Migrating from Stainless?** Anthropic acquired Stainless and is winding\n> down the hosted SDK generator. stainful is a drop-in continuation path —\n> see [`docs\u002Fmigrating-from-stainless.md`](docs\u002Fmigrating-from-stainless.md)\n> for the step-by-step (it should be a few minutes).\n\n## Features\n\n- 🧬 **Typed everything** — pydantic v2 models, real discriminated unions from `oneOf`\n- 🔁 **Auto-pagination** — `for item in client.things.list(): ...`\n- 🛡️ **Typed errors** — `except RateLimitError:` instead of checking status codes\n- 🔄 **Resilient** — retries with exponential backoff + jitter, `Retry-After`, idempotency keys\n- 📡 **Streaming** — typed Server-Sent Events, identical surface in sync and async\n- 🎯 **Precise optionality** — `required`, `optional`, and `nullable` stay distinct\n- 🧭 **Domain-shaped clients** — `client.chat.completions.create(...)`, not flat stubs\n- ⚡ **Sync + async** generated from one model\n- 📦 **Self-contained output** — the generated SDK depends only on `httpx` + `pydantic`\n- 📑 **`stainful docs`** — emit a Stainless-style `api.md` from the same inputs (per-resource sections, Methods lists with verb+path, Mintlify-compatible)\n- 🧰 **`stainful mcp`** — emit a [Model Context Protocol](https:\u002F\u002Fmodelcontextprotocol.io\u002F) server (one tool per HTTP method) so Claude \u002F Cline \u002F mcp-cli can call your API as tools\n\n## Quickstart\n\n```bash\npip install stainful\n\n# generate an idiomatic Python SDK from your OpenAPI spec + stainless.yml\nstainful generate --spec openapi.yml --config stainless.yml --out .\u002Fsdk\n\n# OR emit a Stainless-style api.md doc from the same inputs\nstainful docs --spec openapi.yml --config stainless.yml --out .\u002Fapi.md\n\n# OR emit an MCP server inside the generated SDK\nstainful mcp --spec openapi.yml --config stainless.yml --out .\u002Fsdk\u002F\u003Cpkg>\u002Fmcp_server.py\n```\n\nThe generated SDK feels like an official client:\n\n```python\nfrom onebusaway import OnebusawaySDK\n\nclient = OnebusawaySDK(api_key=\"...\")          # or set ONEBUSAWAY_API_KEY\nagency = client.agency.retrieve(\"1\")           # typed, retried, idiomatic\nprint(agency.data.entry.name)\n```\n\nStreaming, async, and typed errors work the way you'd expect:\n\n```python\nimport asyncio\nfrom chat import AsyncChatSDK\nfrom chat import RateLimitError\n\nasync def main():\n    client = AsyncChatSDK(api_key=\"...\")\n    try:\n        stream = await client.chat.completions.create(\n            model=\"m\", messages=[{\"role\": \"user\", \"content\": \"hi\"}], stream=True\n        )\n        async for chunk in stream:\n            print(chunk.delta, end=\"\")\n    except RateLimitError as e:\n        print(\"rate limited:\", e.request_id)\n\nasyncio.run(main())\n```\n\n## What you get vs. a mechanical generator\n\n```python\n# typical OpenAPI generator                      # stainful\napi = DefaultApi(ApiClient(cfg))                  client = OnebusawaySDK()\nresp = api.agency_agency_id_json_get(id)          agency = client.agency.retrieve(id)\n# loosely typed, no retries, no error classes,    # typed model, retries, typed errors,\n# you hand-write the pagination loop              # auto-pagination, request id, async twin\n```\n\n## How it works\n\nThe pipeline is shown above: an OpenAPI spec and `stainless.yml` are parsed,\nresolved, and lowered into an intermediate representation, which the emitter\nrenders into a Python SDK over a vendored runtime.\n\nThe intermediate representation is a fully-resolved, language-agnostic model:\n`allOf` is merged, `oneOf` becomes a real tagged union, and optionality is\nthree-valued. The emitter is a thin renderer over a hand-written runtime, so the\nidiomatic behavior lives in audited code rather than per-endpoint templates.\n\n## How it compares\n\n|                                   | OpenAPI Generator | Fern | Stainless | stainful |\n|-----------------------------------|:-:|:-:|:-:|:-:|\n| Open source                       | ✅ | ✅ | — | ✅ |\n| Runs fully locally, no account    | ✅ | ✅ | — | ✅ |\n| Reads the `stainless.yml` format  | — | — | ✅ | ✅ |\n| Idiomatic output (pagination, typed errors, streaming) | — | ✅ | ✅ | ✅ |\n\nstainful's niche: idiomatic, fully-open, and a drop-in for the Stainless config\nyou may already have. Different tools fit different teams — this one is for\npeople who want that workflow without a hosted service.\n\n## Project layout\n\n| Path | What |\n|---|---|\n| `src\u002Fstainful\u002Fconfig\u002F`  | `stainless.yml` loader with precise, located diagnostics |\n| `src\u002Fstainful\u002Fopenapi\u002F` | OpenAPI 3.x loader + cycle-safe `$ref` \u002F `allOf` resolver |\n| `src\u002Fstainful\u002Fir\u002F`      | the intermediate representation |\n| `src\u002Fstainful\u002Femit\u002F`    | the Python emitter |\n| `src\u002Fstainful\u002Fruntime\u002F` | the hand-written runtime vendored into generated SDKs |\n| `tests\u002Ffixtures\u002F`       | conformance fixtures (chat \u002F paginated \u002F multipart \u002F binary \u002F webhooks \u002F …) |\n| `examples\u002Fonebusaway\u002F`  | committed dogfood — regenerated SDK is bit-stable; CI guards it |\n| `examples\u002Fopenai\u002F`      | real-world test: the public openai-openapi spec → mypy-clean SDK |\n| `docs\u002F`                 | migration guide and other docs |\n\n## Status\n\n**v0.4.0.** One `stainless.yml` → SDK + Mintlify-shaped `api.md` + MCP\nserver. Verified against the real Stainless-generated SDKs at pinned\nSHAs in CI:\n\n- **OneBusAway:** **29\u002F29 (100%)** of Stainless's own `OneBusAway\u002Fpython-sdk`\n  test files import unchanged against stainful's output; generated SDK is\n  mypy-clean; regeneration is byte-stable (the repo dogfoods itself).\n- **OpenAI:** the public `openai-openapi` spec (162 paths, 983 schemas)\n  generates a **mypy-clean** SDK — see [`examples\u002Fopenai\u002F`](examples\u002Fopenai\u002F)\n  and [`docs\u002Fmigrating-from-stainless.md`](docs\u002Fmigrating-from-stainless.md)\n  for what's verified and what's still on the gap list.\n\nEnd-to-end behavioral conformance covers: cursor pagination (wire param\nconfig-driven — `?after=\u003Clast_id>` matches openai), anthropic-shape\nbi-directional pagination (`before_id` ↔ `after_id`), SSE streaming with\n`@overload` pairs, multipart \u002F file upload, binary download\n(`audio\u002Fmpeg`, `octet-stream`), raw binary request bodies (S3-style PUT),\ntyped webhook unwrap (Standard Webhooks scheme), rich `APIResponse[T]`\nfrom `with_raw_response.*`, typed error-body models\n(`\u003Cpkg>.types.shared.ErrorObject` auto-detected from the spec), spec-\nspecific page class symbols (`SyncTokenPage`\u002F`SyncNextCursorPage`\u002F…),\n`custom_casings`, `.to_json()`\u002F`.to_dict()` aliases, webhook\n`\u003CBRAND>_WEBHOOK_SECRET` env-var fallback.\n\n**118 tests, mypy 0 on 253 generated files, ruff clean, CI green on\npy3.10–3.12.**\n\nKnown scope boundary: multi-content request bodies (one operation\ndeclaring multiple `requestBody.content` types) still pick the first\nmatch — no public Stainless oracle to verify the exact API surface.\nDocumented in the migration guide.\n\n**Roadmap:** Python SDK → MCP server from the same model → a second language →\ndocs site. One language done well first.\n\n## Contributing\n\nPRs welcome — see [`CONTRIBUTING.md`](CONTRIBUTING.md) and the\n[Code of Conduct](CODE_OF_CONDUCT.md).\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Fstainlu\u002Fstainful && cd stainful\nuv venv && uv pip install -e \".[dev,generated-runtime]\"\nuv run pytest -q\nuv run ruff check src tests\n\n# regenerate the dogfood SDK (the repo dogfoods itself; CI fails if a\n# regeneration changes a byte — see examples\u002Fonebusaway\u002F):\nuv run stainful generate \\\n  --spec   examples\u002Fonebusaway\u002Fopenapi.yml \\\n  --config examples\u002Fonebusaway\u002Fstainless.yml \\\n  --out    examples\u002Fonebusaway\u002Fsdk\n```\n\n## License\n\n[MIT](LICENSE). The vendored runtime ships inside generated SDKs under the same\nterms.\n","stainful 是一个开源工具，用于根据 OpenAPI 规范和 `stainless.yml` 配置文件生成符合 Python 习惯的 SDK。其核心功能包括自动生成类型安全的模型、自动分页处理、错误类型化、重试机制、流式传输支持以及同步与异步代码的一致性。技术上，stainful 基于 pydantic 和 httpx 构建，确保了输出 SDK 的自包含性和高效性。此外，它还提供了文档生成和 Model Context Protocol 服务器生成的功能，进一步增强了开发体验。适合需要从现有 API 定义快速构建高质量 Python 客户端库的场景，特别是那些已经在使用 Stainless 并希望迁移到本地解决方案的项目。",2,"2026-06-11 03:58:34","CREATED_QUERY"]