-
-
Notifications
You must be signed in to change notification settings - Fork 563
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This will make it simple for downstream users to construct device instances for all supported devices given only the host and its token. All device subclasses register themselves automatically to the factory. The create(host, token, model=None) class method is the main entry point to use this. Supersedes #1328 Fixes #1117
- Loading branch information
Showing
11 changed files
with
176 additions
and
21 deletions.
There are no files selected for viewing
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
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
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import logging | ||
from typing import Dict, List, Optional, Type | ||
|
||
import click | ||
|
||
from .device import Device | ||
from .exceptions import DeviceException | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
class DeviceFactory: | ||
"""A helper class to construct devices based on their info responses. | ||
This class keeps list of supported integrations and models to allow creating | ||
:class:`Device` instances without knowing anything except the host and the token. | ||
:func:`create` is the main entry point when using this module. Example:: | ||
from miio import DeviceFactory | ||
dev = DeviceFactory.create("127.0.0.1", 32*"0") | ||
""" | ||
|
||
_integration_classes: List[Type[Device]] = [] | ||
_supported_models: Dict[str, Type[Device]] = {} | ||
|
||
@classmethod | ||
def register(cls, integration_cls: Type[Device]): | ||
"""Register class for to the registry.""" | ||
cls._integration_classes.append(integration_cls) | ||
_LOGGER.debug("Registering %s", integration_cls.__name__) | ||
for model in integration_cls.supported_models: # type: ignore | ||
if model in cls._supported_models: | ||
_LOGGER.debug( | ||
"Got duplicate of %s for %s, previously registered by %s", | ||
model, | ||
integration_cls, | ||
cls._supported_models[model], | ||
) | ||
|
||
_LOGGER.debug(" * %s => %s", model, integration_cls) | ||
cls._supported_models[model] = integration_cls | ||
|
||
@classmethod | ||
def supported_models(cls) -> Dict[str, Type[Device]]: | ||
"""Return a dictionary of models and their corresponding implementation | ||
classes.""" | ||
return cls._supported_models | ||
|
||
@classmethod | ||
def integrations(cls) -> List[Type[Device]]: | ||
"""Return the list of integration classes.""" | ||
return cls._integration_classes | ||
|
||
@classmethod | ||
def class_for_model(cls, model: str): | ||
"""Return implementation class for the given model, if available.""" | ||
if model in cls._supported_models: | ||
return cls._supported_models[model] | ||
|
||
wildcard_models = { | ||
m: impl for m, impl in cls._supported_models.items() if m.endswith("*") | ||
} | ||
for wildcard_model, impl in wildcard_models.items(): | ||
m = wildcard_model.rstrip("*") | ||
if model.startswith(m): | ||
_LOGGER.debug( | ||
"Using %s for %s, please add it to supported models for %s", | ||
wildcard_model, | ||
model, | ||
impl, | ||
) | ||
return impl | ||
|
||
raise DeviceException("No implementation found for model %s" % model) | ||
|
||
@classmethod | ||
def create(self, host: str, token: str, model: Optional[str] = None) -> Device: | ||
"""Return instance for the given host and token, with optional model override. | ||
The optional model parameter can be used to override the model detection. | ||
""" | ||
if model is None: | ||
dev: Device = Device(host, token) | ||
info = dev.info() | ||
model = info.model | ||
|
||
return self.class_for_model(model)(host, token, model=model) | ||
|
||
|
||
@click.group() | ||
def factory(): | ||
"""Access to available integrations.""" | ||
|
||
|
||
@factory.command() | ||
def integrations(): | ||
for integration in DeviceFactory.integrations(): | ||
click.echo( | ||
f"* {integration} supports {len(integration.supported_models)} models" | ||
) | ||
|
||
|
||
@factory.command() | ||
def models(): | ||
"""List supported models.""" | ||
for model in DeviceFactory.supported_models(): | ||
click.echo(f"* {model}") |
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
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
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import pytest | ||
|
||
from miio import Device, DeviceException, DeviceFactory, Gateway, MiotDevice | ||
|
||
DEVICE_CLASSES = Device.__subclasses__() + MiotDevice.__subclasses__() # type: ignore | ||
DEVICE_CLASSES.remove(MiotDevice) | ||
|
||
|
||
def test_device_all_supported_models(): | ||
models = DeviceFactory.supported_models() | ||
for model, impl in models.items(): | ||
assert isinstance(model, str) | ||
assert issubclass(impl, Device) | ||
|
||
|
||
@pytest.mark.parametrize("cls", DEVICE_CLASSES) | ||
def test_device_class_for_model(cls): | ||
"""Test that all supported models can be initialized using class_for_model.""" | ||
|
||
if cls == Gateway: | ||
pytest.skip( | ||
"Skipping Gateway as AirConditioningCompanion already implements lumi.acpartner.*" | ||
) | ||
|
||
for supp in cls.supported_models: | ||
dev = DeviceFactory.class_for_model(supp) | ||
assert issubclass(dev, cls) | ||
|
||
|
||
def test_device_class_for_wildcard(): | ||
"""Test that wildcard matching works.""" | ||
|
||
class _DummyDevice(Device): | ||
_supported_models = ["foo.bar.*"] | ||
|
||
assert DeviceFactory.class_for_model("foo.bar.aaaa") == _DummyDevice | ||
|
||
|
||
def test_device_class_for_model_unknown(): | ||
"""Test that unknown model raises an exception.""" | ||
with pytest.raises(DeviceException): | ||
DeviceFactory.class_for_model("foo.foo.xyz") |
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