autographql 0.1.0__py3-none-any.whl
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.
- autographql/__init__.py +15 -0
- autographql/_version.py +24 -0
- autographql/cli.py +66 -0
- autographql/generators/__init__.py +1 -0
- autographql/generators/alias_generator.py +142 -0
- autographql/generators/base_generator.py +503 -0
- autographql/generators/basic_query_generator.py +35 -0
- autographql/generators/directive_generator.py +239 -0
- autographql/generators/external_fragments_generator.py +129 -0
- autographql/generators/filter_generator.py +171 -0
- autographql/generators/generator_factory.py +45 -0
- autographql/generators/inline_fragments_generator.py +113 -0
- autographql/generators/mutation_generator.py +291 -0
- autographql/generators/pagination_generator.py +298 -0
- autographql/query_generator_app.py +138 -0
- autographql/utils/__init__.py +1 -0
- autographql/utils/schema_utils.py +84 -0
- autographql/utils/variable_utils.py +205 -0
- autographql-0.1.0.dist-info/METADATA +200 -0
- autographql-0.1.0.dist-info/RECORD +23 -0
- autographql-0.1.0.dist-info/WHEEL +4 -0
- autographql-0.1.0.dist-info/entry_points.txt +2 -0
- autographql-0.1.0.dist-info/licenses/LICENSE +21 -0
autographql/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""AutoGraphQL: schema-driven generation of NL2GraphQL datasets.
|
|
2
|
+
|
|
3
|
+
Turns a GraphQL SDL schema into validated (schema, natural-language query,
|
|
4
|
+
GraphQL query) triples, covering aliases, directives, fragments, inline
|
|
5
|
+
fragments, filters, pagination and mutations.
|
|
6
|
+
"""
|
|
7
|
+
try:
|
|
8
|
+
# Written at build time by hatch-vcs from the git tag.
|
|
9
|
+
from ._version import __version__
|
|
10
|
+
except ImportError: # running from a source tree, not installed
|
|
11
|
+
__version__ = "0.0.0.dev0"
|
|
12
|
+
|
|
13
|
+
from .query_generator_app import GraphQLQueryGeneratorApp
|
|
14
|
+
|
|
15
|
+
__all__ = ["GraphQLQueryGeneratorApp", "__version__"]
|
autographql/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
autographql/cli.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import argparse
|
|
5
|
+
|
|
6
|
+
root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
|
7
|
+
if root_dir not in sys.path:
|
|
8
|
+
sys.path.insert(0, root_dir)
|
|
9
|
+
|
|
10
|
+
from .query_generator_app import GraphQLQueryGeneratorApp
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
parser = argparse.ArgumentParser(description='Generate GraphQL queries from multiple schemas')
|
|
14
|
+
parser.add_argument('--schema', type=str, help='Path to a specific GraphQL schema file')
|
|
15
|
+
parser.add_argument('--category', type=str, required=True,
|
|
16
|
+
choices=['external_fragments', 'inline_fragments', 'directives', 'filters', 'aliases', 'mutations', 'pagination', 'all'],
|
|
17
|
+
help='Category of queries to generate')
|
|
18
|
+
parser.add_argument('--count', type=int, required=True, help='Number of unique queries to generate per category')
|
|
19
|
+
parser.add_argument('--max-hops', type=int, default=2, help='Maximum number of hops (query depth) allowed in generated queries')
|
|
20
|
+
|
|
21
|
+
args = parser.parse_args()
|
|
22
|
+
|
|
23
|
+
schema_dir = 'schemas'
|
|
24
|
+
output_dir = 'generated_queries'
|
|
25
|
+
|
|
26
|
+
if not os.path.exists(output_dir):
|
|
27
|
+
os.makedirs(output_dir)
|
|
28
|
+
|
|
29
|
+
if not os.path.exists(schema_dir):
|
|
30
|
+
os.makedirs(schema_dir)
|
|
31
|
+
|
|
32
|
+
app = GraphQLQueryGeneratorApp()
|
|
33
|
+
|
|
34
|
+
if args.schema:
|
|
35
|
+
schema_path = args.schema
|
|
36
|
+
|
|
37
|
+
if os.path.isdir(schema_path):
|
|
38
|
+
index_file = os.path.join(schema_path, 'index.graphql')
|
|
39
|
+
if os.path.isfile(index_file):
|
|
40
|
+
schema_path = index_file
|
|
41
|
+
elif not os.path.exists(schema_path):
|
|
42
|
+
schema_dir_path = os.path.join(schema_dir, schema_path)
|
|
43
|
+
index_file = os.path.join(schema_dir_path, 'index.graphql')
|
|
44
|
+
|
|
45
|
+
if os.path.isdir(schema_dir_path) and os.path.isfile(index_file):
|
|
46
|
+
schema_path = index_file
|
|
47
|
+
else:
|
|
48
|
+
schema_file = schema_path
|
|
49
|
+
if not schema_file.endswith('.graphql'):
|
|
50
|
+
schema_file += '.graphql'
|
|
51
|
+
|
|
52
|
+
schema_path = os.path.join(schema_dir, schema_file)
|
|
53
|
+
|
|
54
|
+
if not os.path.exists(schema_path):
|
|
55
|
+
print(f"Error: Schema '{args.schema}' does not exist at any of the possible locations.")
|
|
56
|
+
return 1
|
|
57
|
+
|
|
58
|
+
app.generate_queries_for_schema(schema_path, args.category, args.count, args.max_hops, output_dir)
|
|
59
|
+
else:
|
|
60
|
+
app.generate_queries_from_directory(schema_dir, args.category, args.count, args.max_hops, output_dir)
|
|
61
|
+
|
|
62
|
+
print("\nQuery generation complete!")
|
|
63
|
+
return 0
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
sys.exit(main())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Per-category query generators, selected through `generator_factory`."""
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Dict, Any, Tuple, Set
|
|
4
|
+
import random
|
|
5
|
+
from .base_generator import BaseQueryGenerator
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
class AliasQueryGenerator(BaseQueryGenerator):
|
|
10
|
+
def __init__(self, schema, exclude_fields: Set[str] = None, max_hops: int = 2):
|
|
11
|
+
super().__init__(schema, exclude_fields, max_hops)
|
|
12
|
+
|
|
13
|
+
def generate_query(self) -> Tuple[str, Dict[str, Any]]:
|
|
14
|
+
if not self.root_query_fields:
|
|
15
|
+
return "", {}
|
|
16
|
+
|
|
17
|
+
add_directive = self.should_add_feature(0.3)
|
|
18
|
+
add_filter = False if add_directive else self.should_add_feature(0.3)
|
|
19
|
+
query_sub_category = ''
|
|
20
|
+
query_variables = {}
|
|
21
|
+
|
|
22
|
+
available_fields = []
|
|
23
|
+
for field_name in self.root_query_fields.keys():
|
|
24
|
+
if field_name in self.exclude_fields:
|
|
25
|
+
continue
|
|
26
|
+
|
|
27
|
+
field = self.root_query_fields.get(field_name)
|
|
28
|
+
if field:
|
|
29
|
+
return_type = self._get_field_type_name(field)
|
|
30
|
+
if return_type:
|
|
31
|
+
available_fields.append(field_name)
|
|
32
|
+
|
|
33
|
+
if not available_fields:
|
|
34
|
+
return "", {}
|
|
35
|
+
|
|
36
|
+
object_fields = []
|
|
37
|
+
for field_name in available_fields:
|
|
38
|
+
field = self.root_query_fields.get(field_name)
|
|
39
|
+
if not field:
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
return_type_name = self._get_field_type_name(field)
|
|
43
|
+
if self._is_object_or_interface(return_type_name):
|
|
44
|
+
sub_fields = self._get_type_fields(return_type_name)
|
|
45
|
+
if sub_fields and len(sub_fields) > 0:
|
|
46
|
+
object_fields.append((field_name, field, return_type_name))
|
|
47
|
+
|
|
48
|
+
if object_fields:
|
|
49
|
+
field_name, field, return_type_name = random.choice(object_fields)
|
|
50
|
+
|
|
51
|
+
field_args, field_vars = self._get_field_args(field, use_variables=True)
|
|
52
|
+
query_variables.update(field_vars)
|
|
53
|
+
|
|
54
|
+
if add_filter and not field_args:
|
|
55
|
+
field_name_with_filter, sub_category, filter_variables = self.add_filter_to_field(field, field_name)
|
|
56
|
+
if sub_category == 'filters':
|
|
57
|
+
field_name, field_args = field_name_with_filter.split('(', 1)
|
|
58
|
+
field_args = '(' + field_args
|
|
59
|
+
query_variables.update(filter_variables)
|
|
60
|
+
query_sub_category = 'filters'
|
|
61
|
+
|
|
62
|
+
sub_fields = self._get_type_fields(return_type_name)
|
|
63
|
+
sub_field_names = list(sub_fields.keys())
|
|
64
|
+
|
|
65
|
+
if sub_field_names:
|
|
66
|
+
num_subfields = min(random.randint(2, 5), len(sub_field_names))
|
|
67
|
+
selected_subfields = random.sample(sub_field_names, num_subfields)
|
|
68
|
+
|
|
69
|
+
aliased_fields = []
|
|
70
|
+
for subfield in selected_subfields:
|
|
71
|
+
alias = f"aliased{subfield.capitalize()}"
|
|
72
|
+
subfield_type = self._get_field_type_name(sub_fields[subfield])
|
|
73
|
+
subfield_name = subfield
|
|
74
|
+
apply_directive = add_directive and self.should_add_feature(0.5) and query_sub_category != 'filters'
|
|
75
|
+
|
|
76
|
+
if self._is_object_or_interface(subfield_type):
|
|
77
|
+
nested_fields_str = self._build_fields_string(subfield_type, max_depth=2)
|
|
78
|
+
|
|
79
|
+
if apply_directive:
|
|
80
|
+
subfield_with_directive, sub_category, directive_variables = self.add_directive_to_field(
|
|
81
|
+
subfield_name + " {"
|
|
82
|
+
)
|
|
83
|
+
if sub_category == 'directives':
|
|
84
|
+
query_variables.update(directive_variables)
|
|
85
|
+
query_sub_category = 'directives'
|
|
86
|
+
subfield_opening = subfield_with_directive
|
|
87
|
+
else:
|
|
88
|
+
subfield_opening = f"{subfield_name} {{"
|
|
89
|
+
else:
|
|
90
|
+
subfield_opening = f"{subfield_name} {{"
|
|
91
|
+
|
|
92
|
+
if nested_fields_str:
|
|
93
|
+
indented_nested_str = nested_fields_str.replace('\n', '\n ')
|
|
94
|
+
aliased_fields.append(f"{alias}: {subfield_opening}\n {indented_nested_str}\n }}")
|
|
95
|
+
else:
|
|
96
|
+
default_field = self._get_default_field(subfield_type)
|
|
97
|
+
aliased_fields.append(f"{alias}: {subfield_opening}\n {default_field}\n }}")
|
|
98
|
+
else:
|
|
99
|
+
if apply_directive:
|
|
100
|
+
subfield_with_directive, sub_category, directive_variables = self.add_directive_to_field(subfield_name)
|
|
101
|
+
if sub_category == 'directives':
|
|
102
|
+
query_variables.update(directive_variables)
|
|
103
|
+
query_sub_category = 'directives'
|
|
104
|
+
aliased_fields.append(f"{alias}: {subfield_with_directive}")
|
|
105
|
+
else:
|
|
106
|
+
aliased_fields.append(f"{alias}: {subfield_name}")
|
|
107
|
+
else:
|
|
108
|
+
aliased_fields.append(f"{alias}: {subfield_name}")
|
|
109
|
+
|
|
110
|
+
variable_section = self._format_variable_section(query_variables)
|
|
111
|
+
indented_aliased_fields = "\n ".join(aliased_fields)
|
|
112
|
+
query = f"""query {field_name.capitalize()}AliasQuery{variable_section} {{
|
|
113
|
+
{field_name}{field_args} {{
|
|
114
|
+
{indented_aliased_fields}
|
|
115
|
+
}}
|
|
116
|
+
}}"""
|
|
117
|
+
|
|
118
|
+
final_variables = self._simplify_variables(query_variables)
|
|
119
|
+
return query, final_variables
|
|
120
|
+
|
|
121
|
+
return "", {}
|
|
122
|
+
|
|
123
|
+
def _format_variable_section(self, query_variables: Dict[str, Any]) -> str:
|
|
124
|
+
"""Format the variable section for the query."""
|
|
125
|
+
if not query_variables:
|
|
126
|
+
return ""
|
|
127
|
+
|
|
128
|
+
var_defs = []
|
|
129
|
+
for var_name, var_info in query_variables.items():
|
|
130
|
+
var_type = var_info.get('type', 'String')
|
|
131
|
+
if not var_type.endswith('!'):
|
|
132
|
+
var_type += '!'
|
|
133
|
+
var_defs.append(f"${var_name}: {var_type}")
|
|
134
|
+
|
|
135
|
+
return f"({', '.join(var_defs)})" if var_defs else ""
|
|
136
|
+
|
|
137
|
+
def _simplify_variables(self, query_variables: Dict[str, Any]) -> Dict[str, Any]:
|
|
138
|
+
"""Simplify variable values for the query."""
|
|
139
|
+
final_variables = {}
|
|
140
|
+
for var_name, var_info in query_variables.items():
|
|
141
|
+
final_variables[var_name] = self._simplify_variable_value(var_info)
|
|
142
|
+
return final_variables
|