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-)
str
sowohl 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-)
str
akzeptiert 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
bytes
verwendet werden. - Ebenso liefern die Aufrufe auch diese Typen, d.h. alles
unicode
bis 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:
-
error
veranlasst einenTypeError
(Standard sobaldbytes_mode
angegeben ist) -
warn
gibt eine Warnung (Standard fallsbytes_mode
nicht angegeben ist) aus -
silent
fü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=False
um 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-ldap
verwendet, 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
ldap
um 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-Namekey
eben schon eine Unicode-Zeichenkette ist aber der Attribute-Wertvalue
eben noch eine Byte-Sequenz ist.