diff --git a/Doc/bytes_mode.rst b/Doc/bytes_mode.rst index b18c0ae..c775b27 100644 --- a/Doc/bytes_mode.rst +++ b/Doc/bytes_mode.rst @@ -68,6 +68,10 @@ Unspecified: relaxed mode with warnings Text values supplied to python-ldap should be ``unicode``; warnings are emitted when they are not. + The warnings are of type :class:`~ldap.LDAPBytesWarning`, which + is a subclass of :class:`BytesWarning` designed to be easily + :ref:`filtered out ` if needed. + Backwards-compatible behavior is not scheduled for removal until Python 2 itself reaches end of life. @@ -103,3 +107,20 @@ Note that only the result's *values* are of the ``bytes`` type: 'sn': [b'Barrois'], }), ] + + +.. _filter-bytes-warning: + +Filtering warnings +------------------ + +The bytes mode warnings can be filtered out and ignored with a +simple filter. + +.. code-block:: python + + import warnings + import ldap + + if hasattr(ldap, 'LDAPBytesWarning'): + warnings.simplefilter('ignore', ldap.LDAPBytesWarning) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 1f7ae5b..4b163da 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -564,6 +564,18 @@ The above exceptions are raised when a result code from an underlying API call does not indicate success. +Warnings +======== + +.. py:class:: LDAPBytesWarning + + Raised when bytes/text mismatch in non-strict bytes mode. + + See :ref:`bytes_mode` for details. + + .. versionadded:: 3.0.0 + + .. _ldap-objects: LDAPObject classes diff --git a/Lib/ldap/__init__.py b/Lib/ldap/__init__.py index 7cb16b0..3a86095 100644 --- a/Lib/ldap/__init__.py +++ b/Lib/ldap/__init__.py @@ -86,7 +86,7 @@ def release(self): from ldap.functions import open,initialize,init,get_option,set_option,escape_str,strf_secs,strp_secs -from ldap.ldapobject import NO_UNIQUE_ENTRY +from ldap.ldapobject import NO_UNIQUE_ENTRY, LDAPBytesWarning from ldap.dn import explode_dn,explode_rdn,str2dn,dn2str del str2dn diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index c5e62bd..f37ef24 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -14,6 +14,7 @@ 'LDAPObject', 'SimpleLDAPObject', 'ReconnectLDAPObject', + 'LDAPBytesWarning' ] @@ -37,6 +38,12 @@ else: text_type = str + +class LDAPBytesWarning(BytesWarning): + """python-ldap bytes mode warning + """ + + class NO_UNIQUE_ENTRY(ldap.NO_SUCH_OBJECT): """ Exception raised if a LDAP search returned more than entry entry @@ -84,7 +91,7 @@ def __init__( "Under Python 2, python-ldap uses bytes by default. " "This will be removed in Python 3 (no bytes for DN/RDN/field names). " "Please call initialize(..., bytes_mode=False) explicitly.", - BytesWarning, + LDAPBytesWarning, stacklevel=2, ) bytes_mode = True @@ -122,7 +129,7 @@ def _bytesify_input(self, value): warnings.warn( "Received non-bytes value %r with default (disabled) bytes mode; please choose an explicit " "option for bytes_mode on your LDAP connection" % (value,), - BytesWarning, + LDAPBytesWarning, stacklevel=6, ) return value.encode('utf-8') diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 9b6a090..835512b 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -19,6 +19,7 @@ import os import unittest import pickle +import warnings from slapdtest import SlapdTestCase, requires_sasl # Switch off processing .ldaprc or ldap.conf before importing _ldap @@ -314,7 +315,41 @@ def test007_timeout(self): l.abandon(m) with self.assertRaises(ldap.TIMEOUT): result = l.result(m, timeout=0.001) - + + def assertIsSubclass(self, cls, other): + self.assertTrue( + issubclass(cls, other), + cls.__mro__ + ) + + @unittest.skipUnless(PY2, "no bytes_mode under Py3") + def test_ldapbyteswarning(self): + self.assertIsSubclass(ldap.LDAPBytesWarning, BytesWarning) + self.assertIsSubclass(ldap.LDAPBytesWarning, Warning) + self.assertIsInstance(self.server.suffix, text_type) + with warnings.catch_warnings(record=True) as w: + warnings.resetwarnings() + warnings.simplefilter('default') + conn = self._get_bytes_ldapobject(explicit=False) + result = conn.search_s( + self.server.suffix, + ldap.SCOPE_SUBTREE, + b'(cn=Foo*)', + attrlist=[b'*'], + ) + self.assertEqual(len(result), 4) + + # ReconnectLDAP only emits one warning + self.assertGreaterEqual(len(w), 1, w) + msg = w[-1] + self.assertIs(msg.category, ldap.LDAPBytesWarning) + self.assertEqual( + text_type(msg.message), + "Received non-bytes value u'%s' with default (disabled) bytes " + "mode; please choose an explicit option for bytes_mode on your " + "LDAP connection" % self.server.suffix + ) + class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject): """