Yet Another PEP: Query Protocol Interface or __query__

Clark C. Evans cce at clarkevans.com
Tue Mar 27 09:40:27 EST 2001


I've started to write an example usage of this PEP.  It is
far from perfect, however it should give you an idea of
the usage.  I could use some help with examples and tightening
this up... 

Thanks!  Clark

...


Example Usage (written but not checked)

    Suppose a user has a function that requires a file-like 
    object.  Here is how adapt can be used to ensure that
    an argument does indeed follow the file protocol:

        import types
        from adapt import adapt
        def first_line(file):
            file = adapt(file,types.FileType)
            print file.readline()

        >>> first_line(open("c:/autoexec.bat"))
        SET WXWIN=F:\PROGRA~1\wx2
        >>> first_line('bad')
        ...
        TypeError: test cannot be adapted to <type 'file'>

    Let us assume that the code above becomes the norm, and
    now the user needs to re-use this code with a BLOB
    stored in a database.  Here is how that would be done.

        class PseudoFile:
            def readline(self):             # read line from BLOB
               return "This is a new line"  # real code goes here
            # (other file functions go here)
            def __conform__(self,protocol):
                if protocol is types.FileType: return self

        >>> first_line(PseudoFile())
        This is a new line

    Let us say that this works for a while, but sooner or later
    the concept of a forward-access read-only file is needed.
    So, perhaps the following "standard interface" emerges:

    class ForwardFile:
        def readline(self): pass
        def read(self,size=-1): pass
        class AdaptRead(ForwardFile):
            def readline(self):  # implement using read
        class AdaptReadline(ForwardFile):
            def read(self,size=-1):  # implement using readline
        class Adapt:
            def __call__(self, obj):
                # objects adaptable to FileType is are compliant
                retval = adapt(obj,types.FileType)
                if retval: return retval
                # objects that have both readline and read are ok
                if getattr(obj,'readline',None) and \
                    getattr(obj,'read',None): return obj
                # wrap objects exposing a readline
                if getattr(obj,'readline',None):
                    retval = AdaptReadline()
                    retval.readline = obj.readline
                    return retval
                # wrap objects exposing a read
                if getattr(obj,'read',None):
                    retval = AdaptRead()
                    retval.read = obj.read
                    return retval
        __adapt__= Adapt()

    This would allow our first-line program to be re-written to
    use the new interface:

        def first_line(file):
            file = adapt(file,ForwardFile)
            print file.readline()

    Note that anything which inherits from ForwardFile is a
    adaptable as well as anything which considers itself a FileType
    More so, do to the __adapt__ class method above, anything
    that has read or readline can be adapted to work!

    Let's say that time passes on, and a record-set class is
    created.  And after the above interface is well in use
    (and now immutable due to wide distribution), the creator
    of the record-set class wishes to make this new class
    substitutable for any ForwardFile.

        class RecordSet:
            def readline(): pass  # some intelligent impl
            def read():  pass     # more intelligent impl
            # other record set specific stuff

    There are a few choices.  First, the RecordSet class
    could inherit from ForwardFile.  Or, the following
    __adapt__ method could be added!

            def __conform__(self,protocol):
                if protocol is ForwardFile: return self

    This has a slight problem in that a dependency is now
    created (ForwardFile must be on the user's system).  This
    can be mitigated with something like the following:

            def __conform__(self,protocol):
                if protocol.__name__ = 'ForwardFile': return self

    Note that readline and read could be put in a wrapper class
    instead (so that they don't clutter the RecordSet class!  
    Regardless, in every case where a ForwardFile is accepted, 
    the adapter mechanism will also pass off for objects of 
    this class.


Questions:  

  1. Is there a way to get a fully-qualified name
     (including the package?)

  2.  What kind of "wrapper helpers" could be made?
      (I like Carlos's examples!)

  3.  Perhaps a __conform__ function would also
      like to know who the caller is... I wonder
      if this would be useful.  Can this be
      obtained automatically?

  3.  Would a __protocol__ item also help?  

          class ForwardFile:
               ...
              __protocol__ = "com.zoober.protocols.ForwardFile"

      This would allow the __conform__ above to be re-written:

           def __conform__(self,protocol):
               if protocol.__protocol__ =
                  'com.zoober.protcols.ForwardFile': 
                      return self
          
      In this way, support for multiple protocols could
      be put in place without requiring said classes/interface
      declarations on the user's box.

  4.  Is a protcol registry needed, or does the
      above suffice?  (I think it's good enough)
      A proposal would be like:

        register_adapter(relevant_protocol, the_handler)

      And then, after exhausting the object and the
      protcol, it could check in the registry...


Thank you all for your help!

Clark





More information about the Python-list mailing list