Skip to content

Commit 5c5569a

Browse files
committed
Enhance README.md
1 parent a82180a commit 5c5569a

1 file changed

Lines changed: 96 additions & 10 deletions

File tree

README.md

Lines changed: 96 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# fastapi-msgspec-openapi
22

3+
[![PyPI version](https://badge.fury.io/py/fastapi-msgspec-openapi.svg)](https://pypi.org/project/fastapi-msgspec-openapi/)
4+
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
5+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6+
[![Tests](https://github.com/S3wnkin/fastapi-msgspec-openapi/workflows/Tests/badge.svg)](https://github.com/S3wnkin/fastapi-msgspec-openapi/actions)
7+
38
FastAPI plugin for automatic OpenAPI schema generation from [msgspec](https://github.com/jcrist/msgspec) structs. Enables Swagger UI documentation and TypeScript type generation.
49

510
## Features
@@ -64,6 +69,27 @@ MsgSpecPlugin.inject(app)
6469
# ✅ Full OpenAPI documentation (developer experience)
6570
```
6671

72+
## Why msgspec Over Pydantic?
73+
74+
**Performance**: msgspec is significantly faster than Pydantic for serialization:
75+
76+
- 5-10x faster serialization
77+
- Lower memory usage
78+
- Native JSON encoding
79+
80+
**When to use this plugin**:
81+
82+
- ✅ High-throughput APIs
83+
- ✅ Performance-critical services
84+
- ✅ You want msgspec speed + OpenAPI docs
85+
- ✅ TypeScript type generation needed
86+
87+
**When to stick with Pydantic**:
88+
89+
- ❌ Complex validation logic (Pydantic has richer validation)
90+
- ❌ ORM integration (Pydantic works better with SQLAlchemy)
91+
- ❌ You don't need extreme performance
92+
6793
## TypeScript Type Generation
6894

6995
Generate TypeScript types from your OpenAPI schema:
@@ -75,14 +101,14 @@ npx openapi-typescript http://localhost:8000/openapi.json -o api-types.ts
75101

76102
```typescript
77103
// Use in your frontend
78-
import type { components } from './api-types';
104+
import type { components } from "./api-types";
79105

80-
type User = components['schemas']['User'];
106+
type User = components["schemas"]["User"];
81107

82108
const user: User = {
83109
id: 1,
84110
name: "Alice",
85-
email: "alice@example.com"
111+
email: "alice@example.com",
86112
};
87113
```
88114

@@ -136,18 +162,77 @@ The plugin handles `Optional`, `list`, and other generic types automatically.
136162
3. **Response updates** - Patches FastAPI's OpenAPI schema to reference your structs
137163
4. **Caching** - Schema generation happens once, then cached
138164

165+
## Limitations & Design Decisions
166+
167+
### Why `response_model=Any`?
168+
169+
FastAPI tries to validate/serialize the response using Pydantic when you specify a `response_model`. Since we're using msgspec structs (not Pydantic models), we use `Any` to bypass FastAPI's response handling:
170+
171+
```python
172+
@app.get("/user", response_model=Any) # Tells FastAPI: "I'll handle serialization"
173+
async def get_user() -> User: # Plugin reads this for OpenAPI schema
174+
return msgspec.to_builtins(...) # We handle serialization ourselves
175+
```
176+
177+
The plugin reads the **return type hint** (`-> User`) for schema generation, while `response_model=Any` prevents FastAPI from interfering.
178+
179+
### Type Checker Warnings
180+
181+
You may see type checker warnings like:
182+
183+
```python
184+
return msgspec.to_builtins(user) # "Returning Any from function declared to return User"
185+
```
186+
187+
This is expected - `msgspec.to_builtins()` returns `Any` because it converts to dict/list at runtime. You can suppress this with:
188+
189+
```python
190+
return msgspec.to_builtins(user) # type: ignore[return-value]
191+
```
192+
193+
Or configure your type checker to ignore this pattern:
194+
195+
```toml
196+
# pyproject.toml
197+
[tool.mypy]
198+
[[tool.mypy.overrides]]
199+
module = "your_app.*"
200+
disable_error_code = ["return-value"]
201+
```
202+
203+
### Module-Level Struct Definitions
204+
205+
Structs must be defined at module level for Python's `get_type_hints()` to resolve them properly:
206+
207+
```python
208+
# ✅ Good - Module level
209+
class User(msgspec.Struct):
210+
id: int
211+
212+
# ❌ Bad - Inside function
213+
def create_app():
214+
class User(msgspec.Struct): # Won't be detected
215+
id: int
216+
```
217+
218+
This is a Python limitation, not specific to this plugin.
219+
220+
### Why Not Use `response_model=User` Directly?
221+
222+
FastAPI's `response_model` expects Pydantic models. If we could use msgspec structs directly, we wouldn't need this plugin! The whole purpose is to:
223+
224+
1. Use msgspec for performance (serialization)
225+
2. Generate OpenAPI schemas (documentation)
226+
3. Keep type hints accurate (developer experience)
227+
228+
This plugin bridges the gap by extracting schemas from type hints while letting you handle serialization with msgspec.
229+
139230
## Requirements
140231

141232
- Python 3.10+
142233
- FastAPI 0.100.0+
143234
- msgspec 0.18.0+
144235

145-
## Important Notes
146-
147-
- ⚠️ **Struct types must be defined at module level** (not inside functions) for proper type hint resolution
148-
- ⚠️ **Always use `response_model=Any`** in route decorators when returning msgspec structs
149-
- ⚠️ **Use `msgspec.to_builtins()`** to serialize structs before returning
150-
151236
## Contributing
152237

153238
Contributions are welcome! Please feel free to submit a Pull Request.
@@ -158,9 +243,10 @@ MIT License - see LICENSE file for details
158243

159244
## Credits
160245

161-
Created by [RamsesDev](https://github.com/RamsesDev)
246+
Created by [S3wnkin](https://github.com/S3wnkin)
162247

163248
Inspired by:
249+
164250
- [msgspec](https://github.com/jcrist/msgspec) by Jim Crist-Harif
165251
- [FastAPI](https://github.com/tiangolo/fastapi) by Sebastián Ramírez
166252
- [fastapi-msgspec](https://github.com/iurii-skorniakov/fastapi-msgspec) by Iurii Skorniakov

0 commit comments

Comments
 (0)