RFC - Add an optional plugin context and remove pydantic from core

  • date: 2026-03-28
  • author: Francesco Bartoli
  • contact: xbartolone@gmail.com
  • status: draft
  • modified: 2026-03-29

Overview

This RFC describes the introduction of a PluginContext dataclass to inject optional dependencies in the plugin system and the migration of core pydantic models to standard library dataclasses.

Currently, pygeoapi plugins rely on global state for runtime dependencies (logger, locales) and use pydantic BaseModel for configuration and metadata validation in core modules. The following issues arise from this design:

  • plugins cannot receive per-instance dependencies, making unit testing difficult
  • pydantic is a required core dependency even though most providers do not use its validation features
  • the pydantic v1/v2 compatibility layer adds maintenance burden
  • downstream projects cannot easily extend or override validation behaviour

Proposed solution

Optional PluginContext class to add more dependencies to plugins

An optional context parameter is added to load_plugin() and the base classes (BaseProvider, BaseProcessor, BaseManager):

@dataclass
class PluginContext:
    config: Dict[str, Any]
    logger: Optional[Any] = None
    locales: Optional[List[str]] = None
    base_url: Optional[str] = None

Plugins that accept context gain access to injected dependencies. Plugins that do not are unaffected -- the parameter defaults to None and all base classes fall back to global state.

Dataclasses replacing pydantic in core

All pydantic BaseModel classes in pygeoapi/models/ are replaced with @dataclass equivalents that:

  • validate types at construction via a shared validate_type() utility
  • expose a model_dump(exclude_none=False) method for serialization
  • can be overridden downstream via duck typing (any class with the same interface satisfies a Protocol check)

The affected models are:

  • APIRules in pygeoapi/models/config.py
  • OAPIFormat in pygeoapi/models/openapi.py
  • TileMatrixSetEnumType, TileMatrixLimitsType, TwoDBoundingBoxType, LinkType, GeospatialDataType, StyleType, TilePointType, TileSetMetadata in pygeoapi/models/provider/base.py
  • VectorLayers, MVTTilesJson in pygeoapi/models/provider/mvt.py

Pydantic remains fully usable at the plugin level. Providers that need pydantic validation (e.g. MVT providers for tile metadata) can continue using it as a plugin-level dependency.

Implementation

PluginContext

  • PluginContext dataclass is added to pygeoapi/plugin.py
  • load_plugin() accepts an optional context keyword argument and forwards it to the plugin constructor
  • BaseProvider, BaseProcessor, BaseManager accept context: Optional[PluginContext] = None with typed parameter
  • base classes use injected logger/locales when provided, falling back to module-level globals otherwise

Dataclass migration

  • a validate_type() function is added to pygeoapi/models/validation.py providing runtime type checking equivalent to pydantic's behaviour
  • each BaseModel is replaced with a @dataclass that calls validate_type(self) in __post_init__
  • model_dump() is implemented on each dataclass to maintain serialization compatibility
  • MVTTilesJson accepts and silently ignores unknown kwargs and coerces string values to int where needed, matching pydantic's behaviour when instantiated from arbitrary JSON metadata

Backwards Compatibility Issues

This change is designed for full backwards compatibility:

  • context defaults to None -- existing plugins work unchanged
  • model_dump() method signature and behaviour are preserved
  • APIRules.create() factory method is preserved
  • all existing tests pass without modification (except test_models.py which switches from ModelFactory to DataclassFactory for polyfactory)

The api/ module continues to call load_plugin() without a context. Wiring PluginContext into the API layer is a natural follow-up but intentionally out of scope to keep this change focused and low-risk.

Testing

  • unit tests for all dataclass models (creation, validation, type errors, model_dump, exclude_none)
  • unit tests for PluginContext and dependency injection in plugin loading
  • integration tests verifying tile metadata serialization (test_tiles.py)
  • integration tests verifying APIRules through get_api_rules() and framework apps

Documentation

Documentation will be updated to reflect the new PluginContext parameter and the dataclass-based models.

Issue and Pull Request tracking

Issue: Summit discussion, meeting

Pull Request: #2307

Voting History

TBD