willmcgugan/declare

Some Suggestions

Closed this issue · 0 comments

Saw this on Twitter, cool project! It's great to see experienced developers experimenting with things (I'm a big fan of building experimental things), there's not enough fun with software these days. With that being said, I have a few suggestions:

I do realize that this library is not supposed to replace dataclasses or pydantic, but adding an __init__ could pave the way for some new features in the future. A new decorator could be introduced to build a type at runtime via an automatically generated __init__:

import declare


@declare.init
class Color:
    """A color object with RGB, and alpha components."""

    red = declare.Int(0)
    green = declare.Int(0)
    blue = declare.Int(0)
    alpha = declare.Float(1.0)

    @red.validate
    @green.validate
    @blue.validate
    def _validate_component(self, component: int) -> int:
        """Restrict RGB to 0 -> 255."""
        return max(0, min(255, component))

    @alpha.validate
    def _validate_alpha(self, alpha: float) -> float:
        return max(0.0, min(1.0, alpha))

color = Color(0, 0, 255, 1.0)

If you decide to go that route, than you could also introduce a way to parse a type from a given input:

import declare


@declare.init
class Point:
    x = declare.Int(0)
    y = declare.Int(0)

    @x.parse
    def _parse_x(self, obj: Any) -> int:
        return int(obj)

point = Point('1', 2)

Alternatively, instead of using parse for __init__ only, maybe just call it every time the value is changed? This could be similar to how watch works, but it takes whatever type was passed instead of an old and new type, then whatever type the parser returns can be sent to the watcher and validator:

import declare


class Point:
    x = declare.Int(0)
    y = declare.Int(0)

    @x.parse
    def _parse_x(self, obj: Any) -> int:
        return int(obj)

point = Point()
point.x = '1'

If you want to go the route of heading away from dataclass-like libraries instead, then maybe this could be used to make some debugging tools inside of Textual or something other? I think you could track where and when attributes were changed via some magic with Frame objects:

import declare


class Foo:
    bar = declare.Str("spam")

foo = Foo()
foo.bar = "hello"

print(declare.stack(foo))
"""
{
    "bar": [AttributeCreated(frame=..., value="spam", when=...), AttributeChanged(frame=..., old_value="spam", value="hello", when=...)]
}
"""

Anyways, this is all speculation. This project came out like what, an hour ago? Keep up the good work!