Skip to content

Instantly share code, notes, and snippets.

@FMeinicke
Created May 16, 2024 14:12
Show Gist options
  • Select an option

  • Save FMeinicke/8e728241c9c12f9470cde530bef3a9d6 to your computer and use it in GitHub Desktop.

Select an option

Save FMeinicke/8e728241c9c12f9470cde530bef3a9d6 to your computer and use it in GitHub Desktop.
Python typer CLI application template
import sys
if sys.version_info.minor == 7:
import importlib_metadata as metadata
else:
from importlib import metadata
__version__ = metadata.version(__name__)
import logging
import os
import sys
import time
from pathlib import Path
from typing import Optional
import typer
from PySide6.QtCore import QCoreApplication
from qmixsdk.qmixbus import Bus
from qmixsdk.qmixvalve import Valve as QmixValve
from typing_extensions import Annotated
from . import __name__ as application_name
from . import __version__
_logger = logging.getLogger(__name__)
app = typer.Typer()
def show_version(show_version: bool):
"""
Callback function for the --version option of the application.
Prints the version of the application and exits if the option was set.
Parameters
----------
show_version : bool
Whether the option was set or not
"""
if show_version:
print(f"{application_name} v{__version__}")
raise typer.Exit()
_LOGGING_FORMAT = (
"%(asctime)s [%(threadName)-{thread_name_len}.{thread_name_len}s] %(levelname)-8s| "
"%(name)s %(module)s.%(funcName)s (%(lineno)s): %(message)s"
)
def set_logging_level(log_level: str):
"""
Callback function for the --log-level option of the application.
Sets the logging level for the application.
Parameters
----------
log_level : str
The logging level from the command line option
Returns
-------
str
The logging level that was set
"""
logging_level = log_level.upper()
try:
import coloredlogs
coloredlogs.install(
fmt=_LOGGING_FORMAT.format(thread_name_len=12), datefmt="%Y-%m-%d %H:%M:%S,%f", level=logging_level
)
except ModuleNotFoundError:
print("Cannot find coloredlogs! Please install coloredlogs, if you'd like to have nicer logging output:")
print("`pip install coloredlogs`")
logging.basicConfig(format=_LOGGING_FORMAT.format(thread_name_len=12), level=logging_level)
return log_level
def setup_logging(log_level: str, log_file_dir: Optional[Path] = None) -> None:
"""
Sets up logging for the application.
Parameters
----------
log_level : str
The logging level for the application from the command line option
log_file_dir : Path, optional
The directory to write log files to from the command line option
"""
def log_file_name(dir: Path, log_level: str) -> Path:
return dir.joinpath(f"{application_name}-{log_level.lower()}-{time.strftime('%Y-%m-%d_%H-%M-%S')}.log")
def make_log_file_handler(log_file_dir: Path, log_level: str) -> logging.FileHandler:
os.makedirs(log_file_dir, exist_ok=True)
log_file_handler = logging.FileHandler(log_file_name(log_file_dir, log_level))
log_file_handler.setFormatter(logging.Formatter(_LOGGING_FORMAT.format(thread_name_len=60)))
return log_file_handler
log_file_handler: Optional[logging.FileHandler] = None
if log_file_dir is not None:
log_file_handler = make_log_file_handler(log_file_dir, log_level)
logging.getLogger().addHandler(log_file_handler)
logging.info(f"Writing log to file {log_file_handler.baseFilename!r}")
logging.info(f"Starting log for {sys.executable} with args {sys.argv}")
# -----------------------------------------------------------------------------
# main program
@app.command(no_args_is_help=False, context_settings={"help_option_names": ["-h", "--help"]})
def main(
some_arg: Annotated[
Path,
typer.Argument(
metavar="SOME_ARG",
help="An example argument.",
exists=True,
file_okay=False,
dir_okay=True,
writable=False,
readable=True,
resolve_path=True,
),
],
version: Annotated[
Optional[bool],
typer.Option(
"--version",
"-v",
callback=show_version,
is_eager=True,
help="Show the application's version number and exit.",
),
] = None,
log_level: Annotated[
str,
typer.Option(
"--log-level",
"-l",
callback=set_logging_level,
metavar="LEVEL",
help="Set the logging level for the application.",
case_sensitive=False,
formats=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
),
] = "INFO",
log_file_dir: Annotated[
Optional[Path],
typer.Option(
metavar="DIR",
help="The directory to write log files to (if not given, log messages will only be printed to standard out)",
exists=False,
file_okay=False,
dir_okay=True,
readable=True,
writable=True,
resolve_path=True,
),
] = None,
) -> None:
"""
Typer application template.
"""
setup_logging(log_level, log_file_dir)
qapp = QCoreApplication(sys.argv)
_logger.info("Starting application!")
exit_code = qapp.exec()
_logger.debug(f"Application exited with exit code {exit_code}")
if __name__ == "__main__":
app()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment