tools: Add python types hints

This commit is contained in:
simon.chupin
2022-06-03 14:46:56 +02:00
parent b20aa0612b
commit 44f3c19fa9
12 changed files with 210 additions and 217 deletions
+27 -49
View File
@@ -7,20 +7,22 @@ import re
import shutil
import subprocess
import sys
from typing import Any, Dict, List, Optional
from urllib.error import URLError
from urllib.request import Request, urlopen
from webbrowser import open_new_tab
import click
from click.core import Context
from idf_py_actions.constants import GENERATORS, PREVIEW_TARGETS, SUPPORTED_TARGETS, URL_TO_DOC
from idf_py_actions.errors import FatalError
from idf_py_actions.global_options import global_options
from idf_py_actions.tools import (TargetChoice, ensure_build_directory, get_target, idf_version, merge_action_lists,
realpath, run_target)
from idf_py_actions.tools import (PropertyDict, TargetChoice, ensure_build_directory, get_target, idf_version,
merge_action_lists, realpath, run_target)
def action_extensions(base_actions, project_path):
def build_target(target_name, ctx, args):
def action_extensions(base_actions: Dict, project_path: str) -> Any:
def build_target(target_name: str, ctx: Context, args: PropertyDict) -> None:
"""
Execute the target build system to build target 'target_name'
@@ -30,7 +32,7 @@ def action_extensions(base_actions, project_path):
ensure_build_directory(args, ctx.info_name)
run_target(target_name, args)
def size_target(target_name, ctx, args):
def size_target(target_name: str, ctx: Context, args: PropertyDict) -> None:
"""
Builds the app and then executes a size-related target passed in 'target_name'.
`tool_error_handler` handler is used to suppress errors during the build,
@@ -38,18 +40,18 @@ def action_extensions(base_actions, project_path):
"""
def tool_error_handler(e):
def tool_error_handler(e: int) -> None:
pass
ensure_build_directory(args, ctx.info_name)
run_target('all', args, custom_error_handler=tool_error_handler)
run_target(target_name, args)
def list_build_system_targets(target_name, ctx, args):
def list_build_system_targets(target_name: str, ctx: Context, args: PropertyDict) -> None:
"""Shows list of targets known to build sytem (make/ninja)"""
build_target('help', ctx, args)
def menuconfig(target_name, ctx, args, style):
def menuconfig(target_name: str, ctx: Context, args: PropertyDict, style: str) -> None:
"""
Menuconfig target is build_target extended with the style argument for setting the value for the environment
variable.
@@ -61,7 +63,7 @@ def action_extensions(base_actions, project_path):
os.environ['MENUCONFIG_STYLE'] = style
build_target(target_name, ctx, args)
def fallback_target(target_name, ctx, args):
def fallback_target(target_name: str, ctx: Context, args: PropertyDict) -> None:
"""
Execute targets that are not explicitly known to idf.py
"""
@@ -80,42 +82,22 @@ def action_extensions(base_actions, project_path):
run_target(target_name, args)
def verbose_callback(ctx, param, value):
def verbose_callback(ctx: Context, param: List, value: str) -> Optional[str]:
if not value or ctx.resilient_parsing:
return
return None
for line in ctx.command.verbose_output:
print(line)
return value
def clean(action, ctx, args):
def clean(action: str, ctx: Context, args: PropertyDict) -> None:
if not os.path.isdir(args.build_dir):
print("Build directory '%s' not found. Nothing to clean." % args.build_dir)
return
build_target('clean', ctx, args)
def _delete_windows_symlinks(directory):
"""
It deletes symlinks recursively on Windows. It is useful for Python 2 which doesn't detect symlinks on Windows.
"""
deleted_paths = []
if os.name == 'nt':
import ctypes
for root, dirnames, _filenames in os.walk(directory):
for d in dirnames:
full_path = os.path.join(root, d)
try:
full_path = full_path.decode('utf-8')
except Exception:
pass
if ctypes.windll.kernel32.GetFileAttributesW(full_path) & 0x0400:
os.rmdir(full_path)
deleted_paths.append(full_path)
return deleted_paths
def fullclean(action, ctx, args):
def fullclean(action: str, ctx: Context, args: PropertyDict) -> None:
build_dir = args.build_dir
if not os.path.isdir(build_dir):
print("Build directory '%s' not found. Nothing to clean." % build_dir)
@@ -135,13 +117,8 @@ def action_extensions(base_actions, project_path):
raise FatalError(
"Refusing to automatically delete files in directory containing '%s'. Delete files manually if you're sure."
% red)
# OK, delete everything in the build directory...
# Note: Python 2.7 doesn't detect symlinks on Windows (it is supported form 3.2). Tools promising to not
# follow symlinks will actually follow them. Deleting the build directory with symlinks deletes also items
# outside of this directory.
deleted_symlinks = _delete_windows_symlinks(build_dir)
if args.verbose and len(deleted_symlinks) > 1:
print('The following symlinks were identified and removed:\n%s' % '\n'.join(deleted_symlinks))
if args.verbose and len(build_dir) > 1:
print('The following symlinks were identified and removed:\n%s' % '\n'.join(build_dir))
for f in os.listdir(build_dir): # TODO: once we are Python 3 only, this can be os.scandir()
f = os.path.join(build_dir, f)
if args.verbose:
@@ -151,7 +128,7 @@ def action_extensions(base_actions, project_path):
else:
os.remove(f)
def python_clean(action, ctx, args):
def python_clean(action: str, ctx: Context, args: PropertyDict) -> None:
for root, dirnames, filenames in os.walk(os.environ['IDF_PATH']):
for d in dirnames:
if d == '__pycache__':
@@ -165,7 +142,7 @@ def action_extensions(base_actions, project_path):
print('Removing: %s' % file_to_delete)
os.remove(file_to_delete)
def set_target(action, ctx, args, idf_target):
def set_target(action: str, ctx: Context, args: PropertyDict, idf_target: str) -> None:
if (not args['preview'] and idf_target in PREVIEW_TARGETS):
raise FatalError(
"%s is still in preview. You have to append '--preview' option after idf.py to use any preview feature."
@@ -180,10 +157,10 @@ def action_extensions(base_actions, project_path):
print('Set Target to: %s, new sdkconfig created. Existing sdkconfig renamed to sdkconfig.old.' % idf_target)
ensure_build_directory(args, ctx.info_name, True)
def reconfigure(action, ctx, args):
def reconfigure(action: str, ctx: Context, args: PropertyDict) -> None:
ensure_build_directory(args, ctx.info_name, True)
def validate_root_options(ctx, args, tasks):
def validate_root_options(ctx: Context, args: PropertyDict, tasks: List) -> None:
args.project_dir = realpath(args.project_dir)
if args.build_dir is not None and args.project_dir == realpath(args.build_dir):
raise FatalError(
@@ -193,7 +170,7 @@ def action_extensions(base_actions, project_path):
args.build_dir = os.path.join(args.project_dir, 'build')
args.build_dir = realpath(args.build_dir)
def idf_version_callback(ctx, param, value):
def idf_version_callback(ctx: Context, param: str, value: str) -> None:
if not value or ctx.resilient_parsing:
return
@@ -205,7 +182,7 @@ def action_extensions(base_actions, project_path):
print('ESP-IDF %s' % version)
sys.exit(0)
def list_targets_callback(ctx, param, value):
def list_targets_callback(ctx: Context, param: List, value: int) -> None:
if not value or ctx.resilient_parsing:
return
@@ -218,12 +195,13 @@ def action_extensions(base_actions, project_path):
sys.exit(0)
def show_docs(action, ctx, args, no_browser, language, starting_page, version, target):
def show_docs(action: str, ctx: Context, args: PropertyDict, no_browser: bool, language: str, starting_page: str, version: str, target: str) -> None:
if language == 'cn':
language = 'zh_CN'
if not version:
# '0.0-dev' here because if 'dev' in version it will transform in to 'latest'
version = re.search(r'v\d+\.\d+\.?\d*(-dev|-beta\d|-rc)?', idf_version() or '0.0-dev').group()
version_search = re.search(r'v\d+\.\d+\.?\d*(-dev|-beta\d|-rc)?', idf_version() or '0.0-dev')
version = version_search.group() if version_search else 'latest'
if 'dev' in version:
version = 'latest'
elif version[0] != 'v':
@@ -249,7 +227,7 @@ def action_extensions(base_actions, project_path):
print(link)
sys.exit(0)
def get_default_language():
def get_default_language() -> str:
try:
language = 'zh_CN' if locale.getdefaultlocale()[0] == 'zh_CN' else 'en'
except ValueError:
+11 -7
View File
@@ -6,13 +6,17 @@ import os
import re
import sys
from distutils.dir_util import copy_tree
from typing import Dict
import click
from idf_py_actions.tools import PropertyDict
def get_type(action):
def get_type(action: str) -> str:
return action.split('-')[1]
def replace_in_file(filename, pattern, replacement):
def replace_in_file(filename: str, pattern: str, replacement: str) -> None:
with open(filename, 'r+') as f:
content = f.read()
overwritten_content = re.sub(pattern, replacement, content, flags=re.M)
@@ -21,7 +25,7 @@ def replace_in_file(filename, pattern, replacement):
f.truncate()
def is_empty_and_create(path, action):
def is_empty_and_create(path: str, action: str) -> None:
abspath = os.path.abspath(path)
if not os.path.exists(abspath):
os.makedirs(abspath)
@@ -35,7 +39,7 @@ def is_empty_and_create(path, action):
sys.exit(3)
def create_project(target_path, name):
def create_project(target_path: str, name: str) -> None:
copy_tree(os.path.join(os.environ['IDF_PATH'], 'examples', 'get-started', 'sample_project'), target_path)
main_folder = os.path.join(target_path, 'main')
os.rename(os.path.join(main_folder, 'main.c'), os.path.join(main_folder, '.'.join((name, 'c'))))
@@ -44,7 +48,7 @@ def create_project(target_path, name):
os.remove(os.path.join(target_path, 'README.md'))
def create_component(target_path, name):
def create_component(target_path: str, name: str) -> None:
copy_tree(os.path.join(os.environ['IDF_PATH'], 'tools', 'templates', 'sample_component'), target_path)
os.rename(os.path.join(target_path, 'main.c'), os.path.join(target_path, '.'.join((name, 'c'))))
os.rename(os.path.join(target_path, 'include', 'main.h'),
@@ -54,8 +58,8 @@ def create_component(target_path, name):
replace_in_file(os.path.join(target_path, 'CMakeLists.txt'), 'main', name)
def action_extensions(base_actions, project_path):
def create_new(action, ctx, global_args, **action_args):
def action_extensions(base_actions: Dict, project_path: str) -> Dict:
def create_new(action: str, ctx: click.core.Context, global_args: PropertyDict, **action_args: str) -> Dict:
target_path = action_args.get('path') or os.path.join(project_path, action_args['name'])
is_empty_and_create(target_path, action)
+23 -22
View File
@@ -9,21 +9,22 @@ import sys
import threading
import time
from threading import Thread
from typing import Any, Dict, List
from typing import Any, Dict, List, Optional
from click.core import Context
from idf_py_actions.errors import FatalError
from idf_py_actions.tools import ensure_build_directory
from idf_py_actions.tools import PropertyDict, ensure_build_directory
PYTHON = sys.executable
def action_extensions(base_actions, project_path):
def action_extensions(base_actions: Dict, project_path: str) -> Dict:
OPENOCD_OUT_FILE = 'openocd_out.txt'
GDBGUI_OUT_FILE = 'gdbgui_out.txt'
# Internal dictionary of currently active processes, threads and their output files
processes = {'threads_to_join': [], 'openocd_issues': None}
processes: Dict = {'threads_to_join': [], 'openocd_issues': None}
def _check_for_common_openocd_issues(file_name, print_all=True):
def _check_for_common_openocd_issues(file_name: str, print_all: bool=True) -> Any:
if processes['openocd_issues'] is not None:
return processes['openocd_issues']
try:
@@ -39,7 +40,7 @@ def action_extensions(base_actions, project_path):
processes['openocd_issues'] = message
return message
def _check_openocd_errors(fail_if_openocd_failed, target, ctx):
def _check_openocd_errors(fail_if_openocd_failed: Dict, target: str, ctx: Context) -> None:
if fail_if_openocd_failed:
if 'openocd' in processes and processes['openocd'] is not None:
p = processes['openocd']
@@ -62,7 +63,7 @@ def action_extensions(base_actions, project_path):
# OpenOCD exited or error message detected -> print possible output and terminate
raise FatalError('Action "{}" failed due to errors in OpenOCD:\n{}'.format(target, _check_for_common_openocd_issues(name)), ctx)
def _terminate_async_target(target):
def _terminate_async_target(target: str) -> None:
if target in processes and processes[target] is not None:
try:
if target + '_outfile' in processes:
@@ -86,11 +87,11 @@ def action_extensions(base_actions, project_path):
print('Failed to close/kill {}'.format(target))
processes[target] = None # to indicate this has ended
def is_gdb_with_python(gdb):
def is_gdb_with_python(gdb: str) -> bool:
# execute simple python command to check is it supported
return subprocess.run([gdb, '--batch-silent', '--ex', 'python import os'], stderr=subprocess.DEVNULL).returncode == 0
def create_local_gdbinit(gdb, gdbinit, elf_file):
def create_local_gdbinit(gdb: str, gdbinit: str, elf_file: str) -> None:
with open(gdbinit, 'w') as f:
if is_gdb_with_python(gdb):
f.write('python\n')
@@ -107,7 +108,7 @@ def action_extensions(base_actions, project_path):
f.write('thb app_main\n')
f.write('c\n')
def debug_cleanup():
def debug_cleanup() -> None:
print('cleaning up debug targets')
for t in processes['threads_to_join']:
if threading.currentThread() != t:
@@ -116,7 +117,7 @@ def action_extensions(base_actions, project_path):
_terminate_async_target('gdbgui')
_terminate_async_target('gdb')
def post_debug(action, ctx, args, **kwargs):
def post_debug(action: str, ctx: Context, args: PropertyDict, **kwargs: str) -> None:
""" Deal with asynchronous targets, such as openocd running in background """
if kwargs['block'] == 1:
for target in ['openocd', 'gdbgui']:
@@ -143,7 +144,7 @@ def action_extensions(base_actions, project_path):
_terminate_async_target('openocd')
_terminate_async_target('gdbgui')
def get_project_desc(args, ctx):
def get_project_desc(args: PropertyDict, ctx: Context) -> Any:
desc_path = os.path.join(args.build_dir, 'project_description.json')
if not os.path.exists(desc_path):
ensure_build_directory(args, ctx.info_name)
@@ -151,7 +152,7 @@ def action_extensions(base_actions, project_path):
project_desc = json.load(f)
return project_desc
def openocd(action, ctx, args, openocd_scripts, openocd_commands):
def openocd(action: str, ctx: Context, args: PropertyDict, openocd_scripts: Optional[str], openocd_commands: str) -> None:
"""
Execute openocd as external tool
"""
@@ -188,14 +189,14 @@ def action_extensions(base_actions, project_path):
processes['openocd_outfile_name'] = openocd_out_name
print('OpenOCD started as a background task {}'.format(process.pid))
def get_gdb_args(gdbinit, project_desc: Dict[str, Any]) -> List[str]:
def get_gdb_args(gdbinit: str, project_desc: Dict[str, Any]) -> List:
args = ['-x={}'.format(gdbinit)]
debug_prefix_gdbinit = project_desc.get('debug_prefix_map_gdbinit')
if debug_prefix_gdbinit:
args.append('-ix={}'.format(debug_prefix_gdbinit))
return args
def gdbui(action, ctx, args, gdbgui_port, gdbinit, require_openocd):
def gdbui(action: str, ctx: Context, args: PropertyDict, gdbgui_port: Optional[str], gdbinit: Optional[str], require_openocd: bool) -> None:
"""
Asynchronous GDB-UI target
"""
@@ -211,8 +212,8 @@ def action_extensions(base_actions, project_path):
# - '"-x=foo -x=bar"', would return ['foo bar']
# - '-x=foo', would return ['-x', 'foo'] and mess up the former option '--gdb-args'
# so for one item, use extra double quotes. for more items, use no extra double quotes.
gdb_args = get_gdb_args(gdbinit, project_desc)
gdb_args = '"{}"'.format(' '.join(gdb_args)) if len(gdb_args) == 1 else ' '.join(gdb_args)
gdb_args_list = get_gdb_args(gdbinit, project_desc)
gdb_args = '"{}"'.format(' '.join(gdb_args_list)) if len(gdb_args_list) == 1 else ' '.join(gdb_args_list)
args = ['gdbgui', '-g', gdb, '--gdb-args', gdb_args]
print(args)
@@ -238,8 +239,8 @@ def action_extensions(base_actions, project_path):
print('gdbgui started as a background task {}'.format(process.pid))
_check_openocd_errors(fail_if_openocd_failed, action, ctx)
def global_callback(ctx, global_args, tasks):
def move_to_front(task_name):
def global_callback(ctx: Context, global_args: PropertyDict, tasks: List) -> None:
def move_to_front(task_name: str) -> None:
for index, task in enumerate(tasks):
if task.name == task_name:
tasks.insert(0, tasks.pop(index))
@@ -264,18 +265,18 @@ def action_extensions(base_actions, project_path):
if task.name in ('gdb', 'gdbgui', 'gdbtui'):
task.action_args['require_openocd'] = True
def run_gdb(gdb_args):
def run_gdb(gdb_args: List) -> int:
p = subprocess.Popen(gdb_args)
processes['gdb'] = p
return p.wait()
def gdbtui(action, ctx, args, gdbinit, require_openocd):
def gdbtui(action: str, ctx: Context, args: PropertyDict, gdbinit: str, require_openocd: bool) -> None:
"""
Synchronous GDB target with text ui mode
"""
gdb(action, ctx, args, 1, gdbinit, require_openocd)
def gdb(action, ctx, args, gdb_tui, gdbinit, require_openocd):
def gdb(action: str, ctx: Context, args: PropertyDict, gdb_tui: Optional[int], gdbinit: Optional[str], require_openocd: bool) -> None:
"""
Synchronous GDB target
"""
+8 -5
View File
@@ -1,22 +1,25 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
from typing import Dict
from click.core import Context
from idf_py_actions.errors import FatalError
from idf_py_actions.tools import ensure_build_directory, is_target_supported, run_target
from idf_py_actions.tools import PropertyDict, ensure_build_directory, is_target_supported, run_target
def action_extensions(base_actions, project_path):
def action_extensions(base_actions: Dict, project_path: str) -> Dict:
SUPPORTED_TARGETS = ['esp32s2']
def dfu_target(target_name, ctx, args, part_size):
def dfu_target(target_name: str, ctx: Context, args: PropertyDict, part_size: str) -> None:
ensure_build_directory(args, ctx.info_name)
run_target(target_name, args, {'ESP_DFU_PART_SIZE': part_size} if part_size else {})
def dfu_list_target(target_name, ctx, args):
def dfu_list_target(target_name: str, ctx: Context, args: PropertyDict) -> None:
ensure_build_directory(args, ctx.info_name)
run_target(target_name, args)
def dfu_flash_target(target_name, ctx, args, path):
def dfu_flash_target(target_name: str, ctx: Context, args: PropertyDict, path: str) -> None:
ensure_build_directory(args, ctx.info_name)
try:
+4 -1
View File
@@ -1,11 +1,14 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
from click.core import Context
class FatalError(RuntimeError):
"""
Wrapper class for runtime errors that aren't caused by bugs in idf.py or the build process.
"""
def __init__(self, message, ctx=None):
def __init__(self, message: str, ctx: Context=None):
super(RuntimeError, self).__init__(message)
# if context is defined, check for the cleanup tasks
if ctx is not None and 'cleanup' in ctx.meta:
+13 -11
View File
@@ -4,18 +4,19 @@
import json
import os
import sys
from typing import Any, Dict, List
import click
from idf_monitor_base.output_helpers import yellow_print
from idf_py_actions.errors import FatalError, NoSerialPortFoundError
from idf_py_actions.global_options import global_options
from idf_py_actions.tools import ensure_build_directory, get_sdkconfig_value, run_target, run_tool
from idf_py_actions.tools import PropertyDict, ensure_build_directory, get_sdkconfig_value, run_target, run_tool
PYTHON = sys.executable
def action_extensions(base_actions, project_path):
def _get_project_desc(ctx, args):
def action_extensions(base_actions: Dict, project_path: str) -> Dict:
def _get_project_desc(ctx: click.core.Context, args: PropertyDict) -> Any:
desc_path = os.path.join(args.build_dir, 'project_description.json')
if not os.path.exists(desc_path):
ensure_build_directory(args, ctx.info_name)
@@ -23,7 +24,7 @@ def action_extensions(base_actions, project_path):
project_desc = json.load(f)
return project_desc
def _get_default_serial_port(args):
def _get_default_serial_port(args: PropertyDict) -> Any:
# Import is done here in order to move it after the check_environment() ensured that pyserial has been installed
try:
import esptool
@@ -45,7 +46,7 @@ def action_extensions(base_actions, project_path):
except Exception as e:
raise FatalError('An exception occurred during detection of the serial port: {}'.format(e))
def _get_esptool_args(args):
def _get_esptool_args(args: PropertyDict) -> List:
esptool_path = os.path.join(os.environ['IDF_PATH'], 'components/esptool_py/esptool/esptool.py')
esptool_wrapper_path = os.environ.get('ESPTOOL_WRAPPER', '')
if args.port is None:
@@ -68,7 +69,7 @@ def action_extensions(base_actions, project_path):
result += ['--no-stub']
return result
def _get_commandline_options(ctx):
def _get_commandline_options(ctx: click.core.Context) -> List:
""" Return all the command line options up to first action """
# This approach ignores argument parsing done Click
result = []
@@ -81,7 +82,8 @@ def action_extensions(base_actions, project_path):
return result
def monitor(action, ctx, args, print_filter, monitor_baud, encrypted, no_reset, timestamps, timestamp_format):
def monitor(action: str, ctx: click.core.Context, args: PropertyDict, print_filter: str, monitor_baud: str, encrypted: bool,
no_reset: bool, timestamps: bool, timestamp_format: str) -> None:
"""
Run idf_monitor.py to watch build output
"""
@@ -152,7 +154,7 @@ def action_extensions(base_actions, project_path):
run_tool('idf_monitor', monitor_args, args.project_dir)
def flash(action, ctx, args):
def flash(action: str, ctx: click.core.Context, args: PropertyDict) -> None:
"""
Run esptool to flash the entire project, from an argfile generated by the build system
"""
@@ -165,13 +167,13 @@ def action_extensions(base_actions, project_path):
esp_port = args.port or _get_default_serial_port(args)
run_target(action, args, {'ESPBAUD': str(args.baud), 'ESPPORT': esp_port})
def erase_flash(action, ctx, args):
def erase_flash(action: str, ctx: click.core.Context, args: PropertyDict) -> None:
ensure_build_directory(args, ctx.info_name)
esptool_args = _get_esptool_args(args)
esptool_args += ['erase_flash']
run_tool('esptool.py', esptool_args, args.build_dir)
def global_callback(ctx, global_args, tasks):
def global_callback(ctx: click.core.Context, global_args: Dict, tasks: PropertyDict) -> None:
encryption = any([task.name in ('encrypted-flash', 'encrypted-app-flash') for task in tasks])
if encryption:
for task in tasks:
@@ -179,7 +181,7 @@ def action_extensions(base_actions, project_path):
task.action_args['encrypted'] = True
break
def ota_targets(target_name, ctx, args):
def ota_targets(target_name: str, ctx: click.core.Context, args: PropertyDict) -> None:
"""
Execute the target build system to build target 'target_name'.
Additionally set global variables for baud and port.
+22 -4
View File
@@ -5,6 +5,7 @@ import re
import subprocess
import sys
from io import open
from typing import Any, List
import click
@@ -340,12 +341,12 @@ class TargetChoice(click.Choice):
- ignores hyphens
- not case sensitive
"""
def __init__(self, choices):
def __init__(self, choices: List) -> None:
super(TargetChoice, self).__init__(choices, case_sensitive=False)
def convert(self, value, param, ctx):
def normalize(str):
return str.lower().replace('-', '')
def convert(self, value: Any, param: click.Parameter, ctx: click.Context) -> Any:
def normalize(string: str) -> str:
return string.lower().replace('-', '')
saved_token_normalize_func = ctx.token_normalize_func
ctx.token_normalize_func = normalize
@@ -354,3 +355,20 @@ class TargetChoice(click.Choice):
return super(TargetChoice, self).convert(value, param, ctx)
finally:
ctx.token_normalize_func = saved_token_normalize_func
class PropertyDict(dict):
def __getattr__(self, name: str) -> Any:
if name in self:
return self[name]
else:
raise AttributeError("'PropertyDict' object has no attribute '%s'" % name)
def __setattr__(self, name: str, value: Any) -> None:
self[name] = value
def __delattr__(self, name: str) -> None:
if name in self:
del self[name]
else:
raise AttributeError("'PropertyDict' object has no attribute '%s'" % name)
+6 -3
View File
@@ -1,10 +1,13 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
from idf_py_actions.tools import ensure_build_directory, run_target
from typing import Dict, List
from click.core import Context
from idf_py_actions.tools import PropertyDict, ensure_build_directory, run_target
def action_extensions(base_actions, project_path):
def uf2_target(target_name, ctx, args):
def action_extensions(base_actions: Dict, project_path: List) -> Dict:
def uf2_target(target_name: str, ctx: Context, args: PropertyDict) -> None:
ensure_build_directory(args, ctx.info_name)
run_target(target_name, args)