Skip to content

Commit 88f3a50

Browse files
author
Tom Softreck
committed
update
1 parent a70d8b7 commit 88f3a50

File tree

28 files changed

+2663
-49
lines changed

28 files changed

+2663
-49
lines changed

src/dialogchain/__init__.py

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,46 +17,73 @@
1717
dialogchain validate -c config.yaml
1818
"""
1919

20-
__version__ = "0.1.0"
20+
__version__ = "0.2.0"
2121
__author__ = "DialogChain Team"
2222

2323
# Core components
24-
from .engine import DialogChainEngine
25-
from .processors import *
26-
from .connectors import *
27-
from .exceptions import *
24+
from .core.engine import DialogChainEngine
25+
26+
# Processors
27+
from .processors import (
28+
Processor,
29+
TransformProcessor,
30+
FilterProcessor,
31+
EnrichProcessor,
32+
ValidateProcessor
33+
)
34+
35+
# Sources
36+
from .sources import (
37+
Source,
38+
TimerSource,
39+
FileSource,
40+
IMAPSource,
41+
RTSPSource,
42+
GRPCSource
43+
)
44+
45+
# Destinations
46+
from .destinations import (
47+
Destination,
48+
LogDestination,
49+
FileDestination,
50+
HTTPDestination,
51+
EmailDestination,
52+
MQTTDestination,
53+
GRPCDestination
54+
)
2855

29-
# Import and expose utility functions
56+
# Utils and exceptions
3057
from . import utils
58+
from .exceptions import *
3159

3260
__all__ = [
3361
# Core components
3462
"DialogChainEngine",
3563

3664
# Processors
3765
"Processor",
38-
"ExternalProcessor",
39-
"FilterProcessor",
4066
"TransformProcessor",
41-
"AggregateProcessor",
42-
"DebugProcessor",
67+
"FilterProcessor",
68+
"EnrichProcessor",
69+
"ValidateProcessor",
4370

4471
# Sources
4572
"Source",
46-
"RTSPSource",
4773
"TimerSource",
4874
"FileSource",
49-
"HTTPSource",
5075
"IMAPSource",
51-
"MQTTSource",
76+
"RTSPSource",
77+
"GRPCSource",
5278

5379
# Destinations
5480
"Destination",
55-
"EmailDestination",
81+
"LogDestination",
82+
"FileDestination",
5683
"HTTPDestination",
84+
"EmailDestination",
5785
"MQTTDestination",
58-
"FileDestination",
59-
"LogDestination",
86+
"GRPCDestination",
6087

6188
# Utils
6289
"utils",
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""Connectors for DialogChain data sources and destinations."""
2+
3+
from .base import Source, Destination
4+
from .sources.rtsp import RTSPSource
5+
from .sources.imap import IMAPSource
6+
from .sources.file import FileSource
7+
from .sources.timer import TimerSource
8+
from .destinations.http import HTTPDestination
9+
from .destinations.email import EmailDestination
10+
from .destinations.file import FileDestination
11+
from .destinations.log import LogDestination
12+
13+
# Re-export for backward compatibility
14+
__all__ = [
15+
'Source',
16+
'Destination',
17+
'RTSPSource',
18+
'IMAPSource',
19+
'FileSource',
20+
'TimerSource',
21+
'HTTPDestination',
22+
'EmailDestination',
23+
'FileDestination',
24+
'LogDestination',
25+
]

src/dialogchain/connectors/base.py

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
"""Base classes for DialogChain connectors."""
2+
3+
from abc import ABC, abstractmethod
4+
from typing import Any, AsyncIterator, Dict, Optional
5+
from urllib.parse import urlparse
6+
import logging
7+
8+
logger = logging.getLogger(__name__)
9+
10+
class Connector(ABC):
11+
"""Base class for all connectors."""
12+
13+
def __init__(self, uri: str):
14+
"""Initialize the connector with a URI.
15+
16+
Args:
17+
uri: Connection string in the format 'scheme://[user:password@]host[:port][/path][?query]'
18+
"""
19+
self.uri = uri
20+
self.parsed_uri = urlparse(uri)
21+
self._is_connected = False
22+
23+
@property
24+
def is_connected(self) -> bool:
25+
"""Check if the connector is connected."""
26+
return self._is_connected
27+
28+
async def connect(self):
29+
"""Establish a connection to the resource."""
30+
if self._is_connected:
31+
return self
32+
33+
try:
34+
await self._connect()
35+
self._is_connected = True
36+
logger.debug(f"Connected to {self.__class__.__name__}: {self.uri}")
37+
return self
38+
except Exception as e:
39+
self._is_connected = False
40+
logger.error(f"Failed to connect to {self.uri}: {e}")
41+
raise
42+
43+
async def disconnect(self):
44+
"""Close the connection to the resource."""
45+
if not self._is_connected:
46+
return
47+
48+
try:
49+
await self._disconnect()
50+
logger.debug(f"Disconnected from {self.__class__.__name__}: {self.uri}")
51+
except Exception as e:
52+
logger.error(f"Error disconnecting from {self.uri}: {e}")
53+
raise
54+
finally:
55+
self._is_connected = False
56+
57+
async def __aenter__(self):
58+
"""Async context manager entry."""
59+
return await self.connect()
60+
61+
async def __aexit__(self, exc_type, exc_val, exc_tb):
62+
"""Async context manager exit."""
63+
await self.disconnect()
64+
65+
@abstractmethod
66+
async def _connect(self):
67+
"""Implementation-specific connection logic."""
68+
pass
69+
70+
@abstractmethod
71+
async def _disconnect(self):
72+
"""Implementation-specific disconnection logic."""
73+
pass
74+
75+
76+
class Source(Connector):
77+
"""Base class for all data sources."""
78+
79+
@abstractmethod
80+
async def receive(self) -> AsyncIterator[Dict[str, Any]]:
81+
"""Async generator that yields data from the source.
82+
83+
Yields:
84+
Dictionary containing the received data and metadata
85+
"""
86+
pass
87+
88+
@classmethod
89+
def create(cls, uri: str, **kwargs) -> 'Source':
90+
"""Create a source instance from a URI.
91+
92+
Args:
93+
uri: Source URI (e.g., 'rtsp://...', 'imap://...')
94+
**kwargs: Additional arguments for the source
95+
96+
Returns:
97+
Source: An instance of the appropriate source class
98+
99+
Raises:
100+
ValueError: If the URI scheme is not supported
101+
"""
102+
from . import sources
103+
104+
parsed = urlparse(uri)
105+
scheme = parsed.scheme.lower()
106+
107+
# Map scheme to source class
108+
source_classes = {
109+
'rtsp': sources.RTSPSource,
110+
'imap': sources.IMAPSource,
111+
'file': sources.FileSource,
112+
'timer': sources.TimerSource,
113+
}
114+
115+
if scheme not in source_classes:
116+
raise ValueError(f"Unsupported source scheme: {scheme}")
117+
118+
return source_classes[scheme](uri, **kwargs)
119+
120+
121+
class Destination(Connector):
122+
"""Base class for all data destinations."""
123+
124+
@abstractmethod
125+
async def send(self, data: Any) -> None:
126+
"""Send data to the destination.
127+
128+
Args:
129+
data: The data to send
130+
131+
Raises:
132+
Exception: If sending fails
133+
"""
134+
pass
135+
136+
@classmethod
137+
def create(cls, uri: str, **kwargs) -> 'Destination':
138+
"""Create a destination instance from a URI.
139+
140+
Args:
141+
uri: Destination URI (e.g., 'http://...', 'smtp://...')
142+
**kwargs: Additional arguments for the destination
143+
144+
Returns:
145+
Destination: An instance of the appropriate destination class
146+
147+
Raises:
148+
ValueError: If the URI scheme is not supported
149+
"""
150+
from . import destinations
151+
152+
parsed = urlparse(uri)
153+
scheme = parsed.scheme.lower()
154+
155+
# Map scheme to destination class
156+
dest_classes = {
157+
'http': destinations.HTTPDestination,
158+
'https': destinations.HTTPDestination,
159+
'smtp': destinations.EmailDestination,
160+
'file': destinations.FileDestination,
161+
'log': destinations.LogDestination,
162+
}
163+
164+
if scheme not in dest_classes:
165+
raise ValueError(f"Unsupported destination scheme: {scheme}")
166+
167+
return dest_classes[scheme](uri, **kwargs)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""Destination implementations for DialogChain connectors."""
2+
3+
from .http import HTTPDestination
4+
from .email import EmailDestination
5+
from .file import FileDestination
6+
from .log import LogDestination
7+
8+
__all__ = [
9+
'HTTPDestination',
10+
'EmailDestination',
11+
'FileDestination',
12+
'LogDestination',
13+
]

0 commit comments

Comments
 (0)