anyldap.ldap package

anyldap.ldap is python-ldap’s ldap module with every operation awaited. The names, the arguments they take and the values they hand back are the ones python-ldap documents, so code ported from it reads the same with await in front:

from anyldap import ldap


async def example() -> None:
    async with ldap.initialize("ldap://ldap.example.com") as connection:
        await connection.simple_bind_s("cn=admin,dc=example,dc=com", "secret")
        results = await connection.search_s(
            "dc=example,dc=com", ldap.SCOPE_SUBTREE, "(cn=Babs*)"
        )
        for dn, attributes in results:
            print(dn, attributes)

initialize() opens nothing: the first operation that is awaited connects, which is what lets it stay the plain call it is in python-ldap. The connection is closed by unbind_s(), and async with closes it however the block ends.

Nothing runs in the background. As in python-ldap, an operation is sent when it is started and the connection is read while a result is being waited for, so a connection needs no task group and belongs to whichever task holds it. Operations can be started with search_ext(), add() and the rest, and collected later by message id with result3(); result(msgid, all=ldap.MSG_ONE) walks a large search one entry at a time.

Binding with SASL

sasl_interactive_bind_s() drives the exchange, asking the mechanism what to send for as long as the server says the bind is still in progress:

from anyldap import ldap

async with ldap.initialize("ldapi:///run/slapd/ldapi") as connection:
    await connection.sasl_external_bind_s()
    print(await connection.whoami_s())

The mechanisms in anyldap.ldap.sasl answer for themselves rather than through Cyrus SASL: external, plain, cram_md5, digest_md5, and gssapi if the gssapi package is installed, which is not a dependency. A mechanism of your own works anywhere these do, as long as it has a mech and a process() that answers a challenge.

Controls

Controls are the objects anyldap.ldap.controls builds, encoded with the BER library anyldap already has rather than with pyasn1, and a response hands back the ones it carried, read into the classes that know them:

from anyldap.ldap.controls import SimplePagedResultsControl

paged = SimplePagedResultsControl(True, size=100, cookie=b"")
while True:
    msgid = await connection.search_ext(base, scope, serverctrls=[paged])
    rtype, data, rmsgid, answered = await connection.result3(msgid)
    ...
    cookies = [
        control.cookie
        for control in answered
        if control.controlType == ldap.CONTROL_PAGEDRESULTS
    ]
    if not cookies or not cookies[0]:
        break
    paged.cookie = cookies[0]

The controls with a class of their own are paged results, pre-read and post-read, server-side sorting, the password policy response, OpenLDAP’s no-op search, assertion and matched-values, ManageDSAIT, relax rules, proxied authorization and the authorization identity pair; each lives in the module python-ldap keeps it in, and the names are re-exported from anyldap.ldap.controls as python-ldap re-exports them. Anything else is an LDAPControl carrying the bytes as they came, and a control of your own only has to encode and decode its own value. A (type, criticality, value) triple is still accepted wherever controls are.

Schema

anyldap.ldap.schema reads what a server publishes about itself into the model classes python-ldap names, on top of the schema parsing in anyldap.schema:

subschema = await connection.read_schema_s()
person = subschema.get_obj(ldap.schema.ObjectClass, "person")
must, may = subschema.attribute_types(["inetOrgPerson"])

Every kind of definition is read: object classes, attribute types, matching rules and their uses, syntaxes, content rules, structure rules and name forms. A definition writes itself back out again – str(person) is the objectClasses value the server published – and x_origin and the rest of the X- fields a definition carries are read into extensions.

Entry is an entry that knows the schema its attributes are described by, so entry["cn"], entry["commonName"] and entry["2.5.4.3"] are the same attribute.

urlfetch() is python-ldap’s, taking an LDAP URL and opening a connection of its own to ask; read_schema_s() is not a method python-ldap has, and uses the connection that is already open:

dn, subschema = await ldap.schema.urlfetch("ldap://localhost")

It takes the address of an LDIF file as well, and reads the schema out of its first record – which is how a schema someone else published is read without a server to ask:

dn, subschema = await ldap.schema.urlfetch("file:///tmp/subschema.ldif")

Only file:, http: and https: are read, and anything else is refused. python-ldap hands the address to urllib.request.urlopen(), which fetches whatever scheme it happens to support – so an address that was meant to name a file can turn out to be a request, and one that was meant to be a request can turn out to read a file. An http: address is fetched with httpx2. The same two rules apply to the URLs anyldap.ldap.ldif fetches when it is told to fetch any.

A definition may name itself rather than be numbered (( nsEncryptionConfig-oid NAME 'nsEncryptionConfig' ... )), which RFC 4512 section 1.4 allows and 389-ds publishes a great many of.

TLS

The OPT_X_TLS_* options build the ssl.SSLContext a connection is raised with – the certificates to trust, the certificate to send, what to check, which protocol versions and ciphers – and OPT_X_TLS_NEWCTX starts a new one. A context can be passed to initialize() instead, and then it is used as it stands. ldaps:// raises TLS before anything is sent; start_tls_s() raises it on a connection that is already open.

URLs

anyldap.ldap.ldapurl is python-ldap’s top-level ldapurl module: an LDAP URL taken apart into what it says, and written back out again:

from anyldap.ldap.ldapurl import LDAPUrl

url = LDAPUrl("ldap://localhost/dc=example,dc=com?cn?sub?(cn=jack)")
async with ldap.initialize(url.initializeUrl()) as connection:
    found = await connection.search_s(url.dn, url.scope, url.filterstr)

who and cred are the bind DN and its password, which a URL carries as the bindname and X-BINDPW extensions.

LDIF

anyldap.ldap.ldif is python-ldap’s top-level ldif module. anyldap has an LDIF reader of its own in anyldap.protocols.ldap.ldifprotocol, but it is a line-receiving protocol answering with the objects the rest of anyldap is built out of, which is not what code written against python-ldap asks for. Nothing in it touches the network, so nothing in it is awaited:

from anyldap.ldap import ldif

records = ldif.LDIFRecordList(open("people.ldif"))
records.parse()
for dn, entry in records.all_records:
    await connection.add_s(dn, ldap.modlist.addModlist(entry))

LDIFWriter writes entry records and change records, folding long lines and base64-encoding whatever RFC 2849 does not allow to be written as it stands; LDIFParser reads them, and a class of your own overrides handle() or handle_modify(). LDIFRecordList collects them all, and LDIFCopy reads and writes at once. CreateLDIF() and ParseLDIF(), which python-ldap has deprecated, are not here.

A value an LDIF names by URL (jpegPhoto:< file:///…) is fetched only when process_url_schemes says which schemes to fetch, and is left unread otherwise – which is what python-ldap does too. Of the schemes that can be asked for, file, http and https are the ones that are read.

Following referrals

A server that does not hold what was asked for can say where to look instead, and unless OPT_REFERRALS is turned off the operation is made again there, which is what libldap does and so what python-ldap inherits:

# On by default, and read back as -1 however it was set, as libldap
# keeps it.
connection.set_option(ldap.OPT_REFERRALS, 0)

A referral is followed anonymously: it says where to look and nothing about whose credentials may be sent there. For the same reason a bind is never followed – the referral it is answered with is raised instead. A search continuation is followed too, and what the other server holds is added to what the search found; the continuation itself stays among the results as (None, [uri, ...]), which is what python-ldap hands back whether or not it followed one.

When no server a referral names can be reached, the referral is raised as REFERRAL, carrying the URLs as its info and how far the name was recognised as its matched. Referrals are followed five deep, after which one is called a loop and REFERRAL_LIMIT_EXCEEDED is raised. That is libldap’s own limit; like libldap, this does not let it be set, so asking for OPT_REFHOPLIMIT is an error.

Reconnecting

ReconnectLDAPObject tries an operation again on a connection that went away, opening it and putting it back as it was: the options that were set, StartTLS if it had been raised, and the last bind that was made:

connection = ldap.ReconnectLDAPObject(uri, retry_max=3, retry_delay=5.0)
await connection.simple_bind_s(who, cred)
# Whatever happens to the server in between, this is answered.
entries = await connection.search_ext_s(base, ldap.SCOPE_SUBTREE)

Unlike python-ldap’s, it does not open the connection before the first operation: nothing is sent until something is awaited, so there is nothing to reconnect until an operation has failed.

Keeping a copy

anyldap.ldap.syncrepl is RFC 4533, which is how a copy of what a server holds is kept up to date. A connection mixes in SyncreplConsumer, overrides the syncrepl_* methods to keep whatever it is keeping, and polls:

class Consumer(ldap.syncrepl.SyncreplConsumer, ldap.SimpleLDAPObject):
    def syncrepl_entry(self, dn, attrs, uuid):
        self.entries[uuid] = (dn, attrs)

msgid = await connection.syncrepl_search(base, ldap.SCOPE_SUBTREE)
while await connection.syncrepl_poll(msgid=msgid):
    pass

refreshOnly stops once the copy has caught up and refreshAndPersist stays open and keeps being told. cancel_s() stops a search that is staying open, and unlike abandon() the server answers to say it has.

Extended operations

anyldap.ldap.extop is what an extended operation of your own is built out of: a request that knows its OID and writes its value, and a response that reads one:

from anyldap.ldap.extop.dds import RefreshRequest, RefreshResponse

request = RefreshRequest(entryName=dn, requestTtl=3600)
name, value = await connection.extop_s(
    pureldap.LDAPExtendedRequest(
        requestName=request.requestName,
        requestValue=request.encodedRequestValue(),
    )
)
left = RefreshResponse(name, value).responseTtl

extop.dds is RFC 2589, entries that go away by themselves, and extop.passwd reads the password a server made up when it was asked to change one without being given a new one.

What is not here

  • GSSAPI without the ``gssapi`` package: the mechanism is anyldap.ldap.sasl.gssapi, and it says so if it is asked for without Kerberos to hand. The SASL security layer it negotiates is always none: what protects a connection here is TLS.

  • The ``OPT_X_SASL_*`` options beyond what they say: they supply the defaults a mechanism reads and report what the bind ended up with, rather than configuring Cyrus SASL, which is not what does the exchange here.

  • A referral that points at itself stops rather than being followed until something else gives up. libldap keeps going, and what ends it there is the timeout rather than the hop limit.

abandon() sends a real abandon request and forgets the operation, but the server may already have answered.

anyldap.ldap module

python-ldap’s ldap module, with every operation awaited.

The names, arguments and return values are the ones python-ldap documents:

from anyldap import ldap

async def example() -> None:
    async with ldap.initialize("ldap://ldap.example.com") as connection:
        await connection.simple_bind_s("cn=admin,dc=example,dc=com", "secret")
        for dn, attributes in await connection.search_s(
            "dc=example,dc=com", ldap.SCOPE_SUBTREE, "(cn=Babs*)"
        ):
            print(dn, attributes)

ldap.controls, ldap.schema, ldap.sasl, ldap.dn, ldap.filter, ldap.modlist and ldap.cidict are here too, and so are the TLS options: setting OPT_X_TLS_CACERTFILE and its neighbours builds the ssl.SSLContext the connection is raised with, and one can be passed to initialize() instead.

SASL binds are made with the mechanisms in anyldap.ldap.sasl, which answer for themselves rather than through Cyrus SASL: EXTERNAL, PLAIN, CRAM-MD5, DIGEST-MD5, and GSSAPI if the gssapi package is installed. A mechanism of your own works if it answers process() the same way.

ldap.asyncsearch reads a long search result by result, ldap.syncrepl keeps a copy of what a server holds, and ReconnectLDAPObject opens a connection again when the server goes away and tries the operation once more.

exception anyldap.ldap.ADMINLIMIT_EXCEEDED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Administrative limit exceeded'
errnum: ClassVar[int | None] = 11
anyldap.ldap.ADMIN_LIMIT_EXCEEDED

alias of ADMINLIMIT_EXCEEDED

exception anyldap.ldap.AFFECTS_MULTIPLE_DSAS[source]

Bases: LDAPError

desc: ClassVar[str] = 'Operation affects multiple DSAs'
errnum: ClassVar[int | None] = 71
exception anyldap.ldap.ALIAS_DEREF_PROBLEM[source]

Bases: LDAPError

desc: ClassVar[str] = 'Alias dereferencing problem'
errnum: ClassVar[int | None] = 36
exception anyldap.ldap.ALIAS_PROBLEM[source]

Bases: LDAPError

desc: ClassVar[str] = 'Alias problem'
errnum: ClassVar[int | None] = 33
exception anyldap.ldap.ALREADY_EXISTS[source]

Bases: LDAPError

desc: ClassVar[str] = 'Already exists'
errnum: ClassVar[int | None] = 68
exception anyldap.ldap.ASSERTION_FAILED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Assertion failed'
errnum: ClassVar[int | None] = 122
exception anyldap.ldap.AUTH_METHOD_NOT_SUPPORTED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Authentication method not supported'
errnum: ClassVar[int | None] = 7
exception anyldap.ldap.AUTH_UNKNOWN[source]

Bases: LDAPError

desc: ClassVar[str] = 'Unknown authentication method'
errnum: ClassVar[int | None] = -6
exception anyldap.ldap.BUSY[source]

Bases: LDAPError

desc: ClassVar[str] = 'Server is busy'
errnum: ClassVar[int | None] = 51
exception anyldap.ldap.CANCELLED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Cancelled'
errnum: ClassVar[int | None] = 118
exception anyldap.ldap.CANNOT_CANCEL[source]

Bases: LDAPError

desc: ClassVar[str] = 'Cannot cancel'
errnum: ClassVar[int | None] = 121
exception anyldap.ldap.CLIENT_LOOP[source]

Bases: LDAPError

desc: ClassVar[str] = 'Client loop'
errnum: ClassVar[int | None] = -16
exception anyldap.ldap.COMPARE_FALSE[source]

Bases: LDAPError

A compare that answered no; compare_s() turns this into False.

desc: ClassVar[str] = 'Compare False'
errnum: ClassVar[int | None] = 5
exception anyldap.ldap.COMPARE_TRUE[source]

Bases: LDAPError

A compare that answered yes; compare_s() turns this into True.

desc: ClassVar[str] = 'Compare True'
errnum: ClassVar[int | None] = 6
exception anyldap.ldap.CONFIDENTIALITY_REQUIRED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Confidentiality required'
errnum: ClassVar[int | None] = 13
exception anyldap.ldap.CONNECT_ERROR[source]

Bases: LDAPError

desc: ClassVar[str] = 'Connect error'
errnum: ClassVar[int | None] = -11
exception anyldap.ldap.CONSTRAINT_VIOLATION[source]

Bases: LDAPError

desc: ClassVar[str] = 'Constraint violation'
errnum: ClassVar[int | None] = 19
exception anyldap.ldap.CONTROL_NOT_FOUND[source]

Bases: LDAPError

desc: ClassVar[str] = 'Control not found'
errnum: ClassVar[int | None] = -13
exception anyldap.ldap.DECODING_ERROR[source]

Bases: LDAPError

desc: ClassVar[str] = 'Decoding error'
errnum: ClassVar[int | None] = -4
exception anyldap.ldap.ENCODING_ERROR[source]

Bases: LDAPError

desc: ClassVar[str] = 'Encoding error'
errnum: ClassVar[int | None] = -3
exception anyldap.ldap.FILTER_ERROR[source]

Bases: LDAPError

The search filter could not be parsed.

desc: ClassVar[str] = 'Bad search filter'
errnum: ClassVar[int | None] = -7
exception anyldap.ldap.INAPPROPRIATE_AUTH[source]

Bases: LDAPError

desc: ClassVar[str] = 'Inappropriate authentication'
errnum: ClassVar[int | None] = 48
exception anyldap.ldap.INAPPROPRIATE_MATCHING[source]

Bases: LDAPError

desc: ClassVar[str] = 'Inappropriate matching'
errnum: ClassVar[int | None] = 18
exception anyldap.ldap.INSUFFICIENT_ACCESS[source]

Bases: LDAPError

desc: ClassVar[str] = 'Insufficient access'
errnum: ClassVar[int | None] = 50
exception anyldap.ldap.INVALID_CREDENTIALS[source]

Bases: LDAPError

desc: ClassVar[str] = 'Invalid credentials'
errnum: ClassVar[int | None] = 49
exception anyldap.ldap.INVALID_DN_SYNTAX[source]

Bases: LDAPError

desc: ClassVar[str] = 'Invalid DN syntax'
errnum: ClassVar[int | None] = 34
exception anyldap.ldap.INVALID_SYNTAX[source]

Bases: LDAPError

desc: ClassVar[str] = 'Invalid syntax'
errnum: ClassVar[int | None] = 21
exception anyldap.ldap.IS_LEAF[source]

Bases: LDAPError

desc: ClassVar[str] = 'Is a leaf'
errnum: ClassVar[int | None] = 35
exception anyldap.ldap.LDAPError[source]

Bases: Exception

The base of every error raised here, as ldap.LDAPError is.

desc: ClassVar[str] = 'Unknown error'
errnum: ClassVar[int | None] = None
property info: str | None

The diagnostic message the server sent, if it sent one.

anyldap.ldap.LDAPObject

alias of SimpleLDAPObject

exception anyldap.ldap.LOCAL_ERROR[source]

Bases: LDAPError

desc: ClassVar[str] = 'Local error'
errnum: ClassVar[int | None] = -2
exception anyldap.ldap.LOOP_DETECT[source]

Bases: LDAPError

desc: ClassVar[str] = 'Loop detected'
errnum: ClassVar[int | None] = 54
exception anyldap.ldap.MORE_RESULTS_TO_RETURN[source]

Bases: LDAPError

desc: ClassVar[str] = 'More results to return'
errnum: ClassVar[int | None] = -15
exception anyldap.ldap.NAMING_VIOLATION[source]

Bases: LDAPError

desc: ClassVar[str] = 'Naming violation'
errnum: ClassVar[int | None] = 64
exception anyldap.ldap.NOT_ALLOWED_ON_NONLEAF[source]

Bases: LDAPError

desc: ClassVar[str] = 'Operation not allowed on non-leaf'
errnum: ClassVar[int | None] = 66
exception anyldap.ldap.NOT_ALLOWED_ON_RDN[source]

Bases: LDAPError

desc: ClassVar[str] = 'Operation not allowed on RDN'
errnum: ClassVar[int | None] = 67
exception anyldap.ldap.NOT_SUPPORTED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Not supported'
errnum: ClassVar[int | None] = -12
exception anyldap.ldap.NO_MEMORY[source]

Bases: LDAPError

desc: ClassVar[str] = 'Out of memory'
errnum: ClassVar[int | None] = -10
exception anyldap.ldap.NO_OBJECT_CLASS_MODS[source]

Bases: LDAPError

desc: ClassVar[str] = 'Cannot modify object class'
errnum: ClassVar[int | None] = 69
exception anyldap.ldap.NO_RESULTS_RETURNED[source]

Bases: LDAPError

desc: ClassVar[str] = 'No results returned'
errnum: ClassVar[int | None] = -14
exception anyldap.ldap.NO_SUCH_ATTRIBUTE[source]

Bases: LDAPError

desc: ClassVar[str] = 'No such attribute'
errnum: ClassVar[int | None] = 16
exception anyldap.ldap.NO_SUCH_OBJECT[source]

Bases: LDAPError

desc: ClassVar[str] = 'No such object'
errnum: ClassVar[int | None] = 32
exception anyldap.ldap.NO_SUCH_OPERATION[source]

Bases: LDAPError

desc: ClassVar[str] = 'No such operation'
errnum: ClassVar[int | None] = 119
exception anyldap.ldap.NO_UNIQUE_ENTRY[source]

Bases: NO_SUCH_OBJECT

A read expecting one entry found none, or several.

Not a result code a server sends: it is what this raises when a read that must find exactly one entry did not, which is what python-ldap raises it for too.

desc: ClassVar[str] = 'No or non-unique search result'
exception anyldap.ldap.OBJECT_CLASS_VIOLATION[source]

Bases: LDAPError

desc: ClassVar[str] = 'Object class violation'
errnum: ClassVar[int | None] = 65
exception anyldap.ldap.OPERATIONS_ERROR[source]

Bases: LDAPError

desc: ClassVar[str] = 'Operations error'
errnum: ClassVar[int | None] = 1
exception anyldap.ldap.OTHER[source]

Bases: LDAPError

desc: ClassVar[str] = 'Other'
errnum: ClassVar[int | None] = 80
exception anyldap.ldap.PARAM_ERROR[source]

Bases: LDAPError

desc: ClassVar[str] = 'Bad parameter to an ldap routine'
errnum: ClassVar[int | None] = -9
exception anyldap.ldap.PARTIAL_RESULTS[source]

Bases: LDAPError

desc: ClassVar[str] = 'Partial results and referral received'
errnum: ClassVar[int | None] = 9
exception anyldap.ldap.PROTOCOL_ERROR[source]

Bases: LDAPError

desc: ClassVar[str] = 'Protocol error'
errnum: ClassVar[int | None] = 2
exception anyldap.ldap.PROXIED_AUTHORIZATION_DENIED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Proxied authorization denied'
errnum: ClassVar[int | None] = 123
exception anyldap.ldap.REFERRAL[source]

Bases: LDAPError

desc: ClassVar[str] = 'Referral'
errnum: ClassVar[int | None] = 10
exception anyldap.ldap.REFERRAL_LIMIT_EXCEEDED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Referral limit exceeded'
errnum: ClassVar[int | None] = -17
exception anyldap.ldap.RESULTS_TOO_LARGE[source]

Bases: LDAPError

desc: ClassVar[str] = 'Results too large'
errnum: ClassVar[int | None] = 70
class anyldap.ldap.ReconnectLDAPObject(uri: str, trace_level: int = 0, *, ssl_context: SSLContext | None = None, retry_max: int = 1, retry_delay: float = 60.0)[source]

Bases: SimpleLDAPObject

A connection that opens itself again when the server goes away.

Each operation is tried, and if it fails with one of the errors that mean the connection rather than the request – SERVER_DOWN, UNAVAILABLE, CONNECT_ERROR or TIMEOUT – the connection is made again and the operation tried once more. Reconnecting replays what was done to the connection before: the options that were set, StartTLS if it was raised, and the last bind that was made.

retry_max is how many times to try opening it again before giving up, and retry_delay how long to wait between tries.

Unlike python-ldap’s, this does not open the connection before the first operation: nothing is sent until something is awaited, so there is nothing to reconnect until an operation has failed.

async add_ext_s(*args: Any, **kwargs: Any) → Any[source]
async bind_s(*args: Any, **kwargs: Any) → Any[source]
async cancel_s(*args: Any, **kwargs: Any) → Any[source]
async compare_ext_s(*args: Any, **kwargs: Any) → Any[source]
async delete_ext_s(*args: Any, **kwargs: Any) → Any[source]
async extop_s(*args: Any, **kwargs: Any) → Any[source]

Send an extended operation and wait for what it answers.

Given extop_resp_class, the answer is read into one of those rather than handed back as the name and the bytes; a server that answers to a different name than the class expects is refused.

async modify_ext_s(*args: Any, **kwargs: Any) → Any[source]
async passwd_s(*args: Any, **kwargs: Any) → Any[source]
async reconnect(uri: str, retry_max: int = 1, retry_delay: float = 60.0, force: bool = True) → None[source]

Open the connection again, and put it back as it was.

async rename_s(*args: Any, **kwargs: Any) → Any[source]
async sasl_bind_s(*args: Any, **kwargs: Any) → Any[source]

One step of a SASL bind, for callers driving the exchange itself.

Answers with the credentials the server sent back, which is what the next step is built from.

async sasl_interactive_bind_s(*args: Any, **kwargs: Any) → Any[source]

Bind with a SASL mechanism, answering the server until it is done.

The mechanism is asked what to send, and asked again with whatever the server sent back, for as long as the server says the bind is still in progress. What the OPT_X_SASL_* options say fills in whatever the mechanism was not told directly.

async search_ext_s(*args: Any, **kwargs: Any) → Any[source]
set_option(option: int, invalue: object) → None[source]
async simple_bind_s(*args: Any, **kwargs: Any) → Any[source]
async start_tls_s(*args: Any, **kwargs: Any) → Any[source]

Raise TLS on the connection, as StartTLS does.

Nothing else is reading the connection, so the handshake follows the response on the same stream, with no request in flight to confuse.

async whoami_s(*args: Any, **kwargs: Any) → str[source]

The identity the server has this connection bound as.

exception anyldap.ldap.SASL_BIND_IN_PROGRESS[source]

Bases: LDAPError

desc: ClassVar[str] = 'SASL bind in progress'
errnum: ClassVar[int | None] = 14
exception anyldap.ldap.SERVER_DOWN[source]

Bases: LDAPError

The connection could not be opened, or was lost.

desc: ClassVar[str] = "Can't contact LDAP server"
errnum: ClassVar[int | None] = -1
exception anyldap.ldap.SIZELIMIT_EXCEEDED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Size limit exceeded'
errnum: ClassVar[int | None] = 4
anyldap.ldap.STRONG_AUTH_NOT_SUPPORTED

alias of AUTH_METHOD_NOT_SUPPORTED

exception anyldap.ldap.STRONG_AUTH_REQUIRED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Strong(er) authentication required'
errnum: ClassVar[int | None] = 8
exception anyldap.ldap.SUCCESS[source]

Bases: LDAPError

The code that is not an error; nothing here is raised with it.

desc: ClassVar[str] = 'Success'
errnum: ClassVar[int | None] = 0
class anyldap.ldap.SimpleLDAPObject(uri: str, trace_level: int = 0, *, ssl_context: SSLContext | None = None)[source]

Bases: object

A connection to one LDAP server, with the API of python-ldap’s own.

The connection is opened by the first operation that needs it, so that initialize() stays the plain call python-ldap makes it. Closing it is unbind_s(), and async with closes it however the block ends.

CLASSATTR_OPTION_MAPPING: ClassVar[dict[str, int]] = {'deref': 2, 'network_timeout': 20485, 'protocol_version': 17, 'referrals': 8, 'sizelimit': 3, 'timelimit': 4}
async abandon(msgid: int, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → None[source]
async abandon_ext(msgid: int, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → None[source]

Tell the server to stop working on an operation, and forget it.

Nothing is answered: an abandon is not a request the server replies to, which is what cancel() is for.

async add(dn: str, modlist: Sequence[tuple[str, Sequence[str | bytes] | str | bytes]]) → int[source]
async add_ext(dn: str, modlist: Sequence[tuple[str, Sequence[str | bytes] | str | bytes]], serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → int[source]
async add_ext_s(dn: str, modlist: Sequence[tuple[str, Sequence[str | bytes] | str | bytes]], serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]
async add_s(dn: str, modlist: Sequence[tuple[str, Sequence[str | bytes] | str | bytes]]) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]
async bind(who: str, cred: str | bytes, method: int = 128) → int[source]
async bind_s(who: str, cred: str | bytes, method: int = 128) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]
async cancel(cancelid: int, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → int[source]

Ask the server to stop working on an operation (RFC 3909).

Unlike abandon() this is answered, so the caller learns whether the operation really did stop; the operation itself finishes with CANCELLED.

async cancel_s(cancelid: int, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]]] | None[source]
async compare(dn: str, attr: str, value: str | bytes) → int[source]
async compare_ext(dn: str, attr: str, value: str | bytes, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → int[source]
async compare_ext_s(dn: str, attr: str, value: str | bytes, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → bool[source]
async compare_s(dn: str, attr: str, value: str | bytes) → bool[source]
async delete(dn: str) → int[source]
async delete_ext(dn: str, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → int[source]
async delete_ext_s(dn: str, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]
async delete_s(dn: str) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]
async extop(extreq: LDAPExtendedRequest, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → int[source]
async extop_result(msgid: int = -1, all: int = 1, timeout: float | None = None) → tuple[str | None, bytes | None][source]

Collect an extended operation started with extop().

async extop_s(extreq: LDAPExtendedRequest, serverctrls: Iterable[controls.RequestControl | pureldap.Control] | None = None, clientctrls: Iterable[controls.RequestControl | pureldap.Control] | None = None, extop_resp_class: type[ExtendedResponse] | None = None) → tuple[str | None, bytes | None] | ExtendedResponse[source]

Send an extended operation and wait for what it answers.

Given extop_resp_class, the answer is read into one of those rather than handed back as the name and the bytes; a server that answers to a different name than the class expects is refused.

fileno() → int[source]

The socket this connection is open on.

For a caller that wants to watch the connection with something of its own. It raises if nothing is open yet, because there is no socket to name until the first operation has been awaited.

async find_unique_entry(base: str, scope: int = 2, filterstr: str = '(objectClass=*)', attrlist: Sequence[str] | None = None, attrsonly: int = 0, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, timeout: float = -1) → tuple[str, dict[str, list[bytes]]][source]

The one entry a search found, or an error saying it was not one.

The search asks for two entries, so a server that has more says so rather than sending them all.

async get_naming_contexts() → list[bytes][source]

The naming contexts the server holds.

get_option(option: int) → object[source]
async modify(dn: str, modlist: Sequence[tuple[int, str, Sequence[str | bytes] | str | bytes | None]]) → int[source]
async modify_ext(dn: str, modlist: Sequence[tuple[int, str, Sequence[str | bytes] | str | bytes | None]], serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → int[source]
async modify_ext_s(dn: str, modlist: Sequence[tuple[int, str, Sequence[str | bytes] | str | bytes | None]], serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]
async modify_s(dn: str, modlist: Sequence[tuple[int, str, Sequence[str | bytes] | str | bytes | None]]) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]
async modrdn(dn: str, newrdn: str, delold: int = 1) → int[source]
async modrdn_s(dn: str, newrdn: str, delold: int = 1) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]
async passwd(user: str | bytes | None = None, oldpw: str | bytes | None = None, newpw: str | bytes | None = None, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → int[source]
async passwd_s(user: str | bytes | None = None, oldpw: str | bytes | None = None, newpw: str | bytes | None = None, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, extract_newpw: bool = False) → tuple[str | None, bytes | None][source]
async read_rootdse_s(filterstr: str = '(objectClass=*)', attrlist: Sequence[str] | None = None) → dict[str, list[bytes]][source]

What the server says about itself, from its root DSE.

async read_s(dn: str, filterstr: str = '(objectClass=*)', attrlist: Sequence[str] | None = None, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, timeout: float = -1) → dict[str, list[bytes]] | None[source]

The attributes of one entry, or None when it is not there.

async read_schema_s(dn: str = '') → SubSchema[source]

The schema an entry is written against, read into objects.

Not a method python-ldap has: it fetches schema with ldap.schema.urlfetch(), which opens its own connection. This uses the connection that is already here.

async read_subschemasubentry_s(subschemasubentry_dn: str, attrs: Sequence[str] | None = None) → dict[str, list[bytes]] | None[source]

The schema itself, as the server publishes it.

async rename(dn: str, newrdn: str, newsuperior: str | None = None, delold: int = 1, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → int[source]
async rename_s(dn: str, newrdn: str, newsuperior: str | None = None, delold: int = 1, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]
async result(msgid: int = -1, all: int = 1, timeout: float | None = None) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]]][source]
async result2(msgid: int = -1, all: int = 1, timeout: float | None = None) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int][source]
async result3(msgid: int = -1, all: int = 1, timeout: float | None = None, resp_ctrl_classes: Mapping[str, type[ResponseControl]] | None = None) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]

Wait for an operation started earlier, and answer with its result.

RES_ANY takes the operation that was started first, which is the one python-ldap would most likely hand back.

async result4(msgid: int = -1, all: int = 1, timeout: float | None = None, add_ctrls: int = 0, add_intermediates: int = 0, add_extop: int = 0, resp_ctrl_classes: Mapping[str, type[ResponseControl]] | None = None) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]] | tuple[str | None, bytes | None] | tuple[str, dict[str, list[bytes]], Sequence[ResponseControl]] | tuple[None, list[str], Sequence[ResponseControl]] | tuple[str | None, bytes | None, Sequence[ResponseControl]]], int, Sequence[ResponseControl], str | None, bytes | None][source]
async sasl_bind_s(dn: str, mechanism: str | bytes, cred: str | bytes | None, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → bytes | None[source]

One step of a SASL bind, for callers driving the exchange itself.

Answers with the credentials the server sent back, which is what the next step is built from.

async sasl_external_bind_s(serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, sasl_flags: int = 2, authz_id: str = '') → None[source]

Bind as whoever the connection underneath already proved to be.

async sasl_gssapi_bind_s(serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, sasl_flags: int = 2, authz_id: str = '') → None[source]

Bind with Kerberos, which is what the GSSAPI mechanism is.

async sasl_interactive_bind_s(who: str, auth: sasl, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, sasl_flags: int = 2) → None[source]

Bind with a SASL mechanism, answering the server until it is done.

The mechanism is asked what to send, and asked again with whatever the server sent back, for as long as the server says the bind is still in progress. What the OPT_X_SASL_* options say fills in whatever the mechanism was not told directly.

async sasl_non_interactive_bind_s(sasl_mech: str, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, sasl_flags: int = 2, authz_id: str = '') → None[source]

A SASL bind with a mechanism that asks the caller nothing.

async search(base: str, scope: int = 2, filterstr: str = '(objectClass=*)', attrlist: Sequence[str] | None = None, attrsonly: int = 0) → int[source]
async search_ext(base: str, scope: int = 2, filterstr: str = '(objectClass=*)', attrlist: Sequence[str] | None = None, attrsonly: int = 0, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, timeout: float = -1, sizelimit: int = 0) → int[source]
async search_ext_s(base: str, scope: int = 2, filterstr: str = '(objectClass=*)', attrlist: Sequence[str] | None = None, attrsonly: int = 0, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, timeout: float = -1, sizelimit: int = 0) → list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]][source]
async search_s(base: str, scope: int = 2, filterstr: str = '(objectClass=*)', attrlist: Sequence[str] | None = None, attrsonly: int = 0) → list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]][source]
async search_st(base: str, scope: int = 2, filterstr: str = '(objectClass=*)', attrlist: Sequence[str] | None = None, attrsonly: int = 0, timeout: float = -1) → list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]][source]
async search_subschemasubentry_s(dn: str = '') → str | None[source]

Where the server keeps the schema an entry is written against.

set_option(option: int, invalue: object) → None[source]
async simple_bind(who: str = '', cred: str | bytes = '', serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → int[source]
async simple_bind_s(who: str = '', cred: str | bytes = '', serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]
async start_tls_s() → None[source]

Raise TLS on the connection, as StartTLS does.

Nothing else is reading the connection, so the handshake follows the response on the same stream, with no request in flight to confuse.

async unbind(serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → None

Say goodbye to the server and close the connection.

async unbind_ext(serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → None[source]

Say goodbye to the server and close the connection.

async unbind_ext_s(serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → None

Say goodbye to the server and close the connection.

async unbind_s(serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → None

Say goodbye to the server and close the connection.

async whoami_s(serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → str[source]

The identity the server has this connection bound as.

exception anyldap.ldap.TIMELIMIT_EXCEEDED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Time limit exceeded'
errnum: ClassVar[int | None] = 3
exception anyldap.ldap.TIMEOUT[source]

Bases: LDAPError

The client gave up waiting before the server answered.

desc: ClassVar[str] = 'Timed out'
errnum: ClassVar[int | None] = -5
exception anyldap.ldap.TOO_LATE[source]

Bases: LDAPError

desc: ClassVar[str] = 'Too late'
errnum: ClassVar[int | None] = 120
exception anyldap.ldap.TYPE_OR_VALUE_EXISTS[source]

Bases: LDAPError

desc: ClassVar[str] = 'Type or value exists'
errnum: ClassVar[int | None] = 20
exception anyldap.ldap.UNAVAILABLE[source]

Bases: LDAPError

desc: ClassVar[str] = 'Server is unavailable'
errnum: ClassVar[int | None] = 52
exception anyldap.ldap.UNAVAILABLE_CRITICAL_EXTENSION[source]

Bases: LDAPError

desc: ClassVar[str] = 'Critical extension is unavailable'
errnum: ClassVar[int | None] = 12
exception anyldap.ldap.UNDEFINED_TYPE[source]

Bases: LDAPError

desc: ClassVar[str] = 'Undefined attribute type'
errnum: ClassVar[int | None] = 17
exception anyldap.ldap.UNWILLING_TO_PERFORM[source]

Bases: LDAPError

desc: ClassVar[str] = 'Server is unwilling to perform'
errnum: ClassVar[int | None] = 53
exception anyldap.ldap.USER_CANCELLED[source]

Bases: LDAPError

desc: ClassVar[str] = 'User cancelled operation'
errnum: ClassVar[int | None] = -8
exception anyldap.ldap.VLV_ERROR[source]

Bases: LDAPError

desc: ClassVar[str] = 'Virtual List View error'
errnum: ClassVar[int | None] = 76
exception anyldap.ldap.X_PROXY_AUTHZ_FAILURE[source]

Bases: LDAPError

desc: ClassVar[str] = 'Proxy authorization failure'
errnum: ClassVar[int | None] = 47
anyldap.ldap.addModlist(entry: Mapping[str, Sequence[str | bytes | None] | str | bytes | None], ignore_attr_types: Iterable[str] = ()) → Sequence[tuple[str, Sequence[str | bytes] | str | bytes]][source]

The modlist that adds an entry, skipping attributes with no values.

anyldap.ldap.dn2str(dn: Sequence[Sequence[tuple[str, str, int]]]) → str[source]

The text of a DN that was taken apart by str2dn().

anyldap.ldap.error

alias of LDAPError

anyldap.ldap.error_for_result(code: int, message: str | bytes | None = None, **fields: object) → LDAPError[source]

The exception a result code stands for, carrying the server’s message.

Anything else the response said about itself – which message it was, what kind, the controls it carried – goes in the same dictionary, where python-ldap puts it.

anyldap.ldap.escape_filter_chars(assertion_value: str | bytes, escape_mode: int = 0) → str[source]

An assertion value with the characters a filter would read escaped.

escape_mode 0 escapes what RFC 4515 requires, 1 escapes everything outside the printable range as well, and 2 escapes every character.

Each mode escapes what python-ldap’s own escapes, character by character as it does, so a filter built here is the one it would have built.

anyldap.ldap.escape_str(escape_func: Callable[[_Escapable], str], val: str, *args: _Escapable) → str[source]

A template filled in with values that were escaped first.

escape_func is what to escape them with: escape_filter_chars for a filter, escape_dn_chars for a DN.

anyldap.ldap.explode_dn(dn: str | bytes, notypes: int = 0, flags: int = 0) → list[str][source]

The RDNs of a DN, as text, optionally without their attribute types.

anyldap.ldap.explode_rdn(rdn: str | bytes, notypes: int = 0, flags: int = 0) → list[str][source]

The attribute/value pairs of one RDN, as text.

anyldap.ldap.filter_format(filter_template: str, assertion_values: Iterable[str | bytes]) → str[source]

A filter built from a template, with every value escaped first.

anyldap.ldap.get_option(option: int) → object[source]

What a connection opened from now on would start with.

anyldap.ldap.initialize(uri: str, trace_level: int = 0, *, ssl_context: SSLContext | None = None) → SimpleLDAPObject[source]

A connection to the server named by an ldap:// or ldaps:// URL.

Nothing is sent until the first operation is awaited, which is what lets this stay the plain call python-ldap makes it. Whatever was given to ldap.set_option() is what the connection starts with.

anyldap.ldap.is_dn(s: str, flags: int = 0) → bool[source]

Whether this is a distinguished name that can be parsed.

anyldap.ldap.modifyModlist(old_entry: Mapping[str, Sequence[str | bytes | None] | str | bytes | None], new_entry: Mapping[str, Sequence[str | bytes | None] | str | bytes | None], ignore_attr_types: Iterable[str] = (), ignore_oldexistent: int = 0, case_ignore_attr_types: Iterable[str] = ()) → Sequence[tuple[int, str, Sequence[str | bytes] | str | bytes | None]][source]

The modlist that turns one entry into another.

An attribute whose values changed is deleted and added again rather than replaced, which is what python-ldap’s own does: the server then rejects the change if the entry moved underneath, instead of overwriting it.

anyldap.ldap.open(host: str, port: int = 389, trace_level: int = 0) → SimpleLDAPObject[source]
anyldap.ldap.set_option(option: int, invalue: object) → None[source]

Set an option on every connection opened from now on.

A connection that is already open keeps what it has; set the option on the connection itself to change that one.

anyldap.ldap.str2dn(dn: str | bytes | None, flags: int = 0) → list[list[tuple[str, str, int]]][source]

A DN taken apart into its RDNs, each a list of attribute/value pairs.

Raises DECODING_ERROR for text that is not a distinguished name, which is what python-ldap raises for it.

anyldap.ldap.strf_secs(secs: float) → str[source]

Seconds since the epoch, as the generalized time LDAP writes.

anyldap.ldap.strp_secs(dt_str: str) → int[source]

A generalized time, as seconds since the epoch.

The time read is UTC, so it is turned back into seconds as UTC. python-ldap goes through time.mktime() and subtracts time.timezone, which is an hour out wherever the local zone is on summer time; this is the inverse of strf_secs() everywhere.

anyldap.ldap.time_span_filter(filterstr: str = '', from_timestamp: float = 0, until_timestamp: float | None = None, delta_attr: str = 'modifyTimestamp') → str[source]

This filter, narrowed to what changed between two times.

A negative from_timestamp is that many seconds before the end of the span, which is now unless until_timestamp says otherwise.

anyldap.ldap.timegm(t: tuple[int, ...]) → int[source]

A UTC time tuple, as seconds since the epoch.

python-ldap keeps its own because calendar.timegm() was once slow; this is that one.

anyldap.ldap.ldapobject module

An LDAP connection with python-ldap’s API, awaited rather than blocking.

Every method python-ldap spells synchronously is a coroutine here; the arguments it takes and the values it hands back are the ones python-ldap documents, so code ports by adding await.

Like python-ldap, this reads the connection only while an operation is being waited for: an operation is started by writing its request and remembering its message id, and result3() reads answers off the socket, handing each to whichever operation it belongs to, until the one asked for is finished. Nothing runs in the background, so a connection needs no task group and can be used from whichever task has it.

anyldap.ldap.ldapobject.LDAPObject

alias of SimpleLDAPObject

class anyldap.ldap.ldapobject.ReconnectLDAPObject(uri: str, trace_level: int = 0, *, ssl_context: SSLContext | None = None, retry_max: int = 1, retry_delay: float = 60.0)[source]

Bases: SimpleLDAPObject

A connection that opens itself again when the server goes away.

Each operation is tried, and if it fails with one of the errors that mean the connection rather than the request – SERVER_DOWN, UNAVAILABLE, CONNECT_ERROR or TIMEOUT – the connection is made again and the operation tried once more. Reconnecting replays what was done to the connection before: the options that were set, StartTLS if it was raised, and the last bind that was made.

retry_max is how many times to try opening it again before giving up, and retry_delay how long to wait between tries.

Unlike python-ldap’s, this does not open the connection before the first operation: nothing is sent until something is awaited, so there is nothing to reconnect until an operation has failed.

async add_ext_s(*args: Any, **kwargs: Any) → Any[source]
async bind_s(*args: Any, **kwargs: Any) → Any[source]
async cancel_s(*args: Any, **kwargs: Any) → Any[source]
async compare_ext_s(*args: Any, **kwargs: Any) → Any[source]
async delete_ext_s(*args: Any, **kwargs: Any) → Any[source]
async extop_s(*args: Any, **kwargs: Any) → Any[source]

Send an extended operation and wait for what it answers.

Given extop_resp_class, the answer is read into one of those rather than handed back as the name and the bytes; a server that answers to a different name than the class expects is refused.

async modify_ext_s(*args: Any, **kwargs: Any) → Any[source]
async passwd_s(*args: Any, **kwargs: Any) → Any[source]
async reconnect(uri: str, retry_max: int = 1, retry_delay: float = 60.0, force: bool = True) → None[source]

Open the connection again, and put it back as it was.

async rename_s(*args: Any, **kwargs: Any) → Any[source]
async sasl_bind_s(*args: Any, **kwargs: Any) → Any[source]

One step of a SASL bind, for callers driving the exchange itself.

Answers with the credentials the server sent back, which is what the next step is built from.

async sasl_interactive_bind_s(*args: Any, **kwargs: Any) → Any[source]

Bind with a SASL mechanism, answering the server until it is done.

The mechanism is asked what to send, and asked again with whatever the server sent back, for as long as the server says the bind is still in progress. What the OPT_X_SASL_* options say fills in whatever the mechanism was not told directly.

async search_ext_s(*args: Any, **kwargs: Any) → Any[source]
set_option(option: int, invalue: object) → None[source]
async simple_bind_s(*args: Any, **kwargs: Any) → Any[source]
async start_tls_s(*args: Any, **kwargs: Any) → Any[source]

Raise TLS on the connection, as StartTLS does.

Nothing else is reading the connection, so the handshake follows the response on the same stream, with no request in flight to confuse.

async whoami_s(*args: Any, **kwargs: Any) → str[source]

The identity the server has this connection bound as.

class anyldap.ldap.ldapobject.SimpleLDAPObject(uri: str, trace_level: int = 0, *, ssl_context: SSLContext | None = None)[source]

Bases: object

A connection to one LDAP server, with the API of python-ldap’s own.

The connection is opened by the first operation that needs it, so that initialize() stays the plain call python-ldap makes it. Closing it is unbind_s(), and async with closes it however the block ends.

CLASSATTR_OPTION_MAPPING: ClassVar[dict[str, int]] = {'deref': 2, 'network_timeout': 20485, 'protocol_version': 17, 'referrals': 8, 'sizelimit': 3, 'timelimit': 4}
async abandon(msgid: int, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → None[source]
async abandon_ext(msgid: int, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → None[source]

Tell the server to stop working on an operation, and forget it.

Nothing is answered: an abandon is not a request the server replies to, which is what cancel() is for.

async add(dn: str, modlist: Sequence[tuple[str, Sequence[str | bytes] | str | bytes]]) → int[source]
async add_ext(dn: str, modlist: Sequence[tuple[str, Sequence[str | bytes] | str | bytes]], serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → int[source]
async add_ext_s(dn: str, modlist: Sequence[tuple[str, Sequence[str | bytes] | str | bytes]], serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]
async add_s(dn: str, modlist: Sequence[tuple[str, Sequence[str | bytes] | str | bytes]]) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]
async bind(who: str, cred: str | bytes, method: int = 128) → int[source]
async bind_s(who: str, cred: str | bytes, method: int = 128) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]
async cancel(cancelid: int, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → int[source]

Ask the server to stop working on an operation (RFC 3909).

Unlike abandon() this is answered, so the caller learns whether the operation really did stop; the operation itself finishes with CANCELLED.

async cancel_s(cancelid: int, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]]] | None[source]
async compare(dn: str, attr: str, value: str | bytes) → int[source]
async compare_ext(dn: str, attr: str, value: str | bytes, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → int[source]
async compare_ext_s(dn: str, attr: str, value: str | bytes, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → bool[source]
async compare_s(dn: str, attr: str, value: str | bytes) → bool[source]
async delete(dn: str) → int[source]
async delete_ext(dn: str, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → int[source]
async delete_ext_s(dn: str, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]
async delete_s(dn: str) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]
async extop(extreq: LDAPExtendedRequest, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → int[source]
async extop_result(msgid: int = -1, all: int = 1, timeout: float | None = None) → tuple[str | None, bytes | None][source]

Collect an extended operation started with extop().

async extop_s(extreq: LDAPExtendedRequest, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, extop_resp_class: None = None) → tuple[str | None, bytes | None][source]
async extop_s(extreq: LDAPExtendedRequest, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None, extop_resp_class: type[_Response]) → _Response

Send an extended operation and wait for what it answers.

Given extop_resp_class, the answer is read into one of those rather than handed back as the name and the bytes; a server that answers to a different name than the class expects is refused.

fileno() → int[source]

The socket this connection is open on.

For a caller that wants to watch the connection with something of its own. It raises if nothing is open yet, because there is no socket to name until the first operation has been awaited.

async find_unique_entry(base: str, scope: int = 2, filterstr: str = '(objectClass=*)', attrlist: Sequence[str] | None = None, attrsonly: int = 0, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, timeout: float = -1) → tuple[str, dict[str, list[bytes]]][source]

The one entry a search found, or an error saying it was not one.

The search asks for two entries, so a server that has more says so rather than sending them all.

async get_naming_contexts() → list[bytes][source]

The naming contexts the server holds.

get_option(option: int) → object[source]
async modify(dn: str, modlist: Sequence[tuple[int, str, Sequence[str | bytes] | str | bytes | None]]) → int[source]
async modify_ext(dn: str, modlist: Sequence[tuple[int, str, Sequence[str | bytes] | str | bytes | None]], serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → int[source]
async modify_ext_s(dn: str, modlist: Sequence[tuple[int, str, Sequence[str | bytes] | str | bytes | None]], serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]
async modify_s(dn: str, modlist: Sequence[tuple[int, str, Sequence[str | bytes] | str | bytes | None]]) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]
async modrdn(dn: str, newrdn: str, delold: int = 1) → int[source]
async modrdn_s(dn: str, newrdn: str, delold: int = 1) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]
network_timeout: float | None
async passwd(user: str | bytes | None = None, oldpw: str | bytes | None = None, newpw: str | bytes | None = None, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → int[source]
async passwd_s(user: str | bytes | None = None, oldpw: str | bytes | None = None, newpw: str | bytes | None = None, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, extract_newpw: bool = False) → tuple[str | None, bytes | None][source]
async read_rootdse_s(filterstr: str = '(objectClass=*)', attrlist: Sequence[str] | None = None) → dict[str, list[bytes]][source]

What the server says about itself, from its root DSE.

async read_s(dn: str, filterstr: str = '(objectClass=*)', attrlist: Sequence[str] | None = None, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, timeout: float = -1) → dict[str, list[bytes]] | None[source]

The attributes of one entry, or None when it is not there.

async read_schema_s(dn: str = '') → SubSchema[source]

The schema an entry is written against, read into objects.

Not a method python-ldap has: it fetches schema with ldap.schema.urlfetch(), which opens its own connection. This uses the connection that is already here.

async read_subschemasubentry_s(subschemasubentry_dn: str, attrs: Sequence[str] | None = None) → dict[str, list[bytes]] | None[source]

The schema itself, as the server publishes it.

async rename(dn: str, newrdn: str, newsuperior: str | None = None, delold: int = 1, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → int[source]
async rename_s(dn: str, newrdn: str, newsuperior: str | None = None, delold: int = 1, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]
async result(msgid: int = -1, all: int = 1, timeout: float | None = None) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]]][source]
async result2(msgid: int = -1, all: int = 1, timeout: float | None = None) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int][source]
async result3(msgid: int = -1, all: int = 1, timeout: float | None = None, resp_ctrl_classes: Mapping[str, type[ResponseControl]] | None = None) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]

Wait for an operation started earlier, and answer with its result.

RES_ANY takes the operation that was started first, which is the one python-ldap would most likely hand back.

async result4(msgid: int = -1, all: int = 1, timeout: float | None = None, add_ctrls: int = 0, add_intermediates: int = 0, add_extop: int = 0, resp_ctrl_classes: Mapping[str, type[ResponseControl]] | None = None) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]] | tuple[str | None, bytes | None] | tuple[str, dict[str, list[bytes]], Sequence[ResponseControl]] | tuple[None, list[str], Sequence[ResponseControl]] | tuple[str | None, bytes | None, Sequence[ResponseControl]]], int, Sequence[ResponseControl], str | None, bytes | None][source]
async sasl_bind_s(dn: str, mechanism: str | bytes, cred: str | bytes | None, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → bytes | None[source]

One step of a SASL bind, for callers driving the exchange itself.

Answers with the credentials the server sent back, which is what the next step is built from.

async sasl_external_bind_s(serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, sasl_flags: int = 2, authz_id: str = '') → None[source]

Bind as whoever the connection underneath already proved to be.

async sasl_gssapi_bind_s(serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, sasl_flags: int = 2, authz_id: str = '') → None[source]

Bind with Kerberos, which is what the GSSAPI mechanism is.

async sasl_interactive_bind_s(who: str, auth: sasl, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, sasl_flags: int = 2) → None[source]

Bind with a SASL mechanism, answering the server until it is done.

The mechanism is asked what to send, and asked again with whatever the server sent back, for as long as the server says the bind is still in progress. What the OPT_X_SASL_* options say fills in whatever the mechanism was not told directly.

async sasl_non_interactive_bind_s(sasl_mech: str, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, sasl_flags: int = 2, authz_id: str = '') → None[source]

A SASL bind with a mechanism that asks the caller nothing.

async search(base: str, scope: int = 2, filterstr: str = '(objectClass=*)', attrlist: Sequence[str] | None = None, attrsonly: int = 0) → int[source]
async search_ext(base: str, scope: int = 2, filterstr: str = '(objectClass=*)', attrlist: Sequence[str] | None = None, attrsonly: int = 0, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, timeout: float = -1, sizelimit: int = 0) → int[source]
async search_ext_s(base: str, scope: int = 2, filterstr: str = '(objectClass=*)', attrlist: Sequence[str] | None = None, attrsonly: int = 0, serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, timeout: float = -1, sizelimit: int = 0) → list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]][source]
async search_s(base: str, scope: int = 2, filterstr: str = '(objectClass=*)', attrlist: Sequence[str] | None = None, attrsonly: int = 0) → list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]][source]
async search_st(base: str, scope: int = 2, filterstr: str = '(objectClass=*)', attrlist: Sequence[str] | None = None, attrsonly: int = 0, timeout: float = -1) → list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]][source]
async search_subschemasubentry_s(dn: str = '') → str | None[source]

Where the server keeps the schema an entry is written against.

set_option(option: int, invalue: object) → None[source]
async simple_bind(who: str = '', cred: str | bytes = '', serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → int[source]
async simple_bind_s(who: str = '', cred: str | bytes = '', serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → tuple[int, list[tuple[str, dict[str, list[bytes]]] | tuple[None, list[str]]], int, Sequence[ResponseControl]][source]
async start_tls_s() → None[source]

Raise TLS on the connection, as StartTLS does.

Nothing else is reading the connection, so the handshake follows the response on the same stream, with no request in flight to confuse.

timeout: float | None
async unbind(serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → None

Say goodbye to the server and close the connection.

async unbind_ext(serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → None[source]

Say goodbye to the server and close the connection.

async unbind_ext_s(serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → None

Say goodbye to the server and close the connection.

async unbind_s(serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → None

Say goodbye to the server and close the connection.

async whoami_s(serverctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None, clientctrls: Iterable[RequestControl | tuple[str | bytes, int | None, str | bytes | None]] | None = None) → str[source]

The identity the server has this connection bound as.

anyldap.ldap.ldapobject.initialize(uri: str, trace_level: int = 0, *, ssl_context: SSLContext | None = None) → SimpleLDAPObject[source]

A connection to the server named by an ldap:// or ldaps:// URL.

Nothing is sent until the first operation is awaited, which is what lets this stay the plain call python-ldap makes it. Whatever was given to ldap.set_option() is what the connection starts with.

anyldap.ldap.ldapobject.open(host: str, port: int = 389, trace_level: int = 0) → SimpleLDAPObject[source]

anyldap.ldap.errors module

The exceptions python-ldap raises, keyed by the result code that causes them.

Each class carries the LDAP result code it stands for, and an instance holds the same args[0] dictionary python-ldap builds: a desc describing the code, and the server’s own info message when it sent one.

exception anyldap.ldap.errors.ADMINLIMIT_EXCEEDED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Administrative limit exceeded'
errnum: ClassVar[int | None] = 11
anyldap.ldap.errors.ADMIN_LIMIT_EXCEEDED

alias of ADMINLIMIT_EXCEEDED

exception anyldap.ldap.errors.AFFECTS_MULTIPLE_DSAS[source]

Bases: LDAPError

desc: ClassVar[str] = 'Operation affects multiple DSAs'
errnum: ClassVar[int | None] = 71
exception anyldap.ldap.errors.ALIAS_DEREF_PROBLEM[source]

Bases: LDAPError

desc: ClassVar[str] = 'Alias dereferencing problem'
errnum: ClassVar[int | None] = 36
exception anyldap.ldap.errors.ALIAS_PROBLEM[source]

Bases: LDAPError

desc: ClassVar[str] = 'Alias problem'
errnum: ClassVar[int | None] = 33
exception anyldap.ldap.errors.ALREADY_EXISTS[source]

Bases: LDAPError

desc: ClassVar[str] = 'Already exists'
errnum: ClassVar[int | None] = 68
exception anyldap.ldap.errors.ASSERTION_FAILED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Assertion failed'
errnum: ClassVar[int | None] = 122
exception anyldap.ldap.errors.AUTH_METHOD_NOT_SUPPORTED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Authentication method not supported'
errnum: ClassVar[int | None] = 7
exception anyldap.ldap.errors.AUTH_UNKNOWN[source]

Bases: LDAPError

desc: ClassVar[str] = 'Unknown authentication method'
errnum: ClassVar[int | None] = -6
exception anyldap.ldap.errors.BUSY[source]

Bases: LDAPError

desc: ClassVar[str] = 'Server is busy'
errnum: ClassVar[int | None] = 51
exception anyldap.ldap.errors.CANCELLED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Cancelled'
errnum: ClassVar[int | None] = 118
exception anyldap.ldap.errors.CANNOT_CANCEL[source]

Bases: LDAPError

desc: ClassVar[str] = 'Cannot cancel'
errnum: ClassVar[int | None] = 121
exception anyldap.ldap.errors.CLIENT_LOOP[source]

Bases: LDAPError

desc: ClassVar[str] = 'Client loop'
errnum: ClassVar[int | None] = -16
exception anyldap.ldap.errors.COMPARE_FALSE[source]

Bases: LDAPError

A compare that answered no; compare_s() turns this into False.

desc: ClassVar[str] = 'Compare False'
errnum: ClassVar[int | None] = 5
exception anyldap.ldap.errors.COMPARE_TRUE[source]

Bases: LDAPError

A compare that answered yes; compare_s() turns this into True.

desc: ClassVar[str] = 'Compare True'
errnum: ClassVar[int | None] = 6
exception anyldap.ldap.errors.CONFIDENTIALITY_REQUIRED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Confidentiality required'
errnum: ClassVar[int | None] = 13
exception anyldap.ldap.errors.CONNECT_ERROR[source]

Bases: LDAPError

desc: ClassVar[str] = 'Connect error'
errnum: ClassVar[int | None] = -11
exception anyldap.ldap.errors.CONSTRAINT_VIOLATION[source]

Bases: LDAPError

desc: ClassVar[str] = 'Constraint violation'
errnum: ClassVar[int | None] = 19
exception anyldap.ldap.errors.CONTROL_NOT_FOUND[source]

Bases: LDAPError

desc: ClassVar[str] = 'Control not found'
errnum: ClassVar[int | None] = -13
exception anyldap.ldap.errors.DECODING_ERROR[source]

Bases: LDAPError

desc: ClassVar[str] = 'Decoding error'
errnum: ClassVar[int | None] = -4
exception anyldap.ldap.errors.ENCODING_ERROR[source]

Bases: LDAPError

desc: ClassVar[str] = 'Encoding error'
errnum: ClassVar[int | None] = -3
exception anyldap.ldap.errors.FILTER_ERROR[source]

Bases: LDAPError

The search filter could not be parsed.

desc: ClassVar[str] = 'Bad search filter'
errnum: ClassVar[int | None] = -7
exception anyldap.ldap.errors.INAPPROPRIATE_AUTH[source]

Bases: LDAPError

desc: ClassVar[str] = 'Inappropriate authentication'
errnum: ClassVar[int | None] = 48
exception anyldap.ldap.errors.INAPPROPRIATE_MATCHING[source]

Bases: LDAPError

desc: ClassVar[str] = 'Inappropriate matching'
errnum: ClassVar[int | None] = 18
exception anyldap.ldap.errors.INSUFFICIENT_ACCESS[source]

Bases: LDAPError

desc: ClassVar[str] = 'Insufficient access'
errnum: ClassVar[int | None] = 50
exception anyldap.ldap.errors.INVALID_CREDENTIALS[source]

Bases: LDAPError

desc: ClassVar[str] = 'Invalid credentials'
errnum: ClassVar[int | None] = 49
exception anyldap.ldap.errors.INVALID_DN_SYNTAX[source]

Bases: LDAPError

desc: ClassVar[str] = 'Invalid DN syntax'
errnum: ClassVar[int | None] = 34
exception anyldap.ldap.errors.INVALID_SYNTAX[source]

Bases: LDAPError

desc: ClassVar[str] = 'Invalid syntax'
errnum: ClassVar[int | None] = 21
exception anyldap.ldap.errors.IS_LEAF[source]

Bases: LDAPError

desc: ClassVar[str] = 'Is a leaf'
errnum: ClassVar[int | None] = 35
exception anyldap.ldap.errors.LDAPError[source]

Bases: Exception

The base of every error raised here, as ldap.LDAPError is.

desc: ClassVar[str] = 'Unknown error'
errnum: ClassVar[int | None] = None
property info: str | None

The diagnostic message the server sent, if it sent one.

exception anyldap.ldap.errors.LOCAL_ERROR[source]

Bases: LDAPError

desc: ClassVar[str] = 'Local error'
errnum: ClassVar[int | None] = -2
exception anyldap.ldap.errors.LOOP_DETECT[source]

Bases: LDAPError

desc: ClassVar[str] = 'Loop detected'
errnum: ClassVar[int | None] = 54
exception anyldap.ldap.errors.MORE_RESULTS_TO_RETURN[source]

Bases: LDAPError

desc: ClassVar[str] = 'More results to return'
errnum: ClassVar[int | None] = -15
exception anyldap.ldap.errors.NAMING_VIOLATION[source]

Bases: LDAPError

desc: ClassVar[str] = 'Naming violation'
errnum: ClassVar[int | None] = 64
exception anyldap.ldap.errors.NOT_ALLOWED_ON_NONLEAF[source]

Bases: LDAPError

desc: ClassVar[str] = 'Operation not allowed on non-leaf'
errnum: ClassVar[int | None] = 66
exception anyldap.ldap.errors.NOT_ALLOWED_ON_RDN[source]

Bases: LDAPError

desc: ClassVar[str] = 'Operation not allowed on RDN'
errnum: ClassVar[int | None] = 67
exception anyldap.ldap.errors.NOT_SUPPORTED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Not supported'
errnum: ClassVar[int | None] = -12
exception anyldap.ldap.errors.NO_MEMORY[source]

Bases: LDAPError

desc: ClassVar[str] = 'Out of memory'
errnum: ClassVar[int | None] = -10
exception anyldap.ldap.errors.NO_OBJECT_CLASS_MODS[source]

Bases: LDAPError

desc: ClassVar[str] = 'Cannot modify object class'
errnum: ClassVar[int | None] = 69
exception anyldap.ldap.errors.NO_RESULTS_RETURNED[source]

Bases: LDAPError

desc: ClassVar[str] = 'No results returned'
errnum: ClassVar[int | None] = -14
exception anyldap.ldap.errors.NO_SUCH_ATTRIBUTE[source]

Bases: LDAPError

desc: ClassVar[str] = 'No such attribute'
errnum: ClassVar[int | None] = 16
exception anyldap.ldap.errors.NO_SUCH_OBJECT[source]

Bases: LDAPError

desc: ClassVar[str] = 'No such object'
errnum: ClassVar[int | None] = 32
exception anyldap.ldap.errors.NO_SUCH_OPERATION[source]

Bases: LDAPError

desc: ClassVar[str] = 'No such operation'
errnum: ClassVar[int | None] = 119
exception anyldap.ldap.errors.NO_UNIQUE_ENTRY[source]

Bases: NO_SUCH_OBJECT

A read expecting one entry found none, or several.

Not a result code a server sends: it is what this raises when a read that must find exactly one entry did not, which is what python-ldap raises it for too.

desc: ClassVar[str] = 'No or non-unique search result'
exception anyldap.ldap.errors.OBJECT_CLASS_VIOLATION[source]

Bases: LDAPError

desc: ClassVar[str] = 'Object class violation'
errnum: ClassVar[int | None] = 65
exception anyldap.ldap.errors.OPERATIONS_ERROR[source]

Bases: LDAPError

desc: ClassVar[str] = 'Operations error'
errnum: ClassVar[int | None] = 1
exception anyldap.ldap.errors.OTHER[source]

Bases: LDAPError

desc: ClassVar[str] = 'Other'
errnum: ClassVar[int | None] = 80
exception anyldap.ldap.errors.PARAM_ERROR[source]

Bases: LDAPError

desc: ClassVar[str] = 'Bad parameter to an ldap routine'
errnum: ClassVar[int | None] = -9
exception anyldap.ldap.errors.PARTIAL_RESULTS[source]

Bases: LDAPError

desc: ClassVar[str] = 'Partial results and referral received'
errnum: ClassVar[int | None] = 9
exception anyldap.ldap.errors.PROTOCOL_ERROR[source]

Bases: LDAPError

desc: ClassVar[str] = 'Protocol error'
errnum: ClassVar[int | None] = 2
exception anyldap.ldap.errors.PROXIED_AUTHORIZATION_DENIED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Proxied authorization denied'
errnum: ClassVar[int | None] = 123
exception anyldap.ldap.errors.REFERRAL[source]

Bases: LDAPError

desc: ClassVar[str] = 'Referral'
errnum: ClassVar[int | None] = 10
exception anyldap.ldap.errors.REFERRAL_LIMIT_EXCEEDED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Referral limit exceeded'
errnum: ClassVar[int | None] = -17
exception anyldap.ldap.errors.RESULTS_TOO_LARGE[source]

Bases: LDAPError

desc: ClassVar[str] = 'Results too large'
errnum: ClassVar[int | None] = 70
exception anyldap.ldap.errors.SASL_BIND_IN_PROGRESS[source]

Bases: LDAPError

desc: ClassVar[str] = 'SASL bind in progress'
errnum: ClassVar[int | None] = 14
exception anyldap.ldap.errors.SERVER_DOWN[source]

Bases: LDAPError

The connection could not be opened, or was lost.

desc: ClassVar[str] = "Can't contact LDAP server"
errnum: ClassVar[int | None] = -1
exception anyldap.ldap.errors.SIZELIMIT_EXCEEDED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Size limit exceeded'
errnum: ClassVar[int | None] = 4
anyldap.ldap.errors.STRONG_AUTH_NOT_SUPPORTED

alias of AUTH_METHOD_NOT_SUPPORTED

exception anyldap.ldap.errors.STRONG_AUTH_REQUIRED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Strong(er) authentication required'
errnum: ClassVar[int | None] = 8
exception anyldap.ldap.errors.SUCCESS[source]

Bases: LDAPError

The code that is not an error; nothing here is raised with it.

desc: ClassVar[str] = 'Success'
errnum: ClassVar[int | None] = 0
exception anyldap.ldap.errors.TIMELIMIT_EXCEEDED[source]

Bases: LDAPError

desc: ClassVar[str] = 'Time limit exceeded'
errnum: ClassVar[int | None] = 3
exception anyldap.ldap.errors.TIMEOUT[source]

Bases: LDAPError

The client gave up waiting before the server answered.

desc: ClassVar[str] = 'Timed out'
errnum: ClassVar[int | None] = -5
exception anyldap.ldap.errors.TOO_LATE[source]

Bases: LDAPError

desc: ClassVar[str] = 'Too late'
errnum: ClassVar[int | None] = 120
exception anyldap.ldap.errors.TYPE_OR_VALUE_EXISTS[source]

Bases: LDAPError

desc: ClassVar[str] = 'Type or value exists'
errnum: ClassVar[int | None] = 20
exception anyldap.ldap.errors.UNAVAILABLE[source]

Bases: LDAPError

desc: ClassVar[str] = 'Server is unavailable'
errnum: ClassVar[int | None] = 52
exception anyldap.ldap.errors.UNAVAILABLE_CRITICAL_EXTENSION[source]

Bases: LDAPError

desc: ClassVar[str] = 'Critical extension is unavailable'
errnum: ClassVar[int | None] = 12
exception anyldap.ldap.errors.UNDEFINED_TYPE[source]

Bases: LDAPError

desc: ClassVar[str] = 'Undefined attribute type'
errnum: ClassVar[int | None] = 17
exception anyldap.ldap.errors.UNWILLING_TO_PERFORM[source]

Bases: LDAPError

desc: ClassVar[str] = 'Server is unwilling to perform'
errnum: ClassVar[int | None] = 53
exception anyldap.ldap.errors.USER_CANCELLED[source]

Bases: LDAPError

desc: ClassVar[str] = 'User cancelled operation'
errnum: ClassVar[int | None] = -8
exception anyldap.ldap.errors.VLV_ERROR[source]

Bases: LDAPError

desc: ClassVar[str] = 'Virtual List View error'
errnum: ClassVar[int | None] = 76
exception anyldap.ldap.errors.X_PROXY_AUTHZ_FAILURE[source]

Bases: LDAPError

desc: ClassVar[str] = 'Proxy authorization failure'
errnum: ClassVar[int | None] = 47
anyldap.ldap.errors.error_for_result(code: int, message: str | bytes | None = None, **fields: object) → LDAPError[source]

The exception a result code stands for, carrying the server’s message.

Anything else the response said about itself – which message it was, what kind, the controls it carried – goes in the same dictionary, where python-ldap puts it.

anyldap.ldap.constants module

The names and values python-ldap’s ldap module defines.

Only the constants the client honours are here. An option python-ldap has but this cannot act on is left out, so that setting it fails loudly rather than being quietly ignored.

anyldap.ldap.dn module

ldap.dn: taking distinguished names apart and putting them together.

anyldap.ldap.dn.dn2str(dn: Sequence[Sequence[tuple[str, str, int]]]) → str[source]

The text of a DN that was taken apart by str2dn().

anyldap.ldap.dn.escape_dn_chars(s: str) → str[source]

A value with the characters a DN would read escaped.

anyldap.ldap.dn.explode_dn(dn: str | bytes, notypes: int = 0, flags: int = 0) → list[str][source]

The RDNs of a DN, as text, optionally without their attribute types.

anyldap.ldap.dn.explode_rdn(rdn: str | bytes, notypes: int = 0, flags: int = 0) → list[str][source]

The attribute/value pairs of one RDN, as text.

anyldap.ldap.dn.is_dn(s: str, flags: int = 0) → bool[source]

Whether this is a distinguished name that can be parsed.

anyldap.ldap.dn.str2dn(dn: str | bytes | None, flags: int = 0) → list[list[tuple[str, str, int]]][source]

A DN taken apart into its RDNs, each a list of attribute/value pairs.

Raises DECODING_ERROR for text that is not a distinguished name, which is what python-ldap raises for it.

anyldap.ldap.filter module

ldap.filter: building search filters out of untrusted values.

anyldap.ldap.filter.escape_filter_chars(assertion_value: str | bytes, escape_mode: int = 0) → str[source]

An assertion value with the characters a filter would read escaped.

escape_mode 0 escapes what RFC 4515 requires, 1 escapes everything outside the printable range as well, and 2 escapes every character.

Each mode escapes what python-ldap’s own escapes, character by character as it does, so a filter built here is the one it would have built.

anyldap.ldap.filter.filter_format(filter_template: str, assertion_values: Iterable[str | bytes]) → str[source]

A filter built from a template, with every value escaped first.

anyldap.ldap.filter.time_span_filter(filterstr: str = '', from_timestamp: float = 0, until_timestamp: float | None = None, delta_attr: str = 'modifyTimestamp') → str[source]

This filter, narrowed to what changed between two times.

A negative from_timestamp is that many seconds before the end of the span, which is now unless until_timestamp says otherwise.

anyldap.ldap.modlist module

ldap.modlist: turning entries into the modlists add and modify take.

anyldap.ldap.modlist.addModlist(entry: Mapping[str, Sequence[str | bytes | None] | str | bytes | None], ignore_attr_types: Iterable[str] = ()) → Sequence[tuple[str, Sequence[str | bytes] | str | bytes]][source]

The modlist that adds an entry, skipping attributes with no values.

anyldap.ldap.modlist.modifyModlist(old_entry: Mapping[str, Sequence[str | bytes | None] | str | bytes | None], new_entry: Mapping[str, Sequence[str | bytes | None] | str | bytes | None], ignore_attr_types: Iterable[str] = (), ignore_oldexistent: int = 0, case_ignore_attr_types: Iterable[str] = ()) → Sequence[tuple[int, str, Sequence[str | bytes] | str | bytes | None]][source]

The modlist that turns one entry into another.

An attribute whose values changed is deleted and added again rather than replaced, which is what python-ldap’s own does: the server then rejects the change if the entry moved underneath, instead of overwriting it.

anyldap.ldap.cidict module

ldap.cidict: a dictionary that ignores the case of its keys.

LDAP attribute types are case-insensitive, so an entry read back as givenName answers to givenname too.

class anyldap.ldap.cidict.cidict(default: Mapping[str, _V] | None = None)[source]

Bases: MutableMapping[str, _V]

A dictionary keyed by attribute type, whatever case it is written in.

The case a key was first written in is the one iteration hands back, as python-ldap’s own does.

copy() → cidict[_V][source]

Another dictionary of the same attributes, spelled the same way.

has_key(key: str) → bool[source]

Whether the entry has this attribute, as python-ldap’s asks.

anyldap.ldap.functions module

ldap.functions: the module-level helpers python-ldap keeps here.

anyldap.ldap.functions.escape_str(escape_func: Callable[[_Escapable], str], val: str, *args: _Escapable) → str[source]

A template filled in with values that were escaped first.

escape_func is what to escape them with: escape_filter_chars for a filter, escape_dn_chars for a DN.

anyldap.ldap.functions.get_option(option: int) → object[source]

What a connection opened from now on would start with.

anyldap.ldap.functions.set_option(option: int, invalue: object) → None[source]

Set an option on every connection opened from now on.

A connection that is already open keeps what it has; set the option on the connection itself to change that one.

anyldap.ldap.functions.strf_secs(secs: float) → str[source]

Seconds since the epoch, as the generalized time LDAP writes.

anyldap.ldap.functions.strp_secs(dt_str: str) → int[source]

A generalized time, as seconds since the epoch.

The time read is UTC, so it is turned back into seconds as UTC. python-ldap goes through time.mktime() and subtracts time.timezone, which is an hour out wherever the local zone is on summer time; this is the inverse of strf_secs() everywhere.

anyldap.ldap.functions.timegm(t: tuple[int, ...]) → int[source]

A UTC time tuple, as seconds since the epoch.

python-ldap keeps its own because calendar.timegm() was once slow; this is that one.

anyldap.ldap.sasl module

ldap.sasl: the mechanisms a SASL bind can be made with.

A mechanism is an object that knows its name and answers the challenges the server sends, which is the same shape python-ldap’s ldap.sasl classes have. python-ldap hands the exchange to Cyrus SASL; the mechanisms here answer for themselves, so what is available is what is written below.

class anyldap.ldap.sasl.cram_md5(authc_id: str, password: str)[source]

Bases: sasl

CRAM-MD5: the server’s challenge, answered with a keyed digest.

RFC 2195. The password is not sent, and the challenge is used once.

process(challenge: bytes | None = None) → bytes | None[source]

The credentials to send, given what the server sent.

class anyldap.ldap.sasl.digest_md5(authc_id: str, password: str, authz_id: str = '')[source]

Bases: sasl

DIGEST-MD5: a challenge answered without sending the password.

RFC 2831, which is historic; a server that offers it usually offers something better as well.

process(challenge: bytes | None = None) → bytes | None[source]

The credentials to send, given what the server sent.

class anyldap.ldap.sasl.external(authz_id: str | None = None)[source]

Bases: sasl

EXTERNAL: the identity is the one the connection already proved.

Which is TLS with a client certificate, or the credentials of whoever opened a socket in the filesystem.

process(challenge: bytes | None = None) → bytes[source]

The credentials to send, given what the server sent.

class anyldap.ldap.sasl.gssapi(authz_id: str = '', service: str | None = None)[source]

Bases: sasl

GSSAPI: Kerberos, as RFC 4752 exchanges it.

The exchange needs a Kerberos library, which the gssapi package is: it is not a dependency, and this mechanism says so if it is asked for without it. The ticket comes from the caller’s credential cache, so there is nothing to pass but the identity to act as, if any.

service names what the ticket is for. Left unset, the bind fills it in with ldap@ and the host it is connecting to, which is what a server registers itself as.

process(challenge: bytes | None = None) → bytes[source]

The credentials to send, given what the server sent.

class anyldap.ldap.sasl.plain(authc_id: str, password: str, authz_id: str = '')[source]

Bases: sasl

PLAIN: the name and password, sent as they are (RFC 4616).

process(challenge: bytes | None = None) → bytes[source]

The credentials to send, given what the server sent.

class anyldap.ldap.sasl.sasl(cb_value_dict: Mapping[int, str] | None, mech: str | bytes)[source]

Bases: object

A SASL mechanism, as python-ldap’s base class names one.

process() is given what the server sent, or nothing on the first step, and answers with what to send back; the bind is finished when the server says it is.

callback(cb_id: int, challenge: str, prompt: str, defresult: str) → str[source]

What python-ldap’s interactive bind asks its caller for.

process(challenge: bytes | None = None) → bytes | None[source]

The credentials to send, given what the server sent.

anyldap.ldap.controls package

ldap.controls: the controls a request carries and a response answers with.

A control is an OID, whether the server must understand it, and a value whose encoding the control itself knows. python-ldap encodes those values with pyasn1; here they are encoded with the BER library anyldap already has, so nothing else is needed to send or read one.

A request control is turned into the (type, criticality, value) triple that goes on the wire by encode_controls(), and the triples a server answers with are turned back into control objects by decode_controls(), which knows the controls registered in KNOWN_RESPONSE_CONTROLS.

anyldap.ldap.controls.DecodeControlTuples(ldapControlTuples: Iterable[tuple[str | bytes, int | None, str | bytes | None]] | None, knownLDAPControls: Mapping[str, type[ResponseControl]] | None = None) → list[ResponseControl]

The controls a response carried, read into the classes that know them.

A control whose OID is not registered comes back as an LDAPControl holding the bytes the server sent, so nothing is lost by not knowing it.

anyldap.ldap.controls.EncodeControlTuples(ldapControls: Iterable[RequestControl] | None) → list[tuple[str | bytes, int | None, str | bytes | None]] | None

The triples that go on the wire for these controls.

class anyldap.ldap.controls.LDAPControl(controlType: str | None = None, criticality: bool = False, controlValue: bytes | None = None, encodedControlValue: bytes | None = None)[source]

Bases: RequestControl, ResponseControl

A control that is only its three parts, whatever they mean.

controlValue: bytes | None
decodeControlValue(encodedControlValue: bytes) → None[source]

Read the value the server sent into this control’s own fields.

encodeControlValue() → bytes | None[source]

The value as it goes on the wire, or None when it has none.

class anyldap.ldap.controls.RequestControl(controlType: str | None = None, criticality: bool = False, encodedControlValue: bytes | None = None)[source]

Bases: object

A control sent with a request, as python-ldap’s base class is.

controlType: str | None
criticality: bool
encodeControlValue() → bytes | None[source]

The value as it goes on the wire, or None when it has none.

encodedControlValue: bytes | None
anyldap.ldap.controls.RequestControlTuples(ldapControls: Iterable[RequestControl] | None) → list[tuple[str | bytes, int | None, str | bytes | None]] | None[source]

python-ldap’s name for what goes on the wire.

class anyldap.ldap.controls.ResponseControl(controlType: str | None = None, criticality: bool = False)[source]

Bases: object

A control a response carried, as python-ldap’s base class is.

controlType: str | None
criticality: bool
decodeControlValue(encodedControlValue: bytes) → None[source]

Read the value the server sent into this control’s own fields.

encodedControlValue: bytes | None
anyldap.ldap.controls.ResponseControlTuples(ldapControls: Iterable[ResponseControl] | None) → Sequence[ResponseControl][source]
anyldap.ldap.controls.decodeControlTuples(ldapControlTuples: Iterable[tuple[str | bytes, int | None, str | bytes | None]] | None, knownLDAPControls: Mapping[str, type[ResponseControl]] | None = None) → list[ResponseControl]

The controls a response carried, read into the classes that know them.

A control whose OID is not registered comes back as an LDAPControl holding the bytes the server sent, so nothing is lost by not knowing it.

anyldap.ldap.controls.decode_controls(ldapControlTuples: Iterable[tuple[str | bytes, int | None, str | bytes | None]] | None, knownLDAPControls: Mapping[str, type[ResponseControl]] | None = None) → list[ResponseControl][source]

The controls a response carried, read into the classes that know them.

A control whose OID is not registered comes back as an LDAPControl holding the bytes the server sent, so nothing is lost by not knowing it.

anyldap.ldap.controls.encodeControlTuples(ldapControls: Iterable[RequestControl] | None) → list[tuple[str | bytes, int | None, str | bytes | None]] | None

The triples that go on the wire for these controls.

anyldap.ldap.controls.encode_controls(ldapControls: Iterable[RequestControl] | None) → list[tuple[str | bytes, int | None, str | bytes | None]] | None[source]

The triples that go on the wire for these controls.

ldap.controls.simple: the controls whose value is simple or absent.

class anyldap.ldap.controls.simple.AuthorizationIdentityRequestControl(criticality: bool = False)[source]

Bases: ValueLessRequestControl

Ask the bind to say which identity it ended up as.

class anyldap.ldap.controls.simple.AuthorizationIdentityResponseControl(controlType: str | None = '2.16.840.1.113730.3.4.15', criticality: bool = False)[source]

Bases: ResponseControl

The identity the bind ended up as.

decodeControlValue(encodedControlValue: bytes) → None[source]

Read the value the server sent into this control’s own fields.

class anyldap.ldap.controls.simple.BooleanControl(controlType: str | None = None, criticality: bool = False, booleanValue: bool = False)[source]

Bases: RequestControl, ResponseControl

A control whose value is yes or no.

boolean2ber = {False: b'\x01\x01\x00', True: b'\x01\x01\xff'}
decodeControlValue(encodedControlValue: bytes) → None[source]

Read the value the server sent into this control’s own fields.

encodeControlValue() → bytes[source]

The value as it goes on the wire, or None when it has none.

class anyldap.ldap.controls.simple.GetEffectiveRightsControl(criticality: bool = False, authzId: str | None = None)[source]

Bases: RequestControl

Ask what this identity would be allowed to do with what is found.

class anyldap.ldap.controls.simple.ManageDSAITControl(criticality: bool = False)[source]

Bases: ValueLessRequestControl

Ask for the entry itself rather than what it refers to.

class anyldap.ldap.controls.simple.OctetStringInteger(controlType: str | None = None, criticality: bool = False, integerValue: int | None = None)[source]

Bases: RequestControl, ResponseControl

A control whose value is one integer, written as an octet string.

decodeControlValue(encodedControlValue: bytes) → None[source]

Read the value the server sent into this control’s own fields.

encodeControlValue() → bytes[source]

The value as it goes on the wire, or None when it has none.

class anyldap.ldap.controls.simple.ProxyAuthzControl(criticality: bool, authzId: str)[source]

Bases: RequestControl

Ask the server to act as somebody else, if it will let you.

encodeControlValue() → bytes[source]

The value as it goes on the wire, or None when it has none.

class anyldap.ldap.controls.simple.RelaxRulesControl(criticality: bool = False)[source]

Bases: ValueLessRequestControl

Ask the server to allow what its own rules would not.

class anyldap.ldap.controls.simple.ValueLessRequestControl(controlType: str | None = None, criticality: bool = False)[source]

Bases: RequestControl

A control that says all it has to say by being there at all.

encodeControlValue() → bytes | None[source]

The value as it goes on the wire, or None when it has none.

ldap.controls.pagedresults: reading a large search a page at a time.

RFC 2696. The control carries how many entries a page holds and, after the first page, the cookie the server gave for asking for the next one.

class anyldap.ldap.controls.pagedresults.SimplePagedResultsControl(criticality: bool = False, size: int = 10, cookie: bytes = b'')[source]

Bases: RequestControl, ResponseControl

How big a page is, and where the last one left off.

cookie is empty to ask for the first page, and is whatever the server last sent to ask for the one after it. An empty cookie coming back means there are no more pages.

controlType: str | None = '1.2.840.113556.1.4.319'
decodeControlValue(encodedControlValue: bytes) → None[source]

Read the value the server sent into this control’s own fields.

encodeControlValue() → bytes[source]

The value as it goes on the wire, or None when it has none.

ldap.controls.readentry: the entry as it was, or as it became.

RFC 4527. A pre-read control asks for the entry the way it stood before the operation, a post-read for the way it stands after, so a caller does not have to search again to find out.

class anyldap.ldap.controls.readentry.PostReadControl(criticality: bool = False, attrList: Sequence[str] | None = None)[source]

Bases: ReadEntryControl

The entry as it stands after the operation.

controlType: str | None = '1.3.6.1.1.13.2'
class anyldap.ldap.controls.readentry.PreReadControl(criticality: bool = False, attrList: Sequence[str] | None = None)[source]

Bases: ReadEntryControl

The entry as it stood before the operation.

controlType: str | None = '1.3.6.1.1.13.1'
class anyldap.ldap.controls.readentry.ReadEntryControl(criticality: bool = False, attrList: Sequence[str] | None = None)[source]

Bases: RequestControl, ResponseControl

The attributes to read, and the entry that came back.

decodeControlValue(encodedControlValue: bytes) → None[source]

Read the value the server sent into this control’s own fields.

encodeControlValue() → bytes[source]

The value as it goes on the wire, or None when it has none.

ldap.controls.sss: asking the server to sort what it sends back.

RFC 2891. The request names the attributes to sort by, each optionally with the matching rule to sort with and whether to reverse it; the response says whether the sort could be done.

class anyldap.ldap.controls.sss.SSSRequestControl(criticality: bool = False, ordering_rules: Sequence[str] | str | None = None)[source]

Bases: RequestControl

Sort by these attributes, in this order.

An ordering rule is written after the attribute and a colon, and a leading minus sign sorts that attribute the other way round:

SSSRequestControl(ordering_rules=['-uidNumber', 'cn:caseIgnoreMatch'])
controlType: str | None = '1.2.840.113556.1.4.473'
encodeControlValue() → bytes[source]

The value as it goes on the wire, or None when it has none.

class anyldap.ldap.controls.sss.SSSResponseControl(criticality: bool = False)[source]

Bases: ResponseControl

Whether the sort was done, and what stopped it if it was not.

controlType: str | None = '1.2.840.113556.1.4.474'
decodeControlValue(encodedControlValue: bytes) → None[source]

Read the value the server sent into this control’s own fields.

sortResultCodes = {0: 'success', 1: 'operationsError', 3: 'timeLimitExceeded', 8: 'strongAuthRequired', 11: 'adminLimitExceeded', 16: 'noSuchAttribute', 18: 'inappropriateMatching', 50: 'insufficientAccessRights', 51: 'busy', 53: 'unwillingToPerform', 80: 'other'}

ldap.controls.ppolicy: what the server says about the password.

The password policy response control (draft-behera-ldap-password-policy): how long the password has left, how many grace logins remain, and what is wrong with it if the operation was refused because of it.

class anyldap.ldap.controls.ppolicy.PasswordPolicyControl(criticality: bool = False)[source]

Bases: ValueLessRequestControl, ResponseControl

Ask about the password policy, and read what the server said.

controlType: str | None = '1.3.6.1.4.1.42.2.27.8.5.1'
decodeControlValue(encodedControlValue: bytes) → None[source]

Read the value the server sent into this control’s own fields.

ldap.controls.libldap: the controls python-ldap encodes in its C library.

They are encoded here the same way everything else is, with the BER library anyldap already has, so the module is only a place for the names to live.

class anyldap.ldap.controls.libldap.AssertionControl(criticality: bool = True, filterstr: str = '(objectClass=*)')[source]

Bases: RequestControl

Do the operation only if the entry matches this filter (RFC 4528).

controlType: str | None = '1.3.6.1.1.12'
encodeControlValue() → bytes[source]

The value as it goes on the wire, or None when it has none.

class anyldap.ldap.controls.libldap.MatchedValuesControl(criticality: bool = False, filterstr: str = '(objectClass=*)')[source]

Bases: RequestControl

Send back only the values that match this filter (RFC 3876).

controlType: str | None = '1.2.826.0.1.3344810.2.3'
encodeControlValue() → bytes[source]

The value as it goes on the wire, or None when it has none.

ldap.controls.openldap: the controls OpenLDAP has of its own.

class anyldap.ldap.controls.openldap.SearchNoOpControl(criticality: bool = False)[source]

Bases: ValueLessRequestControl, ResponseControl

Ask what a search would have found, without sending the entries.

The result carries the numbers instead: how many entries the search matched, and how many references it would have followed. See https://www.openldap.org/its/index.cgi?findid=6598.

controlType: str | None = '1.3.6.1.4.1.4203.666.5.18'
decodeControlValue(encodedControlValue: bytes) → None[source]

Read the value the server sent into this control’s own fields.

class anyldap.ldap.controls.openldap.SearchNoOpMixIn[source]

Bases: object

What a connection mixes in to count entries without reading them.

python-ldap keeps noop_search_st here rather than on the connection itself, so that a caller who wants it says so:

class Connection(SearchNoOpMixIn, ldap.SimpleLDAPObject):
    pass
async noop_search_st(base: str, scope: int = 2, filterstr: str = '(objectClass=*)', timeout: float = -1) → tuple[int | None, int | None][source]

How many entries and references the search would have had.

anyldap.ldap.ldapurl module

ldapurl: LDAP URLs, as RFC 4516 writes them.

A URL says where a server is and what to ask it: the host and port, the DN to start from, the attributes, scope and filter of a search, and any extensions after that. python-ldap keeps this in a top-level ldapurl module; the class and its parts are the same here.

class anyldap.ldap.ldapurl.LDAPUrl(ldapUrl: str | None = None, urlscheme: str = 'ldap', hostport: str = '', dn: str = '', attrs: Sequence[str] | None = None, scope: int | None = None, filterstr: str | None = None, extensions: LDAPUrlExtensions | None = None, who: str | None = None, cred: str | None = None)[source]

Bases: object

An LDAP URL, taken apart into what it says.

Either ldapUrl is given and read, or the parts are given one by one. attr2extype says which attribute of the object an extension stands for, which is what lets a subclass carry the bind DN in the URL.

applyDefaults(defaults: Mapping[str, object]) → None[source]

Fill in what the URL did not say from these defaults.

attr2extype: dict[str, str] = {'cred': 'X-BINDPW', 'who': 'bindname'}
htmlHREF(urlPrefix: str = '', hrefText: str | None = None, hrefTarget: str | None = None) → str[source]

This URL as a link, for putting in a page.

initializeUrl() → str[source]

The part of the URL that says which server to open.

unparse() → str[source]

The URL, written out again.

class anyldap.ldap.ldapurl.LDAPUrlExtension(extensionStr: str | None = None, critical: int = 0, extype: str | None = None, exvalue: str | None = None)[source]

Bases: object

One extension of a URL: its type, its value, and whether it is critical.

An extension is written type=value, with a leading exclamation mark when the server must understand it.

unparse() → str[source]
class anyldap.ldap.ldapurl.LDAPUrlExtensions(default: Mapping[str, LDAPUrlExtension] | None = None)[source]

Bases: MutableMapping[str, LDAPUrlExtension]

The extensions of a URL, keyed by the type each one is of.

parse(extListStr: str) → None[source]
unparse() → str[source]
anyldap.ldap.ldapurl.isLDAPUrl(s: str) → bool[source]

Whether this text is an LDAP URL, whichever case it is written in.

anyldap.ldap.ldapurl.ldapUrlEscape(s: str) → str[source]

A part of a URL, with the characters a URL would read escaped.

anyldap.ldap.ldif module

python-ldap’s ldif module: reading and writing LDIF (RFC 2849).

anyldap has an LDIF reader of its own in anyldap.protocols.ldap.ldifprotocol, but it is a line-receiving protocol that answers with the objects the rest of anyldap is built out of, so it is not what code written against python-ldap asks for. This is that module instead: the same classes, taking the same arguments and handing back the same (dn, entry) pairs, so a script that reads or writes LDIF ports without being rewritten.

Nothing here touches the network, so nothing here is awaited:

from anyldap.ldap import ldif

records = ldif.LDIFRecordList(open("people.ldif"))
records.parse()
for dn, entry in records.all_records:
    print(dn, entry)

An attribute value is bytes and a DN is str, which is what python-ldap makes of them too.

class anyldap.ldap.ldif.LDIFCopy(input_file: _Input, output_file: _TextOutput, ignored_attr_types: Iterable[str] | None = None, max_entries: int = 0, process_url_schemes: Iterable[str] | None = None, base64_attrs: Iterable[str] | None = None, cols: int = 76, line_sep: str = '\n')[source]

Bases: LDIFParser

Copy LDIF input to LDIF output containing all data retrieved via URLs

handle(dn: str, entry: dict[str, list[bytes | None]]) → None[source]

Write single LDIF record to output file.

class anyldap.ldap.ldif.LDIFParser(input_file: _Input, ignored_attr_types: Iterable[str] | None = None, max_entries: int = 0, process_url_schemes: Iterable[str] | None = None, line_sep: str = '\n')[source]

Bases: object

Base class for a LDIF parser. Applications should sub-class this class and override method handle() to implement something meaningful.

Public class attributes:

records_read

Counter for records processed so far

handle(dn: str, entry: dict[str, list[bytes | None]]) → None[source]

Process a single content LDIF record. This method should be implemented by applications using LDIFParser.

handle_modify(dn: str, modops: list[tuple[int, str, list[bytes] | None]], controls: list[tuple[str, str, str | None]] | None = None) → None[source]

Process a single LDIF record representing a single modify operation. This method should be implemented by applications using LDIFParser.

parse() → None[source]

Invokes LDIFParser.parse_entry_records() for backward compatibility

parse_change_records() → None[source]
parse_entry_records() → None[source]

Continuously read and parse LDIF entry records

class anyldap.ldap.ldif.LDIFRecordList(input_file: _Input, ignored_attr_types: Iterable[str] | None = None, max_entries: int = 0, process_url_schemes: Iterable[str] | None = None)[source]

Bases: LDIFParser

Collect all records of a LDIF file. It can be a memory hog!

Records are stored in all_records as a single list of 2-tuples (dn, entry), after calling parse().

all_records: list[tuple[str, dict[str, list[bytes | None]]]]

List storing parsed records.

handle(dn: str, entry: dict[str, list[bytes | None]]) → None[source]

Append a single record to the list of all records (all_records).

handle_modify(dn: str, modops: list[tuple[int, str, list[bytes] | None]], controls: list[tuple[str, str, str | None]] | None = None) → None[source]

Process a single LDIF record representing a single modify operation. This method should be implemented by applications using LDIFParser.

class anyldap.ldap.ldif.LDIFWriter(output_file: _TextOutput, base64_attrs: Iterable[str] | None = None, cols: int = 76, line_sep: str = '\n')[source]

Bases: object

Write LDIF entry or change records to file object Copy LDIF input to a file output object containing all data retrieved via URLs

unparse(dn: str, record: Mapping[str, Sequence[bytes]] | Sequence[tuple[str, Sequence[bytes]]] | Sequence[tuple[int, str, Sequence[bytes] | None]]) → None[source]
dn

string-representation of distinguished name

record

Either a dictionary holding the LDAP entry {attrtype:record} or a list with a modify list like for LDAPObject.modify().

anyldap.ldap.ldif.is_dn(s: str) → int[source]

returns 1 if s is a LDAP DN

anyldap.ldap.ldif.list_dict(l: Iterable[str]) → dict[str, None][source]

return a dictionary with all items of l being the keys of the dictionary

anyldap.ldap.asyncsearch module

ldap.asyncsearch: a search read result by result rather than all at once.

A search that would fill memory if it were all asked for at once is read here as it arrives: startSearch() sends the request, and processResults() reads answers and hands each one to _processSingleResult(), which a subclass overrides to do whatever it is collecting or writing. The classes below are python-ldap’s, and are used the same way; only the two methods that touch the connection are awaited.

The hooks – preProcessing(), afterFirstResult(), postProcessing() and _processSingleResult() – are plain methods, as they are in python-ldap: they are called while a result is being dispatched and cannot await.

class anyldap.ldap.asyncsearch.AsyncSearchHandler(l: SimpleLDAPObject)[source]

Bases: object

A search whose results are handled one at a time.

l is the connection to search on.

afterFirstResult() → None[source]

Anything to do once the first result has arrived.

postProcessing() → None[source]

Anything to do once every result has been read.

preProcessing() → None[source]

Anything to do after starting the search, before reading it.

async processResults(ignoreResultsNumber: int = 0, processResultsCount: int = 0, timeout: float = -1) → int[source]

Read the results and hand each one on.

The first ignoreResultsNumber are dropped, and at most processResultsCount are handled after that, or all of them if it is zero. Answers 1 if there are results left unread, which is when the search has been abandoned, and 0 if it ran out.

async startSearch(searchRoot: str, searchScope: int, filterStr: str, attrList: Sequence[str] | None = None, attrsOnly: int = 0, timeout: float = -1, sizelimit: int = 0, serverctrls: Iterable[controls.RequestControl | pureldap.Control] | None = None, clientctrls: Iterable[controls.RequestControl | pureldap.Control] | None = None) → None[source]

Send the search, without waiting for what it answers.

The arguments are search_ext()’s, in the order python-ldap’s startSearch() takes them.

class anyldap.ldap.asyncsearch.Dict(l: SimpleLDAPObject)[source]

Bases: AsyncSearchHandler

Every entry, keyed by its distinguished name.

class anyldap.ldap.asyncsearch.FileWriter(l: SimpleLDAPObject, f: SupportsWrite[Any], headerStr: str | bytes = '', footerStr: str | bytes = '')[source]

Bases: AsyncSearchHandler

Results written to a file as they arrive.

headerStr goes in before the first one and footerStr after the last, which is what the two hooks are for.

postProcessing() → None[source]

The footer, written once every result has been read.

preProcessing() → None[source]

The header, written before any result has been read.

class anyldap.ldap.asyncsearch.IndexedDict(l: SimpleLDAPObject, indexed_attrs: Sequence[str] | None = None)[source]

Bases: Dict

Every entry, and an index of which names hold which values.

class anyldap.ldap.asyncsearch.LDIFWriter(l: SimpleLDAPObject, writer_obj: IO[bytes], headerStr: bytes = b'', footerStr: bytes = b'')[source]

Bases: FileWriter

Results written out as LDIF.

python-ldap takes either a file or one of its own ldif.LDIFWriter objects here; this takes the file, and writes each entry with anyldap.protocols.ldap.ldif.asLDIF(), which is what the rest of anyldap writes LDIF with.

class anyldap.ldap.asyncsearch.List(l: SimpleLDAPObject)[source]

Bases: AsyncSearchHandler

Every result, kept as it came.

Which looks pointless until only a certain part of a long search is wanted, which is what processResults()’s arguments say.

exception anyldap.ldap.asyncsearch.WrongResultType(receivedResultType: int, expectedResultTypes: set[int])[source]

Bases: Exception

A result arrived that a search cannot have answered with.

anyldap.ldap.syncrepl module

ldap.syncrepl: keeping a copy of what a server holds (RFC 4533).

A syncrepl search asks the server to say not only what matches now but what changes afterwards. The server answers each entry with a Sync State control saying whether it is present, added, changed or gone, says where it has got to in Sync Info messages sent while the search runs, and finishes with a Sync Done control carrying the cookie to start again from.

SyncreplConsumer drives that: a connection mixes it in, overrides the syncrepl_* methods to keep whatever copy it is keeping, and calls syncrepl_search() and then syncrepl_poll() until it answers False:

class Consumer(SyncreplConsumer, ldap.SimpleLDAPObject):
    def syncrepl_entry(self, dn, attrs, uuid):
        self.entries[uuid] = (dn, attrs)

async with Consumer(uri) as connection:
    await connection.simple_bind_s(who, cred)
    msgid = await connection.syncrepl_search(base, ldap.SCOPE_SUBTREE)
    while await connection.syncrepl_poll(msgid=msgid):
        pass

Only syncrepl_search() and syncrepl_poll() touch the connection and are awaited; the methods a subclass overrides stay plain, as they are in python-ldap, because they are called while a message is being dispatched.

class anyldap.ldap.syncrepl.SyncDoneControl(criticality: bool = False)[source]

Bases: ResponseControl

The end of a syncrepl search: where it got to, and what that means.

refreshDeletes says whether the server named the entries that are gone, or whether whatever was not sent is what is gone.

controlType: str | None = '1.3.6.1.4.1.4203.1.9.1.3'
decodeControlValue(encodedControlValue: bytes) → None[source]

Read the value the server sent into this control’s own fields.

class anyldap.ldap.syncrepl.SyncInfoMessage(encodedMessage: bytes)[source]

Bases: object

What the server says while a syncrepl search is running.

One of four things: a new cookie on its own, the end of the refresh phase with the entries that are gone named or not named, or a set of entries that are all present or all gone.

responseName = '1.3.6.1.4.1.4203.1.9.1.4'
class anyldap.ldap.syncrepl.SyncRequestControl(criticality: bool = True, cookie: str | bytes | None = None, mode: str = 'refreshOnly', reloadHint: bool = False)[source]

Bases: RequestControl

Ask a search to be a syncrepl search.

mode is refreshOnly to be told what there is now and stop, or refreshAndPersist to be told about changes as they happen. cookie is where a previous search got to, and starting from it asks for what has changed since.

controlType: str | None = '1.3.6.1.4.1.4203.1.9.1.1'
encodeControlValue() → bytes[source]

The value as it goes on the wire, or None when it has none.

class anyldap.ldap.syncrepl.SyncStateControl(criticality: bool = False)[source]

Bases: ResponseControl

What has become of the entry the server just sent.

It is present as it was, newly added, changed, or gone; the entry’s UUID is what says which entry it is from one message to the next.

controlType: str | None = '1.3.6.1.4.1.4203.1.9.1.2'
decodeControlValue(encodedControlValue: bytes) → None[source]

Read the value the server sent into this control’s own fields.

opnames = ('present', 'add', 'modify', 'delete')
class anyldap.ldap.syncrepl.SyncreplConsumer(*args: Any, **kwargs: Any)[source]

Bases: object

A connection that keeps a copy of what the server holds.

Only one syncrepl search can be running on one of these at a time; two at once need two connections, as they do in python-ldap.

syncrepl_delete(uuids: Sequence[str]) → None[source]

These entries are gone.

syncrepl_entry(dn: str, attrs: dict[str, list[bytes]], uuid: str) → None[source]

This entry was added or changed, and this is what it is now.

The cookie that was stored, to start the next search from.

async syncrepl_poll(msgid: int = -1, timeout: float | None = None, all: int = 0) → bool[source]

Read what the search has said, and hand it on.

Answers False once the search is finished and True while it is still running. With all set it reads until the search finishes rather than returning after one message.

syncrepl_present(uuids: Sequence[str] | None, refreshDeletes: bool | None = False) → None[source]

These entries are still there, or the refresh has finished.

Given a list of UUIDs, record them as present. Given None and refreshDeletes false, delete everything that was not recorded and start the record again; given None and refreshDeletes true, start the record again without deleting anything.

syncrepl_refreshdone() → None[source]

The refresh is over, and what follows is what changes next.

Start the search, and answer with the message id it was sent as.

base, scope and the rest are search_ext()’s, with the Sync Request control added to whatever controls were given. The cookie is asked of syncrepl_get_cookie() if it is not passed.

Store a new cookie, which says where the search has got to.

anyldap.ldap.extop package

ldap.extop: the extended operations a server may offer.

An extended operation is a request named by an OID with a value whose shape that OID decides, and a response of the same shape. These classes are what a caller builds one out of: a subclass says what its OID is, writes its request value out and reads its response value back, and extop_s() sends it.

class anyldap.ldap.extop.ExtendedRequest(requestName: str, requestValue: bytes)[source]

Bases: object

An extended operation being asked for.

requestName is the OID that says which operation it is, and requestValue the bytes that go with it. A subclass says which OID it is by naming one here, which is what the connection asks it for.

encodedRequestValue() → bytes[source]

The request value, written out as it goes on the wire.

requestName: str | None = None
class anyldap.ldap.extop.ExtendedResponse(responseName: str | None, encodedResponseValue: bytes)[source]

Bases: object

What the server answered an extended operation with.

A subclass names the OID it reads, which is what extop_s() checks the server answered to before handing the answer over.

decodeResponseValue(value: bytes) → object[source]

Read the response value, and say what it said.

responseName: str | None = None
class anyldap.ldap.extop.PasswordModifyResponse(responseName: str | None, encodedResponseValue: bytes)[source]

Bases: ExtendedResponse

The password the server made up, when it was not given one.

decodeResponseValue(value: bytes) → bytes[source]

Read the response value, and say what it said.

responseName: str | None = None
class anyldap.ldap.extop.RefreshRequest(requestName: str | None = None, entryName: str | None = None, requestTtl: int | None = None)[source]

Bases: ExtendedRequest

Ask that a dynamic entry go on living for another requestTtl.

defaultRequestTtl = 86400
encodedRequestValue() → bytes[source]

The request value, written out as it goes on the wire.

requestName: str | None = '1.3.6.1.4.1.1466.101.119.1'
class anyldap.ldap.extop.RefreshResponse(responseName: str | None, encodedResponseValue: bytes)[source]

Bases: ExtendedResponse

How long the server says the entry has left.

decodeResponseValue(value: bytes) → int[source]

Read the response value, and say what it said.

responseName: str | None = '1.3.6.1.4.1.1466.101.119.1'

ldap.extop.dds: entries that go away by themselves (RFC 2589).

A dynamic entry lives for as long as its time to live says, and a refresh request asks the server to start that clock again.

class anyldap.ldap.extop.dds.RefreshRequest(requestName: str | None = None, entryName: str | None = None, requestTtl: int | None = None)[source]

Bases: ExtendedRequest

Ask that a dynamic entry go on living for another requestTtl.

defaultRequestTtl = 86400
encodedRequestValue() → bytes[source]

The request value, written out as it goes on the wire.

requestName: str | None = '1.3.6.1.4.1.1466.101.119.1'
class anyldap.ldap.extop.dds.RefreshResponse(responseName: str | None, encodedResponseValue: bytes)[source]

Bases: ExtendedResponse

How long the server says the entry has left.

decodeResponseValue(value: bytes) → int[source]

Read the response value, and say what it said.

responseName: str | None = '1.3.6.1.4.1.1466.101.119.1'

ldap.extop.passwd: the password modify operation (RFC 3062).

class anyldap.ldap.extop.passwd.PasswordModifyResponse(responseName: str | None, encodedResponseValue: bytes)[source]

Bases: ExtendedResponse

The password the server made up, when it was not given one.

decodeResponseValue(value: bytes) → bytes[source]

Read the response value, and say what it said.

responseName: str | None = None

anyldap.ldap.schema package

ldap.schema: the schema a server publishes, read into objects.

ldap.schema.models: what a server says its schema is made of.

Each class reads one schema definition – the text a server publishes in its subschema subentry – and answers with the fields python-ldap’s own model classes answer with. The reading itself is anyldap.schema, which the rest of anyldap already parses schema with.

class anyldap.ldap.schema.models.AttributeType(schema_element_str: str | bytes | None = None)[source]

Bases: SchemaElement

An attribute type: how its values are written and compared.

schema_attribute: ClassVar[str] = 'attributeTypes'
class anyldap.ldap.schema.models.DITContentRule(schema_element_str: str | bytes | None = None)[source]

Bases: _TokenisedElement

What an entry of a structural class may hold on top of it.

schema_attribute: ClassVar[str] = 'dITContentRules'
token_defaults: ClassVar[dict[str, tuple[str | None, ...] | None]] = {'AUX': (), 'DESC': (None,), 'MAY': (), 'MUST': (), 'NAME': (), 'NOT': (), 'OBSOLETE': None}
class anyldap.ldap.schema.models.DITStructureRule(schema_element_str: str | bytes | None = None)[source]

Bases: _TokenisedElement

Where in the tree an entry of a name form may be put.

A structure rule is numbered rather than named by an OID, which is what ruleid is; get_id() answers with it.

get_id() → str | None[source]
schema_attribute: ClassVar[str] = 'dITStructureRules'
set_id(element_id: str) → None[source]
token_defaults: ClassVar[dict[str, tuple[str | None, ...] | None]] = {'DESC': (None,), 'FORM': (None,), 'NAME': (), 'OBSOLETE': None, 'SUP': ()}
class anyldap.ldap.schema.models.Entry(schema: subentry.SubSchema, dn: str, entry: Mapping[str, list[bytes]])[source]

Bases: UserDict[Any, list[bytes]]

An entry that knows the schema its attributes are described by.

An attribute is looked up by whichever of its names or its OID the caller has, so entry["cn"], entry["commonName"] and entry["2.5.4.3"] are all the same attribute; sub-types are kept apart, so cn;lang-en is not cn.

attribute_types(attr_type_filter: object = None, raise_keyerror: int = 1) → tuple[dict[str, AttributeType], dict[str, AttributeType]][source]

What an entry of this one’s object classes must and may have.

has_key(nameoroid: str) → bool[source]
items() → a set-like object providing a view on D's items[source]
keys() → a set-like object providing a view on D's keys[source]
update([E, ]**F) → None.  Update D from mapping/iterable E and F.[source]
If E present and has a .keys() method, does:

for k in E.keys(): D[k] = E[k]

If E present and lacks .keys() method, does:

for (k, v) in E: D[k] = v

In either case, this is followed by:

for k, v in F.items(): D[k] = v

class anyldap.ldap.schema.models.LDAPSyntax(schema_element_str: str | bytes | None = None)[source]

Bases: SchemaElement

A syntax: what a value of it looks like.

schema_attribute: ClassVar[str] = 'ldapSyntaxes'
class anyldap.ldap.schema.models.MatchingRule(schema_element_str: str | bytes | None = None)[source]

Bases: SchemaElement

A matching rule: how two values of a syntax are compared.

schema_attribute: ClassVar[str] = 'matchingRules'
class anyldap.ldap.schema.models.MatchingRuleUse(schema_element_str: str | bytes | None = None)[source]

Bases: _TokenisedElement

Which attributes a matching rule may be used with.

schema_attribute: ClassVar[str] = 'matchingRuleUse'
token_defaults: ClassVar[dict[str, tuple[str | None, ...] | None]] = {'APPLIES': (), 'DESC': (None,), 'NAME': (), 'OBSOLETE': None}
class anyldap.ldap.schema.models.NameForm(schema_element_str: str | bytes | None = None)[source]

Bases: _TokenisedElement

Which attributes name an entry of an object class.

schema_attribute: ClassVar[str] = 'nameForms'
token_defaults: ClassVar[dict[str, tuple[str | None, ...] | None]] = {'DESC': (None,), 'MAY': (), 'MUST': (), 'NAME': (), 'OBSOLETE': None, 'OC': (None,)}
class anyldap.ldap.schema.models.ObjectClass(schema_element_str: str | bytes | None = None)[source]

Bases: SchemaElement

An object class: what an entry of it must and may have.

schema_attribute: ClassVar[str] = 'objectClasses'
class anyldap.ldap.schema.models.SchemaElement(schema_element_str: str | bytes | None = None)[source]

Bases: object

One definition out of a schema, whatever kind it is.

get_id() → str | None[source]
key_attr(key: str, value: str | None, quoted: int = 0) → str[source]

One field of a definition, written the way python-ldap writes it.

key_list(key: str, values: tuple[str, ...], sep: str = ' ', quoted: int = 0) → str[source]

A field with several values, written the way python-ldap writes it.

schema_attribute: ClassVar[str] = ''
set_id(element_id: str) → None[source]

ldap.schema.subentry: the schema a server publishes, read into objects.

exception anyldap.ldap.schema.subentry.NameNotUnique(desc: str)[source]

Bases: SubschemaError

Two definitions of the same kind claim one name.

exception anyldap.ldap.schema.subentry.OIDNotUnique(desc: str)[source]

Bases: SubschemaError

Two definitions of the same kind claim one OID.

class anyldap.ldap.schema.subentry.SubSchema(sub_schema_sub_entry: Mapping[str, Iterable[bytes | str]], check_uniqueness: int = 1)[source]

Bases: object

The schema of one server, looked up by name or by OID.

Built from the entry a server publishes at its subschema subentry: the dictionary of attributes a search of it hands back.

attribute_types(object_class_list: Sequence[str], attr_type_filter: object = None, raise_keyerror: int = 1, ignore_dit_content_rule: int = 0) → tuple[dict[str, AttributeType], dict[str, AttributeType]][source]

What entries of these object classes must and may have.

Two dictionaries of attribute types by OID: the ones that are required, and the ones that are allowed.

get_obj(se_class: type[SchemaElement], nameoroid: str, default: SchemaElement | None = None, raise_keyerror: int = 0) → SchemaElement | None[source]

What the server said about this name, or about this OID.

get_structural_oc(oc_list: Sequence[str]) → str | None[source]

The one object class of these that says what the entry is.

getoid(se_class: type[SchemaElement], nameoroid: str, raise_keyerror: int = 0) → str[source]

The OID a name stands for, or the OID itself.

Sub-types are dropped first, so cn;lang-en is asked about as cn. A name the schema does not describe comes back as it was given unless raise_keyerror says to make something of it.

ldap_entry() → dict[str, list[str]][source]

The entry this schema was read from, written out again.

Each element as the definition it would be published as, keyed by the attribute it is published in.

listall(schema_element_class: type[SchemaElement], schema_element_filters: object = None) → list[str][source]

The OIDs of everything of this kind that the server published.

exception anyldap.ldap.schema.subentry.SubschemaError[source]

Bases: ValueError

Something is wrong with the schema a server published.

async anyldap.ldap.schema.subentry.urlfetch(uri: str, trace_level: int = 0) → tuple[str | None, SubSchema | None][source]

The schema an LDAP URL, or an LDIF file, says a server publishes.

An ldap://, ldaps:// or ldapi:// URL is asked: a connection is opened, bound as the URL says to bind, asked where its schema is and then for the schema itself, and closed again – which is what makes this different from read_schema_s() on a connection that is already open. Anything else is the address of an LDIF file, and its first record is the schema, which is what python-ldap makes of one too. Unlike python-ldap’s, which hands the address to urlopen, only file:, http: and https: are read.

ldap.schema.tokenizer: a schema definition, taken apart word by word.

A schema definition is parentheses, bare words and quoted strings, and what each word means depends only on the keyword before it. That is enough to read the kinds of definition that have no parser of their own in anyldap.schema – content rules, structure rules, name forms and matching rule uses – and it is how python-ldap reads every kind.

anyldap.ldap.schema.tokenizer.extract_tokens(l: list[str], known_tokens: dict[str, tuple[str | None, ...] | None]) → dict[str, tuple[str | None, ...] | None][source]

What each keyword was given, for the keywords asked about.

known_tokens says which keywords to look for and what each one is worth when the definition does not mention it.

anyldap.ldap.schema.tokenizer.split_tokens(s: str) → list[str][source]

The elements of a definition, with quotes and spaces stripped.