1
0
mirror of https://github.com/esphome/esphome.git synced 2025-03-14 06:38:17 +00:00

Add cv.deprecated and cv.removed to config validation

These are adapted from HA core

Currently we remove keys from YAML which breaks user configurations
without any notice.

In HA we mark these as deprecated or removed which gives users
time to adapt.

Example https://github.com/esphome/esphome/pull/7770/files#diff-8d5bfacdc6c7d62624eeb1029bdcec74878e95cf16fc5e421c2fb2c87b879680L75
This commit is contained in:
J. Nick Koston 2025-02-19 10:26:44 -06:00
parent 3b6eede039
commit b74a3f9d36
No known key found for this signature in database

112
esphome/frame.py Normal file
View File

@ -0,0 +1,112 @@
from __future__ import annotations
from dataclasses import dataclass
from functools import cached_property
import linecache
import logging
import sys
from types import FrameType
@dataclass(kw_only=True)
class ComponentFrame:
"""Component frame container."""
custom_component: bool
component: str
module: str | None
relative_filename: str
frame: FrameType
@cached_property
def line_number(self) -> int:
"""Return the line number of the frame."""
return self.frame.f_lineno
@cached_property
def filename(self) -> str:
"""Return the filename of the frame."""
return self.frame.f_code.co_filename
@cached_property
def line(self) -> str:
"""Return the line of the frame."""
return (linecache.getline(self.filename, self.line_number) or "?").strip()
def get_current_frame(depth: int = 0) -> FrameType:
"""Return the current frame."""
# Add one to depth since get_current_frame is included
return sys._getframe(depth + 1) # noqa: SLF001
class MissingComponentFrame(Exception):
"""Raised when no component is found in the frame."""
def get_component_frame(exclude_components: set | None = None) -> ComponentFrame:
"""Return the frame, component and component path of the current stack frame."""
found_frame = None
if not exclude_components:
exclude_components = set()
frame: FrameType | None = get_current_frame()
while frame is not None:
filename = frame.f_code.co_filename
for path in ("custom_components/", "esphome/components/"):
try:
index = filename.index(path)
start = index + len(path)
end = filename.index("/", start)
component = filename[start:end]
if component not in exclude_components:
found_frame = frame
break
except ValueError:
continue
if found_frame is not None:
break
frame = frame.f_back
if found_frame is None:
raise MissingComponentFrame
found_module: str | None = None
for module, module_obj in dict(sys.modules).items():
if not hasattr(module_obj, "__file__"):
continue
if module_obj.__file__ == found_frame.f_code.co_filename:
found_module = module
break
return ComponentFrame(
custom_component=path == "custom_components/",
component=component,
module=found_module,
relative_filename=found_frame.f_code.co_filename[index:],
frame=found_frame,
)
def get_component_logger(fallback_name: str) -> logging.Logger:
"""Return a logger by checking the current component frame.
If Python is unable to access the sources files, the call stack frame
will be missing information, so let's guard by requiring a fallback name.
https://github.com/home-assistant/core/issues/24982
"""
try:
component_frame = get_component_frame()
except MissingComponentFrame:
return logging.getLogger(fallback_name)
if component_frame.custom_component:
logger_name = f"custom_components.{component_frame.component}"
else:
logger_name = f"esphome.components.{component_frame.component}"
return logging.getLogger(logger_name)