Hooks: modifying ObjectStore behavior
python-ldap-faker
provides a hook system to allow you to arbitrarily modify
behavior of ObjectStore
. Primarily this is provided so that you can
emulate the behavior of the various LDAP implementations (Redhat Directory
Server, Active Directory, openldap, etc.).
You can also use hooks in your test code to produce behavior that may not be
available out of the box from python-ldap-faker
.
Rules about hooks:
Hooks are run in the order they are registered
Each hook needs a callable with a particular signature
Hooks are global – they apply to all
ObjectStore
instances and instances instantiated (unless they are tagged hooks)
Registering hooks
Hooks have a name and a callable signature. Here is an example of registering a
hook to the pre_set
hook, which will be run in ObjectStore.set
before the object is saved to the internal storage, and requires the callable
signature Callable[[ObjectStore, LDAPRecord, Optional[str]], None]
:
from ldap_faker import hooks, ObjectStore, LDAPRecord
def pre_set_do_something_special(store: ObjectStore, record: LDAPRecord, bind_dn: str = None) -> None:
...
hooks.register('pre_set', pre_set_do_something_special)
Thereafter, whenever any code calls ObjectStore.set
, this function
will be called with the store as the first argument, the record to be written as
the second argument and the bind_dn
of the binding user as the third
argument.
Tagged hooks
Using tags, you can register a hook that will only apply to
ObjectStore
instances which are themselves tagged with one of those
tags:
from ldap_faker import hooks, ObjectStore, LDAPRecord
def pre_set_do_something_special(store: ObjectStore, record: LDAPRecord, bind_dn: str = None) -> None:
print(f'{bind_dn} ran pre_set_do_something_sepcial')
hooks.register('pre_set', pre_set_do_something_special, tags=['special'])
This hook will only be executed for ObjectStore
instances whose tags
include special
:
>>> store = ObjectStore(tags=['special'])
>>> obj = ('mydn', {'objectclass': [b'top']))
>>> store.set(obj, bind_dn='auser')
auser ran pre_set_do_something_special
It will not be executed for ObjectStore
instances whose tags do not
include special:
>>> store = ObjectStore(tags=['other'])
>>> obj = ('mydn', {'objectclass': [b'top']))
>>> store.set(obj, bind_dn='auser')
Tagging ObjectClass instances in LDAPFakerMixin
When using LDAPFakerMixin
, you can tag ldap_fixtures
with particular tags.
To tag the default “server”, specify the fixture as a 2-tuple, where the first element is the filename of the fixture file, and the second element is a list of tags:
import unittest
from ldap_faker import LDAPFakerMixin
class TestDefaultTaggedServer(LDAPFakerMixin, unittest.TestCase):
ldap_modules = ['myapp']
ldap_fixtures = ('data.json', ['special'])
To tag named “servers”, you can tag individual servers by providing a 3-tuple instad of a 2-tuple, where the third element is the list of tags:
import unittest
from ldap_faker import LDAPFakerMixin
class TestDefaultTaggedServer(LDAPFakerMixin, unittest.TestCase):
ldap_modules = ['myapp']
ldap_fixtures = [
('server1.json', 'ldap://server1', ['special']),
('server2.json', 'ldap://server2')
]
Above, ldap://server1
will use all hooks tagged with special
in addition
to any untagged hooks, while ldap://server2
will use only the untagged
hooks.
Available hooks
pre_objectstore_init
Signature:
Callable[[store: ObjectStore], None]
Where
store
is theObjectStore
object.This will be at the end of
ObjectStore.__init__
.You can use this to set up any state you might need for later hooks by adding keys to
ObjectStore.controls
, or to add attributes toObjectStore.operational_attributes
.pre_set
Signature:
Callable[[store: ObjectStore, record: LDAPRecord, bind_dn: Optional[str] = None], None]
Where
store
is theObjectStore
object,record
is the record to beset
andbind_dn
is the dn of the user doing theset
(possiblyNone
)This will be executed on
ObjectStore.set
before the object actually gets saved.ObjectStore.set
is called for every write operation:post_set
Signature:
Callable[[store: ObjectStore, record: LDAPRecord, bind_dn: Optional[str] = None], None]
Where
store
is theObjectStore
object,record
is the record to beset
andbind_dn
is the dn of the user doing theset
(possiblyNone
).This will be executed on
ObjectStore.set
after the object gets saved.pre_copy
Signature:
Callable[[store: ObjectStore, dn: str], None]
Where
store
is theObjectStore
object, anddn
is the DN of the object to copy.This will be executed on
ObjectStore.copy
before the object actually gets retrieved from the store to be copied.post_copy
Signature:
Callable[[store: ObjectStore, data: LDAPData], LDAPData]
Where
store
is theObjectStore
object, anddn
is the DN of the object to copy. It should return the modifiedLDAPData
dict.This will be executed on
ObjectStore.copy
after the object is retrieved from the store and :py:func:copy.deepcopy
has run, but before returning the data to the caller.pre_create
Signature:
Callable[[store: ObjectStore, dn: str, modlist: AddModlist, bind_dn: str = None], None]
Where
store
is theObjectStore
object,dn
is the record to be created,modlist
is modlist to be used for creating the record, andbind_dn
is the dn of the user doing thecreate
(possiblyNone
).This will be executed on
ObjectStore.create
before the modlist gets processed.ObjectStore.create
is what actually does the work whenFakeLDAPObject.add_s
is called.post_create
Signature:
Callable[[store: ObjectStore, record: LDAPRecord, bind_dn: Optional[str] = None], None]
Where
store
is theObjectStore
object,record
is the record to be created, andbind_dn
is the dn of the user doing thecreate
(possiblyNone
).This will be executed on
ObjectStore.create
after the modlist has processed to build the object, but before it has been writen to the data store.pre_update
Signature:
Callable[[store: ObjectStore, dn: str, modlist: Modlist, bind_dn: str = None], None]
Where
store
is theObjectStore
object,dn
is the record to be modified`,modlist
is modlist to be applied to the record, andbind_dn
is the dn of the user doing theupdate
(possiblyNone
).This will be executed on
ObjectStore.update
before the object actually gets saved.ObjectStore.update
is what actually does the work whenFakeLDAPObject.modify_s
is called.post_update
Signature:
Callable[[store: ObjectStore, record: LDAPRecord, bind_dn: Optional[str] = None], None]
Where
store
is theObjectStore
object,record
is the updated record andbind_dn
is the dn of the user doing theupdate
(possiblyNone
)This will be executed on
ObjectStore.update
after the modlist has been applied to the object, but before it has been writen to the data store.pre_delete
Signature:
Callable[[store: ObjectStore, record: LDAPRecord, bind_dn: Optional[str] = None], None]
Where
store
is theObjectStore
object,record
is the record to deleted, andbind_dn
is the dn of the user doing theset
(possiblyNone
).This will be executed on
ObjectStore.delete
before the object actually gets deleted from the data store.ObjectStore.delete
is what actually does the work whenFakeLDAPObject.delete_s
is called, and is also called duringFakeLDAPObject.rename_s
to delete the old object.post_delete
Signature:
Callable[[store: ObjectStore, record: LDAPRecord, bind_dn: Optional[str] = None], None]
Where
store
is theObjectStore
object,record
is the record deleted, andbind_dn
is the dn of the user doing theset
(possiblyNone
).This will be executed on
ObjectStore.delete
after the object actually gets deleted from the data store.pre_register_object
Signature:
Callable[[store: ObjectStore, record: LDAPRecord], None]
Where
store
is theObjectStore
object andrecord
is the record to be registered.This will be executed on
ObjectStore.register_object
before the object actually gets saved.post_register_object
Signature:
Callable[[store: ObjectStore, record: LDAPRecord], None]
Where
store
is theObjectStore
object andrecord
is the record that was registered.This will be executed on
ObjectStore.register_object
after the object gets saved.pre_register_objects
Signature:
Callable[[store: ObjectStore, records: List[LDAPRecord]], None]
Where
store
is theObjectStore
object andrecords
is the list of records to be registered.This will be executed on
ObjectStore.register_objects
before the objects actually get saved.post_register_objects
Signature:
Callable[[store: ObjectStore, records: List[LDAPRecord]], None]
Where
store
is theObjectStore
object andrecords
are the records that were registered.This will be executed on
ObjectStore.register_objects
after the objects get saved.pre_load_objects
Signature:
Callable[[store: ObjectStore, filename: str], None]
Where
store
is theObjectStore
object andfilename
is the name of the data file to load.This will be executed on
ObjectStore.load_objects
before the file gets loaded.post_load_objects
Signature:
Callable[[store: ObjectStore, records: List[LDAPRecord]], None]
Where
store
is theObjectStore
object andrecords
are the records that were loaded from the file.This will be executed on
ObjectStore.load_objects
after the objects loaded from the file get saved.