Added --uuid-from-file to CLI

This commit is contained in:
Rhet Turnbull
2020-07-31 19:02:52 -07:00
parent 002fce8e93
commit 840e9937be
6 changed files with 211 additions and 8 deletions

View File

@@ -138,6 +138,9 @@ Options:
searches top level folders (e.g. does not searches top level folders (e.g. does not
look at subfolders) look at subfolders)
--uuid UUID Search for photos with UUID(s). --uuid UUID Search for photos with UUID(s).
--uuid-from-file FILE Search for photos with UUID(s) loaded from
FILE. Format is a single UUID per line.
Lines preceeded with # are ignored.
--title TITLE Search for TITLE in title of photo. --title TITLE Search for TITLE in title of photo.
--no-title Search for photos with no title. --no-title Search for photos with no title.
--description DESC Search for DESC in description of photo. --description DESC Search for DESC in description of photo.

View File

@@ -298,6 +298,15 @@ def query_options(f):
multiple=True, multiple=True,
help="Search for photos with UUID(s).", help="Search for photos with UUID(s).",
), ),
o(
"--uuid-from-file",
metavar="FILE",
default=None,
multiple=False,
help="Search for photos with UUID(s) loaded from FILE. "
"Format is a single UUID per line. Lines preceeded with # are ignored.",
type=click.Path(exists=True)
),
o( o(
"--title", "--title",
metavar="TITLE", metavar="TITLE",
@@ -891,6 +900,7 @@ def query(
album, album,
folder, folder,
uuid, uuid,
uuid_from_file,
title, title,
no_title, no_title,
description, description,
@@ -954,6 +964,7 @@ def query(
album, album,
folder, folder,
uuid, uuid,
uuid_from_file,
edited, edited,
external_edit, external_edit,
uti, uti,
@@ -998,6 +1009,12 @@ def query(
if only_photos: if only_photos:
ismovie = False ismovie = False
# load UUIDs if necessary and append to any uuids passed with --uuid
if uuid_from_file:
uuid_list = list(uuid) # Click option is a tuple
uuid_list.extend(load_uuid_from_file(uuid_from_file))
uuid = tuple(uuid_list)
# below needed for to make CliRunner work for testing # below needed for to make CliRunner work for testing
cli_db = cli_obj.db if cli_obj is not None else None cli_db = cli_obj.db if cli_obj is not None else None
db = get_photos_db(*photos_library, db, cli_db) db = get_photos_db(*photos_library, db, cli_db)
@@ -1247,6 +1264,7 @@ def export(
album, album,
folder, folder,
uuid, uuid,
uuid_from_file,
title, title,
no_title, no_title,
description, description,
@@ -1329,7 +1347,7 @@ def export(
VERBOSE = True if verbose_ else False VERBOSE = True if verbose_ else False
if not os.path.isdir(dest): if not os.path.isdir(dest):
sys.exit("DEST must be valid path") sys.exit(f"DEST {dest} must be valid path")
# sanity check input args # sanity check input args
exclusive = [ exclusive = [
@@ -1381,6 +1399,12 @@ def export(
if only_photos: if only_photos:
ismovie = False ismovie = False
# load UUIDs if necessary and append to any uuids passed with --uuid
if uuid_from_file:
uuid_list = list(uuid) # Click option is a tuple
uuid_list.extend(load_uuid_from_file(uuid_from_file))
uuid = tuple(uuid_list)
# below needed for to make CliRunner work for testing # below needed for to make CliRunner work for testing
cli_db = cli_obj.db if cli_obj is not None else None cli_db = cli_obj.db if cli_obj is not None else None
db = get_photos_db(*photos_library, db, cli_db) db = get_photos_db(*photos_library, db, cli_db)
@@ -2091,6 +2115,12 @@ def export_photo(
) )
return ExportResults([], [], [], [], []) return ExportResults([], [], [], [], [])
results_exported = []
results_new = []
results_updated = []
results_skipped = []
results_exif_updated = []
filenames = get_filenames_from_template(photo, filename_template, original_name) filenames = get_filenames_from_template(photo, filename_template, original_name)
for filename in filenames: for filename in filenames:
verbose(f"Exporting {photo.filename} as {filename}") verbose(f"Exporting {photo.filename} as {filename}")
@@ -2113,11 +2143,6 @@ def export_photo(
) )
# export the photo to each path in dest_paths # export the photo to each path in dest_paths
results_exported = []
results_new = []
results_updated = []
results_skipped = []
results_exif_updated = []
for dest_path in dest_paths: for dest_path in dest_paths:
export_results = photo.export2( export_results = photo.export2(
dest_path, dest_path,
@@ -2338,6 +2363,31 @@ def find_files_in_branch(pathname, filename):
return files return files
def load_uuid_from_file(filename):
""" Load UUIDs from file. Does not validate UUIDs.
Format is 1 UUID per line, any line beginning with # is ignored.
Whitespace is stripped.
Arguments:
filename: file name of the file containing UUIDs
Returns:
list of UUIDs or empty list of no UUIDs in file
Raises:
FileNotFoundError if file does not exist
"""
if not pathlib.Path(filename).is_file():
raise FileNotFoundError(f"Could not find file {filename}")
uuid = []
with open(filename, "r") as uuid_file:
for line in uuid_file:
line = line.strip()
if len(line) and line[0] != "#":
uuid.append(line)
return uuid
if __name__ == "__main__": if __name__ == "__main__":
cli() # pylint: disable=no-value-for-parameter cli() # pylint: disable=no-value-for-parameter

View File

@@ -1,3 +1,3 @@
""" version info """ """ version info """
__version__ = "0.31.0" __version__ = "0.31.1"

View File

@@ -1,3 +1,5 @@
""" Test the command line interface (CLI) """
import os import os
import pytest import pytest
@@ -14,6 +16,8 @@ PHOTOS_DB_15_4 = "tests/Test-10.15.4.photoslibrary"
PHOTOS_DB_15_5 = "tests/Test-10.15.5.photoslibrary" PHOTOS_DB_15_5 = "tests/Test-10.15.5.photoslibrary"
PHOTOS_DB_14_6 = "tests/Test-10.14.6.photoslibrary" PHOTOS_DB_14_6 = "tests/Test-10.14.6.photoslibrary"
UUID_FILE = "tests/uuid_from_file.txt"
CLI_OUTPUT_NO_SUBCOMMAND = [ CLI_OUTPUT_NO_SUBCOMMAND = [
"Options:", "Options:",
"--db <Photos database path> Specify Photos database path. Path to Photos", "--db <Photos database path> Specify Photos database path. Path to Photos",
@@ -295,6 +299,23 @@ ALBUMS_JSON = {
PERSONS_JSON = {"persons": {"Katie": 3, "Suzy": 2, "_UNKNOWN_": 1, "Maria": 2}} PERSONS_JSON = {"persons": {"Katie": 3, "Suzy": 2, "_UNKNOWN_": 1, "Maria": 2}}
UUID_EXPECTED_FROM_FILE = [
"E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51",
"6191423D-8DB8-4D4C-92BE-9BBBA308AAC4",
"A92D9C26-3A50-4197-9388-CB5F7DB9FA91",
]
UUID_NOT_FROM_FILE = "D79B8D77-BFFC-460B-9312-034F2877D35B"
CLI_EXPORT_UUID_FROM_FILE_FILENAMES = [
"IMG_1994.JPG",
"IMG_1994.cr2",
"Tulips.jpg",
"Tulips_edited.jpeg",
"wedding.jpg",
"wedding_edited.jpeg",
]
# determine if exiftool installed so exiftool tests can be skipped # determine if exiftool installed so exiftool tests can be skipped
try: try:
exiftool = get_exiftool_path() exiftool = get_exiftool_path()
@@ -390,6 +411,72 @@ def test_query_uuid():
assert json_expected[key_] in json_got[key_] assert json_expected[key_] in json_got[key_]
def test_query_uuid_from_file_1():
""" Test query with --uuid-from-file """
import json
import os
import os.path
import osxphotos
from osxphotos.__main__ import query
runner = CliRunner()
cwd = os.getcwd()
result = runner.invoke(
query,
[
"--json",
"--db",
os.path.join(cwd, PHOTOS_DB_15_5),
"--uuid-from-file",
UUID_FILE,
],
)
assert result.exit_code == 0
# build list of uuids we got from the output JSON
json_got = json.loads(result.output)
uuid_got = []
for photo in json_got:
uuid_got.append(photo["uuid"])
assert sorted(UUID_EXPECTED_FROM_FILE) == sorted(uuid_got)
def test_query_uuid_from_file_2():
""" Test query with --uuid-from-file and --uuid """
import json
import os
import os.path
import osxphotos
from osxphotos.__main__ import query
runner = CliRunner()
cwd = os.getcwd()
result = runner.invoke(
query,
[
"--json",
"--db",
os.path.join(cwd, PHOTOS_DB_15_5),
"--uuid-from-file",
UUID_FILE,
"--uuid",
UUID_NOT_FROM_FILE,
],
)
assert result.exit_code == 0
# build list of uuids we got from the output JSON
json_got = json.loads(result.output)
uuid_got = []
for photo in json_got:
uuid_got.append(photo["uuid"])
uuid_expected = UUID_EXPECTED_FROM_FILE.copy()
uuid_expected.append(UUID_NOT_FROM_FILE)
assert sorted(uuid_expected) == sorted(uuid_got)
def test_export(): def test_export():
import glob import glob
import os import os
@@ -407,6 +494,33 @@ def test_export():
assert sorted(files) == sorted(CLI_EXPORT_FILENAMES) assert sorted(files) == sorted(CLI_EXPORT_FILENAMES)
def test_export_uuid_from_file():
""" Test export with --uuid-from-file """
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_5),
".",
"-V",
"--uuid-from-file",
os.path.join(cwd, UUID_FILE),
],
)
assert result.exit_code == 0
files = glob.glob("*")
assert sorted(files) == sorted(CLI_EXPORT_UUID_FROM_FILE_FILENAMES)
def test_export_as_hardlink(): def test_export_as_hardlink():
import glob import glob
import os import os
@@ -2468,7 +2582,7 @@ def test_albums():
def test_persons(): def test_persons():
"""Test osxphotos albums """ """Test osxphotos persons """
import json import json
import osxphotos import osxphotos
import os import os

29
tests/test_cli_utils.py Normal file
View File

@@ -0,0 +1,29 @@
""" Test utility functions in __main__.py """
import pytest
UUID_FILE = "tests/uuid_from_file.txt"
MISSING_UUID_FILE = "tests/uuid_not_found.txt"
UUID_EXPECTED_FROM_FILE = [
"E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51",
"6191423D-8DB8-4D4C-92BE-9BBBA308AAC4",
"A92D9C26-3A50-4197-9388-CB5F7DB9FA91",
]
def test_load_uuid_from_file():
"""Test load_uuid_from_file function """
from osxphotos.__main__ import load_uuid_from_file
uuid_got = load_uuid_from_file(UUID_FILE)
assert uuid_got == UUID_EXPECTED_FROM_FILE
def test_load_uuid_from_file_filenotfound():
"""Test load_uuid_from_file function raises error if file not found"""
from osxphotos.__main__ import load_uuid_from_file
with pytest.raises(FileNotFoundError) as err:
uuid_got = load_uuid_from_file(MISSING_UUID_FILE)
assert "Could not find" in str(err.value)

7
tests/uuid_from_file.txt Normal file
View File

@@ -0,0 +1,7 @@
# Test file for load_uuid_from_file
E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51
6191423D-8DB8-4D4C-92BE-9BBBA308AAC4
# D79B8D77-BFFC-460B-9312-034F2877D35B
A92D9C26-3A50-4197-9388-CB5F7DB9FA91