0
# Plugin System
1
2
Erdantic's plugin system provides extensible support for different data modeling libraries. The system allows registration of plugins that can identify and extract field information from various model types like Pydantic, attrs, dataclasses, and msgspec.
3
4
## Core Plugin Functions
5
6
```python { .api }
7
def list_plugins() -> list[str]:
8
"""List the keys of all registered plugins."""
9
10
def register_plugin(key: str, predicate_fn: ModelPredicate[_ModelType],
11
get_fields_fn: ModelFieldExtractor[_ModelType]):
12
"""Register a plugin for a specific model class type.
13
14
Args:
15
key (str): An identifier for this plugin.
16
predicate_fn (ModelPredicate): A predicate function to determine if an object is a class
17
of the model that is supported by this plugin.
18
get_fields_fn (ModelFieldExtractor): A function to extract fields from a model class that
19
is supported by this plugin.
20
"""
21
22
def get_predicate_fn(key: str) -> ModelPredicate:
23
"""Get the predicate function for a plugin by its key."""
24
25
def get_field_extractor_fn(key: str) -> ModelFieldExtractor:
26
"""Get the field extractor function for a plugin by its key."""
27
28
def identify_field_extractor_fn(tp: type) -> Optional[ModelFieldExtractor]:
29
"""Identify the field extractor function for a model type.
30
31
Args:
32
tp (type): A type annotation.
33
34
Returns:
35
ModelFieldExtractor | None: The field extractor function for a known model type, or None if
36
the model type is not recognized by any registered plugins.
37
"""
38
39
def load_plugins():
40
"""Load all core plugins.
41
42
This function is called automatically when erdantic is imported, but can be called
43
manually if needed. It attempts to load all core plugins (pydantic, attrs, dataclasses,
44
msgspec) and silently skips any with missing dependencies.
45
"""
46
```
47
48
## Plugin Protocol Types
49
50
```python { .api }
51
class ModelPredicate(Protocol[_ModelType_co]):
52
"""Protocol class for a predicate function for a plugin."""
53
54
def __call__(self, obj: Any) -> TypeGuard[_ModelType_co]: ...
55
56
class ModelFieldExtractor(Protocol[_ModelType_contra]):
57
"""Protocol class for a field extractor function for a plugin."""
58
59
def __call__(self, model: _ModelType_contra) -> Sequence[FieldInfo]: ...
60
```
61
62
## Required Imports
63
64
```python
65
from erdantic.plugins import (
66
list_plugins, register_plugin, get_predicate_fn,
67
get_field_extractor_fn, identify_field_extractor_fn,
68
load_plugins, ModelPredicate, ModelFieldExtractor
69
)
70
from erdantic.core import FieldInfo
71
from typing import Any, Optional, Sequence, TypeGuard, TypeVar, Protocol
72
```
73
74
## Built-in Plugins
75
76
Erdantic comes with built-in support for several popular data modeling libraries:
77
78
### Available Plugins
79
80
```python
81
# Check what plugins are currently available
82
from erdantic import list_plugins
83
print(list_plugins())
84
# Output: ['pydantic', 'pydantic_v1', 'dataclasses', 'attrs', 'msgspec']
85
```
86
87
### Pydantic Support
88
89
```python { .api }
90
def is_pydantic_model(obj: Any) -> TypeGuard[Type[pydantic.BaseModel]]:
91
"""Predicate function to determine if an object is a Pydantic model (not an instance)."""
92
93
def get_fields_from_pydantic_model(model: Type[pydantic.BaseModel]) -> list[FieldInfo]:
94
"""Given a Pydantic model, return a list of FieldInfo instances for each field in the model."""
95
96
def is_pydantic_v1_model(obj: Any) -> TypeGuard[Type[pydantic.v1.BaseModel]]:
97
"""Predicate function to determine if an object is a Pydantic V1 model."""
98
99
def get_fields_from_pydantic_v1_model(model: Type[pydantic.v1.BaseModel]) -> list[FieldInfo]:
100
"""Given a Pydantic V1 model, return a list of FieldInfo instances for each field in the model."""
101
```
102
103
## Usage Examples
104
105
### Listing Available Plugins
106
107
```python
108
from erdantic import list_plugins
109
110
# See all registered plugins
111
plugins = list_plugins()
112
print(f"Available plugins: {plugins}")
113
114
# Use in convenience functions to limit model search
115
from erdantic import draw
116
import my_models
117
118
# Only analyze Pydantic models in the module
119
draw(my_models, out="pydantic_only.png", limit_search_models_to=["pydantic"])
120
```
121
122
### Creating Custom Plugins
123
124
```python
125
from erdantic.plugins import register_plugin
126
from erdantic.core import FieldInfo, FullyQualifiedName
127
from typing import Any, TypeGuard
128
import inspect
129
130
# Example: Custom plugin for a hypothetical ORM
131
class MyORMModel:
132
"""Base class for custom ORM models."""
133
pass
134
135
def is_my_orm_model(obj: Any) -> TypeGuard[type]:
136
"""Check if object is a MyORM model class."""
137
return isinstance(obj, type) and issubclass(obj, MyORMModel)
138
139
def get_fields_from_my_orm_model(model: type) -> list[FieldInfo]:
140
"""Extract field information from MyORM model."""
141
fields = []
142
model_full_name = FullyQualifiedName.from_object(model)
143
144
# Example: Inspect class attributes that are field descriptors
145
for name, attr in inspect.getmembers(model):
146
if hasattr(attr, '__field_type__'): # Hypothetical field marker
147
field_info = FieldInfo.from_raw_type(
148
model_full_name=model_full_name,
149
name=name,
150
raw_type=attr.__field_type__
151
)
152
fields.append(field_info)
153
154
return fields
155
156
# Register the custom plugin
157
register_plugin(
158
key="my_orm",
159
predicate_fn=is_my_orm_model,
160
get_fields_fn=get_fields_from_my_orm_model
161
)
162
```
163
164
### Using Custom Plugins
165
166
```python
167
from erdantic import draw, list_plugins
168
169
# Verify custom plugin is registered
170
print(list_plugins()) # Should include "my_orm"
171
172
# Use the custom plugin
173
class User(MyORMModel):
174
# Custom field definitions
175
pass
176
177
class Post(MyORMModel):
178
# Custom field definitions
179
pass
180
181
# Generate diagram using custom models
182
draw(User, Post, out="custom_orm_models.png")
183
184
# Limit to only custom ORM models
185
draw(my_module, out="my_orm_only.png", limit_search_models_to=["my_orm"])
186
```
187
188
### Introspecting Plugin System
189
190
```python
191
from erdantic.plugins import get_predicate_fn, get_field_extractor_fn, identify_field_extractor_fn
192
from pydantic import BaseModel
193
194
# Get specific plugin functions
195
pydantic_predicate = get_predicate_fn("pydantic")
196
pydantic_extractor = get_field_extractor_fn("pydantic")
197
198
# Test predicate function
199
class MyModel(BaseModel):
200
name: str
201
202
print(pydantic_predicate(MyModel)) # True
203
print(pydantic_predicate(dict)) # False
204
205
# Get extractor for a specific model type
206
extractor = identify_field_extractor_fn(MyModel)
207
if extractor:
208
fields = extractor(MyModel)
209
for field in fields:
210
print(f"Field: {field.name} ({field.type_name})")
211
```
212
213
### Plugin-Based Model Discovery
214
215
```python
216
import inspect
217
from erdantic.plugins import identify_field_extractor_fn
218
219
def find_supported_models(module):
220
"""Find all classes in a module that are supported by registered plugins."""
221
supported_models = []
222
223
for name, obj in inspect.getmembers(module, inspect.isclass):
224
if obj.__module__ == module.__name__: # Only classes defined in this module
225
if identify_field_extractor_fn(obj) is not None:
226
supported_models.append(obj)
227
228
return supported_models
229
230
# Usage
231
import my_models
232
supported = find_supported_models(my_models)
233
print(f"Found {len(supported)} supported model classes")
234
```
235
236
### Error Handling
237
238
```python
239
from erdantic.plugins import get_predicate_fn, get_field_extractor_fn
240
from erdantic.exceptions import PluginNotFoundError
241
242
try:
243
# Try to get a non-existent plugin
244
predicate = get_predicate_fn("nonexistent")
245
except PluginNotFoundError as e:
246
print(f"Plugin not found: {e.key}")
247
248
try:
249
extractor = get_field_extractor_fn("nonexistent")
250
except PluginNotFoundError as e:
251
print(f"Plugin not found: {e.key}")
252
```
253
254
## Built-in Plugin Details
255
256
### Core Plugins Available
257
258
1. **`"pydantic"`** - Pydantic V2 models (`pydantic.BaseModel`)
259
2. **`"pydantic_v1"`** - Pydantic V1 legacy models (`pydantic.v1.BaseModel`)
260
3. **`"dataclasses"`** - Python standard library dataclasses
261
4. **`"attrs"`** - attrs library models
262
5. **`"msgspec"`** - msgspec library models
263
264
### Plugin Loading
265
266
```python
267
# Plugins are automatically loaded when erdantic is imported
268
import erdantic # This triggers plugin loading
269
270
# Manual plugin loading (normally not needed)
271
from erdantic.plugins import load_plugins
272
load_plugins()
273
```
274
275
### Plugin Dependencies
276
277
Some plugins require optional dependencies:
278
279
```python
280
# These will only be available if dependencies are installed:
281
# - "attrs" plugin requires: pip install attrs
282
# - "msgspec" plugin requires: pip install msgspec
283
284
# Check which plugins are actually active
285
from erdantic import list_plugins
286
active_plugins = list_plugins()
287
print(f"Active plugins: {active_plugins}")
288
```