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 , ParamSpec , TypeVar
3740
3841from find_libpython ._version import __version__ # noqa: F401
3942
43+ T = TypeVar ("T" )
44+
4045_logger = logging .getLogger ("find_libpython" )
4146
4247_is_apple = sys .platform == "darwin"
5762 _SHLIB_SUFFIX = ".dylib"
5863
5964
60- def _linked_libpython_unix (libpython ) :
65+ def _linked_libpython_unix (libpython : Any ) -> str | None :
6166 if not hasattr (libpython , "Py_GetVersion" ):
6267 return None
6368
@@ -83,7 +88,9 @@ class Dl_info(ctypes.Structure):
8388 return os .path .realpath (dlinfo .dli_fname .decode ())
8489
8590
86- def _library_name (name , suffix = _SHLIB_SUFFIX , _is_windows = _is_windows ):
91+ def _library_name (
92+ name : str , suffix : str = _SHLIB_SUFFIX , _is_windows : bool = _is_windows
93+ ) -> str :
8794 """
8895 Convert a file basename `name` to a library name (no "lib" and ".so" etc.)
8996
@@ -103,12 +110,12 @@ def _library_name(name, suffix=_SHLIB_SUFFIX, _is_windows=_is_windows):
103110 return name
104111
105112
106- def _append_truthy (list , item ) :
113+ def _append_truthy (list : list [ T ] , item : T ) -> None :
107114 if item :
108115 list .append (item )
109116
110117
111- def _uniquifying (items ) :
118+ def _uniquifying (items : Iterable [ str ]) -> Iterable [ str ] :
112119 """
113120 Yield items while excluding the duplicates and preserving the order.
114121
@@ -122,17 +129,20 @@ def _uniquifying(items):
122129 seen .add (x )
123130
124131
125- def _uniquified (func ):
132+ P = ParamSpec ("P" )
133+
134+
135+ def _uniquified (func : Callable [P , Iterable [str ]]) -> Callable [P , Iterable [str ]]:
126136 """Wrap iterator returned from `func` by `_uniquifying`."""
127137
128138 @wraps (func )
129- def wrapper (* args , ** kwds ) :
139+ def wrapper (* args : P . args , ** kwds : P . kwargs ) -> Iterable [ str ] :
130140 return _uniquifying (func (* args , ** kwds ))
131141
132142 return wrapper
133143
134144
135- def _get_proc_library ():
145+ def _get_proc_library () -> Iterable [ str ] :
136146 pid = os .getpid ()
137147 path = f"/proc/{ pid } /maps"
138148 lines = open (path ).readlines ()
@@ -146,7 +156,7 @@ def _get_proc_library():
146156
147157
148158@_uniquified
149- def candidate_names (suffix = _SHLIB_SUFFIX ):
159+ def candidate_names (suffix : str = _SHLIB_SUFFIX ) -> Iterable [ str ] :
150160 """
151161 Iterate over candidate file names of libpython.
152162
@@ -203,23 +213,23 @@ def candidate_names(suffix=_SHLIB_SUFFIX):
203213 yield dlprefix + stem + suffix
204214
205215
206- def _linked_pythondll () -> str :
216+ def _linked_pythondll () -> str | None :
207217 # On Windows there is the `sys.dllhandle` attribute which is the
208218 # DLL Handle ID for the associated python.dll for the installation.
209219 # We can use the GetModuleFileName function to get the path to the
210220 # python.dll this way.
211221
212222 # sys.dllhandle is an module ID, which is just a void* cast to an integer,
213223 # we turn it back into a pointer for the ctypes call
214- dll_hmodule = ctypes .cast (sys .dllhandle , ctypes .c_void_p )
224+ dll_hmodule = ctypes .cast (sys .dllhandle , ctypes .c_void_p ) # type: ignore[attr-defined]
215225
216226 # create a buffer for the return path of the maximum length of filepaths in Windows unicode interfaces
217227 # https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
218228 path_return_buffer = ctypes .create_unicode_buffer (32768 )
219229
220230 # GetModuleFileName sets the return buffer to the value of the path used to load the module.
221231 # We expect it to always be a normalized absolute path to python.dll.
222- r = ctypes .windll .kernel32 .GetModuleFileNameW (
232+ r = ctypes .windll .kernel32 .GetModuleFileNameW ( # type: ignore[attr-defined]
223233 dll_hmodule , path_return_buffer , len (path_return_buffer )
224234 )
225235
@@ -233,7 +243,7 @@ def _linked_pythondll() -> str:
233243
234244
235245@_uniquified
236- def candidate_paths (suffix = _SHLIB_SUFFIX ):
246+ def candidate_paths (suffix : str = _SHLIB_SUFFIX ) -> Iterable [ str ] :
237247 """
238248 Iterate over candidate paths of libpython.
239249
@@ -245,10 +255,11 @@ def candidate_paths(suffix=_SHLIB_SUFFIX):
245255 """
246256
247257 if _is_windows :
248- yield _linked_pythondll ()
258+ if (res := _linked_pythondll ()) is not None :
259+ yield res
249260
250261 # List candidates for directories in which libpython may exist
251- lib_dirs = []
262+ lib_dirs : list [ str ] = []
252263 _append_truthy (lib_dirs , _get_config_var ("LIBPL" ))
253264 _append_truthy (lib_dirs , _get_config_var ("srcdir" ))
254265 _append_truthy (lib_dirs , _get_config_var ("LIBDIR" ))
@@ -284,7 +295,8 @@ def candidate_paths(suffix=_SHLIB_SUFFIX):
284295 except OSError :
285296 pass
286297 else :
287- yield _linked_libpython_unix (libpython )
298+ if (res := _linked_libpython_unix (libpython )) is not None :
299+ yield res
288300
289301 try :
290302 yield from _get_proc_library ()
@@ -297,7 +309,8 @@ def candidate_paths(suffix=_SHLIB_SUFFIX):
297309
298310 # In macOS and Windows, ctypes.util.find_library returns a full path:
299311 for basename in lib_basenames :
300- yield _find_library (_library_name (basename ))
312+ if (res := _find_library (_library_name (basename ))) is not None :
313+ yield res
301314
302315
303316# Possibly useful links:
@@ -306,7 +319,9 @@ def candidate_paths(suffix=_SHLIB_SUFFIX):
306319# * https://github.com/Valloric/ycmd/pull/519
307320
308321
309- def _normalize_path (path , suffix = _SHLIB_SUFFIX , _is_apple = _is_apple ):
322+ def _normalize_path (
323+ path : str , suffix : str = _SHLIB_SUFFIX , _is_apple : bool = _is_apple
324+ ) -> str | None :
310325 """
311326 Normalize shared library `path` to a real path.
312327
@@ -334,7 +349,7 @@ def _normalize_path(path, suffix=_SHLIB_SUFFIX, _is_apple=_is_apple):
334349 return None
335350
336351
337- def _remove_suffix_apple (path ) :
352+ def _remove_suffix_apple (path : str ) -> str :
338353 """
339354 Strip off .so or .dylib.
340355
@@ -353,7 +368,7 @@ def _remove_suffix_apple(path):
353368
354369
355370@_uniquified
356- def _finding_libpython ():
371+ def _finding_libpython () -> Iterable [ str ] :
357372 """
358373 Iterate over existing libpython paths.
359374
@@ -374,7 +389,7 @@ def _finding_libpython():
374389 _logger .debug ("Not found." )
375390
376391
377- def find_libpython ():
392+ def find_libpython () -> str | None :
378393 """
379394 Return a path (`str`) to libpython or `None` if not found.
380395
@@ -385,18 +400,18 @@ def find_libpython():
385400 """
386401 for path in _finding_libpython ():
387402 return os .path .realpath (path )
403+ return None
388404
389405
390- def _print_all (items ):
391- for x in items :
392- print (x )
393-
394-
395- def _cli_find_libpython (cli_op , verbose ):
406+ def _cli_find_libpython (cli_op : str , verbose : bool ) -> int :
396407 # Importing `logging` module here so that using `logging.debug`
397408 # instead of `_logger.debug` outside of this function becomes an
398409 # error.
399410
411+ def _print_all (items : Iterable [str ]) -> None :
412+ for x in items :
413+ print (x )
414+
400415 if verbose :
401416 logging .basicConfig (format = "%(levelname)s %(message)s" , level = logging .DEBUG )
402417
@@ -414,6 +429,8 @@ def _cli_find_libpython(cli_op, verbose):
414429 return 1
415430 print (path , end = "" )
416431
432+ return 0
433+
417434
418435def _log_platform_info ():
419436 print (f"is_windows = { _is_windows } " )
@@ -423,7 +440,7 @@ def _log_platform_info():
423440 print (f"is_posix = { _is_posix } " )
424441
425442
426- def main (args = None ):
443+ def main (args : list [ str ] | None = None ) -> int :
427444 parser = argparse .ArgumentParser (description = __doc__ )
428445 parser .add_argument (
429446 "-v" , "--verbose" , action = "store_true" , help = "Print debugging information."
@@ -463,4 +480,4 @@ def main(args=None):
463480 )
464481
465482 ns = parser .parse_args (args )
466- parser . exit ( _cli_find_libpython (** vars (ns ) ))
483+ return _cli_find_libpython (** vars (ns ))
0 commit comments