From ef7594ffe1bad349dc539f69ee90708460999a71 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sat, 19 Jul 2008 04:28:03 +0000 Subject: [PATCH] 4: Got basics of API.register_command() working; added corresponding unit tests --- ipalib/__init__.py | 4 +++ ipalib/base.py | 64 ++++++++++++++++++++++++++++---------- ipalib/exceptions.py | 8 +++++ ipalib/tests/test_base.py | 65 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 124 insertions(+), 17 deletions(-) diff --git a/ipalib/__init__.py b/ipalib/__init__.py index ddce3ac92..1337d8123 100644 --- a/ipalib/__init__.py +++ b/ipalib/__init__.py @@ -20,3 +20,7 @@ """ IPA library. """ + +import base + +api = base.API() diff --git a/ipalib/base.py b/ipalib/base.py index eb84dd12a..96dd300dd 100644 --- a/ipalib/base.py +++ b/ipalib/base.py @@ -21,10 +21,24 @@ Base classes for plug-in architecture and generative API. """ -from exceptions import SetAttributeError +import inspect +import exceptions -class Command(object): +class Named(object): + #def __init__(self, prefix): + # clsname = self.__class__.__name__ + def __get_name(self): + return self.__class__.__name__ + name = property(__get_name) + + def __get_cli_name(self): + return self.name.replace('_', '-') + cli_name = property(__get_cli_name) + + +class Command(Named): + def normalize(self, kw): raise NotImplementedError @@ -35,11 +49,11 @@ class Command(object): raise NotImplementedError def __call__(self, **kw): - kw = self.normalize(kw) - invalid = self.validate(kw) + normalized = self.normalize(kw) + invalid = self.validate(normalized) if invalid: return invalid - return self.execute(kw) + return self.execute(normalize) class Argument(object): @@ -65,7 +79,7 @@ class NameSpace(object): For example, setting an attribute the normal way will raise an exception: >>> ns.my_message = 'some new value' - (raises ipalib.exceptions.SetAttributeError) + (raises exceptions.SetAttributeError) But a programmer could still set the attribute like this: @@ -96,7 +110,7 @@ class NameSpace(object): NameSpace has been locked; otherwise calls object.__setattr__(). """ if self.__locked: - raise SetAttributeError(name) + raise exceptions.SetAttributeError(name) super(NameSpace, self).__setattr__(name, value) def __getitem__(self, key): @@ -134,17 +148,35 @@ class NameSpace(object): class API(object): + __commands = None + __objects = None + __locked = False + def __init__(self): - self.__c = object() - self.__o = object() + self.__c = {} # Proposed commands + self.__o = {} # Proposed objects - def __get_c(self): - return self.__c - c = property(__get_c) + def __get_objects(self): + return self.__objects + objects = property(__get_objects) - def __get_o(self): - return self.__o - o = property(__get_o) + def __get_commands(self): + return self.__commands + commands = property(__get_commands) - def register_command(self, name, callback, override=False): + def __merge(self, target, base, cls, override): + assert type(target) is dict + assert inspect.isclass(base) + assert inspect.isclass(cls) + assert type(override) is bool + if not issubclass(cls, base): + raise exceptions.RegistrationError( + cls, + '%s.%s' % (base.__module__, base.__name__) + ) + + def register_command(self, cls, override=False): + self.__merge(self.__c, Command, cls, override) + + def finalize(self): pass diff --git a/ipalib/exceptions.py b/ipalib/exceptions.py index 2c1e5a557..752a1e20f 100644 --- a/ipalib/exceptions.py +++ b/ipalib/exceptions.py @@ -47,3 +47,11 @@ class IPAError(Exception): class SetAttributeError(IPAError): msg = 'Cannot set %r: NameSpace does not allow attribute setting' + + +class OverrideError(IPAError): + msg = 'Unexpected override of %r; use override=True if intended' + + +class RegistrationError(IPAError): + msg = '%r is not a subclass of %s' diff --git a/ipalib/tests/test_base.py b/ipalib/tests/test_base.py index 42cb89a1e..7a998f3cd 100644 --- a/ipalib/tests/test_base.py +++ b/ipalib/tests/test_base.py @@ -24,7 +24,23 @@ Unit tests for `ipalib.base` module. from ipalib import base, exceptions -class test_NameSpace(): +def read_only(obj, name): + """ + Check that a given property is read-only. + Returns the value of the property. + """ + assert isinstance(obj, object) + assert hasattr(obj, name) + raised = False + try: + setattr(obj, name, 'some new obj') + except AttributeError: + raised = True + assert raised + return getattr(obj, name) + + +class test_NameSpace: """ Unit tests for `NameSpace` class. """ @@ -149,3 +165,50 @@ class test_NameSpace(): """ (kw, ns) = self.std() assert len(kw) == len(ns) == 3 + + +class test_Command: + def new(self): + return base.Command() + + def test_fresh(self): + c = self.new() + + + +class test_API: + """ + Unit tests for `API` class. + """ + + def new(self): + """ + Returns a new API instance. + """ + return base.API() + + def test_fresh(self): + """ + Test expectations of a fresh API instance. + """ + api = self.new() + assert read_only(api, 'objects') is None + assert read_only(api, 'objects') is None + + def test_register_command(self): + class my_command(base.Command): + pass + class another_command(base.Command): + pass + api = self.new() + + api.register_command(my_command) + + # Check that RegistrationError is raised passing something not + # sub-classed from Command: + raised = False + try: + api.register_command(object) + except exceptions.RegistrationError: + raised = True + assert raised