Bump python min to 3.9 (#3871)

This commit is contained in:
Jesse Hills 2022-10-05 20:09:27 +13:00 committed by GitHub
parent c3a8972550
commit d220d41182
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 130 additions and 147 deletions

View File

@ -30,4 +30,4 @@ repos:
rev: v3.0.0
hooks:
- id: pyupgrade
args: [--py38-plus]
args: [--py39-plus]

View File

@ -1,6 +1,5 @@
import logging
from dataclasses import dataclass
from typing import List
from esphome.const import (
CONF_ID,
@ -200,7 +199,7 @@ async def esp8266_pin_to_code(config):
@coroutine_with_priority(-999.0)
async def add_pin_initial_states_array():
# Add includes at the very end, so that they override everything
initial_states: List[PinInitialState] = CORE.data[KEY_ESP8266][
initial_states: list[PinInitialState] = CORE.data[KEY_ESP8266][
KEY_PIN_INITIAL_STATES
]
initial_modes_s = ", ".join(str(x.mode) for x in initial_states)

View File

@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import Any, List
from typing import Any
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
@ -349,7 +349,7 @@ def _spi_extra_validate(config):
class MethodDescriptor:
method_schema: Any
to_code: Any
supported_chips: List[str]
supported_chips: list[str]
extra_validate: Any = None

View File

@ -1,4 +1,3 @@
from typing import List
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
@ -60,7 +59,7 @@ SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e
)
async def setup_select_core_(var, config, *, options: List[str]):
async def setup_select_core_(var, config, *, options: list[str]):
await setup_entity(var, config)
cg.add(var.traits.set_options(options))
@ -76,14 +75,14 @@ async def setup_select_core_(var, config, *, options: List[str]):
await mqtt.register_mqtt_component(mqtt_, config)
async def register_select(var, config, *, options: List[str]):
async def register_select(var, config, *, options: list[str]):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_select(var))
await setup_select_core_(var, config, options=options)
async def new_select(config, *, options: List[str]):
async def new_select(config, *, options: list[str]):
var = cg.new_Pvariable(config[CONF_ID])
await register_select(var, config, options=options)
return var

View File

@ -23,7 +23,7 @@ from esphome.core import CORE, EsphomeError
from esphome.helpers import indent
from esphome.util import safe_print, OrderedDict
from typing import List, Optional, Tuple, Union
from typing import Optional, Union
from esphome.loader import get_component, get_platform, ComponentManifest
from esphome.yaml_util import is_secret, ESPHomeDataBase, ESPForceValue
from esphome.voluptuous_schema import ExtraKeysInvalid
@ -50,10 +50,10 @@ def iter_components(config):
yield p_name, platform, p_config
ConfigPath = List[Union[str, int]]
ConfigPath = list[Union[str, int]]
def _path_begins_with(path, other): # type: (ConfigPath, ConfigPath) -> bool
def _path_begins_with(path: ConfigPath, other: ConfigPath) -> bool:
if len(path) < len(other):
return False
return path[: len(other)] == other
@ -67,7 +67,7 @@ class _ValidationStepTask:
self.step = step
@property
def _cmp_tuple(self) -> Tuple[float, int]:
def _cmp_tuple(self) -> tuple[float, int]:
return (-self.priority, self.id_number)
def __eq__(self, other):
@ -84,21 +84,20 @@ class Config(OrderedDict, fv.FinalValidateConfig):
def __init__(self):
super().__init__()
# A list of voluptuous errors
self.errors = [] # type: List[vol.Invalid]
self.errors: 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, str]]
self.output_paths: list[tuple[ConfigPath, str]] = []
# A list of components ids with the config path
self.declare_ids = [] # type: List[Tuple[core.ID, ConfigPath]]
self.declare_ids: list[tuple[core.ID, ConfigPath]] = []
self._data = {}
# Store pending validation tasks (in heap order)
self._validation_tasks: List[_ValidationStepTask] = []
self._validation_tasks: list[_ValidationStepTask] = []
# ID to ensure stable order for keys with equal priority
self._validation_tasks_id = 0
def add_error(self, error):
# type: (vol.Invalid) -> None
def add_error(self, error: vol.Invalid) -> None:
if isinstance(error, vol.MultipleInvalid):
for err in error.errors:
self.add_error(err)
@ -132,20 +131,16 @@ class Config(OrderedDict, fv.FinalValidateConfig):
e.prepend(path)
self.add_error(e)
def add_str_error(self, message, path):
# type: (str, ConfigPath) -> None
def add_str_error(self, message: str, path: ConfigPath) -> None:
self.add_error(vol.Invalid(message, path))
def add_output_path(self, path, domain):
# type: (ConfigPath, str) -> None
def add_output_path(self, path: ConfigPath, domain: str) -> None:
self.output_paths.append((path, domain))
def remove_output_path(self, path, domain):
# type: (ConfigPath, str) -> None
def remove_output_path(self, path: ConfigPath, domain: str) -> None:
self.output_paths.remove((path, domain))
def is_in_error_path(self, path):
# type: (ConfigPath) -> bool
def is_in_error_path(self, path: ConfigPath) -> bool:
for err in self.errors:
if _path_begins_with(err.path, path):
return True
@ -157,16 +152,16 @@ class Config(OrderedDict, fv.FinalValidateConfig):
conf = conf[key]
conf[path[-1]] = value
def get_error_for_path(self, path):
# type: (ConfigPath) -> Optional[vol.Invalid]
def get_error_for_path(self, path: ConfigPath) -> Optional[vol.Invalid]:
for err in self.errors:
if self.get_deepest_path(err.path) == path:
self.errors.remove(err)
return err
return None
def get_deepest_document_range_for_path(self, path, get_key=False):
# type: (ConfigPath, bool) -> Optional[ESPHomeDataBase]
def get_deepest_document_range_for_path(
self, path: ConfigPath, get_key: bool = False
) -> Optional[ESPHomeDataBase]:
data = self
doc_range = None
for index, path_item in enumerate(path):
@ -207,8 +202,7 @@ class Config(OrderedDict, fv.FinalValidateConfig):
return {}
return data
def get_deepest_path(self, path):
# type: (ConfigPath) -> ConfigPath
def get_deepest_path(self, path: ConfigPath) -> ConfigPath:
"""Return the path that is the deepest reachable by following path."""
data = self
part = []
@ -532,7 +526,7 @@ class IDPassValidationStep(ConfigValidationStep):
# because the component that did not validate doesn't have any IDs set
return
searching_ids = [] # type: List[Tuple[core.ID, ConfigPath]]
searching_ids: list[tuple[core.ID, ConfigPath]] = []
for id, path in iter_ids(result):
if id.is_declaration:
if id.id is not None:
@ -780,8 +774,7 @@ def _get_parent_name(path, config):
return path[-1]
def _format_vol_invalid(ex, config):
# type: (vol.Invalid, Config) -> str
def _format_vol_invalid(ex: vol.Invalid, config: Config) -> str:
message = ""
paren = _get_parent_name(ex.path[:-1], config)
@ -862,8 +855,9 @@ def _print_on_next_line(obj):
return False
def dump_dict(config, path, at_root=True):
# type: (Config, ConfigPath, bool) -> Tuple[str, bool]
def dump_dict(
config: Config, path: ConfigPath, at_root: bool = True
) -> tuple[str, bool]:
conf = config.get_nested_item(path)
ret = ""
multiline = False

View File

@ -5,8 +5,7 @@ from esphome.core import CORE
from esphome.helpers import read_file
def read_config_file(path):
# type: (str) -> str
def read_config_file(path: str) -> str:
if CORE.vscode and (
not CORE.ace or os.path.abspath(path) == os.path.abspath(CORE.config_path)
):

View File

@ -2,7 +2,7 @@ import logging
import math
import os
import re
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Union
from typing import TYPE_CHECKING, Optional, Union
from esphome.const import (
CONF_COMMENT,
@ -469,19 +469,19 @@ class EsphomeCore:
# Task counter for pending tasks
self.task_counter = 0
# The variable cache, for each ID this holds a MockObj of the variable obj
self.variables: Dict[str, "MockObj"] = {}
self.variables: dict[str, "MockObj"] = {}
# A list of statements that go in the main setup() block
self.main_statements: List["Statement"] = []
self.main_statements: list["Statement"] = []
# A list of statements to insert in the global block (includes and global variables)
self.global_statements: List["Statement"] = []
self.global_statements: list["Statement"] = []
# A set of platformio libraries to add to the project
self.libraries: List[Library] = []
self.libraries: list[Library] = []
# A set of build flags to set in the platformio project
self.build_flags: Set[str] = set()
self.build_flags: set[str] = set()
# A set of defines to set for the compile process in esphome/core/defines.h
self.defines: Set["Define"] = set()
self.defines: set["Define"] = set()
# A map of all platformio options to apply
self.platformio_options: Dict[str, Union[str, List[str]]] = {}
self.platformio_options: dict[str, Union[str, list[str]]] = {}
# A set of strings of names of loaded integrations, used to find namespace ID conflicts
self.loaded_integrations = set()
# A set of component IDs to track what Component subclasses are declared
@ -701,7 +701,7 @@ class EsphomeCore:
_LOGGER.debug("Adding define: %s", define)
return define
def add_platformio_option(self, key: str, value: Union[str, List[str]]) -> None:
def add_platformio_option(self, key: str, value: Union[str, list[str]]) -> None:
new_val = value
old_val = self.platformio_options.get(key)
if isinstance(old_val, list):
@ -734,7 +734,7 @@ class EsphomeCore:
_LOGGER.debug("Waiting for variable %s", id)
yield
async def get_variable_with_full_id(self, id: ID) -> Tuple[ID, "MockObj"]:
async def get_variable_with_full_id(self, id: ID) -> tuple[ID, "MockObj"]:
if not isinstance(id, ID):
raise ValueError(f"ID {id!r} must be of type ID!")
return await _FakeAwaitable(self._get_variable_with_full_id_generator(id))

View File

@ -48,7 +48,8 @@ import heapq
import inspect
import logging
import types
from typing import Any, Awaitable, Callable, Generator, Iterator, List, Tuple
from typing import Any, Callable
from collections.abc import Awaitable, Generator, Iterator
_LOGGER = logging.getLogger(__name__)
@ -177,7 +178,7 @@ class _Task:
return _Task(priority, self.id_number, self.iterator, self.original_function)
@property
def _cmp_tuple(self) -> Tuple[float, int]:
def _cmp_tuple(self) -> tuple[float, int]:
return (-self.priority, self.id_number)
def __eq__(self, other):
@ -194,7 +195,7 @@ class FakeEventLoop:
"""Emulate an asyncio EventLoop to run some registered coroutine jobs in sequence."""
def __init__(self):
self._pending_tasks: List[_Task] = []
self._pending_tasks: list[_Task] = []
self._task_counter = 0
def add_job(self, func, *args, **kwargs):

View File

@ -8,14 +8,10 @@ from esphome.yaml_util import ESPHomeDataBase
from typing import (
Any,
Callable,
Generator,
List,
Optional,
Tuple,
Type,
Union,
Sequence,
)
from collections.abc import Generator, Sequence
from esphome.core import ( # noqa
CORE,
@ -54,9 +50,9 @@ SafeExpType = Union[
int,
float,
TimePeriod,
Type[bool],
Type[int],
Type[float],
type[bool],
type[int],
type[float],
Sequence[Any],
]
@ -150,7 +146,7 @@ class CallExpression(Expression):
class StructInitializer(Expression):
__slots__ = ("base", "args")
def __init__(self, base: Expression, *args: Tuple[str, Optional[SafeExpType]]):
def __init__(self, base: Expression, *args: tuple[str, Optional[SafeExpType]]):
self.base = base
# TODO: args is always a Tuple, is this check required?
if not isinstance(args, OrderedDict):
@ -210,7 +206,7 @@ class ParameterListExpression(Expression):
__slots__ = ("parameters",)
def __init__(
self, *parameters: Union[ParameterExpression, Tuple[SafeExpType, str]]
self, *parameters: Union[ParameterExpression, tuple[SafeExpType, str]]
):
self.parameters = []
for parameter in parameters:
@ -629,7 +625,7 @@ def add_define(name: str, value: SafeExpType = None):
CORE.add_define(Define(name, safe_exp(value)))
def add_platformio_option(key: str, value: Union[str, List[str]]):
def add_platformio_option(key: str, value: Union[str, list[str]]):
CORE.add_platformio_option(key, value)
@ -646,7 +642,7 @@ async def get_variable(id_: ID) -> "MockObj":
return await CORE.get_variable(id_)
async def get_variable_with_full_id(id_: ID) -> Tuple[ID, "MockObj"]:
async def get_variable_with_full_id(id_: ID) -> tuple[ID, "MockObj"]:
"""
Wait for the given ID to be defined in the code generation and
return it as a MockObj.
@ -661,7 +657,7 @@ async def get_variable_with_full_id(id_: ID) -> Tuple[ID, "MockObj"]:
async def process_lambda(
value: Lambda,
parameters: List[Tuple[SafeExpType, str]],
parameters: list[tuple[SafeExpType, str]],
capture: str = "=",
return_type: SafeExpType = None,
) -> Generator[LambdaExpression, None, None]:
@ -715,7 +711,7 @@ def is_template(value):
async def templatable(
value: Any,
args: List[Tuple[SafeExpType, str]],
args: list[tuple[SafeExpType, str]],
output_type: Optional[SafeExpType],
to_exp: Any = None,
):
@ -763,7 +759,7 @@ class MockObj(Expression):
attr = attr[1:]
return MockObj(f"{self.base}{self.op}{attr}", next_op)
def __call__(self, *args): # type: (SafeExpType) -> MockObj
def __call__(self, *args: SafeExpType) -> "MockObj":
call = CallExpression(self.base, *args)
return MockObj(call, self.op)

View File

@ -107,8 +107,9 @@ async def setup_entity(var, config):
add(var.set_entity_category(config[CONF_ENTITY_CATEGORY]))
def extract_registry_entry_config(registry, full_config):
# type: (Registry, ConfigType) -> RegistryEntry
def extract_registry_entry_config(
registry: Registry, full_config: ConfigType
) -> RegistryEntry:
key, config = next((k, v) for k, v in full_config.items() if k in registry)
return registry[key], config

View File

@ -522,7 +522,7 @@ class DashboardEntry:
return os.path.basename(self.path)
@property
def storage(self): # type: () -> Optional[StorageJSON]
def storage(self) -> Optional[StorageJSON]:
if not self._loaded_storage:
self._storage = StorageJSON.load(
ext_storage_path(settings.config_dir, self.filename)
@ -817,7 +817,7 @@ class UndoDeleteRequestHandler(BaseHandler):
shutil.move(os.path.join(trash_path, configuration), config_file)
PING_RESULT = {} # type: dict
PING_RESULT: dict = {}
IMPORT_RESULT = {}
STOP_EVENT = threading.Event()
PING_REQUEST = threading.Event()
@ -933,7 +933,7 @@ def get_static_path(*args):
return os.path.join(get_base_frontend_path(), "static", *args)
@functools.lru_cache(maxsize=None)
@functools.cache
def get_static_file_url(name):
base = f"./static/{name}"

View File

@ -1,5 +1,5 @@
from abc import ABC, abstractmethod
from typing import Dict, Any
from typing import Any
import contextvars
from esphome.types import ConfigFragmentType, ID, ConfigPathType
@ -9,7 +9,7 @@ import esphome.config_validation as cv
class FinalValidateConfig(ABC):
@property
@abstractmethod
def data(self) -> Dict[str, Any]:
def data(self) -> dict[str, Any]:
"""A dictionary that can be used by post validation functions to store
global data during the validation phase. Each component should store its
data under a unique key

View File

@ -40,7 +40,7 @@ def indent(text, padding=" "):
# From https://stackoverflow.com/a/14945195/8924614
def cpp_string_escape(string, encoding="utf-8"):
def _should_escape(byte): # type: (int) -> bool
def _should_escape(byte: int) -> bool:
if not 32 <= byte < 127:
return True
if byte in (ord("\\"), ord('"')):

View File

@ -1,5 +1,5 @@
import logging
from typing import Callable, List, Optional, Any, ContextManager
from typing import Callable, Optional, Any, ContextManager
from types import ModuleType
import importlib
import importlib.util
@ -62,19 +62,19 @@ class ComponentManifest:
return getattr(self.module, "to_code", None)
@property
def dependencies(self) -> List[str]:
def dependencies(self) -> list[str]:
return getattr(self.module, "DEPENDENCIES", [])
@property
def conflicts_with(self) -> List[str]:
def conflicts_with(self) -> list[str]:
return getattr(self.module, "CONFLICTS_WITH", [])
@property
def auto_load(self) -> List[str]:
def auto_load(self) -> list[str]:
return getattr(self.module, "AUTO_LOAD", [])
@property
def codeowners(self) -> List[str]:
def codeowners(self) -> list[str]:
return getattr(self.module, "CODEOWNERS", [])
@property
@ -87,7 +87,7 @@ class ComponentManifest:
return getattr(self.module, "FINAL_VALIDATE_SCHEMA", None)
@property
def resources(self) -> List[FileResource]:
def resources(self) -> list[FileResource]:
"""Return a list of all file resources defined in the package of this component.
This will return all cpp source files that are located in the same folder as the
@ -106,7 +106,7 @@ class ComponentManifest:
class ComponentMetaFinder(importlib.abc.MetaPathFinder):
def __init__(
self, components_path: Path, allowed_components: Optional[List[str]] = None
self, components_path: Path, allowed_components: Optional[list[str]] = None
) -> None:
self._allowed_components = allowed_components
self._finders = []
@ -117,7 +117,7 @@ class ComponentMetaFinder(importlib.abc.MetaPathFinder):
continue
self._finders.append(finder)
def find_spec(self, fullname: str, path: Optional[List[str]], target=None):
def find_spec(self, fullname: str, path: Optional[list[str]], target=None):
if not fullname.startswith("esphome.components."):
return None
parts = fullname.split(".")
@ -144,7 +144,7 @@ def clear_component_meta_finders():
def install_meta_finder(
components_path: Path, allowed_components: Optional[List[str]] = None
components_path: Path, allowed_components: Optional[list[str]] = None
):
sys.meta_path.insert(0, ComponentMetaFinder(components_path, allowed_components))

View File

@ -1,6 +1,6 @@
from dataclasses import dataclass
import json
from typing import List, Union
from typing import Union
from pathlib import Path
import logging
@ -310,7 +310,7 @@ class IDEData:
return str(Path(self.firmware_elf_path).with_suffix(".bin"))
@property
def extra_flash_images(self) -> List[FlashImage]:
def extra_flash_images(self) -> list[FlashImage]:
return [
FlashImage(path=entry["path"], offset=entry["offset"])
for entry in self.raw["extra"]["flash_images"]

View File

@ -4,7 +4,7 @@ from datetime import datetime
import json
import logging
import os
from typing import Any, Optional, List
from typing import Optional
from esphome import const
from esphome.core import CORE
@ -15,19 +15,19 @@ from esphome.types import CoreType
_LOGGER = logging.getLogger(__name__)
def storage_path(): # type: () -> str
def storage_path() -> str:
return CORE.relative_internal_path(f"{CORE.config_filename}.json")
def ext_storage_path(base_path, config_filename): # type: (str, str) -> str
def ext_storage_path(base_path: str, config_filename: str) -> str:
return os.path.join(base_path, ".esphome", f"{config_filename}.json")
def esphome_storage_path(base_path): # type: (str) -> str
def esphome_storage_path(base_path: str) -> str:
return os.path.join(base_path, ".esphome", "esphome.json")
def trash_storage_path(base_path): # type: (str) -> str
def trash_storage_path(base_path: str) -> str:
return os.path.join(base_path, ".esphome", "trash")
@ -49,29 +49,29 @@ class StorageJSON:
):
# Version of the storage JSON schema
assert storage_version is None or isinstance(storage_version, int)
self.storage_version = storage_version # type: int
self.storage_version: int = storage_version
# The name of the node
self.name = name # type: str
self.name: str = name
# The comment of the node
self.comment = comment # type: str
self.comment: str = comment
# The esphome version this was compiled with
self.esphome_version = esphome_version # type: str
self.esphome_version: str = esphome_version
# The version of the file in src/main.cpp - Used to migrate the file
assert src_version is None or isinstance(src_version, int)
self.src_version = src_version # type: int
self.src_version: int = src_version
# Address of the ESP, for example livingroom.local or a static IP
self.address = address # type: str
self.address: str = address
# Web server port of the ESP, for example 80
assert web_port is None or isinstance(web_port, int)
self.web_port = web_port # type: int
self.web_port: int = web_port
# The type of hardware in use, like "ESP32", "ESP32C3", "ESP8266", etc.
self.target_platform = target_platform # type: str
self.target_platform: str = target_platform
# The absolute path to the platformio project
self.build_path = build_path # type: str
self.build_path: str = build_path
# The absolute path to the firmware binary
self.firmware_bin_path = firmware_bin_path # type: str
self.firmware_bin_path: str = firmware_bin_path
# A list of strings of names of loaded integrations
self.loaded_integrations = loaded_integrations # type: List[str]
self.loaded_integrations: list[str] = loaded_integrations
self.loaded_integrations.sort()
def as_dict(self):
@ -97,8 +97,8 @@ class StorageJSON:
@staticmethod
def from_esphome_core(
esph, old
): # type: (CoreType, Optional[StorageJSON]) -> StorageJSON
esph: CoreType, old: Optional["StorageJSON"]
) -> "StorageJSON":
hardware = esph.target_platform.upper()
if esph.is_esp32:
from esphome.components import esp32
@ -135,7 +135,7 @@ class StorageJSON:
)
@staticmethod
def _load_impl(path): # type: (str) -> Optional[StorageJSON]
def _load_impl(path: str) -> Optional["StorageJSON"]:
with codecs.open(path, "r", encoding="utf-8") as f_handle:
storage = json.load(f_handle)
storage_version = storage["storage_version"]
@ -166,13 +166,13 @@ class StorageJSON:
)
@staticmethod
def load(path): # type: (str) -> Optional[StorageJSON]
def load(path: str) -> Optional["StorageJSON"]:
try:
return StorageJSON._load_impl(path)
except Exception: # pylint: disable=broad-except
return None
def __eq__(self, o): # type: (Any) -> bool
def __eq__(self, o) -> bool:
return isinstance(o, StorageJSON) and self.as_dict() == o.as_dict()
@ -182,15 +182,15 @@ class EsphomeStorageJSON:
):
# Version of the storage JSON schema
assert storage_version is None or isinstance(storage_version, int)
self.storage_version = storage_version # type: int
self.storage_version: int = storage_version
# The cookie secret for the dashboard
self.cookie_secret = cookie_secret # type: str
self.cookie_secret: str = cookie_secret
# The last time ESPHome checked for an update as an isoformat encoded str
self.last_update_check_str = last_update_check # type: str
self.last_update_check_str: str = last_update_check
# Cache of the version gotten in the last version check
self.remote_version = remote_version # type: Optional[str]
self.remote_version: Optional[str] = remote_version
def as_dict(self): # type: () -> dict
def as_dict(self) -> dict:
return {
"storage_version": self.storage_version,
"cookie_secret": self.cookie_secret,
@ -199,24 +199,24 @@ class EsphomeStorageJSON:
}
@property
def last_update_check(self): # type: () -> Optional[datetime]
def last_update_check(self) -> Optional[datetime]:
try:
return datetime.strptime(self.last_update_check_str, "%Y-%m-%dT%H:%M:%S")
except Exception: # pylint: disable=broad-except
return None
@last_update_check.setter
def last_update_check(self, new): # type: (datetime) -> None
def last_update_check(self, new: datetime) -> None:
self.last_update_check_str = new.strftime("%Y-%m-%dT%H:%M:%S")
def to_json(self): # type: () -> dict
def to_json(self) -> dict:
return f"{json.dumps(self.as_dict(), indent=2)}\n"
def save(self, path): # type: (str) -> None
def save(self, path: str) -> None:
write_file_if_changed(path, self.to_json())
@staticmethod
def _load_impl(path): # type: (str) -> Optional[EsphomeStorageJSON]
def _load_impl(path: str) -> Optional["EsphomeStorageJSON"]:
with codecs.open(path, "r", encoding="utf-8") as f_handle:
storage = json.load(f_handle)
storage_version = storage["storage_version"]
@ -228,14 +228,14 @@ class EsphomeStorageJSON:
)
@staticmethod
def load(path): # type: (str) -> Optional[EsphomeStorageJSON]
def load(path: str) -> Optional["EsphomeStorageJSON"]:
try:
return EsphomeStorageJSON._load_impl(path)
except Exception: # pylint: disable=broad-except
return None
@staticmethod
def get_default(): # type: () -> EsphomeStorageJSON
def get_default() -> "EsphomeStorageJSON":
return EsphomeStorageJSON(
storage_version=1,
cookie_secret=binascii.hexlify(os.urandom(64)).decode(),
@ -243,5 +243,5 @@ class EsphomeStorageJSON:
remote_version=None,
)
def __eq__(self, o): # type: (Any) -> bool
def __eq__(self, o) -> bool:
return isinstance(o, EsphomeStorageJSON) and self.as_dict() == o.as_dict()

View File

@ -1,5 +1,5 @@
"""This helper module tracks commonly used types in the esphome python codebase."""
from typing import Dict, Union, List
from typing import Union
from esphome.core import ID, Lambda, EsphomeCore
@ -8,11 +8,11 @@ ConfigFragmentType = Union[
int,
float,
None,
Dict[Union[str, int], "ConfigFragmentType"],
List["ConfigFragmentType"],
dict[Union[str, int], "ConfigFragmentType"],
list["ConfigFragmentType"],
ID,
Lambda,
]
ConfigType = Dict[str, ConfigFragmentType]
ConfigType = dict[str, ConfigFragmentType]
CoreType = EsphomeCore
ConfigPathType = Union[str, int]

View File

@ -1,5 +1,4 @@
import typing
from typing import Union, List
from typing import Union
import collections
import io
@ -242,7 +241,7 @@ def is_dev_esphome_version():
return "dev" in const.__version__
def parse_esphome_version() -> typing.Tuple[int, int, int]:
def parse_esphome_version() -> tuple[int, int, int]:
match = re.match(r"^(\d+).(\d+).(\d+)(-dev\d*|b\d*)?$", const.__version__)
if match is None:
raise ValueError(f"Failed to parse ESPHome version '{const.__version__}'")
@ -282,7 +281,7 @@ class SerialPort:
# from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py
def get_serial_ports() -> List[SerialPort]:
def get_serial_ports() -> list[SerialPort]:
from serial.tools.list_ports import comports
result = []

View File

@ -10,15 +10,13 @@ import esphome.config_validation as cv
from typing import Optional
def _get_invalid_range(res, invalid):
# type: (Config, cv.Invalid) -> Optional[DocumentRange]
def _get_invalid_range(res: Config, invalid: cv.Invalid) -> Optional[DocumentRange]:
return res.get_deepest_document_range_for_path(
invalid.path, invalid.error_message == "extra keys not allowed"
)
def _dump_range(range):
# type: (Optional[DocumentRange]) -> Optional[dict]
def _dump_range(range: Optional[DocumentRange]) -> Optional[dict]:
if range is None:
return None
return {

View File

@ -2,7 +2,7 @@ import logging
import os
import re
from pathlib import Path
from typing import Dict, List, Union
from typing import Union
from esphome.config import iter_components
from esphome.const import (
@ -98,7 +98,7 @@ def replace_file_content(text, pattern, repl):
return content_new, count
def storage_should_clean(old, new): # type: (StorageJSON, StorageJSON) -> bool
def storage_should_clean(old: StorageJSON, new: StorageJSON) -> bool:
if old is None:
return True
@ -123,7 +123,7 @@ def update_storage_json():
new.save(path)
def format_ini(data: Dict[str, Union[str, List[str]]]) -> str:
def format_ini(data: dict[str, Union[str, list[str]]]) -> str:
content = ""
for key, value in sorted(data.items()):
if isinstance(value, list):
@ -226,7 +226,7 @@ the custom_components folder or the external_components feature.
def copy_src_tree():
source_files: List[loader.FileResource] = []
source_files: list[loader.FileResource] = []
for _, component, _ in iter_components(CORE.config):
source_files += component.resources
source_files_map = {

View File

@ -1,7 +1,7 @@
import socket
import threading
import time
from typing import Dict, Optional
from typing import Optional
import logging
from dataclasses import dataclass
@ -71,12 +71,12 @@ class DashboardStatus(threading.Thread):
threading.Thread.__init__(self)
self.zc = zc
self.query_hosts: set[str] = set()
self.key_to_host: Dict[str, str] = {}
self.key_to_host: dict[str, str] = {}
self.stop_event = threading.Event()
self.query_event = threading.Event()
self.on_update = on_update
def request_query(self, hosts: Dict[str, str]) -> None:
def request_query(self, hosts: dict[str, str]) -> None:
self.query_hosts = set(hosts.values())
self.key_to_host = hosts
self.query_event.set()

View File

@ -1,3 +1,3 @@
[tool.black]
target-version = ["py36", "py37", "py38"]
target-version = ["py39", "py310"]
exclude = 'generated'

View File

@ -109,7 +109,7 @@ def main():
print_error(file_, linno, msg)
errors += 1
PYUPGRADE_TARGET = "--py38-plus"
PYUPGRADE_TARGET = "--py39-plus"
cmd = ["pyupgrade", PYUPGRADE_TARGET] + files
print()
print("Running pyupgrade...")

View File

@ -74,7 +74,7 @@ setup(
zip_safe=False,
platforms="any",
test_suite="tests",
python_requires=">=3.8,<4.0",
python_requires=">=3.9.0",
install_requires=REQUIRES,
keywords=["home", "automation"],
entry_points={"console_scripts": ["esphome = esphome.__main__:main"]},

View File

@ -1,12 +1,9 @@
from typing import Text
import hypothesis.strategies._internal.core as st
from hypothesis.strategies._internal.strategies import SearchStrategy
@st.defines_strategy(force_reusable_values=True)
def mac_addr_strings():
# type: () -> SearchStrategy[Text]
def mac_addr_strings() -> SearchStrategy[str]:
"""A strategy for MAC address strings.
This consists of six strings representing integers [0..255],