summaryrefslogtreecommitdiff
path: root/src/zope/component/persistentregistry.py
blob: 79eb209eb31002a37a9f54d1e7775de321015315 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
##############################################################################
#
# Copyright (c) 2004 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Persistent component managers.
"""
from persistent import Persistent
from persistent.list import PersistentList
from persistent.mapping import PersistentMapping
from zope.interface.adapter import VerifyingAdapterRegistry
from zope.interface.registry import Components


class PersistentAdapterRegistry(VerifyingAdapterRegistry, Persistent):
    """
    An adapter registry that is also a persistent object.

    .. versionchanged:: 5.0.0
        Internal data structures are now composed of
        :class:`persistent.mapping.PersistentMapping` and
        :class:`persistent.list.PersistentList`. This helps scale to
        larger registries.

        Previously they were :class:`dict`, :class:`list` and
        :class:`tuple`, meaning that as soon as this object was
        unpickled, the entire registry tree had to be unpickled, and
        when a change was made (an object registered or unregisterd),
        the entire registry had to be pickled. Now, only the parts
        that are in use are loaded, and only the parts that are
        modified are stored.

        The above applies without reservation to newly created
        instances. For existing persistent instances, however, the
        data will continue to be in dicts and tuples, with some few
        exceptions for newly added or changed data.

        To fix this, call :meth:`rebuild` and commit the transaction.
        This will rewrite the internal data structures to use the new
        types.
    """

    # The persistent types we use, replacing the basic types inherited
    # from ``BaseAdapterRegistry``.
    _sequenceType = PersistentList
    _leafSequenceType = PersistentList
    _mappingType = PersistentMapping
    _providedType = PersistentMapping

    # The methods needed to manipulate the leaves of the subscriber
    # tree. When we're manipulating unmigrated data, it's safe to
    # migrate it, but not otherwise (we don't want to write in an
    # otherwise read-only transaction).
    def _addValueToLeaf(self, existing_leaf_sequence, new_item):
        if not existing_leaf_sequence:
            existing_leaf_sequence = self._leafSequenceType()
        elif isinstance(existing_leaf_sequence, tuple):
            # Converting from old state.
            existing_leaf_sequence = self._leafSequenceType(
                existing_leaf_sequence)
        existing_leaf_sequence.append(new_item)
        return existing_leaf_sequence

    def _removeValueFromLeaf(self, existing_leaf_sequence, to_remove):
        if isinstance(existing_leaf_sequence, tuple):
            # Converting from old state
            existing_leaf_sequence = self._leafSequenceType(
                existing_leaf_sequence)

        without_removed = VerifyingAdapterRegistry._removeValueFromLeaf(
            self,
            existing_leaf_sequence,
            to_remove)
        existing_leaf_sequence[:] = without_removed
        return existing_leaf_sequence

    def changed(self, originally_changed):
        if originally_changed is self:
            # XXX: This is almost certainly redundant, even if we
            # have old data consisting of plain dict/list/tuple. That's
            # because the super class will now increment the ``_generation``
            # attribute to keep caches in sync. For this same reason,
            # it's not worth trying to "optimize" for the case that we're a
            # new or rebuilt object containing only Persistent sub-objects:
            # the changed() mechanism will still result in mutating this
            # object via ``_generation``.
            self._p_changed = True
        super().changed(originally_changed)

    def __getstate__(self):
        state = super().__getstate__().copy()
        for name in self._delegated:
            state.pop(name, 0)
        state.pop('ro', None)
        return state

    def __setstate__(self, state):
        bases = state.pop('__bases__', ())
        super().__setstate__(state)
        self._createLookup()
        self.__bases__ = bases
        self._v_lookup.changed(self)


class PersistentComponents(Components):
    """
    A component implementation that uses `PersistentAdapterRegistry`.

    Note that this object itself is *not* `Persistent`.
    """

    def _init_registries(self):
        self.adapters = PersistentAdapterRegistry()
        self.utilities = PersistentAdapterRegistry()

    def _init_registrations(self):
        self._utility_registrations = PersistentMapping()
        self._adapter_registrations = PersistentMapping()
        self._subscription_registrations = PersistentList()
        self._handler_registrations = PersistentList()