Skip to content

Commit 70ba141

Browse files
Add info block to service, add optional address field
1 parent 1b7255a commit 70ba141

16 files changed

Lines changed: 255 additions & 209 deletions

examples/cameraServiceExample.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from context_logger import get_logger, setup_logging
55

66
from examples import setup_shutdown
7-
from hello import ServiceInfo, Hello, Group
7+
from hello import Service, Hello, Group
88

99
setup_logging('hello')
1010

@@ -21,7 +21,7 @@ def main() -> None:
2121
group = Group.create(name='effective-range/sniper', address='239.0.1.1', port=5555, if_address=if_address)
2222

2323
# Define the service information for the camera
24-
info = ServiceInfo(uuid=uuid4(), name='er-sniper-camera-1', role='camera', urls={
24+
service = Service(uuid=uuid4(), name='er-sniper-camera-1', role='camera', address=if_address, urls={
2525
'api': f'grpc://{if_address}:50051',
2626
'stream': f'http://{if_address}:8000/video_feed'
2727
})
@@ -32,7 +32,7 @@ def main() -> None:
3232
advertizer.start(group)
3333

3434
# Immediately advertise the service information
35-
advertizer.advertise(info)
35+
advertizer.advertise(service)
3636

3737
# Schedule periodic advertisements every 10 seconds
3838
advertizer.schedule_periodic(interval=10)

hello/advertizer.py

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,20 @@
1010
from common_utility import IReusableTimer
1111
from context_logger import get_logger
1212

13-
from hello import ServiceInfo, Group, Sender, Receiver, ServiceMatcher, ServiceQuery, AbstractScheduler
13+
from hello import Service, Group, Sender, Receiver, ServiceMatcher, ServiceQuery, AbstractScheduler
1414

1515
log = get_logger('Advertizer')
1616

1717

1818
class Advertizer:
1919

20-
def start(self, group: Group, info: ServiceInfo | None = None) -> None:
20+
def start(self, group: Group, service: Service | None = None) -> None:
2121
raise NotImplementedError()
2222

2323
def stop(self) -> None:
2424
raise NotImplementedError()
2525

26-
def advertise(self, info: ServiceInfo | None = None, log_level: int = INFO) -> None:
26+
def advertise(self, service: Service | None = None, log_level: int = INFO) -> None:
2727
raise NotImplementedError()
2828

2929

@@ -32,37 +32,37 @@ class DefaultAdvertizer(Advertizer):
3232
def __init__(self, sender: Sender) -> None:
3333
self._sender = sender
3434
self._group: Group | None = None
35-
self._info: ServiceInfo | None = None
35+
self._service: Service | None = None
3636

3737
def __enter__(self) -> Advertizer:
3838
return self
3939

4040
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
4141
self.stop()
4242

43-
def start(self, group: Group, info: ServiceInfo | None = None) -> None:
43+
def start(self, group: Group, service: Service | None = None) -> None:
4444
self._sender.start(group.hello())
4545
self._group = group
46-
self._info = info
47-
log.info('Advertizer started', group=self._group, service=self._info)
46+
self._service = service
47+
log.info('Advertizer started', group=self._group, service=self._service)
4848

4949
def stop(self) -> None:
5050
self._group = None
51-
self._info = None
51+
self._service = None
5252
self._sender.stop()
5353
log.info('Advertizer stopped')
5454

55-
def advertise(self, info: ServiceInfo | None = None, log_level: int = INFO) -> None:
55+
def advertise(self, service: Service | None = None, log_level: int = INFO) -> None:
5656
if self._group:
57-
if info:
58-
self._info = info
59-
if self._info:
60-
self._sender.send(self._info)
61-
log.log(log_level, 'Service advertised', service=self._info, group=self._group)
57+
if service:
58+
self._service = service
59+
if self._service:
60+
self._sender.send(self._service)
61+
log.log(log_level, 'Service advertised', service=self._service, group=self._group)
6262
else:
63-
log.warning('Cannot advertise service, no service info provided', group=self._group)
63+
log.warning('Cannot advertise service, no service provided', group=self._group)
6464
else:
65-
log.warning('Cannot advertise service, advertizer not started', service=info)
65+
log.warning('Cannot advertise service, advertizer not started', service=service)
6666

6767

6868
class RespondingAdvertizer(DefaultAdvertizer):
@@ -72,8 +72,8 @@ def __init__(self, sender: Sender, receiver: Receiver, max_response_delay: float
7272
self._receiver = receiver
7373
self._max_delay = max_response_delay
7474

75-
def start(self, group: Group, info: ServiceInfo | None = None) -> None:
76-
super().start(group, info)
75+
def start(self, group: Group, service: Service | None = None) -> None:
76+
super().start(group, service)
7777
self._receiver.start(group.query())
7878
self._receiver.register(self._handle_message)
7979

@@ -83,24 +83,24 @@ def stop(self) -> None:
8383
super().stop()
8484

8585
def _handle_message(self, message: dict[str, Any]) -> None:
86-
if self._info:
86+
if self._service:
8787
try:
8888
query = ServiceQuery(**message)
8989
matcher = ServiceMatcher(query)
9090
log.debug('Service query received', group=self._group, query=query)
91-
self._handle_query(matcher, self._info)
91+
self._handle_query(matcher, self._service)
9292
except Exception as error:
9393
log.warning('Invalid service query received', group=self._group, received=message, error=error)
9494

95-
def _handle_query(self, matcher: ServiceMatcher, info: ServiceInfo) -> None:
96-
if matcher.matches(info):
95+
def _handle_query(self, matcher: ServiceMatcher, service: Service) -> None:
96+
if matcher.matches(service):
9797
delay = round(self._max_delay * random.random(), 3)
98-
log.info('Responding to query', group=self._group, query=matcher.query, service=info, delay=delay)
98+
log.info('Responding to query', group=self._group, query=matcher.query, service=service, delay=delay)
9999
time.sleep(delay)
100-
self.advertise(info)
100+
self.advertise(service)
101101

102102

103-
class ScheduledAdvertizer(AbstractScheduler[ServiceInfo], Advertizer):
103+
class ScheduledAdvertizer(AbstractScheduler[Service], Advertizer):
104104

105105
def __init__(self, advertizer: Advertizer, timer: IReusableTimer) -> None:
106106
super().__init__(timer)
@@ -112,15 +112,15 @@ def __enter__(self) -> 'ScheduledAdvertizer':
112112
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
113113
self.stop()
114114

115-
def start(self, group: Group, info: ServiceInfo | None = None) -> None:
116-
self._advertizer.start(group, info)
115+
def start(self, group: Group, service: Service | None = None) -> None:
116+
self._advertizer.start(group, service)
117117

118118
def stop(self) -> None:
119119
super().stop()
120120
self._advertizer.stop()
121121

122-
def advertise(self, info: ServiceInfo | None = None, log_level: int = INFO) -> None:
123-
self._advertizer.advertise(info, log_level)
122+
def advertise(self, service: Service | None = None, log_level: int = INFO) -> None:
123+
self._advertizer.advertise(service, log_level)
124124

125-
def _execute(self, info: ServiceInfo | None = None) -> None:
126-
self.advertise(info, DEBUG)
125+
def _execute(self, service: Service | None = None) -> None:
126+
self.advertise(service, DEBUG)

hello/discoverer.py

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,22 @@
1212
from common_utility import IReusableTimer
1313
from context_logger import get_logger
1414

15-
from hello import Group, ServiceQuery, Sender, Receiver, ServiceInfo, ServiceMatcher, AbstractScheduler
15+
from hello import Group, ServiceQuery, Sender, Receiver, Service, ServiceMatcher, AbstractScheduler
1616

1717
log = get_logger('Discoverer')
1818

1919

2020
class DiscoveryEventType(Enum):
2121
DISCOVERED = 'discovered'
22+
UNCHANGED = 'unchanged'
2223
UPDATED = 'updated'
2324

2425

2526
@dataclass
2627
class DiscoveryEvent:
2728
group: Group
2829
query: ServiceQuery
29-
service: ServiceInfo
30+
service: Service
3031
type: DiscoveryEventType
3132

3233

@@ -45,13 +46,13 @@ def stop(self) -> None:
4546
def discover(self, query: ServiceQuery | None = None, log_level: int = INFO) -> None:
4647
raise NotImplementedError()
4748

48-
def register(self, handler: OnDiscoveryEvent) -> None:
49+
def register(self, handler: OnDiscoveryEvent, types: set[DiscoveryEventType] | None = None) -> None:
4950
raise NotImplementedError()
5051

51-
def deregister(self, handler: OnDiscoveryEvent) -> None:
52+
def deregister(self, handler: OnDiscoveryEvent, types: set[DiscoveryEventType] | None = None) -> None:
5253
raise NotImplementedError()
5354

54-
def get_services(self) -> dict[UUID, ServiceInfo]:
55+
def get_services(self) -> dict[UUID, Service]:
5556
raise NotImplementedError()
5657

5758

@@ -62,8 +63,10 @@ def __init__(self, sender: Sender, receiver: Receiver, max_workers: int = 8) ->
6263
self._receiver = receiver
6364
self._group: Group | None = None
6465
self._matcher: ServiceMatcher | None = None
65-
self._services: dict[UUID, ServiceInfo] = {}
66-
self._handlers: list[OnDiscoveryEvent] = []
66+
self._services: dict[UUID, Service] = {}
67+
self._handlers: dict[DiscoveryEventType, list[OnDiscoveryEvent]] = {
68+
event_type: [] for event_type in DiscoveryEventType
69+
}
6770
self._handler_executor = ThreadPoolExecutor(max_workers=max_workers)
6871

6972
def __enter__(self) -> Discoverer:
@@ -101,48 +104,53 @@ def discover(self, query: ServiceQuery | None = None, log_level: int = INFO) ->
101104
else:
102105
log.warning('Cannot discover services, discoverer not started', query=query)
103106

104-
def register(self, handler: OnDiscoveryEvent) -> None:
105-
self._handlers.append(handler)
107+
def register(self, handler: OnDiscoveryEvent, types: set[DiscoveryEventType] | None = None) -> None:
108+
for event_type in types if types else self._get_event_types():
109+
self._handlers[event_type].append(handler)
106110

107-
def deregister(self, handler: OnDiscoveryEvent) -> None:
108-
self._handlers.remove(handler)
111+
def deregister(self, handler: OnDiscoveryEvent, types: set[DiscoveryEventType] | None = None) -> None:
112+
for event_type in types if types else self._get_event_types():
113+
self._handlers[event_type].remove(handler)
109114

110-
def get_services(self) -> dict[UUID, ServiceInfo]:
115+
def get_services(self) -> dict[UUID, Service]:
111116
return self._services.copy()
112117

118+
def _get_event_types(self) -> set[DiscoveryEventType]:
119+
return set(self._handlers.keys())
120+
113121
def _handle_message(self, message: dict[str, Any]) -> None:
114122
if self._group and self._matcher:
115123
try:
116-
service = ServiceInfo(UUID(message['uuid']), message['name'], message['role'], message.get('urls', {}))
117-
log.debug('Service info received', service=service, group=self._group)
124+
service = Service(UUID(message['uuid']), message['name'], message['role'],
125+
message.get('urls', {}), message.get('info', {}), message['address'])
126+
log.debug('Service received', service=service, group=self._group)
118127
self._handle_service(service, self._group, self._matcher)
119128
except Exception as error:
120-
log.warn('Invalid service info received', group=self._group, data=message, error=error)
129+
log.warn('Invalid service received', group=self._group, data=message, error=error)
121130

122-
def _handle_service(self, service: ServiceInfo, group: Group, matcher: ServiceMatcher) -> None:
131+
def _handle_service(self, service: Service, group: Group, matcher: ServiceMatcher) -> None:
123132
if matcher.matches(service):
124133
stored = self._services.get(service.uuid)
134+
event = self._create_event(group, matcher, stored, service)
135+
self._handle_event(event)
125136

126-
if event := self._create_event(group, matcher, stored, service):
127-
self._handle_event(event)
128-
129-
def _create_event(self, group: Group, matcher: ServiceMatcher,
130-
stored: ServiceInfo | None, service: ServiceInfo) -> DiscoveryEvent | None:
137+
def _create_event(self, group: Group, matcher: ServiceMatcher, stored: Service | None,
138+
service: Service) -> DiscoveryEvent:
131139
if stored:
132140
if stored != service:
133141
log.info('Service updated', group=group, old_service=stored, new_service=service)
134142
return DiscoveryEvent(group, matcher.query, service, DiscoveryEventType.UPDATED)
135143
else:
136144
log.debug('Service unchanged', group=group, service=service)
137-
return None
145+
return DiscoveryEvent(group, matcher.query, service, DiscoveryEventType.UPDATED)
138146
else:
139-
log.info('New service discovered', group=group, service=service)
147+
log.info('Service discovered', group=group, service=service)
140148
return DiscoveryEvent(group, matcher.query, service, DiscoveryEventType.DISCOVERED)
141149

142150
def _handle_event(self, event: DiscoveryEvent) -> None:
143151
self._services[event.service.uuid] = event.service
144152

145-
for handler in self._handlers:
153+
for handler in self._handlers[event.type]:
146154
self._handler_executor.submit(self._execute_handler, handler, event)
147155

148156
def _execute_handler(self, handler: OnDiscoveryEvent, event: DiscoveryEvent) -> None:
@@ -174,14 +182,14 @@ def stop(self) -> None:
174182
def discover(self, query: ServiceQuery | None = None, log_level: int = INFO) -> None:
175183
self._discoverer.discover(query, log_level)
176184

177-
def get_services(self) -> dict[UUID, ServiceInfo]:
185+
def get_services(self) -> dict[UUID, Service]:
178186
return self._discoverer.get_services()
179187

180-
def register(self, handler: OnDiscoveryEvent) -> None:
181-
self._discoverer.register(handler)
188+
def register(self, handler: OnDiscoveryEvent, types: set[DiscoveryEventType] | None = None) -> None:
189+
self._discoverer.register(handler, types)
182190

183-
def deregister(self, handler: OnDiscoveryEvent) -> None:
184-
self._discoverer.deregister(handler)
191+
def deregister(self, handler: OnDiscoveryEvent, types: set[DiscoveryEventType] | None = None) -> None:
192+
self._discoverer.deregister(handler, types)
185193

186194
def _execute(self, query: ServiceQuery | None = None) -> None:
187195
self.discover(query, DEBUG)

hello/service.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,26 @@
99

1010

1111
@dataclass
12-
class ServiceInfo:
12+
class Service:
1313
uuid: UUID
1414
name: str
1515
role: str
1616
urls: dict[str, str] = field(default_factory=dict)
17+
info: dict[str, Any] = field(default_factory=dict)
18+
address: str | None = None
1719

1820
def __repr__(self) -> str:
19-
return f"ServiceInfo(uuid='{self.uuid}', name='{self.name}', role='{self.role}', urls='{self.urls}')"
21+
return (f"Service(uuid='{self.uuid}', name='{self.name}', role='{self.role}', "
22+
f"urls={self.urls}, info={self.info}), addr='{self.address}'")
2023

2124
def to_dict(self) -> dict[str, Any]:
2225
return {
2326
'uuid': str(self.uuid),
2427
'name': self.name,
2528
'role': self.role,
26-
'urls': self.urls
29+
'urls': self.urls,
30+
'info': self.info,
31+
'address': self.address,
2732
}
2833

2934

@@ -40,7 +45,7 @@ def __init__(self, query: ServiceQuery) -> None:
4045
self._name_matcher = re.compile(self.query.name)
4146
self._role_matcher = re.compile(self.query.role)
4247

43-
def matches(self, info: ServiceInfo) -> bool:
44-
name_match = self._name_matcher.match(info.name)
45-
role_match = self._role_matcher.match(info.role)
48+
def matches(self, service: Service) -> bool:
49+
name_match = self._name_matcher.match(service.name)
50+
role_match = self._role_matcher.match(service.role)
4651
return bool(name_match and role_match)

0 commit comments

Comments
 (0)