Last active
May 20, 2023 18:04
-
-
Save kg583/8d224979d8da7edd60768bd7453a7b64 to your computer and use it in GitHub Desktop.
A simple system for adaptively loading different data types into a class
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Dock: | |
""" | |
Base class to inherit to implement the loader system | |
""" | |
loaders = {} | |
def load(self, data): | |
""" | |
Load some data based on the data's type | |
Checks for a matching method in this class's loaders attribute, | |
which can be set explicitly or by decorating methods with @Loader | |
""" | |
# The format for loaders is {(type1, type2, ...): loader} | |
for loader_types, loader in self.loaders.items(): | |
if any(isinstance(data, loader_type) for loader_type in loader_types): | |
# Once a valid loader is found, no others are attempted | |
loader(self, data) | |
return | |
raise TypeError(f"could not find valid loader for type {type(data)}") | |
class Loader: | |
""" | |
Decorator class identifying methods as loaders | |
Specify the type(s) accepted by the loader via @Loader[type1, type2, ...] | |
Requires the owner class to inherit from Dock | |
Must be at the top of any decorator chain | |
""" | |
types = () | |
def __init__(self, func): | |
# We need to hold onto the function we're decorating until the owner is created | |
# You *could* do this with a regular function, but there's hardly any point | |
self._func = func | |
def __call__(self, *args, **kwargs): | |
# This is only here to appease type checkers | |
pass | |
def __class_getitem__(cls, item: tuple[type, ...] | type) -> type: | |
# Implements the syntax @Loader[type1, type2, ...] | |
# IMO it looks better than the obvious @Loader(type1, type2, ...) | |
try: | |
# Loader[type1, type2, ...] | |
return type("Loader", (Loader,), {"types": tuple(item)}) | |
except TypeError: | |
# Loader[type] | |
return type("Loader", (Loader,), {"types": (item,)}) | |
def __set_name__(self, owner, name: str): | |
# Tell the owner class that this a loader at class creation | |
# Note that we build a new copy of the dictionary to prevent parents | |
# from having loaders that belong to their children | |
owner.loaders = owner.loaders | {self.types: self._func} | |
# Keeping the method as a Loader instance would require more magic | |
# So let's forget that we're a Loader | |
setattr(owner, name, self._func) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ByteContainer(Dock): | |
def __init__(self, byte: bytes): | |
self.byte = byte | |
@Loader[int] | |
def load_int(integer: int): | |
self.byte = int.to_bytes(integer % 256, 1, 'big') | |
@Loader[str] | |
def load_string(string: str): | |
self.byte = string[0].encode() | |
@Loader[bytes, bytearray] | |
def load_bytes_like(bytes_like: bytes): | |
self.byte = bytes(bytes_like[:1]) | |
container = ByteContainer(b'\x69') | |
container.load(42) | |
container.load("foo") | |
container.load(b'ar') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment