From 25eacc7caddd6721232b3f77a02532fcd35f7836 Mon Sep 17 00:00:00 2001 From: Rhet Turnbull Date: Sun, 29 Nov 2020 15:30:45 -0800 Subject: [PATCH] Added --missing to export, see issue #277 --- README.md | 6 +++- osxphotos/__main__.py | 38 ++++++++++++++++------ osxphotos/_version.py | 2 +- tests/test_cli.py | 73 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 85943fdb..23264cf7 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,9 @@ Options: --no-comment Search for photos with no comments. --has-likes Search for photos that have likes. --no-likes Search for photos with no likes. + --missing Export only photos missing from the Photos + library; must be used with --download- + missing. --deleted Include photos from the 'Recently Deleted' folder. --deleted-only Include only photos from the 'Recently @@ -234,7 +237,8 @@ Options: --update Only export new or updated files. See notes below on export and --update. --dry-run Dry run (test) the export but don't actually - export any files; most useful with --verbose + export any files; most useful with + --verbose. --export-as-hardlink Hardlink files instead of copying them. Cannot be used with --exiftool which creates copies of the files with embedded EXIF data. diff --git a/osxphotos/__main__.py b/osxphotos/__main__.py index 8670ace2..1d25c7a5 100644 --- a/osxphotos/__main__.py +++ b/osxphotos/__main__.py @@ -98,7 +98,7 @@ class DateTimeISO8601(click.ParamType): def convert(self, value, param, ctx): try: return datetime.datetime.fromisoformat(value) - except: + except Exception: self.fail( f"Invalid value for --{param.name}: invalid datetime format {value}. " "Valid format: YYYY-MM-DD[*HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]]" @@ -651,7 +651,7 @@ def debug_dump(ctx, cli_obj, db, photos_library, dump, uuid, verbose_): val = getattr(photosdb, attr) print(f"{attr}:") pprint.pprint(val) - except: + except Exception: print(f"Did not find attribute {attr} in PhotosDB") @@ -846,12 +846,12 @@ def places(ctx, cli_obj, db, json_, photos_library): if photo.place: try: place_names[photo.place.name] += 1 - except: + except Exception: place_names[photo.place.name] = 1 else: try: place_names[_UNKNOWN_PLACE] += 1 - except: + except Exception: place_names[_UNKNOWN_PLACE] = 1 # sort by place count @@ -1199,6 +1199,11 @@ def query( @DB_OPTION @click.option("--verbose", "-V", "verbose_", is_flag=True, help="Print verbose output.") @query_options +@click.option( + "--missing", + is_flag=True, + help="Export only photos missing from the Photos library; must be used with --download-missing.", +) @deleted_options @click.option( "--update", @@ -1208,7 +1213,7 @@ def query( @click.option( "--dry-run", is_flag=True, - help="Dry run (test) the export but don't actually export any files; most useful with --verbose", + help="Dry run (test) the export but don't actually export any files; most useful with --verbose.", ) @click.option( "--export-as-hardlink", @@ -1451,6 +1456,7 @@ def export( from_date, to_date, verbose_, + missing, update, dry_run, export_as_hardlink, @@ -1567,6 +1573,20 @@ def export( click.echo(cli.commands["export"].get_help(ctx), err=True) return + if export_as_hardlink and download_missing: + click.echo( + "Incompatible export options: --export-as-hardlink is not compatible with --download-missing", + err=True, + ) + raise click.Abort() + + if missing and not download_missing: + click.echo( + "Incompatible export options: --missing must be used with --download-missing", + err=True, + ) + raise click.Abort() + if use_photokit and not check_photokit_authorization(): click.echo( "Requesting access to use your Photos library. Click 'OK' on the dialog box to grant access." @@ -1674,7 +1694,7 @@ def export( not_favorite=not_favorite, hidden=hidden, not_hidden=not_hidden, - missing=None, # missing -- won't export these but will warn user + missing=missing, not_missing=None, shared=shared, not_shared=not_shared, @@ -2575,7 +2595,7 @@ def export_photo( verbose(f"Skipped up to date file {skipped}") for touched in export_results.touched: verbose(f"Touched date on file {touched}") - except: + except Exception: click.echo( f"Error exporting photo {photo.original_filename} ({photo.filename}) as {original_filename}", err=True, @@ -2603,7 +2623,7 @@ def export_photo( f"{edited_filename.stem}{edited_suffix}{edited_ext}" ) verbose( - f"Exporting edited version of {filename} as {edited_filename}" + f"Exporting edited version of {photo.original_filename} ({photo.filename}) as {edited_filename}" ) try: export_results_edited = photo.export2( @@ -2666,7 +2686,7 @@ def export_photo( verbose(f"Skipped up to date file {skipped}") for touched in export_results_edited.touched: verbose(f"Touched date on file {touched}") - except: + except Exception: click.echo( f"Error exporting photo {filename} as {edited_filename}", err=True, diff --git a/osxphotos/_version.py b/osxphotos/_version.py index ed191aac..c4891056 100644 --- a/osxphotos/_version.py +++ b/osxphotos/_version.py @@ -1,4 +1,4 @@ """ version info """ -__version__ = "0.37.1" +__version__ = "0.37.2" diff --git a/tests/test_cli.py b/tests/test_cli.py index 4f4f3c81..2ce267da 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -17,6 +17,7 @@ PLACES_PHOTOS_DB_13 = "tests/Test-Places-High-Sierra-10.13.6.photoslibrary" PHOTOS_DB_15_4 = "tests/Test-10.15.4.photoslibrary" PHOTOS_DB_15_5 = "tests/Test-10.15.5.photoslibrary" PHOTOS_DB_15_6 = "tests/Test-10.15.6.photoslibrary" +PHOTOS_DB_15_7 = "tests/Test-10.15.7.photoslibrary" PHOTOS_DB_TOUCH = PHOTOS_DB_15_6 PHOTOS_DB_14_6 = "tests/Test-10.14.6.photoslibrary" @@ -3804,3 +3805,75 @@ def test_export_report_not_a_file(): assert result.exit_code != 0 assert "Aborted!" in result.output + +def test_export_as_hardlink_download_missing(): + """ test export with incompatible export options """ + import glob + import os + import os.path + import osxphotos + from osxphotos.__main__ import export + + runner = CliRunner() + cwd = os.getcwd() + # pylint: disable=not-context-manager + with runner.isolated_filesystem(): + result = runner.invoke( + export, + [ + os.path.join(cwd, CLI_PHOTOS_DB), + ".", + "-V", + "--download-missing", + "--export-as-hardlink", + ".", + ], + ) + assert result.exit_code != 0 + assert "Aborted!" in result.output + + +def test_export_missing(): + """ test export with --missing """ + import glob + import os + import os.path + import osxphotos + from osxphotos.__main__ import export + + runner = CliRunner() + cwd = os.getcwd() + # pylint: disable=not-context-manager + with runner.isolated_filesystem(): + result = runner.invoke( + export, + [ + os.path.join(cwd, PHOTOS_DB_15_7), + ".", + "-V", + "--missing", + "--download-missing", + ".", + ], + ) + assert result.exit_code == 0 + assert "Exporting 2 photos" in result.output + + +def test_export_missing_not_download_missing(): + """ test export with incompatible export options """ + import glob + import os + import os.path + import osxphotos + from osxphotos.__main__ import export + + runner = CliRunner() + cwd = os.getcwd() + # pylint: disable=not-context-manager + with runner.isolated_filesystem(): + result = runner.invoke( + export, [os.path.join(cwd, CLI_PHOTOS_DB), ".", "-V", "--missing", "."] + ) + assert result.exit_code != 0 + assert "Aborted!" in result.output