====================
The pending database
====================

The pending database is where various types of events which need confirmation
are stored.  These can include email address registration events, held
messages (but only for user confirmation), auto-approvals, and probe bounces.
This is not where messages held for administrator approval are kept.

In order to pend an event, you first need a pending database.

    >>> from mailman.interfaces.pending import IPendings
    >>> from zope.component import getUtility
    >>> pendingdb = getUtility(IPendings)

There are nothing in the pendings database.

    >>> pendingdb.count()
    0

The pending database can add any ``IPendable`` to the database, returning a
token that can be used in urls and such.
::

    >>> from zope.interface import implementer
    >>> from mailman.interfaces.pending import IPendable
    >>> @implementer(IPendable)
    ... class SimplePendable(dict):
    ...     PEND_TYPE = 'subscription'

    >>> subscription = SimplePendable(
    ...     address='aperson@example.com',
    ...     display_name='Anne Person',
    ...     language='en',
    ...     password='xyz')
    >>> token = pendingdb.add(subscription)
    >>> len(token)
    40

There's exactly one entry in the pendings database now.

    >>> pendingdb.count()
    1

You can *confirm* the pending, which means returning the `IPendable` structure
(as a dictionary) from the database that matches the token.

All `IPendable` classes have a `PEND_TYPE` attribute which must be a string.
It is used to identify and query pendables in the database, and will be
returned as the `type` key in the dictionary.  Thus `type` is a reserved key
and pendables may not otherwise set it.

If the token isn't in the database, None is returned.

    >>> pendable = pendingdb.confirm(b'missing')
    >>> print(pendable)
    None
    >>> pendable = pendingdb.confirm(token)
    >>> from mailman.testing.documentation import dump_msgdata    
    >>> dump_msgdata(pendable)
    address     : aperson@example.com
    display_name: Anne Person
    language    : en
    password    : xyz
    type        : subscription

After confirmation, the token is no longer in the database.

    >>> print(pendingdb.confirm(token))
    None

There are a few other things you can do with the pending database.  When you
confirm a token, you can leave it in the database, or in other words, not
expunge it.

    >>> event_1 = SimplePendable(type='one')
    >>> token_1 = pendingdb.add(event_1)
    >>> event_2 = SimplePendable(type='two')
    >>> token_2 = pendingdb.add(event_2)
    >>> event_3 = SimplePendable(type='three')
    >>> token_3 = pendingdb.add(event_3)
    >>> pendable = pendingdb.confirm(token_1, expunge=False)
    >>> dump_msgdata(pendable)
    type: one
    >>> pendable = pendingdb.confirm(token_1, expunge=True)
    >>> dump_msgdata(pendable)
    type: one
    >>> print(pendingdb.confirm(token_1))
    None

You can iterate over all the pendings in the database.

    >>> pendables = list(pendingdb)
    >>> def sort_key(item):
    ...     token, pendable = item
    ...     return pendable['type']
    >>> sorted_pendables = sorted(pendables, key=sort_key)
    >>> for token, pendable in sorted_pendables:
    ...     print(pendable['type'])
    three
    two

An event can be given a lifetime when it is pended, otherwise it just uses a
default lifetime.

    >>> from datetime import timedelta
    >>> yesterday = timedelta(days=-1)
    >>> event_4 = SimplePendable(type='four')
    >>> token_4 = pendingdb.add(event_4, lifetime=yesterday)

Every once in a while the pending database is cleared of old records.

    >>> pendingdb.evict()
    >>> print(pendingdb.confirm(token_4))
    None
    >>> pendable = pendingdb.confirm(token_2)
    >>> dump_msgdata(pendable)
    type: two
