Skip to content

Prevent read-only metadata lookups from creating sidecar entries#48

Merged
AlanRockefeller merged 4 commits intoAlanRockefeller:mainfrom
Dj-Shortcut:mijn-volgende-branch
Mar 12, 2026
Merged

Prevent read-only metadata lookups from creating sidecar entries#48
AlanRockefeller merged 4 commits intoAlanRockefeller:mainfrom
Dj-Shortcut:mijn-volgende-branch

Conversation

@Dj-Shortcut
Copy link
Copy Markdown

@Dj-Shortcut Dj-Shortcut commented Mar 10, 2026

Audited all get_metadata() call sites and updated the pure read paths in faststack/app.py to use create=False, so UI-only reads like folder badges, current metadata display, last-uploaded navigation, batch helpers, and stacked-status checks no longer create persistent sidecar entries. Verified that all mutation paths still use the default creating behavior for upload, favorite, stacked, restack, edit, drag/upload, and Helicon save flows.

Summary by CodeRabbit

  • Improvements
    • Metadata reads no longer create or persist entries by default, reducing unnecessary storage and improving performance.
    • Metadata handling consolidated to a single object format, removing legacy dual-format branching for consistent state reads.
    • Upload/stack indicators (e.g., jump-to-last-uploaded) and related status flags are more reliable and accurate due to consistent, non-creating metadata access.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 10, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 15c78fbd-16c6-410b-9183-24d520967431

📥 Commits

Reviewing files that changed from the base of the PR and between 9ed5967 and 2bad95c.

📒 Files selected for processing (1)
  • faststack/app.py

Walkthrough

SidecarManager.get_metadata gained a keyword-only create: bool parameter. Multiple read paths in faststack/app.py now call get_metadata(stem, create=False), and metadata is read directly from the returned object (no dict/object branching), avoiding implicit persistence on reads.

Changes

Cohort / File(s) Summary
Sidecar metadata control
faststack/io/sidecar.py
Added keyword-only parameter create: bool = True to SidecarManager.get_metadata(). When create=False the method returns a non-persisted EntryMetadata for missing keys instead of storing it; create=True preserves prior behavior of persisting new entries.
App read-only metadata usage
faststack/app.py
Replaced get_metadata(stem) calls with get_metadata(stem, create=False) across multiple methods. Removed dual dict/object handling and now access metadata attributes directly (e.g., meta.uploaded, meta.stacked, meta.todo), preventing implicit sidecar creation during reads. Affected paths include jump_to_last_uploaded, _get_metadata_dict, get_current_metadata, add_favorites_to_batch, add_uploaded_to_batch, and is_stacked.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: preventing read-only metadata lookups from creating sidecar entries, which aligns with the PR's core objective of auditing get_metadata() call sites and updating pure read paths to use create=False.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
faststack/app.py (2)

2123-2131: ⚠️ Potential issue | 🟠 Major

Handle dict-backed metadata before building the badge map.

get_metadata() now returns existing entries unchanged, so a dict can still reach this method. In that case every getattr(meta, "...", False) here falls back to False instead of reading the stored key, which will clear thumbnail badges for existing metadata.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@faststack/app.py` around lines 2123 - 2131, The badge map building uses
getattr(...) which fails for dict-backed metadata returned by
self.sidecar.get_metadata(stem, create=False); change the logic to detect
mapping-backed metadata (e.g., isinstance(meta, dict) or
collections.abc.Mapping) and read flags with meta.get("stacked",
False)/meta.get("uploaded", False)/etc., otherwise fall back to getattr(meta,
"stacked", False) for object-backed metadata so existing dict entries are
preserved; update the code that constructs the returned dict (the block after
get_metadata) to use this branching approach.

2354-2394: ⚠️ Potential issue | 🟠 Major

Normalize metadata reads here too.

These paths still dereference meta as an object, but get_metadata() returns existing entries unchanged. If sidecar.data.entries contains dict-backed metadata, get_current_metadata(), add_favorites_to_batch(), and is_stacked() will raise AttributeError instead of staying read-only. Please reuse the same dict/object normalization pattern already used in jump_to_last_uploaded() and add_uploaded_to_batch().

Also applies to: 2615-2616, 7377-7378

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@faststack/app.py` around lines 2354 - 2394, The code assumes meta is an
attribute-based object but sidecar.get_metadata() can return dicts; normalize
meta to a mapping before dereferencing so attribute access becomes safe (same
approach used in jump_to_last_uploaded() and add_uploaded_to_batch()). Replace
the direct uses of meta (in the block building self._metadata_cache and nearby
spots) by first doing meta_raw = self.sidecar.get_metadata(stem, create=False)
then normalize it to a dict-like object (e.g., if isinstance(meta_raw, dict) use
it, else build a dict by reading stacked, stacked_date, uploaded, uploaded_date,
edited, edited_date, restacked, restacked_date, favorite, todo, todo_date via
getattr) and use that normalized mapping for the keys used by
get_current_metadata(), add_favorites_to_batch(), is_stacked(), and the
metadata_cache assembly so AttributeError is avoided.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@faststack/app.py`:
- Around line 2123-2131: The badge map building uses getattr(...) which fails
for dict-backed metadata returned by self.sidecar.get_metadata(stem,
create=False); change the logic to detect mapping-backed metadata (e.g.,
isinstance(meta, dict) or collections.abc.Mapping) and read flags with
meta.get("stacked", False)/meta.get("uploaded", False)/etc., otherwise fall back
to getattr(meta, "stacked", False) for object-backed metadata so existing dict
entries are preserved; update the code that constructs the returned dict (the
block after get_metadata) to use this branching approach.
- Around line 2354-2394: The code assumes meta is an attribute-based object but
sidecar.get_metadata() can return dicts; normalize meta to a mapping before
dereferencing so attribute access becomes safe (same approach used in
jump_to_last_uploaded() and add_uploaded_to_batch()). Replace the direct uses of
meta (in the block building self._metadata_cache and nearby spots) by first
doing meta_raw = self.sidecar.get_metadata(stem, create=False) then normalize it
to a dict-like object (e.g., if isinstance(meta_raw, dict) use it, else build a
dict by reading stacked, stacked_date, uploaded, uploaded_date, edited,
edited_date, restacked, restacked_date, favorite, todo, todo_date via getattr)
and use that normalized mapping for the keys used by get_current_metadata(),
add_favorites_to_batch(), is_stacked(), and the metadata_cache assembly so
AttributeError is avoided.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bbc63649-f820-421a-bac8-3e91cba46883

📥 Commits

Reviewing files that changed from the base of the PR and between 99c7baa and 1eef33b.

📒 Files selected for processing (2)
  • faststack/app.py
  • faststack/io/sidecar.py

@Dj-Shortcut
Copy link
Copy Markdown
Author

Good catch. I’ll keep this to a small follow-up and align those remaining read-only paths with the existing normalization pattern.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
faststack/app.py (1)

1801-1804: Drop the unreachable dict branches around get_metadata().

SidecarManager.get_metadata() returns an EntryMetadata dataclass even when create=False, so these isinstance(meta, dict) paths—and the if not meta guard on Line 2671—never fire for the real sidecar implementation. Keeping both shapes here makes the caller contract harder to follow and can hide normalization regressions at the sidecar boundary. faststack/io/sidecar.py:121-128 and faststack/models.py:61-77 both point to a single object shape.

Also applies to: 2381-2391, 2616-2616, 2671-2677, 7378-7378

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@faststack/app.py` around lines 1801 - 1804, The code is handling two shapes
for metadata but SidecarManager.get_metadata()/EntryMetadata always returns the
dataclass shape, so remove the isinstance(meta, dict) branches and the redundant
falsy checks and instead treat meta as EntryMetadata: access attributes directly
(e.g., use meta.uploaded or getattr(meta, "uploaded", None) if you need a safe
fallback) in the blocks that reference uploaded and similar fields; update all
affected call sites that use the variable named meta (including the locations
that currently check isinstance(meta, dict)) to assume the single EntryMetadata
shape and drop the dict-branch normalization to keep the caller contract
consistent with SidecarManager.get_metadata()/EntryMetadata.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@faststack/app.py`:
- Around line 1801-1804: The code is handling two shapes for metadata but
SidecarManager.get_metadata()/EntryMetadata always returns the dataclass shape,
so remove the isinstance(meta, dict) branches and the redundant falsy checks and
instead treat meta as EntryMetadata: access attributes directly (e.g., use
meta.uploaded or getattr(meta, "uploaded", None) if you need a safe fallback) in
the blocks that reference uploaded and similar fields; update all affected call
sites that use the variable named meta (including the locations that currently
check isinstance(meta, dict)) to assume the single EntryMetadata shape and drop
the dict-branch normalization to keep the caller contract consistent with
SidecarManager.get_metadata()/EntryMetadata.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c6e852b7-a19a-4270-9f27-ccb919a1c587

📥 Commits

Reviewing files that changed from the base of the PR and between 1eef33b and 3a0d52d.

📒 Files selected for processing (1)
  • faststack/app.py

@Dj-Shortcut
Copy link
Copy Markdown
Author

Dj-Shortcut commented Mar 10, 2026

Fixed. I removed the unreachable dict branches around get_metadata() in the affected read-only paths and dropped the redundant guard in add_uploaded_to_batch() to match the current SidecarManager contract.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
faststack/app.py (1)

2348-2385: ⚠️ Potential issue | 🟠 Major

Guard todo reads for older metadata objects.

Line 2384 and Line 2385 now assume every EntryMetadata has todo / todo_date, but the rest of this file still treats those fields as optional. Older sidecar entries can now blow up get_current_metadata() on a pure read.

Suggested fix
         self._metadata_cache = {
             "filename": filename,
             "exif_brief": exif_brief,
             "stacked": meta.stacked,
             "stacked_date": meta.stacked_date or "",
             "uploaded": meta.uploaded,
             "uploaded_date": meta.uploaded_date or "",
             "edited": meta.edited,
             "edited_date": meta.edited_date or "",
             "restacked": meta.restacked,
             "restacked_date": meta.restacked_date or "",
             "favorite": meta.favorite,
-            "todo": meta.todo,
-            "todo_date": meta.todo_date or "",
+            "todo": getattr(meta, "todo", False),
+            "todo_date": getattr(meta, "todo_date", None) or "",
             "stack_info_text": stack_info,
             "batch_info_text": batch_info,
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@faststack/app.py` around lines 2348 - 2385, get_current_metadata is assuming
EntryMetadata always has todo and todo_date which can raise on older sidecar
entries; change the reads where meta.todo and meta.todo_date are used (in the
block that populates self._metadata_cache) to safely access optional attributes,
e.g. use getattr(meta, "todo", False) and getattr(meta, "todo_date", "") (or an
equivalent hasattr-based guard) so older metadata without those fields yields
sensible defaults and doesn't crash.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@faststack/app.py`:
- Around line 2348-2385: get_current_metadata is assuming EntryMetadata always
has todo and todo_date which can raise on older sidecar entries; change the
reads where meta.todo and meta.todo_date are used (in the block that populates
self._metadata_cache) to safely access optional attributes, e.g. use
getattr(meta, "todo", False) and getattr(meta, "todo_date", "") (or an
equivalent hasattr-based guard) so older metadata without those fields yields
sensible defaults and doesn't crash.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 74c9df96-2f81-40c8-be40-7ae1eee823e4

📥 Commits

Reviewing files that changed from the base of the PR and between 3a0d52d and 9ed5967.

📒 Files selected for processing (1)
  • faststack/app.py

@Dj-Shortcut
Copy link
Copy Markdown
Author

Good catch. I’m currently rate-limited on my tooling, but I’ll update the todo and todo_date reads in get_current_metadata() to use safe getattr defaults shortly.

@AlanRockefeller AlanRockefeller merged commit b0a3a2e into AlanRockefeller:main Mar 12, 2026
1 check passed
@Dj-Shortcut Dj-Shortcut deleted the mijn-volgende-branch branch March 12, 2026 19:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants