Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

HostAPI – The Routing Contract

In the previous chapter, we transferred shared value ownership from fallback storage into audiodeck.

We did this by injecting a HostAPI implementation.

Now we formalize what that means.

This chapter defines the HostAPI contract.


Why HostAPI Exists

A generated plugin never directly accesses storage.

It does not read from:

context.scene.qa_shared

It does not read from:

context.scene.audiodeck_props

Instead, whenever a shared value is:

  • Read
  • Drawn in the UI

The plugin calls:

host_api.get(context, key, fallback_prop)
host_api.draw(layout, context, key, fallback_prop, label=...)

The plugin speaks only in shared keys.

The host decides where those keys live.

That separation is the purpose of HostAPI.


Core Terms

Shared Key

A stable identifier defined in the plugin decorator:

shared={"audio_path": "project.audio_path"}

The plugin only knows:

"project.audio_path"

It does not know how that key is stored.


Fallback Property

A generated property name derived from the shared key:

project__audio_path

This exists so the plugin can function in standalone mode.

If the host does not serve a key, fallback storage is used.


Required Methods

A HostAPI implementation must define:

class HostAPI:

    def get(self, context, key: str, fallback_prop: str):
        """
        Return the value for shared key `key`.

        - `key` is the shared key (e.g. "project.audio_path")
        - `fallback_prop` is the generated fallback property name
        """
        ...

    def draw(self, layout, context, key: str, fallback_prop: str, *, label=None):
        """
        Draw the UI control for shared key `key`.

        Must draw something:
        - host-owned property
        - or fallback property
        """
        ...

These two methods form the entire routing contract.

Nothing else is required.


Fallback Behavior

If a host does not serve a shared key:

return getattr(context.scene.qa_shared, fallback_prop)

and

layout.prop(context.scene.qa_shared, fallback_prop, text=label)

This ensures:

  • Plugins remain functional without a host.
  • Hosts can adopt routing incrementally.
  • Shared ownership is optional, not mandatory.

Fallback is not a special case — it is part of the design.


Injection Model

When embedding a plugin, the host injects its HostAPI:

audio_encode_ops.register(
    mode="plugin",
    host_api=AudioDeckHostAPI()
)

The plugin never creates the HostAPI itself.

The host always controls:

  • Whether a HostAPI is provided
  • Which implementation is used

Control remains centralized.


Design Discipline

HostAPI is:

  • A routing layer
  • Not a transformation layer
  • Not a validation framework
  • Not a business logic container

It should:

  • Map keys to storage
  • Draw the correct property
  • Preserve fallback behavior

Nothing more.


What HostAPI Is Not

HostAPI does not:

  • Know which plugin requested a key
  • Store plugin identity
  • Mutate shared keys
  • Enforce global state

It is intentionally minimal.


Where We Are

At this point, you understand:

  • Plugins declare shared keys.
  • Plugins never access storage directly.
  • Hosts inject HostAPI.
  • HostAPI routes shared keys to storage.

In the next chapter, we will implement a clean and scalable routing pattern inside audiodeck.