Skip to content

Commit

Permalink
Make API init async in Minecraft Server (home-assistant#105403)
Browse files Browse the repository at this point in the history
* Make api init async

* Remove duplicate assignment of address and set server to None in constructor
  • Loading branch information
elmurato authored Dec 10, 2023
1 parent a8148ce commit 7b32e41
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 91 deletions.
27 changes: 17 additions & 10 deletions homeassistant/components/minecraft_server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Minecraft Server from a config entry."""

# Check and create API instance.
# Create API instance.
api = MinecraftServer(
hass,
entry.data.get(CONF_TYPE, MinecraftServerType.JAVA_EDITION),
entry.data[CONF_ADDRESS],
)

# Initialize API instance.
try:
api = await hass.async_add_executor_job(
MinecraftServer,
entry.data.get(CONF_TYPE, MinecraftServerType.JAVA_EDITION),
entry.data[CONF_ADDRESS],
)
await api.async_initialize()
except MinecraftServerAddressError as error:
raise ConfigEntryError(
f"Server address in configuration entry is invalid: {error}"
Expand Down Expand Up @@ -102,9 +105,11 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
config_data = config_entry.data

# Migrate config entry.
address = config_data[CONF_HOST]
api = MinecraftServer(hass, MinecraftServerType.JAVA_EDITION, address)

try:
address = config_data[CONF_HOST]
MinecraftServer(MinecraftServerType.JAVA_EDITION, address)
await api.async_initialize()
host_only_lookup_success = True
except MinecraftServerAddressError as error:
host_only_lookup_success = False
Expand All @@ -114,9 +119,11 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
)

if not host_only_lookup_success:
address = f"{config_data[CONF_HOST]}:{config_data[CONF_PORT]}"
api = MinecraftServer(hass, MinecraftServerType.JAVA_EDITION, address)

try:
address = f"{config_data[CONF_HOST]}:{config_data[CONF_PORT]}"
MinecraftServer(MinecraftServerType.JAVA_EDITION, address)
await api.async_initialize()
except MinecraftServerAddressError as error:
_LOGGER.exception(
"Can't migrate configuration entry due to error while parsing server address, try again later: %s",
Expand Down
39 changes: 30 additions & 9 deletions homeassistant/components/minecraft_server/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from mcstatus import BedrockServer, JavaServer
from mcstatus.status_response import BedrockStatusResponse, JavaStatusResponse

from homeassistant.core import HomeAssistant

_LOGGER = logging.getLogger(__name__)

LOOKUP_TIMEOUT: float = 10
Expand Down Expand Up @@ -52,35 +54,51 @@ class MinecraftServerConnectionError(Exception):
"""Raised when no data can be fechted from the server."""


class MinecraftServerNotInitializedError(Exception):
"""Raised when APIs are used although server instance is not initialized yet."""


class MinecraftServer:
"""Minecraft Server wrapper class for 3rd party library mcstatus."""

_server: BedrockServer | JavaServer
_server: BedrockServer | JavaServer | None

def __init__(self, server_type: MinecraftServerType, address: str) -> None:
def __init__(
self, hass: HomeAssistant, server_type: MinecraftServerType, address: str
) -> None:
"""Initialize server instance."""
self._server = None
self._hass = hass
self._server_type = server_type
self._address = address

async def async_initialize(self) -> None:
"""Perform async initialization of server instance."""
try:
if server_type == MinecraftServerType.JAVA_EDITION:
self._server = JavaServer.lookup(address, timeout=LOOKUP_TIMEOUT)
if self._server_type == MinecraftServerType.JAVA_EDITION:
self._server = await JavaServer.async_lookup(self._address)
else:
self._server = BedrockServer.lookup(address, timeout=LOOKUP_TIMEOUT)
self._server = await self._hass.async_add_executor_job(
BedrockServer.lookup, self._address
)
except (ValueError, LifetimeTimeout) as error:
raise MinecraftServerAddressError(
f"Lookup of '{address}' failed: {self._get_error_message(error)}"
f"Lookup of '{self._address}' failed: {self._get_error_message(error)}"
) from error

self._server.timeout = DATA_UPDATE_TIMEOUT
self._address = address

_LOGGER.debug(
"%s server instance created with address '%s'", server_type, address
"%s server instance created with address '%s'",
self._server_type,
self._address,
)

async def async_is_online(self) -> bool:
"""Check if the server is online, supporting both Java and Bedrock Edition servers."""
try:
await self.async_get_data()
except MinecraftServerConnectionError:
except (MinecraftServerConnectionError, MinecraftServerNotInitializedError):
return False

return True
Expand All @@ -89,6 +107,9 @@ async def async_get_data(self) -> MinecraftServerData:
"""Get updated data from the server, supporting both Java and Bedrock Edition servers."""
status_response: BedrockStatusResponse | JavaStatusResponse

if self._server is None:
raise MinecraftServerNotInitializedError()

try:
status_response = await self._server.async_status(tries=DATA_UPDATE_RETRIES)
except OSError as error:
Expand Down
6 changes: 3 additions & 3 deletions homeassistant/components/minecraft_server/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ async def async_step_user(self, user_input=None) -> FlowResult:

# Some Bedrock Edition servers mimic a Java Edition server, therefore check for a Bedrock Edition server first.
for server_type in MinecraftServerType:
api = MinecraftServer(self.hass, server_type, address)

try:
api = await self.hass.async_add_executor_job(
MinecraftServer, server_type, address
)
await api.async_initialize()
except MinecraftServerAddressError:
pass
else:
Expand Down
12 changes: 10 additions & 2 deletions homeassistant/components/minecraft_server/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .api import MinecraftServer, MinecraftServerConnectionError, MinecraftServerData
from .api import (
MinecraftServer,
MinecraftServerConnectionError,
MinecraftServerData,
MinecraftServerNotInitializedError,
)

SCAN_INTERVAL = timedelta(seconds=60)

Expand All @@ -32,5 +37,8 @@ async def _async_update_data(self) -> MinecraftServerData:
"""Get updated data from the server."""
try:
return await self._api.async_get_data()
except MinecraftServerConnectionError as error:
except (
MinecraftServerConnectionError,
MinecraftServerNotInitializedError,
) as error:
raise UpdateFailed(error) from error
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# serializer version: 1
# name: test_binary_sensor[bedrock_mock_config_entry-BedrockServer-status_response1]
# name: test_binary_sensor[bedrock_mock_config_entry-BedrockServer-lookup-status_response1]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'connectivity',
Expand All @@ -13,7 +13,7 @@
'state': 'on',
})
# ---
# name: test_binary_sensor[java_mock_config_entry-JavaServer-status_response0]
# name: test_binary_sensor[java_mock_config_entry-JavaServer-async_lookup-status_response0]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'connectivity',
Expand All @@ -27,7 +27,7 @@
'state': 'on',
})
# ---
# name: test_binary_sensor_update[bedrock_mock_config_entry-BedrockServer-status_response1]
# name: test_binary_sensor_update[bedrock_mock_config_entry-BedrockServer-lookup-status_response1]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'connectivity',
Expand All @@ -41,7 +41,7 @@
'state': 'on',
})
# ---
# name: test_binary_sensor_update[java_mock_config_entry-JavaServer-status_response0]
# name: test_binary_sensor_update[java_mock_config_entry-JavaServer-async_lookup-status_response0]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'connectivity',
Expand Down
Loading

0 comments on commit 7b32e41

Please sign in to comment.