From 67fcf4351ff30771e83c680dbf8265f52fb207fd Mon Sep 17 00:00:00 2001 From: Dimitri Yatsenko Date: Fri, 9 Jan 2026 00:51:28 -0600 Subject: [PATCH 1/2] fix: provide helpful error when table heading is not configured When using tables from non-activated schemas, operations that access the heading now raise a clear DataJointError instead of confusing "NoneType has no attribute" errors. Example: schema = dj.Schema() # Not activated @schema class MyTable(dj.Manual): ... MyTable().heading # Now raises: "Table `MyTable` is not properly # configured. Ensure the schema is activated..." Closes #1039 Co-Authored-By: Claude Opus 4.5 --- src/datajoint/table.py | 16 ++++++++++++++++ tests/integration/test_schema.py | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/datajoint/table.py b/src/datajoint/table.py index 7543c385e..5021f59e6 100644 --- a/src/datajoint/table.py +++ b/src/datajoint/table.py @@ -120,6 +120,22 @@ def table_name(self): def class_name(self): return self.__class__.__name__ + @property + def heading(self): + """ + Return the table's heading, or raise a helpful error if not configured. + + Overrides QueryExpression.heading to provide a clear error message + when the table is not properly associated with an activated schema. + """ + if self._heading is None: + raise DataJointError( + f"Table `{self.__class__.__name__}` is not properly configured. " + "Ensure the schema is activated before using the table. " + "Example: schema.activate('database_name') or schema = dj.Schema('database_name')" + ) + return self._heading + @property def definition(self): raise NotImplementedError("Subclasses of Table must implement the `definition` property") diff --git a/tests/integration/test_schema.py b/tests/integration/test_schema.py index d463ccf45..8cf231bf5 100644 --- a/tests/integration/test_schema.py +++ b/tests/integration/test_schema.py @@ -110,6 +110,32 @@ class UndecoratedClass(dj.Manual): print(a.full_table_name) +def test_non_activated_schema_heading_error(): + """ + Tables from non-activated schemas should raise informative errors. + Regression test for issue #1039. + """ + # Create schema without activating (no database name) + schema = dj.Schema() + + @schema + class TableA(dj.Manual): + definition = """ + id : int + --- + value : float + """ + + # Accessing heading should raise a helpful error + instance = TableA() + with pytest.raises(dj.DataJointError, match="not properly configured"): + _ = instance.heading + + # Operations that use heading should also raise helpful errors + with pytest.raises(dj.DataJointError, match="not properly configured"): + _ = instance.primary_key # Uses heading.primary_key + + def test_reject_decorated_part(schema_any): """ Decorating a dj.Part table should raise an informative exception. From 940d3c2bd90025de9c1b67469d6dfc0ba426d83e Mon Sep 17 00:00:00 2001 From: Dimitri Yatsenko Date: Fri, 9 Jan 2026 01:03:38 -0600 Subject: [PATCH 2/2] fix: Allow heading introspection on base tier classes The heading property now returns None for base tier classes (Lookup, Manual, Imported, Computed, Part) instead of raising an error. This allows Python's help() and inspect modules to work correctly. User-defined table classes still get the helpful error message when trying to access heading on a non-activated schema. Co-Authored-By: Claude Opus 4.5 --- src/datajoint/table.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/datajoint/table.py b/src/datajoint/table.py index 5021f59e6..ac72f7b32 100644 --- a/src/datajoint/table.py +++ b/src/datajoint/table.py @@ -120,6 +120,9 @@ def table_name(self): def class_name(self): return self.__class__.__name__ + # Base tier class names that should not raise errors when heading is None + _base_tier_classes = frozenset({"Table", "UserTable", "Lookup", "Manual", "Imported", "Computed", "Part"}) + @property def heading(self): """ @@ -127,8 +130,13 @@ def heading(self): Overrides QueryExpression.heading to provide a clear error message when the table is not properly associated with an activated schema. + For base tier classes (Lookup, Manual, etc.), returns None to support + introspection (e.g., help()). """ if self._heading is None: + # Don't raise error for base tier classes - they're used for introspection + if self.__class__.__name__ in self._base_tier_classes: + return None raise DataJointError( f"Table `{self.__class__.__name__}` is not properly configured. " "Ensure the schema is activated before using the table. "