Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3ebd8a1

Browse files
committed
[python-ldap] Add type hints
With this patch applied, ``MYPYPATH=/Stubs mypy --strict Lib/`` passes without any warnings. ``tox`` also passes for everything from ``py37`` to ``py11`` (and also including ``doc`` and ``pypy3``). I've tried my best to not make more modifications than strictly necessary. Most of the time, changes merely serve to make life easier for ``mypy`` (i.e. to enable type inference) or to fixup non-type aware code. The patch might look dauntingly large, but once you cut away the trivial stuff (untyped function definitions which have been replaced with types ones, lots and lots of new import statements, etc) the diff is actually not that big.
1 parent ac5d051 commit 3ebd8a1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2591
-1070
lines changed

Lib/ldap/__init__.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
44
See https://www.python-ldap.org/ for details.
55
"""
6+
from __future__ import annotations
67

78
# This is also the overall release version number
89

@@ -11,16 +12,19 @@
1112
import os
1213
import sys
1314

15+
from typing import Any, Type
16+
17+
1418
if __debug__:
1519
# Tracing is only supported in debugging mode
1620
import atexit
1721
import traceback
1822
_trace_level = int(os.environ.get("PYTHON_LDAP_TRACE_LEVEL", 0))
19-
_trace_file = os.environ.get("PYTHON_LDAP_TRACE_FILE")
20-
if _trace_file is None:
23+
_trace_file_path = os.environ.get("PYTHON_LDAP_TRACE_FILE")
24+
if _trace_file_path is None:
2125
_trace_file = sys.stderr
2226
else:
23-
_trace_file = open(_trace_file, 'a')
27+
_trace_file = open(_trace_file_path, 'a')
2428
atexit.register(_trace_file.close)
2529
_trace_stack_limit = None
2630
else:
@@ -45,18 +49,21 @@
4549

4650
class DummyLock:
4751
"""Define dummy class with methods compatible to threading.Lock"""
48-
def __init__(self):
49-
pass
50-
def acquire(self):
52+
def __init__(self) -> None:
5153
pass
52-
def release(self):
54+
55+
def acquire(self) -> bool:
56+
return True
57+
58+
def release(self) -> None:
5359
pass
5460

5561
try:
5662
# Check if Python installation was build with thread support
63+
# FIXME: This can be simplified, from Python 3.7 this module is mandatory
5764
import threading
5865
except ImportError:
59-
LDAPLockBaseClass = DummyLock
66+
LDAPLockBaseClass: Type[DummyLock] | Type[threading.Lock] = DummyLock
6067
else:
6168
LDAPLockBaseClass = threading.Lock
6269

@@ -69,7 +76,11 @@ class LDAPLock:
6976
"""
7077
_min_trace_level = 3
7178

72-
def __init__(self,lock_class=None,desc=''):
79+
def __init__(
80+
self,
81+
lock_class: Type[Any] | None = None,
82+
desc: str = ''
83+
) -> None:
7384
"""
7485
lock_class
7586
Class compatible to threading.Lock
@@ -79,19 +90,19 @@ def __init__(self,lock_class=None,desc=''):
7990
self._desc = desc
8091
self._lock = (lock_class or LDAPLockBaseClass)()
8192

82-
def acquire(self):
93+
def acquire(self) -> bool:
8394
if __debug__:
8495
global _trace_level
8596
if _trace_level>=self._min_trace_level:
8697
_trace_file.write('***{}.acquire() {} {}\n'.format(self.__class__.__name__,repr(self),self._desc))
8798
return self._lock.acquire()
8899

89-
def release(self):
100+
def release(self) -> None:
90101
if __debug__:
91102
global _trace_level
92103
if _trace_level>=self._min_trace_level:
93104
_trace_file.write('***{}.release() {} {}\n'.format(self.__class__.__name__,repr(self),self._desc))
94-
return self._lock.release()
105+
self._lock.release()
95106

96107

97108
# Create module-wide lock for serializing all calls into underlying LDAP lib

Lib/ldap/async.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import warnings
77

88
from ldap.asyncsearch import *
9-
from ldap.asyncsearch import __version__
9+
from ldap.pkginfo import __version__
1010

1111
warnings.warn(
1212
"'ldap.async module' is deprecated, import 'ldap.asyncsearch' instead.",

Lib/ldap/asyncsearch.py

Lines changed: 87 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@
33
44
See https://www.python-ldap.org/ for details.
55
"""
6+
from __future__ import annotations
67

78
import ldap
89

9-
from ldap import __version__
10+
from ldap.pkginfo import __version__
11+
from ldap.controls import RequestControl
12+
from typing import Any, Dict as DictType, Iterable, List as ListType, Sequence, TextIO, Tuple
13+
from ldap_types import *
1014

1115
import ldif
1216

@@ -24,15 +28,19 @@
2428

2529
class WrongResultType(Exception):
2630

27-
def __init__(self,receivedResultType,expectedResultTypes):
31+
def __init__(
32+
self,
33+
receivedResultType: int,
34+
expectedResultTypes: Iterable[int],
35+
) -> None:
2836
self.receivedResultType = receivedResultType
2937
self.expectedResultTypes = expectedResultTypes
3038
Exception.__init__(self)
3139

32-
def __str__(self):
40+
def __str__(self) -> str:
3341
return 'Received wrong result type {} (expected one of {}).'.format(
3442
self.receivedResultType,
35-
', '.join(self.expectedResultTypes),
43+
', '.join([str(x) for x in self.expectedResultTypes]),
3644
)
3745

3846

@@ -46,23 +54,23 @@ class AsyncSearchHandler:
4654
LDAPObject instance
4755
"""
4856

49-
def __init__(self,l):
57+
def __init__(self, l: ldap.ldapobject.LDAPObject) -> None:
5058
self._l = l
51-
self._msgId = None
59+
self._msgId: int | None = None
5260
self._afterFirstResult = 1
5361

5462
def startSearch(
5563
self,
56-
searchRoot,
57-
searchScope,
58-
filterStr,
59-
attrList=None,
60-
attrsOnly=0,
61-
timeout=-1,
62-
sizelimit=0,
63-
serverctrls=None,
64-
clientctrls=None
65-
):
64+
searchRoot: str,
65+
searchScope: int,
66+
filterStr: str,
67+
attrList: ListType[str] | None = None,
68+
attrsOnly: int = 0,
69+
timeout: int = -1,
70+
sizelimit: int = 0,
71+
serverctrls: ListType[RequestControl] | None = None,
72+
clientctrls: ListType[RequestControl] | None = None,
73+
) -> None:
6674
"""
6775
searchRoot
6876
See parameter base of method LDAPObject.search()
@@ -89,26 +97,30 @@ def startSearch(
8997
attrList,attrsOnly,serverctrls,clientctrls,timeout,sizelimit
9098
)
9199
self._afterFirstResult = 1
92-
return # startSearch()
93100

94-
def preProcessing(self):
101+
def preProcessing(self) -> Any:
95102
"""
96103
Do anything you want after starting search but
97104
before receiving and processing results
98105
"""
99106

100-
def afterFirstResult(self):
107+
def afterFirstResult(self) -> Any:
101108
"""
102109
Do anything you want right after successfully receiving but before
103110
processing first result
104111
"""
105112

106-
def postProcessing(self):
113+
def postProcessing(self) -> Any:
107114
"""
108115
Do anything you want after receiving and processing all results
109116
"""
110117

111-
def processResults(self,ignoreResultsNumber=0,processResultsCount=0,timeout=-1):
118+
def processResults(
119+
self,
120+
ignoreResultsNumber: int = 0,
121+
processResultsCount: int = 0,
122+
timeout: int = -1,
123+
) -> int:
112124
"""
113125
ignoreResultsNumber
114126
Don't process the first ignoreResultsNumber results.
@@ -118,6 +130,9 @@ def processResults(self,ignoreResultsNumber=0,processResultsCount=0,timeout=-1):
118130
timeout
119131
See parameter timeout of ldap.LDAPObject.result()
120132
"""
133+
if self._msgId is None:
134+
raise RuntimeError('processResults() called without calling startSearch() first')
135+
121136
self.preProcessing()
122137
result_counter = 0
123138
end_result_counter = ignoreResultsNumber+processResultsCount
@@ -156,7 +171,11 @@ def processResults(self,ignoreResultsNumber=0,processResultsCount=0,timeout=-1):
156171
self.postProcessing()
157172
return partial # processResults()
158173

159-
def _processSingleResult(self,resultType,resultItem):
174+
def _processSingleResult(
175+
self,
176+
resultType: int,
177+
resultItem: LDAPSearchResult,
178+
) -> Any:
160179
"""
161180
Process single entry
162181
@@ -177,11 +196,15 @@ class List(AsyncSearchHandler):
177196
results.
178197
"""
179198

180-
def __init__(self,l):
199+
def __init__(self, l: ldap.ldapobject.LDAPObject) -> None:
181200
AsyncSearchHandler.__init__(self,l)
182-
self.allResults = []
201+
self.allResults: ListType[Tuple[int, LDAPSearchResult]] = []
183202

184-
def _processSingleResult(self,resultType,resultItem):
203+
def _processSingleResult(
204+
self,
205+
resultType: int,
206+
resultItem: LDAPSearchResult,
207+
) -> None:
185208
self.allResults.append((resultType,resultItem))
186209

187210

@@ -190,11 +213,15 @@ class Dict(AsyncSearchHandler):
190213
Class for collecting all search results into a dictionary {dn:entry}
191214
"""
192215

193-
def __init__(self,l):
216+
def __init__(self, l: ldap.ldapobject.LDAPObject) -> None:
194217
AsyncSearchHandler.__init__(self,l)
195-
self.allEntries = {}
218+
self.allEntries: DictType[str, LDAPEntryDict] = {}
196219

197-
def _processSingleResult(self,resultType,resultItem):
220+
def _processSingleResult(
221+
self,
222+
resultType: int,
223+
resultItem: LDAPSearchResult,
224+
) -> None:
198225
if resultType in ENTRY_RESULT_TYPES:
199226
# Search continuations are ignored
200227
dn,entry = resultItem
@@ -207,12 +234,20 @@ class IndexedDict(Dict):
207234
and maintain case-sensitive equality indexes to entries
208235
"""
209236

210-
def __init__(self,l,indexed_attrs=None):
237+
def __init__(
238+
self,
239+
l: ldap.ldapobject.LDAPObject,
240+
indexed_attrs: Sequence[str] | None = None,
241+
) -> None:
211242
Dict.__init__(self,l)
212243
self.indexed_attrs = indexed_attrs or ()
213-
self.index = {}.fromkeys(self.indexed_attrs,{})
244+
self.index: DictType[str, DictType[bytes, ListType[str]]] = {}.fromkeys(self.indexed_attrs,{})
214245

215-
def _processSingleResult(self,resultType,resultItem):
246+
def _processSingleResult(
247+
self,
248+
resultType: int,
249+
resultItem: LDAPSearchResult,
250+
) -> None:
216251
if resultType in ENTRY_RESULT_TYPES:
217252
# Search continuations are ignored
218253
dn,entry = resultItem
@@ -237,20 +272,26 @@ class FileWriter(AsyncSearchHandler):
237272
File object instance where the LDIF data is written to
238273
"""
239274

240-
def __init__(self,l,f,headerStr='',footerStr=''):
275+
def __init__(
276+
self,
277+
l: ldap.ldapobject.LDAPObject,
278+
f: TextIO,
279+
headerStr: str = '',
280+
footerStr: str = '',
281+
) -> None:
241282
AsyncSearchHandler.__init__(self,l)
242283
self._f = f
243284
self.headerStr = headerStr
244285
self.footerStr = footerStr
245286

246-
def preProcessing(self):
287+
def preProcessing(self) -> None:
247288
"""
248289
The headerStr is written to output after starting search but
249290
before receiving and processing results.
250291
"""
251292
self._f.write(self.headerStr)
252293

253-
def postProcessing(self):
294+
def postProcessing(self) -> None:
254295
"""
255296
The footerStr is written to output after receiving and
256297
processing results.
@@ -270,14 +311,24 @@ class LDIFWriter(FileWriter):
270311
Either a file-like object or a ldif.LDIFWriter instance used for output
271312
"""
272313

273-
def __init__(self,l,writer_obj,headerStr='',footerStr=''):
314+
def __init__(
315+
self,
316+
l: ldap.ldapobject.LDAPObject,
317+
writer_obj: TextIO | ldif.LDIFWriter,
318+
headerStr: str = '',
319+
footerStr: str = '',
320+
) -> None:
274321
if isinstance(writer_obj,ldif.LDIFWriter):
275322
self._ldif_writer = writer_obj
276323
else:
277324
self._ldif_writer = ldif.LDIFWriter(writer_obj)
278325
FileWriter.__init__(self,l,self._ldif_writer._output_file,headerStr,footerStr)
279326

280-
def _processSingleResult(self,resultType,resultItem):
327+
def _processSingleResult(
328+
self,
329+
resultType: int,
330+
resultItem: LDAPSearchResult,
331+
) -> None:
281332
if resultType in ENTRY_RESULT_TYPES:
282333
# Search continuations are ignored
283334
dn,entry = resultItem

0 commit comments

Comments
 (0)