diff --git a/fern/products/sdks/overview/python/configuration.mdx b/fern/products/sdks/overview/python/configuration.mdx index 1c0eb6af..5a0fc58b 100644 --- a/fern/products/sdks/overview/python/configuration.mdx +++ b/fern/products/sdks/overview/python/configuration.mdx @@ -72,7 +72,6 @@ groups: - Specifies the Python package name that users will import your generated client from. @@ -92,6 +91,7 @@ groups: + Configuration options for Pydantic model generation and validation behavior. @@ -121,7 +121,7 @@ groups: - 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 @@ -131,7 +131,23 @@ groups: ```yaml config: pydantic_config: - version: v1 # or v2 or "both" + version: v2 + ``` + + + + 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") ``` diff --git a/fern/products/sdks/overview/python/fastapi-integration.mdx b/fern/products/sdks/overview/python/fastapi-integration.mdx new file mode 100644 index 00000000..2eaf8733 --- /dev/null +++ b/fern/products/sdks/overview/python/fastapi-integration.mdx @@ -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 + + +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. + + +### 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") +``` + + +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. + + +## 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 + + +If you need to handle complex nested objects, ensure all nested models also follow these alias configuration patterns. + \ No newline at end of file diff --git a/fern/products/sdks/overview/python/models.mdx b/fern/products/sdks/overview/python/models.mdx new file mode 100644 index 00000000..89ff09fe --- /dev/null +++ b/fern/products/sdks/overview/python/models.mdx @@ -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 + ) +``` + + + When using FastAPI, ensure your models use the `alias` parameter in Field declarations for proper serialization/deserialization. + + +## 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("_")) + ) +``` + + + The Config class settings affect all fields in the model. Use field-specific aliases when you need different behavior for individual fields. + + +## 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") +``` + + + Always test your models with sample data that matches your production environment to ensure proper validation behavior. + \ No newline at end of file diff --git a/fern/products/sdks/overview/typescript/configuration.mdx b/fern/products/sdks/overview/typescript/configuration.mdx index 6fc21620..1ba18a39 100644 --- a/fern/products/sdks/overview/typescript/configuration.mdx +++ b/fern/products/sdks/overview/typescript/configuration.mdx @@ -2,7 +2,6 @@ title: TypeScript Configuration description: Configuration options for the Fern TypeScript SDK. --- - You can customize the behavior of the TypeScript SDK generator in `generators.yml`: ```yml {7-8} @@ -29,6 +28,22 @@ Allow fields that are not defined in object schemas. This only applies to serde. The default timeout for network requests. In the generated client, this can be overridden at the request level. + +When enabled, generates models with full Pydantic validation support including proper field aliases for camelCase to snake_case conversion. This ensures proper serialization/deserialization when working with frameworks like FastAPI. + +```yaml +# generators.yml +config: + enablePydanticValidation: true +``` + +This will generate models with complete Pydantic field metadata including aliases: + +```python +created_at: typing_extensions.Annotated[dt.datetime, FieldMetadata(_alias_="createdAt")] = pydantic.Field(alias="createdAt") +``` + + When enabled, the inline schemas will be generated as nested types in TypeScript. This results in cleaner type names and a more intuitive developer experience. @@ -323,18 +338,30 @@ Prevent the generator from running any scripts such as `yarn format` or `yarn in - No serialization/deserialization code is generated by default. The client uses `JSON.parse()` and `JSON.stringify()` instead of the default Serde layer. - When `noSerdeLayer: false`, the generated client includes a layer for serializing requests and deserializing responses. This has three benefits: - - 1. The client validates requests and response at runtime (client-side). +When `noSerdeLayer: false`, the generated client includes a layer for serializing requests and deserializing responses. This has three benefits: - 1. The client can support complex types like `Date` and `Set`. - - 1. The generated types can stray from the wire/JSON representation to be more - idiomatic. For example, when the Serde layer is enabled (`noSerdeLayer: false`), all properties are `camelCase`, even if the server is expecting `snake_case`. +1. The client validates requests and responses at runtime (client-side). +2. The client can support complex types like `Date` and `Set`. +3. The generated types can use more idiomatic representations. For example: + - Properties use `camelCase` even when the API uses `snake_case` + - Pydantic validation is properly handled with field aliases + +Example configuration for proper Pydantic compatibility: +```yaml +generators: + - name: fernapi/fern-typescript-sdk + version: 0.7.1 + config: + noSerdeLayer: false # Enable serialization layer +``` +This ensures proper handling of: +- Field aliases between camelCase and snake_case +- Timestamp validation and conversion +- Nested object deserialization +- Custom field metadata @@ -491,7 +518,6 @@ interface ObjectWithLongAndBigInt { - When `useBrandedStringAliases` is disabled (the default), string aliases are generated as normal TypeScript aliases: @@ -517,28 +543,62 @@ interface ObjectWithLongAndBigInt { export type MyString = string & { __MyString: void }; export const MyString = (value: string): MyString => value as MyString; +``` + - export type OtherString = string & { __OtherString: void }; - export const OtherString = (value: string): OtherString => value as OtherString; - ``` + + Type: `object` + + Configure Pydantic validation behavior for Python SDK generation. Allows customizing how models handle validation, serialization, and deserialization. - ```typescript - // consuming the generated type + ```typescript + { + "pydantic": { + "useAliasGenerator": true, + "validateAssignment": true, + "allowPopulateByFieldName": true + } + } + ``` - function printMyString(s: MyString): void { - console.log("MyString: " + s); - } + Configuration options: - // doesn't compile, "foo" is not assignable to MyString - printMyString("foo"); + - `useAliasGenerator`: Generate alias fields for camelCase to snake_case conversion + - `validateAssignment`: Enable validation when assigning values to model fields + - `allowPopulateByFieldName`: Allow populating models using the aliased field names - const otherString = OtherString("other-string"); - // doesn't compile, otherString is not assignable to MyString - printMyString(otherString); + - // compiles - const myString = MyString("my-string"); - printMyString(myString); - ``` + + Type: `boolean` + + Enables generation of branded string types for additional type safety. + + ```typescript + export type MyString = string & { __MyString: void }; + export const MyString = (value: string): MyString => value as MyString; + + export type OtherString = string & { __OtherString: void }; + export const OtherString = (value: string): OtherString => value as OtherString; + ``` + + ```typescript + // consuming the generated type + + function printMyString(s: MyString): void { + console.log("MyString: " + s); + } + + // doesn't compile, "foo" is not assignable to MyString + printMyString("foo"); + + const otherString = OtherString("other-string"); + // doesn't compile, otherString is not assignable to MyString + printMyString(otherString); + + // compiles + const myString = MyString("my-string"); + printMyString(myString); + ``` \ No newline at end of file diff --git a/fern/products/sdks/sdks.yml b/fern/products/sdks/sdks.yml index e7124f10..91db1fb3 100644 --- a/fern/products/sdks/sdks.yml +++ b/fern/products/sdks/sdks.yml @@ -18,6 +18,8 @@ navigation: slug: capabilities - link: Customer Showcase href: https://buildwithfern.com/showcase + - page: Fastapi Integration + path: ./overview/python/fastapi-integration.mdx - section: Generators contents: - section: TypeScript @@ -41,8 +43,6 @@ navigation: slug: custom-code - changelog: ./overview/typescript/changelog slug: changelog - # - link: Customer Showcase - # href: https://buildwithfern.com/showcase - section: Python contents: - page: Quickstart @@ -59,12 +59,10 @@ navigation: path: ./overview/python/publishing-to-pypi.mdx slug: publishing - page: Adding custom code - path: ./overview/python/custom-code.mdx + path: ./overview/python/custom-code.mdx slug: custom-code - changelog: ./overview/python/changelog slug: changelog - # - link: Customer Showcase - # href: https://buildwithfern.com/showcase - section: Go contents: - page: Quickstart @@ -81,12 +79,10 @@ navigation: path: ./overview/go/publishing-to-go-package-manager.mdx slug: publishing - page: Adding custom code - path: ./overview/go/custom-code.mdx + path: ./overview/go/custom-code.mdx slug: custom-code - changelog: ./overview/go/changelog slug: changelog - # - link: Customer Showcase - # href: https://buildwithfern.com/showcase - section: Java contents: - page: Quickstart @@ -107,8 +103,6 @@ navigation: slug: custom-code - changelog: ./overview/java/changelog slug: changelog - # - link: Customer Showcase - # href: https://buildwithfern.com/showcase - section: .NET slug: csharp contents: @@ -131,8 +125,6 @@ navigation: slug: custom-code - changelog: ./overview/csharp/changelog slug: changelog - # - link: Customer Showcase - # href: https://buildwithfern.com/showcase - section: PHP contents: - page: Quickstart @@ -154,8 +146,6 @@ navigation: slug: custom-code - changelog: ./overview/php/changelog slug: changelog - # - link: Customer Showcase - # href: https://buildwithfern.com/showcase - section: Ruby contents: - page: Quickstart @@ -177,13 +167,11 @@ navigation: slug: custom-code - changelog: ./overview/ruby/changelog slug: changelog - # - link: Customer Showcase - # href: https://buildwithfern.com/showcase - page: MCP Server path: ./overview/mcp-server/introduction.mdx slug: mcp-server - section: Deep Dives - contents: + contents: - page: Customize Method Names path: ./guides/customize-method-names.mdx slug: customize-method-names @@ -210,7 +198,7 @@ navigation: path: ./guides/self-host-fern-generators.mdx slug: self-host-generators - section: Reference - contents: + contents: - page: generators.yml path: ./reference/generators-yml-reference.mdx slug: generators-yml