Skip to content

Instantly share code, notes, and snippets.

@Invisi
Last active March 19, 2020 13:30
Show Gist options
  • Save Invisi/2f7f76ad981d34bf5b95fb2b3a764681 to your computer and use it in GitHub Desktop.
Save Invisi/2f7f76ad981d34bf5b95fb2b3a764681 to your computer and use it in GitHub Desktop.
Simple, stupid implementation for (pydantic-like) class-based definitions in Python 3
import enum
import json
from pathlib import Path
class Encoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, enum.Enum):
return o.value
return o.__dict__
class ClassObject:
def __init__(self, **kwargs):
annotation_keys = self.__annotations__.keys()
# Assign values
for key, value in kwargs.items():
if key in annotation_keys:
# Handle classes
if issubclass(self.__annotations__[key], ClassObject) and isinstance(
value, dict
):
# Try to parse class
setattr(self, key, self.__annotations__[key](**value))
continue
# Handle enums, right now only string enums are supported
if issubclass(self.__annotations__[key], enum.Enum) and isinstance(
value, str
):
try:
setattr(self, key, self.__annotations__[key](value))
except ValueError:
# Set default if the config's value does not exist in the enum
setattr(self, key, getattr(self, key))
continue
# Type does not match annotation
if type(value) is not self.__annotations__[key]:
raise TypeError(
f"Value of {key} ({value}, {type(value)}) does not match"
f" annotation's type ({self.__annotations__[key]})."
)
setattr(self, key, value)
else:
raise ValueError(f"Key {key} does not exist in annotation.")
# Check if all values are set
kwargs_keys = kwargs.keys()
missing_keys = []
for key in annotation_keys:
# Set default values so that they appear in __dict__
if not hasattr(self, key) and key not in kwargs_keys:
missing_keys.append(key)
elif key not in kwargs_keys:
setattr(self, key, getattr(self, key))
if len(missing_keys) > 0:
raise AttributeError(
f"{self.__class__.__name__} is missing the following keys: {missing_keys}"
)
def __setattr__(self, key, value):
# Verify type if in annotation
if key in self.__annotations__ and type(value) is not self.__annotations__[key]:
raise TypeError(
f"Value of {key} ({value}, {type(value)}) does not match"
f" annotation's type ({self.__annotations__[key]})."
)
elif key not in self.__annotations__:
print(
f"Warning: Setting unknown value/key combination in config {key}={value} ({type(value)})"
)
super().__setattr__(key, value)
def __str__(self):
sorted_kv = sorted([f"{k}={v}" for k, v in self.__dict__.items()])
return f"<{self.__class__.__name__} {' '.join(sorted_kv)}>"
def __repr__(self):
return self.__dict__
# Example usage
class SubConfig(ClassObject):
d: str = "abc"
class Config(ClassObject):
a: str = ""
b: int = 2
c: int
sub_config: SubConfig = SubConfig()
def save(self):
cf = Path("state.json")
try:
cf.write_text(json.dumps(self, cls=Encoder, sort_keys=True, indent=4))
except OSError:
print("Failed to save to state file")
raise
@staticmethod
def load():
cf = Path("state.json")
if cf.exists():
cj = json.loads(cf.read_text())
return Config(**cj)
else:
return Config()
if __name__ == "__main__":
conf = Config(c=3)
print(conf) # <Config c=5 a= b=2 sub_config=<SubConfig d=abc>>
Config() # ValueError: Key c does not define a default value and is missing in kwargs.
Config(
b="2", c=3
) # TypeError: Value of b (2, <class 'str'>) does not match annotation's type (<class 'int'>).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment