[Zope-dev] Dynamically altering Product methods

boa13@free.fr boa13@free.fr
Wed, 20 Nov 2002 22:22:00 +0100 (CET)


Hello,

I've been playing a bit with the current implementation of WebService that is
availalble from Zope.org. I have tried to adapt the ServiceProxy to a Zope
Product, so that a user can simply drop such a WebServiceProxy in a Folder,
type-in the URL of a WSDL file and then call the methods described in the WSDL
on the WebServiceProxy.

A first prototype incorporated a ServiceProxy instance and offered hard-coded
methods:

    def __init__(self, id):
        self.id = id
        self.gsp = ServiceProxy("http://api.google.com/GoogleSearch.wsdl")

    security.declarePublic('doSpellingSuggestion')
    def doSpellingSuggestion(self, key, phrase):
        "Checks the phrase for correct spelling"
        return self.gsp.doSpellingSuggestion(key, phrase)

It works well, except for a persistence problem that got me stuck for a while.
The WebService package makes use of weakrefs, and I guess this caused
problems. However, removing the usage of weakrefs in ServiceProxy alone seemed
to solve the problem. (If someone can explain that to me, I'm listening!)

While this small test worked well, I wanted to do better: notably, I want the
user to be able to change the WSDL on the fly, without needing to destroy the
object and to add a new one, and also, I want the user to be able to alter the
permission settings for each method in the Web Service.

Well, it seems my wishes have made me enter quite a strange realm of low-level
Zope mechanisms, which I don't always fully grasp!

My current implementation suffers from heavy persistence problems, and
contains the following significant code:

    def setWSDL(self, wsdl, service=None, port=None):
        """
        Sets the Web Service this object proxies. This resets all the Web
        Service methods information, notably the security settings.
        """
        # Remove old WSDL data
        if hasattr(self, '_serializer'):
            binding = self._port.getBinding()
            portType = binding.getPortType()
            for item in portType.operations:
                delattr(self, item.name)
                delattr(self, item.name+'__roles__')
            del self._wsdl
            del self._name
            del self._port
            del self._service
            del self._transport
            del self._serializer
        # Add new WSDL data
        self._security = ClassSecurityInfo()
        declareStaticPermissions(self._security)
        # This code mostly comes from WebService.ServiceProxy
        if not hasattr(wsdl, 'targetNamespace'):
            wsdl = WSDLReader().loadFromURL(wsdl)
        self._serializer = Serializer()
        self._transport = HTTPTransport()
        for item in wsdl.types.items():
            self._serializer.loadSchema(item)
        self._service = wsdl.services[service or 0]
        self._port = self._service.ports[port or 0]
        self._name = self._service.name
        self._wsdl = wsdl
        binding = self._port.getBinding()
        portType = binding.getPortType()
        for item in portType.operations:
            callinfo = callInfoFromWSDL(self._port, item.name)
            method = MethodProxy(self, callinfo)
            setattr(self, item.name, method)
            # Default security for the Web Service methods
            self._security.declareProtected('Use Web Service', item.name)
        self._security.apply(self)
        InitializeClass(self)

declareStaticPermissions() is a module-level function that declares the
security settings for my so-called "static" methods, such as setWSDL, which
are intended to always be present in the WebServiceProxy.

Now, when I use this product, it works well the first time. I can set the WSDL
to the Google Web Service, and then call its spellchecking method
successfully. However, as soon as persistence comes into play, I'm toast.
Simply switching directories a few times is enough for that.

I then get the following error:

2002-11-20T12:20:45 ERROR(200) ZODB Couldn't load state for
 '\x00\x00\x00\x00\x00\x00\n\xa5'
Traceback (innermost last):
  Module ZODB.Connection, line 533, in setstate
TypeError: ('expected 1 arguments, got 0', <extension class
 AccessControl.cAccessControl.PermissionRole at 00E35258>, ())

I have spent quite some time debugging this last night. Line 533 in
ZODB.Connection is

            state = unpickler.load()

and the message means (it took some time to understand!) that unpickler.load()
did not return anything. Also, once this happens, I find myself with two
copies of my WebServiceProxy. From the extreme details of the cache:

1 '\x00\x00\x00\x00\x00\x00\n\xa5'        0 G
   Products.WebServiceProxy.WebServiceProxy.WebServiceProxy
3 '\x00\x00\x00\x00\x00\x00\n\xa5'        4 L
   Products.WebServiceProxy.WebServiceProxy.WebServiceProxy (google)

which I guess is very bad from a consistency point-of-view.

Well, I hope you're still reading me. Here are my questions:

* Am doing something obviously wrong or stupid?

* Is delattr(self, item.name+'__roles__') enough to remove all permission
  information about a method? The persistence error seems to be linked to a
  PermissionRole problem, which is usually what is contained in a m__roles__
  field.
* It is my understanding (after long analysis of the source) that what I'm
  doing with self._security is okay. I mean, it seems to me I should be able
  to redeclare my permission settings at run-time. Is this correct, or am I
  missing something? Acquisition is still rough to me.
* Is it a good idea anyway to remove and add methods from a Product at
  run-time? (delattr(self, item.name), etc.)?

I hope you will be able to give me some insights into this matter. I'd really
like to be able to finish this Product somewhat, even as it is, it could have
some value to the Zope community! :)