Skip to content

Commit

Permalink
Support importing Blueprints from the Home Assistant websites (#95340)
Browse files Browse the repository at this point in the history
* Support importing Blueprints from the Home Assistant websites

* Improve test coverage
  • Loading branch information
frenck committed Jun 27, 2023
1 parent a8b2c1e commit 4b9bfe9
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 4 deletions.
33 changes: 29 additions & 4 deletions homeassistant/components/blueprint/importer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Import logic for blueprint."""

from __future__ import annotations

from contextlib import suppress
from dataclasses import dataclass
import html
import re
Expand Down Expand Up @@ -28,6 +30,10 @@
r"^https://github.com/(?P<repository>.+)/blob/(?P<path>.+)$"
)

WEBSITE_PATTERN = re.compile(
r"^https://(?P<subdomain>[a-z0-9-]+)\.home-assistant\.io/(?P<path>.+).yaml$"
)

COMMUNITY_TOPIC_SCHEMA = vol.Schema(
{
"slug": str,
Expand Down Expand Up @@ -219,18 +225,37 @@ async def fetch_blueprint_from_github_gist_url(
)


async def fetch_blueprint_from_website_url(
hass: HomeAssistant, url: str
) -> ImportedBlueprint:
"""Get a blueprint from our website."""
if (WEBSITE_PATTERN.match(url)) is None:
raise UnsupportedUrl("Not a Home Assistant website URL")

session = aiohttp_client.async_get_clientsession(hass)

resp = await session.get(url, raise_for_status=True)
raw_yaml = await resp.text()
data = yaml.parse_yaml(raw_yaml)
assert isinstance(data, dict)
blueprint = Blueprint(data)

parsed_import_url = yarl.URL(url)
suggested_filename = f"homeassistant/{parsed_import_url.parts[-1][:-5]}"
return ImportedBlueprint(suggested_filename, raw_yaml, blueprint)


async def fetch_blueprint_from_url(hass: HomeAssistant, url: str) -> ImportedBlueprint:
"""Get a blueprint from a url."""
for func in (
fetch_blueprint_from_community_post,
fetch_blueprint_from_github_url,
fetch_blueprint_from_github_gist_url,
fetch_blueprint_from_website_url,
):
try:
with suppress(UnsupportedUrl):
imported_bp = await func(hass, url)
imported_bp.blueprint.update_metadata(source_url=url)
return imported_bp
except UnsupportedUrl:
pass

raise HomeAssistantError("Unsupported url")
raise HomeAssistantError("Unsupported URL")
27 changes: 27 additions & 0 deletions tests/components/blueprint/test_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,30 @@ async def test_fetch_blueprint_from_github_gist_url(
assert imported_blueprint.blueprint.inputs == snapshot
assert imported_blueprint.suggested_filename == "balloob/motion_light"
assert imported_blueprint.blueprint.metadata["source_url"] == url


async def test_fetch_blueprint_from_website_url(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
) -> None:
"""Test fetching blueprint from url."""
aioclient_mock.get(
"https://www.home-assistant.io/blueprints/awesome.yaml",
text=Path(
hass.config.path("blueprints/automation/test_event_service.yaml")
).read_text(),
)

url = "https://www.home-assistant.io/blueprints/awesome.yaml"
imported_blueprint = await importer.fetch_blueprint_from_url(hass, url)
assert isinstance(imported_blueprint, importer.ImportedBlueprint)
assert imported_blueprint.blueprint.domain == "automation"
assert imported_blueprint.suggested_filename == "homeassistant/awesome"
assert imported_blueprint.blueprint.metadata["source_url"] == url


async def test_fetch_blueprint_from_unsupported_url(hass: HomeAssistant) -> None:
"""Test fetching blueprint from an unsupported URL."""
url = "https://example.com/unsupported.yaml"

with pytest.raises(HomeAssistantError, match=r"^Unsupported URL$"):
await importer.fetch_blueprint_from_url(hass, url)

0 comments on commit 4b9bfe9

Please sign in to comment.