diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9473dc87dc..c2ad769156 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,7 +80,7 @@ jobs: uses: actions/setup-python@v2 id: python with: - python-version: '3.7' + python-version: '3.8' - name: Cache virtualenv uses: actions/cache@v2 diff --git a/.gitignore b/.gitignore index 57b8478bd7..110437c368 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,7 @@ venv/ ENV/ env.bak/ venv.bak/ +venv-*/ # mypy .mypy_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e38717fe5b..9549d5cedc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,3 +25,8 @@ repos: - --branch=dev - --branch=release - --branch=beta + - repo: https://github.com/asottile/pyupgrade + rev: v2.31.0 + hooks: + - id: pyupgrade + args: [--py38-plus] diff --git a/esphome/__main__.py b/esphome/__main__.py index a64f096d54..85cf4ede85 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -778,10 +778,10 @@ def run_esphome(argv): _LOGGER.warning("Please instead use:") _LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion)) - if sys.version_info < (3, 7, 0): + if sys.version_info < (3, 8, 0): _LOGGER.error( - "You're running ESPHome with Python <3.7. ESPHome is no longer compatible " - "with this Python version. Please reinstall ESPHome with Python 3.7+" + "You're running ESPHome with Python <3.8. ESPHome is no longer compatible " + "with this Python version. Please reinstall ESPHome with Python 3.8+" ) return 1 diff --git a/esphome/components/lcd_gpio/display.py b/esphome/components/lcd_gpio/display.py index 9fb635eafa..bfef402058 100644 --- a/esphome/components/lcd_gpio/display.py +++ b/esphome/components/lcd_gpio/display.py @@ -43,7 +43,7 @@ async def to_code(config): await lcd_base.setup_lcd_display(var, config) pins_ = [] for conf in config[CONF_DATA_PINS]: - pins_.append((await cg.gpio_pin_expression(conf))) + pins_.append(await cg.gpio_pin_expression(conf)) cg.add(var.set_data_pins(*pins_)) enable = await cg.gpio_pin_expression(config[CONF_ENABLE_PIN]) cg.add(var.set_enable_pin(enable)) diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 72a91a99dd..9bf03aaf28 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -847,7 +847,7 @@ async def rc_switch_raw_action(var, config, args): config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol ) cg.add(var.set_protocol(proto)) - cg.add(var.set_code((await cg.templatable(config[CONF_CODE], args, cg.std_string)))) + cg.add(var.set_code(await cg.templatable(config[CONF_CODE], args, cg.std_string))) @register_binary_sensor( @@ -868,13 +868,11 @@ async def rc_switch_type_a_action(var, config, args): config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol ) cg.add(var.set_protocol(proto)) + cg.add(var.set_group(await cg.templatable(config[CONF_GROUP], args, cg.std_string))) cg.add( - var.set_group((await cg.templatable(config[CONF_GROUP], args, cg.std_string))) + var.set_device(await cg.templatable(config[CONF_DEVICE], args, cg.std_string)) ) - cg.add( - var.set_device((await cg.templatable(config[CONF_DEVICE], args, cg.std_string))) - ) - cg.add(var.set_state((await cg.templatable(config[CONF_STATE], args, bool)))) + cg.add(var.set_state(await cg.templatable(config[CONF_STATE], args, bool))) @register_binary_sensor( @@ -897,13 +895,9 @@ async def rc_switch_type_b_action(var, config, args): config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol ) cg.add(var.set_protocol(proto)) - cg.add( - var.set_address((await cg.templatable(config[CONF_ADDRESS], args, cg.uint8))) - ) - cg.add( - var.set_channel((await cg.templatable(config[CONF_CHANNEL], args, cg.uint8))) - ) - cg.add(var.set_state((await cg.templatable(config[CONF_STATE], args, bool)))) + cg.add(var.set_address(await cg.templatable(config[CONF_ADDRESS], args, cg.uint8))) + cg.add(var.set_channel(await cg.templatable(config[CONF_CHANNEL], args, cg.uint8))) + cg.add(var.set_state(await cg.templatable(config[CONF_STATE], args, bool))) @register_binary_sensor( @@ -932,11 +926,11 @@ async def rc_switch_type_c_action(var, config, args): ) cg.add(var.set_protocol(proto)) cg.add( - var.set_family((await cg.templatable(config[CONF_FAMILY], args, cg.std_string))) + var.set_family(await cg.templatable(config[CONF_FAMILY], args, cg.std_string)) ) - cg.add(var.set_group((await cg.templatable(config[CONF_GROUP], args, cg.uint8)))) - cg.add(var.set_device((await cg.templatable(config[CONF_DEVICE], args, cg.uint8)))) - cg.add(var.set_state((await cg.templatable(config[CONF_STATE], args, bool)))) + cg.add(var.set_group(await cg.templatable(config[CONF_GROUP], args, cg.uint8))) + cg.add(var.set_device(await cg.templatable(config[CONF_DEVICE], args, cg.uint8))) + cg.add(var.set_state(await cg.templatable(config[CONF_STATE], args, bool))) @register_binary_sensor( @@ -959,11 +953,9 @@ async def rc_switch_type_d_action(var, config, args): config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol ) cg.add(var.set_protocol(proto)) - cg.add( - var.set_group((await cg.templatable(config[CONF_GROUP], args, cg.std_string))) - ) - cg.add(var.set_device((await cg.templatable(config[CONF_DEVICE], args, cg.uint8)))) - cg.add(var.set_state((await cg.templatable(config[CONF_STATE], args, bool)))) + cg.add(var.set_group(await cg.templatable(config[CONF_GROUP], args, cg.std_string))) + cg.add(var.set_device(await cg.templatable(config[CONF_DEVICE], args, cg.uint8))) + cg.add(var.set_state(await cg.templatable(config[CONF_STATE], args, bool))) @register_trigger("rc_switch", RCSwitchTrigger, RCSwitchData) diff --git a/esphome/components/tuya/climate/__init__.py b/esphome/components/tuya/climate/__init__.py index 275a87edd3..7d4b37ad22 100644 --- a/esphome/components/tuya/climate/__init__.py +++ b/esphome/components/tuya/climate/__init__.py @@ -36,31 +36,25 @@ def validate_temperature_multipliers(value): or CONF_TARGET_TEMPERATURE_MULTIPLIER in value ): raise cv.Invalid( - ( - f"Cannot have {CONF_TEMPERATURE_MULTIPLIER} at the same time as " - f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER} and " - f"{CONF_TARGET_TEMPERATURE_MULTIPLIER}" - ) + f"Cannot have {CONF_TEMPERATURE_MULTIPLIER} at the same time as " + f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER} and " + f"{CONF_TARGET_TEMPERATURE_MULTIPLIER}" ) if ( CONF_CURRENT_TEMPERATURE_MULTIPLIER in value and CONF_TARGET_TEMPERATURE_MULTIPLIER not in value ): raise cv.Invalid( - ( - f"{CONF_TARGET_TEMPERATURE_MULTIPLIER} required if using " - f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER}" - ) + f"{CONF_TARGET_TEMPERATURE_MULTIPLIER} required if using " + f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER}" ) if ( CONF_TARGET_TEMPERATURE_MULTIPLIER in value and CONF_CURRENT_TEMPERATURE_MULTIPLIER not in value ): raise cv.Invalid( - ( - f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER} required if using " - f"{CONF_TARGET_TEMPERATURE_MULTIPLIER}" - ) + f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER} required if using " + f"{CONF_TARGET_TEMPERATURE_MULTIPLIER}" ) keys = ( CONF_TEMPERATURE_MULTIPLIER, @@ -76,18 +70,14 @@ def validate_active_state_values(value): if CONF_ACTIVE_STATE_DATAPOINT not in value: if CONF_ACTIVE_STATE_COOLING_VALUE in value: raise cv.Invalid( - ( - f"{CONF_ACTIVE_STATE_DATAPOINT} required if using " - f"{CONF_ACTIVE_STATE_COOLING_VALUE}" - ) + f"{CONF_ACTIVE_STATE_DATAPOINT} required if using " + f"{CONF_ACTIVE_STATE_COOLING_VALUE}" ) else: if value[CONF_SUPPORTS_COOL] and CONF_ACTIVE_STATE_COOLING_VALUE not in value: raise cv.Invalid( - ( - f"{CONF_ACTIVE_STATE_COOLING_VALUE} required if using " - f"{CONF_ACTIVE_STATE_DATAPOINT} and device supports cooling" - ) + f"{CONF_ACTIVE_STATE_COOLING_VALUE} required if using " + f"{CONF_ACTIVE_STATE_DATAPOINT} and device supports cooling" ) return value diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 1e5f341717..eaa20ccbb4 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -77,11 +77,11 @@ async def to_code(config): if CONF_CSS_INCLUDE in config: cg.add_define("WEBSERVER_CSS_INCLUDE") path = CORE.relative_config_path(config[CONF_CSS_INCLUDE]) - with open(file=path, mode="r", encoding="utf-8") as myfile: + with open(file=path, encoding="utf-8") as myfile: cg.add(var.set_css_include(myfile.read())) if CONF_JS_INCLUDE in config: cg.add_define("WEBSERVER_JS_INCLUDE") path = CORE.relative_config_path(config[CONF_JS_INCLUDE]) - with open(file=path, mode="r", encoding="utf-8") as myfile: + with open(file=path, encoding="utf-8") as myfile: cg.add(var.set_js_include(myfile.read())) cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL])) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index a24791b458..20f43cb450 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -225,11 +225,11 @@ def _validate(config): if CONF_MANUAL_IP in config: use_address = str(config[CONF_MANUAL_IP][CONF_STATIC_IP]) elif CONF_NETWORKS in config: - ips = set( + ips = { str(net[CONF_MANUAL_IP][CONF_STATIC_IP]) for net in config[CONF_NETWORKS] if CONF_MANUAL_IP in net - ) + } if len(ips) > 1: raise cv.Invalid( "Must specify use_address when using multiple static IP addresses." diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 0ace97f10e..af68f2ae08 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -733,7 +733,7 @@ class EditRequestHandler(BaseHandler): content = "" if os.path.isfile(filename): # pylint: disable=no-value-for-parameter - with open(file=filename, mode="r", encoding="utf-8") as f: + with open(file=filename, encoding="utf-8") as f: content = f.read() self.write(content) diff --git a/requirements_test.txt b/requirements_test.txt index 1e5a5c2ebc..46713adbb9 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,7 @@ pylint==2.12.2 flake8==4.0.1 black==22.1.0 +pyupgrade==2.31.0 pre-commit # Unit tests diff --git a/script/build_jsonschema.py b/script/build_jsonschema.py index 7a3257411c..7673519916 100644 --- a/script/build_jsonschema.py +++ b/script/build_jsonschema.py @@ -170,7 +170,7 @@ def get_logger_tags(): ] for x in os.walk(CORE_COMPONENTS_PATH): for y in glob.glob(os.path.join(x[0], "*.cpp")): - with open(y, "r") as file: + with open(y) as file: data = file.read() match = pattern.search(data) if match: diff --git a/script/ci-custom.py b/script/ci-custom.py index d1efa22d85..2703e7d311 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -281,9 +281,7 @@ def highlight(s): ], ) def lint_no_defines(fname, match): - s = highlight( - "static const uint8_t {} = {};".format(match.group(1), match.group(2)) - ) + s = highlight(f"static const uint8_t {match.group(1)} = {match.group(2)};") return ( "#define macros for integer constants are not allowed, please use " "{} style instead (replace uint8_t with the appropriate " diff --git a/script/clang-format b/script/clang-format index 515df4c027..ae807262f1 100755 --- a/script/clang-format +++ b/script/clang-format @@ -17,14 +17,14 @@ def run_format(args, queue, lock, failed_files): """Takes filenames out of queue and runs clang-format on them.""" while True: path = queue.get() - invocation = ['clang-format-11'] + invocation = ["clang-format-11"] if args.inplace: - invocation.append('-i') + invocation.append("-i") else: - invocation.extend(['--dry-run', '-Werror']) + invocation.extend(["--dry-run", "-Werror"]) invocation.append(path) - proc = subprocess.run(invocation, capture_output=True, encoding='utf-8') + proc = subprocess.run(invocation, capture_output=True, encoding="utf-8") if proc.returncode != 0: with lock: print_error_for_file(path, proc.stderr) @@ -33,28 +33,36 @@ def run_format(args, queue, lock, failed_files): def progress_bar_show(value): - return value if value is not None else '' + return value if value is not None else "" def main(): colorama.init() parser = argparse.ArgumentParser() - parser.add_argument('-j', '--jobs', type=int, - default=multiprocessing.cpu_count(), - help='number of format instances to be run in parallel.') - parser.add_argument('files', nargs='*', default=[], - help='files to be processed (regex on path)') - parser.add_argument('-i', '--inplace', action='store_true', - help='reformat files in-place') - parser.add_argument('-c', '--changed', action='store_true', - help='only run on changed files') + parser.add_argument( + "-j", + "--jobs", + type=int, + default=multiprocessing.cpu_count(), + help="number of format instances to be run in parallel.", + ) + parser.add_argument( + "files", nargs="*", default=[], help="files to be processed (regex on path)" + ) + parser.add_argument( + "-i", "--inplace", action="store_true", help="reformat files in-place" + ) + parser.add_argument( + "-c", "--changed", action="store_true", help="only run on changed files" + ) args = parser.parse_args() try: - get_output('clang-format-11', '-version') + get_output("clang-format-11", "-version") except: - print(""" + print( + """ Oops. It looks like clang-format is not installed. Please check you can run "clang-format-11 -version" in your terminal and install @@ -62,16 +70,17 @@ def main(): Note you can also upload your code as a pull request on GitHub and see the CI check output to apply clang-format. - """) + """ + ) return 1 files = [] - for path in git_ls_files(['*.cpp', '*.h', '*.tcc']): + for path in git_ls_files(["*.cpp", "*.h", "*.tcc"]): files.append(os.path.relpath(path, os.getcwd())) if args.files: # Match against files specified on command-line - file_name_re = re.compile('|'.join(args.files)) + file_name_re = re.compile("|".join(args.files)) files = [p for p in files if file_name_re.search(p)] if args.changed: @@ -84,14 +93,16 @@ def main(): task_queue = queue.Queue(args.jobs) lock = threading.Lock() for _ in range(args.jobs): - t = threading.Thread(target=run_format, - args=(args, task_queue, lock, failed_files)) + t = threading.Thread( + target=run_format, args=(args, task_queue, lock, failed_files) + ) t.daemon = True t.start() # Fill the queue with files. - with click.progressbar(files, width=30, file=sys.stderr, - item_show_func=progress_bar_show) as bar: + with click.progressbar( + files, width=30, file=sys.stderr, item_show_func=progress_bar_show + ) as bar: for name in bar: task_queue.put(name) @@ -100,11 +111,11 @@ def main(): except KeyboardInterrupt: print() - print('Ctrl-C detected, goodbye.') + print("Ctrl-C detected, goodbye.") os.kill(0, 9) sys.exit(len(failed_files)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/script/clang-tidy b/script/clang-tidy index 8a7d229887..327b593008 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -1,7 +1,17 @@ #!/usr/bin/env python3 -from helpers import print_error_for_file, get_output, filter_grep, \ - build_all_include, temp_header_file, git_ls_files, filter_changed, load_idedata, root_path, basepath +from helpers import ( + print_error_for_file, + get_output, + filter_grep, + build_all_include, + temp_header_file, + git_ls_files, + filter_changed, + load_idedata, + root_path, + basepath, +) import argparse import click import colorama @@ -20,67 +30,81 @@ def clang_options(idedata): cmd = [] # extract target architecture from triplet in g++ filename - triplet = os.path.basename(idedata['cxx_path'])[:-4] + triplet = os.path.basename(idedata["cxx_path"])[:-4] if triplet.startswith("xtensa-"): # clang doesn't support Xtensa (yet?), so compile in 32-bit mode and pretend we're the Xtensa compiler - cmd.append('-m32') - cmd.append('-D__XTENSA__') + cmd.append("-m32") + cmd.append("-D__XTENSA__") else: - cmd.append(f'--target={triplet}') + cmd.append(f"--target={triplet}") # set flags - cmd.extend([ - # disable built-in include directories from the host - '-nostdinc', - '-nostdinc++', - # replace pgmspace.h, as it uses GNU extensions clang doesn't support - # https://github.com/earlephilhower/newlib-xtensa/pull/18 - '-D_PGMSPACE_H_', - '-Dpgm_read_byte(s)=(*(const uint8_t *)(s))', - '-Dpgm_read_byte_near(s)=(*(const uint8_t *)(s))', - '-Dpgm_read_word(s)=(*(const uint16_t *)(s))', - '-Dpgm_read_dword(s)=(*(const uint32_t *)(s))', - '-DPROGMEM=', - '-DPGM_P=const char *', - '-DPSTR(s)=(s)', - # this next one is also needed with upstream pgmspace.h - # suppress warning about identifier naming in expansion of this macro - '-DPSTRN(s, n)=(s)', - # suppress warning about attribute cannot be applied to type - # https://github.com/esp8266/Arduino/pull/8258 - '-Ddeprecated(x)=', - # allow to condition code on the presence of clang-tidy - '-DCLANG_TIDY', - # (esp-idf) Disable this header because they use asm with registers clang-tidy doesn't know - '-D__XTENSA_API_H__', - # (esp-idf) Fix __once_callable in some libstdc++ headers - '-D_GLIBCXX_HAVE_TLS', - ]) + cmd.extend( + [ + # disable built-in include directories from the host + "-nostdinc", + "-nostdinc++", + # replace pgmspace.h, as it uses GNU extensions clang doesn't support + # https://github.com/earlephilhower/newlib-xtensa/pull/18 + "-D_PGMSPACE_H_", + "-Dpgm_read_byte(s)=(*(const uint8_t *)(s))", + "-Dpgm_read_byte_near(s)=(*(const uint8_t *)(s))", + "-Dpgm_read_word(s)=(*(const uint16_t *)(s))", + "-Dpgm_read_dword(s)=(*(const uint32_t *)(s))", + "-DPROGMEM=", + "-DPGM_P=const char *", + "-DPSTR(s)=(s)", + # this next one is also needed with upstream pgmspace.h + # suppress warning about identifier naming in expansion of this macro + "-DPSTRN(s, n)=(s)", + # suppress warning about attribute cannot be applied to type + # https://github.com/esp8266/Arduino/pull/8258 + "-Ddeprecated(x)=", + # allow to condition code on the presence of clang-tidy + "-DCLANG_TIDY", + # (esp-idf) Disable this header because they use asm with registers clang-tidy doesn't know + "-D__XTENSA_API_H__", + # (esp-idf) Fix __once_callable in some libstdc++ headers + "-D_GLIBCXX_HAVE_TLS", + ] + ) # copy compiler flags, except those clang doesn't understand. - cmd.extend(flag for flag in idedata['cxx_flags'].split(' ') - if flag not in ('-free', '-fipa-pta', '-fstrict-volatile-bitfields', - '-mlongcalls', '-mtext-section-literals', - '-mfix-esp32-psram-cache-issue', '-mfix-esp32-psram-cache-strategy=memw')) + cmd.extend( + flag + for flag in idedata["cxx_flags"].split(" ") + if flag + not in ( + "-free", + "-fipa-pta", + "-fstrict-volatile-bitfields", + "-mlongcalls", + "-mtext-section-literals", + "-mfix-esp32-psram-cache-issue", + "-mfix-esp32-psram-cache-strategy=memw", + ) + ) # defines - cmd.extend(f'-D{define}' for define in idedata['defines']) + cmd.extend(f"-D{define}" for define in idedata["defines"]) # add toolchain include directories using -isystem to suppress their errors # idedata contains include directories for all toolchains of this platform, only use those from the one in use toolchain_dir = os.path.normpath(f"{idedata['cxx_path']}/../../") - for directory in idedata['includes']['toolchain']: + for directory in idedata["includes"]["toolchain"]: if directory.startswith(toolchain_dir): - cmd.extend(['-isystem', directory]) + cmd.extend(["-isystem", directory]) # add library include directories using -isystem to suppress their errors - for directory in sorted(set(idedata['includes']['build'])): + for directory in sorted(set(idedata["includes"]["build"])): # skip our own directories, we add those later - if not directory.startswith(f"{root_path}/") or directory.startswith(f"{root_path}/.pio/"): - cmd.extend(['-isystem', directory]) + if not directory.startswith(f"{root_path}/") or directory.startswith( + f"{root_path}/.pio/" + ): + cmd.extend(["-isystem", directory]) # add the esphome include directory using -I - cmd.extend(['-I', root_path]) + cmd.extend(["-I", root_path]) return cmd @@ -88,28 +112,28 @@ def clang_options(idedata): def run_tidy(args, options, tmpdir, queue, lock, failed_files): while True: path = queue.get() - invocation = ['clang-tidy-11'] + invocation = ["clang-tidy-11"] if tmpdir is not None: - invocation.append('--export-fixes') + invocation.append("--export-fixes") # Get a temporary file. We immediately close the handle so clang-tidy can # overwrite it. - (handle, name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir) + (handle, name) = tempfile.mkstemp(suffix=".yaml", dir=tmpdir) os.close(handle) invocation.append(name) if args.quiet: - invocation.append('--quiet') + invocation.append("--quiet") if sys.stdout.isatty(): - invocation.append('--use-color') + invocation.append("--use-color") invocation.append(f"--header-filter={os.path.abspath(basepath)}/.*") invocation.append(os.path.abspath(path)) - invocation.append('--') + invocation.append("--") invocation.extend(options) - proc = subprocess.run(invocation, capture_output=True, encoding='utf-8') + proc = subprocess.run(invocation, capture_output=True, encoding="utf-8") if proc.returncode != 0: with lock: print_error_for_file(path, proc.stdout) @@ -119,43 +143,60 @@ def run_tidy(args, options, tmpdir, queue, lock, failed_files): def progress_bar_show(value): if value is None: - return '' + return "" def split_list(a, n): k, m = divmod(len(a), n) - return [a[i * k + min(i, m):(i + 1) * k + min(i + 1, m)] for i in range(n)] + return [a[i * k + min(i, m) : (i + 1) * k + min(i + 1, m)] for i in range(n)] def main(): colorama.init() parser = argparse.ArgumentParser() - parser.add_argument('-j', '--jobs', type=int, - default=multiprocessing.cpu_count(), - help='number of tidy instances to be run in parallel.') - parser.add_argument('-e', '--environment', default='esp32-arduino-tidy', - help='the PlatformIO environment to use (as defined in platformio.ini)') - parser.add_argument('files', nargs='*', default=[], - help='files to be processed (regex on path)') - parser.add_argument('--fix', action='store_true', help='apply fix-its') - parser.add_argument('-q', '--quiet', action='store_false', - help='run clang-tidy in quiet mode') - parser.add_argument('-c', '--changed', action='store_true', - help='only run on changed files') - parser.add_argument('-g', '--grep', help='only run on files containing value') - parser.add_argument('--split-num', type=int, help='split the files into X jobs.', - default=None) - parser.add_argument('--split-at', type=int, help='which split is this? starts at 1', - default=None) - parser.add_argument('--all-headers', action='store_true', - help='create a dummy file that checks all headers') + parser.add_argument( + "-j", + "--jobs", + type=int, + default=multiprocessing.cpu_count(), + help="number of tidy instances to be run in parallel.", + ) + parser.add_argument( + "-e", + "--environment", + default="esp32-arduino-tidy", + help="the PlatformIO environment to use (as defined in platformio.ini)", + ) + parser.add_argument( + "files", nargs="*", default=[], help="files to be processed (regex on path)" + ) + parser.add_argument("--fix", action="store_true", help="apply fix-its") + parser.add_argument( + "-q", "--quiet", action="store_false", help="run clang-tidy in quiet mode" + ) + parser.add_argument( + "-c", "--changed", action="store_true", help="only run on changed files" + ) + parser.add_argument("-g", "--grep", help="only run on files containing value") + parser.add_argument( + "--split-num", type=int, help="split the files into X jobs.", default=None + ) + parser.add_argument( + "--split-at", type=int, help="which split is this? starts at 1", default=None + ) + parser.add_argument( + "--all-headers", + action="store_true", + help="create a dummy file that checks all headers", + ) args = parser.parse_args() try: - get_output('clang-tidy-11', '-version') + get_output("clang-tidy-11", "-version") except: - print(""" + print( + """ Oops. It looks like clang-tidy-11 is not installed. Please check you can run "clang-tidy-11 -version" in your terminal and install @@ -163,19 +204,20 @@ def main(): Note you can also upload your code as a pull request on GitHub and see the CI check output to apply clang-tidy. - """) + """ + ) return 1 idedata = load_idedata(args.environment) options = clang_options(idedata) files = [] - for path in git_ls_files(['*.cpp']): + for path in git_ls_files(["*.cpp"]): files.append(os.path.relpath(path, os.getcwd())) if args.files: # Match against files specified on command-line - file_name_re = re.compile('|'.join(args.files)) + file_name_re = re.compile("|".join(args.files)) files = [p for p in files if file_name_re.search(p)] if args.changed: @@ -202,14 +244,17 @@ def main(): task_queue = queue.Queue(args.jobs) lock = threading.Lock() for _ in range(args.jobs): - t = threading.Thread(target=run_tidy, - args=(args, options, tmpdir, task_queue, lock, failed_files)) + t = threading.Thread( + target=run_tidy, + args=(args, options, tmpdir, task_queue, lock, failed_files), + ) t.daemon = True t.start() # Fill the queue with files. - with click.progressbar(files, width=30, file=sys.stderr, - item_show_func=progress_bar_show) as bar: + with click.progressbar( + files, width=30, file=sys.stderr, item_show_func=progress_bar_show + ) as bar: for name in bar: task_queue.put(name) @@ -218,21 +263,21 @@ def main(): except KeyboardInterrupt: print() - print('Ctrl-C detected, goodbye.') + print("Ctrl-C detected, goodbye.") if tmpdir: shutil.rmtree(tmpdir) os.kill(0, 9) if args.fix and failed_files: - print('Applying fixes ...') + print("Applying fixes ...") try: - subprocess.call(['clang-apply-replacements-11', tmpdir]) + subprocess.call(["clang-apply-replacements-11", tmpdir]) except: - print('Error applying fixes.\n', file=sys.stderr) + print("Error applying fixes.\n", file=sys.stderr) raise sys.exit(len(failed_files)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/script/helpers.py b/script/helpers.py index abf970b8a2..c042362aeb 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -12,13 +12,16 @@ temp_header_file = os.path.join(temp_folder, "all-include.cpp") def styled(color, msg, reset=True): - prefix = ''.join(color) if isinstance(color, tuple) else color - suffix = colorama.Style.RESET_ALL if reset else '' + prefix = "".join(color) if isinstance(color, tuple) else color + suffix = colorama.Style.RESET_ALL if reset else "" return prefix + msg + suffix def print_error_for_file(file, body): - print(styled(colorama.Fore.GREEN, "### File ") + styled((colorama.Fore.GREEN, colorama.Style.BRIGHT), file)) + print( + styled(colorama.Fore.GREEN, "### File ") + + styled((colorama.Fore.GREEN, colorama.Style.BRIGHT), file) + ) print() if body is not None: print(body) @@ -100,7 +103,7 @@ def filter_changed(files): def filter_grep(files, value): matched = [] for file in files: - with open(file, "r") as handle: + with open(file) as handle: contents = handle.read() if value in contents: matched.append(file) diff --git a/script/lint-python b/script/lint-python index 8ee038a661..90b5dcd59f 100755 --- a/script/lint-python +++ b/script/lint-python @@ -1,7 +1,13 @@ #!/usr/bin/env python3 -from __future__ import print_function -from helpers import styled, print_error_for_file, get_output, get_err, git_ls_files, filter_changed +from helpers import ( + styled, + print_error_for_file, + get_output, + get_err, + git_ls_files, + filter_changed, +) import argparse import colorama import os @@ -34,6 +40,12 @@ def main(): parser.add_argument( "-c", "--changed", action="store_true", help="Only run on changed files" ) + parser.add_argument( + "-a", + "--apply", + action="store_true", + help="Apply changes to files where possible", + ) args = parser.parse_args() files = [] @@ -56,7 +68,7 @@ def main(): errors = 0 - cmd = ["black", "--verbose", "--check"] + files + cmd = ["black", "--verbose"] + ([] if args.apply else ["--check"]) + files print("Running black...") print() log = get_err(*cmd) @@ -97,6 +109,21 @@ def main(): print_error(file_, linno, msg) errors += 1 + PYUPGRADE_TARGET = "--py38-plus" + cmd = ["pyupgrade", PYUPGRADE_TARGET] + files + print() + print("Running pyupgrade...") + print() + log = get_err(*cmd) + for line in log.splitlines(): + REWRITING = "Rewriting" + if line.startswith(REWRITING): + file_ = line[len(REWRITING) + 1 :] + print_error( + file_, None, f"Please run pyupgrade {PYUPGRADE_TARGET} on this file" + ) + errors += 1 + sys.exit(errors) diff --git a/setup.py b/setup.py index 967eadd70f..941c8089ec 100755 --- a/setup.py +++ b/setup.py @@ -17,11 +17,11 @@ PROJECT_EMAIL = "esphome@nabucasa.com" PROJECT_GITHUB_USERNAME = "esphome" PROJECT_GITHUB_REPOSITORY = "esphome" -PYPI_URL = "https://pypi.python.org/pypi/{}".format(PROJECT_PACKAGE_NAME) -GITHUB_PATH = "{}/{}".format(PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY) -GITHUB_URL = "https://github.com/{}".format(GITHUB_PATH) +PYPI_URL = f"https://pypi.python.org/pypi/{PROJECT_PACKAGE_NAME}" +GITHUB_PATH = f"{PROJECT_GITHUB_USERNAME}/{PROJECT_GITHUB_REPOSITORY}" +GITHUB_URL = f"https://github.com/{GITHUB_PATH}" -DOWNLOAD_URL = "{}/archive/{}.zip".format(GITHUB_URL, const.__version__) +DOWNLOAD_URL = f"{GITHUB_URL}/archive/{const.__version__}.zip" here = os.path.abspath(os.path.dirname(__file__)) @@ -74,7 +74,7 @@ setup( zip_safe=False, platforms="any", test_suite="tests", - python_requires=">=3.7,<4.0", + python_requires=">=3.8,<4.0", install_requires=REQUIRES, keywords=["home", "automation"], entry_points={"console_scripts": ["esphome = esphome.__main__:main"]},