Connectors¶
One contract for every external system — SMTP, S3, Google Drive, Gmail OAuth2, custom. Connector kinds register themselves against the kernel registry; user-visible records (ir.connector) configure instances per organization.
# Register a new connector kind in your app's __init__.py
from ede.core.connectors.registry import connector_registry
from .connectors.s3 import S3ConnectorProvider
connector_registry.register(S3ConnectorProvider)
Once registered, administrators create ir.connector records via the Settings UI, test the connection, and mark a default per (category, organization). Consumer modules (Email, Storage, future ones) call ConnectorService.resolve(...) to pick the right backend.
What you get¶
ConnectorProviderABC — implement four methods (test_connection,activate,deactivate,import_config).ConnectorRegistry— module-level singleton; connector kinds register here at import time.ir.connectormodel — user-visible records, one per configured backend.ir.connector.parammodel — typed parameters per connector (credentials, endpoints, OAuth tokens).ConnectorService— lifecycle verbs:test_connection(),activate(),deactivate(),import_config(),get_config().- HTTP API —
/api/connectors/*admin endpoints. - Settings UI — Integrations → Connectors, with per-kind config forms.
- Single-default invariant — at most one
is_default=Trueconnector per (category, organization).
How to use it¶
Define a new connector kind¶
from ede.core.connectors.interfaces import ConnectorProvider
class S3ConnectorProvider(ConnectorProvider):
kind = "s3"
category = "storage"
label = "Amazon S3"
parameter_schema = [
{"name": "bucket", "type": "string", "required": True},
{"name": "access_key_id", "type": "string", "required": True, "secret": True},
{"name": "secret_access_key", "type": "string", "required": True, "secret": True},
{"name": "region", "type": "string", "required": True},
]
def test_connection(self, config) -> tuple[bool, str]:
client = boto3.client("s3", **config)
try:
client.head_bucket(Bucket=config["bucket"])
return True, "Connected"
except ClientError as exc:
return False, str(exc)
Register it once at app load:
# my_app/__init__.py
from ede.core.connectors.registry import connector_registry
from .connectors.s3 import S3ConnectorProvider
connector_registry.register(S3ConnectorProvider)
Configure an instance (data file)¶
<record id="connector_acme_s3" model="ir.connector">
<field name="name">Acme S3</field>
<field name="category">storage</field>
<field name="kind">s3</field>
<field name="organization_id" ref="res.organization.acme"/>
<field name="is_default">true</field>
<field name="config" eval="{
'bucket': 'acme-prod',
'region': 'us-east-1',
'access_key_id': '$ENV:S3_KEY',
'secret_access_key': '$ENV:S3_SECRET',
}"/>
</record>
Resolve a backend at runtime¶
from ede.foundation.connectors.services.connector_service import ConnectorService
backend = ConnectorService.from_env(env).resolve(
category="storage",
organization_id=env.active_organization_id,
)
The service returns the default connector for that (category, org) — or None if no default is configured.
Test a connection from the UI¶
The "Test Connection" button in the Settings UI calls ConnectorService.test_connection() synchronously and surfaces the (ok, message) tuple in a toast.
How it composes with other features¶
- Document Storage — registers backend kinds against the connectors framework.
- Email — SMTP / SendGrid / Mailgun / Gmail OAuth2 are connector kinds.
- Multi-Tenant Gateway — every tenant has its own connectors; resolution is
Env-scoped.
Reference¶
| Concept | Where it lives |
|---|---|
ConnectorProvider ABC |
src/ede/core/connectors/interfaces.py |
ConnectorRegistry |
src/ede/core/connectors/registry.py |
ir.connector model |
src/ede/foundation/connectors/models/connector.py |
ConnectorService |
src/ede/foundation/connectors/services/connector_service.py |
| Full reference | Connector Framework architecture guide |