Python 3.8: LDAP
Wie in Python 3.3: unicode bereits beschrieben unterschiedet Python 2 deutlich strikter zwischen Unicode-Zeichenketten und Byte-Strings.
Das wirk sich auch auf LDAP aus und python-ldap ≥ 3 hat dafür zwei neuen Parameter bytes_mode und bytes_strictness bekommen, die das Verhalten bereits für Python 2 grundlegend ändert.
Ersterer kontrolliert primär das Ausgabeformat, zweiterer kontrolliert die akzeptierten Typen der Eingabeparameter.
Python 2
Zur Wiederholung:
Zeichenketten sind Byte-Strings (str = bytes), die (mehr oder minder zufällig) UTF-8 kodierte Zeichenketten enthalten.
python-ldap 2.4.28 (UCS-4)
python-ldap akzeptiert nur (byte-)str und wirft bei unicode sogar einen Fehler:
>>> import ldap
>>> ldap.__version__
'2.4.28'
>>> lo = ldap.initialize("ldapi://")
>>> lo.search_s(u"", ldap.SCOPE_BASE, u'(objectClass=*)', [u'structuralObjectClass'])
TypeError: ('expected string in list', u'structuralObjectClass')
>>> lo.search_s(b"", ldap.SCOPE_BASE, b'(objectClass=*)', [b'structuralObjectClass'])
[("", {'structuralObjectClass': ['OpenLDAProotDSE']})]
python-ldap 3.1-0 (Backport bzw. UCS-5)
python-ldap unterstützt nun den Parameter bytes_mode und zeigt dann unterschiedliches Verhalten:
ohne bytes_mode
>>> import ldap
>>> ldap.__version__
'3.1.0'
>>> lo = ldap.initialize("ldapi://") # Lazy = implicit-auto-codec
>>> lo.search_s(u"", ldap.SCOPE_BASE, u'(objectClass=*)', [u'structuralObjectClass'])
[("", {'structuralObjectClass': ['OpenLDAProotDSE']})]
>>> lo.search_s(b"", ldap.SCOPE_BASE, b'(objectClass=*)', [b'structuralObjectClass'])
[("", {'structuralObjectClass': ['OpenLDAProotDSE']})]
- es werden zusätzlich
unicode-Zeichenketten als Eingabe-Parameter akzeptiert - weiterhin werden (byte-)
strsowohl für Attribute alsauch Werte geliefert
mit bytes_mode=True (Legacy Python 2 mode)
>>> lo = ldap.initialize("ldapi://", bytes_mode=True) # Python2
>>> lo.search_s(u"", ldap.SCOPE_BASE, u'(objectClass=*)', [u'structuralObjectClass'])
TypeError: All provided fields *must* be bytes when bytes mode is on; got type 'unicode' for 'base'.
>>> lo.search_s(b"", ldap.SCOPE_BASE, b'(objectClass=*)', [b'structuralObjectClass'])
[("", {'structuralObjectClass': ['OpenLDAProotDSE']})]
- wie früher werden nur (byte-)
strakzeptiert und auch gelifert.
mit bytes_mode=False (Python 3 mode)
>>> lo = ldap.initialize("ldapi://", bytes_mode=False) # Python3
>>> lo.search_s(u"", ldap.SCOPE_BASE, u'(objectClass=*)', [u'structuralObjectClass'])
[(u"", {u'structuralObjectClass': 'OpenLDAProotDSE']})]
>>> lo.search_s(b"", ldap.SCOPE_BASE, b'(objectClass=*)', [b'structuralObjectClass'])
TypeError: All provided fields *must* be text when bytes mode is off; got type 'str' for 'base'.
- Für DNs, Filter und Attribut-Namen sind nun
unicode-Zeichenketten als Eingabe-Parameter pflicht. - Für Attribut-Werte müssen dagegen
bytesverwendet werden. - Ebenso liefern die Aufrufe auch diese Typen, d.h. alles
unicodebis auf die Attribute-Werte.
Python 3
Zur Wiederholung:
Zeichenketten sind Unicode-Zeichenketten (str = unicode).
Man kann weiterhin u"…" schreiben, aber bei der Ausgabe wird der u-Prefix nicht angezeigt.
Dafür werden byte-Strings als b"…" angezeigt.
python-ldap kennt zwar den Parameter bytes_mode noch, aber dieser akzeptiert nur noch False:
>>> import ldap
>>> ldap.__version__
'3.1.0'
>>> lo = ldap.initialize("ldapi://", bytes_mode=True)
ValueError: bytes_mode is *not* supported under Python 3.
Ob man diesen dann explizit auf False setzt oder ihn weg lässt macht dann keinen Unterschied mehr:
>>> lo = ldap.initialize("ldapi://")
>>> lo.search_s(u"", ldap.SCOPE_BASE, u'(objectClass=*)", [u"structuralObjectClass"])
[("", {"structuralObjectClass": [b"OpenLDAProotDSE"]})]
>>> lo.search_s(b"", ldap.SCOPE_BASE, b'(objectClass=*)", [b"structuralObjectClass"])
TypeError: search_ext() argument 1 must be str, not bytes
- man hat hier dann das gleiche Verhalten wie unter Python2 mit
bytes_mode=False. - DNs, Filter und Attribut-Namen sind als
unicode-Zeichenkette anzugeben. - Attribute-Werte dagegen als
bytes.
bytes_strictness
In Python 2 (und nur da!) gibt es den weiteren Parameter bytes_strictness, mit dem gesteuert werden kann, welche Type für Eingabe-Parameter akzeptiert wird:
-
errorveranlasst einenTypeError(Standard sobaldbytes_modeangegeben ist) -
warngibt eine Warnung (Standard fallsbytes_modenicht angegeben ist) aus -
silentführt zu einer stillen und automatischen En- bzw. Dekodierung.
In Python 3 hast man immer das Verhalten von bytes_strictness="error"!
Zusammenfassung
bytes_mode |
2.4 | 3.1 True (legacy) | 3.1 None | 3.1 False (Python3) |
|---|---|---|---|---|
| DN,Filter,A.Name IN | b | b / U | U | |
| DN,Filter,A.Name OUT | b | |||
| A.Wert IN | b / U | b | ||
| A.Wert OUT | b | |||
| Verhalten | bisher | neu |
Konsequenz
- Im python3-Branch verwenden wir derzeit
bytes_mode=Falseum bereits jetzt bei der Entwicklung in UCS-4 das zukünftige Verhalten zu bekommen. - Das führt damit jetzt zu einem
TypeError, wenn man den falschen Typ verwendet. - Deswegen setzten wir derzeit
bytes_strictness=warn, um daraus eine Warnung zu machen und eine automatische Dekodierung von UTF-8-Bytesequenzen zu erhalten. - Wenn man die low-level Funktionen direkt von
python-ldapverwendet, muss man deshalb sehr genau darauf aufpassen, dass man die richtigen Typen verwendet und ein.encode('utf-8')bzw..decode('utf-8')hinzufügt. - Um den Code jetzt noch mit UCS-4.4 lauffähig zu halten sind wir deshalb dazu übergegangen, jeweils explizit bei den Zeichenketten anzugeben, ob es sich bei dem Argument für
ldapum eineb"…"oderu"…"handelt. - Für UDM ist der Plan, dieses Detail-Wissen über “Attribut-Werte sind
bytes” zu verbergen: Die wenigsten Attribute enthalten Binärdaten (jpegPhoto,userCertificate, …) so dass wir vereinfacht auch dort alle Property-Werte alsunicode-Text behandeln wollen. - Das führt insgesamt derzeit zu unschönen Konstrukten, wenn man z.B. einen LDAP-Filter-String zusammenbaut:
filterstr=u"(%s=%s)" % (key, val.decode("utf-8"),), weil der Attribute-Namekeyeben schon eine Unicode-Zeichenkette ist aber der Attribute-Wertvalueeben noch eine Byte-Sequenz ist.