Skip to content

update type handling#16

Closed
FriendlyUser1 wants to merge 2 commits into
tomlin7:mainfrom
FriendlyUser1:type-checking-clean
Closed

update type handling#16
FriendlyUser1 wants to merge 2 commits into
tomlin7:mainfrom
FriendlyUser1:type-checking-clean

Conversation

@FriendlyUser1

@FriendlyUser1 FriendlyUser1 commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

This adds type checking, and improved types for multiple functions.

Class EntryDetails has been created since the upstream pykeepass package has poor typing, though may be fixed in future

I have also added a label to the entry modal notes text area (which is encouraged by streamlit) and set its visibility to collapsed which removes the gap above the note box.

Summary by Sourcery

Improve type safety and robustness across KeePass utilities and entry UI, while refining the entry comparison output typing.

Enhancements:

  • Introduce a strongly-typed EntryDetails structure and update database utilities and UI components to use it.
  • Guard KeePass entry and group iteration against empty collections to make database operations more robust.
  • Adjust the entry modal notes field to use a hidden label and consistent key handling for better Streamlit integration.
  • Tighten the compare_databases return type to a dictionary of string keys to sorted string lists.

@sourcery-ai

sourcery-ai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds stronger typing across database utilities and the entry modal, including a dedicated EntryDetails TypedDict, and makes small behavioral tweaks to entry/group iteration and the notes text area label.

File-Level Changes

Change Details Files
Introduce a strongly-typed structure for entry details and add type hints to database utilities.
  • Import datetime and TypedDict and define EntryDetails with explicit, nullable fields including timestamps and path.
  • Change get_entry_details return type from untyped Dict to EntryDetails
None and update its construction to match the TypedDict.
  • Adjust get_entries_set and get_entry_details to guard iteration over kp.entries and kp.groups with truthiness checks to satisfy type checking.
  • Add basic runtime validation around group traversal in merge_entry.
    • Guard the source_kp.entries iteration with a truthiness check.
    • Insert a defensive check that current_group is a Group before iterating subgroups, returning False if the type check fails.
    KeePassDiff/utils/database.py
    Tighten typing and behavior of the entry details modal component.
    • Import EntryDetails and make show_entry_details accept an EntryDetails
    None and default key to an empty string.
  • Update the notes text_area to use a named label with label_visibility="collapsed" and always provide a key based on the key parameter.
  • Strengthen typing of comparison results between databases.
    • Import List typing helper and change compare_databases return type from Dict to Dict[str, List[str]] while preserving existing keys and values.
    KeePassDiff/utils/comparison.py

    Tips and commands

    Interacting with Sourcery

    • Trigger a new review: Comment @sourcery-ai review on the pull request.
    • Continue discussions: Reply directly to Sourcery's review comments.
    • Generate a GitHub issue from a review comment: Ask Sourcery to create an
      issue from a review comment by replying to it. You can also reply to a
      review comment with @sourcery-ai issue to create an issue from it.
    • Generate a pull request title: Write @sourcery-ai anywhere in the pull
      request title to generate a title at any time. You can also comment
      @sourcery-ai title on the pull request to (re-)generate the title at any time.
    • Generate a pull request summary: Write @sourcery-ai summary anywhere in
      the pull request body to generate a PR summary at any time exactly where you
      want it. You can also comment @sourcery-ai summary on the pull request to
      (re-)generate the summary at any time.
    • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
      request to (re-)generate the reviewer's guide at any time.
    • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
      pull request to resolve all Sourcery comments. Useful if you've already
      addressed all the comments and don't want to see them anymore.
    • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
      request to dismiss all existing Sourcery reviews. Especially useful if you
      want to start fresh with a new review - don't forget to comment
      @sourcery-ai review to trigger a new review!

    Customizing Your Experience

    Access your dashboard to:

    • Enable or disable review features such as the Sourcery-generated pull request
      summary, the reviewer's guide, and others.
    • Change the review language.
    • Add, remove or edit custom review instructions.
    • Adjust other review settings.

    Getting Help

    @sourcery-ai sourcery-ai Bot left a comment

    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

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

    Hey - I've found 3 issues, and left some high level feedback:

    • In merge_entry, the if current_group != Group: check will always be true because Group is a type, not an instance; if you intend to validate the type, use isinstance(current_group, Group) (or drop the check if root_group is already guaranteed to be a Group).
    • The change to show_entry_details to default key to "" and always use key=f"notes_{key}" means calls that omit key will all share the same Streamlit widget key ("notes_"), which can cause state collisions; consider keeping key as Optional[str] and passing None through when not provided.
    Prompt for AI Agents
    Please address the comments from this code review:
    
    ## Overall Comments
    - In `merge_entry`, the `if current_group != Group:` check will always be true because `Group` is a type, not an instance; if you intend to validate the type, use `isinstance(current_group, Group)` (or drop the check if `root_group` is already guaranteed to be a `Group`).
    - The change to `show_entry_details` to default `key` to `""` and always use `key=f"notes_{key}"` means calls that omit `key` will all share the same Streamlit widget key (`"notes_"`), which can cause state collisions; consider keeping `key` as `Optional[str]` and passing `None` through when not provided.
    
    ## Individual Comments
    
    ### Comment 1
    <location path="KeePassDiff/utils/database.py" line_range="117" />
    <code_context>
         current_group = target_kp.root_group
    +
         for group_name in group_path:
    +        if current_group != Group:
    +            return False
    +
    </code_context>
    <issue_to_address>
    **issue (bug_risk):** The `current_group != Group` check will always be true and prevents `merge_entry` from progressing.
    
    Here `current_group` is an instance and `Group` is the class, so this comparison is always `True`. As a result, the loop returns `False` on its first iteration whenever `group_path` is non-empty, breaking merges for nested entries. If the goal is a type check, use `isinstance(current_group, Group)` and only return `False` when that fails, or remove this condition if `current_group` is already guaranteed to be a `Group`.
    </issue_to_address>
    
    ### Comment 2
    <location path="KeePassDiff/utils/database.py" line_range="120" />
    <code_context>
    +        if current_group != Group:
    +            return False
    +
             next_group = next((g for g in current_group.subgroups if g == group_name), None)
             if not next_group:
                 next_group = target_kp.add_group(current_group, group_name)
    </code_context>
    <issue_to_address>
    **issue (bug_risk):** Comparing `Group` instances directly to `group_name` may never match and could cause redundant group creation.
    
    Here `g` is a `Group` instance while `group_name` is a string, so this comparison will always fail unless `Group.__eq__` is specially implemented. That means you’ll always create a new subgroup instead of reusing existing ones. Compare the group’s identifier (e.g. `g.name` or `str(g)`) to `group_name` instead.
    </issue_to_address>
    
    ### Comment 3
    <location path="KeePassDiff/components/entry_modal.py" line_range="7-8" />
    <code_context>
    
    -def show_entry_details(entry_details: Optional[Dict] = None, key: str = None):
    +def show_entry_details(
    +    entry_details: EntryDetails | None = None,
    +    key: str = "",
    +):
         if entry_details:
    </code_context>
    <issue_to_address>
    **issue (bug_risk):** Changing `key` to default to an empty string may introduce unintended Streamlit key collisions.
    
    With this change, any call that omits `key` will use the same underlying widget key (`"notes_"`), so multiple `show_entry_details` instances without explicit keys will share state in Streamlit. Consider keeping `key: str | None = None` and only prefixing when `key is not None`, or generating a unique key internally for each call.
    </issue_to_address>

    Sourcery is free for open source - if you like our reviews please consider sharing them ✨
    Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

    Comment thread KeePassDiff/utils/database.py Outdated
    Comment thread KeePassDiff/utils/database.py Outdated
    Comment thread KeePassDiff/components/entry_modal.py Outdated
    @FriendlyUser1 FriendlyUser1 force-pushed the type-checking-clean branch from 6d7af3d to eff1d55 Compare June 1, 2026 15:00
    @FriendlyUser1

    Copy link
    Copy Markdown
    Contributor Author

    closing to bundle these changes with larger diff updates

    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.

    1 participant