Skip to content

Instantly share code, notes, and snippets.

@pauleveritt
Created April 18, 2020 15:56
Show Gist options
  • Save pauleveritt/6cac82ab73be9d78c3c378318cb98f23 to your computer and use it in GitHub Desktop.
Save pauleveritt/6cac82ab73be9d78c3c378318cb98f23 to your computer and use it in GitHub Desktop.
Writeup of using Protocols for pluggable components.
"""
I'm interested Python and the Modern Web. Including, a modern
approach to templating: developer-focused, closer to Python,
where code quality tools can help. Draft:
https://viewdom-wired.readthedocs.io/en/latest/why.html
I'm working on pluggable "components", as that link shows. Normally
I'd throw zope.interface at it, but PEP 544 Protocols is more realistic:
mypy support by default, some IDE integration to help DX, etc.
I'm using tagged/htm.py/viewdom/viewdom_wired as my layers for a
React-style component experience. My components usually (but not always)
are dataclasses, so they can express dependency injection:
https://viewdom-wired.readthedocs.io/en/latest/usage.html#hello-world
I'd like to do PEP 544 structural subtyping, rather than inheritance
tricks. Here's an example from Glyph showing a workaround for using
this with callables such as dataclasses:
https://github.com/python/mypy/issues/4717#issuecomment-454609539
I could then use this approach behind a `@component` decorator to use `wired`
as a registry for pluggable components, with Protocols as the pluggability.
"""
from dataclasses import dataclass
# This starts the part that is in framework space, so civilians don't
# have to see it, their tooling, e.g. IDE autocomplete, just starts
# working.
from typing import Callable, Type, TypeVar
from typing_extensions import Protocol
from viewdom import html
protocol = TypeVar("protocol")
# In wired, this decorator would also make a registration in the registry,
# as well as a constructor which sniffs the dataclass fields to do the DI.
def component(c: Callable[[], protocol]) -> Callable[[Type[protocol]], Type[protocol]]:
def decor(input_value: Type[protocol]) -> Type[protocol]:
return input_value
return decor
# /end framework space
# This is done by the creator of some sexy library of ready-to-go
# components. The average consumer doesn't see this.
class Heading(Protocol):
logo_src: str
def __call__(self):
...
@component(Heading)
@dataclass
class GoodHeading:
logo_src: str
def __call__(self):
return html('<nav><{Logo} src={self.logo_src} /></nav>')
@component(Heading)
@dataclass
class BadHeading:
logo_height: int
def __call__(self):
return html('<nav><{Logo} src="logo.png" height={self.logo_height}/></nav>')
# /end component library author consumer
# I will leave out what the end-user will see, as it is specific to
# the work I'm doing.
if __name__ == '__main__':
good = GoodHeading(logo_src='logo.png')
bad = BadHeading(logo_height=50)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment