Hi

First I'm not upset by anything.

You are responsible to maintain the package in an healthy state.
This also your responsibility to add or remove some features and then
to maintain them.
Thanks for doing that.

As you have suggested, I made a class wrapper that keep  both code as
independent as possible, and I'm happy with that.

Anyway I'have some comment about your answerer ...

On 7/19/07, Michael Ströder <[EMAIL PROTECTED]> wrote:
Alain,

Alain Spineux wrote:
>
> When investigating about python and unicode, I read somewhere (in a PEP
> I thing) that python functions should accept and manage unicode string
> as well as normal string.

Without knowing the PEP (reference?) I guess this affects functions
which takes a string as an argument and process it directly returning a
result. In context of python-ldap this would be directly applicable to
the functions in modules ldap.dn and ldap.filter.

Unicode string in python are made in a way that let the developer use them
in a complete transparent way. If the libraries are respecting this
principle too,
the developer can exchange data from different sources (user input,
SQL, ldap ...)
without never making any conversion.

The problem is strings are also used for binary storage and LDAP don't
make difference
between both usage (no charset and unicode types like in SQL), only
the developer know
and can make the conversion.


The basic problem here is that for the sake of backward-compability to
LDAPv2 the charset has to be passed around either. That's what I'm doing
in web2ldap.

> Of course if these strings could contains user
> readable characters.

Let's call that "textual strings".

> Anyway I see 2 solutions
>
> 1. Let result() return non unicode strings. _HERE_ The user know all
> returned
> strings are normal strings utf-8 encoded and he can do the encoding
> himself. A helper function doing the job for the result structure
> should be welcome.
>
> 2. Do the conversion regarding the info provided in the query, as my
> source sample does.
>
> I answer now some of your previous comment:
>
>> > In this case maybe is it possible to use [ '*', u'givenName', u'sn' ]
>> > to convert only 'givenName' and 'sn'
>
>> But then you will not gain much! Still the application has to know which
>> attributes have to be converted. => It's not worth hiding the conversion
>> within python-ldap.
>
> I don't really hide the conversion, because the user has to request it using
> unicode field name.

I don't like this approach. The type of the attribute names is causing a
type conversion side-effect. I don't consider this to be good design and
I guess most Python developers would not expect something like this.
Think about an application accidently passing in Unicode strings but is
not really prepared to get the Unicode/string mix.

Today passing unicode argument to ldap functions raise an exception, then
no accidents is possible :-)
On the other side, with unicode support, things could accidentally
work as expected.
But this is only speculation about witch inconvenient is the worst.


> Do you really consider to add the schema processing for unicode
> integration in the future?

Nope. It's up to the application programmer, especially based on whether
LDAPv2 support is still needed for a particular application or not. I
consider python-ldap to be rather a low-level API.

> Keep in mind, none of my code break compatibility with existing application.

Generally I don't want to discourage people to work on something. But
sorry, I won't add your code to python-ldap's Lib/. I hope you're not
upset. My proposal would be to add it under Demo/ so you're work can be
considered to be used by others. Or you can put it on your own web page
(for further development) and I'll put a link to it on
http://python-ldap.sourceforge.net/docs.shtml.

I have no website today :-(
Please use the last version in attachment.

Regards


Ciao, Michael.



--
--
Alain Spineux
aspineux gmail com
May the sources be with you
#!/usr/bin/env python
# 
# This library wrapper bring some Unicode support to python-ldap
#
# This wrapper helped the author to avoid any explicit
# pre and post unicode conversion, this was the goal.
#
# The main idea is that any unicode argument will be encoded 
# using utf-8 encoding before to reach the ldap server.
# On the other side any result will be decoded regarding the 
# tips you have given in the attribute list.
#
# Read the code at the end to have more info about 
# how to use it
#
# written by Alain Spineux <alain spineux AT gmail com>
#
# v0.1.0 Fri Jul 20 14:49:06 CEST 2007


__version__ = '0.1.0'

import types, datetime
import ldap, ldapurl, ldap.modlist

from ldap.ldapobject import LDAPObject
from ldap.ldapobject import ReconnectLDAPObject

def unicode2utf8(st):
    """Convert unicode (and only unicode) string into utf-8 raw string as expected by ldap"""
    if isinstance(st, types.UnicodeType):
        return st.encode('utf-8')
    else:
        return st

def utf82unicode(st):
    """encode st into utf-8"""
    return st.decode('utf-8')


def encode_modlist(modlist, no_op):
    """encode ldap modlist structure
       set no_op=True for Tuple of kind (int,str,[str,...])
       and False for (str, [str,...])
    """

    for i, mod in enumerate(modlist):
        if no_op:
            attr_name, attr_values=mod
        else:
            op, attr_name, attr_values=mod
        
        attr_name=unicode2utf8(attr_name)
        if isinstance(attr_values, (types.ListType, types.TupleType)):
            attr_values=map(unicode2utf8, attr_values)
        else:
            attr_values=unicode2utf8(attr_values)
        if no_op:
            modlist[i]=(attr_name, attr_values)
        else:
            modlist[i]=(op, attr_name, attr_values)

    return modlist

def _print_ldap_result(ldap_result):
    for dn, item in ldap_result:
        print 'DN=', repr(dn)
        for k, v in item.iteritems():
            print '\t%s: %s' % (k, repr(v))
        print


class UnicodeLDAPInterface:

    ldapbaseclass=None
    decoder_expiration_delay=300 # the expiration delay for an object in self.unicode_decoder
    
    def __init__(self, uri, **kwargs):
        self.ldapbaseclass.__init__(self, uri, **kwargs)
        self.unicode_decoder={} # { (msgid, expiration, decoder_data) ... }
        # I use an expiration time to avoid the list to become to big when the
        # server don't answer some requests

    def _set_unicode_decoder(self, msgid, value):
        """protect unicode_decoder against multi-threading
        update or add the decoder
         """
        self._ldap_object_lock.acquire()
        try:
            self.unicode_decoder[msgid]=value
        finally:
            self._ldap_object_lock.release()

    def _remove_unicode_decoder(self, msgid):
        """protect unicode_decoder against multi-threading
        remove the decoder
         """
        self._ldap_object_lock.acquire()
        try:
            del self.unicode_decoder[msgid]
        except KeyError:
            pass
        self._ldap_object_lock.release()

    def _get_unicode_decoder(self, msgid):
        """protect unicode_decoder against multi-threading
         read the decoder info for msgid
         """
        self._ldap_object_lock.acquire()
        try:
            return self.unicode_decoder[msgid]
        finally:
            self._ldap_object_lock.release()

    def _expire_unicode_decoder(self):
        """cleanup any expired decoder"""
        self._ldap_object_lock.acquire()
        now=datetime.datetime.now()
        for msgid in self.unicode_decoder.keys():
            if self.unicode_decoder[msgid][1]<now:
                del self.unicode_decoder[msgid]
        self._ldap_object_lock.release()


    def search_ext(self,base,scope, filterstr, attrlist, *args, **kwargs):
        # base,scope, filterstr='(objectClass=*)',attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0
        
        # convert filter
        filterstr_u=unicode2utf8(filterstr)
        
        # convert arglist and keep a copy of original values for later decoding
        attrlist_u=[]
        decoder={} # will keep only fields to decode
        if attrlist!=None:
            for attr in attrlist:
                if isinstance(attr, types.UnicodeType):
                    attr=attr.encode('utf-8')
                    decoder[attr]=True
                attrlist_u.append(attr)

        msgid=self.ldapbaseclass.search_ext(self,base,scope, filterstr_u, attrlist_u, *args, **kwargs)

        if decoder:
            timeout=kwargs.get('timeout', None)
            if timeout==None or timeout<=0:
                timeout=self.decoder_expiration_delay
            self._set_unicode_decoder(msgid,(msgid, datetime.datetime.now()+datetime.timedelta(seconds=timeout), decoder))
        return msgid

    def result3(self, *args, **kwargs):
        # kwargs=(self, msgid=_ldap.RES_ANY,all=1,timeout=None):
        rtype, rdata, rmsgid, decoded_serverctrls=self.ldapbaseclass.result3(self, *args, **kwargs)

        try:
            msgid, expire, decoder=self._get_unicode_decoder(rmsgid)
        except KeyError:
            pass
            # no decoder for this => nothing to decode
        else:
            if rtype not in [ ldap.RES_SEARCH_ENTRY, ldap.RES_SEARCH_REFERENCE ]:
                # this was the last result
                self._remove_unicode_decoder(rmsgid)
            else:
                # reset the timeout
                timeout=kwargs.get('timeout', None)
                if timeout==None or timeout<=0:
                    timeout=self.expiration_delay
                self._set_unicode_decoder(msgid, (msgid, datetime.datetime.now()+datetime.timedelta(seconds=timeout), decoder))
            
            # now decode the result
            if rdata:
                if rtype in [ldap.RES_SEARCH_ENTRY, ldap.RES_SEARCH_REFERENCE, ldap.RES_SEARCH_RESULT]:
                    # FIXME: I dont know what is a RES_SEARCH_REFERENCE
                    rdata_u=[]
                    for i, (dn, attrs) in enumerate(rdata):
                        # FIXME: should I handle the 'dn' the same way
                        if decoder.has_key('dn'):
                            dn=utf82unicode(dn)
                        for key in attrs.keys():
                            if decoder.has_key(key):
                                attrs[key]=map(utf82unicode, attrs[key])
                        # print '\tITEM=', dn, attrs
                        rdata[i]=(dn, attrs)

        self._expire_unicode_decoder()
        
        return rtype, rdata, rmsgid, decoded_serverctrls

    def add_ext(self, dn, modlist, *args, **kwargs):
        # args=(self,dn,modlist,serverctrls=None,clientctrls=None)
        dn=unicode2utf8(dn)
        # print 'MODLIST', modlist
        modlist=encode_modlist(modlist, True)
        # print 'MODLIST unicode', modlist
        return self.ldapbaseclass.add_ext(self, dn, modlist, *args, **kwargs)

    def modify_ext(self, dn, modlist, *args, **kwargs):
        # args=(self,dn,modlist,serverctrls=None,clientctrls=None)
        dn=unicode2utf8(dn)
        # print 'MODLIST', modlist
        modlist=encode_modlist(modlist, False)
        # print 'MODLIST unicode', modlist
        return self.ldapbaseclass.modify_ext(self, dn, modlist, *args, **kwargs)

    def delete_ext(self, dn, *args, **kwargs):
        # args=(self,dn,serverctrls=None,clientctrls=None)
        dn=unicode2utf8(dn)
        return self.ldapbaseclass.delete_ext(self, dn, *args, **kwargs)

    def abandon_ext(self, msgid, *args, **kwargs):
        # args=(self,msgid,serverctrls=None,clientctrls=None)
        result=self.ldapbaseclass.abandon_ext(self, msgid, *args, **kwargs)
        self._remove_unicode_decoder(msgid)
        return result

    def cancel_ext(self, cancelid, *args, **kwargs):
        # args=(self,msgid,serverctrls=None,clientctrls=None)
        result=self.ldapbaseclass.cancel_ext(self, cancelid, *args, **kwargs)
        self._remove_unicode_decoder(cancelid)
        return result

class UnicodeLDAPObject(UnicodeLDAPInterface, LDAPObject):
    ldapbaseclass=LDAPObject
    
class UnicodeReconnectLDAPObject(UnicodeLDAPInterface, ReconnectLDAPObject):
    ldapbaseclass=ReconnectLDAPObject


if __name__=='__main__':

    import sys, os, time
    
    host='localhost'
    port=389
    base_dn='dc=asxnet,dc=loc'
    
    if True:
        who='cn=manager,cn=internal,dc=asxnet,dc=loc'
        cred='vishnou'
    else:
        who='cn=nobody,cn=internal,dc=asxnet,dc=loc'
        cred='iMmTWz5pJ+lwY7i6M/BU61ngo1aBLyqQhRrrKbEc'
    
    ldap_url=ldapurl.LDAPUrl('ldap://%s:%d/%s' % (host, port, base_dn))
    ldap_url.applyDefaults({
       'who': who,
       'cred' : cred, })
    print ldap_url
    #l=LDAPObject(ldap_url.initializeUrl())
    #l=UnicodeLDAPObject(ldap_url.initializeUrl())
    l=UnicodeReconnectLDAPObject(ldap_url.initializeUrl())
    l.simple_bind_s(ldap_url.who, ldap_url.cred)
    print 'Connected as', l.whoami_s()
    
    first_name='Michael'
    first_name2=u'Micha\xebl'
    last_name=u'Str\xf6der'
    email='[EMAIL PROTECTED]'
    street=u'Hauptstra\xe1e'
    country='Germany'
    
    cn='%s %s' %(first_name, last_name)
    dn='cn=%s,%s' %(cn, base_dn)
    info={
        u'cn' : (cn, ),
        'mail' : (email, ),
        'objectClass' : ('top', 'inetOrgPerson', 'kolabInetOrgPerson',), 
        u'sn' : (last_name, ),
        u'givenName' : (first_name, ),
        u'street': (street, ),
        'c': (country, ),
        'telephoneNumber': '+49 1111111111',
    }
    
    ldap_result=l.search_s(base_dn, ldap.SCOPE_ONELEVEL, '(cn=%s)' % (cn,) , info.keys())
    if ldap_result:
        print '== Found'
        _print_ldap_result(ldap_result)
        l.delete_s(dn)    
        print '== Deleted'
        
    l.add_s(dn, ldap.modlist.addModlist(info))    
    print '== Created'
    ldap_result=l.search_s(base_dn, ldap.SCOPE_ONELEVEL, '(cn=%s)' % (cn,) , info.keys())
    _print_ldap_result(ldap_result)
    
    l.modify_s(dn, [(ldap.MOD_REPLACE, u'givenName', first_name2),
                    (ldap.MOD_ADD, 'telephoneNumber', ( '+49 1234567890', )),
                     ])
    
    print '==Modified'
    ldap_result=l.search_s(base_dn, ldap.SCOPE_ONELEVEL, '(cn=%s)' % (cn,) , info.keys())
    _print_ldap_result(ldap_result)

    print '==Display once more'
    ldap_result=l.search_s(base_dn, ldap.SCOPE_ONELEVEL, '(cn=%s)' % (cn,) , ['*', '+', u'dn', u'givenName', u'creatorsName'] )
    _print_ldap_result(ldap_result)


-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2005.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Python-LDAP-dev mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/python-ldap-dev

Reply via email to