Repository: linkml/linkml

Issue:
pythongen ifabsent generates incorrect values for class_curie and slot_curie
the metamodel has a slot class_uri, with an `ifabsent` defined as follows:

```yaml
classes:
  ClassDefinition:
     slots:
       - ...
       - class_uri
       - ...
slots:       
  class_uri:
    range: uriorcurie
    description: URI of the class that provides a semantic interpretation of the element in a linked data context. The URI may come from any namespace and may be shared between schemas
    ifabsent: class_curie
```

The documentation for `ifabsent` says:

```
        * class_curie -- CURIE for the containing class
        * class_uri -- URI for the containing class
```

this means that if I would expect

```python
>>> cls = ClassDefinition("Foo")
>>> print(cls.class_curie)
myschema:Foo
```

however, with the `ifabsent_function` changes in

- #1171.   

meta.py now says:

```python
class ClassDefinition(...):
    ...
    class_curie: ... = class_class_curie
```

so the actual behavior we get is:

```python
>>> cls = ClassDefinition("Foo")
>>> print(cls.class_curie)
linkml:ClassDefinition
```

I think we revert to having the generated default being `None` for all x_uri slots.


Comments:

Patch:
diff --git a/linkml/generators/excelgen.py b/linkml/generators/excelgen.py
index fd697895..5fb493f8 100644
--- a/linkml/generators/excelgen.py
+++ b/linkml/generators/excelgen.py
@@ -1,144 +1,173 @@
 import os
-from dataclasses import dataclass, field
-from typing import List, Optional, TextIO, Union
+
+from dataclasses import dataclass
+from typing import List
 
 import click
-from linkml_runtime.linkml_model.meta import (ClassDefinition, EnumDefinition,
-                                              PermissibleValue,
-                                              PermissibleValueText,
-                                              SchemaDefinition, SlotDefinition)
-from linkml_runtime.utils.formatutils import camelcase
-from openpyxl import Workbook, load_workbook
-from openpyxl.utils import get_column_letter
-from openpyxl.worksheet.datavalidation import DataValidation
 
-from linkml._version import __version__
+from linkml_runtime.utils.schemaview import SchemaView
 from linkml.utils.generator import Generator, shared_arguments
+from linkml._version import __version__
+from linkml.utils.helpers import convert_to_snake_case
+from openpyxl import Workbook
+from openpyxl.worksheet.worksheet import Worksheet
+from openpyxl.worksheet.datavalidation import DataValidation
+from openpyxl.utils import get_column_letter
 
 
 @dataclass
 class ExcelGenerator(Generator):
-    """This class is a blueprint for the generator module that is responsible
-    for automatically creating Excel spreadsheets from the LinkML schema.
-
-    :param schema: LinkML schema object
-    :type schema: class:`SchemaDefinition`
-    :param output: LinkML schema specification in YAML format
-    :type output: str
-    """
-
     # ClassVars
-    generator_name = os.path.splitext(os.path.basename(__file__))[0]
-    generator_version = "0.0.1"
+    generatorname = os.path.basename(__file__)
+    generatorversion = "0.1.1"
     valid_formats = ["xlsx"]
-    uses_schemaloader = True
+    uses_schemaloader = False
     requires_metamodel = False
 
-    # ObjectVars
-    sheet_name_cols: List[str] = field(default_factory=lambda: [])
-    output: str = None
-    workbook: Workbook = field(default_factory=lambda: Workbook())
-    wb_name: str = None
-    enum_dict: dict = field(default_factory=lambda: dict())
-    """dictionary with slot types and possibles values for those types"""
-
-    def _workbook_path(self, yaml_filename: str, wb_name: str = None):
-        """Internal method that computes the path where the Excel workbook
-        should be stored.
-
-        :param yaml_filename: Name of provided LinkML schema
-        :type yaml_filename: str
-        :param wb_name: Prefix for the generated Excel spreadsheet name
-        :type wb_name: str
+    def __post_init__(self) -> None:
+        super().__post_init__()
+        self.schemaview = SchemaView(self.schema)
+
+    def create_workbook(self, workbook_name: str) -> Workbook:
+        """
+        Creates an Excel workbook using the openpyxl library and returns it.
+
+        :param workbook_name: Name of the workbook to be created.
+        :return: An openpyxl Workbook object representing the newly created workbook.
+        """
+        workbook = Workbook()
+        workbook.title = workbook_name
+        return workbook
+
+    def get_workbook_name(self, workbook: Workbook) -> str:
         """
-        # handle the case when an output filename is not provided
-        if not wb_name:
-            prefix, _ = os.path.splitext(os.path.basename(yaml_filename))
-            prefix_root, prefix_ext = os.path.splitext(prefix)
+        Returns the name of the given workbook.
 
-            if prefix_ext == ".yaml":
-                prefix = prefix_root
+        :param workbook: The workbook whose name should be returned.
+        :return: Name of the workbook.
+        """
+        return workbook.title
 
-            output_xlsx = (
-                f"{prefix}_{self.generator_name}_{self.generator_version}.xlsx"
-            )
+    def remove_worksheet_by_name(self, workbook: Workbook, worksheet_name: str) -> None:
+        """
+        Remove worksheet from workbook by name.
+        """
+        worksheet = workbook[worksheet_name]
+        workbook.remove(worksheet)
 
-            return output_xlsx
+    def create_worksheet(self, workbook: Workbook, worksheet_name: str) -> Worksheet:
+        """
+        Creates an Excel worksheet with the given name in the given workbook.
 
-        return wb_name
+        :param workbook: The workbook to which the worksheet should be added.
+        :param worksheet_name: Name of the worksheet to be created.
+        """
+        worksheet = workbook.create_sheet(worksheet_name)
+        workbook_name = self.get_workbook_name(workbook)
+        workbook.save(workbook_name)
 
-    def __post_init__(self):
-        super().__post_init__()
-        self.wb_name = self._workbook_path(yaml_filename=self.schema, wb_name=self.output)
-        self.workbook.remove(self.workbook["Sheet"])
+        return worksheet
 
-    def _create_spreadsheet(self, ws_name: str, columns: List[str]) -> None:
-        """Method to add worksheets to the Excel workbook.
+    def create_schema_worksheets(self, workbook: str) -> None:
+        """
+        Creates worksheets in a given Excel workbook based on the classes in the
+        schema.
 
-        :param ws_name: Name of each of the worksheets
-        :type ws_name: str
-        :param columns: Columns that are relevant to each of the worksheets
-        :type columns: List[str]
+        :param workbook: The workbook to which the worksheet should be added.
         """
-        ws = self.workbook.create_sheet(ws_name)
-        self.workbook.active = ws
-        ws.append(columns)
-        self.workbook.save(self.wb_name)
-
-    def visit_class(self, cls: ClassDefinition) -> bool:
-        """Overridden method to intercept classes from generator framework."""
-        self._create_spreadsheet(ws_name=camelcase(cls.name), columns=cls.slots)
-
-        return True
-
-    def visit_enum(self, enum: EnumDefinition) -> bool:
-        """Overridden method to intercept enums from generator framework."""
-
-        def extract_permissible_text(pv):
-            if type(pv) is str:
-                return pv
-            if type(pv) is PermissibleValue:
-                return pv.text.code
-            if type(pv) is PermissibleValueText:
-                return pv
-            raise ValueError(f"Invalid permissible value in enum {enum}: {pv}")
-
-        permissible_values_texts = list(
-            map(extract_permissible_text, enum.permissible_values or [])
-        )
+        sv = self.schemaview
+        for cls_name, cls in sv.all_classes(imports=self.mergeimports).items():
+            if not cls.mixin and not cls.abstract:
+                self.create_worksheet(workbook, cls_name)
 
-        self.enum_dict[enum.name] = permissible_values_texts
+    def add_columns_to_worksheet(
+        self, workbook: Workbook, worksheet_name: str, sheet_headings: List[str]
+    ) -> None:
+        """
+        Get a worksheet by name and add a column to it in an existing workbook.
 
-    def visit_class_slot(
-        self, cls: ClassDefinition, aliased_slot_name: str, slot: SlotDefinition
+        :param workbook: The workbook to which the worksheet should be added.
+        :param worksheet_name: Name of the worksheet to add the column to.
+        :param column_data: List of data to populate the column with.
+        """
+        # Get the worksheet by name
+        worksheet = workbook[worksheet_name]
+
+        # Add the headings to the worksheet
+        for i, heading in enumerate(sheet_headings):
+            worksheet.cell(row=1, column=i + 1, value=heading)
+
+        # Save the changes to the workbook
+        workbook_name = self.get_workbook_name(workbook)
+        workbook.save(workbook_name)
+
+    def column_enum_validation(
+        self,
+        workbook: Workbook,
+        worksheet_name: str,
+        column_name: str,
+        dropdown_values: List[str],
     ) -> None:
-        """Overridden method to intercept classes and associated slots from generator
-        framework."""
-        self.workbook = load_workbook(self.wb_name)
+        """
+        Get worksheet by name and add a dropdown to a specific column in it
+        based on a list of values.
 
-        if cls.name in self.workbook.sheetnames:
-            if slot.range in self.enum_dict:
+        :param workbook: The workbook to which the worksheet should be added.
+        :param worksheet_name: Name of the worksheet to add the column dropdown to.
+        :param column_name: Name of the worksheet column to add the dropdown to.
+        :param dropdown_values: List of dropdown values to add to a column in a worksheet.
+        """
+        worksheet = workbook[worksheet_name]
 
-                valid = ",".join(self.enum_dict[slot.range])
-                valid = '"' + valid + '"'
+        column_list = [cell.value for cell in worksheet[1]]
+        column_number = column_list.index(column_name) + 1
+        column_letter = get_column_letter(column_number)
 
-                ws = self.workbook[cls.name]
+        # Create the data validation object and set the dropdown values
+        dv = DataValidation(
+            type="list", formula1=f'"{",".join(dropdown_values)}"', allow_blank=True
+        )
 
-                rows = ws.iter_rows(min_row=1, max_row=1)  # returns a generator of rows
-                first_row = next(rows)  # get the first row
-                headings = [
-                    c.value for c in first_row
-                ]  # extract the values from the cells
+        worksheet.add_data_validation(dv)
 
-                idx = headings.index(slot.name)
-                col_letter = get_column_letter(idx + 1)
+        dv.add(f"{column_letter}2:{column_letter}1048576")
 
-                dv = DataValidation(type="list", formula1=valid, allow_blank=True)
-                ws.add_data_validation(dv)
+        workbook_name = self.get_workbook_name(workbook)
+        workbook.save(workbook_name)
 
-                dv.add(f"{col_letter}2:{col_letter}1048576")
+    def serialize(self, **kwargs) -> str:
+        output = (
+            convert_to_snake_case(self.schema.name) + ".xlsx"
+            if not self.output
+            else self.output
+        )
 
-                self.workbook.save(self.wb_name)
+        workbook = self.create_workbook(output)
+        self.remove_worksheet_by_name(workbook, "Sheet")
+        self.create_schema_worksheets(workbook)
+
+        sv = self.schemaview
+        for cls_name, cls in sv.all_classes(imports=self.mergeimports).items():
+            if not cls.mixin and not cls.abstract:
+                slots = [
+                    s.name
+                    for s in sv.class_induced_slots(cls_name, imports=self.mergeimports)
+                ]
+                self.add_columns_to_worksheet(workbook, cls_name, slots)
+
+        enum_list = [
+            e_name for e_name, _ in sv.all_enums(imports=self.mergeimports).items()
+        ]
+        for cls_name, cls in sv.all_classes(imports=self.mergeimports).items():
+            if not cls.mixin and not cls.abstract:
+                for s in sv.class_induced_slots(cls_name, imports=self.mergeimports):
+                    if s.range in enum_list:
+                        pv_list = []
+                        for pv_name, _ in sv.get_enum(
+                            s.range
+                        ).permissible_values.items():
+                            pv_list.append(pv_name)
+                        self.column_enum_validation(workbook, cls_name, s.name, pv_list)
 
 
 @shared_arguments(ExcelGenerator)
diff --git a/linkml/generators/owlgen.py b/linkml/generators/owlgen.py
index e4d613db..6fc7c030 100644
--- a/linkml/generators/owlgen.py
+++ b/linkml/generators/owlgen.py
@@ -507,7 +507,7 @@ class OwlSchemaGenerator(Generator):
         for k, v in el.__dict__.items():
             if k in self.metamodel.schema.slots:
                 defining_slot = self.metamodel.schema.slots[k]
-                if v is not None and "owl" in defining_slot.in_subset:
+                if v is not None and ("owl" in defining_slot.in_subset or "OwlProfile" in defining_slot.in_subset):
                     ve = v if isinstance(v, list) else [v]
                     for e in ve:
                         if (
diff --git a/linkml/utils/helpers.py b/linkml/utils/helpers.py
index e9b89987..1d9d348f 100644
--- a/linkml/utils/helpers.py
+++ b/linkml/utils/helpers.py
@@ -1,3 +1,5 @@
+import re
+
 def remove_duplicates(lst):
     """Remove duplicate tuples from a list of tuples."""
     return [t for t in (set(tuple(i) for i in lst))]
@@ -6,3 +8,7 @@ def remove_duplicates(lst):
 def write_to_file(file_path, data, mode="w", encoding="utf-8"):
     with open(file_path, mode, encoding=encoding) as f:
         f.write(data)
+
+def convert_to_snake_case(str):
+    str = re.sub(r"(?<=[a-z])(?=[A-Z])|[^a-zA-Z]", " ", str).strip().replace(' ', '_')
+    return ''.join(str.lower())
diff --git a/linkml/utils/ifabsent_functions.py b/linkml/utils/ifabsent_functions.py
index 33ab4044..fa4aa7e5 100644
--- a/linkml/utils/ifabsent_functions.py
+++ b/linkml/utils/ifabsent_functions.py
@@ -72,11 +72,22 @@ default_library: List[
     (r"float\(([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)\)", False, lambda m, __, ___, ____: float(m[1])),
     (r"date\((\d{4})-(\d{2})-(\d{2})\)", False, lambda m, __, ___, ____: f"datetime.date({m[1]}, {m[2]}, {m[3]})"),
     (r"datetime\((\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z\)", False, lambda m, __, ___, ____: f"datetime.datetime({m[1]}, {m[2]}, {m[3]}, {m[4]}, {m[5]}, {m[6]})"),
-    ("class_uri", False, lambda _, __, class_definition, ____: 'class_class_uri'),
-    ("class_curie", False, lambda _, __, class_definition, ____: 'class_class_curie'),
-    ("slot_uri", True, lambda _, loader, ___, slot_definition: f'slots.{slot_definition.name}.uri'),
-    ("slot_curie", True, lambda _, loader, ___, slot_definition: f'slots.{slot_definition.name}.curie'),
-    ("default_range", False, lambda _, loader, __, ____: f"{strval(loader.schema.default_range)}"),
+    # TODO: We have to make the real URI available before any of these can work
+    # ("class_uri", True, lambda _, loader, ___, ____: f'"{default_uri_for(loader)}" + camelcase(self.name)'),
+    # ("slot_uri", True, lambda _, loader, ___, ____: f'"{default_uri_for(loader)}" + underscore(self.alias if self.alias else self.name)'),
+    # ("class_curie", True, lambda _, loader, ___, ____: curie_for(loader, True)),
+    # ("slot_curie", True, lambda _, loader, ___, ____: curie_for(loader, False)),
+    ("class_uri", True, lambda _, loader, ___, ____: "None"),
+    ("slot_uri", True, lambda _, loader, ___, ____: "None"),
+    ("class_curie", True, lambda _, loader, ___, ____: "None"),
+    ("slot_curie", True, lambda _, loader, ___, ____: "None"),
+    # See: https://github.com/linkml/linkml/issues/1333
+    # ("class_uri", False, lambda _, __, class_definition, ____: 'class_class_uri'),
+    # ("class_curie", False, lambda _, __, class_definition, ____: 'class_class_curie'),
+    # ("slot_uri", True, lambda _, loader, ___, slot_definition: f'slots.{slot_definition.name}.uri'),
+    # ("slot_curie", True, lambda _, loader, ___, slot_definition: f'slots.{slot_definition.name}.curie'),
+    # ("default_range", False, lambda _, loader, __, ____: f"{strval(loader.schema.default_range)}"),
+    ("default_range", False, lambda _, __, ___, ____: "None"),
     ("bnode", False, lambda _, __, ___, ____: "bnode()"),
     (r"string\((.*)\)", False, lambda m, __, ___, ____: strval(m[1])),
     (r"uri\((.*)\)", False, lambda m, loader, _, __: uri_for(m[1], loader)),


Test patch:
diff --git a/tests/test_generators/test_docgen.py b/tests/test_generators/test_docgen.py
index 7764f94a..c6a5a79d 100644
--- a/tests/test_generators/test_docgen.py
+++ b/tests/test_generators/test_docgen.py
@@ -3,6 +3,7 @@ import os
 import shutil
 import tempfile
 import unittest
+import yaml
 from copy import copy
 from typing import List
 
@@ -303,6 +304,16 @@ class DocGeneratorTestCase(unittest.TestCase):
             "Example: Person",
             after="## Examples",)
         
+        # checks correctness of the YAML representation of source schema
+        person_source = gen.yaml(gen.schemaview.get_class("Person"))
+        person_dict = yaml.load(person_source, Loader=yaml.Loader)
+        # consider the species name slot
+        # species name has the Person class repeated multiple times in domain_of
+        domain_of_species_name = person_dict["slot_usage"]["species name"]["domain_of"]
+        self.assertTrue(
+            len(set(domain_of_species_name)) == len(domain_of_species_name)
+        )
+
     def test_docgen_no_mergeimports(self):
         """Tests when imported schemas are not folded into main schema"""
         gen = DocGenerator(SCHEMA, mergeimports=False, no_types_dir=True)
diff --git a/tests/test_generators/test_excelgen.py b/tests/test_generators/test_excelgen.py
index eb307b3b..9644d24e 100644
--- a/tests/test_generators/test_excelgen.py
+++ b/tests/test_generators/test_excelgen.py
@@ -25,47 +25,47 @@ class ExcelGenTestCase(unittest.TestCase):
         wb_obj = load_workbook(xlsx_filename)
 
         # check the names of the created worksheets that are part of the workbook
-        assert wb_obj.sheetnames == ["Employee", "Manager", "Organization"]
+        assert wb_obj.sheetnames == ["organization", "employee", "manager"]
 
         # test case to check the column names in Employee worksheet
         employee_cols_list = []
-        max_col = wb_obj["Employee"].max_column
+        max_col = wb_obj["employee"].max_column
 
         for i in range(1, max_col + 1):
-            cell_obj = wb_obj["Employee"].cell(row=1, column=i)
+            cell_obj = wb_obj["employee"].cell(row=1, column=i)
             employee_cols_list.append(cell_obj.value)
 
         assert sorted(employee_cols_list) == [
             "age in years",
             "aliases",
-            "employee_last name",
             "first name",
             "id",
+            "last name"
         ]
 
         # test case to check the column names in Manager worksheet
         manager_cols_list = []
-        max_col = wb_obj["Manager"].max_column
+        max_col = wb_obj["manager"].max_column
 
         for i in range(1, max_col + 1):
-            cell_obj = wb_obj["Manager"].cell(row=1, column=i)
+            cell_obj = wb_obj["manager"].cell(row=1, column=i)
             manager_cols_list.append(cell_obj.value)
 
         assert sorted(manager_cols_list) == [
             "age in years",
             "aliases",
-            "employee_last name",
             "first name",
             "has employees",
             "id",
+            "last name",
         ]
 
         # test case to check the column names in Organization worksheet
         organization_cols_list = []
-        max_col = wb_obj["Organization"].max_column
+        max_col = wb_obj["organization"].max_column
 
         for i in range(1, max_col + 1):
-            cell_obj = wb_obj["Organization"].cell(row=1, column=i)
+            cell_obj = wb_obj["organization"].cell(row=1, column=i)
             organization_cols_list.append(cell_obj.value)
 
         assert sorted(organization_cols_list) == ["has boss", "id", "name"]
diff --git a/tests/test_issues/test_issue_675.py b/tests/test_issues/test_issue_675.py
index 6a8acb02..e88ab330 100644
--- a/tests/test_issues/test_issue_675.py
+++ b/tests/test_issues/test_issue_675.py
@@ -1,5 +1,6 @@
 import unittest
 
+from linkml.generators.pydanticgen import PydanticGenerator
 from linkml_runtime.utils.compile_python import compile_python
 
 from linkml.generators.pythongen import PythonGenerator
@@ -76,27 +77,77 @@ classes:
 
 class IfAbsentTestCase(unittest.TestCase):
     def test_ifabsent(self):
-        print(PythonGenerator(model_txt).serialize())
+        """
+        Tests pythongenerator with ifabsent_functions.
+
+        See: https://github.com/linkml/linkml/issues/1333
+        """
+        # print(PythonGenerator(model_txt).serialize())
         m = compile_python(PythonGenerator(model_txt).serialize())
         sample = m.HighClass()
         self.assertEqual(sample.bool_true_slot, True)
         self.assertEqual(sample.bool_false_slot, False)
         print("class_curie_slot fails")
-        self.assertEqual(sample.class_curie_slot, m.HighClass.class_class_curie)
+        # self.assertEqual(sample.class_curie_slot, m.HighClass.class_class_curie)
+        self.assertIsNone(sample.class_curie_slot)
+        print("class_uri_slot fails")
+        # self.assertEqual(sample.class_uri_slot, m.HighClass.class_class_uri)
+        self.assertIsNone(sample.class_uri_slot)
+        print(m.HighClass.class_class_uri)
+        print("default_ns fails")
+        self.assertEqual(sample.default_ns_slot, 'ex')
+        print("default_range fails")
+        # self.assertEqual(sample.default_range_slot, 'string')
+        self.assertIsNone(sample.default_range_slot)
+        print("int(0) fails")
+        self.assertEqual(sample.int_0_slot, 0)
+        self.assertEqual(sample.int_42_slot, 42)
+        self.assertEqual(sample.neg_int_slot, -117243)
+        print("slot_curie fails")
+        # self.assertEqual(sample.slot_curie_slot, m.slots.slot_curie_slot.curie)
+        self.assertIsNone(sample.slot_curie_slot)
+        print("slot_uri fails")
+        # self.assertEqual(sample.slot_uri_slot, m.slots.slot_uri_slot.uri)
+        self.assertIsNone(sample.slot_uri_slot)
+        self.assertIsNone(sample.slot_curie_slot)
+        self.assertEqual(sample.string_slot, "s1")
+        self.assertEqual(sample.mt_string_slot, "")
+
+    @unittest.skip("TODO: https://github.com/linkml/linkml/issues/1334")
+    def test_ifabsent_pydantic(self):
+        """
+        Tests pydantic generator with ifabsent_functions.
+
+        See: https://github.com/linkml/linkml/issues/1334
+        """
+        print(PydanticGenerator(model_txt).serialize())
+        m = compile_python(PydanticGenerator(model_txt).serialize())
+        sample = m.HighClass()
+        self.assertEqual(sample.bool_true_slot, True)
+        self.assertEqual(sample.bool_false_slot, False)
+        print("class_curie_slot fails")
+        # self.assertEqual(sample.class_curie_slot, m.HighClass.class_class_curie)
+        self.assertIsNone(sample.class_curie_slot)
         print("class_uri_slot fails")
-        self.assertEqual(sample.class_uri_slot, m.HighClass.class_class_uri)
+        # self.assertEqual(sample.class_uri_slot, m.HighClass.class_class_uri)
+        self.assertIsNone(sample.class_uri_slot)
+        print(m.HighClass.class_class_uri)
         print("default_ns fails")
         self.assertEqual(sample.default_ns_slot, 'ex')
         print("default_range fails")
-        self.assertEqual(sample.default_range_slot, 'string')
+        # self.assertEqual(sample.default_range_slot, 'string')
+        self.assertIsNone(sample.default_range_slot)
         print("int(0) fails")
         self.assertEqual(sample.int_0_slot, 0)
         self.assertEqual(sample.int_42_slot, 42)
         self.assertEqual(sample.neg_int_slot, -117243)
         print("slot_curie fails")
-        self.assertEqual(sample.slot_curie_slot, m.slots.slot_curie_slot.curie)
+        # self.assertEqual(sample.slot_curie_slot, m.slots.slot_curie_slot.curie)
+        self.assertIsNone(sample.slot_curie_slot)
         print("slot_uri fails")
-        self.assertEqual(sample.slot_uri_slot, m.slots.slot_uri_slot.uri)
+        # self.assertEqual(sample.slot_uri_slot, m.slots.slot_uri_slot.uri)
+        self.assertIsNone(sample.slot_uri_slot)
+        self.assertIsNone(sample.slot_curie_slot)
         self.assertEqual(sample.string_slot, "s1")
         self.assertEqual(sample.mt_string_slot, "")