diff options
Diffstat (limited to 'src/services/api/graphql/utils')
-rw-r--r-- | src/services/api/graphql/utils/config_session_function.py | 28 | ||||
-rwxr-xr-x | src/services/api/graphql/utils/schema_from_config_session.py | 119 | ||||
-rwxr-xr-x | src/services/api/graphql/utils/schema_from_op_mode.py | 30 | ||||
-rw-r--r-- | src/services/api/graphql/utils/util.py | 24 |
4 files changed, 175 insertions, 26 deletions
diff --git a/src/services/api/graphql/utils/config_session_function.py b/src/services/api/graphql/utils/config_session_function.py new file mode 100644 index 000000000..fc0dd7a87 --- /dev/null +++ b/src/services/api/graphql/utils/config_session_function.py @@ -0,0 +1,28 @@ +# typing information for native configsession functions; used to generate +# schema definition files +import typing + +def show_config(path: list[str], configFormat: typing.Optional[str]): + pass + +def show(path: list[str]): + pass + +queries = {'show_config': show_config, + 'show': show} + +def save_config_file(fileName: typing.Optional[str]): + pass +def load_config_file(fileName: str): + pass +def add_system_image(location: str): + pass +def delete_system_image(name: str): + pass + +mutations = {'save_config_file': save_config_file, + 'load_config_file': load_config_file, + 'add_system_image': add_system_image, + 'delete_system_image': delete_system_image} + + diff --git a/src/services/api/graphql/utils/schema_from_config_session.py b/src/services/api/graphql/utils/schema_from_config_session.py new file mode 100755 index 000000000..ea78aaf88 --- /dev/null +++ b/src/services/api/graphql/utils/schema_from_config_session.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 <http://www.gnu.org/licenses/>. +# +# +# A utility to generate GraphQL schema defintions from typing information of +# (wrappers of) native configsession functions. + +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 @configsessionquery +} +""" + +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 @configsessionmutation +} +""" + +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_config_session_definitions(): + from config_session_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}/configsession.graphql', 'w') as f: + f.write(out) + +if __name__ == '__main__': + generate_config_session_definitions() diff --git a/src/services/api/graphql/utils/schema_from_op_mode.py b/src/services/api/graphql/utils/schema_from_op_mode.py index 379d15250..57d63628b 100755 --- a/src/services/api/graphql/utils/schema_from_op_mode.py +++ b/src/services/api/graphql/utils/schema_from_op_mode.py @@ -20,15 +20,16 @@ import os import json -import typing from inspect import signature, getmembers, isfunction, isclass, getmro from jinja2 import Template from vyos.defaults import directories if __package__ is None or __package__ == '': from util import load_as_module, is_op_mode_function_name, is_show_function_name + from util import snake_to_pascal_case, map_type_name else: from . util import load_as_module, is_op_mode_function_name, is_show_function_name + from . util import snake_to_pascal_case, map_type_name OP_MODE_PATH = directories['op_mode'] SCHEMA_PATH = directories['api_schema'] @@ -103,35 +104,12 @@ type {{ name }} implements OpModeError { {%- endfor %} """ -def _snake_to_pascal_case(name: str) -> str: - res = ''.join(map(str.title, name.split('_'))) - return res - -def _map_type_name(type_name: type, optional: bool = False) -> str: - if type_name == str: - return 'String!' if not optional else 'String = null' - if type_name == int: - return 'Int!' if not optional else 'Int = null' - if type_name == bool: - return 'Boolean!' if not optional else 'Boolean = false' - if typing.get_origin(type_name) == list: - if not optional: - return f'[{_map_type_name(typing.get_args(type_name)[0])}]!' - return f'[{_map_type_name(typing.get_args(type_name)[0])}]' - # typing.Optional is typing.Union[_, NoneType] - if (typing.get_origin(type_name) is typing.Union and - typing.get_args(type_name)[1] == type(None)): - return f'{_map_type_name(typing.get_args(type_name)[0], optional=True)}' - - # scalar 'Generic' is defined in schema.graphql - return 'Generic' - def create_schema(func_name: str, base_name: str, func: callable) -> str: sig = signature(func) field_dict = {} for k in sig.parameters: - field_dict[sig.parameters[k].name] = _map_type_name(sig.parameters[k].annotation) + field_dict[sig.parameters[k].name] = map_type_name(sig.parameters[k].annotation) # It is assumed that if one is generating a schema for a 'show_*' # function, that 'get_raw_data' is present and 'raw' is desired. @@ -142,7 +120,7 @@ def create_schema(func_name: str, base_name: str, func: callable) -> str: for k,v in field_dict.items(): schema_fields.append(k+': '+v) - schema_data['schema_name'] = _snake_to_pascal_case(func_name + '_' + base_name) + schema_data['schema_name'] = snake_to_pascal_case(func_name + '_' + base_name) schema_data['schema_fields'] = schema_fields if is_show_function_name(func_name): diff --git a/src/services/api/graphql/utils/util.py b/src/services/api/graphql/utils/util.py index 073126853..da2bcdb5b 100644 --- a/src/services/api/graphql/utils/util.py +++ b/src/services/api/graphql/utils/util.py @@ -15,6 +15,7 @@ import os import re +import typing import importlib.util from vyos.defaults import directories @@ -74,3 +75,26 @@ def split_compound_op_mode_name(name: str, files: list): pair = (pair[0], f[0]) return pair return (name, '') + +def snake_to_pascal_case(name: str) -> str: + res = ''.join(map(str.title, name.split('_'))) + return res + +def map_type_name(type_name: type, optional: bool = False) -> str: + if type_name == str: + return 'String!' if not optional else 'String = null' + if type_name == int: + return 'Int!' if not optional else 'Int = null' + if type_name == bool: + return 'Boolean!' if not optional else 'Boolean = false' + if typing.get_origin(type_name) == list: + if not optional: + return f'[{map_type_name(typing.get_args(type_name)[0])}]!' + return f'[{map_type_name(typing.get_args(type_name)[0])}]' + # typing.Optional is typing.Union[_, NoneType] + if (typing.get_origin(type_name) is typing.Union and + typing.get_args(type_name)[1] == type(None)): + return f'{map_type_name(typing.get_args(type_name)[0], optional=True)}' + + # scalar 'Generic' is defined in schema.graphql + return 'Generic' |