Skip to content

🌿 Fern Scribe: Document pydantic validation configuration... #343

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
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
22 changes: 19 additions & 3 deletions fern/products/sdks/overview/python/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ groups:
</ParamField>

<ParamField path="package_name" type="string" default="null" required={false} toc={true}>

Specifies the Python package name that users will import your generated client
from.

Expand All @@ -92,6 +91,7 @@ groups:
</ParamField>

<ParamField path="pydantic_config" type="SdkPydanticModelCustomConfig" default="SdkPydanticModelCustomConfig()" required={false} toc={true}>
Configuration options for Pydantic model generation and validation behavior.
</ParamField>

<ParamField path="pydantic_config.include_union_utils" type="bool" default="false" required={false} toc={true}>
Expand Down Expand Up @@ -121,7 +121,7 @@ groups:
</ParamField>

<ParamField path="pydantic_config.version" type="'v1' | 'v2' | 'both' | 'v1_on_v2'" default="both" required={false} toc={true}>
By default, the generator generates pydantic models that are v1 and v2 compatible. However you can override them to:
Controls which version of Pydantic to use for model generation. Available options:
- `v1`: strictly use Pydantic v1
- `v2`: strictly use Pydantic v2
- `both`: maintain compatibility with both versions
Expand All @@ -131,7 +131,23 @@ groups:
```yaml
config:
pydantic_config:
version: v1 # or v2 or "both"
version: v2
```
</ParamField>

<ParamField path="pydantic_config.use_construct" type="bool" default="false" required={false} toc={true}>
When enabled, the generator will include the Pydantic `_alias_` field in model generation to properly handle camelCase to snake_case field name conversion. This is particularly useful when working with FastAPI or other frameworks that rely on Pydantic validation.

Example:
```yaml
config:
pydantic_config:
use_construct: true
```

This ensures proper deserialization of fields like `createdAt` to `created_at`:
```python
created_at: typing_extensions.Annotated[dt.datetime, FieldMetadata(_alias_="createdAt")] = pydantic.Field(_alias_="createdAt")
```
</ParamField>

Expand Down
111 changes: 111 additions & 0 deletions fern/products/sdks/overview/python/fastapi-integration.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
---
title: "FastAPI Integration"
description: "Configure Pydantic validation for FastAPI applications using Fern-generated Python SDKs"
---

# FastAPI Integration Guide

When using Fern-generated Python SDKs with FastAPI, you may need to configure Pydantic validation to properly handle serialization and deserialization of models. This guide covers common scenarios and solutions.

## Handling Model Validation

### Configuring Model Aliases

When working with camelCase API fields and snake_case Python models, you'll need to properly configure field aliases:

```python
from pydantic import Field
from typing_extensions import Annotated
import datetime as dt

class MyModel(FernABC):
# Correct way to define aliased fields
created_at: Annotated[dt.datetime, FieldMetadata(_alias_="createdAt")] = Field(alias="createdAt")
org_id: Annotated[str, FieldMetadata(_alias_="orgId")] = Field(alias="orgId")
```

### FastAPI Request Handling

For FastAPI endpoints that receive Fern-generated models:

```python
from fastapi import FastAPI, Request
from your_fern_sdk.types import ServerMessage, ServerMessageResponse

app = FastAPI()

@app.post("/webhook")
async def handle_webhook(message: ServerMessage) -> ServerMessageResponse:
# FastAPI will automatically validate the incoming JSON against your model
return ServerMessageResponse(
# Your response fields
)
```

### Timestamp Handling

For endpoints that need to handle timestamp fields:

```python
from pydantic import BaseModel, Field
from datetime import datetime

class TimestampModel(BaseModel):
# Handle both string and integer timestamps
timestamp: datetime = Field(...)

class Config:
json_encoders = {
datetime: lambda dt: int(dt.timestamp() * 1000)
}
```

## Troubleshooting

<Note>
If you encounter validation errors with camelCase to snake_case conversion, verify that all model fields have both the `FieldMetadata(_alias_)` annotation and `Field(alias=)` parameter properly configured.
</Note>

### Common Issues

Here are some typical validation errors and their solutions:

1. **String Type Errors for Timestamps**
```python
# Solution: Ensure proper datetime conversion
from pydantic import validator

class MyModel(FernABC):
timestamp: datetime

@validator("timestamp", pre=True)
def parse_timestamp(cls, value):
if isinstance(value, int):
return datetime.fromtimestamp(value / 1000)
return value
```

2. **Missing Required Fields**
```python
# Solution: Properly define all required fields with aliases
class CallModel(FernABC):
org_id: Annotated[str, FieldMetadata(_alias_="orgId")] = Field(alias="orgId")
created_at: Annotated[datetime, FieldMetadata(_alias_="createdAt")] = Field(alias="createdAt")
updated_at: Annotated[datetime, FieldMetadata(_alias_="updatedAt")] = Field(alias="updatedAt")
```

<Warning>
Remember to test your FastAPI endpoints with both serialization (Python objects to JSON) and deserialization (JSON to Python objects) to ensure proper validation in both directions.
</Warning>

## Best Practices

1. Always include both `FieldMetadata(_alias_)` and `Field(alias=)` for camelCase fields
2. Test validation with sample payloads before deploying
3. Add custom validators when needed for complex type conversions
4. Use middleware for global transformation needs
5. Keep model definitions consistent with your API schema

<Note>
If you need to handle complex nested objects, ensure all nested models also follow these alias configuration patterns.
</Note>
131 changes: 131 additions & 0 deletions fern/products/sdks/overview/python/models.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
---
title: "Pydantic Configuration"
description: "Configure Pydantic validation behavior in your Python SDK"
---

# Pydantic Configuration

When using the Python SDK with Pydantic models, there are several important configuration options to ensure proper serialization and deserialization of data.

## Field Aliases

By default, Fern generates snake_case field names for Python models while preserving camelCase aliases for JSON serialization. To explicitly configure field aliases:

```python
from pydantic import Field
from datetime import datetime
import typing_extensions

class MyModel(BaseModel):
created_at: typing_extensions.Annotated[datetime, FieldMetadata(_alias_="createdAt")] = Field(alias="createdAt")
```

## Timestamp Handling

For fields that handle timestamps:

```python
class Message(BaseModel):
# For string timestamps
timestamp: str = Field(alias="timestamp")

# For integer timestamps
timestamp: int = Field(alias="timestamp")

# For datetime objects
timestamp: datetime = Field(alias="timestamp")
```

## FastAPI Integration

When using the SDK with FastAPI, you may need to configure your endpoints to handle serialization:

```python
from fastapi import FastAPI
from your_sdk import ServerMessage, ServerMessageResponse

app = FastAPI()

@app.post("/webhook")
async def handle_webhook(message: ServerMessage) -> ServerMessageResponse:
# Handle incoming message
return ServerMessageResponse(
# Your response fields
)
```

<Note>
When using FastAPI, ensure your models use the `alias` parameter in Field declarations for proper serialization/deserialization.
</Note>

## Recursive Model Validation

For nested objects, ensure all child models also have proper alias configurations:

```python
class ChildModel(BaseModel):
user_id: str = Field(alias="userId")
created_at: datetime = Field(alias="createdAt")

class ParentModel(BaseModel):
child: ChildModel
updated_at: datetime = Field(alias="updatedAt")
```

## Custom Serialization

For advanced cases, you can customize serialization behavior:

```python
class CustomModel(BaseModel):
class Config:
allow_population_by_field_name = True
alias_generator = lambda field_name: "".join(
word.capitalize() if i > 0 else word
for i, word in enumerate(field_name.split("_"))
)
```

<Note>
The Config class settings affect all fields in the model. Use field-specific aliases when you need different behavior for individual fields.
</Note>

## Troubleshooting

Common validation issues and solutions:

1. **Missing Field Aliases**: Ensure all fields that need to match camelCase JSON have proper aliases configured.

```python
# Correct
user_id: str = Field(alias="userId")

# Incorrect
user_id: str = Field() # Will not match camelCase JSON
```

2. **Timestamp Type Mismatches**: Match the field type to your data format:

```python
# For integer timestamps
timestamp: int = Field(alias="timestamp")

# For ISO format strings
timestamp: str = Field(alias="timestamp")
```

3. **Nested Object Validation**: Configure aliases at all levels of nested objects:

```python
class Address(BaseModel):
street_name: str = Field(alias="streetName")
postal_code: str = Field(alias="postalCode")

class User(BaseModel):
user_id: str = Field(alias="userId")
home_address: Address = Field(alias="homeAddress")
```

<Note>
Always test your models with sample data that matches your production environment to ensure proper validation behavior.
</Note>
Loading