Coverage for pycommons / processes / caller.py: 84%

32 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-11 03:04 +0000

1"""Get information about how this process was called.""" 

2from contextlib import suppress 

3from os import environ, getpid 

4from os.path import basename, isfile 

5from traceback import extract_stack 

6from typing import Final, cast 

7 

8from psutil import Process # type: ignore 

9 

10 

11def is_ci_run() -> bool: 

12 """ 

13 Check if the program runs in a continuous integration environment. 

14 

15 Right now, only GitHub actions are recognized. Other CI tools are 

16 currently not supported. 

17 

18 :returns: `True` if this process is executed as part of, e.g., a GitHub 

19 action, `False` otherwise. 

20 

21 >>> isinstance(is_ci_run(), bool) 

22 True 

23 """ 

24 return any(k in environ for k in ( 

25 "GITHUB_ACTION", "GITHUB_ACTOR", "GITHUB_ENV", "GITHUB_JOB", 

26 "GITHUB_RUN_ID", "GITHUB_WORKFLOW", "GITHUB_WORKSPACE")) 

27 

28 

29def is_build() -> bool: 

30 """ 

31 Check if the program was run inside a build. 

32 

33 This function is `True` if the process is running inside a `make` build 

34 or if :func:`is_ci_run` is `True` or if the evironment variable 

35 `BUILD_SCRIPT` is set. 

36 

37 Since we now need to use virtual environments to install `pip` packages, 

38 using `make` scripts has become too cumbersome to me. I simply cannot be 

39 bothered to figure out how to set up a virtual environment `make` script 

40 wide. Instead, I now use a `bash` script (`make.sh`) in my builds. To 

41 properly detect this, this script sets the environment variable 

42 `BUILD_SCRIPT`. In all my `pycommons`-based projects, I will do this from 

43 now on. 

44 

45 Basically, if you want to signal that code runs inside a build, you can 

46 set an environment variable as `export BUILD_SCRIPT="${BASH_SOURCE[0]}"` 

47 inside your `bash` build script. This will be used as signal by this 

48 function that we are running inside a build. 

49 

50 :returns: `True` if this process is executed as part of a build process, 

51 `False` otherwise. 

52 

53 >>> isinstance(is_build(), bool) 

54 True 

55 """ 

56 obj: Final[object] = is_build 

57 key: Final[str] = "_value" 

58 if hasattr(obj, key): 

59 return cast("bool", getattr(obj, key)) 

60 

61 ret: bool = ("BUILD_SCRIPT" in environ) or is_ci_run() 

62 

63 if not ret: 

64 with suppress(Exception): 

65 process: Process = Process(getpid()) 

66 while process is not None: 

67 process = process.parent() 

68 name: str = process.cmdline()[0] 

69 if not isinstance(name, str): 

70 continue 

71 if not isfile(name): 

72 continue 

73 name = basename(name) 

74 if (str.__eq__(name, "make")) or ( 

75 str.startswith(name, "make.")): 

76 ret = True 

77 break 

78 

79 setattr(obj, key, ret) 

80 return ret 

81 

82 

83def is_doc_test() -> bool: 

84 """ 

85 Check if this process was invoked by a unit doctest. 

86 

87 :returns: `True` if this function was called by a unit doctest, 

88 `False` otherwise 

89 

90 >>> is_doc_test() 

91 True 

92 """ 

93 return any(t.filename.endswith(("docrunner.py", "doctest.py")) 

94 for t in extract_stack())