Graphql-codegen library - a pure python implementation

Related tags

GraphQL turms
Overview

turms

codecov PyPI version

DEVELOPMENT

Inspiration

Turms is a pure python implementation of the awesome graphql-codegen library, following a simliar extensible design. It makes heavy use of pydantic and its serialization capablities and provides fully typed querys, mutations and subscriptions

Supports

  • Documents
  • Fragments
  • Enums
  • Query Functions

Features

  • Fully Modular (agnostic of graphql transport)
  • Specify type mixins, baseclasses...
  • Fully Support type hints for variables (Pylance)
  • Compliant with graphl-config

Installation

pip install turms

Usage

Open your workspace (create a virtual env), in the root folder

turms init

This creates a graphql-config compliant configuration file in the working directory, edit this to reflect your settings (see Configuration)

turms gen

Generate beautifully typed Operations, Enums,...

Why Turms

In Etruscan religion, Turms (usually written as 𐌕𐌖𐌓𐌌𐌑 Turmś in the Etruscan alphabet) was the equivalent of Roman Mercury and Greek Hermes, both gods of trade and the messenger god between people and gods.

Comments
  • Forward references are not always correctly identified.

    Forward references are not always correctly identified.

    I came across an issue with a generated schema that was missing a call to update_forward_refs()

    It has an InputType that references another InputType that is later in the same file. The InputsPlugin looks like it tries to check for this by marking any InputType that references another InputType as self_referential.

    My schema contains a nested non nullable InputType. This is is of type GraphQLNonNull and we don't check the value.type.of_type which shows that this is actually wrapping a GraphQLInputObjectType.

    The reference is correctly written as a string. I'm not 100% sure where the code that determines which type hints should be strings lives.

    When looking into this, I also noticed that there is a (presumably) related issue in the test_arkitekt_operations test.

    This generates the following class at line 305

    class Node(BaseModel):
        typename: Optional[Literal["Node"]] = Field(alias="__typename")
        name: str
        interface: str
        package: str
        description: str
        type: NodeType
        id: str
        args: Optional[List[Optional[ArgPort]]]
        kwargs: Optional[List[Optional[KwargPort]]]
        returns: Optional[List[Optional[ReturnPort]]]
    

    But ArgPort is not defined until line 544

    opened by strue36 17
  • Using only `InputsPlugin` and `EnumsPlugin` generates to much code

    Using only `InputsPlugin` and `EnumsPlugin` generates to much code

    Problem

    The code-generation produces to much output if you're only interested in generating input types and enums. The result isn't syntactically wrong, but not quite beautiful pydantic code.

    Note: I overwrote the input-type base to generate code, that doesn't require turms installed in the final environment.

    Setup

    Project layout

    | - graphql.config.yaml
    |
    | - graphql
    |   | - schema.graphql
    |
    | - generated
    |   | - types.py
    
    

    graphql.config.yaml

    projects:
      default:
        schema: /graphql/**/*.graphql
        documents: graphql/**.graphql
        extensions:
          turms:
            out_dir: /generated
            generated_name: types.py
            plugins:
              - type: turms.plugins.enums.EnumsPlugin
              - type: turms.plugins.inputs.InputsPlugin
                inputtype_bases:
                  - "pydantic.BaseModel"
            processors:
              - type: turms.processor.black.BlackProcessor
    

    schema.graphql

    type Query {
        hello(orderBy: HelloWorldOrder! = ASC, filter: HelloWorldFilter,
            first: Int, last: Int, before: String, after: String): [World!]!
    }
    
    enum HelloWorldOrder {
        ASC
        DESC
    }
    
    input HelloWorldFilter {
        search: String!
    }
    
    type World {
        message: String!
    }
    

    types.py

    from turms.types.object import GraphQLObject              # <-- not required
    from turms.types.object import GraphQLObject              # <-- duplicate
    from pydantic.fields import Field                         # <-- not required
    from typing import Optional, List, Dict, Union, Literal   # Dict, Union and Literal not required
    from enum import Enum
    from pydantic import BaseModel
    
    
    class HelloWorldOrder(str, Enum):
        None                                   # <-- Unused statement
        ASC = "ASC"
        DESC = "DESC"
    
    
    class HelloWorldFilter(BaseModel):
        None                                   # <-- Unused statement
        ids: Optional[List[str]]
    
    

    Expected Output

    types.py

    from typing import Optional, List
    from enum import Enum
    from pydantic import BaseModel
    
    
    class HelloWorldOrder(str, Enum):
        ASC = "ASC"
        DESC = "DESC"
    
    
    class HelloWorldFilter(BaseModel):
        ids: Optional[List[str]]
    
    opened by j-riebe 13
  • Support for schema from local path

    Support for schema from local path

    First of all, this seems like a nice project.

    Would it be hard, to support loading the schema from a bunch of local .graphql files?

    Currently, using a directory path as projects.default.schema in graphql.config.yaml isn't allowed.

    opened by j-riebe 5
  • Documents required

    Documents required

    It appears that "documents" is required in the config and cannot be null. I am relatively new to what documents are and I am having difficulty understanding their purpose. I have a full schema and an unaware of what the requirements is for documents.

    opened by timhughes 4
  • Consistent forward references

    Consistent forward references

    Found that forward references would reorder themselves every time the file was generated. It's difficult to test as the ordering of a set is related to the random number seed.

    I forced these to be sorted alphabetically which keeps the output file more consistent.

    I also refactored some repeated code in registry.py but can remove that if you prefer.

    opened by strue36 3
  • Operation generation fails for complex variable types

    Operation generation fails for complex variable types

    In generate_query_doc(), when parsing the variable definitions, we check if the node is a ListTypeNode. The assumption is that the next node down will be a concrete type, but this can for example be a NonNullTypeNode which does not have a name.

    for v in o.variable_definitions:
        if isinstance(v.type, NonNullTypeNode):
            if isinstance(v.type.type, ListTypeNode):
                description += f"    {v.variable.name.value} (List[{v.type.type.type.name.value}]): {v.type.type.type.name.value}\n"
            else:
                description += f"    {v.variable.name.value} ({v.type.type.name.value}): {v.type.type.name.value}\n"
    

    The variable type that triggers this is [String!]!

    I will have a look at creating a minimal example to reproduce this. I think it will probably require recursively parsing the variable definitions to resolve this.

    opened by strue36 3
  • Non nullable mutation response fix

    Non nullable mutation response fix

    When trying to generate functions for a schema that had mutations that returned a NonNull object I get the following error in ast.py.

    def iter_fields(node):
        """
        Yield a tuple of ``(fieldname, value)`` for each field in ``node._fields``
        that is present on *node*.
        """
        for field in node._fields:
    

    AttributeError: 'tuple' object has no attribute '_fields'

    I have tweaked the beasts schema to cover this case and traced the root cause to a branch in recurse_outputtype_annotation() that returns a tuple. I don't think this should happen so I changed this line to return an ast.Name instead.

    opened by strue36 2
  • Fixed two bugs in ObjectsPlugin

    Fixed two bugs in ObjectsPlugin

    fixes #24

    • Multiple inheritance could result in an invalid MRO of the generated pydantic types.

    • If an interface was used as a forward reference, the wrong variable was used to call update_forward_refs().

    may Fix #23

    • Extended unraveling of wrapped type https://github.com/jhnnsrs/turms/compare/master...j-riebe:master#diff-ba9518bcc774b42fd3b54830810de2aa74832a6584657a094513b61c1d3517ceL243-R258

    • Added test function that checks the generated module for runtime errors https://github.com/jhnnsrs/turms/compare/master...j-riebe:master#diff-9e08e8c7769db7b58089fd7659d75ca96df40bb7b5d50299d3a30abd54da9ad6R10-R23

    opened by j-riebe 2
  • Development

    Development

    • Added ability to create frozen classes

    • Changed standard base class (pydantic Field), no more deps on turms itself

    • Created hierarchy for overwritable baseclasses (object -> interface, object -> query, object-> fragment...)

    • CLI updates

    • FIX: typename for fragments

    opened by jhnnsrs 2
  • [Feature] Generate code in more pythonic snake_case style

    [Feature] Generate code in more pythonic snake_case style

    For the wishlist, I'd be very excited if turms could output snake_case code.

    I know, you could use a BaseModel with an alias_generator to do the job of converting the GraphQL-camelCase to pythons snake_case. Unfortunately most python IDEs won't support code-completition based on this alias-generator.

    Additionally, I noticed that class names can be generated in None-PascalCase if the corresponding GraphQL-Type is formatted accordingly.

    type lowerCaseFirstType --> class lowerCaseFirstType

    The pythonic way, would be to capitalize the first letter, no matter what. I understand, that this might cause confusion due to discrepancies with the schema or existing projects.

    But to generate

    Generate beautifully typed Operations, Enums,... (from Readme)

    this (optional) feature would be awesome.

    I know, that this might be a lot of work for some syntactic sugar (but: no pain no gain).

    opened by j-riebe 2
  • Escape reserved python keywords with alias

    Escape reserved python keywords with alias

    Keywords like from can't be used as field names.

    The pydantic-graphql-codegen package does it like this, (but not for all keywords):

    GQL

    type Foo {
      from: String!
    }
    

    PY

    class Foo(BaseModel)
        from_: str = Field(alias="from")
    
    opened by j-riebe 2
  • Thanks for code generation lib!

    Thanks for code generation lib!

    Hello, thanks for code generation lib for GrtaphQL! It is the real problem in python.

    I created the package for pydantic data-model generation from some GraphQL schema and for queries and mutations generation: https://github.com/denisart/graphql2python Docs: https://denisart.github.io/graphql2python/

    Queries generation examples: https://github.com/denisart/graphql-query Data-model generation examples: https://denisart.github.io/graphql2python/model.html

    I hope that graphql2python can be useful to you and the python GraphQL community.

    opened by denisart 0
  • Workaround for strawberry

    Workaround for strawberry

    I'm trying to make a Strawberry API out of Pydantic classes generated by turms using the experimental https://strawberry.rocks/docs/integrations/pydantic support.

    But I'm getting:

    TypeError: GetExternalRunsResult fields cannot be resolved. Unexpected type 'typing.Literal['ExternalRun']'
    

    which I think is the same problem as: https://github.com/strawberry-graphql/strawberry/issues/2317

    Any idea for a workaround?

    opened by huyz 6
  • Improve discoverability?

    Improve discoverability?

    I searched for python typings graphql schema on github and didn't find this project: https://github.com/search?q=python+typings+graphql+schema&type=repositories

    Not sure the graphql-codegen reference is useful in the description for users who have never used it.

    There is no link to the github repo on https://pypi.org/project/turms/ .

    The only reason I found this project is from the "shameless plug" at https://github.com/qw-in/graphql-codegen-pydantic/issues/10 :)

    opened by huyz 1
  • __future__.annotations simplifies generating Forward References

    __future__.annotations simplifies generating Forward References

    @jhnnsrs why do we even keep track of Forward Reference vs. direct call if we can just add

    from __future__ import annotations
    

    to the top of the file?

    We would still need to call update_forward_refs() but the registration process would be just adding the type to a set "blindly". The annotation could be always generated ast ast.Name().

    Example:

    from pydantic import BaseModel
    
    class Foo(BaseModel):
        bar: "Bar"
    
    class Bar(BaseModel):
        test: str
    
    Foo.update_forward_refs()
    

    vs.

    from __future__ import annotations
    from pydantic import BaseModel
    
    class Foo(BaseModel):
        bar: Bar
    
    class Bar(BaseModel):
        test: str
    
    Foo.update_forward_refs()
    
    opened by j-riebe 1
  • Refactor/reduce code duplication in plugins

    Refactor/reduce code duplication in plugins

    @jhnnsrs I think its time, that you take a look at this.

    The PR contains the refactoring of EnumsPlugin, InputsPlugin and ObjectsPlugin. I mainly focused on breaking down the large functions into smaller methods of each Plugin. Some code duplication is eliminated, the remaining similarities should be pretty obvious by now (as a preparation for the next refactoring iteration). There is a new module ast_generators that contains all concrete AST-Generation logic that was (duplicated a lot) in every plugin before.

    I have the feeling, that there also should be a stricter separation between the concerns:

    • Schema Parsing (Main job of the Plugin)
    • Registration (Which Objects/Enums/Inputs to include)
    • AST-Generation (Translate GraphQL Type to pydantic Model + Plugin-specific types like Operations/Funcs)
    • Custom Styling (influence ast-generation, not during class registration)

    Currently, each plugin does almost everything. Only the styling is somehow part of the ClassRegistry, were it shouldn't belong IMHO.

    Also, have a look at this test tests/plugins/test_objects.py::make_test_cases_union_field_value

    opened by j-riebe 11
  • [REFACTOR] Streamline codebase

    [REFACTOR] Streamline codebase

    Currenty, it's pretty hard to guess how the internal structure works. This is mainly due to the "superfunction" that generates each plugin code and the missing abstractions inside (which lead to a lot of duplication).

    • [x] ClassRegistry #29
    • [x] EnumsPlugin
    • [ ] FragementsPlugin
    • [ ] FuncsPlugin
    • [x] InputsPlugin
    • [x] ObjectsPlugin
    • [ ] OperationsPlugin
    opened by j-riebe 2
Owner
Johannes Roos
I am currently working on deep-learning assisted image analysis of super-resolution microscopy 🔬 :shipit: MD | PhD-Student
Johannes Roos
A Python 3.6+ port of the GraphQL.js reference implementation of GraphQL.

GraphQL-core 3 GraphQL-core 3 is a Python 3.6+ port of GraphQL.js, the JavaScript reference implementation for GraphQL, a query language for APIs crea

GraphQL Python 458 Dec 13, 2022
This is a graphql api build using ariadne python that serves a graphql-endpoint at port 3002 to perform language translation and identification using deep learning in python pytorch.

Language Translation and Identification this machine/deep learning api that will be served as a graphql-api using ariadne, to perform the following ta

crispengari 2 Dec 30, 2021
GraphQL security auditing script with a focus on performing batch GraphQL queries and mutations

BatchQL BatchQL is a GraphQL security auditing script with a focus on performing batch GraphQL queries and mutations. This script is not complex, and

Assetnote 267 Dec 24, 2022
A Django GraphQL Starter that uses graphene and graphene_django to interface GraphQL.

Django GraphQL Starter GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data... According to the doc

0101 Solutions 1 Jan 10, 2022
Ariadne is a Python library for implementing GraphQL servers using schema-first approach.

Ariadne Ariadne is a Python library for implementing GraphQL servers. Schema-first: Ariadne enables Python developers to use schema-first approach to

Mirumee Labs 1.9k Jan 1, 2023
A new GraphQL library for Python 🍓

Strawberry GraphQL Python GraphQL library based on dataclasses Installation ( Quick Start ) The quick start method provides a server and CLI to get go

Strawberry GraphQL 2.8k Jan 1, 2023
A library to help construct a graphql-py server supporting react-relay

Relay Library for GraphQL Python GraphQL-relay-py is the Relay library for GraphQL-core. It allows the easy creation of Relay-compliant servers using

GraphQL Python 143 Nov 15, 2022
GraphQL framework for Python

Graphene ?? Join the community on Slack We are looking for contributors! Please check the ROADMAP to see how you can help ❤️ The below readme is the d

GraphQL Python 7.5k Jan 1, 2023
GraphQL framework for Python

Graphene ?? Join the community on Slack We are looking for contributors! Please check the ROADMAP to see how you can help ❤️ The below readme is the d

GraphQL Python 7.5k Jan 1, 2023
GraphQL Engine built with Python 3.6+ / asyncio

Tartiflette is a GraphQL Server implementation built with Python 3.6+. Summary Motivation Status Usage Installation Installation dependencies Tartifle

tartiflette 839 Dec 31, 2022
A python graphql api, which serves ECB currency rates from last 90 days.

Exchange Rate Api using GraphQL Get Code git pull https://github.com/alaturqua/exchangerate-graphql.git Create .env file with following content and s

Isa 1 Nov 4, 2021
This is a simple Python that will parse instanceStats GraphQL Query into a CSV

GraphQL Python Labs - by Gabs the CSE Table of Contents About The Project Getting Started Prerequisites Installation and Usage Roadmap Contributing Li

Gabriel (Gabs) Cerioni 1 Oct 27, 2021
Pygitstats - a package that allows you to use the GitHub GraphQL API with ease in your Python programs

Pygitstats - a package that allows you to use the GitHub GraphQL API with ease in your Python programs

Dillon Barnes 4 Mar 29, 2022
tartiflette-aiohttp is a wrapper of aiohttp which includes the Tartiflette GraphQL Engine, do not hesitate to take a look of the Tartiflette project.

tartiflette-aiohttp is a wrapper of aiohttp which includes the Tartiflette GraphQL Engine. You can take a look at the Tartiflette API documentation. U

tartiflette 60 Nov 8, 2022
ASGI support for the Tartiflette GraphQL engine

tartiflette-asgi is a wrapper that provides ASGI support for the Tartiflette Python GraphQL engine. It is ideal for serving a GraphQL API over HTTP, o

tartiflette 99 Dec 27, 2022
GraphQL is a query language and execution engine tied to any backend service.

GraphQL The GraphQL specification is edited in the markdown files found in /spec the latest release of which is published at https://graphql.github.io

GraphQL 14k Jan 1, 2023
Integrate GraphQL into your Django project.

Graphene-Django A Django integration for Graphene. ?? Join the community on Slack Documentation Visit the documentation to get started! Quickstart For

GraphQL Python 4k Dec 31, 2022
Adds GraphQL support to your Flask application.

Flask-GraphQL Adds GraphQL support to your Flask application. Usage Just use the GraphQLView view from flask_graphql from flask import Flask from flas

GraphQL Python 1.3k Dec 31, 2022
Django registration and authentication with GraphQL.

Django GraphQL Auth Django registration and authentication with GraphQL. Demo About Abstract all the basic logic of handling user accounts out of your

pedrobern 301 Dec 9, 2022