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>
This commit is contained in:
Jan Cholasta
2016-11-07 14:01:10 +01:00
parent 9fd1981ae8
commit a929ac3338
5 changed files with 96 additions and 13 deletions

View File

@@ -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'] = (

View File

@@ -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,

View File

@@ -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")