Skip to content

Commit

Permalink
core: session: annotations future
Browse files Browse the repository at this point in the history
  • Loading branch information
flit committed Aug 6, 2023
1 parent 3fc2104 commit 4d8b29a
Showing 1 changed file with 51 additions and 25 deletions.
76 changes: 51 additions & 25 deletions pyocd/core/session.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# pyOCD debugger
# Copyright (c) 2018-2020 Arm Limited
# Copyright (c) 2021-2022 Chris Reed
# Copyright (c) 2021-2023 Chris Reed
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -15,16 +15,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations

from contextlib import contextmanager
import logging
import logging.config
import yaml
import os
from pathlib import Path
import sys
import weakref
from inspect import (getfullargspec, signature)
from types import SimpleNamespace
from typing import (Any, Callable, Generator, Sequence, Union, cast, Dict, List, Mapping, Optional, TYPE_CHECKING)
from typing_extensions import Self

from . import exceptions
from .options_manager import OptionsManager
Expand All @@ -38,6 +42,9 @@
from ..gdbserver.gdbserver import GDBServer
from ..board.board import Board

# Check whether the eval_str parameter for inspect.signature is available.
HAS_SIGNATURE_EVAL_STR = (sys.version_info[:2] >= (3, 10))

LOG = logging.getLogger(__name__)

## @brief Set of default config filenames to search for.
Expand Down Expand Up @@ -95,7 +102,7 @@ class Session(Notifier):
_current_session: Optional[weakref.ref] = None

@classmethod
def get_current(cls) -> "Session":
def get_current(cls) -> Self:
"""@brief Return the most recently created Session instance or a default Session.
By default this method will return the most recently created Session object that is
Expand All @@ -111,11 +118,11 @@ def get_current(cls) -> "Session":
if session is not None:
return session

return Session(None)
return cls(None)

def __init__(
self,
probe: Optional["DebugProbe"],
probe: Optional[DebugProbe],
auto_open: bool = True,
options: Optional[Mapping[str, Any]] = None,
option_defaults: Optional[Mapping[str, Any]] = None,
Expand Down Expand Up @@ -159,8 +166,8 @@ def __init__(
self._delegate: Optional[Any] = None
self._auto_open = auto_open
self._options = OptionsManager()
self._gdbservers: Dict[int, "GDBServer"] = {}
self._probeserver: Optional["DebugProbeServer"] = None
self._gdbservers: Dict[int, GDBServer] = {}
self._probeserver: Optional[DebugProbeServer] = None
self._context_state = SimpleNamespace()

# Set this session on the probe, if we were given a probe.
Expand Down Expand Up @@ -314,17 +321,17 @@ def is_open(self) -> bool:
return self._inited and not self._closed

@property
def probe(self) -> Optional["DebugProbe"]:
def probe(self) -> Optional[DebugProbe]:
"""@brief The @ref pyocd.probe.debug_probe.DebugProbe "DebugProbe" instance."""
return self._probe

@property
def board(self) -> Optional["Board"]:
def board(self) -> Optional[Board]:
"""@brief The @ref pyocd.board.board.Board "Board" object."""
return self._board

@property
def target(self) -> Optional["SoCTarget"]:
def target(self) -> Optional[SoCTarget]:
"""@brief The @ref pyocd.core.target.soc_target "SoCTarget" object representing the SoC.
This is the @ref pyocd.core.target.soc_target "SoCTarget" instance owned by the board.
Expand Down Expand Up @@ -352,7 +359,7 @@ def delegate(self, new_delegate: Any) -> None:
self._delegate = new_delegate

@property
def user_script_proxy(self) -> "UserScriptDelegateProxy":
def user_script_proxy(self) -> UserScriptDelegateProxy:
"""@brief The UserScriptDelegateProxy object for a loaded user script."""
# Create a proxy if there isn't already one. This is a fallback in case there isn't a user script,
# yet a Python $-command is executed and needs the user script namespace in which to run.
Expand All @@ -363,21 +370,21 @@ def user_script_proxy(self) -> "UserScriptDelegateProxy":
return self._user_script_proxy

@property
def user_script_print_proxy(self) -> "PrintProxy":
def user_script_print_proxy(self) -> PrintProxy:
return self._user_script_print_proxy

@property
def gdbservers(self) -> Dict[int, "GDBServer"]:
def gdbservers(self) -> Dict[int, GDBServer]:
"""@brief Dictionary of core numbers to @ref pyocd.gdbserver.gdbserver.GDBServer "GDBServer" instances."""
return self._gdbservers

@property
def probeserver(self) -> Optional["DebugProbeServer"]:
def probeserver(self) -> Optional[DebugProbeServer]:
"""@brief A @ref pyocd.probe.tcp_probe_server.DebugProbeServer "DebugProbeServer" instance."""
return self._probeserver

@probeserver.setter
def probeserver(self, server: "DebugProbeServer") -> None:
def probeserver(self, server: DebugProbeServer) -> None:
"""@brief Setter for the `probeserver` property."""
self._probeserver = server

Expand Down Expand Up @@ -405,7 +412,7 @@ def __enter__(self) -> "Session":
raise
return self

def __exit__(self, exc_type: type, value: Any, traceback: "TracebackType") -> bool:
def __exit__(self, exc_type: type, value: Any, traceback: TracebackType) -> bool:
self.close()
return False

Expand Down Expand Up @@ -642,7 +649,10 @@ def _command_decorator(fn: Callable):
classname = names_list[0].capitalize() + "Command"

# Examine the command function's signature to extract arguments and their types.
sig = signature(fn)
if HAS_SIGNATURE_EVAL_STR:
sig = signature(fn, eval_str=True)
else:
sig = signature(fn)
arg_converters = []
has_var_args = False
usage_fields: List[str] = []
Expand All @@ -663,19 +673,35 @@ def _command_decorator(fn: Callable):
if typ is parm.empty:
LOG.error("user command function '%s' is missing type annotation for parameter '%s'",
fn.__name__, parm.name)
return fn
return None

# If we don't have Python 3.10 or later, then we must manually un-stringize the type.
# Using eval() to un-stringize won't work in all cases, but is sufficient for the types
# supported by pyocd's commands.
if not HAS_SIGNATURE_EVAL_STR:
try:
typ = eval(typ, fn.__globals__)
except Exception:
LOG.error("parameter '%s' of user command function '%s' has an unsupported type",
parm.name, fn.__name__)
return None

# Otherwise add to param converter list.
if issubclass(typ, str):
arg_converters.append(lambda _, x: x)
elif issubclass(typ, float):
arg_converters.append(lambda _, x: float(x))
elif issubclass(typ, int):
arg_converters.append(CommandBase._convert_value)
else:
try:
if issubclass(typ, str):
arg_converters.append(lambda _, x: x)
elif issubclass(typ, float):
arg_converters.append(lambda _, x: float(x))
elif issubclass(typ, int):
arg_converters.append(CommandBase._convert_value)
else:
LOG.error("parameter '%s' of user command function '%s' has an unsupported type",
parm.name, fn.__name__)
return None
except TypeError:
LOG.error("parameter '%s' of user command function '%s' has an unsupported type",
parm.name, fn.__name__)
return fn
return None
usage_fields.append(parm.name.upper())

# parse() method of the new command class.
Expand Down

0 comments on commit 4d8b29a

Please sign in to comment.