Repository: linkml/linkml

Issue:
pydanticgen doesn't support inlined dicts
It doesn't seem to be possible to get an inlined dictionary with a class range. 

Using this example schema:
```
classes:
  person:
    slots:
      - id
      - bikes
    attributes:
      - bikes:
          range: bike
          multivalued: true
          inlined: true
          required: true
  bike:
    slots:
      - name
      - color
    slot_usage:
      name:
        identifier: true
        required: true
slots:
  - id:
      required: true
  - name
  - color
  - bikes
```

With gen-typescript I'm getting: 

```
export interface Person  {
     id?: string,
    bikes?: {[index: BikeName]: Bike },
}
```

but gen-pydantic is producing:
```
class Person(ConfiguredBaseModel):    
    id: str = Field(None)
    bikes: List[Bike] = Field(default_factory=list)
```

I think I should get 

```
class Person(ConfiguredBaseModel):    
    id: str = Field(None)
    bikes: Dict[str, Bike] = Field(default_factory=dict)
```

Discussion:
codecov-commenter: # [Codecov](https://codecov.io/gh/linkml/linkml/pull/1095?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkml) Report
> Merging [#1095](https://codecov.io/gh/linkml/linkml/pull/1095?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkml) (29cd34a) into [main](https://codecov.io/gh/linkml/linkml/commit/75d346b41069786731ed5d913497b6913926ff4f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkml) (75d346b) will **decrease** coverage by `0.15%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##             main    #1095      +/-   ##
==========================================
- Coverage   78.29%   78.13%   -0.16%     
==========================================
  Files          74       74              
  Lines        8505     8513       +8     
  Branches     2071     2074       +3     
==========================================
- Hits         6659     6652       -7     
- Misses       1451     1459       +8     
- Partials      395      402       +7     
```


| [Impacted Files](https://codecov.io/gh/linkml/linkml/pull/1095?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkml) | Coverage Δ | |
|---|---|---|
| [linkml/generators/typescriptgen.py](https://codecov.io/gh/linkml/linkml/pull/1095/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkml#diff-bGlua21sL2dlbmVyYXRvcnMvdHlwZXNjcmlwdGdlbi5weQ==) | `90.24% <ø> (ø)` | |
| [linkml/generators/docgen.py](https://codecov.io/gh/linkml/linkml/pull/1095/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkml#diff-bGlua21sL2dlbmVyYXRvcnMvZG9jZ2VuLnB5) | `86.35% <100.00%> (+0.10%)` | :arrow_up: |
| [linkml/generators/pydanticgen.py](https://codecov.io/gh/linkml/linkml/pull/1095/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkml#diff-bGlua21sL2dlbmVyYXRvcnMvcHlkYW50aWNnZW4ucHk=) | `73.51% <100.00%> (-6.49%)` | :arrow_down: |
| [linkml/generators/jsonldgen.py](https://codecov.io/gh/linkml/linkml/pull/1095/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkml#diff-bGlua21sL2dlbmVyYXRvcnMvanNvbmxkZ2VuLnB5) | `90.51% <0.00%> (-1.73%)` | :arrow_down: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkml). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkml)


Patch:
diff --git a/linkml/generators/jsonschemagen.py b/linkml/generators/jsonschemagen.py
index 788219b1..1dd89da3 100644
--- a/linkml/generators/jsonschemagen.py
+++ b/linkml/generators/jsonschemagen.py
@@ -376,6 +376,18 @@ class JsonSchemaGenerator(Generator):
         if slot.equals_number is not None:
             prop['const'] = slot.equals_number
 
+        if slot.minimum_cardinality is not None:
+            if prop['type'] == 'array':
+                prop['minItems'] = slot.minimum_cardinality
+            elif prop['type'] == 'object':
+                prop['minProperties'] = slot.minimum_cardinality
+
+        if slot.maximum_cardinality is not None:
+            if prop['type'] == 'array':
+                prop['maxItems'] = slot.maximum_cardinality
+            elif prop['type'] == 'object':
+                prop['maxProperties'] = slot.maximum_cardinality
+
         if slot.any_of is not None and len(slot.any_of) > 0:
             if not slot_has_range_union:
                 prop['anyOf'] = [self.get_subschema_for_slot(s, omit_type=True) for s in slot.any_of]
diff --git a/linkml/generators/pydanticgen.py b/linkml/generators/pydanticgen.py
index 5aa04a87..49edb3d7 100644
--- a/linkml/generators/pydanticgen.py
+++ b/linkml/generators/pydanticgen.py
@@ -28,6 +28,7 @@ from datetime import datetime, date
 from enum import Enum
 from typing import List, Dict, Optional, Any
 from pydantic import BaseModel as BaseModel, Field
+from linkml_runtime.linkml_model import Decimal
 
 metamodel_version = "{{metamodel_version}}"
 version = "{{version if version else None}}"
@@ -98,7 +99,7 @@ class {{ c.name }}
 
 
 def _get_pyrange(t: TypeDefinition, sv: SchemaView) -> str:
-    pyrange = t.repr
+    pyrange = t.repr if t is not None else None
     if pyrange is None:
         pyrange = t.base
     if t.base == "XSDDateTime":
@@ -208,12 +209,17 @@ class PydanticGenerator(OOCodeGenerator):
                             + slot_values[camelcase(class_def.name)][slot.name]
                             + "]"
                         )
-                # Have a default factory of list for multivalued fields that don't
-                # get any other sort of predefined value above this point
+                # Multivalued slots that are either not inlined (just an identifier) or are
+                # inlined as lists should get default_factory list, if they're inlined but
+                # not as a list, that means a dictionary
                 elif slot.multivalued:
-                    slot_values[camelcase(class_def.name)][
-                        slot.name
-                    ] = "default_factory=list"
+                    if slot.inlined and not slot.inlined_as_list and sv.get_identifier_slot(slot.range) is not None:
+                        slot_values[camelcase(class_def.name)][slot.name] = "default_factory=dict"
+                    else:
+                        slot_values[camelcase(class_def.name)][slot.name] = "default_factory=list"
+
+
+
         return slot_values
 
     def get_class_isa_plus_mixins(self) -> Dict[str, List[str]]:
@@ -341,6 +347,9 @@ class PydanticGenerator(OOCodeGenerator):
                 collection_key = None
                 if s.range in sv.all_classes():
                     pyrange = self.get_class_slot_range(s)
+                    identifier_slot = sv.get_identifier_slot(s.range)
+                    if identifier_slot is not None and identifier_slot.range is not None:
+                        collection_key = _get_pyrange(sv.get_type(identifier_slot.range), sv)
                 elif s.range in sv.all_enums():
                     pyrange = f"{camelcase(s.range)}"
                 elif s.range in sv.all_types():
diff --git a/linkml/utils/converter.py b/linkml/utils/converter.py
index 4c13998f..76d33f38 100644
--- a/linkml/utils/converter.py
+++ b/linkml/utils/converter.py
@@ -160,6 +160,7 @@ def cli(
                 raise Exception("Must pass in context OR schema for RDF output")
         outargs["contexts"] = list(context)
         outargs["fmt"] = "json-ld"
+        outargs["schemaview"] = sv
     if output_format == "rdf" or output_format == "ttl":
         if sv is None:
             raise Exception(f"Must pass schema arg")


Test patch:
diff --git a/tests/test_generators/input/jsonschema_multivalued_slot_cardinality.yaml b/tests/test_generators/input/jsonschema_multivalued_slot_cardinality.yaml
new file mode 100644
index 00000000..944f755a
--- /dev/null
+++ b/tests/test_generators/input/jsonschema_multivalued_slot_cardinality.yaml
@@ -0,0 +1,98 @@
+schema:
+  id: http://example.org/test_multivalued_slot_constraints
+  name: test_multivalued_slot_constraints
+
+  imports:
+    - https://w3id.org/linkml/types
+
+  slots:
+    int_list:
+      range: integer
+      multivalued: true
+      minimum_cardinality: 2
+      maximum_cardinality: 5
+    
+    int_dict:
+      range: KeyedInt
+      multivalued: true
+      inlined: true
+      inlined_as_list: false
+      minimum_cardinality: 3
+      maximum_cardinality: 4
+
+    id:
+      range: string
+      identifier: true
+
+    value:
+      range: integer
+      required: true
+
+  classes:
+    KeyedInt:
+      slots:
+        - id
+        - value
+    Test:
+      tree_root: true
+      slots:
+        - int_list
+        - int_dict
+json_schema:
+  properties:
+    int_list:
+      minItems: 2
+      maxItems: 5
+    int_dict:
+      minProperties: 3
+      maxProperties: 4
+data_cases:
+  - data: 
+      int_list: [1, 2]
+  - data:
+      int_list: [1]
+    error_message: Failed validating 'minItems'
+  - data: 
+      int_list: [1, 2, 3, 4, 5]
+  - data:
+      int_list: [1, 2, 3, 4, 5, 6]
+    error_message: Failed validating 'maxItems'
+  - data:
+      int_dict: 
+        1:
+          value: 1
+        2:
+          value: 2
+        3:
+          value: 3
+  - data:
+      int_dict: 
+        1:
+          value: 1
+        2:
+          value: 2
+    error_message: Failed validating 'minProperties'
+  - data:
+      int_dict: 
+        1:
+          value: 1
+        2:
+          value: 2
+        3:
+          value: 3
+        4:
+          value: 4
+  - data:
+      int_dict: 
+        1:
+          value: 1
+        2:
+          value: 2
+        3:
+          value: 3
+        4:
+          value: 4
+        5:
+          value: 5
+    error_message: Failed validating 'maxProperties'
+        
\ No newline at end of file
diff --git a/tests/test_generators/input/jsonschema_range_union_cases.yaml b/tests/test_generators/input/jsonschema_range_union_cases.yaml
index e2734d5f..8df55dc8 100644
--- a/tests/test_generators/input/jsonschema_range_union_cases.yaml
+++ b/tests/test_generators/input/jsonschema_range_union_cases.yaml
@@ -88,6 +88,7 @@ schema:
       slots:
         - s2
     Test:
+      tree_root: true
       slots:
         - letter_enum_single_value
         - letter_enum_multi_value
@@ -103,74 +104,139 @@ schema:
         - c1_or_c2_multi_value
         - grab_bag
 
-valid_cases:
-  - letter_enum_single_value: "ALPHA"
-  - letter_enum_multi_value: ["BRAVO", "CHARLIE"]
-  - letter_or_number_enum_single_value: "ONE"
-  - letter_or_number_enum_multi_value: ["TWO", "DELTA"]
-  - integer_single_value: 0
-  - integer_multi_value: [0, 1]
-  - integer_or_string_single_value: "zero"
-  - integer_or_string_multi_value: ["zero", 1]
-  - c1_single_value:
-      s1: 'hi'
-  - c1_mutli_value:
-      - s1: 'hi'
-      - s1: 'hello'
-  - c1_or_c2_single_value:
-      s2: 'hi'
-  - c1_or_c2_multi_value:
-      - s1: 'hi'
-      - s2: 'hello'
-  - grab_bag:
-      - s1: 'two'
-      - ONE
-      - 0
-
-invalid_cases:
-  - letter_enum_single_value: "ONE"
-  - letter_enum_single_value: ["ALPHA", "BRAVO"]
-  - letter_enum_multi_value: "ALPHA"
-  - letter_enum_multi_value: ["ONE", "CHARLIE"]
-  - letter_or_number_enum_single_value: ["ONE"]
-  - letter_or_number_enum_single_value: "NOT_A_VALUE"
-  - letter_or_number_enum_multi_value: "ALPHA"
-  - letter_or_number_enum_multi_value: ["ALPHA", "THREE", "NOT_A_VALUE"]
-  - integer_single_value: [0]
-  - integer_single_value: "zero"
-  - integer_multi_value: 0
-  - integer_multi_value: ["zero"]
-  - integer_or_string_single_value: [0]
-  - integer_or_string_single_value: false
-  - integer_or_string_multi_value: "zero"
-  - integer_or_string_multi_value: [0, "one", false]
-  - c1_single_value:
-    - s1: 'hi'
-  - c1_single_value:
-      s2: 'hi'
-  - c1_mutli_value:
-      s2: 'hi'
-  - c1_mutli_value:
-    - s2: 'hi'
-  - c1_or_c2_single_value:
-    - s1: 'hi'
-  - c1_or_c2_single_value:
-      s3: 'hi'
-  - c1_or_c2_multi_value:
-      s1: 'hi'
-  - c1_or_c2_multi_value:
-    - s1: 'hi'
-    - s2: 'hello'
-    - s3: 'hola'
-  - grab_bag:
-    - s2: 'two'
-    - ONE
-    - 0
-  - grab_bag:
-    - s1: 'two'
-    - ONE
-    - false
-  - grab_bag:
-    - s1: 'two'
-    - five
-    - 5
\ No newline at end of file
+data_cases:
+  - data:
+      letter_enum_single_value: ALPHA
+  - data:
+      letter_enum_multi_value: [BRAVO, CHARLIE]
+  - data:
+      letter_or_number_enum_single_value: ONE
+  - data:
+      letter_or_number_enum_multi_value: [TWO, DELTA]
+  - data:
+      integer_single_value: 0
+  - data:
+      integer_multi_value: [0, 1]
+  - data:
+      integer_or_string_single_value: zero
+  - data:
+      integer_or_string_multi_value: [zero, 1]
+  - data:
+      c1_single_value:
+        s1: hi
+  - data:
+      c1_mutli_value:
+        - s1: hi
+        - s1: hello
+  - data:
+      c1_or_c2_single_value:
+        s2: hi
+  - data:
+      c1_or_c2_multi_value:
+        - s1: hi
+        - s2: hello
+  - data:
+      grab_bag:
+        - s1: two
+        - ONE
+        - 0
+  - data:
+      letter_enum_single_value: ONE
+    error_message: is not one of
+  - data:
+      letter_enum_single_value: [ALPHA, BRAVO]
+    error_message: is not one of
+  - data:
+      letter_enum_multi_value: ALPHA
+    error_message: is not of type 'array'
+  - data:
+      letter_enum_multi_value: [ONE, CHARLIE]
+    error_message: is not one of
+  - data:
+      letter_or_number_enum_single_value: [ONE]
+    error_message: Failed validating 'anyOf' in schema
+  - data:
+      letter_or_number_enum_single_value: NOT_A_VALUE
+    error_message: Failed validating 'anyOf' in schema
+  - data:
+      letter_or_number_enum_multi_value: ALPHA
+    error_message: is not of type 'array'
+  - data:
+      letter_or_number_enum_multi_value: [ALPHA, THREE, NOT_A_VALUE]
+    error_message: Failed validating 'anyOf' in schema
+  - data:
+      integer_single_value: [0]
+    error_message: is not of type 'integer'
+  - data:
+      integer_single_value: zero
+    error_message: is not of type 'integer'
+  - data:
+      integer_multi_value: 0
+    error_message: is not of type 'array'
+  - data:
+      integer_multi_value: [zero]
+    error_message: is not of type 'integer'
+  - data:
+      integer_or_string_single_value: [0]
+    error_message: Failed validating 'anyOf' in schema
+  - data:
+      integer_or_string_single_value: false
+    error_message: Failed validating 'anyOf' in schema
+  - data:
+      integer_or_string_multi_value: zero
+    error_message: is not of type 'array'
+  - data:
+      integer_or_string_multi_value: [0, one, false]
+    error_message: Failed validating 'anyOf' in schema
+  - data:
+      c1_single_value:
+        - s1: 'hi'
+    error_message: is not of type 'object'
+  - data:
+      c1_single_value:
+        s2: 'hi'
+    error_message: Additional properties are not allowed
+  - data:
+      c1_mutli_value:
+        s2: 'hi'
+    error_message: is not of type 'array'
+  - data:
+      c1_mutli_value:
+        - s2: 'hi'
+    error_message: Additional properties are not allowed
+  - data:
+      c1_or_c2_single_value:
+        - s1: 'hi'
+    error_message: Failed validating 'anyOf' in schema
+  - data:
+      c1_or_c2_single_value:
+        s3: 'hi'
+    error_message: Failed validating 'anyOf' in schema
+  - data:
+      c1_or_c2_multi_value:
+        s1: 'hi'
+    error_message: is not of type 'array'
+  - data:
+      c1_or_c2_multi_value:
+        - s1: 'hi'
+        - s2: 'hello'
+        - s3: 'hola'
+    error_message: Failed validating 'anyOf' in schema
+  - data:
+      grab_bag:
+        - s2: 'two'
+        - ONE
+        - 0
+    error_message: Failed validating 'anyOf' in schema
+  - data:
+      grab_bag:
+        - s1: 'two'
+        - ONE
+        - false
+    error_message: Failed validating 'anyOf' in schema
+  - data:
+      grab_bag:
+        - s1: 'two'
+        - five
+        - 5
+    error_message: is not one of
diff --git a/tests/test_generators/input/jsonschema_top_class_identifier.yaml b/tests/test_generators/input/jsonschema_top_class_identifier.yaml
new file mode 100644
index 00000000..41184168
--- /dev/null
+++ b/tests/test_generators/input/jsonschema_top_class_identifier.yaml
@@ -0,0 +1,16 @@
+schema:
+  id: http://example.org/test_top_class_identifier
+  name: test_top_class_identifier
+
+  slots:
+    id:
+      identifier: true
+
+  classes:
+    Test:
+      tree_root: true
+      slots:
+        - id
+json_schema:
+  required:
+    - id
diff --git a/tests/test_generators/input/jsonschema_type_inheritance.yaml b/tests/test_generators/input/jsonschema_type_inheritance.yaml
new file mode 100644
index 00000000..34e6cdee
--- /dev/null
+++ b/tests/test_generators/input/jsonschema_type_inheritance.yaml
@@ -0,0 +1,29 @@
+schema:
+  id: http://example.org/test_type_inheritance
+  name: test_type_inheritance
+
+  types:
+    alpha:
+      base: double 
+    beta:
+      typeof: alpha
+
+  slots:
+    alpha_slot:
+      range: alpha
+    beta_slot:
+      range: beta
+
+  classes:
+    Test:
+      tree_root: true
+      slots:
+        - alpha_slot
+        - beta_slot
+
+json_schema:
+  properties:
+    alpha_slot:
+      type: number
+    beta_slot:
+      type: number
\ No newline at end of file
diff --git a/tests/test_generators/test_docgen.py b/tests/test_generators/test_docgen.py
index 61ee364c..aaace21c 100644
--- a/tests/test_generators/test_docgen.py
+++ b/tests/test_generators/test_docgen.py
@@ -248,7 +248,7 @@ class DocGeneratorTestCase(unittest.TestCase):
             "[schema:Person](http://schema.org/Person)",
             after="## See Also"
         )
-         
+
         # test that Aliases is showing from common metadata
         assert_mdfile_contains(
             "EmploymentEventType.md", "* HR code", after="## Aliases"
diff --git a/tests/test_generators/test_jsonschemagen.py b/tests/test_generators/test_jsonschemagen.py
index afab82e3..88836970 100644
--- a/tests/test_generators/test_jsonschemagen.py
+++ b/tests/test_generators/test_jsonschemagen.py
@@ -7,6 +7,7 @@ import yaml
 from linkml_runtime.linkml_model import SchemaDefinition
 from linkml_runtime.dumpers import json_dumper
 from linkml_runtime.loaders import yaml_loader
+from typing import Union
 
 from linkml.generators.jsonschemagen import JsonSchemaGenerator
 from tests.test_generators.environment import env
@@ -23,19 +24,6 @@ class JsonSchemaTestCase(unittest.TestCase):
     Tests generation of JSON-Schema
     """
 
-    def setUp(self):
-        generator = JsonSchemaGenerator(
-            SCHEMA, 
-            mergeimports=True, 
-            top_class="Dataset", 
-            not_closed=False
-        )
-        self.kitchen_sink_json_schema = json.loads(generator.serialize())
-
-        generator.not_closed = True
-        self.kitchen_sink_json_schema_not_closed = json.loads(generator.serialize())
-
-
     def test_jsonschema_integration(self):
         """Integration test for JsonSchemaGenerator.
         
@@ -46,6 +34,14 @@ class JsonSchemaTestCase(unittest.TestCase):
         able to validate the instance data.
         """
 
+        generator = JsonSchemaGenerator(
+            SCHEMA, 
+            mergeimports=True, 
+            top_class="Dataset", 
+            not_closed=False
+        )
+        kitchen_sink_json_schema = json.loads(generator.serialize())
+
         kitchen_module = make_python(False)
         inst: Dataset
         inst = yaml_loader.load(DATA, target_class=kitchen_module.Dataset)
@@ -72,7 +68,7 @@ class JsonSchemaTestCase(unittest.TestCase):
         json_instance = json.loads(json_dumper.dumps(inst))
         del json_instance['@type']
 
-        jsonschema.validate(json_instance, self.kitchen_sink_json_schema)
+        jsonschema.validate(json_instance, kitchen_sink_json_schema)
         
 
     def test_class_uri_any(self):
@@ -82,8 +78,7 @@ class JsonSchemaTestCase(unittest.TestCase):
         See also https://github.com/linkml/linkml/issues/579
         """
 
-        self.assertIn("$defs", self.kitchen_sink_json_schema)
-        self.assertTrue(self.kitchen_sink_json_schema["$defs"]["AnyObject"]["additionalProperties"])
+        self.assertSchemaValidates(SCHEMA, { "$defs": { "AnyObject": { "additionalProperties": True } }})
 
 
     def test_compliance_cases(self):
@@ -94,6 +89,18 @@ class JsonSchemaTestCase(unittest.TestCase):
         validated against the kitchen sink schema. By default each instance is expected to *fail*
         validation, but cases can also be marked with valid: true if validation should pass.
         """
+
+        generator = JsonSchemaGenerator(
+            SCHEMA, 
+            mergeimports=True, 
+            top_class="Dataset", 
+            not_closed=False
+        )
+        kitchen_sink_json_schema = json.loads(generator.serialize())
+
+        generator.not_closed = True
+        kitchen_sink_json_schema_not_closed = json.loads(generator.serialize())
+
         with open(COMPLIANCE_CASES, "r") as io:
             cases = yaml.load(io, Loader=yaml.loader.SafeLoader)
 
@@ -107,9 +114,9 @@ class JsonSchemaTestCase(unittest.TestCase):
                 expected_valid = case.get("valid", False)
                 
                 if case.get("closed", True):
-                    schema = self.kitchen_sink_json_schema
+                    schema = kitchen_sink_json_schema
                 else:
-                    schema = self.kitchen_sink_json_schema_not_closed
+                    schema = kitchen_sink_json_schema_not_closed
                 
                 do_validate = lambda: jsonschema.validate(dataset, schema, format_checker=jsonschema.Draft7Validator.FORMAT_CHECKER)
                 if expected_valid:
@@ -120,57 +127,16 @@ class JsonSchemaTestCase(unittest.TestCase):
                     self.assertRaises(jsonschema.ValidationError, do_validate)
 
     def test_type_inheritance(self):
-        # TODO: can this be a compliance case too?
-        schema = """
-id: http://example.org/test_type_inheritance
-name: test_type_inheritance
-
-types:
-  alpha:
-    base: double 
-  beta:
-    typeof: alpha
-
-slots:
-  alpha_slot:
-    range: alpha
-  beta_slot:
-    range: beta
-
-classes:
-  Test:
-    slots:
-      - alpha_slot
-      - beta_slot
-"""
-        generator = JsonSchemaGenerator(schema, top_class="Test")
-        json_schema = json.loads(generator.serialize())
-        test_def_properties = json_schema['$defs']['Test']['properties']
-        self.assertEqual(test_def_properties['alpha_slot']['type'], 'number')
-        self.assertEqual(test_def_properties['beta_slot']['type'], 'number')
+        """Tests that a type definition's typeof slot is correctly accounted for."""
+
+        self.externalFileTest("jsonschema_type_inheritance.yaml")
+
 
     def test_top_class_identifier(self):
         """Test that an identifier slot on the top_class becomes a required
         property in the JSON Schema."""
 
-        schema = """
-id: http://example.org/test_top_class_identifier
-name: test_top_class_identifier
-
-slots:
-  id:
-    identifier: true
-
-classes:
-  Test:
-    slots:
-      - id
-"""
-        generator = JsonSchemaGenerator(schema, top_class="Test")
-        json_schema = json.loads(generator.serialize())
-
-        self.assertIn("id", json_schema["$defs"]["Test"]["required"])
-        self.assertIn("id", json_schema["required"])
+        self.externalFileTest("jsonschema_top_class_identifier.yaml")
 
 
     def test_value_constraints(self):
@@ -194,6 +160,19 @@ classes:
 
 
     def test_rules(self):
+        """Tests translation of various types of class rules.
+        
+        The external YAML file holds various test cases. Each test case defines
+        the `rules` that will be inserted into a class of the baseline schema, 
+        some expected JSON Schema, and various data instances. The test iterates
+        through each test case and:
+          1) constructs a LinkML schema and passes it to JsonSchemaGenerator
+          2) verifies that the expected JSON Schema is a subset of the actual
+             JSON Schema that was generated
+          3) validates each data instance against the JSON Schema and verifies
+             that it either successfully validates or does not validate with 
+             the expected error message
+        """
         with open(RULES_CASES) as cases_file:
             cases = yaml.safe_load(cases_file)
 
@@ -214,46 +193,83 @@ classes:
                     },
                     classes={
                         "Test": {
+                            "tree_root": True,
                             "slots": ["s1", "s2", "s3", "s4"],
                             "rules": case['linkml_rules']
                         }
                     }
                 )
-                generator = JsonSchemaGenerator(schema, top_class="Test")
-                actual_json_schema = json.loads(generator.serialize())
-
-                # Validate the structure of the generated JSON Schema
-                expected_json_schema_subset = case["json_schema"]
-                self.assertDictContainsSubset(expected_json_schema_subset, actual_json_schema)
-
-                # Validate data instances against the JSON Schema
-                for data_case in case.get('data_cases', []):
-                    data = data_case['data']
-                    with self.subTest(data=data):
-                        if 'error_message' in data_case:
-                            self.assertRaisesRegex(
-                                jsonschema.ValidationError, 
-                                data_case['error_message'],
-                                lambda: jsonschema.validate(data, actual_json_schema),
-                            )
-                        else:
-                            jsonschema.validate(data, actual_json_schema)
+
+                self.assertSchemaValidates(schema, case["json_schema"], case.get('data_cases', []))
 
 
     def test_range_unions(self):
-        with open(RANGE_UNION_CASES, "r") as f:
-            cases = yaml.safe_load(f)
+        """Tests various permutations of range unions.
+        
+        The external YAML files holds a complete LinkML schema and various data 
+        instances. The schema defines one class with numerous slots. Each slot
+        represents a combination of:
+          * simple range vs range union
+          * ranges of enums, types, classes
+          * multivalued true vs false
+        """
+
+        self.externalFileTest("jsonschema_range_union_cases.yaml")
+
+
+    def test_multivalued_slot_cardinality(self):
+        """Tests that cardinality constrains on multivalued slots are translated correctly."""
+
+        self.externalFileTest("jsonschema_multivalued_slot_cardinality.yaml")
+
+
+    # **********************************************************
+    #
+    #    Utility methods
+    # 
+    # **********************************************************
+
+
+    def externalFileTest(self, file: str) -> None:
+        with open(env.input_path(file)) as f:
+            test_definition = yaml.safe_load(f)
+
+        self.assertSchemaValidates(yaml.dump(test_definition["schema"]),
+                                   test_definition.get("json_schema", {}),
+                                   test_definition.get('data_cases', []))
 
-        generator = JsonSchemaGenerator(yaml.dump(cases["schema"]), top_class="Test")
+
+    def assertSchemaValidates(self, schema: Union[str, SchemaDefinition], expected_json_schema_subset = {}, data_cases = []):
+        generator = JsonSchemaGenerator(schema)
         json_schema = json.loads(generator.serialize())
 
-        for valid_case in cases.get("valid_cases", []):
-            with self.subTest(valid_case=valid_case):
-                jsonschema.validate(valid_case, json_schema)
+        self.assertDictSubset(expected_json_schema_subset, json_schema)
+
+        for data_case in data_cases:
+            data = data_case['data']
+            with self.subTest(data=data):
+                if 'error_message' in data_case:
+                    self.assertRaisesRegex(
+                        jsonschema.ValidationError, 
+                        data_case['error_message'],
+                        lambda: jsonschema.validate(data, json_schema),
+                    )
+                else:
+                    jsonschema.validate(data, json_schema)
+
+    def assertDictSubset(self, subset: dict, full: dict, path = ''):
+        for key in subset.keys():
+            self.assertIn(key, full, f"in path {path}")
+
+            new_path = f'{path}["{key}"]'
+
+            self.assertIsInstance(full[key], subset[key].__class__, f"in path {new_path}")
+
+            if isinstance(full[key], dict):
+                self.assertDictSubset(subset[key], full[key], new_path)
+            else:
+                self.assertEquals(full[key], subset[key], f"in path {new_path}")
 
-        for invalid_case in cases.get("invalid_cases", []):
-            with self.subTest(invalid_case=invalid_case):
-                self.assertRaises(jsonschema.ValidationError, lambda: jsonschema.validate(invalid_case, json_schema))
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/test_issues/test_linkml_issue_1094.py b/tests/test_issues/test_linkml_issue_1094.py
new file mode 100644
index 00000000..00cdf16e
--- /dev/null
+++ b/tests/test_issues/test_linkml_issue_1094.py
@@ -0,0 +1,59 @@
+import  unittest
+
+from linkml.generators.pydanticgen import PydanticGenerator
+from tests.test_issues.environment import env
+from tests.utils.test_environment import TestEnvironmentTestCase
+
+schema_str = """
+id: http://example.org
+name: inline-dict-test
+imports:
+  - https://w3id.org/linkml/types
+prefixes:
+  x: http://example.org/
+default_prefix: x
+default_range: string
+description: test
+
+classes:
+  person:
+    slots:
+      - id      
+      - has_bikes
+    slot_usage:
+      - has_bikes:
+  bike:
+    slots:
+      - name
+      - color
+    slot_usage:
+      name:
+        identifier: true
+        required: true
+slots:
+  - id:
+      required: true
+  - name
+  - color
+  - has_bikes:
+      range: bike
+      multivalued: true
+      inlined: true
+      required: true
+"""
+
+
+class Issue1094ConstCase(TestEnvironmentTestCase):
+    env = env
+
+    def test_pydanticgen_inline_dict(self):
+        gen = PydanticGenerator(schema_str)
+        output = gen.serialize()
+
+        output_subset = [line for line in output.splitlines() if "has_bikes: " in line]
+        assert len(output_subset) == 1
+        assert "has_bikes: Dict[str, Bike] = Field(default_factory=dict)" in output_subset[0]
+
+
+if __name__ == "__main__":
+    unittest.main()