summaryrefslogtreecommitdiff
path: root/src/zope/schema/accessors.py
blob: decf3aa0ddb78ba2e48143b544e3a85cdda89e96 (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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
##############################################################################
#
# Copyright (c) 2003 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.
#
##############################################################################
"""
Field accessors
===============

Accessors are used to model methods used to access data defined by fields.
Accessors are fields that work by decorating existing fields.

To define accessors in an interface, use the accessors function::

  class IMyInterface(Interface):

     getFoo, setFoo = accessors(Text(title=u'Foo', ...))

     getBar = accessors(TextLine(title=u'Foo', readonly=True, ...)


Normally a read accessor and a write accessor are defined.  Only a
read accessor is defined for read-only fields.

Read accessors function as access method specifications and as field
specifications.  Write accessors are solely method specifications.
"""

from zope.interface import providedBy, implementedBy
from zope.interface.interface import Method
from zope.interface.declarations import Declaration


class FieldReadAccessor(Method):
    """Field read accessor
    """

    def __init__(self, field):
        self.field = field
        Method.__init__(self, '')
        self.__doc__ = 'get %s' % field.__doc__

    # A read field accessor is a method and a field.
    # A read accessor is a decorator of a field, using the given
    # field's properties to provide meta data.

    @property
    def __provides__(self):
        provided = providedBy(self.field)
        implemented = implementedBy(FieldReadAccessor)

        # Declaration.__add__ is not very smart in zope.interface 5.0.0.
        # It's very easy to produce C3 inconsistent orderings using
        # it, because it uses itself plus any new interfaces from the
        # second argument as the ``__bases__``, ignoring their
        # relative order.
        #
        # Here, we can easily work around that. We know that ``field``
        # will be some sub-class of Attribute, just as we are
        # (FieldReadAccessor <- Method <- Attribute). So there will be
        # overlap, and commonly only IMethod would be added to the end
        # of the list of bases; but since IMethod extends IAttribute,
        # having IAttribute earlier in the bases will be inconsistent.
        # The fix here is to remove those duplicates from the first
        # element so that we don't get into that situation.
        provided_list = list(provided)
        for iface in implemented:
            if iface in provided_list:
                provided_list.remove(iface)
        provided = Declaration(*provided_list)
        try:
            return provided + implemented
        except BaseException as e: # pragma: no cover pylint:disable=broad-except
            # Sadly, zope.interface catches and silently ignores
            # any exceptions raised in ``__providedBy__``,
            # which is the class descriptor that invokes ``__provides__``.
            # So, for example, if we're in strict C3 mode and fail to produce
            # a resolution order, that gets ignored and we fallback to just what's
            # implemented by the class.
            # That's not good. Do our best to propagate the exception by returning it.
            # There will be downstream errors later.
            return e

    def getSignatureString(self):
        return '()'

    def getSignatureInfo(self):
        return {'positional': (),
                'required': (),
                'optional': (),
                'varargs': None,
                'kwargs': None,
                }

    def get(self, object):
        return getattr(object, self.__name__)()

    def query(self, object, default=None):
        try:
            f = getattr(object, self.__name__)
        except AttributeError:
            return default
        else:
            return f()

    def set(self, object, value):
        if self.readonly:
            raise TypeError("Can't set values on read-only fields")
        getattr(object, self.writer.__name__)(value)

    def __getattr__(self, name):
        return getattr(self.field, name)

    def bind(self, object):
        clone = self.__class__.__new__(self.__class__)
        clone.__dict__.update(self.__dict__)
        clone.field = self.field.bind(object)
        return clone


class FieldWriteAccessor(Method):

    def __init__(self, field):
        Method.__init__(self, '')
        self.field = field
        self.__doc__ = 'set %s' % field.__doc__

    def getSignatureString(self):
        return '(newvalue)'

    def getSignatureInfo(self):
        return {'positional': ('newvalue',),
                'required': ('newvalue',),
                'optional': (),
                'varargs': None,
                'kwargs': None,
                }


def accessors(field):
    reader = FieldReadAccessor(field)
    yield reader
    if not field.readonly:
        writer = FieldWriteAccessor(field)
        reader.writer = writer
        yield writer