Improve string formatting v5 (#33697)

* Improve string formatting v5

* Address review comments
This commit is contained in:
springstan 2020-04-05 17:48:55 +02:00 committed by GitHub
parent 39336d3ea3
commit fca90a8ddc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 221 additions and 252 deletions

View File

@ -8,19 +8,19 @@ Loosely based on https://github.com/astropy/astropy/pull/347
import os
import warnings
__licence__ = 'BSD (3 clause)'
__licence__ = "BSD (3 clause)"
def get_github_url(app, view, path):
github_fmt = 'https://github.com/{}/{}/{}/{}{}'
return (
github_fmt.format(app.config.edit_on_github_project, view,
app.config.edit_on_github_branch,
app.config.edit_on_github_src_path, path))
f"https://github.com/{app.config.edit_on_github_project}/"
f"{view}/{app.config.edit_on_github_branch}/"
f"{app.config.edit_on_github_src_path}{path}"
)
def html_page_context(app, pagename, templatename, context, doctree):
if templatename != 'page.html':
if templatename != "page.html":
return
if not app.config.edit_on_github_project:
@ -29,16 +29,16 @@ def html_page_context(app, pagename, templatename, context, doctree):
if not doctree:
warnings.warn("doctree is None")
return
path = os.path.relpath(doctree.get('source'), app.builder.srcdir)
show_url = get_github_url(app, 'blob', path)
edit_url = get_github_url(app, 'edit', path)
path = os.path.relpath(doctree.get("source"), app.builder.srcdir)
show_url = get_github_url(app, "blob", path)
edit_url = get_github_url(app, "edit", path)
context['show_on_github_url'] = show_url
context['edit_on_github_url'] = edit_url
context["show_on_github_url"] = show_url
context["edit_on_github_url"] = edit_url
def setup(app):
app.add_config_value('edit_on_github_project', '', True)
app.add_config_value('edit_on_github_branch', 'master', True)
app.add_config_value('edit_on_github_src_path', '', True) # 'eg' "docs/"
app.connect('html-page-context', html_page_context)
app.add_config_value("edit_on_github_project", "", True)
app.add_config_value("edit_on_github_branch", "master", True)
app.add_config_value("edit_on_github_src_path", "", True) # 'eg' "docs/"
app.connect("html-page-context", html_page_context)

View File

@ -22,25 +22,26 @@ import sys
from homeassistant.const import __short_version__, __version__
PROJECT_NAME = 'Home Assistant'
PROJECT_PACKAGE_NAME = 'homeassistant'
PROJECT_AUTHOR = 'The Home Assistant Authors'
PROJECT_COPYRIGHT = f' 2013-2020, {PROJECT_AUTHOR}'
PROJECT_LONG_DESCRIPTION = ('Home Assistant is an open-source '
'home automation platform running on Python 3. '
'Track and control all devices at home and '
'automate control. '
'Installation in less than a minute.')
PROJECT_GITHUB_USERNAME = 'home-assistant'
PROJECT_GITHUB_REPOSITORY = 'home-assistant'
PROJECT_NAME = "Home Assistant"
PROJECT_PACKAGE_NAME = "homeassistant"
PROJECT_AUTHOR = "The Home Assistant Authors"
PROJECT_COPYRIGHT = f" 2013-2020, {PROJECT_AUTHOR}"
PROJECT_LONG_DESCRIPTION = (
"Home Assistant is an open-source "
"home automation platform running on Python 3. "
"Track and control all devices at home and "
"automate control. "
"Installation in less than a minute."
)
PROJECT_GITHUB_USERNAME = "home-assistant"
PROJECT_GITHUB_REPOSITORY = "home-assistant"
GITHUB_PATH = '{}/{}'.format(
PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY)
GITHUB_URL = f'https://github.com/{GITHUB_PATH}'
GITHUB_PATH = f"{PROJECT_GITHUB_USERNAME}/{PROJECT_GITHUB_REPOSITORY}"
GITHUB_URL = f"https://github.com/{GITHUB_PATH}"
sys.path.insert(0, os.path.abspath('_ext'))
sys.path.insert(0, os.path.abspath('../homeassistant'))
sys.path.insert(0, os.path.abspath("_ext"))
sys.path.insert(0, os.path.abspath("../homeassistant"))
# -- General configuration ------------------------------------------------
@ -52,27 +53,27 @@ sys.path.insert(0, os.path.abspath('../homeassistant'))
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.linkcode',
'sphinx_autodoc_annotation',
'edit_on_github'
"sphinx.ext.autodoc",
"sphinx.ext.linkcode",
"sphinx_autodoc_annotation",
"edit_on_github",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
templates_path = ["_templates"]
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
source_suffix = ".rst"
# The encoding of source files.
#
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
master_doc = "index"
# General information about the project.
project = PROJECT_NAME
@ -88,25 +89,25 @@ version = __short_version__
# The full version, including alpha/beta/rc tags.
release = __version__
code_branch = 'dev' if 'dev' in __version__ else 'master'
code_branch = "dev" if "dev" in __version__ else "master"
# Edit on Github config
edit_on_github_project = GITHUB_PATH
edit_on_github_branch = code_branch
edit_on_github_src_path = 'docs/source/'
edit_on_github_src_path = "docs/source/"
def linkcode_resolve(domain, info):
"""Determine the URL corresponding to Python object."""
if domain != 'py':
if domain != "py":
return None
modname = info['module']
fullname = info['fullname']
modname = info["module"]
fullname = info["fullname"]
submod = sys.modules.get(modname)
if submod is None:
return None
obj = submod
for part in fullname.split('.'):
for part in fullname.split("."):
try:
obj = getattr(obj, part)
except:
@ -131,7 +132,8 @@ def linkcode_resolve(domain, info):
fn = fn[index:]
return f'{GITHUB_URL}/blob/{code_branch}/{fn}{linespec}'
return f"{GITHUB_URL}/blob/{code_branch}/{fn}{linespec}"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@ -174,7 +176,7 @@ exclude_patterns = []
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
@ -191,22 +193,22 @@ todo_include_todos = False
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
html_theme = "alabaster"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
html_theme_options = {
'logo': 'logo.png',
'logo_name': PROJECT_NAME,
'description': PROJECT_LONG_DESCRIPTION,
'github_user': PROJECT_GITHUB_USERNAME,
'github_repo': PROJECT_GITHUB_REPOSITORY,
'github_type': 'star',
'github_banner': True,
'travis_button': True,
'touch_icon': 'logo-apple.png',
"logo": "logo.png",
"logo_name": PROJECT_NAME,
"description": PROJECT_LONG_DESCRIPTION,
"github_user": PROJECT_GITHUB_USERNAME,
"github_repo": PROJECT_GITHUB_REPOSITORY,
"github_type": "star",
"github_banner": True,
"travis_button": True,
"touch_icon": "logo-apple.png",
# 'fixed_sidebar': True, # Re-enable when we have more content
}
@ -232,12 +234,12 @@ html_theme_options = {
# This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#
html_favicon = '_static/favicon.ico'
html_favicon = "_static/favicon.ico"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html_static_path = ["_static"]
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
@ -249,7 +251,7 @@ html_static_path = ['_static']
# bottom, using the given strftime format.
# The empty string is equivalent to '%b %d, %Y'.
#
html_last_updated_fmt = '%b %d, %Y'
html_last_updated_fmt = "%b %d, %Y"
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
@ -259,13 +261,13 @@ html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#
html_sidebars = {
'**': [
'about.html',
'links.html',
'searchbox.html',
'sourcelink.html',
'navigation.html',
'relations.html'
"**": [
"about.html",
"links.html",
"searchbox.html",
"sourcelink.html",
"navigation.html",
"relations.html",
]
}
@ -326,34 +328,36 @@ html_sidebars = {
# html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = 'Home-Assistantdoc'
htmlhelp_basename = "Home-Assistantdoc"
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'home-assistant.tex', 'Home Assistant Documentation',
'Home Assistant Team', 'manual'),
(
master_doc,
"home-assistant.tex",
"Home Assistant Documentation",
"Home Assistant Team",
"manual",
)
]
# The name of an image file (relative to this directory) to place at the top of
@ -394,8 +398,7 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'home-assistant', 'Home Assistant Documentation',
[author], 1)
(master_doc, "home-assistant", "Home Assistant Documentation", [author], 1)
]
# If true, show URL addresses after external links.
@ -409,9 +412,15 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'Home-Assistant', 'Home Assistant Documentation',
author, 'Home Assistant', 'Open-source home automation platform.',
'Miscellaneous'),
(
master_doc,
"Home-Assistant",
"Home Assistant Documentation",
author,
"Home Assistant",
"Open-source home automation platform.",
"Miscellaneous",
)
]
# Documents to append as an appendix to all manuals.

View File

@ -36,9 +36,8 @@ def validate_python() -> None:
"""Validate that the right Python version is running."""
if sys.version_info[:3] < REQUIRED_PYTHON_VER:
print(
"Home Assistant requires at least Python {}.{}.{}".format(
*REQUIRED_PYTHON_VER
)
"Home Assistant requires at least Python "
f"{REQUIRED_PYTHON_VER[0]}.{REQUIRED_PYTHON_VER[1]}.{REQUIRED_PYTHON_VER[2]}"
)
sys.exit(1)

View File

@ -191,9 +191,7 @@ class AfterShipSensor(Entity):
"name": name,
"tracking_number": track["tracking_number"],
"slug": track["slug"],
"link": "{}{}/{}".format(
BASE, track["slug"], track["tracking_number"]
),
"link": f"{BASE}{track['slug']}/{track['tracking_number']}",
"last_update": track["updated_at"],
"expected_delivery": track["expected_delivery"],
"status": track["tag"],

View File

@ -90,8 +90,11 @@ async def async_request_validation(hass, config_entry, august_gateway):
hass.data[DOMAIN][entry_id][TWO_FA_REVALIDATE] = configurator.async_request_config(
f"{DEFAULT_NAME} ({username})",
async_august_configuration_validation_callback,
description="August must be re-verified. Please check your {} ({}) and enter the verification "
"code below".format(login_method, username),
description=(
"August must be re-verified. "
f"Please check your {login_method} ({username}) "
"and enter the verification code below"
),
submit_caption="Verify",
fields=[
{"id": VERIFICATION_CODE_KEY, "name": "Verification code", "type": "string"}
@ -265,7 +268,7 @@ class AugustData(AugustSubscriberMixin):
self._api.async_get_doorbell_detail,
)
_LOGGER.debug(
"async_signal_device_id_update (from detail updates): %s", device_id,
"async_signal_device_id_update (from detail updates): %s", device_id
)
self.async_signal_device_id_update(device_id)

View File

@ -92,7 +92,7 @@ def calculate_offset(event, offset):
time = search.group(1)
if ":" not in time:
if time[0] == "+" or time[0] == "-":
time = "{}0:{}".format(time[0], time[1:])
time = f"{time[0]}0:{time[1:]}"
else:
time = f"0:{time}"

View File

@ -49,9 +49,7 @@ def setup(hass, config):
except (ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect to Canary service: %s", str(ex))
hass.components.persistent_notification.create(
"Error: {}<br />"
"You will need to restart hass after fixing."
"".format(ex),
f"Error: {ex}<br />You will need to restart hass after fixing.",
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID,
)

View File

@ -76,7 +76,7 @@ class CanarySensor(Entity):
@property
def unique_id(self):
"""Return the unique ID of this sensor."""
return "{}_{}".format(self._device_id, self._sensor_type[0])
return f"{self._device_id}_{self._sensor_type[0]}"
@property
def unit_of_measurement(self):

View File

@ -48,7 +48,7 @@ class CiscoWebexTeamsNotificationService(BaseNotificationService):
title = ""
if kwargs.get(ATTR_TITLE) is not None:
title = "{}{}".format(kwargs.get(ATTR_TITLE), "<br>")
title = f"{kwargs.get(ATTR_TITLE)}<br>"
try:
self.client.messages.create(roomId=self.room, html=f"{title}{message}")

View File

@ -119,9 +119,9 @@ class AlexaConfig(alexa_config.AbstractConfig):
if self.should_report_state:
await self._prefs.async_update(alexa_report_state=False)
self.hass.components.persistent_notification.async_create(
"There was an error reporting state to Alexa ({}). "
f"There was an error reporting state to Alexa ({body['reason']}). "
"Please re-link your Alexa skill via the Alexa app to "
"continue using it.".format(body["reason"]),
"continue using it.",
"Alexa state reporting disabled",
"cloud_alexa_report",
)

View File

@ -82,7 +82,7 @@ class AccountSensor(Entity):
"""Get the latest state of the sensor."""
self._coinbase_data.update()
for account in self._coinbase_data.accounts["data"]:
if self._name == "Coinbase {}".format(account["name"]):
if self._name == f"Coinbase {account['name']}":
self._state = account["balance"]["amount"]
self._native_balance = account["native_balance"]["amount"]
self._native_currency = account["native_balance"]["currency"]

View File

@ -219,15 +219,16 @@ class HassEzvizCamera(Camera):
ffmpeg = ImageFrame(self._ffmpeg.binary, loop=self.hass.loop)
image = await asyncio.shield(
ffmpeg.get_image(self._rtsp_stream, output_format=IMAGE_JPEG,)
ffmpeg.get_image(self._rtsp_stream, output_format=IMAGE_JPEG)
)
return image
async def stream_source(self):
"""Return the stream source."""
if self._local_rtsp_port:
rtsp_stream_source = "rtsp://{}:{}@{}:{}".format(
self._username, self._password, self._local_ip, self._local_rtsp_port
rtsp_stream_source = (
f"rtsp://{self._username}:{self._password}@"
f"{self._local_ip}:{self._local_rtsp_port}"
)
_LOGGER.debug(
"Camera %s source stream: %s", self._serial, rtsp_stream_source

View File

@ -67,9 +67,7 @@ class CecPlayerDevice(CecDevice, MediaPlayerDevice):
def __init__(self, device, logical) -> None:
"""Initialize the HDMI device."""
CecDevice.__init__(self, device, logical)
self.entity_id = "{}.{}_{}".format(
DOMAIN, "hdmi", hex(self._logical_address)[2:]
)
self.entity_id = f"{DOMAIN}.hdmi_{hex(self._logical_address)[2:]}"
def send_keypress(self, key):
"""Send keypress to CEC adapter."""

View File

@ -28,9 +28,7 @@ class CecSwitchDevice(CecDevice, SwitchDevice):
def __init__(self, device, logical) -> None:
"""Initialize the HDMI device."""
CecDevice.__init__(self, device, logical)
self.entity_id = "{}.{}_{}".format(
DOMAIN, "hdmi", hex(self._logical_address)[2:]
)
self.entity_id = f"{DOMAIN}.hdmi_{hex(self._logical_address)[2:]}"
def turn_on(self, **kwargs) -> None:
"""Turn device on."""

View File

@ -71,7 +71,7 @@ class N26Account(Entity):
@property
def name(self) -> str:
"""Friendly name of the sensor."""
return "n26_{}".format(self._iban[-4:])
return f"n26_{self._iban[-4:]}"
@property
def state(self) -> float:
@ -110,7 +110,7 @@ class N26Account(Entity):
}
for limit in self._data.limits:
limit_attr_name = "limit_{}".format(limit["limit"].lower())
limit_attr_name = f"limit_{limit['limit'].lower()}"
attributes[limit_attr_name] = limit["amount"]
return attributes
@ -143,7 +143,7 @@ class N26Card(Entity):
@property
def name(self) -> str:
"""Friendly name of the sensor."""
return "{}_card_{}".format(self._account_name.lower(), self._card["id"])
return f"{self._account_name.lower()}_card_{self._card['id']}"
@property
def state(self) -> float:
@ -206,9 +206,7 @@ class N26Space(Entity):
@property
def unique_id(self):
"""Return the unique ID of the entity."""
return "space_{}_{}".format(
self._data.balance["iban"][-4:], self._space["name"].lower()
)
return f"space_{self._data.balance['iban'][-4:]}_{self._space['name'].lower()}"
@property
def name(self) -> str:

View File

@ -42,7 +42,7 @@ class N26CardSwitch(SwitchDevice):
@property
def name(self) -> str:
"""Friendly name of the sensor."""
return "card_{}".format(self._card["id"])
return f"card_{self._card['id']}"
@property
def is_on(self):

View File

@ -200,7 +200,7 @@ async def async_setup_entry(hass, entry):
now = datetime.utcnow()
trip_id = service.data.get(
ATTR_TRIP_ID, "trip_{}".format(int(now.timestamp()))
ATTR_TRIP_ID, f"trip_{int(now.timestamp())}"
)
eta_begin = now + service.data[ATTR_ETA]
eta_window = service.data.get(ATTR_ETA_WINDOW, timedelta(minutes=1))
@ -368,15 +368,11 @@ class NestSensorDevice(Entity):
if device is not None:
# device specific
self.device = device
self._name = "{} {}".format(
self.device.name_long, self.variable.replace("_", " ")
)
self._name = f"{self.device.name_long} {self.variable.replace('_', ' ')}"
else:
# structure only
self.device = structure
self._name = "{} {}".format(
self.structure.name, self.variable.replace("_", " ")
)
self._name = f"{self.structure.name} {self.variable.replace('_', ' ')}"
self._state = None
self._unit = None

View File

@ -57,8 +57,8 @@ class AuthPhase:
try:
msg = AUTH_MESSAGE_SCHEMA(msg)
except vol.Invalid as err:
error_msg = "Auth message incorrectly formatted: {}".format(
humanize_error(msg, err)
error_msg = (
f"Auth message incorrectly formatted: {humanize_error(msg, err)}"
)
self._logger.warning(error_msg)
self._send_message(auth_invalid_message(error_msg))

View File

@ -233,15 +233,13 @@ def _request_app_setup(hass, config):
start_url = f"{hass.config.api.base_url}{WINK_AUTH_CALLBACK_PATH}"
description = """Please create a Wink developer app at
description = f"""Please create a Wink developer app at
https://developer.wink.com.
Add a Redirect URI of {}.
Add a Redirect URI of {start_url}.
They will provide you a Client ID and secret
after reviewing your request.
(This can take several days).
""".format(
start_url
)
"""
hass.data[DOMAIN]["configuring"][DOMAIN] = configurator.request_config(
DOMAIN,
@ -351,9 +349,7 @@ def setup(hass, config):
# Home .
else:
redirect_uri = "{}{}".format(
hass.config.api.base_url, WINK_AUTH_CALLBACK_PATH
)
redirect_uri = f"{hass.config.api.base_url}{WINK_AUTH_CALLBACK_PATH}"
wink_auth_start_url = pywink.get_authorization_url(
config_file.get(ATTR_CLIENT_ID), redirect_uri

View File

@ -58,8 +58,8 @@ class WirelessTagSensor(WirelessTagBaseSensor):
# sensor.wirelesstag_bedroom_temperature
# and not as sensor.bedroom for temperature and
# sensor.bedroom_2 for humidity
self._entity_id = "{}.{}_{}_{}".format(
"sensor", WIRELESSTAG_DOMAIN, self.underscored_name, self._sensor_type
self._entity_id = (
f"sensor.{WIRELESSTAG_DOMAIN}_{self.underscored_name}_{self._sensor_type}"
)
async def async_added_to_hass(self):

View File

@ -57,7 +57,7 @@ class WirelessTagSwitch(WirelessTagBaseSensor, SwitchDevice):
super().__init__(api, tag)
self._switch_type = switch_type
self.sensor_type = SWITCH_TYPES[self._switch_type][1]
self._name = "{} {}".format(self._tag.name, SWITCH_TYPES[self._switch_type][0])
self._name = f"{self._tag.name} {SWITCH_TYPES[self._switch_type][0]}"
def turn_on(self, **kwargs):
"""Turn on the switch."""

View File

@ -320,8 +320,9 @@ class WithingsHealthSensor(Entity):
@property
def unique_id(self) -> str:
"""Return a unique, Home Assistant friendly identifier for this entity."""
return "withings_{}_{}_{}".format(
self._slug, self._user_id, slugify(self._attribute.measurement)
return (
f"withings_{self._slug}_{self._user_id}_"
f"{slugify(self._attribute.measurement)}"
)
@property

View File

@ -217,9 +217,9 @@ class WUHourlyForecastSensorConfig(WUSensorConfig):
:param field: field name to use as value
"""
super().__init__(
friendly_name=lambda wu: "{} {}".format(
wu.data["hourly_forecast"][period]["FCTTIME"]["weekday_name_abbrev"],
wu.data["hourly_forecast"][period]["FCTTIME"]["civil"],
friendly_name=lambda wu: (
f"{wu.data['hourly_forecast'][period]['FCTTIME']['weekday_name_abbrev']} "
f"{wu.data['hourly_forecast'][period]['FCTTIME']['civil']}"
),
feature="hourly",
value=lambda wu: wu.data["hourly_forecast"][period][field],
@ -477,8 +477,9 @@ SENSOR_TYPES = {
"Wind Summary", "wind_string", "mdi:weather-windy"
),
"temp_high_record_c": WUAlmanacSensorConfig(
lambda wu: "High Temperature Record ({})".format(
wu.data["almanac"]["temp_high"]["recordyear"]
lambda wu: (
f"High Temperature Record "
f"({wu.data['almanac']['temp_high']['recordyear']})"
),
"temp_high",
"record",
@ -487,8 +488,9 @@ SENSOR_TYPES = {
"mdi:thermometer",
),
"temp_high_record_f": WUAlmanacSensorConfig(
lambda wu: "High Temperature Record ({})".format(
wu.data["almanac"]["temp_high"]["recordyear"]
lambda wu: (
f"High Temperature Record "
f"({wu.data['almanac']['temp_high']['recordyear']})"
),
"temp_high",
"record",
@ -497,8 +499,9 @@ SENSOR_TYPES = {
"mdi:thermometer",
),
"temp_low_record_c": WUAlmanacSensorConfig(
lambda wu: "Low Temperature Record ({})".format(
wu.data["almanac"]["temp_low"]["recordyear"]
lambda wu: (
f"Low Temperature Record "
f"({wu.data['almanac']['temp_low']['recordyear']})"
),
"temp_low",
"record",
@ -507,8 +510,9 @@ SENSOR_TYPES = {
"mdi:thermometer",
),
"temp_low_record_f": WUAlmanacSensorConfig(
lambda wu: "Low Temperature Record ({})".format(
wu.data["almanac"]["temp_low"]["recordyear"]
lambda wu: (
f"Low Temperature Record "
f"({wu.data['almanac']['temp_low']['recordyear']})"
),
"temp_low",
"record",

View File

@ -136,9 +136,7 @@ class XiaomiCamera(Camera):
else:
video = videos[-1]
return "ftp://{}:{}@{}:{}{}/{}".format(
self.user, self.passwd, host, self.port, ftp.pwd(), video
)
return f"ftp://{self.user}:{self.passwd}@{host}:{self.port}{ftp.pwd()}/{video}"
async def async_camera_image(self):
"""Return a still image response from the camera."""

View File

@ -242,8 +242,8 @@ class XiaomiDevice(Entity):
self.parse_voltage(device["data"])
if hasattr(self, "_data_key") and self._data_key: # pylint: disable=no-member
self._unique_id = "{}{}".format(
self._data_key, self._sid # pylint: disable=no-member
self._unique_id = (
f"{self._data_key}{self._sid}" # pylint: disable=no-member
)
else:
self._unique_id = f"{self._type}{self._sid}"

View File

@ -718,7 +718,7 @@ class XiaomiPhilipsEyecareLampAmbientLight(XiaomiPhilipsAbstractLight):
"""Initialize the light device."""
name = f"{name} Ambient Light"
if unique_id is not None:
unique_id = "{}-{}".format(unique_id, "ambient")
unique_id = f"{unique_id}-ambient"
super().__init__(name, light, model, unique_id)
async def async_turn_on(self, **kwargs):

View File

@ -429,7 +429,7 @@ class ChuangMiPlugSwitch(XiaomiPlugGenericSwitch):
name = f"{name} USB" if channel_usb else name
if unique_id is not None and channel_usb:
unique_id = "{}-{}".format(unique_id, "usb")
unique_id = f"{unique_id}-usb"
super().__init__(name, plug, model, unique_id)
self._channel_usb = channel_usb

View File

@ -110,14 +110,9 @@ class YiCamera(Camera):
await ftp.quit()
self._is_on = True
return "ftp://{}:{}@{}:{}{}/{}/{}".format(
self.user,
self.passwd,
self.host,
self.port,
self.path,
latest_dir,
videos[-1],
return (
f"ftp://{self.user}:{self.passwd}@{self.host}:"
f"{self.port}{self.path}/{latest_dir}/{videos[-1]}"
)
except (ConnectionRefusedError, StatusCodeError) as err:
_LOGGER.error("Error while fetching video: %s", err)

View File

@ -159,7 +159,7 @@ class ZamgData:
"""The class for handling the data retrieval."""
API_URL = "http://www.zamg.ac.at/ogd/"
API_HEADERS = {USER_AGENT: "{} {}".format("home-assistant.zamg/", __version__)}
API_HEADERS = {USER_AGENT: f"home-assistant.zamg/ {__version__}"}
def __init__(self, station_id):
"""Initialize the probe."""

View File

@ -78,8 +78,9 @@ class ZamgWeather(WeatherEntity):
@property
def name(self):
"""Return the name of the sensor."""
return self.stationname or "ZAMG {}".format(
self.zamg_data.data.get("Name") or "(unknown station)"
return (
self.stationname
or f"ZAMG {self.zamg_data.data.get('Name') or '(unknown station)'}"
)
@property

View File

@ -93,9 +93,7 @@ class ZHADevice(LogMixin):
self._zigpy_device = zigpy_device
self._zha_gateway = zha_gateway
self._available = False
self._available_signal = "{}_{}_{}".format(
self.name, self.ieee, SIGNAL_AVAILABLE
)
self._available_signal = f"{self.name}_{self.ieee}_{SIGNAL_AVAILABLE}"
self._checkins_missed_count = 0
self.unsubs = []
self.unsubs.append(
@ -104,10 +102,11 @@ class ZHADevice(LogMixin):
)
)
self.quirk_applied = isinstance(self._zigpy_device, zigpy.quirks.CustomDevice)
self.quirk_class = "{}.{}".format(
self._zigpy_device.__class__.__module__,
self._zigpy_device.__class__.__name__,
self.quirk_class = (
f"{self._zigpy_device.__class__.__module__}."
f"{self._zigpy_device.__class__.__name__}"
)
if self.is_mains_powered:
self._consider_unavailable_time = _CONSIDER_UNAVAILABLE_MAINS
else:
@ -352,9 +351,7 @@ class ZHADevice(LogMixin):
if self._available != available and available:
# Update the state the first time the device comes online
async_dispatcher_send(self.hass, self._available_signal, False)
async_dispatcher_send(
self.hass, "{}_{}".format(self._available_signal, "entity"), available
)
async_dispatcher_send(self.hass, f"{self._available_signal}_entity", available)
self._available = available
@property

View File

@ -119,7 +119,7 @@ class BaseZhaEntity(RestoreEntity, LogMixin, entity.Entity):
self.remove_future = asyncio.Future()
await self.async_accept_signal(
None,
"{}_{}".format(SIGNAL_REMOVE, str(self.zha_device.ieee)),
f"{SIGNAL_REMOVE}_{self.zha_device.ieee}",
self.async_remove,
signal_override=True,
)
@ -182,7 +182,7 @@ class ZhaEntity(BaseZhaEntity):
await self.async_check_recently_seen()
await self.async_accept_signal(
None,
"{}_{}".format(self.zha_device.available_signal, "entity"),
f"{self.zha_device.available_signal}_entity",
self.async_set_available,
signal_override=True,
)

View File

@ -261,7 +261,7 @@ def _obj_to_dict(obj):
def _value_name(value):
"""Return the name of the value."""
return "{} {}".format(node_name(value.node), value.label).strip()
return f"{node_name(value.node)} {value.label}".strip()
def nice_print_node(node):
@ -826,9 +826,7 @@ async def async_setup_entry(hass, config_entry):
)
return
_LOGGER.info(
"Node %s on instance %s does not have resettable meters.",
node_id,
instance,
"Node %s on instance %s does not have resettable meters.", node_id, instance
)
def heal_node(service):

View File

@ -337,21 +337,20 @@ class ZwaveLock(ZWaveDeviceEntity, LockDevice):
)
if alarm_type == 21:
self._lock_status = "{}{}".format(
LOCK_ALARM_TYPE.get(str(alarm_type)),
MANUAL_LOCK_ALARM_LEVEL.get(str(alarm_level)),
self._lock_status = (
f"{LOCK_ALARM_TYPE.get(str(alarm_type))}"
f"{MANUAL_LOCK_ALARM_LEVEL.get(str(alarm_level))}"
)
return
if str(alarm_type) in ALARM_TYPE_STD:
self._lock_status = "{}{}".format(
LOCK_ALARM_TYPE.get(str(alarm_type)), str(alarm_level)
)
self._lock_status = f"{LOCK_ALARM_TYPE.get(str(alarm_type))}{alarm_level}"
return
if alarm_type == 161:
self._lock_status = "{}{}".format(
LOCK_ALARM_TYPE.get(str(alarm_type)),
TAMPER_ALARM_LEVEL.get(str(alarm_level)),
self._lock_status = (
f"{LOCK_ALARM_TYPE.get(str(alarm_type))}"
f"{TAMPER_ALARM_LEVEL.get(str(alarm_level))}"
)
return
if alarm_type != 0:
self._lock_status = LOCK_ALARM_TYPE.get(str(alarm_type))

View File

@ -116,10 +116,9 @@ def _no_duplicate_auth_provider(
key = (config[CONF_TYPE], config.get(CONF_ID))
if key in config_keys:
raise vol.Invalid(
"Duplicate auth provider {} found. Please add unique IDs if "
"you want to have the same auth provider twice".format(
config[CONF_TYPE]
)
f"Duplicate auth provider {config[CONF_TYPE]} found. "
"Please add unique IDs "
"if you want to have the same auth provider twice"
)
config_keys.add(key)
return configs
@ -140,8 +139,9 @@ def _no_duplicate_auth_mfa_module(
key = config.get(CONF_ID, config[CONF_TYPE])
if key in config_keys:
raise vol.Invalid(
"Duplicate mfa module {} found. Please add unique IDs if "
"you want to have the same mfa module twice".format(config[CONF_TYPE])
f"Duplicate mfa module {config[CONF_TYPE]} found. "
"Please add unique IDs "
"if you want to have the same mfa module twice"
)
config_keys.add(key)
return configs
@ -319,8 +319,9 @@ def load_yaml_config_file(config_path: str) -> Dict[Any, Any]:
conf_dict = load_yaml(config_path)
if not isinstance(conf_dict, dict):
msg = "The configuration file {} does not contain a dictionary".format(
os.path.basename(config_path)
msg = (
f"The configuration file {os.path.basename(config_path)} "
"does not contain a dictionary"
)
_LOGGER.error(msg)
raise HomeAssistantError(msg)
@ -415,16 +416,13 @@ def _format_config_error(
message = f"Invalid config for [{domain}]: "
if isinstance(ex, vol.Invalid):
if "extra keys not allowed" in ex.error_message:
path = "->".join(str(m) for m in ex.path)
message += (
"[{option}] is an invalid option for [{domain}]. "
"Check: {domain}->{path}.".format(
option=ex.path[-1],
domain=domain,
path="->".join(str(m) for m in ex.path),
)
f"[{ex.path[-1]}] is an invalid option for [{domain}]. "
f"Check: {domain}->{path}."
)
else:
message += "{}.".format(humanize_error(config, ex))
message += f"{humanize_error(config, ex)}."
else:
message += str(ex)
@ -433,9 +431,9 @@ def _format_config_error(
except AttributeError:
domain_config = config
message += " (See {}, line {}). ".format(
getattr(domain_config, "__config_file__", "?"),
getattr(domain_config, "__line__", "?"),
message += (
f" (See {getattr(domain_config, '__config_file__', '?')}, "
f"line {getattr(domain_config, '__line__', '?')}). "
)
if domain != CONF_CORE and link:
@ -551,9 +549,9 @@ def _log_pkg_error(package: str, component: str, config: Dict, message: str) ->
message = f"Package {package} setup failed. Integration {component} {message}"
pack_config = config[CONF_CORE][CONF_PACKAGES].get(package, config)
message += " (See {}:{}). ".format(
getattr(pack_config, "__config_file__", "?"),
getattr(pack_config, "__line__", "?"),
message += (
f" (See {getattr(pack_config, '__config_file__', '?')}:"
f"{getattr(pack_config, '__line__', '?')}). "
)
_LOGGER.error(message)

View File

@ -179,9 +179,7 @@ class FlowManager(abc.ABC):
RESULT_TYPE_ABORT,
RESULT_TYPE_EXTERNAL_STEP_DONE,
):
raise ValueError(
"Handler returned incorrect type: {}".format(result["type"])
)
raise ValueError(f"Handler returned incorrect type: {result['type']}")
if result["type"] in (
RESULT_TYPE_FORM,

View File

@ -146,7 +146,7 @@ class EntityRegistry:
Conflicts checked against registered and currently existing entities.
"""
return ensure_unique_string(
"{}.{}".format(domain, slugify(suggested_object_id)),
f"{domain}.{slugify(suggested_object_id)}",
chain(
self.entities.keys(),
self.hass.states.async_entity_ids(domain),

View File

@ -425,8 +425,7 @@ def _load_file(
if str(err) not in white_listed_errors:
_LOGGER.exception(
("Error loading %s. Make sure all dependencies are installed"),
path,
("Error loading %s. Make sure all dependencies are installed"), path
)
return None

View File

@ -233,9 +233,7 @@ def line_info(obj, **kwargs):
"""Display line config source."""
if hasattr(obj, "__config_file__"):
return color(
"cyan",
"[source {}:{}]".format(obj.__config_file__, obj.__line__ or "?"),
**kwargs,
"cyan", f"[source {obj.__config_file__}:{obj.__line__ or '?'}]", **kwargs
)
return "?"

View File

@ -54,9 +54,8 @@ def _yaml_unsupported(
constructor: ExtSafeConstructor, node: ruamel.yaml.nodes.Node
) -> None:
raise UnsupportedYamlError(
"Unsupported YAML, you can not use {} in {}".format(
node.tag, os.path.basename(constructor.name or "(None)")
)
f"Unsupported YAML, you can not use {node.tag} in "
f"{os.path.basename(constructor.name or '(None)')}"
)

View File

@ -182,7 +182,7 @@ def gather_requirements_from_modules(errors, reqs):
try:
module = importlib.import_module(package)
except ImportError as err:
print("{}.py: {}".format(package.replace(".", "/"), err))
print(f"{package.replace('.', '/')}.py: {err}")
errors.append(package)
continue

View File

@ -48,9 +48,7 @@ def generate_and_validate(integrations: Dict[str, Integration]):
"codeowners", "Code owners need to be valid GitHub handles."
)
parts.append(
"homeassistant/components/{}/* {}".format(domain, " ".join(codeowners))
)
parts.append(f"homeassistant/components/{domain}/* {' '.join(codeowners)}")
parts.append(f"\n{INDIVIDUAL_FILES.strip()}")

View File

@ -12,12 +12,7 @@ DOCUMENTATION_URL_HOST = "www.home-assistant.io"
DOCUMENTATION_URL_PATH_PREFIX = "/integrations/"
DOCUMENTATION_URL_EXCEPTIONS = ["https://www.home-assistant.io/hassio"]
SUPPORTED_QUALITY_SCALES = [
"gold",
"internal",
"platinum",
"silver",
]
SUPPORTED_QUALITY_SCALES = ["gold", "internal", "platinum", "silver"]
def documentation_url(value: str) -> str:
@ -68,8 +63,7 @@ def validate_manifest(integration: Integration):
MANIFEST_SCHEMA(integration.manifest)
except vol.Invalid as err:
integration.add_error(
"manifest",
"Invalid manifest: {}".format(humanize_error(integration.manifest, err)),
"manifest", f"Invalid manifest: {humanize_error(integration.manifest, err)}"
)
integration.manifest = None
return

View File

@ -79,7 +79,7 @@ def validate_services(integration: Integration):
SERVICES_SCHEMA(data)
except vol.Invalid as err:
integration.add_error(
"services", "Invalid services.yaml: {}".format(humanize_error(data, err))
"services", f"Invalid services.yaml: {humanize_error(data, err)}"
)

View File

@ -79,8 +79,8 @@ def generate_and_validate(integrations: Dict[str, Integration]):
if model in homekit_dict:
integration.add_error(
"zeroconf",
"Integrations {} and {} have overlapping HomeKit "
"models".format(domain, homekit_dict[model]),
f"Integrations {domain} and {homekit_dict[model]} "
"have overlapping HomeKit models",
)
break
@ -100,8 +100,8 @@ def generate_and_validate(integrations: Dict[str, Integration]):
if key.startswith(key_2) or key_2.startswith(key):
integration.add_error(
"zeroconf",
"Integrations {} and {} have overlapping HomeKit "
"models".format(homekit_dict[key], homekit_dict[key_2]),
f"Integrations {homekit_dict[key]} and {homekit_dict[key_2]} "
"have overlapping HomeKit models",
)
warned.add(key)
warned.add(key_2)

View File

@ -61,7 +61,7 @@ async def async_exec(*args, display=False):
argsp = []
for arg in args:
if os.path.isfile(arg):
argsp.append("\\\n {}".format(shlex.quote(arg)))
argsp.append(f"\\\n {shlex.quote(arg)}")
else:
argsp.append(shlex.quote(arg))
printc("cyan", *argsp)
@ -75,9 +75,7 @@ async def async_exec(*args, display=False):
kwargs["stderr"] = asyncio.subprocess.PIPE
proc = await asyncio.create_subprocess_exec(*args, **kwargs)
except FileNotFoundError as err:
printc(
FAIL, f"Could not execute {args[0]}. Did you install test requirements?",
)
printc(FAIL, f"Could not execute {args[0]}. Did you install test requirements?")
raise err
if not display: