dapalex / py-graphql-mapper

A python library for generating GraphQL queries and mutations using plain python objects, no hardcoded strings.Any questions or issues, please report here https://github.com/dapalex/py-graphql-mapper/issues
MIT License
19 stars 1 forks source link

Inline fragmentation support? #31

Open pmpddylothar opened 1 week ago

pmpddylothar commented 1 week ago

I have a schema that has queries that require inline fragments. Is there something I'm missing that allows that to to render properly or potential modification I can do to the types to make it render properly? Right now I'm only getting the base entitlement section and none of the inline fragments.

This is an example snippet of the query with the inline fragments;

entitlement {
  sku
  plan
  status
  expirationDate
  startDate
  lastUpdated

  ... on QuantifiableEntitlement  {
    total
  }
  ... on DataEntitlement {
    dpaVersion
  }
  ... on PooledBandwidthEntitlement {
    siteEntitlementGroup
    siteEntitlementType
    allocatedBandwidth
    sites {
      site {
        id
        name
      }
      allocatedBandwidth
    }
  }
  ... on SiteEntitlement {
    siteEntitlementGroup
    regionality
    siteEntitlementType
    site {
      id
      name
    }
  }
  ... on ZtnaUsersEntitlement {
    ztnaUsersEntitlementGroup
  }
}
pmpddylothar commented 1 week ago

I hate replying to myself, but I managed to make it work, though it's a little goofy and does require some minimal manual intervention. If there was some other way already built-in that would do the same thing, please do point that out, but for now I'll share what I did.

The schema itself doesn't really outline building the query objects in a way to know inherently when it will have inline fragments. So, what I did was only slightly modify the code to look for a keyword, then manually adjusted the Object in question to have a specific keyword in the field, which then could be matched on to produce the output. In doing that, the query worked perfectly against the API endpoint. I'm not sure if it's something directly that you'd want added, it's probably not done in the most "pythonic" way, certainly a bit hacky, but it worked, haha.

For now I'm going to just fork the code onto my own branch to use locally, but if you're interested, I'm not opposed to adding it as an option thought I have not fully tested every scenario but the change should not impact anything. Again, I'm sure there's an infinitely better way of handling this, but this was just a quick solution I thought of that I'll share if others are in need.

In base.py I added logic to look for a "inlineFrag_" in the name of the field.

class GQLExporter():
  def export_gql_dict(self):
      # omitted section to keep post short
                      elif FieldsShow in inspect.getmro(type(fieldObject)):
                          if "inlineFrag_" in field:
                              new_name = f"... on {field.split('_')[1]}"
                              outputGqlDict[Translate.to_graphql_field_name(new_name)] = fieldObject.export_gql_dict
                          else:
                              outputGqlDict[Translate.to_graphql_field_name(field)] = fieldObject.export_gql_dict
                      elif list in inspect.getmro(type(fieldObject)):
                          for list_el in fieldObject:
                              if FieldsShow in inspect.getmro(type(list_el)):
                                  if "inlineFrag_" in field:
                                      new_name = f"... on {field.split('_')[1]}"
                                      outputGqlDict[
                                          Translate.to_graphql_field_name(new_name)] = fieldObject.export_gql_dict
                                  else:
                                      outputGqlDict[
                                          Translate.to_graphql_field_name(field)] = fieldObject.export_gql_dict
                              else:
                                  pass
                      # omitted section to keep post short

Then in the generated gql_types.py after running pgmcodegen, I modified the necessary classes to include the keyword. The inlineFrag_X classes are just renamed existing classes that were auto generated from the schema, then modified to only have the inner fields necessary.

class list_test_Entitlement(list, Entitlement):
   inlineFrag_QuantifiableEntitlement: list[inlineFrag_QuantifiableEntitlement]
   inlineFrag_DataLakeEntitlement: list[inlineFrag_DataLakeEntitlement]
   inlineFrag_PooledBandwidthEntitlement: list[inlineFrag_PooledBandwidthEntitlement]
   inlineFrag_SiteEntitlement: list[inlineFrag_SiteEntitlement]
   inlineFrag_ZtnaUsersEntitlement: list[inlineFrag_ZtnaUsersEntitlement]

class EntitlementInfo(GQLObject):
   """
   EntitlementInfo - Public Entitlement API
   entitlements - Entitlement inventory
   globalEntitlementAllocations - Entitlement usage and allocation across the managed accounts
   """
   entitlements: list_test_Entitlement[Entitlement]
   globalEntitlementAllocations: globalEntitlementAllocations

Then when I run the export_gql_source it generates the query with the proper inline fragmentation structure shown in my op.

dapalex commented 4 days ago

Hi,

Thanks for your feedback.

You didn't miss anything, currently polymorphism is not supported by the mapper (so inline fragmentation as well as interfaces and unions) and, given other activities, I don't think it will be in the near future.

About your workaround, although it works for your solution, I wouldn't add it in the repo since it requires a manual intervention.

Thanks anyway for sharing your code, if I'll get back to the project most probably this will be the first priority.

Cheers