Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions docs/development/rfc/6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
title: RFC6 - Add an optional plugin context and remove pydantic from core
---

# 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`):

```python
@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](https://github.com/geopython/pygeoapi/wiki/Code-Sprint-2024-02-29-to-2024-03-01#notes), [meeting](https://github.com/geopython/pygeoapi/wiki/Meeting-2025-05-02)

Pull Request: [#2307](https://github.com/geopython/pygeoapi/pull/2307)

## Voting History

TBD
1 change: 1 addition & 0 deletions docs/development/rfc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ the project.
* [RFC3: Architectural Decision Records](3)
* [RFC4: GitHub Issue and Pull Request management](4)
* [RFC5: Enhanced data limit handling](5)
* [RFC6: Add an optional plugin context and remove pydantic from core](6)
Loading