Drop Python 2 Support (#793)

* Remove Python 2 support

* Remove u-strings

* Remove docker symlinks

* Remove from travis

* Update requirements

* Upgrade flake8/pylint

* Fixes

* Manual

* Run pyupgrade

* Lint

* Remove base_int

* Fix

* Update platformio_api.py

* Update component.cpp
This commit is contained in:
Otto Winter 2019-12-07 18:28:55 +01:00 committed by GitHub
parent b5714cd70f
commit 056c72d50d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
78 changed files with 815 additions and 1097 deletions

View file

@ -21,12 +21,6 @@ matrix:
- esphome tests/test1.yaml compile - esphome tests/test1.yaml compile
- esphome tests/test2.yaml compile - esphome tests/test2.yaml compile
- esphome tests/test3.yaml compile - esphome tests/test3.yaml compile
- python: "2.7"
env: TARGET=Test2.7
script:
- esphome tests/test1.yaml compile
- esphome tests/test2.yaml compile
- esphome tests/test3.yaml compile
- env: TARGET=Cpp-Lint - env: TARGET=Cpp-Lint
dist: trusty dist: trusty
sudo: required sudo: required

View file

@ -14,7 +14,5 @@ RUN \
COPY requirements_test.txt /requirements_test.txt COPY requirements_test.txt /requirements_test.txt
RUN pip3 install --no-cache-dir wheel && pip3 install --no-cache-dir -r /requirements_test.txt RUN pip3 install --no-cache-dir wheel && pip3 install --no-cache-dir -r /requirements_test.txt
RUN ln -s /usr/bin/pip3 /usr/bin/pip && ln -f -s /usr/bin/python3 /usr/bin/python
VOLUME ["/esphome"] VOLUME ["/esphome"]
WORKDIR /esphome WORKDIR /esphome

View file

@ -1,5 +1,3 @@
from __future__ import print_function
import argparse import argparse
import functools import functools
import logging import logging
@ -14,7 +12,6 @@ from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \
CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
from esphome.helpers import color, indent from esphome.helpers import color, indent
from esphome.py_compat import IS_PY2, safe_input, IS_PY3
from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -42,12 +39,12 @@ def choose_prompt(options):
if len(options) == 1: if len(options) == 1:
return options[0][1] return options[0][1]
safe_print(u"Found multiple options, please choose one:") safe_print("Found multiple options, please choose one:")
for i, (desc, _) in enumerate(options): for i, (desc, _) in enumerate(options):
safe_print(u" [{}] {}".format(i + 1, desc)) safe_print(f" [{i+1}] {desc}")
while True: while True:
opt = safe_input('(number): ') opt = input('(number): ')
if opt in options: if opt in options:
opt = options.index(opt) opt = options.index(opt)
break break
@ -57,20 +54,20 @@ def choose_prompt(options):
raise ValueError raise ValueError
break break
except ValueError: except ValueError:
safe_print(color('red', u"Invalid option: '{}'".format(opt))) safe_print(color('red', f"Invalid option: '{opt}'"))
return options[opt - 1][1] return options[opt - 1][1]
def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api): def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api):
options = [] options = []
for res, desc in get_serial_ports(): for res, desc in get_serial_ports():
options.append((u"{} ({})".format(res, desc), res)) options.append((f"{res} ({desc})", res))
if (show_ota and 'ota' in CORE.config) or (show_api and 'api' in CORE.config): if (show_ota and 'ota' in CORE.config) or (show_api and 'api' in CORE.config):
options.append((u"Over The Air ({})".format(CORE.address), CORE.address)) options.append((f"Over The Air ({CORE.address})", CORE.address))
if default == 'OTA': if default == 'OTA':
return CORE.address return CORE.address
if show_mqtt and 'mqtt' in CORE.config: if show_mqtt and 'mqtt' in CORE.config:
options.append((u"MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT')) options.append(("MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT'))
if default == 'OTA': if default == 'OTA':
return 'MQTT' return 'MQTT'
if default is not None: if default is not None:
@ -108,11 +105,7 @@ def run_miniterm(config, port):
except serial.SerialException: except serial.SerialException:
_LOGGER.error("Serial port closed!") _LOGGER.error("Serial port closed!")
return return
if IS_PY2: line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8', 'backslashreplace')
line = raw.replace('\r', '').replace('\n', '')
else:
line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8',
'backslashreplace')
time = datetime.now().time().strftime('[%H:%M:%S]') time = datetime.now().time().strftime('[%H:%M:%S]')
message = time + line message = time + line
safe_print(message) safe_print(message)
@ -127,11 +120,9 @@ def wrap_to_code(name, comp):
@functools.wraps(comp.to_code) @functools.wraps(comp.to_code)
@coroutine_with_priority(coro.priority) @coroutine_with_priority(coro.priority)
def wrapped(conf): def wrapped(conf):
cg.add(cg.LineComment(u"{}:".format(name))) cg.add(cg.LineComment(f"{name}:"))
if comp.config_schema is not None: if comp.config_schema is not None:
conf_str = yaml_util.dump(conf) conf_str = yaml_util.dump(conf)
if IS_PY2:
conf_str = conf_str.decode('utf-8')
conf_str = conf_str.replace('//', '') conf_str = conf_str.replace('//', '')
cg.add(cg.LineComment(indent(conf_str))) cg.add(cg.LineComment(indent(conf_str)))
yield coro(conf) yield coro(conf)
@ -243,7 +234,7 @@ def setup_log(debug=False, quiet=False):
log_level = logging.INFO log_level = logging.INFO
logging.basicConfig(level=log_level) logging.basicConfig(level=log_level)
fmt = "%(levelname)s %(message)s" fmt = "%(levelname)s %(message)s"
colorfmt = "%(log_color)s{}%(reset)s".format(fmt) colorfmt = f"%(log_color)s{fmt}%(reset)s"
datefmt = '%H:%M:%S' datefmt = '%H:%M:%S'
logging.getLogger('urllib3').setLevel(logging.WARNING) logging.getLogger('urllib3').setLevel(logging.WARNING)
@ -292,12 +283,12 @@ def command_compile(args, config):
if exit_code != 0: if exit_code != 0:
return exit_code return exit_code
if args.only_generate: if args.only_generate:
_LOGGER.info(u"Successfully generated source code.") _LOGGER.info("Successfully generated source code.")
return 0 return 0
exit_code = compile_program(args, config) exit_code = compile_program(args, config)
if exit_code != 0: if exit_code != 0:
return exit_code return exit_code
_LOGGER.info(u"Successfully compiled program.") _LOGGER.info("Successfully compiled program.")
return 0 return 0
@ -307,7 +298,7 @@ def command_upload(args, config):
exit_code = upload_program(config, args, port) exit_code = upload_program(config, args, port)
if exit_code != 0: if exit_code != 0:
return exit_code return exit_code
_LOGGER.info(u"Successfully uploaded program.") _LOGGER.info("Successfully uploaded program.")
return 0 return 0
@ -324,13 +315,13 @@ def command_run(args, config):
exit_code = compile_program(args, config) exit_code = compile_program(args, config)
if exit_code != 0: if exit_code != 0:
return exit_code return exit_code
_LOGGER.info(u"Successfully compiled program.") _LOGGER.info("Successfully compiled program.")
port = choose_upload_log_host(default=args.upload_port, check_default=None, port = choose_upload_log_host(default=args.upload_port, check_default=None,
show_ota=True, show_mqtt=False, show_api=True) show_ota=True, show_mqtt=False, show_api=True)
exit_code = upload_program(config, args, port) exit_code = upload_program(config, args, port)
if exit_code != 0: if exit_code != 0:
return exit_code return exit_code
_LOGGER.info(u"Successfully uploaded program.") _LOGGER.info("Successfully uploaded program.")
if args.no_logs: if args.no_logs:
return 0 return 0
port = choose_upload_log_host(default=args.upload_port, check_default=port, port = choose_upload_log_host(default=args.upload_port, check_default=port,
@ -349,7 +340,7 @@ def command_mqtt_fingerprint(args, config):
def command_version(args): def command_version(args):
safe_print(u"Version: {}".format(const.__version__)) safe_print(f"Version: {const.__version__}")
return 0 return 0
@ -377,10 +368,10 @@ def command_update_all(args):
twidth = 60 twidth = 60
def print_bar(middle_text): def print_bar(middle_text):
middle_text = " {} ".format(middle_text) middle_text = f" {middle_text} "
width = len(click.unstyle(middle_text)) width = len(click.unstyle(middle_text))
half_line = "=" * ((twidth - width) // 2) half_line = "=" * ((twidth - width) // 2)
click.echo("%s%s%s" % (half_line, middle_text, half_line)) click.echo(f"{half_line}{middle_text}{half_line}")
for f in files: for f in files:
print("Updating {}".format(color('cyan', f))) print("Updating {}".format(color('cyan', f)))
@ -431,7 +422,7 @@ POST_CONFIG_ACTIONS = {
def parse_args(argv): def parse_args(argv):
parser = argparse.ArgumentParser(description='ESPHome v{}'.format(const.__version__)) parser = argparse.ArgumentParser(description=f'ESPHome v{const.__version__}')
parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.", parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.",
action='store_true') action='store_true')
parser.add_argument('-q', '--quiet', help="Disable all esphome logs.", parser.add_argument('-q', '--quiet', help="Disable all esphome logs.",
@ -525,14 +516,10 @@ def run_esphome(argv):
_LOGGER.error("Missing configuration parameter, see esphome --help.") _LOGGER.error("Missing configuration parameter, see esphome --help.")
return 1 return 1
if IS_PY2: if sys.version_info < (3, 6, 0):
_LOGGER.warning("You're using ESPHome with python 2. Support for python 2 is deprecated " _LOGGER.error("You're running ESPHome with Python <3.6. ESPHome is no longer compatible "
"and will be removed in 1.15.0. Please reinstall ESPHome with python 3.6 " "with this Python version. Please reinstall ESPHome with Python 3.6+")
"or higher.") return 1
elif IS_PY3 and sys.version_info < (3, 6, 0):
_LOGGER.warning("You're using ESPHome with python 3.5. Support for python 3.5 is "
"deprecated and will be removed in 1.15.0. Please reinstall ESPHome with "
"python 3.6 or higher.")
if args.command in PRE_CONFIG_ACTIONS: if args.command in PRE_CONFIG_ACTIONS:
try: try:
@ -551,7 +538,7 @@ def run_esphome(argv):
CORE.config = config CORE.config = config
if args.command not in POST_CONFIG_ACTIONS: if args.command not in POST_CONFIG_ACTIONS:
safe_print(u"Unknown command {}".format(args.command)) safe_print(f"Unknown command {args.command}")
try: try:
rc = POST_CONFIG_ACTIONS[args.command](args, config) rc = POST_CONFIG_ACTIONS[args.command](args, config)

View file

@ -14,7 +14,6 @@ import esphome.api.api_pb2 as pb
from esphome.const import CONF_PASSWORD, CONF_PORT from esphome.const import CONF_PASSWORD, CONF_PORT
from esphome.core import EsphomeError from esphome.core import EsphomeError
from esphome.helpers import resolve_ip_address, indent, color from esphome.helpers import resolve_ip_address, indent, color
from esphome.py_compat import text_type, IS_PY2, byte_to_bytes, char_to_byte
from esphome.util import safe_print from esphome.util import safe_print
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -67,16 +66,16 @@ MESSAGE_TYPE_TO_PROTO = {
def _varuint_to_bytes(value): def _varuint_to_bytes(value):
if value <= 0x7F: if value <= 0x7F:
return byte_to_bytes(value) return bytes([value])
ret = bytes() ret = bytes()
while value: while value:
temp = value & 0x7F temp = value & 0x7F
value >>= 7 value >>= 7
if value: if value:
ret += byte_to_bytes(temp | 0x80) ret += bytes([temp | 0x80])
else: else:
ret += byte_to_bytes(temp) ret += bytes([temp])
return ret return ret
@ -84,8 +83,7 @@ def _varuint_to_bytes(value):
def _bytes_to_varuint(value): def _bytes_to_varuint(value):
result = 0 result = 0
bitpos = 0 bitpos = 0
for c in value: for val in value:
val = char_to_byte(c)
result |= (val & 0x7F) << bitpos result |= (val & 0x7F) << bitpos
bitpos += 7 bitpos += 7
if (val & 0x80) == 0: if (val & 0x80) == 0:
@ -191,8 +189,8 @@ class APIClient(threading.Thread):
self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
try: try:
self._socket.connect((ip, self._port)) self._socket.connect((ip, self._port))
except socket.error as err: except OSError as err:
err = APIConnectionError("Error connecting to {}: {}".format(ip, err)) err = APIConnectionError(f"Error connecting to {ip}: {err}")
self._fatal_error(err) self._fatal_error(err)
raise err raise err
self._socket.settimeout(0.1) self._socket.settimeout(0.1)
@ -200,7 +198,7 @@ class APIClient(threading.Thread):
self._socket_open_event.set() self._socket_open_event.set()
hello = pb.HelloRequest() hello = pb.HelloRequest()
hello.client_info = 'ESPHome v{}'.format(const.__version__) hello.client_info = f'ESPHome v{const.__version__}'
try: try:
resp = self._send_message_await_response(hello, pb.HelloResponse) resp = self._send_message_await_response(hello, pb.HelloResponse)
except APIConnectionError as err: except APIConnectionError as err:
@ -251,8 +249,8 @@ class APIClient(threading.Thread):
with self._socket_write_lock: with self._socket_write_lock:
try: try:
self._socket.sendall(data) self._socket.sendall(data)
except socket.error as err: except OSError as err:
err = APIConnectionError("Error while writing data: {}".format(err)) err = APIConnectionError(f"Error while writing data: {err}")
self._fatal_error(err) self._fatal_error(err)
raise err raise err
@ -265,10 +263,7 @@ class APIClient(threading.Thread):
raise ValueError raise ValueError
encoded = msg.SerializeToString() encoded = msg.SerializeToString()
_LOGGER.debug("Sending %s:\n%s", type(msg), indent(text_type(msg))) _LOGGER.debug("Sending %s:\n%s", type(msg), indent(str(msg)))
if IS_PY2:
req = chr(0x00)
else:
req = bytes([0]) req = bytes([0])
req += _varuint_to_bytes(len(encoded)) req += _varuint_to_bytes(len(encoded))
req += _varuint_to_bytes(message_type) req += _varuint_to_bytes(message_type)
@ -355,14 +350,14 @@ class APIClient(threading.Thread):
raise APIConnectionError("Socket was closed") raise APIConnectionError("Socket was closed")
except socket.timeout: except socket.timeout:
continue continue
except socket.error as err: except OSError as err:
raise APIConnectionError("Error while receiving data: {}".format(err)) raise APIConnectionError(f"Error while receiving data: {err}")
ret += val ret += val
return ret return ret
def _recv_varint(self): def _recv_varint(self):
raw = bytes() raw = bytes()
while not raw or char_to_byte(raw[-1]) & 0x80: while not raw or raw[-1] & 0x80:
raw += self._recv(1) raw += self._recv(1)
return _bytes_to_varuint(raw) return _bytes_to_varuint(raw)
@ -371,7 +366,7 @@ class APIClient(threading.Thread):
return return
# Preamble # Preamble
if char_to_byte(self._recv(1)[0]) != 0x00: if self._recv(1)[0] != 0x00:
raise APIConnectionError("Invalid preamble") raise APIConnectionError("Invalid preamble")
length = self._recv_varint() length = self._recv_varint()
@ -436,7 +431,7 @@ def run_logs(config, address):
return return
if err: if err:
_LOGGER.warning(u"Disconnected from API: %s", err) _LOGGER.warning("Disconnected from API: %s", err)
while retry_timer: while retry_timer:
retry_timer.pop(0).cancel() retry_timer.pop(0).cancel()
@ -454,18 +449,18 @@ def run_logs(config, address):
wait_time = int(min(1.5**min(tries, 100), 30)) wait_time = int(min(1.5**min(tries, 100), 30))
if not has_connects: if not has_connects:
_LOGGER.warning(u"Initial connection failed. The ESP might not be connected " _LOGGER.warning("Initial connection failed. The ESP might not be connected "
u"to WiFi yet (%s). Re-Trying in %s seconds", "to WiFi yet (%s). Re-Trying in %s seconds",
error, wait_time) error, wait_time)
else: else:
_LOGGER.warning(u"Couldn't connect to API (%s). Trying to reconnect in %s seconds", _LOGGER.warning("Couldn't connect to API (%s). Trying to reconnect in %s seconds",
error, wait_time) error, wait_time)
timer = threading.Timer(wait_time, functools.partial(try_connect, None, tries + 1)) timer = threading.Timer(wait_time, functools.partial(try_connect, None, tries + 1))
timer.start() timer.start()
retry_timer.append(timer) retry_timer.append(timer)
def on_log(msg): def on_log(msg):
time_ = datetime.now().time().strftime(u'[%H:%M:%S]') time_ = datetime.now().time().strftime('[%H:%M:%S]')
text = msg.message text = msg.message
if msg.send_failed: if msg.send_failed:
text = color('white', '(Message skipped because it was too big to fit in ' text = color('white', '(Message skipped because it was too big to fit in '

View file

@ -83,9 +83,9 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
try: try:
return cv.Schema([schema])(value) return cv.Schema([schema])(value)
except cv.Invalid as err2: except cv.Invalid as err2:
if u'extra keys not allowed' in str(err2) and len(err2.path) == 2: if 'extra keys not allowed' in str(err2) and len(err2.path) == 2:
raise err raise err
if u'Unable to find action' in str(err): if 'Unable to find action' in str(err):
raise err2 raise err2
raise cv.MultipleInvalid([err, err2]) raise cv.MultipleInvalid([err, err2])
elif isinstance(value, dict): elif isinstance(value, dict):

View file

@ -36,4 +36,4 @@ def to_code(config):
continue continue
conf = config[key] conf = config[key]
sens = yield sensor.new_sensor(conf) sens = yield sensor.new_sensor(conf)
cg.add(getattr(var, 'set_{}_sensor'.format(key))(sens)) cg.add(getattr(var, f'set_{key}_sensor')(sens))

View file

@ -2,7 +2,6 @@ import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import sensor, voltage_sampler from esphome.components import sensor, voltage_sampler
from esphome.const import CONF_GAIN, CONF_MULTIPLEXER, ICON_FLASH, UNIT_VOLT, CONF_ID from esphome.const import CONF_GAIN, CONF_MULTIPLEXER, ICON_FLASH, UNIT_VOLT, CONF_ID
from esphome.py_compat import string_types
from . import ads1115_ns, ADS1115Component from . import ads1115_ns, ADS1115Component
DEPENDENCIES = ['ads1115'] DEPENDENCIES = ['ads1115']
@ -32,9 +31,9 @@ GAIN = {
def validate_gain(value): def validate_gain(value):
if isinstance(value, float): if isinstance(value, float):
value = u'{:0.03f}'.format(value) value = f'{value:0.03f}'
elif not isinstance(value, string_types): elif not isinstance(value, str):
raise cv.Invalid('invalid gain "{}"'.format(value)) raise cv.Invalid(f'invalid gain "{value}"')
return cv.enum(GAIN)(value) return cv.enum(GAIN)(value)

View file

@ -102,7 +102,7 @@ def homeassistant_service_to_code(config, action_id, template_arg, args):
def validate_homeassistant_event(value): def validate_homeassistant_event(value):
value = cv.string(value) value = cv.string(value)
if not value.startswith(u'esphome.'): if not value.startswith('esphome.'):
raise cv.Invalid("ESPHome can only generate Home Assistant events that begin with " raise cv.Invalid("ESPHome can only generate Home Assistant events that begin with "
"esphome. For example 'esphome.xyz'") "esphome. For example 'esphome.xyz'")
return value return value

View file

@ -9,7 +9,6 @@ from esphome.const import CONF_DEVICE_CLASS, CONF_FILTERS, \
CONF_ON_DOUBLE_CLICK, CONF_ON_MULTI_CLICK, CONF_ON_PRESS, CONF_ON_RELEASE, CONF_ON_STATE, \ CONF_ON_DOUBLE_CLICK, CONF_ON_MULTI_CLICK, CONF_ON_PRESS, CONF_ON_RELEASE, CONF_ON_STATE, \
CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID, CONF_FOR, CONF_NAME, CONF_MQTT_ID CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID, CONF_FOR, CONF_NAME, CONF_MQTT_ID
from esphome.core import CORE, coroutine, coroutine_with_priority from esphome.core import CORE, coroutine, coroutine_with_priority
from esphome.py_compat import string_types
from esphome.util import Registry from esphome.util import Registry
DEVICE_CLASSES = [ DEVICE_CLASSES = [
@ -94,7 +93,7 @@ MULTI_CLICK_TIMING_SCHEMA = cv.Schema({
def parse_multi_click_timing_str(value): def parse_multi_click_timing_str(value):
if not isinstance(value, string_types): if not isinstance(value, str):
return value return value
parts = value.lower().split(' ') parts = value.lower().split(' ')
@ -104,10 +103,10 @@ def parse_multi_click_timing_str(value):
try: try:
state = cv.boolean(parts[0]) state = cv.boolean(parts[0])
except cv.Invalid: except cv.Invalid:
raise cv.Invalid(u"First word must either be ON or OFF, not {}".format(parts[0])) raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0]))
if parts[1] != 'for': if parts[1] != 'for':
raise cv.Invalid(u"Second word must be 'for', got {}".format(parts[1])) raise cv.Invalid("Second word must be 'for', got {}".format(parts[1]))
if parts[2] == 'at': if parts[2] == 'at':
if parts[3] == 'least': if parts[3] == 'least':
@ -115,12 +114,12 @@ def parse_multi_click_timing_str(value):
elif parts[3] == 'most': elif parts[3] == 'most':
key = CONF_MAX_LENGTH key = CONF_MAX_LENGTH
else: else:
raise cv.Invalid(u"Third word after at must either be 'least' or 'most', got {}" raise cv.Invalid("Third word after at must either be 'least' or 'most', got {}"
u"".format(parts[3])) "".format(parts[3]))
try: try:
length = cv.positive_time_period_milliseconds(parts[4]) length = cv.positive_time_period_milliseconds(parts[4])
except cv.Invalid as err: except cv.Invalid as err:
raise cv.Invalid(u"Multi Click Grammar Parsing length failed: {}".format(err)) raise cv.Invalid(f"Multi Click Grammar Parsing length failed: {err}")
return { return {
CONF_STATE: state, CONF_STATE: state,
key: str(length) key: str(length)
@ -132,12 +131,12 @@ def parse_multi_click_timing_str(value):
try: try:
min_length = cv.positive_time_period_milliseconds(parts[2]) min_length = cv.positive_time_period_milliseconds(parts[2])
except cv.Invalid as err: except cv.Invalid as err:
raise cv.Invalid(u"Multi Click Grammar Parsing minimum length failed: {}".format(err)) raise cv.Invalid(f"Multi Click Grammar Parsing minimum length failed: {err}")
try: try:
max_length = cv.positive_time_period_milliseconds(parts[4]) max_length = cv.positive_time_period_milliseconds(parts[4])
except cv.Invalid as err: except cv.Invalid as err:
raise cv.Invalid(u"Multi Click Grammar Parsing minimum length failed: {}".format(err)) raise cv.Invalid(f"Multi Click Grammar Parsing minimum length failed: {err}")
return { return {
CONF_STATE: state, CONF_STATE: state,

View file

@ -8,8 +8,8 @@ from esphome.const import CONF_ID, CONF_MODE, CONF_NUMBER, CONF_PINS, CONF_RUN_C
def validate_pin_number(value): def validate_pin_number(value):
valid_pins = [0, 2, 4, 12, 13, 14, 15, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39] valid_pins = [0, 2, 4, 12, 13, 14, 15, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39]
if value[CONF_NUMBER] not in valid_pins: if value[CONF_NUMBER] not in valid_pins:
raise cv.Invalid(u"Only pins {} support wakeup" raise cv.Invalid("Only pins {} support wakeup"
u"".format(', '.join(str(x) for x in valid_pins))) "".format(', '.join(str(x) for x in valid_pins)))
return value return value

View file

@ -1,4 +1,3 @@
# coding=utf-8
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import core, automation from esphome import core, automation
@ -27,7 +26,7 @@ DISPLAY_ROTATIONS = {
def validate_rotation(value): def validate_rotation(value):
value = cv.string(value) value = cv.string(value)
if value.endswith(u"°"): if value.endswith("°"):
value = value[:-1] value = value[:-1]
return cv.enum(DISPLAY_ROTATIONS, int=True)(value) return cv.enum(DISPLAY_ROTATIONS, int=True)(value)

View file

@ -46,33 +46,33 @@ def bt_uuid(value):
pattern = re.compile("^[A-F|0-9]{4,}$") pattern = re.compile("^[A-F|0-9]{4,}$")
if not pattern.match(value): if not pattern.match(value):
raise cv.Invalid( raise cv.Invalid(
u"Invalid hexadecimal value for 16 bit UUID format: '{}'".format(in_value)) f"Invalid hexadecimal value for 16 bit UUID format: '{in_value}'")
return value return value
if len(value) == len(bt_uuid32_format): if len(value) == len(bt_uuid32_format):
pattern = re.compile("^[A-F|0-9]{8,}$") pattern = re.compile("^[A-F|0-9]{8,}$")
if not pattern.match(value): if not pattern.match(value):
raise cv.Invalid( raise cv.Invalid(
u"Invalid hexadecimal value for 32 bit UUID format: '{}'".format(in_value)) f"Invalid hexadecimal value for 32 bit UUID format: '{in_value}'")
return value return value
if len(value) == len(bt_uuid128_format): if len(value) == len(bt_uuid128_format):
pattern = re.compile( pattern = re.compile(
"^[A-F|0-9]{8,}-[A-F|0-9]{4,}-[A-F|0-9]{4,}-[A-F|0-9]{4,}-[A-F|0-9]{12,}$") "^[A-F|0-9]{8,}-[A-F|0-9]{4,}-[A-F|0-9]{4,}-[A-F|0-9]{4,}-[A-F|0-9]{12,}$")
if not pattern.match(value): if not pattern.match(value):
raise cv.Invalid( raise cv.Invalid(
u"Invalid hexadecimal value for 128 UUID format: '{}'".format(in_value)) f"Invalid hexadecimal value for 128 UUID format: '{in_value}'")
return value return value
raise cv.Invalid( raise cv.Invalid(
u"Service UUID must be in 16 bit '{}', 32 bit '{}', or 128 bit '{}' format".format( "Service UUID must be in 16 bit '{}', 32 bit '{}', or 128 bit '{}' format".format(
bt_uuid16_format, bt_uuid32_format, bt_uuid128_format)) bt_uuid16_format, bt_uuid32_format, bt_uuid128_format))
def as_hex(value): def as_hex(value):
return cg.RawExpression('0x{}ULL'.format(value)) return cg.RawExpression(f'0x{value}ULL')
def as_hex_array(value): def as_hex_array(value):
value = value.replace("-", "") value = value.replace("-", "")
cpp_array = ['0x{}'.format(part) for part in [value[i:i+2] for i in range(0, len(value), 2)]] cpp_array = [f'0x{part}' for part in [value[i:i+2] for i in range(0, len(value), 2)]]
return cg.RawExpression( return cg.RawExpression(
'(uint8_t*)(const uint8_t[16]){{{}}}'.format(','.join(reversed(cpp_array)))) '(uint8_t*)(const uint8_t[16]){{{}}}'.format(','.join(reversed(cpp_array))))

View file

@ -27,7 +27,7 @@ TOUCH_PADS = {
def validate_touch_pad(value): def validate_touch_pad(value):
value = validate_gpio_pin(value) value = validate_gpio_pin(value)
if value not in TOUCH_PADS: if value not in TOUCH_PADS:
raise cv.Invalid("Pin {} does not support touch pads.".format(value)) raise cv.Invalid(f"Pin {value} does not support touch pads.")
return value return value

View file

@ -1,11 +1,11 @@
# coding=utf-8 import functools
from esphome import core from esphome import core
from esphome.components import display from esphome.components import display
import esphome.config_validation as cv import esphome.config_validation as cv
import esphome.codegen as cg import esphome.codegen as cg
from esphome.const import CONF_FILE, CONF_GLYPHS, CONF_ID, CONF_SIZE from esphome.const import CONF_FILE, CONF_GLYPHS, CONF_ID, CONF_SIZE
from esphome.core import CORE, HexInt from esphome.core import CORE, HexInt
from esphome.py_compat import sort_by_cmp
DEPENDENCIES = ['display'] DEPENDENCIES = ['display']
MULTI_CONF = True MULTI_CONF = True
@ -33,9 +33,9 @@ def validate_glyphs(value):
return -1 return -1
if len(x_) > len(y_): if len(x_) > len(y_):
return 1 return 1
raise cv.Invalid(u"Found duplicate glyph {}".format(x)) raise cv.Invalid(f"Found duplicate glyph {x}")
sort_by_cmp(value, comparator) value.sort(key=functools.cmp_to_key(comparator))
return value return value
@ -55,15 +55,15 @@ def validate_pillow_installed(value):
def validate_truetype_file(value): def validate_truetype_file(value):
if value.endswith('.zip'): # for Google Fonts downloads if value.endswith('.zip'): # for Google Fonts downloads
raise cv.Invalid(u"Please unzip the font archive '{}' first and then use the .ttf files " raise cv.Invalid("Please unzip the font archive '{}' first and then use the .ttf files "
u"inside.".format(value)) "inside.".format(value))
if not value.endswith('.ttf'): if not value.endswith('.ttf'):
raise cv.Invalid(u"Only truetype (.ttf) files are supported. Please make sure you're " raise cv.Invalid("Only truetype (.ttf) files are supported. Please make sure you're "
u"using the correct format or rename the extension to .ttf") "using the correct format or rename the extension to .ttf")
return cv.file_(value) return cv.file_(value)
DEFAULT_GLYPHS = u' !"%()+,-.:0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' DEFAULT_GLYPHS = ' !"%()+,-.:0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
CONF_RAW_DATA_ID = 'raw_data_id' CONF_RAW_DATA_ID = 'raw_data_id'
FONT_SCHEMA = cv.Schema({ FONT_SCHEMA = cv.Schema({
@ -84,7 +84,7 @@ def to_code(config):
try: try:
font = ImageFont.truetype(path, config[CONF_SIZE]) font = ImageFont.truetype(path, config[CONF_SIZE])
except Exception as e: except Exception as e:
raise core.EsphomeError(u"Could not load truetype file {}: {}".format(path, e)) raise core.EsphomeError(f"Could not load truetype file {path}: {e}")
ascent, descent = font.getmetrics() ascent, descent = font.getmetrics()

View file

@ -4,7 +4,6 @@ from esphome import config_validation as cv, automation
from esphome import codegen as cg from esphome import codegen as cg
from esphome.const import CONF_ID, CONF_INITIAL_VALUE, CONF_RESTORE_VALUE, CONF_TYPE, CONF_VALUE from esphome.const import CONF_ID, CONF_INITIAL_VALUE, CONF_RESTORE_VALUE, CONF_TYPE, CONF_VALUE
from esphome.core import coroutine_with_priority from esphome.core import coroutine_with_priority
from esphome.py_compat import IS_PY3
globals_ns = cg.esphome_ns.namespace('globals') globals_ns = cg.esphome_ns.namespace('globals')
GlobalsComponent = globals_ns.class_('GlobalsComponent', cg.Component) GlobalsComponent = globals_ns.class_('GlobalsComponent', cg.Component)
@ -36,7 +35,7 @@ def to_code(config):
if config[CONF_RESTORE_VALUE]: if config[CONF_RESTORE_VALUE]:
value = config[CONF_ID].id value = config[CONF_ID].id
if IS_PY3 and isinstance(value, str): if isinstance(value, str):
value = value.encode() value = value.encode()
hash_ = int(hashlib.md5(value).hexdigest()[:8], 16) hash_ = int(hashlib.md5(value).hexdigest()[:8], 16)
cg.add(glob.set_restore_value(hash_)) cg.add(glob.set_restore_value(hash_))

View file

@ -1,11 +1,9 @@
# coding=utf-8
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import i2c, sensor from esphome.components import i2c, sensor
from esphome.const import (CONF_ADDRESS, CONF_ID, CONF_OVERSAMPLING, CONF_RANGE, ICON_MAGNET, from esphome.const import (CONF_ADDRESS, CONF_ID, CONF_OVERSAMPLING, CONF_RANGE, ICON_MAGNET,
UNIT_MICROTESLA, UNIT_DEGREES, ICON_SCREEN_ROTATION, UNIT_MICROTESLA, UNIT_DEGREES, ICON_SCREEN_ROTATION,
CONF_UPDATE_INTERVAL) CONF_UPDATE_INTERVAL)
from esphome.py_compat import text_type
DEPENDENCIES = ['i2c'] DEPENDENCIES = ['i2c']
@ -54,7 +52,7 @@ def validate_enum(enum_values, units=None, int=True):
_units = [] _units = []
if units is not None: if units is not None:
_units = units if isinstance(units, list) else [units] _units = units if isinstance(units, list) else [units]
_units = [text_type(x) for x in _units] _units = [str(x) for x in _units]
enum_bound = cv.enum(enum_values, int=int) enum_bound = cv.enum(enum_values, int=int)
def validate_enum_bound(value): def validate_enum_bound(value):
@ -74,7 +72,7 @@ CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(HMC5883LComponent), cv.GenerateID(): cv.declare_id(HMC5883LComponent),
cv.Optional(CONF_ADDRESS): cv.i2c_address, cv.Optional(CONF_ADDRESS): cv.i2c_address,
cv.Optional(CONF_OVERSAMPLING, default='1x'): validate_enum(HMC5883LOversamplings, units="x"), cv.Optional(CONF_OVERSAMPLING, default='1x'): validate_enum(HMC5883LOversamplings, units="x"),
cv.Optional(CONF_RANGE, default=u'130µT'): validate_enum(HMC5883L_RANGES, units=["uT", u"µT"]), cv.Optional(CONF_RANGE, default='130µT'): validate_enum(HMC5883L_RANGES, units=["uT", "µT"]),
cv.Optional(CONF_FIELD_STRENGTH_X): field_strength_schema, cv.Optional(CONF_FIELD_STRENGTH_X): field_strength_schema,
cv.Optional(CONF_FIELD_STRENGTH_Y): field_strength_schema, cv.Optional(CONF_FIELD_STRENGTH_Y): field_strength_schema,
cv.Optional(CONF_FIELD_STRENGTH_Z): field_strength_schema, cv.Optional(CONF_FIELD_STRENGTH_Z): field_strength_schema,

View file

@ -1,3 +1,5 @@
import urllib.parse as urlparse
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import automation from esphome import automation
@ -5,12 +7,6 @@ from esphome.const import CONF_ID, CONF_TIMEOUT, CONF_ESPHOME, CONF_METHOD, \
CONF_ARDUINO_VERSION, ARDUINO_VERSION_ESP8266_2_5_0 CONF_ARDUINO_VERSION, ARDUINO_VERSION_ESP8266_2_5_0
from esphome.core import CORE, Lambda from esphome.core import CORE, Lambda
from esphome.core_config import PLATFORMIO_ESP8266_LUT from esphome.core_config import PLATFORMIO_ESP8266_LUT
from esphome.py_compat import IS_PY3
if IS_PY3:
import urllib.parse as urlparse # pylint: disable=no-name-in-module,import-error
else:
import urlparse # pylint: disable=import-error
DEPENDENCIES = ['network'] DEPENDENCIES = ['network']
AUTO_LOAD = ['json'] AUTO_LOAD = ['json']

View file

@ -1,4 +1,3 @@
# coding=utf-8
import logging import logging
from esphome import core from esphome import core
@ -33,7 +32,7 @@ def to_code(config):
try: try:
image = Image.open(path) image = Image.open(path)
except Exception as e: except Exception as e:
raise core.EsphomeError(u"Could not load image file {}: {}".format(path, e)) raise core.EsphomeError(f"Could not load image file {path}: {e}")
if CONF_RESIZE in config: if CONF_RESIZE in config:
image.thumbnail(config[CONF_RESIZE]) image.thumbnail(config[CONF_RESIZE])

View file

@ -1,4 +1,3 @@
# coding=utf-8
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import i2c, sensor from esphome.components import i2c, sensor

View file

@ -1,4 +1,3 @@
# coding=utf-8
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import i2c, sensor from esphome.components import i2c, sensor

View file

@ -1,4 +1,3 @@
# coding=utf-8
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import i2c, sensor from esphome.components import i2c, sensor

View file

@ -253,8 +253,8 @@ def validate_effects(allowed_effects):
name = x[key][CONF_NAME] name = x[key][CONF_NAME]
if name in names: if name in names:
errors.append( errors.append(
cv.Invalid(u"Found the effect name '{}' twice. All effects must have " cv.Invalid("Found the effect name '{}' twice. All effects must have "
u"unique names".format(name), [i]) "unique names".format(name), [i])
) )
continue continue
names.add(name) names.add(name)

View file

@ -7,7 +7,6 @@ from esphome.automation import LambdaAction
from esphome.const import CONF_ARGS, CONF_BAUD_RATE, CONF_FORMAT, CONF_HARDWARE_UART, CONF_ID, \ from esphome.const import CONF_ARGS, CONF_BAUD_RATE, CONF_FORMAT, CONF_HARDWARE_UART, CONF_ID, \
CONF_LEVEL, CONF_LOGS, CONF_ON_MESSAGE, CONF_TAG, CONF_TRIGGER_ID, CONF_TX_BUFFER_SIZE CONF_LEVEL, CONF_LOGS, CONF_ON_MESSAGE, CONF_TAG, CONF_TRIGGER_ID, CONF_TX_BUFFER_SIZE
from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority
from esphome.py_compat import text_type
logger_ns = cg.esphome_ns.namespace('logger') logger_ns = cg.esphome_ns.namespace('logger')
LOG_LEVELS = { LOG_LEVELS = {
@ -64,8 +63,8 @@ def validate_local_no_higher_than_global(value):
global_level = value.get(CONF_LEVEL, 'DEBUG') global_level = value.get(CONF_LEVEL, 'DEBUG')
for tag, level in value.get(CONF_LOGS, {}).items(): for tag, level in value.get(CONF_LOGS, {}).items():
if LOG_LEVEL_SEVERITY.index(level) > LOG_LEVEL_SEVERITY.index(global_level): if LOG_LEVEL_SEVERITY.index(level) > LOG_LEVEL_SEVERITY.index(global_level):
raise EsphomeError(u"The local log level {} for {} must be less severe than the " raise EsphomeError("The local log level {} for {} must be less severe than the "
u"global log level {}.".format(level, tag, global_level)) "global log level {}.".format(level, tag, global_level))
return value return value
@ -119,7 +118,7 @@ def to_code(config):
if CORE.is_esp8266 and has_serial_logging and is_at_least_verbose: if CORE.is_esp8266 and has_serial_logging and is_at_least_verbose:
debug_serial_port = HARDWARE_UART_TO_SERIAL[config.get(CONF_HARDWARE_UART)] debug_serial_port = HARDWARE_UART_TO_SERIAL[config.get(CONF_HARDWARE_UART)]
cg.add_build_flag("-DDEBUG_ESP_PORT={}".format(debug_serial_port)) cg.add_build_flag(f"-DDEBUG_ESP_PORT={debug_serial_port}")
cg.add_build_flag("-DLWIP_DEBUG") cg.add_build_flag("-DLWIP_DEBUG")
DEBUG_COMPONENTS = { DEBUG_COMPONENTS = {
'HTTP_CLIENT', 'HTTP_CLIENT',
@ -134,7 +133,7 @@ def to_code(config):
# 'MDNS_RESPONDER', # 'MDNS_RESPONDER',
} }
for comp in DEBUG_COMPONENTS: for comp in DEBUG_COMPONENTS:
cg.add_build_flag("-DDEBUG_ESP_{}".format(comp)) cg.add_build_flag(f"-DDEBUG_ESP_{comp}")
if CORE.is_esp32 and is_at_least_verbose: if CORE.is_esp32 and is_at_least_verbose:
cg.add_build_flag('-DCORE_DEBUG_LEVEL=5') cg.add_build_flag('-DCORE_DEBUG_LEVEL=5')
if CORE.is_esp32 and is_at_least_very_verbose: if CORE.is_esp32 and is_at_least_very_verbose:
@ -165,7 +164,7 @@ def maybe_simple_message(schema):
def validate_printf(value): def validate_printf(value):
# https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python # https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python
# pylint: disable=anomalous-backslash-in-string # pylint: disable=anomalous-backslash-in-string
cfmt = u"""\ cfmt = """\
( # start of capture group 1 ( # start of capture group 1
% # literal "%" % # literal "%"
(?: # first option (?: # first option
@ -179,8 +178,8 @@ def validate_printf(value):
""" # noqa """ # noqa
matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.X) matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.X)
if len(matches) != len(value[CONF_ARGS]): if len(matches) != len(value[CONF_ARGS]):
raise cv.Invalid(u"Found {} printf-patterns ({}), but {} args were given!" raise cv.Invalid("Found {} printf-patterns ({}), but {} args were given!"
u"".format(len(matches), u', '.join(matches), len(value[CONF_ARGS]))) "".format(len(matches), ', '.join(matches), len(value[CONF_ARGS])))
return value return value
@ -196,9 +195,9 @@ LOGGER_LOG_ACTION_SCHEMA = cv.All(maybe_simple_message({
@automation.register_action(CONF_LOGGER_LOG, LambdaAction, LOGGER_LOG_ACTION_SCHEMA) @automation.register_action(CONF_LOGGER_LOG, LambdaAction, LOGGER_LOG_ACTION_SCHEMA)
def logger_log_action_to_code(config, action_id, template_arg, args): def logger_log_action_to_code(config, action_id, template_arg, args):
esp_log = LOG_LEVEL_TO_ESP_LOG[config[CONF_LEVEL]] esp_log = LOG_LEVEL_TO_ESP_LOG[config[CONF_LEVEL]]
args_ = [cg.RawExpression(text_type(x)) for x in config[CONF_ARGS]] args_ = [cg.RawExpression(str(x)) for x in config[CONF_ARGS]]
text = text_type(cg.statement(esp_log(config[CONF_TAG], config[CONF_FORMAT], *args_))) text = str(cg.statement(esp_log(config[CONF_TAG], config[CONF_FORMAT], *args_)))
lambda_ = yield cg.process_lambda(Lambda(text), args, return_type=cg.void) lambda_ = yield cg.process_lambda(Lambda(text), args, return_type=cg.void)
yield cg.new_Pvariable(action_id, template_arg, lambda_) yield cg.new_Pvariable(action_id, template_arg, lambda_)

View file

@ -39,14 +39,14 @@ def to_code(config):
yield i2c.register_i2c_device(var, config) yield i2c.register_i2c_device(var, config)
for d in ['x', 'y', 'z']: for d in ['x', 'y', 'z']:
accel_key = 'accel_{}'.format(d) accel_key = f'accel_{d}'
if accel_key in config: if accel_key in config:
sens = yield sensor.new_sensor(config[accel_key]) sens = yield sensor.new_sensor(config[accel_key])
cg.add(getattr(var, 'set_accel_{}_sensor'.format(d))(sens)) cg.add(getattr(var, f'set_accel_{d}_sensor')(sens))
accel_key = 'gyro_{}'.format(d) accel_key = f'gyro_{d}'
if accel_key in config: if accel_key in config:
sens = yield sensor.new_sensor(config[accel_key]) sens = yield sensor.new_sensor(config[accel_key])
cg.add(getattr(var, 'set_gyro_{}_sensor'.format(d))(sens)) cg.add(getattr(var, f'set_gyro_{d}_sensor')(sens))
if CONF_TEMPERATURE in config: if CONF_TEMPERATURE in config:
sens = yield sensor.new_sensor(config[CONF_TEMPERATURE]) sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])

View file

@ -64,28 +64,28 @@ def validate_config(value):
topic_prefix = value[CONF_TOPIC_PREFIX] topic_prefix = value[CONF_TOPIC_PREFIX]
if CONF_BIRTH_MESSAGE not in value: if CONF_BIRTH_MESSAGE not in value:
out[CONF_BIRTH_MESSAGE] = { out[CONF_BIRTH_MESSAGE] = {
CONF_TOPIC: '{}/status'.format(topic_prefix), CONF_TOPIC: f'{topic_prefix}/status',
CONF_PAYLOAD: 'online', CONF_PAYLOAD: 'online',
CONF_QOS: 0, CONF_QOS: 0,
CONF_RETAIN: True, CONF_RETAIN: True,
} }
if CONF_WILL_MESSAGE not in value: if CONF_WILL_MESSAGE not in value:
out[CONF_WILL_MESSAGE] = { out[CONF_WILL_MESSAGE] = {
CONF_TOPIC: '{}/status'.format(topic_prefix), CONF_TOPIC: f'{topic_prefix}/status',
CONF_PAYLOAD: 'offline', CONF_PAYLOAD: 'offline',
CONF_QOS: 0, CONF_QOS: 0,
CONF_RETAIN: True, CONF_RETAIN: True,
} }
if CONF_SHUTDOWN_MESSAGE not in value: if CONF_SHUTDOWN_MESSAGE not in value:
out[CONF_SHUTDOWN_MESSAGE] = { out[CONF_SHUTDOWN_MESSAGE] = {
CONF_TOPIC: '{}/status'.format(topic_prefix), CONF_TOPIC: f'{topic_prefix}/status',
CONF_PAYLOAD: 'offline', CONF_PAYLOAD: 'offline',
CONF_QOS: 0, CONF_QOS: 0,
CONF_RETAIN: True, CONF_RETAIN: True,
} }
if CONF_LOG_TOPIC not in value: if CONF_LOG_TOPIC not in value:
out[CONF_LOG_TOPIC] = { out[CONF_LOG_TOPIC] = {
CONF_TOPIC: '{}/debug'.format(topic_prefix), CONF_TOPIC: f'{topic_prefix}/debug',
CONF_QOS: 0, CONF_QOS: 0,
CONF_RETAIN: True, CONF_RETAIN: True,
} }
@ -95,7 +95,7 @@ def validate_config(value):
def validate_fingerprint(value): def validate_fingerprint(value):
value = cv.string(value) value = cv.string(value)
if re.match(r'^[0-9a-f]{40}$', value) is None: if re.match(r'^[0-9a-f]{40}$', value) is None:
raise cv.Invalid(u"fingerprint must be valid SHA1 hash") raise cv.Invalid("fingerprint must be valid SHA1 hash")
return value return value

View file

@ -82,7 +82,7 @@ def validate_method_pin(value):
for opt in (CONF_PIN, CONF_CLOCK_PIN, CONF_DATA_PIN): for opt in (CONF_PIN, CONF_CLOCK_PIN, CONF_DATA_PIN):
if opt in value and value[opt] not in pins_: if opt in value and value[opt] not in pins_:
raise cv.Invalid("Method {} only supports pin(s) {}".format( raise cv.Invalid("Method {} only supports pin(s) {}".format(
method, ', '.join('GPIO{}'.format(x) for x in pins_) method, ', '.join(f'GPIO{x}' for x in pins_)
), path=[CONF_METHOD]) ), path=[CONF_METHOD])
return value return value

View file

@ -1,4 +1,3 @@
# coding=utf-8
from math import log from math import log
import esphome.config_validation as cv import esphome.config_validation as cv
@ -28,7 +27,7 @@ def validate_calibration_parameter(value):
value = cv.string(value) value = cv.string(value)
parts = value.split('->') parts = value.split('->')
if len(parts) != 2: if len(parts) != 2:
raise cv.Invalid(u"Calibration parameter must be of form 3000 -> 23°C") raise cv.Invalid("Calibration parameter must be of form 3000 -> 23°C")
voltage = cv.resistance(parts[0].strip()) voltage = cv.resistance(parts[0].strip())
temperature = cv.temperature(parts[1].strip()) temperature = cv.temperature(parts[1].strip())
return validate_calibration_parameter({ return validate_calibration_parameter({

View file

@ -10,8 +10,8 @@ PartitionLightOutput = partitions_ns.class_('PartitionLightOutput', light.Addres
def validate_from_to(value): def validate_from_to(value):
if value[CONF_FROM] > value[CONF_TO]: if value[CONF_FROM] > value[CONF_TO]:
raise cv.Invalid(u"From ({}) must not be larger than to ({})" raise cv.Invalid("From ({}) must not be larger than to ({})"
u"".format(value[CONF_FROM], value[CONF_TO])) "".format(value[CONF_FROM], value[CONF_TO]))
return value return value

View file

@ -36,7 +36,7 @@ SENSORS_TO_TYPE = {
def validate_pmsx003_sensors(value): def validate_pmsx003_sensors(value):
for key, types in SENSORS_TO_TYPE.items(): for key, types in SENSORS_TO_TYPE.items():
if key in value and value[CONF_TYPE] not in types: if key in value and value[CONF_TYPE] not in types:
raise cv.Invalid(u"{} does not have {} sensor!".format(value[CONF_TYPE], key)) raise cv.Invalid("{} does not have {} sensor!".format(value[CONF_TYPE], key))
return value return value

View file

@ -1,11 +1,9 @@
# coding=utf-8
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import i2c, sensor from esphome.components import i2c, sensor
from esphome.const import (CONF_ADDRESS, CONF_ID, CONF_OVERSAMPLING, CONF_RANGE, ICON_MAGNET, from esphome.const import (CONF_ADDRESS, CONF_ID, CONF_OVERSAMPLING, CONF_RANGE, ICON_MAGNET,
UNIT_MICROTESLA, UNIT_DEGREES, ICON_SCREEN_ROTATION, UNIT_MICROTESLA, UNIT_DEGREES, ICON_SCREEN_ROTATION,
CONF_UPDATE_INTERVAL) CONF_UPDATE_INTERVAL)
from esphome.py_compat import text_type
DEPENDENCIES = ['i2c'] DEPENDENCIES = ['i2c']
@ -46,7 +44,7 @@ def validate_enum(enum_values, units=None, int=True):
_units = [] _units = []
if units is not None: if units is not None:
_units = units if isinstance(units, list) else [units] _units = units if isinstance(units, list) else [units]
_units = [text_type(x) for x in _units] _units = [str(x) for x in _units]
enum_bound = cv.enum(enum_values, int=int) enum_bound = cv.enum(enum_values, int=int)
def validate_enum_bound(value): def validate_enum_bound(value):
@ -65,7 +63,7 @@ heading_schema = sensor.sensor_schema(UNIT_DEGREES, ICON_SCREEN_ROTATION, 1)
CONFIG_SCHEMA = cv.Schema({ CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(QMC5883LComponent), cv.GenerateID(): cv.declare_id(QMC5883LComponent),
cv.Optional(CONF_ADDRESS): cv.i2c_address, cv.Optional(CONF_ADDRESS): cv.i2c_address,
cv.Optional(CONF_RANGE, default=u'200µT'): validate_enum(QMC5883L_RANGES, units=["uT", u"µT"]), cv.Optional(CONF_RANGE, default='200µT'): validate_enum(QMC5883L_RANGES, units=["uT", "µT"]),
cv.Optional(CONF_OVERSAMPLING, default="512x"): validate_enum(QMC5883LOversamplings, units="x"), cv.Optional(CONF_OVERSAMPLING, default="512x"): validate_enum(QMC5883LOversamplings, units="x"),
cv.Optional(CONF_FIELD_STRENGTH_X): field_strength_schema, cv.Optional(CONF_FIELD_STRENGTH_X): field_strength_schema,
cv.Optional(CONF_FIELD_STRENGTH_Y): field_strength_schema, cv.Optional(CONF_FIELD_STRENGTH_Y): field_strength_schema,

View file

@ -7,7 +7,6 @@ from esphome.const import CONF_DATA, CONF_TRIGGER_ID, CONF_NBITS, CONF_ADDRESS,
CONF_PROTOCOL, CONF_GROUP, CONF_DEVICE, CONF_STATE, CONF_CHANNEL, CONF_FAMILY, CONF_REPEAT, \ CONF_PROTOCOL, CONF_GROUP, CONF_DEVICE, CONF_STATE, CONF_CHANNEL, CONF_FAMILY, CONF_REPEAT, \
CONF_WAIT_TIME, CONF_TIMES, CONF_TYPE_ID, CONF_CARRIER_FREQUENCY CONF_WAIT_TIME, CONF_TIMES, CONF_TYPE_ID, CONF_CARRIER_FREQUENCY
from esphome.core import coroutine from esphome.core import coroutine
from esphome.py_compat import string_types, text_type
from esphome.util import Registry, SimpleRegistry from esphome.util import Registry, SimpleRegistry
AUTO_LOAD = ['binary_sensor'] AUTO_LOAD = ['binary_sensor']
@ -52,7 +51,7 @@ def register_trigger(name, type, data_type):
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(type), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(type),
cv.GenerateID(CONF_RECEIVER_ID): cv.use_id(RemoteReceiverBase), cv.GenerateID(CONF_RECEIVER_ID): cv.use_id(RemoteReceiverBase),
}) })
registerer = TRIGGER_REGISTRY.register('on_{}'.format(name), validator) registerer = TRIGGER_REGISTRY.register(f'on_{name}', validator)
def decorator(func): def decorator(func):
@coroutine @coroutine
@ -98,7 +97,7 @@ def register_action(name, type_, schema):
cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(RemoteTransmitterBase), cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(RemoteTransmitterBase),
cv.Optional(CONF_REPEAT): validate_repeat, cv.Optional(CONF_REPEAT): validate_repeat,
}) })
registerer = automation.register_action('remote_transmitter.transmit_{}'.format(name), registerer = automation.register_action(f'remote_transmitter.transmit_{name}',
type_, validator) type_, validator)
def decorator(func): def decorator(func):
@ -122,11 +121,11 @@ def register_action(name, type_, schema):
def declare_protocol(name): def declare_protocol(name):
data = ns.struct('{}Data'.format(name)) data = ns.struct(f'{name}Data')
binary_sensor_ = ns.class_('{}BinarySensor'.format(name), RemoteReceiverBinarySensorBase) binary_sensor_ = ns.class_(f'{name}BinarySensor', RemoteReceiverBinarySensorBase)
trigger = ns.class_('{}Trigger'.format(name), RemoteReceiverTrigger) trigger = ns.class_(f'{name}Trigger', RemoteReceiverTrigger)
action = ns.class_('{}Action'.format(name), RemoteTransmitterActionBase) action = ns.class_(f'{name}Action', RemoteTransmitterActionBase)
dumper = ns.class_('{}Dumper'.format(name), RemoteTransmitterDumper) dumper = ns.class_(f'{name}Dumper', RemoteTransmitterDumper)
return data, binary_sensor_, trigger, action, dumper return data, binary_sensor_, trigger, action, dumper
@ -141,7 +140,7 @@ DUMPER_REGISTRY = Registry({
def validate_dumpers(value): def validate_dumpers(value):
if isinstance(value, string_types) and value.lower() == 'all': if isinstance(value, str) and value.lower() == 'all':
return validate_dumpers(list(DUMPER_REGISTRY.keys())) return validate_dumpers(list(DUMPER_REGISTRY.keys()))
return cv.validate_registry('dumper', DUMPER_REGISTRY)(value) return cv.validate_registry('dumper', DUMPER_REGISTRY)(value)
@ -432,12 +431,12 @@ RC_SWITCH_PROTOCOL_SCHEMA = cv.Any(
def validate_rc_switch_code(value): def validate_rc_switch_code(value):
if not isinstance(value, (str, text_type)): if not isinstance(value, (str, str)):
raise cv.Invalid("All RCSwitch codes must be in quotes ('')") raise cv.Invalid("All RCSwitch codes must be in quotes ('')")
for c in value: for c in value:
if c not in ('0', '1'): if c not in ('0', '1'):
raise cv.Invalid(u"Invalid RCSwitch code character '{}'. Only '0' and '1' are allowed" raise cv.Invalid("Invalid RCSwitch code character '{}'. Only '0' and '1' are allowed"
u"".format(c)) "".format(c))
if len(value) > 64: if len(value) > 64:
raise cv.Invalid("Maximum length for RCSwitch codes is 64, code '{}' has length {}" raise cv.Invalid("Maximum length for RCSwitch codes is 64, code '{}' has length {}"
"".format(value, len(value))) "".format(value, len(value)))
@ -447,7 +446,7 @@ def validate_rc_switch_code(value):
def validate_rc_switch_raw_code(value): def validate_rc_switch_raw_code(value):
if not isinstance(value, (str, text_type)): if not isinstance(value, (str, str)):
raise cv.Invalid("All RCSwitch raw codes must be in quotes ('')") raise cv.Invalid("All RCSwitch raw codes must be in quotes ('')")
for c in value: for c in value:
if c not in ('0', '1', 'x'): if c not in ('0', '1', 'x'):

View file

@ -19,12 +19,12 @@ def show_new(value):
if 'name' in value: if 'name' in value:
args.append(('name', value['name'])) args.append(('name', value['name']))
args.append(('turn_on_action', { args.append(('turn_on_action', {
'remote_transmitter.transmit_{}'.format(key): val f'remote_transmitter.transmit_{key}': val
})) }))
text = yaml_util.dump([OrderedDict(args)]) text = yaml_util.dump([OrderedDict(args)])
raise cv.Invalid(u"This platform has been removed in 1.13, please change to:\n\n{}\n\n." raise cv.Invalid("This platform has been removed in 1.13, please change to:\n\n{}\n\n."
u"".format(text)) "".format(text))
CONFIG_SCHEMA = show_new CONFIG_SCHEMA = show_new

View file

@ -28,7 +28,7 @@ def validate_acceleration(value):
try: try:
value = float(value) value = float(value)
except ValueError: except ValueError:
raise cv.Invalid("Expected acceleration as floating point number, got {}".format(value)) raise cv.Invalid(f"Expected acceleration as floating point number, got {value}")
if value <= 0: if value <= 0:
raise cv.Invalid("Acceleration must be larger than 0 steps/s^2!") raise cv.Invalid("Acceleration must be larger than 0 steps/s^2!")
@ -48,7 +48,7 @@ def validate_speed(value):
try: try:
value = float(value) value = float(value)
except ValueError: except ValueError:
raise cv.Invalid("Expected speed as floating point number, got {}".format(value)) raise cv.Invalid(f"Expected speed as floating point number, got {value}")
if value <= 0: if value <= 0:
raise cv.Invalid("Speed must be larger than 0 steps/s!") raise cv.Invalid("Speed must be larger than 0 steps/s!")

View file

@ -3,7 +3,6 @@ import re
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import core from esphome import core
from esphome.py_compat import string_types
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -24,8 +23,8 @@ def validate_substitution_key(value):
for char in value: for char in value:
if char not in VALID_SUBSTITUTIONS_CHARACTERS: if char not in VALID_SUBSTITUTIONS_CHARACTERS:
raise cv.Invalid( raise cv.Invalid(
u"Substitution must only consist of upper/lowercase characters, the underscore " "Substitution must only consist of upper/lowercase characters, the underscore "
u"and numbers. The character '{}' cannot be used".format(char)) "and numbers. The character '{}' cannot be used".format(char))
return value return value
@ -42,7 +41,7 @@ VARIABLE_PROG = re.compile('\\$([{0}]+|\\{{[{0}]*\\}})'.format(VALID_SUBSTITUTIO
def _expand_substitutions(substitutions, value, path): def _expand_substitutions(substitutions, value, path):
if u'$' not in value: if '$' not in value:
return value return value
orig_value = value orig_value = value
@ -56,11 +55,11 @@ def _expand_substitutions(substitutions, value, path):
i, j = m.span(0) i, j = m.span(0)
name = m.group(1) name = m.group(1)
if name.startswith(u'{') and name.endswith(u'}'): if name.startswith('{') and name.endswith('}'):
name = name[1:-1] name = name[1:-1]
if name not in substitutions: if name not in substitutions:
_LOGGER.warning(u"Found '%s' (see %s) which looks like a substitution, but '%s' was " _LOGGER.warning("Found '%s' (see %s) which looks like a substitution, but '%s' was "
u"not declared", orig_value, u'->'.join(str(x) for x in path), name) "not declared", orig_value, '->'.join(str(x) for x in path), name)
i = j i = j
continue continue
@ -91,7 +90,7 @@ def _substitute_item(substitutions, item, path):
for old, new in replace_keys: for old, new in replace_keys:
item[new] = item[old] item[new] = item[old]
del item[old] del item[old]
elif isinstance(item, string_types): elif isinstance(item, str):
sub = _expand_substitutions(substitutions, item, path) sub = _expand_substitutions(substitutions, item, path)
if sub != item: if sub != item:
return sub return sub
@ -109,8 +108,8 @@ def do_substitution_pass(config):
substitutions = config[CONF_SUBSTITUTIONS] substitutions = config[CONF_SUBSTITUTIONS]
with cv.prepend_path('substitutions'): with cv.prepend_path('substitutions'):
if not isinstance(substitutions, dict): if not isinstance(substitutions, dict):
raise cv.Invalid(u"Substitutions must be a key to value mapping, got {}" raise cv.Invalid("Substitutions must be a key to value mapping, got {}"
u"".format(type(substitutions))) "".format(type(substitutions)))
replace_keys = [] replace_keys = []
for key, value in substitutions.items(): for key, value in substitutions.items():

View file

@ -3,7 +3,6 @@ import esphome.config_validation as cv
from esphome import automation from esphome import automation
from esphome.components import time from esphome.components import time
from esphome.const import CONF_TIME_ID, CONF_ID, CONF_TRIGGER_ID from esphome.const import CONF_TIME_ID, CONF_ID, CONF_TRIGGER_ID
from esphome.py_compat import string_types
sun_ns = cg.esphome_ns.namespace('sun') sun_ns = cg.esphome_ns.namespace('sun')
@ -32,7 +31,7 @@ ELEVATION_MAP = {
def elevation(value): def elevation(value):
if isinstance(value, string_types): if isinstance(value, str):
try: try:
value = ELEVATION_MAP[cv.one_of(*ELEVATION_MAP, lower=True, space='_')(value)] value = ELEVATION_MAP[cv.one_of(*ELEVATION_MAP, lower=True, space='_')(value)]
except cv.Invalid: except cv.Invalid:

View file

@ -1,4 +1,3 @@
# coding=utf-8
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import i2c, sensor from esphome.components import i2c, sensor

View file

@ -14,7 +14,6 @@ from esphome.const import CONF_CRON, CONF_DAYS_OF_MONTH, CONF_DAYS_OF_WEEK, CONF
CONF_MINUTES, CONF_MONTHS, CONF_ON_TIME, CONF_SECONDS, CONF_TIMEZONE, CONF_TRIGGER_ID, \ CONF_MINUTES, CONF_MONTHS, CONF_ON_TIME, CONF_SECONDS, CONF_TIMEZONE, CONF_TRIGGER_ID, \
CONF_AT, CONF_SECOND, CONF_HOUR, CONF_MINUTE CONF_AT, CONF_SECOND, CONF_HOUR, CONF_MINUTE
from esphome.core import coroutine, coroutine_with_priority from esphome.core import coroutine, coroutine_with_priority
from esphome.py_compat import string_types
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -33,10 +32,10 @@ def _tz_timedelta(td):
if offset_hour == 0 and offset_minute == 0 and offset_second == 0: if offset_hour == 0 and offset_minute == 0 and offset_second == 0:
return '0' return '0'
if offset_minute == 0 and offset_second == 0: if offset_minute == 0 and offset_second == 0:
return '{}'.format(offset_hour) return f'{offset_hour}'
if offset_second == 0: if offset_second == 0:
return '{}:{}'.format(offset_hour, offset_minute) return f'{offset_hour}:{offset_minute}'
return '{}:{}:{}'.format(offset_hour, offset_minute, offset_second) return f'{offset_hour}:{offset_minute}:{offset_second}'
# https://stackoverflow.com/a/16804556/8924614 # https://stackoverflow.com/a/16804556/8924614
@ -133,7 +132,7 @@ def detect_tz():
def _parse_cron_int(value, special_mapping, message): def _parse_cron_int(value, special_mapping, message):
special_mapping = special_mapping or {} special_mapping = special_mapping or {}
if isinstance(value, string_types) and value in special_mapping: if isinstance(value, str) and value in special_mapping:
return special_mapping[value] return special_mapping[value]
try: try:
return int(value) return int(value)
@ -143,41 +142,40 @@ def _parse_cron_int(value, special_mapping, message):
def _parse_cron_part(part, min_value, max_value, special_mapping): def _parse_cron_part(part, min_value, max_value, special_mapping):
if part in ('*', '?'): if part in ('*', '?'):
return set(x for x in range(min_value, max_value + 1)) return set(range(min_value, max_value + 1))
if '/' in part: if '/' in part:
data = part.split('/') data = part.split('/')
if len(data) > 2: if len(data) > 2:
raise cv.Invalid(u"Can't have more than two '/' in one time expression, got {}" raise cv.Invalid("Can't have more than two '/' in one time expression, got {}"
.format(part)) .format(part))
offset, repeat = data offset, repeat = data
offset_n = 0 offset_n = 0
if offset: if offset:
offset_n = _parse_cron_int(offset, special_mapping, offset_n = _parse_cron_int(offset, special_mapping,
u"Offset for '/' time expression must be an integer, got {}") "Offset for '/' time expression must be an integer, got {}")
try: try:
repeat_n = int(repeat) repeat_n = int(repeat)
except ValueError: except ValueError:
raise cv.Invalid(u"Repeat for '/' time expression must be an integer, got {}" raise cv.Invalid("Repeat for '/' time expression must be an integer, got {}"
.format(repeat)) .format(repeat))
return set(x for x in range(offset_n, max_value + 1, repeat_n)) return set(range(offset_n, max_value + 1, repeat_n))
if '-' in part: if '-' in part:
data = part.split('-') data = part.split('-')
if len(data) > 2: if len(data) > 2:
raise cv.Invalid(u"Can't have more than two '-' in range time expression '{}'" raise cv.Invalid("Can't have more than two '-' in range time expression '{}'"
.format(part)) .format(part))
begin, end = data begin, end = data
begin_n = _parse_cron_int(begin, special_mapping, u"Number for time range must be integer, " begin_n = _parse_cron_int(begin, special_mapping, "Number for time range must be integer, "
u"got {}") "got {}")
end_n = _parse_cron_int(end, special_mapping, u"Number for time range must be integer, " end_n = _parse_cron_int(end, special_mapping, "Number for time range must be integer, "
u"got {}") "got {}")
if end_n < begin_n: if end_n < begin_n:
return set(x for x in range(end_n, max_value + 1)) | \ return set(range(end_n, max_value + 1)) | set(range(min_value, begin_n + 1))
set(x for x in range(min_value, begin_n + 1)) return set(range(begin_n, end_n + 1))
return set(x for x in range(begin_n, end_n + 1))
return {_parse_cron_int(part, special_mapping, u"Number for time expression must be an " return {_parse_cron_int(part, special_mapping, "Number for time expression must be an "
u"integer, got {}")} "integer, got {}")}
def cron_expression_validator(name, min_value, max_value, special_mapping=None): def cron_expression_validator(name, min_value, max_value, special_mapping=None):
@ -249,7 +247,7 @@ def validate_cron_keys(value):
if CONF_CRON in value: if CONF_CRON in value:
for key in value.keys(): for key in value.keys():
if key in CRON_KEYS: if key in CRON_KEYS:
raise cv.Invalid("Cannot use option {} when cron: is specified.".format(key)) raise cv.Invalid(f"Cannot use option {key} when cron: is specified.")
if CONF_AT in value: if CONF_AT in value:
raise cv.Invalid("Cannot use option at with cron!") raise cv.Invalid("Cannot use option at with cron!")
cron_ = value[CONF_CRON] cron_ = value[CONF_CRON]
@ -259,7 +257,7 @@ def validate_cron_keys(value):
if CONF_AT in value: if CONF_AT in value:
for key in value.keys(): for key in value.keys():
if key in CRON_KEYS: if key in CRON_KEYS:
raise cv.Invalid("Cannot use option {} when at: is specified.".format(key)) raise cv.Invalid(f"Cannot use option {key} when at: is specified.")
at_ = value[CONF_AT] at_ = value[CONF_AT]
value = {x: value[x] for x in value if x != CONF_AT} value = {x: value[x] for x in value if x != CONF_AT}
value.update(at_) value.update(at_)

View file

@ -3,7 +3,6 @@ import esphome.config_validation as cv
from esphome import pins, automation from esphome import pins, automation
from esphome.const import CONF_BAUD_RATE, CONF_ID, CONF_RX_PIN, CONF_TX_PIN, CONF_UART_ID, CONF_DATA from esphome.const import CONF_BAUD_RATE, CONF_ID, CONF_RX_PIN, CONF_TX_PIN, CONF_UART_ID, CONF_DATA
from esphome.core import CORE, coroutine from esphome.core import CORE, coroutine
from esphome.py_compat import text_type, binary_type, char_to_byte
uart_ns = cg.esphome_ns.namespace('uart') uart_ns = cg.esphome_ns.namespace('uart')
UARTComponent = uart_ns.class_('UARTComponent', cg.Component) UARTComponent = uart_ns.class_('UARTComponent', cg.Component)
@ -13,7 +12,7 @@ MULTI_CONF = True
def validate_raw_data(value): def validate_raw_data(value):
if isinstance(value, text_type): if isinstance(value, str):
return value.encode('utf-8') return value.encode('utf-8')
if isinstance(value, str): if isinstance(value, str):
return value return value
@ -77,8 +76,8 @@ def uart_write_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg) var = cg.new_Pvariable(action_id, template_arg)
yield cg.register_parented(var, config[CONF_ID]) yield cg.register_parented(var, config[CONF_ID])
data = config[CONF_DATA] data = config[CONF_DATA]
if isinstance(data, binary_type): if isinstance(data, bytes):
data = [char_to_byte(x) for x in data] data = list(data)
if cg.is_template(data): if cg.is_template(data):
templ = yield cg.templatable(data, args, cg.std_vector.template(cg.uint8)) templ = yield cg.templatable(data, args, cg.std_vector.template(cg.uint8))

View file

@ -3,7 +3,6 @@ import esphome.config_validation as cv
from esphome.components import switch, uart from esphome.components import switch, uart
from esphome.const import CONF_DATA, CONF_ID, CONF_INVERTED from esphome.const import CONF_DATA, CONF_ID, CONF_INVERTED
from esphome.core import HexInt from esphome.core import HexInt
from esphome.py_compat import binary_type, char_to_byte
from .. import uart_ns, validate_raw_data from .. import uart_ns, validate_raw_data
DEPENDENCIES = ['uart'] DEPENDENCIES = ['uart']
@ -25,6 +24,6 @@ def to_code(config):
yield uart.register_uart_device(var, config) yield uart.register_uart_device(var, config)
data = config[CONF_DATA] data = config[CONF_DATA]
if isinstance(data, binary_type): if isinstance(data, bytes):
data = [HexInt(char_to_byte(x)) for x in data] data = [HexInt(x) for x in data]
cg.add(var.set_data(data)) cg.add(var.set_data(data))

View file

@ -30,9 +30,9 @@ def validate_password(value):
if not value: if not value:
return value return value
if len(value) < 8: if len(value) < 8:
raise cv.Invalid(u"WPA password must be at least 8 characters long") raise cv.Invalid("WPA password must be at least 8 characters long")
if len(value) > 64: if len(value) > 64:
raise cv.Invalid(u"WPA password must be at most 64 characters long") raise cv.Invalid("WPA password must be at most 64 characters long")
return value return value

View file

@ -1,5 +1,3 @@
from __future__ import print_function
import collections import collections
import importlib import importlib
import logging import logging
@ -18,7 +16,6 @@ from esphome.components.substitutions import CONF_SUBSTITUTIONS
from esphome.const import CONF_ESPHOME, CONF_PLATFORM, ESP_PLATFORMS from esphome.const import CONF_ESPHOME, CONF_PLATFORM, ESP_PLATFORMS
from esphome.core import CORE, EsphomeError # noqa from esphome.core import CORE, EsphomeError # noqa
from esphome.helpers import color, indent from esphome.helpers import color, indent
from esphome.py_compat import text_type, IS_PY2, decode_text, string_types
from esphome.util import safe_print, OrderedDict from esphome.util import safe_print, OrderedDict
from typing import List, Optional, Tuple, Union # noqa from typing import List, Optional, Tuple, Union # noqa
@ -31,7 +28,7 @@ _LOGGER = logging.getLogger(__name__)
_COMPONENT_CACHE = {} _COMPONENT_CACHE = {}
class ComponentManifest(object): class ComponentManifest:
def __init__(self, module, base_components_path, is_core=False, is_platform=False): def __init__(self, module, base_components_path, is_core=False, is_platform=False):
self.module = module self.module = module
self._is_core = is_core self._is_core = is_core
@ -89,7 +86,7 @@ class ComponentManifest(object):
source_files = core.find_source_files(os.path.join(core_p, 'dummy')) source_files = core.find_source_files(os.path.join(core_p, 'dummy'))
ret = {} ret = {}
for f in source_files: for f in source_files:
ret['esphome/core/{}'.format(f)] = os.path.join(core_p, f) ret[f'esphome/core/{f}'] = os.path.join(core_p, f)
return ret return ret
source_files = core.find_source_files(self.module.__file__) source_files = core.find_source_files(self.module.__file__)
@ -101,7 +98,7 @@ class ComponentManifest(object):
rel = os.path.relpath(full_file, self.base_components_path) rel = os.path.relpath(full_file, self.base_components_path)
# Always use / for C++ include names # Always use / for C++ include names
rel = rel.replace(os.sep, '/') rel = rel.replace(os.sep, '/')
target_file = 'esphome/components/{}'.format(rel) target_file = f'esphome/components/{rel}'
ret[target_file] = full_file ret[target_file] = full_file
return ret return ret
@ -119,12 +116,6 @@ def _mount_config_dir():
if not os.path.isdir(custom_path): if not os.path.isdir(custom_path):
CUSTOM_COMPONENTS_PATH = None CUSTOM_COMPONENTS_PATH = None
return return
init_path = os.path.join(custom_path, '__init__.py')
if IS_PY2 and not os.path.isfile(init_path):
_LOGGER.warning("Found 'custom_components' folder, but file __init__.py was not found. "
"ESPHome will automatically create it now....")
with open(init_path, 'w') as f:
f.write('\n')
if CORE.config_dir not in sys.path: if CORE.config_dir not in sys.path:
sys.path.insert(0, CORE.config_dir) sys.path.insert(0, CORE.config_dir)
CUSTOM_COMPONENTS_PATH = custom_path CUSTOM_COMPONENTS_PATH = custom_path
@ -137,7 +128,7 @@ def _lookup_module(domain, is_platform):
_mount_config_dir() _mount_config_dir()
# First look for custom_components # First look for custom_components
try: try:
module = importlib.import_module('custom_components.{}'.format(domain)) module = importlib.import_module(f'custom_components.{domain}')
except ImportError as e: except ImportError as e:
# ImportError when no such module # ImportError when no such module
if 'No module named' not in str(e): if 'No module named' not in str(e):
@ -153,7 +144,7 @@ def _lookup_module(domain, is_platform):
return manif return manif
try: try:
module = importlib.import_module('esphome.components.{}'.format(domain)) module = importlib.import_module(f'esphome.components.{domain}')
except ImportError as e: except ImportError as e:
if 'No module named' not in str(e): if 'No module named' not in str(e):
_LOGGER.error("Unable to import component %s:", domain, exc_info=True) _LOGGER.error("Unable to import component %s:", domain, exc_info=True)
@ -173,7 +164,7 @@ def get_component(domain):
def get_platform(domain, platform): def get_platform(domain, platform):
full = '{}.{}'.format(platform, domain) full = f'{platform}.{domain}'
return _lookup_module(full, True) return _lookup_module(full, True)
@ -192,7 +183,7 @@ def iter_components(config):
yield domain, component, conf yield domain, component, conf
if component.is_platform_component: if component.is_platform_component:
for p_config in conf: for p_config in conf:
p_name = u"{}.{}".format(domain, p_config[CONF_PLATFORM]) p_name = "{}.{}".format(domain, p_config[CONF_PLATFORM])
platform = get_platform(domain, p_config[CONF_PLATFORM]) platform = get_platform(domain, p_config[CONF_PLATFORM])
yield p_name, platform, p_config yield p_name, platform, p_config
@ -208,13 +199,13 @@ def _path_begins_with(path, other): # type: (ConfigPath, ConfigPath) -> bool
class Config(OrderedDict): class Config(OrderedDict):
def __init__(self): def __init__(self):
super(Config, self).__init__() super().__init__()
# A list of voluptuous errors # A list of voluptuous errors
self.errors = [] # type: List[vol.Invalid] self.errors = [] # type: List[vol.Invalid]
# A list of paths that should be fully outputted # A list of paths that should be fully outputted
# The values will be the paths to all "domain", for example (['logger'], 'logger') # The values will be the paths to all "domain", for example (['logger'], 'logger')
# or (['sensor', 'ultrasonic'], 'sensor.ultrasonic') # or (['sensor', 'ultrasonic'], 'sensor.ultrasonic')
self.output_paths = [] # type: List[Tuple[ConfigPath, unicode]] self.output_paths = [] # type: List[Tuple[ConfigPath, str]]
def add_error(self, error): def add_error(self, error):
# type: (vol.Invalid) -> None # type: (vol.Invalid) -> None
@ -234,15 +225,15 @@ class Config(OrderedDict):
self.add_error(e) self.add_error(e)
def add_str_error(self, message, path): def add_str_error(self, message, path):
# type: (basestring, ConfigPath) -> None # type: (str, ConfigPath) -> None
self.add_error(vol.Invalid(message, path)) self.add_error(vol.Invalid(message, path))
def add_output_path(self, path, domain): def add_output_path(self, path, domain):
# type: (ConfigPath, unicode) -> None # type: (ConfigPath, str) -> None
self.output_paths.append((path, domain)) self.output_paths.append((path, domain))
def remove_output_path(self, path, domain): def remove_output_path(self, path, domain):
# type: (ConfigPath, unicode) -> None # type: (ConfigPath, str) -> None
self.output_paths.remove((path, domain)) self.output_paths.remove((path, domain))
def is_in_error_path(self, path): def is_in_error_path(self, path):
@ -312,12 +303,10 @@ def iter_ids(config, path=None):
yield id, path yield id, path
elif isinstance(config, list): elif isinstance(config, list):
for i, item in enumerate(config): for i, item in enumerate(config):
for result in iter_ids(item, path + [i]): yield from iter_ids(item, path + [i])
yield result
elif isinstance(config, dict): elif isinstance(config, dict):
for key, value in config.items(): for key, value in config.items():
for result in iter_ids(value, path + [key]): yield from iter_ids(value, path + [key])
yield result
def do_id_pass(result): # type: (Config) -> None def do_id_pass(result): # type: (Config) -> None
@ -332,8 +321,8 @@ def do_id_pass(result): # type: (Config) -> None
# Look for duplicate definitions # Look for duplicate definitions
match = next((v for v in declare_ids if v[0].id == id.id), None) match = next((v for v in declare_ids if v[0].id == id.id), None)
if match is not None: if match is not None:
opath = u'->'.join(text_type(v) for v in match[1]) opath = '->'.join(str(v) for v in match[1])
result.add_str_error(u"ID {} redefined! Check {}".format(id.id, opath), path) result.add_str_error(f"ID {id.id} redefined! Check {opath}", path)
continue continue
declare_ids.append((id, path)) declare_ids.append((id, path))
else: else:
@ -357,8 +346,8 @@ def do_id_pass(result): # type: (Config) -> None
# Find candidates # Find candidates
matches = difflib.get_close_matches(id.id, [v[0].id for v in declare_ids]) matches = difflib.get_close_matches(id.id, [v[0].id for v in declare_ids])
if matches: if matches:
matches_s = ', '.join('"{}"'.format(x) for x in matches) matches_s = ', '.join(f'"{x}"' for x in matches)
error += " These IDs look similar: {}.".format(matches_s) error += f" These IDs look similar: {matches_s}."
result.add_str_error(error, path) result.add_str_error(error, path)
continue continue
if not isinstance(match.type, MockObjClass) or not isinstance(id.type, MockObjClass): if not isinstance(match.type, MockObjClass) or not isinstance(id.type, MockObjClass):
@ -377,7 +366,7 @@ def do_id_pass(result): # type: (Config) -> None
id.id = v[0].id id.id = v[0].id
break break
else: else:
result.add_str_error("Couldn't resolve ID for type '{}'".format(id.type), path) result.add_str_error(f"Couldn't resolve ID for type '{id.type}'", path)
def recursive_check_replaceme(value): def recursive_check_replaceme(value):
@ -389,7 +378,7 @@ def recursive_check_replaceme(value):
return cv.Schema({cv.valid: recursive_check_replaceme})(value) return cv.Schema({cv.valid: recursive_check_replaceme})(value)
if isinstance(value, ESPForceValue): if isinstance(value, ESPForceValue):
pass pass
if isinstance(value, string_types) and value == 'REPLACEME': if isinstance(value, str) and value == 'REPLACEME':
raise cv.Invalid("Found 'REPLACEME' in configuration, this is most likely an error. " raise cv.Invalid("Found 'REPLACEME' in configuration, this is most likely an error. "
"Please make sure you have replaced all fields from the sample " "Please make sure you have replaced all fields from the sample "
"configuration.\n" "configuration.\n"
@ -455,8 +444,8 @@ def validate_config(config):
while load_queue: while load_queue:
domain, conf = load_queue.popleft() domain, conf = load_queue.popleft()
domain = text_type(domain) domain = str(domain)
if domain.startswith(u'.'): if domain.startswith('.'):
# Ignore top-level keys starting with a dot # Ignore top-level keys starting with a dot
continue continue
result.add_output_path([domain], domain) result.add_output_path([domain], domain)
@ -464,7 +453,7 @@ def validate_config(config):
component = get_component(domain) component = get_component(domain)
path = [domain] path = [domain]
if component is None: if component is None:
result.add_str_error(u"Component not found: {}".format(domain), path) result.add_str_error(f"Component not found: {domain}", path)
continue continue
CORE.loaded_integrations.add(domain) CORE.loaded_integrations.add(domain)
@ -492,24 +481,24 @@ def validate_config(config):
for i, p_config in enumerate(conf): for i, p_config in enumerate(conf):
path = [domain, i] path = [domain, i]
# Construct temporary unknown output path # Construct temporary unknown output path
p_domain = u'{}.unknown'.format(domain) p_domain = f'{domain}.unknown'
result.add_output_path(path, p_domain) result.add_output_path(path, p_domain)
result[domain][i] = p_config result[domain][i] = p_config
if not isinstance(p_config, dict): if not isinstance(p_config, dict):
result.add_str_error(u"Platform schemas must be key-value pairs.", path) result.add_str_error("Platform schemas must be key-value pairs.", path)
continue continue
p_name = p_config.get('platform') p_name = p_config.get('platform')
if p_name is None: if p_name is None:
result.add_str_error(u"No platform specified! See 'platform' key.", path) result.add_str_error("No platform specified! See 'platform' key.", path)
continue continue
# Remove temp output path and construct new one # Remove temp output path and construct new one
result.remove_output_path(path, p_domain) result.remove_output_path(path, p_domain)
p_domain = u'{}.{}'.format(domain, p_name) p_domain = f'{domain}.{p_name}'
result.add_output_path(path, p_domain) result.add_output_path(path, p_domain)
# Try Load platform # Try Load platform
platform = get_platform(domain, p_name) platform = get_platform(domain, p_name)
if platform is None: if platform is None:
result.add_str_error(u"Platform not found: '{}'".format(p_domain), path) result.add_str_error(f"Platform not found: '{p_domain}'", path)
continue continue
CORE.loaded_integrations.add(p_name) CORE.loaded_integrations.add(p_name)
@ -537,8 +526,8 @@ def validate_config(config):
success = True success = True
for dependency in comp.dependencies: for dependency in comp.dependencies:
if dependency not in config: if dependency not in config:
result.add_str_error(u"Component {} requires component {}" result.add_str_error("Component {} requires component {}"
u"".format(domain, dependency), path) "".format(domain, dependency), path)
success = False success = False
if not success: if not success:
continue continue
@ -546,22 +535,22 @@ def validate_config(config):
success = True success = True
for conflict in comp.conflicts_with: for conflict in comp.conflicts_with:
if conflict in config: if conflict in config:
result.add_str_error(u"Component {} cannot be used together with component {}" result.add_str_error("Component {} cannot be used together with component {}"
u"".format(domain, conflict), path) "".format(domain, conflict), path)
success = False success = False
if not success: if not success:
continue continue
if CORE.esp_platform not in comp.esp_platforms: if CORE.esp_platform not in comp.esp_platforms:
result.add_str_error(u"Component {} doesn't support {}.".format(domain, result.add_str_error("Component {} doesn't support {}.".format(domain,
CORE.esp_platform), CORE.esp_platform),
path) path)
continue continue
if not comp.is_platform_component and comp.config_schema is None and \ if not comp.is_platform_component and comp.config_schema is None and \
not isinstance(conf, core.AutoLoad): not isinstance(conf, core.AutoLoad):
result.add_str_error(u"Component {} cannot be loaded via YAML " result.add_str_error("Component {} cannot be loaded via YAML "
u"(no CONFIG_SCHEMA).".format(domain), path) "(no CONFIG_SCHEMA).".format(domain), path)
continue continue
if comp.is_multi_conf: if comp.is_multi_conf:
@ -611,13 +600,13 @@ def _nested_getitem(data, path):
def humanize_error(config, validation_error): def humanize_error(config, validation_error):
validation_error = text_type(validation_error) validation_error = str(validation_error)
m = re.match(r'^(.*?)\s*(?:for dictionary value )?@ data\[.*$', validation_error, re.DOTALL) m = re.match(r'^(.*?)\s*(?:for dictionary value )?@ data\[.*$', validation_error, re.DOTALL)
if m is not None: if m is not None:
validation_error = m.group(1) validation_error = m.group(1)
validation_error = validation_error.strip() validation_error = validation_error.strip()
if not validation_error.endswith(u'.'): if not validation_error.endswith('.'):
validation_error += u'.' validation_error += '.'
return validation_error return validation_error
@ -634,22 +623,22 @@ def _get_parent_name(path, config):
def _format_vol_invalid(ex, config): def _format_vol_invalid(ex, config):
# type: (vol.Invalid, Config) -> unicode # type: (vol.Invalid, Config) -> str
message = u'' message = ''
paren = _get_parent_name(ex.path[:-1], config) paren = _get_parent_name(ex.path[:-1], config)
if isinstance(ex, ExtraKeysInvalid): if isinstance(ex, ExtraKeysInvalid):
if ex.candidates: if ex.candidates:
message += u'[{}] is an invalid option for [{}]. Did you mean {}?'.format( message += '[{}] is an invalid option for [{}]. Did you mean {}?'.format(
ex.path[-1], paren, u', '.join(u'[{}]'.format(x) for x in ex.candidates)) ex.path[-1], paren, ', '.join(f'[{x}]' for x in ex.candidates))
else: else:
message += u'[{}] is an invalid option for [{}]. Please check the indentation.'.format( message += '[{}] is an invalid option for [{}]. Please check the indentation.'.format(
ex.path[-1], paren) ex.path[-1], paren)
elif u'extra keys not allowed' in text_type(ex): elif 'extra keys not allowed' in str(ex):
message += u'[{}] is an invalid option for [{}].'.format(ex.path[-1], paren) message += '[{}] is an invalid option for [{}].'.format(ex.path[-1], paren)
elif u'required key not provided' in text_type(ex): elif 'required key not provided' in str(ex):
message += u"'{}' is a required option for [{}].".format(ex.path[-1], paren) message += "'{}' is a required option for [{}].".format(ex.path[-1], paren)
else: else:
message += humanize_error(config, ex) message += humanize_error(config, ex)
@ -662,9 +651,8 @@ class InvalidYAMLError(EsphomeError):
base = str(base_exc) base = str(base_exc)
except UnicodeDecodeError: except UnicodeDecodeError:
base = repr(base_exc) base = repr(base_exc)
base = decode_text(base) message = f"Invalid YAML syntax:\n\n{base}"
message = u"Invalid YAML syntax:\n\n{}".format(base) super().__init__(message)
super(InvalidYAMLError, self).__init__(message)
self.base_exc = base_exc self.base_exc = base_exc
@ -680,7 +668,7 @@ def _load_config():
except EsphomeError: except EsphomeError:
raise raise
except Exception: except Exception:
_LOGGER.error(u"Unexpected exception while reading configuration:") _LOGGER.error("Unexpected exception while reading configuration:")
raise raise
return result return result
@ -690,7 +678,7 @@ def load_config():
try: try:
return _load_config() return _load_config()
except vol.Invalid as err: except vol.Invalid as err:
raise EsphomeError("Error while parsing config: {}".format(err)) raise EsphomeError(f"Error while parsing config: {err}")
def line_info(obj, highlight=True): def line_info(obj, highlight=True):
@ -699,7 +687,7 @@ def line_info(obj, highlight=True):
return None return None
if isinstance(obj, ESPHomeDataBase) and obj.esp_range is not None: if isinstance(obj, ESPHomeDataBase) and obj.esp_range is not None:
mark = obj.esp_range.start_mark mark = obj.esp_range.start_mark
source = u"[source {}:{}]".format(mark.document, mark.line + 1) source = "[source {}:{}]".format(mark.document, mark.line + 1)
return color('cyan', source) return color('cyan', source)
return None return None
@ -715,82 +703,82 @@ def _print_on_next_line(obj):
def dump_dict(config, path, at_root=True): def dump_dict(config, path, at_root=True):
# type: (Config, ConfigPath, bool) -> Tuple[unicode, bool] # type: (Config, ConfigPath, bool) -> Tuple[str, bool]
conf = config.get_nested_item(path) conf = config.get_nested_item(path)
ret = u'' ret = ''
multiline = False multiline = False
if at_root: if at_root:
error = config.get_error_for_path(path) error = config.get_error_for_path(path)
if error is not None: if error is not None:
ret += u'\n' + color('bold_red', _format_vol_invalid(error, config)) + u'\n' ret += '\n' + color('bold_red', _format_vol_invalid(error, config)) + '\n'
if isinstance(conf, (list, tuple)): if isinstance(conf, (list, tuple)):
multiline = True multiline = True
if not conf: if not conf:
ret += u'[]' ret += '[]'
multiline = False multiline = False
for i in range(len(conf)): for i in range(len(conf)):
path_ = path + [i] path_ = path + [i]
error = config.get_error_for_path(path_) error = config.get_error_for_path(path_)
if error is not None: if error is not None:
ret += u'\n' + color('bold_red', _format_vol_invalid(error, config)) + u'\n' ret += '\n' + color('bold_red', _format_vol_invalid(error, config)) + '\n'
sep = u'- ' sep = '- '
if config.is_in_error_path(path_): if config.is_in_error_path(path_):
sep = color('red', sep) sep = color('red', sep)
msg, _ = dump_dict(config, path_, at_root=False) msg, _ = dump_dict(config, path_, at_root=False)
msg = indent(msg) msg = indent(msg)
inf = line_info(config.get_nested_item(path_), highlight=config.is_in_error_path(path_)) inf = line_info(config.get_nested_item(path_), highlight=config.is_in_error_path(path_))
if inf is not None: if inf is not None:
msg = inf + u'\n' + msg msg = inf + '\n' + msg
elif msg: elif msg:
msg = msg[2:] msg = msg[2:]
ret += sep + msg + u'\n' ret += sep + msg + '\n'
elif isinstance(conf, dict): elif isinstance(conf, dict):
multiline = True multiline = True
if not conf: if not conf:
ret += u'{}' ret += '{}'
multiline = False multiline = False
for k in conf.keys(): for k in conf.keys():
path_ = path + [k] path_ = path + [k]
error = config.get_error_for_path(path_) error = config.get_error_for_path(path_)
if error is not None: if error is not None:
ret += u'\n' + color('bold_red', _format_vol_invalid(error, config)) + u'\n' ret += '\n' + color('bold_red', _format_vol_invalid(error, config)) + '\n'
st = u'{}: '.format(k) st = f'{k}: '
if config.is_in_error_path(path_): if config.is_in_error_path(path_):
st = color('red', st) st = color('red', st)
msg, m = dump_dict(config, path_, at_root=False) msg, m = dump_dict(config, path_, at_root=False)
inf = line_info(config.get_nested_item(path_), highlight=config.is_in_error_path(path_)) inf = line_info(config.get_nested_item(path_), highlight=config.is_in_error_path(path_))
if m: if m:
msg = u'\n' + indent(msg) msg = '\n' + indent(msg)
if inf is not None: if inf is not None:
if m: if m:
msg = u' ' + inf + msg msg = ' ' + inf + msg
else: else:
msg = msg + u' ' + inf msg = msg + ' ' + inf
ret += st + msg + u'\n' ret += st + msg + '\n'
elif isinstance(conf, str): elif isinstance(conf, str):
if is_secret(conf): if is_secret(conf):
conf = u'!secret {}'.format(is_secret(conf)) conf = '!secret {}'.format(is_secret(conf))
if not conf: if not conf:
conf += u"''" conf += "''"
if len(conf) > 80: if len(conf) > 80:
conf = u'|-\n' + indent(conf) conf = '|-\n' + indent(conf)
error = config.get_error_for_path(path) error = config.get_error_for_path(path)
col = 'bold_red' if error else 'white' col = 'bold_red' if error else 'white'
ret += color(col, text_type(conf)) ret += color(col, str(conf))
elif isinstance(conf, core.Lambda): elif isinstance(conf, core.Lambda):
if is_secret(conf): if is_secret(conf):
conf = u'!secret {}'.format(is_secret(conf)) conf = '!secret {}'.format(is_secret(conf))
conf = u'!lambda |-\n' + indent(text_type(conf.value)) conf = '!lambda |-\n' + indent(str(conf.value))
error = config.get_error_for_path(path) error = config.get_error_for_path(path)
col = 'bold_red' if error else 'white' col = 'bold_red' if error else 'white'
ret += color(col, conf) ret += color(col, conf)
@ -799,8 +787,8 @@ def dump_dict(config, path, at_root=True):
else: else:
error = config.get_error_for_path(path) error = config.get_error_for_path(path)
col = 'bold_red' if error else 'white' col = 'bold_red' if error else 'white'
ret += color(col, text_type(conf)) ret += color(col, str(conf))
multiline = u'\n' in ret multiline = '\n' in ret
return ret, multiline return ret, multiline
@ -830,20 +818,20 @@ def read_config():
try: try:
res = load_config() res = load_config()
except EsphomeError as err: except EsphomeError as err:
_LOGGER.error(u"Error while reading config: %s", err) _LOGGER.error("Error while reading config: %s", err)
return None return None
if res.errors: if res.errors:
if not CORE.verbose: if not CORE.verbose:
res = strip_default_ids(res) res = strip_default_ids(res)
safe_print(color('bold_red', u"Failed config")) safe_print(color('bold_red', "Failed config"))
safe_print('') safe_print('')
for path, domain in res.output_paths: for path, domain in res.output_paths:
if not res.is_in_error_path(path): if not res.is_in_error_path(path):
continue continue
safe_print(color('bold_red', u'{}:'.format(domain)) + u' ' + safe_print(color('bold_red', f'{domain}:') + ' ' +
(line_info(res.get_nested_item(path)) or u'')) (line_info(res.get_nested_item(path)) or ''))
safe_print(indent(dump_dict(res, path)[0])) safe_print(indent(dump_dict(res, path)[0]))
return None return None
return OrderedDict(res) return OrderedDict(res)

View file

@ -1,22 +1,19 @@
from __future__ import print_function
import json import json
import os import os
from esphome.core import CORE from esphome.core import CORE
from esphome.helpers import read_file from esphome.helpers import read_file
from esphome.py_compat import safe_input
def read_config_file(path): def read_config_file(path):
# type: (basestring) -> unicode # type: (str) -> str
if CORE.vscode and (not CORE.ace or if CORE.vscode and (not CORE.ace or
os.path.abspath(path) == os.path.abspath(CORE.config_path)): os.path.abspath(path) == os.path.abspath(CORE.config_path)):
print(json.dumps({ print(json.dumps({
'type': 'read_file', 'type': 'read_file',
'path': path, 'path': path,
})) }))
data = json.loads(safe_input()) data = json.loads(input())
assert data['type'] == 'file_response' assert data['type'] == 'file_response'
return data['content'] return data['content']

View file

@ -1,6 +1,4 @@
# coding=utf-8
"""Helpers for config validation using voluptuous.""" """Helpers for config validation using voluptuous."""
from __future__ import print_function
import logging import logging
import os import os
@ -20,7 +18,6 @@ from esphome.const import CONF_AVAILABILITY, CONF_COMMAND_TOPIC, CONF_DISCOVERY,
from esphome.core import CORE, HexInt, IPAddress, Lambda, TimePeriod, TimePeriodMicroseconds, \ from esphome.core import CORE, HexInt, IPAddress, Lambda, TimePeriod, TimePeriodMicroseconds, \
TimePeriodMilliseconds, TimePeriodSeconds, TimePeriodMinutes TimePeriodMilliseconds, TimePeriodSeconds, TimePeriodMinutes
from esphome.helpers import list_starts_with, add_class_to_obj from esphome.helpers import list_starts_with, add_class_to_obj
from esphome.py_compat import integer_types, string_types, text_type, IS_PY2, decode_text
from esphome.voluptuous_schema import _Schema from esphome.voluptuous_schema import _Schema
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -43,7 +40,7 @@ ALLOW_EXTRA = vol.ALLOW_EXTRA
UNDEFINED = vol.UNDEFINED UNDEFINED = vol.UNDEFINED
RequiredFieldInvalid = vol.RequiredFieldInvalid RequiredFieldInvalid = vol.RequiredFieldInvalid
ALLOWED_NAME_CHARS = u'abcdefghijklmnopqrstuvwxyz0123456789_' ALLOWED_NAME_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_'
RESERVED_IDS = [ RESERVED_IDS = [
# C++ keywords http://en.cppreference.com/w/cpp/keyword # C++ keywords http://en.cppreference.com/w/cpp/keyword
@ -82,7 +79,7 @@ class Optional(vol.Optional):
""" """
def __init__(self, key, default=UNDEFINED): def __init__(self, key, default=UNDEFINED):
super(Optional, self).__init__(key, default=default) super().__init__(key, default=default)
class Required(vol.Required): class Required(vol.Required):
@ -94,7 +91,7 @@ class Required(vol.Required):
""" """
def __init__(self, key): def __init__(self, key):
super(Required, self).__init__(key) super().__init__(key)
def check_not_templatable(value): def check_not_templatable(value):
@ -105,7 +102,7 @@ def check_not_templatable(value):
def alphanumeric(value): def alphanumeric(value):
if value is None: if value is None:
raise Invalid("string value is None") raise Invalid("string value is None")
value = text_type(value) value = str(value)
if not value.isalnum(): if not value.isalnum():
raise Invalid("string value is not alphanumeric") raise Invalid("string value is not alphanumeric")
return value return value
@ -115,8 +112,8 @@ def valid_name(value):
value = string_strict(value) value = string_strict(value)
for c in value: for c in value:
if c not in ALLOWED_NAME_CHARS: if c not in ALLOWED_NAME_CHARS:
raise Invalid(u"'{}' is an invalid character for names. Valid characters are: {}" raise Invalid(f"'{c}' is an invalid character for names. Valid characters are: "
u" (lowercase, no spaces)".format(c, ALLOWED_NAME_CHARS)) f"{ALLOWED_NAME_CHARS} (lowercase, no spaces)")
return value return value
@ -131,10 +128,10 @@ def string(value):
raise Invalid("string value cannot be dictionary or list.") raise Invalid("string value cannot be dictionary or list.")
if isinstance(value, bool): if isinstance(value, bool):
raise Invalid("Auto-converted this value to boolean, please wrap the value in quotes.") raise Invalid("Auto-converted this value to boolean, please wrap the value in quotes.")
if isinstance(value, text_type): if isinstance(value, str):
return value return value
if value is not None: if value is not None:
return text_type(value) return str(value)
raise Invalid("string value is None") raise Invalid("string value is None")
@ -142,10 +139,8 @@ def string_strict(value):
"""Like string, but only allows strings, and does not automatically convert other types to """Like string, but only allows strings, and does not automatically convert other types to
strings.""" strings."""
check_not_templatable(value) check_not_templatable(value)
if isinstance(value, text_type): if isinstance(value, str):
return value return value
if isinstance(value, string_types):
return text_type(value)
raise Invalid("Must be string, got {}. did you forget putting quotes " raise Invalid("Must be string, got {}. did you forget putting quotes "
"around the value?".format(type(value))) "around the value?".format(type(value)))
@ -172,14 +167,14 @@ def boolean(value):
check_not_templatable(value) check_not_templatable(value)
if isinstance(value, bool): if isinstance(value, bool):
return value return value
if isinstance(value, string_types): if isinstance(value, str):
value = value.lower() value = value.lower()
if value in ('true', 'yes', 'on', 'enable'): if value in ('true', 'yes', 'on', 'enable'):
return True return True
if value in ('false', 'no', 'off', 'disable'): if value in ('false', 'no', 'off', 'disable'):
return False return False
raise Invalid(u"Expected boolean value, but cannot convert {} to a boolean. " raise Invalid("Expected boolean value, but cannot convert {} to a boolean. "
u"Please use 'true' or 'false'".format(value)) "Please use 'true' or 'false'".format(value))
def ensure_list(*validators): def ensure_list(*validators):
@ -228,7 +223,7 @@ def int_(value):
Automatically also converts strings to ints. Automatically also converts strings to ints.
""" """
check_not_templatable(value) check_not_templatable(value)
if isinstance(value, integer_types): if isinstance(value, int):
return value return value
if isinstance(value, float): if isinstance(value, float):
if int(value) == value: if int(value) == value:
@ -242,15 +237,15 @@ def int_(value):
try: try:
return int(value, base) return int(value, base)
except ValueError: except ValueError:
raise Invalid(u"Expected integer, but cannot parse {} as an integer".format(value)) raise Invalid(f"Expected integer, but cannot parse {value} as an integer")
def int_range(min=None, max=None, min_included=True, max_included=True): def int_range(min=None, max=None, min_included=True, max_included=True):
"""Validate that the config option is an integer in the given range.""" """Validate that the config option is an integer in the given range."""
if min is not None: if min is not None:
assert isinstance(min, integer_types) assert isinstance(min, int)
if max is not None: if max is not None:
assert isinstance(max, integer_types) assert isinstance(max, int)
return All(int_, Range(min=min, max=max, min_included=min_included, max_included=max_included)) return All(int_, Range(min=min, max=max, min_included=min_included, max_included=max_included))
@ -291,14 +286,14 @@ def validate_id_name(value):
valid_chars = ascii_letters + digits + '_' valid_chars = ascii_letters + digits + '_'
for char in value: for char in value:
if char not in valid_chars: if char not in valid_chars:
raise Invalid(u"IDs must only consist of upper/lowercase characters, the underscore" raise Invalid("IDs must only consist of upper/lowercase characters, the underscore"
u"character and numbers. The character '{}' cannot be used" "character and numbers. The character '{}' cannot be used"
u"".format(char)) "".format(char))
if value in RESERVED_IDS: if value in RESERVED_IDS:
raise Invalid(u"ID '{}' is reserved internally and cannot be used".format(value)) raise Invalid(f"ID '{value}' is reserved internally and cannot be used")
if value in CORE.loaded_integrations: if value in CORE.loaded_integrations:
raise Invalid(u"ID '{}' conflicts with the name of an esphome integration, please use " raise Invalid("ID '{}' conflicts with the name of an esphome integration, please use "
u"another ID name.".format(value)) "another ID name.".format(value))
return value return value
@ -358,7 +353,7 @@ def only_on(platforms):
def validator_(obj): def validator_(obj):
if CORE.esp_platform not in platforms: if CORE.esp_platform not in platforms:
raise Invalid(u"This feature is only available on {}".format(platforms)) raise Invalid(f"This feature is only available on {platforms}")
return obj return obj
return validator_ return validator_
@ -463,7 +458,7 @@ def time_period_str_unit(value):
"'{0}s'?".format(value)) "'{0}s'?".format(value))
if isinstance(value, TimePeriod): if isinstance(value, TimePeriod):
value = str(value) value = str(value)
if not isinstance(value, string_types): if not isinstance(value, str):
raise Invalid("Expected string for time period with unit.") raise Invalid("Expected string for time period with unit.")
unit_to_kwarg = { unit_to_kwarg = {
@ -485,8 +480,8 @@ def time_period_str_unit(value):
match = re.match(r"^([-+]?[0-9]*\.?[0-9]*)\s*(\w*)$", value) match = re.match(r"^([-+]?[0-9]*\.?[0-9]*)\s*(\w*)$", value)
if match is None: if match is None:
raise Invalid(u"Expected time period with unit, " raise Invalid("Expected time period with unit, "
u"got {}".format(value)) "got {}".format(value))
kwarg = unit_to_kwarg[one_of(*unit_to_kwarg)(match.group(2))] kwarg = unit_to_kwarg[one_of(*unit_to_kwarg)(match.group(2))]
return TimePeriod(**{kwarg: float(match.group(1))}) return TimePeriod(**{kwarg: float(match.group(1))})
@ -545,7 +540,7 @@ def time_of_day(value):
try: try:
date = datetime.strptime(value, '%H:%M:%S %p') date = datetime.strptime(value, '%H:%M:%S %p')
except ValueError: except ValueError:
raise Invalid("Invalid time of day: {}".format(err)) raise Invalid(f"Invalid time of day: {err}")
return { return {
CONF_HOUR: date.hour, CONF_HOUR: date.hour,
@ -577,7 +572,7 @@ def uuid(value):
METRIC_SUFFIXES = { METRIC_SUFFIXES = {
'E': 1e18, 'P': 1e15, 'T': 1e12, 'G': 1e9, 'M': 1e6, 'k': 1e3, 'da': 10, 'd': 1e-1, 'E': 1e18, 'P': 1e15, 'T': 1e12, 'G': 1e9, 'M': 1e6, 'k': 1e3, 'da': 10, 'd': 1e-1,
'c': 1e-2, 'm': 0.001, u'µ': 1e-6, 'u': 1e-6, 'n': 1e-9, 'p': 1e-12, 'f': 1e-15, 'a': 1e-18, 'c': 1e-2, 'm': 0.001, 'µ': 1e-6, 'u': 1e-6, 'n': 1e-9, 'p': 1e-12, 'f': 1e-15, 'a': 1e-18,
'': 1 '': 1
} }
@ -594,11 +589,11 @@ def float_with_unit(quantity, regex_suffix, optional_unit=False):
match = pattern.match(string(value)) match = pattern.match(string(value))
if match is None: if match is None:
raise Invalid(u"Expected {} with unit, got {}".format(quantity, value)) raise Invalid(f"Expected {quantity} with unit, got {value}")
mantissa = float(match.group(1)) mantissa = float(match.group(1))
if match.group(2) not in METRIC_SUFFIXES: if match.group(2) not in METRIC_SUFFIXES:
raise Invalid(u"Invalid {} suffix {}".format(quantity, match.group(2))) raise Invalid("Invalid {} suffix {}".format(quantity, match.group(2)))
multiplier = METRIC_SUFFIXES[match.group(2)] multiplier = METRIC_SUFFIXES[match.group(2)]
return mantissa * multiplier return mantissa * multiplier
@ -606,30 +601,17 @@ def float_with_unit(quantity, regex_suffix, optional_unit=False):
return validator return validator
frequency = float_with_unit("frequency", u"(Hz|HZ|hz)?") frequency = float_with_unit("frequency", "(Hz|HZ|hz)?")
resistance = float_with_unit("resistance", u"(Ω|Ω|ohm|Ohm|OHM)?") resistance = float_with_unit("resistance", "(Ω|Ω|ohm|Ohm|OHM)?")
current = float_with_unit("current", u"(a|A|amp|Amp|amps|Amps|ampere|Ampere)?") current = float_with_unit("current", "(a|A|amp|Amp|amps|Amps|ampere|Ampere)?")
voltage = float_with_unit("voltage", u"(v|V|volt|Volts)?") voltage = float_with_unit("voltage", "(v|V|volt|Volts)?")
distance = float_with_unit("distance", u"(m)") distance = float_with_unit("distance", "(m)")
framerate = float_with_unit("framerate", u"(FPS|fps|Fps|FpS|Hz)") framerate = float_with_unit("framerate", "(FPS|fps|Fps|FpS|Hz)")
angle = float_with_unit("angle", u"(°|deg)", optional_unit=True) angle = float_with_unit("angle", "(°|deg)", optional_unit=True)
_temperature_c = float_with_unit("temperature", u"(°C|° C|°|C)?") _temperature_c = float_with_unit("temperature", "(°C|° C|°|C)?")
_temperature_k = float_with_unit("temperature", u"(° K|° K|K)?") _temperature_k = float_with_unit("temperature", "(° K|° K|K)?")
_temperature_f = float_with_unit("temperature", u"(°F|° F|F)?") _temperature_f = float_with_unit("temperature", "(°F|° F|F)?")
decibel = float_with_unit("decibel", u"(dB|dBm|db|dbm)", optional_unit=True) decibel = float_with_unit("decibel", "(dB|dBm|db|dbm)", optional_unit=True)
if IS_PY2:
# Override voluptuous invalid to unicode for py2
def _vol_invalid_unicode(self):
path = u' @ data[%s]' % u']['.join(map(repr, self.path)) \
if self.path else u''
# pylint: disable=no-member
output = decode_text(self.message)
if self.error_type:
output += u' for ' + self.error_type
return output + path
Invalid.__unicode__ = _vol_invalid_unicode
def temperature(value): def temperature(value):
@ -672,15 +654,15 @@ def validate_bytes(value):
match = re.match(r"^([0-9]+)\s*(\w*?)(?:byte|B|b)?s?$", value) match = re.match(r"^([0-9]+)\s*(\w*?)(?:byte|B|b)?s?$", value)
if match is None: if match is None:
raise Invalid(u"Expected number of bytes with unit, got {}".format(value)) raise Invalid(f"Expected number of bytes with unit, got {value}")
mantissa = int(match.group(1)) mantissa = int(match.group(1))
if match.group(2) not in METRIC_SUFFIXES: if match.group(2) not in METRIC_SUFFIXES:
raise Invalid(u"Invalid metric suffix {}".format(match.group(2))) raise Invalid("Invalid metric suffix {}".format(match.group(2)))
multiplier = METRIC_SUFFIXES[match.group(2)] multiplier = METRIC_SUFFIXES[match.group(2)]
if multiplier < 1: if multiplier < 1:
raise Invalid(u"Only suffixes with positive exponents are supported. " raise Invalid("Only suffixes with positive exponents are supported. "
u"Got {}".format(match.group(2))) "Got {}".format(match.group(2)))
return int(mantissa * multiplier) return int(mantissa * multiplier)
@ -701,7 +683,7 @@ def domain(value):
try: try:
return str(ipv4(value)) return str(ipv4(value))
except Invalid: except Invalid:
raise Invalid("Invalid domain: {}".format(value)) raise Invalid(f"Invalid domain: {value}")
def domain_name(value): def domain_name(value):
@ -730,7 +712,7 @@ def ssid(value):
def ipv4(value): def ipv4(value):
if isinstance(value, list): if isinstance(value, list):
parts = value parts = value
elif isinstance(value, string_types): elif isinstance(value, str):
parts = value.split('.') parts = value.split('.')
elif isinstance(value, IPAddress): elif isinstance(value, IPAddress):
return value return value
@ -806,7 +788,7 @@ def mqtt_qos(value):
try: try:
value = int(value) value = int(value)
except (TypeError, ValueError): except (TypeError, ValueError):
raise Invalid(u"MQTT Quality of Service must be integer, got {}".format(value)) raise Invalid(f"MQTT Quality of Service must be integer, got {value}")
return one_of(0, 1, 2)(value) return one_of(0, 1, 2)(value)
@ -814,7 +796,7 @@ def requires_component(comp):
"""Validate that this option can only be specified when the component `comp` is loaded.""" """Validate that this option can only be specified when the component `comp` is loaded."""
def validator(value): def validator(value):
if comp not in CORE.raw_config: if comp not in CORE.raw_config:
raise Invalid("This option requires component {}".format(comp)) raise Invalid(f"This option requires component {comp}")
return value return value
return validator return validator
@ -839,7 +821,7 @@ def percentage(value):
def possibly_negative_percentage(value): def possibly_negative_percentage(value):
has_percent_sign = isinstance(value, string_types) and value.endswith('%') has_percent_sign = isinstance(value, str) and value.endswith('%')
if has_percent_sign: if has_percent_sign:
value = float(value[:-1].rstrip()) / 100.0 value = float(value[:-1].rstrip()) / 100.0
if value > 1: if value > 1:
@ -856,7 +838,7 @@ def possibly_negative_percentage(value):
def percentage_int(value): def percentage_int(value):
if isinstance(value, string_types) and value.endswith('%'): if isinstance(value, str) and value.endswith('%'):
value = int(value[:-1].rstrip()) value = int(value[:-1].rstrip())
return value return value
@ -916,7 +898,7 @@ def one_of(*values, **kwargs):
- *float* (``bool``, default=False): Whether to convert the incoming values to floats. - *float* (``bool``, default=False): Whether to convert the incoming values to floats.
- *space* (``str``, default=' '): What to convert spaces in the input string to. - *space* (``str``, default=' '): What to convert spaces in the input string to.
""" """
options = u', '.join(u"'{}'".format(x) for x in values) options = ', '.join(f"'{x}'" for x in values)
lower = kwargs.pop('lower', False) lower = kwargs.pop('lower', False)
upper = kwargs.pop('upper', False) upper = kwargs.pop('upper', False)
string_ = kwargs.pop('string', False) or lower or upper string_ = kwargs.pop('string', False) or lower or upper
@ -940,13 +922,13 @@ def one_of(*values, **kwargs):
value = Upper(value) value = Upper(value)
if value not in values: if value not in values:
import difflib import difflib
options_ = [text_type(x) for x in values] options_ = [str(x) for x in values]
option = text_type(value) option = str(value)
matches = difflib.get_close_matches(option, options_) matches = difflib.get_close_matches(option, options_)
if matches: if matches:
raise Invalid(u"Unknown value '{}', did you mean {}?" raise Invalid("Unknown value '{}', did you mean {}?"
u"".format(value, u", ".join(u"'{}'".format(x) for x in matches))) "".format(value, ", ".join(f"'{x}'" for x in matches)))
raise Invalid(u"Unknown value '{}', valid options are {}.".format(value, options)) raise Invalid(f"Unknown value '{value}', valid options are {options}.")
return value return value
return validator return validator
@ -996,7 +978,7 @@ def returning_lambda(value):
Additionally, make sure the lambda returns something. Additionally, make sure the lambda returns something.
""" """
value = lambda_(value) value = lambda_(value)
if u'return' not in value.value: if 'return' not in value.value:
raise Invalid("Lambda doesn't contain a 'return' statement, but the lambda " raise Invalid("Lambda doesn't contain a 'return' statement, but the lambda "
"is expected to return a value. \n" "is expected to return a value. \n"
"Please make sure the lambda contains at least one " "Please make sure the lambda contains at least one "
@ -1007,24 +989,23 @@ def returning_lambda(value):
def dimensions(value): def dimensions(value):
if isinstance(value, list): if isinstance(value, list):
if len(value) != 2: if len(value) != 2:
raise Invalid(u"Dimensions must have a length of two, not {}".format(len(value))) raise Invalid("Dimensions must have a length of two, not {}".format(len(value)))
try: try:
width, height = int(value[0]), int(value[1]) width, height = int(value[0]), int(value[1])
except ValueError: except ValueError:
raise Invalid(u"Width and height dimensions must be integers") raise Invalid("Width and height dimensions must be integers")
if width <= 0 or height <= 0: if width <= 0 or height <= 0:
raise Invalid(u"Width and height must at least be 1") raise Invalid("Width and height must at least be 1")
return [width, height] return [width, height]
value = string(value) value = string(value)
match = re.match(r"\s*([0-9]+)\s*[xX]\s*([0-9]+)\s*", value) match = re.match(r"\s*([0-9]+)\s*[xX]\s*([0-9]+)\s*", value)
if not match: if not match:
raise Invalid(u"Invalid value '{}' for dimensions. Only WIDTHxHEIGHT is allowed.") raise Invalid("Invalid value '{}' for dimensions. Only WIDTHxHEIGHT is allowed.")
return dimensions([match.group(1), match.group(2)]) return dimensions([match.group(1), match.group(2)])
def directory(value): def directory(value):
import json import json
from esphome.py_compat import safe_input
value = string(value) value = string(value)
path = CORE.relative_config_path(value) path = CORE.relative_config_path(value)
@ -1034,25 +1015,24 @@ def directory(value):
'type': 'check_directory_exists', 'type': 'check_directory_exists',
'path': path, 'path': path,
})) }))
data = json.loads(safe_input()) data = json.loads(input())
assert data['type'] == 'directory_exists_response' assert data['type'] == 'directory_exists_response'
if data['content']: if data['content']:
return value return value
raise Invalid(u"Could not find directory '{}'. Please make sure it exists (full path: {})." raise Invalid("Could not find directory '{}'. Please make sure it exists (full path: {})."
u"".format(path, os.path.abspath(path))) "".format(path, os.path.abspath(path)))
if not os.path.exists(path): if not os.path.exists(path):
raise Invalid(u"Could not find directory '{}'. Please make sure it exists (full path: {})." raise Invalid("Could not find directory '{}'. Please make sure it exists (full path: {})."
u"".format(path, os.path.abspath(path))) "".format(path, os.path.abspath(path)))
if not os.path.isdir(path): if not os.path.isdir(path):
raise Invalid(u"Path '{}' is not a directory (full path: {})." raise Invalid("Path '{}' is not a directory (full path: {})."
u"".format(path, os.path.abspath(path))) "".format(path, os.path.abspath(path)))
return value return value
def file_(value): def file_(value):
import json import json
from esphome.py_compat import safe_input
value = string(value) value = string(value)
path = CORE.relative_config_path(value) path = CORE.relative_config_path(value)
@ -1062,19 +1042,19 @@ def file_(value):
'type': 'check_file_exists', 'type': 'check_file_exists',
'path': path, 'path': path,
})) }))
data = json.loads(safe_input()) data = json.loads(input())
assert data['type'] == 'file_exists_response' assert data['type'] == 'file_exists_response'
if data['content']: if data['content']:
return value return value
raise Invalid(u"Could not find file '{}'. Please make sure it exists (full path: {})." raise Invalid("Could not find file '{}'. Please make sure it exists (full path: {})."
u"".format(path, os.path.abspath(path))) "".format(path, os.path.abspath(path)))
if not os.path.exists(path): if not os.path.exists(path):
raise Invalid(u"Could not find file '{}'. Please make sure it exists (full path: {})." raise Invalid("Could not find file '{}'. Please make sure it exists (full path: {})."
u"".format(path, os.path.abspath(path))) "".format(path, os.path.abspath(path)))
if not os.path.isfile(path): if not os.path.isfile(path):
raise Invalid(u"Path '{}' is not a file (full path: {})." raise Invalid("Path '{}' is not a file (full path: {})."
u"".format(path, os.path.abspath(path))) "".format(path, os.path.abspath(path)))
return value return value
@ -1092,7 +1072,7 @@ def entity_id(value):
for x in value.split('.'): for x in value.split('.'):
for c in x: for c in x:
if c not in ENTITY_ID_CHARACTERS: if c not in ENTITY_ID_CHARACTERS:
raise Invalid("Invalid character for entity ID: {}".format(c)) raise Invalid(f"Invalid character for entity ID: {c}")
return value return value
@ -1103,9 +1083,9 @@ def extract_keys(schema):
assert isinstance(schema, dict) assert isinstance(schema, dict)
keys = [] keys = []
for skey in list(schema.keys()): for skey in list(schema.keys()):
if isinstance(skey, string_types): if isinstance(skey, str):
keys.append(skey) keys.append(skey)
elif isinstance(skey, vol.Marker) and isinstance(skey.schema, string_types): elif isinstance(skey, vol.Marker) and isinstance(skey.schema, str):
keys.append(skey.schema) keys.append(skey.schema)
else: else:
raise ValueError() raise ValueError()
@ -1136,14 +1116,14 @@ class GenerateID(Optional):
"""Mark this key as being an auto-generated ID key.""" """Mark this key as being an auto-generated ID key."""
def __init__(self, key=CONF_ID): def __init__(self, key=CONF_ID):
super(GenerateID, self).__init__(key, default=lambda: None) super().__init__(key, default=lambda: None)
class SplitDefault(Optional): class SplitDefault(Optional):
"""Mark this key to have a split default for ESP8266/ESP32.""" """Mark this key to have a split default for ESP8266/ESP32."""
def __init__(self, key, esp8266=vol.UNDEFINED, esp32=vol.UNDEFINED): def __init__(self, key, esp8266=vol.UNDEFINED, esp32=vol.UNDEFINED):
super(SplitDefault, self).__init__(key) super().__init__(key)
self._esp8266_default = vol.default_factory(esp8266) self._esp8266_default = vol.default_factory(esp8266)
self._esp32_default = vol.default_factory(esp32) self._esp32_default = vol.default_factory(esp32)
@ -1165,7 +1145,7 @@ class OnlyWith(Optional):
"""Set the default value only if the given component is loaded.""" """Set the default value only if the given component is loaded."""
def __init__(self, key, component, default=None): def __init__(self, key, component, default=None):
super(OnlyWith, self).__init__(key) super().__init__(key)
self._component = component self._component = component
self._default = vol.default_factory(default) self._default = vol.default_factory(default)
@ -1207,21 +1187,21 @@ def validate_registry_entry(name, registry):
ignore_keys = extract_keys(base_schema) ignore_keys = extract_keys(base_schema)
def validator(value): def validator(value):
if isinstance(value, string_types): if isinstance(value, str):
value = {value: {}} value = {value: {}}
if not isinstance(value, dict): if not isinstance(value, dict):
raise Invalid(u"{} must consist of key-value mapping! Got {}" raise Invalid("{} must consist of key-value mapping! Got {}"
u"".format(name.title(), value)) "".format(name.title(), value))
key = next((x for x in value if x not in ignore_keys), None) key = next((x for x in value if x not in ignore_keys), None)
if key is None: if key is None:
raise Invalid(u"Key missing from {}! Got {}".format(name, value)) raise Invalid(f"Key missing from {name}! Got {value}")
if key not in registry: if key not in registry:
raise Invalid(u"Unable to find {} with the name '{}'".format(name, key), [key]) raise Invalid(f"Unable to find {name} with the name '{key}'", [key])
key2 = next((x for x in value if x != key and x not in ignore_keys), None) key2 = next((x for x in value if x != key and x not in ignore_keys), None)
if key2 is not None: if key2 is not None:
raise Invalid(u"Cannot have two {0}s in one item. Key '{1}' overrides '{2}'! " raise Invalid("Cannot have two {0}s in one item. Key '{1}' overrides '{2}'! "
u"Did you forget to indent the block inside the {0}?" "Did you forget to indent the block inside the {0}?"
u"".format(name, key, key2)) "".format(name, key, key2))
if value[key] is None: if value[key] is None:
value[key] = {} value[key] = {}
@ -1296,7 +1276,7 @@ def polling_component_schema(default_update_interval):
return COMPONENT_SCHEMA.extend({ return COMPONENT_SCHEMA.extend({
Required(CONF_UPDATE_INTERVAL): default_update_interval, Required(CONF_UPDATE_INTERVAL): default_update_interval,
}) })
assert isinstance(default_update_interval, string_types) assert isinstance(default_update_interval, str)
return COMPONENT_SCHEMA.extend({ return COMPONENT_SCHEMA.extend({
Optional(CONF_UPDATE_INTERVAL, default=default_update_interval): update_interval, Optional(CONF_UPDATE_INTERVAL, default=default_update_interval): update_interval,
}) })

View file

@ -1,17 +1,16 @@
# coding=utf-8
"""Constants used by esphome.""" """Constants used by esphome."""
MAJOR_VERSION = 1 MAJOR_VERSION = 1
MINOR_VERSION = 15 MINOR_VERSION = 15
PATCH_VERSION = '0-dev' PATCH_VERSION = '0-dev'
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __short_version__ = f'{MAJOR_VERSION}.{MINOR_VERSION}'
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) __version__ = f'{__short_version__}.{PATCH_VERSION}'
ESP_PLATFORM_ESP32 = 'ESP32' ESP_PLATFORM_ESP32 = 'ESP32'
ESP_PLATFORM_ESP8266 = 'ESP8266' ESP_PLATFORM_ESP8266 = 'ESP8266'
ESP_PLATFORMS = [ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266] ESP_PLATFORMS = [ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266]
ALLOWED_NAME_CHARS = u'abcdefghijklmnopqrstuvwxyz0123456789_' ALLOWED_NAME_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_'
ARDUINO_VERSION_ESP32_DEV = 'https://github.com/platformio/platform-espressif32.git#feature/stage' ARDUINO_VERSION_ESP32_DEV = 'https://github.com/platformio/platform-espressif32.git#feature/stage'
ARDUINO_VERSION_ESP32_1_0_0 = 'espressif32@1.5.0' ARDUINO_VERSION_ESP32_1_0_0 = 'espressif32@1.5.0'
ARDUINO_VERSION_ESP32_1_0_1 = 'espressif32@1.6.0' ARDUINO_VERSION_ESP32_1_0_1 = 'espressif32@1.6.0'
@ -544,12 +543,12 @@ ICON_WEATHER_WINDY = 'mdi:weather-windy'
ICON_WIFI = 'mdi:wifi' ICON_WIFI = 'mdi:wifi'
UNIT_AMPERE = 'A' UNIT_AMPERE = 'A'
UNIT_CELSIUS = u'°C' UNIT_CELSIUS = '°C'
UNIT_COUNTS_PER_CUBIC_METER = u'#/m³' UNIT_COUNTS_PER_CUBIC_METER = '#/m³'
UNIT_DECIBEL = 'dB' UNIT_DECIBEL = 'dB'
UNIT_DECIBEL_MILLIWATT = 'dBm' UNIT_DECIBEL_MILLIWATT = 'dBm'
UNIT_DEGREE_PER_SECOND = u'°/s' UNIT_DEGREE_PER_SECOND = '°/s'
UNIT_DEGREES = u'°' UNIT_DEGREES = '°'
UNIT_EMPTY = '' UNIT_EMPTY = ''
UNIT_G = 'G' UNIT_G = 'G'
UNIT_HECTOPASCAL = 'hPa' UNIT_HECTOPASCAL = 'hPa'
@ -559,12 +558,12 @@ UNIT_KILOMETER = 'km'
UNIT_KILOMETER_PER_HOUR = 'km/h' UNIT_KILOMETER_PER_HOUR = 'km/h'
UNIT_LUX = 'lx' UNIT_LUX = 'lx'
UNIT_METER = 'm' UNIT_METER = 'm'
UNIT_METER_PER_SECOND_SQUARED = u'm/s²' UNIT_METER_PER_SECOND_SQUARED = 'm/s²'
UNIT_MICROGRAMS_PER_CUBIC_METER = u'µg/m³' UNIT_MICROGRAMS_PER_CUBIC_METER = 'µg/m³'
UNIT_MICROMETER = 'µm' UNIT_MICROMETER = 'µm'
UNIT_MICROSIEMENS_PER_CENTIMETER = u'µS/cm' UNIT_MICROSIEMENS_PER_CENTIMETER = 'µS/cm'
UNIT_MICROTESLA = u'µT' UNIT_MICROTESLA = 'µT'
UNIT_OHM = u'Ω' UNIT_OHM = 'Ω'
UNIT_PARTS_PER_BILLION = 'ppb' UNIT_PARTS_PER_BILLION = 'ppb'
UNIT_PARTS_PER_MILLION = 'ppm' UNIT_PARTS_PER_MILLION = 'ppm'
UNIT_PERCENT = '%' UNIT_PERCENT = '%'

View file

@ -13,7 +13,6 @@ from typing import Any, Dict, List, Optional, Set # noqa
from esphome.const import CONF_ARDUINO_VERSION, SOURCE_FILE_EXTENSIONS, \ from esphome.const import CONF_ARDUINO_VERSION, SOURCE_FILE_EXTENSIONS, \
CONF_COMMENT, CONF_ESPHOME, CONF_USE_ADDRESS, CONF_WIFI CONF_COMMENT, CONF_ESPHOME, CONF_USE_ADDRESS, CONF_WIFI
from esphome.helpers import ensure_unique_string, is_hassio from esphome.helpers import ensure_unique_string, is_hassio
from esphome.py_compat import IS_PY2, integer_types, text_type, string_types
from esphome.util import OrderedDict from esphome.util import OrderedDict
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -23,53 +22,47 @@ class EsphomeError(Exception):
"""General ESPHome exception occurred.""" """General ESPHome exception occurred."""
if IS_PY2: class HexInt(int):
base_int = long
else:
base_int = int
class HexInt(base_int):
def __str__(self): def __str__(self):
if 0 <= self <= 255: if 0 <= self <= 255:
return "0x{:02X}".format(self) return f"0x{self:02X}"
return "0x{:X}".format(self) return f"0x{self:X}"
class IPAddress(object): class IPAddress:
def __init__(self, *args): def __init__(self, *args):
if len(args) != 4: if len(args) != 4:
raise ValueError(u"IPAddress must consist up 4 items") raise ValueError("IPAddress must consist up 4 items")
self.args = args self.args = args
def __str__(self): def __str__(self):
return '.'.join(str(x) for x in self.args) return '.'.join(str(x) for x in self.args)
class MACAddress(object): class MACAddress:
def __init__(self, *parts): def __init__(self, *parts):
if len(parts) != 6: if len(parts) != 6:
raise ValueError(u"MAC Address must consist of 6 items") raise ValueError("MAC Address must consist of 6 items")
self.parts = parts self.parts = parts
def __str__(self): def __str__(self):
return ':'.join('{:02X}'.format(part) for part in self.parts) return ':'.join(f'{part:02X}' for part in self.parts)
@property @property
def as_hex(self): def as_hex(self):
from esphome.cpp_generator import RawExpression from esphome.cpp_generator import RawExpression
num = ''.join('{:02X}'.format(part) for part in self.parts) num = ''.join(f'{part:02X}' for part in self.parts)
return RawExpression('0x{}ULL'.format(num)) return RawExpression(f'0x{num}ULL')
def is_approximately_integer(value): def is_approximately_integer(value):
if isinstance(value, integer_types): if isinstance(value, int):
return True return True
return abs(value - round(value)) < 0.001 return abs(value - round(value)) < 0.001
class TimePeriod(object): class TimePeriod:
def __init__(self, microseconds=None, milliseconds=None, seconds=None, def __init__(self, microseconds=None, milliseconds=None, seconds=None,
minutes=None, hours=None, days=None): minutes=None, hours=None, days=None):
if days is not None: if days is not None:
@ -137,17 +130,17 @@ class TimePeriod(object):
def __str__(self): def __str__(self):
if self.microseconds is not None: if self.microseconds is not None:
return '{}us'.format(self.total_microseconds) return f'{self.total_microseconds}us'
if self.milliseconds is not None: if self.milliseconds is not None:
return '{}ms'.format(self.total_milliseconds) return f'{self.total_milliseconds}ms'
if self.seconds is not None: if self.seconds is not None:
return '{}s'.format(self.total_seconds) return f'{self.total_seconds}s'
if self.minutes is not None: if self.minutes is not None:
return '{}min'.format(self.total_minutes) return f'{self.total_minutes}min'
if self.hours is not None: if self.hours is not None:
return '{}h'.format(self.total_hours) return f'{self.total_hours}h'
if self.days is not None: if self.days is not None:
return '{}d'.format(self.total_days) return f'{self.total_days}d'
return '0s' return '0s'
@property @property
@ -224,7 +217,7 @@ class TimePeriodMinutes(TimePeriod):
LAMBDA_PROG = re.compile(r'id\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)(\.?)') LAMBDA_PROG = re.compile(r'id\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)(\.?)')
class Lambda(object): class Lambda:
def __init__(self, value): def __init__(self, value):
# pylint: disable=protected-access # pylint: disable=protected-access
if isinstance(value, Lambda): if isinstance(value, Lambda):
@ -260,10 +253,10 @@ class Lambda(object):
return self.value return self.value
def __repr__(self): def __repr__(self):
return u'Lambda<{}>'.format(self.value) return f'Lambda<{self.value}>'
class ID(object): class ID:
def __init__(self, id, is_declaration=False, type=None, is_manual=None): def __init__(self, id, is_declaration=False, type=None, is_manual=None):
self.id = id self.id = id
if is_manual is None: if is_manual is None:
@ -289,7 +282,7 @@ class ID(object):
return self.id return self.id
def __repr__(self): def __repr__(self):
return u'ID<{} declaration={}, type={}, manual={}>'.format( return 'ID<{} declaration={}, type={}, manual={}>'.format(
self.id, self.is_declaration, self.type, self.is_manual) self.id, self.is_declaration, self.type, self.is_manual)
def __eq__(self, other): def __eq__(self, other):
@ -305,10 +298,10 @@ class ID(object):
is_manual=self.is_manual) is_manual=self.is_manual)
class DocumentLocation(object): class DocumentLocation:
def __init__(self, document, line, column): def __init__(self, document, line, column):
# type: (basestring, int, int) -> None # type: (str, int, int) -> None
self.document = document # type: basestring self.document = document # type: str
self.line = line # type: int self.line = line # type: int
self.column = column # type: int self.column = column # type: int
@ -321,10 +314,10 @@ class DocumentLocation(object):
) )
def __str__(self): def __str__(self):
return u'{} {}:{}'.format(self.document, self.line, self.column) return f'{self.document} {self.line}:{self.column}'
class DocumentRange(object): class DocumentRange:
def __init__(self, start_mark, end_mark): def __init__(self, start_mark, end_mark):
# type: (DocumentLocation, DocumentLocation) -> None # type: (DocumentLocation, DocumentLocation) -> None
self.start_mark = start_mark # type: DocumentLocation self.start_mark = start_mark # type: DocumentLocation
@ -338,10 +331,10 @@ class DocumentRange(object):
) )
def __str__(self): def __str__(self):
return u'[{} - {}]'.format(self.start_mark, self.end_mark) return f'[{self.start_mark} - {self.end_mark}]'
class Define(object): class Define:
def __init__(self, name, value=None): def __init__(self, name, value=None):
self.name = name self.name = name
self.value = value self.value = value
@ -349,14 +342,14 @@ class Define(object):
@property @property
def as_build_flag(self): def as_build_flag(self):
if self.value is None: if self.value is None:
return u'-D{}'.format(self.name) return f'-D{self.name}'
return u'-D{}={}'.format(self.name, self.value) return f'-D{self.name}={self.value}'
@property @property
def as_macro(self): def as_macro(self):
if self.value is None: if self.value is None:
return u'#define {}'.format(self.name) return f'#define {self.name}'
return u'#define {} {}'.format(self.name, self.value) return f'#define {self.name} {self.value}'
@property @property
def as_tuple(self): def as_tuple(self):
@ -369,7 +362,7 @@ class Define(object):
return isinstance(self, type(other)) and self.as_tuple == other.as_tuple return isinstance(self, type(other)) and self.as_tuple == other.as_tuple
class Library(object): class Library:
def __init__(self, name, version): def __init__(self, name, version):
self.name = name self.name = name
self.version = version self.version = version
@ -378,7 +371,7 @@ class Library(object):
def as_lib_dep(self): def as_lib_dep(self):
if self.version is None: if self.version is None:
return self.name return self.name
return u'{}@{}'.format(self.name, self.version) return f'{self.name}@{self.version}'
@property @property
def as_tuple(self): def as_tuple(self):
@ -461,7 +454,7 @@ def find_source_files(file):
# pylint: disable=too-many-instance-attributes,too-many-public-methods # pylint: disable=too-many-instance-attributes,too-many-public-methods
class EsphomeCore(object): class EsphomeCore:
def __init__(self): def __init__(self):
# True if command is run from dashboard # True if command is run from dashboard
self.dashboard = False self.dashboard = False
@ -499,7 +492,7 @@ class EsphomeCore(object):
# A set of build flags to set in the platformio project # A set of build flags to set in the platformio project
self.build_flags = set() # type: Set[str] self.build_flags = set() # type: Set[str]
# A set of defines to set for the compile process in esphome/core/defines.h # A set of defines to set for the compile process in esphome/core/defines.h
self.defines = set() # type: Set[Define] self.defines = set() # type: Set['Define']
# A dictionary of started coroutines, used to warn when a coroutine was not # A dictionary of started coroutines, used to warn when a coroutine was not
# awaited. # awaited.
self.active_coroutines = {} # type: Dict[int, Any] self.active_coroutines = {} # type: Dict[int, Any]
@ -634,15 +627,15 @@ class EsphomeCore(object):
# Print not-awaited coroutines # Print not-awaited coroutines
for obj in self.active_coroutines.values(): for obj in self.active_coroutines.values():
_LOGGER.warning(u"Coroutine '%s' %s was never awaited with 'yield'.", obj.__name__, obj) _LOGGER.warning("Coroutine '%s' %s was never awaited with 'yield'.", obj.__name__, obj)
_LOGGER.warning(u"Please file a bug report with your configuration.") _LOGGER.warning("Please file a bug report with your configuration.")
if self.active_coroutines: if self.active_coroutines:
raise EsphomeError() raise EsphomeError()
if self.component_ids: if self.component_ids:
comps = u', '.join(u"'{}'".format(x) for x in self.component_ids) comps = ', '.join(f"'{x}'" for x in self.component_ids)
_LOGGER.warning(u"Components %s were never registered. Please create a bug report", _LOGGER.warning("Components %s were never registered. Please create a bug report",
comps) comps)
_LOGGER.warning(u"with your configuration.") _LOGGER.warning("with your configuration.")
raise EsphomeError() raise EsphomeError()
self.active_coroutines.clear() self.active_coroutines.clear()
@ -652,8 +645,8 @@ class EsphomeCore(object):
if isinstance(expression, Expression): if isinstance(expression, Expression):
expression = statement(expression) expression = statement(expression)
if not isinstance(expression, Statement): if not isinstance(expression, Statement):
raise ValueError(u"Add '{}' must be expression or statement, not {}" raise ValueError("Add '{}' must be expression or statement, not {}"
u"".format(expression, type(expression))) "".format(expression, type(expression)))
self.main_statements.append(expression) self.main_statements.append(expression)
_LOGGER.debug("Adding: %s", expression) _LOGGER.debug("Adding: %s", expression)
@ -665,16 +658,16 @@ class EsphomeCore(object):
if isinstance(expression, Expression): if isinstance(expression, Expression):
expression = statement(expression) expression = statement(expression)
if not isinstance(expression, Statement): if not isinstance(expression, Statement):
raise ValueError(u"Add '{}' must be expression or statement, not {}" raise ValueError("Add '{}' must be expression or statement, not {}"
u"".format(expression, type(expression))) "".format(expression, type(expression)))
self.global_statements.append(expression) self.global_statements.append(expression)
_LOGGER.debug("Adding global: %s", expression) _LOGGER.debug("Adding global: %s", expression)
return expression return expression
def add_library(self, library): def add_library(self, library):
if not isinstance(library, Library): if not isinstance(library, Library):
raise ValueError(u"Library {} must be instance of Library, not {}" raise ValueError("Library {} must be instance of Library, not {}"
u"".format(library, type(library))) "".format(library, type(library)))
_LOGGER.debug("Adding library: %s", library) _LOGGER.debug("Adding library: %s", library)
for other in self.libraries[:]: for other in self.libraries[:]:
if other.name != library.name: if other.name != library.name:
@ -689,9 +682,9 @@ class EsphomeCore(object):
if other.version == library.version: if other.version == library.version:
break break
raise ValueError(u"Version pinning failed! Libraries {} and {} " raise ValueError("Version pinning failed! Libraries {} and {} "
u"requested with conflicting versions!" "requested with conflicting versions!"
u"".format(library, other)) "".format(library, other))
else: else:
self.libraries.append(library) self.libraries.append(library)
return library return library
@ -702,20 +695,20 @@ class EsphomeCore(object):
return build_flag return build_flag
def add_define(self, define): def add_define(self, define):
if isinstance(define, string_types): if isinstance(define, str):
define = Define(define) define = Define(define)
elif isinstance(define, Define): elif isinstance(define, Define):
pass pass
else: else:
raise ValueError(u"Define {} must be string or Define, not {}" raise ValueError("Define {} must be string or Define, not {}"
u"".format(define, type(define))) "".format(define, type(define)))
self.defines.add(define) self.defines.add(define)
_LOGGER.debug("Adding define: %s", define) _LOGGER.debug("Adding define: %s", define)
return define return define
def get_variable(self, id): def get_variable(self, id):
if not isinstance(id, ID): if not isinstance(id, ID):
raise ValueError("ID {!r} must be of type ID!".format(id)) raise ValueError(f"ID {id!r} must be of type ID!")
while True: while True:
if id in self.variables: if id in self.variables:
yield self.variables[id] yield self.variables[id]
@ -735,7 +728,7 @@ class EsphomeCore(object):
def register_variable(self, id, obj): def register_variable(self, id, obj):
if id in self.variables: if id in self.variables:
raise EsphomeError("ID {} is already registered".format(id)) raise EsphomeError(f"ID {id} is already registered")
_LOGGER.debug("Registered variable %s of type %s", id.id, id.type) _LOGGER.debug("Registered variable %s of type %s", id.id, id.type)
self.variables[id] = obj self.variables[id] = obj
@ -748,10 +741,10 @@ class EsphomeCore(object):
main_code = [] main_code = []
for exp in self.main_statements: for exp in self.main_statements:
text = text_type(statement(exp)) text = str(statement(exp))
text = text.rstrip() text = text.rstrip()
main_code.append(text) main_code.append(text)
return u'\n'.join(main_code) + u'\n\n' return '\n'.join(main_code) + '\n\n'
@property @property
def cpp_global_section(self): def cpp_global_section(self):
@ -759,17 +752,17 @@ class EsphomeCore(object):
global_code = [] global_code = []
for exp in self.global_statements: for exp in self.global_statements:
text = text_type(statement(exp)) text = str(statement(exp))
text = text.rstrip() text = text.rstrip()
global_code.append(text) global_code.append(text)
return u'\n'.join(global_code) + u'\n' return '\n'.join(global_code) + '\n'
class AutoLoad(OrderedDict): class AutoLoad(OrderedDict):
pass pass
class EnumValue(object): class EnumValue:
"""Special type used by ESPHome to mark enum values for cv.enum.""" """Special type used by ESPHome to mark enum values for cv.enum."""
@property @property
def enum_value(self): def enum_value(self):

View file

@ -138,6 +138,9 @@ float Component::get_actual_setup_priority() const {
return this->setup_priority_override_; return this->setup_priority_override_;
} }
void Component::set_setup_priority(float priority) { this->setup_priority_override_ = priority; } void Component::set_setup_priority(float priority) { this->setup_priority_override_ = priority; }
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpmf-conversions"
bool Component::has_overridden_loop() const { bool Component::has_overridden_loop() const {
#ifdef CLANG_TIDY #ifdef CLANG_TIDY
bool loop_overridden = true; bool loop_overridden = true;
@ -148,6 +151,7 @@ bool Component::has_overridden_loop() const {
#endif #endif
return loop_overridden || call_loop_overridden; return loop_overridden || call_loop_overridden;
} }
#pragma GCC diagnostic pop
PollingComponent::PollingComponent(uint32_t update_interval) : Component(), update_interval_(update_interval) {} PollingComponent::PollingComponent(uint32_t update_interval) : Component(), update_interval_(update_interval) {}

View file

@ -37,8 +37,8 @@ def validate_board(value):
raise NotImplementedError raise NotImplementedError
if value not in board_pins: if value not in board_pins:
raise cv.Invalid(u"Could not find board '{}'. Valid boards are {}".format( raise cv.Invalid("Could not find board '{}'. Valid boards are {}".format(
value, u', '.join(sorted(board_pins.keys())))) value, ', '.join(sorted(board_pins.keys()))))
return value return value
@ -108,8 +108,8 @@ def valid_include(value):
value = cv.file_(value) value = cv.file_(value)
_, ext = os.path.splitext(value) _, ext = os.path.splitext(value)
if ext not in VALID_INCLUDE_EXTS: if ext not in VALID_INCLUDE_EXTS:
raise cv.Invalid(u"Include has invalid file extension {} - valid extensions are {}" raise cv.Invalid("Include has invalid file extension {} - valid extensions are {}"
u"".format(ext, ', '.join(VALID_INCLUDE_EXTS))) "".format(ext, ', '.join(VALID_INCLUDE_EXTS)))
return value return value
@ -184,7 +184,7 @@ def include_file(path, basename):
_, ext = os.path.splitext(path) _, ext = os.path.splitext(path)
if ext in ['.h', '.hpp', '.tcc']: if ext in ['.h', '.hpp', '.tcc']:
# Header, add include statement # Header, add include statement
cg.add_global(cg.RawStatement(u'#include "{}"'.format(basename))) cg.add_global(cg.RawStatement(f'#include "{basename}"'))
@coroutine_with_priority(-1000.0) @coroutine_with_priority(-1000.0)
@ -238,7 +238,7 @@ def to_code(config):
ld_script = ld_scripts[1] ld_script = ld_scripts[1]
if ld_script is not None: if ld_script is not None:
cg.add_build_flag('-Wl,-T{}'.format(ld_script)) cg.add_build_flag(f'-Wl,-T{ld_script}')
cg.add_build_flag('-fno-exceptions') cg.add_build_flag('-fno-exceptions')

View file

@ -10,22 +10,21 @@ from esphome.core import ( # noqa
TimePeriodMilliseconds, TimePeriodMinutes, TimePeriodSeconds, coroutine, Library, Define, TimePeriodMilliseconds, TimePeriodMinutes, TimePeriodSeconds, coroutine, Library, Define,
EnumValue) EnumValue)
from esphome.helpers import cpp_string_escape, indent_all_but_first_and_last from esphome.helpers import cpp_string_escape, indent_all_but_first_and_last
from esphome.py_compat import integer_types, string_types, text_type
from esphome.util import OrderedDict from esphome.util import OrderedDict
class Expression(object): class Expression:
def __str__(self): def __str__(self):
raise NotImplementedError raise NotImplementedError
SafeExpType = Union[Expression, bool, str, text_type, int, float, TimePeriod, SafeExpType = Union[Expression, bool, str, str, int, float, TimePeriod,
Type[bool], Type[int], Type[float], List[Any]] Type[bool], Type[int], Type[float], List[Any]]
class RawExpression(Expression): class RawExpression(Expression):
def __init__(self, text): # type: (Union[str, unicode]) -> None def __init__(self, text): # type: (Union[str, str]) -> None
super(RawExpression, self).__init__() super().__init__()
self.text = text self.text = text
def __str__(self): def __str__(self):
@ -35,7 +34,7 @@ class RawExpression(Expression):
# pylint: disable=redefined-builtin # pylint: disable=redefined-builtin
class AssignmentExpression(Expression): class AssignmentExpression(Expression):
def __init__(self, type, modifier, name, rhs, obj): def __init__(self, type, modifier, name, rhs, obj):
super(AssignmentExpression, self).__init__() super().__init__()
self.type = type self.type = type
self.modifier = modifier self.modifier = modifier
self.name = name self.name = name
@ -44,24 +43,24 @@ class AssignmentExpression(Expression):
def __str__(self): def __str__(self):
if self.type is None: if self.type is None:
return u"{} = {}".format(self.name, self.rhs) return f"{self.name} = {self.rhs}"
return u"{} {}{} = {}".format(self.type, self.modifier, self.name, self.rhs) return f"{self.type} {self.modifier}{self.name} = {self.rhs}"
class VariableDeclarationExpression(Expression): class VariableDeclarationExpression(Expression):
def __init__(self, type, modifier, name): def __init__(self, type, modifier, name):
super(VariableDeclarationExpression, self).__init__() super().__init__()
self.type = type self.type = type
self.modifier = modifier self.modifier = modifier
self.name = name self.name = name
def __str__(self): def __str__(self):
return u"{} {}{}".format(self.type, self.modifier, self.name) return f"{self.type} {self.modifier}{self.name}"
class ExpressionList(Expression): class ExpressionList(Expression):
def __init__(self, *args): def __init__(self, *args):
super(ExpressionList, self).__init__() super().__init__()
# Remove every None on end # Remove every None on end
args = list(args) args = list(args)
while args and args[-1] is None: while args and args[-1] is None:
@ -69,7 +68,7 @@ class ExpressionList(Expression):
self.args = [safe_exp(arg) for arg in args] self.args = [safe_exp(arg) for arg in args]
def __str__(self): def __str__(self):
text = u", ".join(text_type(x) for x in self.args) text = ", ".join(str(x) for x in self.args)
return indent_all_but_first_and_last(text) return indent_all_but_first_and_last(text)
def __iter__(self): def __iter__(self):
@ -78,11 +77,11 @@ class ExpressionList(Expression):
class TemplateArguments(Expression): class TemplateArguments(Expression):
def __init__(self, *args): # type: (*SafeExpType) -> None def __init__(self, *args): # type: (*SafeExpType) -> None
super(TemplateArguments, self).__init__() super().__init__()
self.args = ExpressionList(*args) self.args = ExpressionList(*args)
def __str__(self): def __str__(self):
return u'<{}>'.format(self.args) return f'<{self.args}>'
def __iter__(self): def __iter__(self):
return iter(self.args) return iter(self.args)
@ -90,7 +89,7 @@ class TemplateArguments(Expression):
class CallExpression(Expression): class CallExpression(Expression):
def __init__(self, base, *args): # type: (Expression, *SafeExpType) -> None def __init__(self, base, *args): # type: (Expression, *SafeExpType) -> None
super(CallExpression, self).__init__() super().__init__()
self.base = base self.base = base
if args and isinstance(args[0], TemplateArguments): if args and isinstance(args[0], TemplateArguments):
self.template_args = args[0] self.template_args = args[0]
@ -101,13 +100,13 @@ class CallExpression(Expression):
def __str__(self): def __str__(self):
if self.template_args is not None: if self.template_args is not None:
return u'{}{}({})'.format(self.base, self.template_args, self.args) return f'{self.base}{self.template_args}({self.args})'
return u'{}({})'.format(self.base, self.args) return f'{self.base}({self.args})'
class StructInitializer(Expression): class StructInitializer(Expression):
def __init__(self, base, *args): # type: (Expression, *Tuple[str, SafeExpType]) -> None def __init__(self, base, *args): # type: (Expression, *Tuple[str, SafeExpType]) -> None
super(StructInitializer, self).__init__() super().__init__()
self.base = base self.base = base
if not isinstance(args, OrderedDict): if not isinstance(args, OrderedDict):
args = OrderedDict(args) args = OrderedDict(args)
@ -119,16 +118,16 @@ class StructInitializer(Expression):
self.args[key] = exp self.args[key] = exp
def __str__(self): def __str__(self):
cpp = u'{}{{\n'.format(self.base) cpp = f'{self.base}{{\n'
for key, value in self.args.items(): for key, value in self.args.items():
cpp += u' .{} = {},\n'.format(key, value) cpp += f' .{key} = {value},\n'
cpp += u'}' cpp += '}'
return cpp return cpp
class ArrayInitializer(Expression): class ArrayInitializer(Expression):
def __init__(self, *args, **kwargs): # type: (*Any, **Any) -> None def __init__(self, *args, **kwargs): # type: (*Any, **Any) -> None
super(ArrayInitializer, self).__init__() super().__init__()
self.multiline = kwargs.get('multiline', False) self.multiline = kwargs.get('multiline', False)
self.args = [] self.args = []
for arg in args: for arg in args:
@ -139,30 +138,30 @@ class ArrayInitializer(Expression):
def __str__(self): def __str__(self):
if not self.args: if not self.args:
return u'{}' return '{}'
if self.multiline: if self.multiline:
cpp = u'{\n' cpp = '{\n'
for arg in self.args: for arg in self.args:
cpp += u' {},\n'.format(arg) cpp += f' {arg},\n'
cpp += u'}' cpp += '}'
else: else:
cpp = u'{' + u', '.join(str(arg) for arg in self.args) + u'}' cpp = '{' + ', '.join(str(arg) for arg in self.args) + '}'
return cpp return cpp
class ParameterExpression(Expression): class ParameterExpression(Expression):
def __init__(self, type, id): def __init__(self, type, id):
super(ParameterExpression, self).__init__() super().__init__()
self.type = safe_exp(type) self.type = safe_exp(type)
self.id = id self.id = id
def __str__(self): def __str__(self):
return u"{} {}".format(self.type, self.id) return f"{self.type} {self.id}"
class ParameterListExpression(Expression): class ParameterListExpression(Expression):
def __init__(self, *parameters): def __init__(self, *parameters):
super(ParameterListExpression, self).__init__() super().__init__()
self.parameters = [] self.parameters = []
for parameter in parameters: for parameter in parameters:
if not isinstance(parameter, ParameterExpression): if not isinstance(parameter, ParameterExpression):
@ -170,12 +169,12 @@ class ParameterListExpression(Expression):
self.parameters.append(parameter) self.parameters.append(parameter)
def __str__(self): def __str__(self):
return u", ".join(text_type(x) for x in self.parameters) return ", ".join(str(x) for x in self.parameters)
class LambdaExpression(Expression): class LambdaExpression(Expression):
def __init__(self, parts, parameters, capture='=', return_type=None): def __init__(self, parts, parameters, capture='=', return_type=None):
super(LambdaExpression, self).__init__() super().__init__()
self.parts = parts self.parts = parts
if not isinstance(parameters, ParameterListExpression): if not isinstance(parameters, ParameterListExpression):
parameters = ParameterListExpression(*parameters) parameters = ParameterListExpression(*parameters)
@ -184,15 +183,15 @@ class LambdaExpression(Expression):
self.return_type = safe_exp(return_type) if return_type is not None else None self.return_type = safe_exp(return_type) if return_type is not None else None
def __str__(self): def __str__(self):
cpp = u'[{}]({})'.format(self.capture, self.parameters) cpp = f'[{self.capture}]({self.parameters})'
if self.return_type is not None: if self.return_type is not None:
cpp += u' -> {}'.format(self.return_type) cpp += f' -> {self.return_type}'
cpp += u' {{\n{}\n}}'.format(self.content) cpp += f' {{\n{self.content}\n}}'
return indent_all_but_first_and_last(cpp) return indent_all_but_first_and_last(cpp)
@property @property
def content(self): def content(self):
return u''.join(text_type(part) for part in self.parts) return ''.join(str(part) for part in self.parts)
class Literal(Expression): class Literal(Expression):
@ -201,41 +200,41 @@ class Literal(Expression):
class StringLiteral(Literal): class StringLiteral(Literal):
def __init__(self, string): # type: (Union[str, unicode]) -> None def __init__(self, string): # type: (Union[str, str]) -> None
super(StringLiteral, self).__init__() super().__init__()
self.string = string self.string = string
def __str__(self): def __str__(self):
return u'{}'.format(cpp_string_escape(self.string)) return '{}'.format(cpp_string_escape(self.string))
class IntLiteral(Literal): class IntLiteral(Literal):
def __init__(self, i): # type: (Union[int, long]) -> None def __init__(self, i): # type: (Union[int]) -> None
super(IntLiteral, self).__init__() super().__init__()
self.i = i self.i = i
def __str__(self): def __str__(self):
if self.i > 4294967295: if self.i > 4294967295:
return u'{}ULL'.format(self.i) return f'{self.i}ULL'
if self.i > 2147483647: if self.i > 2147483647:
return u'{}UL'.format(self.i) return f'{self.i}UL'
if self.i < -2147483648: if self.i < -2147483648:
return u'{}LL'.format(self.i) return f'{self.i}LL'
return text_type(self.i) return str(self.i)
class BoolLiteral(Literal): class BoolLiteral(Literal):
def __init__(self, binary): # type: (bool) -> None def __init__(self, binary): # type: (bool) -> None
super(BoolLiteral, self).__init__() super().__init__()
self.binary = binary self.binary = binary
def __str__(self): def __str__(self):
return u"true" if self.binary else u"false" return "true" if self.binary else "false"
class HexIntLiteral(Literal): class HexIntLiteral(Literal):
def __init__(self, i): # type: (int) -> None def __init__(self, i): # type: (int) -> None
super(HexIntLiteral, self).__init__() super().__init__()
self.i = HexInt(i) self.i = HexInt(i)
def __str__(self): def __str__(self):
@ -244,18 +243,18 @@ class HexIntLiteral(Literal):
class FloatLiteral(Literal): class FloatLiteral(Literal):
def __init__(self, value): # type: (float) -> None def __init__(self, value): # type: (float) -> None
super(FloatLiteral, self).__init__() super().__init__()
self.float_ = value self.float_ = value
def __str__(self): def __str__(self):
if math.isnan(self.float_): if math.isnan(self.float_):
return u"NAN" return "NAN"
return u"{}f".format(self.float_) return f"{self.float_}f"
# pylint: disable=bad-continuation # pylint: disable=bad-continuation
def safe_exp( def safe_exp(
obj # type: Union[Expression, bool, str, unicode, int, long, float, TimePeriod, list] obj # type: Union[Expression, bool, str, int, float, TimePeriod, list]
): ):
# type: (...) -> Expression # type: (...) -> Expression
"""Try to convert obj to an expression by automatically converting native python types to """Try to convert obj to an expression by automatically converting native python types to
@ -269,11 +268,11 @@ def safe_exp(
return safe_exp(obj.enum_value) return safe_exp(obj.enum_value)
if isinstance(obj, bool): if isinstance(obj, bool):
return BoolLiteral(obj) return BoolLiteral(obj)
if isinstance(obj, string_types): if isinstance(obj, str):
return StringLiteral(obj) return StringLiteral(obj)
if isinstance(obj, HexInt): if isinstance(obj, HexInt):
return HexIntLiteral(obj) return HexIntLiteral(obj)
if isinstance(obj, integer_types): if isinstance(obj, int):
return IntLiteral(obj) return IntLiteral(obj)
if isinstance(obj, float): if isinstance(obj, float):
return FloatLiteral(obj) return FloatLiteral(obj)
@ -294,15 +293,15 @@ def safe_exp(
if obj is float: if obj is float:
return float_ return float_
if isinstance(obj, ID): if isinstance(obj, ID):
raise ValueError(u"Object {} is an ID. Did you forget to register the variable?" raise ValueError("Object {} is an ID. Did you forget to register the variable?"
u"".format(obj)) "".format(obj))
if inspect.isgenerator(obj): if inspect.isgenerator(obj):
raise ValueError(u"Object {} is a coroutine. Did you forget to await the expression with " raise ValueError("Object {} is a coroutine. Did you forget to await the expression with "
u"'yield'?".format(obj)) "'yield'?".format(obj))
raise ValueError(u"Object is not an expression", obj) raise ValueError("Object is not an expression", obj)
class Statement(object): class Statement:
def __init__(self): def __init__(self):
pass pass
@ -312,7 +311,7 @@ class Statement(object):
class RawStatement(Statement): class RawStatement(Statement):
def __init__(self, text): def __init__(self, text):
super(RawStatement, self).__init__() super().__init__()
self.text = text self.text = text
def __str__(self): def __str__(self):
@ -321,38 +320,38 @@ class RawStatement(Statement):
class ExpressionStatement(Statement): class ExpressionStatement(Statement):
def __init__(self, expression): def __init__(self, expression):
super(ExpressionStatement, self).__init__() super().__init__()
self.expression = safe_exp(expression) self.expression = safe_exp(expression)
def __str__(self): def __str__(self):
return u"{};".format(self.expression) return f"{self.expression};"
class LineComment(Statement): class LineComment(Statement):
def __init__(self, value): # type: (unicode) -> None def __init__(self, value): # type: (str) -> None
super(LineComment, self).__init__() super().__init__()
self._value = value self._value = value
def __str__(self): def __str__(self):
parts = self._value.split(u'\n') parts = self._value.split('\n')
parts = [u'// {}'.format(x) for x in parts] parts = [f'// {x}' for x in parts]
return u'\n'.join(parts) return '\n'.join(parts)
class ProgmemAssignmentExpression(AssignmentExpression): class ProgmemAssignmentExpression(AssignmentExpression):
def __init__(self, type, name, rhs, obj): def __init__(self, type, name, rhs, obj):
super(ProgmemAssignmentExpression, self).__init__( super().__init__(
type, '', name, rhs, obj type, '', name, rhs, obj
) )
def __str__(self): def __str__(self):
type_ = self.type type_ = self.type
return u"static const {} {}[] PROGMEM = {}".format(type_, self.name, self.rhs) return f"static const {type_} {self.name}[] PROGMEM = {self.rhs}"
def progmem_array(id, rhs): def progmem_array(id, rhs):
rhs = safe_exp(rhs) rhs = safe_exp(rhs)
obj = MockObj(id, u'.') obj = MockObj(id, '.')
assignment = ProgmemAssignmentExpression(id.type, id, rhs, obj) assignment = ProgmemAssignmentExpression(id.type, id, rhs, obj)
CORE.add(assignment) CORE.add(assignment)
CORE.register_variable(id, obj) CORE.register_variable(id, obj)
@ -381,7 +380,7 @@ def variable(id, # type: ID
""" """
assert isinstance(id, ID) assert isinstance(id, ID)
rhs = safe_exp(rhs) rhs = safe_exp(rhs)
obj = MockObj(id, u'.') obj = MockObj(id, '.')
if type is not None: if type is not None:
id.type = type id.type = type
assignment = AssignmentExpression(id.type, '', id, rhs, obj) assignment = AssignmentExpression(id.type, '', id, rhs, obj)
@ -405,7 +404,7 @@ def Pvariable(id, # type: ID
:returns The new variable as a MockObj. :returns The new variable as a MockObj.
""" """
rhs = safe_exp(rhs) rhs = safe_exp(rhs)
obj = MockObj(id, u'->') obj = MockObj(id, '->')
if type is not None: if type is not None:
id.type = type id.type = type
decl = VariableDeclarationExpression(id.type, '*', id) decl = VariableDeclarationExpression(id.type, '*', id)
@ -594,51 +593,51 @@ class MockObj(Expression):
Mostly consists of magic methods that allow ESPHome's codegen syntax. Mostly consists of magic methods that allow ESPHome's codegen syntax.
""" """
def __init__(self, base, op=u'.'): def __init__(self, base, op='.'):
self.base = base self.base = base
self.op = op self.op = op
super(MockObj, self).__init__() super().__init__()
def __getattr__(self, attr): # type: (str) -> MockObj def __getattr__(self, attr): # type: (str) -> MockObj
next_op = u'.' next_op = '.'
if attr.startswith(u'P') and self.op not in ['::', '']: if attr.startswith('P') and self.op not in ['::', '']:
attr = attr[1:] attr = attr[1:]
next_op = u'->' next_op = '->'
if attr.startswith(u'_'): if attr.startswith('_'):
attr = attr[1:] attr = attr[1:]
return MockObj(u'{}{}{}'.format(self.base, self.op, attr), next_op) return MockObj(f'{self.base}{self.op}{attr}', next_op)
def __call__(self, *args): # type: (SafeExpType) -> MockObj def __call__(self, *args): # type: (SafeExpType) -> MockObj
call = CallExpression(self.base, *args) call = CallExpression(self.base, *args)
return MockObj(call, self.op) return MockObj(call, self.op)
def __str__(self): # type: () -> unicode def __str__(self): # type: () -> str
return text_type(self.base) return str(self.base)
def __repr__(self): def __repr__(self):
return u'MockObj<{}>'.format(text_type(self.base)) return 'MockObj<{}>'.format(str(self.base))
@property @property
def _(self): # type: () -> MockObj def _(self): # type: () -> MockObj
return MockObj(u'{}{}'.format(self.base, self.op)) return MockObj(f'{self.base}{self.op}')
@property @property
def new(self): # type: () -> MockObj def new(self): # type: () -> MockObj
return MockObj(u'new {}'.format(self.base), u'->') return MockObj(f'new {self.base}', '->')
def template(self, *args): # type: (*SafeExpType) -> MockObj def template(self, *args): # type: (*SafeExpType) -> MockObj
if len(args) != 1 or not isinstance(args[0], TemplateArguments): if len(args) != 1 or not isinstance(args[0], TemplateArguments):
args = TemplateArguments(*args) args = TemplateArguments(*args)
else: else:
args = args[0] args = args[0]
return MockObj(u'{}{}'.format(self.base, args)) return MockObj(f'{self.base}{args}')
def namespace(self, name): # type: (str) -> MockObj def namespace(self, name): # type: (str) -> MockObj
return MockObj(u'{}{}'.format(self._, name), u'::') return MockObj(f'{self._}{name}', '::')
def class_(self, name, *parents): # type: (str, *MockObjClass) -> MockObjClass def class_(self, name, *parents): # type: (str, *MockObjClass) -> MockObjClass
op = '' if self.op == '' else '::' op = '' if self.op == '' else '::'
return MockObjClass(u'{}{}{}'.format(self.base, op, name), u'.', parents=parents) return MockObjClass(f'{self.base}{op}{name}', '.', parents=parents)
def struct(self, name): # type: (str) -> MockObjClass def struct(self, name): # type: (str) -> MockObjClass
return self.class_(name) return self.class_(name)
@ -648,24 +647,24 @@ class MockObj(Expression):
def operator(self, name): # type: (str) -> MockObj def operator(self, name): # type: (str) -> MockObj
if name == 'ref': if name == 'ref':
return MockObj(u'{} &'.format(self.base), u'') return MockObj(f'{self.base} &', '')
if name == 'ptr': if name == 'ptr':
return MockObj(u'{} *'.format(self.base), u'') return MockObj(f'{self.base} *', '')
if name == "const": if name == "const":
return MockObj(u'const {}'.format(self.base), u'') return MockObj(f'const {self.base}', '')
raise NotImplementedError raise NotImplementedError
@property @property
def using(self): # type: () -> MockObj def using(self): # type: () -> MockObj
assert self.op == '::' assert self.op == '::'
return MockObj(u'using namespace {}'.format(self.base)) return MockObj(f'using namespace {self.base}')
def __getitem__(self, item): # type: (Union[str, Expression]) -> MockObj def __getitem__(self, item): # type: (Union[str, Expression]) -> MockObj
next_op = u'.' next_op = '.'
if isinstance(item, str) and item.startswith(u'P'): if isinstance(item, str) and item.startswith('P'):
item = item[1:] item = item[1:]
next_op = u'->' next_op = '->'
return MockObj(u'{}[{}]'.format(self.base, item), next_op) return MockObj(f'{self.base}[{item}]', next_op)
class MockObjEnum(MockObj): class MockObjEnum(MockObj):
@ -679,13 +678,13 @@ class MockObjEnum(MockObj):
kwargs['base'] = base kwargs['base'] = base
MockObj.__init__(self, *args, **kwargs) MockObj.__init__(self, *args, **kwargs)
def __str__(self): # type: () -> unicode def __str__(self): # type: () -> str
if self._is_class: if self._is_class:
return super(MockObjEnum, self).__str__() return super().__str__()
return u'{}{}{}'.format(self.base, self.op, self._enum) return f'{self.base}{self.op}{self._enum}'
def __repr__(self): def __repr__(self):
return u'MockObj<{}>'.format(text_type(self.base)) return 'MockObj<{}>'.format(str(self.base))
class MockObjClass(MockObj): class MockObjClass(MockObj):
@ -716,7 +715,7 @@ class MockObjClass(MockObj):
args = args[0] args = args[0]
new_parents = self._parents[:] new_parents = self._parents[:]
new_parents.append(self) new_parents.append(self)
return MockObjClass(u'{}{}'.format(self.base, args), parents=new_parents) return MockObjClass(f'{self.base}{args}', parents=new_parents)
def __repr__(self): def __repr__(self):
return u'MockObjClass<{}, parents={}>'.format(text_type(self.base), self._parents) return 'MockObjClass<{}, parents={}>'.format(str(self.base), self._parents)

View file

@ -1,9 +1,10 @@
from esphome.const import CONF_INVERTED, CONF_MODE, CONF_NUMBER, CONF_SETUP_PRIORITY, \ from esphome.const import CONF_INVERTED, CONF_MODE, CONF_NUMBER, CONF_SETUP_PRIORITY, \
CONF_UPDATE_INTERVAL, CONF_TYPE_ID CONF_UPDATE_INTERVAL, CONF_TYPE_ID
from esphome.core import coroutine, ID, CORE # pylint: disable=unused-import
from esphome.core import coroutine, ID, CORE, ConfigType
from esphome.cpp_generator import RawExpression, add, get_variable from esphome.cpp_generator import RawExpression, add, get_variable
from esphome.cpp_types import App, GPIOPin from esphome.cpp_types import App, GPIOPin
from esphome.py_compat import text_type from esphome.util import Registry, RegistryEntry
@coroutine @coroutine
@ -35,11 +36,11 @@ def register_component(var, config):
:param var: The variable representing the component. :param var: The variable representing the component.
:param config: The configuration for the component. :param config: The configuration for the component.
""" """
id_ = text_type(var.base) id_ = str(var.base)
if id_ not in CORE.component_ids: if id_ not in CORE.component_ids:
raise ValueError(u"Component ID {} was not declared to inherit from Component, " raise ValueError("Component ID {} was not declared to inherit from Component, "
u"or was registered twice. Please create a bug report with your " "or was registered twice. Please create a bug report with your "
u"configuration.".format(id_)) "configuration.".format(id_))
CORE.component_ids.remove(id_) CORE.component_ids.remove(id_)
if CONF_SETUP_PRIORITY in config: if CONF_SETUP_PRIORITY in config:
add(var.set_setup_priority(config[CONF_SETUP_PRIORITY])) add(var.set_setup_priority(config[CONF_SETUP_PRIORITY]))
@ -59,7 +60,7 @@ def register_parented(var, value):
def extract_registry_entry_config(registry, full_config): def extract_registry_entry_config(registry, full_config):
# type: ('Registry', 'ConfigType') -> 'RegistryEntry' # type: (Registry, ConfigType) -> RegistryEntry
key, config = next((k, v) for k, v in full_config.items() if k in registry) key, config = next((k, v) for k, v in full_config.items() if k in registry)
return registry[key], config return registry[key], config

View file

@ -1,5 +1,4 @@
# pylint: disable=wrong-import-position # pylint: disable=wrong-import-position
from __future__ import print_function
import codecs import codecs
import collections import collections
@ -29,7 +28,6 @@ import tornado.websocket
from esphome import const, util from esphome import const, util
from esphome.__main__ import get_serial_ports from esphome.__main__ import get_serial_ports
from esphome.helpers import mkdir_p, get_bool_env, run_system_command from esphome.helpers import mkdir_p, get_bool_env, run_system_command
from esphome.py_compat import IS_PY2, decode_text, encode_text
from esphome.storage_json import EsphomeStorageJSON, StorageJSON, \ from esphome.storage_json import EsphomeStorageJSON, StorageJSON, \
esphome_storage_path, ext_storage_path, trash_storage_path esphome_storage_path, ext_storage_path, trash_storage_path
from esphome.util import shlex_quote from esphome.util import shlex_quote
@ -42,7 +40,7 @@ from esphome.zeroconf import DashboardStatus, Zeroconf
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
class DashboardSettings(object): class DashboardSettings:
def __init__(self): def __init__(self):
self.config_dir = '' self.config_dir = ''
self.password_digest = '' self.password_digest = ''
@ -58,9 +56,6 @@ class DashboardSettings(object):
self.username = args.username or os.getenv('USERNAME', '') self.username = args.username or os.getenv('USERNAME', '')
self.using_password = bool(password) self.using_password = bool(password)
if self.using_password: if self.using_password:
if IS_PY2:
self.password_digest = hmac.new(password).digest()
else:
self.password_digest = hmac.new(password.encode()).digest() self.password_digest = hmac.new(password.encode()).digest()
self.config_dir = args.configuration[0] self.config_dir = args.configuration[0]
@ -88,8 +83,8 @@ class DashboardSettings(object):
if username != self.username: if username != self.username:
return False return False
password_digest = hmac.new(encode_text(password)).digest() password = hmac.new(password.encode()).digest()
return hmac.compare_digest(self.password_digest, password_digest) return username == self.username and hmac.compare_digest(self.password_digest, password)
def rel_path(self, *args): def rel_path(self, *args):
return os.path.join(self.config_dir, *args) return os.path.join(self.config_dir, *args)
@ -100,10 +95,7 @@ class DashboardSettings(object):
settings = DashboardSettings() settings = DashboardSettings()
if IS_PY2: cookie_authenticated_yes = b'yes'
cookie_authenticated_yes = 'yes'
else:
cookie_authenticated_yes = b'yes'
def template_args(): def template_args():
@ -181,7 +173,7 @@ def websocket_method(name):
@websocket_class @websocket_class
class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler): class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
def __init__(self, application, request, **kwargs): def __init__(self, application, request, **kwargs):
super(EsphomeCommandWebSocket, self).__init__(application, request, **kwargs) super().__init__(application, request, **kwargs)
self._proc = None self._proc = None
self._is_closed = False self._is_closed = False
@ -204,7 +196,7 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
# spawn can only be called once # spawn can only be called once
return return
command = self.build_command(json_message) command = self.build_command(json_message)
_LOGGER.info(u"Running command '%s'", ' '.join(shlex_quote(x) for x in command)) _LOGGER.info("Running command '%s'", ' '.join(shlex_quote(x) for x in command))
self._proc = tornado.process.Subprocess(command, self._proc = tornado.process.Subprocess(command,
stdout=tornado.process.Subprocess.STREAM, stdout=tornado.process.Subprocess.STREAM,
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
@ -227,9 +219,6 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
@tornado.gen.coroutine @tornado.gen.coroutine
def _redirect_stdout(self): def _redirect_stdout(self):
if IS_PY2:
reg = '[\n\r]'
else:
reg = b'[\n\r]' reg = b'[\n\r]'
while True: while True:
@ -336,8 +325,8 @@ class WizardRequestHandler(BaseHandler):
def post(self): def post(self):
from esphome import wizard from esphome import wizard
kwargs = {k: u''.join(decode_text(x) for x in v) for k, v in self.request.arguments.items()} kwargs = {k: ''.join(str(x) for x in v) for k, v in self.request.arguments.items()}
destination = settings.rel_path(kwargs['name'] + u'.yaml') destination = settings.rel_path(kwargs['name'] + '.yaml')
wizard.wizard_write(path=destination, **kwargs) wizard.wizard_write(path=destination, **kwargs)
self.redirect('./?begin=True') self.redirect('./?begin=True')
@ -355,8 +344,8 @@ class DownloadBinaryRequestHandler(BaseHandler):
path = storage_json.firmware_bin_path path = storage_json.firmware_bin_path
self.set_header('Content-Type', 'application/octet-stream') self.set_header('Content-Type', 'application/octet-stream')
filename = '{}.bin'.format(storage_json.name) filename = f'{storage_json.name}.bin'
self.set_header("Content-Disposition", 'attachment; filename="{}"'.format(filename)) self.set_header("Content-Disposition", f'attachment; filename="{filename}"')
with open(path, 'rb') as f: with open(path, 'rb') as f:
while True: while True:
data = f.read(16384) data = f.read(16384)
@ -371,7 +360,7 @@ def _list_dashboard_entries():
return [DashboardEntry(file) for file in files] return [DashboardEntry(file) for file in files]
class DashboardEntry(object): class DashboardEntry:
def __init__(self, path): def __init__(self, path):
self.path = path self.path = path
self._storage = None self._storage = None
@ -609,8 +598,8 @@ class LoginHandler(BaseHandler):
'X-HASSIO-KEY': os.getenv('HASSIO_TOKEN'), 'X-HASSIO-KEY': os.getenv('HASSIO_TOKEN'),
} }
data = { data = {
'username': decode_text(self.get_argument('username', '')), 'username': self.get_argument('username', ''),
'password': decode_text(self.get_argument('password', '')) 'password': self.get_argument('password', '')
} }
try: try:
req = requests.post('http://hassio/auth', headers=headers, data=data) req = requests.post('http://hassio/auth', headers=headers, data=data)
@ -627,8 +616,8 @@ class LoginHandler(BaseHandler):
self.render_login_page(error="Invalid username or password") self.render_login_page(error="Invalid username or password")
def post_native_login(self): def post_native_login(self):
username = decode_text(self.get_argument("username", '')) username = self.get_argument("username", '')
password = decode_text(self.get_argument("password", '')) password = self.get_argument("password", '')
if settings.check_password(username, password): if settings.check_password(username, password):
self.set_secure_cookie("authenticated", cookie_authenticated_yes) self.set_secure_cookie("authenticated", cookie_authenticated_yes)
self.redirect("/") self.redirect("/")
@ -663,7 +652,7 @@ def get_static_file_url(name):
with open(path, 'rb') as f_handle: with open(path, 'rb') as f_handle:
hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8] hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8]
_STATIC_FILE_HASHES[name] = hash_ _STATIC_FILE_HASHES[name] = hash_
return u'./static/{}?hash={}'.format(name, hash_) return f'./static/{name}?hash={hash_}'
def make_app(debug=False): def make_app(debug=False):
@ -754,7 +743,7 @@ def start_web_server(args):
if args.open_ui: if args.open_ui:
import webbrowser import webbrowser
webbrowser.open('localhost:{}'.format(args.port)) webbrowser.open(f'localhost:{args.port}')
if settings.status_use_ping: if settings.status_use_ping:
status_thread = PingStatusThread() status_thread = PingStatusThread()

View file

@ -7,7 +7,6 @@ import time
from esphome.core import EsphomeError from esphome.core import EsphomeError
from esphome.helpers import is_ip_address, resolve_ip_address from esphome.helpers import is_ip_address, resolve_ip_address
from esphome.py_compat import IS_PY2, char_to_byte
RESPONSE_OK = 0 RESPONSE_OK = 0
RESPONSE_REQUEST_AUTH = 1 RESPONSE_REQUEST_AUTH = 1
@ -38,7 +37,7 @@ MAGIC_BYTES = [0x6C, 0x26, 0xF7, 0x5C, 0x45]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
class ProgressBar(object): class ProgressBar:
def __init__(self): def __init__(self):
self.last_progress = None self.last_progress = None
@ -72,33 +71,31 @@ def recv_decode(sock, amount, decode=True):
data = sock.recv(amount) data = sock.recv(amount)
if not decode: if not decode:
return data return data
return [char_to_byte(x) for x in data] return list(data)
def receive_exactly(sock, amount, msg, expect, decode=True): def receive_exactly(sock, amount, msg, expect, decode=True):
if decode: if decode:
data = [] data = []
elif IS_PY2:
data = ''
else: else:
data = b'' data = b''
try: try:
data += recv_decode(sock, 1, decode=decode) data += recv_decode(sock, 1, decode=decode)
except socket.error as err: except OSError as err:
raise OTAError("Error receiving acknowledge {}: {}".format(msg, err)) raise OTAError(f"Error receiving acknowledge {msg}: {err}")
try: try:
check_error(data, expect) check_error(data, expect)
except OTAError as err: except OTAError as err:
sock.close() sock.close()
raise OTAError("Error {}: {}".format(msg, err)) raise OTAError(f"Error {msg}: {err}")
while len(data) < amount: while len(data) < amount:
try: try:
data += recv_decode(sock, amount - len(data), decode=decode) data += recv_decode(sock, amount - len(data), decode=decode)
except socket.error as err: except OSError as err:
raise OTAError("Error receiving {}: {}".format(msg, err)) raise OTAError(f"Error receiving {msg}: {err}")
return data return data
@ -145,12 +142,6 @@ def check_error(data, expect):
def send_check(sock, data, msg): def send_check(sock, data, msg):
try: try:
if IS_PY2:
if isinstance(data, (list, tuple)):
data = ''.join([chr(x) for x in data])
elif isinstance(data, int):
data = chr(data)
else:
if isinstance(data, (list, tuple)): if isinstance(data, (list, tuple)):
data = bytes(data) data = bytes(data)
elif isinstance(data, int): elif isinstance(data, int):
@ -159,8 +150,8 @@ def send_check(sock, data, msg):
data = data.encode('utf8') data = data.encode('utf8')
sock.sendall(data) sock.sendall(data)
except socket.error as err: except OSError as err:
raise OTAError("Error sending {}: {}".format(msg, err)) raise OTAError(f"Error sending {msg}: {err}")
def perform_ota(sock, password, file_handle, filename): def perform_ota(sock, password, file_handle, filename):
@ -176,7 +167,7 @@ def perform_ota(sock, password, file_handle, filename):
_, version = receive_exactly(sock, 2, 'version', RESPONSE_OK) _, version = receive_exactly(sock, 2, 'version', RESPONSE_OK)
if version != OTA_VERSION_1_0: if version != OTA_VERSION_1_0:
raise OTAError("Unsupported OTA version {}".format(version)) raise OTAError(f"Unsupported OTA version {version}")
# Features # Features
send_check(sock, 0x00, 'features') send_check(sock, 0x00, 'features')
@ -186,9 +177,7 @@ def perform_ota(sock, password, file_handle, filename):
if auth == RESPONSE_REQUEST_AUTH: if auth == RESPONSE_REQUEST_AUTH:
if not password: if not password:
raise OTAError("ESP requests password, but no password given!") raise OTAError("ESP requests password, but no password given!")
nonce = receive_exactly(sock, 32, 'authentication nonce', [], decode=False) nonce = receive_exactly(sock, 32, 'authentication nonce', [], decode=False).decode()
if not IS_PY2:
nonce = nonce.decode()
_LOGGER.debug("Auth: Nonce is %s", nonce) _LOGGER.debug("Auth: Nonce is %s", nonce)
cnonce = hashlib.md5(str(random.random()).encode()).hexdigest() cnonce = hashlib.md5(str(random.random()).encode()).hexdigest()
_LOGGER.debug("Auth: CNonce is %s", cnonce) _LOGGER.debug("Auth: CNonce is %s", cnonce)
@ -235,9 +224,9 @@ def perform_ota(sock, password, file_handle, filename):
try: try:
sock.sendall(chunk) sock.sendall(chunk)
except socket.error as err: except OSError as err:
sys.stderr.write('\n') sys.stderr.write('\n')
raise OTAError("Error sending data: {}".format(err)) raise OTAError(f"Error sending data: {err}")
progress.update(offset / float(file_size)) progress.update(offset / float(file_size))
progress.done() progress.done()
@ -277,7 +266,7 @@ def run_ota_impl_(remote_host, remote_port, password, filename):
sock.settimeout(10.0) sock.settimeout(10.0)
try: try:
sock.connect((ip, remote_port)) sock.connect((ip, remote_port))
except socket.error as err: except OSError as err:
sock.close() sock.close()
_LOGGER.error("Connecting to %s:%s failed: %s", remote_host, remote_port, err) _LOGGER.error("Connecting to %s:%s failed: %s", remote_host, remote_port, err)
return 1 return 1

View file

@ -1,12 +1,8 @@
from __future__ import print_function
import codecs import codecs
import logging import logging
import os import os
from esphome.py_compat import char_to_byte, text_type, IS_PY2, encode_text
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -18,24 +14,24 @@ def ensure_unique_string(preferred_string, current_strings):
while test_string in current_strings_set: while test_string in current_strings_set:
tries += 1 tries += 1
test_string = u"{}_{}".format(preferred_string, tries) test_string = f"{preferred_string}_{tries}"
return test_string return test_string
def indent_all_but_first_and_last(text, padding=u' '): def indent_all_but_first_and_last(text, padding=' '):
lines = text.splitlines(True) lines = text.splitlines(True)
if len(lines) <= 2: if len(lines) <= 2:
return text return text
return lines[0] + u''.join(padding + line for line in lines[1:-1]) + lines[-1] return lines[0] + ''.join(padding + line for line in lines[1:-1]) + lines[-1]
def indent_list(text, padding=u' '): def indent_list(text, padding=' '):
return [padding + line for line in text.splitlines()] return [padding + line for line in text.splitlines()]
def indent(text, padding=u' '): def indent(text, padding=' '):
return u'\n'.join(indent_list(text, padding)) return '\n'.join(indent_list(text, padding))
# From https://stackoverflow.com/a/14945195/8924614 # From https://stackoverflow.com/a/14945195/8924614
@ -43,17 +39,16 @@ def cpp_string_escape(string, encoding='utf-8'):
def _should_escape(byte): # type: (int) -> bool def _should_escape(byte): # type: (int) -> bool
if not 32 <= byte < 127: if not 32 <= byte < 127:
return True return True
if byte in (char_to_byte('\\'), char_to_byte('"')): if byte in (ord('\\'), ord('"')):
return True return True
return False return False
if isinstance(string, text_type): if isinstance(string, str):
string = string.encode(encoding) string = string.encode(encoding)
result = '' result = ''
for character in string: for character in string:
character = char_to_byte(character)
if _should_escape(character): if _should_escape(character):
result += '\\%03o' % character result += f'\\{character:03o}'
else: else:
result += chr(character) result += chr(character)
return '"' + result + '"' return '"' + result + '"'
@ -91,7 +86,7 @@ def mkdir_p(path):
pass pass
else: else:
from esphome.core import EsphomeError from esphome.core import EsphomeError
raise EsphomeError(u"Error creating directories {}: {}".format(path, err)) raise EsphomeError(f"Error creating directories {path}: {err}")
def is_ip_address(host): def is_ip_address(host):
@ -118,7 +113,7 @@ def _resolve_with_zeroconf(host):
try: try:
info = zc.resolve_host(host + '.') info = zc.resolve_host(host + '.')
except Exception as err: except Exception as err:
raise EsphomeError("Error resolving mDNS hostname: {}".format(err)) raise EsphomeError(f"Error resolving mDNS hostname: {err}")
finally: finally:
zc.close() zc.close()
if info is None: if info is None:
@ -141,7 +136,7 @@ def resolve_ip_address(host):
try: try:
return socket.gethostbyname(host) return socket.gethostbyname(host)
except socket.error as err: except OSError as err:
errs.append(str(err)) errs.append(str(err))
raise EsphomeError("Error resolving IP address: {}" raise EsphomeError("Error resolving IP address: {}"
"".format(', '.join(errs))) "".format(', '.join(errs)))
@ -167,10 +162,10 @@ def read_file(path):
return f_handle.read() return f_handle.read()
except OSError as err: except OSError as err:
from esphome.core import EsphomeError from esphome.core import EsphomeError
raise EsphomeError(u"Error reading file {}: {}".format(path, err)) raise EsphomeError(f"Error reading file {path}: {err}")
except UnicodeDecodeError as err: except UnicodeDecodeError as err:
from esphome.core import EsphomeError from esphome.core import EsphomeError
raise EsphomeError(u"Error reading file {}: {}".format(path, err)) raise EsphomeError(f"Error reading file {path}: {err}")
def _write_file(path, text): def _write_file(path, text):
@ -179,18 +174,15 @@ def _write_file(path, text):
mkdir_p(directory) mkdir_p(directory)
tmp_path = None tmp_path = None
data = encode_text(text) data = text
if isinstance(text, str):
data = text.encode()
try: try:
with tempfile.NamedTemporaryFile(mode="wb", dir=directory, delete=False) as f_handle: with tempfile.NamedTemporaryFile(mode="wb", dir=directory, delete=False) as f_handle:
tmp_path = f_handle.name tmp_path = f_handle.name
f_handle.write(data) f_handle.write(data)
# Newer tempfile implementations create the file with mode 0o600 # Newer tempfile implementations create the file with mode 0o600
os.chmod(tmp_path, 0o644) os.chmod(tmp_path, 0o644)
if IS_PY2:
if os.path.exists(path):
os.remove(path)
os.rename(tmp_path, path)
else:
# If destination exists, will be overwritten # If destination exists, will be overwritten
os.replace(tmp_path, path) os.replace(tmp_path, path)
finally: finally:
@ -206,7 +198,7 @@ def write_file(path, text):
_write_file(path, text) _write_file(path, text)
except OSError: except OSError:
from esphome.core import EsphomeError from esphome.core import EsphomeError
raise EsphomeError(u"Could not write file at {}".format(path)) raise EsphomeError(f"Could not write file at {path}")
def write_file_if_changed(path, text): def write_file_if_changed(path, text):
@ -226,7 +218,7 @@ def copy_file_if_changed(src, dst):
shutil.copy(src, dst) shutil.copy(src, dst)
except OSError as err: except OSError as err:
from esphome.core import EsphomeError from esphome.core import EsphomeError
raise EsphomeError(u"Error copying file {} to {}: {}".format(src, dst, err)) raise EsphomeError(f"Error copying file {src} to {dst}: {err}")
def list_starts_with(list_, sub): def list_starts_with(list_, sub):
@ -273,10 +265,6 @@ _TYPE_OVERLOADS = {
list: type('EList', (list,), dict()), list: type('EList', (list,), dict()),
} }
if IS_PY2:
_TYPE_OVERLOADS[long] = type('long', (long,), dict())
_TYPE_OVERLOADS[unicode] = type('unicode', (unicode,), dict())
# cache created classes here # cache created classes here
_CLASS_LOOKUP = {} _CLASS_LOOKUP = {}

View file

@ -1,4 +1,3 @@
from __future__ import print_function
import sys import sys

View file

@ -1,9 +1,6 @@
from __future__ import print_function
from datetime import datetime from datetime import datetime
import hashlib import hashlib
import logging import logging
import socket
import ssl import ssl
import sys import sys
import time import time
@ -15,7 +12,6 @@ from esphome.const import CONF_BROKER, CONF_DISCOVERY_PREFIX, CONF_ESPHOME, \
CONF_TOPIC, CONF_TOPIC_PREFIX, CONF_USERNAME CONF_TOPIC, CONF_TOPIC_PREFIX, CONF_USERNAME
from esphome.core import CORE, EsphomeError from esphome.core import CORE, EsphomeError
from esphome.helpers import color from esphome.helpers import color
from esphome.py_compat import decode_text
from esphome.util import safe_print from esphome.util import safe_print
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -37,7 +33,7 @@ def initialize(config, subscriptions, on_message, username, password, client_id)
if client.reconnect() == 0: if client.reconnect() == 0:
_LOGGER.info("Successfully reconnected to the MQTT server") _LOGGER.info("Successfully reconnected to the MQTT server")
break break
except socket.error: except OSError:
pass pass
wait_time = min(2**tries, 300) wait_time = min(2**tries, 300)
@ -47,7 +43,7 @@ def initialize(config, subscriptions, on_message, username, password, client_id)
time.sleep(wait_time) time.sleep(wait_time)
tries += 1 tries += 1
client = mqtt.Client(client_id or u'') client = mqtt.Client(client_id or '')
client.on_connect = on_connect client.on_connect = on_connect
client.on_message = on_message client.on_message = on_message
client.on_disconnect = on_disconnect client.on_disconnect = on_disconnect
@ -70,8 +66,8 @@ def initialize(config, subscriptions, on_message, username, password, client_id)
host = str(config[CONF_MQTT][CONF_BROKER]) host = str(config[CONF_MQTT][CONF_BROKER])
port = int(config[CONF_MQTT][CONF_PORT]) port = int(config[CONF_MQTT][CONF_PORT])
client.connect(host, port) client.connect(host, port)
except socket.error as err: except OSError as err:
raise EsphomeError("Cannot connect to MQTT broker: {}".format(err)) raise EsphomeError(f"Cannot connect to MQTT broker: {err}")
try: try:
client.loop_forever() client.loop_forever()
@ -88,17 +84,17 @@ def show_logs(config, topic=None, username=None, password=None, client_id=None):
if CONF_LOG_TOPIC in conf: if CONF_LOG_TOPIC in conf:
topic = config[CONF_MQTT][CONF_LOG_TOPIC][CONF_TOPIC] topic = config[CONF_MQTT][CONF_LOG_TOPIC][CONF_TOPIC]
elif CONF_TOPIC_PREFIX in config[CONF_MQTT]: elif CONF_TOPIC_PREFIX in config[CONF_MQTT]:
topic = config[CONF_MQTT][CONF_TOPIC_PREFIX] + u'/debug' topic = config[CONF_MQTT][CONF_TOPIC_PREFIX] + '/debug'
else: else:
topic = config[CONF_ESPHOME][CONF_NAME] + u'/debug' topic = config[CONF_ESPHOME][CONF_NAME] + '/debug'
else: else:
_LOGGER.error(u"MQTT isn't setup, can't start MQTT logs") _LOGGER.error("MQTT isn't setup, can't start MQTT logs")
return 1 return 1
_LOGGER.info(u"Starting log output from %s", topic) _LOGGER.info("Starting log output from %s", topic)
def on_message(client, userdata, msg): def on_message(client, userdata, msg):
time_ = datetime.now().time().strftime(u'[%H:%M:%S]') time_ = datetime.now().time().strftime('[%H:%M:%S]')
payload = decode_text(msg.payload) payload = msg.payload.decode(errors='backslashreplace')
message = time_ + payload message = time_ + payload
safe_print(message) safe_print(message)
@ -107,20 +103,20 @@ def show_logs(config, topic=None, username=None, password=None, client_id=None):
def clear_topic(config, topic, username=None, password=None, client_id=None): def clear_topic(config, topic, username=None, password=None, client_id=None):
if topic is None: if topic is None:
discovery_prefix = config[CONF_MQTT].get(CONF_DISCOVERY_PREFIX, u'homeassistant') discovery_prefix = config[CONF_MQTT].get(CONF_DISCOVERY_PREFIX, 'homeassistant')
name = config[CONF_ESPHOME][CONF_NAME] name = config[CONF_ESPHOME][CONF_NAME]
topic = u'{}/+/{}/#'.format(discovery_prefix, name) topic = f'{discovery_prefix}/+/{name}/#'
_LOGGER.info(u"Clearing messages from '%s'", topic) _LOGGER.info("Clearing messages from '%s'", topic)
_LOGGER.info(u"Please close this window when no more messages appear and the " _LOGGER.info("Please close this window when no more messages appear and the "
u"MQTT topic has been cleared of retained messages.") "MQTT topic has been cleared of retained messages.")
def on_message(client, userdata, msg): def on_message(client, userdata, msg):
if not msg.payload or not msg.retain: if not msg.payload or not msg.retain:
return return
try: try:
print(u"Clearing topic {}".format(msg.topic)) print(f"Clearing topic {msg.topic}")
except UnicodeDecodeError: except UnicodeDecodeError:
print(u"Skipping non-UTF-8 topic (prohibited by MQTT standard)") print("Skipping non-UTF-8 topic (prohibited by MQTT standard)")
return return
client.publish(msg.topic, None, retain=True) client.publish(msg.topic, None, retain=True)
@ -133,14 +129,14 @@ def get_fingerprint(config):
_LOGGER.info("Getting fingerprint from %s:%s", addr[0], addr[1]) _LOGGER.info("Getting fingerprint from %s:%s", addr[0], addr[1])
try: try:
cert_pem = ssl.get_server_certificate(addr) cert_pem = ssl.get_server_certificate(addr)
except IOError as err: except OSError as err:
_LOGGER.error("Unable to connect to server: %s", err) _LOGGER.error("Unable to connect to server: %s", err)
return 1 return 1
cert_der = ssl.PEM_cert_to_DER_cert(cert_pem) cert_der = ssl.PEM_cert_to_DER_cert(cert_pem)
sha1 = hashlib.sha1(cert_der).hexdigest() sha1 = hashlib.sha1(cert_der).hexdigest()
safe_print(u"SHA1 Fingerprint: " + color('cyan', sha1)) safe_print("SHA1 Fingerprint: " + color('cyan', sha1))
safe_print(u"Copy the string above into mqtt.ssl_fingerprints section of {}" safe_print("Copy the string above into mqtt.ssl_fingerprints section of {}"
u"".format(CORE.config_path)) "".format(CORE.config_path))
return 0 return 0

View file

@ -1,5 +1,3 @@
from __future__ import division
import logging import logging
import esphome.config_validation as cv import esphome.config_validation as cv
@ -271,13 +269,13 @@ def _lookup_pin(value):
return board_pins[value] return board_pins[value]
if value in base_pins: if value in base_pins:
return base_pins[value] return base_pins[value]
raise cv.Invalid(u"Cannot resolve pin name '{}' for board {}.".format(value, CORE.board)) raise cv.Invalid(f"Cannot resolve pin name '{value}' for board {CORE.board}.")
def _translate_pin(value): def _translate_pin(value):
if isinstance(value, dict) or value is None: if isinstance(value, dict) or value is None:
raise cv.Invalid(u"This variable only supports pin numbers, not full pin schemas " raise cv.Invalid("This variable only supports pin numbers, not full pin schemas "
u"(with inverted and mode).") "(with inverted and mode).")
if isinstance(value, int): if isinstance(value, int):
return value return value
try: try:
@ -301,27 +299,27 @@ def validate_gpio_pin(value):
value = _translate_pin(value) value = _translate_pin(value)
if CORE.is_esp32: if CORE.is_esp32:
if value < 0 or value > 39: if value < 0 or value > 39:
raise cv.Invalid(u"ESP32: Invalid pin number: {}".format(value)) raise cv.Invalid(f"ESP32: Invalid pin number: {value}")
if value in _ESP_SDIO_PINS: if value in _ESP_SDIO_PINS:
raise cv.Invalid("This pin cannot be used on ESP32s and is already used by " raise cv.Invalid("This pin cannot be used on ESP32s and is already used by "
"the flash interface (function: {})".format(_ESP_SDIO_PINS[value])) "the flash interface (function: {})".format(_ESP_SDIO_PINS[value]))
if 9 <= value <= 10: if 9 <= value <= 10:
_LOGGER.warning(u"ESP32: Pin %s (9-10) might already be used by the " _LOGGER.warning("ESP32: Pin %s (9-10) might already be used by the "
u"flash interface in QUAD IO flash mode.", value) "flash interface in QUAD IO flash mode.", value)
if value in (20, 24, 28, 29, 30, 31): if value in (20, 24, 28, 29, 30, 31):
# These pins are not exposed in GPIO mux (reason unknown) # These pins are not exposed in GPIO mux (reason unknown)
# but they're missing from IO_MUX list in datasheet # but they're missing from IO_MUX list in datasheet
raise cv.Invalid("The pin GPIO{} is not usable on ESP32s.".format(value)) raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.")
return value return value
if CORE.is_esp8266: if CORE.is_esp8266:
if value < 0 or value > 17: if value < 0 or value > 17:
raise cv.Invalid(u"ESP8266: Invalid pin number: {}".format(value)) raise cv.Invalid(f"ESP8266: Invalid pin number: {value}")
if value in _ESP_SDIO_PINS: if value in _ESP_SDIO_PINS:
raise cv.Invalid("This pin cannot be used on ESP8266s and is already used by " raise cv.Invalid("This pin cannot be used on ESP8266s and is already used by "
"the flash interface (function: {})".format(_ESP_SDIO_PINS[value])) "the flash interface (function: {})".format(_ESP_SDIO_PINS[value]))
if 9 <= value <= 10: if 9 <= value <= 10:
_LOGGER.warning(u"ESP8266: Pin %s (9-10) might already be used by the " _LOGGER.warning("ESP8266: Pin %s (9-10) might already be used by the "
u"flash interface in QUAD IO flash mode.", value) "flash interface in QUAD IO flash mode.", value)
return value return value
raise NotImplementedError raise NotImplementedError
@ -349,8 +347,8 @@ def output_pin(value):
value = validate_gpio_pin(value) value = validate_gpio_pin(value)
if CORE.is_esp32: if CORE.is_esp32:
if 34 <= value <= 39: if 34 <= value <= 39:
raise cv.Invalid(u"ESP32: GPIO{} (34-39) can only be used as an " raise cv.Invalid("ESP32: GPIO{} (34-39) can only be used as an "
u"input pin.".format(value)) "input pin.".format(value))
return value return value
if CORE.is_esp8266: if CORE.is_esp8266:
if value == 17: if value == 17:
@ -364,11 +362,11 @@ def analog_pin(value):
if CORE.is_esp32: if CORE.is_esp32:
if 32 <= value <= 39: # ADC1 if 32 <= value <= 39: # ADC1
return value return value
raise cv.Invalid(u"ESP32: Only pins 32 though 39 support ADC.") raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.")
if CORE.is_esp8266: if CORE.is_esp8266:
if value == 17: # A0 if value == 17: # A0
return value return value
raise cv.Invalid(u"ESP8266: Only pin A0 (GPIO17) supports ADC.") raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.")
raise NotImplementedError raise NotImplementedError

View file

@ -1,5 +1,3 @@
from __future__ import print_function
import json import json
import logging import logging
import os import os
@ -7,7 +5,6 @@ import re
import subprocess import subprocess
from esphome.core import CORE from esphome.core import CORE
from esphome.py_compat import decode_text
from esphome.util import run_external_command, run_external_process from esphome.util import run_external_command, run_external_process
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -61,6 +58,7 @@ FILTER_PLATFORMIO_LINES = [
r'Installing dependencies', r'Installing dependencies',
r'.* @ .* is already installed', r'.* @ .* is already installed',
r'Building in .* mode', r'Building in .* mode',
r'Advanced Memory Usage is available via .*',
] ]
@ -100,8 +98,7 @@ def run_upload(config, verbose, port):
def run_idedata(config): def run_idedata(config):
args = ['-t', 'idedata'] args = ['-t', 'idedata']
stdout = run_platformio_cli_run(config, False, *args, capture_stdout=True) stdout = run_platformio_cli_run(config, False, *args, capture_stdout=True).decode()
stdout = decode_text(stdout)
match = re.search(r'{\s*".*}', stdout) match = re.search(r'{\s*".*}', stdout)
if match is None: if match is None:
_LOGGER.debug("Could not match IDEData for %s", stdout) _LOGGER.debug("Could not match IDEData for %s", stdout)
@ -172,7 +169,7 @@ def _decode_pc(config, addr):
return return
command = [idedata.addr2line_path, '-pfiaC', '-e', idedata.firmware_elf_path, addr] command = [idedata.addr2line_path, '-pfiaC', '-e', idedata.firmware_elf_path, addr]
try: try:
translation = decode_text(subprocess.check_output(command)).strip() translation = subprocess.check_output(command).decode().strip()
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.debug("Caught exception for command %s", command, exc_info=1) _LOGGER.debug("Caught exception for command %s", command, exc_info=1)
return return
@ -246,7 +243,7 @@ def process_stacktrace(config, line, backtrace_state):
return backtrace_state return backtrace_state
class IDEData(object): class IDEData:
def __init__(self, raw): def __init__(self, raw):
if not isinstance(raw, dict): if not isinstance(raw, dict):
self.raw = {} self.raw = {}

View file

@ -1,89 +0,0 @@
import functools
import sys
import codecs
PYTHON_MAJOR = sys.version_info[0]
IS_PY2 = PYTHON_MAJOR == 2
IS_PY3 = PYTHON_MAJOR == 3
# pylint: disable=no-else-return
def safe_input(prompt=None):
if IS_PY2:
if prompt is None:
return raw_input()
return raw_input(prompt)
else:
if prompt is None:
return input()
return input(prompt)
if IS_PY2:
text_type = unicode
string_types = (str, unicode)
integer_types = (int, long)
binary_type = str
else:
text_type = str
string_types = (str,)
integer_types = (int,)
binary_type = bytes
def byte_to_bytes(val): # type: (int) -> bytes
if IS_PY2:
return chr(val)
else:
return bytes([val])
def char_to_byte(val): # type: (str) -> int
if IS_PY2:
if isinstance(val, string_types):
return ord(val)
elif isinstance(val, int):
return val
else:
raise ValueError
else:
if isinstance(val, str):
return ord(val)
elif isinstance(val, int):
return val
else:
raise ValueError
def format_bytes(val):
if IS_PY2:
return ' '.join('{:02X}'.format(ord(x)) for x in val)
else:
return ' '.join('{:02X}'.format(x) for x in val)
def sort_by_cmp(list_, cmp):
if IS_PY2:
list_.sort(cmp=cmp)
else:
list_.sort(key=functools.cmp_to_key(cmp))
def indexbytes(buf, i):
if IS_PY3:
return buf[i]
else:
return ord(buf[i])
def decode_text(data, encoding='utf-8', errors='strict'):
if isinstance(data, text_type):
return data
return codecs.decode(data, encoding, errors)
def encode_text(data, encoding='utf-8', errors='strict'):
if isinstance(data, binary_type):
return data
return codecs.encode(data, encoding, errors)

View file

@ -13,17 +13,15 @@ from esphome.helpers import write_file_if_changed
from esphome.core import CoreType from esphome.core import CoreType
from typing import Any, Optional, List from typing import Any, Optional, List
from esphome.py_compat import text_type
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def storage_path(): # type: () -> str def storage_path(): # type: () -> str
return CORE.relative_config_path('.esphome', '{}.json'.format(CORE.config_filename)) return CORE.relative_config_path('.esphome', f'{CORE.config_filename}.json')
def ext_storage_path(base_path, config_filename): # type: (str, str) -> str def ext_storage_path(base_path, config_filename): # type: (str, str) -> str
return os.path.join(base_path, '.esphome', '{}.json'.format(config_filename)) return os.path.join(base_path, '.esphome', f'{config_filename}.json')
def esphome_storage_path(base_path): # type: (str) -> str def esphome_storage_path(base_path): # type: (str) -> str
@ -35,7 +33,7 @@ def trash_storage_path(base_path): # type: (str) -> str
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
class StorageJSON(object): class StorageJSON:
def __init__(self, storage_version, name, comment, esphome_version, def __init__(self, storage_version, name, comment, esphome_version,
src_version, arduino_version, address, esp_platform, board, build_path, src_version, arduino_version, address, esp_platform, board, build_path,
firmware_bin_path, loaded_integrations): firmware_bin_path, loaded_integrations):
@ -85,7 +83,7 @@ class StorageJSON(object):
} }
def to_json(self): def to_json(self):
return json.dumps(self.as_dict(), indent=2) + u'\n' return json.dumps(self.as_dict(), indent=2) + '\n'
def save(self, path): def save(self, path):
write_file_if_changed(path, self.to_json()) write_file_if_changed(path, self.to_json())
@ -156,7 +154,7 @@ class StorageJSON(object):
return isinstance(o, StorageJSON) and self.as_dict() == o.as_dict() return isinstance(o, StorageJSON) and self.as_dict() == o.as_dict()
class EsphomeStorageJSON(object): class EsphomeStorageJSON:
def __init__(self, storage_version, cookie_secret, last_update_check, def __init__(self, storage_version, cookie_secret, last_update_check,
remote_version): remote_version):
# Version of the storage JSON schema # Version of the storage JSON schema
@ -189,7 +187,7 @@ class EsphomeStorageJSON(object):
self.last_update_check_str = new.strftime("%Y-%m-%dT%H:%M:%S") self.last_update_check_str = new.strftime("%Y-%m-%dT%H:%M:%S")
def to_json(self): # type: () -> dict def to_json(self): # type: () -> dict
return json.dumps(self.as_dict(), indent=2) + u'\n' return json.dumps(self.as_dict(), indent=2) + '\n'
def save(self, path): # type: (str) -> None def save(self, path): # type: (str) -> None
write_file_if_changed(path, self.to_json()) write_file_if_changed(path, self.to_json())
@ -216,7 +214,7 @@ class EsphomeStorageJSON(object):
def get_default(): # type: () -> EsphomeStorageJSON def get_default(): # type: () -> EsphomeStorageJSON
return EsphomeStorageJSON( return EsphomeStorageJSON(
storage_version=1, storage_version=1,
cookie_secret=text_type(binascii.hexlify(os.urandom(64))), cookie_secret=binascii.hexlify(os.urandom(64)).decode(),
last_update_check=None, last_update_check=None,
remote_version=None, remote_version=None,
) )

View file

@ -1,5 +1,3 @@
from __future__ import print_function
import collections import collections
import io import io
import logging import logging
@ -9,12 +7,11 @@ import subprocess
import sys import sys
from esphome import const from esphome import const
from esphome.py_compat import IS_PY2, decode_text, text_type
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
class RegistryEntry(object): class RegistryEntry:
def __init__(self, name, fun, type_id, schema): def __init__(self, name, fun, type_id, schema):
self.name = name self.name = name
self.fun = fun self.fun = fun
@ -34,7 +31,7 @@ class RegistryEntry(object):
class Registry(dict): class Registry(dict):
def __init__(self, base_schema=None, type_id_key=None): def __init__(self, base_schema=None, type_id_key=None):
super(Registry, self).__init__() super().__init__()
self.base_schema = base_schema or {} self.base_schema = base_schema or {}
self.type_id_key = type_id_key self.type_id_key = type_id_key
@ -81,17 +78,17 @@ def safe_print(message=""):
def shlex_quote(s): def shlex_quote(s):
if not s: if not s:
return u"''" return "''"
if re.search(r'[^\w@%+=:,./-]', s) is None: if re.search(r'[^\w@%+=:,./-]', s) is None:
return s return s
return u"'" + s.replace(u"'", u"'\"'\"'") + u"'" return "'" + s.replace("'", "'\"'\"'") + "'"
ANSI_ESCAPE = re.compile(r'\033[@-_][0-?]*[ -/]*[@-~]') ANSI_ESCAPE = re.compile(r'\033[@-_][0-?]*[ -/]*[@-~]')
class RedirectText(object): class RedirectText:
def __init__(self, out, filter_lines=None): def __init__(self, out, filter_lines=None):
self._out = out self._out = out
if filter_lines is None: if filter_lines is None:
@ -116,13 +113,12 @@ class RedirectText(object):
self._out.write(s) self._out.write(s)
def write(self, s): def write(self, s):
# s is usually a text_type already (self._out is of type TextIOWrapper) # s is usually a str already (self._out is of type TextIOWrapper)
# However, s is sometimes also a bytes object in python3. Let's make sure it's a # However, s is sometimes also a bytes object in python3. Let's make sure it's a
# text_type # str
# If the conversion fails, we will create an exception, which is okay because we won't # If the conversion fails, we will create an exception, which is okay because we won't
# be able to print it anyway. # be able to print it anyway.
text = decode_text(s) text = s.decode()
assert isinstance(text, text_type)
if self._filter_pattern is not None: if self._filter_pattern is not None:
self._line_buffer += text self._line_buffer += text
@ -160,8 +156,8 @@ def run_external_command(func, *cmd, **kwargs):
orig_argv = sys.argv orig_argv = sys.argv
orig_exit = sys.exit # mock sys.exit orig_exit = sys.exit # mock sys.exit
full_cmd = u' '.join(shlex_quote(x) for x in cmd) full_cmd = ' '.join(shlex_quote(x) for x in cmd)
_LOGGER.info(u"Running: %s", full_cmd) _LOGGER.info("Running: %s", full_cmd)
filter_lines = kwargs.get('filter_lines') filter_lines = kwargs.get('filter_lines')
orig_stdout = sys.stdout orig_stdout = sys.stdout
@ -182,8 +178,8 @@ def run_external_command(func, *cmd, **kwargs):
except SystemExit as err: except SystemExit as err:
return err.args[0] return err.args[0]
except Exception as err: # pylint: disable=broad-except except Exception as err: # pylint: disable=broad-except
_LOGGER.error(u"Running command failed: %s", err) _LOGGER.error("Running command failed: %s", err)
_LOGGER.error(u"Please try running %s locally.", full_cmd) _LOGGER.error("Please try running %s locally.", full_cmd)
return 1 return 1
finally: finally:
sys.argv = orig_argv sys.argv = orig_argv
@ -198,8 +194,8 @@ def run_external_command(func, *cmd, **kwargs):
def run_external_process(*cmd, **kwargs): def run_external_process(*cmd, **kwargs):
full_cmd = u' '.join(shlex_quote(x) for x in cmd) full_cmd = ' '.join(shlex_quote(x) for x in cmd)
_LOGGER.info(u"Running: %s", full_cmd) _LOGGER.info("Running: %s", full_cmd)
filter_lines = kwargs.get('filter_lines') filter_lines = kwargs.get('filter_lines')
capture_stdout = kwargs.get('capture_stdout', False) capture_stdout = kwargs.get('capture_stdout', False)
@ -215,8 +211,8 @@ def run_external_process(*cmd, **kwargs):
stdout=sub_stdout, stdout=sub_stdout,
stderr=sub_stderr) stderr=sub_stderr)
except Exception as err: # pylint: disable=broad-except except Exception as err: # pylint: disable=broad-except
_LOGGER.error(u"Running command failed: %s", err) _LOGGER.error("Running command failed: %s", err)
_LOGGER.error(u"Please try running %s locally.", full_cmd) _LOGGER.error("Please try running %s locally.", full_cmd)
return 1 return 1
finally: finally:
if capture_stdout: if capture_stdout:
@ -233,29 +229,6 @@ class OrderedDict(collections.OrderedDict):
def __repr__(self): def __repr__(self):
return dict(self).__repr__() return dict(self).__repr__()
def move_to_end(self, key, last=True):
if IS_PY2:
if len(self) == 1:
return
if last:
# When moving to end, just pop and re-add
val = self.pop(key)
self[key] = val
else:
# When moving to front, use internals here
# https://stackoverflow.com/a/16664932
root = self._OrderedDict__root # pylint: disable=no-member
first = root[1]
link = self._OrderedDict__map[key] # pylint: disable=no-member
link_prev, link_next, _ = link
link_prev[1] = link_next
link_next[0] = link_prev
link[0] = root
link[1] = first
root[1] = first[0] = link
else:
super(OrderedDict, self).move_to_end(key, last=last) # pylint: disable=no-member
def list_yaml_files(folder): def list_yaml_files(folder):
files = filter_yaml_files([os.path.join(folder, p) for p in os.listdir(folder)]) files = filter_yaml_files([os.path.join(folder, p) for p in os.listdir(folder)])

View file

@ -3,8 +3,6 @@ import itertools
import voluptuous as vol import voluptuous as vol
from esphome.py_compat import string_types
class ExtraKeysInvalid(vol.Invalid): class ExtraKeysInvalid(vol.Invalid):
def __init__(self, *arg, **kwargs): def __init__(self, *arg, **kwargs):
@ -22,14 +20,14 @@ def ensure_multiple_invalid(err):
class _Schema(vol.Schema): class _Schema(vol.Schema):
"""Custom cv.Schema that prints similar keys on error.""" """Custom cv.Schema that prints similar keys on error."""
def __init__(self, schema, required=False, extra=vol.PREVENT_EXTRA, extra_schemas=None): def __init__(self, schema, required=False, extra=vol.PREVENT_EXTRA, extra_schemas=None):
super(_Schema, self).__init__(schema, required=required, extra=extra) super().__init__(schema, required=required, extra=extra)
# List of extra schemas to apply after validation # List of extra schemas to apply after validation
# Should be used sparingly, as it's not a very voluptuous-way/clean way of # Should be used sparingly, as it's not a very voluptuous-way/clean way of
# doing things. # doing things.
self._extra_schemas = extra_schemas or [] self._extra_schemas = extra_schemas or []
def __call__(self, data): def __call__(self, data):
res = super(_Schema, self).__call__(data) res = super().__call__(data)
for extra in self._extra_schemas: for extra in self._extra_schemas:
try: try:
res = extra(res) res = extra(res)
@ -51,10 +49,10 @@ class _Schema(vol.Schema):
raise ValueError("All schema keys must be wrapped in cv.Required or cv.Optional") raise ValueError("All schema keys must be wrapped in cv.Required or cv.Optional")
# Keys that may be required # Keys that may be required
all_required_keys = set(key for key in schema if isinstance(key, vol.Required)) all_required_keys = {key for key in schema if isinstance(key, vol.Required)}
# Keys that may have defaults # Keys that may have defaults
all_default_keys = set(key for key in schema if isinstance(key, vol.Optional)) all_default_keys = {key for key in schema if isinstance(key, vol.Optional)}
# Recursively compile schema # Recursively compile schema
_compiled_schema = {} _compiled_schema = {}
@ -84,9 +82,9 @@ class _Schema(vol.Schema):
key_names = [] key_names = []
for skey in schema: for skey in schema:
if isinstance(skey, string_types): if isinstance(skey, str):
key_names.append(skey) key_names.append(skey)
elif isinstance(skey, vol.Marker) and isinstance(skey.schema, string_types): elif isinstance(skey, vol.Marker) and isinstance(skey.schema, str):
key_names.append(skey.schema) key_names.append(skey.schema)
def validate_mapping(path, iterable, out): def validate_mapping(path, iterable, out):
@ -156,7 +154,7 @@ class _Schema(vol.Schema):
if self.extra == vol.ALLOW_EXTRA: if self.extra == vol.ALLOW_EXTRA:
out[key] = value out[key] = value
elif self.extra != vol.REMOVE_EXTRA: elif self.extra != vol.REMOVE_EXTRA:
if isinstance(key, string_types) and key_names: if isinstance(key, str) and key_names:
matches = difflib.get_close_matches(key, key_names) matches = difflib.get_close_matches(key, key_names)
errors.append(ExtraKeysInvalid('extra keys not allowed', key_path, errors.append(ExtraKeysInvalid('extra keys not allowed', key_path,
candidates=matches)) candidates=matches))
@ -195,5 +193,5 @@ class _Schema(vol.Schema):
schema = schemas[0] schema = schemas[0]
if isinstance(schema, vol.Schema): if isinstance(schema, vol.Schema):
schema = schema.schema schema = schema.schema
ret = super(_Schema, self).extend(schema, extra=extra) ret = super().extend(schema, extra=extra)
return _Schema(ret.schema, extra=ret.extra, extra_schemas=self._extra_schemas) return _Schema(ret.schema, extra=ret.extra, extra_schemas=self._extra_schemas)

View file

@ -1,19 +1,17 @@
from __future__ import print_function
import json import json
import os import os
# pylint: disable=unused-import
from esphome.config import load_config, _format_vol_invalid, Config from esphome.config import load_config, _format_vol_invalid, Config
from esphome.core import CORE, DocumentRange from esphome.core import CORE, DocumentRange
from esphome.py_compat import text_type, safe_input import esphome.config_validation as cv
# pylint: disable=unused-import, wrong-import-order # pylint: disable=unused-import, wrong-import-order
import voluptuous as vol
from typing import Optional from typing import Optional
def _get_invalid_range(res, invalid): def _get_invalid_range(res, invalid):
# type: (Config, vol.Invalid) -> Optional[DocumentRange] # type: (Config, cv.Invalid) -> Optional[DocumentRange]
return res.get_deepest_document_range_for_path(invalid.path) return res.get_deepest_document_range_for_path(invalid.path)
@ -30,7 +28,7 @@ def _dump_range(range):
} }
class VSCodeResult(object): class VSCodeResult:
def __init__(self): def __init__(self):
self.yaml_errors = [] self.yaml_errors = []
self.validation_errors = [] self.validation_errors = []
@ -57,7 +55,7 @@ class VSCodeResult(object):
def read_config(args): def read_config(args):
while True: while True:
CORE.reset() CORE.reset()
data = json.loads(safe_input()) data = json.loads(input())
assert data['type'] == 'validate' assert data['type'] == 'validate'
CORE.vscode = True CORE.vscode = True
CORE.ace = args.ace CORE.ace = args.ace
@ -70,7 +68,7 @@ def read_config(args):
try: try:
res = load_config() res = load_config()
except Exception as err: # pylint: disable=broad-except except Exception as err: # pylint: disable=broad-except
vs.add_yaml_error(text_type(err)) vs.add_yaml_error(str(err))
else: else:
for err in res.errors: for err in res.errors:
try: try:

View file

@ -1,5 +1,3 @@
from __future__ import print_function
import os import os
import random import random
import string import string
@ -11,7 +9,6 @@ import esphome.config_validation as cv
from esphome.helpers import color, get_bool_env, write_file from esphome.helpers import color, get_bool_env, write_file
# pylint: disable=anomalous-backslash-in-string # pylint: disable=anomalous-backslash-in-string
from esphome.pins import ESP32_BOARD_PINS, ESP8266_BOARD_PINS from esphome.pins import ESP32_BOARD_PINS, ESP8266_BOARD_PINS
from esphome.py_compat import safe_input, text_type
from esphome.storage_json import StorageJSON, ext_storage_path from esphome.storage_json import StorageJSON, ext_storage_path
from esphome.util import safe_print from esphome.util import safe_print
@ -44,7 +41,7 @@ OTA_BIG = r""" ____ _______
\____/ |_/_/ \_\\ \____/ |_/_/ \_\\
""" """
BASE_CONFIG = u"""esphome: BASE_CONFIG = """esphome:
name: {name} name: {name}
platform: {platform} platform: {platform}
board: {board} board: {board}
@ -75,7 +72,7 @@ def sanitize_double_quotes(value):
def wizard_file(**kwargs): def wizard_file(**kwargs):
letters = string.ascii_letters + string.digits letters = string.ascii_letters + string.digits
ap_name_base = kwargs['name'].replace('_', ' ').title() ap_name_base = kwargs['name'].replace('_', ' ').title()
ap_name = "{} Fallback Hotspot".format(ap_name_base) ap_name = f"{ap_name_base} Fallback Hotspot"
if len(ap_name) > 32: if len(ap_name) > 32:
ap_name = ap_name_base ap_name = ap_name_base
kwargs['fallback_name'] = ap_name kwargs['fallback_name'] = ap_name
@ -84,9 +81,9 @@ def wizard_file(**kwargs):
config = BASE_CONFIG.format(**kwargs) config = BASE_CONFIG.format(**kwargs)
if kwargs['password']: if kwargs['password']:
config += u' password: "{0}"\n\nota:\n password: "{0}"\n'.format(kwargs['password']) config += ' password: "{0}"\n\nota:\n password: "{0}"\n'.format(kwargs['password'])
else: else:
config += u"\nota:\n" config += "\nota:\n"
return config return config
@ -119,7 +116,7 @@ else:
def safe_print_step(step, big): def safe_print_step(step, big):
safe_print() safe_print()
safe_print() safe_print()
safe_print("============= STEP {} =============".format(step)) safe_print(f"============= STEP {step} =============")
safe_print(big) safe_print(big)
safe_print("===================================") safe_print("===================================")
sleep(0.25) sleep(0.25)
@ -127,24 +124,24 @@ def safe_print_step(step, big):
def default_input(text, default): def default_input(text, default):
safe_print() safe_print()
safe_print(u"Press ENTER for default ({})".format(default)) safe_print(f"Press ENTER for default ({default})")
return safe_input(text.format(default)) or default return input(text.format(default)) or default
# From https://stackoverflow.com/a/518232/8924614 # From https://stackoverflow.com/a/518232/8924614
def strip_accents(value): def strip_accents(value):
return u''.join(c for c in unicodedata.normalize('NFD', text_type(value)) return ''.join(c for c in unicodedata.normalize('NFD', str(value))
if unicodedata.category(c) != 'Mn') if unicodedata.category(c) != 'Mn')
def wizard(path): def wizard(path):
if not path.endswith('.yaml') and not path.endswith('.yml'): if not path.endswith('.yaml') and not path.endswith('.yml'):
safe_print(u"Please make your configuration file {} have the extension .yaml or .yml" safe_print("Please make your configuration file {} have the extension .yaml or .yml"
u"".format(color('cyan', path))) "".format(color('cyan', path)))
return 1 return 1
if os.path.exists(path): if os.path.exists(path):
safe_print(u"Uh oh, it seems like {} already exists, please delete that file first " safe_print("Uh oh, it seems like {} already exists, please delete that file first "
u"or chose another configuration file.".format(color('cyan', path))) "or chose another configuration file.".format(color('cyan', path)))
return 1 return 1
safe_print("Hi there!") safe_print("Hi there!")
sleep(1.5) sleep(1.5)
@ -164,21 +161,21 @@ def wizard(path):
color('bold_white', "livingroom"))) color('bold_white', "livingroom")))
safe_print() safe_print()
sleep(1) sleep(1)
name = safe_input(color("bold_white", "(name): ")) name = input(color("bold_white", "(name): "))
while True: while True:
try: try:
name = cv.valid_name(name) name = cv.valid_name(name)
break break
except vol.Invalid: except vol.Invalid:
safe_print(color("red", u"Oh noes, \"{}\" isn't a valid name. Names can only include " safe_print(color("red", "Oh noes, \"{}\" isn't a valid name. Names can only include "
u"numbers, letters and underscores.".format(name))) "numbers, letters and underscores.".format(name)))
name = strip_accents(name).replace(' ', '_') name = strip_accents(name).replace(' ', '_')
name = u''.join(c for c in name if c in cv.ALLOWED_NAME_CHARS) name = ''.join(c for c in name if c in cv.ALLOWED_NAME_CHARS)
safe_print(u"Shall I use \"{}\" as the name instead?".format(color('cyan', name))) safe_print("Shall I use \"{}\" as the name instead?".format(color('cyan', name)))
sleep(0.5) sleep(0.5)
name = default_input(u"(name [{}]): ", name) name = default_input("(name [{}]): ", name)
safe_print(u"Great! Your node is now called \"{}\".".format(color('cyan', name))) safe_print("Great! Your node is now called \"{}\".".format(color('cyan', name)))
sleep(1) sleep(1)
safe_print_step(2, ESP_BIG) safe_print_step(2, ESP_BIG)
safe_print("Now I'd like to know what microcontroller you're using so that I can compile " safe_print("Now I'd like to know what microcontroller you're using so that I can compile "
@ -189,14 +186,14 @@ def wizard(path):
sleep(0.5) sleep(0.5)
safe_print() safe_print()
safe_print("Please enter either ESP32 or ESP8266.") safe_print("Please enter either ESP32 or ESP8266.")
platform = safe_input(color("bold_white", "(ESP32/ESP8266): ")) platform = input(color("bold_white", "(ESP32/ESP8266): "))
try: try:
platform = vol.All(vol.Upper, vol.Any('ESP32', 'ESP8266'))(platform) platform = vol.All(vol.Upper, vol.Any('ESP32', 'ESP8266'))(platform)
break break
except vol.Invalid: except vol.Invalid:
safe_print(u"Unfortunately, I can't find an espressif microcontroller called " safe_print("Unfortunately, I can't find an espressif microcontroller called "
u"\"{}\". Please try again.".format(platform)) "\"{}\". Please try again.".format(platform))
safe_print(u"Thanks! You've chosen {} as your platform.".format(color('cyan', platform))) safe_print("Thanks! You've chosen {} as your platform.".format(color('cyan', platform)))
safe_print() safe_print()
sleep(1) sleep(1)
@ -221,17 +218,17 @@ def wizard(path):
safe_print("Options: {}".format(', '.join(sorted(boards)))) safe_print("Options: {}".format(', '.join(sorted(boards))))
while True: while True:
board = safe_input(color("bold_white", "(board): ")) board = input(color("bold_white", "(board): "))
try: try:
board = vol.All(vol.Lower, vol.Any(*boards))(board) board = vol.All(vol.Lower, vol.Any(*boards))(board)
break break
except vol.Invalid: except vol.Invalid:
safe_print(color('red', "Sorry, I don't think the board \"{}\" exists.".format(board))) safe_print(color('red', f"Sorry, I don't think the board \"{board}\" exists."))
safe_print() safe_print()
sleep(0.25) sleep(0.25)
safe_print() safe_print()
safe_print(u"Way to go! You've chosen {} as your board.".format(color('cyan', board))) safe_print("Way to go! You've chosen {} as your board.".format(color('cyan', board)))
safe_print() safe_print()
sleep(1) sleep(1)
@ -241,22 +238,22 @@ def wizard(path):
safe_print() safe_print()
sleep(1) sleep(1)
safe_print("First, what's the " + color('green', 'SSID') + safe_print("First, what's the " + color('green', 'SSID') +
u" (the name) of the WiFi network {} I should connect to?".format(name)) f" (the name) of the WiFi network {name} I should connect to?")
sleep(1.5) sleep(1.5)
safe_print("For example \"{}\".".format(color('bold_white', "Abraham Linksys"))) safe_print("For example \"{}\".".format(color('bold_white', "Abraham Linksys")))
while True: while True:
ssid = safe_input(color('bold_white', "(ssid): ")) ssid = input(color('bold_white', "(ssid): "))
try: try:
ssid = cv.ssid(ssid) ssid = cv.ssid(ssid)
break break
except vol.Invalid: except vol.Invalid:
safe_print(color('red', u"Unfortunately, \"{}\" doesn't seem to be a valid SSID. " safe_print(color('red', "Unfortunately, \"{}\" doesn't seem to be a valid SSID. "
u"Please try again.".format(ssid))) "Please try again.".format(ssid)))
safe_print() safe_print()
sleep(1) sleep(1)
safe_print(u"Thank you very much! You've just chosen \"{}\" as your SSID." safe_print("Thank you very much! You've just chosen \"{}\" as your SSID."
u"".format(color('cyan', ssid))) "".format(color('cyan', ssid)))
safe_print() safe_print()
sleep(0.75) sleep(0.75)
@ -265,7 +262,7 @@ def wizard(path):
safe_print() safe_print()
safe_print("For example \"{}\"".format(color('bold_white', 'PASSWORD42'))) safe_print("For example \"{}\"".format(color('bold_white', 'PASSWORD42')))
sleep(0.5) sleep(0.5)
psk = safe_input(color('bold_white', '(PSK): ')) psk = input(color('bold_white', '(PSK): '))
safe_print("Perfect! WiFi is now set up (you can create static IPs and so on later).") safe_print("Perfect! WiFi is now set up (you can create static IPs and so on later).")
sleep(1.5) sleep(1.5)
@ -277,7 +274,7 @@ def wizard(path):
safe_print() safe_print()
sleep(0.25) sleep(0.25)
safe_print("Press ENTER for no password") safe_print("Press ENTER for no password")
password = safe_input(color('bold_white', '(password): ')) password = input(color('bold_white', '(password): '))
wizard_write(path=path, name=name, platform=platform, board=board, wizard_write(path=path, name=name, platform=platform, board=board,
ssid=ssid, psk=psk, password=password) ssid=ssid, psk=psk, password=password)

View file

@ -1,5 +1,3 @@
from __future__ import print_function
import logging import logging
import os import os
import re import re
@ -14,19 +12,19 @@ from esphome.storage_json import StorageJSON, storage_path
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CPP_AUTO_GENERATE_BEGIN = u'// ========== AUTO GENERATED CODE BEGIN ===========' CPP_AUTO_GENERATE_BEGIN = '// ========== AUTO GENERATED CODE BEGIN ==========='
CPP_AUTO_GENERATE_END = u'// =========== AUTO GENERATED CODE END ============' CPP_AUTO_GENERATE_END = '// =========== AUTO GENERATED CODE END ============'
CPP_INCLUDE_BEGIN = u'// ========== AUTO GENERATED INCLUDE BLOCK BEGIN ===========' CPP_INCLUDE_BEGIN = '// ========== AUTO GENERATED INCLUDE BLOCK BEGIN ==========='
CPP_INCLUDE_END = u'// ========== AUTO GENERATED INCLUDE BLOCK END ===========' CPP_INCLUDE_END = '// ========== AUTO GENERATED INCLUDE BLOCK END ==========='
INI_AUTO_GENERATE_BEGIN = u'; ========== AUTO GENERATED CODE BEGIN ===========' INI_AUTO_GENERATE_BEGIN = '; ========== AUTO GENERATED CODE BEGIN ==========='
INI_AUTO_GENERATE_END = u'; =========== AUTO GENERATED CODE END ============' INI_AUTO_GENERATE_END = '; =========== AUTO GENERATED CODE END ============'
CPP_BASE_FORMAT = (u"""// Auto generated code by esphome CPP_BASE_FORMAT = ("""// Auto generated code by esphome
""", u"""" """, """"
void setup() { void setup() {
// ===== DO NOT EDIT ANYTHING BELOW THIS LINE ===== // ===== DO NOT EDIT ANYTHING BELOW THIS LINE =====
""", u""" """, """
// ========= YOU CAN EDIT AFTER THIS LINE ========= // ========= YOU CAN EDIT AFTER THIS LINE =========
App.setup(); App.setup();
} }
@ -36,7 +34,7 @@ void loop() {
} }
""") """)
INI_BASE_FORMAT = (u"""; Auto generated code by esphome INI_BASE_FORMAT = ("""; Auto generated code by esphome
[common] [common]
lib_deps = lib_deps =
@ -44,7 +42,7 @@ build_flags =
upload_flags = upload_flags =
; ===== DO NOT EDIT ANYTHING BELOW THIS LINE ===== ; ===== DO NOT EDIT ANYTHING BELOW THIS LINE =====
""", u""" """, """
; ========= YOU CAN EDIT AFTER THIS LINE ========= ; ========= YOU CAN EDIT AFTER THIS LINE =========
""") """)
@ -62,8 +60,8 @@ def get_flags(key):
def get_include_text(): def get_include_text():
include_text = u'#include "esphome.h"\n' \ include_text = '#include "esphome.h"\n' \
u'using namespace esphome;\n' 'using namespace esphome;\n'
for _, component, conf in iter_components(CORE.config): for _, component, conf in iter_components(CORE.config):
if not hasattr(component, 'includes'): if not hasattr(component, 'includes'):
continue continue
@ -106,7 +104,7 @@ def migrate_src_version_0_to_1():
if CPP_INCLUDE_BEGIN not in content: if CPP_INCLUDE_BEGIN not in content:
content, count = replace_file_content(content, r'#include "esphomelib/application.h"', content, count = replace_file_content(content, r'#include "esphomelib/application.h"',
CPP_INCLUDE_BEGIN + u'\n' + CPP_INCLUDE_END) CPP_INCLUDE_BEGIN + '\n' + CPP_INCLUDE_END)
if count == 0: if count == 0:
_LOGGER.error("Migration failed. ESPHome 1.10.0 needs to have a new auto-generated " _LOGGER.error("Migration failed. ESPHome 1.10.0 needs to have a new auto-generated "
"include section in the %s file. Please remove %s and let it be " "include section in the %s file. Please remove %s and let it be "
@ -160,14 +158,14 @@ def update_storage_json():
def format_ini(data): def format_ini(data):
content = u'' content = ''
for key, value in sorted(data.items()): for key, value in sorted(data.items()):
if isinstance(value, (list, set, tuple)): if isinstance(value, (list, set, tuple)):
content += u'{} =\n'.format(key) content += f'{key} =\n'
for x in value: for x in value:
content += u' {}\n'.format(x) content += f' {x}\n'
else: else:
content += u'{} = {}\n'.format(key, value) content += f'{key} = {value}\n'
return content return content
@ -216,7 +214,7 @@ def get_ini_content():
# data['lib_ldf_mode'] = 'chain' # data['lib_ldf_mode'] = 'chain'
data.update(CORE.config[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS, {})) data.update(CORE.config[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS, {}))
content = u'[env:{}]\n'.format(CORE.name) content = f'[env:{CORE.name}]\n'
content += format_ini(data) content += format_ini(data)
return content return content
@ -225,18 +223,18 @@ def get_ini_content():
def find_begin_end(text, begin_s, end_s): def find_begin_end(text, begin_s, end_s):
begin_index = text.find(begin_s) begin_index = text.find(begin_s)
if begin_index == -1: if begin_index == -1:
raise EsphomeError(u"Could not find auto generated code begin in file, either " raise EsphomeError("Could not find auto generated code begin in file, either "
u"delete the main sketch file or insert the comment again.") "delete the main sketch file or insert the comment again.")
if text.find(begin_s, begin_index + 1) != -1: if text.find(begin_s, begin_index + 1) != -1:
raise EsphomeError(u"Found multiple auto generate code begins, don't know " raise EsphomeError("Found multiple auto generate code begins, don't know "
u"which to chose, please remove one of them.") "which to chose, please remove one of them.")
end_index = text.find(end_s) end_index = text.find(end_s)
if end_index == -1: if end_index == -1:
raise EsphomeError(u"Could not find auto generated code end in file, either " raise EsphomeError("Could not find auto generated code end in file, either "
u"delete the main sketch file or insert the comment again.") "delete the main sketch file or insert the comment again.")
if text.find(end_s, end_index + 1) != -1: if text.find(end_s, end_index + 1) != -1:
raise EsphomeError(u"Found multiple auto generate code endings, don't know " raise EsphomeError("Found multiple auto generate code endings, don't know "
u"which to chose, please remove one of them.") "which to chose, please remove one of them.")
return text[:begin_index], text[(end_index + len(end_s)):] return text[:begin_index], text[(end_index + len(end_s)):]
@ -263,17 +261,17 @@ def write_platformio_project():
write_platformio_ini(content) write_platformio_ini(content)
DEFINES_H_FORMAT = ESPHOME_H_FORMAT = u"""\ DEFINES_H_FORMAT = ESPHOME_H_FORMAT = """\
#pragma once #pragma once
{} {}
""" """
VERSION_H_FORMAT = u"""\ VERSION_H_FORMAT = """\
#pragma once #pragma once
#define ESPHOME_VERSION "{}" #define ESPHOME_VERSION "{}"
""" """
DEFINES_H_TARGET = 'esphome/core/defines.h' DEFINES_H_TARGET = 'esphome/core/defines.h'
VERSION_H_TARGET = 'esphome/core/version.h' VERSION_H_TARGET = 'esphome/core/version.h'
ESPHOME_README_TXT = u""" ESPHOME_README_TXT = """
THIS DIRECTORY IS AUTO-GENERATED, DO NOT MODIFY THIS DIRECTORY IS AUTO-GENERATED, DO NOT MODIFY
ESPHome automatically populates the esphome/ directory, and any ESPHome automatically populates the esphome/ directory, and any
@ -298,9 +296,9 @@ def copy_src_tree():
include_l = [] include_l = []
for target, path in source_files_l: for target, path in source_files_l:
if os.path.splitext(path)[1] in HEADER_FILE_EXTENSIONS: if os.path.splitext(path)[1] in HEADER_FILE_EXTENSIONS:
include_l.append(u'#include "{}"'.format(target)) include_l.append(f'#include "{target}"')
include_l.append(u'') include_l.append('')
include_s = u'\n'.join(include_l) include_s = '\n'.join(include_l)
source_files_copy = source_files.copy() source_files_copy = source_files.copy()
source_files_copy.pop(DEFINES_H_TARGET) source_files_copy.pop(DEFINES_H_TARGET)
@ -340,7 +338,7 @@ def copy_src_tree():
def generate_defines_h(): def generate_defines_h():
define_content_l = [x.as_macro for x in CORE.defines] define_content_l = [x.as_macro for x in CORE.defines]
define_content_l.sort() define_content_l.sort()
return DEFINES_H_FORMAT.format(u'\n'.join(define_content_l)) return DEFINES_H_FORMAT.format('\n'.join(define_content_l))
def write_cpp(code_s): def write_cpp(code_s):
@ -354,11 +352,11 @@ def write_cpp(code_s):
code_format = CPP_BASE_FORMAT code_format = CPP_BASE_FORMAT
copy_src_tree() copy_src_tree()
global_s = u'#include "esphome.h"\n' global_s = '#include "esphome.h"\n'
global_s += CORE.cpp_global_section global_s += CORE.cpp_global_section
full_file = code_format[0] + CPP_INCLUDE_BEGIN + u'\n' + global_s + CPP_INCLUDE_END full_file = code_format[0] + CPP_INCLUDE_BEGIN + '\n' + global_s + CPP_INCLUDE_END
full_file += code_format[1] + CPP_AUTO_GENERATE_BEGIN + u'\n' + code_s + CPP_AUTO_GENERATE_END full_file += code_format[1] + CPP_AUTO_GENERATE_BEGIN + '\n' + code_s + CPP_AUTO_GENERATE_END
full_file += code_format[2] full_file += code_format[2]
write_file_if_changed(path, full_file) write_file_if_changed(path, full_file)

View file

@ -1,5 +1,3 @@
from __future__ import print_function
import fnmatch import fnmatch
import functools import functools
import inspect import inspect
@ -15,7 +13,6 @@ from esphome import core
from esphome.config_helpers import read_config_file from esphome.config_helpers import read_config_file
from esphome.core import EsphomeError, IPAddress, Lambda, MACAddress, TimePeriod, DocumentRange from esphome.core import EsphomeError, IPAddress, Lambda, MACAddress, TimePeriod, DocumentRange
from esphome.helpers import add_class_to_obj from esphome.helpers import add_class_to_obj
from esphome.py_compat import text_type, IS_PY2
from esphome.util import OrderedDict, filter_yaml_files from esphome.util import OrderedDict, filter_yaml_files
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -23,12 +20,12 @@ _LOGGER = logging.getLogger(__name__)
# Mostly copied from Home Assistant because that code works fine and # Mostly copied from Home Assistant because that code works fine and
# let's not reinvent the wheel here # let's not reinvent the wheel here
SECRET_YAML = u'secrets.yaml' SECRET_YAML = 'secrets.yaml'
_SECRET_CACHE = {} _SECRET_CACHE = {}
_SECRET_VALUES = {} _SECRET_VALUES = {}
class ESPHomeDataBase(object): class ESPHomeDataBase:
@property @property
def esp_range(self): def esp_range(self):
return getattr(self, '_esp_range', None) return getattr(self, '_esp_range', None)
@ -38,7 +35,7 @@ class ESPHomeDataBase(object):
self._esp_range = DocumentRange.from_marks(node.start_mark, node.end_mark) self._esp_range = DocumentRange.from_marks(node.start_mark, node.end_mark)
class ESPForceValue(object): class ESPForceValue:
pass pass
@ -74,27 +71,27 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors
@_add_data_ref @_add_data_ref
def construct_yaml_int(self, node): def construct_yaml_int(self, node):
return super(ESPHomeLoader, self).construct_yaml_int(node) return super().construct_yaml_int(node)
@_add_data_ref @_add_data_ref
def construct_yaml_float(self, node): def construct_yaml_float(self, node):
return super(ESPHomeLoader, self).construct_yaml_float(node) return super().construct_yaml_float(node)
@_add_data_ref @_add_data_ref
def construct_yaml_binary(self, node): def construct_yaml_binary(self, node):
return super(ESPHomeLoader, self).construct_yaml_binary(node) return super().construct_yaml_binary(node)
@_add_data_ref @_add_data_ref
def construct_yaml_omap(self, node): def construct_yaml_omap(self, node):
return super(ESPHomeLoader, self).construct_yaml_omap(node) return super().construct_yaml_omap(node)
@_add_data_ref @_add_data_ref
def construct_yaml_str(self, node): def construct_yaml_str(self, node):
return super(ESPHomeLoader, self).construct_yaml_str(node) return super().construct_yaml_str(node)
@_add_data_ref @_add_data_ref
def construct_yaml_seq(self, node): def construct_yaml_seq(self, node):
return super(ESPHomeLoader, self).construct_yaml_seq(node) return super().construct_yaml_seq(node)
@_add_data_ref @_add_data_ref
def construct_yaml_map(self, node): def construct_yaml_map(self, node):
@ -130,12 +127,12 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors
hash(key) hash(key)
except TypeError: except TypeError:
raise yaml.constructor.ConstructorError( raise yaml.constructor.ConstructorError(
'Invalid key "{}" (not hashable)'.format(key), key_node.start_mark) f'Invalid key "{key}" (not hashable)', key_node.start_mark)
# Check if it is a duplicate key # Check if it is a duplicate key
if key in seen_keys: if key in seen_keys:
raise yaml.constructor.ConstructorError( raise yaml.constructor.ConstructorError(
'Duplicate key "{}"'.format(key), key_node.start_mark, f'Duplicate key "{key}"', key_node.start_mark,
'NOTE: Previous declaration here:', seen_keys[key], 'NOTE: Previous declaration here:', seen_keys[key],
) )
seen_keys[key] = key_node.start_mark seen_keys[key] = key_node.start_mark
@ -194,11 +191,11 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors
args = node.value.split() args = node.value.split()
# Check for a default value # Check for a default value
if len(args) > 1: if len(args) > 1:
return os.getenv(args[0], u' '.join(args[1:])) return os.getenv(args[0], ' '.join(args[1:]))
if args[0] in os.environ: if args[0] in os.environ:
return os.environ[args[0]] return os.environ[args[0]]
raise yaml.MarkedYAMLError( raise yaml.MarkedYAMLError(
u"Environment variable '{}' not defined".format(node.value), node.start_mark f"Environment variable '{node.value}' not defined", node.start_mark
) )
@property @property
@ -213,10 +210,10 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors
secrets = _load_yaml_internal(self._rel_path(SECRET_YAML)) secrets = _load_yaml_internal(self._rel_path(SECRET_YAML))
if node.value not in secrets: if node.value not in secrets:
raise yaml.MarkedYAMLError( raise yaml.MarkedYAMLError(
u"Secret '{}' not defined".format(node.value), node.start_mark f"Secret '{node.value}' not defined", node.start_mark
) )
val = secrets[node.value] val = secrets[node.value]
_SECRET_VALUES[text_type(val)] = node.value _SECRET_VALUES[str(val)] = node.value
return val return val
@_add_data_ref @_add_data_ref
@ -259,7 +256,7 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors
@_add_data_ref @_add_data_ref
def construct_lambda(self, node): def construct_lambda(self, node):
return Lambda(text_type(node.value)) return Lambda(str(node.value))
@_add_data_ref @_add_data_ref
def construct_force(self, node): def construct_force(self, node):
@ -267,13 +264,13 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors
return add_class_to_obj(obj, ESPForceValue) return add_class_to_obj(obj, ESPForceValue)
ESPHomeLoader.add_constructor(u'tag:yaml.org,2002:int', ESPHomeLoader.construct_yaml_int) ESPHomeLoader.add_constructor('tag:yaml.org,2002:int', ESPHomeLoader.construct_yaml_int)
ESPHomeLoader.add_constructor(u'tag:yaml.org,2002:float', ESPHomeLoader.construct_yaml_float) ESPHomeLoader.add_constructor('tag:yaml.org,2002:float', ESPHomeLoader.construct_yaml_float)
ESPHomeLoader.add_constructor(u'tag:yaml.org,2002:binary', ESPHomeLoader.construct_yaml_binary) ESPHomeLoader.add_constructor('tag:yaml.org,2002:binary', ESPHomeLoader.construct_yaml_binary)
ESPHomeLoader.add_constructor(u'tag:yaml.org,2002:omap', ESPHomeLoader.construct_yaml_omap) ESPHomeLoader.add_constructor('tag:yaml.org,2002:omap', ESPHomeLoader.construct_yaml_omap)
ESPHomeLoader.add_constructor(u'tag:yaml.org,2002:str', ESPHomeLoader.construct_yaml_str) ESPHomeLoader.add_constructor('tag:yaml.org,2002:str', ESPHomeLoader.construct_yaml_str)
ESPHomeLoader.add_constructor(u'tag:yaml.org,2002:seq', ESPHomeLoader.construct_yaml_seq) ESPHomeLoader.add_constructor('tag:yaml.org,2002:seq', ESPHomeLoader.construct_yaml_seq)
ESPHomeLoader.add_constructor(u'tag:yaml.org,2002:map', ESPHomeLoader.construct_yaml_map) ESPHomeLoader.add_constructor('tag:yaml.org,2002:map', ESPHomeLoader.construct_yaml_map)
ESPHomeLoader.add_constructor('!env_var', ESPHomeLoader.construct_env_var) ESPHomeLoader.add_constructor('!env_var', ESPHomeLoader.construct_env_var)
ESPHomeLoader.add_constructor('!secret', ESPHomeLoader.construct_secret) ESPHomeLoader.add_constructor('!secret', ESPHomeLoader.construct_secret)
ESPHomeLoader.add_constructor('!include', ESPHomeLoader.construct_include) ESPHomeLoader.add_constructor('!include', ESPHomeLoader.construct_include)
@ -313,7 +310,7 @@ def dump(dict_):
def _is_file_valid(name): def _is_file_valid(name):
"""Decide if a file is valid.""" """Decide if a file is valid."""
return not name.startswith(u'.') return not name.startswith('.')
def _find_files(directory, pattern): def _find_files(directory, pattern):
@ -328,7 +325,7 @@ def _find_files(directory, pattern):
def is_secret(value): def is_secret(value):
try: try:
return _SECRET_VALUES[text_type(value)] return _SECRET_VALUES[str(value)]
except (KeyError, ValueError): except (KeyError, ValueError):
return None return None
@ -358,31 +355,31 @@ class ESPHomeDumper(yaml.SafeDumper): # pylint: disable=too-many-ancestors
return node return node
def represent_secret(self, value): def represent_secret(self, value):
return self.represent_scalar(tag=u'!secret', value=_SECRET_VALUES[text_type(value)]) return self.represent_scalar(tag='!secret', value=_SECRET_VALUES[str(value)])
def represent_stringify(self, value): def represent_stringify(self, value):
if is_secret(value): if is_secret(value):
return self.represent_secret(value) return self.represent_secret(value)
return self.represent_scalar(tag=u'tag:yaml.org,2002:str', value=text_type(value)) return self.represent_scalar(tag='tag:yaml.org,2002:str', value=str(value))
# pylint: disable=arguments-differ # pylint: disable=arguments-differ
def represent_bool(self, value): def represent_bool(self, value):
return self.represent_scalar(u'tag:yaml.org,2002:bool', u'true' if value else u'false') return self.represent_scalar('tag:yaml.org,2002:bool', 'true' if value else 'false')
def represent_int(self, value): def represent_int(self, value):
if is_secret(value): if is_secret(value):
return self.represent_secret(value) return self.represent_secret(value)
return self.represent_scalar(tag=u'tag:yaml.org,2002:int', value=text_type(value)) return self.represent_scalar(tag='tag:yaml.org,2002:int', value=str(value))
def represent_float(self, value): def represent_float(self, value):
if is_secret(value): if is_secret(value):
return self.represent_secret(value) return self.represent_secret(value)
if math.isnan(value): if math.isnan(value):
value = u'.nan' value = '.nan'
elif math.isinf(value): elif math.isinf(value):
value = u'.inf' if value > 0 else u'-.inf' value = '.inf' if value > 0 else '-.inf'
else: else:
value = text_type(repr(value)).lower() value = str(repr(value)).lower()
# Note that in some cases `repr(data)` represents a float number # Note that in some cases `repr(data)` represents a float number
# without the decimal parts. For instance: # without the decimal parts. For instance:
# >>> repr(1e17) # >>> repr(1e17)
@ -390,9 +387,9 @@ class ESPHomeDumper(yaml.SafeDumper): # pylint: disable=too-many-ancestors
# Unfortunately, this is not a valid float representation according # Unfortunately, this is not a valid float representation according
# to the definition of the `!!float` tag. We fix this by adding # to the definition of the `!!float` tag. We fix this by adding
# '.0' before the 'e' symbol. # '.0' before the 'e' symbol.
if u'.' not in value and u'e' in value: if '.' not in value and 'e' in value:
value = value.replace(u'e', u'.0e', 1) value = value.replace('e', '.0e', 1)
return self.represent_scalar(tag=u'tag:yaml.org,2002:float', value=value) return self.represent_scalar(tag='tag:yaml.org,2002:float', value=value)
def represent_lambda(self, value): def represent_lambda(self, value):
if is_secret(value.value): if is_secret(value.value):
@ -417,9 +414,6 @@ ESPHomeDumper.add_multi_representer(bool, ESPHomeDumper.represent_bool)
ESPHomeDumper.add_multi_representer(str, ESPHomeDumper.represent_stringify) ESPHomeDumper.add_multi_representer(str, ESPHomeDumper.represent_stringify)
ESPHomeDumper.add_multi_representer(int, ESPHomeDumper.represent_int) ESPHomeDumper.add_multi_representer(int, ESPHomeDumper.represent_int)
ESPHomeDumper.add_multi_representer(float, ESPHomeDumper.represent_float) ESPHomeDumper.add_multi_representer(float, ESPHomeDumper.represent_float)
if IS_PY2:
ESPHomeDumper.add_multi_representer(unicode, ESPHomeDumper.represent_stringify)
ESPHomeDumper.add_multi_representer(long, ESPHomeDumper.represent_int)
ESPHomeDumper.add_multi_representer(IPAddress, ESPHomeDumper.represent_stringify) ESPHomeDumper.add_multi_representer(IPAddress, ESPHomeDumper.represent_stringify)
ESPHomeDumper.add_multi_representer(MACAddress, ESPHomeDumper.represent_stringify) ESPHomeDumper.add_multi_representer(MACAddress, ESPHomeDumper.represent_stringify)
ESPHomeDumper.add_multi_representer(TimePeriod, ESPHomeDumper.represent_stringify) ESPHomeDumper.add_multi_representer(TimePeriod, ESPHomeDumper.represent_stringify)

View file

@ -12,8 +12,6 @@ import time
import ifaddr import ifaddr
from esphome.py_compat import indexbytes, text_type
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
# Some timing constants # Some timing constants
@ -83,7 +81,7 @@ class IncomingDecodeError(Error):
# pylint: disable=no-init # pylint: disable=no-init
class QuietLogger(object): class QuietLogger:
_seen_logs = {} _seen_logs = {}
@classmethod @classmethod
@ -112,7 +110,7 @@ class QuietLogger(object):
logger(*args) logger(*args)
class DNSEntry(object): class DNSEntry:
"""A DNS entry""" """A DNS entry"""
def __init__(self, name, type_, class_): def __init__(self, name, type_, class_):
@ -281,7 +279,7 @@ class DNSIncoming(QuietLogger):
def read_utf(self, offset, length): def read_utf(self, offset, length):
"""Reads a UTF-8 string of a given length from the packet""" """Reads a UTF-8 string of a given length from the packet"""
return text_type(self.data[offset:offset + length], 'utf-8', 'replace') return str(self.data[offset:offset + length], 'utf-8', 'replace')
def read_name(self): def read_name(self):
"""Reads a domain name from the packet""" """Reads a domain name from the packet"""
@ -291,7 +289,7 @@ class DNSIncoming(QuietLogger):
first = off first = off
while True: while True:
length = indexbytes(self.data, off) length = self.data[off]
off += 1 off += 1
if length == 0: if length == 0:
break break
@ -302,13 +300,13 @@ class DNSIncoming(QuietLogger):
elif t == 0xC0: elif t == 0xC0:
if next_ < 0: if next_ < 0:
next_ = off + 1 next_ = off + 1
off = ((length & 0x3F) << 8) | indexbytes(self.data, off) off = ((length & 0x3F) << 8) | self.data[off]
if off >= first: if off >= first:
raise IncomingDecodeError( raise IncomingDecodeError(
"Bad domain name (circular) at %s" % (off,)) f"Bad domain name (circular) at {off}")
first = off first = off
else: else:
raise IncomingDecodeError("Bad domain name at %s" % (off,)) raise IncomingDecodeError(f"Bad domain name at {off}")
if next_ >= 0: if next_ >= 0:
self.offset = next_ self.offset = next_
@ -318,7 +316,7 @@ class DNSIncoming(QuietLogger):
return result return result
class DNSOutgoing(object): class DNSOutgoing:
"""Object representation of an outgoing packet""" """Object representation of an outgoing packet"""
def __init__(self, flags): def __init__(self, flags):
@ -461,7 +459,7 @@ class Engine(threading.Thread):
if reader: if reader:
reader.handle_read(socket_) reader.handle_read(socket_)
except (select.error, socket.error) as e: except OSError as e:
# If the socket was closed by another thread, during # If the socket was closed by another thread, during
# shutdown, ignore it and exit # shutdown, ignore it and exit
if e.args[0] != socket.EBADF or not self.zc.done: if e.args[0] != socket.EBADF or not self.zc.done:
@ -500,7 +498,7 @@ class Listener(QuietLogger):
self.zc.handle_response(msg) self.zc.handle_response(msg)
class RecordUpdateListener(object): class RecordUpdateListener:
def update_record(self, zc, now, record): def update_record(self, zc, now, record):
raise NotImplementedError() raise NotImplementedError()
@ -578,7 +576,7 @@ class DashboardStatus(RecordUpdateListener, threading.Thread):
self.on_update({key: self.host_status(key) for key in self.key_to_host}) self.on_update({key: self.host_status(key) for key in self.key_to_host})
def request_query(self, hosts): def request_query(self, hosts):
self.query_hosts = set(host for host in hosts.values()) self.query_hosts = set(hosts.values())
self.key_to_host = hosts self.key_to_host = hosts
self.query_event.set() self.query_event.set()
@ -605,12 +603,12 @@ class DashboardStatus(RecordUpdateListener, threading.Thread):
def get_all_addresses(): def get_all_addresses():
return list(set( return list({
addr.ip addr.ip
for iface in ifaddr.get_adapters() for iface in ifaddr.get_adapters()
for addr in iface.ips for addr in iface.ips
if addr.is_IPv4 and addr.network_prefix != 32 # Host only netmask 255.255.255.255 if addr.is_IPv4 and addr.network_prefix != 32 # Host only netmask 255.255.255.255
)) })
def new_socket(): def new_socket():
@ -631,7 +629,7 @@ def new_socket():
else: else:
try: try:
s.setsockopt(socket.SOL_SOCKET, reuseport, 1) s.setsockopt(socket.SOL_SOCKET, reuseport, 1)
except (OSError, socket.error) as err: except OSError as err:
# OSError on python 3, socket.error on python 2 # OSError on python 3, socket.error on python 2
if err.errno != errno.ENOPROTOOPT: if err.errno != errno.ENOPROTOOPT:
raise raise
@ -662,7 +660,7 @@ class Zeroconf(QuietLogger):
_value = socket.inet_aton(_MDNS_ADDR) + socket.inet_aton(i) _value = socket.inet_aton(_MDNS_ADDR) + socket.inet_aton(i)
self._listen_socket.setsockopt( self._listen_socket.setsockopt(
socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, _value) socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, _value)
except socket.error as e: except OSError as e:
_errno = e.args[0] _errno = e.args[0]
if _errno == errno.EADDRINUSE: if _errno == errno.EADDRINUSE:
log.info( log.info(

View file

@ -25,9 +25,3 @@ disable=
stop-iteration-return, stop-iteration-return,
no-self-use, no-self-use,
import-outside-toplevel, import-outside-toplevel,
additional-builtins=
unicode,
long,
raw_input

View file

@ -3,7 +3,6 @@ PyYAML==5.2
paho-mqtt==1.5.0 paho-mqtt==1.5.0
colorlog==4.0.2 colorlog==4.0.2
tornado==5.1.1 tornado==5.1.1
typing>=3.6.6;python_version<"3.5"
protobuf==3.11.1 protobuf==3.11.1
tzlocal==2.0.0 tzlocal==2.0.0
pytz==2019.3 pytz==2019.3

View file

@ -3,7 +3,6 @@ PyYAML==5.2
paho-mqtt==1.5.0 paho-mqtt==1.5.0
colorlog==4.0.2 colorlog==4.0.2
tornado==5.1.1 tornado==5.1.1
typing>=3.6.6;python_version<"3.5"
protobuf==3.11.1 protobuf==3.11.1
tzlocal==2.0.0 tzlocal==2.0.0
pytz==2019.3 pytz==2019.3

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: api_options.proto # source: api_options.proto

View file

@ -39,12 +39,12 @@ content = prot.read_bytes()
d = descriptor.FileDescriptorSet.FromString(content) d = descriptor.FileDescriptorSet.FromString(content)
def indent_list(text, padding=u' '): def indent_list(text, padding=' '):
return [padding + line for line in text.splitlines()] return [padding + line for line in text.splitlines()]
def indent(text, padding=u' '): def indent(text, padding=' '):
return u'\n'.join(indent_list(text, padding)) return '\n'.join(indent_list(text, padding))
def camel_to_snake(name): def camel_to_snake(name):
@ -432,7 +432,7 @@ class SInt64Type(TypeInfo):
class RepeatedTypeInfo(TypeInfo): class RepeatedTypeInfo(TypeInfo):
def __init__(self, field): def __init__(self, field):
super(RepeatedTypeInfo, self).__init__(field) super().__init__(field)
self._ti = TYPE_INFO[field.type](field) self._ti = TYPE_INFO[field.type](field)
@property @property

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
import sys import sys
import os.path import os.path

View file

@ -1,5 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
from __future__ import print_function
import codecs import codecs
import collections import collections
@ -105,7 +104,7 @@ def lint_re_check(regex, **kwargs):
err = func(fname, match) err = func(fname, match)
if err is None: if err is None:
continue continue
errors.append("{} See line {}.".format(err, lineno)) errors.append(f"{err} See line {lineno}.")
return errors return errors
return decor(new_func) return decor(new_func)
return decorator return decorator
@ -134,7 +133,7 @@ def lint_ino(fname):
return "This file extension (.ino) is not allowed. Please use either .cpp or .h" return "This file extension (.ino) is not allowed. Please use either .cpp or .h"
@lint_file_check(exclude=['*{}'.format(f) for f in file_types] + [ @lint_file_check(exclude=[f'*{f}' for f in file_types] + [
'.clang-*', '.dockerignore', '.editorconfig', '*.gitignore', 'LICENSE', 'pylintrc', '.clang-*', '.dockerignore', '.editorconfig', '*.gitignore', 'LICENSE', 'pylintrc',
'MANIFEST.in', 'docker/Dockerfile*', 'docker/rootfs/*', 'script/*', 'MANIFEST.in', 'docker/Dockerfile*', 'docker/rootfs/*', 'script/*',
]) ])
@ -177,7 +176,7 @@ CPP_RE_EOL = r'\s*?(?://.*?)?$'
def highlight(s): def highlight(s):
return '\033[36m{}\033[0m'.format(s) return f'\033[36m{s}\033[0m'
@lint_re_check(r'^#define\s+([a-zA-Z0-9_]+)\s+([0-9bx]+)' + CPP_RE_EOL, @lint_re_check(r'^#define\s+([a-zA-Z0-9_]+)\s+([0-9bx]+)' + CPP_RE_EOL,
@ -268,7 +267,7 @@ def lint_constants_usage():
def relative_cpp_search_text(fname, content): def relative_cpp_search_text(fname, content):
parts = fname.split('/') parts = fname.split('/')
integration = parts[2] integration = parts[2]
return '#include "esphome/components/{}'.format(integration) return f'#include "esphome/components/{integration}'
@lint_content_find_check(relative_cpp_search_text, include=['esphome/components/*.cpp']) @lint_content_find_check(relative_cpp_search_text, include=['esphome/components/*.cpp'])
@ -284,7 +283,7 @@ def lint_relative_cpp_import(fname):
def relative_py_search_text(fname, content): def relative_py_search_text(fname, content):
parts = fname.split('/') parts = fname.split('/')
integration = parts[2] integration = parts[2]
return 'esphome.components.{}'.format(integration) return f'esphome.components.{integration}'
@lint_content_find_check(relative_py_search_text, include=['esphome/components/*.py'], @lint_content_find_check(relative_py_search_text, include=['esphome/components/*.py'],
@ -303,7 +302,7 @@ def lint_relative_py_import(fname):
def lint_namespace(fname, content): def lint_namespace(fname, content):
expected_name = re.match(r'^esphome/components/([^/]+)/.*', expected_name = re.match(r'^esphome/components/([^/]+)/.*',
fname.replace(os.path.sep, '/')).group(1) fname.replace(os.path.sep, '/')).group(1)
search = 'namespace {}'.format(expected_name) search = f'namespace {expected_name}'
if search in content: if search in content:
return None return None
return 'Invalid namespace found in C++ file. All integration C++ files should put all ' \ return 'Invalid namespace found in C++ file. All integration C++ files should put all ' \
@ -380,7 +379,7 @@ for fname in files:
run_checks(LINT_POST_CHECKS, 'POST') run_checks(LINT_POST_CHECKS, 'POST')
for f, errs in sorted(errors.items()): for f, errs in sorted(errors.items()):
print("\033[0;32m************* File \033[1;32m{}\033[0m".format(f)) print(f"\033[0;32m************* File \033[1;32m{f}\033[0m")
for err in errs: for err in errs:
print(err) print(err)
print() print()

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
from __future__ import print_function from __future__ import print_function

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
from __future__ import print_function from __future__ import print_function

View file

@ -12,11 +12,11 @@ temp_header_file = os.path.join(root_path, '.temp-clang-tidy.cpp')
def shlex_quote(s): def shlex_quote(s):
if not s: if not s:
return u"''" return "''"
if re.search(r'[^\w@%+=:,./-]', s) is None: if re.search(r'[^\w@%+=:,./-]', s) is None:
return s return s
return u"'" + s.replace(u"'", u"'\"'\"'") + u"'" return "'" + s.replace("'", "'\"'\"'") + "'"
def build_all_include(): def build_all_include():
@ -29,7 +29,7 @@ def build_all_include():
if ext in filetypes: if ext in filetypes:
path = os.path.relpath(path, root_path) path = os.path.relpath(path, root_path)
include_p = path.replace(os.path.sep, '/') include_p = path.replace(os.path.sep, '/')
headers.append('#include "{}"'.format(include_p)) headers.append(f'#include "{include_p}"')
headers.sort() headers.sort()
headers.append('') headers.append('')
content = '\n'.join(headers) content = '\n'.join(headers)
@ -47,7 +47,7 @@ def build_compile_commands():
gcc_flags = json.load(f) gcc_flags = json.load(f)
exec_path = gcc_flags['execPath'] exec_path = gcc_flags['execPath']
include_paths = gcc_flags['gccIncludePaths'].split(',') include_paths = gcc_flags['gccIncludePaths'].split(',')
includes = ['-I{}'.format(p) for p in include_paths] includes = [f'-I{p}' for p in include_paths]
cpp_flags = gcc_flags['gccDefaultCppFlags'].split(' ') cpp_flags = gcc_flags['gccDefaultCppFlags'].split(' ')
defines = [flag for flag in cpp_flags if flag.startswith('-D')] defines = [flag for flag in cpp_flags if flag.startswith('-D')]
command = [exec_path] command = [exec_path]
@ -102,7 +102,7 @@ def splitlines_no_ends(string):
def changed_files(): def changed_files():
for remote in ('upstream', 'origin'): for remote in ('upstream', 'origin'):
command = ['git', 'merge-base', '{}/dev'.format(remote), 'HEAD'] command = ['git', 'merge-base', f'{remote}/dev', 'HEAD']
try: try:
merge_base = splitlines_no_ends(get_output(*command))[0] merge_base = splitlines_no_ends(get_output(*command))[0]
break break
@ -124,7 +124,7 @@ def filter_changed(files):
if not files: if not files:
print(" No changed files!") print(" No changed files!")
for c in files: for c in files:
print(" {}".format(c)) print(f" {c}")
return files return files

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
from __future__ import print_function from __future__ import print_function
@ -61,7 +61,7 @@ def main():
continue continue
file_ = line[0] file_ = line[0]
linno = line[1] linno = line[1]
msg = (u':'.join(line[3:])).strip() msg = (':'.join(line[3:])).strip()
print_error(file_, linno, msg) print_error(file_, linno, msg)
errors += 1 errors += 1
@ -74,7 +74,7 @@ def main():
continue continue
file_ = line[0] file_ = line[0]
linno = line[1] linno = line[1]
msg = (u':'.join(line[2:])).strip() msg = (':'.join(line[2:])).strip()
print_error(file_, linno, msg) print_error(file_, linno, msg)
errors += 1 errors += 1

View file

@ -18,7 +18,6 @@ Topic :: Home Automation
[flake8] [flake8]
max-line-length = 120 max-line-length = 120
builtins = unicode, long, raw_input, basestring
exclude = api_pb2.py exclude = api_pb2.py
[bdist_wheel] [bdist_wheel]

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
"""esphome setup script.""" """esphome setup script."""
from setuptools import setup, find_packages from setuptools import setup, find_packages
import os import os
@ -28,7 +28,6 @@ REQUIRES = [
'paho-mqtt==1.5.0', 'paho-mqtt==1.5.0',
'colorlog==4.0.2', 'colorlog==4.0.2',
'tornado==5.1.1', 'tornado==5.1.1',
'typing>=3.6.6;python_version<"3.6"',
'protobuf==3.11.1', 'protobuf==3.11.1',
'tzlocal==2.0.0', 'tzlocal==2.0.0',
'pytz==2019.3', 'pytz==2019.3',
@ -69,7 +68,7 @@ setup(
zip_safe=False, zip_safe=False,
platforms='any', platforms='any',
test_suite='tests', test_suite='tests',
python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,<4.0', python_requires='>=3.6,<4.0',
install_requires=REQUIRES, install_requires=REQUIRES,
keywords=['home', 'automation'], keywords=['home', 'automation'],
entry_points={ entry_points={