Last week-end I tweeted that I had a PoC of a Tryton integration for Graphene.
Graphene is a GraphQL framework for Python. It consists of a type system and query language that allows to create APIs reachable through one specific webhook.
Here is the review of the implementation: https://codereview.tryton.org/304791002
Some of the requirements I had for this project:
- Easily define GraphQL types
- Tryton fields available in the GraphQL fields should be defined in trytond modules and thus evolve according to the activated tryton modules
- GraphiQL support (WiP)
- A Tryton Model can be mapped to multiple GraphQL types (WiP)
I also had another requirement myself: make this implementation support multiple databases.
I took a lot of ideas from the graphene-django implementation although the multi database support made everything a bit more complicated.
A small example
from graphene_tryton import ModelObjectType, GraphQLEndpoint
class Party(metaclass=PoolMeta):
__name__ = 'party.party'
@classmethod
def graphql_fields(cls):
return ['id', 'name', 'code', 'addresses', 'code_readonly']
class Address(metaclass=PoolMeta):
__name__ = 'party.address'
@classmethod
def graphql_fields(cls):
return ['id', 'name', 'party']
class PartyType(ModelObjectType):
class Meta:
name = 'Party'
tryton_model = 'party.party'
class AddressType(ModelObjectType):
class Meta:
name = 'Address'
tryton_model = 'party.address'
class Query(graphene.ObjectType):
all_parties = graphene.List(PartyType)
addresses_by_party = graphene.List(AddressType, code=graphene.String(required=True))
def resolve_all_parties(root, info):
pool = Pool()
Party = pool.get('party.party')
return Party.search([])
def resolve_addresses_by_party(root, info, code):
pool = Pool()
Party = pool.get('party.party')
try:
party, = Party.search([
('code', '=', code),
])
return party.addresses
except ValueError:
return []
@app.route('/<database_name>/party/graphql')
@with_pool
@with_transaction()
@GraphQLEndpoint(Query)
def party_graphql():
pass
You could test it with the following GraphQL query (saved as t.graphql
):
query {
allParties {
name
code
codeReadonly
}
partyByCode(code: "1") {
name
addresses {
name
}
}
addressesByParty(code: "4") {
name
party {
name
code
}
}
}
Thanks to this small curl command:
% curl --header "Content-Type: application/graphql" --data @t.graphql http://localhost:8000/graphql/party/graphql
The multi database support
As you can see from the small example the GraphQLEndpoint decorator takes a graphene.ObjectType
argument, this argument is the schema of this endpoint.
The multidatabase support implies that multiple schemas are possible for this endpoint. The code I submitted use a lot of introspection on the graphene objects created in order to use the schema provided as a template and then on the first query that is bounded to a database, compute all the GraphQL types (thanks to the calls to graphql_fields
) and the related schema. This computation is kept in cache for the next calls.
Mapping a tryton model to multiple GraphQL types
As I said this is not done yet. But I think I can reach this goal by replacing the way graphql_fields
work. Right now it returns a list of field names. It could return a dictionary that specify the name of a type and the fields that are available for this type. Relational fields will have to specify which type name they are pointing to.
Tryton to graphene type mapping
Most of the types are supported either through graphene.Scalar
types or graphene.List
types. There are some exception though.
Lack of timedelta
support
graphene do not support timedelta
fields.
One way to support those would be to have a specific resolver for those that transform them into a float representing the number of seconds of the timedelta.
Handling fields.Selection
graphene handle those using a python Enum
. The implementation creates a enum from the field definition.
Handling fields.Reference
GraphQL is typed, thus having a Reference field is a bit difficult. But I noticed today that types can be an union of types. I will exploit this in another iteration to support the reference fields.
GraphiQL support
One of the nice perks of GraphQL is that a lot of tools already work out of the box (from auto-completion in editors to data binding in javascript libraries). One of those (that is already supported by graphene-django
) is GraphiQL which is kind of a query sandbox: https://www.onegraph.com/graphiql
As from my understanding it’s only a matter of serving an HTML page with some javascript libraries it’s not technically difficult to do. If we were not to include this feature in graphene-tryton
adding it would only be a matter of subclassing GraphQLEndpoint
to handle correctly those queries (so for me it’s not a big issue either way ).
Conclusion
This implementation do not relies on the tryton internals (but way more so on the graphene internals) so I think it could be a side project under the Tryton umbrella that will provide yet another way to access the tryton data.
So if after the review process if its quality is deemed sufficient by the community I propose to add it to https://hg.tryton.org