From 49cac25cb7461ab04bced874ea9f14bf62510054 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 19 Oct 2026 08:12:00 +0200 Subject: [PATCH] core: action and handler types, gen_proc; revert proc and handler args order --- scopes/core/core.py | 37 ++++++++++++++++++++++++++++--------- scopes/core/environ.py | 6 +++--- scopes/util.py | 15 +++++++++++++++ tests/test_core.py | 6 ++---- tests/test_util.py | 7 ++++++- 5 files changed, 54 insertions(+), 17 deletions(-) diff --git a/scopes/core/core.py b/scopes/core/core.py index f518de4..027491b 100644 --- a/scopes/core/core.py +++ b/scopes/core/core.py @@ -6,21 +6,26 @@ from __future__ import annotations # no check of type annotations at runtime import asyncio from collections.abc import Awaitable, Callable, Coroutine from dataclasses import dataclass, field -from typing import Any +from typing import Any, Self from scopes.core import actor from scopes.core.message import Message +from scopes import util from scopes.util import keywords as k, Symbol type Mailbox = asyncio.Queue 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 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) - return scope global_mailbox: Mailbox = asyncio.Queue() @@ -38,19 +43,17 @@ class Scope: def __post_init__(self): self.set_proc() - def set_proc(self) -> Scope: + def set_proc(self) -> Self: self.proc = self.program(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) def neuron(scope: Scope) -> Mailbox: async def process(msg: Message) -> actor.Behavior | None: nonlocal scope - new_scope = await scope.proc(scope, msg) - if new_scope is not None: - scope = new_scope + scope = await scope.proc(msg, scope) or scope return process return actor.create(process) @@ -59,3 +62,19 @@ def create_zero(env: Mailbox) -> Mailbox: zero = neuron(scope) 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 + diff --git a/scopes/core/environ.py b/scopes/core/environ.py index 804efca..3bcd65f 100644 --- a/scopes/core/environ.py +++ b/scopes/core/environ.py @@ -4,13 +4,13 @@ from scopes import core from scopes.core import actor +from scopes.core.message import Message 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) - #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) return core.neuron(scope) diff --git a/scopes/util.py b/scopes/util.py index 94a41c9..f0bc861 100644 --- a/scopes/util.py +++ b/scopes/util.py @@ -1,6 +1,7 @@ # scopes.util import base64 +from collections.abc import Iterable import hashlib from secrets import choice import string @@ -24,6 +25,20 @@ class 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 # for authentication, encryption, and other purposes diff --git a/tests/test_core.py b/tests/test_core.py index fd6626b..2e01ed5 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -67,14 +67,12 @@ async def meta_env(collector): @pytest.mark.anyio async def test_core(meta_env, collector): 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) + await env.put(Message(k.env, k.hello)) await zero.put(Message(k.csys, k.hello)) await asyncio.sleep(0.01) assert len(collector) == 2 + assert collector[0].domain is k.env assert collector[1].domain is k.csys #print(collector) diff --git a/tests/test_util.py b/tests/test_util.py index d862b4c..f2b6ee1 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -4,7 +4,7 @@ import pytest -from scopes.util import keywords as k +from scopes.util import keywords as k, flatten def test_keywords(): assert k.dummy is k.dummy @@ -13,3 +13,8 @@ def test_keywords(): assert k('dummy') is k.dummy 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] +