diff options
author | Jan Cholasta <jcholast@redhat.com> | 2016-11-07 14:01:10 +0100 |
---|---|---|
committer | Jan Cholasta <jcholast@redhat.com> | 2016-11-11 12:17:25 +0100 |
commit | a929ac333833a5cbf503d1fcbdee150658d933a4 (patch) | |
tree | 48ba4ca356525b8433507a17bee4ebbfd719b506 /ipapython | |
parent | 9fd1981ae8abf720f5234b6049c9beabbb1f2211 (diff) | |
download | freeipa-a929ac333833a5cbf503d1fcbdee150658d933a4.tar.gz freeipa-a929ac333833a5cbf503d1fcbdee150658d933a4.tar.xz freeipa-a929ac333833a5cbf503d1fcbdee150658d933a4.zip |
install: use standard Python classes to declare knob types
Use type(None) rather than bool to define knobs which are represented as
command line flags. This allows declaring both "--option" and
"--option={0,1}"-style command line options.
Use enum.Enum subclasses instead of set literals to declare enumerations.
Use typing.List[T] instead of (list, T) to declare lists. (Note that a
minimal reimplementation of typing.List is used instead of the Python 2
backport of the typing module due to non-technical reasons.)
Use CheckedIPAddress instead of 'ip' and 'ip-local' to declare IP
addresses.
https://fedorahosted.org/freeipa/ticket/6392
Reviewed-By: Martin Basti <mbasti@redhat.com>
Diffstat (limited to 'ipapython')
-rw-r--r-- | ipapython/install/cli.py | 25 | ||||
-rw-r--r-- | ipapython/install/core.py | 36 | ||||
-rw-r--r-- | ipapython/install/typing.py | 34 |
3 files changed, 83 insertions, 12 deletions
diff --git a/ipapython/install/cli.py b/ipapython/install/cli.py index a91afe5fd..65fc9529f 100644 --- a/ipapython/install/cli.py +++ b/ipapython/install/cli.py @@ -7,6 +7,7 @@ Command line support. """ import collections +import enum import functools import optparse import signal @@ -14,7 +15,7 @@ import signal import six from ipapython import admintool, ipa_log_manager -from ipapython.ipautil import private_ccache +from ipapython.ipautil import CheckedIPAddress, private_ccache from . import core, common @@ -23,6 +24,8 @@ __all__ = ['install_tool', 'uninstall_tool'] if six.PY3: long = int +NoneType = type(None) + def _get_usage(configurable_class): usage = '%prog [options]' @@ -143,13 +146,17 @@ class ConfigureTool(admintool.AdminTool): parser, "{0} options".format(group_cls.description)) knob_type = knob_cls.type - if isinstance(knob_type, tuple): - knob_scalar_type = knob_type[1] + if issubclass(knob_type, list): + try: + # typing.List[X].__parameters__ == (X,) + knob_scalar_type = knob_type.__parameters__[0] + except AttributeError: + knob_scalar_type = str else: knob_scalar_type = knob_type kwargs = dict() - if knob_scalar_type is bool: + if knob_scalar_type is NoneType: kwargs['type'] = None kwargs['const'] = True kwargs['default'] = False @@ -159,16 +166,16 @@ class ConfigureTool(admintool.AdminTool): kwargs['type'] = 'int' elif knob_scalar_type is long: kwargs['type'] = 'long' - elif knob_scalar_type in ('ip', 'ip-local'): - kwargs['type'] = knob_scalar_type - elif isinstance(knob_scalar_type, set): + elif knob_scalar_type is CheckedIPAddress: + kwargs['type'] = 'ip' + elif issubclass(knob_scalar_type, enum.Enum): kwargs['type'] = 'choice' - kwargs['choices'] = list(knob_scalar_type) + kwargs['choices'] = [i.value for i in knob_scalar_type] else: kwargs['nargs'] = 1 kwargs['callback_args'] = (knob_scalar_type,) kwargs['dest'] = name - if isinstance(knob_type, tuple): + if issubclass(knob_type, list): if 'type' not in kwargs: kwargs['action'] = 'callback' kwargs['callback'] = ( diff --git a/ipapython/install/core.py b/ipapython/install/core.py index c3dc90846..111b71090 100644 --- a/ipapython/install/core.py +++ b/ipapython/install/core.py @@ -8,20 +8,25 @@ The framework core. import abc import collections +import enum import functools import itertools +import re import sys import six from ipapython.ipa_log_manager import root_logger +from ipapython.ipautil import CheckedIPAddress -from . import util +from . import util, typing from .util import from_ __all__ = ['InvalidStateError', 'KnobValueError', 'Property', 'Knob', 'Configurable', 'Group', 'Component', 'Composite'] +NoneType = type(None) + # Configurable states _VALIDATE_PENDING = 'VALIDATE_PENDING' _VALIDATE_RUNNING = 'VALIDATE_RUNNING' @@ -145,11 +150,15 @@ def knob(type_or_base, default=_missing, sensitive=_missing, deprecated=_missing, description=_missing, cli_positional=_missing, cli_name=_missing, cli_short_name=_missing, cli_aliases=_missing, cli_metavar=_missing): + if type_or_base is None: + type_or_base = NoneType + + assert isinstance(type_or_base, type) + class_dict = {} class_dict['_order'] = next(_counter) - if (not isinstance(type_or_base, type) or - not issubclass(type_or_base, KnobBase)): + if not issubclass(type_or_base, KnobBase): class_dict['type'] = type_or_base type_or_base = KnobBase @@ -179,6 +188,27 @@ def Knob(type_or_base, default=_missing, sensitive=_missing, deprecated=_missing, description=_missing, cli_positional=_missing, cli_name=_missing, cli_short_name=_missing, cli_aliases=_missing, cli_metavar=_missing): + if isinstance(type_or_base, tuple): + assert type_or_base[0] is list + scalar_type = type_or_base[1] + else: + scalar_type = type_or_base + + if scalar_type is bool: + scalar_type = NoneType + elif scalar_type == 'ip': + scalar_type = CheckedIPAddress + elif isinstance(scalar_type, set): + scalar_type = type( + 'Enum', + (enum.Enum,), + {re.sub(r'[^0-9A-Za-z_]', '', n): n for n in scalar_type}) + + if isinstance(type_or_base, tuple): + type_or_base = typing.List[scalar_type] + else: + type_or_base = scalar_type + return knob(type_or_base, default=default, sensitive=sensitive, diff --git a/ipapython/install/typing.py b/ipapython/install/typing.py new file mode 100644 index 000000000..d86fc8f31 --- /dev/null +++ b/ipapython/install/typing.py @@ -0,0 +1,34 @@ +# +# Copyright (C) 2016 FreeIPA Contributors see COPYING for license +# + +import weakref + +import six + +_cache = weakref.WeakValueDictionary() + + +class ListMeta(type): + def __getitem__(cls, key): + if not isinstance(key, type): + raise TypeError("Parameters to generic types must be types. " + "Got {!r}.".format(key)) + + t = ListMeta( + cls.__name__, + cls.__bases__, + { + '__parameters__': (key,), + '__init__': cls.__init__, + } + ) + + return _cache.get(key, t) + + +class List(six.with_metaclass(ListMeta, list)): + __parameters__ = () + + def __init__(self, *_args, **_kwargs): + raise TypeError("Type List cannot be instantiated; use list() instead") |