from typing import Literal, overload, TypeVar, Generic, Type
import enum
import abc
import typing
class Version(enum.Enum):
Version1 = 1
Version2 = 2
Version3 = 3
import abc
from typing import Type
class Machine1BaseConfig:
@abc.abstractmethod
def __init__(self, *args, **kwargs) -> None:
pass
class Machine1Config_1(Machine1BaseConfig):
def __init__(self, fueltype, speed) -> None:
self.fueltype = fueltype
self.speed = speed
class Machine1Config_2(Machine1BaseConfig):
def __init__(self, speed, weight) -> None:
self.speed = speed
self.weight = weight
class Machine1FacadeConfig:
@classmethod
def get_version(cls, version: Version) -> Type[typing.Union[Machine1Config_1, Machine1Config_2]]:
config_map = {
Version.Version1: Machine1Config_1,
Version.Version2: Machine1Config_2,
Version.Version3: Machine1Config_2,
}
return config_map[version]
class Machine2BaseConfig:
@abc.abstractmethod
def __init__(self, *args, **kwargs) -> None:
pass
class Machine2Config_1(Machine2BaseConfig):
def __init__(self, gridsize) -> None:
self.gridsize = gridsize
class Machine2Config_2(Machine2BaseConfig):
def __init__(self, loadtype, duration) -> None:
self.loadtype = loadtype
self.duration = duration
class Machine2FacadeConfig:
@classmethod
def get_version(cls, version: Version) -> Type[typing.Union[Machine2Config_1, Machine2Config_2]]:
config_map = {
Version.Version1: Machine2Config_1,
Version.Version2: Machine2Config_1,
Version.Version3: Machine2Config_2,
}
return config_map[version]
class Factory:
def __init__(self, version: Version) -> None:
self.version = version
@property
def Machine1Config(self):
return Machine1FacadeConfig.get_version(self.version)
@property
def Machine2Config(self):
return Machine2FacadeConfig.get_version(self.version)
factory_instance = Factory(Version.Version1)
machine1_config_instance = factory_instance.Machine1Config()
machine2_config_instance = factory_instance.Machine2Config()
In the provided Python code, the Factory class is used to instantiate configuration objects for two different types of machines (Machine1 and Machine2) based on a specified version. The problem is when using Pylance/Pyright with Visual Studio Code, I'm experiencing issues with autocomplete not correctly suggesting parameters for dynamically instantiated classes (Machine1Config and Machine2Config) in a factory design pattern. How can I improve my code to enable more accurate and helpful autocompletion suggestions by Pylance for these dynamically determined types?
I have thought that this should somehow work with @overload decorater but I can't wrap my head around it how to quite implement it.
Furthermore currently with the type hint Type[typing.Union[Machine1Config_1, Machine1Config_2]]
Pylance suggests all key word arguments of Machine1Config_1 and Machine1Config_2, so fueltype, speed, weight. If I leave this type hint away there is no autocompletion at all.
Looking at the factory, there is no way to tell which of Type[typing.Union[Machine2Config_1, Machine2Config_2]]
will be returned when calling Machine1FacadeConfig.get_version(self.version)
in isolation.
As the facade and the factory are extremely coupled anyways, I would suggest combining these into a single utility, where the types for version and configs can be more tightly coupled.
You can declare a generic class for factory and provide a helper function which returns an instance of that factory where the version and config types have been bound together. The helper function would be overloaded for the different combinations.
_Config1 = TypeVar("_Config1", Machine1Config_1, Machine1Config_2)
_Config2 = TypeVar("_Config2", Machine2Config_1, Machine2Config_2)
class _Factory(Generic[_Config1, _Config2]):
def __init__(self, config1: Type[_Config1], config2: Type[_Config2]):
self._config1 = config1
self._config2 = config2
@property
def Machine1Config(self) -> Type[_Config1]:
return self._config1
@property
def Machine2Config(self) -> Type[_Config2]:
return self._config2
@overload
def Factory(version: Literal[Version.Version1]) -> _Factory[Machine1Config_1, Machine2Config_1]:
...
@overload
def Factory(version: Literal[Version.Version2]) -> _Factory[Machine1Config_1, Machine2Config_2]:
...
@overload
def Factory(version: Literal[Version.Version3]) -> _Factory[Machine1Config_2, Machine2Config_2]:
...
def Factory(version: Version) -> _Factory:
config_map1 = {
Version.Version1: Machine1Config_1,
Version.Version2: Machine1Config_1,
Version.Version3: Machine1Config_2,
}
config_map2 = {
Version.Version1: Machine2Config_1,
Version.Version2: Machine2Config_2,
Version.Version3: Machine2Config_2,
}
return _Factory(config_map1[version], config_map2[version])
factory_instance = Factory(Version.Version1)
machine1_config_instance = factory_instance.Machine1Config
machine2_config_instance = factory_instance.Machine2Config
Example screenshot from vscode: