-
Notifications
You must be signed in to change notification settings - Fork 1
Add basic interface inheritance support (fixes #8) #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
8af7296 to
38ab9fe
Compare
miki725
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 some hacks here and there but we should prioritize getting schema to function. I have a feeling well need more refactors soon so prolly worth seeing all requirements first before cleaning things up further
gql_schema_codegen/block/block.py
Outdated
|
|
||
|
|
||
| # a dictionary where for each node, we hold its children | ||
| inheritanceTree: Dict[str, Set[str]] = {"root": set()} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| inheritanceTree: Dict[str, Set[str]] = {"root": set()} | |
| inheritanceTree: Dict[str, Set[str]] = defaultdict(lambda: set()) |
https://docs.python.org/3.11/library/collections.html#collections.defaultdict
then whatever you access on the dict is always present automatically
gql_schema_codegen/block/block.py
Outdated
| siblings = inheritanceTree.get(deps[display_name], set()) | ||
| siblings.add(display_name) | ||
| inheritanceTree[deps[display_name]] = siblings |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| siblings = inheritanceTree.get(deps[display_name], set()) | |
| siblings.add(display_name) | |
| inheritanceTree[deps[display_name]] = siblings | |
| inheritanceTree.setdefault(deps[display_name], set()).add(display_name) |
https://docs.python.org/3.11/library/stdtypes.html#dict.setdefault
or if you do defaultdict as per above:
| siblings = inheritanceTree.get(deps[display_name], set()) | |
| siblings.add(display_name) | |
| inheritanceTree[deps[display_name]] = siblings | |
| inheritanceTree[deps[display_name]].add(display_name) |
gql_schema_codegen/block/block.py
Outdated
| siblings = inheritanceTree.get(p, set()) | ||
| siblings.add(display_name) | ||
| inheritanceTree[p] = siblings |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
similar pattern as above with either setdefault or defaultdict()[]
|
|
||
| def update_interface_dependencies(config_file_content): | ||
| global INTERMEDIATE_INTERFACES | ||
| if type(config_file_content) is dict: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if type(config_file_content) is dict: | |
| if isinstance(config_file_content, dict) |
doing type check with is is not ideal:
>>> class Foo(dict): pass
>>> type(Foo()) is dict
False
>>> isinstance(Foo(), dict)
TrueThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know but I am explicitly followed the style of the rest of the repo here in the thought that we will perhaps refactor everything together
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you do the defaultdict suggestion isinstance will be required. either way its up to you
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it makes sense to make the change - leaving extra bad code in was not great and leaves more work for potential future refactorings
| global INTERMEDIATE_INTERFACES | ||
| if type(config_file_content) is dict: | ||
| data = config_file_content.get("interfaceInheritance") | ||
| if type(data) is dict: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above
| if not intermediate_interfaces: | ||
| intermediate_interfaces = INTERMEDIATE_INTERFACES |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if not intermediate_interfaces: | |
| intermediate_interfaces = INTERMEDIATE_INTERFACES | |
| intermediate_interfaces = intermediate_interfaces or INTERMEDIATE_INTERFACES |
usually simplifies things a bit, especially if you count the mccabe function complexity its nice to reduce any direct if statements to more simple alternatives
This PR partially addresses #8. Currently, most graphql servers (and particularly for our case, neo4j over apollo) don't appropriately support interfaces inheriting from interfaces. Thus, if we have smth like
then
CloudResourceEntitywill be a regular interface without implementing the secondary interface in the schema that will be emitted. As we autogenerate types, we want any field or directive (e.g., a@relationshipdirective) that is of typeResourceEntity, to supportCloudResourceEntity. We want to be able to do this in a best effort way for auto-generated code, until things are supported by neo4j or other servers.For
typesthat implementCloudResourceEntity, they need to declare that they implementResourceEntityas well, so for python, we just need to fix inheritance forCloudResourceEntityclasses, as well as any types that implement the interface (e.g., so as to not have method resolution order problems). Thus, assuming a typewe want the emitted python code to be
This was not supported as of today as
AWSCloudPlatformparent class hierarchy would be getting emitted as(CloudPlatform, CloudResourceEntity, ResourceEntity), which messes up MRO.This still leaves a question open as to how do we deal with accessing said types via graphql when it comes to neo4j. For instance, if we have a field of type
ResourceEntity, and we want to passCloudResourceEntitythere, that is allowed by the RFC (see graphql/graphql-spec#373) but this won't be understood by the graphql server for now. Unions of interfaces are also not allowed by the SPEC (graphql/graphql-js#451).That said, I don't know if we will ever run into this issue, since we would be able to pass all types implementing the interface as inputs (they will be implementing all interfaces in case of intermediate interfaces by default, thus
AWSCloudPlatformalways has to implement bothCloudResourceEntityandResourceEntity). Thus I think this will be OK for now for most practical scenarios.Since the
GraphQLthat will be getting emitted by Apollo+Neo4j as of today will strip away theimplementspart of any interface, essentially convertingto
we are forced to pass interface inheritance information via the config file. To do so, we use
interfaceInheritanceas a magic key in the yaml config, so we would be passing smth like: