linkml 1.7.6__tar.gz → 1.7.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. {linkml-1.7.6 → linkml-1.7.8}/PKG-INFO +7 -4
  2. {linkml-1.7.6 → linkml-1.7.8}/linkml/__init__.py +14 -0
  3. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/docgen/slot.md.jinja2 +4 -3
  4. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/docgen/subset.md.jinja2 +7 -7
  5. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/jsonldcontextgen.py +32 -12
  6. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/owlgen.py +2 -0
  7. linkml-1.7.8/linkml/generators/pydanticgen/array.py +469 -0
  8. linkml-1.7.8/linkml/generators/pydanticgen/black.py +29 -0
  9. linkml-1.7.8/linkml/generators/pydanticgen/build.py +79 -0
  10. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/pydanticgen/pydanticgen.py +74 -17
  11. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/pydanticgen/template.py +89 -14
  12. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/pydanticgen/templates/base_model.py.jinja +3 -1
  13. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/pythongen.py +3 -0
  14. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/rdfgen.py +9 -1
  15. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/shacl/ifabsent_processor.py +22 -4
  16. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/shaclgen.py +16 -5
  17. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/sqlalchemygen.py +1 -0
  18. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/sqltablegen.py +16 -1
  19. {linkml-1.7.6 → linkml-1.7.8}/linkml/transformers/relmodel_transformer.py +21 -1
  20. {linkml-1.7.6 → linkml-1.7.8}/linkml/utils/generator.py +10 -6
  21. {linkml-1.7.6 → linkml-1.7.8}/linkml/utils/rawloader.py +5 -1
  22. {linkml-1.7.6 → linkml-1.7.8}/linkml/utils/schemaloader.py +2 -1
  23. {linkml-1.7.6 → linkml-1.7.8}/linkml/utils/sqlutils.py +13 -14
  24. {linkml-1.7.6 → linkml-1.7.8}/linkml/validator/plugins/shacl_validation_plugin.py +10 -4
  25. {linkml-1.7.6 → linkml-1.7.8}/pyproject.toml +30 -14
  26. {linkml-1.7.6 → linkml-1.7.8}/setup.py +6 -5
  27. {linkml-1.7.6 → linkml-1.7.8}/LICENSE +0 -0
  28. {linkml-1.7.6 → linkml-1.7.8}/README.md +0 -0
  29. {linkml-1.7.6 → linkml-1.7.8}/linkml/_version.py +0 -0
  30. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/PythonGenNotes.md +0 -0
  31. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/README.md +0 -0
  32. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/__init__.py +0 -0
  33. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/common/__init__.py +0 -0
  34. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/common/type_designators.py +0 -0
  35. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/csvgen.py +0 -0
  36. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/docgen/class.md.jinja2 +0 -0
  37. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/docgen/class_diagram.md.jinja2 +0 -0
  38. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/docgen/common_metadata.md.jinja2 +0 -0
  39. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/docgen/enum.md.jinja2 +0 -0
  40. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/docgen/index.md.jinja2 +0 -0
  41. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/docgen/index.tex.jinja2 +0 -0
  42. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/docgen/schema.md.jinja2 +0 -0
  43. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/docgen/type.md.jinja2 +0 -0
  44. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/docgen.py +0 -0
  45. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/dotgen.py +0 -0
  46. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/erdiagramgen.py +0 -0
  47. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/excelgen.py +0 -0
  48. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/golanggen.py +0 -0
  49. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/golrgen.py +0 -0
  50. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/graphqlgen.py +0 -0
  51. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/javagen/example_template.java.jinja2 +0 -0
  52. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/javagen/java_record_template.jinja2 +0 -0
  53. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/javagen.py +0 -0
  54. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/jsonldgen.py +1 -1
  55. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/jsonschemagen.py +0 -0
  56. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/legacy/__init__.py +0 -0
  57. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/linkmlgen.py +0 -0
  58. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/markdowngen.py +0 -0
  59. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/namespacegen.py +0 -0
  60. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/oocodegen.py +0 -0
  61. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/plantumlgen.py +0 -0
  62. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/prefixmapgen.py +0 -0
  63. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/projectgen.py +0 -0
  64. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/protogen.py +0 -0
  65. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/pydanticgen/__init__.py +0 -0
  66. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/pydanticgen/templates/attribute.py.jinja +0 -0
  67. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/pydanticgen/templates/class.py.jinja +0 -0
  68. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/pydanticgen/templates/conditional_import.py.jinja +0 -0
  69. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/pydanticgen/templates/enum.py.jinja +0 -0
  70. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/pydanticgen/templates/footer.py.jinja +0 -0
  71. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/pydanticgen/templates/imports.py.jinja +0 -0
  72. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/pydanticgen/templates/module.py.jinja +0 -0
  73. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/pydanticgen/templates/validator.py.jinja +0 -0
  74. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/shacl/__init__.py +0 -0
  75. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/shacl/shacl_data_type.py +0 -0
  76. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/shexgen.py +0 -0
  77. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/sparqlgen.py +0 -0
  78. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/sqlalchemy/__init__.py +0 -0
  79. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/sqlalchemy/sqlalchemy_declarative_template.py +0 -0
  80. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/sqlalchemy/sqlalchemy_imperative_template.py +0 -0
  81. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/sssomgen.py +0 -0
  82. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/string_template.md +0 -0
  83. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/summarygen.py +0 -0
  84. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/terminusdbgen.py +0 -0
  85. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/typescriptgen.py +0 -0
  86. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/yamlgen.py +0 -0
  87. {linkml-1.7.6 → linkml-1.7.8}/linkml/generators/yumlgen.py +0 -0
  88. {linkml-1.7.6 → linkml-1.7.8}/linkml/linter/__init__.py +0 -0
  89. {linkml-1.7.6 → linkml-1.7.8}/linkml/linter/cli.py +0 -0
  90. {linkml-1.7.6 → linkml-1.7.8}/linkml/linter/config/datamodel/.linkmllint.yaml +0 -0
  91. {linkml-1.7.6 → linkml-1.7.8}/linkml/linter/config/datamodel/__init__.py +0 -0
  92. {linkml-1.7.6 → linkml-1.7.8}/linkml/linter/config/datamodel/config.py +0 -0
  93. {linkml-1.7.6 → linkml-1.7.8}/linkml/linter/config/datamodel/config.yaml +0 -0
  94. {linkml-1.7.6 → linkml-1.7.8}/linkml/linter/config/default.yaml +0 -0
  95. {linkml-1.7.6 → linkml-1.7.8}/linkml/linter/config/recommended.yaml +0 -0
  96. {linkml-1.7.6 → linkml-1.7.8}/linkml/linter/formatters/__init__.py +0 -0
  97. {linkml-1.7.6 → linkml-1.7.8}/linkml/linter/formatters/formatter.py +0 -0
  98. {linkml-1.7.6 → linkml-1.7.8}/linkml/linter/formatters/json_formatter.py +0 -0
  99. {linkml-1.7.6 → linkml-1.7.8}/linkml/linter/formatters/markdown_formatter.py +0 -0
  100. {linkml-1.7.6 → linkml-1.7.8}/linkml/linter/formatters/terminal_formatter.py +0 -0
  101. {linkml-1.7.6 → linkml-1.7.8}/linkml/linter/formatters/tsv_formatter.py +0 -0
  102. {linkml-1.7.6 → linkml-1.7.8}/linkml/linter/linter.py +0 -0
  103. {linkml-1.7.6 → linkml-1.7.8}/linkml/linter/rules.py +0 -0
  104. {linkml-1.7.6 → linkml-1.7.8}/linkml/reporting/__init__.py +0 -0
  105. {linkml-1.7.6 → linkml-1.7.8}/linkml/reporting/model.py +0 -0
  106. {linkml-1.7.6 → linkml-1.7.8}/linkml/transformers/__init__.py +0 -0
  107. {linkml-1.7.6 → linkml-1.7.8}/linkml/transformers/logical_model_transformer.py +0 -0
  108. {linkml-1.7.6 → linkml-1.7.8}/linkml/transformers/model_transformer.py +0 -0
  109. {linkml-1.7.6 → linkml-1.7.8}/linkml/transformers/schema_renamer.py +0 -0
  110. {linkml-1.7.6 → linkml-1.7.8}/linkml/utils/__init__.py +0 -0
  111. {linkml-1.7.6 → linkml-1.7.8}/linkml/utils/cli_utils.py +0 -0
  112. {linkml-1.7.6 → linkml-1.7.8}/linkml/utils/converter.py +0 -0
  113. {linkml-1.7.6 → linkml-1.7.8}/linkml/utils/datautils.py +0 -0
  114. {linkml-1.7.6 → linkml-1.7.8}/linkml/utils/datavalidator.py +0 -0
  115. {linkml-1.7.6 → linkml-1.7.8}/linkml/utils/execute_tutorial.py +0 -0
  116. {linkml-1.7.6 → linkml-1.7.8}/linkml/utils/helpers.py +0 -0
  117. {linkml-1.7.6 → linkml-1.7.8}/linkml/utils/ifabsent_functions.py +0 -0
  118. {linkml-1.7.6 → linkml-1.7.8}/linkml/utils/logictools.py +0 -0
  119. {linkml-1.7.6 → linkml-1.7.8}/linkml/utils/mergeutils.py +0 -0
  120. {linkml-1.7.6 → linkml-1.7.8}/linkml/utils/schema_builder.py +0 -0
  121. {linkml-1.7.6 → linkml-1.7.8}/linkml/utils/schema_fixer.py +0 -0
  122. {linkml-1.7.6 → linkml-1.7.8}/linkml/utils/schemasynopsis.py +0 -0
  123. {linkml-1.7.6 → linkml-1.7.8}/linkml/utils/typereferences.py +0 -0
  124. {linkml-1.7.6 → linkml-1.7.8}/linkml/utils/validation.py +0 -0
  125. {linkml-1.7.6 → linkml-1.7.8}/linkml/validator/__init__.py +0 -0
  126. {linkml-1.7.6 → linkml-1.7.8}/linkml/validator/cli.py +0 -0
  127. {linkml-1.7.6 → linkml-1.7.8}/linkml/validator/loaders/__init__.py +0 -0
  128. {linkml-1.7.6 → linkml-1.7.8}/linkml/validator/loaders/delimited_file_loader.py +0 -0
  129. {linkml-1.7.6 → linkml-1.7.8}/linkml/validator/loaders/json_loader.py +0 -0
  130. {linkml-1.7.6 → linkml-1.7.8}/linkml/validator/loaders/loader.py +0 -0
  131. {linkml-1.7.6 → linkml-1.7.8}/linkml/validator/loaders/passthrough_loader.py +0 -0
  132. {linkml-1.7.6 → linkml-1.7.8}/linkml/validator/loaders/yaml_loader.py +0 -0
  133. {linkml-1.7.6 → linkml-1.7.8}/linkml/validator/plugins/__init__.py +0 -0
  134. {linkml-1.7.6 → linkml-1.7.8}/linkml/validator/plugins/jsonschema_validation_plugin.py +0 -0
  135. {linkml-1.7.6 → linkml-1.7.8}/linkml/validator/plugins/pydantic_validation_plugin.py +0 -0
  136. {linkml-1.7.6 → linkml-1.7.8}/linkml/validator/plugins/recommended_slots_plugin.py +0 -0
  137. {linkml-1.7.6 → linkml-1.7.8}/linkml/validator/plugins/validation_plugin.py +0 -0
  138. {linkml-1.7.6 → linkml-1.7.8}/linkml/validator/report.py +0 -0
  139. {linkml-1.7.6 → linkml-1.7.8}/linkml/validator/validation_context.py +0 -0
  140. {linkml-1.7.6 → linkml-1.7.8}/linkml/validator/validator.py +0 -0
  141. {linkml-1.7.6 → linkml-1.7.8}/linkml/validators/__init__.py +0 -0
  142. {linkml-1.7.6 → linkml-1.7.8}/linkml/validators/jsonschemavalidator.py +0 -0
  143. {linkml-1.7.6 → linkml-1.7.8}/linkml/validators/sparqlvalidator.py +0 -0
  144. {linkml-1.7.6 → linkml-1.7.8}/linkml/workspaces/__init__.py +0 -0
  145. {linkml-1.7.6 → linkml-1.7.8}/linkml/workspaces/datamodel/__init__.py +0 -0
  146. {linkml-1.7.6 → linkml-1.7.8}/linkml/workspaces/datamodel/workspaces.py +0 -0
  147. {linkml-1.7.6 → linkml-1.7.8}/linkml/workspaces/datamodel/workspaces.yaml +0 -0
  148. {linkml-1.7.6 → linkml-1.7.8}/linkml/workspaces/example_runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: linkml
3
- Version: 1.7.6
3
+ Version: 1.7.8
4
4
  Summary: Linked Open Data Modeling Language
5
5
  Home-page: https://linkml.io/linkml/
6
6
  Keywords: schema,linked data,data modeling,rdf,owl,biolink
@@ -19,12 +19,15 @@ Classifier: Programming Language :: Python :: 3.10
19
19
  Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.10
21
21
  Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
22
23
  Classifier: Programming Language :: Python :: 3.8
23
24
  Classifier: Programming Language :: Python :: 3.9
24
25
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Provides-Extra: black
25
27
  Provides-Extra: shacl
26
28
  Provides-Extra: tests
27
29
  Requires-Dist: antlr4-python3-runtime (>=4.9.0,<4.10)
30
+ Requires-Dist: black (>=24.0.0) ; extra == "black" or extra == "tests"
28
31
  Requires-Dist: click (>=7.0)
29
32
  Requires-Dist: graphviz (>=0.10.1)
30
33
  Requires-Dist: hbreader
@@ -33,11 +36,11 @@ Requires-Dist: jinja2 (>=3.1.0)
33
36
  Requires-Dist: jsonasobj2 (>=1.0.3,<2.0.0)
34
37
  Requires-Dist: jsonschema[format] (>=4.0.0)
35
38
  Requires-Dist: linkml-dataops
36
- Requires-Dist: linkml-runtime (>=1.7.0)
39
+ Requires-Dist: linkml-runtime (>=1.7.4)
37
40
  Requires-Dist: openpyxl
38
41
  Requires-Dist: parse
39
42
  Requires-Dist: prefixcommons (>=0.1.7)
40
- Requires-Dist: prefixmaps (>=0.1.3)
43
+ Requires-Dist: prefixmaps (>=0.2.2)
41
44
  Requires-Dist: pydantic (>=1.0.0,<3.0.0)
42
45
  Requires-Dist: pyjsg (>=0.11.6)
43
46
  Requires-Dist: pyshacl (>=0.25.0,<0.26.0) ; extra == "shacl" or extra == "tests"
@@ -48,7 +51,7 @@ Requires-Dist: pyyaml
48
51
  Requires-Dist: rdflib (>=6.0.0)
49
52
  Requires-Dist: requests (>=2.22)
50
53
  Requires-Dist: sqlalchemy (>=1.4.31)
51
- Requires-Dist: typing-extensions (>=4.5.0,<5.0.0) ; python_version == "3.7"
54
+ Requires-Dist: typing-extensions (>=4.4.0) ; python_version < "3.9"
52
55
  Requires-Dist: watchdog (>=0.9.0)
53
56
  Project-URL: Documentation, https://linkml.io/linkml/
54
57
  Project-URL: Repository, https://github.com/linkml/linkml
@@ -19,6 +19,13 @@ LOCAL_TYPES_YAML_FILE = linkml_files.LOCAL_PATH_FOR(Source.TYPES, Format.YAML)
19
19
  LOCAL_MAPPINGS_YAML_FILE = linkml_files.LOCAL_PATH_FOR(Source.MAPPINGS, Format.YAML)
20
20
  LOCAL_ANNOTATIONS_YAML_FILE = linkml_files.LOCAL_PATH_FOR(Source.ANNOTATIONS, Format.YAML)
21
21
  LOCAL_EXTENSIONS_YAML_FILE = linkml_files.LOCAL_PATH_FOR(Source.EXTENSIONS, Format.YAML)
22
+ LOCAL_MODEL_YAML_FILES = (
23
+ LOCAL_METAMODEL_YAML_FILE,
24
+ LOCAL_TYPES_YAML_FILE,
25
+ LOCAL_MAPPINGS_YAML_FILE,
26
+ LOCAL_ANNOTATIONS_YAML_FILE,
27
+ LOCAL_EXTENSIONS_YAML_FILE,
28
+ )
22
29
 
23
30
  # Local location of jsonld and context.jsonld files
24
31
  LOCAL_METAMODEL_LDCONTEXT_FILE = linkml_files.LOCAL_PATH_FOR(Source.META, Format.JSONLD)
@@ -55,6 +62,13 @@ METATYPE_NAMESPACE = METAMODEL_NAMESPACE
55
62
  METAMAPPING_NAMESPACE = METAMODEL_NAMESPACE
56
63
  METAANNOTATIONS_NAMESPACE = METAMODEL_NAMESPACE
57
64
  METAEXTENSIONS_NAMESPACE = METAMODEL_NAMESPACE
65
+ NAMESPACES = (
66
+ METAMODEL_NAMESPACE,
67
+ METATYPE_NAMESPACE,
68
+ METAMAPPING_NAMESPACE,
69
+ METAANNOTATIONS_NAMESPACE,
70
+ METAEXTENSIONS_NAMESPACE,
71
+ )
58
72
 
59
73
  # Metamodel Context URI
60
74
  METAMODEL_CONTEXT_URI = linkml_files.URL_FOR(Source.META, Format.JSONLD)
@@ -46,14 +46,15 @@ URI: {{ gen.uri_link(element) }}
46
46
  <!-- no inheritance hierarchy -->
47
47
  {% endif %}
48
48
 
49
- {% if schemaview.get_classes_by_slot(element, include_induced=True) %}
49
+ {% set classes_by_slot = schemaview.get_classes_by_slot(element, include_induced=True) %}
50
+ {% if classes_by_slot %}
50
51
 
51
52
  ## Applicable Classes
52
53
 
53
54
  | Name | Description | Modifies Slot |
54
55
  | --- | --- | --- |
55
- {% for c in schemaview.get_classes_by_slot(element, include_induced=True) -%}
56
- {{ gen.link(c) }} | {{ schemaview.get_class(c).description|enshorten }} | {% if c in schemaview.get_classes_modifying_slot(element) %} yes {% else %} no {% endif %} |
56
+ {% for c in classes_by_slot -%}
57
+ | {{ gen.link(c) }} | {{ schemaview.get_class(c).description|enshorten }} | {% if c in schemaview.get_classes_modifying_slot(element) %} yes {% else %} no {% endif %} |
57
58
  {% endfor %}
58
59
 
59
60
  {% endif %}
@@ -43,20 +43,18 @@ URI: {{ gen.link(element) }}
43
43
 
44
44
  | Class | Description |
45
45
  | --- | --- |
46
- {% for c in gen.all_class_objects()|sort(attribute=sort_by) -%}
46
+ {% for c in classes_in_subset -%}
47
47
  {%- if element.name in c.in_subset -%}
48
48
  | {{gen.link(c)}} | {{c.description|enshorten}} |
49
49
  {% endif -%}
50
50
  {% endfor %}
51
51
 
52
- {% for c in gen.all_class_objects()|sort(attribute=sort_by) -%}
52
+ {% for c in classes_in_subset -%}
53
53
  {%- if element.name in c.in_subset -%}
54
- ### {{ gen.name(c) }}
55
54
 
56
- {{c.description}}
55
+ {% set induced_slots = gen.class_induced_slots(c.name)|sort(attribute=sort_by) %}
57
56
 
58
57
  {%- set filtered_slots = [] -%}
59
-
60
58
  {%- for s in induced_slots|sort(attribute=sort_by) -%}
61
59
  {%- if element.name in s.in_subset or element.name in schemaview.get_slot(s.name).in_subset -%}
62
60
  {% set _ = filtered_slots.append(s) %}
@@ -64,10 +62,12 @@ URI: {{ gen.link(element) }}
64
62
  {%- endfor %}
65
63
 
66
64
  {%- if filtered_slots|length > 0 -%}
67
- | Name | Cardinality and Range | Description |
65
+ ### Slots from {{ gen.link(c) }} also in _{{ element.name }}_
66
+
67
+ | Name | Cardinality and Range | Description |
68
68
  | --- | --- | --- |
69
69
  {% for s in filtered_slots -%}
70
- | {{gen.link(s)}} | {{ gen.cardinality(s) }} <br/> {{gen.link(s.range)}} | {{s.description|enshorten}} {% if s.identifier %}**identifier**{% endif %} |
70
+ | {{gen.link(s)}} | {{ gen.cardinality(s) }} <br/> {{gen.link(s.range)}} | {{s.description|enshorten}} {% if s.identifier %}**identifier**{% endif %} |
71
71
  {% endfor %}
72
72
  {%- endif %}
73
73
 
@@ -6,7 +6,7 @@ Generate JSON-LD contexts
6
6
  import os
7
7
  import re
8
8
  from dataclasses import dataclass, field
9
- from typing import Dict, Optional, Set, Union
9
+ from typing import Any, Dict, Optional, Set, Union
10
10
 
11
11
  import click
12
12
  from jsonasobj2 import JsonObj, as_json
@@ -133,16 +133,13 @@ class ContextGenerator(Generator):
133
133
  class_def = {}
134
134
  cn = camelcase(cls.name)
135
135
  self.add_mappings(cls)
136
- cls_uri_prefix, cls_uri_suffix = self.namespaces.prefix_suffix(cls.class_uri)
137
- if not self.default_ns or not cls_uri_prefix or cls_uri_prefix != self.default_ns:
138
- class_def["@id"] = (cls_uri_prefix + ":" + cls_uri_suffix) if cls_uri_prefix else cls.class_uri
139
- if cls_uri_prefix:
140
- self.add_prefix(cls_uri_prefix)
136
+
137
+ self._build_element_id(class_def, cls.class_uri)
141
138
  if class_def:
142
139
  self.slot_class_maps[cn] = class_def
143
140
 
144
141
  # We don't bother to visit class slots - just all slots
145
- return False
142
+ return True
146
143
 
147
144
  def visit_slot(self, aliased_slot_name: str, slot: SlotDefinition) -> None:
148
145
  if slot.identifier:
@@ -169,15 +166,38 @@ class ContextGenerator(Generator):
169
166
  slot_def["@type"] = "@id"
170
167
  else:
171
168
  slot_def["@type"] = range_type.uri
172
- slot_prefix = self.namespaces.prefix_for(slot.slot_uri)
173
- if not self.default_ns or not slot_prefix or slot_prefix != self.default_ns:
174
- slot_def["@id"] = slot.slot_uri
175
- if slot_prefix:
176
- self.add_prefix(slot_prefix)
169
+
170
+ self._build_element_id(slot_def, slot.slot_uri)
177
171
  self.add_mappings(slot)
178
172
  if slot_def:
179
173
  self.context_body[underscore(aliased_slot_name)] = slot_def
180
174
 
175
+ def _build_element_id(self, definition: Any, uri: str) -> None:
176
+ """
177
+ Defines the elements @id attribute according to the default namespace prefix of the schema.
178
+
179
+ The @id namespace prefix is added only if it doesn't correspond to the default schema namespace prefix
180
+ whether it is in URI format or as an alias.
181
+
182
+ @param definition: the element (class or slot) definition
183
+ @param uri: the uri of the element (class or slot)
184
+ @return: None
185
+ """
186
+ uri_prefix, uri_suffix = self.namespaces.prefix_suffix(uri)
187
+ is_default_namespace = uri_prefix == self.context_body["@vocab"] or uri_prefix == self.namespaces.prefix_for(
188
+ self.context_body["@vocab"]
189
+ )
190
+
191
+ if not uri_prefix and not uri_suffix:
192
+ definition["@id"] = uri
193
+ elif not uri_prefix or is_default_namespace:
194
+ definition["@id"] = uri_suffix
195
+ else:
196
+ definition["@id"] = (uri_prefix + ":" + uri_suffix) if uri_prefix else uri
197
+
198
+ if uri_prefix and not is_default_namespace:
199
+ self.add_prefix(uri_prefix)
200
+
181
201
  def serialize(self, base: Optional[Union[str, Namespace]] = None, **kwargs) -> str:
182
202
  return super().serialize(base=base, **kwargs)
183
203
 
@@ -274,6 +274,8 @@ class OwlSchemaGenerator(Generator):
274
274
  self.graph.add((uri, metaslot_uri, obj))
275
275
 
276
276
  for k, v in e.annotations.items():
277
+ if isinstance(v, dict) or isinstance(v, list):
278
+ continue
277
279
  if ":" not in k:
278
280
  default_prefix = this_sv.schema.default_prefix
279
281
  if default_prefix in this_sv.schema.prefixes:
@@ -0,0 +1,469 @@
1
+ import sys
2
+ from abc import ABC, abstractmethod
3
+ from enum import Enum
4
+ from typing import (
5
+ Any,
6
+ ClassVar,
7
+ Generic,
8
+ Iterable,
9
+ List,
10
+ Optional,
11
+ Type,
12
+ TypeVar,
13
+ Union,
14
+ get_args,
15
+ )
16
+
17
+ from linkml_runtime.linkml_model import Element
18
+ from linkml_runtime.linkml_model.meta import ArrayExpression, DimensionExpression
19
+ from pydantic import VERSION as PYDANTIC_VERSION
20
+
21
+ if int(PYDANTIC_VERSION[0]) < 2:
22
+ pass
23
+ else:
24
+ from pydantic import GetCoreSchemaHandler
25
+ from pydantic_core import CoreSchema, core_schema
26
+
27
+ if sys.version_info.minor <= 8:
28
+ from typing_extensions import Annotated
29
+ else:
30
+ from typing import Annotated
31
+
32
+ from linkml.generators.pydanticgen.build import SlotResult
33
+ from linkml.generators.pydanticgen.template import ConditionalImport, Import, Imports, ObjectImport
34
+
35
+
36
+ class ArrayRepresentation(Enum):
37
+ LIST = "list"
38
+ NPARRAY = "nparray" # numpy and nptyping must be installed to use this
39
+
40
+
41
+ _BOUNDED_ARRAY_FIELDS = ("exact_number_dimensions", "minimum_number_dimensions", "maximum_number_dimensions")
42
+
43
+ _T = TypeVar("_T")
44
+ _RecursiveListType = Iterable[Union[_T, Iterable["_RecursiveListType"]]]
45
+ if int(PYDANTIC_VERSION[0]) >= 2:
46
+
47
+ class AnyShapeArrayType(Generic[_T]):
48
+ @classmethod
49
+ def __get_pydantic_core_schema__(cls, source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema:
50
+ # double-nested parameterized types here
51
+ # source_type: List[Union[T,List[...]]]
52
+ item_type = Any if get_args(get_args(source_type)[0])[0] is _T else get_args(get_args(source_type)[0])[0]
53
+
54
+ item_schema = handler.generate_schema(item_type)
55
+ if item_schema.get("type", "any") != "any":
56
+ item_schema["strict"] = True
57
+
58
+ if item_type is Any:
59
+ # Before python 3.11, `Any` type was a special object without a __name__
60
+ item_name = "Any"
61
+ else:
62
+ item_name = item_type.__name__
63
+
64
+ array_ref = f"any-shape-array-{item_name}"
65
+
66
+ schema = core_schema.definitions_schema(
67
+ core_schema.list_schema(core_schema.definition_reference_schema(array_ref)),
68
+ [
69
+ core_schema.union_schema(
70
+ [
71
+ core_schema.list_schema(core_schema.definition_reference_schema(array_ref)),
72
+ item_schema,
73
+ ],
74
+ ref=array_ref,
75
+ )
76
+ ],
77
+ )
78
+
79
+ return schema
80
+
81
+ AnyShapeArray = Annotated[_RecursiveListType, AnyShapeArrayType]
82
+
83
+ _AnyShapeArrayImports = (
84
+ Imports()
85
+ + Import(
86
+ module="typing",
87
+ objects=[
88
+ ObjectImport(name="Generic"),
89
+ ObjectImport(name="Iterable"),
90
+ ObjectImport(name="TypeVar"),
91
+ ObjectImport(name="Union"),
92
+ ObjectImport(name="get_args"),
93
+ ],
94
+ )
95
+ + ConditionalImport(
96
+ condition="sys.version_info.minor > 8",
97
+ module="typing",
98
+ objects=[ObjectImport(name="Annotated")],
99
+ alternative=Import(module="typing_extensions", objects=[ObjectImport(name="Annotated")]),
100
+ )
101
+ + Import(module="pydantic", objects=[ObjectImport(name="GetCoreSchemaHandler")])
102
+ + Import(module="pydantic_core", objects=[ObjectImport(name="CoreSchema"), ObjectImport(name="core_schema")])
103
+ )
104
+
105
+ # annotated types are special and inspect.getsource() can't stringify them
106
+ _AnyShapeArrayInjects = [
107
+ '_T = TypeVar("_T")',
108
+ '_RecursiveListType = Iterable[Union[_T, Iterable["_RecursiveListType"]]]',
109
+ AnyShapeArrayType,
110
+ "AnyShapeArray = Annotated[_RecursiveListType, AnyShapeArrayType]",
111
+ ]
112
+
113
+ else:
114
+
115
+ class AnyShapeArray(Generic[_T]):
116
+ type_: Type[Any] = Any
117
+
118
+ def __class_getitem__(cls, item):
119
+ alias = type(f"AnyShape_{str(item.__name__)}", (AnyShapeArray,), {"type_": item})
120
+ alias.type_ = item
121
+ return alias
122
+
123
+ @classmethod
124
+ def __get_validators__(cls):
125
+ yield cls.validate
126
+
127
+ @classmethod
128
+ def __modify_schema__(cls, field_schema):
129
+ try:
130
+ item_type = field_schema["allOf"][0]["type"]
131
+ type_schema = {"type": item_type}
132
+ del field_schema["allOf"]
133
+ except KeyError as e:
134
+ if "allOf" in str(e):
135
+ item_type = "Any"
136
+ type_schema = {}
137
+ else:
138
+ raise e
139
+
140
+ array_id = f"#any-shape-array-{item_type}"
141
+ field_schema["anyOf"] = [
142
+ type_schema,
143
+ {"type": "array", "items": {"$ref": array_id}},
144
+ ]
145
+ field_schema["$id"] = array_id
146
+
147
+ @classmethod
148
+ def validate(cls, v: Union[List[_T], list]):
149
+ if str(type(v)) == "<class 'numpy.ndarray'>":
150
+ v = v.tolist()
151
+
152
+ if not isinstance(v, list):
153
+ raise TypeError(f"Must be a list of lists! got {v}")
154
+
155
+ def _validate(_v: Union[List[_T], list]):
156
+ for item in _v:
157
+ if isinstance(item, list):
158
+ _validate(item)
159
+ else:
160
+ try:
161
+ anytype = cls.type_.__name__ in ("AnyType", "Any")
162
+ except AttributeError:
163
+ # in python 3.8 and 3.9, `typing.Any` has no __name__
164
+ anytype = str(cls.type_).split(".")[-1] in ("AnyType", "Any")
165
+
166
+ if not anytype and not isinstance(item, cls.type_):
167
+ raise TypeError(
168
+ (
169
+ f"List items must be list of lists, or the type used in "
170
+ f"the subscript ({cls.type_}. Got item {item} and outer value {v}"
171
+ )
172
+ )
173
+ return _v
174
+
175
+ return _validate(v)
176
+
177
+ _AnyShapeArrayImports = Imports() + Import(
178
+ module="typing",
179
+ objects=[ObjectImport(name="Generic"), ObjectImport(name="TypeVar"), ObjectImport(name="_GenericAlias")],
180
+ )
181
+ _AnyShapeArrayInjects = [
182
+ '_T = TypeVar("_T")',
183
+ AnyShapeArray,
184
+ ]
185
+
186
+ _ConListImports = Imports() + Import(module="pydantic", objects=[ObjectImport(name="conlist")])
187
+
188
+
189
+ class ArrayRangeGenerator(ABC):
190
+ """
191
+ Metaclass for generating a given format of array annotation.
192
+
193
+ See :ref:`array-forms` for more details on array range forms.
194
+
195
+ These classes do only enough validation of the array specification to decide
196
+ which kind of representation to generate. Proper value validation should
197
+ happen elsewhere (ie. in the metamodel and generated :class:`.ArrayExpression` class.)
198
+
199
+ Each of the array representation generation methods should be able to handle
200
+ the supported pydantic versions (currently still 1 and 2).
201
+
202
+ Notes:
203
+
204
+ When checking for array specification, recall that there is a semantic difference between
205
+ ``None`` and ``False`` , particularly for :attr:`.ArrayExpression.max_number_dimensions` -
206
+ check for absence of specification with ``is None`` rather than checking for truthiness/falsiness
207
+ (unless that's what you intend to do ofc ;)
208
+
209
+ Attributes:
210
+ array (:class:`.ArrayExpression` ): Array to create an annotation for
211
+ dtype (Union[str, :class:`.Element` ): dtype of the entire array as a string
212
+ pydantic_ver (str): Pydantic version to generate array form for -
213
+ currently only pydantic 1 and 2 are differentiated, and pydantic 1 will be deprecated soon.
214
+
215
+ """
216
+
217
+ REPR: ClassVar[ArrayRepresentation]
218
+
219
+ def __init__(
220
+ self, array: Optional[ArrayExpression], dtype: Union[str, Element], pydantic_ver: str = PYDANTIC_VERSION
221
+ ):
222
+ self.array = array
223
+ self.dtype = dtype
224
+ self.pydantic_ver = pydantic_ver
225
+
226
+ def make(self) -> SlotResult:
227
+ """Create the string form of the array representation"""
228
+ if not self.array.dimensions and not self.has_bounded_dimensions:
229
+ # any-shaped array
230
+ return self.any_shape(self.array)
231
+ elif not self.array.dimensions and self.has_bounded_dimensions:
232
+ return self.bounded_dimensions(self.array)
233
+ elif self.array.dimensions and not self.has_bounded_dimensions:
234
+ return self.parameterized_dimensions(self.array)
235
+ else:
236
+ return self.complex_dimensions(self.array)
237
+
238
+ @property
239
+ def has_bounded_dimensions(self) -> bool:
240
+ """Whether the :class:`.ArrayExpression` has some shape specification aside from ``dimensions``"""
241
+ return any([getattr(self.array, arr_field, None) is not None for arr_field in _BOUNDED_ARRAY_FIELDS])
242
+
243
+ @classmethod
244
+ def get_generator(cls, repr: ArrayRepresentation) -> Type["ArrayRangeGenerator"]:
245
+ """Get the generator class for a given array representation"""
246
+ for subclass in cls.__subclasses__():
247
+ if repr in (subclass.REPR, subclass.REPR.value):
248
+ return subclass
249
+ raise ValueError(f"Generator for array representation {repr} not found!")
250
+
251
+ @abstractmethod
252
+ def any_shape(self, array: Optional[ArrayRepresentation] = None) -> SlotResult:
253
+ """Any shaped array!"""
254
+ pass
255
+
256
+ @abstractmethod
257
+ def bounded_dimensions(self, array: ArrayExpression) -> SlotResult:
258
+ """Array shape specified numerically, without axis parameterization"""
259
+ pass
260
+
261
+ @abstractmethod
262
+ def parameterized_dimensions(self, array: ArrayExpression) -> SlotResult:
263
+ """Array shape specified with ``dimensions`` without additional parameterized dimensions"""
264
+ pass
265
+
266
+ @abstractmethod
267
+ def complex_dimensions(self, array: ArrayExpression) -> SlotResult:
268
+ """Array shape with both ``parameterized`` and ``bounded`` dimensions"""
269
+ pass
270
+
271
+
272
+ class ListOfListsArray(ArrayRangeGenerator):
273
+ """
274
+ Represent arrays as lists of lists!
275
+
276
+ TODO: Move all validation of values (eg. anywhere we raise a ValueError) to the ArrayExpression
277
+ dataclass and out of the generator class
278
+ """
279
+
280
+ REPR = ArrayRepresentation.LIST
281
+
282
+ @staticmethod
283
+ def _list_of_lists(dimensions: int, dtype: str) -> str:
284
+ return ("List[" * dimensions) + dtype + ("]" * dimensions)
285
+
286
+ @staticmethod
287
+ def _parameterized_dimension(dimension: DimensionExpression, dtype: str) -> SlotResult:
288
+ # TODO: Preserve label representation in some readable way! doing the MVP now of using conlist
289
+ if dimension.exact_cardinality and (dimension.minimum_cardinality or dimension.maximum_cardinality):
290
+ raise ValueError("Can only specify EITHER exact_cardinality OR minimum/maximum cardinality")
291
+ elif dimension.exact_cardinality:
292
+ dmin = dimension.exact_cardinality
293
+ dmax = dimension.exact_cardinality
294
+ elif dimension.minimum_cardinality or dimension.maximum_cardinality:
295
+ dmin = dimension.minimum_cardinality
296
+ dmax = dimension.maximum_cardinality
297
+ else:
298
+ # TODO: handle labels for labeled but unshaped arrays
299
+ return SlotResult(annotation="List[" + dtype + "]")
300
+
301
+ items = []
302
+ if int(PYDANTIC_VERSION[0]) >= 2:
303
+ if dmin is not None:
304
+ items.append(f"min_length={dmin}")
305
+ if dmax is not None:
306
+ items.append(f"max_length={dmax}")
307
+ else:
308
+ if dmin is not None:
309
+ items.append(f"min_items={dmin}")
310
+ if dmax is not None:
311
+ items.append(f"max_items={dmax}")
312
+ items.append(f"item_type={dtype}")
313
+ items = ", ".join(items)
314
+ annotation = f"conlist({items})"
315
+
316
+ return SlotResult(annotation=annotation, imports=_ConListImports)
317
+
318
+ def any_shape(self, array: Optional[ArrayExpression] = None, with_inner_union: bool = False) -> SlotResult:
319
+ """
320
+ An AnyShaped array (using :class:`.AnyShapeArray` )
321
+
322
+ Args:
323
+ array (:class:`.ArrayExpression`): The array expression (not used)
324
+ with_inner_union (bool): If ``True`` , the innermost type is a ``Union`` of the ``AnyShapeArray`` class
325
+ and ``dtype`` (default: ``False`` )
326
+
327
+ """
328
+ if self.dtype in ("Any", "AnyType"):
329
+ annotation = "AnyShapeArray"
330
+ else:
331
+ annotation = f"AnyShapeArray[{self.dtype}]"
332
+
333
+ if with_inner_union:
334
+ annotation = f"Union[{annotation}, {self.dtype}]"
335
+ return SlotResult(annotation=annotation, injected_classes=_AnyShapeArrayInjects, imports=_AnyShapeArrayImports)
336
+
337
+ def bounded_dimensions(self, array: ArrayExpression) -> SlotResult:
338
+ """
339
+ A nested series of ``List[]`` annotations with :attr:`.dtype` at the center.
340
+
341
+ When an array expression allows for a range of dimensions, each set of ``List`` s is joined by a ``Union`` .
342
+ """
343
+ if array.exact_number_dimensions or (
344
+ array.minimum_number_dimensions
345
+ and array.maximum_number_dimensions
346
+ and array.minimum_number_dimensions == array.maximum_number_dimensions
347
+ ):
348
+ exact_dims = array.exact_number_dimensions or array.minimum_number_dimensions
349
+ return SlotResult(annotation=self._list_of_lists(exact_dims, self.dtype))
350
+ elif not array.maximum_number_dimensions and (
351
+ array.minimum_number_dimensions is None or array.minimum_number_dimensions == 1
352
+ ):
353
+ return self.any_shape()
354
+ elif array.maximum_number_dimensions:
355
+ # e.g., if min = 2, max = 3, annotation = Union[List[List[dtype]], List[List[List[dtype]]]]
356
+ min_dims = array.minimum_number_dimensions if array.minimum_number_dimensions is not None else 1
357
+ annotations = [
358
+ self._list_of_lists(i, self.dtype) for i in range(min_dims, array.maximum_number_dimensions + 1)
359
+ ]
360
+ # TODO: Format this nicely!
361
+ return SlotResult(annotation="Union[" + ", ".join(annotations) + "]")
362
+ else:
363
+ # min specified with no max
364
+ # e.g., if min = 3, annotation = List[List[AnyShapeArray[dtype]]]
365
+ return SlotResult(
366
+ annotation=self._list_of_lists(array.minimum_number_dimensions - 1, self.any_shape().annotation),
367
+ injected_classes=_AnyShapeArrayInjects,
368
+ imports=_AnyShapeArrayImports,
369
+ )
370
+
371
+ def parameterized_dimensions(self, array: ArrayExpression) -> SlotResult:
372
+ """
373
+ Constrained shapes using :func:`pydantic.conlist`
374
+
375
+ TODO:
376
+ - preservation of aliases
377
+ - (what other metadata is allowable on labeled dimensions?)
378
+ """
379
+ # generate dimensions from inside out and then format
380
+ # e.g., if dimensions = [{min_card: 3}, {min_card: 2}],
381
+ # annotation = conlist(min_length=3, item_type=conlist(min_length=2, item_type=dtype))
382
+ range = self.dtype
383
+ for dimension in reversed(array.dimensions):
384
+ range = self._parameterized_dimension(dimension, range).annotation
385
+
386
+ return SlotResult(annotation=range, imports=_ConListImports)
387
+
388
+ def complex_dimensions(self, array: ArrayExpression) -> SlotResult:
389
+ """
390
+ Mixture of parameterized dimensions with a max or min (or both) shape for anonymous dimensions.
391
+
392
+ A mixture of ``List`` , :class:`.conlist` , and :class:`.AnyShapeArray` .
393
+ """
394
+ # first process any unlabeled dimensions which must be the innermost level of the annotation,
395
+ # then wrap that with labeled dimensions
396
+ if array.exact_number_dimensions or (
397
+ array.minimum_number_dimensions
398
+ and array.maximum_number_dimensions
399
+ and array.minimum_number_dimensions == array.maximum_number_dimensions
400
+ ):
401
+ exact_dims = array.exact_number_dimensions or array.minimum_number_dimensions
402
+ if exact_dims > len(array.dimensions):
403
+ res = SlotResult(annotation=self._list_of_lists(exact_dims - len(array.dimensions), self.dtype))
404
+ elif exact_dims == len(array.dimensions):
405
+ # equivalent to labeled shape
406
+ return self.parameterized_dimensions(array)
407
+ else:
408
+ raise ValueError(
409
+ "if exact_number_dimensions is provided, it must be greater than the parameterized dimensions"
410
+ )
411
+
412
+ elif array.maximum_number_dimensions is not None and not array.maximum_number_dimensions:
413
+ # unlimited n dimensions, so innermost is AnyShape with dtype
414
+ res = self.any_shape(with_inner_union=True)
415
+
416
+ if array.minimum_number_dimensions and array.minimum_number_dimensions > len(array.dimensions):
417
+ # some minimum anonymous dimensions but unlimited max dimensions
418
+ # e.g., if min = 3, len(dim) = 2, then res.annotation = List[Union[AnyShapeArray[dtype], dtype]]
419
+ # res.annotation will be wrapped with the 2 labeled dimensions later
420
+ res.annotation = self._list_of_lists(
421
+ array.minimum_number_dimensions - len(array.dimensions), res.annotation
422
+ )
423
+
424
+ elif array.minimum_number_dimensions and array.maximum_number_dimensions is None:
425
+ raise ValueError(
426
+ (
427
+ "Cannot specify a minimum_number_dimensions while maximum is None while using labeled dimensions - "
428
+ "either use exact_number_dimensions > len(dimensions) for extra parameterized dimensions or set "
429
+ "maximum_number_dimensions explicitly to False for unbounded dimensions"
430
+ )
431
+ )
432
+ elif array.maximum_number_dimensions:
433
+ initial_min = array.minimum_number_dimensions if array.minimum_number_dimensions is not None else 0
434
+ dmin = max(len(array.dimensions), initial_min) - len(array.dimensions)
435
+ dmax = array.maximum_number_dimensions - len(array.dimensions)
436
+
437
+ res = self.bounded_dimensions(
438
+ ArrayExpression(minimum_number_dimensions=dmin, maximum_number_dimensions=dmax)
439
+ )
440
+ else:
441
+ raise ValueError("Unsupported array specification! this is almost certainly a bug!") # pragma: no cover
442
+
443
+ # Wrap inner dimension with labeled dimension
444
+ # e.g., if dimensions = [{min_card: 3}, {min_card: 2}]
445
+ # and res.annotation = List[Union[AnyShapeArray[dtype], dtype]]
446
+ # (min 3 dims, no max dims)
447
+ # then the final annotation = conlist(
448
+ # min_length=3,
449
+ # item_type=conlist(
450
+ # min_length=2,
451
+ # item_type=List[Union[AnyShapeArray[dtype], dtype]]
452
+ # )
453
+ # )
454
+ for dim in reversed(array.dimensions):
455
+ res = res.merge(self._parameterized_dimension(dim, dtype=res.annotation))
456
+
457
+ return res
458
+
459
+
460
+ class NPTypingArray(ArrayRangeGenerator):
461
+ """
462
+ Represent array range with nptyping, and serialization/loading with an ArrayProxy
463
+ """
464
+
465
+ REPR = ArrayRepresentation.NPARRAY
466
+
467
+ def __init__(self, **kwargs):
468
+ super(self).__init__(**kwargs)
469
+ raise NotImplementedError("NPTyping array ranges are not implemented yet :(")