Source code for linkml.generators.graphqlgen

import logging
import os
import re
from dataclasses import dataclass

import click
from linkml_runtime.linkml_model.meta import ClassDefinition, EnumDefinition, SlotDefinition
from linkml_runtime.utils.formatutils import camelcase, lcamelcase

from linkml._version import __version__
from linkml.generators.common.naming import NameCompatibility, NamingProfiles
from linkml.utils.generator import Generator, shared_arguments


[docs]@dataclass class GraphqlGenerator(Generator): # ClassVars generatorname = os.path.basename(__file__) generatorversion = "0.1.1" valid_formats = ["graphql"] visit_all_class_slots = True uses_schemaloader = True requires_metamodel = False strict_naming: bool = False _permissible_value_valid_characters = re.compile("^[_A-Za-z][_0-9A-Za-z]*?$") def __post_init__(self): self.name_compatiblity = NameCompatibility(profile=NamingProfiles.graphql, do_not_fix=self.strict_naming) super().__post_init__() def visit_schema(self, **kwargs) -> str: return self.generate_header() def generate_header(self) -> str: out = f"# metamodel_version: {self.schema.metamodel_version}\n" if self.schema.version: out += f"# version: {self.schema.version}\n" return out def visit_class(self, cls: ClassDefinition) -> str: etype = "interface" if (cls.abstract or cls.mixin) and not cls.mixins else "type" mixins = ", ".join([camelcase(mixin) for mixin in cls.mixins]) out = f"{etype} {camelcase(cls.name)}" + (f" implements {mixins}" if mixins else "") out = "\n".join([out, " {"]) return out def end_class(self, cls: ClassDefinition) -> str: return "\n }\n\n" def visit_class_slot(self, cls: ClassDefinition, aliased_slot_name: str, slot: SlotDefinition) -> str: slotrange = ( camelcase(slot.range) if slot.range in self.schema.classes or slot.range in self.schema.types or slot.range in self.schema.enums else "String" ) if slot.multivalued: slotrange = f"[{slotrange}]" if slot.required: slotrange = slotrange + "!" return f"\n {lcamelcase(aliased_slot_name)}: {slotrange}" def visit_enum(self, enum: EnumDefinition): if enum.permissible_values: permissible_values = [] for value in enum.permissible_values: permissible_values.append(self.name_compatiblity.compatible(value)) values = "\n ".join(permissible_values) return f"enum {camelcase(enum.name).replace(' ', '')}\n {{\n {values}\n }}\n\n" else: logging.warning( f"Enumeration {enum.name} using `reachable_from` instead of `permissible_values` " + "to specify permissible values is not supported yet." + "Enumeration {enum.name} will be silently ignored!!" ) return ""
@shared_arguments(GraphqlGenerator) @click.command(name="graphql") @click.option( "--strict-naming", is_flag=True, show_default=True, help="Treat warnings about invalid names or schema elements as errors.", ) @click.version_option(__version__, "-V", "--version") def cli(yamlfile, **args): """Generate graphql representation of a LinkML model""" generator = GraphqlGenerator(yamlfile, **args) print(generator.serialize(**args)) if __name__ == "__main__": cli()