Get rid of dict annotations

This commit is contained in:
Florian Bruhin 2016-05-10 22:03:10 +02:00
parent d990032a2e
commit 1611562271
5 changed files with 28 additions and 62 deletions

View File

@ -416,21 +416,17 @@ The types of the function arguments are inferred based on their default values,
e.g. an argument `foo=True` will be converted to a flag `-f`/`--foo` in e.g. an argument `foo=True` will be converted to a flag `-f`/`--foo` in
qutebrowser's commandline. qutebrowser's commandline.
This behavior can be overridden using Python's The type can be overridden using Python's
http://legacy.python.org/dev/peps/pep-3107/[function annotations]. The http://legacy.python.org/dev/peps/pep-3107/[function annotations]:
annotation should always be a `dict`, like this:
[source,python] [source,python]
---- ----
@cmdutils.register(...) @cmdutils.register(...)
def foo(bar: {'type': int}, baz=True): def foo(bar: int, baz=True):
... ...
---- ----
The following keys are supported in the dict: Possible values:
* `type`: The type this value should have. The value entered by the user is
then automatically checked. Possible values:
- A callable (`int`, `float`, etc.): Gets called to validate/convert the - A callable (`int`, `float`, etc.): Gets called to validate/convert the
value. value.
- A string: The value must match exactly (mainly useful with tuples to get - A string: The value must match exactly (mainly useful with tuples to get

View File

@ -455,8 +455,7 @@ class CommandDispatcher:
self._open(url, tab, background, window) self._open(url, tab, background, window)
@cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.register(instance='command-dispatcher', scope='window')
def navigate(self, where: {'type': ('prev', 'next', 'up', 'increment', def navigate(self, where: ('prev', 'next', 'up', 'increment', 'decrement'),
'decrement')},
tab=False, bg=False, window=False): tab=False, bg=False, window=False):
"""Open typical prev/next links or navigate using the URL path. """Open typical prev/next links or navigate using the URL path.
@ -505,7 +504,7 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', hide=True, @cmdutils.register(instance='command-dispatcher', hide=True,
scope='window') scope='window')
@cmdutils.argument('count', count=True) @cmdutils.argument('count', count=True)
def scroll_px(self, dx: {'type': int}, dy: {'type': int}, count=1): def scroll_px(self, dx: int, dy: int, count=1):
"""Scroll the current tab by 'count * dx/dy' pixels. """Scroll the current tab by 'count * dx/dy' pixels.
Args: Args:
@ -522,7 +521,7 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', hide=True, @cmdutils.register(instance='command-dispatcher', hide=True,
scope='window') scope='window')
@cmdutils.argument('count', count=True) @cmdutils.argument('count', count=True)
def scroll(self, direction: {'type': (str, int)}, count=1): def scroll(self, direction: (str, int), count=1):
"""Scroll the current tab in the given direction. """Scroll the current tab in the given direction.
Args: Args:
@ -583,8 +582,7 @@ class CommandDispatcher:
scope='window') scope='window')
@cmdutils.argument('count', count=True) @cmdutils.argument('count', count=True)
@cmdutils.argument('horizontal', flag='x') @cmdutils.argument('horizontal', flag='x')
def scroll_perc(self, perc: {'type': float}=None, horizontal=False, def scroll_perc(self, perc: float=None, horizontal=False, count=None):
count=None):
"""Scroll to a specific percentage of the page. """Scroll to a specific percentage of the page.
The percentage can be given either as argument or as count. The percentage can be given either as argument or as count.
@ -622,9 +620,9 @@ class CommandDispatcher:
@cmdutils.argument('count', count=True) @cmdutils.argument('count', count=True)
@cmdutils.argument('top_navigate', metavar='ACTION') @cmdutils.argument('top_navigate', metavar='ACTION')
@cmdutils.argument('bottom_navigate', metavar='ACTION') @cmdutils.argument('bottom_navigate', metavar='ACTION')
def scroll_page(self, x: {'type': float}, y: {'type': float}, *, def scroll_page(self, x: float, y: float, *,
top_navigate: {'type': ('prev', 'decrement')}=None, top_navigate: ('prev', 'decrement')=None,
bottom_navigate: {'type': ('next', 'increment')}=None, bottom_navigate: ('next', 'increment')=None,
count=1): count=1):
"""Scroll the frame page-wise. """Scroll the frame page-wise.
@ -738,7 +736,7 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True) @cmdutils.argument('count', count=True)
def zoom(self, zoom: {'type': int}=None, count=None): def zoom(self, zoom: int=None, count=None):
"""Set the zoom level for the current tab. """Set the zoom level for the current tab.
The zoom can be given as argument or as [count]. If neither of both is The zoom can be given as argument or as [count]. If neither of both is
@ -921,7 +919,7 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True) @cmdutils.argument('count', count=True)
def tab_focus(self, index: {'type': (int, 'last')}=None, count=None): def tab_focus(self, index: (int, 'last')=None, count=None):
"""Select the tab given as argument/[count]. """Select the tab given as argument/[count].
If neither count nor index are given, it behaves like tab-next. If neither count nor index are given, it behaves like tab-next.
@ -954,7 +952,7 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True) @cmdutils.argument('count', count=True)
def tab_move(self, direction: {'type': ('+', '-')}=None, count=None): def tab_move(self, direction: ('+', '-')=None, count=None):
"""Move the current tab. """Move the current tab.
Args: Args:

View File

@ -87,14 +87,8 @@ class Command:
_count: The count set for the command. _count: The count set for the command.
_instance: The object to bind 'self' to. _instance: The object to bind 'self' to.
_scope: The scope to get _instance for in the object registry. _scope: The scope to get _instance for in the object registry.
Class attributes:
AnnotationInfo: Named tuple for info from an annotation.
""" """
AnnotationInfo = collections.namedtuple(
'AnnotationInfo', ['type'])
def __init__(self, *, handler, name, instance=None, maxsplit=None, def __init__(self, *, handler, name, instance=None, maxsplit=None,
hide=False, completion=None, modes=None, not_modes=None, hide=False, completion=None, modes=None, not_modes=None,
needs_js=False, debug=False, ignore_args=False, needs_js=False, debug=False, ignore_args=False,
@ -256,14 +250,13 @@ class Command:
if not self.ignore_args: if not self.ignore_args:
for param in signature.parameters.values(): for param in signature.parameters.values():
annotation_info = self._parse_annotation(param)
if param.name == 'self': if param.name == 'self':
continue continue
if self._inspect_special_param(param): if self._inspect_special_param(param):
continue continue
typ = self._get_type(param, annotation_info) typ = self._get_type(param)
kwargs = self._param_to_argparse_kwargs(param, annotation_info) kwargs = self._param_to_argparse_kwargs(param, typ)
args = self._param_to_argparse_args(param, annotation_info) args = self._param_to_argparse_args(param, typ)
self._type_conv.update(self._get_typeconv(param, typ)) self._type_conv.update(self._get_typeconv(param, typ))
callsig = debug_utils.format_call( callsig = debug_utils.format_call(
self.parser.add_argument, args, kwargs, self.parser.add_argument, args, kwargs,
@ -273,18 +266,17 @@ class Command:
self.parser.add_argument(*args, **kwargs) self.parser.add_argument(*args, **kwargs)
return signature.parameters.keys() return signature.parameters.keys()
def _param_to_argparse_kwargs(self, param, annotation_info): def _param_to_argparse_kwargs(self, param, typ):
"""Get argparse keyword arguments for a parameter. """Get argparse keyword arguments for a parameter.
Args: Args:
param: The inspect.Parameter object to get the args for. param: The inspect.Parameter object to get the args for.
annotation_info: An AnnotationInfo tuple for the parameter. typ: The type of the parameter
Return: Return:
A kwargs dict. A kwargs dict.
""" """
kwargs = {} kwargs = {}
typ = self._get_type(param, annotation_info)
try: try:
kwargs['help'] = self.docparser.arg_descs[param.name] kwargs['help'] = self.docparser.arg_descs[param.name]
@ -314,12 +306,12 @@ class Command:
kwargs['nargs'] = '?' kwargs['nargs'] = '?'
return kwargs return kwargs
def _param_to_argparse_args(self, param, annotation_info): def _param_to_argparse_args(self, param, typ):
"""Get argparse positional arguments for a parameter. """Get argparse positional arguments for a parameter.
Args: Args:
param: The inspect.Parameter object to get the args for. param: The inspect.Parameter object to get the args for.
annotation_info: An AnnotationInfo tuple for the parameter. typ: The type of the parameter
Return: Return:
A list of args. A list of args.
@ -337,7 +329,6 @@ class Command:
raise ValueError("Flag '{}' of parameter {} (command {}) must be " raise ValueError("Flag '{}' of parameter {} (command {}) must be "
"exactly 1 char!".format(shortname, name, "exactly 1 char!".format(shortname, name,
self.name)) self.name))
typ = self._get_type(param, annotation_info)
if typ is bool or param.kind == inspect.Parameter.KEYWORD_ONLY: if typ is bool or param.kind == inspect.Parameter.KEYWORD_ONLY:
long_flag = '--{}'.format(name) long_flag = '--{}'.format(name)
short_flag = '-{}'.format(shortname) short_flag = '-{}'.format(shortname)
@ -351,33 +342,14 @@ class Command:
self.pos_args.append((param.name, name)) self.pos_args.append((param.name, name))
return args return args
def _parse_annotation(self, param): def _get_type(self, param):
"""Get argparse arguments and type from a parameter annotation.
Args:
param: A inspect.Parameter instance.
Return:
An AnnotationInfo namedtuple.
typ: The type to use for this argument.
name: The long name if overridden.
"""
info = {'type': None}
if param.annotation is not inspect.Parameter.empty:
log.commands.vdebug("Parsing annotation {}".format(
param.annotation))
info['type'] = param.annotation['type']
return self.AnnotationInfo(**info)
def _get_type(self, param, annotation_info):
"""Get the type of an argument from its default value or annotation. """Get the type of an argument from its default value or annotation.
Args: Args:
param: The inspect.Parameter to look at. param: The inspect.Parameter to look at.
annotation_info: An AnnotationInfo tuple which overrides the type.
""" """
if annotation_info.type is not None: if param.annotation is not inspect.Parameter.empty:
return annotation_info.type return param.annotation
elif param.default is None or param.default is inspect.Parameter.empty: elif param.default is None or param.default is inspect.Parameter.empty:
return None return None
else: else:

View File

@ -388,7 +388,7 @@ class SessionManager(QObject):
completion=[usertypes.Completion.sessions], completion=[usertypes.Completion.sessions],
instance='session-manager') instance='session-manager')
@cmdutils.argument('win_id', win_id=True) @cmdutils.argument('win_id', win_id=True)
def session_save(self, win_id, name: {'type': str}=default, current=False, def session_save(self, win_id, name: str=default, current=False,
quiet=False, force=False): quiet=False, force=False):
"""Save a session. """Save a session.

View File

@ -41,7 +41,7 @@ from PyQt5.QtWidgets import QApplication # pylint: disable=unused-import
@cmdutils.register(maxsplit=1, no_cmd_split=True) @cmdutils.register(maxsplit=1, no_cmd_split=True)
@cmdutils.argument('win_id', win_id=True) @cmdutils.argument('win_id', win_id=True)
def later(ms: {'type': int}, command, win_id): def later(ms: int, command, win_id):
"""Execute a command after some time. """Execute a command after some time.
Args: Args:
@ -71,7 +71,7 @@ def later(ms: {'type': int}, command, win_id):
@cmdutils.register(maxsplit=1, no_cmd_split=True) @cmdutils.register(maxsplit=1, no_cmd_split=True)
@cmdutils.argument('win_id', win_id=True) @cmdutils.argument('win_id', win_id=True)
def repeat(times: {'type': int}, command, win_id): def repeat(times: int, command, win_id):
"""Repeat a given command. """Repeat a given command.
Args: Args:
@ -119,7 +119,7 @@ def message_warning(win_id, text):
@cmdutils.register(debug=True) @cmdutils.register(debug=True)
def debug_crash(typ: {'type': ('exception', 'segfault')}='exception'): def debug_crash(typ: ('exception', 'segfault')='exception'):
"""Crash for debugging purposes. """Crash for debugging purposes.
Args: Args: