From 8cafffff3169018abb09bb1652a63dc4be01b983 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sun, 16 Oct 2022 15:56:49 -0500 Subject: graphql: T4753: generalize system_status to composite_{query,mutation} --- src/services/api/graphql/graphql/directives.py | 15 ++- src/services/api/graphql/graphql/mutations.py | 4 + src/services/api/graphql/graphql/queries.py | 5 +- .../api/graphql/graphql/schema/composite.graphql | 18 ++++ .../api/graphql/graphql/schema/schema.graphql | 8 +- .../graphql/graphql/schema/system_status.graphql | 18 ---- .../api/graphql/utils/composite_function.py | 11 ++ .../api/graphql/utils/schema_from_composite.py | 119 +++++++++++++++++++++ 8 files changed, 170 insertions(+), 28 deletions(-) create mode 100644 src/services/api/graphql/graphql/schema/composite.graphql delete mode 100644 src/services/api/graphql/graphql/schema/system_status.graphql create mode 100644 src/services/api/graphql/utils/composite_function.py create mode 100755 src/services/api/graphql/utils/schema_from_composite.py (limited to 'src/services') diff --git a/src/services/api/graphql/graphql/directives.py b/src/services/api/graphql/graphql/directives.py index d75d72582..a7919854a 100644 --- a/src/services/api/graphql/graphql/directives.py +++ b/src/services/api/graphql/graphql/directives.py @@ -63,16 +63,25 @@ class GenOpMutationDirective(VyosDirective): super().visit_field_definition(field, object_type, make_resolver=make_gen_op_mutation_resolver) -class SystemStatusDirective(VyosDirective): +class CompositeQueryDirective(VyosDirective): """ Class providing implementation of 'system_status' directive in schema. """ def visit_field_definition(self, field, object_type): super().visit_field_definition(field, object_type, - make_resolver=make_system_status_resolver) + make_resolver=make_composite_query_resolver) + +class CompositeMutationDirective(VyosDirective): + """ + Class providing implementation of 'system_status' directive in schema. + """ + def visit_field_definition(self, field, object_type): + super().visit_field_definition(field, object_type, + make_resolver=make_composite_mutation_resolver) directives_dict = {"configsessionquery": ConfigSessionQueryDirective, "configsessionmutation": ConfigSessionMutationDirective, "genopquery": GenOpQueryDirective, "genopmutation": GenOpMutationDirective, - "systemstatus": SystemStatusDirective} + "compositequery": CompositeQueryDirective, + "compositemutation": CompositeMutationDirective} diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py index f7d285a77..32da0eeb7 100644 --- a/src/services/api/graphql/graphql/mutations.py +++ b/src/services/api/graphql/graphql/mutations.py @@ -112,3 +112,7 @@ def make_config_session_mutation_resolver(mutation_name): def make_gen_op_mutation_resolver(mutation_name): return make_mutation_resolver(mutation_name, mutation_name, 'gen_op_mutation') + +def make_composite_mutation_resolver(mutation_name): + return make_mutation_resolver(mutation_name, mutation_name, + convert_camel_case_to_snake(mutation_name)) diff --git a/src/services/api/graphql/graphql/queries.py b/src/services/api/graphql/graphql/queries.py index 5f3a7d005..791b0d3e0 100644 --- a/src/services/api/graphql/graphql/queries.py +++ b/src/services/api/graphql/graphql/queries.py @@ -113,5 +113,6 @@ def make_config_session_query_resolver(query_name): def make_gen_op_query_resolver(query_name): return make_query_resolver(query_name, query_name, 'gen_op_query') -def make_system_status_resolver(query_name): - return make_query_resolver(query_name, query_name, 'system_status') +def make_composite_query_resolver(query_name): + return make_query_resolver(query_name, query_name, + convert_camel_case_to_snake(query_name)) diff --git a/src/services/api/graphql/graphql/schema/composite.graphql b/src/services/api/graphql/graphql/schema/composite.graphql new file mode 100644 index 000000000..717fbd89d --- /dev/null +++ b/src/services/api/graphql/graphql/schema/composite.graphql @@ -0,0 +1,18 @@ + +input SystemStatusInput { + key: String! +} + +type SystemStatus { + result: Generic +} + +type SystemStatusResult { + data: SystemStatus + success: Boolean! + errors: [String] +} + +extend type Query { + SystemStatus(data: SystemStatusInput) : SystemStatusResult @compositequery +} \ No newline at end of file diff --git a/src/services/api/graphql/graphql/schema/schema.graphql b/src/services/api/graphql/graphql/schema/schema.graphql index 2acecade4..62b0d30bb 100644 --- a/src/services/api/graphql/graphql/schema/schema.graphql +++ b/src/services/api/graphql/graphql/schema/schema.graphql @@ -3,7 +3,8 @@ schema { mutation: Mutation } -directive @systemstatus on FIELD_DEFINITION +directive @compositequery on FIELD_DEFINITION +directive @compositemutation on FIELD_DEFINITION directive @configsessionquery on FIELD_DEFINITION directive @configsessionmutation on FIELD_DEFINITION directive @genopquery on FIELD_DEFINITION @@ -11,8 +12,5 @@ directive @genopmutation on FIELD_DEFINITION scalar Generic -type Query { - SystemStatus(data: SystemStatusInput) : SystemStatusResult @systemstatus -} - +type Query type Mutation diff --git a/src/services/api/graphql/graphql/schema/system_status.graphql b/src/services/api/graphql/graphql/schema/system_status.graphql deleted file mode 100644 index be8d87535..000000000 --- a/src/services/api/graphql/graphql/schema/system_status.graphql +++ /dev/null @@ -1,18 +0,0 @@ -""" -Use 'scalar Generic' for system status output, to avoid attempts to -JSON-serialize in case of JSON output. -""" - -input SystemStatusInput { - key: String! -} - -type SystemStatus { - result: Generic -} - -type SystemStatusResult { - data: SystemStatus - success: Boolean! - errors: [String] -} diff --git a/src/services/api/graphql/utils/composite_function.py b/src/services/api/graphql/utils/composite_function.py new file mode 100644 index 000000000..bc9d80fbb --- /dev/null +++ b/src/services/api/graphql/utils/composite_function.py @@ -0,0 +1,11 @@ +# typing information for composite functions: those that invoke several +# elementary requests, and return the result as a single dict +import typing + +def system_status(): + pass + +queries = {'system_status': system_status} + +mutations = {} + diff --git a/src/services/api/graphql/utils/schema_from_composite.py b/src/services/api/graphql/utils/schema_from_composite.py new file mode 100755 index 000000000..f9983cd98 --- /dev/null +++ b/src/services/api/graphql/utils/schema_from_composite.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# +# A utility to generate GraphQL schema defintions from typing information of +# composite functions comprising several requests. + +import os +import json +from inspect import signature, getmembers, isfunction, isclass, getmro +from jinja2 import Template + +if __package__ is None or __package__ == '': + from util import snake_to_pascal_case, map_type_name +else: + from . util import snake_to_pascal_case, map_type_name + +# this will be run locally before the build +SCHEMA_PATH = '../graphql/schema' + +schema_data: dict = {'schema_name': '', + 'schema_fields': []} + +query_template = """ +input {{ schema_name }}Input { + key: String! + {%- for field_entry in schema_fields %} + {{ field_entry }} + {%- endfor %} +} + +type {{ schema_name }} { + result: Generic +} + +type {{ schema_name }}Result { + data: {{ schema_name }} + success: Boolean! + errors: [String] +} + +extend type Query { + {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @compositequery +} +""" + +mutation_template = """ +input {{ schema_name }}Input { + key: String! + {%- for field_entry in schema_fields %} + {{ field_entry }} + {%- endfor %} +} + +type {{ schema_name }} { + result: Generic +} + +type {{ schema_name }}Result { + data: {{ schema_name }} + success: Boolean! + errors: [String] +} + +extend type Mutation { + {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @compositemutation +} +""" + +def create_schema(func_name: str, func: callable, template: str) -> str: + sig = signature(func) + + field_dict = {} + for k in sig.parameters: + field_dict[sig.parameters[k].name] = map_type_name(sig.parameters[k].annotation) + + schema_fields = [] + for k,v in field_dict.items(): + schema_fields.append(k+': '+v) + + schema_data['schema_name'] = snake_to_pascal_case(func_name) + schema_data['schema_fields'] = schema_fields + + j2_template = Template(template) + res = j2_template.render(schema_data) + + return res + +def generate_composite_definitions(): + from composite_function import queries, mutations + + results = [] + for name,func in queries.items(): + res = create_schema(name, func, query_template) + results.append(res) + + for name,func in mutations.items(): + res = create_schema(name, func, mutation_template) + results.append(res) + + out = '\n'.join(results) + with open(f'{SCHEMA_PATH}/composite.graphql', 'w') as f: + f.write(out) + +if __name__ == '__main__': + generate_composite_definitions() -- cgit v1.2.3