module documentation

Provides a set of functions which I missed in more than one project build. Most functions are rather trivial to write but having to repeat them in each project is a waste.

Using this module is completely optional, obviously.

Class Paths An iterable on files, much like Path().glob() but keeps the path from which the iteration starts.
Class RootPathMapper A Callable[[Path], Iterator[Path]] which maps paths provided by another iterator to a new prefix, think ["src/a.py", "src/b.py"] mapped to ["dest/a.py", "dest/b.py"].
Function copyFiles Copies files from source to dest preserving the path segments below source.root. If the source contains directory paths, these are ignored.
Function filterPath An iterable producing file and, possibly, directory paths recursively contained in the root path. Symlinks are never followed but are handled as regular files.
Function filterPathRe An iterable producing file and, possibly, directory paths recursively contained in the root path. This is a wrapper around filterPath which creates its fileOK and dirOK from the provided regular expressions as follows:...
Function main Simple wrapper around pythonbuilder.updateTargets to set the log level and/or force targets to be re-created.
Function printTargets Undocumented
Function touch A function intended to be used as the cmd parameter of a Target to write the current iso datetime into the targetPath.
Function _forceTargets Helper for main.
Function _setLogLevel Helper for main.
Function _targetDepth Undocumented
Constant _loglevels FIMXE: when we have python 3.11 or newer, switch to logging.getLevelNamesMapping()
def copyFiles(dest: Paths | Path, source: Paths):

Copies files from source to dest preserving the path segments below source.root. If the source contains directory paths, these are ignored.

Parameters
dest:Paths | Pathwhere to copy files to using dest.root if it is a Paths object.
source:Pathsfiles to copy
def filterPath(root: Path, fileOK: Callable[[Path], bool] = lambda p: True, dirOK: Callable[[Path], bool] = lambda p: True, dirs: bool = False) -> Iterable[Path]:

An iterable producing file and, possibly, directory paths recursively contained in the root path. Symlinks are never followed but are handled as regular files.

fileOK decides which files to include in the output.

dirOK decides which directories to traverse. This can avoid unnecessary traversal of subdirectories and may keep file tests simpler. For example if dirOK is only true for a subdirectory "src/module1", getting python files from this subdirectory only can just check if a path ends with ".py". Without a dirOK, the prefix "src/module1/" would also have to be checked for each file and, in addition, other subdirectories would be traversed unneccesarily.

Directory paths will only show up in the result if dirs=True and if they are traversed.

Parameters
root:Pathdirectory to traverse
fileOK:Callable[[Path], bool]function to test if a file shall be in the output
dirOK:Callable[[Path], bool]function to test if a directory shall be traversed at all
dirs:boolif True, provides traversed directory paths in the output
Returns
Iterable[Path]paths of files and directories
def filterPathRe(root: Path, fileI: str = '.', fileX: str | None = None, dirI: str = '.', dirX: str | None = None, dirs: bool = False) -> Iterable[Path]:

An iterable producing file and, possibly, directory paths recursively contained in the root path. This is a wrapper around filterPath which creates its fileOK and dirOK from the provided regular expressions as follows:

Both parameters for filterPath are constructed such that the exclude pattern beats the include pattern. All patterns are searched in paths relative to root after conversion with Path.as_posix(), so paths directly in root will have no '/' inside while deeper nested paths will look like "a/b/c.d"

For example to only include files in subdirectories of root use fileI="/" as it requires that the path contains at least one '/', like module/some.py.

Use dirI and dirX to prevent traversal of subdirectories which have not files to include as described for filterPath.

Parameters
root:Pathdirectory to traverse
fileI:strregex to match against file paths for inclusion
fileX:str | Noneregex to match against file paths for exclusion, overrules fileI
dirI:strregex to match against directory paths for traversal
dirX:str | Noneregex to match against directory paths to skip, overrules dirI
dirs:boolif True, provides traversed directory paths in the output
Returns
Iterable[Path]paths of files and directories
def main(*argv: str) -> bool:

Simple wrapper around pythonbuilder.updateTargets to set the log level and/or force targets to be re-created.

In your build script, after setting up all targets and defining all commands to build them, use it in your main block like:

if not pbmain.main(*(sys.argv[1:])):
  sys.exit(1)

The arguments on the command line are used as follows:

ll=level sets the log level, where level can be any string abbreviating one of the lowercase loglevel keyswords, debug, verbose, info, warning, error, critical. An example is ll=v to switch to VERBOSE logging.

force=targetlist forces all targets in the comma separated list (no whitespace) to be handled as if out-of-date. This is useful for debugging a build step, for example doc, which produces its output files but with wrong content. By using force=doc the build step for doc can be re-run without other more cumbersome ways of invalidating its state hash, like deleting an output file. More than one target name can be provided like force=doc,coverage. All force targets are also added to the list of targets to consider for building. So when providing the command line arguments doc force=doc,apidoc, the doc argument is redundant and apidoc will be build even if doc does not depend on it.

Further command line arguments are assumed to be names of targets to be build, even if they contain an equal sign. Put another way, only the prefixes ll= and force= are interpreted as options. If, in the end, there is no target specified, all target names are listed on the console.

NOTE: There is no -- in front of the option names, following the only UNIX command which does this right, namely dd :-)

Parameters
*argv:strshould typically be just sys.argv or a trimmed version of it if you have your own command line parsing
Returns
boolthe return value of updateTargets or True if no targets are given
def printTargets(targets: Iterable[pb.Target[Any]] = pb.getTargets().values(), seen: set[str] = set()):

Undocumented

def touch(targetPath: Path, *deps: Any):

A function intended to be used as the cmd parameter of a Target to write the current iso datetime into the targetPath.

Use this function for

  • a shortcut/alias target, for example allLibs, which has no command to execute itself, but merely combines a list of targets into one to be conveniently invoked by the alias.
  • a target with no natural output, like a linter which is only run for its error output.

The targetPath is merely a bookkeeping file which is freshly written if and only if any of the dependencies was re-created (or the file itself does not exist, of course), so it should be a file in the build output, say BUILD / "allLibs.done" where BUILD is your build output directory.

Parameters
targetPath:Pathto write the current iso date/time into
*deps:Anythe dependencies are ignored
def _forceTargets(targetNamesOut: list[str], commaList: str) -> bool:

Helper for main.

def _setLogLevel(shortcut: str) -> bool:

Helper for main.

def _targetDepth(target: pb.Target) -> int:

Undocumented

_loglevels: list[str] =

FIMXE: when we have python 3.11 or newer, switch to logging.getLevelNamesMapping()

Value
['debug', 'verbose', 'info', 'warning', 'error', 'critical']