Skip to content

Commit

Permalink
wip: Collect refs to delegate while marshaling; define all event hand…
Browse files Browse the repository at this point in the history
…lers as Awaitable
  • Loading branch information
lo5 committed Dec 11, 2021
1 parent 240cbf5 commit 681ad48
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 26 deletions.
4 changes: 2 additions & 2 deletions compiler/lib/python.dart
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ class PythonTranslator {
final params = t.parameters.isNotEmpty
? (t.element == IRElement.func)
// 'Callable' must be used as 'Callable[[arg, ...], result]'
? '[[${comma(ps.sublist(0, ps.length - 1))}], ${ps.last}]'
? '[[${comma(ps.sublist(0, ps.length - 1))}], Awaitable[${ps.last}]]'
: '[${comma(ps)}]'
: '';
final name = _n(t.name);
Expand Down Expand Up @@ -400,7 +400,7 @@ class PythonTranslator {
String _emit(Iterable<IRElement> elements) {
p('from abc import ABC');
p('from enum import Enum');
p('from typing import Generic, TypeVar, Callable, Any, Optional, Iterable, List, Dict');
p('from typing import Generic, TypeVar, Callable, Awaitable, Any, Optional, Iterable, List, Dict');
p('');
p('');
p('def _noop(*args, **kwargs) -> Any:');
Expand Down
36 changes: 21 additions & 15 deletions python/examples/startup_namer.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,43 @@
_biggerFont = TextStyle(font_size=18.0)


def _build_suggestions():
_suggestions = [random.choice(prefixes) + random.choice(suffixes) for _ in range(10)]
return ListView(
padding=EdgeInsets.all(16.0),
# TODO insert Divider() in between
children=[ListTile(title=Text(suggestion, style=_biggerFont)) for suggestion in _suggestions],
)
async def _increment_counter(ui: UI):
print('button clicked')


def build():
return MaterialApp(
async def on_load(ui: UI):
_suggestions = [random.choice(prefixes) + random.choice(suffixes) for _ in range(10)]
ui[''] = MaterialApp(
title='Startup Name Generator',
home=Scaffold(
app_bar=AppBar(
title=Text('Startup Name Generator'),
),
body=_build_suggestions(),
body=ListView(
padding=EdgeInsets.all(16.0),
# TODO insert Divider() in between
children=[ListTile(title=Text(suggestion, style=_biggerFont)) for suggestion in _suggestions],
),
floating_action_button=FloatingActionButton(
on_pressed=_increment_counter,
tooltip='Increment',
child=Icon(icon=Icons.add),
),
),
)
await ui.save()


async def serve(ui: UI):
ui[''] = build()
await ui.save()
nitro = Nitro(on_load)


async def websocket_endpoint(websocket: starlette.websockets.WebSocket):
host = websocket.client.host
port = websocket.client.port
await websocket.accept()
await serve(UI('', websocket.send_text))
await nitro.load(websocket.send_text)
while True:
await serve(UI(await websocket.receive_text(), websocket.send_text))
await nitro.handle(await websocket.receive_text(), websocket.send_text)


app: Any = Starlette(
Expand Down
2 changes: 1 addition & 1 deletion python/h2o_nitro/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .types import *
from .core import UI
from .core import Nitro, UI

__version__ = '0.1.0'
__author__ = 'Prithvi Prabhu'
96 changes: 88 additions & 8 deletions python/h2o_nitro/core.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,99 @@
from typing import Any
from .protocol import dump, marshal
import json
import inspect
from typing import Any, Callable, Dict, List, Tuple, Optional, Awaitable

ChangeSet = List[Optional[
Tuple[str, str],
Tuple[str, str, Any],
]]

Delegate = Callable[['UI'], Awaitable[None]]
Send = Callable[[str], Awaitable[None]]


def _marshal(x: Any):
return json.dumps(x, allow_nan=False, separators=(',', ':'))


def _fqn(x: Any):
return f'{x.__module__}.{x.__name__}' if hasattr(x, '__name__') and hasattr(x, '__module__') else ''


class _GUID:
def __init__(self):
self._id: int = 0

def read(self):
self._id += 1
return str(self._id)


class _DispatchTable:
def __init__(self):
self._guid = _GUID()
self._guids: Dict[str, Delegate] = {}
self._delegates: Dict[Delegate, str] = {}

def add(self, d: Delegate) -> str:
if d in self._delegates:
return self._delegates[d]
guid = self._guid.read()
self._delegates[d] = guid
self._guids[guid] = d
return guid

def lookup(self, guid: str) -> Delegate:
return self._guids.get(guid, None)


def _dump(d: _DispatchTable, xs: Any) -> Any:
if isinstance(xs, (list, tuple)):
return [_dump(d, x) for x in xs]
elif isinstance(xs, dict):
return {k: _dump(d, v) for k, v in xs.items()}
elif hasattr(xs, '_nx_'):
return _dump(d, xs._nx_)
elif inspect.isfunction(xs):
if inspect.iscoroutinefunction(xs):
return {'f': d.add(xs)}
else:
raise ValueError(f'dump failed: want async function, got non-async: {_fqn(xs)}() ')
else:
return xs


class Nitro:
def __init__(self, on_load: Delegate):
self.dispatch_table = _DispatchTable()
self._on_load = on_load

async def load(self, send: Send, **context):
self._on_load(UI(self, send, context))

async def handle(self, request: str, send: Send, **context):
r = json.loads(request)
if isinstance(r, dict):
guid = r.get('f', None)
if guid is not None:
d = self.dispatch_table.lookup(guid)
await d(UI(self, send, context)) # TODO pass event args


class UI:
def __init__(self, request: str, on_send):
self._on_send = on_send
self._changes = []
def __init__(self, nitro: Nitro, send: Send, context: dict):
self._nitro = nitro
self._send = send
self._changes: ChangeSet = []
for k, v in context.items():
setattr(self, k, v)

def __setitem__(self, key: str, value: Any):
self._changes.append(('=', key, dump(value)))
self._changes.append(('=', key, _dump(self._nitro.dispatch_table, value)))

def __delitem__(self, key: str):
self._changes.append(('~', key))

async def save(self):
data = marshal(self._changes)
changes = _marshal(self._changes)
self._changes.clear()
await self._on_send(data)
await self._send(changes)

0 comments on commit 681ad48

Please sign in to comment.