# Acquisition

Copyright (C) 1996-1998, Digital Creations.

Acquisition [1] is a mechanism that allows objects to obtain attributes from their environment. It is similar to inheritence, except that, rather than traversing an inheritence hierarchy to obtain attributes, a containment hierarchy is traversed.

The ExtensionClass. release includes mix-in extension base classes that can be used to add acquisition as a feature to extension subclasses. These mix-in classes use the context-wrapping feature of ExtensionClasses to implement acquisition. Consider the following example:

        import ExtensionClass, Acquisition

        class C(ExtensionClass.Base):
          color='red'

        class A(Acquisition.Implicit):

          def report(self):
            print self.color

        a=A()
        c=C()
        c.a=A()

        c.a.report() # prints 'red'

        d=C()
        d.color='green'
        d.a=a

        d.a.report() # prints 'green'

        a.report() # raises an attribute error

The class `A` inherits acquisition behavior from `Acquisition.Implicit`. The object, `a`, "has" the color of objects `c` and `d` when it is accessed through them, but it has no color by itself. The object `a` obtains attributes from it's environment, where it's environment is defined by the access path used to reach `a`.

## Acquisition wrappers

When an object that supports acquisition is accessed through an extension class instance, a special object, called an acquisition wrapper, is returned. In the example above, the expression `c.a` returns an acquisition wrapper that contains references to both `c` and `a`. It is this wrapper that performs attribute lookup in `c` when an attribute cannot be found in `a`.

Aquisition wrappers provide access to the wrapped objects through the attributes `aq_parent`, `aq_self`, `aq_base`. In the example above, the expressions:

           'c.a.aq_parent is c'

and:

           'c.a.aq_self is a'

both evaluate to true, but the expression:

           'c.a is a'

evaluates to false, because the expression `c.a` evaluates to an acquisition wrapper around `c` and `a`, not `a` itself.

The attribute `aq_base` is similar to `aq_self`. Wrappers may be nested and `aq_self` may be a wrapped object. The `aq_base` attribute is the underlying object with all wrappers removed.

## Acquisition Control

Two styles of acquisition are supported in the current ExtensionClass release, implicit and explicit aquisition.

### Implicit acquisition

Implicit acquisition is so named because it searches for attributes from the environment automatically whenever an attribute cannot be obtained directly from an object or through inheritence.

An attribute may be implicitly acquired if it's name does not begin with an underscore, `_`.

To support implicit acquisition, an object should inherit from the mix-in class `Acquisition.Implicit`.

### Explicit Acquisition

When explicit acquisition is used, attributes are not automatically obtained from the environment. Instead, the method `aq_aquire` must be used, as in:

            print c.a.aq_acquire('color')

To support explicit acquisition, an object should inherit from the mix-in class `Acquisition.Explicit`.

### Controlled Acquisition

A class (or instance) can provide attribute by attribute control over acquisition. This is done by:

- subclassing from `Acquisition.Explicit`, and
- setting all attributes that should be acquired to the special value: `Acquisition.Acquired`. Setting an attribute to this value also allows inherited attributes to be overridden with acquired ones.

  For example, in:

                class C(Acquisition.Explicit):
                   id=1
                   secret=2
                   color=Acquisition.Acquired
                   __roles__=Acquisition.Acquired

  The only attributes that are automatically acquired from containing objects are `color`, and `__roles__`. Note also that the `__roles__` attribute is acquired even though it's name begins with an underscore. In fact, the special `Acquisition.Acquired` value can be used in `Acquisition.Implicit` objects to implicitly acquire selected objects that smell like private objects.

### Filtered Acquisition

The acquisition method, `aq_acquire`, accepts two optional arguments. The first of the additional arguments is a "filtering" function that is used when considering whether to acquire an object. The second of the additional arguments is an object that is passed as extra data when calling the filtering function and which defaults to `None`.

The filter function is called with five arguments:

- The object that the `aq_acquire` method was called on,
- The object where an object was found,
- The name of the object, as passed to `aq_acquire`,
- The object found, and
- The extra data passed to `aq_acquire`.

If the filter returns a true object that the object found is returned, otherwise, the acquisition search continues.

For example, in:

            from Acquisition import Explicit

            class HandyForTesting:
                def __init__(self, name): self.name=name
                def __str__(self):
                    return "%s(%s)" % (self.name, self.__class__.__name__)
                __repr__=__str__

            class E(Explicit, HandyForTesting): pass

            class Nice(HandyForTesting):
                isNice=1
                def __str__(self):
                    return HandyForTesting.__str__(self)+' and I am nice!'
                __repr__=__str__

            a=E('a')
            a.b=E('b')
            a.b.c=E('c')
            a.p=Nice('spam')
            a.b.p=E('p')

            def find_nice(self, ancestor, name, object, extra):
                return hasattr(object,'isNice') and object.isNice

            print a.b.c.aq_acquire('p', find_nice)

The filtered acquisition in the last line skips over the first attribute it finds with the name `p`, because the attribute doesn't satisfy the condition given in the filter. The output of the last line is:

            spam(Nice) and I am nice!

## Acquisition and methods

Python methods of objects that support acquisition can use acquired attributes as in the `report` method of the first example above. When a Python method is called on an object that is wrapped by an acquisition wrapper, the wrapper is passed to the method as the first argument. This rule also applies to user-defined method types and to C methods defined in pure mix-in classes.

Unfortunately, C methods defined in extension base classes that define their own data structures, cannot use aquired attributes at this time. This is because wrapper objects do not conform to the data structures expected by these methods.

## Acquiring Acquiring objects

Consider the following example:

          from Acquisition import Implicit

          class C(Implicit):
              def __init__(self, name): self.name=name
              def __str__(self):
                  return "%s(%s)" % (self.name, self.__class__.__name__)
              __repr__=__str__

          a=C("a")
          a.b=C("b")
          a.b.pref="spam"
          a.b.c=C("c")
          a.b.c.color="red"
          a.b.c.pref="eggs"
          a.x=C("x")

          o=a.b.c.x

The expression `o.color` might be expected to return `"red"`. In earlier versions of ExtensionClass, however, this expression failed. Acquired acquiring objects did not acquire from the environment they were accessed in, because objects were only wrapped when they were first found, and were not rewrapped as they were passed down the acquisition tree.

In the current release of ExtensionClass, the expression "o.color" does indeed return `"red"`.

When searching for an attribute in `o`, objects are searched in the order `x`, `a`, `b`, `c`. So, for example, the expression, `o.pref` returns `"spam"`, not `"eggs"`. In earlier releases of ExtensionClass, the attempt to get the `pref` attribute from `o` would have failed.

If desired, the current rules for looking up attributes in complex expressions can best be understood through repeated application of the `__of__` method:

`a.x`
  `x.__of__(a)`
`a.b`
  `b.__of__(a)`
`a.b.x`
  `x.__of__(a).__of__(b.__of__(a))`
`a.b.c`
  `c.__of__(b.__of__(a))`
`a.b.c.x`
  `x.__of__(a).__of__(b.__of__(a)).__of__(c.__of__(b.__of__(a)))`

and by keeping in mind that attribute lookup in a wrapper is done by trying to lookup the attribute in the wrapped object first and then in the parent object. In the expressions above involving the `__of__` method, lookup proceeds from left to right.

Note that heuristics are used to avoid most of the repeated lookups. For example, in the expression: `a.b.c.x.foo`, the object `a` is searched no more than once, even though it is wrapped three times.

[1] Gil, J., Lorenz, D., Environmental Acquisition--A New Inheritance-Like Abstraction Mechanism, OOPSLA '96 Proceedings, ACM SIG-PLAN, October, 1996