%load_ext nb_mypy
# Ignore no_redef https://github.com/python/mypy/issues/11065
Version 1.0.5
from typing import Optional, List, Type, Dict, Any
class MetaConstant(type):
def __repr__(cls) -> str:
parent = cls.mro()[1]
if parent == object:
return cls.__name__
else:
repr = f"{parent.__name__}.{cls.__name__}"
if hasattr(cls, "_value"):
repr += f'(\'{cls._value}\')'
return repr
class Constant(metaclass=MetaConstant):
_value: str
def __new__(
cls, val: Optional[str] = None, _visited: Optional[List[Type]] = None
) -> "Constant":
if _visited is None:
_visited = []
_value: Optional[str] = getattr(cls, "_value", None)
if _value is None and val is None:
msg = f'Abstract {cls.__bases__[0].__name__} "{cls.__name__}" cannot be instantiated'
raise TypeError(msg)
elif _value is None:
# DFS through the class tree to find a matching subclass.
for child in cls.__subclasses__():
try:
if match := child.__new__(child, val, _visited=_visited):
return match
except:
continue
# Compile a list of the subclassese we've tried for a better exception message.
valids = ", ".join([f"{c}" for c in _visited])
msg = f"invalid {cls.__name__}:'{val}' is not valid for any {cls.__name__}s: [{valids}]"
raise ValueError(msg)
elif val is None:
# Default case.
return super(Constant, cls).__new__(cls)
else:
# Base case for DFS.
if _value == val:
return super(Constant, cls).__new__(cls)
else:
_visited.append(cls)
msg = f"invalid value for {cls}: '{val}'"
raise ValueError(msg)
def __init_subclass__(cls) -> None:
"""Register subclass as parent attribute
Ex. Datacenter.fra4
"""
setattr(type(cls), cls.__name__, cls)
@property
def value(self) -> str:
return self._value
def __repr__(self) -> str:
return "Value: "+self._value
def __str__(self) -> str:
return self._value
def __eq__(self, other:Any) -> bool:
return self._value == other._value
class Location(Constant):
DataCenter: "DataCenter"
AvailabilityZone: "AvailabilityZone"
class DataCenter(Location):
ams1: "ams1"
lon1: "lon1"
class ams1(DataCenter):
_value = "ams1"
class lon1(DataCenter):
_value = "lon1"
class AvailabilityZone(Location):
west1_a: "west1_a"
west2_a: "west2_a"
class west1_a(AvailabilityZone):
_value = "west1-a"
class west2_a(AvailabilityZone):
_value = "west2-a"
# Typed
ams1
DataCenter.ams1('ams1')
# Singleton value
ams1()
Value: ams1
ams1().value
'ams1'
ams1() == ams1()
True
# REPR, string format works
f"My datacenter is {ams1=}"
"My datacenter is ams1=DataCenter.ams1('ams1')"
# Validation
DataCenter("ams1").value
'ams1'
Location("ams1").value
'ams1'
type(Location("ams1"))
DataCenter.ams1('ams1')
type(Location("west1-a"))
AvailabilityZone.west1_a('west1-a')
# Free Validation with helpful error messages!
try:
DataCenter("foo1")
except ValueError as e:
print(e)
invalid DataCenter:'foo1' is not valid for any DataCenters: [DataCenter.ams1('ams1'), DataCenter.lon1('lon1')]
# Free Validation with helpful error messages!
try:
Location("foo1")
except ValueError as e:
print(e)
invalid Location:'foo1' is not valid for any Locations: [DataCenter.ams1('ams1'), DataCenter.lon1('lon1'), AvailabilityZone.west1_a('west1-a'), AvailabilityZone.west2_a('west2-a')]
# Multiple ways to instantiate, type stable output
Location("ams1") == DataCenter("ams1") == Location.DataCenter.ams1 == DataCenter.ams1 == ams1()
True
type(Location("ams1")) == type(DataCenter("ams1")) == ams1
True
def print_my_location(location:Location):
print(location)
def wrapper(value:str):
print_my_location(value)
<cell>2: error: Argument 1 to "print_my_location" has incompatible type "str"; expected "Location" [arg-type]
from multimethod import multimethod
DOMAIN = "moll.dev"
@multimethod # type: ignore[no-redef]
def generate_vm_name(vmname:str, location:Location):
raise ValueError(f"Location '{type(location)}' not supported yet!")
@multimethod # type: ignore[no-redef]
def generate_vm_name(vmname:str, location:DataCenter) -> str:
print(type(location))
return f"{vmname}.{location}.{DOMAIN}"
generate_vm_name("test1-vm", Location("ams1"))
DataCenter.ams1('ams1')
'test1-vm.ams1.moll.dev'
try:
generate_vm_name("test1-vm", Location("west2-a"))
except ValueError as e:
print(f"Got exception >>> {e}")
Got exception >>> Location 'AvailabilityZone.west2_a('west2-a')' not supported yet!
@multimethod
def generate_vm_name(vmname:str, location:AvailabilityZone) -> str:
print(type(location))
return f"{vmname}-{location}.{DOMAIN}"
generate_vm_name("test1-vm", Location("west2-a"))
AvailabilityZone.west2_a('west2-a')
'test1-vm-west2-a.moll.dev'
class Environment(Constant):
Prod:"Prod"
Dev:"Dev"
PCC:"PCC"
class Prod(Environment):
_value = "prod"
class Dev(Environment):
_value = "dev"
class PCC(Environment):
_value = "pcc"
↓env \ location → | lon1 | ams1 | west1-a | west2-a |
---|---|---|---|---|
prod | ✅ | ✅ | ✅ | ✅ |
dev | ❌ | ❌ | ✅ | ✅ |
pcc | ❌ | ✅ | ❌ | ❌ |
@multimethod # type: ignore[no-redef]
def check(environment:Environment, location:Location):
# log f"Not a supported deployment combination ({environment}, {location})!"
return False
@multimethod # type: ignore[no-redef]
def check(environment:Prod, location:Location):
return True
@multimethod # type: ignore[no-redef]
def check(environment:Dev, location:AvailabilityZone):
return True
@multimethod # type: ignore[no-redef]
def check(environment:Dev, location:AvailabilityZone):
return True
@multimethod # type: ignore[no-redef]
def check(environment:PCC, location:ams1):
return True
environments = [Prod(), Dev(), PCC()]
locations = [lon1(), ams1(), west1_a(), west2_a()]
print(f"\t{'\t'.join([str(l) for l in locations])}")
for environment in environments:
print(environment, end="\t")
for location in locations:
val = "✅" if check(environment, location) else "❌"
print(val, end="\t")
print()
lon1 ams1 west1-a west2-a prod ✅ ✅ ✅ ✅ dev ❌ ❌ ✅ ✅ pcc ❌ ✅ ❌ ❌