Add CODEOWNERS mechanism (#1199)

This commit is contained in:
Otto Winter 2020-07-25 15:57:18 +02:00 committed by GitHub
parent 5887fe8302
commit 4996967c79
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 195 additions and 8 deletions

View file

@ -143,6 +143,8 @@ jobs:
run: script/ci-custom.py
- name: Lint Python
run: script/lint-python
- name: Lint CODEOWNERS
run: script/build_codeowners.py --check
test:
runs-on: ubuntu-latest

View file

@ -98,6 +98,8 @@ jobs:
run: script/ci-custom.py
- name: Lint Python
run: script/lint-python
- name: Lint CODEOWNERS
run: script/build_codeowners.py --check
test:
runs-on: ubuntu-latest

View file

@ -97,6 +97,8 @@ jobs:
run: script/ci-custom.py
- name: Lint Python
run: script/lint-python
- name: Lint CODEOWNERS
run: script/build_codeowners.py --check
test:
runs-on: ubuntu-latest

54
CODEOWNERS Normal file
View file

@ -0,0 +1,54 @@
# This file is generated by script/build_codeowners.py
# People marked here will be automatically requested for a review
# when the code that they own is touched.
#
# Every time an issue is created with a label corresponding to an integration,
# the integration's code owner is automatically notified.
# Core Code
setup.py @esphome/core
esphome/*.py @esphome/core
esphome/core/* @esphome/core
# Integrations
esphome/components/adc/* @esphome/core
esphome/components/api/* @OttoWinter
esphome/components/async_tcp/* @OttoWinter
esphome/components/bang_bang/* @OttoWinter
esphome/components/binary_sensor/* @esphome/core
esphome/components/captive_portal/* @OttoWinter
esphome/components/climate/* @esphome/core
esphome/components/cover/* @esphome/core
esphome/components/debug/* @OttoWinter
esphome/components/dht/* @OttoWinter
esphome/components/exposure_notifications/* @OttoWinter
esphome/components/fastled_base/* @OttoWinter
esphome/components/globals/* @esphome/core
esphome/components/gpio/* @esphome/core
esphome/components/homeassistant/* @OttoWinter
esphome/components/i2c/* @esphome/core
esphome/components/integration/* @OttoWinter
esphome/components/interval/* @esphome/core
esphome/components/json/* @OttoWinter
esphome/components/ledc/* @OttoWinter
esphome/components/light/* @esphome/core
esphome/components/logger/* @esphome/core
esphome/components/network/* @esphome/core
esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core
esphome/components/pid/* @OttoWinter
esphome/components/pn532/* @OttoWinter
esphome/components/power_supply/* @esphome/core
esphome/components/restart/* @esphome/core
esphome/components/script/* @esphome/core
esphome/components/sensor/* @esphome/core
esphome/components/shutdown/* @esphome/core
esphome/components/spi/* @esphome/core
esphome/components/substitutions/* @esphome/core
esphome/components/sun/* @OttoWinter
esphome/components/switch/* @esphome/core
esphome/components/time/* @OttoWinter
esphome/components/uart/* @esphome/core
esphome/components/ultrasonic/* @OttoWinter
esphome/components/version/* @esphome/core
esphome/components/web_server_base/* @OttoWinter

View file

@ -0,0 +1 @@
CODEOWNERS = ['@esphome/core']

View file

@ -8,6 +8,7 @@ from esphome.core import coroutine_with_priority
DEPENDENCIES = ['network']
AUTO_LOAD = ['async_tcp']
CODEOWNERS = ['@OttoWinter']
api_ns = cg.esphome_ns.namespace('api')
APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller)

View file

@ -2,6 +2,8 @@
import esphome.codegen as cg
from esphome.core import CORE, coroutine_with_priority
CODEOWNERS = ['@OttoWinter']
@coroutine_with_priority(200.0)
def to_code(config):

View file

@ -0,0 +1 @@
CODEOWNERS = ['@OttoWinter']

View file

@ -11,6 +11,7 @@ from esphome.const import CONF_DEVICE_CLASS, CONF_FILTERS, \
from esphome.core import CORE, coroutine, coroutine_with_priority
from esphome.util import Registry
CODEOWNERS = ['@esphome/core']
DEVICE_CLASSES = [
'', 'battery', 'cold', 'connectivity', 'door', 'garage_door', 'gas',
'heat', 'light', 'lock', 'moisture', 'motion', 'moving', 'occupancy',

View file

@ -7,6 +7,7 @@ from esphome.core import coroutine_with_priority
AUTO_LOAD = ['web_server_base']
DEPENDENCIES = ['wifi']
CODEOWNERS = ['@OttoWinter']
captive_portal_ns = cg.esphome_ns.namespace('captive_portal')
CaptivePortal = captive_portal_ns.class_('CaptivePortal', cg.Component)

View file

@ -10,6 +10,7 @@ from esphome.core import CORE, coroutine, coroutine_with_priority
IS_PLATFORM_COMPONENT = True
CODEOWNERS = ['@esphome/core']
climate_ns = cg.esphome_ns.namespace('climate')
Climate = climate_ns.class_('Climate', cg.Nameable)

View file

@ -9,6 +9,7 @@ from esphome.core import CORE, coroutine, coroutine_with_priority
IS_PLATFORM_COMPONENT = True
CODEOWNERS = ['@esphome/core']
DEVICE_CLASSES = [
'', 'awning', 'blind', 'curtain', 'damper', 'door', 'garage',
'gate', 'shade', 'shutter', 'window'

View file

@ -2,6 +2,7 @@ import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import CONF_ID
CODEOWNERS = ['@OttoWinter']
DEPENDENCIES = ['logger']
debug_ns = cg.esphome_ns.namespace('debug')

View file

@ -0,0 +1 @@
CODEOWNERS = ['@OttoWinter']

View file

@ -4,6 +4,7 @@ import esphome.config_validation as cv
from esphome.components import esp32_ble_tracker
from esphome.const import CONF_TRIGGER_ID
CODEOWNERS = ['@OttoWinter']
DEPENDENCIES = ['esp32_ble_tracker']
exposure_notifications_ns = cg.esphome_ns.namespace('exposure_notifications')

View file

@ -4,6 +4,7 @@ from esphome.components import light
from esphome.const import CONF_OUTPUT_ID, CONF_NUM_LEDS, CONF_RGB_ORDER, CONF_MAX_REFRESH_RATE
from esphome.core import coroutine
CODEOWNERS = ['@OttoWinter']
fastled_base_ns = cg.esphome_ns.namespace('fastled_base')
FastLEDLightOutput = fastled_base_ns.class_('FastLEDLightOutput', light.AddressableLight)

View file

@ -5,6 +5,7 @@ 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
CODEOWNERS = ['@esphome/core']
globals_ns = cg.esphome_ns.namespace('globals')
GlobalsComponent = globals_ns.class_('GlobalsComponent', cg.Component)
GlobalVarSetAction = globals_ns.class_('GlobalVarSetAction', automation.Action)

View file

@ -1,3 +1,4 @@
import esphome.codegen as cg
CODEOWNERS = ['@esphome/core']
gpio_ns = cg.esphome_ns.namespace('gpio')

View file

@ -1,3 +1,4 @@
import esphome.codegen as cg
CODEOWNERS = ['@OttoWinter']
homeassistant_ns = cg.esphome_ns.namespace('homeassistant')

View file

@ -5,6 +5,7 @@ from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_SCAN, CONF_SCL, CONF_SDA
CONF_I2C_ID
from esphome.core import coroutine, coroutine_with_priority
CODEOWNERS = ['@esphome/core']
i2c_ns = cg.esphome_ns.namespace('i2c')
I2CComponent = i2c_ns.class_('I2CComponent', cg.Component)
I2CDevice = i2c_ns.class_('I2CDevice')

View file

@ -0,0 +1 @@
CODEOWNERS = ['@OttoWinter']

View file

@ -3,6 +3,7 @@ import esphome.config_validation as cv
from esphome import automation
from esphome.const import CONF_ID, CONF_INTERVAL
CODEOWNERS = ['@esphome/core']
interval_ns = cg.esphome_ns.namespace('interval')
IntervalTrigger = interval_ns.class_('IntervalTrigger', automation.Trigger.template(),
cg.PollingComponent)

View file

@ -1,6 +1,7 @@
import esphome.codegen as cg
from esphome.core import coroutine_with_priority
CODEOWNERS = ['@OttoWinter']
json_ns = cg.esphome_ns.namespace('json')

View file

@ -0,0 +1 @@
CODEOWNERS = ['@OttoWinter']

View file

@ -14,6 +14,7 @@ from .types import ( # noqa
LightState, AddressableLightState, light_ns, LightOutput, AddressableLight, \
LightTurnOnTrigger, LightTurnOffTrigger)
CODEOWNERS = ['@esphome/core']
IS_PLATFORM_COMPONENT = True
LightRestoreMode = light_ns.enum('LightRestoreMode')

View file

@ -8,6 +8,7 @@ from esphome.const import CONF_ARGS, CONF_BAUD_RATE, CONF_FORMAT, CONF_HARDWARE_
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
CODEOWNERS = ['@esphome/core']
logger_ns = cg.esphome_ns.namespace('logger')
LOG_LEVELS = {
'NONE': cg.global_ns.ESPHOME_LOG_LEVEL_NONE,

View file

@ -1 +1,2 @@
# Dummy package to allow components to depend on network
CODEOWNERS = ['@esphome/core']

View file

@ -3,6 +3,7 @@ import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_PASSWORD, CONF_PORT, CONF_SAFE_MODE
from esphome.core import CORE, coroutine_with_priority
CODEOWNERS = ['@esphome/core']
DEPENDENCIES = ['network']
ota_ns = cg.esphome_ns.namespace('ota')

View file

@ -7,6 +7,8 @@ from esphome.const import CONF_ID, CONF_INVERTED, CONF_LEVEL, CONF_MAX_POWER, \
CONF_MIN_POWER, CONF_POWER_SUPPLY
from esphome.core import CORE, coroutine
CODEOWNERS = ['@esphome/core']
IS_PLATFORM_COMPONENT = True
BINARY_OUTPUT_SCHEMA = cv.Schema({

View file

@ -0,0 +1 @@
CODEOWNERS = ['@OttoWinter']

View file

@ -4,6 +4,7 @@ from esphome import automation
from esphome.components import spi
from esphome.const import CONF_ID, CONF_ON_TAG, CONF_TRIGGER_ID
CODEOWNERS = ['@OttoWinter']
DEPENDENCIES = ['spi']
AUTO_LOAD = ['binary_sensor']
MULTI_CONF = True

View file

@ -3,6 +3,7 @@ import esphome.config_validation as cv
from esphome import pins
from esphome.const import CONF_ENABLE_TIME, CONF_ID, CONF_KEEP_ON_TIME, CONF_PIN
CODEOWNERS = ['@esphome/core']
power_supply_ns = cg.esphome_ns.namespace('power_supply')
PowerSupply = power_supply_ns.class_('PowerSupply', cg.Component)
MULTI_CONF = True

View file

@ -0,0 +1 @@
CODEOWNERS = ['@esphome/core']

View file

@ -4,6 +4,7 @@ from esphome import automation
from esphome.automation import maybe_simple_id
from esphome.const import CONF_ID, CONF_MODE
CODEOWNERS = ['@esphome/core']
script_ns = cg.esphome_ns.namespace('script')
Script = script_ns.class_('Script', automation.Trigger.template())
ScriptExecuteAction = script_ns.class_('ScriptExecuteAction', automation.Action)

View file

@ -12,6 +12,7 @@ from esphome.const import CONF_ABOVE, CONF_ACCURACY_DECIMALS, CONF_ALPHA, CONF_B
from esphome.core import CORE, coroutine, coroutine_with_priority
from esphome.util import Registry
CODEOWNERS = ['@esphome/core']
IS_PLATFORM_COMPONENT = True

View file

@ -0,0 +1 @@
CODEOWNERS = ['@esphome/core']

View file

@ -5,6 +5,7 @@ from esphome.const import CONF_CLK_PIN, CONF_ID, CONF_MISO_PIN, CONF_MOSI_PIN, C
CONF_CS_PIN
from esphome.core import coroutine, coroutine_with_priority
CODEOWNERS = ['@esphome/core']
spi_ns = cg.esphome_ns.namespace('spi')
SPIComponent = spi_ns.class_('SPIComponent', cg.Component)
SPIDevice = spi_ns.class_('SPIDevice')

View file

@ -5,6 +5,7 @@ import esphome.config_validation as cv
from esphome import core
from esphome.const import CONF_SUBSTITUTIONS
CODEOWNERS = ['@esphome/core']
_LOGGER = logging.getLogger(__name__)
VALID_SUBSTITUTIONS_CHARACTERS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' \

View file

@ -4,6 +4,7 @@ from esphome import automation
from esphome.components import time
from esphome.const import CONF_TIME_ID, CONF_ID, CONF_TRIGGER_ID
CODEOWNERS = ['@OttoWinter']
sun_ns = cg.esphome_ns.namespace('sun')
Sun = sun_ns.class_('Sun')

View file

@ -7,6 +7,7 @@ from esphome.const import CONF_ICON, CONF_ID, CONF_INTERNAL, CONF_INVERTED, CONF
CONF_ON_TURN_ON, CONF_TRIGGER_ID, CONF_MQTT_ID, CONF_NAME
from esphome.core import CORE, coroutine, coroutine_with_priority
CODEOWNERS = ['@esphome/core']
IS_PLATFORM_COMPONENT = True
switch_ns = cg.esphome_ns.namespace('switch_')

View file

@ -17,6 +17,7 @@ from esphome.core import coroutine, coroutine_with_priority
_LOGGER = logging.getLogger(__name__)
CODEOWNERS = ['@OttoWinter']
IS_PLATFORM_COMPONENT = True
time_ns = cg.esphome_ns.namespace('time')

View file

@ -5,6 +5,7 @@ from esphome.const import CONF_BAUD_RATE, CONF_ID, CONF_RX_PIN, CONF_TX_PIN, CON
CONF_DATA, CONF_RX_BUFFER_SIZE
from esphome.core import CORE, coroutine
CODEOWNERS = ['@esphome/core']
uart_ns = cg.esphome_ns.namespace('uart')
UARTComponent = uart_ns.class_('UARTComponent', cg.Component)
UARTDevice = uart_ns.class_('UARTDevice')

View file

@ -0,0 +1 @@
CODEOWNERS = ['@OttoWinter']

View file

@ -0,0 +1 @@
CODEOWNERS = ['@esphome/core']

View file

@ -3,6 +3,7 @@ import esphome.codegen as cg
from esphome.const import CONF_ID
from esphome.core import coroutine_with_priority, CORE
CODEOWNERS = ['@OttoWinter']
DEPENDENCIES = ['network']
AUTO_LOAD = ['async_tcp']

View file

@ -66,6 +66,10 @@ class ComponentManifest:
def auto_load(self):
return getattr(self.module, 'AUTO_LOAD', [])
@property
def codeowners(self) -> List[str]:
return getattr(self.module, 'CODEOWNERS', [])
def _get_flags_set(self, name, config):
if not hasattr(self.module, name):
return set()

View file

@ -2,6 +2,9 @@ import codecs
import logging
import os
from pathlib import Path
from typing import Union
import tempfile
_LOGGER = logging.getLogger(__name__)
@ -168,15 +171,21 @@ def read_file(path):
raise EsphomeError(f"Error reading file {path}: {err}")
def _write_file(path, text):
import tempfile
directory = os.path.dirname(path)
mkdir_p(directory)
def _write_file(path: Union[Path, str], text: Union[str, bytes]):
"""Atomically writes `text` to the given path.
tmp_path = None
Automatically creates all parent directories.
"""
if not isinstance(path, Path):
path = Path(path)
data = text
if isinstance(text, str):
data = text.encode()
directory = path.parent
directory.mkdir(exist_ok=True, parents=True)
tmp_path = None
try:
with tempfile.NamedTemporaryFile(mode="wb", dir=directory, delete=False) as f_handle:
tmp_path = f_handle.name
@ -193,7 +202,7 @@ def _write_file(path, text):
_LOGGER.error("Write file cleanup failed: %s", err)
def write_file(path, text):
def write_file(path: Union[Path, str], text: str):
try:
_write_file(path, text)
except OSError:
@ -201,9 +210,12 @@ def write_file(path, text):
raise EsphomeError(f"Could not write file at {path}")
def write_file_if_changed(path, text):
def write_file_if_changed(path: Union[Path, str], text: str):
if not isinstance(path, Path):
path = Path(path)
src_content = None
if os.path.isfile(path):
if path.is_file():
src_content = read_file(path)
if src_content != text:
write_file(path, text)

68
script/build_codeowners.py Executable file
View file

@ -0,0 +1,68 @@
#!/usr/bin/env python3
from pathlib import Path
import sys
import argparse
from esphome.helpers import write_file_if_changed
from esphome.config import get_component
from esphome.core import CORE
parser = argparse.ArgumentParser()
parser.add_argument('--check', help="Check if the CODEOWNERS file is up to date.",
action='store_true')
args = parser.parse_args()
# The root directory of the repo
root = Path(__file__).parent.parent
components_dir = root / 'esphome' / 'components'
BASE = """
# This file is generated by script/build_codeowners.py
# People marked here will be automatically requested for a review
# when the code that they own is touched.
#
# Every time an issue is created with a label corresponding to an integration,
# the integration's code owner is automatically notified.
# Core Code
setup.py @esphome/core
esphome/*.py @esphome/core
esphome/core/* @esphome/core
# Integrations
""".strip()
parts = [BASE]
# Fake some diretory so that get_component works
CORE.config_path = str(root)
for path in sorted(components_dir.iterdir()):
if not path.is_dir():
continue
if not (path / '__init__.py').is_file():
continue
name = path.name
comp = get_component(name)
if comp.codeowners:
for owner in comp.codeowners:
if not owner.startswith('@'):
print(f"Codeowner {owner} for integration {name} must start with an '@' symbol!")
sys.exit(1)
parts.append(f"esphome/components/{name}/* {' '.join(comp.codeowners)}")
# End newline
parts.append('')
content = '\n'.join(parts)
codeowners_file = root / 'CODEOWNERS'
if args.check:
if codeowners_file.read_text() != content:
print("CODEOWNERS file is not up to date.")
print("Please run `script/build_codeowners.py`")
sys.exit(1)
print("CODEOWNERS file is up to date")
else:
write_file_if_changed(codeowners_file, content)
print("Wrote CODEOWNERS")