MsgSpecΒΆ
MsgSpec is a fast serialization and validation library that integrates seamlessly with Ravyn. If you need maximum performance for JSON serialization, MsgSpec is an excellent alternative to Pydantic.
What You'll LearnΒΆ
- Using MsgSpec Structs in Ravyn
- MsgSpec vs Pydantic performance
- Integrating with OpenAPI documentation
- Mixing MsgSpec with Pydantic
- Validation with MsgSpec
Quick StartΒΆ
from ravyn import Ravyn, get, post
from ravyn.core.datastructures.msgspec import Struct
class User(Struct):
name: str
email: str
age: int = 0
@get("/user")
def get_user() -> User:
return User(name="Alice", email="alice@example.com", age=30)
@post("/user")
def create_user(user: User) -> User:
# Validation happens automatically
return user
app = Ravyn()
app.add_route(get_user)
app.add_route(create_user)
Info
Always import from ravyn.datastructures.msgspec import Struct for full Ravyn integration, not directly from msgspec.
Why Use MsgSpec?ΒΆ
Performance Benefits:ΒΆ
-
Faster Serialization - 5-10x faster than Pydantic for JSON
-
Lower Memory Usage - More efficient than Pydantic
-
Fast Validation - High-performance data validation
-
Multiple Formats - JSON, MessagePack, YAML, TOML support
When to Use MsgSpec:ΒΆ
- High-throughput APIs
- Performance-critical endpoints
- Large data payloads
- Microservices with tight latency requirements
When to Use Pydantic:ΒΆ
- Complex validation logic
- Rich ecosystem of validators
- Extensive field customization
- ORM integration (SQLAlchemy, etc.)
MsgSpec vs PydanticΒΆ
| Feature | MsgSpec | Pydantic |
|---|---|---|
| Speed | β‘β‘β‘ Very Fast | β‘β‘ Fast |
| Memory | Low | Higher |
| Validation | Built-in types | Extensive validators |
| Ecosystem | Smaller | Very Large |
| Learning Curve | Simple | Moderate |
Using MsgSpec StructsΒΆ
Basic StructΒΆ
from ravyn.core.datastructures.msgspec import Struct
class Product(Struct):
name: str
price: float
in_stock: bool = True
With ValidationΒΆ
from ravyn.core.datastructures.msgspec import Struct
from ravyn import post
from ravyn.exceptions import ValidationError
class CreateUser(Struct):
username: str
email: str
age: int
def __post_init__(self):
if self.age < 18:
raise ValidationError("Must be 18 or older")
if "@" not in self.email:
raise ValidationError("Invalid email format")
@post("/users")
def create_user(data: CreateUser) -> dict:
return {"created": data.username}
Nested StructsΒΆ
from ravyn.core.datastructures.msgspec import Struct
class Address(Struct):
street: str
city: str
zip_code: str
class User(Struct):
name: str
email: str
address: Address
# Usage
user = User(
name="Alice",
email="alice@example.com",
address=Address(
street="123 Main St",
city="Springfield",
zip_code="12345"
)
)
Ravyn IntegrationΒΆ
Import from RavynΒΆ
# Correct - Full Ravyn integration
from ravyn.core.datastructures.msgspec import Struct
# Wrong - Missing OpenAPI support
from msgspec import Struct
Warning
Using msgspec.Struct directly will cause errors with OpenAPI documentation. Always use ravyn.datastructures.msgspec.Struct.
As Request BodyΒΆ
from ravyn import post
from ravyn.core.datastructures.msgspec import Struct
class LoginRequest(Struct):
username: str
password: str
@post("/login")
def login(credentials: LoginRequest) -> dict:
# MsgSpec validates automatically
return {"token": "abc123"}
As ResponseΒΆ
from ravyn import get
from ravyn.core.datastructures.msgspec import Struct
class UserResponse(Struct):
id: int
name: str
email: str
@get("/users/{user_id}")
def get_user(user_id: int) -> UserResponse:
return UserResponse(
id=user_id,
name="Alice",
email="alice@example.com"
)
OpenAPI DocumentationΒΆ
MsgSpec Structs work seamlessly with OpenAPI:
Single ResponseΒΆ
from ravyn import get
from ravyn.core.datastructures.msgspec import Struct
from ravyn.openapi.datastructures import OpenAPIResponse
class User(Struct):
name: str
email: str
@get(
"/user",
responses={
200: OpenAPIResponse(model=User, description="User details")
}
)
def get_user() -> User:
return User(name="Alice", email="alice@example.com")
List ResponseΒΆ
@get(
"/users",
responses={
200: OpenAPIResponse(model=[User], description="List of users")
}
)
def list_users() -> list[User]:
return [
User(name="Alice", email="alice@example.com"),
User(name="Bob", email="bob@example.com")
]
Mixing MsgSpec with PydanticΒΆ
You can use MsgSpec Structs inside Pydantic models (but not vice versa):
from ravyn.core.datastructures.msgspec import Struct
from pydantic import BaseModel
class Address(Struct):
street: str
city: str
class User(BaseModel):
name: str
email: str
address: Address # MsgSpec Struct inside Pydantic model
# This works!
user = User(
name="Alice",
email="alice@example.com",
address=Address(street="123 Main St", city="Springfield")
)
[!INFO] Each type is validated by its own library: MsgSpec validates
Address, Pydantic validatesUser.
Common Pitfalls & FixesΒΆ
Pitfall 1: Using msgspec.Struct DirectlyΒΆ
Problem: OpenAPI documentation fails.
# Wrong - breaks OpenAPI
from msgspec import Struct
class User(Struct):
name: str
Solution: Import from Ravyn:
# Correct
from ravyn.core.datastructures.msgspec import Struct
class User(Struct):
name: str
Pitfall 2: Pydantic Inside MsgSpecΒΆ
Problem: Trying to nest Pydantic in MsgSpec.
# Won't work
from pydantic import BaseModel
from ravyn.core.datastructures.msgspec import Struct
class Address(BaseModel):
street: str
class User(Struct):
name: str
address: Address # Pydantic inside MsgSpec - not supported
Solution: Use MsgSpec for nested types or use Pydantic for everything:
# Option 1: All MsgSpec
class Address(Struct):
street: str
class User(Struct):
name: str
address: Address
# Option 2: All Pydantic
class Address(BaseModel):
street: str
class User(BaseModel):
name: str
address: Address
Pitfall 3: Missing ValidationΒΆ
Problem: No validation on struct fields.
# No validation
class User(Struct):
age: int # Accepts any int, even negative
Solution: Add validation in __post_init__:
# With validation
class User(Struct):
age: int
def __post_init__(self):
if self.age < 0:
raise ValueError("Age must be positive")
Pitfall 4: Forgetting Default ValuesΒΆ
Problem: Required fields not provided.
# Error if in_stock not provided
class Product(Struct):
name: str
price: float
in_stock: bool # Required!
Solution: Provide defaults for optional fields:
# Correct
class Product(Struct):
name: str
price: float
in_stock: bool = True # Optional with default
Performance ComparisonΒΆ
Serialization SpeedΒΆ
# MsgSpec: ~5-10x faster than Pydantic
import msgspec
import time
class User(Struct):
name: str
email: str
user = User(name="Alice", email="alice@example.com")
# Serialize 100,000 times
start = time.time()
for _ in range(100000):
msgspec.json.encode(user)
print(f"MsgSpec: {time.time() - start:.2f}s")
# Compare with Pydantic for same operation
# Pydantic typically takes 5-10x longer
Best PracticesΒΆ
1. Use for High-Performance EndpointsΒΆ
# Good - high-throughput endpoint
@get("/api/data")
def get_data() -> DataStruct:
return DataStruct(...) # Fast serialization
2. Keep Validation SimpleΒΆ
# Good - simple validation
class User(Struct):
age: int
def __post_init__(self):
if not 0 <= self.age <= 150:
raise ValueError("Invalid age")
3. Use Type HintsΒΆ
# Good - clear types
class Product(Struct):
name: str
price: float
tags: list[str]
metadata: dict[str, str]
Next StepsΒΆ
Now that you understand MsgSpec, explore:
- Encoders - Custom data type support
- Responses - Response types
- Requests - Request handling
- OpenAPI - API documentation
- MsgSpec Documentation - Official docs