core: action and handler types, gen_proc; revert proc and handler args order

This commit is contained in:
Helmut Merz 2026-10-19 08:12:00 +02:00
parent 4b3af802cd
commit 49cac25cb7
5 changed files with 54 additions and 17 deletions

View file

@ -6,21 +6,26 @@ from __future__ import annotations # no check of type annotations at runtime
import asyncio import asyncio
from collections.abc import Awaitable, Callable, Coroutine from collections.abc import Awaitable, Callable, Coroutine
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Any from typing import Any, Self
from scopes.core import actor from scopes.core import actor
from scopes.core.message import Message from scopes.core.message import Message
from scopes import util
from scopes.util import keywords as k, Symbol from scopes.util import keywords as k, Symbol
type Mailbox = asyncio.Queue type Mailbox = asyncio.Queue
type Categ = tuple[Symbol, Symbol] type Categ = tuple[Symbol, Symbol]
type Proc = Callable[[Scope, Message], Awaitable[Scope]] type Proc = Callable[[Message, Scope], Awaitable[Scope | None]]
type Program = Callable[[Scope], Proc] type Program = Callable[[Scope], Proc]
type Syn = Callable[[Message], Message] type Syn = Callable[[Message], Message]
async def proc_dummy(scope: Scope, msg: Message) -> Scope: type Handler = Callable[[Message, Scope],
Awaitable[Message | None | tuple[Message | None, Scope | None]]]
type Handlers = Handler | None | list[Handlers]
type Actions = dict[Symbol, Handlers]
async def proc_dummy(msg: Message, scope: Scope) -> None:
await scope.environ.put(msg) await scope.environ.put(msg)
return scope
global_mailbox: Mailbox = asyncio.Queue() global_mailbox: Mailbox = asyncio.Queue()
@ -38,19 +43,17 @@ class Scope:
def __post_init__(self): def __post_init__(self):
self.set_proc() self.set_proc()
def set_proc(self) -> Scope: def set_proc(self) -> Self:
self.proc = self.program(self) self.proc = self.program(self)
return self return self
def create_scope(env: Mailbox, cat=(k.csys, k.c00), **kw) -> Scope: def create_scope(env: Mailbox, cat: Categ = (k.csys, k.c00), **kw) -> Scope:
return Scope(environ=env, categ=cat, **kw) return Scope(environ=env, categ=cat, **kw)
def neuron(scope: Scope) -> Mailbox: def neuron(scope: Scope) -> Mailbox:
async def process(msg: Message) -> actor.Behavior | None: async def process(msg: Message) -> actor.Behavior | None:
nonlocal scope nonlocal scope
new_scope = await scope.proc(scope, msg) scope = await scope.proc(msg, scope) or scope
if new_scope is not None:
scope = new_scope
return process return process
return actor.create(process) return actor.create(process)
@ -59,3 +62,19 @@ def create_zero(env: Mailbox) -> Mailbox:
zero = neuron(scope) zero = neuron(scope)
return zero return zero
def gen_proc(actions: Actions, default: Handlers = None,
before: Handlers = None, after: Handlers = None) -> Proc:
async def proc(msg: Message, scope: Scope) -> Scope:
acts = actions.get(msg.head['action'], actions.get(k.default, default))
acts = util.flatten(before, acts, after, ignore_none=True)
for act in acts:
msg = await act(msg, scope)
if isinstance(msg, tuple):
msg, new_scope = msg
if new_scope is not None:
scope = new_scope
if msg is None:
break
return scope
return proc

View file

@ -4,13 +4,13 @@
from scopes import core from scopes import core
from scopes.core import actor from scopes.core import actor
from scopes.core.message import Message
from scopes.util import keywords as k from scopes.util import keywords as k
async def proc_env(scope, msg): async def proc_env(msg: Message, scope: core.Scope) -> None:
await scope.environ.put(msg) await scope.environ.put(msg)
#return scope
def create(meta_env): def create(meta_env: core.Mailbox) -> core.Mailbox:
scope = core.create_scope(meta_env, (k.env, k.c00), program=lambda x: proc_env) scope = core.create_scope(meta_env, (k.env, k.c00), program=lambda x: proc_env)
return core.neuron(scope) return core.neuron(scope)

View file

@ -1,6 +1,7 @@
# scopes.util # scopes.util
import base64 import base64
from collections.abc import Iterable
import hashlib import hashlib
from secrets import choice from secrets import choice
import string import string
@ -24,6 +25,20 @@ class Symbols:
keywords = Symbols() keywords = Symbols()
# list / iterable utilities
def flatten(*it, ignore_none=False):
accu = []
def doit(it):
if ignore_none and it is None:
return
if isinstance(it, Iterable):
for t in it:
doit(t)
else:
accu.append(it)
doit(it)
return accu
# random strings, hashes, encodings # random strings, hashes, encodings
# for authentication, encryption, and other purposes # for authentication, encryption, and other purposes

View file

@ -67,14 +67,12 @@ async def meta_env(collector):
@pytest.mark.anyio @pytest.mark.anyio
async def test_core(meta_env, collector): async def test_core(meta_env, collector):
env = environ.create(meta_env) env = environ.create(meta_env)
await env.put(Message(k.env, k.hello))
await asyncio.sleep(0.01)
assert len(collector) == 1
assert collector[0].domain is k.env
zero = core.create_zero(env) zero = core.create_zero(env)
await env.put(Message(k.env, k.hello))
await zero.put(Message(k.csys, k.hello)) await zero.put(Message(k.csys, k.hello))
await asyncio.sleep(0.01) await asyncio.sleep(0.01)
assert len(collector) == 2 assert len(collector) == 2
assert collector[0].domain is k.env
assert collector[1].domain is k.csys assert collector[1].domain is k.csys
#print(collector) #print(collector)

View file

@ -4,7 +4,7 @@
import pytest import pytest
from scopes.util import keywords as k from scopes.util import keywords as k, flatten
def test_keywords(): def test_keywords():
assert k.dummy is k.dummy assert k.dummy is k.dummy
@ -13,3 +13,8 @@ def test_keywords():
assert k('dummy') is k.dummy assert k('dummy') is k.dummy
assert k('new') is k('new') assert k('new') is k('new')
def test_flatten():
lst = (1, (None, 2), (3, (4, 5)))
assert flatten(lst) == [1, None, 2, 3, 4, 5]
assert flatten(42, lst, (6, 7), ignore_none=True) == [42, 1, 2, 3, 4, 5, 6, 7]