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
« 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
8from psutil import Process # type: ignore
11def is_ci_run() -> bool:
12 """
13 Check if the program runs in a continuous integration environment.
15 Right now, only GitHub actions are recognized. Other CI tools are
16 currently not supported.
18 :returns: `True` if this process is executed as part of, e.g., a GitHub
19 action, `False` otherwise.
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"))
29def is_build() -> bool:
30 """
31 Check if the program was run inside a build.
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.
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.
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.
50 :returns: `True` if this process is executed as part of a build process,
51 `False` otherwise.
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))
61 ret: bool = ("BUILD_SCRIPT" in environ) or is_ci_run()
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
79 setattr(obj, key, ret)
80 return ret
83def is_doc_test() -> bool:
84 """
85 Check if this process was invoked by a unit doctest.
87 :returns: `True` if this function was called by a unit doctest,
88 `False` otherwise
90 >>> is_doc_test()
91 True
92 """
93 return any(t.filename.endswith(("docrunner.py", "doctest.py"))
94 for t in extract_stack())