diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d6798314f..ce6a120f7a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml index 896b6cddb4..c289c44afc 100644 --- a/.github/workflows/release-dev.yml +++ b/.github/workflows/release-dev.yml @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 651064047a..d13a6fd55d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000000..79b2d0503c --- /dev/null +++ b/CODEOWNERS @@ -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 diff --git a/esphome/components/adc/__init__.py b/esphome/components/adc/__init__.py index e69de29bb2..63db7aee2e 100644 --- a/esphome/components/adc/__init__.py +++ b/esphome/components/adc/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ['@esphome/core'] diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index eef60602ba..23cfa51d2b 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -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) diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index cf9d2f1585..8af33bc4ef 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -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): diff --git a/esphome/components/bang_bang/__init__.py b/esphome/components/bang_bang/__init__.py index e69de29bb2..6f14e10033 100644 --- a/esphome/components/bang_bang/__init__.py +++ b/esphome/components/bang_bang/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ['@OttoWinter'] diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 2b5dd302c4..8361ee8004 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -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', diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index 52885ae449..bb8b2198a1 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -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) diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 843b888218..38e254bb9d 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -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) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 85f7f746ba..f39e7ba540 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -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' diff --git a/esphome/components/debug/__init__.py b/esphome/components/debug/__init__.py index a40dadb5c2..d43b0de06a 100644 --- a/esphome/components/debug/__init__.py +++ b/esphome/components/debug/__init__.py @@ -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') diff --git a/esphome/components/dht/__init__.py b/esphome/components/dht/__init__.py index e69de29bb2..6f14e10033 100644 --- a/esphome/components/dht/__init__.py +++ b/esphome/components/dht/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ['@OttoWinter'] diff --git a/esphome/components/exposure_notifications/__init__.py b/esphome/components/exposure_notifications/__init__.py index de9c3a58df..8175a2d3aa 100644 --- a/esphome/components/exposure_notifications/__init__.py +++ b/esphome/components/exposure_notifications/__init__.py @@ -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') diff --git a/esphome/components/fastled_base/__init__.py b/esphome/components/fastled_base/__init__.py index ffa49f43c2..e438876d33 100644 --- a/esphome/components/fastled_base/__init__.py +++ b/esphome/components/fastled_base/__init__.py @@ -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) diff --git a/esphome/components/globals/__init__.py b/esphome/components/globals/__init__.py index e59a7e6acb..f1c4f4faf2 100644 --- a/esphome/components/globals/__init__.py +++ b/esphome/components/globals/__init__.py @@ -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) diff --git a/esphome/components/gpio/__init__.py b/esphome/components/gpio/__init__.py index ccb920e654..c36ba8f433 100644 --- a/esphome/components/gpio/__init__.py +++ b/esphome/components/gpio/__init__.py @@ -1,3 +1,4 @@ import esphome.codegen as cg +CODEOWNERS = ['@esphome/core'] gpio_ns = cg.esphome_ns.namespace('gpio') diff --git a/esphome/components/homeassistant/__init__.py b/esphome/components/homeassistant/__init__.py index 9fb3836d49..69d759b977 100644 --- a/esphome/components/homeassistant/__init__.py +++ b/esphome/components/homeassistant/__init__.py @@ -1,3 +1,4 @@ import esphome.codegen as cg +CODEOWNERS = ['@OttoWinter'] homeassistant_ns = cg.esphome_ns.namespace('homeassistant') diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index 0c71f18019..91c6a97190 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -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') diff --git a/esphome/components/integration/__init__.py b/esphome/components/integration/__init__.py index e69de29bb2..6f14e10033 100644 --- a/esphome/components/integration/__init__.py +++ b/esphome/components/integration/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ['@OttoWinter'] diff --git a/esphome/components/interval/__init__.py b/esphome/components/interval/__init__.py index e0816b7407..be37526ad1 100644 --- a/esphome/components/interval/__init__.py +++ b/esphome/components/interval/__init__.py @@ -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) diff --git a/esphome/components/json/__init__.py b/esphome/components/json/__init__.py index f719b05340..63bc16dfa2 100644 --- a/esphome/components/json/__init__.py +++ b/esphome/components/json/__init__.py @@ -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') diff --git a/esphome/components/ledc/__init__.py b/esphome/components/ledc/__init__.py index e69de29bb2..6f14e10033 100644 --- a/esphome/components/ledc/__init__.py +++ b/esphome/components/ledc/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ['@OttoWinter'] diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index 2a44b044b9..9d58fa47bf 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -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') diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 329c515aee..c447b0eee1 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -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, diff --git a/esphome/components/network/__init__.py b/esphome/components/network/__init__.py index 4486d62e1d..a380e32bfe 100644 --- a/esphome/components/network/__init__.py +++ b/esphome/components/network/__init__.py @@ -1 +1,2 @@ # Dummy package to allow components to depend on network +CODEOWNERS = ['@esphome/core'] diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 869de777d6..e6bcff045f 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -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') diff --git a/esphome/components/output/__init__.py b/esphome/components/output/__init__.py index b406f62ee1..34cb7c3f7a 100644 --- a/esphome/components/output/__init__.py +++ b/esphome/components/output/__init__.py @@ -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({ diff --git a/esphome/components/pid/__init__.py b/esphome/components/pid/__init__.py index e69de29bb2..6f14e10033 100644 --- a/esphome/components/pid/__init__.py +++ b/esphome/components/pid/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ['@OttoWinter'] diff --git a/esphome/components/pn532/__init__.py b/esphome/components/pn532/__init__.py index 931b6ada6b..cbd41d11cc 100644 --- a/esphome/components/pn532/__init__.py +++ b/esphome/components/pn532/__init__.py @@ -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 diff --git a/esphome/components/power_supply/__init__.py b/esphome/components/power_supply/__init__.py index 5646ffdc0b..d502788637 100644 --- a/esphome/components/power_supply/__init__.py +++ b/esphome/components/power_supply/__init__.py @@ -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 diff --git a/esphome/components/restart/__init__.py b/esphome/components/restart/__init__.py index e69de29bb2..63db7aee2e 100644 --- a/esphome/components/restart/__init__.py +++ b/esphome/components/restart/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ['@esphome/core'] diff --git a/esphome/components/script/__init__.py b/esphome/components/script/__init__.py index eb337d7681..d33f64a23b 100644 --- a/esphome/components/script/__init__.py +++ b/esphome/components/script/__init__.py @@ -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) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 605f72a103..671bbe2b09 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -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 diff --git a/esphome/components/shutdown/__init__.py b/esphome/components/shutdown/__init__.py index e69de29bb2..63db7aee2e 100644 --- a/esphome/components/shutdown/__init__.py +++ b/esphome/components/shutdown/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ['@esphome/core'] diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index 71dac0f8c9..e7f8bc378d 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -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') diff --git a/esphome/components/substitutions/__init__.py b/esphome/components/substitutions/__init__.py index a6f70fe928..9c5d444b2c 100644 --- a/esphome/components/substitutions/__init__.py +++ b/esphome/components/substitutions/__init__.py @@ -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' \ diff --git a/esphome/components/sun/__init__.py b/esphome/components/sun/__init__.py index e4d2023a8e..a92442ea56 100644 --- a/esphome/components/sun/__init__.py +++ b/esphome/components/sun/__init__.py @@ -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') diff --git a/esphome/components/switch/__init__.py b/esphome/components/switch/__init__.py index 3870631e13..7378b2a140 100644 --- a/esphome/components/switch/__init__.py +++ b/esphome/components/switch/__init__.py @@ -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_') diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py index 6283392103..49ed53c47e 100644 --- a/esphome/components/time/__init__.py +++ b/esphome/components/time/__init__.py @@ -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') diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 65a9a3eaf8..b83aedad9f 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -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') diff --git a/esphome/components/ultrasonic/__init__.py b/esphome/components/ultrasonic/__init__.py index e69de29bb2..6f14e10033 100644 --- a/esphome/components/ultrasonic/__init__.py +++ b/esphome/components/ultrasonic/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ['@OttoWinter'] diff --git a/esphome/components/version/__init__.py b/esphome/components/version/__init__.py index e69de29bb2..63db7aee2e 100644 --- a/esphome/components/version/__init__.py +++ b/esphome/components/version/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ['@esphome/core'] diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index d2faaf7162..f7b2238552 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -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'] diff --git a/esphome/config.py b/esphome/config.py index 84d3ca3803..85f48c64b7 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -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() diff --git a/esphome/helpers.py b/esphome/helpers.py index 6413a25e01..b30ace89d2 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -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) diff --git a/script/build_codeowners.py b/script/build_codeowners.py new file mode 100755 index 0000000000..3f2bf5d8c3 --- /dev/null +++ b/script/build_codeowners.py @@ -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")