Specification Unification

Status: IsDraftProposal

Author

JimFulton

Problem

Zope interfaces are object specifications. They specify the external behavior of objects that provide them. Specifications include formal information, including:

  • Attribute specifications
  • Invariants

Specifications are also provided informally as text in docstrings included within interfaces.

Attribute specifications are provided using zope.interface.Attribute objects.

There is an ad-hoc tool for verifying interfaces. It can provide crude checks for interface compliance. It is not extensible. For example, it doesn't know how to verify schema fields.

Similarly, when defining interfaces, we can use Python functions as stand-ins for method specifications. The code to handle this is hard-coded in the interface implementation.

When providing attribute definitions to interface definitions, only Python functions and objects that subclass zope.interface.Attribute can be provided.

Proposal

  • Use interfaces for attribute specifications

    An interface is an object specification. Attribute values are objects, thus it makes sense to use interfaces to specify attribute values. An attribute specification is more than the specification for the attribute value. It contains information about how the attribute is used and may contain additional meta data, such as whether the attribute value is required.

    We will extend zope.interface.interfaces.IAttribute to include the following additional attributes:

    • type

      A specification for the attribute value. This is

  • Use extended interface types to provide more structured specifications.

    Consider a specification for an object that must be an integer between one and ten. We could express this using an invariant:

          >>> def check(ob):
          ...     if not (ob >= 1 and ob <= 10):
          ...         raise Invalid(ob, "must be between 1 and 10")
    
          >>> class IIntBetweenOneAndTen(IInt):
          ...     invariant(check)
    

    The problem with this approach is that the minimum and maximum values are "hidden" in the invariants. We can't tell from the outside that there are minimum and maximum values or what they are. It would be useful to have a special kind of interface that expressed the range invariant as minimum and maximum values. Having such information would allow automated tools to help select values that satisfy the constraint. Of course, this is what schema fields do:

          >>> IIntBetweenOneAndTen = schema.Int(min=1, max=10)
    

    Unlike the current schema fields, we would arrange that fields are actually interfaces. For example, with the above:

          >>> IIntBetweenOneAndTen.extends(IInt)
          True
    

  • Use adaptation in interface definitions

    Interfaces are created by calling an interface class (e.g. zope.interface.interface.InterfaceClass) with a name, a collection of base interfaces, and a dictionary of attribute specifications. The interface class will adapt each of the values in the attribute dictionary to zope.interface.interfaces.IInterface. This means that the algorithm for defining attribute specifications will be pluggable. So, when a Python function is used in an interface definition, an adapter will be used to adapt the function to an interface. The logic for doing the conversion will no-longer be hard-coded in the interface implementation.

    This has a downside. Interfaces will depend on adaptation for their definition. We will need to provide an adapter registry by default. We will also need to provide the logic for computing adapters in the adapter registry. This has the effect of bringing some of the component architecture to the interface package. (Some might view this as a positive. :)

  • Use adapters for interface verification

    When we want to verify an object against an interface, we'll adapt the interface to IObjectVerifier, and call the verifier's verify method. The default verifier will apply this mechanism recursively for the attributes specifications for an interface.

    It's worth noting that class verification (against the interfaces it implements) is iffy at best. There is no reason to require that the class provides all of the attributes that are specified in its implementation specification, or that the values of the attributes match the specifications. For example, classes may cause instance attributes to be created during initialization or a class may have attributes with the same name that it uses for its own purposes.

    It's also worth noting that not all specifications can be verified. Certainly, informal specifications (words in docstrings) can't be verified formally. Even formal specifications, such as method return values may not be verifiable in practice.

Risks

  • This proposal ties adaptation and interface definition together rather closely. We'll need to work out how best to do this. This will require some prototyping effort. This is risky because some systems need to be able to extend the adaptation process. For example, Zope 3 requires different adapter lookup for different "sites" with an application.

    A posible mitigation of this risk is to use a separate adapter registry just for interface definition.

  • This proposal represents a fairly major change to the way zope.schema works. We will need to make sure that existing fields, continue to work.

Opportunities

It seems possible that thinking of specifications in terms of interfaces could provide some clarity to querying problems. After all, a query is an object specification. Perhaps interface refinement could provide a useful model of query refinement and adapters could provide a useful mechanism for managing query handlers.



( 97 subscribers )