diff --git a/README_DEV.md b/README_DEV.md index 79e1b5d1..4d7926dc 100644 --- a/README_DEV.md +++ b/README_DEV.md @@ -27,6 +27,7 @@ If you want to contribute to osxphotos, please open a pull request. Here's how t - Make your changes - Add tests for your changes - Run the tests: `pytest` +- Format the code: `isort .` then `black .` - Update the README.md and other files as needed - Add your changes using `git add` - Commit your changes: `git commit -m "My changes description"` diff --git a/dev_requirements.txt b/dev_requirements.txt index bddc9789..7656bc1b 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -1,14 +1,16 @@ +black build -bump2version==1.0.1 +bump2version==1.0.1 cogapp>=3.3.0,<4.0.0 furo +isort m2r2==0.3.3.post2 pdbpp pyinstaller==5.13.2 -pytest-cov==4.0.0 +pytest-cov==4.0.0 pytest-mock pytest==7.4.0 -ruff==0.0.286 +ruff==0.7.2 Sphinx sphinx_click sphinx_rtd_theme @@ -16,4 +18,4 @@ sphinx-copybutton sphinxcontrib-programoutput twine wheel -setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability \ No newline at end of file +setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability diff --git a/examples/add_finder_tags.py b/examples/add_finder_tags.py index 3da204dc..d73dd638 100644 --- a/examples/add_finder_tags.py +++ b/examples/add_finder_tags.py @@ -118,7 +118,9 @@ def add_metadata_to_photo( ): """Add Finder tags to matching photo in Photos library as keywords.""" tags = get_finder_tags(filepath, include, exclude) - echo(f"Finder tags for [filepath]{filepath}[/]: [i]{', '.join(tags) if tags else None}[/]") + echo( + f"Finder tags for [filepath]{filepath}[/]: [i]{', '.join(tags) if tags else None}[/]" + ) if matches := fq.possible_duplicates(filepath): echo( f"Adding Finder tags to [num]{len(matches)}[/] matching photo(s) in Photos library..." @@ -128,7 +130,9 @@ def add_metadata_to_photo( if not photo: echo(f"Could not find photo with UUID [uuid]{match[0]}[/]", err=True) continue - echo(f"Processing photo [filename]{photo.filename}[/] ([uuid]{photo.uuid}[/])") + echo( + f"Processing photo [filename]{photo.filename}[/] ([uuid]{photo.uuid}[/])" + ) add_keywords_to_photo(photo, tags, dry_run) if caption: add_comment_to_photo(photo, filepath, dry_run) diff --git a/examples/timewarp_function_creation_date.py b/examples/timewarp_function_creation_date.py index a50b6ca4..e1b480ae 100644 --- a/examples/timewarp_function_creation_date.py +++ b/examples/timewarp_function_creation_date.py @@ -27,7 +27,9 @@ def creation_date( """ if not path: - verbose(f"Could not get path for photo {photo.original_filename} ({photo.uuid}); skipping") + verbose( + f"Could not get path for photo {photo.original_filename} ({photo.uuid}); skipping" + ) return photo.date, tz_sec # this example uses's the file's creation date/time; the timezone is not changed diff --git a/examples/top_10_unnamed_faces.py b/examples/top_10_unnamed_faces.py index 3a3648ed..7fc7ce92 100644 --- a/examples/top_10_unnamed_faces.py +++ b/examples/top_10_unnamed_faces.py @@ -32,5 +32,6 @@ def main(): album = PhotosAlbum(album_name) album.update(photos) + if __name__ == "__main__": main() diff --git a/osxphotos/cli/cli.py b/osxphotos/cli/cli.py index 11ed5959..786d50f2 100644 --- a/osxphotos/cli/cli.py +++ b/osxphotos/cli/cli.py @@ -9,7 +9,7 @@ from osxphotos._constants import PROFILE_SORT_KEYS from osxphotos._version import __version__ -from osxphotos.disclaim import disclaim, pyinstaller, pyapp +from osxphotos.disclaim import disclaim, pyapp, pyinstaller from osxphotos.platform import is_macos from .about import about diff --git a/osxphotos/cli/photo_inspect.py b/osxphotos/cli/photo_inspect.py index 851cc25d..81f60be8 100644 --- a/osxphotos/cli/photo_inspect.py +++ b/osxphotos/cli/photo_inspect.py @@ -413,7 +413,7 @@ def get_photo_type(photo: PhotoInfo) -> str: if photo.screenshot: photo_type += " screenshot" if photo.screen_recording: - photo_type += " screen recording" + photo_type += " screen recording" if photo.slow_mo: photo_type += " slow-mo" if photo.time_lapse: diff --git a/osxphotos/cli/push_exif.py b/osxphotos/cli/push_exif.py index d3700307..29ed9a58 100644 --- a/osxphotos/cli/push_exif.py +++ b/osxphotos/cli/push_exif.py @@ -641,10 +641,14 @@ def compare_location(photo: PhotoInfo, file_data: dict[str, Any]) -> str: exif_latitude_ref = file_data.get("EXIF:GPSLatitudeRef") exif_longitude_ref = file_data.get("EXIF:GPSLongitudeRef") elif photo.ismovie: - exif_coordinates = file_data.get('QuickTime:GPSCoordinates') - exif_latitude, exif_longitude = [float(x) for x in exif_coordinates.split()[:2]] if exif_coordinates else [ None, None] + exif_coordinates = file_data.get("QuickTime:GPSCoordinates") + exif_latitude, exif_longitude = ( + [float(x) for x in exif_coordinates.split()[:2]] + if exif_coordinates + else [None, None] + ) exif_latitude_ref = None - exif_longitude_ref = None + exif_longitude_ref = None if exif_longitude and exif_longitude_ref == "W": exif_longitude = -exif_longitude diff --git a/osxphotos/cli/sidecar.py b/osxphotos/cli/sidecar.py index bb63a93c..ffbca2c8 100644 --- a/osxphotos/cli/sidecar.py +++ b/osxphotos/cli/sidecar.py @@ -225,9 +225,7 @@ def get_sidecar_file_with_template( if not sidecar_file or not sidecar_file.exists(): if edited_suffix: # try again with the edited suffix removed - filepath = strip_edited_suffix( - filepath, edited_suffix, exiftool_path - ) + filepath = strip_edited_suffix(filepath, edited_suffix, exiftool_path) return get_sidecar_file_with_template( filepath, sidecar, diff --git a/osxphotos/cli/sync.py b/osxphotos/cli/sync.py index b6439525..47a98736 100644 --- a/osxphotos/cli/sync.py +++ b/osxphotos/cli/sync.py @@ -379,7 +379,7 @@ def import_metadata_for_photo( # If --set use origin location in the destination photo results += _set_location_for_photo(photo, metadata, dry_run, verbose) elif "location" in merge: - # if --merge + # if --merge # and property is set in the destination then no action is taken; # if property is not set in the destination but is set in the source, # then the value is copied to destination. @@ -390,6 +390,7 @@ def import_metadata_for_photo( return results + def _process_location_for_photo( photo: PhotoInfo, metadata: dict[str, Any], @@ -433,7 +434,8 @@ def _process_location_for_photo( field, updated, before, - value) + value, + ) return results @@ -520,7 +522,7 @@ def _set_metadata_for_photo( value = metadata[field] before = getattr(photo, field) - + if isinstance(value, list): value = sorted(value) if isinstance(before, list): diff --git a/osxphotos/cli/timewarp.py b/osxphotos/cli/timewarp.py index 17ac4080..dd862d37 100644 --- a/osxphotos/cli/timewarp.py +++ b/osxphotos/cli/timewarp.py @@ -532,7 +532,7 @@ def timewarp_cli( if inspect and compare_exif: raise click.UsageError("--inspect and --compare-exif are mutually exclusive.") - + if date and date_delta: raise click.UsageError("--date and --date-delta are mutually exclusive.") diff --git a/osxphotos/compare_exif.py b/osxphotos/compare_exif.py index 20d38856..dd0ec05a 100644 --- a/osxphotos/compare_exif.py +++ b/osxphotos/compare_exif.py @@ -94,10 +94,11 @@ def timewarp_compare_exif(self, photo: Photo, plain: bool = False) -> ExifDiff: Args: photo (Photo): Photo object to compare plain (bool): Flag to determine if plain (True) or markup (False) should be applied - """ + """ + def compare_values(photo_value: str, exif_value: str) -> tuple: """Compare two values and return them with or without markup. - + Affects nonlocal variable diff (from timewarp_compare_exif) with result. """ @@ -110,7 +111,7 @@ def compare_values(photo_value: str, exif_value: str) -> tuple: if not plain: return no_change(photo_value), no_change(exif_value) return photo_value, exif_value - + # Get values from comparison function photos_date, photos_tz, exif_date, exif_tz = self.compare_exif(photo) diff = False @@ -136,4 +137,3 @@ def compare_values(photo_value: str, exif_value: str) -> tuple: exif_time, exif_tz, ) - \ No newline at end of file diff --git a/osxphotos/export_db_utils.py b/osxphotos/export_db_utils.py index 0fde32ff..7e68300b 100644 --- a/osxphotos/export_db_utils.py +++ b/osxphotos/export_db_utils.py @@ -9,8 +9,8 @@ import pathlib import sqlite3 from typing import Any, Callable, Optional, Tuple, Union -import tenacity +import tenacity import toml from rich import print diff --git a/osxphotos/photoexporter.py b/osxphotos/photoexporter.py index a8695ac1..739e333c 100644 --- a/osxphotos/photoexporter.py +++ b/osxphotos/photoexporter.py @@ -589,8 +589,7 @@ def _stage_photos_for_export(self, options: ExportOptions) -> StagedFiles: staged = StagedFiles() if options.use_photos_export: - return self._stage_missing_photos_for_export_helper( - options=options) + return self._stage_missing_photos_for_export_helper(options=options) if options.raw_photo and self.photo.has_raw: staged.raw = self.photo.path_raw @@ -648,7 +647,8 @@ def _stage_missing_photos_for_export( use_photokit=options.use_photokit, ) missing_staged = self._stage_missing_photos_for_export_helper( - options=missing_options) + options=missing_options + ) staged |= missing_staged return staged @@ -1233,7 +1233,10 @@ def _export_aae( if action == "skip": if dest.exists(): options.export_db.set_history( - filename=str(dest), uuid=self.photo.uuid, action=f"AAE: {action}", diff=None + filename=str(dest), + uuid=self.photo.uuid, + action=f"AAE: {action}", + diff=None, ) return ExportResults(aae_skipped=[str(dest)], skipped=[str(dest)]) else: diff --git a/osxphotos/photoinfo_protocol.py b/osxphotos/photoinfo_protocol.py index 8bd6689c..924cab22 100644 --- a/osxphotos/photoinfo_protocol.py +++ b/osxphotos/photoinfo_protocol.py @@ -23,6 +23,7 @@ __all__ = ["PhotoInfoProtocol", "PhotoInfoMixin"] + @runtime_checkable class PhotoInfoProtocol(Protocol): @property diff --git a/osxphotos/photokit.py b/osxphotos/photokit.py index ea7b4e79..3fd036ba 100644 --- a/osxphotos/photokit.py +++ b/osxphotos/photokit.py @@ -353,7 +353,9 @@ def screenshot(self): @property def screen_recording(self): """return True if asset is screen recordings, otherwise False""" - return bool(self.media_subtypes & Photos.PHAssetMediaSubtypePhotoScreenRecording) + return bool( + self.media_subtypes & Photos.PHAssetMediaSubtypePhotoScreenRecording + ) @property def live(self): diff --git a/osxphotos/photokit_utils.py b/osxphotos/photokit_utils.py index 8122d87f..e48e66ed 100644 --- a/osxphotos/photokit_utils.py +++ b/osxphotos/photokit_utils.py @@ -16,6 +16,7 @@ # seconds to sleep between authorization check AUTHORIZATION_SLEEP = 0.25 + def wait_for_photokit_authorization() -> bool: """Request and wait for authorization to access Photos library.""" if check_photokit_authorization(): diff --git a/osxphotos/photosalbum.py b/osxphotos/photosalbum.py index b0d20fab..7d142c75 100644 --- a/osxphotos/photosalbum.py +++ b/osxphotos/photosalbum.py @@ -198,6 +198,7 @@ def extend(self, photos: Iterable[Photo]): """Add list of photos to album""" self.update(photos) + class PhotosAlbumPhotoScriptByPath(PhotosAlbumPhotoScript): """Add photoscript.Photo objects to album""" diff --git a/osxphotos/photosdb/photosdb_utils.py b/osxphotos/photosdb/photosdb_utils.py index 81345ff2..b1ab798c 100644 --- a/osxphotos/photosdb/photosdb_utils.py +++ b/osxphotos/photosdb/photosdb_utils.py @@ -16,11 +16,11 @@ _PHOTOS_6_MODEL_VERSION, _PHOTOS_7_MODEL_VERSION, _PHOTOS_8_MODEL_VERSION, + _PHOTOS_9_14_6_MODEL_VERSION, _PHOTOS_9_MODEL_VERSION, _PHOTOS_10_MODEL_VERSION, _PHOTOS_10B1_MODEL_VERSION, _TESTED_DB_VERSIONS, - _PHOTOS_9_14_6_MODEL_VERSION, ) from ..sqlite_utils import sqlite_open_ro diff --git a/osxphotos/phototemplate.py b/osxphotos/phototemplate.py index a33fef09..3b6e974f 100644 --- a/osxphotos/phototemplate.py +++ b/osxphotos/phototemplate.py @@ -9,7 +9,7 @@ import sys from contextlib import suppress from dataclasses import dataclass -from typing import List, Optional, Tuple, TYPE_CHECKING +from typing import TYPE_CHECKING, List, Optional, Tuple from textx import TextXSyntaxError, metamodel_from_file diff --git a/tests/test_cli_push_exif.py b/tests/test_cli_push_exif.py index ff853477..3d01a868 100644 --- a/tests/test_cli_push_exif.py +++ b/tests/test_cli_push_exif.py @@ -750,7 +750,9 @@ def test_cli_push_exif_metadata_arg(monkeypatch): assert "EXIF:CreateDate" not in exif # verify faces were pushed - assert sorted(get_exiftool_tag_as_list(photo, "XMP:RegionsRegionListName")) == sorted(photo.persons) + assert sorted( + get_exiftool_tag_as_list(photo, "XMP:RegionsRegionListName") + ) == sorted(photo.persons) # clear metadata photosdb = PhotosDB(test_library) diff --git a/tests/test_cli_sync.py b/tests/test_cli_sync.py index 8ec0312d..62a93002 100644 --- a/tests/test_cli_sync.py +++ b/tests/test_cli_sync.py @@ -121,6 +121,7 @@ def test_sync_export_import(): assert report_data[uuid]["albums"]["updated"] assert not report_data[uuid]["error"] + @pytest.mark.test_sync def test_sync_export_import_location(): """Test --export and --import location""" diff --git a/tests/test_sidecar_utils.py b/tests/test_sidecar_utils.py index 14dc4a28..7e7a2a71 100644 --- a/tests/test_sidecar_utils.py +++ b/tests/test_sidecar_utils.py @@ -102,6 +102,7 @@ def test_get_sidecar_file_with_template(tmp_path, data): (tmp_path / file).touch() import logging + sidecar_name = get_sidecar_file_with_template( tmp_path / data["files"][0], data["sidecar"], diff --git a/tests/test_timezones.py b/tests/test_timezones.py index c2ed5b10..0fe247bf 100644 --- a/tests/test_timezones.py +++ b/tests/test_timezones.py @@ -36,12 +36,14 @@ def test_timezone_by_offset(): assert tz.offset_str == "-05:00" assert isinstance(tz.tzinfo(datetime.datetime.now()), ZoneInfo) + @pytest.mark.skipif(not is_macos, reason="macOS only") def test_timezone_invalid_zone(): """Test that Timezone creation fails with an invalid zone name.""" with pytest.raises(ValueError, match="Invalid timezone: Invalid/Zone"): Timezone("Invalid/Zone") + @pytest.mark.skipif(not is_macos, reason="macOS only") def test_timezone_invalid_offset(): """Test that Timezone creation fails with bad input type."""