Skip to content

Commit e815a61

Browse files
committed
[PoC] Fix stacklevel for warning
Closes: #108 Signed-off-by: Christian Heimes <cheimes@redhat.com>
1 parent 084ffe0 commit e815a61

File tree

2 files changed

+113
-55
lines changed

2 files changed

+113
-55
lines changed

Lib/ldap/ldapobject.py

Lines changed: 60 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class SimpleLDAPObject:
4848
"""
4949
Drop-in wrapper class around _ldap.LDAPObject
5050
"""
51+
_stacklevel = 3
5152

5253
CLASSATTR_OPTION_MAPPING = {
5354
"protocol_version": ldap.OPT_PROTOCOL_VERSION,
@@ -95,7 +96,7 @@ def __init__(
9596
# On by default on Py2, off on Py3.
9697
self.bytes_mode = bytes_mode
9798

98-
def _bytesify_input(self, value):
99+
def _bytesify_input(self, value, stack_mod=0):
99100
"""Adapt a value following bytes_mode in Python 2.
100101
101102
In Python 3, returns the original value unmodified.
@@ -123,7 +124,7 @@ def _bytesify_input(self, value):
123124
"Received non-bytes value %r with default (disabled) bytes mode; please choose an explicit "
124125
"option for bytes_mode on your LDAP connection" % (value,),
125126
BytesWarning,
126-
stacklevel=6,
127+
stacklevel=self._stacklevel + stack_mod,
127128
)
128129
return value.encode('utf-8')
129130
else:
@@ -132,21 +133,6 @@ def _bytesify_input(self, value):
132133
assert not isinstance(value, bytes)
133134
return value.encode('utf-8')
134135

135-
def _bytesify_inputs(self, *values):
136-
"""Adapt values following bytes_mode.
137-
138-
Applies _bytesify_input on each arg.
139-
140-
Usage:
141-
>>> a, b, c = self._bytesify_inputs(a, b, c)
142-
"""
143-
if not PY2:
144-
return values
145-
return (
146-
self._bytesify_input(value)
147-
for value in values
148-
)
149-
150136
def _bytesify_modlist(self, modlist, with_opcode):
151137
"""Adapt a modlist according to bytes_mode.
152138
@@ -159,12 +145,12 @@ def _bytesify_modlist(self, modlist, with_opcode):
159145
return modlist
160146
if with_opcode:
161147
return tuple(
162-
(op, self._bytesify_input(attr), val)
148+
(op, self._bytesify_input(attr, stack_mod=1), val)
163149
for op, attr, val in modlist
164150
)
165151
else:
166152
return tuple(
167-
(self._bytesify_input(attr), val)
153+
(self._bytesify_input(attr, stack_mod=1), val)
168154
for attr, val in modlist
169155
)
170156

@@ -373,8 +359,9 @@ def add_ext(self,dn,modlist,serverctrls=None,clientctrls=None):
373359
The parameter modlist is similar to the one passed to modify(),
374360
except that no operation integer need be included in the tuples.
375361
"""
376-
dn = self._bytesify_input(dn)
377-
modlist = self._bytesify_modlist(modlist, with_opcode=False)
362+
if PY2:
363+
dn = self._bytesify_input(dn)
364+
modlist = self._bytesify_modlist(modlist, with_opcode=False)
378365
return self._ldap_call(self._l.add_ext,dn,modlist,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
379366

380367
def add_ext_s(self,dn,modlist,serverctrls=None,clientctrls=None):
@@ -399,7 +386,9 @@ def simple_bind(self,who='',cred='',serverctrls=None,clientctrls=None):
399386
"""
400387
simple_bind([who='' [,cred='']]) -> int
401388
"""
402-
who, cred = self._bytesify_inputs(who, cred)
389+
if PY2:
390+
who = self._bytesify_input(who)
391+
cred = self._bytesify_input(cred)
403392
return self._ldap_call(self._l.simple_bind,who,cred,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
404393

405394
def simple_bind_s(self,who='',cred='',serverctrls=None,clientctrls=None):
@@ -458,7 +447,7 @@ def sasl_bind_s(self,dn,mechanism,cred,serverctrls=None,clientctrls=None):
458447
"""
459448
return self._ldap_call(self._l.sasl_bind_s,dn,mechanism,cred,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
460449

461-
def compare_ext(self,dn,attr,value,serverctrls=None,clientctrls=None):
450+
def compare_ext(self,dn,attr,value,serverctrls=None,clientctrls=None, _stackup=0):
462451
"""
463452
compare_ext(dn, attr, value [,serverctrls=None[,clientctrls=None]]) -> int
464453
compare_ext_s(dn, attr, value [,serverctrls=None[,clientctrls=None]]) -> int
@@ -476,11 +465,13 @@ def compare_ext(self,dn,attr,value,serverctrls=None,clientctrls=None):
476465
A design bug in the library prevents value from containing
477466
nul characters.
478467
"""
479-
dn, attr = self._bytesify_inputs(dn, attr)
468+
if PY2:
469+
dn = self._bytesify_input(dn, _stackup)
470+
attr = self._bytesify_input(attr, _stackup)
480471
return self._ldap_call(self._l.compare_ext,dn,attr,value,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
481472

482-
def compare_ext_s(self,dn,attr,value,serverctrls=None,clientctrls=None):
483-
msgid = self.compare_ext(dn,attr,value,serverctrls,clientctrls)
473+
def compare_ext_s(self,dn,attr,value,serverctrls=None,clientctrls=None, _stackup=0):
474+
msgid = self.compare_ext(dn,attr,value,serverctrls,clientctrls, _stackup=_stackup+1)
484475
try:
485476
ldap_res = self.result3(msgid,all=1,timeout=self.timeout)
486477
except ldap.COMPARE_TRUE:
@@ -492,12 +483,12 @@ def compare_ext_s(self,dn,attr,value,serverctrls=None,clientctrls=None):
492483
)
493484

494485
def compare(self,dn,attr,value):
495-
return self.compare_ext(dn,attr,value,None,None)
486+
return self.compare_ext(dn,attr,value,None,None, _stackup=1)
496487

497488
def compare_s(self,dn,attr,value):
498-
return self.compare_ext_s(dn,attr,value,None,None)
489+
return self.compare_ext_s(dn,attr,value,None,None, _stackup=1)
499490

500-
def delete_ext(self,dn,serverctrls=None,clientctrls=None):
491+
def delete_ext(self,dn,serverctrls=None,clientctrls=None, _stackup=0):
501492
"""
502493
delete(dn) -> int
503494
delete_s(dn) -> None
@@ -507,19 +498,20 @@ def delete_ext(self,dn,serverctrls=None,clientctrls=None):
507498
form returns the message id of the initiated request, and the
508499
result can be obtained from a subsequent call to result().
509500
"""
510-
dn = self._bytesify_input(dn)
501+
if PY2:
502+
dn = self._bytesify_input(dn, _stackup)
511503
return self._ldap_call(self._l.delete_ext,dn,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
512504

513-
def delete_ext_s(self,dn,serverctrls=None,clientctrls=None):
514-
msgid = self.delete_ext(dn,serverctrls,clientctrls)
505+
def delete_ext_s(self,dn,serverctrls=None,clientctrls=None, _stackup=0):
506+
msgid = self.delete_ext(dn,serverctrls,clientctrls, _stackup=_stackup+1)
515507
resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
516508
return resp_type, resp_data, resp_msgid, resp_ctrls
517509

518510
def delete(self,dn):
519-
return self.delete_ext(dn,None,None)
511+
return self.delete_ext(dn,None,None, _stackup=1)
520512

521513
def delete_s(self,dn):
522-
return self.delete_ext_s(dn,None,None)
514+
return self.delete_ext_s(dn,None,None, _stackup=1)
523515

524516
def extop(self,extreq,serverctrls=None,clientctrls=None):
525517
"""
@@ -556,8 +548,9 @@ def modify_ext(self,dn,modlist,serverctrls=None,clientctrls=None):
556548
"""
557549
modify_ext(dn, modlist[,serverctrls=None[,clientctrls=None]]) -> int
558550
"""
559-
dn = self._bytesify_input(dn)
560-
modlist = self._bytesify_modlist(modlist, with_opcode=True)
551+
if PY2:
552+
dn = self._bytesify_input(dn)
553+
modlist = self._bytesify_modlist(modlist, with_opcode=True)
561554
return self._ldap_call(self._l.modify_ext,dn,modlist,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
562555

563556
def modify_ext_s(self,dn,modlist,serverctrls=None,clientctrls=None):
@@ -591,7 +584,7 @@ def modify(self,dn,modlist):
591584
def modify_s(self,dn,modlist):
592585
return self.modify_ext_s(dn,modlist,None,None)
593586

594-
def modrdn(self,dn,newrdn,delold=1):
587+
def modrdn(self,dn,newrdn,delold=1,_stackup=0):
595588
"""
596589
modrdn(dn, newrdn [,delold=1]) -> int
597590
modrdn_s(dn, newrdn [,delold=1]) -> None
@@ -605,20 +598,23 @@ def modrdn(self,dn,newrdn,delold=1):
605598
This operation is emulated by rename() and rename_s() methods
606599
since the modrdn2* routines in the C library are deprecated.
607600
"""
608-
return self.rename(dn,newrdn,None,delold)
601+
return self.rename(dn,newrdn,None,delold, _stackup=_stackup+1)
609602

610603
def modrdn_s(self,dn,newrdn,delold=1):
611-
return self.rename_s(dn,newrdn,None,delold)
604+
return self.rename_s(dn,newrdn,None,delold, _stackup=1)
612605

613606
def passwd(self,user,oldpw,newpw,serverctrls=None,clientctrls=None):
614-
user, oldpw, newpw = self._bytesify_inputs(user, oldpw, newpw)
607+
if PY2:
608+
user = self._bytesify_input(user)
609+
oldpw = self._bytesify_input(oldpw)
610+
newpw = self._bytesify_input(newpw)
615611
return self._ldap_call(self._l.passwd,user,oldpw,newpw,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
616612

617613
def passwd_s(self,user,oldpw,newpw,serverctrls=None,clientctrls=None):
618614
msgid = self.passwd(user,oldpw,newpw,serverctrls,clientctrls)
619615
return self.extop_result(msgid,all=1,timeout=self.timeout)
620616

621-
def rename(self,dn,newrdn,newsuperior=None,delold=1,serverctrls=None,clientctrls=None):
617+
def rename(self,dn,newrdn,newsuperior=None,delold=1,serverctrls=None,clientctrls=None, _stackup=0):
622618
"""
623619
rename(dn, newrdn [, newsuperior=None [,delold=1][,serverctrls=None[,clientctrls=None]]]) -> int
624620
rename_s(dn, newrdn [, newsuperior=None] [,delold=1][,serverctrls=None[,clientctrls=None]]) -> None
@@ -633,11 +629,14 @@ def rename(self,dn,newrdn,newsuperior=None,delold=1,serverctrls=None,clientctrls
633629
This actually corresponds to the rename* routines in the
634630
LDAP-EXT C API library.
635631
"""
636-
dn, newrdn, newsuperior = self._bytesify_inputs(dn, newrdn, newsuperior)
632+
if PY2:
633+
dn = self._bytesify_input(dn, _stackup)
634+
newrdn = self._bytesify_input(newrdn, _stackup)
635+
newsuperior = self._bytesify_input(newsuperior, _stackup)
637636
return self._ldap_call(self._l.rename,dn,newrdn,newsuperior,delold,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
638637

639638
def rename_s(self,dn,newrdn,newsuperior=None,delold=1,serverctrls=None,clientctrls=None):
640-
msgid = self.rename(dn,newrdn,newsuperior,delold,serverctrls,clientctrls)
639+
msgid = self.rename(dn,newrdn,newsuperior,delold,serverctrls,clientctrls, _stackup=1)
641640
resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
642641
return resp_type, resp_data, resp_msgid, resp_ctrls
643642

@@ -726,7 +725,7 @@ def result4(self,msgid=ldap.RES_ANY,all=1,timeout=None,add_ctrls=0,add_intermedi
726725
resp_data = self._bytesify_results(resp_data, with_ctrls=add_ctrls)
727726
return resp_type, resp_data, resp_msgid, decoded_resp_ctrls, resp_name, resp_value
728727

729-
def search_ext(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0):
728+
def search_ext(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0,_stackup=0):
730729
"""
731730
search(base, scope [,filterstr='(objectClass=*)' [,attrlist=None [,attrsonly=0]]]) -> int
732731
search_s(base, scope [,filterstr='(objectClass=*)' [,attrlist=None [,attrsonly=0]]])
@@ -771,9 +770,12 @@ def search_ext(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrson
771770
The amount of search results retrieved can be limited with the
772771
sizelimit parameter if non-zero.
773772
"""
774-
base, filterstr = self._bytesify_inputs(base, filterstr)
775-
if attrlist is not None:
776-
attrlist = tuple(self._bytesify_inputs(*attrlist))
773+
if PY2:
774+
bytesify_input = self._bytesify_input
775+
base = bytesify_input(base, _stackup)
776+
filterstr = bytesify_input(filterstr, _stackup)
777+
if attrlist is not None:
778+
attrlist = tuple(bytesify_input(attr, _stackup+1) for attr in attrlist)
777779
return self._ldap_call(
778780
self._l.search_ext,
779781
base,scope,filterstr,
@@ -783,18 +785,18 @@ def search_ext(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrson
783785
timeout,sizelimit,
784786
)
785787

786-
def search_ext_s(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0):
787-
msgid = self.search_ext(base,scope,filterstr,attrlist,attrsonly,serverctrls,clientctrls,timeout,sizelimit)
788+
def search_ext_s(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0, _stackup=1):
789+
msgid = self.search_ext(base,scope,filterstr,attrlist,attrsonly,serverctrls,clientctrls,timeout,sizelimit, _stackup=_stackup)
788790
return self.result(msgid,all=1,timeout=timeout)[1]
789791

790792
def search(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0):
791-
return self.search_ext(base,scope,filterstr,attrlist,attrsonly,None,None)
793+
return self.search_ext(base,scope,filterstr,attrlist,attrsonly,None,None, _stackup=1)
792794

793-
def search_s(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0):
794-
return self.search_ext_s(base,scope,filterstr,attrlist,attrsonly,None,None,timeout=self.timeout)
795+
def search_s(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0, _stackup=0):
796+
return self.search_ext_s(base,scope,filterstr,attrlist,attrsonly,None,None,timeout=self.timeout, _stackup=_stackup+2)
795797

796798
def search_st(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0,timeout=-1):
797-
return self.search_ext_s(base,scope,filterstr,attrlist,attrsonly,None,None,timeout)
799+
return self.search_ext_s(base,scope,filterstr,attrlist,attrsonly,None,None,timeout, _stackup=2)
798800

799801
def start_tls_s(self):
800802
"""
@@ -872,7 +874,8 @@ def search_subschemasubentry_s(self,dn=''):
872874
"""
873875
try:
874876
r = self.search_s(
875-
dn,ldap.SCOPE_BASE,'(objectClass=*)',['subschemaSubentry']
877+
dn,ldap.SCOPE_BASE,'(objectClass=*)',['subschemaSubentry'],
878+
_stackup=1
876879
)
877880
except (ldap.NO_SUCH_OBJECT,ldap.NO_SUCH_ATTRIBUTE,ldap.INSUFFICIENT_ACCESS):
878881
r = []
@@ -984,6 +987,9 @@ class ReconnectLDAPObject(SimpleLDAPObject):
984987
application.
985988
"""
986989

990+
# public method + _apply_method_s()
991+
_stacklevel = SimpleLDAPObject._stacklevel + 2
992+
987993
__transient_attrs__ = set([
988994
'_l',
989995
'_ldap_object_lock',

Tests/t_ldapobject.py

Lines changed: 53 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
from slapdtest import SlapdTestCase, requires_sasl
2326

@@ -314,7 +317,56 @@ def test007_timeout(self):
314317
l.abandon(m)
315318
with self.assertRaises(ldap.TIMEOUT):
316319
result = l.result(m, timeout=0.001)
317-
320+
321+
@contextlib.contextmanager
322+
def catch_byteswarnings(self, *args, **kwargs):
323+
with warnings.catch_warnings(record=True) as w:
324+
conn = self._get_bytes_ldapobject(*args, **kwargs)
325+
warnings.resetwarnings()
326+
warnings.simplefilter('always', BytesWarning)
327+
yield conn, w
328+
329+
def _test_byteswarning_level_search(self, methodname):
330+
with self.catch_byteswarnings(explicit=False) as (conn, w):
331+
method = getattr(conn, methodname)
332+
result = method(
333+
self.server.suffix.encode('utf-8'),
334+
ldap.SCOPE_SUBTREE,
335+
'(cn=Foo*)',
336+
attrlist=['*'], # CORRECT LINE
337+
)
338+
self.assertEqual(len(result), 4)
339+
340+
self.assertEqual(len(w), 2, w)
341+
342+
self.assertIs(w[0].category, BytesWarning)
343+
self.assertIn(
344+
u"Received non-bytes value u'(cn=Foo*)'",
345+
text_type(w[0].message)
346+
)
347+
self.assertEqual(w[0].filename, __file__)
348+
self.assertIn(
349+
'CORRECT LINE',
350+
linecache.getline(w[0].filename, w[0].lineno)
351+
)
352+
353+
self.assertIs(w[1].category, BytesWarning)
354+
self.assertIn(
355+
u"Received non-bytes value u'*'",
356+
text_type(w[1].message)
357+
)
358+
self.assertEqual(w[1].filename, __file__)
359+
self.assertIn(
360+
'CORRECT LINE',
361+
linecache.getline(w[1].filename, w[1].lineno)
362+
)
363+
364+
@unittest.skipUnless(PY2, "no bytes_mode under Py3")
365+
def test_byteswarning_level_search(self):
366+
self._test_byteswarning_level_search('search_s')
367+
self._test_byteswarning_level_search('search_st')
368+
self._test_byteswarning_level_search('search_ext_s')
369+
318370

319371
class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject):
320372
"""

0 commit comments

Comments
 (0)