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
Add basic interface inheritance support (fixes #8) #9
Conversation
8af7296
to
38ab9fe
Compare
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
@@ -18,6 +20,14 @@ class BlockFieldInfo(NamedTuple): | |||
value_type: str | |||
|
|||
|
|||
# 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)
True
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 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
CloudResourceEntity
will 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@relationship
directive) 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
types
that implementCloudResourceEntity
, they need to declare that they implementResourceEntity
as well, so for python, we just need to fix inheritance forCloudResourceEntity
classes, 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
AWSCloudPlatform
parent 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 passCloudResourceEntity
there, 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
AWSCloudPlatform
always has to implement bothCloudResourceEntity
andResourceEntity
). Thus I think this will be OK for now for most practical scenarios.Since the
GraphQL
that will be getting emitted by Apollo+Neo4j as of today will strip away theimplements
part of any interface, essentially convertingto
we are forced to pass interface inheritance information via the config file. To do so, we use
interfaceInheritance
as a magic key in the yaml config, so we would be passing smth like: