-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fetch only required fields based on User Query (#83)
* [WIP] feat: Experiments * feat: DataLoader * fix: CursorPaginator Select Fields * fix: deferred_list callback list * fix: support parent link fields * fix: remove deprecated code * fix: removed unused code * feat: Basic Perms for new Resolvers fix: Use get_list in doc-dataloader feat: Basic Perms for new resolvers Co-authored-by: Fahim Ali Zain <[email protected]> Merge-request: ROMMAN-MR-126 Merged-by: Fahim Ali Zain <[email protected]> * feat: Field Level Perms (#66) * feat: Field Level Perms * fix: keywords in field names * fix: DeferredValue support for Mutations (#67) * fix: Reduced no. of iterations in default schema binding * feat: Introduce hook 'doctype_resolver_processors' Co-authored-by: Fahim Ali Zain <[email protected]> Merge-request: ROMMAN-MR-178 Merged-by: Fahim Ali Zain <[email protected]> * feat: Translations Support fix: remove redundant resolver check Merge branch 'ROMMAN-T-289-kick-default-resolver' into ROMMAN-T-481-translations feat: Translations Support Co-authored-by: Fahim Ali Zain <[email protected]> Merge-request: ROMMAN-MR-177 Merged-by: Fahim Ali Zain <[email protected]> * fix: Setup GQLType.doctype resolver manually * feat: implement select field resolver Co-authored-by: Abadulrehman <[email protected]> Merge-request: ROMMAN-MR-196 Merged-by: Fahim Ali Zain <[email protected]> * refactor: check if return type is scalar before link field binded (#70) * fix: get_allowed_fieldnames_for_doctype on plain child-doctype support * fix: default_fields link fields like owner * fix: cache get_allowed_fieldnames_for_doctype at the request level * refactor: query only user requested fields * feat: pre load schema's utility (#78) * [ROMMAN-T-521] GQL Dataloader: Raise Perm Error on GQLNonNull Permlevel Restricted Fields fix: check for GraphQLNonNull fix: use default_field_resolver from graphql fix: refactored perm checks fix: Raise Perm Error on GQLNonNull Permlevel Restricted Fields Co-authored-by: Fahim Ali Zain <[email protected]> Merge-request: ROMMAN-MR-214 Merged-by: Fahim Ali Zain <[email protected]> * use new graphql-sync-dataloaders package (#81) * refactor: use graphql-sync-dataloader package * refactor: update package graphql-sync-dataloader * refactor: fetch required fields * refactor: suppress JMESPathTypeError * refactor: move dedicated functions * refactor: use is_introspection_key wrapper * feat: get fields selected from child table query * feat: get fields selected from doctype dataloader * refactor: support use of aliases ie dataloaders re-used in query * refactor: extract fields from all field nodes * refactor: always query name cursor paginator fields * refactor: query name and parent for child tables * refactor: return of_type * refactor: use merge deep * refactor: remove unused imports * refactor: type error if extra_fields empty * refactor: get_doctype_requested_fields wrapper * refactor: query name always * refactor: cache fields in get_doctype_requested_fields * refactor: add parent doctype kwarg to get_fields_cursor_paginator * refactor: file names --------- Co-authored-by: Fahim Ali Zain <[email protected]> Co-authored-by: Abadulrehman <[email protected]>
- Loading branch information
1 parent
fd34023
commit 3985b3e
Showing
10 changed files
with
232 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from graphql import GraphQLResolveInfo | ||
|
||
|
||
def get_info_path_key(info: GraphQLResolveInfo): | ||
return "-".join([p for p in info.path.as_list() if isinstance(p, str)]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
from graphql import GraphQLResolveInfo | ||
|
||
from mergedeep import merge, Strategy | ||
|
||
from frappe_graphql.utils.introspection import is_introspection_key | ||
from frappe_graphql.utils import get_info_path_key | ||
from frappe_graphql.utils.permissions import get_allowed_fieldnames_for_doctype | ||
|
||
|
||
def collect_fields(node: dict, fragments: dict): | ||
""" | ||
Recursively collects fields from the AST | ||
Inspired from https://gist.github.com/mixxorz/dc36e180d1888629cf33 | ||
Notes: | ||
=> Please make sure your node and fragments passed have been converted to dicts | ||
=> Best used in conjunction with `get_allowed_fieldnames_for_doctype()` | ||
Args: | ||
node (dict): A node in the AST | ||
fragments (dict): Fragment definitions | ||
Returns: | ||
A dict mapping each field found, along with their sub fields. | ||
{'name': {}, | ||
'sentimentsPerLanguage': {'id': {}, | ||
'name': {}, | ||
'totalSentiments': {}}, | ||
'slug': {}} | ||
""" | ||
|
||
field = {} | ||
|
||
if node.get('selection_set'): | ||
for leaf in node['selection_set']['selections']: | ||
if leaf['kind'] == 'field': | ||
field[leaf['name']['value']] = collect_fields(leaf, fragments) | ||
elif leaf['kind'] == 'fragment_spread': | ||
field.update(collect_fields(fragments[leaf['name']['value']], | ||
fragments)) | ||
return field | ||
|
||
|
||
def get_field_tree_dict(info: GraphQLResolveInfo): | ||
""" | ||
A hierarchical dictionary of the graphql resolver fields nodes merged and returned. | ||
Args: | ||
info (GraphQLResolveInfo): GraphqlResolver Info | ||
Returns: | ||
A dict mapping each field found, along with their sub fields. | ||
{'name': {}, | ||
'sentimentsPerLanguage': {'id': {}, | ||
'name': {}, | ||
'totalSentiments': {}}, | ||
'slug': {}} | ||
""" | ||
fragments = {name: value.to_dict() for name, value in info.fragments.items()} | ||
fields = {} | ||
for field_node in info.field_nodes: | ||
merge(fields, collect_fields(field_node.to_dict(), fragments), strategy=Strategy.ADDITIVE) | ||
return fields | ||
|
||
|
||
def get_doctype_requested_fields( | ||
doctype: str, | ||
info: GraphQLResolveInfo, | ||
mandatory_fields: set = None, | ||
parent_doctype: str = None | ||
): | ||
""" | ||
Returns the list of requested fields for the given doctype from a GraphQL query. | ||
:param doctype: The doctype to retrieve requested fields for. | ||
:type doctype: str | ||
:param info: The GraphQLResolveInfo object representing information about a | ||
resolver's execution. | ||
:type info: GraphQLResolveInfo | ||
:param mandatory_fields: A set of fields that should always be included in the returned list, | ||
even if not requested. | ||
:type mandatory_fields: set | ||
:param parent_doctype: The doctype of the parent object, if any. | ||
:type parent_doctype: str | ||
:return: The list of requested fields for the given doctype. | ||
:rtype: list of str | ||
""" | ||
p_key = get_info_path_key(info) | ||
requested_fields = info.context.get(p_key) | ||
|
||
if requested_fields is not None: | ||
return requested_fields | ||
|
||
selected_fields = { | ||
key.replace('__name', '') | ||
for key in get_field_tree_dict(info).keys() | ||
if not is_introspection_key(key) | ||
} | ||
|
||
fieldnames = set(get_allowed_fieldnames_for_doctype( | ||
doctype=doctype, | ||
parent_doctype=parent_doctype | ||
)) | ||
|
||
requested_fields = selected_fields.intersection(fieldnames) | ||
if mandatory_fields: | ||
requested_fields.update(mandatory_fields) | ||
|
||
# send name always.. | ||
requested_fields.add("name") | ||
|
||
# cache it in context.. | ||
info.context[p_key] = requested_fields | ||
|
||
return list(requested_fields) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
def is_introspection_key(key): | ||
# from: https://spec.graphql.org/June2018/#sec-Schema | ||
# > All types and directives defined within a schema must not have a name which | ||
# > begins with "__" (two underscores), as this is used exclusively | ||
# > by GraphQL’s introspection system. | ||
return str(key).startswith("__") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.