Tutorial: ldap3 Abstraction Layer - Writing data ################################################ Writing entries --------------- Modifying data on an LDAP server is easy with the Abstraction Layer, just add a new Entry with its Attribute values (or change the Attribute values of an existing Entry) and commit the pending changes to the DIT via the Writer cursor. You can obtain a Writer cursor in a number of ways: * from a Reader Cursor, using the ``Writer.from_cursor()`` static method that populates the ``entries`` collection with a copy of the Entries from the Reader cursor. * from a Search response, using the ``Writer.from_response()`` static method, that populates the ``entries`` collection with a copy of the Entries from the Search response. * from a single Entry in a Reader cursor, using the ``entry_writable()`` method of the Entry, that returns a new Writable Entry (and also creates its Writer cursor). * from a single Entry in a Search response, using the ``entry_writable()`` method of the Entry, that returns a new Writable Entry (and also creates its Writer cursor) * as a new instance of the ldap3.abstract.Writer class, using ``Writer()`` that creates a new Writer cursor with an empty ``entries`` collection. With this cursor you can only create new Entries. Let's obtain a Writer cursor from the inetOrgPerson Reader we used in the previous chapter:: >>> r = Reader(conn, obj_inetorgperson, 'ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org') >>> r.search() >>> w = Writer.from_cursor(r) >>> w CURSOR : Writer CONN : ldap://ipa.demo1.freeipa.org:389 - cleartext - user: uid=admin,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org - not lazy - bound - open - - tls not started - listening - SyncStrategy - internal decoder DEFS : ['inetOrgPerson'] [audio, businessCategory, carLicense, cn, departmentNumber, description, destinationIndicator, displayName, employeeNumber, employeeType, facsimileTelephoneNumber, givenName, homePhone, homePostalAddress, initials, internationalISDNNumber, jpegPhoto, l, labeledURI, mail, manager, mobile, o, objectClass, ou, pager, photo, physicalDeliveryOfficeName, postOfficeBox, postalAddress, postalCode, preferredDeliveryMethod, preferredLanguage, registeredAddress, roomNumber, secretary, seeAlso, sn, st, street, telephoneNumber, teletexTerminalIdentifier, telexNumber, title, uid, userCertificate, userPKCS12, userPassword, userSMIMECertificate, x121Address, x500UniqueIdentifier] ATTRS : ['audio', 'businessCategory', 'carLicense', 'cn', 'departmentNumber', 'description', 'destinationIndicator', 'displayName', 'employeeNumber', 'employeeType', 'facsimileTelephoneNumber', 'givenName', 'homePhone', 'homePostalAddress', 'initials', 'internationalISDNNumber', 'jpegPhoto', 'l', 'labeledURI', 'mail', 'manager', 'mobile', 'o', 'objectClass', 'ou', 'pager', 'photo', 'physicalDeliveryOfficeName', 'postOfficeBox', 'postalAddress', 'postalCode', 'preferredDeliveryMethod', 'preferredLanguage', 'registeredAddress', 'roomNumber', 'secretary', 'seeAlso', 'sn', 'st', 'street', 'telephoneNumber', 'teletexTerminalIdentifier', 'telexNumber', 'title', 'uid', 'userCertificate', 'userPKCS12', 'userPassword', 'userSMIMECertificate', 'x121Address', 'x500UniqueIdentifier'] ENTRIES: 3 [executed at: 2016-11-09T14:24:49.374675] Entries in a Writer cursor are standard Python object, so you can modify them with standard Python code:: >>> w[0] DN: cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org - STATUS: Writable - READ TIME: 2016-11-09T14:26:03.866351 cn: b.young departmentNumber: DEV givenName: Beatrix objectClass: inetOrgPerson organizationalPerson person top sn: Young telephoneNumber: 1111 The entry is in **Writable** status now, so you can try to update some values. All modifications are stored in memory until committed or the entry is returned to its original values:: >>> w[0].sn += 'Smith' # Add 'Smith' value from the sn >>> w[0].sn += 'Johnson' # Add 'Johnson' value from the sn >>> w[0].sn -= 'Young' # remove the 'Young' value from the sn Now let's revise the modifications we have requested:: >>> w[0].entry_changes OrderedDict([('sn', [('MODIFY_ADD', ['Smith']), ('MODIFY_ADD', ['Johnson']), ('MODIFY_DELETE', ['Young'])])]) Modifications to an Entry are stored in a way (OrderedDict) that preserves the insertion sequence. This can be helpful with specific LDAP operations that request that an attribute is modified before an other one in the same LDAP operation We made a typo so discard the changes and insert the correct values: >>> w[0].sn.discard() >>> w[0].sn += ['Smith', 'Johnson'] # add a list of values >>> w[0].sn -= 'Young' # remove the 'Young' value from the sn >>> w[0] DN: cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org - STATUS: Writable, Pending changes - READ TIME: 2016-11-09T14:30:43.181520 cn: b.young departmentNumber: DEV givenName: Beatrix objectClass: inetOrgPerson organizationalPerson person top sn: Young CHANGES: [('MODIFY_ADD', ['Smith', 'Johnson']), ('MODIFY_DELETE', ['Young'])] telephoneNumber: 1111 Entry status is set to *Writable, Pending changes*, this means that mandatory Attributes are set and the Entry can be written in the DIT:: >>> w.commit() # commit all entries with pending changes >>> w[0] DN: cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org - STATUS: Writable, Committed - READ TIME: 2016-11-09T14:32:14.377498 cn: b.young departmentNumber: DEV givenName: Beatrix objectClass: inetOrgPerson organizationalPerson person top sn: Smith Johnson telephoneNumber: 1111 Entry has been written on the DIT and its state is now *Writable, Committed*. If you look at the original Entry in the Reader you will find that it's been updated with the new values:: >>> r[0] DN: cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org - STATUS: Read - READ TIME: 2016-11-09T14:32:14.377498 cn: b.young departmentNumber: DEV givenName: Beatrix objectClass: inetOrgPerson organizationalPerson person top sn: Smith Johnson telephoneNumber: 1111 Refreshing of the original Entry is triggered only if both cursors are using the same Server object. If you use the Writer cursor to copy Entries to another LDAP server refreshing of the original Entry is not executed. For specific types (boolean, integers and dates) you can set the value to the relevant Python type. The ldap3 library will perform the necessary conversion to the value expected from the LDAP server.