Added --profile, --watch, --breakpoint, --debug as global options (#917)
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user