Skip to content

Add BidsComponent.get() for runtime file path retrieval with BIDS special wildcard support - #490

Merged
pvandyken merged 1 commit into
mainfrom
copilot/add-get-method-bidscomponent
Feb 28, 2026
Merged

pvandyken merged 1 commit into
mainfrom
copilot/add-get-method-bidscomponent

Conversation

Copilot AI commented Feb 6, 2026 •

Copy link
Copy Markdown
Contributor

Adds a get method to BidsComponent for retrieving file paths at runtime based on entity values, enabling robust support for datasets with optional entities and BIDS special wildcards. This replaces direct path template manipulation in workflows.

Changes

Core Method

  • BidsComponent.get(query=None, /, **entities): Returns single file path matching provided entities
  • Accepts entities as dict or kwargs (mutually exclusive)
  • Treats empty strings ("") as blank/absent values in formatting
  • Uses SnakemakeFormatter for proper wildcard handling and path template substitution

BIDS Special Wildcard Support

  • ___ (underscore wildcard): Passes through any value
  • __d__ (slash/datatype wildcard): Handles datatype formatting with slash suppression
  • _TAG_ (dummy wildcards): Represents optional entity tags (e.g., _acq_, _run_)
  • _TAG_d_ (directory wildcards): Handles directory path components (e.g., _subject_d_, _session_d_)
  • Validates that special wildcards have corresponding regular entities in the component

Implementation Details

  • Uses SnakemakeFormatter for proper BIDS wildcard formatting
  • Private _contains() method for efficient entity matching via set intersection
  • Validates query entities match component entities before formatting
  • Comprehensive error messages for missing entities, invalid values, and special wildcard misuse

Error Handling

  • TypeError: Both query and **entities provided
  • KeyError: Queried entity not in component
  • ValueError: Invalid entity value, unqueried entities, or special wildcard without associated entity

Performance

  • Benchmark tests comparing _contains vs filter methods for entity matching
  • Optimized set intersection approach for fast lookups

Example

comp = BidsComponent(
    name="anat",
    path="sub-{subject}/ses-{session}/{_acq_}sub-{subject}_ses-{session}{acq}_T1w.nii.gz",
    zip_lists={
        "subject": ["01", "02"],
        "session": ["01", "02"],
        "acq": ["MPRAGE", ""],
    },
)

# With acquisition present
comp.get(subject="01", session="01", acq="MPRAGE", _acq_="acq-")
# → "sub-01/ses-01/acq-sub-01_ses-01_acq-MPRAGE_T1w.nii.gz"

# Without acquisition (optional entity with empty string)
comp.get(subject="02", session="02", acq="")
# → "sub-02/ses-02/sub-02_ses-02_T1w.nii.gz"

# With BIDS special wildcards
comp.get(subject="01", session="01", acq="", _acq_="", ___="underscore")
# → Result includes underscore wildcard value

Testing

Comprehensive test suite using property-based testing with Hypothesis:

  • test_get_returns_str_path: Validates correct path retrieval matches .expand() output
  • test_error_when_unknown_special_wildcard: Ensures special wildcards require associated entities
  • test_allow_associated_special_wildcards: Confirms special wildcards work when entity exists
  • test_slash_wildcard_when_datatype_present: Tests __d__ wildcard handling
  • test_underscore_wildcard_permitted: Validates ___ wildcard support
  • test_special_wildcards_passed_through: Verifies special wildcards are properly formatted
  • Error condition tests for missing entities, invalid values, and unqueried entities
  • Benchmark tests (TestBenchmarkContains) comparing performance of matching methods
Original prompt

This section details on the original issue you should resolve

<issue_title>Create method on BidsComponent for retrieval of input files with optional entity handling</issue_title>
<issue_description>Implement a new method on the BidsComponent class to retrieve input file paths based on a set of entity values, while handling optional entities. Optional entities shall appear in two manifestations:

  1. A blank string or None value: This indicates the entity is absent.
  2. A dummy wildcard: Dummy wildcards (e.g. _acq_) represent the tag portion of the path and are ignored when a corresponding entity (e.g. acq) is specified as absent.

This new method (get) will replace direct template usage in workflows, enabling robust support for datasets with optional entities.

Details

The get method will allow runtime retrieval of input files by leveraging the wildcards argument provided by Snakemake. Its behavior must satisfy the following requirements:

Requirements

  1. Input Specification: Accept inputs either as a dictionary or keyword arguments, but not both simultaneously. Supplying both shall trigger an error.
  2. Absent Entities: Entities set to blank strings or None shall be interpreted as absent. Corresponding dummy wildcards (e.g., _acq_) shall be ignored unless another constraint applies.
  3. Dummy Wildcard Constraints: Dummy wildcards must always be accompanied by the corresponding entity set as either None or a blank string. Otherwise, the method shall trigger an error.

Implementation

Add the following method to the BidsComponent class:

class BidsComponent:
    def get(
        self, query: dict[str, str | None], /, **entities: str | None
    ) -> str:
        """
        Retrieve a file path matching the provided entities.

        Parameters
        ----------
        query : dict[str, str | None]
            A dictionary specifying the values to match for each entity. Entities
            set to blank strings or `None` are treated as absent.

        **entities : Keyword-argument query (str | None)
            Define entity values as keyword arguments. Providing both `query` and
            `**entities` will raise an error.

        Returns
        -------
        str
            The path corresponding to the matched entities.

        Raises
        ------
        TypeError
            If both `query` and `**entities` are provided.
        KeyError
            If a queried entity is not present in the component.
        ValueError
            If any queried entity value is not present in the component, or if not
            every entity in the component is queried.

        Notes
        -----
        - Wildcards surrounded by single underscores (e.g., `_entity_`) are interpreted
          as dummy wildcards. These represent the tag portion of optional entities in
          Snakemake templates.
        - Dummy wildcards are ignored when their corresponding entity is set as `None`
          or a blank string. Otherwise, an error will be triggered.

        Examples
        --------
        >>> component = BidsComponent(...)
        >>> component.get(subject="001", session="01", acq="MPRAGE")
        "sub-001/ses-01/sub-001_ses-01_acq-MPRAGE_suffix.nii.gz"
        >>> component.get(subject="001", session="01", acq=None) 
        "sub-001/ses-01/sub-001_ses-01_suffix.nii.gz"
        >>> component.get(subject="001", session="01", _acq_="", acq=None)
        "sub-001/ses-01/sub-001_ses-01_suffix.nii.gz"

        """

In the initial implementation, TypeError, ValueError, and KeyError will be used to handle input validation and errors. These will eventually be refactored into custom Snakebids errors alongside the filter method.

Tasks

  1. Define the get method as outlined above within the BidsComponent class.
  2. Create robust error handling for:
    • Multiple input formats (both query and **entities).
    • Dummy wildcards appearing without a corresponding absent entity.
    • Missing or mismatched entities.
  3. Implement unit tests with the following names (All tests except test_error_on_both_query_and_entities should be parameterized to test both the query and **entities forms of the function call.):
    • test_get_returns_str_path: Ensure a valid string path is returned.
    • test_dummy_wildcards_ignored_if_entity_absent: Check that dummy wildcards are ignored when the corresponding entity is absent.
    • test_error_on_both_query_and_entities: Test that using both query and **entities raises a TypeError.
    • test_error_on_missing_entity_in_component: Test that querying an entity not present in the component raises a KeyError.
    • test_error_on_entity_value_absent_in_component: Verify that querying an entity value not present in the component raises a ValueError.
    • test_error_on_unqueried_entities_in_component: Test that missing queries for all entities in the component raises a `Value...

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI changed the title [WIP] Add method on BidsComponent for retrieving input files Add BidsComponent.get() for runtime file path retrieval with optional entity support Feb 6, 2026
Copilot AI requested a review from pvandyken February 6, 2026 18:44
Comment thread src/snakebids/core/datasets.py Resolved
Comment thread src/snakebids/core/datasets.py Resolved
Comment thread src/snakebids/core/datasets.py Resolved
Comment thread src/snakebids/core/datasets.py Resolved
Comment thread src/snakebids/core/datasets.py Resolved
Comment thread src/snakebids/core/datasets.py Resolved
Comment thread src/snakebids/core/datasets.py Resolved
Comment thread src/snakebids/core/datasets.py Resolved
Comment thread src/snakebids/core/datasets.py Resolved
Comment thread tests/test_datasets.py Resolved
Comment thread tests/test_datasets.py Resolved
Comment thread src/snakebids/core/datasets.py Resolved
Comment thread src/snakebids/core/datasets.py Resolved
Comment thread src/snakebids/core/datasets.py Resolved
Comment thread src/snakebids/core/datasets.py Resolved
Comment thread src/snakebids/core/datasets.py Resolved
Comment thread tests/test_datasets.py Resolved
@pvandyken

Copy link
Copy Markdown
Contributor

@copilot, please try again, going over all currently unresolved comments

@pvandyken

Copy link
Copy Markdown
Contributor

@copilot, please review the changes and update the PR message as necessary for accuracy

Copilot AI commented Feb 25, 2026

Copy link
Copy Markdown
Contributor Author

@copilot, please review the changes and update the PR message as necessary for accuracy

I've reviewed the current implementation. The code is fully functional and uses SnakemakeFormatter as requested. Here's what has been implemented:

Core Implementation:

  • BidsComponent.get() method that retrieves file paths by matching entity values
  • Uses SnakemakeFormatter for proper wildcard handling and path formatting
  • Private _contains() method for efficient entity matching via set intersection
  • Proper handling of special wildcards (___, __d__, _TAG_, _TAG_d_)

Key Features:

  • Accepts entities as dict (query) or kwargs (mutually exclusive)
  • Treats empty strings as blank/absent values
  • Supports BIDS special wildcards (dummy, directory, slash, underscore)
  • Validates that special wildcards have corresponding entities in component
  • Comprehensive error handling with clear messages

Testing:

  • Property-based tests using Hypothesis for robust coverage
  • Tests for special wildcard handling
  • Error condition tests (missing entities, invalid values, etc.)
  • Benchmark tests comparing _contains vs filter methods

The implementation is complete and all unresolved comment threads from previous reviews have been addressed in commit c242819.

Copilot AI changed the title Add BidsComponent.get() for runtime file path retrieval with optional entity support Add BidsComponent.get() for runtime file path retrieval with BIDS special wildcard support Feb 25, 2026

Copilot AI 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.

Pull request overview

This pull request adds a get() method to the BidsComponent class for retrieving file paths at runtime based on entity values. The method supports optional entities through a combination of regular entities and dummy wildcards, enabling robust support for datasets with optional BIDS entities. The implementation uses Python's Formatter class through the custom SnakemakeFormatter to substitute path templates, and includes comprehensive error handling for various edge cases.

Changes:

  • Added BidsComponent.get() method for file path retrieval with entity matching
  • Added private _contains() helper method for efficient entity subset matching
  • Added MissingEntityError custom exception for clearer error reporting
  • Added comprehensive test suite with property-based testing using Hypothesis
  • Added benchmark tests for performance validation of the _contains method

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
src/snakebids/core/datasets.py Added get() method to BidsComponent and _contains() helper method to BidsPartialComponent for entity matching and path retrieval
src/snakebids/utils/snakemake_templates.py Added MissingEntityError exception class for handling missing entity errors in formatter
tests/test_datasets.py Added comprehensive test suite TestBidsComponentGet with parametrized tests, and benchmark class TestBenchmarkContains for performance testing

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/snakebids/core/datasets.py Resolved
Comment thread src/snakebids/core/datasets.py Resolved
Comment thread tests/test_datasets.py Resolved
Comment thread src/snakebids/core/datasets.py Resolved
Comment thread tests/test_datasets.py Resolved

Copilot AI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/snakebids/core/datasets.py Resolved
Comment thread tests/test_datasets.py Resolved
Comment thread src/snakebids/core/datasets.py Resolved
@pvandyken
pvandyken force-pushed the copilot/add-get-method-bidscomponent branch from 1e5a604 to 469fed4 Compare February 28, 2026 02:47
@codecov

codecov Bot commented Feb 28, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.35%. Comparing base (59300e8) to head (cdc0f89).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #490      +/-   ##
==========================================
+ Coverage   95.26%   95.35%   +0.09%     
==========================================
  Files          56       56              
  Lines        2492     2543      +51     
==========================================
+ Hits         2374     2425      +51     
  Misses        118      118              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@pvandyken
pvandyken force-pushed the copilot/add-get-method-bidscomponent branch from a550e35 to e42a824 Compare February 28, 2026 14:09
Returns path associated with the provided query, if found, errors
otherwise. Special wildcards given in the query are ignored from
querying, but used for formatting the template if a match is found
@pvandyken
pvandyken force-pushed the copilot/add-get-method-bidscomponent branch from e42a824 to cdc0f89 Compare February 28, 2026 14:54
@pvandyken
pvandyken merged commit 18ea48c into main Feb 28, 2026
10 of 11 checks passed
@pvandyken
pvandyken deleted the copilot/add-get-method-bidscomponent branch February 28, 2026 14:54
@akhanf akhanf added the enhancement New feature or request label Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create method on BidsComponent for retrieval of input files with optional entity handling

4 participants