Added --dry-run option to CLI export, closes #91

This commit is contained in:
Rhet Turnbull
2020-05-25 10:37:30 -07:00
parent 46fdc94398
commit 9eae66030e
13 changed files with 888 additions and 428 deletions

View File

@@ -1299,8 +1299,116 @@ def test_export_then_hardlink():
assert not os.path.samefile(CLI_EXPORT_UUID_FILENAME, photo.path)
result = runner.invoke(
export, [os.path.join(cwd, CLI_PHOTOS_DB), ".", "--export-as-hardlink", "--overwrite"]
export,
[
os.path.join(cwd, CLI_PHOTOS_DB),
".",
"--export-as-hardlink",
"--overwrite",
],
)
assert result.exit_code == 0
assert "Exported: 8 photos" in result.output
assert os.path.samefile(CLI_EXPORT_UUID_FILENAME, photo.path)
def test_export_dry_run():
""" test export with dry-run flag """
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", "--dry-run"]
)
assert result.exit_code == 0
assert "Exported: 8 photos" in result.output
for filepath in CLI_EXPORT_FILENAMES:
assert f"Exported {filepath}" in result.output
assert not os.path.isfile(filepath)
def test_export_update_edits_dry_run():
""" test export then update after removing and editing files with dry-run flag """
import glob
import os
import os.path
import shutil
import osxphotos
from osxphotos.__main__ import export
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
# basic export
result = runner.invoke(
export, [os.path.join(cwd, CLI_PHOTOS_DB), ".", "-V", "--export-by-date"]
)
assert result.exit_code == 0
# change a couple of destination photos
os.unlink(CLI_EXPORT_BY_DATE[1])
shutil.copyfile(CLI_EXPORT_BY_DATE[0], CLI_EXPORT_BY_DATE[1])
os.unlink(CLI_EXPORT_BY_DATE[0])
# update dry-run
result = runner.invoke(
export,
[
os.path.join(cwd, CLI_PHOTOS_DB),
".",
"--update",
"--export-by-date",
"--dry-run",
],
)
assert result.exit_code == 0
assert (
"Exported: 1 photo, updated: 1 photo, skipped: 6 photos, updated EXIF data: 0 photos"
in result.output
)
# make sure file didn't really get copied
assert not os.path.isfile(CLI_EXPORT_BY_DATE[0])
def test_export_directory_template_1_dry_run():
""" test export using directory template with dry-run flag """
import glob
import locale
import os
import os.path
import osxphotos
from osxphotos.__main__ import export
locale.setlocale(locale.LC_ALL, "en_US")
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",
"--directory",
"{created.year}/{created.month}",
"--dry-run",
],
)
assert result.exit_code == 0
assert "Exported: 8 photos" in result.output
workdir = os.getcwd()
for filepath in CLI_EXPORTED_DIRECTORY_TEMPLATE_FILENAMES1:
assert f"Exported {filepath}" in result.output
assert not os.path.isfile(os.path.join(workdir, filepath))

View File

@@ -94,12 +94,12 @@ def test_setvalue_1():
# test setting a tag value
import os.path
import tempfile
from osxphotos.utils import _copy_file
import osxphotos.exiftool
from osxphotos.fileutil import FileUtil
tempdir = tempfile.TemporaryDirectory(prefix="osxphotos_")
tempfile = os.path.join(tempdir.name, os.path.basename(TEST_FILE_ONE_KEYWORD))
_copy_file(TEST_FILE_ONE_KEYWORD, tempfile)
FileUtil.copy(TEST_FILE_ONE_KEYWORD, tempfile)
exif = osxphotos.exiftool.ExifTool(tempfile)
exif.setvalue("IPTC:Keywords", "test")
@@ -111,12 +111,12 @@ def test_clear_value():
# test clearing a tag value
import os.path
import tempfile
from osxphotos.utils import _copy_file
import osxphotos.exiftool
from osxphotos.fileutil import FileUtil
tempdir = tempfile.TemporaryDirectory(prefix="osxphotos_")
tempfile = os.path.join(tempdir.name, os.path.basename(TEST_FILE_ONE_KEYWORD))
_copy_file(TEST_FILE_ONE_KEYWORD, tempfile)
FileUtil.copy(TEST_FILE_ONE_KEYWORD, tempfile)
exif = osxphotos.exiftool.ExifTool(tempfile)
assert "IPTC:Keywords" in exif.data
@@ -130,12 +130,12 @@ def test_addvalues_1():
# test setting a tag value
import os.path
import tempfile
from osxphotos.utils import _copy_file
import osxphotos.exiftool
from osxphotos.fileutil import FileUtil
tempdir = tempfile.TemporaryDirectory(prefix="osxphotos_")
tempfile = os.path.join(tempdir.name, os.path.basename(TEST_FILE_ONE_KEYWORD))
_copy_file(TEST_FILE_ONE_KEYWORD, tempfile)
FileUtil.copy(TEST_FILE_ONE_KEYWORD, tempfile)
exif = osxphotos.exiftool.ExifTool(tempfile)
exif.addvalues("IPTC:Keywords", "test")
@@ -147,12 +147,12 @@ def test_addvalues_2():
# test setting a tag value where multiple values already exist
import os.path
import tempfile
from osxphotos.utils import _copy_file
import osxphotos.exiftool
from osxphotos.fileutil import FileUtil
tempdir = tempfile.TemporaryDirectory(prefix="osxphotos_")
tempfile = os.path.join(tempdir.name, os.path.basename(TEST_FILE_MULTI_KEYWORD))
_copy_file(TEST_FILE_MULTI_KEYWORD, tempfile)
FileUtil.copy(TEST_FILE_MULTI_KEYWORD, tempfile)
exif = osxphotos.exiftool.ExifTool(tempfile)
assert sorted(exif.data["IPTC:Keywords"]) == sorted(TEST_MULTI_KEYWORDS)

149
tests/test_export_db.py Normal file
View File

@@ -0,0 +1,149 @@
""" Test ExportDB """
import pytest
EXIF_DATA = """[{"_CreatedBy": "osxphotos, https://github.com/RhetTbull/osxphotos", "EXIF:ImageDescription": "\u2068Elder Park\u2069, \u2068Adelaide\u2069, \u2068Australia\u2069", "XMP:Description": "\u2068Elder Park\u2069, \u2068Adelaide\u2069, \u2068Australia\u2069", "XMP:Title": "Elder Park", "EXIF:GPSLatitude": "34 deg 55' 8.01\" S", "EXIF:GPSLongitude": "138 deg 35' 48.70\" E", "Composite:GPSPosition": "34 deg 55' 8.01\" S, 138 deg 35' 48.70\" E", "EXIF:GPSLatitudeRef": "South", "EXIF:GPSLongitudeRef": "East", "EXIF:DateTimeOriginal": "2017:06:20 17:18:56", "EXIF:OffsetTimeOriginal": "+09:30", "EXIF:ModifyDate": "2020:05:18 14:42:04"}]"""
INFO_DATA = """{"uuid": "3DD2C897-F19E-4CA6-8C22-B027D5A71907", "filename": "3DD2C897-F19E-4CA6-8C22-B027D5A71907.jpeg", "original_filename": "IMG_4547.jpg", "date": "2017-06-20T17:18:56.518000+09:30", "description": "\u2068Elder Park\u2069, \u2068Adelaide\u2069, \u2068Australia\u2069", "title": "Elder Park", "keywords": [], "labels": ["Statue", "Art"], "albums": ["AlbumInFolder"], "folders": {"AlbumInFolder": ["Folder1", "SubFolder2"]}, "persons": [], "path": "/Users/rhet/Pictures/Test-10.15.4.photoslibrary/originals/3/3DD2C897-F19E-4CA6-8C22-B027D5A71907.jpeg", "ismissing": false, "hasadjustments": true, "external_edit": false, "favorite": false, "hidden": false, "latitude": -34.91889167000001, "longitude": 138.59686167, "path_edited": "/Users/rhet/Pictures/Test-10.15.4.photoslibrary/resources/renders/3/3DD2C897-F19E-4CA6-8C22-B027D5A71907_1_201_a.jpeg", "shared": false, "isphoto": true, "ismovie": false, "uti": "public.jpeg", "burst": false, "live_photo": false, "path_live_photo": null, "iscloudasset": false, "incloud": null, "date_modified": "2020-05-18T14:42:04.608664+09:30", "portrait": false, "screenshot": false, "slow_mo": false, "time_lapse": false, "hdr": false, "selfie": false, "panorama": false, "has_raw": false, "uti_raw": null, "path_raw": null, "place": {"name": "Elder Park, Adelaide, South Australia, Australia, River Torrens", "names": {"field0": [], "country": ["Australia"], "state_province": ["South Australia"], "sub_administrative_area": ["Adelaide"], "city": ["Adelaide", "Adelaide"], "field5": [], "additional_city_info": ["Adelaide CBD", "Tarndanya"], "ocean": [], "area_of_interest": ["Elder Park", ""], "inland_water": ["River Torrens", "River Torrens"], "field10": [], "region": [], "sub_throughfare": [], "field13": [], "postal_code": [], "field15": [], "field16": [], "street_address": [], "body_of_water": ["River Torrens", "River Torrens"]}, "country_code": "AU", "ishome": false, "address_str": "River Torrens, Adelaide SA, Australia", "address": {"street": null, "sub_locality": "Tarndanya", "city": "Adelaide", "sub_administrative_area": "Adelaide", "state_province": "SA", "postal_code": null, "country": "Australia", "iso_country_code": "AU"}}, "exif": {"flash_fired": false, "iso": 320, "metering_mode": 3, "sample_rate": null, "track_format": null, "white_balance": 0, "aperture": 2.2, "bit_rate": null, "duration": null, "exposure_bias": 0.0, "focal_length": 4.15, "fps": null, "latitude": null, "longitude": null, "shutter_speed": 0.058823529411764705, "camera_make": "Apple", "camera_model": "iPhone 6s", "codec": null, "lens_model": "iPhone 6s back camera 4.15mm f/2.2"}}"""
EXIF_DATA2 = """[{"_CreatedBy": "osxphotos, https://github.com/RhetTbull/osxphotos", "XMP:Title": "St. James's Park", "XMP:TagsList": ["London 2018", "St. James's Park", "England", "United Kingdom", "UK", "London"], "IPTC:Keywords": ["London 2018", "St. James's Park", "England", "United Kingdom", "UK", "London"], "XMP:Subject": ["London 2018", "St. James's Park", "England", "United Kingdom", "UK", "London"], "EXIF:GPSLatitude": "51 deg 30' 12.86\" N", "EXIF:GPSLongitude": "0 deg 7' 54.50\" W", "Composite:GPSPosition": "51 deg 30' 12.86\" N, 0 deg 7' 54.50\" W", "EXIF:GPSLatitudeRef": "North", "EXIF:GPSLongitudeRef": "West", "EXIF:DateTimeOriginal": "2018:10:13 09:18:12", "EXIF:OffsetTimeOriginal": "-04:00", "EXIF:ModifyDate": "2019:12:08 14:06:44"}]"""
INFO_DATA2 = """{"uuid": "F2BB3F98-90F0-4E4C-A09B-25C6822A4529", "filename": "F2BB3F98-90F0-4E4C-A09B-25C6822A4529.jpeg", "original_filename": "IMG_8440.JPG", "date": "2019-06-11T11:42:06.711805-07:00", "description": null, "title": null, "keywords": [], "labels": ["Sky", "Cloudy", "Fence", "Land", "Outdoor", "Park", "Amusement Park", "Roller Coaster"], "albums": [], "folders": {}, "persons": [], "path": "/Volumes/MacBook Catalina - Data/Users/rhet/Pictures/Photos Library.photoslibrary/originals/F/F2BB3F98-90F0-4E4C-A09B-25C6822A4529.jpeg", "ismissing": false, "hasadjustments": false, "external_edit": false, "favorite": false, "hidden": false, "latitude": 33.81558666666667, "longitude": -117.99298, "path_edited": null, "shared": false, "isphoto": true, "ismovie": false, "uti": "public.jpeg", "burst": false, "live_photo": false, "path_live_photo": null, "iscloudasset": true, "incloud": true, "date_modified": "2019-10-14T00:51:47.141950-07:00", "portrait": false, "screenshot": false, "slow_mo": false, "time_lapse": false, "hdr": false, "selfie": false, "panorama": false, "has_raw": false, "uti_raw": null, "path_raw": null, "place": {"name": "Adventure City, Stanton, California, United States", "names": {"field0": [], "country": ["United States"], "state_province": ["California"], "sub_administrative_area": ["Orange"], "city": ["Stanton", "Anaheim", "Anaheim"], "field5": [], "additional_city_info": ["West Anaheim"], "ocean": [], "area_of_interest": ["Adventure City", "Adventure City"], "inland_water": [], "field10": [], "region": [], "sub_throughfare": [], "field13": [], "postal_code": [], "field15": [], "field16": [], "street_address": [], "body_of_water": []}, "country_code": "US", "ishome": false, "address_str": "Adventure City, 1240 S Beach Blvd, Anaheim, CA 92804, United States", "address": {"street": "1240 S Beach Blvd", "sub_locality": "West Anaheim", "city": "Stanton", "sub_administrative_area": "Orange", "state_province": "CA", "postal_code": "92804", "country": "United States", "iso_country_code": "US"}}, "exif": {"flash_fired": false, "iso": 25, "metering_mode": 5, "sample_rate": null, "track_format": null, "white_balance": 0, "aperture": 2.2, "bit_rate": null, "duration": null, "exposure_bias": 0.0, "focal_length": 4.15, "fps": null, "latitude": null, "longitude": null, "shutter_speed": 0.0004940711462450593, "camera_make": "Apple", "camera_model": "iPhone 6s", "codec": null, "lens_model": "iPhone 6s back camera 4.15mm f/2.2"}}"""
def test_export_db():
""" test ExportDB """
import os
import tempfile
from osxphotos._export_db import ExportDB
tempdir = tempfile.TemporaryDirectory(prefix="osxphotos_")
dbname = os.path.join(tempdir.name, ".osxphotos_export.db")
db = ExportDB(dbname)
assert os.path.isfile(dbname)
filepath = os.path.join(tempdir.name,"test.JPG")
filepath_lower = os.path.join(tempdir.name,"test.jpg")
db.set_uuid_for_file(filepath, "FOO-BAR")
# filename should be case-insensitive
assert db.get_uuid_for_file(filepath_lower) == "FOO-BAR"
db.set_info_for_uuid("FOO-BAR", INFO_DATA)
assert db.get_info_for_uuid("FOO-BAR") == INFO_DATA
db.set_exifdata_for_file(filepath, EXIF_DATA)
assert db.get_exifdata_for_file(filepath) == EXIF_DATA
db.set_stat_orig_for_file(filepath, (1, 2, 3))
assert db.get_stat_orig_for_file(filepath) == (1, 2, 3)
db.set_stat_exif_for_file(filepath, (4, 5, 6))
assert db.get_stat_exif_for_file(filepath) == (4, 5, 6)
# test set_data which sets all at the same time
filepath2 = os.path.join(tempdir.name,"test2.jpg")
db.set_data(filepath2, "BAR-FOO", (1, 2, 3), (4, 5, 6), INFO_DATA, EXIF_DATA)
assert db.get_uuid_for_file(filepath2) == "BAR-FOO"
assert db.get_info_for_uuid("BAR-FOO") == INFO_DATA
assert db.get_exifdata_for_file(filepath2) == EXIF_DATA
assert db.get_stat_orig_for_file(filepath2) == (1, 2, 3)
assert db.get_stat_exif_for_file(filepath2) == (4, 5, 6)
# close and re-open
db.close()
db = ExportDB(dbname)
assert db.get_uuid_for_file(filepath2) == "BAR-FOO"
assert db.get_info_for_uuid("BAR-FOO") == INFO_DATA
assert db.get_exifdata_for_file(filepath2) == EXIF_DATA
assert db.get_stat_orig_for_file(filepath2) == (1, 2, 3)
assert db.get_stat_exif_for_file(filepath2) == (4, 5, 6)
# update data
db.set_uuid_for_file(filepath, "FUBAR")
assert db.get_uuid_for_file(filepath) == "FUBAR"
def test_export_db_no_op():
""" test ExportDBNoOp """
import os
import tempfile
from osxphotos._export_db import ExportDBNoOp
tempdir = tempfile.TemporaryDirectory(prefix="osxphotos_")
db = ExportDBNoOp()
filepath = os.path.join(tempdir.name,"test.JPG")
filepath_lower = os.path.join(tempdir.name,"test.jpg")
db.set_uuid_for_file(filepath, "FOO-BAR")
# filename should be case-insensitive
assert db.get_uuid_for_file(filepath_lower) is None
db.set_info_for_uuid("FOO-BAR", INFO_DATA)
assert db.get_info_for_uuid("FOO-BAR") is None
db.set_exifdata_for_file(filepath, EXIF_DATA)
assert db.get_exifdata_for_file(filepath) is None
db.set_stat_orig_for_file(filepath, (1, 2, 3))
assert db.get_stat_orig_for_file(filepath) is None
db.set_stat_exif_for_file(filepath, (4, 5, 6))
assert db.get_stat_exif_for_file(filepath) is None
# test set_data which sets all at the same time
filepath2 = os.path.join(tempdir.name,"test2.jpg")
db.set_data(filepath2, "BAR-FOO", (1, 2, 3), (4, 5, 6), INFO_DATA, EXIF_DATA)
assert db.get_uuid_for_file(filepath2) is None
assert db.get_info_for_uuid("BAR-FOO") is None
assert db.get_exifdata_for_file(filepath2) is None
assert db.get_stat_orig_for_file(filepath2) is None
assert db.get_stat_exif_for_file(filepath2) is None
# update data
db.set_uuid_for_file(filepath, "FUBAR")
assert db.get_uuid_for_file(filepath) is None
def test_export_db_in_memory():
""" test ExportDBInMemory """
import os
import tempfile
from osxphotos._export_db import ExportDB, ExportDBInMemory
tempdir = tempfile.TemporaryDirectory(prefix="osxphotos_")
dbname = os.path.join(tempdir.name, ".osxphotos_export.db")
db = ExportDB(dbname)
assert os.path.isfile(dbname)
filepath = os.path.join(tempdir.name,"test.JPG")
filepath_lower = os.path.join(tempdir.name,"test.jpg")
db.set_uuid_for_file(filepath, "FOO-BAR")
db.set_info_for_uuid("FOO-BAR", INFO_DATA)
db.set_exifdata_for_file(filepath, EXIF_DATA)
db.set_stat_orig_for_file(filepath, (1, 2, 3))
db.set_stat_exif_for_file(filepath, (4, 5, 6))
db.close()
dbram = ExportDBInMemory(dbname)
# verify values as expected
assert dbram.get_uuid_for_file(filepath_lower) == "FOO-BAR"
assert dbram.get_info_for_uuid("FOO-BAR") == INFO_DATA
assert dbram.get_exifdata_for_file(filepath) == EXIF_DATA
assert dbram.get_stat_orig_for_file(filepath) == (1, 2, 3)
assert dbram.get_stat_exif_for_file(filepath) == (4, 5, 6)
# change a value
dbram.set_uuid_for_file(filepath, "FUBAR")
dbram.set_info_for_uuid("FUBAR", INFO_DATA2)
dbram.set_exifdata_for_file(filepath, EXIF_DATA2)
dbram.set_stat_orig_for_file(filepath, (7,8,9))
dbram.set_stat_exif_for_file(filepath, (10,11,12))
assert dbram.get_uuid_for_file(filepath_lower) == "FUBAR"
assert dbram.get_info_for_uuid("FUBAR") == INFO_DATA2
assert dbram.get_exifdata_for_file(filepath) == EXIF_DATA2
assert dbram.get_stat_orig_for_file(filepath) == (7,8,9)
assert dbram.get_stat_exif_for_file(filepath) == (10,11,12)
dbram.close()
# re-open on disk and verify no changes
db = ExportDB(dbname)
assert db.get_uuid_for_file(filepath_lower) == "FOO-BAR"
assert db.get_info_for_uuid("FOO-BAR") == INFO_DATA
assert db.get_exifdata_for_file(filepath) == EXIF_DATA
assert db.get_stat_orig_for_file(filepath) == (1, 2, 3)
assert db.get_stat_exif_for_file(filepath) == (4, 5, 6)
assert db.get_info_for_uuid("FUBAR") is None

69
tests/test_fileutil.py Normal file
View File

@@ -0,0 +1,69 @@
""" test FileUtil """
import pytest
def test_copy_file_valid():
# copy file with valid src, dest
import os.path
import tempfile
from osxphotos.fileutil import FileUtil
temp_dir = tempfile.TemporaryDirectory(prefix="osxphotos_")
src = "tests/test-images/wedding.jpg"
result = FileUtil.copy(src, temp_dir.name)
assert result == 0
assert os.path.isfile(os.path.join(temp_dir.name, "wedding.jpg"))
def test_copy_file_invalid():
# copy file with invalid src
import tempfile
from osxphotos.fileutil import FileUtil
temp_dir = tempfile.TemporaryDirectory(prefix="osxphotos_")
src = "tests/test-images/wedding_DOES_NOT_EXIST.jpg"
with pytest.raises(Exception) as e:
assert FileUtil.copy(src, temp_dir.name)
assert e.type == FileNotFoundError
def test_copy_file_norsrc():
# copy file with --norsrc
import os.path
import tempfile
from osxphotos.fileutil import FileUtil
temp_dir = tempfile.TemporaryDirectory(prefix="osxphotos_")
src = "tests/test-images/wedding.jpg"
result = FileUtil.copy(src, temp_dir.name, norsrc=True)
assert result == 0
assert os.path.isfile(os.path.join(temp_dir.name, "wedding.jpg"))
def test_hardlink_file_valid():
# hardlink file with valid src, dest
import os.path
import tempfile
from osxphotos.fileutil import FileUtil
temp_dir = tempfile.TemporaryDirectory(prefix="osxphotos_")
src = "tests/test-images/wedding.jpg"
dest = os.path.join(temp_dir.name, "wedding.jpg")
FileUtil.hardlink(src, dest)
assert os.path.isfile(dest)
assert os.path.samefile(src, dest)
def test_unlink_file():
import os.path
import tempfile
from osxphotos.fileutil import FileUtil
temp_dir = tempfile.TemporaryDirectory(prefix="osxphotos_")
src = "tests/test-images/wedding.jpg"
dest = os.path.join(temp_dir.name, "wedding.jpg")
result = FileUtil.copy(src, temp_dir.name)
assert os.path.isfile(dest)
FileUtil.unlink(dest)
assert not os.path.isfile(dest)

View File

@@ -55,44 +55,6 @@ def test_db_is_locked_unlocked():
assert not osxphotos.utils._db_is_locked(DB_UNLOCKED_10_15)
def test_copy_file_valid():
# _copy_file with valid src, dest
import os.path
import tempfile
from osxphotos.utils import _copy_file
temp_dir = tempfile.TemporaryDirectory(prefix="osxphotos_")
src = "tests/test-images/wedding.jpg"
result = _copy_file(src, temp_dir.name)
assert result == 0
assert os.path.isfile(os.path.join(temp_dir.name, "wedding.jpg"))
def test_copy_file_invalid():
# _copy_file with invalid src
import tempfile
from osxphotos.utils import _copy_file
temp_dir = tempfile.TemporaryDirectory(prefix="osxphotos_")
src = "tests/test-images/wedding_DOES_NOT_EXIST.jpg"
with pytest.raises(Exception) as e:
assert _copy_file(src, temp_dir.name)
assert e.type == FileNotFoundError
def test_copy_file_norsrc():
# _copy_file with --norsrc
import os.path
import tempfile
from osxphotos.utils import _copy_file
temp_dir = tempfile.TemporaryDirectory(prefix="osxphotos_")
src = "tests/test-images/wedding.jpg"
result = _copy_file(src, temp_dir.name, norsrc=True)
assert result == 0
assert os.path.isfile(os.path.join(temp_dir.name, "wedding.jpg"))
def test_get_preferred_uti_extension():
from osxphotos.utils import get_preferred_uti_extension