~leonardr/lazr.restful/multiversion-collection

« back to all changes in this revision

Viewing changes to src/lazr/restful/_resource.py

  • Committer: Leonard Richardson
  • Date: 2010-01-12 17:44:41 UTC
  • mfrom: (93.1.46 entry-traverse)
  • Revision ID: leonard.richardson@canonical.com-20100112174441-jq2a2ovya8b4hj6y
[r=flacoste] It's now possible to define two distinct web services based on the same data model.

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
    'JSONItem',
19
19
    'ReadOnlyResource',
20
20
    'RedirectResource',
 
21
    'register_versioned_request_utility',
21
22
    'render_field_to_html',
22
23
    'ResourceJSONEncoder',
23
24
    'RESTUtilityBase',
47
48
from zope.app.pagetemplate.engine import TrustedAppPT
48
49
from zope import component
49
50
from zope.component import (
50
 
    adapts, getAdapters, getAllUtilitiesRegisteredFor, getMultiAdapter,
51
 
    getUtility, queryAdapter, getGlobalSiteManager)
 
51
    adapts, getAdapters, getAllUtilitiesRegisteredFor,
 
52
    getGlobalSiteManager, getMultiAdapter, getSiteManager, getUtility,
 
53
    queryMultiAdapter)
52
54
from zope.component.interfaces import ComponentLookupError
53
55
from zope.event import notify
54
56
from zope.publisher.http import init_status_codes, status_reasons
55
57
from zope.interface import (
56
 
    implementer, implements, implementedBy, providedBy, Interface)
 
58
    alsoProvides, implementer, implements, implementedBy, providedBy,
 
59
    Interface)
57
60
from zope.interface.common.sequence import IFiniteSequence
58
61
from zope.interface.interfaces import IInterface
59
62
from zope.location.interfaces import ILocation
82
85
    IResourceDELETEOperation, IResourceGETOperation, IResourcePOSTOperation,
83
86
    IScopedCollection, IServiceRootResource, ITopLevelEntryLink,
84
87
    IUnmarshallingDoesntNeedValue, IWebServiceClientRequest,
85
 
    IWebServiceConfiguration, LAZR_WEBSERVICE_NAME)
 
88
    IWebServiceConfiguration, IWebServiceLayer, IWebServiceVersion,
 
89
    LAZR_WEBSERVICE_NAME)
86
90
from lazr.restful.utils import get_current_browser_request
87
91
 
88
92
 
112
116
        return unicode(value)
113
117
 
114
118
 
 
119
def register_versioned_request_utility(interface, version):
 
120
    """Registers a marker interface as a utility for version lookup.
 
121
 
 
122
    This function registers the given interface class as the
 
123
    IWebServiceVersion utility for the given version string.
 
124
    """
 
125
    alsoProvides(interface, IWebServiceVersion)
 
126
    getSiteManager().registerUtility(
 
127
        interface, IWebServiceVersion, name=version)
 
128
 
 
129
 
115
130
class LazrPageTemplateFile(TrustedAppPT, PageTemplateFile):
116
131
    "A page template class for generating web service-related documents."
117
132
    pass
143
158
                return tuple(obj)
144
159
            if isinstance(underlying_object, dict):
145
160
                return dict(obj)
146
 
        if queryAdapter(obj, IEntry):
147
 
            obj = EntryResource(obj, get_current_browser_request())
 
161
        request = get_current_browser_request()
 
162
        if queryMultiAdapter((obj, request), IEntry):
 
163
            obj = EntryResource(obj, request)
148
164
 
149
165
        return IJSONPublishable(obj).toDataForJSON()
150
166
 
1220
1236
        self.__parent__ = self.entry.context
1221
1237
        self.__name__ = self.name
1222
1238
 
 
1239
 
1223
1240
class EntryFieldURL(AbsoluteURL):
1224
1241
    """An IAbsoluteURL adapter for EntryField objects."""
1225
1242
    component.adapts(EntryField, IHTTPRequest)
1243
1260
    def __init__(self, context, request):
1244
1261
        """Associate this resource with a specific object and request."""
1245
1262
        super(EntryResource, self).__init__(context, request)
1246
 
        self.entry = IEntry(context)
 
1263
        self.entry = getMultiAdapter((context, request), IEntry)
1247
1264
 
1248
1265
    def _getETagCore(self, unmarshalled_field_values=None):
1249
1266
        """Calculate the ETag for an entry.
1442
1459
    def __init__(self, context, request):
1443
1460
        """Associate this resource with a specific object and request."""
1444
1461
        super(CollectionResource, self).__init__(context, request)
1445
 
        self.collection = ICollection(context)
 
1462
        if ICollection.providedBy(context):
 
1463
            self.collection = context
 
1464
        else:
 
1465
            self.collection = getMultiAdapter((context, request), ICollection)
1446
1466
 
1447
1467
    def do_GET(self):
1448
1468
        """Fetch a collection and render it as JSON."""
1492
1512
            # Scoped collection. The type URL depends on what type of
1493
1513
            # entry the collection holds.
1494
1514
            schema = self.context.relationship.value_type.schema
1495
 
            adapter = EntryAdapterUtility.forSchemaInterface(schema)
 
1515
            adapter = EntryAdapterUtility.forSchemaInterface(
 
1516
                schema, self.request)
1496
1517
            return adapter.entry_page_type_link
1497
1518
        else:
1498
1519
            # Top-level collection.
1499
1520
            schema = self.collection.entry_schema
1500
 
            adapter = EntryAdapterUtility.forEntryInterface(schema)
 
1521
            adapter = EntryAdapterUtility.forEntryInterface(
 
1522
                schema, self.request)
1501
1523
            return adapter.collection_type_link
1502
1524
 
1503
1525
 
1588
1610
                    # class's singular or plural names.
1589
1611
                    schema = registration.required[0]
1590
1612
                    adapter = EntryAdapterUtility.forSchemaInterface(
1591
 
                        schema)
 
1613
                        schema, self.request)
1592
1614
 
1593
1615
                    singular = adapter.singular_type
1594
1616
                    assert not singular_names.has_key(singular), (
1662
1684
                        # It's not a top-level resource.
1663
1685
                        continue
1664
1686
                    adapter = EntryAdapterUtility.forEntryInterface(
1665
 
                        entry_schema)
 
1687
                        entry_schema, self.request)
1666
1688
                    link_name = ("%s_collection_link" % adapter.plural_type)
1667
1689
                    top_level_resources[link_name] = utility
1668
1690
        # Now, collect the top-level entries.
1687
1709
    """An individual entry."""
1688
1710
    implements(IEntry)
1689
1711
 
1690
 
    def __init__(self, context):
 
1712
    def __init__(self, context, request):
1691
1713
        """Associate the entry with some database model object."""
1692
1714
        self.context = context
 
1715
        self.request = request
1693
1716
 
1694
1717
 
1695
1718
class Collection:
1696
1719
    """A collection of entries."""
1697
1720
    implements(ICollection)
1698
1721
 
1699
 
    def __init__(self, context):
 
1722
    def __init__(self, context, request):
1700
1723
        """Associate the entry with some database model object."""
1701
1724
        self.context = context
 
1725
        self.request = request
1702
1726
 
1703
1727
 
1704
1728
class ScopedCollection:
1705
1729
    """A collection associated with some parent object."""
1706
1730
    implements(IScopedCollection)
1707
 
    adapts(Interface, Interface)
 
1731
    adapts(Interface, Interface, IWebServiceLayer)
1708
1732
 
1709
 
    def __init__(self, context, collection):
 
1733
    def __init__(self, context, collection, request):
1710
1734
        """Initialize the scoped collection.
1711
1735
 
1712
1736
        :param context: The object to which the collection is scoped.
1714
1738
        """
1715
1739
        self.context = context
1716
1740
        self.collection = collection
 
1741
        self.request = request
1717
1742
        # Unknown at this time. Should be set by our call-site.
1718
1743
        self.relationship = None
1719
1744
 
1723
1748
        # We are given a model schema (IFoo). Look up the
1724
1749
        # corresponding entry schema (IFooEntry).
1725
1750
        model_schema = self.relationship.value_type.schema
1726
 
        return getGlobalSiteManager().adapters.lookup1(
1727
 
            model_schema, IEntry).schema
 
1751
        request_interface = getUtility(
 
1752
            IWebServiceVersion,
 
1753
            name=self.request.version)
 
1754
        return getGlobalSiteManager().adapters.lookup(
 
1755
            (model_schema, request_interface), IEntry).schema
1728
1756
 
1729
1757
    def find(self):
1730
1758
        """See `ICollection`."""
1748
1776
    """
1749
1777
 
1750
1778
    @classmethod
1751
 
    def forSchemaInterface(cls, entry_interface):
 
1779
    def forSchemaInterface(cls, entry_interface, request):
1752
1780
        """Create an entry adapter utility, given a schema interface.
1753
1781
 
1754
1782
        A schema interface is one that can be annotated to produce a
1755
1783
        subclass of IEntry.
1756
1784
        """
 
1785
        request_interface = getUtility(
 
1786
            IWebServiceVersion, name=request.version)
1757
1787
        entry_class = getGlobalSiteManager().adapters.lookup(
1758
 
            (entry_interface,), IEntry)
 
1788
            (entry_interface, request_interface), IEntry)
1759
1789
        assert entry_class is not None, (
1760
 
            "No IEntry adapter found for %s." % entry_interface.__name__)
 
1790
            ("No IEntry adapter found for %s (web service version: %s)."
 
1791
             % (entry_interface.__name__, request.version)))
1761
1792
        return EntryAdapterUtility(entry_class)
1762
1793
 
1763
1794
    @classmethod
1764
 
    def forEntryInterface(cls, entry_interface):
 
1795
    def forEntryInterface(cls, entry_interface, request):
1765
1796
        """Create an entry adapter utility, given a subclass of IEntry."""
1766
1797
        registrations = getGlobalSiteManager().registeredAdapters()
 
1798
        # There should be one IEntry subclass registered for every
 
1799
        # version of the web service. We'll go through the appropriate
 
1800
        # IEntry registrations looking for one associated with the
 
1801
        # same IWebServiceVersion interface we find on the 'request'
 
1802
        # object.
1767
1803
        entry_classes = [
1768
1804
            registration.factory for registration in registrations
1769
1805
            if (IInterface.providedBy(registration.provided)
1770
1806
                and registration.provided.isOrExtends(IEntry)
1771
 
                and entry_interface.implementedBy(registration.factory))]
 
1807
                and entry_interface.implementedBy(registration.factory)
 
1808
                and registration.required[1].providedBy(request))]
1772
1809
        assert not len(entry_classes) > 1, (
1773
 
            "%s provides more than one IEntry subclass." %
1774
 
            entry_interface.__name__)
 
1810
            "%s provides more than one IEntry subclass for version %s." %
 
1811
            entry_interface.__name__, request.version)
1775
1812
        assert not len(entry_classes) < 1, (
1776
 
            "%s does not provide any IEntry subclass." %
1777
 
            entry_interface.__name__)
 
1813
            "%s does not provide any IEntry subclass for version %s." %
 
1814
            entry_interface.__name__, request.version)
1778
1815
        return EntryAdapterUtility(entry_classes[0])
1779
1816
 
1780
1817
    def __init__(self, entry_class):