Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[QA] Add/fix type hints to Python code #24871

Draft
wants to merge 5 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 24 additions & 12 deletions lib/python/qmk/cli/chibios/confmigrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,23 @@
import re
import sys
import os
from typing import TextIO, TypedDict

from qmk.constants import QMK_FIRMWARE
from qmk.path import normpath
from milc import cli
from milc import MILC, cli


def eprint(*args, **kwargs):
class Defines(TypedDict):
keys: list[str]
dict: dict[str, str]


Override = tuple[str, str]
Diff = tuple[list[Override], list[str], list[str]]


def eprint(*args: object, **kwargs: object) -> None:
print(*args, file=sys.stderr, **kwargs)


Expand Down Expand Up @@ -39,25 +49,27 @@ def eprint(*args, **kwargs):
"""


def collect_defines(filepath):
def collect_defines(filepath: str) -> Defines:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
define_search = re.compile(r'(?m)^#\s*define\s+(?:.*\\\r?\n)*.*$', re.MULTILINE)
value_search = re.compile(r'^#\s*define\s+(?P<name>[a-zA-Z0-9_]+(\([^\)]*\))?)\s*(?P<value>.*)', re.DOTALL)
define_matches = define_search.findall(content)

defines = {"keys": [], "dict": {}}
defines: Defines = {"keys": [], "dict": {}}
for define_match in define_matches:
value_match = value_search.search(define_match)
if value_match is None:
raise RuntimeError
defines["keys"].append(value_match.group("name"))
defines["dict"][value_match.group("name")] = value_match.group("value")
return defines


def check_diffs(input_defs, reference_defs):
not_present_in_input = []
not_present_in_reference = []
to_override = []
def check_diffs(input_defs: Defines, reference_defs: Defines) -> Diff:
not_present_in_input: list[str] = []
not_present_in_reference: list[str] = []
to_override: list[Override] = []

for key in reference_defs["keys"]:
if key not in input_defs["dict"]:
Expand All @@ -76,7 +88,7 @@ def check_diffs(input_defs, reference_defs):
return (to_override, not_present_in_input, not_present_in_reference)


def migrate_chconf_h(to_override, outfile):
def migrate_chconf_h(to_override: list[Override], outfile: TextIO) -> None:
print(file_header.format(cli.args.input.relative_to(QMK_FIRMWARE), cli.args.reference.relative_to(QMK_FIRMWARE)), file=outfile)

for override in to_override:
Expand All @@ -86,7 +98,7 @@ def migrate_chconf_h(to_override, outfile):
print("#include_next <chconf.h>\n", file=outfile)


def migrate_halconf_h(to_override, outfile):
def migrate_halconf_h(to_override: list[Override], outfile: TextIO) -> None:
print(file_header.format(cli.args.input.relative_to(QMK_FIRMWARE), cli.args.reference.relative_to(QMK_FIRMWARE)), file=outfile)

for override in to_override:
Expand All @@ -96,7 +108,7 @@ def migrate_halconf_h(to_override, outfile):
print("#include_next <halconf.h>\n", file=outfile)


def migrate_mcuconf_h(to_override, outfile):
def migrate_mcuconf_h(to_override: list[Override], outfile: TextIO) -> None:
print(file_header.format(cli.args.input.relative_to(QMK_FIRMWARE), cli.args.reference.relative_to(QMK_FIRMWARE)), file=outfile)

print("#include_next <mcuconf.h>\n", file=outfile)
Expand All @@ -113,7 +125,7 @@ def migrate_mcuconf_h(to_override, outfile):
@cli.argument('-d', '--delete', arg_only=True, action='store_true', help='If the file has no overrides, migration will delete the input file.')
@cli.argument('-f', '--force', arg_only=True, action='store_true', help='Re-migrates an already migrated file, even if it doesn\'t detect a full ChibiOS config.')
@cli.subcommand('Generates a migrated ChibiOS configuration file, as a result of comparing the input against a reference')
def chibios_confmigrate(cli):
def chibios_confmigrate(cli: MILC) -> None:
"""Generates a usable ChibiOS replacement configuration file, based on a fully-defined conf and a reference config.
"""

Expand Down
11 changes: 6 additions & 5 deletions lib/python/qmk/cli/ci/validate_aliases.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
"""Validates the list of keyboard aliases.
"""
from milc import cli
from typing import Optional
from milc import MILC, cli

from qmk.keyboard import resolve_keyboard, keyboard_folder, keyboard_alias_definitions


def _safe_keyboard_folder(target):
def _safe_keyboard_folder(target: str) -> Optional[str]:
try:
return keyboard_folder(target) # throws ValueError if it's invalid
except Exception:
return None


def _target_keyboard_exists(target):
def _target_keyboard_exists(target: Optional[str]) -> bool:
# If there's no target, then we can't build it.
if not target:
return False
Expand All @@ -30,12 +31,12 @@ def _target_keyboard_exists(target):


@cli.subcommand('Validates the list of keyboard aliases.', hidden=True)
def ci_validate_aliases(cli):
def ci_validate_aliases(cli: MILC) -> bool:
aliases = keyboard_alias_definitions()

success = True
for alias in aliases.keys():
target = aliases[alias].get('target', None)
target: Optional[str] = aliases[alias].get('target', None)
if not _target_keyboard_exists(target):
cli.log.error(f'Keyboard alias {alias} has a target that doesn\'t exist: {target}')
success = False
Expand Down
35 changes: 22 additions & 13 deletions lib/python/qmk/cli/doctor/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@
from subprocess import DEVNULL, TimeoutExpired
from tempfile import TemporaryDirectory
from pathlib import Path
from typing import TypedDict

from milc import cli
from qmk import submodules


class Version(TypedDict):
major: int
minor: int
patch: int


class CheckStatus(Enum):
OK = 1
WARNING = 2
Expand All @@ -30,8 +37,10 @@ class CheckStatus(Enum):
}


def _parse_gcc_version(version):
def _parse_gcc_version(version: str) -> Version:
m = re.match(r"(\d+)(?:\.(\d+))?(?:\.(\d+))?", version)
if m is None:
raise RuntimeError

return {
'major': int(m.group(1)),
Expand All @@ -40,7 +49,7 @@ def _parse_gcc_version(version):
}


def _check_arm_gcc_version():
def _check_arm_gcc_version() -> CheckStatus:
"""Returns True if the arm-none-eabi-gcc version is not known to cause problems.
"""
version_number = ESSENTIAL_BINARIES['arm-none-eabi-gcc']['output'].strip()
Expand All @@ -50,7 +59,7 @@ def _check_arm_gcc_version():
return _check_arm_gcc_installation()


def _check_arm_gcc_installation():
def _check_arm_gcc_installation() -> CheckStatus:
"""Returns OK if the arm-none-eabi-gcc is fully installed and can produce binaries.
"""
with TemporaryDirectory() as temp_dir:
Expand All @@ -77,7 +86,7 @@ def _check_arm_gcc_installation():
return CheckStatus.OK


def _check_avr_gcc_version():
def _check_avr_gcc_version() -> CheckStatus:
"""Returns True if the avr-gcc version is not known to cause problems.
"""
version_number = ESSENTIAL_BINARIES['avr-gcc']['output'].strip()
Expand All @@ -87,7 +96,7 @@ def _check_avr_gcc_version():
return _check_avr_gcc_installation()


def _check_avr_gcc_installation():
def _check_avr_gcc_installation() -> CheckStatus:
"""Returns OK if the avr-gcc is fully installed and can produce binaries.
"""
with TemporaryDirectory() as temp_dir:
Expand All @@ -114,31 +123,31 @@ def _check_avr_gcc_installation():
return CheckStatus.OK


def _check_avrdude_version():
def _check_avrdude_version() -> CheckStatus:
last_line = ESSENTIAL_BINARIES['avrdude']['output'].split('\n')[-2]
version_number = last_line.split()[2][:-1]
cli.log.info('Found avrdude version %s', version_number)

return CheckStatus.OK


def _check_dfu_util_version():
def _check_dfu_util_version() -> CheckStatus:
first_line = ESSENTIAL_BINARIES['dfu-util']['output'].split('\n')[0]
version_number = first_line.split()[1]
cli.log.info('Found dfu-util version %s', version_number)

return CheckStatus.OK


def _check_dfu_programmer_version():
def _check_dfu_programmer_version() -> CheckStatus:
first_line = ESSENTIAL_BINARIES['dfu-programmer']['output'].split('\n')[0]
version_number = first_line.split()[1]
cli.log.info('Found dfu-programmer version %s', version_number)

return CheckStatus.OK


def check_binaries():
def check_binaries() -> CheckStatus:
"""Iterates through ESSENTIAL_BINARIES and tests them.
"""
ok = CheckStatus.OK
Expand All @@ -155,7 +164,7 @@ def check_binaries():
return ok


def check_binary_versions():
def check_binary_versions() -> list[CheckStatus]:
"""Check the versions of ESSENTIAL_BINARIES
"""
checks = {
Expand All @@ -178,7 +187,7 @@ def check_binary_versions():
return versions


def check_submodules():
def check_submodules() -> CheckStatus:
"""Iterates through all submodules to make sure they're cloned and up to date.
"""
for submodule in submodules.status().values():
Expand All @@ -190,7 +199,7 @@ def check_submodules():
return CheckStatus.OK


def is_executable(command):
def is_executable(command: str) -> bool:
"""Returns True if command exists and can be executed.
"""
# Make sure the command is in the path.
Expand All @@ -213,7 +222,7 @@ def is_executable(command):
return False


def release_info(file='/etc/os-release'):
def release_info(file: str = '/etc/os-release') -> dict[str, str]:
"""Parse release info to dict
"""
ret = {}
Expand Down
19 changes: 10 additions & 9 deletions lib/python/qmk/cli/doctor/linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@
import platform
import shutil
from pathlib import Path
from typing import Optional

from milc import cli

from qmk.constants import QMK_FIRMWARE, BOOTLOADER_VIDS_PIDS
from .check import CheckStatus, release_info


def _is_wsl():
def _is_wsl() -> bool:
return 'microsoft' in platform.uname().release.lower()


def _udev_rule(vid, pid=None, *args):
def _udev_rule(vid: str, pid: Optional[str] = None, *args: str) -> str:
""" Helper function that return udev rules
"""
rule = ""
Expand All @@ -30,8 +31,8 @@ def _udev_rule(vid, pid=None, *args):
return rule


def _generate_desired_rules(bootloader_vids_pids):
rules = dict()
def _generate_desired_rules(bootloader_vids_pids: dict[str, set[tuple[str, str]]]) -> dict[str, set[str]]:
rules: dict[str, set[str]] = {}
for bl in bootloader_vids_pids.keys():
rules[bl] = set()
for vid_pid in bootloader_vids_pids[bl]:
Expand All @@ -42,7 +43,7 @@ def _generate_desired_rules(bootloader_vids_pids):
return rules


def _deprecated_udev_rule(vid, pid=None):
def _deprecated_udev_rule(vid: str, pid: Optional[str] = None) -> str:
""" Helper function that return udev rules

Note: these are no longer the recommended rules, this is just used to check for them
Expand All @@ -53,7 +54,7 @@ def _deprecated_udev_rule(vid, pid=None):
return 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", MODE:="0666"' % vid


def check_udev_rules():
def check_udev_rules() -> CheckStatus:
"""Make sure the udev rules look good.
"""
rc = CheckStatus.OK
Expand Down Expand Up @@ -111,13 +112,13 @@ def check_udev_rules():
return rc


def check_systemd():
def check_systemd() -> bool:
"""Check if it's a systemd system
"""
return bool(shutil.which("systemctl"))


def check_modem_manager():
def check_modem_manager() -> bool:
"""Returns True if ModemManager is running.

"""
Expand All @@ -131,7 +132,7 @@ def check_modem_manager():
return False


def os_test_linux():
def os_test_linux() -> CheckStatus:
"""Run the Linux specific tests.
"""
info = release_info()
Expand Down
2 changes: 1 addition & 1 deletion lib/python/qmk/cli/doctor/macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from .check import CheckStatus


def os_test_macos():
def os_test_macos() -> CheckStatus:
"""Run the Mac specific tests.
"""
cli.log.info("Detected {fg_cyan}macOS %s (%s){fg_reset}.", platform.mac_ver()[0], 'Apple Silicon' if platform.processor() == 'arm' else 'Intel')
Expand Down
Loading
Loading