mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	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:
		| @@ -21,12 +21,6 @@ matrix: | ||||
|         - esphome tests/test1.yaml compile | ||||
|         - esphome tests/test2.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 | ||||
|       dist: trusty | ||||
|       sudo: required | ||||
|   | ||||
| @@ -14,7 +14,5 @@ RUN \ | ||||
| COPY requirements_test.txt /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"] | ||||
| WORKDIR /esphome | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| from __future__ import print_function | ||||
|  | ||||
| import argparse | ||||
| import functools | ||||
| 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 | ||||
| from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority | ||||
| 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 | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
| @@ -42,12 +39,12 @@ def choose_prompt(options): | ||||
|     if len(options) == 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): | ||||
|         safe_print(u"  [{}] {}".format(i + 1, desc)) | ||||
|         safe_print(f"  [{i+1}] {desc}") | ||||
|  | ||||
|     while True: | ||||
|         opt = safe_input('(number): ') | ||||
|         opt = input('(number): ') | ||||
|         if opt in options: | ||||
|             opt = options.index(opt) | ||||
|             break | ||||
| @@ -57,20 +54,20 @@ def choose_prompt(options): | ||||
|                 raise ValueError | ||||
|             break | ||||
|         except ValueError: | ||||
|             safe_print(color('red', u"Invalid option: '{}'".format(opt))) | ||||
|             safe_print(color('red', f"Invalid option: '{opt}'")) | ||||
|     return options[opt - 1][1] | ||||
|  | ||||
|  | ||||
| def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api): | ||||
|     options = [] | ||||
|     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): | ||||
|         options.append((u"Over The Air ({})".format(CORE.address), CORE.address)) | ||||
|         options.append((f"Over The Air ({CORE.address})", CORE.address)) | ||||
|         if default == 'OTA': | ||||
|             return CORE.address | ||||
|     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': | ||||
|             return 'MQTT' | ||||
|     if default is not None: | ||||
| @@ -108,11 +105,7 @@ def run_miniterm(config, port): | ||||
|             except serial.SerialException: | ||||
|                 _LOGGER.error("Serial port closed!") | ||||
|                 return | ||||
|             if IS_PY2: | ||||
|                 line = raw.replace('\r', '').replace('\n', '') | ||||
|             else: | ||||
|                 line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8', | ||||
|                                                                           'backslashreplace') | ||||
|             line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8', 'backslashreplace') | ||||
|             time = datetime.now().time().strftime('[%H:%M:%S]') | ||||
|             message = time + line | ||||
|             safe_print(message) | ||||
| @@ -127,11 +120,9 @@ def wrap_to_code(name, comp): | ||||
|     @functools.wraps(comp.to_code) | ||||
|     @coroutine_with_priority(coro.priority) | ||||
|     def wrapped(conf): | ||||
|         cg.add(cg.LineComment(u"{}:".format(name))) | ||||
|         cg.add(cg.LineComment(f"{name}:")) | ||||
|         if comp.config_schema is not None: | ||||
|             conf_str = yaml_util.dump(conf) | ||||
|             if IS_PY2: | ||||
|                 conf_str = conf_str.decode('utf-8') | ||||
|             conf_str = conf_str.replace('//', '') | ||||
|             cg.add(cg.LineComment(indent(conf_str))) | ||||
|         yield coro(conf) | ||||
| @@ -243,7 +234,7 @@ def setup_log(debug=False, quiet=False): | ||||
|         log_level = logging.INFO | ||||
|     logging.basicConfig(level=log_level) | ||||
|     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' | ||||
|  | ||||
|     logging.getLogger('urllib3').setLevel(logging.WARNING) | ||||
| @@ -292,12 +283,12 @@ def command_compile(args, config): | ||||
|     if exit_code != 0: | ||||
|         return exit_code | ||||
|     if args.only_generate: | ||||
|         _LOGGER.info(u"Successfully generated source code.") | ||||
|         _LOGGER.info("Successfully generated source code.") | ||||
|         return 0 | ||||
|     exit_code = compile_program(args, config) | ||||
|     if exit_code != 0: | ||||
|         return exit_code | ||||
|     _LOGGER.info(u"Successfully compiled program.") | ||||
|     _LOGGER.info("Successfully compiled program.") | ||||
|     return 0 | ||||
|  | ||||
|  | ||||
| @@ -307,7 +298,7 @@ def command_upload(args, config): | ||||
|     exit_code = upload_program(config, args, port) | ||||
|     if exit_code != 0: | ||||
|         return exit_code | ||||
|     _LOGGER.info(u"Successfully uploaded program.") | ||||
|     _LOGGER.info("Successfully uploaded program.") | ||||
|     return 0 | ||||
|  | ||||
|  | ||||
| @@ -324,13 +315,13 @@ def command_run(args, config): | ||||
|     exit_code = compile_program(args, config) | ||||
|     if exit_code != 0: | ||||
|         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, | ||||
|                                   show_ota=True, show_mqtt=False, show_api=True) | ||||
|     exit_code = upload_program(config, args, port) | ||||
|     if exit_code != 0: | ||||
|         return exit_code | ||||
|     _LOGGER.info(u"Successfully uploaded program.") | ||||
|     _LOGGER.info("Successfully uploaded program.") | ||||
|     if args.no_logs: | ||||
|         return 0 | ||||
|     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): | ||||
|     safe_print(u"Version: {}".format(const.__version__)) | ||||
|     safe_print(f"Version: {const.__version__}") | ||||
|     return 0 | ||||
|  | ||||
|  | ||||
| @@ -377,10 +368,10 @@ def command_update_all(args): | ||||
|     twidth = 60 | ||||
|  | ||||
|     def print_bar(middle_text): | ||||
|         middle_text = " {} ".format(middle_text) | ||||
|         middle_text = f" {middle_text} " | ||||
|         width = len(click.unstyle(middle_text)) | ||||
|         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: | ||||
|         print("Updating {}".format(color('cyan', f))) | ||||
| @@ -431,7 +422,7 @@ POST_CONFIG_ACTIONS = { | ||||
|  | ||||
|  | ||||
| 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.", | ||||
|                         action='store_true') | ||||
|     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.") | ||||
|         return 1 | ||||
|  | ||||
|     if IS_PY2: | ||||
|         _LOGGER.warning("You're using ESPHome with python 2. Support for python 2 is deprecated " | ||||
|                         "and will be removed in 1.15.0. Please reinstall ESPHome with python 3.6 " | ||||
|                         "or higher.") | ||||
|     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 sys.version_info < (3, 6, 0): | ||||
|         _LOGGER.error("You're running ESPHome with Python <3.6. ESPHome is no longer compatible " | ||||
|                       "with this Python version. Please reinstall ESPHome with Python 3.6+") | ||||
|         return 1 | ||||
|  | ||||
|     if args.command in PRE_CONFIG_ACTIONS: | ||||
|         try: | ||||
| @@ -551,7 +538,7 @@ def run_esphome(argv): | ||||
|         CORE.config = config | ||||
|  | ||||
|         if args.command not in POST_CONFIG_ACTIONS: | ||||
|             safe_print(u"Unknown command {}".format(args.command)) | ||||
|             safe_print(f"Unknown command {args.command}") | ||||
|  | ||||
|         try: | ||||
|             rc = POST_CONFIG_ACTIONS[args.command](args, config) | ||||
|   | ||||
| @@ -14,7 +14,6 @@ import esphome.api.api_pb2 as pb | ||||
| from esphome.const import CONF_PASSWORD, CONF_PORT | ||||
| from esphome.core import EsphomeError | ||||
| 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 | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
| @@ -67,16 +66,16 @@ MESSAGE_TYPE_TO_PROTO = { | ||||
|  | ||||
| def _varuint_to_bytes(value): | ||||
|     if value <= 0x7F: | ||||
|         return byte_to_bytes(value) | ||||
|         return bytes([value]) | ||||
|  | ||||
|     ret = bytes() | ||||
|     while value: | ||||
|         temp = value & 0x7F | ||||
|         value >>= 7 | ||||
|         if value: | ||||
|             ret += byte_to_bytes(temp | 0x80) | ||||
|             ret += bytes([temp | 0x80]) | ||||
|         else: | ||||
|             ret += byte_to_bytes(temp) | ||||
|             ret += bytes([temp]) | ||||
|  | ||||
|     return ret | ||||
|  | ||||
| @@ -84,8 +83,7 @@ def _varuint_to_bytes(value): | ||||
| def _bytes_to_varuint(value): | ||||
|     result = 0 | ||||
|     bitpos = 0 | ||||
|     for c in value: | ||||
|         val = char_to_byte(c) | ||||
|     for val in value: | ||||
|         result |= (val & 0x7F) << bitpos | ||||
|         bitpos += 7 | ||||
|         if (val & 0x80) == 0: | ||||
| @@ -191,8 +189,8 @@ class APIClient(threading.Thread): | ||||
|         self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) | ||||
|         try: | ||||
|             self._socket.connect((ip, self._port)) | ||||
|         except socket.error as err: | ||||
|             err = APIConnectionError("Error connecting to {}: {}".format(ip, err)) | ||||
|         except OSError as err: | ||||
|             err = APIConnectionError(f"Error connecting to {ip}: {err}") | ||||
|             self._fatal_error(err) | ||||
|             raise err | ||||
|         self._socket.settimeout(0.1) | ||||
| @@ -200,7 +198,7 @@ class APIClient(threading.Thread): | ||||
|         self._socket_open_event.set() | ||||
|  | ||||
|         hello = pb.HelloRequest() | ||||
|         hello.client_info = 'ESPHome v{}'.format(const.__version__) | ||||
|         hello.client_info = f'ESPHome v{const.__version__}' | ||||
|         try: | ||||
|             resp = self._send_message_await_response(hello, pb.HelloResponse) | ||||
|         except APIConnectionError as err: | ||||
| @@ -251,8 +249,8 @@ class APIClient(threading.Thread): | ||||
|         with self._socket_write_lock: | ||||
|             try: | ||||
|                 self._socket.sendall(data) | ||||
|             except socket.error as err: | ||||
|                 err = APIConnectionError("Error while writing data: {}".format(err)) | ||||
|             except OSError as err: | ||||
|                 err = APIConnectionError(f"Error while writing data: {err}") | ||||
|                 self._fatal_error(err) | ||||
|                 raise err | ||||
|  | ||||
| @@ -265,11 +263,8 @@ class APIClient(threading.Thread): | ||||
|             raise ValueError | ||||
|  | ||||
|         encoded = msg.SerializeToString() | ||||
|         _LOGGER.debug("Sending %s:\n%s", type(msg), indent(text_type(msg))) | ||||
|         if IS_PY2: | ||||
|             req = chr(0x00) | ||||
|         else: | ||||
|             req = bytes([0]) | ||||
|         _LOGGER.debug("Sending %s:\n%s", type(msg), indent(str(msg))) | ||||
|         req = bytes([0]) | ||||
|         req += _varuint_to_bytes(len(encoded)) | ||||
|         req += _varuint_to_bytes(message_type) | ||||
|         req += encoded | ||||
| @@ -355,14 +350,14 @@ class APIClient(threading.Thread): | ||||
|                 raise APIConnectionError("Socket was closed") | ||||
|             except socket.timeout: | ||||
|                 continue | ||||
|             except socket.error as err: | ||||
|                 raise APIConnectionError("Error while receiving data: {}".format(err)) | ||||
|             except OSError as err: | ||||
|                 raise APIConnectionError(f"Error while receiving data: {err}") | ||||
|             ret += val | ||||
|         return ret | ||||
|  | ||||
|     def _recv_varint(self): | ||||
|         raw = bytes() | ||||
|         while not raw or char_to_byte(raw[-1]) & 0x80: | ||||
|         while not raw or raw[-1] & 0x80: | ||||
|             raw += self._recv(1) | ||||
|         return _bytes_to_varuint(raw) | ||||
|  | ||||
| @@ -371,7 +366,7 @@ class APIClient(threading.Thread): | ||||
|             return | ||||
|  | ||||
|         # Preamble | ||||
|         if char_to_byte(self._recv(1)[0]) != 0x00: | ||||
|         if self._recv(1)[0] != 0x00: | ||||
|             raise APIConnectionError("Invalid preamble") | ||||
|  | ||||
|         length = self._recv_varint() | ||||
| @@ -436,7 +431,7 @@ def run_logs(config, address): | ||||
|             return | ||||
|  | ||||
|         if err: | ||||
|             _LOGGER.warning(u"Disconnected from API: %s", err) | ||||
|             _LOGGER.warning("Disconnected from API: %s", err) | ||||
|  | ||||
|         while retry_timer: | ||||
|             retry_timer.pop(0).cancel() | ||||
| @@ -454,18 +449,18 @@ def run_logs(config, address): | ||||
|  | ||||
|         wait_time = int(min(1.5**min(tries, 100), 30)) | ||||
|         if not has_connects: | ||||
|             _LOGGER.warning(u"Initial connection failed. The ESP might not be connected " | ||||
|                             u"to WiFi yet (%s). Re-Trying in %s seconds", | ||||
|             _LOGGER.warning("Initial connection failed. The ESP might not be connected " | ||||
|                             "to WiFi yet (%s). Re-Trying in %s seconds", | ||||
|                             error, wait_time) | ||||
|         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) | ||||
|         timer = threading.Timer(wait_time, functools.partial(try_connect, None, tries + 1)) | ||||
|         timer.start() | ||||
|         retry_timer.append(timer) | ||||
|  | ||||
|     def on_log(msg): | ||||
|         time_ = datetime.now().time().strftime(u'[%H:%M:%S]') | ||||
|         time_ = datetime.now().time().strftime('[%H:%M:%S]') | ||||
|         text = msg.message | ||||
|         if msg.send_failed: | ||||
|             text = color('white', '(Message skipped because it was too big to fit in ' | ||||
|   | ||||
| @@ -83,9 +83,9 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False): | ||||
|                 try: | ||||
|                     return cv.Schema([schema])(value) | ||||
|                 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 | ||||
|                     if u'Unable to find action' in str(err): | ||||
|                     if 'Unable to find action' in str(err): | ||||
|                         raise err2 | ||||
|                     raise cv.MultipleInvalid([err, err2]) | ||||
|         elif isinstance(value, dict): | ||||
|   | ||||
| @@ -36,4 +36,4 @@ def to_code(config): | ||||
|             continue | ||||
|         conf = config[key] | ||||
|         sens = yield sensor.new_sensor(conf) | ||||
|         cg.add(getattr(var, 'set_{}_sensor'.format(key))(sens)) | ||||
|         cg.add(getattr(var, f'set_{key}_sensor')(sens)) | ||||
|   | ||||
| @@ -2,7 +2,6 @@ import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, voltage_sampler | ||||
| 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 | ||||
|  | ||||
| DEPENDENCIES = ['ads1115'] | ||||
| @@ -32,9 +31,9 @@ GAIN = { | ||||
|  | ||||
| def validate_gain(value): | ||||
|     if isinstance(value, float): | ||||
|         value = u'{:0.03f}'.format(value) | ||||
|     elif not isinstance(value, string_types): | ||||
|         raise cv.Invalid('invalid gain "{}"'.format(value)) | ||||
|         value = f'{value:0.03f}' | ||||
|     elif not isinstance(value, str): | ||||
|         raise cv.Invalid(f'invalid gain "{value}"') | ||||
|  | ||||
|     return cv.enum(GAIN)(value) | ||||
|  | ||||
|   | ||||
| @@ -102,7 +102,7 @@ def homeassistant_service_to_code(config, action_id, template_arg, args): | ||||
|  | ||||
| def validate_homeassistant_event(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 " | ||||
|                          "esphome. For example 'esphome.xyz'") | ||||
|     return value | ||||
|   | ||||
| @@ -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_STATE, CONF_TIMING, CONF_TRIGGER_ID, CONF_FOR, CONF_NAME, CONF_MQTT_ID | ||||
| from esphome.core import CORE, coroutine, coroutine_with_priority | ||||
| from esphome.py_compat import string_types | ||||
| from esphome.util import Registry | ||||
|  | ||||
| DEVICE_CLASSES = [ | ||||
| @@ -94,7 +93,7 @@ MULTI_CLICK_TIMING_SCHEMA = cv.Schema({ | ||||
|  | ||||
|  | ||||
| def parse_multi_click_timing_str(value): | ||||
|     if not isinstance(value, string_types): | ||||
|     if not isinstance(value, str): | ||||
|         return value | ||||
|  | ||||
|     parts = value.lower().split(' ') | ||||
| @@ -104,10 +103,10 @@ def parse_multi_click_timing_str(value): | ||||
|     try: | ||||
|         state = cv.boolean(parts[0]) | ||||
|     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': | ||||
|         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[3] == 'least': | ||||
| @@ -115,12 +114,12 @@ def parse_multi_click_timing_str(value): | ||||
|         elif parts[3] == 'most': | ||||
|             key = CONF_MAX_LENGTH | ||||
|         else: | ||||
|             raise cv.Invalid(u"Third word after at must either be 'least' or 'most', got {}" | ||||
|                              u"".format(parts[3])) | ||||
|             raise cv.Invalid("Third word after at must either be 'least' or 'most', got {}" | ||||
|                              "".format(parts[3])) | ||||
|         try: | ||||
|             length = cv.positive_time_period_milliseconds(parts[4]) | ||||
|         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 { | ||||
|             CONF_STATE: state, | ||||
|             key: str(length) | ||||
| @@ -132,12 +131,12 @@ def parse_multi_click_timing_str(value): | ||||
|     try: | ||||
|         min_length = cv.positive_time_period_milliseconds(parts[2]) | ||||
|     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: | ||||
|         max_length = cv.positive_time_period_milliseconds(parts[4]) | ||||
|     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 { | ||||
|         CONF_STATE: state, | ||||
|   | ||||
| @@ -8,8 +8,8 @@ from esphome.const import CONF_ID, CONF_MODE, CONF_NUMBER, CONF_PINS, CONF_RUN_C | ||||
| 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] | ||||
|     if value[CONF_NUMBER] not in valid_pins: | ||||
|         raise cv.Invalid(u"Only pins {} support wakeup" | ||||
|                          u"".format(', '.join(str(x) for x in valid_pins))) | ||||
|         raise cv.Invalid("Only pins {} support wakeup" | ||||
|                          "".format(', '.join(str(x) for x in valid_pins))) | ||||
|     return value | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| # coding=utf-8 | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import core, automation | ||||
| @@ -27,7 +26,7 @@ DISPLAY_ROTATIONS = { | ||||
|  | ||||
| def validate_rotation(value): | ||||
|     value = cv.string(value) | ||||
|     if value.endswith(u"°"): | ||||
|     if value.endswith("°"): | ||||
|         value = value[:-1] | ||||
|     return cv.enum(DISPLAY_ROTATIONS, int=True)(value) | ||||
|  | ||||
|   | ||||
| @@ -46,33 +46,33 @@ def bt_uuid(value): | ||||
|         pattern = re.compile("^[A-F|0-9]{4,}$") | ||||
|         if not pattern.match(value): | ||||
|             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 | ||||
|     if len(value) == len(bt_uuid32_format): | ||||
|         pattern = re.compile("^[A-F|0-9]{8,}$") | ||||
|         if not pattern.match(value): | ||||
|             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 | ||||
|     if len(value) == len(bt_uuid128_format): | ||||
|         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,}$") | ||||
|         if not pattern.match(value): | ||||
|             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 | ||||
|     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)) | ||||
|  | ||||
|  | ||||
| def as_hex(value): | ||||
|     return cg.RawExpression('0x{}ULL'.format(value)) | ||||
|     return cg.RawExpression(f'0x{value}ULL') | ||||
|  | ||||
|  | ||||
| def as_hex_array(value): | ||||
|     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( | ||||
|         '(uint8_t*)(const uint8_t[16]){{{}}}'.format(','.join(reversed(cpp_array)))) | ||||
|  | ||||
|   | ||||
| @@ -27,7 +27,7 @@ TOUCH_PADS = { | ||||
| def validate_touch_pad(value): | ||||
|     value = validate_gpio_pin(value) | ||||
|     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 | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| # coding=utf-8 | ||||
| import functools | ||||
|  | ||||
| from esphome import core | ||||
| from esphome.components import display | ||||
| import esphome.config_validation as cv | ||||
| import esphome.codegen as cg | ||||
| from esphome.const import CONF_FILE, CONF_GLYPHS, CONF_ID, CONF_SIZE | ||||
| from esphome.core import CORE, HexInt | ||||
| from esphome.py_compat import sort_by_cmp | ||||
|  | ||||
| DEPENDENCIES = ['display'] | ||||
| MULTI_CONF = True | ||||
| @@ -33,9 +33,9 @@ def validate_glyphs(value): | ||||
|             return -1 | ||||
|         if len(x_) > len(y_): | ||||
|             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 | ||||
|  | ||||
|  | ||||
| @@ -55,15 +55,15 @@ def validate_pillow_installed(value): | ||||
|  | ||||
| def validate_truetype_file(value): | ||||
|     if value.endswith('.zip'):  # for Google Fonts downloads | ||||
|         raise cv.Invalid(u"Please unzip the font archive '{}' first and then use the .ttf files " | ||||
|                          u"inside.".format(value)) | ||||
|         raise cv.Invalid("Please unzip the font archive '{}' first and then use the .ttf files " | ||||
|                          "inside.".format(value)) | ||||
|     if not value.endswith('.ttf'): | ||||
|         raise cv.Invalid(u"Only truetype (.ttf) files are supported. Please make sure you're " | ||||
|                          u"using the correct format or rename the extension to .ttf") | ||||
|         raise cv.Invalid("Only truetype (.ttf) files are supported. Please make sure you're " | ||||
|                          "using the correct format or rename the extension to .ttf") | ||||
|     return cv.file_(value) | ||||
|  | ||||
|  | ||||
| DEFAULT_GLYPHS = u' !"%()+,-.:0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' | ||||
| DEFAULT_GLYPHS = ' !"%()+,-.:0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' | ||||
| CONF_RAW_DATA_ID = 'raw_data_id' | ||||
|  | ||||
| FONT_SCHEMA = cv.Schema({ | ||||
| @@ -84,7 +84,7 @@ def to_code(config): | ||||
|     try: | ||||
|         font = ImageFont.truetype(path, config[CONF_SIZE]) | ||||
|     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() | ||||
|  | ||||
|   | ||||
| @@ -4,7 +4,6 @@ from esphome import config_validation as cv, automation | ||||
| from esphome import codegen as cg | ||||
| 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.py_compat import IS_PY3 | ||||
|  | ||||
| globals_ns = cg.esphome_ns.namespace('globals') | ||||
| GlobalsComponent = globals_ns.class_('GlobalsComponent', cg.Component) | ||||
| @@ -36,7 +35,7 @@ def to_code(config): | ||||
|  | ||||
|     if config[CONF_RESTORE_VALUE]: | ||||
|         value = config[CONF_ID].id | ||||
|         if IS_PY3 and isinstance(value, str): | ||||
|         if isinstance(value, str): | ||||
|             value = value.encode() | ||||
|         hash_ = int(hashlib.md5(value).hexdigest()[:8], 16) | ||||
|         cg.add(glob.set_restore_value(hash_)) | ||||
|   | ||||
| @@ -1,11 +1,9 @@ | ||||
| # coding=utf-8 | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.const import (CONF_ADDRESS, CONF_ID, CONF_OVERSAMPLING, CONF_RANGE, ICON_MAGNET, | ||||
|                            UNIT_MICROTESLA, UNIT_DEGREES, ICON_SCREEN_ROTATION, | ||||
|                            CONF_UPDATE_INTERVAL) | ||||
| from esphome.py_compat import text_type | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
|  | ||||
| @@ -54,7 +52,7 @@ def validate_enum(enum_values, units=None, int=True): | ||||
|     _units = [] | ||||
|     if units is not None: | ||||
|         _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) | ||||
|  | ||||
|     def validate_enum_bound(value): | ||||
| @@ -74,7 +72,7 @@ CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(HMC5883LComponent), | ||||
|     cv.Optional(CONF_ADDRESS): cv.i2c_address, | ||||
|     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_Y): field_strength_schema, | ||||
|     cv.Optional(CONF_FIELD_STRENGTH_Z): field_strength_schema, | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| import urllib.parse as urlparse | ||||
|  | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| 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 | ||||
| from esphome.core import CORE, Lambda | ||||
| 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'] | ||||
| AUTO_LOAD = ['json'] | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| # coding=utf-8 | ||||
| import logging | ||||
|  | ||||
| from esphome import core | ||||
| @@ -33,7 +32,7 @@ def to_code(config): | ||||
|     try: | ||||
|         image = Image.open(path) | ||||
|     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: | ||||
|         image.thumbnail(config[CONF_RESIZE]) | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| # coding=utf-8 | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| # coding=utf-8 | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| # coding=utf-8 | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
|   | ||||
| @@ -253,8 +253,8 @@ def validate_effects(allowed_effects): | ||||
|             name = x[key][CONF_NAME] | ||||
|             if name in names: | ||||
|                 errors.append( | ||||
|                     cv.Invalid(u"Found the effect name '{}' twice. All effects must have " | ||||
|                                u"unique names".format(name), [i]) | ||||
|                     cv.Invalid("Found the effect name '{}' twice. All effects must have " | ||||
|                                "unique names".format(name), [i]) | ||||
|                 ) | ||||
|                 continue | ||||
|             names.add(name) | ||||
|   | ||||
| @@ -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, \ | ||||
|     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.py_compat import text_type | ||||
|  | ||||
| logger_ns = cg.esphome_ns.namespace('logger') | ||||
| LOG_LEVELS = { | ||||
| @@ -64,8 +63,8 @@ def validate_local_no_higher_than_global(value): | ||||
|     global_level = value.get(CONF_LEVEL, 'DEBUG') | ||||
|     for tag, level in value.get(CONF_LOGS, {}).items(): | ||||
|         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 " | ||||
|                                u"global log level {}.".format(level, tag, global_level)) | ||||
|             raise EsphomeError("The local log level {} for {} must be less severe than the " | ||||
|                                "global log level {}.".format(level, tag, global_level)) | ||||
|     return value | ||||
|  | ||||
|  | ||||
| @@ -119,7 +118,7 @@ def to_code(config): | ||||
|  | ||||
|     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)] | ||||
|         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") | ||||
|         DEBUG_COMPONENTS = { | ||||
|             'HTTP_CLIENT', | ||||
| @@ -134,7 +133,7 @@ def to_code(config): | ||||
|             # 'MDNS_RESPONDER', | ||||
|         } | ||||
|         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: | ||||
|         cg.add_build_flag('-DCORE_DEBUG_LEVEL=5') | ||||
|     if CORE.is_esp32 and is_at_least_very_verbose: | ||||
| @@ -165,7 +164,7 @@ def maybe_simple_message(schema): | ||||
| def validate_printf(value): | ||||
|     # https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python | ||||
|     # pylint: disable=anomalous-backslash-in-string | ||||
|     cfmt = u"""\ | ||||
|     cfmt = """\ | ||||
|     (                                  # start of capture group 1 | ||||
|     %                                  # literal "%" | ||||
|     (?:                                # first option | ||||
| @@ -179,8 +178,8 @@ def validate_printf(value): | ||||
|     """  # noqa | ||||
|     matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.X) | ||||
|     if len(matches) != len(value[CONF_ARGS]): | ||||
|         raise cv.Invalid(u"Found {} printf-patterns ({}), but {} args were given!" | ||||
|                          u"".format(len(matches), u', '.join(matches), len(value[CONF_ARGS]))) | ||||
|         raise cv.Invalid("Found {} printf-patterns ({}), but {} args were given!" | ||||
|                          "".format(len(matches), ', '.join(matches), len(value[CONF_ARGS]))) | ||||
|     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) | ||||
| def logger_log_action_to_code(config, action_id, template_arg, args): | ||||
|     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) | ||||
|     yield cg.new_Pvariable(action_id, template_arg, lambda_) | ||||
|   | ||||
| @@ -39,14 +39,14 @@ def to_code(config): | ||||
|     yield i2c.register_i2c_device(var, config) | ||||
|  | ||||
|     for d in ['x', 'y', 'z']: | ||||
|         accel_key = 'accel_{}'.format(d) | ||||
|         accel_key = f'accel_{d}' | ||||
|         if accel_key in config: | ||||
|             sens = yield sensor.new_sensor(config[accel_key]) | ||||
|             cg.add(getattr(var, 'set_accel_{}_sensor'.format(d))(sens)) | ||||
|         accel_key = 'gyro_{}'.format(d) | ||||
|             cg.add(getattr(var, f'set_accel_{d}_sensor')(sens)) | ||||
|         accel_key = f'gyro_{d}' | ||||
|         if accel_key in config: | ||||
|             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: | ||||
|         sens = yield sensor.new_sensor(config[CONF_TEMPERATURE]) | ||||
|   | ||||
| @@ -64,28 +64,28 @@ def validate_config(value): | ||||
|     topic_prefix = value[CONF_TOPIC_PREFIX] | ||||
|     if CONF_BIRTH_MESSAGE not in value: | ||||
|         out[CONF_BIRTH_MESSAGE] = { | ||||
|             CONF_TOPIC: '{}/status'.format(topic_prefix), | ||||
|             CONF_TOPIC: f'{topic_prefix}/status', | ||||
|             CONF_PAYLOAD: 'online', | ||||
|             CONF_QOS: 0, | ||||
|             CONF_RETAIN: True, | ||||
|         } | ||||
|     if CONF_WILL_MESSAGE not in value: | ||||
|         out[CONF_WILL_MESSAGE] = { | ||||
|             CONF_TOPIC: '{}/status'.format(topic_prefix), | ||||
|             CONF_TOPIC: f'{topic_prefix}/status', | ||||
|             CONF_PAYLOAD: 'offline', | ||||
|             CONF_QOS: 0, | ||||
|             CONF_RETAIN: True, | ||||
|         } | ||||
|     if CONF_SHUTDOWN_MESSAGE not in value: | ||||
|         out[CONF_SHUTDOWN_MESSAGE] = { | ||||
|             CONF_TOPIC: '{}/status'.format(topic_prefix), | ||||
|             CONF_TOPIC: f'{topic_prefix}/status', | ||||
|             CONF_PAYLOAD: 'offline', | ||||
|             CONF_QOS: 0, | ||||
|             CONF_RETAIN: True, | ||||
|         } | ||||
|     if CONF_LOG_TOPIC not in value: | ||||
|         out[CONF_LOG_TOPIC] = { | ||||
|             CONF_TOPIC: '{}/debug'.format(topic_prefix), | ||||
|             CONF_TOPIC: f'{topic_prefix}/debug', | ||||
|             CONF_QOS: 0, | ||||
|             CONF_RETAIN: True, | ||||
|         } | ||||
| @@ -95,7 +95,7 @@ def validate_config(value): | ||||
| def validate_fingerprint(value): | ||||
|     value = cv.string(value) | ||||
|     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 | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -82,7 +82,7 @@ def validate_method_pin(value): | ||||
|     for opt in (CONF_PIN, CONF_CLOCK_PIN, CONF_DATA_PIN): | ||||
|         if opt in value and value[opt] not in pins_: | ||||
|             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]) | ||||
|     return value | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| # coding=utf-8 | ||||
| from math import log | ||||
|  | ||||
| import esphome.config_validation as cv | ||||
| @@ -28,7 +27,7 @@ def validate_calibration_parameter(value): | ||||
|     value = cv.string(value) | ||||
|     parts = value.split('->') | ||||
|     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()) | ||||
|     temperature = cv.temperature(parts[1].strip()) | ||||
|     return validate_calibration_parameter({ | ||||
|   | ||||
| @@ -10,8 +10,8 @@ PartitionLightOutput = partitions_ns.class_('PartitionLightOutput', light.Addres | ||||
|  | ||||
| def validate_from_to(value): | ||||
|     if value[CONF_FROM] > value[CONF_TO]: | ||||
|         raise cv.Invalid(u"From ({}) must not be larger than to ({})" | ||||
|                          u"".format(value[CONF_FROM], value[CONF_TO])) | ||||
|         raise cv.Invalid("From ({}) must not be larger than to ({})" | ||||
|                          "".format(value[CONF_FROM], value[CONF_TO])) | ||||
|     return value | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -36,7 +36,7 @@ SENSORS_TO_TYPE = { | ||||
| def validate_pmsx003_sensors(value): | ||||
|     for key, types in SENSORS_TO_TYPE.items(): | ||||
|         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 | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,11 +1,9 @@ | ||||
| # coding=utf-8 | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.const import (CONF_ADDRESS, CONF_ID, CONF_OVERSAMPLING, CONF_RANGE, ICON_MAGNET, | ||||
|                            UNIT_MICROTESLA, UNIT_DEGREES, ICON_SCREEN_ROTATION, | ||||
|                            CONF_UPDATE_INTERVAL) | ||||
| from esphome.py_compat import text_type | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
|  | ||||
| @@ -46,7 +44,7 @@ def validate_enum(enum_values, units=None, int=True): | ||||
|     _units = [] | ||||
|     if units is not None: | ||||
|         _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) | ||||
|  | ||||
|     def validate_enum_bound(value): | ||||
| @@ -65,7 +63,7 @@ heading_schema = sensor.sensor_schema(UNIT_DEGREES, ICON_SCREEN_ROTATION, 1) | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(QMC5883LComponent), | ||||
|     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_FIELD_STRENGTH_X): field_strength_schema, | ||||
|     cv.Optional(CONF_FIELD_STRENGTH_Y): field_strength_schema, | ||||
|   | ||||
| @@ -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_WAIT_TIME, CONF_TIMES, CONF_TYPE_ID, CONF_CARRIER_FREQUENCY | ||||
| from esphome.core import coroutine | ||||
| from esphome.py_compat import string_types, text_type | ||||
| from esphome.util import Registry, SimpleRegistry | ||||
|  | ||||
| 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_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): | ||||
|         @coroutine | ||||
| @@ -98,7 +97,7 @@ def register_action(name, type_, schema): | ||||
|         cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(RemoteTransmitterBase), | ||||
|         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) | ||||
|  | ||||
|     def decorator(func): | ||||
| @@ -122,11 +121,11 @@ def register_action(name, type_, schema): | ||||
|  | ||||
|  | ||||
| def declare_protocol(name): | ||||
|     data = ns.struct('{}Data'.format(name)) | ||||
|     binary_sensor_ = ns.class_('{}BinarySensor'.format(name), RemoteReceiverBinarySensorBase) | ||||
|     trigger = ns.class_('{}Trigger'.format(name), RemoteReceiverTrigger) | ||||
|     action = ns.class_('{}Action'.format(name), RemoteTransmitterActionBase) | ||||
|     dumper = ns.class_('{}Dumper'.format(name), RemoteTransmitterDumper) | ||||
|     data = ns.struct(f'{name}Data') | ||||
|     binary_sensor_ = ns.class_(f'{name}BinarySensor', RemoteReceiverBinarySensorBase) | ||||
|     trigger = ns.class_(f'{name}Trigger', RemoteReceiverTrigger) | ||||
|     action = ns.class_(f'{name}Action', RemoteTransmitterActionBase) | ||||
|     dumper = ns.class_(f'{name}Dumper', RemoteTransmitterDumper) | ||||
|     return data, binary_sensor_, trigger, action, dumper | ||||
|  | ||||
|  | ||||
| @@ -141,7 +140,7 @@ DUMPER_REGISTRY = Registry({ | ||||
|  | ||||
|  | ||||
| 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 cv.validate_registry('dumper', DUMPER_REGISTRY)(value) | ||||
|  | ||||
| @@ -432,12 +431,12 @@ RC_SWITCH_PROTOCOL_SCHEMA = cv.Any( | ||||
|  | ||||
|  | ||||
| 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 ('')") | ||||
|     for c in value: | ||||
|         if c not in ('0', '1'): | ||||
|             raise cv.Invalid(u"Invalid RCSwitch code character '{}'. Only '0' and '1' are allowed" | ||||
|                              u"".format(c)) | ||||
|             raise cv.Invalid("Invalid RCSwitch code character '{}'. Only '0' and '1' are allowed" | ||||
|                              "".format(c)) | ||||
|     if len(value) > 64: | ||||
|         raise cv.Invalid("Maximum length for RCSwitch codes is 64, code '{}' has length {}" | ||||
|                          "".format(value, len(value))) | ||||
| @@ -447,7 +446,7 @@ def validate_rc_switch_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 ('')") | ||||
|     for c in value: | ||||
|         if c not in ('0', '1', 'x'): | ||||
|   | ||||
| @@ -19,12 +19,12 @@ def show_new(value): | ||||
|     if 'name' in value: | ||||
|         args.append(('name', value['name'])) | ||||
|     args.append(('turn_on_action', { | ||||
|         'remote_transmitter.transmit_{}'.format(key): val | ||||
|         f'remote_transmitter.transmit_{key}': val | ||||
|     })) | ||||
|  | ||||
|     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." | ||||
|                      u"".format(text)) | ||||
|     raise cv.Invalid("This platform has been removed in 1.13, please change to:\n\n{}\n\n." | ||||
|                      "".format(text)) | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = show_new | ||||
|   | ||||
| @@ -28,7 +28,7 @@ def validate_acceleration(value): | ||||
|     try: | ||||
|         value = float(value) | ||||
|     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: | ||||
|         raise cv.Invalid("Acceleration must be larger than 0 steps/s^2!") | ||||
| @@ -48,7 +48,7 @@ def validate_speed(value): | ||||
|     try: | ||||
|         value = float(value) | ||||
|     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: | ||||
|         raise cv.Invalid("Speed must be larger than 0 steps/s!") | ||||
|   | ||||
| @@ -3,7 +3,6 @@ import re | ||||
|  | ||||
| import esphome.config_validation as cv | ||||
| from esphome import core | ||||
| from esphome.py_compat import string_types | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
| @@ -24,8 +23,8 @@ def validate_substitution_key(value): | ||||
|     for char in value: | ||||
|         if char not in VALID_SUBSTITUTIONS_CHARACTERS: | ||||
|             raise cv.Invalid( | ||||
|                 u"Substitution must only consist of upper/lowercase characters, the underscore " | ||||
|                 u"and numbers. The character '{}' cannot be used".format(char)) | ||||
|                 "Substitution must only consist of upper/lowercase characters, the underscore " | ||||
|                 "and numbers. The character '{}' cannot be used".format(char)) | ||||
|     return value | ||||
|  | ||||
|  | ||||
| @@ -42,7 +41,7 @@ VARIABLE_PROG = re.compile('\\$([{0}]+|\\{{[{0}]*\\}})'.format(VALID_SUBSTITUTIO | ||||
|  | ||||
|  | ||||
| def _expand_substitutions(substitutions, value, path): | ||||
|     if u'$' not in value: | ||||
|     if '$' not in value: | ||||
|         return value | ||||
|  | ||||
|     orig_value = value | ||||
| @@ -56,11 +55,11 @@ def _expand_substitutions(substitutions, value, path): | ||||
|  | ||||
|         i, j = m.span(0) | ||||
|         name = m.group(1) | ||||
|         if name.startswith(u'{') and name.endswith(u'}'): | ||||
|         if name.startswith('{') and name.endswith('}'): | ||||
|             name = name[1:-1] | ||||
|         if name not in substitutions: | ||||
|             _LOGGER.warning(u"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) | ||||
|             _LOGGER.warning("Found '%s' (see %s) which looks like a substitution, but '%s' was " | ||||
|                             "not declared", orig_value, '->'.join(str(x) for x in path), name) | ||||
|             i = j | ||||
|             continue | ||||
|  | ||||
| @@ -91,7 +90,7 @@ def _substitute_item(substitutions, item, path): | ||||
|         for old, new in replace_keys: | ||||
|             item[new] = item[old] | ||||
|             del item[old] | ||||
|     elif isinstance(item, string_types): | ||||
|     elif isinstance(item, str): | ||||
|         sub = _expand_substitutions(substitutions, item, path) | ||||
|         if sub != item: | ||||
|             return sub | ||||
| @@ -109,8 +108,8 @@ def do_substitution_pass(config): | ||||
|     substitutions = config[CONF_SUBSTITUTIONS] | ||||
|     with cv.prepend_path('substitutions'): | ||||
|         if not isinstance(substitutions, dict): | ||||
|             raise cv.Invalid(u"Substitutions must be a key to value mapping, got {}" | ||||
|                              u"".format(type(substitutions))) | ||||
|             raise cv.Invalid("Substitutions must be a key to value mapping, got {}" | ||||
|                              "".format(type(substitutions))) | ||||
|  | ||||
|         replace_keys = [] | ||||
|         for key, value in substitutions.items(): | ||||
|   | ||||
| @@ -3,7 +3,6 @@ import esphome.config_validation as cv | ||||
| from esphome import automation | ||||
| from esphome.components import time | ||||
| 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') | ||||
|  | ||||
| @@ -32,7 +31,7 @@ ELEVATION_MAP = { | ||||
|  | ||||
|  | ||||
| def elevation(value): | ||||
|     if isinstance(value, string_types): | ||||
|     if isinstance(value, str): | ||||
|         try: | ||||
|             value = ELEVATION_MAP[cv.one_of(*ELEVATION_MAP, lower=True, space='_')(value)] | ||||
|         except cv.Invalid: | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| # coding=utf-8 | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
|   | ||||
| @@ -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_AT, CONF_SECOND, CONF_HOUR, CONF_MINUTE | ||||
| from esphome.core import coroutine, coroutine_with_priority | ||||
| from esphome.py_compat import string_types | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
| @@ -33,10 +32,10 @@ def _tz_timedelta(td): | ||||
|     if offset_hour == 0 and offset_minute == 0 and offset_second == 0: | ||||
|         return '0' | ||||
|     if offset_minute == 0 and offset_second == 0: | ||||
|         return '{}'.format(offset_hour) | ||||
|         return f'{offset_hour}' | ||||
|     if offset_second == 0: | ||||
|         return '{}:{}'.format(offset_hour, offset_minute) | ||||
|     return '{}:{}:{}'.format(offset_hour, offset_minute, offset_second) | ||||
|         return f'{offset_hour}:{offset_minute}' | ||||
|     return f'{offset_hour}:{offset_minute}:{offset_second}' | ||||
|  | ||||
|  | ||||
| # https://stackoverflow.com/a/16804556/8924614 | ||||
| @@ -133,7 +132,7 @@ def detect_tz(): | ||||
|  | ||||
| def _parse_cron_int(value, special_mapping, message): | ||||
|     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] | ||||
|     try: | ||||
|         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): | ||||
|     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: | ||||
|         data = part.split('/') | ||||
|         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)) | ||||
|         offset, repeat = data | ||||
|         offset_n = 0 | ||||
|         if offset: | ||||
|             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: | ||||
|             repeat_n = int(repeat) | ||||
|         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)) | ||||
|         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: | ||||
|         data = part.split('-') | ||||
|         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)) | ||||
|         begin, end = data | ||||
|         begin_n = _parse_cron_int(begin, special_mapping, u"Number for time range must be integer, " | ||||
|                                                           u"got {}") | ||||
|         end_n = _parse_cron_int(end, special_mapping, u"Number for time range must be integer, " | ||||
|                                                       u"got {}") | ||||
|         begin_n = _parse_cron_int(begin, special_mapping, "Number for time range must be integer, " | ||||
|                                                           "got {}") | ||||
|         end_n = _parse_cron_int(end, special_mapping, "Number for time range must be integer, " | ||||
|                                                       "got {}") | ||||
|         if end_n < begin_n: | ||||
|             return set(x for x in range(end_n, max_value + 1)) | \ | ||||
|                    set(x for x in range(min_value, begin_n + 1)) | ||||
|         return set(x for x in range(begin_n, end_n + 1)) | ||||
|             return set(range(end_n, max_value + 1)) | set(range(min_value, begin_n + 1)) | ||||
|         return set(range(begin_n, end_n + 1)) | ||||
|  | ||||
|     return {_parse_cron_int(part, special_mapping, u"Number for time expression must be an " | ||||
|                                                    u"integer, got {}")} | ||||
|     return {_parse_cron_int(part, special_mapping, "Number for time expression must be an " | ||||
|                                                    "integer, got {}")} | ||||
|  | ||||
|  | ||||
| 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: | ||||
|         for key in value.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: | ||||
|             raise cv.Invalid("Cannot use option at with cron!") | ||||
|         cron_ = value[CONF_CRON] | ||||
| @@ -259,7 +257,7 @@ def validate_cron_keys(value): | ||||
|     if CONF_AT in value: | ||||
|         for key in value.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] | ||||
|         value = {x: value[x] for x in value if x != CONF_AT} | ||||
|         value.update(at_) | ||||
|   | ||||
| @@ -3,7 +3,6 @@ import esphome.config_validation as cv | ||||
| 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.core import CORE, coroutine | ||||
| from esphome.py_compat import text_type, binary_type, char_to_byte | ||||
|  | ||||
| uart_ns = cg.esphome_ns.namespace('uart') | ||||
| UARTComponent = uart_ns.class_('UARTComponent', cg.Component) | ||||
| @@ -13,7 +12,7 @@ MULTI_CONF = True | ||||
|  | ||||
|  | ||||
| def validate_raw_data(value): | ||||
|     if isinstance(value, text_type): | ||||
|     if isinstance(value, str): | ||||
|         return value.encode('utf-8') | ||||
|     if isinstance(value, str): | ||||
|         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) | ||||
|     yield cg.register_parented(var, config[CONF_ID]) | ||||
|     data = config[CONF_DATA] | ||||
|     if isinstance(data, binary_type): | ||||
|         data = [char_to_byte(x) for x in data] | ||||
|     if isinstance(data, bytes): | ||||
|         data = list(data) | ||||
|  | ||||
|     if cg.is_template(data): | ||||
|         templ = yield cg.templatable(data, args, cg.std_vector.template(cg.uint8)) | ||||
|   | ||||
| @@ -3,7 +3,6 @@ import esphome.config_validation as cv | ||||
| from esphome.components import switch, uart | ||||
| from esphome.const import CONF_DATA, CONF_ID, CONF_INVERTED | ||||
| from esphome.core import HexInt | ||||
| from esphome.py_compat import binary_type, char_to_byte | ||||
| from .. import uart_ns, validate_raw_data | ||||
|  | ||||
| DEPENDENCIES = ['uart'] | ||||
| @@ -25,6 +24,6 @@ def to_code(config): | ||||
|     yield uart.register_uart_device(var, config) | ||||
|  | ||||
|     data = config[CONF_DATA] | ||||
|     if isinstance(data, binary_type): | ||||
|         data = [HexInt(char_to_byte(x)) for x in data] | ||||
|     if isinstance(data, bytes): | ||||
|         data = [HexInt(x) for x in data] | ||||
|     cg.add(var.set_data(data)) | ||||
|   | ||||
| @@ -30,9 +30,9 @@ def validate_password(value): | ||||
|     if not value: | ||||
|         return value | ||||
|     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: | ||||
|         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 | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| from __future__ import print_function | ||||
|  | ||||
| import collections | ||||
| import importlib | ||||
| 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.core import CORE, EsphomeError  # noqa | ||||
| 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 typing import List, Optional, Tuple, Union  # noqa | ||||
| @@ -31,7 +28,7 @@ _LOGGER = logging.getLogger(__name__) | ||||
| _COMPONENT_CACHE = {} | ||||
|  | ||||
|  | ||||
| class ComponentManifest(object): | ||||
| class ComponentManifest: | ||||
|     def __init__(self, module, base_components_path, is_core=False, is_platform=False): | ||||
|         self.module = module | ||||
|         self._is_core = is_core | ||||
| @@ -89,7 +86,7 @@ class ComponentManifest(object): | ||||
|             source_files = core.find_source_files(os.path.join(core_p, 'dummy')) | ||||
|             ret = {} | ||||
|             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 | ||||
|  | ||||
|         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) | ||||
|             # Always use / for C++ include names | ||||
|             rel = rel.replace(os.sep, '/') | ||||
|             target_file = 'esphome/components/{}'.format(rel) | ||||
|             target_file = f'esphome/components/{rel}' | ||||
|             ret[target_file] = full_file | ||||
|         return ret | ||||
|  | ||||
| @@ -119,12 +116,6 @@ def _mount_config_dir(): | ||||
|     if not os.path.isdir(custom_path): | ||||
|         CUSTOM_COMPONENTS_PATH = None | ||||
|         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: | ||||
|         sys.path.insert(0, CORE.config_dir) | ||||
|     CUSTOM_COMPONENTS_PATH = custom_path | ||||
| @@ -137,7 +128,7 @@ def _lookup_module(domain, is_platform): | ||||
|     _mount_config_dir() | ||||
|     # First look for custom_components | ||||
|     try: | ||||
|         module = importlib.import_module('custom_components.{}'.format(domain)) | ||||
|         module = importlib.import_module(f'custom_components.{domain}') | ||||
|     except ImportError as e: | ||||
|         # ImportError when no such module | ||||
|         if 'No module named' not in str(e): | ||||
| @@ -153,7 +144,7 @@ def _lookup_module(domain, is_platform): | ||||
|         return manif | ||||
|  | ||||
|     try: | ||||
|         module = importlib.import_module('esphome.components.{}'.format(domain)) | ||||
|         module = importlib.import_module(f'esphome.components.{domain}') | ||||
|     except ImportError as e: | ||||
|         if 'No module named' not in str(e): | ||||
|             _LOGGER.error("Unable to import component %s:", domain, exc_info=True) | ||||
| @@ -173,7 +164,7 @@ def get_component(domain): | ||||
|  | ||||
|  | ||||
| def get_platform(domain, platform): | ||||
|     full = '{}.{}'.format(platform, domain) | ||||
|     full = f'{platform}.{domain}' | ||||
|     return _lookup_module(full, True) | ||||
|  | ||||
|  | ||||
| @@ -192,7 +183,7 @@ def iter_components(config): | ||||
|             yield domain, component, conf | ||||
|         if component.is_platform_component: | ||||
|             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]) | ||||
|                 yield p_name, platform, p_config | ||||
|  | ||||
| @@ -208,13 +199,13 @@ def _path_begins_with(path, other):  # type: (ConfigPath, ConfigPath) -> bool | ||||
|  | ||||
| class Config(OrderedDict): | ||||
|     def __init__(self): | ||||
|         super(Config, self).__init__() | ||||
|         super().__init__() | ||||
|         # A list of voluptuous errors | ||||
|         self.errors = []  # type: List[vol.Invalid] | ||||
|         # A list of paths that should be fully outputted | ||||
|         # The values will be the paths to all "domain", for example (['logger'], 'logger') | ||||
|         # 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): | ||||
|         # type: (vol.Invalid) -> None | ||||
| @@ -234,15 +225,15 @@ class Config(OrderedDict): | ||||
|             self.add_error(e) | ||||
|  | ||||
|     def add_str_error(self, message, path): | ||||
|         # type: (basestring, ConfigPath) -> None | ||||
|         # type: (str, ConfigPath) -> None | ||||
|         self.add_error(vol.Invalid(message, path)) | ||||
|  | ||||
|     def add_output_path(self, path, domain): | ||||
|         # type: (ConfigPath, unicode) -> None | ||||
|         # type: (ConfigPath, str) -> None | ||||
|         self.output_paths.append((path, domain)) | ||||
|  | ||||
|     def remove_output_path(self, path, domain): | ||||
|         # type: (ConfigPath, unicode) -> None | ||||
|         # type: (ConfigPath, str) -> None | ||||
|         self.output_paths.remove((path, domain)) | ||||
|  | ||||
|     def is_in_error_path(self, path): | ||||
| @@ -312,12 +303,10 @@ def iter_ids(config, path=None): | ||||
|             yield id, path | ||||
|     elif isinstance(config, list): | ||||
|         for i, item in enumerate(config): | ||||
|             for result in iter_ids(item, path + [i]): | ||||
|                 yield result | ||||
|             yield from iter_ids(item, path + [i]) | ||||
|     elif isinstance(config, dict): | ||||
|         for key, value in config.items(): | ||||
|             for result in iter_ids(value, path + [key]): | ||||
|                 yield result | ||||
|             yield from iter_ids(value, path + [key]) | ||||
|  | ||||
|  | ||||
| def do_id_pass(result):  # type: (Config) -> None | ||||
| @@ -332,8 +321,8 @@ def do_id_pass(result):  # type: (Config) -> None | ||||
|                 # Look for duplicate definitions | ||||
|                 match = next((v for v in declare_ids if v[0].id == id.id), None) | ||||
|                 if match is not None: | ||||
|                     opath = u'->'.join(text_type(v) for v in match[1]) | ||||
|                     result.add_str_error(u"ID {} redefined! Check {}".format(id.id, opath), path) | ||||
|                     opath = '->'.join(str(v) for v in match[1]) | ||||
|                     result.add_str_error(f"ID {id.id} redefined! Check {opath}", path) | ||||
|                     continue | ||||
|             declare_ids.append((id, path)) | ||||
|         else: | ||||
| @@ -357,8 +346,8 @@ def do_id_pass(result):  # type: (Config) -> None | ||||
|                 # Find candidates | ||||
|                 matches = difflib.get_close_matches(id.id, [v[0].id for v in declare_ids]) | ||||
|                 if matches: | ||||
|                     matches_s = ', '.join('"{}"'.format(x) for x in matches) | ||||
|                     error += " These IDs look similar: {}.".format(matches_s) | ||||
|                     matches_s = ', '.join(f'"{x}"' for x in matches) | ||||
|                     error += f" These IDs look similar: {matches_s}." | ||||
|                 result.add_str_error(error, path) | ||||
|                 continue | ||||
|             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 | ||||
|                     break | ||||
|             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): | ||||
| @@ -389,7 +378,7 @@ def recursive_check_replaceme(value): | ||||
|         return cv.Schema({cv.valid: recursive_check_replaceme})(value) | ||||
|     if isinstance(value, ESPForceValue): | ||||
|         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. " | ||||
|                          "Please make sure you have replaced all fields from the sample " | ||||
|                          "configuration.\n" | ||||
| @@ -455,8 +444,8 @@ def validate_config(config): | ||||
|  | ||||
|     while load_queue: | ||||
|         domain, conf = load_queue.popleft() | ||||
|         domain = text_type(domain) | ||||
|         if domain.startswith(u'.'): | ||||
|         domain = str(domain) | ||||
|         if domain.startswith('.'): | ||||
|             # Ignore top-level keys starting with a dot | ||||
|             continue | ||||
|         result.add_output_path([domain], domain) | ||||
| @@ -464,7 +453,7 @@ def validate_config(config): | ||||
|         component = get_component(domain) | ||||
|         path = [domain] | ||||
|         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 | ||||
|         CORE.loaded_integrations.add(domain) | ||||
|  | ||||
| @@ -492,24 +481,24 @@ def validate_config(config): | ||||
|         for i, p_config in enumerate(conf): | ||||
|             path = [domain, i] | ||||
|             # Construct temporary unknown output path | ||||
|             p_domain = u'{}.unknown'.format(domain) | ||||
|             p_domain = f'{domain}.unknown' | ||||
|             result.add_output_path(path, p_domain) | ||||
|             result[domain][i] = p_config | ||||
|             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 | ||||
|             p_name = p_config.get('platform') | ||||
|             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 | ||||
|             # Remove temp output path and construct new one | ||||
|             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) | ||||
|             # Try Load platform | ||||
|             platform = get_platform(domain, p_name) | ||||
|             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 | ||||
|             CORE.loaded_integrations.add(p_name) | ||||
|  | ||||
| @@ -537,8 +526,8 @@ def validate_config(config): | ||||
|         success = True | ||||
|         for dependency in comp.dependencies: | ||||
|             if dependency not in config: | ||||
|                 result.add_str_error(u"Component {} requires component {}" | ||||
|                                      u"".format(domain, dependency), path) | ||||
|                 result.add_str_error("Component {} requires component {}" | ||||
|                                      "".format(domain, dependency), path) | ||||
|                 success = False | ||||
|         if not success: | ||||
|             continue | ||||
| @@ -546,22 +535,22 @@ def validate_config(config): | ||||
|         success = True | ||||
|         for conflict in comp.conflicts_with: | ||||
|             if conflict in config: | ||||
|                 result.add_str_error(u"Component {} cannot be used together with component {}" | ||||
|                                      u"".format(domain, conflict), path) | ||||
|                 result.add_str_error("Component {} cannot be used together with component {}" | ||||
|                                      "".format(domain, conflict), path) | ||||
|                 success = False | ||||
|         if not success: | ||||
|             continue | ||||
|  | ||||
|         if CORE.esp_platform not in comp.esp_platforms: | ||||
|             result.add_str_error(u"Component {} doesn't support {}.".format(domain, | ||||
|                                                                             CORE.esp_platform), | ||||
|             result.add_str_error("Component {} doesn't support {}.".format(domain, | ||||
|                                                                            CORE.esp_platform), | ||||
|                                  path) | ||||
|             continue | ||||
|  | ||||
|         if not comp.is_platform_component and comp.config_schema is None and \ | ||||
|                 not isinstance(conf, core.AutoLoad): | ||||
|             result.add_str_error(u"Component {} cannot be loaded via YAML " | ||||
|                                  u"(no CONFIG_SCHEMA).".format(domain), path) | ||||
|             result.add_str_error("Component {} cannot be loaded via YAML " | ||||
|                                  "(no CONFIG_SCHEMA).".format(domain), path) | ||||
|             continue | ||||
|  | ||||
|         if comp.is_multi_conf: | ||||
| @@ -611,13 +600,13 @@ def _nested_getitem(data, path): | ||||
|  | ||||
|  | ||||
| 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) | ||||
|     if m is not None: | ||||
|         validation_error = m.group(1) | ||||
|     validation_error = validation_error.strip() | ||||
|     if not validation_error.endswith(u'.'): | ||||
|         validation_error += u'.' | ||||
|     if not validation_error.endswith('.'): | ||||
|         validation_error += '.' | ||||
|     return validation_error | ||||
|  | ||||
|  | ||||
| @@ -634,22 +623,22 @@ def _get_parent_name(path, config): | ||||
|  | ||||
|  | ||||
| def _format_vol_invalid(ex, config): | ||||
|     # type: (vol.Invalid, Config) -> unicode | ||||
|     message = u'' | ||||
|     # type: (vol.Invalid, Config) -> str | ||||
|     message = '' | ||||
|  | ||||
|     paren = _get_parent_name(ex.path[:-1], config) | ||||
|  | ||||
|     if isinstance(ex, ExtraKeysInvalid): | ||||
|         if ex.candidates: | ||||
|             message += u'[{}] is an invalid option for [{}]. Did you mean {}?'.format( | ||||
|                 ex.path[-1], paren, u', '.join(u'[{}]'.format(x) for x in ex.candidates)) | ||||
|             message += '[{}] is an invalid option for [{}]. Did you mean {}?'.format( | ||||
|                 ex.path[-1], paren, ', '.join(f'[{x}]' for x in ex.candidates)) | ||||
|         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) | ||||
|     elif u'extra keys not allowed' in text_type(ex): | ||||
|         message += u'[{}] is an invalid option for [{}].'.format(ex.path[-1], paren) | ||||
|     elif u'required key not provided' in text_type(ex): | ||||
|         message += u"'{}' is a required option for [{}].".format(ex.path[-1], paren) | ||||
|     elif 'extra keys not allowed' in str(ex): | ||||
|         message += '[{}] is an invalid option for [{}].'.format(ex.path[-1], paren) | ||||
|     elif 'required key not provided' in str(ex): | ||||
|         message += "'{}' is a required option for [{}].".format(ex.path[-1], paren) | ||||
|     else: | ||||
|         message += humanize_error(config, ex) | ||||
|  | ||||
| @@ -662,9 +651,8 @@ class InvalidYAMLError(EsphomeError): | ||||
|             base = str(base_exc) | ||||
|         except UnicodeDecodeError: | ||||
|             base = repr(base_exc) | ||||
|         base = decode_text(base) | ||||
|         message = u"Invalid YAML syntax:\n\n{}".format(base) | ||||
|         super(InvalidYAMLError, self).__init__(message) | ||||
|         message = f"Invalid YAML syntax:\n\n{base}" | ||||
|         super().__init__(message) | ||||
|         self.base_exc = base_exc | ||||
|  | ||||
|  | ||||
| @@ -680,7 +668,7 @@ def _load_config(): | ||||
|     except EsphomeError: | ||||
|         raise | ||||
|     except Exception: | ||||
|         _LOGGER.error(u"Unexpected exception while reading configuration:") | ||||
|         _LOGGER.error("Unexpected exception while reading configuration:") | ||||
|         raise | ||||
|  | ||||
|     return result | ||||
| @@ -690,7 +678,7 @@ def load_config(): | ||||
|     try: | ||||
|         return _load_config() | ||||
|     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): | ||||
| @@ -699,7 +687,7 @@ def line_info(obj, highlight=True): | ||||
|         return None | ||||
|     if isinstance(obj, ESPHomeDataBase) and obj.esp_range is not None: | ||||
|         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 None | ||||
|  | ||||
| @@ -715,82 +703,82 @@ def _print_on_next_line(obj): | ||||
|  | ||||
|  | ||||
| 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) | ||||
|     ret = u'' | ||||
|     ret = '' | ||||
|     multiline = False | ||||
|  | ||||
|     if at_root: | ||||
|         error = config.get_error_for_path(path) | ||||
|         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)): | ||||
|         multiline = True | ||||
|         if not conf: | ||||
|             ret += u'[]' | ||||
|             ret += '[]' | ||||
|             multiline = False | ||||
|  | ||||
|         for i in range(len(conf)): | ||||
|             path_ = path + [i] | ||||
|             error = config.get_error_for_path(path_) | ||||
|             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_): | ||||
|                 sep = color('red', sep) | ||||
|             msg, _ = dump_dict(config, path_, at_root=False) | ||||
|             msg = indent(msg) | ||||
|             inf = line_info(config.get_nested_item(path_), highlight=config.is_in_error_path(path_)) | ||||
|             if inf is not None: | ||||
|                 msg = inf + u'\n' + msg | ||||
|                 msg = inf + '\n' + msg | ||||
|             elif msg: | ||||
|                 msg = msg[2:] | ||||
|             ret += sep + msg + u'\n' | ||||
|             ret += sep + msg + '\n' | ||||
|     elif isinstance(conf, dict): | ||||
|         multiline = True | ||||
|         if not conf: | ||||
|             ret += u'{}' | ||||
|             ret += '{}' | ||||
|             multiline = False | ||||
|  | ||||
|         for k in conf.keys(): | ||||
|             path_ = path + [k] | ||||
|             error = config.get_error_for_path(path_) | ||||
|             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_): | ||||
|                 st = color('red', st) | ||||
|             msg, m = dump_dict(config, path_, at_root=False) | ||||
|  | ||||
|             inf = line_info(config.get_nested_item(path_), highlight=config.is_in_error_path(path_)) | ||||
|             if m: | ||||
|                 msg = u'\n' + indent(msg) | ||||
|                 msg = '\n' + indent(msg) | ||||
|  | ||||
|             if inf is not None: | ||||
|                 if m: | ||||
|                     msg = u' ' + inf + msg | ||||
|                     msg = ' ' + inf + msg | ||||
|                 else: | ||||
|                     msg = msg + u' ' + inf | ||||
|             ret += st + msg + u'\n' | ||||
|                     msg = msg + ' ' + inf | ||||
|             ret += st + msg + '\n' | ||||
|     elif isinstance(conf, str): | ||||
|         if is_secret(conf): | ||||
|             conf = u'!secret {}'.format(is_secret(conf)) | ||||
|             conf = '!secret {}'.format(is_secret(conf)) | ||||
|         if not conf: | ||||
|             conf += u"''" | ||||
|             conf += "''" | ||||
|  | ||||
|         if len(conf) > 80: | ||||
|             conf = u'|-\n' + indent(conf) | ||||
|             conf = '|-\n' + indent(conf) | ||||
|         error = config.get_error_for_path(path) | ||||
|         col = 'bold_red' if error else 'white' | ||||
|         ret += color(col, text_type(conf)) | ||||
|         ret += color(col, str(conf)) | ||||
|     elif isinstance(conf, core.Lambda): | ||||
|         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) | ||||
|         col = 'bold_red' if error else 'white' | ||||
|         ret += color(col, conf) | ||||
| @@ -799,8 +787,8 @@ def dump_dict(config, path, at_root=True): | ||||
|     else: | ||||
|         error = config.get_error_for_path(path) | ||||
|         col = 'bold_red' if error else 'white' | ||||
|         ret += color(col, text_type(conf)) | ||||
|         multiline = u'\n' in ret | ||||
|         ret += color(col, str(conf)) | ||||
|         multiline = '\n' in ret | ||||
|  | ||||
|     return ret, multiline | ||||
|  | ||||
| @@ -830,20 +818,20 @@ def read_config(): | ||||
|     try: | ||||
|         res = load_config() | ||||
|     except EsphomeError as err: | ||||
|         _LOGGER.error(u"Error while reading config: %s", err) | ||||
|         _LOGGER.error("Error while reading config: %s", err) | ||||
|         return None | ||||
|     if res.errors: | ||||
|         if not CORE.verbose: | ||||
|             res = strip_default_ids(res) | ||||
|  | ||||
|         safe_print(color('bold_red', u"Failed config")) | ||||
|         safe_print(color('bold_red', "Failed config")) | ||||
|         safe_print('') | ||||
|         for path, domain in res.output_paths: | ||||
|             if not res.is_in_error_path(path): | ||||
|                 continue | ||||
|  | ||||
|             safe_print(color('bold_red', u'{}:'.format(domain)) + u' ' + | ||||
|                        (line_info(res.get_nested_item(path)) or u'')) | ||||
|             safe_print(color('bold_red', f'{domain}:') + ' ' + | ||||
|                        (line_info(res.get_nested_item(path)) or '')) | ||||
|             safe_print(indent(dump_dict(res, path)[0])) | ||||
|         return None | ||||
|     return OrderedDict(res) | ||||
|   | ||||
| @@ -1,22 +1,19 @@ | ||||
| from __future__ import print_function | ||||
|  | ||||
| import json | ||||
| import os | ||||
|  | ||||
| from esphome.core import CORE | ||||
| from esphome.helpers import read_file | ||||
| from esphome.py_compat import safe_input | ||||
|  | ||||
|  | ||||
| def read_config_file(path): | ||||
|     # type: (basestring) -> unicode | ||||
|     # type: (str) -> str | ||||
|     if CORE.vscode and (not CORE.ace or | ||||
|                         os.path.abspath(path) == os.path.abspath(CORE.config_path)): | ||||
|         print(json.dumps({ | ||||
|             'type': 'read_file', | ||||
|             'path': path, | ||||
|         })) | ||||
|         data = json.loads(safe_input()) | ||||
|         data = json.loads(input()) | ||||
|         assert data['type'] == 'file_response' | ||||
|         return data['content'] | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,4 @@ | ||||
| # coding=utf-8 | ||||
| """Helpers for config validation using voluptuous.""" | ||||
| from __future__ import print_function | ||||
|  | ||||
| import logging | ||||
| 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, \ | ||||
|     TimePeriodMilliseconds, TimePeriodSeconds, TimePeriodMinutes | ||||
| 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 | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
| @@ -43,7 +40,7 @@ ALLOW_EXTRA = vol.ALLOW_EXTRA | ||||
| UNDEFINED = vol.UNDEFINED | ||||
| RequiredFieldInvalid = vol.RequiredFieldInvalid | ||||
|  | ||||
| ALLOWED_NAME_CHARS = u'abcdefghijklmnopqrstuvwxyz0123456789_' | ||||
| ALLOWED_NAME_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_' | ||||
|  | ||||
| RESERVED_IDS = [ | ||||
|     # C++ keywords http://en.cppreference.com/w/cpp/keyword | ||||
| @@ -82,7 +79,7 @@ class Optional(vol.Optional): | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, key, default=UNDEFINED): | ||||
|         super(Optional, self).__init__(key, default=default) | ||||
|         super().__init__(key, default=default) | ||||
|  | ||||
|  | ||||
| class Required(vol.Required): | ||||
| @@ -94,7 +91,7 @@ class Required(vol.Required): | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, key): | ||||
|         super(Required, self).__init__(key) | ||||
|         super().__init__(key) | ||||
|  | ||||
|  | ||||
| def check_not_templatable(value): | ||||
| @@ -105,7 +102,7 @@ def check_not_templatable(value): | ||||
| def alphanumeric(value): | ||||
|     if value is None: | ||||
|         raise Invalid("string value is None") | ||||
|     value = text_type(value) | ||||
|     value = str(value) | ||||
|     if not value.isalnum(): | ||||
|         raise Invalid("string value is not alphanumeric") | ||||
|     return value | ||||
| @@ -115,8 +112,8 @@ def valid_name(value): | ||||
|     value = string_strict(value) | ||||
|     for c in value: | ||||
|         if c not in ALLOWED_NAME_CHARS: | ||||
|             raise Invalid(u"'{}' is an invalid character for names. Valid characters are: {}" | ||||
|                           u" (lowercase, no spaces)".format(c, ALLOWED_NAME_CHARS)) | ||||
|             raise Invalid(f"'{c}' is an invalid character for names. Valid characters are: " | ||||
|                           f"{ALLOWED_NAME_CHARS} (lowercase, no spaces)") | ||||
|     return value | ||||
|  | ||||
|  | ||||
| @@ -131,10 +128,10 @@ def string(value): | ||||
|         raise Invalid("string value cannot be dictionary or list.") | ||||
|     if isinstance(value, bool): | ||||
|         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 | ||||
|     if value is not None: | ||||
|         return text_type(value) | ||||
|         return str(value) | ||||
|     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 | ||||
|     strings.""" | ||||
|     check_not_templatable(value) | ||||
|     if isinstance(value, text_type): | ||||
|     if isinstance(value, str): | ||||
|         return value | ||||
|     if isinstance(value, string_types): | ||||
|         return text_type(value) | ||||
|     raise Invalid("Must be string, got {}. did you forget putting quotes " | ||||
|                   "around the value?".format(type(value))) | ||||
|  | ||||
| @@ -172,14 +167,14 @@ def boolean(value): | ||||
|     check_not_templatable(value) | ||||
|     if isinstance(value, bool): | ||||
|         return value | ||||
|     if isinstance(value, string_types): | ||||
|     if isinstance(value, str): | ||||
|         value = value.lower() | ||||
|         if value in ('true', 'yes', 'on', 'enable'): | ||||
|             return True | ||||
|         if value in ('false', 'no', 'off', 'disable'): | ||||
|             return False | ||||
|     raise Invalid(u"Expected boolean value, but cannot convert {} to a boolean. " | ||||
|                   u"Please use 'true' or 'false'".format(value)) | ||||
|     raise Invalid("Expected boolean value, but cannot convert {} to a boolean. " | ||||
|                   "Please use 'true' or 'false'".format(value)) | ||||
|  | ||||
|  | ||||
| def ensure_list(*validators): | ||||
| @@ -228,7 +223,7 @@ def int_(value): | ||||
|     Automatically also converts strings to ints. | ||||
|     """ | ||||
|     check_not_templatable(value) | ||||
|     if isinstance(value, integer_types): | ||||
|     if isinstance(value, int): | ||||
|         return value | ||||
|     if isinstance(value, float): | ||||
|         if int(value) == value: | ||||
| @@ -242,15 +237,15 @@ def int_(value): | ||||
|     try: | ||||
|         return int(value, base) | ||||
|     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): | ||||
|     """Validate that the config option is an integer in the given range.""" | ||||
|     if min is not None: | ||||
|         assert isinstance(min, integer_types) | ||||
|         assert isinstance(min, int) | ||||
|     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)) | ||||
|  | ||||
|  | ||||
| @@ -291,14 +286,14 @@ def validate_id_name(value): | ||||
|     valid_chars = ascii_letters + digits + '_' | ||||
|     for char in value: | ||||
|         if char not in valid_chars: | ||||
|             raise Invalid(u"IDs must only consist of upper/lowercase characters, the underscore" | ||||
|                           u"character and numbers. The character '{}' cannot be used" | ||||
|                           u"".format(char)) | ||||
|             raise Invalid("IDs must only consist of upper/lowercase characters, the underscore" | ||||
|                           "character and numbers. The character '{}' cannot be used" | ||||
|                           "".format(char)) | ||||
|     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: | ||||
|         raise Invalid(u"ID '{}' conflicts with the name of an esphome integration, please use " | ||||
|                       u"another ID name.".format(value)) | ||||
|         raise Invalid("ID '{}' conflicts with the name of an esphome integration, please use " | ||||
|                       "another ID name.".format(value)) | ||||
|     return value | ||||
|  | ||||
|  | ||||
| @@ -358,7 +353,7 @@ def only_on(platforms): | ||||
|  | ||||
|     def validator_(obj): | ||||
|         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 validator_ | ||||
| @@ -463,7 +458,7 @@ def time_period_str_unit(value): | ||||
|                       "'{0}s'?".format(value)) | ||||
|     if isinstance(value, TimePeriod): | ||||
|         value = str(value) | ||||
|     if not isinstance(value, string_types): | ||||
|     if not isinstance(value, str): | ||||
|         raise Invalid("Expected string for time period with unit.") | ||||
|  | ||||
|     unit_to_kwarg = { | ||||
| @@ -485,8 +480,8 @@ def time_period_str_unit(value): | ||||
|     match = re.match(r"^([-+]?[0-9]*\.?[0-9]*)\s*(\w*)$", value) | ||||
|  | ||||
|     if match is None: | ||||
|         raise Invalid(u"Expected time period with unit, " | ||||
|                       u"got {}".format(value)) | ||||
|         raise Invalid("Expected time period with unit, " | ||||
|                       "got {}".format(value)) | ||||
|     kwarg = unit_to_kwarg[one_of(*unit_to_kwarg)(match.group(2))] | ||||
|  | ||||
|     return TimePeriod(**{kwarg: float(match.group(1))}) | ||||
| @@ -545,7 +540,7 @@ def time_of_day(value): | ||||
|         try: | ||||
|             date = datetime.strptime(value, '%H:%M:%S %p') | ||||
|         except ValueError: | ||||
|             raise Invalid("Invalid time of day: {}".format(err)) | ||||
|             raise Invalid(f"Invalid time of day: {err}") | ||||
|  | ||||
|     return { | ||||
|         CONF_HOUR: date.hour, | ||||
| @@ -577,7 +572,7 @@ def uuid(value): | ||||
|  | ||||
| METRIC_SUFFIXES = { | ||||
|     '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 | ||||
| } | ||||
|  | ||||
| @@ -594,11 +589,11 @@ def float_with_unit(quantity, regex_suffix, optional_unit=False): | ||||
|         match = pattern.match(string(value)) | ||||
|  | ||||
|         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)) | ||||
|         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)] | ||||
|         return mantissa * multiplier | ||||
| @@ -606,30 +601,17 @@ def float_with_unit(quantity, regex_suffix, optional_unit=False): | ||||
|     return validator | ||||
|  | ||||
|  | ||||
| frequency = float_with_unit("frequency", u"(Hz|HZ|hz)?") | ||||
| resistance = float_with_unit("resistance", u"(Ω|Ω|ohm|Ohm|OHM)?") | ||||
| current = float_with_unit("current", u"(a|A|amp|Amp|amps|Amps|ampere|Ampere)?") | ||||
| voltage = float_with_unit("voltage", u"(v|V|volt|Volts)?") | ||||
| distance = float_with_unit("distance", u"(m)") | ||||
| framerate = float_with_unit("framerate", u"(FPS|fps|Fps|FpS|Hz)") | ||||
| angle = float_with_unit("angle", u"(°|deg)", optional_unit=True) | ||||
| _temperature_c = float_with_unit("temperature", u"(°C|° C|°|C)?") | ||||
| _temperature_k = float_with_unit("temperature", u"(° K|° K|K)?") | ||||
| _temperature_f = float_with_unit("temperature", u"(°F|° F|F)?") | ||||
| decibel = float_with_unit("decibel", u"(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 | ||||
| frequency = float_with_unit("frequency", "(Hz|HZ|hz)?") | ||||
| resistance = float_with_unit("resistance", "(Ω|Ω|ohm|Ohm|OHM)?") | ||||
| current = float_with_unit("current", "(a|A|amp|Amp|amps|Amps|ampere|Ampere)?") | ||||
| voltage = float_with_unit("voltage", "(v|V|volt|Volts)?") | ||||
| distance = float_with_unit("distance", "(m)") | ||||
| framerate = float_with_unit("framerate", "(FPS|fps|Fps|FpS|Hz)") | ||||
| angle = float_with_unit("angle", "(°|deg)", optional_unit=True) | ||||
| _temperature_c = float_with_unit("temperature", "(°C|° C|°|C)?") | ||||
| _temperature_k = float_with_unit("temperature", "(° K|° K|K)?") | ||||
| _temperature_f = float_with_unit("temperature", "(°F|° F|F)?") | ||||
| decibel = float_with_unit("decibel", "(dB|dBm|db|dbm)", optional_unit=True) | ||||
|  | ||||
|  | ||||
| def temperature(value): | ||||
| @@ -672,15 +654,15 @@ def validate_bytes(value): | ||||
|     match = re.match(r"^([0-9]+)\s*(\w*?)(?:byte|B|b)?s?$", value) | ||||
|  | ||||
|     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)) | ||||
|     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)] | ||||
|     if multiplier < 1: | ||||
|         raise Invalid(u"Only suffixes with positive exponents are supported. " | ||||
|                       u"Got {}".format(match.group(2))) | ||||
|         raise Invalid("Only suffixes with positive exponents are supported. " | ||||
|                       "Got {}".format(match.group(2))) | ||||
|     return int(mantissa * multiplier) | ||||
|  | ||||
|  | ||||
| @@ -701,7 +683,7 @@ def domain(value): | ||||
|     try: | ||||
|         return str(ipv4(value)) | ||||
|     except Invalid: | ||||
|         raise Invalid("Invalid domain: {}".format(value)) | ||||
|         raise Invalid(f"Invalid domain: {value}") | ||||
|  | ||||
|  | ||||
| def domain_name(value): | ||||
| @@ -730,7 +712,7 @@ def ssid(value): | ||||
| def ipv4(value): | ||||
|     if isinstance(value, list): | ||||
|         parts = value | ||||
|     elif isinstance(value, string_types): | ||||
|     elif isinstance(value, str): | ||||
|         parts = value.split('.') | ||||
|     elif isinstance(value, IPAddress): | ||||
|         return value | ||||
| @@ -806,7 +788,7 @@ def mqtt_qos(value): | ||||
|     try: | ||||
|         value = int(value) | ||||
|     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) | ||||
|  | ||||
|  | ||||
| @@ -814,7 +796,7 @@ def requires_component(comp): | ||||
|     """Validate that this option can only be specified when the component `comp` is loaded.""" | ||||
|     def validator(value): | ||||
|         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 validator | ||||
| @@ -839,7 +821,7 @@ def 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: | ||||
|         value = float(value[:-1].rstrip()) / 100.0 | ||||
|     if value > 1: | ||||
| @@ -856,7 +838,7 @@ def possibly_negative_percentage(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()) | ||||
|     return value | ||||
|  | ||||
| @@ -916,7 +898,7 @@ def one_of(*values, **kwargs): | ||||
|       - *float* (``bool``, default=False): Whether to convert the incoming values to floats. | ||||
|       - *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) | ||||
|     upper = kwargs.pop('upper', False) | ||||
|     string_ = kwargs.pop('string', False) or lower or upper | ||||
| @@ -940,13 +922,13 @@ def one_of(*values, **kwargs): | ||||
|             value = Upper(value) | ||||
|         if value not in values: | ||||
|             import difflib | ||||
|             options_ = [text_type(x) for x in values] | ||||
|             option = text_type(value) | ||||
|             options_ = [str(x) for x in values] | ||||
|             option = str(value) | ||||
|             matches = difflib.get_close_matches(option, options_) | ||||
|             if matches: | ||||
|                 raise Invalid(u"Unknown value '{}', did you mean {}?" | ||||
|                               u"".format(value, u", ".join(u"'{}'".format(x) for x in matches))) | ||||
|             raise Invalid(u"Unknown value '{}', valid options are {}.".format(value, options)) | ||||
|                 raise Invalid("Unknown value '{}', did you mean {}?" | ||||
|                               "".format(value, ", ".join(f"'{x}'" for x in matches))) | ||||
|             raise Invalid(f"Unknown value '{value}', valid options are {options}.") | ||||
|         return value | ||||
|  | ||||
|     return validator | ||||
| @@ -996,7 +978,7 @@ def returning_lambda(value): | ||||
|     Additionally, make sure the lambda returns something. | ||||
|     """ | ||||
|     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 " | ||||
|                       "is expected to return a value. \n" | ||||
|                       "Please make sure the lambda contains at least one " | ||||
| @@ -1007,24 +989,23 @@ def returning_lambda(value): | ||||
| def dimensions(value): | ||||
|     if isinstance(value, list): | ||||
|         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: | ||||
|             width, height = int(value[0]), int(value[1]) | ||||
|         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: | ||||
|             raise Invalid(u"Width and height must at least be 1") | ||||
|             raise Invalid("Width and height must at least be 1") | ||||
|         return [width, height] | ||||
|     value = string(value) | ||||
|     match = re.match(r"\s*([0-9]+)\s*[xX]\s*([0-9]+)\s*", value) | ||||
|     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)]) | ||||
|  | ||||
|  | ||||
| def directory(value): | ||||
|     import json | ||||
|     from esphome.py_compat import safe_input | ||||
|     value = string(value) | ||||
|     path = CORE.relative_config_path(value) | ||||
|  | ||||
| @@ -1034,25 +1015,24 @@ def directory(value): | ||||
|             'type': 'check_directory_exists', | ||||
|             'path': path, | ||||
|         })) | ||||
|         data = json.loads(safe_input()) | ||||
|         data = json.loads(input()) | ||||
|         assert data['type'] == 'directory_exists_response' | ||||
|         if data['content']: | ||||
|             return value | ||||
|         raise Invalid(u"Could not find directory '{}'. Please make sure it exists (full path: {})." | ||||
|                       u"".format(path, os.path.abspath(path))) | ||||
|         raise Invalid("Could not find directory '{}'. Please make sure it exists (full path: {})." | ||||
|                       "".format(path, os.path.abspath(path))) | ||||
|  | ||||
|     if not os.path.exists(path): | ||||
|         raise Invalid(u"Could not find directory '{}'. Please make sure it exists (full path: {})." | ||||
|                       u"".format(path, os.path.abspath(path))) | ||||
|         raise Invalid("Could not find directory '{}'. Please make sure it exists (full path: {})." | ||||
|                       "".format(path, os.path.abspath(path))) | ||||
|     if not os.path.isdir(path): | ||||
|         raise Invalid(u"Path '{}' is not a directory (full path: {})." | ||||
|                       u"".format(path, os.path.abspath(path))) | ||||
|         raise Invalid("Path '{}' is not a directory (full path: {})." | ||||
|                       "".format(path, os.path.abspath(path))) | ||||
|     return value | ||||
|  | ||||
|  | ||||
| def file_(value): | ||||
|     import json | ||||
|     from esphome.py_compat import safe_input | ||||
|     value = string(value) | ||||
|     path = CORE.relative_config_path(value) | ||||
|  | ||||
| @@ -1062,19 +1042,19 @@ def file_(value): | ||||
|             'type': 'check_file_exists', | ||||
|             'path': path, | ||||
|         })) | ||||
|         data = json.loads(safe_input()) | ||||
|         data = json.loads(input()) | ||||
|         assert data['type'] == 'file_exists_response' | ||||
|         if data['content']: | ||||
|             return value | ||||
|         raise Invalid(u"Could not find file '{}'. Please make sure it exists (full path: {})." | ||||
|                       u"".format(path, os.path.abspath(path))) | ||||
|         raise Invalid("Could not find file '{}'. Please make sure it exists (full path: {})." | ||||
|                       "".format(path, os.path.abspath(path))) | ||||
|  | ||||
|     if not os.path.exists(path): | ||||
|         raise Invalid(u"Could not find file '{}'. Please make sure it exists (full path: {})." | ||||
|                       u"".format(path, os.path.abspath(path))) | ||||
|         raise Invalid("Could not find file '{}'. Please make sure it exists (full path: {})." | ||||
|                       "".format(path, os.path.abspath(path))) | ||||
|     if not os.path.isfile(path): | ||||
|         raise Invalid(u"Path '{}' is not a file (full path: {})." | ||||
|                       u"".format(path, os.path.abspath(path))) | ||||
|         raise Invalid("Path '{}' is not a file (full path: {})." | ||||
|                       "".format(path, os.path.abspath(path))) | ||||
|     return value | ||||
|  | ||||
|  | ||||
| @@ -1092,7 +1072,7 @@ def entity_id(value): | ||||
|     for x in value.split('.'): | ||||
|         for c in x: | ||||
|             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 | ||||
|  | ||||
|  | ||||
| @@ -1103,9 +1083,9 @@ def extract_keys(schema): | ||||
|     assert isinstance(schema, dict) | ||||
|     keys = [] | ||||
|     for skey in list(schema.keys()): | ||||
|         if isinstance(skey, string_types): | ||||
|         if isinstance(skey, str): | ||||
|             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) | ||||
|         else: | ||||
|             raise ValueError() | ||||
| @@ -1136,14 +1116,14 @@ class GenerateID(Optional): | ||||
|     """Mark this key as being an auto-generated ID key.""" | ||||
|  | ||||
|     def __init__(self, key=CONF_ID): | ||||
|         super(GenerateID, self).__init__(key, default=lambda: None) | ||||
|         super().__init__(key, default=lambda: None) | ||||
|  | ||||
|  | ||||
| class SplitDefault(Optional): | ||||
|     """Mark this key to have a split default for ESP8266/ESP32.""" | ||||
|  | ||||
|     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._esp32_default = vol.default_factory(esp32) | ||||
|  | ||||
| @@ -1165,7 +1145,7 @@ class OnlyWith(Optional): | ||||
|     """Set the default value only if the given component is loaded.""" | ||||
|  | ||||
|     def __init__(self, key, component, default=None): | ||||
|         super(OnlyWith, self).__init__(key) | ||||
|         super().__init__(key) | ||||
|         self._component = component | ||||
|         self._default = vol.default_factory(default) | ||||
|  | ||||
| @@ -1207,21 +1187,21 @@ def validate_registry_entry(name, registry): | ||||
|     ignore_keys = extract_keys(base_schema) | ||||
|  | ||||
|     def validator(value): | ||||
|         if isinstance(value, string_types): | ||||
|         if isinstance(value, str): | ||||
|             value = {value: {}} | ||||
|         if not isinstance(value, dict): | ||||
|             raise Invalid(u"{} must consist of key-value mapping! Got {}" | ||||
|                           u"".format(name.title(), value)) | ||||
|             raise Invalid("{} must consist of key-value mapping! Got {}" | ||||
|                           "".format(name.title(), value)) | ||||
|         key = next((x for x in value if x not in ignore_keys), 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: | ||||
|             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) | ||||
|         if key2 is not None: | ||||
|             raise Invalid(u"Cannot have two {0}s in one item. Key '{1}' overrides '{2}'! " | ||||
|                           u"Did you forget to indent the block inside the {0}?" | ||||
|                           u"".format(name, key, key2)) | ||||
|             raise Invalid("Cannot have two {0}s in one item. Key '{1}' overrides '{2}'! " | ||||
|                           "Did you forget to indent the block inside the {0}?" | ||||
|                           "".format(name, key, key2)) | ||||
|  | ||||
|         if value[key] is None: | ||||
|             value[key] = {} | ||||
| @@ -1296,7 +1276,7 @@ def polling_component_schema(default_update_interval): | ||||
|         return COMPONENT_SCHEMA.extend({ | ||||
|             Required(CONF_UPDATE_INTERVAL): default_update_interval, | ||||
|         }) | ||||
|     assert isinstance(default_update_interval, string_types) | ||||
|     assert isinstance(default_update_interval, str) | ||||
|     return COMPONENT_SCHEMA.extend({ | ||||
|         Optional(CONF_UPDATE_INTERVAL, default=default_update_interval): update_interval, | ||||
|     }) | ||||
|   | ||||
| @@ -1,17 +1,16 @@ | ||||
| # coding=utf-8 | ||||
| """Constants used by esphome.""" | ||||
|  | ||||
| MAJOR_VERSION = 1 | ||||
| MINOR_VERSION = 15 | ||||
| PATCH_VERSION = '0-dev' | ||||
| __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) | ||||
| __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) | ||||
| __short_version__ = f'{MAJOR_VERSION}.{MINOR_VERSION}' | ||||
| __version__ = f'{__short_version__}.{PATCH_VERSION}' | ||||
|  | ||||
| ESP_PLATFORM_ESP32 = 'ESP32' | ||||
| ESP_PLATFORM_ESP8266 = '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_1_0_0 = 'espressif32@1.5.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' | ||||
|  | ||||
| UNIT_AMPERE = 'A' | ||||
| UNIT_CELSIUS = u'°C' | ||||
| UNIT_COUNTS_PER_CUBIC_METER = u'#/m³' | ||||
| UNIT_CELSIUS = '°C' | ||||
| UNIT_COUNTS_PER_CUBIC_METER = '#/m³' | ||||
| UNIT_DECIBEL = 'dB' | ||||
| UNIT_DECIBEL_MILLIWATT = 'dBm' | ||||
| UNIT_DEGREE_PER_SECOND = u'°/s' | ||||
| UNIT_DEGREES = u'°' | ||||
| UNIT_DEGREE_PER_SECOND = '°/s' | ||||
| UNIT_DEGREES = '°' | ||||
| UNIT_EMPTY = '' | ||||
| UNIT_G = 'G' | ||||
| UNIT_HECTOPASCAL = 'hPa' | ||||
| @@ -559,12 +558,12 @@ UNIT_KILOMETER = 'km' | ||||
| UNIT_KILOMETER_PER_HOUR = 'km/h' | ||||
| UNIT_LUX = 'lx' | ||||
| UNIT_METER = 'm' | ||||
| UNIT_METER_PER_SECOND_SQUARED = u'm/s²' | ||||
| UNIT_MICROGRAMS_PER_CUBIC_METER = u'µg/m³' | ||||
| UNIT_METER_PER_SECOND_SQUARED = 'm/s²' | ||||
| UNIT_MICROGRAMS_PER_CUBIC_METER = 'µg/m³' | ||||
| UNIT_MICROMETER = 'µm' | ||||
| UNIT_MICROSIEMENS_PER_CENTIMETER = u'µS/cm' | ||||
| UNIT_MICROTESLA = u'µT' | ||||
| UNIT_OHM = u'Ω' | ||||
| UNIT_MICROSIEMENS_PER_CENTIMETER = 'µS/cm' | ||||
| UNIT_MICROTESLA = 'µT' | ||||
| UNIT_OHM = 'Ω' | ||||
| UNIT_PARTS_PER_BILLION = 'ppb' | ||||
| UNIT_PARTS_PER_MILLION = 'ppm' | ||||
| UNIT_PERCENT = '%' | ||||
|   | ||||
							
								
								
									
										129
									
								
								esphome/core.py
									
									
									
									
									
								
							
							
						
						
									
										129
									
								
								esphome/core.py
									
									
									
									
									
								
							| @@ -13,7 +13,6 @@ from typing import Any, Dict, List, Optional, Set  # noqa | ||||
| from esphome.const import CONF_ARDUINO_VERSION, SOURCE_FILE_EXTENSIONS, \ | ||||
|     CONF_COMMENT, CONF_ESPHOME, CONF_USE_ADDRESS, CONF_WIFI | ||||
| 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 | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
| @@ -23,53 +22,47 @@ class EsphomeError(Exception): | ||||
|     """General ESPHome exception occurred.""" | ||||
|  | ||||
|  | ||||
| if IS_PY2: | ||||
|     base_int = long | ||||
| else: | ||||
|     base_int = int | ||||
|  | ||||
|  | ||||
| class HexInt(base_int): | ||||
| class HexInt(int): | ||||
|     def __str__(self): | ||||
|         if 0 <= self <= 255: | ||||
|             return "0x{:02X}".format(self) | ||||
|         return "0x{:X}".format(self) | ||||
|             return f"0x{self:02X}" | ||||
|         return f"0x{self:X}" | ||||
|  | ||||
|  | ||||
| class IPAddress(object): | ||||
| class IPAddress: | ||||
|     def __init__(self, *args): | ||||
|         if len(args) != 4: | ||||
|             raise ValueError(u"IPAddress must consist up 4 items") | ||||
|             raise ValueError("IPAddress must consist up 4 items") | ||||
|         self.args = args | ||||
|  | ||||
|     def __str__(self): | ||||
|         return '.'.join(str(x) for x in self.args) | ||||
|  | ||||
|  | ||||
| class MACAddress(object): | ||||
| class MACAddress: | ||||
|     def __init__(self, *parts): | ||||
|         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 | ||||
|  | ||||
|     def __str__(self): | ||||
|         return ':'.join('{:02X}'.format(part) for part in self.parts) | ||||
|         return ':'.join(f'{part:02X}' for part in self.parts) | ||||
|  | ||||
|     @property | ||||
|     def as_hex(self): | ||||
|         from esphome.cpp_generator import RawExpression | ||||
|  | ||||
|         num = ''.join('{:02X}'.format(part) for part in self.parts) | ||||
|         return RawExpression('0x{}ULL'.format(num)) | ||||
|         num = ''.join(f'{part:02X}' for part in self.parts) | ||||
|         return RawExpression(f'0x{num}ULL') | ||||
|  | ||||
|  | ||||
| def is_approximately_integer(value): | ||||
|     if isinstance(value, integer_types): | ||||
|     if isinstance(value, int): | ||||
|         return True | ||||
|     return abs(value - round(value)) < 0.001 | ||||
|  | ||||
|  | ||||
| class TimePeriod(object): | ||||
| class TimePeriod: | ||||
|     def __init__(self, microseconds=None, milliseconds=None, seconds=None, | ||||
|                  minutes=None, hours=None, days=None): | ||||
|         if days is not None: | ||||
| @@ -137,17 +130,17 @@ class TimePeriod(object): | ||||
|  | ||||
|     def __str__(self): | ||||
|         if self.microseconds is not None: | ||||
|             return '{}us'.format(self.total_microseconds) | ||||
|             return f'{self.total_microseconds}us' | ||||
|         if self.milliseconds is not None: | ||||
|             return '{}ms'.format(self.total_milliseconds) | ||||
|             return f'{self.total_milliseconds}ms' | ||||
|         if self.seconds is not None: | ||||
|             return '{}s'.format(self.total_seconds) | ||||
|             return f'{self.total_seconds}s' | ||||
|         if self.minutes is not None: | ||||
|             return '{}min'.format(self.total_minutes) | ||||
|             return f'{self.total_minutes}min' | ||||
|         if self.hours is not None: | ||||
|             return '{}h'.format(self.total_hours) | ||||
|             return f'{self.total_hours}h' | ||||
|         if self.days is not None: | ||||
|             return '{}d'.format(self.total_days) | ||||
|             return f'{self.total_days}d' | ||||
|         return '0s' | ||||
|  | ||||
|     @property | ||||
| @@ -224,7 +217,7 @@ class TimePeriodMinutes(TimePeriod): | ||||
| LAMBDA_PROG = re.compile(r'id\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)(\.?)') | ||||
|  | ||||
|  | ||||
| class Lambda(object): | ||||
| class Lambda: | ||||
|     def __init__(self, value): | ||||
|         # pylint: disable=protected-access | ||||
|         if isinstance(value, Lambda): | ||||
| @@ -260,10 +253,10 @@ class Lambda(object): | ||||
|         return self.value | ||||
|  | ||||
|     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): | ||||
|         self.id = id | ||||
|         if is_manual is None: | ||||
| @@ -289,7 +282,7 @@ class ID(object): | ||||
|         return self.id | ||||
|  | ||||
|     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) | ||||
|  | ||||
|     def __eq__(self, other): | ||||
| @@ -305,10 +298,10 @@ class ID(object): | ||||
|                   is_manual=self.is_manual) | ||||
|  | ||||
|  | ||||
| class DocumentLocation(object): | ||||
| class DocumentLocation: | ||||
|     def __init__(self, document, line, column): | ||||
|         # type: (basestring, int, int) -> None | ||||
|         self.document = document  # type: basestring | ||||
|         # type: (str, int, int) -> None | ||||
|         self.document = document  # type: str | ||||
|         self.line = line  # type: int | ||||
|         self.column = column  # type: int | ||||
|  | ||||
| @@ -321,10 +314,10 @@ class DocumentLocation(object): | ||||
|         ) | ||||
|  | ||||
|     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): | ||||
|         # type: (DocumentLocation, DocumentLocation) -> None | ||||
|         self.start_mark = start_mark  # type: DocumentLocation | ||||
| @@ -338,10 +331,10 @@ class DocumentRange(object): | ||||
|         ) | ||||
|  | ||||
|     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): | ||||
|         self.name = name | ||||
|         self.value = value | ||||
| @@ -349,14 +342,14 @@ class Define(object): | ||||
|     @property | ||||
|     def as_build_flag(self): | ||||
|         if self.value is None: | ||||
|             return u'-D{}'.format(self.name) | ||||
|         return u'-D{}={}'.format(self.name, self.value) | ||||
|             return f'-D{self.name}' | ||||
|         return f'-D{self.name}={self.value}' | ||||
|  | ||||
|     @property | ||||
|     def as_macro(self): | ||||
|         if self.value is None: | ||||
|             return u'#define {}'.format(self.name) | ||||
|         return u'#define {} {}'.format(self.name, self.value) | ||||
|             return f'#define {self.name}' | ||||
|         return f'#define {self.name} {self.value}' | ||||
|  | ||||
|     @property | ||||
|     def as_tuple(self): | ||||
| @@ -369,7 +362,7 @@ class Define(object): | ||||
|         return isinstance(self, type(other)) and self.as_tuple == other.as_tuple | ||||
|  | ||||
|  | ||||
| class Library(object): | ||||
| class Library: | ||||
|     def __init__(self, name, version): | ||||
|         self.name = name | ||||
|         self.version = version | ||||
| @@ -378,7 +371,7 @@ class Library(object): | ||||
|     def as_lib_dep(self): | ||||
|         if self.version is None: | ||||
|             return self.name | ||||
|         return u'{}@{}'.format(self.name, self.version) | ||||
|         return f'{self.name}@{self.version}' | ||||
|  | ||||
|     @property | ||||
|     def as_tuple(self): | ||||
| @@ -461,7 +454,7 @@ def find_source_files(file): | ||||
|  | ||||
|  | ||||
| # pylint: disable=too-many-instance-attributes,too-many-public-methods | ||||
| class EsphomeCore(object): | ||||
| class EsphomeCore: | ||||
|     def __init__(self): | ||||
|         # True if command is run from dashboard | ||||
|         self.dashboard = False | ||||
| @@ -499,7 +492,7 @@ class EsphomeCore(object): | ||||
|         # A set of build flags to set in the platformio project | ||||
|         self.build_flags = set()  # type: Set[str] | ||||
|         # 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 | ||||
|         # awaited. | ||||
|         self.active_coroutines = {}  # type: Dict[int, Any] | ||||
| @@ -634,15 +627,15 @@ class EsphomeCore(object): | ||||
|  | ||||
|         # Print not-awaited coroutines | ||||
|         for obj in self.active_coroutines.values(): | ||||
|             _LOGGER.warning(u"Coroutine '%s' %s was never awaited with 'yield'.", obj.__name__, obj) | ||||
|             _LOGGER.warning(u"Please file a bug report with your configuration.") | ||||
|             _LOGGER.warning("Coroutine '%s' %s was never awaited with 'yield'.", obj.__name__, obj) | ||||
|             _LOGGER.warning("Please file a bug report with your configuration.") | ||||
|         if self.active_coroutines: | ||||
|             raise EsphomeError() | ||||
|         if self.component_ids: | ||||
|             comps = u', '.join(u"'{}'".format(x) for x in self.component_ids) | ||||
|             _LOGGER.warning(u"Components %s were never registered. Please create a bug report", | ||||
|             comps = ', '.join(f"'{x}'" for x in self.component_ids) | ||||
|             _LOGGER.warning("Components %s were never registered. Please create a bug report", | ||||
|                             comps) | ||||
|             _LOGGER.warning(u"with your configuration.") | ||||
|             _LOGGER.warning("with your configuration.") | ||||
|             raise EsphomeError() | ||||
|         self.active_coroutines.clear() | ||||
|  | ||||
| @@ -652,8 +645,8 @@ class EsphomeCore(object): | ||||
|         if isinstance(expression, Expression): | ||||
|             expression = statement(expression) | ||||
|         if not isinstance(expression, Statement): | ||||
|             raise ValueError(u"Add '{}' must be expression or statement, not {}" | ||||
|                              u"".format(expression, type(expression))) | ||||
|             raise ValueError("Add '{}' must be expression or statement, not {}" | ||||
|                              "".format(expression, type(expression))) | ||||
|  | ||||
|         self.main_statements.append(expression) | ||||
|         _LOGGER.debug("Adding: %s", expression) | ||||
| @@ -665,16 +658,16 @@ class EsphomeCore(object): | ||||
|         if isinstance(expression, Expression): | ||||
|             expression = statement(expression) | ||||
|         if not isinstance(expression, Statement): | ||||
|             raise ValueError(u"Add '{}' must be expression or statement, not {}" | ||||
|                              u"".format(expression, type(expression))) | ||||
|             raise ValueError("Add '{}' must be expression or statement, not {}" | ||||
|                              "".format(expression, type(expression))) | ||||
|         self.global_statements.append(expression) | ||||
|         _LOGGER.debug("Adding global: %s", expression) | ||||
|         return expression | ||||
|  | ||||
|     def add_library(self, library): | ||||
|         if not isinstance(library, Library): | ||||
|             raise ValueError(u"Library {} must be instance of Library, not {}" | ||||
|                              u"".format(library, type(library))) | ||||
|             raise ValueError("Library {} must be instance of Library, not {}" | ||||
|                              "".format(library, type(library))) | ||||
|         _LOGGER.debug("Adding library: %s", library) | ||||
|         for other in self.libraries[:]: | ||||
|             if other.name != library.name: | ||||
| @@ -689,9 +682,9 @@ class EsphomeCore(object): | ||||
|             if other.version == library.version: | ||||
|                 break | ||||
|  | ||||
|             raise ValueError(u"Version pinning failed! Libraries {} and {} " | ||||
|                              u"requested with conflicting versions!" | ||||
|                              u"".format(library, other)) | ||||
|             raise ValueError("Version pinning failed! Libraries {} and {} " | ||||
|                              "requested with conflicting versions!" | ||||
|                              "".format(library, other)) | ||||
|         else: | ||||
|             self.libraries.append(library) | ||||
|         return library | ||||
| @@ -702,20 +695,20 @@ class EsphomeCore(object): | ||||
|         return build_flag | ||||
|  | ||||
|     def add_define(self, define): | ||||
|         if isinstance(define, string_types): | ||||
|         if isinstance(define, str): | ||||
|             define = Define(define) | ||||
|         elif isinstance(define, Define): | ||||
|             pass | ||||
|         else: | ||||
|             raise ValueError(u"Define {} must be string or Define, not {}" | ||||
|                              u"".format(define, type(define))) | ||||
|             raise ValueError("Define {} must be string or Define, not {}" | ||||
|                              "".format(define, type(define))) | ||||
|         self.defines.add(define) | ||||
|         _LOGGER.debug("Adding define: %s", define) | ||||
|         return define | ||||
|  | ||||
|     def get_variable(self, 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: | ||||
|             if id in self.variables: | ||||
|                 yield self.variables[id] | ||||
| @@ -735,7 +728,7 @@ class EsphomeCore(object): | ||||
|  | ||||
|     def register_variable(self, id, obj): | ||||
|         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) | ||||
|         self.variables[id] = obj | ||||
|  | ||||
| @@ -748,10 +741,10 @@ class EsphomeCore(object): | ||||
|  | ||||
|         main_code = [] | ||||
|         for exp in self.main_statements: | ||||
|             text = text_type(statement(exp)) | ||||
|             text = str(statement(exp)) | ||||
|             text = text.rstrip() | ||||
|             main_code.append(text) | ||||
|         return u'\n'.join(main_code) + u'\n\n' | ||||
|         return '\n'.join(main_code) + '\n\n' | ||||
|  | ||||
|     @property | ||||
|     def cpp_global_section(self): | ||||
| @@ -759,17 +752,17 @@ class EsphomeCore(object): | ||||
|  | ||||
|         global_code = [] | ||||
|         for exp in self.global_statements: | ||||
|             text = text_type(statement(exp)) | ||||
|             text = str(statement(exp)) | ||||
|             text = text.rstrip() | ||||
|             global_code.append(text) | ||||
|         return u'\n'.join(global_code) + u'\n' | ||||
|         return '\n'.join(global_code) + '\n' | ||||
|  | ||||
|  | ||||
| class AutoLoad(OrderedDict): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class EnumValue(object): | ||||
| class EnumValue: | ||||
|     """Special type used by ESPHome to mark enum values for cv.enum.""" | ||||
|     @property | ||||
|     def enum_value(self): | ||||
|   | ||||
| @@ -138,6 +138,9 @@ float Component::get_actual_setup_priority() const { | ||||
|   return this->setup_priority_override_; | ||||
| } | ||||
| 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 { | ||||
| #ifdef CLANG_TIDY | ||||
|   bool loop_overridden = true; | ||||
| @@ -148,6 +151,7 @@ bool Component::has_overridden_loop() const { | ||||
| #endif | ||||
|   return loop_overridden || call_loop_overridden; | ||||
| } | ||||
| #pragma GCC diagnostic pop | ||||
|  | ||||
| PollingComponent::PollingComponent(uint32_t update_interval) : Component(), update_interval_(update_interval) {} | ||||
|  | ||||
|   | ||||
| @@ -37,8 +37,8 @@ def validate_board(value): | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     if value not in board_pins: | ||||
|         raise cv.Invalid(u"Could not find board '{}'. Valid boards are {}".format( | ||||
|             value, u', '.join(sorted(board_pins.keys())))) | ||||
|         raise cv.Invalid("Could not find board '{}'. Valid boards are {}".format( | ||||
|             value, ', '.join(sorted(board_pins.keys())))) | ||||
|     return value | ||||
|  | ||||
|  | ||||
| @@ -108,8 +108,8 @@ def valid_include(value): | ||||
|     value = cv.file_(value) | ||||
|     _, ext = os.path.splitext(value) | ||||
|     if ext not in VALID_INCLUDE_EXTS: | ||||
|         raise cv.Invalid(u"Include has invalid file extension {} - valid extensions are {}" | ||||
|                          u"".format(ext, ', '.join(VALID_INCLUDE_EXTS))) | ||||
|         raise cv.Invalid("Include has invalid file extension {} - valid extensions are {}" | ||||
|                          "".format(ext, ', '.join(VALID_INCLUDE_EXTS))) | ||||
|     return value | ||||
|  | ||||
|  | ||||
| @@ -184,7 +184,7 @@ def include_file(path, basename): | ||||
|     _, ext = os.path.splitext(path) | ||||
|     if ext in ['.h', '.hpp', '.tcc']: | ||||
|         # 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) | ||||
| @@ -238,7 +238,7 @@ def to_code(config): | ||||
|             ld_script = ld_scripts[1] | ||||
|  | ||||
|         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') | ||||
|  | ||||
|   | ||||
| @@ -10,22 +10,21 @@ from esphome.core import (  # noqa | ||||
|     TimePeriodMilliseconds, TimePeriodMinutes, TimePeriodSeconds, coroutine, Library, Define, | ||||
|     EnumValue) | ||||
| 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 | ||||
|  | ||||
|  | ||||
| class Expression(object): | ||||
| class Expression: | ||||
|     def __str__(self): | ||||
|         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]] | ||||
|  | ||||
|  | ||||
| class RawExpression(Expression): | ||||
|     def __init__(self, text):  # type: (Union[str, unicode]) -> None | ||||
|         super(RawExpression, self).__init__() | ||||
|     def __init__(self, text):  # type: (Union[str, str]) -> None | ||||
|         super().__init__() | ||||
|         self.text = text | ||||
|  | ||||
|     def __str__(self): | ||||
| @@ -35,7 +34,7 @@ class RawExpression(Expression): | ||||
| # pylint: disable=redefined-builtin | ||||
| class AssignmentExpression(Expression): | ||||
|     def __init__(self, type, modifier, name, rhs, obj): | ||||
|         super(AssignmentExpression, self).__init__() | ||||
|         super().__init__() | ||||
|         self.type = type | ||||
|         self.modifier = modifier | ||||
|         self.name = name | ||||
| @@ -44,24 +43,24 @@ class AssignmentExpression(Expression): | ||||
|  | ||||
|     def __str__(self): | ||||
|         if self.type is None: | ||||
|             return u"{} = {}".format(self.name, self.rhs) | ||||
|         return u"{} {}{} = {}".format(self.type, self.modifier, self.name, self.rhs) | ||||
|             return f"{self.name} = {self.rhs}" | ||||
|         return f"{self.type} {self.modifier}{self.name} = {self.rhs}" | ||||
|  | ||||
|  | ||||
| class VariableDeclarationExpression(Expression): | ||||
|     def __init__(self, type, modifier, name): | ||||
|         super(VariableDeclarationExpression, self).__init__() | ||||
|         super().__init__() | ||||
|         self.type = type | ||||
|         self.modifier = modifier | ||||
|         self.name = name | ||||
|  | ||||
|     def __str__(self): | ||||
|         return u"{} {}{}".format(self.type, self.modifier, self.name) | ||||
|         return f"{self.type} {self.modifier}{self.name}" | ||||
|  | ||||
|  | ||||
| class ExpressionList(Expression): | ||||
|     def __init__(self, *args): | ||||
|         super(ExpressionList, self).__init__() | ||||
|         super().__init__() | ||||
|         # Remove every None on end | ||||
|         args = list(args) | ||||
|         while args and args[-1] is None: | ||||
| @@ -69,7 +68,7 @@ class ExpressionList(Expression): | ||||
|         self.args = [safe_exp(arg) for arg in args] | ||||
|  | ||||
|     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) | ||||
|  | ||||
|     def __iter__(self): | ||||
| @@ -78,11 +77,11 @@ class ExpressionList(Expression): | ||||
|  | ||||
| class TemplateArguments(Expression): | ||||
|     def __init__(self, *args):  # type: (*SafeExpType) -> None | ||||
|         super(TemplateArguments, self).__init__() | ||||
|         super().__init__() | ||||
|         self.args = ExpressionList(*args) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return u'<{}>'.format(self.args) | ||||
|         return f'<{self.args}>' | ||||
|  | ||||
|     def __iter__(self): | ||||
|         return iter(self.args) | ||||
| @@ -90,7 +89,7 @@ class TemplateArguments(Expression): | ||||
|  | ||||
| class CallExpression(Expression): | ||||
|     def __init__(self, base, *args):  # type: (Expression, *SafeExpType) -> None | ||||
|         super(CallExpression, self).__init__() | ||||
|         super().__init__() | ||||
|         self.base = base | ||||
|         if args and isinstance(args[0], TemplateArguments): | ||||
|             self.template_args = args[0] | ||||
| @@ -101,13 +100,13 @@ class CallExpression(Expression): | ||||
|  | ||||
|     def __str__(self): | ||||
|         if self.template_args is not None: | ||||
|             return u'{}{}({})'.format(self.base, self.template_args, self.args) | ||||
|         return u'{}({})'.format(self.base, self.args) | ||||
|             return f'{self.base}{self.template_args}({self.args})' | ||||
|         return f'{self.base}({self.args})' | ||||
|  | ||||
|  | ||||
| class StructInitializer(Expression): | ||||
|     def __init__(self, base, *args):  # type: (Expression, *Tuple[str, SafeExpType]) -> None | ||||
|         super(StructInitializer, self).__init__() | ||||
|         super().__init__() | ||||
|         self.base = base | ||||
|         if not isinstance(args, OrderedDict): | ||||
|             args = OrderedDict(args) | ||||
| @@ -119,16 +118,16 @@ class StructInitializer(Expression): | ||||
|             self.args[key] = exp | ||||
|  | ||||
|     def __str__(self): | ||||
|         cpp = u'{}{{\n'.format(self.base) | ||||
|         cpp = f'{self.base}{{\n' | ||||
|         for key, value in self.args.items(): | ||||
|             cpp += u'  .{} = {},\n'.format(key, value) | ||||
|         cpp += u'}' | ||||
|             cpp += f'  .{key} = {value},\n' | ||||
|         cpp += '}' | ||||
|         return cpp | ||||
|  | ||||
|  | ||||
| class ArrayInitializer(Expression): | ||||
|     def __init__(self, *args, **kwargs):  # type: (*Any, **Any) -> None | ||||
|         super(ArrayInitializer, self).__init__() | ||||
|         super().__init__() | ||||
|         self.multiline = kwargs.get('multiline', False) | ||||
|         self.args = [] | ||||
|         for arg in args: | ||||
| @@ -139,30 +138,30 @@ class ArrayInitializer(Expression): | ||||
|  | ||||
|     def __str__(self): | ||||
|         if not self.args: | ||||
|             return u'{}' | ||||
|             return '{}' | ||||
|         if self.multiline: | ||||
|             cpp = u'{\n' | ||||
|             cpp = '{\n' | ||||
|             for arg in self.args: | ||||
|                 cpp += u'  {},\n'.format(arg) | ||||
|             cpp += u'}' | ||||
|                 cpp += f'  {arg},\n' | ||||
|             cpp += '}' | ||||
|         else: | ||||
|             cpp = u'{' + u', '.join(str(arg) for arg in self.args) + u'}' | ||||
|             cpp = '{' + ', '.join(str(arg) for arg in self.args) + '}' | ||||
|         return cpp | ||||
|  | ||||
|  | ||||
| class ParameterExpression(Expression): | ||||
|     def __init__(self, type, id): | ||||
|         super(ParameterExpression, self).__init__() | ||||
|         super().__init__() | ||||
|         self.type = safe_exp(type) | ||||
|         self.id = id | ||||
|  | ||||
|     def __str__(self): | ||||
|         return u"{} {}".format(self.type, self.id) | ||||
|         return f"{self.type} {self.id}" | ||||
|  | ||||
|  | ||||
| class ParameterListExpression(Expression): | ||||
|     def __init__(self, *parameters): | ||||
|         super(ParameterListExpression, self).__init__() | ||||
|         super().__init__() | ||||
|         self.parameters = [] | ||||
|         for parameter in parameters: | ||||
|             if not isinstance(parameter, ParameterExpression): | ||||
| @@ -170,12 +169,12 @@ class ParameterListExpression(Expression): | ||||
|             self.parameters.append(parameter) | ||||
|  | ||||
|     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): | ||||
|     def __init__(self, parts, parameters, capture='=', return_type=None): | ||||
|         super(LambdaExpression, self).__init__() | ||||
|         super().__init__() | ||||
|         self.parts = parts | ||||
|         if not isinstance(parameters, ParameterListExpression): | ||||
|             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 | ||||
|  | ||||
|     def __str__(self): | ||||
|         cpp = u'[{}]({})'.format(self.capture, self.parameters) | ||||
|         cpp = f'[{self.capture}]({self.parameters})' | ||||
|         if self.return_type is not None: | ||||
|             cpp += u' -> {}'.format(self.return_type) | ||||
|         cpp += u' {{\n{}\n}}'.format(self.content) | ||||
|             cpp += f' -> {self.return_type}' | ||||
|         cpp += f' {{\n{self.content}\n}}' | ||||
|         return indent_all_but_first_and_last(cpp) | ||||
|  | ||||
|     @property | ||||
|     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): | ||||
| @@ -201,41 +200,41 @@ class Literal(Expression): | ||||
|  | ||||
|  | ||||
| class StringLiteral(Literal): | ||||
|     def __init__(self, string):  # type: (Union[str, unicode]) -> None | ||||
|         super(StringLiteral, self).__init__() | ||||
|     def __init__(self, string):  # type: (Union[str, str]) -> None | ||||
|         super().__init__() | ||||
|         self.string = string | ||||
|  | ||||
|     def __str__(self): | ||||
|         return u'{}'.format(cpp_string_escape(self.string)) | ||||
|         return '{}'.format(cpp_string_escape(self.string)) | ||||
|  | ||||
|  | ||||
| class IntLiteral(Literal): | ||||
|     def __init__(self, i):  # type: (Union[int, long]) -> None | ||||
|         super(IntLiteral, self).__init__() | ||||
|     def __init__(self, i):  # type: (Union[int]) -> None | ||||
|         super().__init__() | ||||
|         self.i = i | ||||
|  | ||||
|     def __str__(self): | ||||
|         if self.i > 4294967295: | ||||
|             return u'{}ULL'.format(self.i) | ||||
|             return f'{self.i}ULL' | ||||
|         if self.i > 2147483647: | ||||
|             return u'{}UL'.format(self.i) | ||||
|             return f'{self.i}UL' | ||||
|         if self.i < -2147483648: | ||||
|             return u'{}LL'.format(self.i) | ||||
|         return text_type(self.i) | ||||
|             return f'{self.i}LL' | ||||
|         return str(self.i) | ||||
|  | ||||
|  | ||||
| class BoolLiteral(Literal): | ||||
|     def __init__(self, binary):  # type: (bool) -> None | ||||
|         super(BoolLiteral, self).__init__() | ||||
|         super().__init__() | ||||
|         self.binary = binary | ||||
|  | ||||
|     def __str__(self): | ||||
|         return u"true" if self.binary else u"false" | ||||
|         return "true" if self.binary else "false" | ||||
|  | ||||
|  | ||||
| class HexIntLiteral(Literal): | ||||
|     def __init__(self, i):  # type: (int) -> None | ||||
|         super(HexIntLiteral, self).__init__() | ||||
|         super().__init__() | ||||
|         self.i = HexInt(i) | ||||
|  | ||||
|     def __str__(self): | ||||
| @@ -244,18 +243,18 @@ class HexIntLiteral(Literal): | ||||
|  | ||||
| class FloatLiteral(Literal): | ||||
|     def __init__(self, value):  # type: (float) -> None | ||||
|         super(FloatLiteral, self).__init__() | ||||
|         super().__init__() | ||||
|         self.float_ = value | ||||
|  | ||||
|     def __str__(self): | ||||
|         if math.isnan(self.float_): | ||||
|             return u"NAN" | ||||
|         return u"{}f".format(self.float_) | ||||
|             return "NAN" | ||||
|         return f"{self.float_}f" | ||||
|  | ||||
|  | ||||
| # pylint: disable=bad-continuation | ||||
| 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 | ||||
|     """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) | ||||
|     if isinstance(obj, bool): | ||||
|         return BoolLiteral(obj) | ||||
|     if isinstance(obj, string_types): | ||||
|     if isinstance(obj, str): | ||||
|         return StringLiteral(obj) | ||||
|     if isinstance(obj, HexInt): | ||||
|         return HexIntLiteral(obj) | ||||
|     if isinstance(obj, integer_types): | ||||
|     if isinstance(obj, int): | ||||
|         return IntLiteral(obj) | ||||
|     if isinstance(obj, float): | ||||
|         return FloatLiteral(obj) | ||||
| @@ -294,15 +293,15 @@ def safe_exp( | ||||
|     if obj is float: | ||||
|         return float_ | ||||
|     if isinstance(obj, ID): | ||||
|         raise ValueError(u"Object {} is an ID. Did you forget to register the variable?" | ||||
|                          u"".format(obj)) | ||||
|         raise ValueError("Object {} is an ID. Did you forget to register the variable?" | ||||
|                          "".format(obj)) | ||||
|     if inspect.isgenerator(obj): | ||||
|         raise ValueError(u"Object {} is a coroutine. Did you forget to await the expression with " | ||||
|                          u"'yield'?".format(obj)) | ||||
|     raise ValueError(u"Object is not an expression", obj) | ||||
|         raise ValueError("Object {} is a coroutine. Did you forget to await the expression with " | ||||
|                          "'yield'?".format(obj)) | ||||
|     raise ValueError("Object is not an expression", obj) | ||||
|  | ||||
|  | ||||
| class Statement(object): | ||||
| class Statement: | ||||
|     def __init__(self): | ||||
|         pass | ||||
|  | ||||
| @@ -312,7 +311,7 @@ class Statement(object): | ||||
|  | ||||
| class RawStatement(Statement): | ||||
|     def __init__(self, text): | ||||
|         super(RawStatement, self).__init__() | ||||
|         super().__init__() | ||||
|         self.text = text | ||||
|  | ||||
|     def __str__(self): | ||||
| @@ -321,38 +320,38 @@ class RawStatement(Statement): | ||||
|  | ||||
| class ExpressionStatement(Statement): | ||||
|     def __init__(self, expression): | ||||
|         super(ExpressionStatement, self).__init__() | ||||
|         super().__init__() | ||||
|         self.expression = safe_exp(expression) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return u"{};".format(self.expression) | ||||
|         return f"{self.expression};" | ||||
|  | ||||
|  | ||||
| class LineComment(Statement): | ||||
|     def __init__(self, value):  # type: (unicode) -> None | ||||
|         super(LineComment, self).__init__() | ||||
|     def __init__(self, value):  # type: (str) -> None | ||||
|         super().__init__() | ||||
|         self._value = value | ||||
|  | ||||
|     def __str__(self): | ||||
|         parts = self._value.split(u'\n') | ||||
|         parts = [u'// {}'.format(x) for x in parts] | ||||
|         return u'\n'.join(parts) | ||||
|         parts = self._value.split('\n') | ||||
|         parts = [f'// {x}' for x in parts] | ||||
|         return '\n'.join(parts) | ||||
|  | ||||
|  | ||||
| class ProgmemAssignmentExpression(AssignmentExpression): | ||||
|     def __init__(self, type, name, rhs, obj): | ||||
|         super(ProgmemAssignmentExpression, self).__init__( | ||||
|         super().__init__( | ||||
|             type, '', name, rhs, obj | ||||
|         ) | ||||
|  | ||||
|     def __str__(self): | ||||
|         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): | ||||
|     rhs = safe_exp(rhs) | ||||
|     obj = MockObj(id, u'.') | ||||
|     obj = MockObj(id, '.') | ||||
|     assignment = ProgmemAssignmentExpression(id.type, id, rhs, obj) | ||||
|     CORE.add(assignment) | ||||
|     CORE.register_variable(id, obj) | ||||
| @@ -381,7 +380,7 @@ def variable(id,  # type: ID | ||||
|     """ | ||||
|     assert isinstance(id, ID) | ||||
|     rhs = safe_exp(rhs) | ||||
|     obj = MockObj(id, u'.') | ||||
|     obj = MockObj(id, '.') | ||||
|     if type is not None: | ||||
|         id.type = type | ||||
|     assignment = AssignmentExpression(id.type, '', id, rhs, obj) | ||||
| @@ -405,7 +404,7 @@ def Pvariable(id,  # type: ID | ||||
|     :returns The new variable as a MockObj. | ||||
|     """ | ||||
|     rhs = safe_exp(rhs) | ||||
|     obj = MockObj(id, u'->') | ||||
|     obj = MockObj(id, '->') | ||||
|     if type is not None: | ||||
|         id.type = type | ||||
|     decl = VariableDeclarationExpression(id.type, '*', id) | ||||
| @@ -594,51 +593,51 @@ class MockObj(Expression): | ||||
|  | ||||
|     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.op = op | ||||
|         super(MockObj, self).__init__() | ||||
|         super().__init__() | ||||
|  | ||||
|     def __getattr__(self, attr):  # type: (str) -> MockObj | ||||
|         next_op = u'.' | ||||
|         if attr.startswith(u'P') and self.op not in ['::', '']: | ||||
|         next_op = '.' | ||||
|         if attr.startswith('P') and self.op not in ['::', '']: | ||||
|             attr = attr[1:] | ||||
|             next_op = u'->' | ||||
|         if attr.startswith(u'_'): | ||||
|             next_op = '->' | ||||
|         if attr.startswith('_'): | ||||
|             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 | ||||
|         call = CallExpression(self.base, *args) | ||||
|         return MockObj(call, self.op) | ||||
|  | ||||
|     def __str__(self):  # type: () -> unicode | ||||
|         return text_type(self.base) | ||||
|     def __str__(self):  # type: () -> str | ||||
|         return str(self.base) | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return u'MockObj<{}>'.format(text_type(self.base)) | ||||
|         return 'MockObj<{}>'.format(str(self.base)) | ||||
|  | ||||
|     @property | ||||
|     def _(self):  # type: () -> MockObj | ||||
|         return MockObj(u'{}{}'.format(self.base, self.op)) | ||||
|         return MockObj(f'{self.base}{self.op}') | ||||
|  | ||||
|     @property | ||||
|     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 | ||||
|         if len(args) != 1 or not isinstance(args[0], TemplateArguments): | ||||
|             args = TemplateArguments(*args) | ||||
|         else: | ||||
|             args = args[0] | ||||
|         return MockObj(u'{}{}'.format(self.base, args)) | ||||
|         return MockObj(f'{self.base}{args}') | ||||
|  | ||||
|     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 | ||||
|         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 | ||||
|         return self.class_(name) | ||||
| @@ -648,24 +647,24 @@ class MockObj(Expression): | ||||
|  | ||||
|     def operator(self, name):  # type: (str) -> MockObj | ||||
|         if name == 'ref': | ||||
|             return MockObj(u'{} &'.format(self.base), u'') | ||||
|             return MockObj(f'{self.base} &', '') | ||||
|         if name == 'ptr': | ||||
|             return MockObj(u'{} *'.format(self.base), u'') | ||||
|             return MockObj(f'{self.base} *', '') | ||||
|         if name == "const": | ||||
|             return MockObj(u'const {}'.format(self.base), u'') | ||||
|             return MockObj(f'const {self.base}', '') | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     @property | ||||
|     def using(self):  # type: () -> MockObj | ||||
|         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 | ||||
|         next_op = u'.' | ||||
|         if isinstance(item, str) and item.startswith(u'P'): | ||||
|         next_op = '.' | ||||
|         if isinstance(item, str) and item.startswith('P'): | ||||
|             item = item[1:] | ||||
|             next_op = u'->' | ||||
|         return MockObj(u'{}[{}]'.format(self.base, item), next_op) | ||||
|             next_op = '->' | ||||
|         return MockObj(f'{self.base}[{item}]', next_op) | ||||
|  | ||||
|  | ||||
| class MockObjEnum(MockObj): | ||||
| @@ -679,13 +678,13 @@ class MockObjEnum(MockObj): | ||||
|         kwargs['base'] = base | ||||
|         MockObj.__init__(self, *args, **kwargs) | ||||
|  | ||||
|     def __str__(self):  # type: () -> unicode | ||||
|     def __str__(self):  # type: () -> str | ||||
|         if self._is_class: | ||||
|             return super(MockObjEnum, self).__str__() | ||||
|         return u'{}{}{}'.format(self.base, self.op, self._enum) | ||||
|             return super().__str__() | ||||
|         return f'{self.base}{self.op}{self._enum}' | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return u'MockObj<{}>'.format(text_type(self.base)) | ||||
|         return 'MockObj<{}>'.format(str(self.base)) | ||||
|  | ||||
|  | ||||
| class MockObjClass(MockObj): | ||||
| @@ -716,7 +715,7 @@ class MockObjClass(MockObj): | ||||
|             args = args[0] | ||||
|         new_parents = self._parents[:] | ||||
|         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): | ||||
|         return u'MockObjClass<{}, parents={}>'.format(text_type(self.base), self._parents) | ||||
|         return 'MockObjClass<{}, parents={}>'.format(str(self.base), self._parents) | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| from esphome.const import CONF_INVERTED, CONF_MODE, CONF_NUMBER, CONF_SETUP_PRIORITY, \ | ||||
|     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_types import App, GPIOPin | ||||
| from esphome.py_compat import text_type | ||||
| from esphome.util import Registry, RegistryEntry | ||||
|  | ||||
|  | ||||
| @coroutine | ||||
| @@ -35,11 +36,11 @@ def register_component(var, config): | ||||
|     :param var: The variable representing 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: | ||||
|         raise ValueError(u"Component ID {} was not declared to inherit from Component, " | ||||
|                          u"or was registered twice. Please create a bug report with your " | ||||
|                          u"configuration.".format(id_)) | ||||
|         raise ValueError("Component ID {} was not declared to inherit from Component, " | ||||
|                          "or was registered twice. Please create a bug report with your " | ||||
|                          "configuration.".format(id_)) | ||||
|     CORE.component_ids.remove(id_) | ||||
|     if CONF_SETUP_PRIORITY in config: | ||||
|         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): | ||||
|     # type: ('Registry', 'ConfigType') -> 'RegistryEntry' | ||||
|     # type: (Registry, ConfigType) -> RegistryEntry | ||||
|     key, config = next((k, v) for k, v in full_config.items() if k in registry) | ||||
|     return registry[key], config | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| # pylint: disable=wrong-import-position | ||||
| from __future__ import print_function | ||||
|  | ||||
| import codecs | ||||
| import collections | ||||
| @@ -29,7 +28,6 @@ import tornado.websocket | ||||
| from esphome import const, util | ||||
| from esphome.__main__ import get_serial_ports | ||||
| 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, \ | ||||
|     esphome_storage_path, ext_storage_path, trash_storage_path | ||||
| from esphome.util import shlex_quote | ||||
| @@ -42,7 +40,7 @@ from esphome.zeroconf import DashboardStatus, Zeroconf | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class DashboardSettings(object): | ||||
| class DashboardSettings: | ||||
|     def __init__(self): | ||||
|         self.config_dir = '' | ||||
|         self.password_digest = '' | ||||
| @@ -58,10 +56,7 @@ class DashboardSettings(object): | ||||
|             self.username = args.username or os.getenv('USERNAME', '') | ||||
|             self.using_password = bool(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] | ||||
|  | ||||
|     @property | ||||
| @@ -88,8 +83,8 @@ class DashboardSettings(object): | ||||
|         if username != self.username: | ||||
|             return False | ||||
|  | ||||
|         password_digest = hmac.new(encode_text(password)).digest() | ||||
|         return hmac.compare_digest(self.password_digest, password_digest) | ||||
|         password = hmac.new(password.encode()).digest() | ||||
|         return username == self.username and hmac.compare_digest(self.password_digest, password) | ||||
|  | ||||
|     def rel_path(self, *args): | ||||
|         return os.path.join(self.config_dir, *args) | ||||
| @@ -100,10 +95,7 @@ class DashboardSettings(object): | ||||
|  | ||||
| settings = DashboardSettings() | ||||
|  | ||||
| if IS_PY2: | ||||
|     cookie_authenticated_yes = 'yes' | ||||
| else: | ||||
|     cookie_authenticated_yes = b'yes' | ||||
| cookie_authenticated_yes = b'yes' | ||||
|  | ||||
|  | ||||
| def template_args(): | ||||
| @@ -181,7 +173,7 @@ def websocket_method(name): | ||||
| @websocket_class | ||||
| class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler): | ||||
|     def __init__(self, application, request, **kwargs): | ||||
|         super(EsphomeCommandWebSocket, self).__init__(application, request, **kwargs) | ||||
|         super().__init__(application, request, **kwargs) | ||||
|         self._proc = None | ||||
|         self._is_closed = False | ||||
|  | ||||
| @@ -204,7 +196,7 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler): | ||||
|             # spawn can only be called once | ||||
|             return | ||||
|         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, | ||||
|                                                 stdout=tornado.process.Subprocess.STREAM, | ||||
|                                                 stderr=subprocess.STDOUT, | ||||
| @@ -227,10 +219,7 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler): | ||||
|  | ||||
|     @tornado.gen.coroutine | ||||
|     def _redirect_stdout(self): | ||||
|         if IS_PY2: | ||||
|             reg = '[\n\r]' | ||||
|         else: | ||||
|             reg = b'[\n\r]' | ||||
|         reg = b'[\n\r]' | ||||
|  | ||||
|         while True: | ||||
|             try: | ||||
| @@ -336,8 +325,8 @@ class WizardRequestHandler(BaseHandler): | ||||
|     def post(self): | ||||
|         from esphome import wizard | ||||
|  | ||||
|         kwargs = {k: u''.join(decode_text(x) for x in v) for k, v in self.request.arguments.items()} | ||||
|         destination = settings.rel_path(kwargs['name'] + u'.yaml') | ||||
|         kwargs = {k: ''.join(str(x) for x in v) for k, v in self.request.arguments.items()} | ||||
|         destination = settings.rel_path(kwargs['name'] + '.yaml') | ||||
|         wizard.wizard_write(path=destination, **kwargs) | ||||
|         self.redirect('./?begin=True') | ||||
|  | ||||
| @@ -355,8 +344,8 @@ class DownloadBinaryRequestHandler(BaseHandler): | ||||
|  | ||||
|         path = storage_json.firmware_bin_path | ||||
|         self.set_header('Content-Type', 'application/octet-stream') | ||||
|         filename = '{}.bin'.format(storage_json.name) | ||||
|         self.set_header("Content-Disposition", 'attachment; filename="{}"'.format(filename)) | ||||
|         filename = f'{storage_json.name}.bin' | ||||
|         self.set_header("Content-Disposition", f'attachment; filename="{filename}"') | ||||
|         with open(path, 'rb') as f: | ||||
|             while True: | ||||
|                 data = f.read(16384) | ||||
| @@ -371,7 +360,7 @@ def _list_dashboard_entries(): | ||||
|     return [DashboardEntry(file) for file in files] | ||||
|  | ||||
|  | ||||
| class DashboardEntry(object): | ||||
| class DashboardEntry: | ||||
|     def __init__(self, path): | ||||
|         self.path = path | ||||
|         self._storage = None | ||||
| @@ -609,8 +598,8 @@ class LoginHandler(BaseHandler): | ||||
|             'X-HASSIO-KEY': os.getenv('HASSIO_TOKEN'), | ||||
|         } | ||||
|         data = { | ||||
|             'username': decode_text(self.get_argument('username', '')), | ||||
|             'password': decode_text(self.get_argument('password', '')) | ||||
|             'username': self.get_argument('username', ''), | ||||
|             'password': self.get_argument('password', '') | ||||
|         } | ||||
|         try: | ||||
|             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") | ||||
|  | ||||
|     def post_native_login(self): | ||||
|         username = decode_text(self.get_argument("username", '')) | ||||
|         password = decode_text(self.get_argument("password", '')) | ||||
|         username = self.get_argument("username", '') | ||||
|         password = self.get_argument("password", '') | ||||
|         if settings.check_password(username, password): | ||||
|             self.set_secure_cookie("authenticated", cookie_authenticated_yes) | ||||
|             self.redirect("/") | ||||
| @@ -663,7 +652,7 @@ def get_static_file_url(name): | ||||
|         with open(path, 'rb') as f_handle: | ||||
|             hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8] | ||||
|         _STATIC_FILE_HASHES[name] = hash_ | ||||
|     return u'./static/{}?hash={}'.format(name, hash_) | ||||
|     return f'./static/{name}?hash={hash_}' | ||||
|  | ||||
|  | ||||
| def make_app(debug=False): | ||||
| @@ -754,7 +743,7 @@ def start_web_server(args): | ||||
|         if args.open_ui: | ||||
|             import webbrowser | ||||
|  | ||||
|             webbrowser.open('localhost:{}'.format(args.port)) | ||||
|             webbrowser.open(f'localhost:{args.port}') | ||||
|  | ||||
|     if settings.status_use_ping: | ||||
|         status_thread = PingStatusThread() | ||||
|   | ||||
| @@ -7,7 +7,6 @@ import time | ||||
|  | ||||
| from esphome.core import EsphomeError | ||||
| from esphome.helpers import is_ip_address, resolve_ip_address | ||||
| from esphome.py_compat import IS_PY2, char_to_byte | ||||
|  | ||||
| RESPONSE_OK = 0 | ||||
| RESPONSE_REQUEST_AUTH = 1 | ||||
| @@ -38,7 +37,7 @@ MAGIC_BYTES = [0x6C, 0x26, 0xF7, 0x5C, 0x45] | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class ProgressBar(object): | ||||
| class ProgressBar: | ||||
|     def __init__(self): | ||||
|         self.last_progress = None | ||||
|  | ||||
| @@ -72,33 +71,31 @@ def recv_decode(sock, amount, decode=True): | ||||
|     data = sock.recv(amount) | ||||
|     if not decode: | ||||
|         return data | ||||
|     return [char_to_byte(x) for x in data] | ||||
|     return list(data) | ||||
|  | ||||
|  | ||||
| def receive_exactly(sock, amount, msg, expect, decode=True): | ||||
|     if decode: | ||||
|         data = [] | ||||
|     elif IS_PY2: | ||||
|         data = '' | ||||
|     else: | ||||
|         data = b'' | ||||
|  | ||||
|     try: | ||||
|         data += recv_decode(sock, 1, decode=decode) | ||||
|     except socket.error as err: | ||||
|         raise OTAError("Error receiving acknowledge {}: {}".format(msg, err)) | ||||
|     except OSError as err: | ||||
|         raise OTAError(f"Error receiving acknowledge {msg}: {err}") | ||||
|  | ||||
|     try: | ||||
|         check_error(data, expect) | ||||
|     except OTAError as err: | ||||
|         sock.close() | ||||
|         raise OTAError("Error {}: {}".format(msg, err)) | ||||
|         raise OTAError(f"Error {msg}: {err}") | ||||
|  | ||||
|     while len(data) < amount: | ||||
|         try: | ||||
|             data += recv_decode(sock, amount - len(data), decode=decode) | ||||
|         except socket.error as err: | ||||
|             raise OTAError("Error receiving {}: {}".format(msg, err)) | ||||
|         except OSError as err: | ||||
|             raise OTAError(f"Error receiving {msg}: {err}") | ||||
|     return data | ||||
|  | ||||
|  | ||||
| @@ -145,22 +142,16 @@ def check_error(data, expect): | ||||
|  | ||||
| def send_check(sock, data, msg): | ||||
|     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)): | ||||
|                 data = bytes(data) | ||||
|             elif isinstance(data, int): | ||||
|                 data = bytes([data]) | ||||
|             elif isinstance(data, str): | ||||
|                 data = data.encode('utf8') | ||||
|         if isinstance(data, (list, tuple)): | ||||
|             data = bytes(data) | ||||
|         elif isinstance(data, int): | ||||
|             data = bytes([data]) | ||||
|         elif isinstance(data, str): | ||||
|             data = data.encode('utf8') | ||||
|  | ||||
|         sock.sendall(data) | ||||
|     except socket.error as err: | ||||
|         raise OTAError("Error sending {}: {}".format(msg, err)) | ||||
|     except OSError as err: | ||||
|         raise OTAError(f"Error sending {msg}: {err}") | ||||
|  | ||||
|  | ||||
| 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) | ||||
|     if version != OTA_VERSION_1_0: | ||||
|         raise OTAError("Unsupported OTA version {}".format(version)) | ||||
|         raise OTAError(f"Unsupported OTA version {version}") | ||||
|  | ||||
|     # Features | ||||
|     send_check(sock, 0x00, 'features') | ||||
| @@ -186,9 +177,7 @@ def perform_ota(sock, password, file_handle, filename): | ||||
|     if auth == RESPONSE_REQUEST_AUTH: | ||||
|         if not password: | ||||
|             raise OTAError("ESP requests password, but no password given!") | ||||
|         nonce = receive_exactly(sock, 32, 'authentication nonce', [], decode=False) | ||||
|         if not IS_PY2: | ||||
|             nonce = nonce.decode() | ||||
|         nonce = receive_exactly(sock, 32, 'authentication nonce', [], decode=False).decode() | ||||
|         _LOGGER.debug("Auth: Nonce is %s", nonce) | ||||
|         cnonce = hashlib.md5(str(random.random()).encode()).hexdigest() | ||||
|         _LOGGER.debug("Auth: CNonce is %s", cnonce) | ||||
| @@ -235,9 +224,9 @@ def perform_ota(sock, password, file_handle, filename): | ||||
|  | ||||
|         try: | ||||
|             sock.sendall(chunk) | ||||
|         except socket.error as err: | ||||
|         except OSError as err: | ||||
|             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.done() | ||||
| @@ -277,7 +266,7 @@ def run_ota_impl_(remote_host, remote_port, password, filename): | ||||
|     sock.settimeout(10.0) | ||||
|     try: | ||||
|         sock.connect((ip, remote_port)) | ||||
|     except socket.error as err: | ||||
|     except OSError as err: | ||||
|         sock.close() | ||||
|         _LOGGER.error("Connecting to %s:%s failed: %s", remote_host, remote_port, err) | ||||
|         return 1 | ||||
|   | ||||
| @@ -1,12 +1,8 @@ | ||||
| from __future__ import print_function | ||||
|  | ||||
| import codecs | ||||
|  | ||||
| import logging | ||||
| import os | ||||
|  | ||||
| from esphome.py_compat import char_to_byte, text_type, IS_PY2, encode_text | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| @@ -18,24 +14,24 @@ def ensure_unique_string(preferred_string, current_strings): | ||||
|  | ||||
|     while test_string in current_strings_set: | ||||
|         tries += 1 | ||||
|         test_string = u"{}_{}".format(preferred_string, tries) | ||||
|         test_string = f"{preferred_string}_{tries}" | ||||
|  | ||||
|     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) | ||||
|     if len(lines) <= 2: | ||||
|         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()] | ||||
|  | ||||
|  | ||||
| def indent(text, padding=u'  '): | ||||
|     return u'\n'.join(indent_list(text, padding)) | ||||
| def indent(text, padding='  '): | ||||
|     return '\n'.join(indent_list(text, padding)) | ||||
|  | ||||
|  | ||||
| # 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 | ||||
|         if not 32 <= byte < 127: | ||||
|             return True | ||||
|         if byte in (char_to_byte('\\'), char_to_byte('"')): | ||||
|         if byte in (ord('\\'), ord('"')): | ||||
|             return True | ||||
|         return False | ||||
|  | ||||
|     if isinstance(string, text_type): | ||||
|     if isinstance(string, str): | ||||
|         string = string.encode(encoding) | ||||
|     result = '' | ||||
|     for character in string: | ||||
|         character = char_to_byte(character) | ||||
|         if _should_escape(character): | ||||
|             result += '\\%03o' % character | ||||
|             result += f'\\{character:03o}' | ||||
|         else: | ||||
|             result += chr(character) | ||||
|     return '"' + result + '"' | ||||
| @@ -91,7 +86,7 @@ def mkdir_p(path): | ||||
|             pass | ||||
|         else: | ||||
|             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): | ||||
| @@ -118,7 +113,7 @@ def _resolve_with_zeroconf(host): | ||||
|     try: | ||||
|         info = zc.resolve_host(host + '.') | ||||
|     except Exception as err: | ||||
|         raise EsphomeError("Error resolving mDNS hostname: {}".format(err)) | ||||
|         raise EsphomeError(f"Error resolving mDNS hostname: {err}") | ||||
|     finally: | ||||
|         zc.close() | ||||
|     if info is None: | ||||
| @@ -141,7 +136,7 @@ def resolve_ip_address(host): | ||||
|  | ||||
|     try: | ||||
|         return socket.gethostbyname(host) | ||||
|     except socket.error as err: | ||||
|     except OSError as err: | ||||
|         errs.append(str(err)) | ||||
|         raise EsphomeError("Error resolving IP address: {}" | ||||
|                            "".format(', '.join(errs))) | ||||
| @@ -167,10 +162,10 @@ def read_file(path): | ||||
|             return f_handle.read() | ||||
|     except OSError as err: | ||||
|         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: | ||||
|         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): | ||||
| @@ -179,20 +174,17 @@ def _write_file(path, text): | ||||
|     mkdir_p(directory) | ||||
|  | ||||
|     tmp_path = None | ||||
|     data = encode_text(text) | ||||
|     data = text | ||||
|     if isinstance(text, str): | ||||
|         data = text.encode() | ||||
|     try: | ||||
|         with tempfile.NamedTemporaryFile(mode="wb", dir=directory, delete=False) as f_handle: | ||||
|             tmp_path = f_handle.name | ||||
|             f_handle.write(data) | ||||
|         # Newer tempfile implementations create the file with mode 0o600 | ||||
|         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 | ||||
|             os.replace(tmp_path, path) | ||||
|         # If destination exists, will be overwritten | ||||
|         os.replace(tmp_path, path) | ||||
|     finally: | ||||
|         if tmp_path is not None and os.path.exists(tmp_path): | ||||
|             try: | ||||
| @@ -206,7 +198,7 @@ def write_file(path, text): | ||||
|         _write_file(path, text) | ||||
|     except OSError: | ||||
|         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): | ||||
| @@ -226,7 +218,7 @@ def copy_file_if_changed(src, dst): | ||||
|         shutil.copy(src, dst) | ||||
|     except OSError as err: | ||||
|         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): | ||||
| @@ -273,10 +265,6 @@ _TYPE_OVERLOADS = { | ||||
|     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 | ||||
| _CLASS_LOOKUP = {} | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| from __future__ import print_function | ||||
| import sys | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,9 +1,6 @@ | ||||
| from __future__ import print_function | ||||
|  | ||||
| from datetime import datetime | ||||
| import hashlib | ||||
| import logging | ||||
| import socket | ||||
| import ssl | ||||
| import sys | ||||
| import time | ||||
| @@ -15,7 +12,6 @@ from esphome.const import CONF_BROKER, CONF_DISCOVERY_PREFIX, CONF_ESPHOME, \ | ||||
|     CONF_TOPIC, CONF_TOPIC_PREFIX, CONF_USERNAME | ||||
| from esphome.core import CORE, EsphomeError | ||||
| from esphome.helpers import color | ||||
| from esphome.py_compat import decode_text | ||||
| from esphome.util import safe_print | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
| @@ -37,7 +33,7 @@ def initialize(config, subscriptions, on_message, username, password, client_id) | ||||
|                 if client.reconnect() == 0: | ||||
|                     _LOGGER.info("Successfully reconnected to the MQTT server") | ||||
|                     break | ||||
|             except socket.error: | ||||
|             except OSError: | ||||
|                 pass | ||||
|  | ||||
|             wait_time = min(2**tries, 300) | ||||
| @@ -47,7 +43,7 @@ def initialize(config, subscriptions, on_message, username, password, client_id) | ||||
|             time.sleep(wait_time) | ||||
|             tries += 1 | ||||
|  | ||||
|     client = mqtt.Client(client_id or u'') | ||||
|     client = mqtt.Client(client_id or '') | ||||
|     client.on_connect = on_connect | ||||
|     client.on_message = on_message | ||||
|     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]) | ||||
|         port = int(config[CONF_MQTT][CONF_PORT]) | ||||
|         client.connect(host, port) | ||||
|     except socket.error as err: | ||||
|         raise EsphomeError("Cannot connect to MQTT broker: {}".format(err)) | ||||
|     except OSError as err: | ||||
|         raise EsphomeError(f"Cannot connect to MQTT broker: {err}") | ||||
|  | ||||
|     try: | ||||
|         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: | ||||
|             topic = config[CONF_MQTT][CONF_LOG_TOPIC][CONF_TOPIC] | ||||
|         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: | ||||
|             topic = config[CONF_ESPHOME][CONF_NAME] + u'/debug' | ||||
|             topic = config[CONF_ESPHOME][CONF_NAME] + '/debug' | ||||
|     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 | ||||
|     _LOGGER.info(u"Starting log output from %s", topic) | ||||
|     _LOGGER.info("Starting log output from %s", topic) | ||||
|  | ||||
|     def on_message(client, userdata, msg): | ||||
|         time_ = datetime.now().time().strftime(u'[%H:%M:%S]') | ||||
|         payload = decode_text(msg.payload) | ||||
|         time_ = datetime.now().time().strftime('[%H:%M:%S]') | ||||
|         payload = msg.payload.decode(errors='backslashreplace') | ||||
|         message = time_ + payload | ||||
|         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): | ||||
|     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] | ||||
|         topic = u'{}/+/{}/#'.format(discovery_prefix, name) | ||||
|     _LOGGER.info(u"Clearing messages from '%s'", topic) | ||||
|     _LOGGER.info(u"Please close this window when no more messages appear and the " | ||||
|                  u"MQTT topic has been cleared of retained messages.") | ||||
|         topic = f'{discovery_prefix}/+/{name}/#' | ||||
|     _LOGGER.info("Clearing messages from '%s'", topic) | ||||
|     _LOGGER.info("Please close this window when no more messages appear and the " | ||||
|                  "MQTT topic has been cleared of retained messages.") | ||||
|  | ||||
|     def on_message(client, userdata, msg): | ||||
|         if not msg.payload or not msg.retain: | ||||
|             return | ||||
|         try: | ||||
|             print(u"Clearing topic {}".format(msg.topic)) | ||||
|             print(f"Clearing topic {msg.topic}") | ||||
|         except UnicodeDecodeError: | ||||
|             print(u"Skipping non-UTF-8 topic (prohibited by MQTT standard)") | ||||
|             print("Skipping non-UTF-8 topic (prohibited by MQTT standard)") | ||||
|             return | ||||
|         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]) | ||||
|     try: | ||||
|         cert_pem = ssl.get_server_certificate(addr) | ||||
|     except IOError as err: | ||||
|     except OSError as err: | ||||
|         _LOGGER.error("Unable to connect to server: %s", err) | ||||
|         return 1 | ||||
|     cert_der = ssl.PEM_cert_to_DER_cert(cert_pem) | ||||
|  | ||||
|     sha1 = hashlib.sha1(cert_der).hexdigest() | ||||
|  | ||||
|     safe_print(u"SHA1 Fingerprint: " + color('cyan', sha1)) | ||||
|     safe_print(u"Copy the string above into mqtt.ssl_fingerprints section of {}" | ||||
|                u"".format(CORE.config_path)) | ||||
|     safe_print("SHA1 Fingerprint: " + color('cyan', sha1)) | ||||
|     safe_print("Copy the string above into mqtt.ssl_fingerprints section of {}" | ||||
|                "".format(CORE.config_path)) | ||||
|     return 0 | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| from __future__ import division | ||||
|  | ||||
| import logging | ||||
|  | ||||
| import esphome.config_validation as cv | ||||
| @@ -271,13 +269,13 @@ def _lookup_pin(value): | ||||
|         return board_pins[value] | ||||
|     if value in base_pins: | ||||
|         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): | ||||
|     if isinstance(value, dict) or value is None: | ||||
|         raise cv.Invalid(u"This variable only supports pin numbers, not full pin schemas " | ||||
|                          u"(with inverted and mode).") | ||||
|         raise cv.Invalid("This variable only supports pin numbers, not full pin schemas " | ||||
|                          "(with inverted and mode).") | ||||
|     if isinstance(value, int): | ||||
|         return value | ||||
|     try: | ||||
| @@ -301,27 +299,27 @@ def validate_gpio_pin(value): | ||||
|     value = _translate_pin(value) | ||||
|     if CORE.is_esp32: | ||||
|         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: | ||||
|             raise cv.Invalid("This pin cannot be used on ESP32s and is already used by " | ||||
|                              "the flash interface (function: {})".format(_ESP_SDIO_PINS[value])) | ||||
|         if 9 <= value <= 10: | ||||
|             _LOGGER.warning(u"ESP32: Pin %s (9-10) might already be used by the " | ||||
|                             u"flash interface in QUAD IO flash mode.", value) | ||||
|             _LOGGER.warning("ESP32: Pin %s (9-10) might already be used by the " | ||||
|                             "flash interface in QUAD IO flash mode.", value) | ||||
|         if value in (20, 24, 28, 29, 30, 31): | ||||
|             # These pins are not exposed in GPIO mux (reason unknown) | ||||
|             # 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 | ||||
|     if CORE.is_esp8266: | ||||
|         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: | ||||
|             raise cv.Invalid("This pin cannot be used on ESP8266s and is already used by " | ||||
|                              "the flash interface (function: {})".format(_ESP_SDIO_PINS[value])) | ||||
|         if 9 <= value <= 10: | ||||
|             _LOGGER.warning(u"ESP8266: Pin %s (9-10) might already be used by the " | ||||
|                             u"flash interface in QUAD IO flash mode.", value) | ||||
|             _LOGGER.warning("ESP8266: Pin %s (9-10) might already be used by the " | ||||
|                             "flash interface in QUAD IO flash mode.", value) | ||||
|         return value | ||||
|     raise NotImplementedError | ||||
|  | ||||
| @@ -349,8 +347,8 @@ def output_pin(value): | ||||
|     value = validate_gpio_pin(value) | ||||
|     if CORE.is_esp32: | ||||
|         if 34 <= value <= 39: | ||||
|             raise cv.Invalid(u"ESP32: GPIO{} (34-39) can only be used as an " | ||||
|                              u"input pin.".format(value)) | ||||
|             raise cv.Invalid("ESP32: GPIO{} (34-39) can only be used as an " | ||||
|                              "input pin.".format(value)) | ||||
|         return value | ||||
|     if CORE.is_esp8266: | ||||
|         if value == 17: | ||||
| @@ -364,11 +362,11 @@ def analog_pin(value): | ||||
|     if CORE.is_esp32: | ||||
|         if 32 <= value <= 39:  # ADC1 | ||||
|             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 value == 17:  # A0 | ||||
|             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 | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| from __future__ import print_function | ||||
|  | ||||
| import json | ||||
| import logging | ||||
| import os | ||||
| @@ -7,7 +5,6 @@ import re | ||||
| import subprocess | ||||
|  | ||||
| from esphome.core import CORE | ||||
| from esphome.py_compat import decode_text | ||||
| from esphome.util import run_external_command, run_external_process | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
| @@ -61,6 +58,7 @@ FILTER_PLATFORMIO_LINES = [ | ||||
|     r'Installing dependencies', | ||||
|     r'.* @ .* is already installed', | ||||
|     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): | ||||
|     args = ['-t', 'idedata'] | ||||
|     stdout = run_platformio_cli_run(config, False, *args, capture_stdout=True) | ||||
|     stdout = decode_text(stdout) | ||||
|     stdout = run_platformio_cli_run(config, False, *args, capture_stdout=True).decode() | ||||
|     match = re.search(r'{\s*".*}', stdout) | ||||
|     if match is None: | ||||
|         _LOGGER.debug("Could not match IDEData for %s", stdout) | ||||
| @@ -172,7 +169,7 @@ def _decode_pc(config, addr): | ||||
|         return | ||||
|     command = [idedata.addr2line_path, '-pfiaC', '-e', idedata.firmware_elf_path, addr] | ||||
|     try: | ||||
|         translation = decode_text(subprocess.check_output(command)).strip() | ||||
|         translation = subprocess.check_output(command).decode().strip() | ||||
|     except Exception:  # pylint: disable=broad-except | ||||
|         _LOGGER.debug("Caught exception for command %s", command, exc_info=1) | ||||
|         return | ||||
| @@ -246,7 +243,7 @@ def process_stacktrace(config, line, backtrace_state): | ||||
|     return backtrace_state | ||||
|  | ||||
|  | ||||
| class IDEData(object): | ||||
| class IDEData: | ||||
|     def __init__(self, raw): | ||||
|         if not isinstance(raw, dict): | ||||
|             self.raw = {} | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -13,17 +13,15 @@ from esphome.helpers import write_file_if_changed | ||||
| from esphome.core import CoreType | ||||
| from typing import Any, Optional, List | ||||
|  | ||||
| from esphome.py_compat import text_type | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| 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 | ||||
|     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 | ||||
| @@ -35,7 +33,7 @@ def trash_storage_path(base_path):  # type: (str) -> str | ||||
|  | ||||
|  | ||||
| # pylint: disable=too-many-instance-attributes | ||||
| class StorageJSON(object): | ||||
| class StorageJSON: | ||||
|     def __init__(self, storage_version, name, comment, esphome_version, | ||||
|                  src_version, arduino_version, address, esp_platform, board, build_path, | ||||
|                  firmware_bin_path, loaded_integrations): | ||||
| @@ -85,7 +83,7 @@ class StorageJSON(object): | ||||
|         } | ||||
|  | ||||
|     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): | ||||
|         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() | ||||
|  | ||||
|  | ||||
| class EsphomeStorageJSON(object): | ||||
| class EsphomeStorageJSON: | ||||
|     def __init__(self, storage_version, cookie_secret, last_update_check, | ||||
|                  remote_version): | ||||
|         # 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") | ||||
|  | ||||
|     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 | ||||
|         write_file_if_changed(path, self.to_json()) | ||||
| @@ -216,7 +214,7 @@ class EsphomeStorageJSON(object): | ||||
|     def get_default():  # type: () -> EsphomeStorageJSON | ||||
|         return EsphomeStorageJSON( | ||||
|             storage_version=1, | ||||
|             cookie_secret=text_type(binascii.hexlify(os.urandom(64))), | ||||
|             cookie_secret=binascii.hexlify(os.urandom(64)).decode(), | ||||
|             last_update_check=None, | ||||
|             remote_version=None, | ||||
|         ) | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| from __future__ import print_function | ||||
|  | ||||
| import collections | ||||
| import io | ||||
| import logging | ||||
| @@ -9,12 +7,11 @@ import subprocess | ||||
| import sys | ||||
|  | ||||
| from esphome import const | ||||
| from esphome.py_compat import IS_PY2, decode_text, text_type | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class RegistryEntry(object): | ||||
| class RegistryEntry: | ||||
|     def __init__(self, name, fun, type_id, schema): | ||||
|         self.name = name | ||||
|         self.fun = fun | ||||
| @@ -34,7 +31,7 @@ class RegistryEntry(object): | ||||
|  | ||||
| class Registry(dict): | ||||
|     def __init__(self, base_schema=None, type_id_key=None): | ||||
|         super(Registry, self).__init__() | ||||
|         super().__init__() | ||||
|         self.base_schema = base_schema or {} | ||||
|         self.type_id_key = type_id_key | ||||
|  | ||||
| @@ -81,17 +78,17 @@ def safe_print(message=""): | ||||
|  | ||||
| def shlex_quote(s): | ||||
|     if not s: | ||||
|         return u"''" | ||||
|         return "''" | ||||
|     if re.search(r'[^\w@%+=:,./-]', s) is None: | ||||
|         return s | ||||
|  | ||||
|     return u"'" + s.replace(u"'", u"'\"'\"'") + u"'" | ||||
|     return "'" + s.replace("'", "'\"'\"'") + "'" | ||||
|  | ||||
|  | ||||
| ANSI_ESCAPE = re.compile(r'\033[@-_][0-?]*[ -/]*[@-~]') | ||||
|  | ||||
|  | ||||
| class RedirectText(object): | ||||
| class RedirectText: | ||||
|     def __init__(self, out, filter_lines=None): | ||||
|         self._out = out | ||||
|         if filter_lines is None: | ||||
| @@ -116,13 +113,12 @@ class RedirectText(object): | ||||
|         self._out.write(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 | ||||
|         # text_type | ||||
|         # str | ||||
|         # If the conversion fails, we will create an exception, which is okay because we won't | ||||
|         # be able to print it anyway. | ||||
|         text = decode_text(s) | ||||
|         assert isinstance(text, text_type) | ||||
|         text = s.decode() | ||||
|  | ||||
|         if self._filter_pattern is not None: | ||||
|             self._line_buffer += text | ||||
| @@ -160,8 +156,8 @@ def run_external_command(func, *cmd, **kwargs): | ||||
|  | ||||
|     orig_argv = sys.argv | ||||
|     orig_exit = sys.exit  # mock sys.exit | ||||
|     full_cmd = u' '.join(shlex_quote(x) for x in cmd) | ||||
|     _LOGGER.info(u"Running:  %s", full_cmd) | ||||
|     full_cmd = ' '.join(shlex_quote(x) for x in cmd) | ||||
|     _LOGGER.info("Running:  %s", full_cmd) | ||||
|  | ||||
|     filter_lines = kwargs.get('filter_lines') | ||||
|     orig_stdout = sys.stdout | ||||
| @@ -182,8 +178,8 @@ def run_external_command(func, *cmd, **kwargs): | ||||
|     except SystemExit as err: | ||||
|         return err.args[0] | ||||
|     except Exception as err:  # pylint: disable=broad-except | ||||
|         _LOGGER.error(u"Running command failed: %s", err) | ||||
|         _LOGGER.error(u"Please try running %s locally.", full_cmd) | ||||
|         _LOGGER.error("Running command failed: %s", err) | ||||
|         _LOGGER.error("Please try running %s locally.", full_cmd) | ||||
|         return 1 | ||||
|     finally: | ||||
|         sys.argv = orig_argv | ||||
| @@ -198,8 +194,8 @@ def run_external_command(func, *cmd, **kwargs): | ||||
|  | ||||
|  | ||||
| def run_external_process(*cmd, **kwargs): | ||||
|     full_cmd = u' '.join(shlex_quote(x) for x in cmd) | ||||
|     _LOGGER.info(u"Running:  %s", full_cmd) | ||||
|     full_cmd = ' '.join(shlex_quote(x) for x in cmd) | ||||
|     _LOGGER.info("Running:  %s", full_cmd) | ||||
|     filter_lines = kwargs.get('filter_lines') | ||||
|  | ||||
|     capture_stdout = kwargs.get('capture_stdout', False) | ||||
| @@ -215,8 +211,8 @@ def run_external_process(*cmd, **kwargs): | ||||
|                                stdout=sub_stdout, | ||||
|                                stderr=sub_stderr) | ||||
|     except Exception as err:  # pylint: disable=broad-except | ||||
|         _LOGGER.error(u"Running command failed: %s", err) | ||||
|         _LOGGER.error(u"Please try running %s locally.", full_cmd) | ||||
|         _LOGGER.error("Running command failed: %s", err) | ||||
|         _LOGGER.error("Please try running %s locally.", full_cmd) | ||||
|         return 1 | ||||
|     finally: | ||||
|         if capture_stdout: | ||||
| @@ -233,29 +229,6 @@ class OrderedDict(collections.OrderedDict): | ||||
|     def __repr__(self): | ||||
|         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): | ||||
|     files = filter_yaml_files([os.path.join(folder, p) for p in os.listdir(folder)]) | ||||
|   | ||||
| @@ -3,8 +3,6 @@ import itertools | ||||
|  | ||||
| import voluptuous as vol | ||||
|  | ||||
| from esphome.py_compat import string_types | ||||
|  | ||||
|  | ||||
| class ExtraKeysInvalid(vol.Invalid): | ||||
|     def __init__(self, *arg, **kwargs): | ||||
| @@ -22,14 +20,14 @@ def ensure_multiple_invalid(err): | ||||
| class _Schema(vol.Schema): | ||||
|     """Custom cv.Schema that prints similar keys on error.""" | ||||
|     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 | ||||
|         # Should be used sparingly, as it's not a very voluptuous-way/clean way of | ||||
|         # doing things. | ||||
|         self._extra_schemas = extra_schemas or [] | ||||
|  | ||||
|     def __call__(self, data): | ||||
|         res = super(_Schema, self).__call__(data) | ||||
|         res = super().__call__(data) | ||||
|         for extra in self._extra_schemas: | ||||
|             try: | ||||
|                 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") | ||||
|  | ||||
|         # 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 | ||||
|         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 | ||||
|         _compiled_schema = {} | ||||
| @@ -84,9 +82,9 @@ class _Schema(vol.Schema): | ||||
|  | ||||
|         key_names = [] | ||||
|         for skey in schema: | ||||
|             if isinstance(skey, string_types): | ||||
|             if isinstance(skey, str): | ||||
|                 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) | ||||
|  | ||||
|         def validate_mapping(path, iterable, out): | ||||
| @@ -156,7 +154,7 @@ class _Schema(vol.Schema): | ||||
|                     if self.extra == vol.ALLOW_EXTRA: | ||||
|                         out[key] = value | ||||
|                     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) | ||||
|                             errors.append(ExtraKeysInvalid('extra keys not allowed', key_path, | ||||
|                                                            candidates=matches)) | ||||
| @@ -195,5 +193,5 @@ class _Schema(vol.Schema): | ||||
|         schema = schemas[0] | ||||
|         if isinstance(schema, vol.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) | ||||
|   | ||||
| @@ -1,19 +1,17 @@ | ||||
| from __future__ import print_function | ||||
|  | ||||
| import json | ||||
| import os | ||||
|  | ||||
| # pylint: disable=unused-import | ||||
| from esphome.config import load_config, _format_vol_invalid, Config | ||||
| 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 | ||||
| import voluptuous as vol | ||||
| from typing import Optional | ||||
|  | ||||
|  | ||||
| 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) | ||||
|  | ||||
|  | ||||
| @@ -30,7 +28,7 @@ def _dump_range(range): | ||||
|     } | ||||
|  | ||||
|  | ||||
| class VSCodeResult(object): | ||||
| class VSCodeResult: | ||||
|     def __init__(self): | ||||
|         self.yaml_errors = [] | ||||
|         self.validation_errors = [] | ||||
| @@ -57,7 +55,7 @@ class VSCodeResult(object): | ||||
| def read_config(args): | ||||
|     while True: | ||||
|         CORE.reset() | ||||
|         data = json.loads(safe_input()) | ||||
|         data = json.loads(input()) | ||||
|         assert data['type'] == 'validate' | ||||
|         CORE.vscode = True | ||||
|         CORE.ace = args.ace | ||||
| @@ -70,7 +68,7 @@ def read_config(args): | ||||
|         try: | ||||
|             res = load_config() | ||||
|         except Exception as err:  # pylint: disable=broad-except | ||||
|             vs.add_yaml_error(text_type(err)) | ||||
|             vs.add_yaml_error(str(err)) | ||||
|         else: | ||||
|             for err in res.errors: | ||||
|                 try: | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| from __future__ import print_function | ||||
|  | ||||
| import os | ||||
| import random | ||||
| import string | ||||
| @@ -11,7 +9,6 @@ import esphome.config_validation as cv | ||||
| from esphome.helpers import color, get_bool_env, write_file | ||||
| # pylint: disable=anomalous-backslash-in-string | ||||
| 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.util import safe_print | ||||
|  | ||||
| @@ -44,7 +41,7 @@ OTA_BIG = r"""       ____ _______ | ||||
|       \____/  |_/_/    \_\\ | ||||
| """ | ||||
|  | ||||
| BASE_CONFIG = u"""esphome: | ||||
| BASE_CONFIG = """esphome: | ||||
|   name: {name} | ||||
|   platform: {platform} | ||||
|   board: {board} | ||||
| @@ -75,7 +72,7 @@ def sanitize_double_quotes(value): | ||||
| def wizard_file(**kwargs): | ||||
|     letters = string.ascii_letters + string.digits | ||||
|     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: | ||||
|         ap_name = ap_name_base | ||||
|     kwargs['fallback_name'] = ap_name | ||||
| @@ -84,9 +81,9 @@ def wizard_file(**kwargs): | ||||
|     config = BASE_CONFIG.format(**kwargs) | ||||
|  | ||||
|     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: | ||||
|         config += u"\nota:\n" | ||||
|         config += "\nota:\n" | ||||
|  | ||||
|     return config | ||||
|  | ||||
| @@ -119,7 +116,7 @@ else: | ||||
| def safe_print_step(step, big): | ||||
|     safe_print() | ||||
|     safe_print() | ||||
|     safe_print("============= STEP {} =============".format(step)) | ||||
|     safe_print(f"============= STEP {step} =============") | ||||
|     safe_print(big) | ||||
|     safe_print("===================================") | ||||
|     sleep(0.25) | ||||
| @@ -127,24 +124,24 @@ def safe_print_step(step, big): | ||||
|  | ||||
| def default_input(text, default): | ||||
|     safe_print() | ||||
|     safe_print(u"Press ENTER for default ({})".format(default)) | ||||
|     return safe_input(text.format(default)) or default | ||||
|     safe_print(f"Press ENTER for default ({default})") | ||||
|     return input(text.format(default)) or default | ||||
|  | ||||
|  | ||||
| # From https://stackoverflow.com/a/518232/8924614 | ||||
| def strip_accents(value): | ||||
|     return u''.join(c for c in unicodedata.normalize('NFD', text_type(value)) | ||||
|                     if unicodedata.category(c) != 'Mn') | ||||
|     return ''.join(c for c in unicodedata.normalize('NFD', str(value)) | ||||
|                    if unicodedata.category(c) != 'Mn') | ||||
|  | ||||
|  | ||||
| def wizard(path): | ||||
|     if not path.endswith('.yaml') and not path.endswith('.yml'): | ||||
|         safe_print(u"Please make your configuration file {} have the extension .yaml or .yml" | ||||
|                    u"".format(color('cyan', path))) | ||||
|         safe_print("Please make your configuration file {} have the extension .yaml or .yml" | ||||
|                    "".format(color('cyan', path))) | ||||
|         return 1 | ||||
|     if os.path.exists(path): | ||||
|         safe_print(u"Uh oh, it seems like {} already exists, please delete that file first " | ||||
|                    u"or chose another configuration file.".format(color('cyan', path))) | ||||
|         safe_print("Uh oh, it seems like {} already exists, please delete that file first " | ||||
|                    "or chose another configuration file.".format(color('cyan', path))) | ||||
|         return 1 | ||||
|     safe_print("Hi there!") | ||||
|     sleep(1.5) | ||||
| @@ -164,21 +161,21 @@ def wizard(path): | ||||
|         color('bold_white', "livingroom"))) | ||||
|     safe_print() | ||||
|     sleep(1) | ||||
|     name = safe_input(color("bold_white", "(name): ")) | ||||
|     name = input(color("bold_white", "(name): ")) | ||||
|     while True: | ||||
|         try: | ||||
|             name = cv.valid_name(name) | ||||
|             break | ||||
|         except vol.Invalid: | ||||
|             safe_print(color("red", u"Oh noes, \"{}\" isn't a valid name. Names can only include " | ||||
|                                     u"numbers, letters and underscores.".format(name))) | ||||
|             safe_print(color("red", "Oh noes, \"{}\" isn't a valid name. Names can only include " | ||||
|                                     "numbers, letters and underscores.".format(name))) | ||||
|             name = strip_accents(name).replace(' ', '_') | ||||
|             name = u''.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))) | ||||
|             name = ''.join(c for c in name if c in cv.ALLOWED_NAME_CHARS) | ||||
|             safe_print("Shall I use \"{}\" as the name instead?".format(color('cyan', name))) | ||||
|             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) | ||||
|     safe_print_step(2, ESP_BIG) | ||||
|     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) | ||||
|         safe_print() | ||||
|         safe_print("Please enter either ESP32 or ESP8266.") | ||||
|         platform = safe_input(color("bold_white", "(ESP32/ESP8266): ")) | ||||
|         platform = input(color("bold_white", "(ESP32/ESP8266): ")) | ||||
|         try: | ||||
|             platform = vol.All(vol.Upper, vol.Any('ESP32', 'ESP8266'))(platform) | ||||
|             break | ||||
|         except vol.Invalid: | ||||
|             safe_print(u"Unfortunately, I can't find an espressif microcontroller called " | ||||
|                        u"\"{}\". Please try again.".format(platform)) | ||||
|     safe_print(u"Thanks! You've chosen {} as your platform.".format(color('cyan', platform))) | ||||
|             safe_print("Unfortunately, I can't find an espressif microcontroller called " | ||||
|                        "\"{}\". Please try again.".format(platform)) | ||||
|     safe_print("Thanks! You've chosen {} as your platform.".format(color('cyan', platform))) | ||||
|     safe_print() | ||||
|     sleep(1) | ||||
|  | ||||
| @@ -221,17 +218,17 @@ def wizard(path): | ||||
|     safe_print("Options: {}".format(', '.join(sorted(boards)))) | ||||
|  | ||||
|     while True: | ||||
|         board = safe_input(color("bold_white", "(board): ")) | ||||
|         board = input(color("bold_white", "(board): ")) | ||||
|         try: | ||||
|             board = vol.All(vol.Lower, vol.Any(*boards))(board) | ||||
|             break | ||||
|         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() | ||||
|             sleep(0.25) | ||||
|             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() | ||||
|     sleep(1) | ||||
|  | ||||
| @@ -241,22 +238,22 @@ def wizard(path): | ||||
|     safe_print() | ||||
|     sleep(1) | ||||
|     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) | ||||
|     safe_print("For example \"{}\".".format(color('bold_white', "Abraham Linksys"))) | ||||
|     while True: | ||||
|         ssid = safe_input(color('bold_white', "(ssid): ")) | ||||
|         ssid = input(color('bold_white', "(ssid): ")) | ||||
|         try: | ||||
|             ssid = cv.ssid(ssid) | ||||
|             break | ||||
|         except vol.Invalid: | ||||
|             safe_print(color('red', u"Unfortunately, \"{}\" doesn't seem to be a valid SSID. " | ||||
|                                     u"Please try again.".format(ssid))) | ||||
|             safe_print(color('red', "Unfortunately, \"{}\" doesn't seem to be a valid SSID. " | ||||
|                                     "Please try again.".format(ssid))) | ||||
|             safe_print() | ||||
|             sleep(1) | ||||
|  | ||||
|     safe_print(u"Thank you very much! You've just chosen \"{}\" as your SSID." | ||||
|                u"".format(color('cyan', ssid))) | ||||
|     safe_print("Thank you very much! You've just chosen \"{}\" as your SSID." | ||||
|                "".format(color('cyan', ssid))) | ||||
|     safe_print() | ||||
|     sleep(0.75) | ||||
|  | ||||
| @@ -265,7 +262,7 @@ def wizard(path): | ||||
|     safe_print() | ||||
|     safe_print("For example \"{}\"".format(color('bold_white', 'PASSWORD42'))) | ||||
|     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).") | ||||
|     sleep(1.5) | ||||
|  | ||||
| @@ -277,7 +274,7 @@ def wizard(path): | ||||
|     safe_print() | ||||
|     sleep(0.25) | ||||
|     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, | ||||
|                  ssid=ssid, psk=psk, password=password) | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| from __future__ import print_function | ||||
|  | ||||
| import logging | ||||
| import os | ||||
| import re | ||||
| @@ -14,19 +12,19 @@ from esphome.storage_json import StorageJSON, storage_path | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
| CPP_AUTO_GENERATE_BEGIN = u'// ========== AUTO GENERATED CODE BEGIN ===========' | ||||
| CPP_AUTO_GENERATE_END = u'// =========== AUTO GENERATED CODE END ============' | ||||
| CPP_INCLUDE_BEGIN = u'// ========== AUTO GENERATED INCLUDE BLOCK BEGIN ===========' | ||||
| CPP_INCLUDE_END = u'// ========== AUTO GENERATED INCLUDE BLOCK END ===========' | ||||
| INI_AUTO_GENERATE_BEGIN = u'; ========== AUTO GENERATED CODE BEGIN ===========' | ||||
| INI_AUTO_GENERATE_END = u'; =========== AUTO GENERATED CODE END ============' | ||||
| CPP_AUTO_GENERATE_BEGIN = '// ========== AUTO GENERATED CODE BEGIN ===========' | ||||
| CPP_AUTO_GENERATE_END = '// =========== AUTO GENERATED CODE END ============' | ||||
| CPP_INCLUDE_BEGIN = '// ========== AUTO GENERATED INCLUDE BLOCK BEGIN ===========' | ||||
| CPP_INCLUDE_END = '// ========== AUTO GENERATED INCLUDE BLOCK END ===========' | ||||
| INI_AUTO_GENERATE_BEGIN = '; ========== AUTO GENERATED CODE BEGIN ===========' | ||||
| INI_AUTO_GENERATE_END = '; =========== AUTO GENERATED CODE END ============' | ||||
|  | ||||
| CPP_BASE_FORMAT = (u"""// Auto generated code by esphome | ||||
| """, u"""" | ||||
| CPP_BASE_FORMAT = ("""// Auto generated code by esphome | ||||
| """, """" | ||||
|  | ||||
| void setup() { | ||||
|   // ===== DO NOT EDIT ANYTHING BELOW THIS LINE ===== | ||||
|   """, u""" | ||||
|   """, """ | ||||
|   // ========= YOU CAN EDIT AFTER THIS LINE ========= | ||||
|   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] | ||||
| lib_deps = | ||||
| @@ -44,7 +42,7 @@ build_flags = | ||||
| upload_flags = | ||||
|  | ||||
| ; ===== DO NOT EDIT ANYTHING BELOW THIS LINE ===== | ||||
| """, u""" | ||||
| """, """ | ||||
| ; ========= YOU CAN EDIT AFTER THIS LINE ========= | ||||
|  | ||||
| """) | ||||
| @@ -62,8 +60,8 @@ def get_flags(key): | ||||
|  | ||||
|  | ||||
| def get_include_text(): | ||||
|     include_text = u'#include "esphome.h"\n' \ | ||||
|                    u'using namespace esphome;\n' | ||||
|     include_text = '#include "esphome.h"\n' \ | ||||
|                    'using namespace esphome;\n' | ||||
|     for _, component, conf in iter_components(CORE.config): | ||||
|         if not hasattr(component, 'includes'): | ||||
|             continue | ||||
| @@ -106,7 +104,7 @@ def migrate_src_version_0_to_1(): | ||||
|  | ||||
|     if CPP_INCLUDE_BEGIN not in content: | ||||
|         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: | ||||
|             _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 " | ||||
| @@ -160,14 +158,14 @@ def update_storage_json(): | ||||
|  | ||||
|  | ||||
| def format_ini(data): | ||||
|     content = u'' | ||||
|     content = '' | ||||
|     for key, value in sorted(data.items()): | ||||
|         if isinstance(value, (list, set, tuple)): | ||||
|             content += u'{} =\n'.format(key) | ||||
|             content += f'{key} =\n' | ||||
|             for x in value: | ||||
|                 content += u'    {}\n'.format(x) | ||||
|                 content += f'    {x}\n' | ||||
|         else: | ||||
|             content += u'{} = {}\n'.format(key, value) | ||||
|             content += f'{key} = {value}\n' | ||||
|     return content | ||||
|  | ||||
|  | ||||
| @@ -216,7 +214,7 @@ def get_ini_content(): | ||||
|     # data['lib_ldf_mode'] = 'chain' | ||||
|     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) | ||||
|  | ||||
|     return content | ||||
| @@ -225,18 +223,18 @@ def get_ini_content(): | ||||
| def find_begin_end(text, begin_s, end_s): | ||||
|     begin_index = text.find(begin_s) | ||||
|     if begin_index == -1: | ||||
|         raise EsphomeError(u"Could not find auto generated code begin in file, either " | ||||
|                            u"delete the main sketch file or insert the comment again.") | ||||
|         raise EsphomeError("Could not find auto generated code begin in file, either " | ||||
|                            "delete the main sketch file or insert the comment again.") | ||||
|     if text.find(begin_s, begin_index + 1) != -1: | ||||
|         raise EsphomeError(u"Found multiple auto generate code begins, don't know " | ||||
|                            u"which to chose, please remove one of them.") | ||||
|         raise EsphomeError("Found multiple auto generate code begins, don't know " | ||||
|                            "which to chose, please remove one of them.") | ||||
|     end_index = text.find(end_s) | ||||
|     if end_index == -1: | ||||
|         raise EsphomeError(u"Could not find auto generated code end in file, either " | ||||
|                            u"delete the main sketch file or insert the comment again.") | ||||
|         raise EsphomeError("Could not find auto generated code end in file, either " | ||||
|                            "delete the main sketch file or insert the comment again.") | ||||
|     if text.find(end_s, end_index + 1) != -1: | ||||
|         raise EsphomeError(u"Found multiple auto generate code endings, don't know " | ||||
|                            u"which to chose, please remove one of them.") | ||||
|         raise EsphomeError("Found multiple auto generate code endings, don't know " | ||||
|                            "which to chose, please remove one of them.") | ||||
|  | ||||
|     return text[:begin_index], text[(end_index + len(end_s)):] | ||||
|  | ||||
| @@ -263,17 +261,17 @@ def write_platformio_project(): | ||||
|     write_platformio_ini(content) | ||||
|  | ||||
|  | ||||
| DEFINES_H_FORMAT = ESPHOME_H_FORMAT = u"""\ | ||||
| DEFINES_H_FORMAT = ESPHOME_H_FORMAT = """\ | ||||
| #pragma once | ||||
| {} | ||||
| """ | ||||
| VERSION_H_FORMAT = u"""\ | ||||
| VERSION_H_FORMAT = """\ | ||||
| #pragma once | ||||
| #define ESPHOME_VERSION "{}" | ||||
| """ | ||||
| DEFINES_H_TARGET = 'esphome/core/defines.h' | ||||
| VERSION_H_TARGET = 'esphome/core/version.h' | ||||
| ESPHOME_README_TXT = u""" | ||||
| ESPHOME_README_TXT = """ | ||||
| THIS DIRECTORY IS AUTO-GENERATED, DO NOT MODIFY | ||||
|  | ||||
| ESPHome automatically populates the esphome/ directory, and any | ||||
| @@ -298,9 +296,9 @@ def copy_src_tree(): | ||||
|     include_l = [] | ||||
|     for target, path in source_files_l: | ||||
|         if os.path.splitext(path)[1] in HEADER_FILE_EXTENSIONS: | ||||
|             include_l.append(u'#include "{}"'.format(target)) | ||||
|     include_l.append(u'') | ||||
|     include_s = u'\n'.join(include_l) | ||||
|             include_l.append(f'#include "{target}"') | ||||
|     include_l.append('') | ||||
|     include_s = '\n'.join(include_l) | ||||
|  | ||||
|     source_files_copy = source_files.copy() | ||||
|     source_files_copy.pop(DEFINES_H_TARGET) | ||||
| @@ -340,7 +338,7 @@ def copy_src_tree(): | ||||
| def generate_defines_h(): | ||||
|     define_content_l = [x.as_macro for x in CORE.defines] | ||||
|     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): | ||||
| @@ -354,11 +352,11 @@ def write_cpp(code_s): | ||||
|         code_format = CPP_BASE_FORMAT | ||||
|  | ||||
|     copy_src_tree() | ||||
|     global_s = u'#include "esphome.h"\n' | ||||
|     global_s = '#include "esphome.h"\n' | ||||
|     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[1] + CPP_AUTO_GENERATE_BEGIN + u'\n' + code_s + CPP_AUTO_GENERATE_END | ||||
|     full_file = code_format[0] + CPP_INCLUDE_BEGIN + '\n' + global_s + CPP_INCLUDE_END | ||||
|     full_file += code_format[1] + CPP_AUTO_GENERATE_BEGIN + '\n' + code_s + CPP_AUTO_GENERATE_END | ||||
|     full_file += code_format[2] | ||||
|     write_file_if_changed(path, full_file) | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| from __future__ import print_function | ||||
|  | ||||
| import fnmatch | ||||
| import functools | ||||
| import inspect | ||||
| @@ -15,7 +13,6 @@ from esphome import core | ||||
| from esphome.config_helpers import read_config_file | ||||
| from esphome.core import EsphomeError, IPAddress, Lambda, MACAddress, TimePeriod, DocumentRange | ||||
| 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 | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
| @@ -23,12 +20,12 @@ _LOGGER = logging.getLogger(__name__) | ||||
| # Mostly copied from Home Assistant because that code works fine and | ||||
| # let's not reinvent the wheel here | ||||
|  | ||||
| SECRET_YAML = u'secrets.yaml' | ||||
| SECRET_YAML = 'secrets.yaml' | ||||
| _SECRET_CACHE = {} | ||||
| _SECRET_VALUES = {} | ||||
|  | ||||
|  | ||||
| class ESPHomeDataBase(object): | ||||
| class ESPHomeDataBase: | ||||
|     @property | ||||
|     def esp_range(self): | ||||
|         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) | ||||
|  | ||||
|  | ||||
| class ESPForceValue(object): | ||||
| class ESPForceValue: | ||||
|     pass | ||||
|  | ||||
|  | ||||
| @@ -74,27 +71,27 @@ class ESPHomeLoader(yaml.SafeLoader):  # pylint: disable=too-many-ancestors | ||||
|  | ||||
|     @_add_data_ref | ||||
|     def construct_yaml_int(self, node): | ||||
|         return super(ESPHomeLoader, self).construct_yaml_int(node) | ||||
|         return super().construct_yaml_int(node) | ||||
|  | ||||
|     @_add_data_ref | ||||
|     def construct_yaml_float(self, node): | ||||
|         return super(ESPHomeLoader, self).construct_yaml_float(node) | ||||
|         return super().construct_yaml_float(node) | ||||
|  | ||||
|     @_add_data_ref | ||||
|     def construct_yaml_binary(self, node): | ||||
|         return super(ESPHomeLoader, self).construct_yaml_binary(node) | ||||
|         return super().construct_yaml_binary(node) | ||||
|  | ||||
|     @_add_data_ref | ||||
|     def construct_yaml_omap(self, node): | ||||
|         return super(ESPHomeLoader, self).construct_yaml_omap(node) | ||||
|         return super().construct_yaml_omap(node) | ||||
|  | ||||
|     @_add_data_ref | ||||
|     def construct_yaml_str(self, node): | ||||
|         return super(ESPHomeLoader, self).construct_yaml_str(node) | ||||
|         return super().construct_yaml_str(node) | ||||
|  | ||||
|     @_add_data_ref | ||||
|     def construct_yaml_seq(self, node): | ||||
|         return super(ESPHomeLoader, self).construct_yaml_seq(node) | ||||
|         return super().construct_yaml_seq(node) | ||||
|  | ||||
|     @_add_data_ref | ||||
|     def construct_yaml_map(self, node): | ||||
| @@ -130,12 +127,12 @@ class ESPHomeLoader(yaml.SafeLoader):  # pylint: disable=too-many-ancestors | ||||
|                     hash(key) | ||||
|                 except TypeError: | ||||
|                     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 | ||||
|                 if key in seen_keys: | ||||
|                     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], | ||||
|                     ) | ||||
|                 seen_keys[key] = key_node.start_mark | ||||
| @@ -194,11 +191,11 @@ class ESPHomeLoader(yaml.SafeLoader):  # pylint: disable=too-many-ancestors | ||||
|         args = node.value.split() | ||||
|         # Check for a default value | ||||
|         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: | ||||
|             return os.environ[args[0]] | ||||
|         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 | ||||
| @@ -213,10 +210,10 @@ class ESPHomeLoader(yaml.SafeLoader):  # pylint: disable=too-many-ancestors | ||||
|         secrets = _load_yaml_internal(self._rel_path(SECRET_YAML)) | ||||
|         if node.value not in secrets: | ||||
|             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] | ||||
|         _SECRET_VALUES[text_type(val)] = node.value | ||||
|         _SECRET_VALUES[str(val)] = node.value | ||||
|         return val | ||||
|  | ||||
|     @_add_data_ref | ||||
| @@ -259,7 +256,7 @@ class ESPHomeLoader(yaml.SafeLoader):  # pylint: disable=too-many-ancestors | ||||
|  | ||||
|     @_add_data_ref | ||||
|     def construct_lambda(self, node): | ||||
|         return Lambda(text_type(node.value)) | ||||
|         return Lambda(str(node.value)) | ||||
|  | ||||
|     @_add_data_ref | ||||
|     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) | ||||
|  | ||||
|  | ||||
| ESPHomeLoader.add_constructor(u'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(u'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(u'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(u'tag:yaml.org,2002:map', ESPHomeLoader.construct_yaml_map) | ||||
| ESPHomeLoader.add_constructor('tag:yaml.org,2002:int', ESPHomeLoader.construct_yaml_int) | ||||
| ESPHomeLoader.add_constructor('tag:yaml.org,2002:float', ESPHomeLoader.construct_yaml_float) | ||||
| ESPHomeLoader.add_constructor('tag:yaml.org,2002:binary', ESPHomeLoader.construct_yaml_binary) | ||||
| ESPHomeLoader.add_constructor('tag:yaml.org,2002:omap', ESPHomeLoader.construct_yaml_omap) | ||||
| ESPHomeLoader.add_constructor('tag:yaml.org,2002:str', ESPHomeLoader.construct_yaml_str) | ||||
| ESPHomeLoader.add_constructor('tag:yaml.org,2002:seq', ESPHomeLoader.construct_yaml_seq) | ||||
| ESPHomeLoader.add_constructor('tag:yaml.org,2002:map', ESPHomeLoader.construct_yaml_map) | ||||
| ESPHomeLoader.add_constructor('!env_var', ESPHomeLoader.construct_env_var) | ||||
| ESPHomeLoader.add_constructor('!secret', ESPHomeLoader.construct_secret) | ||||
| ESPHomeLoader.add_constructor('!include', ESPHomeLoader.construct_include) | ||||
| @@ -313,7 +310,7 @@ def dump(dict_): | ||||
|  | ||||
| def _is_file_valid(name): | ||||
|     """Decide if a file is valid.""" | ||||
|     return not name.startswith(u'.') | ||||
|     return not name.startswith('.') | ||||
|  | ||||
|  | ||||
| def _find_files(directory, pattern): | ||||
| @@ -328,7 +325,7 @@ def _find_files(directory, pattern): | ||||
|  | ||||
| def is_secret(value): | ||||
|     try: | ||||
|         return _SECRET_VALUES[text_type(value)] | ||||
|         return _SECRET_VALUES[str(value)] | ||||
|     except (KeyError, ValueError): | ||||
|         return None | ||||
|  | ||||
| @@ -358,31 +355,31 @@ class ESPHomeDumper(yaml.SafeDumper):  # pylint: disable=too-many-ancestors | ||||
|         return node | ||||
|  | ||||
|     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): | ||||
|         if is_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 | ||||
|     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): | ||||
|         if is_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): | ||||
|         if is_secret(value): | ||||
|             return self.represent_secret(value) | ||||
|         if math.isnan(value): | ||||
|             value = u'.nan' | ||||
|             value = '.nan' | ||||
|         elif math.isinf(value): | ||||
|             value = u'.inf' if value > 0 else u'-.inf' | ||||
|             value = '.inf' if value > 0 else '-.inf' | ||||
|         else: | ||||
|             value = text_type(repr(value)).lower() | ||||
|             value = str(repr(value)).lower() | ||||
|             # Note that in some cases `repr(data)` represents a float number | ||||
|             # without the decimal parts.  For instance: | ||||
|             #   >>> repr(1e17) | ||||
| @@ -390,9 +387,9 @@ class ESPHomeDumper(yaml.SafeDumper):  # pylint: disable=too-many-ancestors | ||||
|             # Unfortunately, this is not a valid float representation according | ||||
|             # to the definition of the `!!float` tag.  We fix this by adding | ||||
|             # '.0' before the 'e' symbol. | ||||
|             if u'.' not in value and u'e' in value: | ||||
|                 value = value.replace(u'e', u'.0e', 1) | ||||
|         return self.represent_scalar(tag=u'tag:yaml.org,2002:float', value=value) | ||||
|             if '.' not in value and 'e' in value: | ||||
|                 value = value.replace('e', '.0e', 1) | ||||
|         return self.represent_scalar(tag='tag:yaml.org,2002:float', value=value) | ||||
|  | ||||
|     def represent_lambda(self, 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(int, ESPHomeDumper.represent_int) | ||||
| 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(MACAddress, ESPHomeDumper.represent_stringify) | ||||
| ESPHomeDumper.add_multi_representer(TimePeriod, ESPHomeDumper.represent_stringify) | ||||
|   | ||||
| @@ -12,8 +12,6 @@ import time | ||||
|  | ||||
| import ifaddr | ||||
|  | ||||
| from esphome.py_compat import indexbytes, text_type | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
| # Some timing constants | ||||
| @@ -83,7 +81,7 @@ class IncomingDecodeError(Error): | ||||
|  | ||||
|  | ||||
| # pylint: disable=no-init | ||||
| class QuietLogger(object): | ||||
| class QuietLogger: | ||||
|     _seen_logs = {} | ||||
|  | ||||
|     @classmethod | ||||
| @@ -112,7 +110,7 @@ class QuietLogger(object): | ||||
|         logger(*args) | ||||
|  | ||||
|  | ||||
| class DNSEntry(object): | ||||
| class DNSEntry: | ||||
|     """A DNS entry""" | ||||
|  | ||||
|     def __init__(self, name, type_, class_): | ||||
| @@ -281,7 +279,7 @@ class DNSIncoming(QuietLogger): | ||||
|  | ||||
|     def read_utf(self, offset, length): | ||||
|         """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): | ||||
|         """Reads a domain name from the packet""" | ||||
| @@ -291,7 +289,7 @@ class DNSIncoming(QuietLogger): | ||||
|         first = off | ||||
|  | ||||
|         while True: | ||||
|             length = indexbytes(self.data, off) | ||||
|             length = self.data[off] | ||||
|             off += 1 | ||||
|             if length == 0: | ||||
|                 break | ||||
| @@ -302,13 +300,13 @@ class DNSIncoming(QuietLogger): | ||||
|             elif t == 0xC0: | ||||
|                 if next_ < 0: | ||||
|                     next_ = off + 1 | ||||
|                 off = ((length & 0x3F) << 8) | indexbytes(self.data, off) | ||||
|                 off = ((length & 0x3F) << 8) | self.data[off] | ||||
|                 if off >= first: | ||||
|                     raise IncomingDecodeError( | ||||
|                         "Bad domain name (circular) at %s" % (off,)) | ||||
|                         f"Bad domain name (circular) at {off}") | ||||
|                 first = off | ||||
|             else: | ||||
|                 raise IncomingDecodeError("Bad domain name at %s" % (off,)) | ||||
|                 raise IncomingDecodeError(f"Bad domain name at {off}") | ||||
|  | ||||
|         if next_ >= 0: | ||||
|             self.offset = next_ | ||||
| @@ -318,7 +316,7 @@ class DNSIncoming(QuietLogger): | ||||
|         return result | ||||
|  | ||||
|  | ||||
| class DNSOutgoing(object): | ||||
| class DNSOutgoing: | ||||
|     """Object representation of an outgoing packet""" | ||||
|  | ||||
|     def __init__(self, flags): | ||||
| @@ -461,7 +459,7 @@ class Engine(threading.Thread): | ||||
|                             if reader: | ||||
|                                 reader.handle_read(socket_) | ||||
|  | ||||
|                 except (select.error, socket.error) as e: | ||||
|                 except OSError as e: | ||||
|                     # If the socket was closed by another thread, during | ||||
|                     # shutdown, ignore it and exit | ||||
|                     if e.args[0] != socket.EBADF or not self.zc.done: | ||||
| @@ -500,7 +498,7 @@ class Listener(QuietLogger): | ||||
|             self.zc.handle_response(msg) | ||||
|  | ||||
|  | ||||
| class RecordUpdateListener(object): | ||||
| class RecordUpdateListener: | ||||
|     def update_record(self, zc, now, record): | ||||
|         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}) | ||||
|  | ||||
|     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.query_event.set() | ||||
|  | ||||
| @@ -605,12 +603,12 @@ class DashboardStatus(RecordUpdateListener, threading.Thread): | ||||
|  | ||||
|  | ||||
| def get_all_addresses(): | ||||
|     return list(set( | ||||
|     return list({ | ||||
|         addr.ip | ||||
|         for iface in ifaddr.get_adapters() | ||||
|         for addr in iface.ips | ||||
|         if addr.is_IPv4 and addr.network_prefix != 32  # Host only netmask 255.255.255.255 | ||||
|     )) | ||||
|     }) | ||||
|  | ||||
|  | ||||
| def new_socket(): | ||||
| @@ -631,7 +629,7 @@ def new_socket(): | ||||
|     else: | ||||
|         try: | ||||
|             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 | ||||
|             if err.errno != errno.ENOPROTOOPT: | ||||
|                 raise | ||||
| @@ -662,7 +660,7 @@ class Zeroconf(QuietLogger): | ||||
|                 _value = socket.inet_aton(_MDNS_ADDR) + socket.inet_aton(i) | ||||
|                 self._listen_socket.setsockopt( | ||||
|                     socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, _value) | ||||
|             except socket.error as e: | ||||
|             except OSError as e: | ||||
|                 _errno = e.args[0] | ||||
|                 if _errno == errno.EADDRINUSE: | ||||
|                     log.info( | ||||
|   | ||||
							
								
								
									
										6
									
								
								pylintrc
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								pylintrc
									
									
									
									
									
								
							| @@ -25,9 +25,3 @@ disable= | ||||
|   stop-iteration-return, | ||||
|   no-self-use, | ||||
|   import-outside-toplevel, | ||||
|  | ||||
|  | ||||
| additional-builtins= | ||||
|   unicode, | ||||
|   long, | ||||
|   raw_input | ||||
|   | ||||
| @@ -3,7 +3,6 @@ PyYAML==5.2 | ||||
| paho-mqtt==1.5.0 | ||||
| colorlog==4.0.2 | ||||
| tornado==5.1.1 | ||||
| typing>=3.6.6;python_version<"3.5" | ||||
| protobuf==3.11.1 | ||||
| tzlocal==2.0.0 | ||||
| pytz==2019.3 | ||||
|   | ||||
| @@ -3,7 +3,6 @@ PyYAML==5.2 | ||||
| paho-mqtt==1.5.0 | ||||
| colorlog==4.0.2 | ||||
| tornado==5.1.1 | ||||
| typing>=3.6.6;python_version<"3.5" | ||||
| protobuf==3.11.1 | ||||
| tzlocal==2.0.0 | ||||
| pytz==2019.3 | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Generated by the protocol buffer compiler.  DO NOT EDIT! | ||||
| # source: api_options.proto | ||||
|  | ||||
|   | ||||
| @@ -39,12 +39,12 @@ content = prot.read_bytes() | ||||
| d = descriptor.FileDescriptorSet.FromString(content) | ||||
|  | ||||
|  | ||||
| def indent_list(text, padding=u'  '): | ||||
| def indent_list(text, padding='  '): | ||||
|     return [padding + line for line in text.splitlines()] | ||||
|  | ||||
|  | ||||
| def indent(text, padding=u'  '): | ||||
|     return u'\n'.join(indent_list(text, padding)) | ||||
| def indent(text, padding='  '): | ||||
|     return '\n'.join(indent_list(text, padding)) | ||||
|  | ||||
|  | ||||
| def camel_to_snake(name): | ||||
| @@ -432,7 +432,7 @@ class SInt64Type(TypeInfo): | ||||
|  | ||||
| class RepeatedTypeInfo(TypeInfo): | ||||
|     def __init__(self, field): | ||||
|         super(RepeatedTypeInfo, self).__init__(field) | ||||
|         super().__init__(field) | ||||
|         self._ti = TYPE_INFO[field.type](field) | ||||
|  | ||||
|     @property | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| #!/usr/bin/env python | ||||
| #!/usr/bin/env python3 | ||||
| import sys | ||||
| import os.path | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| #!/usr/bin/env python | ||||
| from __future__ import print_function | ||||
| #!/usr/bin/env python3 | ||||
|  | ||||
| import codecs | ||||
| import collections | ||||
| @@ -105,7 +104,7 @@ def lint_re_check(regex, **kwargs): | ||||
|                 err = func(fname, match) | ||||
|                 if err is None: | ||||
|                     continue | ||||
|                 errors.append("{} See line {}.".format(err, lineno)) | ||||
|                 errors.append(f"{err} See line {lineno}.") | ||||
|             return errors | ||||
|         return decor(new_func) | ||||
|     return decorator | ||||
| @@ -134,7 +133,7 @@ def lint_ino(fname): | ||||
|     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', | ||||
|     'MANIFEST.in', 'docker/Dockerfile*', 'docker/rootfs/*', 'script/*', | ||||
| ]) | ||||
| @@ -177,7 +176,7 @@ CPP_RE_EOL = r'\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, | ||||
| @@ -268,7 +267,7 @@ def lint_constants_usage(): | ||||
| def relative_cpp_search_text(fname, content): | ||||
|     parts = fname.split('/') | ||||
|     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']) | ||||
| @@ -284,7 +283,7 @@ def lint_relative_cpp_import(fname): | ||||
| def relative_py_search_text(fname, content): | ||||
|     parts = fname.split('/') | ||||
|     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'], | ||||
| @@ -303,7 +302,7 @@ def lint_relative_py_import(fname): | ||||
| def lint_namespace(fname, content): | ||||
|     expected_name = re.match(r'^esphome/components/([^/]+)/.*', | ||||
|                              fname.replace(os.path.sep, '/')).group(1) | ||||
|     search = 'namespace {}'.format(expected_name) | ||||
|     search = f'namespace {expected_name}' | ||||
|     if search in content: | ||||
|         return None | ||||
|     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') | ||||
|  | ||||
| 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: | ||||
|         print(err) | ||||
|     print() | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| #!/usr/bin/env python | ||||
| #!/usr/bin/env python3 | ||||
|  | ||||
| from __future__ import print_function | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| #!/usr/bin/env python | ||||
| #!/usr/bin/env python3 | ||||
|  | ||||
| from __future__ import print_function | ||||
|  | ||||
|   | ||||
| @@ -12,11 +12,11 @@ temp_header_file = os.path.join(root_path, '.temp-clang-tidy.cpp') | ||||
|  | ||||
| def shlex_quote(s): | ||||
|     if not s: | ||||
|         return u"''" | ||||
|         return "''" | ||||
|     if re.search(r'[^\w@%+=:,./-]', s) is None: | ||||
|         return s | ||||
|  | ||||
|     return u"'" + s.replace(u"'", u"'\"'\"'") + u"'" | ||||
|     return "'" + s.replace("'", "'\"'\"'") + "'" | ||||
|  | ||||
|  | ||||
| def build_all_include(): | ||||
| @@ -29,7 +29,7 @@ def build_all_include(): | ||||
|         if ext in filetypes: | ||||
|             path = os.path.relpath(path, root_path) | ||||
|             include_p = path.replace(os.path.sep, '/') | ||||
|             headers.append('#include "{}"'.format(include_p)) | ||||
|             headers.append(f'#include "{include_p}"') | ||||
|     headers.sort() | ||||
|     headers.append('') | ||||
|     content = '\n'.join(headers) | ||||
| @@ -47,7 +47,7 @@ def build_compile_commands(): | ||||
|         gcc_flags = json.load(f) | ||||
|     exec_path = gcc_flags['execPath'] | ||||
|     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(' ') | ||||
|     defines = [flag for flag in cpp_flags if flag.startswith('-D')] | ||||
|     command = [exec_path] | ||||
| @@ -102,7 +102,7 @@ def splitlines_no_ends(string): | ||||
|  | ||||
| def changed_files(): | ||||
|     for remote in ('upstream', 'origin'): | ||||
|         command = ['git', 'merge-base', '{}/dev'.format(remote), 'HEAD'] | ||||
|         command = ['git', 'merge-base', f'{remote}/dev', 'HEAD'] | ||||
|         try: | ||||
|             merge_base = splitlines_no_ends(get_output(*command))[0] | ||||
|             break | ||||
| @@ -124,7 +124,7 @@ def filter_changed(files): | ||||
|     if not files: | ||||
|         print("    No changed files!") | ||||
|     for c in files: | ||||
|         print("    {}".format(c)) | ||||
|         print(f"    {c}") | ||||
|     return files | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| #!/usr/bin/env python | ||||
| #!/usr/bin/env python3 | ||||
|  | ||||
| from __future__ import print_function | ||||
|  | ||||
| @@ -61,7 +61,7 @@ def main(): | ||||
|             continue | ||||
|         file_ = line[0] | ||||
|         linno = line[1] | ||||
|         msg = (u':'.join(line[3:])).strip() | ||||
|         msg = (':'.join(line[3:])).strip() | ||||
|         print_error(file_, linno, msg) | ||||
|         errors += 1 | ||||
|  | ||||
| @@ -74,7 +74,7 @@ def main(): | ||||
|             continue | ||||
|         file_ = line[0] | ||||
|         linno = line[1] | ||||
|         msg = (u':'.join(line[2:])).strip() | ||||
|         msg = (':'.join(line[2:])).strip() | ||||
|         print_error(file_, linno, msg) | ||||
|         errors += 1 | ||||
|  | ||||
|   | ||||
| @@ -18,7 +18,6 @@ Topic :: Home Automation | ||||
|  | ||||
| [flake8] | ||||
| max-line-length = 120 | ||||
| builtins = unicode, long, raw_input, basestring | ||||
| exclude = api_pb2.py | ||||
|  | ||||
| [bdist_wheel] | ||||
|   | ||||
							
								
								
									
										5
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								setup.py
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| #!/usr/bin/env python | ||||
| #!/usr/bin/env python3 | ||||
| """esphome setup script.""" | ||||
| from setuptools import setup, find_packages | ||||
| import os | ||||
| @@ -28,7 +28,6 @@ REQUIRES = [ | ||||
|     'paho-mqtt==1.5.0', | ||||
|     'colorlog==4.0.2', | ||||
|     'tornado==5.1.1', | ||||
|     'typing>=3.6.6;python_version<"3.6"', | ||||
|     'protobuf==3.11.1', | ||||
|     'tzlocal==2.0.0', | ||||
|     'pytz==2019.3', | ||||
| @@ -69,7 +68,7 @@ setup( | ||||
|     zip_safe=False, | ||||
|     platforms='any', | ||||
|     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, | ||||
|     keywords=['home', 'automation'], | ||||
|     entry_points={ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user