Repository: caikit/caikit
Issue: Support Functional Inheritance

## Describe the bug

When creating a `class` that inherits from a `DataObjectBase` and overrides parent functions without adding or changing any of the schema, an exception is raised:

```
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "/Users/ghart/Projects/github/caikit/caikit2/caikit/core/data_model/dataobject.py", line 88, in __new__
    raise TypeError(
TypeError: All DataObjectBase classes must follow dataclass syntax
```

## Sample Code

```py
from caikit.core import DataObjectBase, dataobject

@dataobject
class Base(DataObjectBase):
    foo: int
    def doit(self):
        return self.foo * 2

@dataobject
class Derived(Base):
    def doit(self):
        return self.foo * 3
```

## Expected behavior

```py
b_inst = Base(1)
assert b_inst.doit() == 2

d_inst = Derived(1)
assert d_inst.doit() == 3
```

## Observed behavior

```
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "/Users/ghart/Projects/github/caikit/caikit2/caikit/core/data_model/dataobject.py", line 88, in __new__
    raise TypeError(
TypeError: All DataObjectBase classes must follow dataclass syntax
```

## Additional context

This will be very important for allowing content libraries to share common data structures with custom backend, construction, and functional interfaces


Changed files:
tests/core/data_model/test_dataobject.py, caikit/core/data_model/dataobject.py

diff --git a/caikit/core/data_model/dataobject.py b/caikit/core/data_model/dataobject.py
index 00bc775..efa0892 100644
--- a/caikit/core/data_model/dataobject.py
+++ b/caikit/core/data_model/dataobject.py
@@ -84,10 +84,25 @@ class _DataObjectBaseMetaClass(_DataBaseMetaClass):
         # Get the annotations that will go into the dataclass
         if name != "DataObjectBase":
             field_names = attrs.get("__annotations__")
-            if field_names is None:
+            parent_dataobjects = [
+                base for base in bases if isinstance(base, _DataBaseMetaClass)
+            ]
+            field_name_sets = [base.fields for base in parent_dataobjects]
+            if field_names is not None:
+                field_name_sets = [field_names] + field_name_sets
+
+            # We need at least one field name set!
+            if not field_name_sets:
                 raise TypeError(
                     "All DataObjectBase classes must follow dataclass syntax"
                 )
+
+            # Flatten the field names
+            field_names = {
+                field_name
+                for field_name_set in field_name_sets
+                for field_name in field_name_set
+            }
             attrs[_DataBaseMetaClass._FWD_DECL_FIELDS] = field_names
 
         # Delegate to the base metaclass
diff --git a/tests/core/data_model/test_dataobject.py b/tests/core/data_model/test_dataobject.py
index 3048a14..c606d8a 100644
--- a/tests/core/data_model/test_dataobject.py
+++ b/tests/core/data_model/test_dataobject.py
@@ -876,3 +876,27 @@ def test_dataobject_inheritance(temp_dpool):
     assert inst.foo == 1
     assert inst.bar == "asdf"
     assert inst.baz == "qwer"
+
+
+def test_dataobject_function_inheritance(temp_dpool):
+    """Make sure inheritance works to override functionality without changing
+    the schema of the parent
+    """
+
+    @dataobject
+    class Base(DataObjectBase):
+        foo: int
+
+        def doit(self):
+            return self.foo * 2
+
+    @dataobject
+    class Derived(Base):
+        def doit(self):
+            return self.foo * 3
+
+    b_inst = Base(1)
+    assert b_inst.doit() == 2
+
+    d_inst = Derived(1)
+    assert d_inst.doit() == 3