LDIF (LDAP Data Interchange Format) ################################### LDIF is a data interchange format for LDAP. It is defined in RFC2849 (June 2000) in two different flavours: *LDIF-CONTENT* and *LDIF-CHANGE*. LDIF-CONTENT is used to describe LDAP entries in an ASCII stream (i.e. a file), while LDIF-CHANGE is used to describe Add, Delete, Modify and ModifyDn operations. *These two formats have different purposes and cannot be mixed in the same stream.* If the DN of the entry or an attribute value contains any unicode character the value must be base64 encoded, as specified in RFC2849. ldap3 is compliant to the latest LDIF format (version: 1). ldap3 can stream the output of a search (LDIF-CONTENT) or of an operation (LDIF-CHANGE) to a file object. It cannot read a file with LDIF content because this is not defined in the LDAPv3 standard. Each LDAP server has its own tools to import an LDIF-CONTENT or to perform LDIF-CHANGE. LDIF-CONTENT ============ You can use the LDIF-CONTENT flavour with any search result:: ... # request a few objects from the ldap server result = c.search('o=test','(cn=test-ldif*)', SUBTREE, attributes = ['sn', 'objectClass']) ldif_output = c.response_to_ldif() ... ldif_output will contain:: version: 1 dn: cn=test-ldif-1,o=test objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: Person objectClass: ndsLoginProperties objectClass: Top sn: test-ldif-1 dn: cn=test-ldif-2,o=test objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: Person objectClass: ndsLoginProperties objectClass: Top sn:: dGVzdC1sZGlmLTItw6DDssO5 # total number of entries: 2 you can even request a LDIF-CONTENT for a response you saved early:: # request a few objects from the ldap server response1 = c.search('o=test','(cn=test-ldif*)', SUBTREE, attributes = ['sn', 'objectClass']) response2 = c.search('o=test','(!(cn=test-ldif*))', SUBTREE, attributes = ['sn', 'objectClass']) ldif_output = c.response_to_ldif(response1) ldif_output will contain the LDIF representation of the response entries. LDIF-CHANGE =========== To get the LDIF representation of Add, Modify, Delete and ModifyDn operation you must use the LDIF strategy. With this strategy operations are not executed on an LDAP server but are converted to an LDIF-CHANGE format that can be sent to an LDAP server. For example:: from ldap3 import Connection, LDIF connection = Connection(server = None, client_strategy = LDIF) # no need of real LDAP server connection.open() connection.add('cn=test-add-operation,o=test'), 'inetOrgPerson', {'objectClass': 'inetOrgPerson', 'sn': 'test-add', 'cn': 'test-add-operation'}) in connection.response you will find:: version: 1 dn: cn=test-add-operation,o=test changetype: add objectClass: inetorgperson sn: test-add cn: test-add-operation A more complex modify operation (from the RFC2849 examples):: from ldap3 import MODIFY_ADD. MODIFY_DELETE, MODIFY_REPLACE connection.open() connection.modify('cn=Paula Jensen,ou=Product Development,dc=airius,dc=com', {'postaladdress': (MODIFY_ADD, ['123 Anystreet $ Sunnyvale, CA $ 94086']), 'description': (MODIFY_DELETE, []), 'telephonenumber': (MODIFY_REPLACE, ['+1 408 555 1234', '+1 408 555 5678']), 'facsimiletelephonenumber': (MODIFY_DELETE, ['+1 408 555 9876']) }) returns: version: 1 dn: cn=Paula Jensen,ou=Product Development,dc=airius,dc=com changetype: modify add: postaladdress postaladdress: 123 Anystreet $ Sunnyvale, CA $ 94086 - delete: description - replace: telephonenumber telephonenumber: +1 408 555 1234 telephonenumber: +1 408 555 5678 - delete: facsimiletelephonenumber facsimiletelephonenumber: +1 408 555 9876 - Streaming the output to a file ============================== When producing LDIF-CONTENT output you can have all operation results in a single stream. To get this simply set the stream attribute of the Connection to a stream object (for example to a file) and *open* the connection. If you don't specify the stream object a StringIO will be used. You can get the value with the c.stream.getvalue() method:: from ldap3 import Connection, LDIF c = Connection(None, client_strategy=LDIF) with c: c.delete('cn=test1, o=test') c.delete('cn=test2, o=test') result = c.stream.getvalue() # needed because the stream is closed when the connection exits the context result will be:: version: 1 dn: cn=test1,o=test changetype: delete dn: cn=test2,o=test changetype: delete If you just define a file object as stream you'll find the output in the file:: c = Connection(None, client_strategy=LDIF) c.stream = open('output.ldif', 'w') with c: c.delete('cn=test1, o= test') c.delete('cn=test2, o=test') you will find the LDIF output in the output.ldif file. When producing LDIF-CONTENT you can pass an existing stream object to the response_to_ldif() method to add the LDIF output to the stream. If the stream is empty the ldif version header will be added. Custom line separator ===================== The LDIF stream uses the default line separator (os.linesep) of the system where ldap3 is running as line separator in the LDIF stream. If you need a different line separator you can specify it in the *c.strategy.line_separator* attribute:: c.strategy.line_separator = '\\r\\n' Customizable descriptor order ============================= RFC 2849 doesn't specify any specific order for the lines in the LDIF output except than *version: 1* in the first line of the stream. The library starts any new record with the dn and all subsequent *descriptor: value* lines are in the order they are received by the library. This should no be an issue with an LDIF import in another system, but if you have problems you can force a specific order for the descriptors in any of the LDIF operation: To achieve this you must set the c.strategy.order attribute to a dict where the keys are set to the names of the operations you want their resulting descriptor order is changed and the value to a list of descriptor. The LDIF output lines will be ordered following the order of the descriptor in the list. For example if you add to the previous code:: c.strategy.order = dict(delRequest = ['changetype:', 'dn:']) you will get:: version: 1 changetype: delete dn: cn=test1,o=test changetype: delete dn: cn=test2,o=test The possible operation names are: addRequest, delRequest, modifyRequest, modDNRequest. To change the order of a searchRequest just pass the list in the requested order.