#!/usr/bin/env python # coding: utf-8 # In[1]: get_ipython().run_line_magic('load_ext', 'nb_mypy') # Ignore no_redef https://github.com/python/mypy/issues/11065 # In[2]: 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 # In[3]: 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" # In[4]: # Typed ams1 # In[5]: # Singleton value ams1() # In[6]: ams1().value # In[7]: ams1() == ams1() # In[8]: # REPR, string format works f"My datacenter is {ams1=}" # In[9]: # Validation DataCenter("ams1").value # In[10]: Location("ams1").value # In[11]: type(Location("ams1")) # In[12]: type(Location("west1-a")) # In[13]: # Free Validation with helpful error messages! try: DataCenter("foo1") except ValueError as e: print(e) # In[17]: # Free Validation with helpful error messages! try: Location("foo1") except ValueError as e: print(e) # In[14]: # Multiple ways to instantiate, type stable output Location("ams1") == DataCenter("ams1") == Location.DataCenter.ams1 == DataCenter.ams1 == ams1() # In[15]: type(Location("ams1")) == type(DataCenter("ams1")) == ams1 # In[16]: def print_my_location(location:Location): print(location) # In[18]: def wrapper(value:str): print_my_location(value) # In[20]: 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}" # In[21]: generate_vm_name("test1-vm", Location("ams1")) # In[22]: try: generate_vm_name("test1-vm", Location("west2-a")) except ValueError as e: print(f"Got exception >>> {e}") # In[24]: @multimethod def generate_vm_name(vmname:str, location:AvailabilityZone) -> str: print(type(location)) return f"{vmname}-{location}.{DOMAIN}" # In[25]: generate_vm_name("test1-vm", Location("west2-a")) # In[26]: 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 | ❌ | ✅ | ❌ | ❌ | # In[28]: @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 # In[29]: 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() # In[ ]: # In[ ]: