0
# Plugin Interfaces
1
2
Abstract interfaces and base classes that define the plugin architecture for loaders, processors, renderers, and additional functionality like source linking and development servers.
3
4
## Capabilities
5
6
### Core Interfaces
7
8
Base interfaces that form the foundation of pydoc-markdown's plugin system.
9
10
```python { .api }
11
class Context:
12
"""
13
Context data passed to plugins during initialization.
14
15
Attributes:
16
directory: Working directory for plugin operations
17
"""
18
directory: str
19
20
def __init__(self, directory: str) -> None:
21
"""
22
Initialize context with working directory.
23
24
Args:
25
directory: Working directory path
26
"""
27
28
class PluginBase(ABC):
29
"""
30
Abstract base class for all pydoc-markdown plugins.
31
32
Provides basic plugin lifecycle management.
33
"""
34
35
def init(self, context: Context) -> None:
36
"""
37
Initialize plugin with context data.
38
39
Args:
40
context: Context containing directory and other initialization data
41
"""
42
```
43
44
### Loader Interface
45
46
Interface for loading documentation data from various sources.
47
48
```python { .api }
49
class Loader(PluginBase):
50
"""
51
Interface for loading documentation data.
52
53
Loaders extract API information from source code, documentation files,
54
or other sources and convert them into docspec.Module objects.
55
"""
56
57
def load(self) -> Iterable[docspec.Module]:
58
"""
59
Load documentation data.
60
61
Returns:
62
Iterable of docspec.Module objects containing API information
63
64
Raises:
65
LoaderError: If loading fails
66
"""
67
68
class LoaderError(Exception):
69
"""Exception raised when loader operations fail."""
70
```
71
72
### Processor Interface
73
74
Interface for transforming and enhancing loaded documentation data.
75
76
```python { .api }
77
class Processor(PluginBase):
78
"""
79
Interface for processing and transforming docspec.Module objects.
80
81
Processors typically modify docstrings, filter content, resolve references,
82
or apply other transformations to prepare data for rendering.
83
"""
84
85
def process(self, modules: List[docspec.Module], resolver: Optional[Resolver]) -> None:
86
"""
87
Process modules in place.
88
89
Args:
90
modules: List of modules to process (modified in place)
91
resolver: Optional resolver for handling cross-references
92
"""
93
```
94
95
### Renderer Interface
96
97
Interface for generating documentation output in various formats.
98
99
```python { .api }
100
class Renderer(PluginBase):
101
"""
102
Interface for rendering docspec.Module objects to output files.
103
104
Renderers generate final documentation in formats like Markdown, HTML,
105
or integration with static site generators.
106
"""
107
108
def process(self, modules: List[docspec.Module], resolver: Optional[Resolver]) -> None:
109
"""
110
Optional processing step before rendering.
111
112
Args:
113
modules: List of modules to process
114
resolver: Optional resolver for cross-references
115
"""
116
117
def get_resolver(self, modules: List[docspec.Module]) -> Optional[Resolver]:
118
"""
119
Get resolver for cross-reference handling.
120
121
Args:
122
modules: List of modules for resolver context
123
124
Returns:
125
Resolver instance or None if not supported
126
"""
127
128
def render(self, modules: List[docspec.Module]) -> None:
129
"""
130
Render modules to output format.
131
132
Args:
133
modules: List of modules to render
134
"""
135
```
136
137
### Resolver Interfaces
138
139
Interfaces for resolving cross-references between API objects.
140
141
```python { .api }
142
class Resolver(ABC):
143
"""
144
Interface for resolving cross-references to hyperlinks.
145
146
Used by processors to convert references in docstrings to URLs or other
147
link formats appropriate for the target documentation format.
148
"""
149
150
def resolve_ref(self, scope: docspec.ApiObject, ref: str) -> Optional[str]:
151
"""
152
Resolve reference to a hyperlink.
153
154
Args:
155
scope: API object containing the reference
156
ref: Reference string to resolve
157
158
Returns:
159
Resolved hyperlink or None if resolution fails
160
"""
161
162
class ResolverV2(ABC):
163
"""
164
New-style resolver interface that returns resolved objects directly.
165
166
Provides more flexibility than the original Resolver interface by
167
returning the actual resolved API object instead of a string representation.
168
"""
169
170
def resolve_reference(self, suite: ApiSuite, scope: docspec.ApiObject, ref: str) -> Optional[docspec.ApiObject]:
171
"""
172
Resolve reference to an API object.
173
174
Args:
175
suite: API suite containing all available objects
176
scope: API object containing the reference
177
ref: Reference string to resolve
178
179
Returns:
180
Resolved API object or None if resolution fails
181
"""
182
```
183
184
### Additional Renderer Interfaces
185
186
Specialized renderer interfaces for specific rendering capabilities.
187
188
```python { .api }
189
class SinglePageRenderer(PluginBase):
190
"""
191
Interface for renderers that can generate single-page output.
192
"""
193
194
def render_single_page(
195
self,
196
fp: TextIO,
197
modules: List[docspec.Module],
198
page_title: Optional[str] = None
199
) -> None:
200
"""
201
Render modules to a single page.
202
203
Args:
204
fp: File object to write output to
205
modules: List of modules to render
206
page_title: Optional title for the page
207
"""
208
209
class SingleObjectRenderer(PluginBase):
210
"""
211
Interface for renderers that can render individual API objects.
212
"""
213
214
def render_object(self, fp: TextIO, obj: docspec.ApiObject, options: Dict[str, Any]) -> None:
215
"""
216
Render a single API object.
217
218
Args:
219
fp: File object to write output to
220
obj: API object to render
221
options: Rendering options and configuration
222
"""
223
```
224
225
### Server Interface
226
227
Interface for development server functionality.
228
229
```python { .api }
230
class Server(ABC):
231
"""
232
Interface for development server capabilities.
233
234
Renderers implementing this interface can provide live preview
235
functionality with automatic reloading when source files change.
236
"""
237
238
def get_server_url(self) -> str:
239
"""
240
Get the development server URL.
241
242
Returns:
243
URL where the development server is accessible
244
"""
245
246
def start_server(self) -> subprocess.Popen:
247
"""
248
Start the development server process.
249
250
Returns:
251
Process handle for the running server
252
"""
253
254
def reload_server(self, process: subprocess.Popen) -> subprocess.Popen:
255
"""
256
Reload server process when files change.
257
258
Args:
259
process: Current server process
260
261
Returns:
262
New server process or same process if reload not needed
263
"""
264
```
265
266
### Builder Interface
267
268
Interface for build functionality after rendering.
269
270
```python { .api }
271
class Builder(ABC):
272
"""
273
Interface for renderers that support building final output.
274
275
Builders can generate final static sites, optimize output,
276
or perform other post-rendering operations.
277
"""
278
279
def build(self, site_dir: str) -> None:
280
"""
281
Build final output after rendering.
282
283
Args:
284
site_dir: Directory where build output should be placed
285
"""
286
```
287
288
### Source Linker Interface
289
290
Interface for linking documentation to source code.
291
292
```python { .api }
293
class SourceLinker(PluginBase):
294
"""
295
Interface for generating links to source code.
296
297
Source linkers determine URLs to the original source code for
298
API objects, enabling documentation to include "view source" links.
299
"""
300
301
def get_source_url(self, obj: docspec.ApiObject) -> Optional[str]:
302
"""
303
Get URL to source code for an API object.
304
305
Args:
306
obj: API object to get source URL for
307
308
Returns:
309
URL to source code or None if not available
310
"""
311
```
312
313
## Plugin Implementation Examples
314
315
### Custom Loader Example
316
317
```python
318
from pydoc_markdown.interfaces import Loader, Context
319
import docspec
320
321
class CustomLoader(Loader):
322
def __init__(self, source_path: str):
323
self.source_path = source_path
324
325
def init(self, context: Context) -> None:
326
# Initialize with context
327
self.working_dir = context.directory
328
329
def load(self) -> Iterable[docspec.Module]:
330
# Load from custom source
331
module = docspec.Module(
332
name="custom_module",
333
location=docspec.Location("custom.py", 1),
334
docstring="Custom loaded module"
335
)
336
return [module]
337
```
338
339
### Custom Processor Example
340
341
```python
342
from pydoc_markdown.interfaces import Processor, Resolver
343
import docspec
344
345
class CustomProcessor(Processor):
346
def __init__(self, transform_pattern: str):
347
self.transform_pattern = transform_pattern
348
349
def process(self, modules: List[docspec.Module], resolver: Optional[Resolver]) -> None:
350
for module in modules:
351
for obj in module.members:
352
if obj.docstring:
353
# Apply custom transformation
354
obj.docstring = obj.docstring.replace(
355
self.transform_pattern,
356
"**" + self.transform_pattern + "**"
357
)
358
```
359
360
### Custom Renderer Example
361
362
```python
363
from pydoc_markdown.interfaces import Renderer, Resolver
364
import docspec
365
366
class CustomRenderer(Renderer):
367
def __init__(self, output_file: str):
368
self.output_file = output_file
369
370
def render(self, modules: List[docspec.Module]) -> None:
371
with open(self.output_file, 'w') as f:
372
for module in modules:
373
f.write(f"# {module.name}\n\n")
374
if module.docstring:
375
f.write(f"{module.docstring}\n\n")
376
377
for obj in module.members:
378
f.write(f"## {obj.name}\n\n")
379
if obj.docstring:
380
f.write(f"{obj.docstring}\n\n")
381
```
382
383
### Custom Source Linker Example
384
385
```python
386
from pydoc_markdown.interfaces import SourceLinker
387
import docspec
388
389
class GitHubSourceLinker(SourceLinker):
390
def __init__(self, repo_url: str, branch: str = "main"):
391
self.repo_url = repo_url.rstrip('/')
392
self.branch = branch
393
394
def get_source_url(self, obj: docspec.ApiObject) -> Optional[str]:
395
if obj.location and obj.location.filename:
396
return f"{self.repo_url}/blob/{self.branch}/{obj.location.filename}#L{obj.location.lineno}"
397
return None
398
```
399
400
## Plugin Registration
401
402
Plugins are typically registered via entry points in setup.py or pyproject.toml:
403
404
### Poetry Configuration (pyproject.toml)
405
406
```toml
407
[tool.poetry.plugins."pydoc_markdown.interfaces.Loader"]
408
custom = "mypackage.loaders:CustomLoader"
409
410
[tool.poetry.plugins."pydoc_markdown.interfaces.Processor"]
411
custom = "mypackage.processors:CustomProcessor"
412
413
[tool.poetry.plugins."pydoc_markdown.interfaces.Renderer"]
414
custom = "mypackage.renderers:CustomRenderer"
415
416
[tool.poetry.plugins."pydoc_markdown.interfaces.SourceLinker"]
417
custom = "mypackage.linkers:CustomSourceLinker"
418
```
419
420
### Using Custom Plugins
421
422
```yaml
423
# pydoc-markdown.yml
424
loaders:
425
- type: custom
426
source_path: "/path/to/source"
427
428
processors:
429
- type: custom
430
transform_pattern: "TODO"
431
432
renderer:
433
type: custom
434
output_file: "custom-docs.md"
435
```
436
437
## Interface Compatibility
438
439
### Multiple Interface Implementation
440
441
Renderers can implement multiple interfaces for enhanced functionality:
442
443
```python
444
class AdvancedRenderer(Renderer, Server, Builder, SinglePageRenderer):
445
def render(self, modules: List[docspec.Module]) -> None:
446
# Standard rendering
447
pass
448
449
def get_server_url(self) -> str:
450
return "http://localhost:8000"
451
452
def start_server(self) -> subprocess.Popen:
453
# Start development server
454
pass
455
456
def build(self, site_dir: str) -> None:
457
# Build final output
458
pass
459
460
def render_single_page(self, fp: TextIO, modules: List[docspec.Module], page_title: Optional[str] = None) -> None:
461
# Single page rendering
462
pass
463
```
464
465
### Interface Detection
466
467
Check renderer capabilities at runtime:
468
469
```python
470
from pydoc_markdown.interfaces import Server, Builder
471
472
# Check if renderer supports server
473
if isinstance(renderer, Server):
474
server_url = renderer.get_server_url()
475
process = renderer.start_server()
476
477
# Check if renderer supports building
478
if isinstance(renderer, Builder):
479
renderer.build("/path/to/site/")
480
```