Skip to content

Commit e74dd63

Browse files
committed
Added development docs.
1 parent 8d63b5a commit e74dd63

File tree

2 files changed

+220
-22
lines changed

2 files changed

+220
-22
lines changed

README.md

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -77,34 +77,18 @@ See [examples/config.yaml](./examples/config.yaml) for a complete configuration
7777
#### Outputs
7878
- `mqtt`: Publish events to MQTT broker
7979

80-
## Testing
81-
82-
Install test dependencies and run tests with `uv`:
83-
84-
```bash
85-
uv sync --extra test
86-
uv run pytest
87-
```
88-
8980
## Development
9081

91-
Install development dependencies including pre-commit:
92-
93-
```bash
94-
uv sync --extra dev
95-
```
82+
For development setup, testing, and creating new plugins, see the [Development Guide](docs/development.md).
9683

97-
Set up pre-commit hooks:
84+
## Contributing
9885

99-
```bash
100-
uv run pre-commit install
101-
```
86+
We welcome contributions to eoAPI-notifier! Whether you want to fix a bug, add a new feature, or create a custom plugin, your contributions are appreciated.
10287

103-
Run pre-commit manually:
88+
- Found a bug or have a feature request? [Open an issue](https://github.com/developmentseed/eoapi-notifier/issues).
89+
- Have a fix, improvement, or you want to add a new plugin? [Submit a pull request](https://github.com/developmentseed/eoapi-notifier/pulls) with your changes.
10490

105-
```bash
106-
uv run pre-commit run --all-files
107-
```
91+
Please make sure to read the [Development Guide](docs/development.md) for setup instructions and coding standards.
10892

10993
## License
11094

docs/development.md

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
# Development Guide
2+
3+
## Development Setup
4+
5+
```bash
6+
uv sync --extra dev
7+
uv run pre-commit install
8+
uv run pre-commit run --all-files
9+
```
10+
11+
## Testing
12+
13+
```bash
14+
uv sync --extra test
15+
uv run pytest
16+
uv run pytest --cov=eoapi_notifier --cov-report=html
17+
```
18+
19+
## Creating Plugins
20+
21+
Two plugin types:
22+
- **Sources**: Listen for events from external systems
23+
- **Outputs**: Send events to external systems
24+
25+
All plugins need:
26+
1. Configuration class (inherits `BasePluginConfig`)
27+
2. Plugin class (inherits `BaseSource` or `BaseOutput`)
28+
3. Registration in the registry
29+
30+
### Source Plugin Example
31+
32+
```python
33+
from typing import Any
34+
from collections.abc import AsyncIterator
35+
from datetime import UTC, datetime
36+
from eoapi_notifier.core.plugin import BasePluginConfig, BaseSource, PluginMetadata
37+
from eoapi_notifier.core.event import NotificationEvent
38+
39+
class MySourceConfig(BasePluginConfig):
40+
host: str = "localhost"
41+
port: int = 8080
42+
api_key: str = ""
43+
poll_interval: float = 5.0
44+
45+
@classmethod
46+
def get_metadata(cls) -> PluginMetadata:
47+
return PluginMetadata(
48+
name="mysource",
49+
description="Custom API polling source",
50+
version="1.0.0",
51+
category="api",
52+
)
53+
54+
class MySource(BaseSource):
55+
def __init__(self, config: MySourceConfig):
56+
super().__init__(config)
57+
self.config = config
58+
self._session = None
59+
60+
async def start(self) -> None:
61+
# Initialize connections here
62+
await super().start()
63+
64+
async def stop(self) -> None:
65+
# Cleanup connections here
66+
await super().stop()
67+
68+
async def listen(self) -> AsyncIterator[NotificationEvent]:
69+
while self._running:
70+
try:
71+
await asyncio.sleep(self.config.poll_interval)
72+
data = await self._fetch_data()
73+
74+
if data:
75+
yield NotificationEvent(
76+
source=f"/my-source/{self.config.host}",
77+
type="com.example.data.change",
78+
operation="UPDATE",
79+
collection="my_collection",
80+
item_id=data.get("id"),
81+
timestamp=datetime.now(UTC),
82+
data=data,
83+
)
84+
except Exception as e:
85+
self.logger.error(f"Listen error: {e}")
86+
await asyncio.sleep(1.0)
87+
88+
async def _fetch_data(self) -> dict | None:
89+
# Your data fetching logic
90+
return {"id": "example", "status": "updated"}
91+
```
92+
93+
### Output Plugin Example
94+
95+
```python
96+
from eoapi_notifier.core.plugin import BasePluginConfig, BaseOutput, PluginMetadata
97+
from eoapi_notifier.core.event import NotificationEvent
98+
99+
class MyOutputConfig(BasePluginConfig):
100+
webhook_url: str
101+
timeout: float = 30.0
102+
headers: dict[str, str] = {}
103+
104+
@classmethod
105+
def get_metadata(cls) -> PluginMetadata:
106+
return PluginMetadata(
107+
name="webhook",
108+
description="HTTP webhook output",
109+
version="1.0.0",
110+
category="http",
111+
)
112+
113+
class MyOutput(BaseOutput):
114+
def __init__(self, config: MyOutputConfig):
115+
super().__init__(config)
116+
self.config = config
117+
self._session = None
118+
119+
async def start(self) -> None:
120+
# Initialize HTTP session
121+
await super().start()
122+
123+
async def stop(self) -> None:
124+
# Cleanup session
125+
await super().stop()
126+
127+
async def send_event(self, event: NotificationEvent) -> bool:
128+
try:
129+
payload = {
130+
"id": event.id,
131+
"source": event.source,
132+
"type": event.type,
133+
"operation": event.operation,
134+
"collection": event.collection,
135+
"item_id": event.item_id,
136+
"timestamp": event.timestamp.isoformat(),
137+
"data": event.data,
138+
}
139+
140+
# Send HTTP request here
141+
# async with self._session.post(self.config.webhook_url, json=payload) as response:
142+
# response.raise_for_status()
143+
# return True
144+
145+
return True
146+
except Exception as e:
147+
self.logger.error(f"Send failed: {e}")
148+
return False
149+
```
150+
151+
### Registration
152+
153+
Add to `eoapi_notifier/core/registry.py`:
154+
155+
```python
156+
# In SourceRegistry._register_builtin_sources():
157+
self.register(
158+
name="mysource",
159+
module_path="eoapi_notifier.sources.mysource",
160+
class_name="MySource",
161+
config_class_name="MySourceConfig",
162+
)
163+
164+
# In OutputRegistry._register_builtin_outputs():
165+
self.register(
166+
name="webhook",
167+
module_path="eoapi_notifier.outputs.webhook",
168+
class_name="MyOutput",
169+
config_class_name="MyOutputConfig",
170+
)
171+
```
172+
173+
### File Structure
174+
175+
```
176+
eoapi_notifier/
177+
├── sources/
178+
│ └── mysource.py
179+
├── outputs/
180+
│ └── webhook.py
181+
```
182+
183+
### Testing
184+
185+
```python
186+
import pytest
187+
from eoapi_notifier.sources.mysource import MySource, MySourceConfig
188+
189+
@pytest.fixture
190+
async def source():
191+
config = MySourceConfig(host="localhost", api_key="test")
192+
source = MySource(config)
193+
await source.start()
194+
yield source
195+
await source.stop()
196+
197+
async def test_source_lifecycle(source):
198+
assert source.is_running
199+
```
200+
201+
### Configuration
202+
203+
```yaml
204+
sources:
205+
- type: mysource
206+
config:
207+
host: api.example.com
208+
api_key: your-key
209+
210+
outputs:
211+
- type: webhook
212+
config:
213+
webhook_url: https://hooks.example.com/notify
214+
```

0 commit comments

Comments
 (0)