1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-19 08:15:49 +00:00
* Add black

Update pre commit

Update pre commit

add empty line

* Format with black
This commit is contained in:
Guillermo Ruffino
2021-03-07 16:03:16 -03:00
committed by GitHub
parent 2b60b0f1fa
commit 69879920eb
398 changed files with 21624 additions and 12644 deletions

View File

@@ -27,8 +27,13 @@ import tornado.websocket
from esphome import const, util
from esphome.helpers import mkdir_p, get_bool_env, run_system_command
from esphome.storage_json import EsphomeStorageJSON, StorageJSON, \
esphome_storage_path, ext_storage_path, trash_storage_path
from esphome.storage_json import (
EsphomeStorageJSON,
StorageJSON,
esphome_storage_path,
ext_storage_path,
trash_storage_path,
)
from esphome.util import shlex_quote, get_serial_ports
from .util import password_hash
@@ -42,18 +47,18 @@ _LOGGER = logging.getLogger(__name__)
class DashboardSettings:
def __init__(self):
self.config_dir = ''
self.password_hash = ''
self.username = ''
self.config_dir = ""
self.password_hash = ""
self.username = ""
self.using_password = False
self.on_hassio = False
self.cookie_secret = None
def parse_args(self, args):
self.on_hassio = args.hassio
password = args.password or os.getenv('PASSWORD', '')
password = args.password or os.getenv("PASSWORD", "")
if not self.on_hassio:
self.username = args.username or os.getenv('USERNAME', '')
self.username = args.username or os.getenv("USERNAME", "")
self.using_password = bool(password)
if self.using_password:
self.password_hash = password_hash(password)
@@ -61,17 +66,17 @@ class DashboardSettings:
@property
def relative_url(self):
return os.getenv('ESPHOME_DASHBOARD_RELATIVE_URL', '/')
return os.getenv("ESPHOME_DASHBOARD_RELATIVE_URL", "/")
@property
def status_use_ping(self):
return get_bool_env('ESPHOME_DASHBOARD_USE_PING')
return get_bool_env("ESPHOME_DASHBOARD_USE_PING")
@property
def using_hassio_auth(self):
if not self.on_hassio:
return False
return not get_bool_env('DISABLE_HA_AUTHENTICATION')
return not get_bool_env("DISABLE_HA_AUTHENTICATION")
@property
def using_auth(self):
@@ -84,10 +89,7 @@ class DashboardSettings:
return False
# Compare password in constant running time (to prevent timing attacks)
return hmac.compare_digest(
self.password_hash,
password_hash(password)
)
return hmac.compare_digest(self.password_hash, password_hash(password))
def rel_path(self, *args):
return os.path.join(self.config_dir, *args)
@@ -98,24 +100,24 @@ class DashboardSettings:
settings = DashboardSettings()
cookie_authenticated_yes = b'yes'
cookie_authenticated_yes = b"yes"
def template_args():
version = const.__version__
if 'b' in version:
docs_link = 'https://beta.esphome.io/'
elif 'dev' in version:
docs_link = 'https://next.esphome.io/'
if "b" in version:
docs_link = "https://beta.esphome.io/"
elif "dev" in version:
docs_link = "https://next.esphome.io/"
else:
docs_link = 'https://www.esphome.io/'
docs_link = "https://www.esphome.io/"
return {
'version': version,
'docs_link': docs_link,
'get_static_file_url': get_static_file_url,
'relative_url': settings.relative_url,
'streamer_mode': get_bool_env('ESPHOME_STREAMER_MODE'),
'config_dir': settings.config_dir,
"version": version,
"docs_link": docs_link,
"get_static_file_url": get_static_file_url,
"relative_url": settings.relative_url,
"streamer_mode": get_bool_env("ESPHOME_STREAMER_MODE"),
"config_dir": settings.config_dir,
}
@@ -123,9 +125,10 @@ def authenticated(func):
@functools.wraps(func)
def decorator(self, *args, **kwargs):
if not is_authenticated(self):
self.redirect('./login')
self.redirect("./login")
return None
return func(self, *args, **kwargs)
return decorator
@@ -133,23 +136,27 @@ def is_authenticated(request_handler):
if settings.on_hassio:
# Handle ingress - disable auth on ingress port
# X-Hassio-Ingress is automatically stripped on the non-ingress server in nginx
header = request_handler.request.headers.get('X-Hassio-Ingress', 'NO')
if str(header) == 'YES':
header = request_handler.request.headers.get("X-Hassio-Ingress", "NO")
if str(header) == "YES":
return True
if settings.using_auth:
return request_handler.get_secure_cookie('authenticated') == cookie_authenticated_yes
return (
request_handler.get_secure_cookie("authenticated")
== cookie_authenticated_yes
)
return True
def bind_config(func):
def decorator(self, *args, **kwargs):
configuration = self.get_argument('configuration')
configuration = self.get_argument("configuration")
if not is_allowed(configuration):
self.set_status(500)
return None
kwargs = kwargs.copy()
kwargs['configuration'] = configuration
kwargs["configuration"] = configuration
return func(self, *args, **kwargs)
return decorator
@@ -160,7 +167,7 @@ class BaseHandler(tornado.web.RequestHandler):
def websocket_class(cls):
# pylint: disable=protected-access
if not hasattr(cls, '_message_handlers'):
if not hasattr(cls, "_message_handlers"):
cls._message_handlers = {}
for _, method in cls.__dict__.items():
@@ -175,6 +182,7 @@ def websocket_method(name):
# pylint: disable=protected-access
fn._message_handler = name
return fn
return wrap
@@ -190,7 +198,7 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
def on_message(self, message):
# Messages are always JSON, 500 when not
json_message = json.loads(message)
type_ = json_message['type']
type_ = json_message["type"]
# pylint: disable=no-member
handlers = type(self)._message_handlers
if type_ not in handlers:
@@ -199,17 +207,19 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
handlers[type_](self, json_message)
@websocket_method('spawn')
@websocket_method("spawn")
def handle_spawn(self, json_message):
if self._proc is not None:
# spawn can only be called once
return
command = self.build_command(json_message)
_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,
stdin=tornado.process.Subprocess.STREAM)
_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,
stdin=tornado.process.Subprocess.STREAM,
)
self._proc.set_exit_callback(self._proc_on_exit)
tornado.ioloop.IOLoop.current().spawn_callback(self._redirect_stdout)
@@ -217,34 +227,34 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
def is_process_active(self):
return self._proc is not None and self._proc.returncode is None
@websocket_method('stdin')
@websocket_method("stdin")
def handle_stdin(self, json_message):
if not self.is_process_active:
return
data = json_message['data']
data = codecs.encode(data, 'utf8', 'replace')
data = json_message["data"]
data = codecs.encode(data, "utf8", "replace")
_LOGGER.debug("< stdin: %s", data)
self._proc.stdin.write(data)
@tornado.gen.coroutine
def _redirect_stdout(self):
reg = b'[\n\r]'
reg = b"[\n\r]"
while True:
try:
data = yield self._proc.stdout.read_until_regex(reg)
except tornado.iostream.StreamClosedError:
break
data = codecs.decode(data, 'utf8', 'replace')
data = codecs.decode(data, "utf8", "replace")
_LOGGER.debug("> stdout: %s", data)
self.write_message({'event': 'line', 'data': data})
self.write_message({"event": "line", "data": data})
def _proc_on_exit(self, returncode):
if not self._is_closed:
# Check if the proc was not forcibly closed
_LOGGER.info("Process exited with return code %s", returncode)
self.write_message({'event': 'exit', 'code': returncode})
self.write_message({"event": "exit", "code": returncode})
def on_close(self):
# Check if proc exists (if 'start' has been run)
@@ -260,45 +270,57 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
class EsphomeLogsHandler(EsphomeCommandWebSocket):
def build_command(self, json_message):
config_file = settings.rel_path(json_message['configuration'])
return ["esphome", "--dashboard", config_file, "logs", '--serial-port',
json_message["port"]]
config_file = settings.rel_path(json_message["configuration"])
return [
"esphome",
"--dashboard",
config_file,
"logs",
"--serial-port",
json_message["port"],
]
class EsphomeUploadHandler(EsphomeCommandWebSocket):
def build_command(self, json_message):
config_file = settings.rel_path(json_message['configuration'])
return ["esphome", "--dashboard", config_file, "run", '--upload-port',
json_message["port"]]
config_file = settings.rel_path(json_message["configuration"])
return [
"esphome",
"--dashboard",
config_file,
"run",
"--upload-port",
json_message["port"],
]
class EsphomeCompileHandler(EsphomeCommandWebSocket):
def build_command(self, json_message):
config_file = settings.rel_path(json_message['configuration'])
config_file = settings.rel_path(json_message["configuration"])
return ["esphome", "--dashboard", config_file, "compile"]
class EsphomeValidateHandler(EsphomeCommandWebSocket):
def build_command(self, json_message):
config_file = settings.rel_path(json_message['configuration'])
config_file = settings.rel_path(json_message["configuration"])
return ["esphome", "--dashboard", config_file, "config"]
class EsphomeCleanMqttHandler(EsphomeCommandWebSocket):
def build_command(self, json_message):
config_file = settings.rel_path(json_message['configuration'])
config_file = settings.rel_path(json_message["configuration"])
return ["esphome", "--dashboard", config_file, "clean-mqtt"]
class EsphomeCleanHandler(EsphomeCommandWebSocket):
def build_command(self, json_message):
config_file = settings.rel_path(json_message['configuration'])
config_file = settings.rel_path(json_message["configuration"])
return ["esphome", "--dashboard", config_file, "clean"]
class EsphomeVscodeHandler(EsphomeCommandWebSocket):
def build_command(self, json_message):
return ["esphome", "--dashboard", "-q", 'dummy', "vscode"]
return ["esphome", "--dashboard", "-q", "dummy", "vscode"]
class EsphomeAceEditorHandler(EsphomeCommandWebSocket):
@@ -318,15 +340,15 @@ class SerialPortRequestHandler(BaseHandler):
data = []
for port in ports:
desc = port.description
if port.path == '/dev/ttyAMA0':
desc = 'UART pins on GPIO header'
split_desc = desc.split(' - ')
if port.path == "/dev/ttyAMA0":
desc = "UART pins on GPIO header"
split_desc = desc.split(" - ")
if len(split_desc) == 2 and split_desc[0] == split_desc[1]:
# Some serial ports repeat their values
desc = split_desc[0]
data.append({'port': port.path, 'desc': desc})
data.append({'port': 'OTA', 'desc': 'Over-The-Air'})
data.sort(key=lambda x: x['port'], reverse=True)
data.append({"port": port.path, "desc": desc})
data.append({"port": "OTA", "desc": "Over-The-Air"})
data.sort(key=lambda x: x["port"], reverse=True)
self.write(json.dumps(data))
@@ -336,12 +358,11 @@ class WizardRequestHandler(BaseHandler):
from esphome import wizard
kwargs = {
k: ''.join(x.decode() for x in v)
for k, v in self.request.arguments.items()
k: "".join(x.decode() for x in v) for k, v in self.request.arguments.items()
}
destination = settings.rel_path(kwargs['name'] + '.yaml')
destination = settings.rel_path(kwargs["name"] + ".yaml")
wizard.wizard_write(path=destination, **kwargs)
self.redirect('./?begin=True')
self.redirect("./?begin=True")
class DownloadBinaryRequestHandler(BaseHandler):
@@ -356,10 +377,10 @@ class DownloadBinaryRequestHandler(BaseHandler):
return
path = storage_json.firmware_bin_path
self.set_header('Content-Type', 'application/octet-stream')
filename = f'{storage_json.name}.bin'
self.set_header("Content-Type", "application/octet-stream")
filename = f"{storage_json.name}.bin"
self.set_header("Content-Disposition", f'attachment; filename="{filename}"')
with open(path, 'rb') as f:
with open(path, "rb") as f:
while True:
data = f.read(16384)
if not data:
@@ -386,7 +407,9 @@ class DashboardEntry:
@property
def storage(self): # type: () -> Optional[StorageJSON]
if not self._loaded_storage:
self._storage = StorageJSON.load(ext_storage_path(settings.config_dir, self.filename))
self._storage = StorageJSON.load(
ext_storage_path(settings.config_dir, self.filename)
)
self._loaded_storage = True
return self._storage
@@ -399,7 +422,7 @@ class DashboardEntry:
@property
def name(self):
if self.storage is None:
return self.filename[:-len('.yaml')]
return self.filename[: -len(".yaml")]
return self.storage.name
@property
@@ -429,8 +452,8 @@ class DashboardEntry:
@property
def update_old(self):
if self.storage is None:
return ''
return self.storage.esphome_version or ''
return ""
return self.storage.esphome_version or ""
@property
def update_new(self):
@@ -446,18 +469,23 @@ class DashboardEntry:
class MainRequestHandler(BaseHandler):
@authenticated
def get(self):
begin = bool(self.get_argument('begin', False))
begin = bool(self.get_argument("begin", False))
entries = _list_dashboard_entries()
self.render("templates/index.html", entries=entries, begin=begin,
**template_args(), login_enabled=settings.using_auth)
self.render(
"templates/index.html",
entries=entries,
begin=begin,
**template_args(),
login_enabled=settings.using_auth,
)
def _ping_func(filename, address):
if os.name == 'nt':
command = ['ping', '-n', '1', address]
if os.name == "nt":
command = ["ping", "-n", "1", address]
else:
command = ['ping', '-c', '1', address]
command = ["ping", "-c", "1", address]
rc, _, _ = run_system_command(*command)
return filename, rc == 0
@@ -474,7 +502,9 @@ class MDNSStatusThread(threading.Thread):
stat.start()
while not STOP_EVENT.is_set():
entries = _list_dashboard_entries()
stat.request_query({entry.filename: entry.name + '.local.' for entry in entries})
stat.request_query(
{entry.filename: entry.name + ".local." for entry in entries}
)
PING_REQUEST.wait()
PING_REQUEST.clear()
@@ -499,8 +529,9 @@ class PingStatusThread(threading.Thread):
PING_RESULT[entry.filename] = None
continue
result = pool.apply_async(_ping_func, (entry.filename, entry.address),
callback=callback)
result = pool.apply_async(
_ping_func, (entry.filename, entry.address), callback=callback
)
queue.append(result)
while queue:
@@ -541,10 +572,10 @@ class EditRequestHandler(BaseHandler):
@bind_config
def get(self, configuration=None):
filename = settings.rel_path(configuration)
content = ''
content = ""
if os.path.isfile(filename):
# pylint: disable=no-value-for-parameter
with open(filename, 'r') as f:
with open(filename, "r") as f:
content = f.read()
self.write(content)
@@ -552,7 +583,7 @@ class EditRequestHandler(BaseHandler):
@bind_config
def post(self, configuration=None):
# pylint: disable=no-value-for-parameter
with open(settings.rel_path(configuration), 'wb') as f:
with open(settings.rel_path(configuration), "wb") as f:
f.write(self.request.body)
self.set_status(200)
@@ -596,29 +627,34 @@ PING_REQUEST = threading.Event()
class LoginHandler(BaseHandler):
def get(self):
if is_authenticated(self):
self.redirect('/')
self.redirect("/")
else:
self.render_login_page()
def render_login_page(self, error=None):
self.render("templates/login.html", error=error, hassio=settings.using_hassio_auth,
has_username=bool(settings.username), **template_args())
self.render(
"templates/login.html",
error=error,
hassio=settings.using_hassio_auth,
has_username=bool(settings.username),
**template_args(),
)
def post_hassio_login(self):
import requests
headers = {
'X-HASSIO-KEY': os.getenv('HASSIO_TOKEN'),
"X-HASSIO-KEY": os.getenv("HASSIO_TOKEN"),
}
data = {
'username': self.get_argument('username', ''),
'password': 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)
req = requests.post("http://hassio/auth", headers=headers, data=data)
if req.status_code == 200:
self.set_secure_cookie("authenticated", cookie_authenticated_yes)
self.redirect('/')
self.redirect("/")
return
except Exception as err: # pylint: disable=broad-except
_LOGGER.warning("Error during Hass.io auth request: %s", err)
@@ -629,13 +665,15 @@ class LoginHandler(BaseHandler):
self.render_login_page(error="Invalid username or password")
def post_native_login(self):
username = self.get_argument("username", '')
password = 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("/")
return
error_str = "Invalid username or password" if settings.username else "Invalid password"
error_str = (
"Invalid username or password" if settings.username else "Invalid password"
)
self.set_status(401)
self.render_login_page(error=error_str)
@@ -650,22 +688,22 @@ class LogoutHandler(BaseHandler):
@authenticated
def get(self):
self.clear_cookie("authenticated")
self.redirect('./login')
self.redirect("./login")
_STATIC_FILE_HASHES = {}
def get_static_file_url(name):
static_path = os.path.join(os.path.dirname(__file__), 'static')
static_path = os.path.join(os.path.dirname(__file__), "static")
if name in _STATIC_FILE_HASHES:
hash_ = _STATIC_FILE_HASHES[name]
else:
path = os.path.join(static_path, name)
with open(path, 'rb') as f_handle:
with open(path, "rb") as f_handle:
hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8]
_STATIC_FILE_HASHES[name] = hash_
return f'./static/{name}?hash={hash_}'
return f"./static/{name}?hash={hash_}"
def make_app(debug=False):
@@ -684,44 +722,53 @@ def make_app(debug=False):
request_time = 1000.0 * handler.request.request_time()
# pylint: disable=protected-access
log_method("%d %s %.2fms", handler.get_status(),
handler._request_summary(), request_time)
log_method(
"%d %s %.2fms",
handler.get_status(),
handler._request_summary(),
request_time,
)
class StaticFileHandler(tornado.web.StaticFileHandler):
def set_extra_headers(self, path):
if debug:
self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
self.set_header(
"Cache-Control", "no-store, no-cache, must-revalidate, max-age=0"
)
static_path = os.path.join(os.path.dirname(__file__), 'static')
static_path = os.path.join(os.path.dirname(__file__), "static")
app_settings = {
'debug': debug,
'cookie_secret': settings.cookie_secret,
'log_function': log_function,
'websocket_ping_interval': 30.0,
"debug": debug,
"cookie_secret": settings.cookie_secret,
"log_function": log_function,
"websocket_ping_interval": 30.0,
}
rel = settings.relative_url
app = tornado.web.Application([
(rel + "", MainRequestHandler),
(rel + "login", LoginHandler),
(rel + "logout", LogoutHandler),
(rel + "logs", EsphomeLogsHandler),
(rel + "upload", EsphomeUploadHandler),
(rel + "compile", EsphomeCompileHandler),
(rel + "validate", EsphomeValidateHandler),
(rel + "clean-mqtt", EsphomeCleanMqttHandler),
(rel + "clean", EsphomeCleanHandler),
(rel + "vscode", EsphomeVscodeHandler),
(rel + "ace", EsphomeAceEditorHandler),
(rel + "update-all", EsphomeUpdateAllHandler),
(rel + "edit", EditRequestHandler),
(rel + "download.bin", DownloadBinaryRequestHandler),
(rel + "serial-ports", SerialPortRequestHandler),
(rel + "ping", PingRequestHandler),
(rel + "delete", DeleteRequestHandler),
(rel + "undo-delete", UndoDeleteRequestHandler),
(rel + "wizard.html", WizardRequestHandler),
(rel + r"static/(.*)", StaticFileHandler, {'path': static_path}),
], **app_settings)
app = tornado.web.Application(
[
(rel + "", MainRequestHandler),
(rel + "login", LoginHandler),
(rel + "logout", LogoutHandler),
(rel + "logs", EsphomeLogsHandler),
(rel + "upload", EsphomeUploadHandler),
(rel + "compile", EsphomeCompileHandler),
(rel + "validate", EsphomeValidateHandler),
(rel + "clean-mqtt", EsphomeCleanMqttHandler),
(rel + "clean", EsphomeCleanHandler),
(rel + "vscode", EsphomeVscodeHandler),
(rel + "ace", EsphomeAceEditorHandler),
(rel + "update-all", EsphomeUpdateAllHandler),
(rel + "edit", EditRequestHandler),
(rel + "download.bin", DownloadBinaryRequestHandler),
(rel + "serial-ports", SerialPortRequestHandler),
(rel + "ping", PingRequestHandler),
(rel + "delete", DeleteRequestHandler),
(rel + "undo-delete", UndoDeleteRequestHandler),
(rel + "wizard.html", WizardRequestHandler),
(rel + r"static/(.*)", StaticFileHandler, {"path": static_path}),
],
**app_settings,
)
if debug:
_STATIC_FILE_HASHES.clear()
@@ -743,20 +790,26 @@ def start_web_server(args):
app = make_app(args.verbose)
if args.socket is not None:
_LOGGER.info("Starting dashboard web server on unix socket %s and configuration dir %s...",
args.socket, settings.config_dir)
_LOGGER.info(
"Starting dashboard web server on unix socket %s and configuration dir %s...",
args.socket,
settings.config_dir,
)
server = tornado.httpserver.HTTPServer(app)
socket = tornado.netutil.bind_unix_socket(args.socket, mode=0o666)
server.add_socket(socket)
else:
_LOGGER.info("Starting dashboard web server on port %s and configuration dir %s...",
args.port, settings.config_dir)
_LOGGER.info(
"Starting dashboard web server on port %s and configuration dir %s...",
args.port,
settings.config_dir,
)
app.listen(args.port)
if args.open_ui:
import webbrowser
webbrowser.open(f'localhost:{args.port}')
webbrowser.open(f"localhost:{args.port}")
if settings.status_use_ping:
status_thread = PingStatusThread()