apischema

repository·master·Indexed 42 days ago

https://github.com/wyfo/apischema

A high-performance Python library for JSON (de)serialization, GraphQL schema generation, and JSON Schema (Draft 2020-12) generation using Python typing and dataclasses. Version 0.19.0 provides tools for validation via ValidationError, custom serialization/deserialization decorators, polymorphic type discrimination, and conditional field requirements.

Tokens
31.9K
Snippets
104
Records
159
Agent score
54%

What's inside apischema

  1. Core capabilities of apischema

    master

    apischema is a library designed for API data handling with the following primary capabilities:

    • JSON (de)serialization: Convert between Python objects and JSON.
    • GraphQL: Full GraphQL library support.
    • JSON Schema Generation: Generate JSON schemas from Python typing.

    Key design principles include:

    • Standard Library Proximity: Uses standard dataclasses and typing to ensure compatibility with IDEs, linters, and editors without requiring plugins.
    • No Base Class Requirement: Does not require models to inherit from a specific base class, allowing it to handle any type (e.g., Foo, list[Bar], NewType) identically.
    • Adaptability: Supports user-defined types and foreign library types (like ORM models) via a conversions feature.
  2. Use relay.GlobalId for ID serialization

    master

    While Node[Id] uses your internal ID type, Relay requires a global identifier (usually a combination of type and ID). apischema provides relay.GlobalId to handle this.

    • Automatic Conversion: apischema adds a field converter that automatically transforms your internal Id (like a UUID) into a GlobalId (serialized as ID!) and vice versa.
    • Accessing ID: You can retrieve the serialized global ID of a node instance using the .global_id property.
    • Encoding: To follow Relay best practices, pass relay.base64_encoding to the id_encoding parameter of graphql_schema to encode these IDs in Base64.
    from apischema.graphql.relay import GlobalId, base64_encoding
    from apischema import graphql_schema
    
    # When creating the schema, enable base64 encoding
    schema = graphql_schema(..., id_encoding=base64_encoding)
  3. Customizing validation in apischema

    master

    Beyond basic type checking (e.g., ensuring a value is an integer), apischema supports advanced validation:

    • JSON Schema Validation: Use standard constraints like maxItems, pattern, etc., which are embedded in your generated schema.
    • Custom Python Validators: Add custom validation logic for specific classes, fields, or NewType definitions.
  4. Use composition over inheritance with flattened fields

    master
    Instead of creating complex inheritance hierarchies, apischema allows you to build complex models from smaller fragments using composition. The flattened fields feature automatically handles the merging of fields from composed dataclasses, keeping your code simple and modular.
  5. How apischema conversions work

    master

    An apischema conversion allows you to support custom classes or third-party libraries by defining how to transform data between a Source type and a Target type.

    • Deserialization: apischema looks for a conversion where the input type is the Target. It deserializes the Source type first, then applies a converter function (Source) -> Target.
    • Serialization: apischema looks for a conversion where the input type is the Source. It applies the converter (Source) -> Target to get the Target type, which is then serialized.
    • Schema Generation: Conversions are integrated into JSON Schema generation. For deserialization schemas, the source schema is merged into the target; for serialization, the target schema is merged into the source.
  6. Handle missing vs null values with Undefined

    master

    To distinguish between a null field and an absent/undefined field (useful for HTTP PATCH requests), apischema provides an Undefined constant.

    • Use Undefined as a default value to mark a field as absent.
    • Dataclass/NamedTuple fields are ignored during serialization if their value is Undefined.
    • Constraint: Undefined should only be used inside a Union (e.g., Union[str, Undefined]).

    Alternatively, you can use None as a placeholder for missing values by applying the none_as_undefined metadata. This makes the type non-nullable and prevents serialization if the value is None.

    from typing import Optional, Union
    from dataclasses import dataclass
    from apischema import Undefined
    
    @dataclass
    class UserUpdate:
        # Field is absent if not provided in input
        name: Union[str, Undefined] = Undefined
        
        # Using None as Undefined via metadata
        age: Optional[int] = field(metadata={"none_as_undefined": True})
  7. Configure Mutation Resolver Arguments

    master

    Relay mutations are treated as operations. You can configure them using standard operation parameters (like error_handler).

    In apischema, these are passed as class variables to the Mutation subclass, prefixed with an underscore _.

    Tip: You can define these parameters in a base class to share configuration (like a specific error handler) across multiple mutation classes.

  8. Distinguish between `null` and `undefined` in Resolvers

    master

    To distinguish between a user providing an explicit null value versus omitting a field entirely (undefined), use apischema.Undefined as the default value for resolver parameters.

    • null input: Results in a None argument passed to the resolver.
    • Absent input: Results in the Undefined default value being used.
  9. Reuse data models for both GraphQL and REST APIs

    master
    You can use the same classes for both GraphQL and REST APIs. While GraphQL has certain restrictions compared to JSON Schema (which should be reviewed in the data model and resolvers documentation), your code remains highly reusable. GraphQL endpoints can be exposed via a GraphQL API or used within traditional REST/RPC patterns.
  10. Understand the core differences between apischema and Pydantic

    master

    If you are deciding between apischema and Pydantic, consider these architectural differences:

    • Performance: apischema is significantly faster, particularly for serialization.
    • Standardization: apischema uses standard Python dataclasses and types. Unlike Pydantic, it does not require inheriting from a BaseModel, meaning your code remains decoupled from the library and is easier to reuse with external libraries.
    • Tooling: Because it uses standard types, apischema does not require special plugins for Mypy, linters, or IDEs to provide correct type checking.
    • Separation of Concerns: apischema uses dedicated functions for (de)serialization. In Pydantic, the model constructor often acts as the deserializer. With apischema, your dataclasses are instantiated normally, and (de)serialization is a separate I/O step.
    • Approach: apischema follows a functional approach (using simple functions), whereas Pydantic follows an object-oriented approach (relying on BaseModel methods).
    • Complexity Management: apischema supports features like Generic types, forward type references (recursive models), and composition (flattened fields) out-of-the-box without extra configuration or method calls like update_forward_refs.
  11. How apischema handles data models and types

    master

    apischema uses Python's type hinting system to define data models. It supports several standard Python features to construct complex schemas:

    • Dataclasses: The primary way to define models. apischema assumes the dataclass __init__ can be called using all its fields as keyword arguments.
    • Standard Library Types: Uses standard types (e.g., int, str, list, dict) for schema definition.
    • Generics: Supports generic types for defining collections and complex structures.
    • Recursive Types: Supports recursive definitions using string annotations or PEP 563 (from __future__ import annotations).
    • Annotated (PEP 593): Uses typing.Annotated to attach metadata to types, which can be used for validation or schema constraints.
    • Custom Types: Allows for custom type definitions.
    • Object Types: Supports dataclass-like types for representing structured objects.

    Handling Nullability

    In apischema, None is used to represent both null and undefined in the resulting schema. Use None in your type hints to indicate that a field is nullable.