linkml 1.8.4__tar.gz → 1.8.5__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 (160) hide show
  1. {linkml-1.8.4 → linkml-1.8.5}/PKG-INFO +1 -1
  2. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/common/ifabsent_processor.py +98 -21
  3. linkml-1.8.5/linkml/generators/common/naming.py +106 -0
  4. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/docgen.py +16 -7
  5. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/graphqlgen.py +34 -2
  6. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/pydanticgen/template.py +12 -1
  7. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/python/python_ifabsent_processor.py +1 -1
  8. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/pythongen.py +123 -21
  9. {linkml-1.8.4 → linkml-1.8.5}/pyproject.toml +5 -2
  10. {linkml-1.8.4 → linkml-1.8.5}/setup.py +1 -1
  11. {linkml-1.8.4 → linkml-1.8.5}/LICENSE +0 -0
  12. {linkml-1.8.4 → linkml-1.8.5}/README.md +0 -0
  13. {linkml-1.8.4 → linkml-1.8.5}/linkml/__init__.py +0 -0
  14. {linkml-1.8.4 → linkml-1.8.5}/linkml/_version.py +0 -0
  15. {linkml-1.8.4 → linkml-1.8.5}/linkml/cli/__init__.py +0 -0
  16. {linkml-1.8.4 → linkml-1.8.5}/linkml/cli/__main__.py +0 -0
  17. {linkml-1.8.4 → linkml-1.8.5}/linkml/cli/main.py +0 -0
  18. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/PythonGenNotes.md +0 -0
  19. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/README.md +0 -0
  20. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/__init__.py +0 -0
  21. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/common/__init__.py +0 -0
  22. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/common/build.py +0 -0
  23. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/common/lifecycle.py +0 -0
  24. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/common/template.py +0 -0
  25. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/common/type_designators.py +0 -0
  26. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/csvgen.py +0 -0
  27. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/docgen/class.md.jinja2 +0 -0
  28. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/docgen/class_diagram.md.jinja2 +0 -0
  29. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/docgen/common_metadata.md.jinja2 +0 -0
  30. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/docgen/enum.md.jinja2 +0 -0
  31. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/docgen/index.md.jinja2 +0 -0
  32. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/docgen/index.tex.jinja2 +0 -0
  33. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/docgen/schema.md.jinja2 +0 -0
  34. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/docgen/slot.md.jinja2 +0 -0
  35. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/docgen/subset.md.jinja2 +0 -0
  36. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/docgen/type.md.jinja2 +0 -0
  37. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/dotgen.py +0 -0
  38. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/erdiagramgen.py +0 -0
  39. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/excelgen.py +0 -0
  40. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/golanggen.py +0 -0
  41. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/golrgen.py +0 -0
  42. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/javagen/example_template.java.jinja2 +0 -0
  43. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/javagen/java_record_template.jinja2 +0 -0
  44. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/javagen.py +0 -0
  45. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/jsonldcontextgen.py +0 -0
  46. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/jsonldgen.py +0 -0
  47. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/jsonschemagen.py +0 -0
  48. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/legacy/__init__.py +0 -0
  49. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/linkmlgen.py +0 -0
  50. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/markdowngen.py +0 -0
  51. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/namespacegen.py +0 -0
  52. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/oocodegen.py +0 -0
  53. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/owlgen.py +0 -0
  54. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/plantumlgen.py +0 -0
  55. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/prefixmapgen.py +0 -0
  56. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/projectgen.py +0 -0
  57. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/protogen.py +0 -0
  58. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/pydanticgen/__init__.py +0 -0
  59. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/pydanticgen/array.py +0 -0
  60. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/pydanticgen/black.py +0 -0
  61. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/pydanticgen/build.py +0 -0
  62. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/pydanticgen/includes.py +0 -0
  63. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/pydanticgen/pydanticgen.py +0 -0
  64. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/pydanticgen/templates/attribute.py.jinja +0 -0
  65. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/pydanticgen/templates/base_model.py.jinja +0 -0
  66. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/pydanticgen/templates/class.py.jinja +0 -0
  67. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/pydanticgen/templates/conditional_import.py.jinja +0 -0
  68. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/pydanticgen/templates/enum.py.jinja +0 -0
  69. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/pydanticgen/templates/footer.py.jinja +0 -0
  70. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/pydanticgen/templates/imports.py.jinja +0 -0
  71. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/pydanticgen/templates/module.py.jinja +0 -0
  72. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/pydanticgen/templates/validator.py.jinja +0 -0
  73. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/python/__init__.py +0 -0
  74. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/rdfgen.py +0 -0
  75. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/shacl/__init__.py +0 -0
  76. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/shacl/shacl_data_type.py +0 -0
  77. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/shacl/shacl_ifabsent_processor.py +0 -0
  78. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/shaclgen.py +0 -0
  79. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/shexgen.py +0 -0
  80. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/sparqlgen.py +0 -0
  81. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/sqlalchemy/__init__.py +0 -0
  82. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/sqlalchemy/sqlalchemy_declarative_template.py +0 -0
  83. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/sqlalchemy/sqlalchemy_imperative_template.py +0 -0
  84. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/sqlalchemygen.py +0 -0
  85. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/sqltablegen.py +0 -0
  86. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/sssomgen.py +0 -0
  87. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/string_template.md +0 -0
  88. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/summarygen.py +0 -0
  89. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/terminusdbgen.py +0 -0
  90. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/typescriptgen.py +0 -0
  91. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/yamlgen.py +0 -0
  92. {linkml-1.8.4 → linkml-1.8.5}/linkml/generators/yumlgen.py +0 -0
  93. {linkml-1.8.4 → linkml-1.8.5}/linkml/linter/__init__.py +0 -0
  94. {linkml-1.8.4 → linkml-1.8.5}/linkml/linter/cli.py +0 -0
  95. {linkml-1.8.4 → linkml-1.8.5}/linkml/linter/config/datamodel/.linkmllint.yaml +0 -0
  96. {linkml-1.8.4 → linkml-1.8.5}/linkml/linter/config/datamodel/__init__.py +0 -0
  97. {linkml-1.8.4 → linkml-1.8.5}/linkml/linter/config/datamodel/config.py +0 -0
  98. {linkml-1.8.4 → linkml-1.8.5}/linkml/linter/config/datamodel/config.yaml +0 -0
  99. {linkml-1.8.4 → linkml-1.8.5}/linkml/linter/config/default.yaml +0 -0
  100. {linkml-1.8.4 → linkml-1.8.5}/linkml/linter/config/recommended.yaml +0 -0
  101. {linkml-1.8.4 → linkml-1.8.5}/linkml/linter/formatters/__init__.py +0 -0
  102. {linkml-1.8.4 → linkml-1.8.5}/linkml/linter/formatters/formatter.py +0 -0
  103. {linkml-1.8.4 → linkml-1.8.5}/linkml/linter/formatters/json_formatter.py +0 -0
  104. {linkml-1.8.4 → linkml-1.8.5}/linkml/linter/formatters/markdown_formatter.py +0 -0
  105. {linkml-1.8.4 → linkml-1.8.5}/linkml/linter/formatters/terminal_formatter.py +0 -0
  106. {linkml-1.8.4 → linkml-1.8.5}/linkml/linter/formatters/tsv_formatter.py +0 -0
  107. {linkml-1.8.4 → linkml-1.8.5}/linkml/linter/linter.py +0 -0
  108. {linkml-1.8.4 → linkml-1.8.5}/linkml/linter/rules.py +0 -0
  109. {linkml-1.8.4 → linkml-1.8.5}/linkml/reporting/__init__.py +0 -0
  110. {linkml-1.8.4 → linkml-1.8.5}/linkml/reporting/model.py +0 -0
  111. {linkml-1.8.4 → linkml-1.8.5}/linkml/transformers/__init__.py +0 -0
  112. {linkml-1.8.4 → linkml-1.8.5}/linkml/transformers/logical_model_transformer.py +0 -0
  113. {linkml-1.8.4 → linkml-1.8.5}/linkml/transformers/model_transformer.py +0 -0
  114. {linkml-1.8.4 → linkml-1.8.5}/linkml/transformers/relmodel_transformer.py +0 -0
  115. {linkml-1.8.4 → linkml-1.8.5}/linkml/transformers/schema_renamer.py +0 -0
  116. {linkml-1.8.4 → linkml-1.8.5}/linkml/utils/__init__.py +0 -0
  117. {linkml-1.8.4 → linkml-1.8.5}/linkml/utils/cli_utils.py +0 -0
  118. {linkml-1.8.4 → linkml-1.8.5}/linkml/utils/converter.py +0 -0
  119. {linkml-1.8.4 → linkml-1.8.5}/linkml/utils/datautils.py +0 -0
  120. {linkml-1.8.4 → linkml-1.8.5}/linkml/utils/datavalidator.py +0 -0
  121. {linkml-1.8.4 → linkml-1.8.5}/linkml/utils/deprecation.py +0 -0
  122. {linkml-1.8.4 → linkml-1.8.5}/linkml/utils/exceptions.py +0 -0
  123. {linkml-1.8.4 → linkml-1.8.5}/linkml/utils/execute_tutorial.py +0 -0
  124. {linkml-1.8.4 → linkml-1.8.5}/linkml/utils/generator.py +0 -0
  125. {linkml-1.8.4 → linkml-1.8.5}/linkml/utils/helpers.py +0 -0
  126. {linkml-1.8.4 → linkml-1.8.5}/linkml/utils/logictools.py +0 -0
  127. {linkml-1.8.4 → linkml-1.8.5}/linkml/utils/mergeutils.py +0 -0
  128. {linkml-1.8.4 → linkml-1.8.5}/linkml/utils/rawloader.py +0 -0
  129. {linkml-1.8.4 → linkml-1.8.5}/linkml/utils/schema_builder.py +0 -0
  130. {linkml-1.8.4 → linkml-1.8.5}/linkml/utils/schema_fixer.py +0 -0
  131. {linkml-1.8.4 → linkml-1.8.5}/linkml/utils/schemaloader.py +0 -0
  132. {linkml-1.8.4 → linkml-1.8.5}/linkml/utils/schemasynopsis.py +0 -0
  133. {linkml-1.8.4 → linkml-1.8.5}/linkml/utils/sqlutils.py +0 -0
  134. {linkml-1.8.4 → linkml-1.8.5}/linkml/utils/typereferences.py +0 -0
  135. {linkml-1.8.4 → linkml-1.8.5}/linkml/utils/validation.py +0 -0
  136. {linkml-1.8.4 → linkml-1.8.5}/linkml/validator/__init__.py +0 -0
  137. {linkml-1.8.4 → linkml-1.8.5}/linkml/validator/cli.py +0 -0
  138. {linkml-1.8.4 → linkml-1.8.5}/linkml/validator/loaders/__init__.py +0 -0
  139. {linkml-1.8.4 → linkml-1.8.5}/linkml/validator/loaders/delimited_file_loader.py +0 -0
  140. {linkml-1.8.4 → linkml-1.8.5}/linkml/validator/loaders/json_loader.py +0 -0
  141. {linkml-1.8.4 → linkml-1.8.5}/linkml/validator/loaders/loader.py +0 -0
  142. {linkml-1.8.4 → linkml-1.8.5}/linkml/validator/loaders/passthrough_loader.py +0 -0
  143. {linkml-1.8.4 → linkml-1.8.5}/linkml/validator/loaders/yaml_loader.py +0 -0
  144. {linkml-1.8.4 → linkml-1.8.5}/linkml/validator/plugins/__init__.py +0 -0
  145. {linkml-1.8.4 → linkml-1.8.5}/linkml/validator/plugins/jsonschema_validation_plugin.py +0 -0
  146. {linkml-1.8.4 → linkml-1.8.5}/linkml/validator/plugins/pydantic_validation_plugin.py +0 -0
  147. {linkml-1.8.4 → linkml-1.8.5}/linkml/validator/plugins/recommended_slots_plugin.py +0 -0
  148. {linkml-1.8.4 → linkml-1.8.5}/linkml/validator/plugins/shacl_validation_plugin.py +0 -0
  149. {linkml-1.8.4 → linkml-1.8.5}/linkml/validator/plugins/validation_plugin.py +0 -0
  150. {linkml-1.8.4 → linkml-1.8.5}/linkml/validator/report.py +0 -0
  151. {linkml-1.8.4 → linkml-1.8.5}/linkml/validator/validation_context.py +0 -0
  152. {linkml-1.8.4 → linkml-1.8.5}/linkml/validator/validator.py +0 -0
  153. {linkml-1.8.4 → linkml-1.8.5}/linkml/validators/__init__.py +0 -0
  154. {linkml-1.8.4 → linkml-1.8.5}/linkml/validators/jsonschemavalidator.py +0 -0
  155. {linkml-1.8.4 → linkml-1.8.5}/linkml/validators/sparqlvalidator.py +0 -0
  156. {linkml-1.8.4 → linkml-1.8.5}/linkml/workspaces/__init__.py +0 -0
  157. {linkml-1.8.4 → linkml-1.8.5}/linkml/workspaces/datamodel/__init__.py +0 -0
  158. {linkml-1.8.4 → linkml-1.8.5}/linkml/workspaces/datamodel/workspaces.py +0 -0
  159. {linkml-1.8.4 → linkml-1.8.5}/linkml/workspaces/datamodel/workspaces.yaml +0 -0
  160. {linkml-1.8.4 → linkml-1.8.5}/linkml/workspaces/example_runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: linkml
3
- Version: 1.8.4
3
+ Version: 1.8.5
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
@@ -1,7 +1,13 @@
1
1
  import abc
2
2
  import re
3
+ import sys
3
4
  from abc import ABC
4
- from typing import Any, Optional
5
+ from typing import Any, Optional, Type, Union
6
+
7
+ if sys.version_info < (3, 10):
8
+ from typing_extensions import TypeAlias
9
+ else:
10
+ from typing import TypeAlias
5
11
 
6
12
  from linkml_runtime import SchemaView
7
13
  from linkml_runtime.linkml_model import (
@@ -18,6 +24,7 @@ from linkml_runtime.linkml_model import (
18
24
  String,
19
25
  Time,
20
26
  Uri,
27
+ types,
21
28
  )
22
29
  from linkml_runtime.linkml_model.types import (
23
30
  Curie,
@@ -31,6 +38,34 @@ from linkml_runtime.linkml_model.types import (
31
38
  Uriorcurie,
32
39
  )
33
40
 
41
+ TYPES_TYPE: TypeAlias = Union[
42
+ Type[Boolean],
43
+ Type[Curie],
44
+ Type[Date],
45
+ Type[DateOrDatetime],
46
+ Type[Datetime],
47
+ Type[Decimal],
48
+ Type[Double],
49
+ Type[Float],
50
+ Type[Integer],
51
+ Type[Jsonpath],
52
+ Type[Jsonpointer],
53
+ Type[Ncname],
54
+ Type[Nodeidentifier],
55
+ Type[Objectidentifier],
56
+ Type[Sparqlpath],
57
+ Type[String],
58
+ Type[Time],
59
+ Type[Uri],
60
+ Type[Uriorcurie],
61
+ ]
62
+
63
+ TYPES = [
64
+ t
65
+ for t in types.__dict__.values()
66
+ if isinstance(t, type) and t.__module__ == types.__name__ and hasattr(t, "type_name")
67
+ ]
68
+
34
69
 
35
70
  class IfAbsentProcessor(ABC):
36
71
  """
@@ -39,7 +74,7 @@ class IfAbsentProcessor(ABC):
39
74
  See `<https://w3id.org/linkml/ifabsent>`_.
40
75
  """
41
76
 
42
- ifabsent_regex = re.compile("""(?:(?P<type>\w+)\()?[\"\']?(?P<default_value>[^\(\)\"\')]*)[\"\']?\)?""")
77
+ ifabsent_regex = re.compile(r"""(?:(?P<type>\w+)\()?[\"\']?(?P<default_value>[^\(\)\"\')]*)[\"\']?\)?""")
43
78
 
44
79
  def __init__(self, schema_view: SchemaView):
45
80
  self.schema_view = schema_view
@@ -61,10 +96,12 @@ class IfAbsentProcessor(ABC):
61
96
  if mapped:
62
97
  return custom_default_value
63
98
 
64
- if slot.range == String.type_name:
99
+ base_type = self._base_type(slot.range)
100
+
101
+ if base_type is String:
65
102
  return self.map_string_default_value(ifabsent_default_value, slot, cls)
66
103
 
67
- if slot.range == Boolean.type_name:
104
+ if base_type is Boolean:
68
105
  if re.match(r"^[Tt]rue$", ifabsent_default_value):
69
106
  return self.map_boolean_true_default_value(slot, cls)
70
107
  elif re.match(r"^[Ff]alse$", ifabsent_default_value):
@@ -75,19 +112,19 @@ class IfAbsentProcessor(ABC):
75
112
  f"value"
76
113
  )
77
114
 
78
- if slot.range == Integer.type_name:
115
+ if base_type is Integer:
79
116
  return self.map_integer_default_value(ifabsent_default_value, slot, cls)
80
117
 
81
- if slot.range == Float.type_name:
118
+ if base_type is Float:
82
119
  return self.map_float_default_value(ifabsent_default_value, slot, cls)
83
120
 
84
- if slot.range == Double.type_name:
121
+ if base_type is Double:
85
122
  return self.map_double_default_value(ifabsent_default_value, slot, cls)
86
123
 
87
- if slot.range == Decimal.type_name:
124
+ if base_type is Decimal:
88
125
  return self.map_decimal_default_value(ifabsent_default_value, slot, cls)
89
126
 
90
- if slot.range == Time.type_name:
127
+ if base_type is Time:
91
128
  match = re.match(r"^(\d{2}):(\d{2}):(\d{2}).*$", ifabsent_default_value)
92
129
  if match:
93
130
  return self.map_time_default_value(match[1], match[2], match[3], slot, cls)
@@ -97,7 +134,7 @@ class IfAbsentProcessor(ABC):
97
134
  )
98
135
 
99
136
  # TODO manage timezones and offsets
100
- if slot.range == Date.type_name:
137
+ if base_type is Date:
101
138
  match = re.match(r"^(\d{4})-(\d{2})-(\d{2})$", ifabsent_default_value)
102
139
  if match:
103
140
  return self.map_date_default_value(match[1], match[2], match[3], slot, cls)
@@ -107,7 +144,7 @@ class IfAbsentProcessor(ABC):
107
144
  )
108
145
 
109
146
  # TODO manage timezones and offsets
110
- if slot.range == Datetime.type_name:
147
+ if base_type is Datetime:
111
148
  match = re.match(r"^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}).*$", ifabsent_default_value)
112
149
  if match:
113
150
  return self.map_datetime_default_value(
@@ -120,7 +157,7 @@ class IfAbsentProcessor(ABC):
120
157
  )
121
158
 
122
159
  # TODO manage timezones and offsets
123
- if slot.range == DateOrDatetime.type_name:
160
+ if base_type is DateOrDatetime:
124
161
  match = re.match(r"^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}):(\d{2}))?.*$", ifabsent_default_value)
125
162
  if match and (match[4] is None or match[5] is None or match[6] is None):
126
163
  return self.map_date_default_value(match[1], match[2], match[3], slot, cls)
@@ -134,31 +171,31 @@ class IfAbsentProcessor(ABC):
134
171
  f"datetime value"
135
172
  )
136
173
 
137
- if slot.range == Uri.type_name:
174
+ if base_type is Uri:
138
175
  return self.map_uri_default_value(ifabsent_default_value, slot, cls)
139
176
 
140
- if slot.range == Curie.type_name:
177
+ if base_type is Curie:
141
178
  return self.map_curie_default_value(ifabsent_default_value, slot, cls)
142
179
 
143
- if slot.range == Uriorcurie.type_name:
180
+ if base_type is Uriorcurie:
144
181
  return self.map_uri_or_curie_default_value(ifabsent_default_value, slot, cls)
145
182
 
146
- if slot.range == Ncname.type_name:
183
+ if base_type is Ncname:
147
184
  return self.map_nc_name_default_value(ifabsent_default_value, slot, cls)
148
185
 
149
- if slot.range == Objectidentifier.type_name:
186
+ if base_type is Objectidentifier:
150
187
  return self.map_object_identifier_default_value(ifabsent_default_value, slot, cls)
151
188
 
152
- if slot.range == Nodeidentifier.type_name:
189
+ if base_type is Nodeidentifier:
153
190
  return self.map_node_identifier_default_value(ifabsent_default_value, slot, cls)
154
191
 
155
- if slot.range == Jsonpointer.type_name:
192
+ if base_type is Jsonpointer:
156
193
  return self.map_json_pointer_default_value(ifabsent_default_value, slot, cls)
157
194
 
158
- if slot.range == Jsonpath.type_name:
195
+ if base_type is Jsonpath:
159
196
  return self.map_json_path_default_value(ifabsent_default_value, slot, cls)
160
197
 
161
- if slot.range == Sparqlpath.type_name:
198
+ if base_type is Sparqlpath:
162
199
  return self.map_sparql_path_default_value(ifabsent_default_value, slot, cls)
163
200
 
164
201
  # -----------------------
@@ -173,6 +210,46 @@ class IfAbsentProcessor(ABC):
173
210
 
174
211
  raise ValueError(f"The ifabsent value `{slot.ifabsent}` of the `{slot.name}` slot could not be processed")
175
212
 
213
+ def _base_type(self, range_: str) -> Optional[TYPES_TYPE]:
214
+ """
215
+ Find the linkml base type that corresponds to either a matching type_name or custom type
216
+
217
+ First check for an explicit match of the range == TypeDefinition.type_name
218
+ Then check for explicit inheritance via typeof
219
+ Finally check for implicit matching via matching base
220
+
221
+ Don't raise here, just return None in case another method of resolution like enums are
222
+ available
223
+ """
224
+ # first check for matching type using type_name - ie. range is already a base type
225
+
226
+ for typ in TYPES:
227
+ if range_ == typ.type_name:
228
+ return typ
229
+
230
+ # if we're not a type, return None to indicate that, e.g. if an enum's permissible_value
231
+ if range_ not in self.schema_view.all_types(imports=True):
232
+ return
233
+
234
+ # then check explicit descendents of types
235
+ # base types do not inherit from one another, so the last ancestor is always a base type
236
+ # if it is inheriting from a base type
237
+ ancestor = self.schema_view.type_ancestors(range_)[-1]
238
+ for typ in TYPES:
239
+ if ancestor == typ.type_name:
240
+ return typ
241
+
242
+ # finally check if we have a matching base
243
+ induced_typ = self.schema_view.induced_type(range_)
244
+ if induced_typ.repr is None and induced_typ.base is None:
245
+ return None
246
+ for typ in TYPES:
247
+ # types always inherit from a single type, and that type is their base
248
+ # our range can match it with repr or base
249
+ typ_base = typ.__mro__[1].__name__
250
+ if typ_base == induced_typ.base:
251
+ return typ
252
+
176
253
  @abc.abstractmethod
177
254
  def map_custom_default_values(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition) -> (bool, str):
178
255
  """
@@ -0,0 +1,106 @@
1
+ import logging
2
+ import re
3
+ import unicodedata
4
+ from enum import Enum
5
+
6
+
7
+ class NamingProfiles(str, Enum):
8
+ # GraphQL naming profile ensures compatibility with the GraphQL specification
9
+ # WRT names: https://spec.graphql.org/October2021/#Name
10
+ graphql = "graphql"
11
+
12
+
13
+ class NameCompatibility(object):
14
+ """Make a name compatible to the given profile"""
15
+
16
+ # heading double underscores and digit reserved to names starting with a digit
17
+ re_reserved_heading_digit = re.compile("^__[0-9]")
18
+ # valid name between double underscores is reserved for unicode name transformations
19
+ re_reserved_unicode_name_transformation = re.compile("__[0-9a-zA-Z][0-9a-zA-Z_]*__")
20
+ # something like '__U_xxxx_' is reserved for unicode code transformations
21
+ re_reserved_unicode_code_transformation = re.compile("__U_[0-9a-eA-E]{4}_")
22
+ # strings starting with a digit
23
+ re_heading_digit = re.compile("^[0-9]")
24
+ # character that is neither alphanumeric nor underscore
25
+ re_no_alphanum_underscore = re.compile("[^0-9a-zA-Z_]")
26
+
27
+ def __init__(self, profile: NamingProfiles, do_not_fix: bool = False):
28
+ """Specify the naming policy on instantiation"""
29
+ self.profile = profile
30
+ self.do_not_fix = do_not_fix
31
+
32
+ def _no_heading_digits(self, input: str) -> str:
33
+ """Ensure name does not start with a heading digit"""
34
+ output = input
35
+ if self.re_heading_digit.match(input):
36
+ if self.do_not_fix:
37
+ raise ValueError(f"Name '{input}' starts with digit (illegal GraphQL) and will not be fixed!")
38
+ else:
39
+ logging.warning(
40
+ f"Name '{input}' starts with digit (illegal GraphQL), "
41
+ + f"it has been prefixed with '__', resulting in {output}"
42
+ )
43
+ output = f"__{input}"
44
+ return output
45
+
46
+ def _transform_char(self, char: str) -> str:
47
+ """Transform unsupported characters"""
48
+ if len(char) != 1:
49
+ raise Exception(f"Single character expected, but got '{char}'!!")
50
+ # replace whitespaces with underscores
51
+ # the transformation cannot be inverted, but is a well-established
52
+ # and expected transformation
53
+ if char == " ":
54
+ return "_"
55
+ # try to use names for ASCII characters
56
+ if ord(char) < 128:
57
+ try:
58
+ # unicodedata.lookup should be able to invert the transformation
59
+ return f"__{unicodedata.name(char).replace(' ', '_').replace('-', '_')}__"
60
+ except ValueError:
61
+ pass
62
+ # fallback to code-transformation if none of the previous has worked
63
+ return f"__U_{ord(char):04X}_"
64
+
65
+ def _only_alphanum_underscore(self, input: str) -> str:
66
+ """Ensure name does not contain any unsupported characters"""
67
+ output = input
68
+ # with re.split and re.findall we get in the same order and separated in two arrays
69
+ # the substrings between special characters and the special characters
70
+ no_alphanum_underscore_match = self.re_no_alphanum_underscore.findall(input)
71
+ if no_alphanum_underscore_match:
72
+ if self.do_not_fix:
73
+ raise ValueError(f"Name '{input}' contains a character illegal in GraphQL and will not be fixed!")
74
+ else:
75
+ logging.warning(
76
+ f"Name '{input}' contains a character illegal in GraphQL, "
77
+ + f"the resulting encoded name is {output}"
78
+ )
79
+ to_keep = self.re_no_alphanum_underscore.split(input)
80
+ # first comes first substring to keep
81
+ output = to_keep[0]
82
+ # each char replacement is followed by the next substring to keep
83
+ for offset in range(0, len(to_keep) - 1):
84
+ output = output + self._transform_char(no_alphanum_underscore_match[offset])
85
+ output = output + to_keep[offset + 1]
86
+ return output
87
+
88
+ def _graphql_compatibility(self, input: str) -> str:
89
+ """Ensure name compatibility with GraphQL name policies"""
90
+ # as of now, some (hopefully) very rare patterns are reserved to mark transformations
91
+ if self.re_reserved_heading_digit.match(input):
92
+ raise NotImplementedError("Names starting with a double underscore followed by a digit are not supported!!")
93
+ if self.re_reserved_unicode_name_transformation.match(input):
94
+ raise NotImplementedError("Names containing valid names between double underscores are not supported!!")
95
+ if self.re_reserved_unicode_code_transformation.match(input):
96
+ raise NotImplementedError("Names containing strings like '__U_xxxx_' are not supported!!")
97
+ # apply transformation
98
+ output = input
99
+ output = self._no_heading_digits(output)
100
+ output = self._only_alphanum_underscore(output)
101
+ return output
102
+
103
+ def compatible(self, input: str) -> str:
104
+ """Make given name compatible with the given naming policy."""
105
+ if self.profile == "graphql":
106
+ return self._graphql_compatibility(input)
@@ -164,6 +164,7 @@ class DocGenerator(Generator):
164
164
  use_slot_uris: bool = False
165
165
  use_class_uris: bool = False
166
166
  hierarchical_class_view: bool = False
167
+ render_imports: bool = False
167
168
 
168
169
  def __post_init__(self):
169
170
  dialect = self.dialect
@@ -759,7 +760,7 @@ class DocGenerator(Generator):
759
760
  Ensures rank is non-null
760
761
  :return: iterator
761
762
  """
762
- elts = self.schemaview.all_classes(imports=self.mergeimports).values()
763
+ elts = self.schemaview.all_classes(imports=self.render_imports).values()
763
764
  _ensure_ranked(elts)
764
765
  for e in elts:
765
766
  yield e
@@ -771,7 +772,7 @@ class DocGenerator(Generator):
771
772
  Ensures rank is non-null
772
773
  :return: iterator
773
774
  """
774
- elts = self.schemaview.all_slots(imports=self.mergeimports).values()
775
+ elts = self.schemaview.all_slots(imports=self.render_imports).values()
775
776
  _ensure_ranked(elts)
776
777
  for e in elts:
777
778
  yield e
@@ -783,7 +784,7 @@ class DocGenerator(Generator):
783
784
  Ensures rank is non-null
784
785
  :return: iterator
785
786
  """
786
- elts = self.schemaview.all_types(imports=self.mergeimports).values()
787
+ elts = self.schemaview.all_types(imports=self.render_imports).values()
787
788
  _ensure_ranked(elts)
788
789
  for e in elts:
789
790
  yield e
@@ -798,7 +799,7 @@ class DocGenerator(Generator):
798
799
  Ensures rank is non-null
799
800
  :return: iterator
800
801
  """
801
- elts = self.schemaview.all_enums(imports=self.mergeimports).values()
802
+ elts = self.schemaview.all_enums(imports=self.render_imports).values()
802
803
  _ensure_ranked(elts)
803
804
  for e in elts:
804
805
  yield e
@@ -810,7 +811,7 @@ class DocGenerator(Generator):
810
811
  Ensures rank is non-null
811
812
  :return: iterator
812
813
  """
813
- elts = self.schemaview.all_subsets(imports=self.mergeimports).values()
814
+ elts = self.schemaview.all_subsets(imports=self.render_imports).values()
814
815
  _ensure_ranked(elts)
815
816
  for e in elts:
816
817
  yield e
@@ -834,7 +835,7 @@ class DocGenerator(Generator):
834
835
  :return: tuples (depth: int, cls: ClassDefinitionName)
835
836
  """
836
837
  sv = self.schemaview
837
- roots = sv.class_roots(mixins=False, imports=self.mergeimports)
838
+ roots = sv.class_roots(mixins=False, imports=self.render_imports)
838
839
 
839
840
  # by default the classes are sorted alphabetically
840
841
  roots = sorted(roots, key=str.casefold, reverse=True)
@@ -848,7 +849,7 @@ class DocGenerator(Generator):
848
849
  depth, class_name = stack.pop()
849
850
  yield depth, class_name
850
851
  children = sorted(
851
- sv.class_children(class_name=class_name, mixins=False, imports=self.mergeimports),
852
+ sv.class_children(class_name=class_name, mixins=False, imports=self.render_imports),
852
853
  key=str.casefold,
853
854
  reverse=True,
854
855
  )
@@ -1008,6 +1009,12 @@ class DocGenerator(Generator):
1008
1009
  default=True,
1009
1010
  help="Render class table on index page in a hierarchically indented view",
1010
1011
  )
1012
+ @click.option(
1013
+ "--render-imports/--no-render-imports",
1014
+ default=False,
1015
+ show_default=True,
1016
+ help="Render also the documentation of elements from imported schemas",
1017
+ )
1011
1018
  @click.option(
1012
1019
  "--example-directory",
1013
1020
  help="Folder in which example files are found. These are used to make inline examples",
@@ -1037,6 +1044,7 @@ def cli(
1037
1044
  use_class_uris,
1038
1045
  hierarchical_class_view,
1039
1046
  subfolder_type_separation,
1047
+ render_imports,
1040
1048
  **args,
1041
1049
  ):
1042
1050
  """Generate documentation folder from a LinkML YAML schema
@@ -1068,6 +1076,7 @@ def cli(
1068
1076
  hierarchical_class_view=hierarchical_class_view,
1069
1077
  index_name=index_name,
1070
1078
  subfolder_type_separation=subfolder_type_separation,
1079
+ render_imports=render_imports,
1071
1080
  **args,
1072
1081
  )
1073
1082
  print(gen.serialize())
@@ -1,11 +1,14 @@
1
+ import logging
1
2
  import os
3
+ import re
2
4
  from dataclasses import dataclass
3
5
 
4
6
  import click
5
- from linkml_runtime.linkml_model.meta import ClassDefinition, SlotDefinition
7
+ from linkml_runtime.linkml_model.meta import ClassDefinition, EnumDefinition, SlotDefinition
6
8
  from linkml_runtime.utils.formatutils import camelcase, lcamelcase
7
9
 
8
10
  from linkml._version import __version__
11
+ from linkml.generators.common.naming import NameCompatibility, NamingProfiles
9
12
  from linkml.utils.generator import Generator, shared_arguments
10
13
 
11
14
 
@@ -19,6 +22,13 @@ class GraphqlGenerator(Generator):
19
22
  uses_schemaloader = True
20
23
  requires_metamodel = False
21
24
 
25
+ strict_naming: bool = False
26
+ _permissible_value_valid_characters = re.compile("^[_A-Za-z][_0-9A-Za-z]*?$")
27
+
28
+ def __post_init__(self):
29
+ self.name_compatiblity = NameCompatibility(profile=NamingProfiles.graphql, do_not_fix=self.strict_naming)
30
+ super().__post_init__()
31
+
22
32
  def visit_schema(self, **kwargs) -> str:
23
33
  return self.generate_header()
24
34
 
@@ -50,13 +60,35 @@ class GraphqlGenerator(Generator):
50
60
  slotrange = slotrange + "!"
51
61
  return f"\n {lcamelcase(aliased_slot_name)}: {slotrange}"
52
62
 
63
+ def visit_enum(self, enum: EnumDefinition):
64
+ if enum.permissible_values:
65
+ permissible_values = []
66
+ for value in enum.permissible_values:
67
+ permissible_values.append(self.name_compatiblity.compatible(value))
68
+ values = "\n ".join(permissible_values)
69
+ return f"enum {camelcase(enum.name).replace(' ','')}\n {{\n {values}\n }}\n\n"
70
+ else:
71
+ logging.warning(
72
+ f"Enumeration {enum.name} using `reachable_from` instead of `permissible_values` "
73
+ + "to specify permissible values is not supported yet."
74
+ + "Enumeration {enum.name} will be silently ignored!!"
75
+ )
76
+ return ""
77
+
53
78
 
54
79
  @shared_arguments(GraphqlGenerator)
55
80
  @click.command(name="graphql")
81
+ @click.option(
82
+ "--strict-naming",
83
+ is_flag=True,
84
+ show_default=True,
85
+ help="Treat warnings about invalid names or schema elements as errors.",
86
+ )
56
87
  @click.version_option(__version__, "-V", "--version")
57
88
  def cli(yamlfile, **args):
58
89
  """Generate graphql representation of a LinkML model"""
59
- print(GraphqlGenerator(yamlfile, **args).serialize(**args))
90
+ generator = GraphqlGenerator(yamlfile, **args)
91
+ print(generator.serialize(**args))
60
92
 
61
93
 
62
94
  if __name__ == "__main__":
@@ -675,7 +675,18 @@ class PydanticModule(PydanticTemplateModel):
675
675
  return [c.name for c in self.classes.values()]
676
676
 
677
677
 
678
- _some_stdlib_module_names = {"copy", "datetime", "decimal", "enum", "inspect", "os", "re", "sys", "typing"}
678
+ _some_stdlib_module_names = {
679
+ "copy",
680
+ "datetime",
681
+ "decimal",
682
+ "enum",
683
+ "inspect",
684
+ "os",
685
+ "re",
686
+ "sys",
687
+ "typing",
688
+ "dataclasses",
689
+ }
679
690
  """
680
691
  sys.stdlib_module_names is only present in 3.10 and later
681
692
  so we make a cheap copy of the stdlib modules that we commonly use here,
@@ -71,7 +71,7 @@ class PythonIfAbsentProcessor(IfAbsentProcessor):
71
71
  def map_enum_default_value(
72
72
  self, enum_name: EnumDefinitionName, permissible_value_name: str, slot: SlotDefinition, cls: ClassDefinition
73
73
  ):
74
- return f"{enum_name}.{permissible_value_name}"
74
+ return f"'{permissible_value_name}'"
75
75
 
76
76
  def map_nc_name_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
77
77
  raise NotImplementedError()