From 51f7e393e8fea7a9f8fe7926ef92465b541996a7 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 8 May 2020 02:30:23 +0200 Subject: [PATCH] Add get_url helper documentation (#497) --- docs/instance_url.md | 136 +++++++++++++++++++++++++++++++++++++++++++ sidebars.js | 66 ++++++++++----------- 2 files changed, 169 insertions(+), 33 deletions(-) create mode 100644 docs/instance_url.md diff --git a/docs/instance_url.md b/docs/instance_url.md new file mode 100644 index 00000000..2bd07c3b --- /dev/null +++ b/docs/instance_url.md @@ -0,0 +1,136 @@ +--- +title: "Getting the instance URL" +--- + +In some cases, an integration requires to know the URL of the users' Home +Assistant instance that matches the requirements needed for the use cases at +hand. For example, cause a device needs to communicate back data to Home +Assistant, or for an external service or device to fetch data from Home +Assistant (e.g., a generated image or sound file). + +Getting an instance URL can be rather complex, considering a user can have a +bunch of different URLs available: + +- A user-configured internal home network URL. +- An automatically detected internal home network URL. +- A user-configured, public accessible, external URL that works from the internet. +- An URL provided by Home Assistant Cloud by Nabu Casa, in case the user has a subscription. + +Extra complexity is added by the fact that URLs can be served on non-standard ports +(e.g., not 80 or 443) and with or without SSL (`http://` vs `https://`). + +Luckily, Home Assistant provides a helper method to ease that a bit. + +## The URL helper + +Home Assistant provides a network helper method to get the instance URL, +that matches the requirements the integration needs, called `async_get_url`. + +The signature of the helper method: + +```py +# homeassistant.helpers.network.async_get_url +def async_get_url( + hass: HomeAssistant, + *, + require_ssl: bool = False, + require_standard_port: bool = False, + allow_internal: bool = True, + allow_external: bool = True, + allow_cloud: bool = True, + allow_ip: bool = True, + prefer_external: bool = False, + prefer_cloud: bool = False, +) -> str: +``` + +The different parameters of the method: + +- `require_ssl`: + Require the returned URL to use the `https` scheme. + +- `require_standard_port`: + Require the returned URL use a standard HTTP port. So, it requires port 80 + for the `http` scheme, and port 443 on the `https` scheme. + +- `allow_internal`: + Allow the URL to be an internal set URL by the user or a detected URL on the + internal network. Set this one to `False` if one requires an external URL + exclusively. + +- `allow_external`: + Allow the URL to be an external set URL by the user or a Home Assistant Cloud + URL. Set this one to `False` if one requires an internal URL exclusively. + +- `allow_cloud`: + Allow a Home Assistant Cloud URL to be returned, set to `False` in case one + requires anything but a Cloud URL. + +- `allow_ip`: + Allow the host part of an URL to be an IP address, set to `False` in case + that is not usable for the use case. + +- `prefer_external`: + By default, we prefer internal URLs over external ones. Set this option to + `True` to turn that logic around and prefer an external URL over + an internal one. + +- `prefer_cloud`: + By default, an external URL set by the user is preferred, however, in rare + cases a cloud URL might be more reliable. Setting this option to `True` + prefers the Home Assistant Cloud URL over the user-defined external URL. + +## Default behavior + +By default, without passing additional parameters (`async_get_url(hass)`), +it will try to: + +- Get an internal URL set by the user, or if not available, try to detect one + from the network interface (based on `http` settings). + +- If an internal URL fails, it will try to get an external URL. It prefers the + external URL set by the user, in case that fails; Get a Home Assistant Cloud + URL if that is available. + +The default is aimed to be: allow any URL, but prefer a local one, +without requirements. + +## Example usage + +The most basic example of using the helper: + +```py +from homeassistant.helpers.network import async_get_url + +instance_url = async_get_url(hass) +``` + +This example call to the helper method would return an internal URL, preferably, +that is either user set or detected. If it cannot provide that, it will try +the users' external URL. Lastly, if that isn't set by the user, it will try to +make use of the Home Assistant Cloud URL. + +If absolutely no URL is available (or none match given requirements), +an exception will be raised: `NoURLAvailableError`. + +```py +from homeassistant.helpers import network + +try: + external_url = network.async_get_url( + hass, + allow_internal=False, + allow_ip=False, + require_ssl=True, + require_standard_port=True, + ) +except network.NoURLAvailableError: + raise MyInvalidValueError("Failed to find suitable URL for my integration") +``` + +The above example shows a little more complex use of the URL helper. In this case +the requested URL may not be an internal address, the URL may not contain an +IP address, requires SSL and must be served on a standard port. + +If none is available, the `NoURLAvailableError` exception can be caught and +handled. diff --git a/sidebars.js b/sidebars.js index 28186eca..5ef0d99f 100644 --- a/sidebars.js +++ b/sidebars.js @@ -6,14 +6,14 @@ */ module.exports = { - "Architecture": { - "Architecture": [ + Architecture: { + Architecture: [ "architecture_index", "architecture_components", "architecture_entities", - "architecture_hassio" + "architecture_hassio", ], - "Entities": [ + Entities: [ "entity_index", "entity_air_quality", "entity_alarm_control_panel", @@ -29,36 +29,36 @@ module.exports = { "entity_switch", "entity_vacuum", "entity_water_heater", - "entity_weather" + "entity_weather", ], - "Authentication": [ + Authentication: [ "auth_index", "auth_permissions", "auth_api", "auth_auth_provider", - "auth_auth_module" + "auth_auth_module", ], "Config Entries": ["config_entries_index"], "Data Entry Flow": ["data_entry_flow_index"], "Entity Registry": ["entity_registry_index", "entity_registry_disabled_by"], "Device Registry": ["device_registry_index"], - "Area Registry": ["area_registry_index"] + "Area Registry": ["area_registry_index"], }, "Extending Frontend": { - "Frontend": [ + Frontend: [ "frontend_index", "frontend_architecture", "frontend_development", "frontend_data", "frontend_external_auth", - "frontend_external_bus" + "frontend_external_bus", ], "Extending the frontend": [ "frontend_add_card", "frontend_add_more_info", - "frontend_add_websocket_api" + "frontend_add_websocket_api", ], - "Custom UI": ["lovelace_custom_card", "frontend_creating_custom_panels"] + "Custom UI": ["lovelace_custom_card", "frontend_creating_custom_panels"], }, "Extending Home Assistant": { "Development Workflow": [ @@ -67,7 +67,7 @@ module.exports = { "development_submitting", "development_guidelines", "development_testing", - "development_catching_up" + "development_catching_up", ], "Building Integrations": [ "creating_integration_file_structure", @@ -80,54 +80,54 @@ module.exports = { "creating_platform_index", "creating_component_generic_discovery", "reproduce_state_index", - "integration_fetching_data" + "integration_fetching_data", ], "Development Checklist": [ "development_checklist", "creating_component_code_review", "creating_platform_code_review", - "integration_quality_scale_index" + "integration_quality_scale_index", ], "Home Assistant Core 101": [ "dev_101_index", "dev_101_hass", "dev_101_events", "dev_101_states", - "dev_101_config" + "dev_101_config", ], "Device Automations": [ "device_automation_index", "device_automation_trigger", "device_automation_condition", - "device_automation_action" + "device_automation_action", ], - "Misc": ["development_validation", "development_typing"] + Misc: ["development_validation", "development_typing", "instance_url"], }, - "Misc": { - "Introduction": ["misc"], + Misc: { + Introduction: ["misc"], "External API": [ "external_api_rest", "external_api_rest_python", "external_api_websocket", - "external_api_server_sent_events" + "external_api_server_sent_events", ], - "Internationalization": [ + Internationalization: [ "internationalization_index", "internationalization_backend_localization", "internationalization_custom_component_localization", - "internationalization_translation" + "internationalization_translation", ], - "Documentation": [ + Documentation: [ "documentation_index", "documentation_standards", - "documentation_create_page" + "documentation_create_page", ], - "Intents": [ + Intents: [ "intent_index", "intent_firing", "intent_handling", "intent_conversation", - "intent_builtin" + "intent_builtin", ], "Native App Integration": [ "app_integration_index", @@ -135,18 +135,18 @@ module.exports = { "app_integration_sending_data", "app_integration_sensors", "app_integration_notifications", - "app_integration_webview" + "app_integration_webview", ], "Building a Python library": [ "api_lib_index", "api_lib_auth", - "api_lib_data_models" + "api_lib_data_models", ], - "asyncio": [ + asyncio: [ "asyncio_index", "asyncio_101", "asyncio_categorizing_functions", - "asyncio_working_with_async" + "asyncio_working_with_async", ], "Hass.io": ["hassio_debugging", "hassio_hass"], "Hass.io Add-Ons": [ @@ -158,8 +158,8 @@ module.exports = { "hassio_addon_publishing", "hassio_addon_presentation", "hassio_addon_repository", - "hassio_addon_security" + "hassio_addon_security", ], - "Maintainer docs": ["maintenance", "releasing"] + "Maintainer docs": ["maintenance", "releasing"], }, };