Of course! Getting results from an LDAP query in Python is a fundamental task. The process involves connecting, searching, and then parsing the returned data.

The most common library for this is python-ldap. It's a robust, feature-rich library that provides a fairly direct mapping to the C LDAP API.
Here's a complete guide, from installation to a detailed explanation of the result object.
Installation
First, you need to install the library. It also requires the OpenLDAP development libraries to be installed on your system.
Using pip:

pip install python-ldap
System Dependencies (for Linux): You'll need to install the development headers for OpenLDAP.
- Debian / Ubuntu:
sudo apt-get update sudo apt-get install libldap2-dev libsasl2-dev
- Red Hat / CentOS / Fedora:
sudo yum install openldap-devel python3-devel
- macOS (using Homebrew):
brew install openldap
Basic LDAP Search and Result Parsing
Let's walk through a complete example. We'll perform a simple search and then explore the structure of the result.
Example Code
import ldap
import ldap.filter
# --- Configuration ---
# Use 'ldap://' for non-SSL, 'ldaps://' for SSL
SERVER_URI = 'ldap://ldap.forumsys.com'
# A public test LDAP server that allows anonymous binds
BASE_DN = 'dc=example,dc=com'
SEARCH_SCOPE = ldap.SCOPE_SUBTREE # or ldap.SCOPE_ONELEVEL, ldap.SCOPE_BASE
SEARCH_FILTER = '(objectClass=person)' # Search for all entries with objectClass 'person'
# --- 1. Establish Connection ---
# For a public server, we can use anonymous bind.
# For a real server, you would use: conn.simple_bind_s('cn=admin,dc=example,dc=com', 'your_password')
try:
conn = ldap.initialize(SERVER_URI)
conn.protocol_version = ldap.VERSION3
# Perform an anonymous bind
conn.simple_bind_s('', '')
print("Successfully connected to LDAP server.")
except ldap.LDAPError as e:
print(f"Error connecting to LDAP server: {e}")
exit()
# --- 2. Perform Search ---
try:
# The search method returns a tuple: (msgid, result_list)
# We use the synchronous version search_s() to get the results directly.
result_set = conn.search_s(
base=BASE_DN,
scope=SEARCH_SCOPE,
filterstr=SEARCH_FILTER
)
print(f"\nFound {len(result_set)} results.")
# --- 3. Parse the Result ---
for dn, entry in result_set:
print("-" * 40)
print(f"Distinguished Name (DN): {dn}")
print("Attributes:")
for attr_name, attr_values in entry.items():
# LDAP attributes can have multiple values (e.g., multiple email addresses)
print(f" - {attr_name}: {attr_values}")
except ldap.LDAPError as e:
print(f"Error during LDAP search: {e}")
finally:
# --- 4. Close the Connection ---
conn.unbind_s()
print("\nConnection closed.")
Running the Example Output
When you run the code above, you'll get output similar to this:
Successfully connected to LDAP server.
Found 5 results.
----------------------------------------
Distinguished Name (DN): uid=jwayne,dc=example,dc=com
Attributes:
- objectClass: ['top', 'person', 'organizationalPerson', 'inetOrgPerson']
- cn: ['John Wayne']
- sn: ['Wayne']
- telephoneNumber: ['+1 512 555 0199']
- mail: ['jwayne@example.com']
- userPassword: ['password']
- uid: ['jwayne']
----------------------------------------
Distinguished Name (DN): uid=jsmith,dc=example,dc=com
Attributes:
- objectClass: ['top', 'person', 'organizationalPerson', 'inetOrgPerson']
- cn: ['John Smith']
- sn: ['Smith']
- telephoneNumber: ['+1 512 555 0187']
- mail: ['jsmith@example.com']
- userPassword: ['password']
- uid: ['jsmith']
... (and so on for the other entries)
----------------------------------------
Connection closed.
Understanding the LDAP Result Object
The key to working with LDAP results in Python is understanding the structure returned by conn.search_s().

The function returns a list of tuples. Each tuple represents a single LDAP entry found by the search.
Structure: [(dn_1, entry_1), (dn_2, entry_2), ...]
Let's break down each part of the tuple:
Part 1: The Distinguished Name (DN)
The first element of the tuple is a string representing the entry's unique identifier in the directory tree.
dn, entry = result_set[0] print(dn) # Output: uid=jwayne,dc=example,dc=com
Part 2: The Entry (Attributes Dictionary)
The second element is a dictionary containing the entry's attributes. This is the most important part.
Structure: {attribute_name: [value1, value2, ...], ...}
Key characteristics of this dictionary:
- Keys: The keys are strings representing the attribute names (e.g.,
cn,mail,telephoneNumber). - Values: Crucially, the values are always lists. This is because LDAP attributes can have multiple values. Even if an attribute only has one value (like a person's
snoruid), it is returned as a single-element list.
dn, entry = result_set[0] # Accessing a single-valued attribute common_names = entry['cn'] print(common_names) # Output: ['John Wayne'] # You must access the first element of the list to get the actual string value first_cn = entry['cn'][0] print(first_cn) # Output: John Wayne # Accessing a multi-valued attribute (though 'cn' is usually single-valued) object_classes = entry['objectClass'] print(object_classes) # Output: ['top', 'person', 'organizationalPerson', 'inetOrgPerson']
Part 3: Handling Binary Data (e.g., JPEGPhoto)
Some attributes, like jpegPhoto or userCertificate, contain binary data, not text. The python-ldap library handles this gracefully.
If an attribute contains binary data, its values in the dictionary will be bytes objects instead of strings.
# Example search for an entry with a photo
# SEARCH_FILTER_PHOTO = '(uid=jkondo)'
# result_set_photo = conn.search_s(BASE_DN, SCOPE_SUBTREE, SEARCH_FILTER_PHOTO)
# dn, entry = result_set_photo[0]
# The 'jpegPhoto' attribute will contain bytes
# photo_data = entry['jpegPhoto'][0]
# print(type(photo_data))
# Output: <class 'bytes'>
# You can then write this to a file
# with open('user_photo.jpg', 'wb') as f:
# f.write(photo_data)
Common Tasks and Best Practices
Searching with Specific Attributes
By default, search_s() returns all attributes for an entry. To improve performance and get only what you need, use the attrlist parameter.
# Only request the 'cn' and 'mail' attributes
result_set_specific = conn.search_s(
base=BASE_DN,
scope=SEARCH_SCOPE,
filterstr=SEARCH_FILTER,
attrlist=['cn', 'mail']
)
for dn, entry in result_set_specific:
print(f"DN: {dn}, CN: {entry.get('cn', ['N/A'])[0]}, Mail: {entry.get('mail', ['N/A'])[0]}")
Note: Using entry.get(key, []) is safer than entry[key] because it returns an empty list if the attribute doesn't exist, preventing a KeyError.
Handling Search Errors
LDAP searches can fail for many reasons (invalid DN, insufficient permissions, malformed filter, etc.). Always wrap your search in a try...except block.
try:
# A malformed filter will cause an exception
conn.search_s(BASE_DN, SCOPE_SUBTREE, filterstr='(&(objectClass=person))')
except ldap.FILTER_ERROR as e:
print(f"LDAP Filter Error: The search filter was invalid. Details: {e}")
except ldap.INSUFFICIENT_ACCESS as e:
print(f"LDAP Access Error: You don't have permission to perform this search. Details: {e}")
except ldap.LDAPError as e:
print(f"A general LDAP error occurred: {e}")
Asynchronous Searches
For very large directories or to avoid blocking your application, you can use asynchronous searches.
msgid = conn.search(
base=BASE_DN,
scope=SEARCH_SCOPE,
filterstr=SEARCH_FILTER
)
print(f"Search request sent with message ID: {msgid}")
# Now, you can do other work...
# time.sleep(1)
# Retrieve the results when you're ready
try:
# ldap.RES_SEARCH_RESULT is the message type you're waiting for
result_type, result_data = conn.result(msgid, all=0)
if result_type == ldap.RES_SEARCH_RESULT:
print("Asynchronous search complete.")
for dn, entry in result_data:
print(f"Found entry: {dn}")
except ldap.LDAPError as e:
print(f"Error getting async results: {e}") 