diff --git a/osxphotos/cli/export.py b/osxphotos/cli/export.py index 99f89f56..df6a4bbf 100644 --- a/osxphotos/cli/export.py +++ b/osxphotos/cli/export.py @@ -55,7 +55,7 @@ from osxphotos.photosalbum import PhotosAlbum from osxphotos.phototemplate import PhotoTemplate, RenderOptions from osxphotos.queryoptions import QueryOptions from osxphotos.uti import get_preferred_uti_extension -from osxphotos.utils import format_sec_to_hhmmss, normalize_fs_path +from osxphotos.utils import format_sec_to_hhmmss, normalize_fs_path, pluralize from .click_rich_echo import ( rich_click_echo, @@ -138,6 +138,13 @@ from .verbose import get_verbose_console, time_stamp, verbose_print help="If used with --update, ignores any previously exported files, even if missing from " "the export folder and only exports new files that haven't previously been exported.", ) +@click.option( + "--limit", + metavar="LIMIT", + help="Export at most LIMIT photos. " + "Useful for testing. Maybe used with --update to export incrementally.", + type=int, +) @click.option( "--dry-run", is_flag=True, @@ -730,6 +737,7 @@ def export( keyword_template, keyword, label, + limit, live, load_config, location, @@ -945,6 +953,7 @@ def export( keyword = cfg.keyword keyword_template = cfg.keyword_template label = cfg.label + limit = cfg.limit live = cfg.live location = cfg.location max_size = cfg.max_size @@ -1383,8 +1392,7 @@ def export( if photos: num_photos = len(photos) - # TODO: photos or photo appears several times, pull into a separate function - photo_str = "photos" if num_photos > 1 else "photo" + photo_str = pluralize(num_photos, "photo", "photos") rich_echo( f"Exporting [num]{num_photos}[/num] {photo_str} to [filepath]{dest}[/]..." ) @@ -1412,9 +1420,11 @@ def export( ) photo_num = 0 + num_exported = 0 + limit_str = f" (limit = [num]{limit}[/num])" if limit else "" with rich_progress(console=get_verbose_console(), mock=no_progress) as progress: task = progress.add_task( - f"Exporting [num]{num_photos}[/] photos", total=num_photos + f"Exporting [num]{num_photos}[/] photos{limit_str}", total=num_photos ) for p in photos: photo_num += 1 @@ -1579,7 +1589,17 @@ def export( progress.advance(task) - photo_str_total = "photos" if len(photos) != 1 else "photo" + # handle limit + if export_results.exported: + # if any photos were exported, increment num_exported used by limit + # limit considers each PhotoInfo object as a single photo even if multiple files are exported + num_exported += 1 + if limit and num_exported >= limit: + # advance progress to end + progress.advance(task, num_photos - photo_num) + break + + photo_str_total = pluralize(len(photos), "photo", "photos") if update or force_update: summary = ( f"Processed: [num]{len(photos)}[/] {photo_str_total}, " @@ -1597,6 +1617,8 @@ def export( summary += f"error: [num]{len(results.error)}[/]" if touch_file: summary += f", touched date: [num]{len(results.touched)}[/]" + if limit: + summary += f", limit: [num]{num_exported}[/]/[num]{limit}[/] exported" rich_echo(summary) stop_time = time.perf_counter() rich_echo(f"Elapsed time: [time]{format_sec_to_hhmmss(stop_time-start_time)}") diff --git a/osxphotos/utils.py b/osxphotos/utils.py index a5e2a137..bd03d743 100644 --- a/osxphotos/utils.py +++ b/osxphotos/utils.py @@ -516,7 +516,7 @@ def get_latest_version() -> Tuple[Optional[str], str]: return None, e -def pluralize(count, singular, plural): +def pluralize(count: Optional[int], singular: str, plural: str) -> str: """Return singular or plural based on count""" return singular if count == 1 else plural diff --git a/tests/conftest.py b/tests/conftest.py index dc125675..d0fed118 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,8 +12,12 @@ from osxphotos.exiftool import _ExifToolProc from .test_catalina_10_15_7 import UUID_DICT_LOCAL +# run timewarp tests (configured with --timewarp) TEST_TIMEWARP = False +# don't clean up crash logs (configured with --no-cleanup) +NO_CLEANUP = False + def get_os_version(): import platform @@ -68,6 +72,12 @@ def pytest_addoption(parser): parser.addoption( "--timewarp", action="store_true", default=False, help="run --timewarp tests" ) + parser.addoption( + "--no-cleanup", + action="store_true", + default=False, + help="don't clean up crash logs after tests", + ) def pytest_configure(config): @@ -81,10 +91,15 @@ def pytest_configure(config): "markers", "timewarp: mark test as requiring --timewarp to run" ) + # this is hacky but I can't figure out how to check config options in other fixtures if config.getoption("--timewarp"): global TEST_TIMEWARP TEST_TIMEWARP = True + if config.getoption("--no-cleanup"): + global NO_CLEANUP + NO_CLEANUP = True + def pytest_collection_modifyitems(config, items): if not (config.getoption("--addalbum") and TEST_LIBRARY is not None): @@ -163,7 +178,7 @@ def delete_crash_logs(): """Delete left over crash logs from tests that were supposed to crash""" yield path = pathlib.Path(os.getcwd()) / "osxphotos_crash.log" - if path.is_file(): + if path.is_file() and not NO_CLEANUP: path.unlink() diff --git a/tests/test_cli.py b/tests/test_cli.py index f17e3123..d7e819dc 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -7715,3 +7715,60 @@ def test_export_added_in_last(): ) assert result.exit_code == 0 assert "Exporting" in result.output + + +def test_export_limit(): + """test export --limit""" + + # Use --added-before so test doesn't break if photos added in the future + + runner = CliRunner() + cwd = os.getcwd() + with runner.isolated_filesystem(): + result = runner.invoke( + export, + [ + ".", + "--db", + os.path.join(cwd, PHOTOS_DB_15_7), + "--update", + "--limit", + "20", + "--added-before", + "2022-05-07", + ], + ) + assert result.exit_code == 0 + assert "limit: 20/20 exported" in result.output + + result = runner.invoke( + export, + [ + ".", + "--db", + os.path.join(cwd, PHOTOS_DB_15_7), + "--update", + "--limit", + "20", + "--added-before", + "2022-05-07", + ], + ) + assert result.exit_code == 0 + assert "limit: 5/20 exported" in result.output + + result = runner.invoke( + export, + [ + ".", + "--db", + os.path.join(cwd, PHOTOS_DB_15_7), + "--update", + "--limit", + "20", + "--added-before", + "2022-05-07", + ], + ) + assert result.exit_code == 0 + assert "limit: 0/20 exported" in result.output