Registry And Serialization

The most distinctive part of fastkml is its registry. Instead of teaching every model class how to parse and emit every field itself, the library stores attribute-to-XML rules in a shared Registry declared in fastkml/registry.py. That registry is then consumed by _XMLObject in fastkml/base.py for both directions: parsing with _get_kwargs() and serialization with populate_element().

This matters because it explains both the extensibility story and the consistency story. Once a class has registry items for an attribute, that field participates in from_string(), class_from_element(), to_string(), and validate() automatically. The same helper functions in fastkml/helpers.py are reused across modules, so a subelement text field, XML attribute, enum, or repeated child list behaves the same everywhere. A RegistryItem ties four things together: the attr_name on the Python class, the node_name in the XML, a get_kwarg helper used while parsing, and a set_element helper used while serializing — both consumed by _XMLObject._get_kwargs() and _XMLObject.populate_element() respectively.

How It Relates To Other Concepts

  • The document model (KML Document Model) relies on the registry for feature lists.

  • Geometry wrappers (Geometry Bridge) rely on the registry for coordinates, boundaries, and altitude modes.

  • The extension story relies on the registry because custom classes become parseable only after you register them (see the “Extending FastKML” section of Working with KML Files).

How It Works Internally

RegistryItem stores the namespace IDs to search, the Python classes allowed for a field, the target XML node name, and the helper functions to read and write the field. registry.get(cls) walks the class MRO, so subclasses inherit registrations from their parents. That is why Document automatically gets the core feature fields defined for _Feature and _BaseObject without duplicating registration code.

The delayed registrations in fastkml/_registry_setup.py are a useful clue about the architecture. Those registrations are separated only to avoid circular imports. The package import in fastkml/__init__.py loads _registry_setup for side effects, then discards the module name. If you are extending fastkml with custom classes, that is the pattern to follow: define the class, register its fields, and make sure that registration code runs before parsing.

Basic Usage

>>> from fastkml import KML

>>> doc = """<kml xmlns="http://www.opengis.net/kml/2.2">
... <Document>
...   <name>Demo</name>
... </Document>
... </kml>"""

>>> k = KML.from_string(doc)
>>> print(k.features[0].name)
Demo
>>> print(k.to_string(prettyprint=True))
<kml xmlns="http://www.opengis.net/kml/2.2">
  <Document>
    <name>Demo</name>
  </Document>
</kml>

Advanced Usage

>>> from fastkml import Document, KML
>>> from fastkml.helpers import (
...     text_subelement,
...     subelement_text_kwarg,
...     xml_subelement_list,
...     xml_subelement_list_kwarg,
... )
>>> from fastkml.kml_base import _BaseObject
>>> from fastkml.registry import RegistryItem, registry

>>> class AuditTag(_BaseObject):
...     def __init__(
...         self,
...         ns=None,
...         name_spaces=None,
...         id=None,
...         target_id=None,
...         label=None,
...         **kwargs,
...     ) -> None:
...         super().__init__(
...             ns=ns, name_spaces=name_spaces, id=id, target_id=target_id, **kwargs
...         )
...         self.label = label
...

>>> registry.register(
...     AuditTag,
...     RegistryItem(
...         ns_ids=("kml", ""),
...         attr_name="label",
...         node_name="name",
...         classes=(str,),
...         get_kwarg=subelement_text_kwarg,
...         set_element=text_subelement,
...     ),
... )
>>> registry.register(
...     Document,
...     RegistryItem(
...         ns_ids=("kml", ""),
...         attr_name="audit_tags",
...         node_name="AuditTag",
...         classes=(AuditTag,),
...         get_kwarg=xml_subelement_list_kwarg,
...         set_element=xml_subelement_list,
...     ),
... )

>>> k = KML.from_string(
...     '<kml xmlns="http://www.opengis.net/kml/2.2">'
...     "<Document><AuditTag><name>verified</name></AuditTag></Document></kml>"
... )
>>> print(k.features[0].audit_tags[0].label)
verified

Warning

Custom registration must happen before you call KML.parse() or from_string(). If the registry does not know about your custom element at parse time, the XML will be ignored or fail validation, and the missing data will not magically appear later.

Why fastkml prefers declarative registration over custom parsers

Declarative registration keeps parsing rules centralized and reusable. The same helper that reads a styleUrl node can read a custom text node elsewhere, and inherited classes automatically pick up the registrations from their parents. The price is that debugging sometimes means tracing registry entries rather than just reading one class body. For a library that mirrors a large XML spec, that trade-off pays off because it drastically reduces duplication.

When to override behavior instead of registering fields

Most extensions should use more RegistryItem entries, not custom parsing methods, because registration composes cleanly with inheritance and shared helpers. Overriding _get_kwargs() or etree_element() is best reserved for cases where XML structure truly does not map to the helper model, or when a class needs to synthesize constructor state from multiple nodes at once. That keeps custom code small and lets the common XML behaviors continue to work. If you override too early, you lose a lot of the maintainability benefits that make fastkml attractive in the first place.