2525# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
2626# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2727# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28+ from __future__ import annotations
2829
2930import argparse
3031import ctypes
3132import logging
3233import os
3334import sys
35+ from collections .abc import Iterable
3436from ctypes .util import find_library as _find_library
3537from functools import wraps
3638from sysconfig import get_config_var as _get_config_var
39+ from typing import Any , Callable , TypeVar
3740
3841from find_libpython ._version import __version__ # noqa: F401
3942
43+ if sys .version_info >= (3 , 10 ):
44+ from typing import ParamSpec
45+
46+ P = ParamSpec ("P" )
47+
48+ T = TypeVar ("T" )
49+
4050_logger = logging .getLogger ("find_libpython" )
4151
4252_is_apple = sys .platform == "darwin"
5767 _SHLIB_SUFFIX = ".dylib"
5868
5969
60- def _linked_libpython_unix (libpython ) :
70+ def _linked_libpython_unix (libpython : Any ) -> str | None :
6171 if not hasattr (libpython , "Py_GetVersion" ):
6272 return None
6373
@@ -83,7 +93,9 @@ class Dl_info(ctypes.Structure):
8393 return os .path .realpath (dlinfo .dli_fname .decode ())
8494
8595
86- def _library_name (name , suffix = _SHLIB_SUFFIX , _is_windows = _is_windows ):
96+ def _library_name (
97+ name : str , suffix : str = _SHLIB_SUFFIX , _is_windows : bool = _is_windows
98+ ) -> str :
8799 """
88100 Convert a file basename `name` to a library name (no "lib" and ".so" etc.)
89101
@@ -103,12 +115,12 @@ def _library_name(name, suffix=_SHLIB_SUFFIX, _is_windows=_is_windows):
103115 return name
104116
105117
106- def _append_truthy (list , item ) :
118+ def _append_truthy (list : list [ T ] , item : T ) -> None :
107119 if item :
108120 list .append (item )
109121
110122
111- def _uniquifying (items ) :
123+ def _uniquifying (items : Iterable [ str ]) -> Iterable [ str ] :
112124 """
113125 Yield items while excluding the duplicates and preserving the order.
114126
@@ -122,17 +134,17 @@ def _uniquifying(items):
122134 seen .add (x )
123135
124136
125- def _uniquified (func ) :
137+ def _uniquified (func : Callable [ P , Iterable [ str ]]) -> Callable [ P , Iterable [ str ]] :
126138 """Wrap iterator returned from `func` by `_uniquifying`."""
127139
128140 @wraps (func )
129- def wrapper (* args , ** kwds ) :
141+ def wrapper (* args : P . args , ** kwds : P . kwargs ) -> Iterable [ str ] :
130142 return _uniquifying (func (* args , ** kwds ))
131143
132144 return wrapper
133145
134146
135- def _get_proc_library ():
147+ def _get_proc_library () -> Iterable [ str ] :
136148 pid = os .getpid ()
137149 path = f"/proc/{ pid } /maps"
138150 lines = open (path ).readlines ()
@@ -146,7 +158,7 @@ def _get_proc_library():
146158
147159
148160@_uniquified
149- def candidate_names (suffix = _SHLIB_SUFFIX ):
161+ def candidate_names (suffix : str = _SHLIB_SUFFIX ) -> Iterable [ str ] :
150162 """
151163 Iterate over candidate file names of libpython.
152164
@@ -203,23 +215,23 @@ def candidate_names(suffix=_SHLIB_SUFFIX):
203215 yield dlprefix + stem + suffix
204216
205217
206- def _linked_pythondll () -> str :
218+ def _linked_pythondll () -> str | None :
207219 # On Windows there is the `sys.dllhandle` attribute which is the
208220 # DLL Handle ID for the associated python.dll for the installation.
209221 # We can use the GetModuleFileName function to get the path to the
210222 # python.dll this way.
211223
212224 # sys.dllhandle is an module ID, which is just a void* cast to an integer,
213225 # we turn it back into a pointer for the ctypes call
214- dll_hmodule = ctypes .cast (sys .dllhandle , ctypes .c_void_p )
226+ dll_hmodule = ctypes .cast (sys .dllhandle , ctypes .c_void_p ) # type: ignore[attr-defined]
215227
216228 # create a buffer for the return path of the maximum length of filepaths in Windows unicode interfaces
217229 # https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
218230 path_return_buffer = ctypes .create_unicode_buffer (32768 )
219231
220232 # GetModuleFileName sets the return buffer to the value of the path used to load the module.
221233 # We expect it to always be a normalized absolute path to python.dll.
222- r = ctypes .windll .kernel32 .GetModuleFileNameW (
234+ r = ctypes .windll .kernel32 .GetModuleFileNameW ( # type: ignore[attr-defined]
223235 dll_hmodule , path_return_buffer , len (path_return_buffer )
224236 )
225237
@@ -233,7 +245,7 @@ def _linked_pythondll() -> str:
233245
234246
235247@_uniquified
236- def candidate_paths (suffix = _SHLIB_SUFFIX ):
248+ def candidate_paths (suffix : str = _SHLIB_SUFFIX ) -> Iterable [ str ] :
237249 """
238250 Iterate over candidate paths of libpython.
239251
@@ -245,10 +257,11 @@ def candidate_paths(suffix=_SHLIB_SUFFIX):
245257 """
246258
247259 if _is_windows :
248- yield _linked_pythondll ()
260+ if (res := _linked_pythondll ()) is not None :
261+ yield res
249262
250263 # List candidates for directories in which libpython may exist
251- lib_dirs = []
264+ lib_dirs : list [ str ] = []
252265 _append_truthy (lib_dirs , _get_config_var ("LIBPL" ))
253266 _append_truthy (lib_dirs , _get_config_var ("srcdir" ))
254267 _append_truthy (lib_dirs , _get_config_var ("LIBDIR" ))
@@ -284,7 +297,8 @@ def candidate_paths(suffix=_SHLIB_SUFFIX):
284297 except OSError :
285298 pass
286299 else :
287- yield _linked_libpython_unix (libpython )
300+ if (res := _linked_libpython_unix (libpython )) is not None :
301+ yield res
288302
289303 try :
290304 yield from _get_proc_library ()
@@ -297,7 +311,8 @@ def candidate_paths(suffix=_SHLIB_SUFFIX):
297311
298312 # In macOS and Windows, ctypes.util.find_library returns a full path:
299313 for basename in lib_basenames :
300- yield _find_library (_library_name (basename ))
314+ if (res := _find_library (_library_name (basename ))) is not None :
315+ yield res
301316
302317
303318# Possibly useful links:
@@ -306,7 +321,9 @@ def candidate_paths(suffix=_SHLIB_SUFFIX):
306321# * https://github.com/Valloric/ycmd/pull/519
307322
308323
309- def _normalize_path (path , suffix = _SHLIB_SUFFIX , _is_apple = _is_apple ):
324+ def _normalize_path (
325+ path : str , suffix : str = _SHLIB_SUFFIX , _is_apple : bool = _is_apple
326+ ) -> str | None :
310327 """
311328 Normalize shared library `path` to a real path.
312329
@@ -334,7 +351,7 @@ def _normalize_path(path, suffix=_SHLIB_SUFFIX, _is_apple=_is_apple):
334351 return None
335352
336353
337- def _remove_suffix_apple (path ) :
354+ def _remove_suffix_apple (path : str ) -> str :
338355 """
339356 Strip off .so or .dylib.
340357
@@ -353,7 +370,7 @@ def _remove_suffix_apple(path):
353370
354371
355372@_uniquified
356- def _finding_libpython ():
373+ def _finding_libpython () -> Iterable [ str ] :
357374 """
358375 Iterate over existing libpython paths.
359376
@@ -374,7 +391,7 @@ def _finding_libpython():
374391 _logger .debug ("Not found." )
375392
376393
377- def find_libpython ():
394+ def find_libpython () -> str | None :
378395 """
379396 Return a path (`str`) to libpython or `None` if not found.
380397
@@ -385,18 +402,18 @@ def find_libpython():
385402 """
386403 for path in _finding_libpython ():
387404 return os .path .realpath (path )
405+ return None
388406
389407
390- def _print_all (items ):
391- for x in items :
392- print (x )
393-
394-
395- def _cli_find_libpython (cli_op , verbose ):
408+ def _cli_find_libpython (cli_op : str , verbose : bool ) -> int :
396409 # Importing `logging` module here so that using `logging.debug`
397410 # instead of `_logger.debug` outside of this function becomes an
398411 # error.
399412
413+ def _print_all (items : Iterable [str ]) -> None :
414+ for x in items :
415+ print (x )
416+
400417 if verbose :
401418 logging .basicConfig (format = "%(levelname)s %(message)s" , level = logging .DEBUG )
402419
@@ -414,6 +431,8 @@ def _cli_find_libpython(cli_op, verbose):
414431 return 1
415432 print (path , end = "" )
416433
434+ return 0
435+
417436
418437def _log_platform_info ():
419438 print (f"is_windows = { _is_windows } " )
@@ -423,7 +442,7 @@ def _log_platform_info():
423442 print (f"is_posix = { _is_posix } " )
424443
425444
426- def main (args = None ):
445+ def main (args : list [ str ] | None = None ) -> int :
427446 parser = argparse .ArgumentParser (description = __doc__ )
428447 parser .add_argument (
429448 "-v" , "--verbose" , action = "store_true" , help = "Print debugging information."
@@ -463,4 +482,4 @@ def main(args=None):
463482 )
464483
465484 ns = parser .parse_args (args )
466- parser . exit ( _cli_find_libpython (** vars (ns ) ))
485+ return _cli_find_libpython (** vars (ns ))
0 commit comments