xsdata Python XML Binding

repository·main·Indexed 19 days ago

https://github.com/tefra/xsdata

A data binding library for Python that allows developers to work with XML and JSON documents as Python dataclasses. It includes a CLI for generating models from XML schemas (.xsd), DTDs, WSDL definitions, or existing XML/JSON documents, and provides optimized parsers and serializers to convert these documents into typed Python objects.

Tokens
23.5K
Snippets
83
Records
122
Agent score
65%

What's inside xsdata

  1. Overview of xsData features

    main

    xsData is a data binding library for Python that provides:

    Code Generation

    • Support for XML Schemas 1.0 & 1.1, WSDL 1.1 (with SOAP 1.1), and DTD.
    • Generation directly from XML and JSON documents.
    • Pluggable code writers for custom output formats.
    • Output consists of pure Python dataclasses with type hints, enumerations, and support for namespaces.

    Data Binding

    • Optimized XML and JSON parsers and serializers.
    • Support for wildcard elements/attributes, xinclude statements, and unknown properties.
    • Multiple handlers and writers (based on lxml or native Python XML).
    • Customization via configuration properties.
  2. How class validation works during code generation

    main

    During the analysis phase, the xsdata.codegen.validator.ClassValidator performs several cleanup tasks to ensure the generated classes are valid and usable:

    • Remove types with unknown references: Deletes types that point to missing or unknown elements (e.g., <xs:element name="root" ref="xs:missingOrUnknown"/>).
    • Remove duplicate types: If multiple definitions exist for the same type, the last definition is kept.
    • Remove duplicate overridden types: Cleans up duplicate <xs:override> definitions.
    • Merge redefined types: Handles <xs:redefine> by merging the redefined types into the existing structure.
  3. Understand XmlHandlers and performance

    main

    xsData uses XmlHandlers to read XML sources and trigger build events. There are two primary handlers:

    1. LxmlEventHandler: Used if lxml is installed. Generally offers different performance characteristics and features.
    2. XmlEventHandler: The native Python handler used if lxml is not available.

    It is recommended to test both handlers for your specific use case, as results may vary. You can also extend these handlers to customize behavior or optimize performance.

  4. Handle type conversion and validation in xsdata

    main

    xsdata uses Python dataclasses for data models. It is important to note that dataclasses do not validate values during instantiation or field assignment. Automatic conversion from raw data (XML/JSON) to Python types occurs only during the parsing process.

    If the parser encounters a type discrepancy, it handles it leniently and issues a ConverterWarning. To treat these warnings as errors, enable the fail_on_converter_warnings option in your ParserConfig.

  5. How the xsdata code generation procedure works

    main

    The code generation process is orchestrated by the xsdata.codegen.transformer.ResourceTransformer. The procedure follows a linear pipeline:

    1. Load Resources: The generator accepts URIs for local or remote files. Resource types (xsd, wsdl, dtd, xml, json) are identified by file extensions or syntax markings. Circular imports are handled by loading resources only once.
    2. Parse transfer objects: Resource-specific parsers bind document information to transfer objects and assign common values like namespace prefix-URI maps.
    3. Convert to classes: Mappers convert transfer objects into codegen classes based on the specific resource type logic.
    4. Analyze classes: The classes undergo validation and a multi-step processing pipeline to refine the class structure.
    5. Write Output: The final classes are written to the target destination.
  6. Handle Wildcards and Attributes (xs:any / xs:anyAttribute)

    main

    To handle unknown or dynamic XML content, use the Wildcard or Attributes types:

    Wildcards (xs:any)

    Use metadata={"type": "Wildcard"}. If the field is an object, the parser will use AnyElement to capture the tag, text, and attributes. You can use namespace generics:

    • ##any: Any namespace.
    • ##other: Any namespace except the parent's.
    • ##local: No namespace.
    • ##targetNamespace: The parent's namespace.

    Attributes (xs:anyAttribute)

    Use metadata={"type": "Attributes"}. The field must be a dict. It will capture all attributes not explicitly defined in the class.

    # Wildcard Example
    @dataclass
    class Root:
        any: object = field(metadata={"type": "Wildcard"})
    
    # Attributes Example
    @dataclass
    class Root:
        known: int = field(metadata={"type": "Attribute"})
        attrs: dict = field(metadata={"type": "Attributes"})
  7. Handle mixed content with choices

    main

    For XML elements containing mixed content (text and elements) where the elements belong to a known set of types, you can use the mixed: True metadata flag. This allows you to skip wrapping instances in generic models by defining choices in the field metadata. During serialization, xsdata will attempt to match the objects in the list to the specified types and namespaces.

    @dataclass
    class Doc:
        class Meta:
            name = "doc"
    
        content: List[object] = field(
            default_factory=list,
            metadata={
                 "type": "Wildcard",
                 "namespace": "##any",
                 "mixed": True,
                 "choices": (
                     {"name": "a", "type": Alpha, "namespace": ""},
                     {"name": "b", "type": Beta, "namespace": ""},
                 ),
            }
        )
  8. Configure class metadata using a Meta inner class

    main
    To access advanced serialization features, you can define a nested Meta class within your dataclass. This allows you to control how the class is represented in the output format (e.g., XML/JSON) without changing the Python attribute names.
  9. Parse JSON without an explicit target class

    main

    If you do not provide a target class, the parser attempts to scan all imported modules to find a matching dataclass.

    Warning: The class locator only works if the dataclass includes all properties present in the input JSON. This process will fail for documents containing unknown properties, even if fail_on_unknown_properties is set to False in the configuration.

    # The parser scans imports to find a match
    order = parser.parse("tests/fixtures/books/books.json")
    print(type(order))
  10. Manage binding metadata with XmlContext

    main

    All binding metadata in xsdata is managed within an XmlContext instance. To ensure consistency and performance, it is highly recommended to reuse the same XmlContext instance across your parser and serializer instances. This allows the library to cache metadata efficiently.

    from xsdata.formats.dataclass.context import XmlContext
    from xsdata.formats.dataclass.parsers import XmlParser, JsonParser
    from xsdata.formats.dataclass.serializers import XmlSerializer, JsonSerializer
    
    # Create a single context to be shared
    context = XmlContext()
    
    # Reuse the context in all components
    xml_parser = XmlParser(context=context)
    json_parser = JsonParser(context=context)
    xml_serializer = XmlSerializer(context=context)
    json_serializer = JsonSerializer(context=context)
  11. Understand why non-nullable fields are marked as optional

    main
    In Python, a TypeError occurs if a dataclass field without a default value follows a field that has a default value. To prevent this and support non-nullable fields correctly, xsdata (which requires Python 3.10+) generates dataclasses using kw_only=True. This allows non-nullable fields to follow fields with default values without violating Python's argument ordering rules.
  12. Use AnyElement and DerivedElement for wildcards

    main

    To handle XML schema wildcards (<xs:any>), xsdata provides two generic models:

    1. AnyElement: Represents any XML structure, similar to a DOM Element. It supports qname, children, text, and attributes.
    2. DerivedElement: A wrapper used for type substitution (e.g., <b xsi:type="a">...</b>).

    These are typically used within dataclasses where the field metadata specifies "type": "Wildcard".

    from xsdata.formats.dataclass.models.generics import AnyElement, DerivedElement
    
    obj = MetadataType(
        any_element=[
            AnyElement(
                qname="bar",
                children=[
                    AnyElement(qname="first", text="1st", attributes={"a": "1"}),
                    DerivedElement(
                        qname="fourth",
                        value=MetadataType(other_attributes={"c": "3"})
                    )
                ]
            )
        ]
    )