pyyapi 0.1.0__tar.gz → 0.2.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 (34) hide show
  1. pyyapi-0.2.0/PKG-INFO +241 -0
  2. pyyapi-0.2.0/README.md +206 -0
  3. {pyyapi-0.1.0 → pyyapi-0.2.0}/pyproject.toml +4 -1
  4. pyyapi-0.2.0/pyyapi.egg-info/PKG-INFO +241 -0
  5. {pyyapi-0.1.0 → pyyapi-0.2.0}/pyyapi.egg-info/SOURCES.txt +5 -0
  6. pyyapi-0.2.0/tests/test_compat.py +111 -0
  7. pyyapi-0.2.0/tests/test_dx.py +155 -0
  8. {pyyapi-0.1.0 → pyyapi-0.2.0}/tests/test_integration.py +3 -3
  9. pyyapi-0.2.0/tests/test_router.py +432 -0
  10. pyyapi-0.2.0/tests/test_runner.py +145 -0
  11. pyyapi-0.2.0/yapi/__init__.py +18 -0
  12. pyyapi-0.2.0/yapi/agent.py +49 -0
  13. {pyyapi-0.1.0 → pyyapi-0.2.0}/yapi/errors.py +2 -2
  14. pyyapi-0.2.0/yapi/py.typed +0 -0
  15. pyyapi-0.2.0/yapi/router.py +315 -0
  16. pyyapi-0.2.0/yapi/runner.py +49 -0
  17. pyyapi-0.2.0/yapi/runtime.py +99 -0
  18. pyyapi-0.1.0/PKG-INFO +0 -153
  19. pyyapi-0.1.0/README.md +0 -118
  20. pyyapi-0.1.0/pyyapi.egg-info/PKG-INFO +0 -153
  21. pyyapi-0.1.0/tests/test_router.py +0 -106
  22. pyyapi-0.1.0/yapi/__init__.py +0 -3
  23. pyyapi-0.1.0/yapi/agent.py +0 -42
  24. pyyapi-0.1.0/yapi/router.py +0 -137
  25. pyyapi-0.1.0/yapi/runtime.py +0 -60
  26. {pyyapi-0.1.0 → pyyapi-0.2.0}/LICENSE +0 -0
  27. {pyyapi-0.1.0 → pyyapi-0.2.0}/pyyapi.egg-info/dependency_links.txt +0 -0
  28. {pyyapi-0.1.0 → pyyapi-0.2.0}/pyyapi.egg-info/requires.txt +0 -0
  29. {pyyapi-0.1.0 → pyyapi-0.2.0}/pyyapi.egg-info/top_level.txt +0 -0
  30. {pyyapi-0.1.0 → pyyapi-0.2.0}/setup.cfg +0 -0
  31. {pyyapi-0.1.0 → pyyapi-0.2.0}/tests/test_exports.py +0 -0
  32. {pyyapi-0.1.0 → pyyapi-0.2.0}/tests/test_runtime.py +0 -0
  33. {pyyapi-0.1.0 → pyyapi-0.2.0}/yapi/endpoint.py +0 -0
  34. {pyyapi-0.1.0 → pyyapi-0.2.0}/yapi/models.py +0 -0
pyyapi-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,241 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyyapi
3
+ Version: 0.2.0
4
+ Summary: Prompt-first declarative HTTP framework on top of FastAPI and PydanticAI
5
+ Author-email: DJJ <shuaiqijianhao@qq.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/TokenRollAI/yapi
8
+ Project-URL: Repository, https://github.com/TokenRollAI/yapi
9
+ Project-URL: Issues, https://github.com/TokenRollAI/yapi/issues
10
+ Keywords: fastapi,pydantic,pydantic-ai,llm,prompt,http,framework,declarative
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Framework :: FastAPI
19
+ Classifier: Framework :: Pydantic
20
+ Classifier: Topic :: Internet :: WWW/HTTP
21
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.12
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: fastapi<1,>=0.115
27
+ Requires-Dist: pydantic<3,>=2.7
28
+ Requires-Dist: pydantic-ai<1,>=0.0.18
29
+ Requires-Dist: uvicorn<1,>=0.30
30
+ Provides-Extra: dev
31
+ Requires-Dist: httpx<1,>=0.27; extra == "dev"
32
+ Requires-Dist: pytest<9,>=8.2; extra == "dev"
33
+ Requires-Dist: pytest-asyncio<1,>=0.23; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # yapi
37
+
38
+ [![PyPI](https://img.shields.io/pypi/v/pyyapi.svg)](https://pypi.org/project/pyyapi/)
39
+ [![Python](https://img.shields.io/pypi/pyversions/pyyapi.svg)](https://pypi.org/project/pyyapi/)
40
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
41
+
42
+ > 中文文档请见 [README.zh-CN.md](./README.zh-CN.md)
43
+
44
+ **Prompt-first declarative HTTP framework** — write a normal Python function with a docstring, get an LLM-powered HTTP endpoint with structured JSON responses.
45
+
46
+ `yapi` is a thin layer on top of [FastAPI](https://fastapi.tiangolo.com/) and [PydanticAI](https://ai.pydantic.dev/). `PromptRouter` is a true *superset* of `fastapi.APIRouter`: native routes work as-is, and prompt routes live in the `router.prompt.*` namespace.
47
+
48
+ > Package name on PyPI is `pyyapi` (the unhyphenated `yapi` was taken by a 2018 project). Import path is still `yapi`.
49
+
50
+ ## Install
51
+
52
+ ```bash
53
+ pip install pyyapi
54
+ ```
55
+
56
+ Python 3.12+ required.
57
+
58
+ ## Quick start
59
+
60
+ ```python
61
+ from fastapi import FastAPI
62
+ from pydantic import BaseModel
63
+
64
+ from yapi import PromptRouter
65
+
66
+
67
+ class WishIn(BaseModel):
68
+ user_id: str
69
+ wish: str
70
+
71
+
72
+ class WishOut(BaseModel):
73
+ """You are a wish-granting entity. Decide whether to grant the wish."""
74
+
75
+ granted: bool
76
+ message: str
77
+
78
+
79
+ app = FastAPI(title="yapi showcase")
80
+ router = PromptRouter()
81
+
82
+
83
+ @router.prompt.post("/wish")
84
+ def make_a_wish(req: WishIn) -> WishOut:
85
+ """Decide whether to grant the user's wish."""
86
+
87
+
88
+ app.include_router(router)
89
+ ```
90
+
91
+ Run it:
92
+
93
+ ```bash
94
+ YAPI_MODEL=test uvicorn examples.wish_api:app --reload
95
+ ```
96
+
97
+ `YAPI_MODEL=test` activates PydanticAI's built-in `TestModel` — no API key, no network, perfect for offline smoke tests. For real models, set e.g. `YAPI_MODEL=openai:gpt-4o` or `YAPI_MODEL=anthropic:claude-3-5-sonnet`.
98
+
99
+ Open `http://localhost:8000/docs` for the auto-generated OpenAPI UI.
100
+
101
+ ## Mixing native FastAPI routes with prompt routes
102
+
103
+ `PromptRouter` is now a real `APIRouter` superset. `.get/.post/...` keep their FastAPI semantics; only `router.prompt.*` enters the LLM pipeline.
104
+
105
+ ```python
106
+ router = PromptRouter(prefix="/v1", tags=["wishes"])
107
+
108
+
109
+ @router.get("/health")
110
+ def health() -> dict:
111
+ return {"status": "ok"}
112
+
113
+
114
+ @router.prompt.post("/wish")
115
+ def make_a_wish(req: WishIn) -> WishOut:
116
+ """Decide whether to grant the user's wish."""
117
+ ```
118
+
119
+ ## Configuration
120
+
121
+ `yapi` is configured entirely through environment variables — the package never reads `.env` files itself. Use a launcher that injects them (recommended: `uvicorn --env-file .env`; alternatives: `set -a; source .env; set +a` in your shell, Docker `--env-file`, Kubernetes secrets, etc.).
122
+
123
+ ### `YAPI_MODEL` (required for the default runner)
124
+
125
+ PydanticAI model string in `provider:model` form. Read once when `PromptRouter()` is constructed without an explicit `agent_runner`.
126
+
127
+ ```bash
128
+ YAPI_MODEL=openai:gpt-4o # OpenAI
129
+ YAPI_MODEL=anthropic:claude-3-5-sonnet # Anthropic
130
+ YAPI_MODEL=openai:deepseek-chat # DeepSeek (OpenAI-compatible)
131
+ YAPI_MODEL=test # PydanticAI TestModel, no key, no network
132
+ ```
133
+
134
+ Unset → constructor emits a `YapiUsageWarning`, first request returns HTTP 500.
135
+
136
+ ### Provider credentials (read directly by PydanticAI)
137
+
138
+ `yapi` does **not** validate or even look at these — they are consumed by the underlying PydanticAI provider via `os.environ`:
139
+
140
+ | Provider | Env vars |
141
+ |---|---|
142
+ | OpenAI | `OPENAI_API_KEY` |
143
+ | OpenAI-compatible endpoints (DeepSeek, Azure OpenAI, OneAPI, local servers, …) | `OPENAI_API_KEY` + `OPENAI_BASE_URL` (e.g. `https://api.deepseek.com/v1`) |
144
+ | Anthropic | `ANTHROPIC_API_KEY` |
145
+ | Others (Google, Groq, Mistral, …) | See [PydanticAI providers docs](https://ai.pydantic.dev/models/) |
146
+
147
+ ### Example `.env` (DeepSeek)
148
+
149
+ ```dotenv
150
+ YAPI_MODEL=openai:deepseek-chat
151
+ OPENAI_API_KEY=sk-...
152
+ OPENAI_BASE_URL=https://api.deepseek.com/v1
153
+ ```
154
+
155
+ ```bash
156
+ uv run uvicorn examples.wish_api:app --reload --env-file .env
157
+ ```
158
+
159
+ > DeepSeek's "thinking" models (`deepseek-reasoner`, `deepseek-v4-flash`) currently reject OpenAI Function Calling's `tool_choice` parameter, which PydanticAI uses by default for structured output. Use `deepseek-chat` for now.
160
+
161
+ ## How a prompt route runs
162
+
163
+ For each request to a `router.prompt.*` route, `yapi`:
164
+
165
+ 1. parses path/query/header/cookie/body parameters via the function signature (FastAPI semantics, plus a single `BaseModel` request body),
166
+ 2. calls your function (sync or `async def`) to optionally produce a **dynamic prompt** (the function's `return` value, must be `None` or `str`),
167
+ 3. composes the final system prompt from: response-model docstring + function docstring + dynamic prompt,
168
+ 4. invokes the configured `agent_runner` (defaulting to a PydanticAI `Agent`) with a `RunnerContext` containing the prompt, request payload, injected fields, response model, path and method,
169
+ 5. validates the agent's output against your return annotation and serializes via FastAPI.
170
+
171
+ ## Contract (hard rules)
172
+
173
+ Applies inside `router.prompt.*`:
174
+
175
+ - Return annotation **must** be a `BaseModel` subclass.
176
+ - At most one parameter may be a `BaseModel` (the request body). Supports both `req: WishIn` and `req: Annotated[WishIn, Body()]`.
177
+ - Other parameters must be one of:
178
+ - `Depends(...)` default or `Annotated[T, Depends(...)]`
179
+ - `Annotated[T, Query()/Header()/Cookie()/Path()/Form()/File()]` or the equivalent `= Query(...)` default
180
+ - `*args` / `**kwargs` are rejected at decoration time.
181
+ - Function body must `return` `None` or a `str` (the dynamic prompt). Anything else raises at request time.
182
+ - `async def` is supported.
183
+
184
+ Decoration kwargs:
185
+
186
+ - Passed through to FastAPI: `tags`, `summary`, `description`, `status_code`, `deprecated`, `operation_id`, `name`, `include_in_schema`, `responses`, `openapi_extra`.
187
+ - Rejected at decoration time with `YapiDeclarationError`: `response_model`, `response_class`, `dependencies`.
188
+ - Any other unknown kwarg emits a `YapiUsageWarning`.
189
+
190
+ Violations are raised as `YapiDeclarationError` at decoration time — broken routes fail at import, not at request time.
191
+
192
+ ## Dependency injection
193
+
194
+ ```python
195
+ from fastapi import Depends
196
+ from typing import Annotated
197
+
198
+ def get_db():
199
+ ...
200
+
201
+ @router.prompt.post("/wish")
202
+ def make_a_wish(
203
+ req: WishIn,
204
+ db: Annotated[Database, Depends(get_db)],
205
+ ) -> WishOut:
206
+ """..."""
207
+ return f"user has {db.balance(req.user_id)} wishes left"
208
+ ```
209
+
210
+ ## Custom agent runner
211
+
212
+ Implement the `AgentRunner` Protocol — any object with a `.run(ctx: RunnerContext) -> dict | BaseModel` method is accepted:
213
+
214
+ ```python
215
+ from yapi import AgentRunner, PromptRouter, RunnerContext
216
+
217
+ class MockRunner:
218
+ def run(self, ctx: RunnerContext) -> dict:
219
+ return {
220
+ "granted": "moon" not in ctx.request["wish"].lower(),
221
+ "message": f"path={ctx.path}",
222
+ }
223
+
224
+ router = PromptRouter(agent_runner=MockRunner())
225
+ ```
226
+
227
+ The legacy v2-style `(*, prompt, request, injected, response_model) -> dict` callable is still accepted (auto-adapted).
228
+
229
+ You can also inject a custom `prompt_composer=` to customize how the system prompt is assembled.
230
+
231
+ ## Development
232
+
233
+ ```bash
234
+ uv sync --extra dev
235
+ uv run pytest
236
+ uv run uvicorn examples.wish_api:app --reload
237
+ ```
238
+
239
+ ## License
240
+
241
+ MIT
pyyapi-0.2.0/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # yapi
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/pyyapi.svg)](https://pypi.org/project/pyyapi/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/pyyapi.svg)](https://pypi.org/project/pyyapi/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ > 中文文档请见 [README.zh-CN.md](./README.zh-CN.md)
8
+
9
+ **Prompt-first declarative HTTP framework** — write a normal Python function with a docstring, get an LLM-powered HTTP endpoint with structured JSON responses.
10
+
11
+ `yapi` is a thin layer on top of [FastAPI](https://fastapi.tiangolo.com/) and [PydanticAI](https://ai.pydantic.dev/). `PromptRouter` is a true *superset* of `fastapi.APIRouter`: native routes work as-is, and prompt routes live in the `router.prompt.*` namespace.
12
+
13
+ > Package name on PyPI is `pyyapi` (the unhyphenated `yapi` was taken by a 2018 project). Import path is still `yapi`.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ pip install pyyapi
19
+ ```
20
+
21
+ Python 3.12+ required.
22
+
23
+ ## Quick start
24
+
25
+ ```python
26
+ from fastapi import FastAPI
27
+ from pydantic import BaseModel
28
+
29
+ from yapi import PromptRouter
30
+
31
+
32
+ class WishIn(BaseModel):
33
+ user_id: str
34
+ wish: str
35
+
36
+
37
+ class WishOut(BaseModel):
38
+ """You are a wish-granting entity. Decide whether to grant the wish."""
39
+
40
+ granted: bool
41
+ message: str
42
+
43
+
44
+ app = FastAPI(title="yapi showcase")
45
+ router = PromptRouter()
46
+
47
+
48
+ @router.prompt.post("/wish")
49
+ def make_a_wish(req: WishIn) -> WishOut:
50
+ """Decide whether to grant the user's wish."""
51
+
52
+
53
+ app.include_router(router)
54
+ ```
55
+
56
+ Run it:
57
+
58
+ ```bash
59
+ YAPI_MODEL=test uvicorn examples.wish_api:app --reload
60
+ ```
61
+
62
+ `YAPI_MODEL=test` activates PydanticAI's built-in `TestModel` — no API key, no network, perfect for offline smoke tests. For real models, set e.g. `YAPI_MODEL=openai:gpt-4o` or `YAPI_MODEL=anthropic:claude-3-5-sonnet`.
63
+
64
+ Open `http://localhost:8000/docs` for the auto-generated OpenAPI UI.
65
+
66
+ ## Mixing native FastAPI routes with prompt routes
67
+
68
+ `PromptRouter` is now a real `APIRouter` superset. `.get/.post/...` keep their FastAPI semantics; only `router.prompt.*` enters the LLM pipeline.
69
+
70
+ ```python
71
+ router = PromptRouter(prefix="/v1", tags=["wishes"])
72
+
73
+
74
+ @router.get("/health")
75
+ def health() -> dict:
76
+ return {"status": "ok"}
77
+
78
+
79
+ @router.prompt.post("/wish")
80
+ def make_a_wish(req: WishIn) -> WishOut:
81
+ """Decide whether to grant the user's wish."""
82
+ ```
83
+
84
+ ## Configuration
85
+
86
+ `yapi` is configured entirely through environment variables — the package never reads `.env` files itself. Use a launcher that injects them (recommended: `uvicorn --env-file .env`; alternatives: `set -a; source .env; set +a` in your shell, Docker `--env-file`, Kubernetes secrets, etc.).
87
+
88
+ ### `YAPI_MODEL` (required for the default runner)
89
+
90
+ PydanticAI model string in `provider:model` form. Read once when `PromptRouter()` is constructed without an explicit `agent_runner`.
91
+
92
+ ```bash
93
+ YAPI_MODEL=openai:gpt-4o # OpenAI
94
+ YAPI_MODEL=anthropic:claude-3-5-sonnet # Anthropic
95
+ YAPI_MODEL=openai:deepseek-chat # DeepSeek (OpenAI-compatible)
96
+ YAPI_MODEL=test # PydanticAI TestModel, no key, no network
97
+ ```
98
+
99
+ Unset → constructor emits a `YapiUsageWarning`, first request returns HTTP 500.
100
+
101
+ ### Provider credentials (read directly by PydanticAI)
102
+
103
+ `yapi` does **not** validate or even look at these — they are consumed by the underlying PydanticAI provider via `os.environ`:
104
+
105
+ | Provider | Env vars |
106
+ |---|---|
107
+ | OpenAI | `OPENAI_API_KEY` |
108
+ | OpenAI-compatible endpoints (DeepSeek, Azure OpenAI, OneAPI, local servers, …) | `OPENAI_API_KEY` + `OPENAI_BASE_URL` (e.g. `https://api.deepseek.com/v1`) |
109
+ | Anthropic | `ANTHROPIC_API_KEY` |
110
+ | Others (Google, Groq, Mistral, …) | See [PydanticAI providers docs](https://ai.pydantic.dev/models/) |
111
+
112
+ ### Example `.env` (DeepSeek)
113
+
114
+ ```dotenv
115
+ YAPI_MODEL=openai:deepseek-chat
116
+ OPENAI_API_KEY=sk-...
117
+ OPENAI_BASE_URL=https://api.deepseek.com/v1
118
+ ```
119
+
120
+ ```bash
121
+ uv run uvicorn examples.wish_api:app --reload --env-file .env
122
+ ```
123
+
124
+ > DeepSeek's "thinking" models (`deepseek-reasoner`, `deepseek-v4-flash`) currently reject OpenAI Function Calling's `tool_choice` parameter, which PydanticAI uses by default for structured output. Use `deepseek-chat` for now.
125
+
126
+ ## How a prompt route runs
127
+
128
+ For each request to a `router.prompt.*` route, `yapi`:
129
+
130
+ 1. parses path/query/header/cookie/body parameters via the function signature (FastAPI semantics, plus a single `BaseModel` request body),
131
+ 2. calls your function (sync or `async def`) to optionally produce a **dynamic prompt** (the function's `return` value, must be `None` or `str`),
132
+ 3. composes the final system prompt from: response-model docstring + function docstring + dynamic prompt,
133
+ 4. invokes the configured `agent_runner` (defaulting to a PydanticAI `Agent`) with a `RunnerContext` containing the prompt, request payload, injected fields, response model, path and method,
134
+ 5. validates the agent's output against your return annotation and serializes via FastAPI.
135
+
136
+ ## Contract (hard rules)
137
+
138
+ Applies inside `router.prompt.*`:
139
+
140
+ - Return annotation **must** be a `BaseModel` subclass.
141
+ - At most one parameter may be a `BaseModel` (the request body). Supports both `req: WishIn` and `req: Annotated[WishIn, Body()]`.
142
+ - Other parameters must be one of:
143
+ - `Depends(...)` default or `Annotated[T, Depends(...)]`
144
+ - `Annotated[T, Query()/Header()/Cookie()/Path()/Form()/File()]` or the equivalent `= Query(...)` default
145
+ - `*args` / `**kwargs` are rejected at decoration time.
146
+ - Function body must `return` `None` or a `str` (the dynamic prompt). Anything else raises at request time.
147
+ - `async def` is supported.
148
+
149
+ Decoration kwargs:
150
+
151
+ - Passed through to FastAPI: `tags`, `summary`, `description`, `status_code`, `deprecated`, `operation_id`, `name`, `include_in_schema`, `responses`, `openapi_extra`.
152
+ - Rejected at decoration time with `YapiDeclarationError`: `response_model`, `response_class`, `dependencies`.
153
+ - Any other unknown kwarg emits a `YapiUsageWarning`.
154
+
155
+ Violations are raised as `YapiDeclarationError` at decoration time — broken routes fail at import, not at request time.
156
+
157
+ ## Dependency injection
158
+
159
+ ```python
160
+ from fastapi import Depends
161
+ from typing import Annotated
162
+
163
+ def get_db():
164
+ ...
165
+
166
+ @router.prompt.post("/wish")
167
+ def make_a_wish(
168
+ req: WishIn,
169
+ db: Annotated[Database, Depends(get_db)],
170
+ ) -> WishOut:
171
+ """..."""
172
+ return f"user has {db.balance(req.user_id)} wishes left"
173
+ ```
174
+
175
+ ## Custom agent runner
176
+
177
+ Implement the `AgentRunner` Protocol — any object with a `.run(ctx: RunnerContext) -> dict | BaseModel` method is accepted:
178
+
179
+ ```python
180
+ from yapi import AgentRunner, PromptRouter, RunnerContext
181
+
182
+ class MockRunner:
183
+ def run(self, ctx: RunnerContext) -> dict:
184
+ return {
185
+ "granted": "moon" not in ctx.request["wish"].lower(),
186
+ "message": f"path={ctx.path}",
187
+ }
188
+
189
+ router = PromptRouter(agent_runner=MockRunner())
190
+ ```
191
+
192
+ The legacy v2-style `(*, prompt, request, injected, response_model) -> dict` callable is still accepted (auto-adapted).
193
+
194
+ You can also inject a custom `prompt_composer=` to customize how the system prompt is assembled.
195
+
196
+ ## Development
197
+
198
+ ```bash
199
+ uv sync --extra dev
200
+ uv run pytest
201
+ uv run uvicorn examples.wish_api:app --reload
202
+ ```
203
+
204
+ ## License
205
+
206
+ MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pyyapi"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Prompt-first declarative HTTP framework on top of FastAPI and PydanticAI"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -50,6 +50,9 @@ Issues = "https://github.com/TokenRollAI/yapi/issues"
50
50
  include = ["yapi*"]
51
51
  exclude = ["tests*", "examples*", "docs*", "llmdoc*"]
52
52
 
53
+ [tool.setuptools.package-data]
54
+ yapi = ["py.typed"]
55
+
53
56
  [tool.pytest.ini_options]
54
57
  pythonpath = ["."]
55
58
  testpaths = ["tests"]