Skip to content

Instantly share code, notes, and snippets.

@adrianschneider94
Last active July 12, 2021 01:37
Show Gist options
  • Save adrianschneider94/9ddb7f6c906fd13f7c215c3a10a9749c to your computer and use it in GitHub Desktop.
Save adrianschneider94/9ddb7f6c906fd13f7c215c3a10a9749c to your computer and use it in GitHub Desktop.
Current state of my graphql-codegen plugin for graphene.No pretty code yet.
overwrite: true
schema: "test.graphql"
documents: null
generates:
generated/test.py:
plugins:
- "lib/graphql-codegen-graphene.js"
import {GraphQLSchema} from "graphql"
import {Types} from '@graphql-codegen/plugin-helpers'
import {GrapheneVisitor} from "./visitor"
const {printSchema, parse, visit} = require('graphql')
export const plugin = (schema: GraphQLSchema, documents: Types.DocumentFile[], config: {}) => {
const visitor = new GrapheneVisitor(schema, {})
const printedSchema = printSchema(schema) // Returns a string representation of the schema
const astNode = parse(printedSchema) // Transforms the string into ASTNode
const result = visit(astNode, {leave: visitor})
return ("from graphene import *\n\n\n" + result.definitions.join('\n')).replace(/\n+$/g, "\n")
}
import {BaseTypesVisitor} from '@graphql-codegen/visitor-plugin-common'
import {
DirectiveDefinitionNode,
EnumTypeDefinitionNode,
FieldDefinitionNode,
GraphQLSchema,
InputObjectTypeDefinitionNode,
InputValueDefinitionNode,
InterfaceTypeDefinitionNode,
ListTypeNode,
NamedTypeNode,
NonNullTypeNode,
ObjectTypeDefinitionNode,
ScalarTypeDefinitionNode,
UnionTypeDefinitionNode,
ValueNode
} from 'graphql'
import snakeCase from "snake-case-typescript"
function indent(fieldDefinition: string, count: number = 1) {
return " ".repeat(count) + fieldDefinition
}
function valueToPythonValue(value: ValueNode): string {
if (!value.kind) {
return `"${value}"`
} else {
switch (value.kind) {
case "BooleanValue":
return value.value ? "True" : "False"
case "FloatValue":
return value.value.toString()
case "IntValue":
return value.value.toString()
case "NullValue":
return "None"
case "StringValue":
return `"${value.value}"`
case "ListValue":
return `[${value.values.map((value) => valueToPythonValue(value)).join(", ")}]`
case "ObjectValue":
return `{${value.fields.map((value) => `"${value.name}": ${valueToPythonValue(value.value)}`).join(", ")}}`
case "EnumValue":
return `"${value.value}"`
default:
throw Error(`Type ${value.kind} is not yet implemented.`)
}
}
}
function appendKwargs(current: string, newKwargs: { [k: string]: string }) {
let kwargStrings = []
for (let key in newKwargs) {
newKwargs[key] && kwargStrings.push(`${key}=${newKwargs[key]}`)
}
if (kwargStrings.length > 0) {
let kwargString = kwargStrings.join(", ")
let newString = current.replace(/\(\)$/g, `(${kwargString})`)
if (newString === current) {
newString = current.replace(/\)$/g, `, ${kwargString})`)
}
return newString
} else {
return current
}
}
export class GrapheneVisitor extends BaseTypesVisitor<any, any> {
constructor(schema: GraphQLSchema, pluginConfig: any, additionalConfig?: any) {
super(schema, {...pluginConfig}, {})
}
InterfaceTypeDefinition(node: InterfaceTypeDefinitionNode, key: number | string | undefined, parent: any): string {
let classDescription = (node.description as any) as string
if (classDescription) {
classDescription = "\n" + indent(`"""${classDescription}"""`)
} else {
classDescription = ""
}
return `class ${node.name}(Interface):` + classDescription + "\n" + node.fields!.map((field) => indent((field as any) as string)).join("\n") + "\n\n"
}
ObjectTypeDefinition(node: ObjectTypeDefinitionNode, key: number | string | undefined, parent: any): string {
let classDescription = (node.description as any) as string
if (classDescription) {
classDescription = "\n" + indent(`"""${classDescription}"""`)
} else {
classDescription = ""
}
let classHeader = `class ${node.name}(ObjectType):` + classDescription + "\n"
/*
Does not work with graphene in it's current state
let classMetaLines = ['class Meta:']
if (node.interfaces && node.interfaces.length > 0) {
classMetaLines.push(indent(`interfaces = [${node.interfaces.map((interface_) => `${interface_}`)}]`))
}
let classMeta = classMetaLines.length > 1 ? classMetaLines.map(line => indent(line)).join("\n") + "\n\n" : ""
*/
if (node.interfaces && node.interfaces.length > 0) {
console.log("Warning: Interface inheritance is not yet supported!")
}
let classBody = node.fields!.map(field => indent((field as any) as string)).join("\n")
return classHeader + classBody + "\n\n"
}
InputObjectTypeDefinition(node: InputObjectTypeDefinitionNode): string {
let classDescription = (node.description as any) as string
if (classDescription) {
classDescription = "\n" + indent(`"""${classDescription}"""`)
} else {
classDescription = ""
}
let classHeader = `class ${node.name}(InputObjectType):` + classDescription + "\n"
let classBody = node.fields!.map(field => indent((field as any) as string)).join("\n")
return classHeader + classBody + "\n\n"
}
UnionTypeDefinition(node: UnionTypeDefinitionNode, key: string | number | undefined, parent: any): string {
let unionHeader = `class ${node.name}(Union):\n`
if (node.description) {
unionHeader += indent(`"""${(node.description as any) as string}"""\n`)
}
let unionMetaLines = ['class Meta:']
if (node.types && node.types.length > 0) {
unionMetaLines.push(indent(`types = (${node.types.map((type) => `lambda: ${type}`).join(", ")})`))
}
let unionMeta = unionMetaLines.length > 1 ? unionMetaLines.map((line) => indent(line)).join("\n") : ""
return unionHeader + unionMeta + "\n\n"
}
NamedType(node: NamedTypeNode): string {
return super.NamedType(node)
}
NonNullType(node: NonNullTypeNode): string {
return `NonNull(${node.type})`
}
ListType(node: ListTypeNode): string {
return `List(${node.type})`
}
FieldDefinition(node: FieldDefinitionNode, key?: number | string, parent?: any): string {
const nodeName = snakeCase((node.name as any) as string)
let field = `Field(lambda: ${node.type})`
const nodeDescription = (node.description as any) as string
if (nodeDescription) {
field = appendKwargs(field, {description: `"${nodeDescription}"`})
}
if (node.arguments && node.arguments.length > 0) {
let parsedArguments = node.arguments.map((argument: InputValueDefinitionNode, index: number, schema: ReadonlyArray<InputValueDefinitionNode>) => {
let argumentString = (argument as any) as string
argumentString = argumentString.replace("Field", "Argument")
argumentString = argumentString.replace(" = ", "=")
return argumentString
})
field = field.replace(/\)$/g, ", " + parsedArguments.join(", ") + ")")
}
if (node.directives) {
node.directives.filter((directive) => ((directive.name as any) as string) === "deprecated").forEach(directive => {
let reason = directive.arguments && directive.arguments.filter((argument) => (((argument.name as any) as string) === "reason"))
if (reason && reason[0]) {
field = appendKwargs(field, {deprecation_reason: `"${reason[0].value}"`})
} else {
field = appendKwargs(field, {deprecation_reason: `"No reason specified."`})
}
}
)
}
return `${nodeName} = ${field}`
}
ScalarTypeDefinition(node: ScalarTypeDefinitionNode): string {
return `class ${node.name}(Scalar):\n` + indent(`serialize = lambda x: str(x)`) + "\n\n"
}
InputValueDefinition(node: InputValueDefinitionNode, key?: number | string, parent?: any): string {
const nodeName = snakeCase((node.name as any) as string)
let field = `Field(lambda: ${node.type})`
const nodeDescription = (node.description as any) as string
if (nodeDescription) {
field = appendKwargs(field, {description: `"${nodeDescription}"`})
}
if (node.defaultValue) {
field = appendKwargs(field, {default_value: `${valueToPythonValue(node.defaultValue)}`})
}
return `${nodeName} = ${field}`
}
EnumTypeDefinition(node: EnumTypeDefinitionNode): string {
let parsedValues: string[] = []
if (node.values) {
parsedValues = node.values.map((value => `"${(value.name as any) as string}"`))
}
let Enum = `Enum("${node.name}", [${parsedValues.join(", ")}])`
if (node.description) {
Enum = appendKwargs(Enum, {description: `"${(node.description as any) as string}"`})
}
return `${node.name} = ${Enum}\n\n`
}
DirectiveDefinition(node: DirectiveDefinitionNode): string {
console.log("Warning: Directives are not implemented yet!")
/*
GraphQLDirective uses other types than graphene.
return `${node.name} = GraphQLDirective(
name="${node.name}",
description="${node.description || ""}",
args={
${node.arguments!.map((argument) => `"${argument}"`).join(",\n")}
}
)
`
return "DirectiveDefinition"
*/
return ""
}
protected _getTypeForNode(node: NamedTypeNode): string {
return `${node.name}`
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment