diff --git a/grabbit/core.py b/grabbit/core.py
index cdb0663..8c3c375 100644
--- a/grabbit/core.py
+++ b/grabbit/core.py
@@ -9,6 +9,7 @@ from os.path import (join, basename, dirname, abspath, split, isabs, exists)
 from functools import partial
 from copy import deepcopy
 import warnings
+from keyword import iskeyword
 
 
 __all__ = ['File', 'Entity', 'Layout']
@@ -90,7 +91,18 @@ class File(object):
         Returns the File as a named tuple. The full path plus all entity
         key/value pairs are returned as attributes.
         """
-        entities = self.entities
+        keys = list(self.entities.keys())
+        replaced = []
+        for i, k in enumerate(keys):
+            if iskeyword(k):
+                replaced.append(k)
+                keys[i] = '%s_' % k
+        if replaced:
+            safe = ['%s_' % k for k in replaced]
+            warnings.warn("Entity names cannot be reserved keywords when "
+                          "representing a File as a namedtuple. Replacing "
+                          "entities %s with safe versions %s." % (keys, safe))
+        entities = dict(zip(keys, self.entities.values()))
         _File = namedtuple('File', 'filename ' + ' '.join(entities.keys()))
         return _File(filename=self.path, **entities)
 

Handle entity names that conflict with reserved keywords
In cases where an `Entity` uses a reserved keyword as its name (e.g., `class`), exceptions can occur for some `.get()` queries (e.g., when `return_type='tuple'`, because reserved keywords can't be attributes). We need to find some workaround for this--e.g., setting a different name internally, or adopting a convention of appending underscores, etc. See INCF/pybids#142 for relevant discussion.
diff --git a/grabbit/tests/test_core.py b/grabbit/tests/test_core.py
index e85cbe8..d13da59 100644
--- a/grabbit/tests/test_core.py
+++ b/grabbit/tests/test_core.py
@@ -5,6 +5,7 @@ from os.path import join
 import posixpath as psp
 import tempfile
 import json
+from copy import copy
 
 
 DIRNAME = os.path.dirname(__file__)
@@ -64,6 +65,7 @@ class TestFile:
         assert f.entities == {}
 
     def test_matches(self, file):
+        file = copy(file)
         assert file._matches()
         assert file._matches(extensions='nii.gz')
         assert not file._matches(extensions=['.txt', '.rtf'])
@@ -79,6 +81,7 @@ class TestFile:
                              regex_search=True)
 
     def test_named_tuple(self, file):
+        file = copy(file)
         file.tags = {'attrA': Tag(None, 'apple'), 'attrB': Tag(None, 'banana')}
         tup = file.as_named_tuple()
         assert(tup.filename == file.path)
@@ -86,6 +89,15 @@ class TestFile:
         assert not hasattr(tup, 'task')
         assert tup.attrA == 'apple'
 
+    def test_named_tuple_with_reserved_name(self, file):
+        file = copy(file)
+        file.tags['class'] = Tag(None, 'invalid')
+        with pytest.warns(UserWarning) as w:
+            res = file.as_named_tuple()
+            assert w[0].message.args[0].startswith('Entity names cannot')
+            assert hasattr(res, 'class_')
+            assert not hasattr(res, 'class')
+
 
 class TestEntity:
 

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest six
apt-get update apt-get install -y gcc
3.9
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
exceptiongroup==1.2.2
-e git+https://github.com/grabbles/grabbit.git@2d48a95f25c674ae64225d426722ac22b64f278b#egg=grabbit
iniconfig==2.1.0
packaging==24.2
pluggy==1.5.0
pytest==8.3.5
six==1.17.0
tomli==2.2.1

name: grabbit
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - exceptiongroup==1.2.2
      - iniconfig==2.1.0
      - packaging==24.2
      - pluggy==1.5.0
      - pytest==8.3.5
      - six==1.17.0
      - tomli==2.2.1
prefix: /opt/conda/envs/grabbit

grabbit/tests/test_core.py::TestFile::test_named_tuple_with_reserved_name
grabbit/tests/test_core.py::TestFile::test_init grabbit/tests/test_core.py::TestFile::test_matches grabbit/tests/test_core.py::TestFile::test_named_tuple grabbit/tests/test_core.py::TestEntity::test_init grabbit/tests/test_core.py::TestEntity::test_matches grabbit/tests/test_core.py::TestEntity::test_unique_and_count grabbit/tests/test_core.py::TestEntity::test_add_file grabbit/tests/test_core.py::TestLayout::test_init[local] grabbit/tests/test_core.py::TestLayout::test_init_with_include_arg[local] grabbit/tests/test_core.py::TestLayout::test_init_with_exclude_arg[local] grabbit/tests/test_core.py::TestLayout::test_absolute_paths[local] grabbit/tests/test_core.py::TestLayout::test_querying[local] grabbit/tests/test_core.py::TestLayout::test_natsort[local] grabbit/tests/test_core.py::TestLayout::test_unique_and_count[local] grabbit/tests/test_core.py::TestLayout::test_get_nearest[local] grabbit/tests/test_core.py::TestLayout::test_index_regex[local] grabbit/tests/test_core.py::TestLayout::test_save_index[local] grabbit/tests/test_core.py::TestLayout::test_load_index[local] grabbit/tests/test_core.py::TestLayout::test_clone[local] grabbit/tests/test_core.py::TestLayout::test_parse_file_entities[local] grabbit/tests/test_core.py::test_merge_layouts[local] grabbit/tests/test_core.py::TestLayout::test_dynamic_getters[/grabbit/grabbit/tests/data/7t_trt-/grabbit/grabbit/tests/specs/test.json] grabbit/tests/test_core.py::TestLayout::test_entity_mapper grabbit/tests/test_core.py::TestLayout::test_excludes grabbit/tests/test_core.py::TestLayout::test_multiple_domains grabbit/tests/test_core.py::TestLayout::test_get_by_domain
grabbit/core.py
grabbit/core.py