Added --profile, --watch, --breakpoint, --debug as global options (#917)

This commit is contained in:
Rhet Turnbull
2023-01-13 17:49:30 -08:00
committed by GitHub
parent 106b258c6d
commit 4b3433fc20
5 changed files with 67 additions and 71 deletions

View File

@@ -1,4 +1,5 @@
"""cli package for osxphotos""" """cli package for osxphotos"""
import sys import sys
from rich import print from rich import print
@@ -26,13 +27,13 @@ for func_name in args.get("--watch", []):
wrap_function(func_name, debug_watch) wrap_function(func_name, debug_watch)
print(f"Watching {func_name}") print(f"Watching {func_name}")
except AttributeError: except AttributeError:
print(f"{func_name} does not exist") print(f"{func_name} does not exist", file=sys.stderr)
sys.exit(1) sys.exit(1)
for func_name in args.get("--breakpoint", []): for func_name in args.get("--breakpoint", []):
try: try:
wrap_function(func_name, debug_breakpoint) wrap_function(func_name, debug_breakpoint)
print(f"Breakpoint added for {func_name}") print(f"Breakpoint added for {func_name}", file=sys.stderr)
except AttributeError: except AttributeError:
print(f"{func_name} does not exist") print(f"{func_name} does not exist")
sys.exit(1) sys.exit(1)
@@ -40,7 +41,7 @@ for func_name in args.get("--breakpoint", []):
args = get_debug_flags(["--debug"], sys.argv) args = get_debug_flags(["--debug"], sys.argv)
if args.get("--debug", False): if args.get("--debug", False):
set_debug(True) set_debug(True)
print("Debugging enabled") print("Debugging enabled", file=sys.stderr)
from .about import about from .about import about
from .albums import albums from .albums import albums

View File

@@ -1,7 +1,13 @@
"""Command line interface for osxphotos """ """Command line interface for osxphotos """
import atexit
import cProfile
import io
import pstats
import click import click
from osxphotos._constants import PROFILE_SORT_KEYS
from osxphotos._version import __version__ from osxphotos._version import __version__
from .about import about from .about import about
@@ -33,6 +39,7 @@ from .timewarp import timewarp
from .tutorial import tutorial from .tutorial import tutorial
from .uuid import uuid from .uuid import uuid
from .version import version from .version import version
from .common import DEBUG_OPTIONS
# Click CLI object & context settings # Click CLI object & context settings
@@ -47,20 +54,51 @@ CTX_SETTINGS = dict(help_option_names=["-h", "--help"])
@click.group(context_settings=CTX_SETTINGS) @click.group(context_settings=CTX_SETTINGS)
@click.version_option(__version__, "--version", "-v")
@DB_OPTION @DB_OPTION
@JSON_OPTION @JSON_OPTION
@DEBUG_OPTIONS
@click.option( @click.option(
"--debug", "--profile", is_flag=True, hidden=OSXPHOTOS_HIDDEN, help="Enable profiling"
required=False, )
is_flag=True, @click.option(
help="Enable debug output", "--profile-sort",
hidden=OSXPHOTOS_HIDDEN, default=None,
hidden=OSXPHOTOS_HIDDEN,
multiple=True,
metavar="SORT_KEY",
type=click.Choice(
PROFILE_SORT_KEYS,
case_sensitive=True,
),
help="Sort profiler output by SORT_KEY as specified at https://docs.python.org/3/library/profile.html#pstats.Stats.sort_stats. "
f"Can be specified multiple times. Valid options are: {PROFILE_SORT_KEYS}. "
"Default = 'cumulative'.",
) )
@click.version_option(__version__, "--version", "-v")
@click.pass_context @click.pass_context
def cli_main(ctx, db, json_, debug): def cli_main(ctx, db, json_, profile, profile_sort, **kwargs):
"""osxphotos: query and export your Photos library""" """osxphotos: the multi-tool for your Photos library"""
# Note: kwargs is used to catch any debug options passed in
# the debug options are handled in cli/__init__.py
# before this function is called
ctx.obj = CLI_Obj(db=db, json=json_, group=cli_main) ctx.obj = CLI_Obj(db=db, json=json_, group=cli_main)
if profile:
click.echo("Profiling...")
profile_sort = profile_sort or ["cumulative"]
click.echo(f"Profile sort_stats order: {profile_sort}")
pr = cProfile.Profile()
pr.enable()
def at_exit():
pr.disable()
click.echo("Profiling completed")
s = io.StringIO()
pstats.Stats(pr, stream=s).strip_dirs().sort_stats(
*profile_sort
).print_stats()
click.echo(s.getvalue())
atexit.register(at_exit)
# install CLI commands # install CLI commands

View File

@@ -570,18 +570,24 @@ def DEBUG_OPTIONS(f):
), ),
o( o(
"--watch", "--watch",
metavar="FUNCTION_PATH", metavar="MODULE::NAME",
multiple=True, multiple=True,
help="Watch function calls. For example, to watch all calls to FileUtil.copy: " help="Watch function or method calls. The function to watch must be in the form "
"'--watch osxphotos.fileutil.FileUtil.copy'. More than one --watch option can be specified.", "MODULE::NAME where MODULE is the module path and NAME is the function or method name "
"contained in the module. For example, to watch all calls to FileUtil.copy() which is in "
"osxphotos.fileutil, use: "
"'--watch osxphotos.fileutil::FileUtil.copy'. More than one --watch option can be specified.",
hidden=OSXPHOTOS_HIDDEN, hidden=OSXPHOTOS_HIDDEN,
), ),
o( o(
"--breakpoint", "--breakpoint",
metavar="FUNCTION_PATH", metavar="MODULE::NAME",
multiple=True, multiple=True,
help="Add breakpoint to function calls. For example, to add breakpoint to FileUtil.copy: " help="Add breakpoint to function calls. The function to watch must be in the form "
"'--breakpoint osxphotos.fileutil.FileUtil.copy'. More than one --breakpoint option can be specified.", "MODULE::NAME where MODULE is the module path and NAME is the function or method name "
"contained in the module. For example, to set a breakpoint for calls to "
"FileUtil.copy() which is in osxphotos.fileutil, use: "
"'--breakpoint osxphotos.fileutil::FileUtil.copy'. More than one --breakpoint option can be specified.",
hidden=OSXPHOTOS_HIDDEN, hidden=OSXPHOTOS_HIDDEN,
), ),
] ]

View File

@@ -1,12 +1,7 @@
"""export command for osxphotos CLI""" """export command for osxphotos CLI"""
import atexit
import cProfile
import csv
import io
import os import os
import pathlib import pathlib
import pstats
import shlex import shlex
import subprocess import subprocess
import sys import sys
@@ -47,7 +42,7 @@ from osxphotos.configoptions import (
) )
from osxphotos.crash_reporter import crash_reporter, set_crash_data from osxphotos.crash_reporter import crash_reporter, set_crash_data
from osxphotos.datetime_formatter import DateTimeFormatter from osxphotos.datetime_formatter import DateTimeFormatter
from osxphotos.debug import is_debug, set_debug from osxphotos.debug import is_debug
from osxphotos.exiftool import get_exiftool_path from osxphotos.exiftool import get_exiftool_path
from osxphotos.export_db import ExportDB, ExportDBInMemory from osxphotos.export_db import ExportDB, ExportDBInMemory
from osxphotos.fileutil import FileUtil, FileUtilNoOp, FileUtilShUtil from osxphotos.fileutil import FileUtil, FileUtilNoOp, FileUtilShUtil
@@ -78,7 +73,6 @@ from .common import (
CLI_COLOR_WARNING, CLI_COLOR_WARNING,
DB_ARGUMENT, DB_ARGUMENT,
DB_OPTION, DB_OPTION,
DEBUG_OPTIONS,
DELETED_OPTIONS, DELETED_OPTIONS,
JSON_OPTION, JSON_OPTION,
OSXPHOTOS_CRASH_LOG, OSXPHOTOS_CRASH_LOG,
@@ -713,29 +707,7 @@ from .verbose import get_verbose_console, time_stamp, verbose_print
hidden=OSXPHOTOS_HIDDEN, hidden=OSXPHOTOS_HIDDEN,
help="Enable beta options.", help="Enable beta options.",
) )
@click.option(
"--profile",
is_flag=True,
default=False,
hidden=OSXPHOTOS_HIDDEN,
help="Run export with code profiler.",
)
@click.option(
"--profile-sort",
default=None,
hidden=OSXPHOTOS_HIDDEN,
multiple=True,
metavar="SORT_KEY",
type=click.Choice(
PROFILE_SORT_KEYS,
case_sensitive=True,
),
help="Sort profiler output by SORT_KEY as specified at https://docs.python.org/3/library/profile.html#pstats.Stats.sort_stats. "
f"Can be specified multiple times. Valid options are: {PROFILE_SORT_KEYS}. "
"Default = 'cumulative'.",
)
@THEME_OPTION @THEME_OPTION
@DEBUG_OPTIONS
@DB_ARGUMENT @DB_ARGUMENT
@click.argument("dest", nargs=1, type=click.Path(exists=True)) @click.argument("dest", nargs=1, type=click.Path(exists=True))
@click.pass_obj @click.pass_obj
@@ -865,8 +837,6 @@ def export(
preview_if_missing, preview_if_missing,
preview_suffix, preview_suffix,
print_template, print_template,
profile,
profile_sort,
query_eval, query_eval,
query_function, query_function,
ramdb, ramdb,
@@ -908,9 +878,9 @@ def export(
verbose, verbose,
xattr_template, xattr_template,
year, year,
debug, # debug, watch, breakpoint handled in cli/__init__.py # debug, # debug, watch, breakpoint handled in cli/__init__.py
watch, # watch,
breakpoint, # breakpoint,
): ):
"""Export photos from the Photos database. """Export photos from the Photos database.
Export path DEST is required. Export path DEST is required.
@@ -936,27 +906,8 @@ def export(
# capture locals for use with ConfigOptions before changing any of them # capture locals for use with ConfigOptions before changing any of them
locals_ = locals() locals_ = locals()
set_crash_data("locals", locals_) set_crash_data("locals", locals_)
if profile:
click.echo("Profiling...")
profile_sort = profile_sort or ["cumulative"]
click.echo(f"Profile sort_stats order: {profile_sort}")
pr = cProfile.Profile()
pr.enable()
def at_exit():
pr.disable()
click.echo("Profiling completed")
s = io.StringIO()
pstats.Stats(pr, stream=s).strip_dirs().sort_stats(
*profile_sort
).print_stats()
click.echo(s.getvalue())
atexit.register(at_exit)
# NOTE: because of the way ConfigOptions works, Click options must not # NOTE: because of the way ConfigOptions works, Click options must not
# set defaults which are not None or False. If defaults need to be set # set defaults which are not None or False. If defaults need to be set
# do so below after load_config and save_config are handled. # do so below after load_config and save_config are handled.

View File

@@ -49,7 +49,7 @@ def debug_breakpoint(wrapped, instance, args, kwargs):
def wrap_function(function_path, wrapper): def wrap_function(function_path, wrapper):
"""Wrap a function with wrapper function""" """Wrap a function with wrapper function"""
module, name = function_path.split(".", 1) module, name = function_path.split("::", 1)
try: try:
return wrapt.wrap_function_wrapper(module, name, wrapper) return wrapt.wrap_function_wrapper(module, name, wrapper)
except AttributeError as e: except AttributeError as e: