Skip to content

Commit

Permalink
Drop Python 2 Support (esphome#793)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
OttoWinter authored Dec 7, 2019
1 parent b5714cd commit 056c72d
Show file tree
Hide file tree
Showing 78 changed files with 815 additions and 1,097 deletions.
6 changes: 0 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions docker/Dockerfile.lint
Original file line number Diff line number Diff line change
Expand Up @@ -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
61 changes: 24 additions & 37 deletions esphome/__main__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import print_function

import argparse
import functools
import logging
Expand All @@ -14,7 +12,6 @@
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__)
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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


Expand All @@ -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


Expand All @@ -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,
Expand All @@ -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


Expand Down Expand Up @@ -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)))
Expand Down Expand Up @@ -431,7 +422,7 @@ def print_bar(middle_text):


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.",
Expand Down Expand Up @@ -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:
Expand All @@ -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)
Expand Down
45 changes: 20 additions & 25 deletions esphome/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
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__)
Expand Down Expand Up @@ -67,25 +66,24 @@ class APIConnectionError(EsphomeError):

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


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:
Expand Down Expand Up @@ -191,16 +189,16 @@ def connect(self):
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)

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:
Expand Down Expand Up @@ -251,8 +249,8 @@ def _write(self, data): # type: (bytes) -> None
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

Expand All @@ -265,11 +263,8 @@ def _send_message(self, msg):
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
Expand Down Expand Up @@ -355,14 +350,14 @@ def _recv(self, amount):
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)

Expand All @@ -371,7 +366,7 @@ def _run_once(self):
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()
Expand Down Expand Up @@ -436,7 +431,7 @@ def try_connect(err, tries=0):
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()
Expand All @@ -454,18 +449,18 @@ def try_connect(err, tries=0):

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 '
Expand Down
4 changes: 2 additions & 2 deletions esphome/automation.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ def validator_(value):
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):
Expand Down
2 changes: 1 addition & 1 deletion esphome/components/ade7953/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
7 changes: 3 additions & 4 deletions esphome/components/ads1115/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
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']
Expand Down Expand Up @@ -32,9 +31,9 @@

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)

Expand Down
2 changes: 1 addition & 1 deletion esphome/components/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 056c72d

Please sign in to comment.