Skip to content

Commit deb411c

Browse files
authored
Merge pull request #171 – Add bytes_strictness
#171
2 parents 671d9f1 + 2920ac2 commit deb411c

File tree

4 files changed

+177
-58
lines changed

4 files changed

+177
-58
lines changed

Doc/bytes_mode.rst

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,37 +43,51 @@ Encoding/decoding to other formats – text, images, etc. – is left to the cal
4343
The bytes mode
4444
--------------
4545

46-
The behavior of python-ldap 3.0 in Python 2 is influenced by a ``bytes_mode``
47-
argument to :func:`ldap.initialize`.
48-
The argument can take these values:
46+
In Python 3, text values are represented as ``str``, the Unicode text type.
4947

50-
``bytes_mode=True``: backwards-compatible
48+
In Python 2, the behavior of python-ldap 3.0 is influenced by a ``bytes_mode``
49+
argument to :func:`ldap.initialize`:
5150

52-
Text values returned from python-ldap are always bytes (``str``).
53-
Text values supplied to python-ldap may be either bytes or Unicode.
54-
The encoding for bytes is always assumed to be UTF-8.
51+
``bytes_mode=True`` (backwards compatible):
52+
Text values are represented as bytes (``str``) encoded using UTF-8.
5553

56-
Not available in Python 3.
54+
``bytes_mode=False`` (future compatible):
55+
Text values are represented as ``unicode``.
5756

58-
``bytes_mode=False``: strictly future-compatible
57+
If not given explicitly, python-ldap will default to ``bytes_mode=True``,
58+
but if an ``unicode`` value supplied to it, if will warn and use that value.
5959

60-
Text values must be represented as ``unicode``.
61-
An error is raised if python-ldap receives a text value as bytes (``str``).
60+
Backwards-compatible behavior is not scheduled for removal until Python 2
61+
itself reaches end of life.
6262

63-
Unspecified: relaxed mode with warnings
6463

65-
Causes a warning on Python 2.
64+
Errors, warnings, and automatic encoding
65+
----------------------------------------
6666

67-
Text values returned from python-ldap are always ``unicode``.
68-
Text values supplied to python-ldap should be ``unicode``;
69-
warnings are emitted when they are not.
67+
While the type of values *returned* from python-ldap is always given by
68+
``bytes_mode``, for Python 2 the behavior for “wrong-type” values *passed in*
69+
can be controlled by the ``bytes_strictness`` argument to
70+
:func:`ldap.initialize`:
7071

71-
The warnings are of type :class:`~ldap.LDAPBytesWarning`, which
72-
is a subclass of :class:`BytesWarning` designed to be easily
73-
:ref:`filtered out <filter-bytes-warning>` if needed.
72+
``bytes_strictness='error'`` (default if ``bytes_mode`` is specified):
73+
A ``TypeError`` is raised.
7474

75-
Backwards-compatible behavior is not scheduled for removal until Python 2
76-
itself reaches end of life.
75+
``bytes_strictness='warn'`` (default when ``bytes_mode`` is not given explicitly):
76+
A warning is raised, and the value is encoded/decoded
77+
using the UTF-8 encoding.
78+
79+
The warnings are of type :class:`~ldap.LDAPBytesWarning`, which
80+
is a subclass of :class:`BytesWarning` designed to be easily
81+
:ref:`filtered out <filter-bytes-warning>` if needed.
82+
83+
``bytes_strictness='silent'``:
84+
The value is automatically encoded/decoded using the UTF-8 encoding.
85+
86+
On Python 3, ``bytes_strictness`` is ignored and a ``TypeError`` is always
87+
raised.
88+
89+
When setting ``bytes_strictness``, an explicit value for ``bytes_mode`` needs
90+
to be given as well.
7791

7892

7993
Porting recommendations

Doc/reference/ldap.rst

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Functions
2929

3030
This module defines the following functions:
3131

32-
.. py:function:: initialize(uri [, trace_level=0 [, trace_file=sys.stdout [, trace_stack_limit=None, [bytes_mode=None]]]]) -> LDAPObject object
32+
.. py:function:: initialize(uri [, trace_level=0 [, trace_file=sys.stdout [, trace_stack_limit=None, [bytes_mode=None, [bytes_strictness=None]]]]]) -> LDAPObject object
3333
3434
Initializes a new connection object for accessing the given LDAP server,
3535
and return an LDAP object (see :ref:`ldap-objects`) used to perform operations
@@ -53,7 +53,8 @@ This module defines the following functions:
5353
*trace_file* specifies a file-like object as target of the debug log and
5454
*trace_stack_limit* specifies the stack limit of tracebacks in debug log.
5555

56-
The *bytes_mode* argument specifies text/bytes behavior under Python 2.
56+
The *bytes_mode* and *bytes_strictness* arguments specify text/bytes
57+
behavior under Python 2.
5758
See :ref:`text-bytes` for a complete documentation.
5859

5960
Possible values for *trace_level* are
@@ -696,6 +697,9 @@ and wait for and return with the server's result, or with
696697

697698
*serverctrls* and *clientctrls* like described in section :ref:`ldap-controls`.
698699

700+
The *dn* argument, and mod_type (second item) of *modlist* are text strings;
701+
see :ref:`bytes_mode`.
702+
699703

700704
.. py:method:: LDAPObject.bind(who, cred, method) -> int
701705
@@ -737,6 +741,8 @@ and wait for and return with the server's result, or with
737741

738742
*serverctrls* and *clientctrls* like described in section :ref:`ldap-controls`.
739743

744+
The *dn* and *attr* arguments are text strings; see :ref:`bytes_mode`.
745+
740746
.. note::
741747

742748
A design fault in the LDAP API prevents *value*
@@ -757,6 +763,8 @@ and wait for and return with the server's result, or with
757763

758764
*serverctrls* and *clientctrls* like described in section :ref:`ldap-controls`.
759765

766+
The *dn* argument is text string; see :ref:`bytes_mode`.
767+
760768

761769
.. py:method:: LDAPObject.extop(extreq[,serverctrls=None[,clientctrls=None]]]) -> int
762770
@@ -810,6 +818,9 @@ and wait for and return with the server's result, or with
810818
You might want to look into sub-module :py:mod:`ldap.modlist` for
811819
generating *modlist*.
812820

821+
The *dn* argument, and mod_type (second item) of *modlist* are text strings;
822+
see :ref:`bytes_mode`.
823+
813824

814825
.. py:method:: LDAPObject.modrdn(dn, newrdn [, delold=1]) -> int
815826
@@ -826,6 +837,8 @@ and wait for and return with the server's result, or with
826837
This operation is emulated by :py:meth:`rename()` and :py:meth:`rename_s()` methods
827838
since the modrdn2* routines in the C library are deprecated.
828839

840+
The *dn* and *newrdn* arguments are text strings; see :ref:`bytes_mode`.
841+
829842

830843
.. py:method:: LDAPObject.passwd(user, oldpw, newpw [, serverctrls=None [, clientctrls=None]]) -> int
831844
@@ -844,6 +857,8 @@ and wait for and return with the server's result, or with
844857

845858
The asynchronous version returns the initiated message id.
846859

860+
The *user*, *oldpw* and *newpw* arguments are text strings; see :ref:`bytes_mode`.
861+
847862
.. seealso::
848863

849864
:rfc:`3062` - LDAP Password Modify Extended Operation
@@ -865,6 +880,8 @@ and wait for and return with the server's result, or with
865880

866881
*serverctrls* and *clientctrls* like described in section :ref:`ldap-controls`.
867882

883+
The *dn* and *newdn* arguments are text strings; see :ref:`bytes_mode`.
884+
868885

869886
.. py:method:: LDAPObject.result([msgid=RES_ANY [, all=1 [, timeout=None]]]) -> 2-tuple
870887
@@ -1015,12 +1032,13 @@ and wait for and return with the server's result, or with
10151032

10161033
*serverctrls* and *clientctrls* like described in section :ref:`ldap-controls`.
10171034

1035+
The *who* and *cred* arguments are text strings; see :ref:`bytes_mode`.
1036+
10181037
.. versionchanged:: 3.0
10191038

10201039
:meth:`~LDAPObject.simple_bind` and :meth:`~LDAPObject.simple_bind_s`
10211040
now accept ``None`` for *who* and *cred*, too.
10221041

1023-
10241042
.. py:method:: LDAPObject.search(base, scope [,filterstr='(objectClass=*)' [, attrlist=None [, attrsonly=0]]]) ->int
10251043
10261044
.. py:method:: LDAPObject.search_s(base, scope [,filterstr='(objectClass=*)' [, attrlist=None [, attrsonly=0]]]) ->list|None
@@ -1073,6 +1091,9 @@ and wait for and return with the server's result, or with
10731091
or :py:meth:`search_ext_s()` (client-side search limit). If non-zero
10741092
not more than *sizelimit* results are returned by the server.
10751093

1094+
The *base* and *filterstr* arguments, and *attrlist* contents,
1095+
are text strings; see :ref:`bytes_mode`.
1096+
10761097
.. versionchanged:: 3.0
10771098

10781099
``filterstr=None`` is equivalent to ``filterstr='(objectClass=*)'``.

Lib/ldap/ldapobject.py

Lines changed: 52 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ class SimpleLDAPObject:
9393

9494
def __init__(
9595
self,uri,
96-
trace_level=0,trace_file=None,trace_stack_limit=5,bytes_mode=None
96+
trace_level=0,trace_file=None,trace_stack_limit=5,bytes_mode=None,
97+
bytes_strictness=None,
9798
):
9899
self._trace_level = trace_level
99100
self._trace_file = trace_file or sys.stdout
@@ -107,20 +108,26 @@ def __init__(
107108
# Bytes mode
108109
# ----------
109110

110-
# By default, raise a TypeError when receiving invalid args
111-
self.bytes_mode_hardfail = True
112-
if bytes_mode is None and PY2:
113-
_raise_byteswarning(
114-
"Under Python 2, python-ldap uses bytes by default. "
115-
"This will be removed in Python 3 (no bytes for DN/RDN/field names). "
116-
"Please call initialize(..., bytes_mode=False) explicitly.")
117-
bytes_mode = True
118-
# Disable hard failure when running in backwards compatibility mode.
119-
self.bytes_mode_hardfail = False
120-
elif bytes_mode and not PY2:
121-
raise ValueError("bytes_mode is *not* supported under Python 3.")
122-
# On by default on Py2, off on Py3.
111+
if PY2:
112+
if bytes_mode is None:
113+
bytes_mode = True
114+
if bytes_strictness is None:
115+
_raise_byteswarning(
116+
"Under Python 2, python-ldap uses bytes by default. "
117+
"This will be removed in Python 3 (no bytes for "
118+
"DN/RDN/field names). "
119+
"Please call initialize(..., bytes_mode=False) explicitly.")
120+
bytes_strictness = 'warn'
121+
else:
122+
if bytes_strictness is None:
123+
bytes_strictness = 'error'
124+
else:
125+
if bytes_mode:
126+
raise ValueError("bytes_mode is *not* supported under Python 3.")
127+
bytes_mode = False
128+
bytes_strictness = 'error'
123129
self.bytes_mode = bytes_mode
130+
self.bytes_strictness = bytes_strictness
124131

125132
def _bytesify_input(self, arg_name, value):
126133
"""Adapt a value following bytes_mode in Python 2.
@@ -130,38 +137,46 @@ def _bytesify_input(self, arg_name, value):
130137
With bytes_mode ON, takes bytes or None and returns bytes or None.
131138
With bytes_mode OFF, takes unicode or None and returns bytes or None.
132139
133-
This function should be applied on all text inputs (distinguished names
134-
and attribute names in modlists) to convert them to the bytes expected
135-
by the C bindings.
140+
For the wrong argument type (unicode or bytes, respectively),
141+
behavior depends on the bytes_strictness setting.
142+
In all cases, bytes or None are returned (or an exception is raised).
136143
"""
137144
if not PY2:
138145
return value
139-
140146
if value is None:
141147
return value
148+
142149
elif self.bytes_mode:
143150
if isinstance(value, bytes):
144151
return value
152+
elif self.bytes_strictness == 'silent':
153+
pass
154+
elif self.bytes_strictness == 'warn':
155+
_raise_byteswarning(
156+
"Received non-bytes value for '{}' in bytes mode; "
157+
"please choose an explicit "
158+
"option for bytes_mode on your LDAP connection".format(arg_name))
145159
else:
146-
if self.bytes_mode_hardfail:
147160
raise TypeError(
148161
"All provided fields *must* be bytes when bytes mode is on; "
149162
"got type '{}' for '{}'.".format(type(value).__name__, arg_name)
150163
)
151-
else:
152-
_raise_byteswarning(
153-
"Received non-bytes value for '{}' with default (disabled) bytes mode; "
154-
"please choose an explicit "
155-
"option for bytes_mode on your LDAP connection".format(arg_name))
156-
return value.encode('utf-8')
164+
return value.encode('utf-8')
157165
else:
158-
if not isinstance(value, text_type):
166+
if isinstance(value, unicode):
167+
return value.encode('utf-8')
168+
elif self.bytes_strictness == 'silent':
169+
pass
170+
elif self.bytes_strictness == 'warn':
171+
_raise_byteswarning(
172+
"Received non-text value for '{}' with bytes_mode off and "
173+
"bytes_strictness='warn'".format(arg_name))
174+
else:
159175
raise TypeError(
160176
"All provided fields *must* be text when bytes mode is off; "
161177
"got type '{}' for '{}'.".format(type(value).__name__, arg_name)
162178
)
163-
assert not isinstance(value, bytes)
164-
return value.encode('utf-8')
179+
return value
165180

166181
def _bytesify_modlist(self, arg_name, modlist, with_opcode):
167182
"""Adapt a modlist according to bytes_mode.
@@ -1064,7 +1079,7 @@ class ReconnectLDAPObject(SimpleLDAPObject):
10641079
def __init__(
10651080
self,uri,
10661081
trace_level=0,trace_file=None,trace_stack_limit=5,bytes_mode=None,
1067-
retry_max=1,retry_delay=60.0
1082+
bytes_strictness=None, retry_max=1, retry_delay=60.0
10681083
):
10691084
"""
10701085
Parameters like SimpleLDAPObject.__init__() with these
@@ -1078,7 +1093,9 @@ def __init__(
10781093
self._uri = uri
10791094
self._options = []
10801095
self._last_bind = None
1081-
SimpleLDAPObject.__init__(self,uri,trace_level,trace_file,trace_stack_limit,bytes_mode)
1096+
SimpleLDAPObject.__init__(self, uri, trace_level, trace_file,
1097+
trace_stack_limit, bytes_mode,
1098+
bytes_strictness=bytes_strictness)
10821099
self._reconnect_lock = ldap.LDAPLock(desc='reconnect lock within %s' % (repr(self)))
10831100
self._retry_max = retry_max
10841101
self._retry_delay = retry_delay
@@ -1097,6 +1114,11 @@ def __getstate__(self):
10971114

10981115
def __setstate__(self,d):
10991116
"""set up the object from pickled data"""
1117+
hardfail = d.get('bytes_mode_hardfail')
1118+
if hardfail:
1119+
d.setdefault('bytes_strictness', 'error')
1120+
else:
1121+
d.setdefault('bytes_strictness', 'warn')
11001122
self.__dict__.update(d)
11011123
self._last_bind = getattr(SimpleLDAPObject, self._last_bind[0]), self._last_bind[1], self._last_bind[2]
11021124
self._ldap_object_lock = self._ldap_lock()

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy