tombulled/roster

DevLog

Opened this issue ยท 18 comments

DevLog

Implement a set-based record?

import registrate

record = registrate.SetBasedRecord() # rename

record(1)
record(2)
record(3)
record(1)
>>> record
{1, 2, 3}

Note: Support others (e.g. deque)?

Implement decorators:

import roster

@roster.record
def numbers(number: int) -> int:
    return number

@roster.roster(key=True)
def functions(func) -> str:
    return func.__name__

@roster.register(key=True)
def routes(path: str, method: str = 'GET') -> Route:
    return Route(path, method=method)

Draw inspiration from annotate

Ability to change the underlying store? E.g., set instead of list, Namespace instead of dict

record: Record[Set[int]] = Record(set())
class Namespace(Record[T]):
    store: SimpleNamespace[T]

    def record(self, item: T, /) -> T:
        self.store[...] = ...

        return item
def value_register(hook):
    @register
    def r(func: Callable, *args, **kwargs) -> Tuple[Callable, Any]:
        return (func, hook(*args, **kwargs))

    return r

def key_register(hook):
    @register
    def r(func: Callable, *args, **kwargs) -> Tuple[Callable, Any]:
        return (hook(*args, **kwargs), func)

    return r

Make Register accept a single item, have to subclass to 'flatten', e.g:

class BasicRegister(Register):
    def __init__(self, factory):
        super(lambda k, v: (k, v))

        self._factory = factory

    def __call__(self, *args, **kwargs):
        return super().__call__(self._factory(*args, **kwargs))
        

Consider:

@routes('/', method='GET')
def root(): pass
>>> routes
[Route(path='/', method='GET', target=<function root>)]

Hence, all containers should support an entrypoint

Maybe the decorators get you to provide the entrypoint, and you can choose to specify a resolver?

E.g:

@register(resolve=lambda k, v: (v, k))
def routes(path: str, method: str='GET') -> Route:
    return Route(path=path, method=method)
store = {}

def func(func):
    store[func] = 'FUNCTION'

    return func

def cls(cls):
    store[cls] = 'CLASS'

    return cls

@func
def foo(): pass

@cls
class Bar: pass
routes = MapStore()

@routes.key
def route(path: str, method: str='GET') -> Route:
    return Route(path=path, method=method)
class Record(Generic[V], ABC):
    @abstractmethod
    def record(self, value: V, /) -> None:
        ...

    def __call__(self, value: V, /) -> V:
        self.record(value)

        return value

    def item(self, func: Callable[[T], V], /) -> Callable[[T], T]:
        def proxy(item: T, /) -> T:
            self.record(func(item))

            return item

        return proxy

class ListRecord(Record[V], List[V]):
    def record(self, value: V, /) -> None:
        self.append(value)

class SetRecord(Record[V], Set[V]):
    def record(self, value: V, /) -> None:
        self.add(value)
class Register(Generic[K, V], ABC):
    @abstractmethod
    def register(self, key: K, value: V, /) -> None:
        ...

    def __call__(self, key: K, /) -> Callable[[V], V]:
        def proxy(value: V, /) -> V:
            self.register(key, value)

            return value

        return proxy

    def key(self, func: Callable[..., K], /) -> Callable[..., Callable[[V], V]]:
        def proxy(*args: Any, **kwargs: Any) -> Callable[[V], V]:
            key: K = func(*args, **kwargs)

            def decorator(value: V, /) -> V:
                self.register(key, value)

                return value

            return decorator

        return proxy

    def value(self, func: Callable[..., V], /) -> Callable[..., Callable[[K], K]]:
        def proxy(*args: Any, **kwargs: Any) -> Callable[[K], K]:
            value: V = func(*args, **kwargs)

            def decorator(key: K, /) -> K:
                self.register(key, value)

                return key

            return decorator

        return proxy

    def entry(self, func: Callable[[T], Tuple[K, V]], /) -> Callable[[T], T]:
        def proxy(item: T, /) -> T:
            self.register(*func(item))

            return item

        return proxy

class DictRegister(Register[K, V], Dict[K, V]):
    def register(self, key: K, value: V, /) -> None:
        self[key] = value

Implement decorators:

import roster

@roster.record
def numbers(number: int) -> int:
    return number

@roster.roster(key=True)
def functions(func) -> str:
    return func.__name__

@roster.register(key=True)
def routes(path: str, method: str = 'GET') -> Route:
    return Route(path, method=method)

Draw inspiration from annotate

Not a fan of this idea as it becomes unclear.

record: Record[Set[int]] = Record(set())

This can't be done because a set is a Collection, whereas a list is a Sequence. Handy ref: https://docs.python.org/3/library/collections.abc.html#collections.abc.AsyncIterable

Consider:

@routes('/', method='GET')
def root(): pass
>>> routes
[Route(path='/', method='GET', target=<function root>)]

Hence, all containers should support an entrypoint

^^^ TODO. Currently RecordABC.item expects a func that returns its first parameter - I assume the value would need to be returned instead?

Add PyPI link to GitHub repo

TODO: Improve typing - use param specs