Module eoxserver.core.interfaces

This module contains the core logic for interface declaration and validation.

Introduction

Interfaces play a key role in the extension mechanism of EOxServer which is described in RFC 1: An Extensible Software Architecture for EOxServer and RFC 2: Extension Mechanism for EOxServer. Extensibility is one of the main features of the EOxServer architecture. Based on its generic core, the different EOxServer layers shall be able to dynamically integrate additional behaviour by defining interfaces that can be implemented by different modules and plugins.

The module eoxserver.core.registry implements the actual extension mechanism based on the capabilities of this module.

eoxserver.core.interfaces is completely independent from the EOxServer extension mechanism on the other hand. Actually, due its generic nature, it is completely independent from the EOxServer project itself and can be used in any other situation where interface declaration and validation might be of interest.

How does it work?

An interface is an ordinary Python class deriving from the Interface class or one of its descendants. Interfaces contain method declarations. As there is no way to declare method signatures without implementing the method in Python an alternative solution has been chosen: declarations are made using class variables that contain instances of the Method class provided by this module.

Interfaces inherit declarations from their parents. They even support multiple inheritance. This allows to extend and combine existing interfaces in a straightforward way comparable to the way interfaces are declared in Java for instance.

The Method constructor accepts an arbitrary number of input argument declarations as well as an optional output declaration. Similar to method declarations, argument declarations are made using instances of special classes derived from the Arg base class.

Implementations can be derived from new-style classes which we will call implementing class in this document. Each interface has an implement() method that accepts an implementing class and returns an implementation (which is a Python class deriving from the implementing class). An exception will be raised when calling implement() with an implementing class that does not validate, e.g. because methods do not match the declarations made in the interface.

As a development tool, eoxserver.core.interfaces supports runtime validation of interfaces. This allows to check for consistency of the argument types sent by a calling object to an implementation instance with the argument type declaration in the interface.

Interface Declaration

As mentioned in the introduction, interfaces are ordinary Python classes deriving from Interface or one of its descendants. Method declarations are made using the Method class.

The Method constructor accepts an arbitrary number of argument declarations as positional arguments as well as an optional output declaration stated with the returns keyword argument. Argument and output declarations are made using instances of the Arg class and its descendants.

All argument types take a name as input. For an implementation to validate, this must be a valid Python argument name (except for output declarations). Furthermore, all argument types accept a default keyword argument that defines a default value for the argument and marks it as optional.

Let’s see an example:

from eoxserver.core.interfaces import *

class SomeInterface(Interface):

    f = Method(
        IntArg("x"),
        returns = IntArg("@return")
    )

class AnotherInterface(Interface):

    g = Method(
        FloatArg("x", default=0.0),
        returns = FloatArg("@return")
    )

class SomeDerivedInterface(SomeInterface, AnotherInterface):

    pass

In this short code snippet, we declare three interfaces. Implementations of SomeInterface shall have a method f() that takes an integer x as an argument and returns an integer value. Implementations of AnotherInterface shall have a method g() that takes a float x as an argument and returns a float value. SomeDerivedInterface inherits from both, so implementations of that interface must exhibit f() and g() methods that work in the way described above.

Interfaces can have an interface configuration, i.e. a class variable called INTERFACE_CONF which contains a dictionary of configuration values. So far, only runtime_validation_level is supported, see Validation of Implementations.

Implementations

Implementations will be constructed from implementing classes using the implement() method of the interface. This method will validate the implementating class and return an implementation class that inherits from the input class.

The implementation exhibits exactly the behaviour of the implementing class. Internally, the implementation may differ considerably from the implementing class, especially if you use runtime validation capabilities, see under Descriptors below.

Note that you can define any number of additional public or private methods in an implementing class which will be present in the implementation as well. You cannot omit any method or argument declared in the interface, though, as the implementing class would not validate then.

Now for an example of implementations of the interface defined above in section Interface Declaration:

class SomeImplementingClass(object):

    def f(self, x):
        return int(x)

class AnotherImplementingClass(object):

    def g(self, x=0.0):
        return float(x)

class AThirdImplementingClass(SomeImplementingClass):

    def g(self, x=0.0):
        return 2.0 * float(x)

SomeImplementation = SomeInterface.implement(SomeImplementingClass)
AnotherImplementation = AnotherInterface.implement(AnotherImplementingClass)
AThirdImplementation = SomeDerivedInterface.implement(AThirdImplementingClass)

As you can see, SomeImplementingClass implements SomeInterface. The required method f() is present and has the correctly named input parameters and even enforces that the output has the correct type, though this can only be validated using runtime validation (not when creating the implementation).

In AnotherImplementingClass you see an example for default value declaration.

AThirdImplementingClass is interesting in two ways. First, it derives from SomeImplementingClass inheriting its f() method. This way you can build hierarchies of implementing classes similar to the way you can build hierarchies of interfaces. Second, you see that the implementation hierarchies may deviate from the interface hierarchies; instead of inheriting the g() method from AnotherImplementingClass an alternative version of this method is implemented that again matches the interface declaration.

If you have an implementation and want to know which interface it implements you can use the magic __ifclass__ attribute:

>>> AThirdImplementingClass.__ifclass__.__name__
'SomeDerivedInterface'

Implementing classes can define an implementation configuration, i.e. class variable called IMPL_CONF that contains a dictionary of configuration settings. So far, only runtime_validation_level is supported, see Validation of Implementations.

Validation of Implementations

The validation of implementations is performed in two ways:

  • at class creation time
  • at instance method invocation time (“runtime”)

Validation at class creation time checks:

  • if all methods declared by the interface are implemented
  • if the method arguments of the interface and implementation match in the sense that
    • all declared arguments are present
    • the names and the order of the arguments in the implementation match the interface declaration
    • the optional default value declarations match

Class creation time validation is performed unconditionally.

Instance method invocation time (“runtime”) validation is optional. It can be triggered by the runtime_validation_level setting. There are three possible values for this option:

  • trust: no runtime validation
  • warn: argument types are checked against interface declaration; in case of mismatch a warning is written to the log file
  • fail: argument types are checked against interface declaration; in case of mismatch an exception is raised

The runtime_validation_level option can be set

  • globally (in the configuration file, see Config Reader)
  • per interface (in the INTERFACE_CONF dictionary)
  • per implementation (in the IMPL_CONF dictionary)

where stricter settings override weaker ones.

Note

The warn and fail levels are intended for use throughout the development process. In a production setting trust should be used.

Reference

This documentation concentrates on the public methods of the involved classes. Actually, there is only one public method you will need to invoke and that is Interface.implement(); all others are public only to the extent that they are invoked by other objects defined in this module.

The implementation of eoxserver.core.interfaces involves some deep and beautiful Python magic. We skip most of these details here, only in the Descriptors sections you will find a reference to some of it.

Interfaces

class eoxserver.core.interfaces.Interface

This is the base class for all interface declarations. Derive from it or one of its subclasses to create your own interface declaration.

The Interface class has only class variables (the method declarations) and class methods.

classmethod implement(InterfaceCls, ImplementationCls)

This method takes an implementing class as input, validates it, and returns the implementation.

In the validation step, Method.validateImplementation() is called for each method declared in the interface. InternalError is raised if a method is not found or if the method signature does not match the declaration.

If validation has passed, the implementation is getting prepared. The implementation inherits from the implementing class. The __ifclass__ magic attribute is added to the class dictionary. If runtime validation has been enabled, the methods of the implementing class defined in the interface are replaced by descriptors (instances of WarningDescriptor or FailingDescriptor).

Finally, the implementation class is generated and returned.

Methods

class eoxserver.core.interfaces.Method(*args, **kwargs)

The Method is used for method declarations in interfaces. Its constructor accepts an arbitrary number of positional arguments representing input arguments to the method to be defined, and one optional keyword argument returns which represents the methods return value, if any.

All arguments must be instances of Arg or one of its subclasses.

The methods of the Method class are intended for internal use by the Interface validation algorithms only.

validateArgs(args)

Validate the input arguments. That is, check if they are in the right order and no argument is defined more than once. Raises InternalError if the arguments do not validate.

Used internally by the constructor during instance creation.

validateImplementation(impl_method)

This method is at implementation class creation time to check if the implementing class method conforms to the method declaration. It expects the corresponding method as its single input argument impl_method. It makes extensive use of Python’s great introspection capabilities.

Raises InternalError in case the implementation does not validate.

validateReturnType(method_name, ret_value)

This method is called for runtime argument type validation. It expects the method name method_name and the return value ret_value as input and checks the return value against the return value declaration, if any.

Raises TypeMismatch if validation fails.

validateType(method_name, *args, **kwargs)

This method is called for runtime argument type validation. It gets the input of the implementing method and checks it against the argument declarations.

Raises TypeMismatch if validation fails.

Arguments

class eoxserver.core.interfaces.Arg(name, **kwargs)

This is the common base class for arguments of any kind; it can be used in interface declarations as well to represent an argument of arbitrary type.

The constructor requires a name argument which denotes the argument name. The validation will check at class creation time if the method of an implementing class defines an argument of the given name, so you should always use valid Python variable names here (you can use arbitrary strings for return value declarations though).

Furthermore, the constructor accepts a default keyword argument which defines a default value for the declared argument. The validation will check at class creation time if this default value is present in the implementing class and fail if it is not.

Its methods are intended for internal use in runtime validation.

getExpectedType()

Returns the expected type name; used in error messages only. This method is overridden by Arg subclasses in order to customize error reporting. The base class implementation returns "".

isOptional()

Returns True if the argument is optional, meaning that a default value has been defined for it, False otherwise.

isValid(arg_value)

Returns True if arg_value is an acceptable value for the argument, False otherwise. Acceptable values are either the default value if it has been defined or values of the expected type.

isValidType(arg_value)

Returns True if the argument value arg_value has a valid type, False otherwise. This method is overridden by Arg subclasses in order to check for individual types. The base class implementation always returns True meaning that all types of argument values are accepted.

class eoxserver.core.interfaces.StrArg(name, **kwargs)

Represents an argument of type str.

class eoxserver.core.interfaces.UnicodeArg(name, **kwargs)

Represents an argument of type unicode.

class eoxserver.core.interfaces.StringArg(name, **kwargs)

Represents an argument of types str or unicode.

class eoxserver.core.interfaces.BoolArg(name, **kwargs)

Represents an argument of type bool.

class eoxserver.core.interfaces.IntArg(name, **kwargs)

Represents an argument of type int.

class eoxserver.core.interfaces.LongArg(name, **kwargs)

Represents an argument of type long.

class eoxserver.core.interfaces.FloatArg(name, **kwargs)

Represents an argument of type float.

class eoxserver.core.interfaces.RealArg(name, **kwargs)

Represents a real number argument, i.e. an argument of types int, long or float.

class eoxserver.core.interfaces.ComplexArg(name, **kwargs)

Represents a complex number argument of type complex.

class eoxserver.core.interfaces.IterableArg(name, **kwargs)

Represents an iterable argument.

class eoxserver.core.interfaces.SubscriptableArg(name, **kwargs)

Represents a subscriptable argument.

class eoxserver.core.interfaces.ListArg(name, **kwargs)

Represents an argument of type list.

class eoxserver.core.interfaces.DictArg(name, **kwargs)

Represents an argument of type dict.

class eoxserver.core.interfaces.ObjectArg(name, **kwargs)

Represents an new-style class argument. The range of accepted objects can be restricted by providing the arg_class keyword argument to the constructor. Runtime validation will then check if the argument value is an instance of arg_class (or one of its subclasses) and fail otherwise.

class eoxserver.core.interfaces.PosArgs(name, **kwargs)

Represents arbitrary positional arguments as supported by Python with the method(self, *args) syntax. The range of accepted objects can be restricted by providing the arg_class keyword argument to the constructor. Runtime validation will then check if the argument value is an instance of arg_class (or one of its subclasses) and fail otherwise.

Note that a PosArgs argument declaration can only be followed by a KwArgs declaration, otherwise validation will fail.

class eoxserver.core.interfaces.KwArgs(name, **kwargs)

Represents arbitrary keyword arguments as supported by Python with the method(self, **kwargs) syntax. Note that this must always be the last input argument declaration in a method, otherwise validation will fail.

Config Reader

class eoxserver.core.interfaces.IntfConfigReader(config)

This is the configuration reader for eoxserver.core.interfaces.

Its constructor expects a Config instance config as input.

getRuntimeValidationLevel()

Returns the global runtime validation level setting or None if it is not defined.

validate()

Validates the configuration. Raises ConfigError if the runtime_validation_level configuration setting in the core.interfaces section contains an invalid value.

Descriptors

Descriptors are used to customize method access in Python. They are some of the more advanced Python language features; if you want to know more about them, please refer to the Python Language Reference.

class eoxserver.core.interfaces.ValidationDescriptor(method, func)

This is the common base class for WarningDescriptor and FailingDescriptor. The constructor expects the method declaration method and the implementing function func as input.

The __get__() method returns a callable wrapper around the instance it is called with, the method declaration and the function that implements the method. It is that object that gets finally invoked when runtime validation is enabled.

class eoxserver.core.interfaces.ValidationWrapper(method, func, instance)

This is the common base class for WarningWrapper and FailingWrapper. Its constructor expects the method declaration, the implementing function and the instance as input.

class eoxserver.core.interfaces.WarningDescriptor(method, func)
class eoxserver.core.interfaces.WarningWrapper(method, func, instance)

This wrapper is callable. Its __call__() method expects arbitrary positional and keyword arguments, validates them against the method declaration using Method.validateType(), calls the implementing function with these arguments and returns whatever it returns, calling Method.validateReturnType().

If the validation methods raise a TypeMismatch exception the exception text is logged as a warning, but the normal process of execution goes on.

class eoxserver.core.interfaces.FailingDescriptor(method, func)
class eoxserver.core.interfaces.FailingWrapper(method, func, instance)

This wrapper is callable. Its __call__() method expects arbitrary positional and keyword arguments, validates them against the method declaration using Method.validateType(), calls the implementing function with these arguments and returns whatever it returns, calling Method.validateReturnType().

If the validation methods raise a TypeMismatch exception it will not be caught and thus cause the program to fail.