Skip to content

Commit 43691e3

Browse files
committed
fixed some issues and added some docs
1 parent cb0a535 commit 43691e3

File tree

11 files changed

+170
-79
lines changed

11 files changed

+170
-79
lines changed

README.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,37 @@
11
# ObjectLink Core Protocol in Python
22

3-
The ObjectLink protocol is designed to link a local object with a remote objec over a websocket connection.
3+
The ObjectLink protocol is designed to link a local object with a remote object over a websocket connection.
44

5-
It supports distributed properties, asynchronous method invokation as also server side signals.
5+
It supports distributed properties, asynchronous method invocation as also server side signals.
66

77
It is designed to wort together with ApiGear Object Model to generate complete client and server side APIs.
88

9-
# Install for testing
9+
# Setup for testing
1010

1111
This will install the package as editable python package, which can be used for local development.
1212

1313
```
1414
pip install -e .
1515
```
1616

17-
# Publish package
17+
# Testing
1818

19-
TBD
19+
```
20+
pip install pytest
21+
pytest
22+
```
23+
24+
# Running the server
2025

26+
The server is a starlette server (https://www.starlette.io) which can be installed with
2127

28+
```
29+
pip3 install starlette
30+
pip3 install uvicorn
31+
```
32+
33+
and run using
34+
35+
```
36+
uvicorn demo:app --port=8080
37+
```

server.py renamed to demo_server.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,40 +13,48 @@
1313
class Counter:
1414
count = 0
1515
_node: RemoteNode
16+
1617
def increment(self):
1718
self.count += 1
18-
self._node.notify_property_change('demo.Counter/count', self.count)
19+
# notify all registered clients
20+
RemoteNode.notify_property_change('demo.Counter/count', self.count)
21+
1922

2023
class CounterAdapter(IObjectSource):
2124
node: RemoteNode = None
25+
2226
def __init__(self, impl):
2327
self.impl = impl
24-
RemoteNode.add_object_source(self)
28+
# need to register this source with the registry
29+
RemoteNode.register_source(self)
2530

2631
def olink_object_name(self):
32+
# name this source is registered under
2733
return 'demo.Counter'
2834

2935
def olink_invoke(self, name: str, args: list[Any]) -> Any:
36+
# called on incoming invoke message
3037
path = Name.path_from_name(name)
3138
func = getattr(self.impl, path)
3239
func()
3340

3441
def olink_set_property(self, name: str, value: Any):
42+
# called on incoming set property message
3543
path = Name.path_from_name(name)
3644
setattr(self, self.impl, value)
3745

3846
def olink_linked(self, name: str, node: "RemoteNode"):
47+
# called when a remote node is linked to this node
3948
self.impl._node = node
4049

4150
def olink_collect_properties(self) -> object:
4251
return {k: getattr(self.impl, k) for k in ['count']}
4352

53+
4454
counter = Counter()
4555
adapter = CounterAdapter(counter)
4656

4757

48-
49-
5058
class RemoteEndpoint(WebSocketEndpoint):
5159
encoding = "text"
5260
node = RemoteNode()
@@ -59,7 +67,7 @@ async def sender(self, ws):
5967
msg = await self.queue.get()
6068
print('send', msg)
6169
await ws.send_text(msg)
62-
self.queue.task_done()
70+
self.queue.task_done()
6371

6472
async def on_connect(self, ws: WebSocket):
6573
print('on_connect')
@@ -71,7 +79,6 @@ def writer(msg: str):
7179
self.node.on_write(writer)
7280
await super().on_connect(ws)
7381

74-
7582
async def on_receive(self, ws: WebSocket, data: Any) -> None:
7683
print('on_receive', data)
7784
self.node.handle_message(data)
@@ -82,9 +89,8 @@ async def on_disconnect(self, websocket: WebSocket, close_code: int) -> None:
8289
await self.queue.join()
8390

8491

85-
8692
routes = [
8793
WebSocketRoute("/ws", RemoteEndpoint)
8894
]
8995

90-
app = Starlette(routes=routes)
96+
app = Starlette(routes=routes)

src/olink/clientnode.py

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@ def __init__(self, name: str, value: Any):
1212
name: str
1313
value: Any
1414

15+
1516
InvokeReplyFunc = Callable[[InvokeReplyArg], None]
1617

18+
1719
class IObjectSink(ProtocolType):
20+
# interface for object sinks
1821
def olink_object_name() -> str:
1922
# return object name
2023
raise NotImplementedError()
@@ -35,9 +38,12 @@ def olink_on_release(self) -> None:
3538
# called when sink is released
3639
raise NotImplementedError()
3740

41+
3842
class SinkToClientEntry:
43+
# entry in the client registry
3944
sink: IObjectSink = None
4045
node: "ClientNode" = None
46+
4147
def __init__(self, sink=None):
4248
self.sink = sink
4349
self.node = None
@@ -53,7 +59,7 @@ def remove_node(self, node: "ClientNode"):
5359
if entry.node is node:
5460
entry.node = None
5561

56-
def add_node_to_sink(self, name: str, node: "ClientNode"):
62+
def add_node_to_sink(self, name: str, node: "ClientNode"):
5763
# add not to named sink
5864
self._entry(name).node = node
5965

@@ -64,7 +70,8 @@ def remove_node_from_sink(self, name: str, node: "ClientNode"):
6470
if self.entries[resource].node is node:
6571
self.entries[resource].node = None
6672
else:
67-
self.emit_log(LogLevel.DEBUG, f"unlink node failed, not the same node: {resource}")
73+
self.emit_log(
74+
LogLevel.DEBUG, f"unlink node failed, not the same node: {resource}")
6875

6976
def register_sink(self, sink: IObjectSink) -> "ClientNode":
7077
# register sink using object name
@@ -77,7 +84,7 @@ def unregister_sink(self, sink: IObjectSink):
7784
# unregister sink using object name
7885
name = sink.olink_object_name()
7986
self._remove_entry(name)
80-
87+
8188
def get_sink(self, name: str) -> Optional[IObjectSink]:
8289
# get sink using name
8390
return self._entry(name).sink
@@ -103,10 +110,14 @@ def _remove_entry(self, name: str) -> None:
103110
# global client registry
104111
_registry = ClientRegistry()
105112

113+
106114
def get_client_registry() -> ClientRegistry:
115+
# get global client registry
107116
return _registry
108117

118+
109119
class ClientNode(BaseNode):
120+
# client side node
110121
invokes_pending: dict[int, InvokeReplyFunc] = {}
111122
requestId = 0
112123

@@ -121,15 +132,17 @@ def next_request_id(self) -> int:
121132
return self.requestId
122133

123134
def invoke_remote(self, name: str, args: list[Any], func: Optional[InvokeReplyFunc]) -> None:
124-
self.emit_log(LogLevel.DEBUG, f"ClientNode.invoke_remote: {name} {args}")
135+
self.emit_log(LogLevel.DEBUG,
136+
f"ClientNode.invoke_remote: {name} {args}")
125137
request_id = self.next_request_id()
126138
if func:
127139
self.invokes_pending[request_id] = func
128140
self.emit_write(Protocol.invoke_message(request_id, name, args))
129141

130142
def set_remote_property(self, name: str, value: Any) -> None:
131143
# send remote propertymessage
132-
self.emit_log(LogLevel.DEBUG, f"ClientNode.set_remote_property: {name} {value}")
144+
self.emit_log(LogLevel.DEBUG,
145+
f"ClientNode.set_remote_property: {name} {value}")
133146
self.emit_write(Protocol.set_property_message(name, value))
134147

135148
def link_node(self, name: str):
@@ -144,18 +157,19 @@ def unlink_node(self, name: str) -> None:
144157
def register_sink(sink: IObjectSink) -> Optional["ClientNode"]:
145158
# register sink to registry
146159
return get_client_registry().register_sink(sink)
147-
160+
148161
@staticmethod
149162
def unregister_sink(sink: IObjectSink) -> None:
150163
# unregister sink from registry
151164
return get_client_registry().unregister_sink(sink)
152165

153166
@staticmethod
154167
def get_sink(name: str) -> Optional[IObjectSink]:
168+
# get sink from registry
155169
return get_client_registry().get_sink(name)
156170

157-
def link_remote(self, name: str):
158-
# register this node from sink and send a link message
171+
def link_remote(self, name: str):
172+
# register this node from sink and send a link message
159173
self.emit_log(LogLevel.DEBUG, f"ClientNode.linkRemote: {name}")
160174
self.registry().add_node_to_sink(name, self)
161175
self.emit_write(Protocol.link_message(name))
@@ -175,14 +189,16 @@ def handle_init(self, name: str, props: object):
175189

176190
def handle_property_change(self, name: str, value: Any) -> None:
177191
# handle property change message from source
178-
self.emit_log(LogLevel.DEBUG, f"ClientNode.handle_property_change: {name}")
179-
sink =self.get_sink(name)
192+
self.emit_log(LogLevel.DEBUG,
193+
f"ClientNode.handle_property_change: {name}")
194+
sink = self.get_sink(name)
180195
if sink:
181196
sink.olink_on_property_changed(name, value)
182197

183198
def handle_invoke_reply(self, id: int, name: str, value: Any) -> None:
184199
# handle invoke reply message from source
185-
self.emit_log(LogLevel.DEBUG, f"ClientNode.handle_invoke_reply: {id} {name} {value}")
200+
self.emit_log(LogLevel.DEBUG,
201+
f"ClientNode.handle_invoke_reply: {id} {name} {value}")
186202
if id in self.invokes_pending:
187203
func = self.invokes_pending[id]
188204
if func:
@@ -194,14 +210,13 @@ def handle_invoke_reply(self, id: int, name: str, value: Any) -> None:
194210

195211
def handle_signal(self, name: str, args: list[Any]) -> None:
196212
# handle signal message from source
197-
self.emit_log(LogLevel.DEBUG, f"ClientNode.handle_signal: {name} {args}")
213+
self.emit_log(LogLevel.DEBUG,
214+
f"ClientNode.handle_signal: {name} {args}")
198215
sink = self.get_sink(name)
199216
if sink:
200217
sink.olink_on_signal(name, args)
201218

202219
def handle_error(self, msgType: MsgType, id: int, error: str):
203220
# handle error message from source
204-
self.emit_log(LogLevel.DEBUG, f"ClientNode.handle_error: {msgType} {id} {error}")
205-
206-
207-
221+
self.emit_log(LogLevel.DEBUG,
222+
f"ClientNode.handle_error: {msgType} {id} {error}")

src/olink/core/node.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,29 @@
44

55

66
class BaseNode(Base, IProtocolListener):
7+
# base node class
78
write_func: WriteMessageFunc = None
8-
converter: MessageConverter = None
9+
converter: MessageConverter = None
910
protocol: Protocol = None
11+
1012
def __init__(self):
1113
super()
1214
self.protocol = Protocol(self)
1315
self.converter = MessageConverter(MessageFormat.JSON)
14-
16+
1517
def on_write(self, func: WriteMessageFunc) -> None:
18+
# set the write function
1619
self.write_func = func
1720

1821
def emit_write(self, msg: list[Any]) -> None:
22+
# emit a message using the write function
1923
if self.write_func:
2024
data = self.converter.to_string(msg)
2125
self.write_func(data)
2226
else:
2327
self.emit_log(LogLevel.DEBUG, f"write not set on protocol: {msg}")
2428

2529
def handle_message(self, data: str) -> None:
30+
# handle a message and pass is on to the protocol
2631
msg = self.converter.from_string(data)
2732
self.protocol.handle_message(msg)

src/olink/core/protocol.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,53 @@
22
from typing import Protocol as ProptocolType
33
from .types import Base, LogLevel, MsgType
44

5+
56
class IProtocolListener(ProptocolType):
7+
# interface for protocol listeners
68
def handle_link(self, name: str) -> None:
9+
# called when a link is created
710
raise NotImplementedError()
811

912
def handle_unlink(self, name: str) -> None:
13+
# called when a link is released
1014
raise NotImplementedError()
1115

1216
def handle_init(self, name: str, props: object) -> None:
17+
# called when a node is initialized
1318
raise NotImplementedError()
1419

1520
def handle_set_property(self, name: str, value: Any) -> None:
21+
# called when a property is set
1622
raise NotImplementedError()
1723

1824
def handle_property_change(self, name: str, value: Any) -> None:
25+
# called when a property is changed
1926
raise NotImplementedError()
2027

2128
def handle_invoke(self, id: int, name: str, args: list[Any]) -> None:
29+
# called when a node invokes a method
2230
raise NotImplementedError()
2331

2432
def handle_invoke_reply(self, id: int, name: str, value: Any) -> None:
33+
# called when a node replies to an invoke
2534
raise NotImplementedError()
2635

2736
def handle_signal(self, name: str, args: Any) -> None:
37+
# called when a signal is emitted
2838
raise NotImplementedError()
2939

3040
def handle_error(self, msgType: int, id: int, error: str) -> None:
41+
# called when an error occurs
3142
raise NotImplementedError()
3243

44+
3345
class Protocol(Base):
3446
listener: IProtocolListener = None
35-
def __init__(self, listener: IProtocolListener):
47+
48+
def __init__(self, listener: IProtocolListener):
3649
super()
3750
self.listener = listener
38-
51+
3952
@staticmethod
4053
def link_message(name: str) -> list[Any]:
4154
"""links remote object"""
@@ -44,7 +57,8 @@ def link_message(name: str) -> list[Any]:
4457
@staticmethod
4558
def init_message(name: str, props: object) -> list[Any]:
4659
return [MsgType.INIT, name, props]
47-
@staticmethod
60+
61+
@staticmethod
4862
def unlink_message(name: str) -> list[Any]:
4963
"""unlinks remote object"""
5064
return [MsgType.UNLINK, name]
@@ -110,13 +124,7 @@ def handle_message(self, msg: list[Any]) -> bool:
110124
_, msgType, id, error = msg
111125
self.listener.handle_error(msgType, id, error)
112126
else:
113-
self.emit_log(LogLevel.DEBUG, f"not supported message type: {msgType}")
127+
self.emit_log(LogLevel.DEBUG,
128+
f"not supported message type: {msgType}")
114129
return False
115130
return True
116-
117-
118-
119-
120-
121-
122-

0 commit comments

Comments
 (0)