Skip to content

Commit a3723bc

Browse files
tiranencukou
authored andcommitted
When raising LDAPBytesWarning, walk the stack to determine stacklevel
Closes: python-ldap#108 Signed-off-by: Christian Heimes <cheimes@redhat.com> Some simplification by: Petr Viktorin
1 parent cf24a54 commit a3723bc

File tree

3 files changed

+89
-2
lines changed

3 files changed

+89
-2
lines changed

Lib/ldap/ldapobject.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
text_type = str
4040

4141

42+
# See SimpleLDAPObject._bytesify_input
43+
_LDAP_WARN_SKIP_FRAME = True
44+
4245
class LDAPBytesWarning(BytesWarning):
4346
"""python-ldap bytes mode warning
4447
"""
@@ -126,11 +129,27 @@ def _bytesify_input(self, value):
126129
if self.bytes_mode_hardfail:
127130
raise TypeError("All provided fields *must* be bytes when bytes mode is on; got %r" % (value,))
128131
else:
132+
# Raise LDAPBytesWarning.
133+
# Call stacks with _bytesify_input tend to be complicated, so
134+
# getting a useful stacklevel is tricky.
135+
# We walk stack frames, ignoring all functions in this file
136+
# and in the _ldap extension, based on a marker in globals().
137+
stacklevel = 0
138+
try:
139+
getframe = sys._getframe
140+
except AttributeError:
141+
pass
142+
else:
143+
frame = sys._getframe(stacklevel)
144+
# walk up the stacks until we leave the file
145+
while frame and frame.f_globals.get('_LDAP_WARN_SKIP_FRAME'):
146+
stacklevel += 1
147+
frame = frame.f_back
129148
warnings.warn(
130149
"Received non-bytes value %r with default (disabled) bytes mode; please choose an explicit "
131150
"option for bytes_mode on your LDAP connection" % (value,),
132151
LDAPBytesWarning,
133-
stacklevel=6,
152+
stacklevel=stacklevel+1,
134153
)
135154
return value.encode('utf-8')
136155
else:

Modules/ldapmodule.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ PyObject* init_ldap_module(void)
7272
LDAPinit_functions(d);
7373
LDAPinit_control(d);
7474

75+
/* Marker for LDAPBytesWarning stack walking
76+
* see SimpleLDAPObject._bytesify_input in ldapobject.py
77+
*/
78+
if (PyModule_AddIntConstant(m, "_LDAP_WARN_SKIP_FRAME", 1) != 0) {
79+
return NULL;
80+
}
81+
7582
/* Check for errors */
7683
if (PyErr_Occurred())
7784
Py_FatalError("can't initialize module _ldap");

Tests/t_ldapobject.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@
1616
PY2 = False
1717
text_type = str
1818

19+
import contextlib
20+
import linecache
1921
import os
2022
import unittest
23+
import warnings
2124
import pickle
2225
import warnings
2326
from slapdtest import SlapdTestCase, requires_sasl
@@ -329,7 +332,7 @@ def test_ldapbyteswarning(self):
329332
self.assertIsInstance(self.server.suffix, text_type)
330333
with warnings.catch_warnings(record=True) as w:
331334
warnings.resetwarnings()
332-
warnings.simplefilter('default')
335+
warnings.simplefilter('always', ldap.LDAPBytesWarning)
333336
conn = self._get_bytes_ldapobject(explicit=False)
334337
result = conn.search_s(
335338
self.server.suffix,
@@ -350,6 +353,64 @@ def test_ldapbyteswarning(self):
350353
"LDAP connection" % self.server.suffix
351354
)
352355

356+
@contextlib.contextmanager
357+
def catch_byteswarnings(self, *args, **kwargs):
358+
with warnings.catch_warnings(record=True) as w:
359+
conn = self._get_bytes_ldapobject(*args, **kwargs)
360+
warnings.resetwarnings()
361+
warnings.simplefilter('always', ldap.LDAPBytesWarning)
362+
yield conn, w
363+
364+
def _test_byteswarning_level_search(self, methodname):
365+
with self.catch_byteswarnings(explicit=False) as (conn, w):
366+
method = getattr(conn, methodname)
367+
result = method(
368+
self.server.suffix.encode('utf-8'),
369+
ldap.SCOPE_SUBTREE,
370+
'(cn=Foo*)',
371+
attrlist=['*'], # CORRECT LINE
372+
)
373+
self.assertEqual(len(result), 4)
374+
375+
self.assertEqual(len(w), 2, w)
376+
377+
def _normalize(filename):
378+
# Python 2 likes to report the ".pyc" file in warnings,
379+
# tracebacks or __file__.
380+
# Use the corresponding ".py" in that case.
381+
if filename.endswith('.pyc'):
382+
return filename[:-1]
383+
return filename
384+
385+
self.assertIs(w[0].category, ldap.LDAPBytesWarning)
386+
self.assertIn(
387+
u"Received non-bytes value u'(cn=Foo*)'",
388+
text_type(w[0].message)
389+
)
390+
self.assertEqual(_normalize(w[1].filename), _normalize(__file__))
391+
self.assertEqual(_normalize(w[0].filename), _normalize(__file__))
392+
self.assertIn(
393+
'CORRECT LINE',
394+
linecache.getline(w[0].filename, w[0].lineno)
395+
)
396+
397+
self.assertIs(w[1].category, ldap.LDAPBytesWarning)
398+
self.assertIn(
399+
u"Received non-bytes value u'*'",
400+
text_type(w[1].message)
401+
)
402+
self.assertIn(_normalize(w[1].filename), _normalize(__file__))
403+
self.assertIn(
404+
'CORRECT LINE',
405+
linecache.getline(w[1].filename, w[1].lineno)
406+
)
407+
408+
@unittest.skipUnless(PY2, "no bytes_mode under Py3")
409+
def test_byteswarning_level_search(self):
410+
self._test_byteswarning_level_search('search_s')
411+
self._test_byteswarning_level_search('search_st')
412+
self._test_byteswarning_level_search('search_ext_s')
413+
353414

354415
class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject):
355416
"""

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