summaryrefslogtreecommitdiffstats
path: root/ipapython
diff options
context:
space:
mode:
authorJan Cholasta <jcholast@redhat.com>2016-11-07 14:01:10 +0100
committerJan Cholasta <jcholast@redhat.com>2016-11-11 12:17:25 +0100
commita929ac333833a5cbf503d1fcbdee150658d933a4 (patch)
tree48ba4ca356525b8433507a17bee4ebbfd719b506 /ipapython
parent9fd1981ae8abf720f5234b6049c9beabbb1f2211 (diff)
downloadfreeipa-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.py25
-rw-r--r--ipapython/install/core.py36
-rw-r--r--ipapython/install/typing.py34
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")