core.actor: start with simple actor implementation

This commit is contained in:
Helmut Merz 2026-10-01 22:28:25 +02:00
parent f0745f4a7b
commit 5b54e76327
6 changed files with 81 additions and 41 deletions

View file

@ -2,48 +2,22 @@
import asyncio
QUIT = object()
from scopes import util
quit = util.Symbol("quit")
async def loop(q, bhv):
while True:
msg = await q.get()
if msg == QUIT:
if msg is quit:
break
bhv = await bhv(msg) or bhv
def create(bhv):
def create(bhv, group=None):
q = asyncio.Queue()
task = asyncio.create_task(loop(q, bhv))
return q, task
# interactive tests
async def handle_basic(msg):
match msg[0]:
case "show": show(msg)
case "switch": return handle_alt
case _: show(msg)
async def handle_alt(msg):
await asyncio.sleep(0.01)
match msg[0]:
case "show": print('no way')
case _: show(msg)
def show(msg):
print(msg)
async def main():
q, task = create(handle_basic)
await q.put(["Hello World!"])
await q.put(["switch"])
await q.put(["show", "nope"])
await q.put(["Hello World!"])
await q.put(QUIT)
#await task
await asyncio.wait([task])
#await asyncio.sleep(10)
def run():
asyncio.run(main())
if group is None:
return q, asyncio.create_task(loop(q, bhv))
else:
group.create_task(loop(q, bhv))
return q

21
scopes/shape.py Normal file
View file

@ -0,0 +1,21 @@
# scopes.shape
"""really abstract basic data shape definitions"""
class Record:
headFields = [] # to be defined by subclass
def __init__(self, *headValues, data=None, **kw):
self.head = {}
for k, v in kw.items():
if k in self.headFields:
self.head[k] = kw.pop(k)
for ix, hv in enumerate(headValues):
self.head[self.headFields[ix]] = hv
for k in self.headFields:
if self.head.get(k) is None:
self.head[k] = ''
setattr(self, k, self.head[k])
self.data = data or {}

View file

@ -20,14 +20,14 @@ class Track:
headFields = ['taskId', 'userName']
prefix = 'rec'
def __init__(self, *keys, data=None, timeStamp=None, trackId=None,
def __init__(self, *headValues, data=None, timeStamp=None, trackId=None,
container=None, **kw):
self.head = {}
for k, v in kw.items():
if k in self.headFields:
self.head[k] = kw.pop(k)
for ix, k in enumerate(keys):
self.head[self.headFields[ix]] = k
for ix, hv in enumerate(headValues):
self.head[self.headFields[ix]] = hv
for k in self.headFields:
if self.head.get(k) is None:
self.head[k] = ''

45
tests/test_actor.py Normal file
View file

@ -0,0 +1,45 @@
# py-scopes: tests.test_actor
""" Tests for scopes.core.actor"""
import asyncio
import pytest
from scopes.core import actor
from scopes.util import keywords as k
collector = []
def show(msg):
collector.append(msg)
async def handle_basic(msg):
match msg:
case k.show: show(msg)
case k.switch: return handle_alt
case _: show(msg)
async def handle_alt(msg):
match msg:
case k.show: show('switched')
case _: await asyncio.sleep(0.01); show(msg)
@pytest.mark.anyio
async def test_actor():
async with asyncio.TaskGroup() as group:
q1 = actor.create(handle_basic, group)
q2 = actor.create(handle_basic, group)
await q1.put("actor 1")
await q1.put(k.show)
await q1.put(k.switch)
await q1.put(k.show)
await q1.put("actor 1: Goodbye")
await q2.put("actor 2")
await q2.put(k.show)
await q1.put(actor.quit)
await q2.put(actor.quit)
#await asyncio.wait([t1, t2])
#print(collector)
assert len(collector) == 6
assert collector[-1] == 'actor 1: Goodbye'

View file

@ -59,5 +59,5 @@ def test_tracking(storage, caplog):
tr['timeStamp'] = '...'
assert tr == {'uid': 'rec-1', 'head': {'taskId': 't01', 'userName': 'john'},
'data': {'activity': 'testing'}, 'timeStamp': '...'}
print('\nLogs: ', caplog.text)
#print('\nLogs: ', caplog.text)

View file

@ -1,6 +1,6 @@
# py-scopes: tests.test_util
"""Tests for scopes.util package"""
"""Tests for the scopes.util package"""
import pytest