Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ requires-python = '>=3.9'
'core.bool' = 'aiida.orm.nodes.data.bool:Bool'
'core.cif' = 'aiida.orm.nodes.data.cif:CifData'
'core.code' = 'aiida.orm.nodes.data.code.legacy:Code'
'core.code.abstract' = 'aiida.orm.nodes.data.code.abstract:AbstractCode'
'core.code.containerized' = 'aiida.orm.nodes.data.code.containerized:ContainerizedCode'
'core.code.installed' = 'aiida.orm.nodes.data.code.installed:InstalledCode'
'core.code.portable' = 'aiida.orm.nodes.data.code.portable:PortableCode'
Expand Down
10 changes: 9 additions & 1 deletion src/aiida/orm/querybuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -1338,11 +1338,19 @@ def _get_node_type_filter(classifiers: Classifier, subclassing: bool) -> dict:

value = classifiers.ormclass_type_string

# Since `AbstractCode` was introduced later and does not have a direct entry point,
# filtering for `data.core.code.abstract%` does not return any results. However, both
# `InstalledCode` and `PortableCode` (which inherit from `AbstractCode`) use `data.core.code.%`
# as their node type. To ensure `AbstractCode` queries correctly return all code instances,
# we adjust the filter to `data.core.code%`, matching all subclasses properly.

if value == 'data.core.code.abstract':
value = 'data.core.code' # Ensure it matches all codes

if not subclassing:
filters = {'==': value}
else:
# Note: the query_type_string always ends with a dot. This ensures that "like {str}%" matches *only*
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please keep this comment

# the query type string
filters = {'like': f'{escape_for_sql_like(get_query_type_from_type_string(value))}%'}

return filters
Expand Down
52 changes: 52 additions & 0 deletions tests/orm/test_querybuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,58 @@ def test_empty_filters(self):
qb = orm.QueryBuilder().append(orm.Data, filters={'or': [{}, {}]})
assert qb.count() == count

@pytest.mark.usefixtures('aiida_profile_clean')
def test_abstract_code_filtering(self, tmp_path):
"""Test that querying for AbstractCode correctly returns all code instances.

This tests the fix for issue #6687, where QueryBuilder couldn't find codes
when looking for AbstractCode due to a node_type mismatch.
"""
# Set up test environment

computer = orm.Computer(
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should use existing test fixtures here instead of managing manually, e.g. aiida_code or aiida_code_installed.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think in this case yes, you can just use them. When I had a go I was not yet sure what the test should do. Sometimes you need a computer with a specific label or so. But in this case the fixture should be usable.

label='test_computer_abstract',
hostname='localhost',
transport_type='core.local',
scheduler_type='core.direct',
).store()

# Create test codes of different types
installed_code = orm.InstalledCode(
label='test_installed_abstract',
computer=computer,
filepath_executable='/bin/bash',
).store()

(tmp_path / 'exec').touch()
portable_code = orm.PortableCode(
label='test_portable_abstract',
filepath_executable='exec',
filepath_files=tmp_path,
).store()

# Test 1: AbstractCode query should find all code types
qb_abstract = orm.QueryBuilder().append(orm.AbstractCode)
abstract_results = qb_abstract.all(flat=True)
assert (
installed_code in abstract_results
), f'InstalledCode not found with AbstractCode query. Result: {abstract_results}'
assert (
portable_code in abstract_results
), f'PortableCode not found with AbstractCode query. Result: {abstract_results}'

# Test 2: Verify specific code type queries work as expected
qb_installed = orm.QueryBuilder().append(orm.InstalledCode)
installed_results = qb_installed.all(flat=True)
assert installed_code in installed_results
assert portable_code not in installed_results

# Test 3: Test with basic filtering
qb_filtered = orm.QueryBuilder().append(orm.AbstractCode, filters={'label': 'test_installed_abstract'})
filtered_results = qb_filtered.all(flat=True)
assert len(filtered_results) == 1
assert installed_code in filtered_results


class TestAttributes:
@pytest.mark.requires_psql
Expand Down
Loading