Added --field to dump and query, #777 (#779)

This commit is contained in:
Rhet Turnbull 2022-08-27 21:05:57 -07:00 committed by GitHub
parent 02d772c921
commit e39776b51e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 213 additions and 22 deletions

View File

@ -35,6 +35,7 @@ __all__ = [
"DB_OPTION",
"DEBUG_OPTIONS",
"DELETED_OPTIONS",
"FIELD_OPTION",
"JSON_OPTION",
"QUERY_OPTIONS",
"THEME_OPTION",
@ -116,6 +117,19 @@ JSON_OPTION = click.option(
help="Print output in JSON format.",
)
FIELD_OPTION = click.option(
"--field",
"-f",
metavar="FIELD TEMPLATE",
multiple=True,
nargs=2,
help="Output only specified custom fields. "
"FIELD is the name of the field and TEMPLATE is the template to use as the field value. "
"May be repeated to output multiple fields. "
"For example, to output photo uuid, name, and title: "
'`--field uuid "{uuid}" --field name "{original_name}" --field title "{title}"`.',
)
def DELETED_OPTIONS(f):
o = click.option

View File

@ -12,9 +12,16 @@ from osxphotos.phototemplate import RenderOptions
from osxphotos.queryoptions import QueryOptions
from .color_themes import get_default_theme
from .common import DB_ARGUMENT, DB_OPTION, DELETED_OPTIONS, JSON_OPTION, get_photos_db
from .common import (
DB_ARGUMENT,
DB_OPTION,
DELETED_OPTIONS,
FIELD_OPTION,
JSON_OPTION,
get_photos_db,
)
from .list import _list_libraries
from .print_photo_info import print_photo_info
from .print_photo_info import print_photo_fields, print_photo_info
from .verbose import get_verbose_console
@ -22,7 +29,7 @@ from .verbose import get_verbose_console
@DB_OPTION
@JSON_OPTION
@DELETED_OPTIONS
@DB_ARGUMENT
@FIELD_OPTION
@click.option(
"--print",
"print_template",
@ -35,10 +42,19 @@ from .verbose import get_verbose_console
"and only the rendered TEMPLATE values are printed. "
"May be repeated to print multiple template strings. ",
)
@DB_ARGUMENT
@click.pass_obj
@click.pass_context
def dump(
ctx, cli_obj, db, json_, deleted, deleted_only, photos_library, print_template
ctx,
cli_obj,
db,
deleted,
deleted_only,
field,
json_,
photos_library,
print_template,
):
"""Print list of all photos & associated info from the Photos library."""
@ -71,22 +87,28 @@ def dump(
if not deleted_only:
photos += photosdb.photos(movies=True)
if not print_template:
if not print_template and not field:
# just dump and be done
print_photo_info(photos, cli_json or json_)
return
# have print template(s)
options = RenderOptions()
for p in photos:
for template in print_template:
rendered_templates, unmatched = p.render_template(
template,
options,
)
if unmatched:
rich_click_echo(f"[warning]Unmatched template field: {unmatched}[/]")
for rendered_template in rendered_templates:
if not rendered_template:
continue
print(rendered_template)
if field:
print_photo_fields(photos, field, cli_json or json_)
if print_template:
# have print template(s)
options = RenderOptions()
for p in photos:
for template in print_template:
rendered_templates, unmatched = p.render_template(
template,
options,
)
if unmatched:
rich_click_echo(
f"[warning]Unmatched template field: {unmatched}[/]"
)
for rendered_template in rendered_templates:
if not rendered_template:
continue
print(rendered_template)

View File

@ -1,8 +1,9 @@
"""print_photo_info function to print PhotoInfo objects"""
import csv
import json
import sys
from typing import Callable, List
from typing import Callable, List, Tuple
from osxphotos.photoinfo import PhotoInfo
@ -110,3 +111,44 @@ def print_photo_info(
)
for row in dump:
csv_writer.writerow(row)
def print_photo_fields(
photos: List[PhotoInfo], fields: Tuple[Tuple[str]], json_format: bool
):
"""Output custom field templates from PhotoInfo objects
Args:
photos: List of PhotoInfo objects
fields: Tuple of Tuple of field names/field templates to output"""
keys = [f[0] for f in fields]
data = []
for p in photos:
record = {}
for field in fields:
rendered_value, unmatched = p.render_template(field[1])
if unmatched:
raise ValueError(
f"Unmatched template variables in field {field[0]}: {field[1]}"
)
field_value = (
rendered_value[0]
if len(rendered_value) == 1
else ",".join(rendered_value)
if not json_format
else rendered_value
)
record[field[0]] = field_value
data.append(record)
if json_format:
print(json.dumps(data, indent=4))
else:
# dump as CSV
csv_writer = csv.writer(
sys.stdout, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
)
# add headers
csv_writer.writerow(keys)
for record in data:
csv_writer.writerow(record.values())

View File

@ -20,6 +20,7 @@ from .common import (
DB_ARGUMENT,
DB_OPTION,
DELETED_OPTIONS,
FIELD_OPTION,
JSON_OPTION,
OSXPHOTOS_HIDDEN,
QUERY_OPTIONS,
@ -27,7 +28,7 @@ from .common import (
load_uuid_from_file,
)
from .list import _list_libraries
from .print_photo_info import print_photo_info
from .print_photo_info import print_photo_fields, print_photo_info
from .verbose import get_verbose_console
@ -76,6 +77,7 @@ from .verbose import get_verbose_console
help="Quiet output; doesn't actually print query results. "
"Useful with --print and --add-to-album if you don't want to see the actual query results.",
)
@FIELD_OPTION
@click.option(
"--print",
"print_template",
@ -113,6 +115,7 @@ def query(
exif,
external_edit,
favorite,
field,
folder,
from_date,
from_time,
@ -399,6 +402,9 @@ def query(
err=True,
)
if field:
print_photo_fields(photos, field, cli_json or json_)
if print_template:
options = RenderOptions()
for p in photos:
@ -416,5 +422,5 @@ def query(
continue
print(rendered_template)
if not quiet:
if not quiet and not field:
print_photo_info(photos, cli_json or json_, print_func=click.echo)

View File

@ -8363,3 +8363,56 @@ def test_query_print_quiet():
)
assert result.exit_code == 0
assert result.output.strip() == f"uuid: {UUID_FAVORITE}"
def test_query_field():
"""test query --field"""
runner = CliRunner()
cwd = os.getcwd()
with runner.isolated_filesystem():
result = runner.invoke(
query,
[
"--db",
os.path.join(cwd, PHOTOS_DB_15_7),
"--field",
"uuid",
"{uuid}",
"--field",
"name",
"{photo.original_filename}",
"--uuid",
UUID_FAVORITE,
],
)
assert result.exit_code == 0
assert result.output.strip() == f"uuid,name\n{UUID_FAVORITE},{FILE_FAVORITE}"
def test_query_field_json():
"""test query --field --json"""
runner = CliRunner()
cwd = os.getcwd()
with runner.isolated_filesystem():
result = runner.invoke(
query,
[
"--db",
os.path.join(cwd, PHOTOS_DB_15_7),
"--field",
"uuid",
"{uuid}",
"--field",
"name",
"{photo.original_filename}",
"--uuid",
UUID_FAVORITE,
"--json",
],
)
assert result.exit_code == 0
json_results = json.loads(result.output)
assert json_results[0]["uuid"] == UUID_FAVORITE
assert json_results[0]["name"] == FILE_FAVORITE

View File

@ -69,3 +69,57 @@ def test_dump_print(photos):
assert result.exit_code == 0
for photo in photos:
assert f"{photo.uuid}\t{photo.original_filename}" in result.output
def test_dump_field(photos):
"""Test osxphotos dump --field"""
runner = CliRunner()
cwd = os.getcwd()
db_path = os.path.join(cwd, CLI_PHOTOS_DB)
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
result = runner.invoke(
dump,
[
"--db",
db_path,
"--deleted",
"--field",
"uuid",
"{uuid}",
"--field",
"name",
"{photo.original_filename}",
],
)
assert result.exit_code == 0
for photo in photos:
assert f"{photo.uuid},{photo.original_filename}" in result.output
def test_dump_field_json(photos):
"""Test osxphotos dump --field --jso"""
runner = CliRunner()
cwd = os.getcwd()
db_path = os.path.join(cwd, CLI_PHOTOS_DB)
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
result = runner.invoke(
dump,
[
"--db",
db_path,
"--deleted",
"--field",
"uuid",
"{uuid}",
"--field",
"name",
"{photo.original_filename}",
"--json",
],
)
assert result.exit_code == 0
json_data = {record["uuid"]: record for record in json.loads(result.output)}
for photo in photos:
assert photo.uuid in json_data
assert json_data[photo.uuid]["name"] == photo.original_filename