Giter Club home page Giter Club logo

tartiflette's Introduction

Tartiflette

Quality Gate Status Total alerts Language grade: Python

Tartiflette is a GraphQL Server implementation built with Python 3.7+.

Summary

Motivation

Read this blogpost about our motivations TL; DR We reached the limits of Graphene, we wanted to build something which met certain requirements:

  • Offers a better developer experience that respects the Python mindset
  • Uses SDL (Schema Definition Language)
  • Uses asyncio as the sole execution engine
  • Be 100% open source

Status

The first milestone is behind us, we are now on the road to the milestone 2.

DNA

Discover Tartiflette with our fabulous tutorial on https://tartiflette.io/docs/tutorial/getting-started

Usage

import asyncio

from tartiflette import Resolver, create_engine

@Resolver("Query.hello")
async def resolver_hello(parent, args, ctx, info):
    return "hello " + args["name"]


async def run():
    engine = await create_engine(
        """
        type Query {
            hello(name: String): String
        }
        """
    )

    result = await engine.execute(
        query='query { hello(name: "Chuck") }'
    )

    print(result)
    # {'data': {'hello': 'hello Chuck'}}

if __name__ == "__main__":
    asyncio.run(run())

More details on the API Documentation

Installation

Tartiflette is available on pypi.org.

While the project depends on libgraphqlparser, wheels are provided since version 1.4.0, ensuring that no system dependency is required.

To install the library:

pip install tartiflette

Building from source

If you use a platform incompatible with the provided wheels, you'll need to install cmake to build libgraphqlparser in order to install the library.

macOS

brew install cmake

Debian/Ubuntu

apt-get install cmake

HTTP server implementations

tartiflette library itself is transport agnostic, but to simplify integration with existing HTTP servers, two different libraries are available:

Roadmaps

How to contribute to the documentation?

As you may know, the documentation is hosted on https://tartiflette.io. This fabulous website is built thanks to another amazing tool, docusaurus.

The content of the documentation is hosted in this repository, to be as close as possible to the code. You will find everything you need/want in the folder /docs.

How to run the website locally?

We built a docker image for the documentation (tartiflette/tartiflette.io on docker hub), which allow us to provide you an easy way to launch the documentation locally, without installing a specific version of node.

prerequisite:

  • Docker
  • Docker Compose
  • Make
make run-docs

Every change you will make in the /docs folder will be automatically hot reloaded. 🎉

tartiflette's People

Contributors

abusi avatar achedeuzot avatar alexchamberlain avatar coretl avatar davestone avatar dependabot-preview[bot] avatar dependabot[bot] avatar erezsh avatar florimondmanca avatar garyd203 avatar lilja-at-funnel avatar maximilien-r avatar mazzi avatar mkniewallner avatar tbekolay avatar tsunammis avatar yezz123 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tartiflette's Issues

The engine should raise a syntax error when a complex type is specified instead of one or more of his leaves

The engine doesn't raise an error when a complex type is specified as a leaf. We can't have a complex type used as a leaf.

e.g

type Query {
  mobility: Mobility
}

type Mobility {
  tramStations(offset: Int, limit: Int): [TramStation]
}

type Location {
  latitude: Float
  longitude: Float
}

type TramStation {
  id: String
  name: String
  line: String
  location: Location
}

This following query returns a dict for the location property instead of a syntax error.

query {
  mobility {
    tramStations {
      id
      name
      location
      line
    }
  }
}

returns

{
  "data": {
    "mobility": {
      "tramStations": [
        {
          "id": "P2",
          "name": "Place d'Arc",
          "location": {},
          "line": null
        },
        {
          "id": "P4",
          "name": "Patinoire",
          "location": {},
          "line": null
        },
        {
          "id": "P17",
          "name": "Pont de l'Europe",
          "location": {},
          "line": null
        }
      ]
    }
  }
}
  • Tartiflette version: 0.3.4
  • Python version: 3.7.1
  • Executed in docker: No
  • GraphQL Schema & Query: c.f above
  • Is a regression from a previous versions? No

Unable to load a directory which contains a list of SDL files

Hello,

The engine should be able to load the SDL from 4 ways.

  • Directly from a string
  • From a list of files
  • From a list of directories
  • From a tartiflette.Schema object

As specified in the API.

When the sdl parameter targets a folder.

Every file which ends by .sdl will be concatenated, in lexicographical order.

import tartiflette

engine = tartiflette.Engine(
    "/User/chuck/workspace/mytartiflette"
)

If I try to load the SDL from a directory, I get an exception.

  • Tartiflette version: 0.3.4
  • Python version: 3.7.1
  • Executed in docker: No
  • GraphQL Schema & Query: c.f below
  • Stacktrace c.f below

app.py

import os
from tartiflette import Resolver, Engine

@Resolver("Mobility.parkings")
async def resolver_hello(parent, args, ctx, info):
    pass

engine = Engine(
    os.path.dirname(os.path.abspath(__file__)) + "/sdl"
)

SDL

./sdl/default.gql

type Query {
  mobility: Mobility
}

type Mobility {
  parkings(offset: Int, limit: Int): [Parking]
}

type Parking {
  id: String
  name: String
  location: Location
  parkingSpace: ParkingSpace
}

type ParkingSpace {
  total: Int
  currentlyAvailable: Int
}

type Location {
  latitude: Float
  longitude: Float
}

Stacktrace

Traceback (most recent call last):
  File "/Users/s.chollet/.local/share/virtualenvs/open-data-graphql-orleans-kWiDIuqs/lib/python3.7/site-packages/tartiflette/schema/schema.py", line 255, in get_field_by_name
    return self._gql_types[object_name].find_field(field_name)
KeyError: 'Mobility'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/Users/s.chollet/workspace/p/life/projects/open-data-graphql-orleans/open_data_orleans/__main__.py", line 4, in <module>
    from open_data_orleans.app import run
  File "/Users/s.chollet/workspace/p/life/projects/open-data-graphql-orleans/open_data_orleans/app.py", line 43, in <module>
    os.path.dirname(os.path.abspath(__file__)) + "/sdl"
  File "/Users/s.chollet/.local/share/virtualenvs/open-data-graphql-orleans-kWiDIuqs/lib/python3.7/site-packages/tartiflette/engine.py", line 26, in __init__
    schema_name, custom_default_resolver, exclude_builtins_scalars
  File "/Users/s.chollet/.local/share/virtualenvs/open-data-graphql-orleans-kWiDIuqs/lib/python3.7/site-packages/tartiflette/schema/bakery.py", line 37, in bake
    schema = SchemaBakery._preheat(schema_name, exclude_builtins_scalars)
  File "/Users/s.chollet/.local/share/virtualenvs/open-data-graphql-orleans-kWiDIuqs/lib/python3.7/site-packages/tartiflette/schema/bakery.py", line 25, in _preheat
    obj.bake(schema)
  File "/Users/s.chollet/.local/share/virtualenvs/open-data-graphql-orleans-kWiDIuqs/lib/python3.7/site-packages/tartiflette/resolver/resolver.py", line 46, in bake
    field = schema.get_field_by_name(self._name)
  File "/Users/s.chollet/.local/share/virtualenvs/open-data-graphql-orleans-kWiDIuqs/lib/python3.7/site-packages/tartiflette/schema/schema.py", line 258, in get_field_by_name
    "field `{}` was not found in GraphQL schema.".format(name)
tartiflette.types.exceptions.tartiflette.UnknownSchemaFieldResolver: field `Mobility.parkings` was not found in GraphQL schema.

Redefining directives, scalars or resolvers doesn't raise any error

Decorating multiple time the same directives, scalars or resolvers name doesn't raise any error.

  • Tartiflette version: 0.3.2
  • Python version: 3.6.1
  • Executed in docker: No
  • Is a regression from a previous versions? No
type Dog {
  name: String!
  owner: Human
}

type Query {
  dog: Dog
}
# sample.py
from tartiflette import Resolver


@Resolver("Query.dog")
def first_query_dog_resolver(*_args, **_kwargs):
    return {"name": "Dog 1"}


@Resolver("Query.dog")
def second_query_dog_resolver(*_args, **_kwargs):
    return {"name": "Dog 2"}

This should raise an error since have multiple resolvers for the same node could lead to mistakes (since the resolver used during query execution will be the last discovered).

The same apply for directives and scalars.

Subscription operations with multiple root field doesn't raise any error

A GraphQL request containing non-unique named operation definition doesn't raise any error (cf. GraphQL spec):

interface Sentient {
  name: String!
}

interface Pet {
  name: String!
}

type Human implements Sentient {
  name: String!
}

type Dog implements Pet {
  name: String!
  owner: Human
}

type Query {
  dog: Dog
}

type Subscription {
  newDog: Dog
  newHuman: Human
}

Following requests should raise an error:

subscription Sub {
  newDog {
    name
  }
  newHuman {
    name
  }
}
fragment MultipleSubscriptionsFields on Subscription {
  newDog {
    name
  }
  newHuman {
    name
  }
}

subscription Sub {
  ...MultipleSubscriptionsFields
}
subscription Sub {
  newDog {
    name
  }
  __typename
}

(Directive API) remove `_` as a argument prefix

The Directive API exposes 4 methods:

  • on_field_execution
  • on_argument_execution
  • on_introspection
  • on_build

Some method's parameters are prefixed with an underscore, with the aim of avoiding the pylint unused warning.

In the Python community, a single underscore in front of a variable name (prefix) is a hint that a variable is meant for internal use only.

In our case, we want to provide these variables for developers who want to extend the capabilities of the engine. Thus, I suggest to remove these underscore and add some pylint comments to avoid the warnings.

class OnBuildDirective:
    @staticmethod
    def on_build(schema: "GraphQLSchema") -> None:
        pass


class OnExecutionDirective:
    @staticmethod
    async def on_field_execution(
        directive_args: Dict[str, Any],
        next_resolver: Callable,
        parent_result: Optional[Any],
        args: Dict[str, Any],
        ctx: Optional[Dict[str, Any]],
        info: "Info",
    ) -> Any:
        return await next_resolver(parent_result, args, ctx, info)

    @staticmethod
    async def on_argument_execution(
        directive_args: Dict[str, Any],
        next_directive: Callable,
        argument_definition: "GraphQLArgument",
        args: Dict[str, Any],
        ctx: Optional[Dict[str, Any]],
        info: "Info",
    ) -> Any:
        return await next_directive(argument_definition, args, ctx, info)


class OnIntrospectionDirective:
    @staticmethod
    def on_introspection(
        directive_args: Dict[str, Any],
        next_directive: Callable,
        introspected_element: Any,
        ctx: Optional[Dict[str, Any]],
        info: "Info",
    ) -> Any:
        return next_directive(introspected_element)

Miscellaneous Questions

First of all, I'm impressed by the amount of effort and code quality this project has. So congrats to all team that has worked towards that 👏

After setting up the project locally and trying it, I think there are some things that Graphene and GraphQL-core can learn from it, especially regarding Developer Experience generating the schema from SDL.

Questions

However, I have some questions that I would love to know the reasoning behind it:

  • This project almost clones all the type definitions necessary to have the GraphQL schema running. Most of this definitions are already existing in GraphQL-core, so I wonder what was the reason to duplicate the effort here (shouldn't be easier to at least reuse the data structures?). If the goal was to provide a much faster execution model, now there are better ways to do it in graphql-core (with custom backend).
  • For running a GraphQL query, the AST has to be re-visited at runtime for each Query. (Similar thing happens for GraphQL-core but it's quite optimized in Quiver) Do you think this will alleviate performance issues?
  • A schema is mutable, so resolvers can be attached after the fact of schema creation. GraphQL-core implementation of schemas are immutable (as same as any other definition), this is a great pattern to avoid parallelism issues and other nice things. But I'm curious to know if there are more reasonings (apart of plugging resolvers after schema creation) for a mutable schema.
  • All resolvers are async. Running everything in an event loop introduces significant overhead, why run in the event loop if the function can be resolved synchronously?

Benchmarking

This project also aims to be focused on performance first. However, after benchmarking some cases (such as the supported introspection query, and a hello world), the results are not much better than with GraphQL-core.

And here are the results (mean values):

  • Full Introspection Query: 8ms with Tartiflette / 6ms with Cached AST in GraphQL-core / 150us Quiver
  • Hello world Query: 240us (0.24ms) Tartiflette / 400us (0.4ms) GraphQL-Core / 12us Quiver

Tartiflette

Here is a snippet of how I benchmarked a Hello world query with Tartiflette:

def test_tartiflette_execute_basic_type_introspection_output(benchmark):
    schema_sdl = """
    type Query {
        hello: String
    }
    """

    ttftt = Tartiflette(schema_sdl, serialize_callback=lambda x: x) # So we don't add json-encoding overhead

    @Resolver("Query.hello", schema=ttftt.schema)
    async def func_field_resolver(*args, **kwargs):
        return "World"

    ttftt.schema.bake()

    async def execute():
        result = await ttftt.execute("query { hello }")

    loop = get_event_loop()
    from asyncio import Event, ensure_future

    def execute_async():
        loop.run_until_complete(execute())

    p = benchmark(execute_async)

GraphQL-core

def test_benchmark_graphql_core(benchmark):
    from graphql import (
        GraphQLSchema,
        GraphQLObjectType,
        GraphQLField,
        GraphQLString,
        GraphQLCoreBackend,
    )

    # from graphql_quiver import GraphQLQuiverBackend

    def func_field_resolver(*args, **kwargs):
        return "World"

    Query = GraphQLObjectType(
        "Query",
        fields={"hello": GraphQLField(GraphQLString, resolver=func_field_resolver)},
    )

    schema = GraphQLSchema(Query)
    backend = GraphQLCoreBackend()
    # backend = GraphQLQuiverBackend()
    document = backend.document_from_string(schema, query)

    def execute():
        result = document.execute()
        # print(result.data)

    loop = get_event_loop()

    def execute_async():
        loop.run_until_complete(execute())

    p = benchmark(execute)
    # execute_async()

I would love to join efforts together, especially regarding things that can be reused across projects, so companies don't duplicate efforts towards the same goals.

If possible, I would love to jump into a talk and chat about it :)

Keep up the good work!

Enhance customization imports

Currently, to be able to use custom scalar / directive / resolver & subscription, we need to manually import those elements at the same level as the engine initialization.

This is not very practical and forces to make imports that are not used.

Shouldn't we provide an API at engine initialization time that would take a module list to dynamically import ?

Schema and Operations directives

Directives on schema doesn't seems to be executed.

  • Tartiflette version: 0.4.0
  • Python version: 3.7.1
  • Executed in docker: No
  • Is a regression from a previous versions? No

SDL example:

directive @non_introspectable on SCHEMA | FIELD_DEFINITION

type Query {
  aField: String
}

schema @nonIntrospectable {
  query: Query
}

Directive:

import logging

from typing import Any, Callable, Dict

from tartiflette.directive import CommonDirective, Directive

logger = logging.getLogger(__name__)


@Directive("nonIntrospectable")
class NonIntrospectable(CommonDirective):
    @staticmethod
    def on_introspection(
        _directive_args: Dict[str, Any],
        _next_directive: Callable,
        _introspected_element: Any,
    ) -> None:
        logger.debug(">> NonIntrospectable.on_introspection")
        return None

When querying, the log is never triggered.

Missing required argument doesn't raise any error

A GraphQL request where there is missing required arguments on nodes or directives doesn't raise any error (cf. GraphQL spec):

  • Tartiflette version: 0.3.2
  • Python version: 3.6.1
  • Executed in docker: No
  • Is a regression from a previous versions? No

SDL Schema:

interface Pet {
  name: String!
}

enum CatCommand { JUMP }

type Cat implements Pet {
  name: String!
  doesKnowCommand(catCommand: CatCommand!): Boolean!
}

type Query {
  catById(id: Int!, isSold: Boolean! = false)
}

Following queries should raise errors:

query {
  catById {
    name
  }
}
query {
  catById(id: 1) @include {
    name
  }
}

Undefined arguments on nodes or directives doesn't raise any error

A GraphQL request containing undefined arguments on nodes or directives doesn't raise any error (cf. GraphQL spec):

  • Tartiflette version: 0.3.2
  • Python version: 3.6.1
  • Executed in docker: No
  • Is a regression from a previous versions? No

SDL Schema:

interface Pet {
  name: String!
}

enum DogCommand { SIT, DOWN, HEEL }

type Dog implements Pet {
  name: String!
  doesKnowCommand(dogCommand: DogCommand!): Boolean!
  isHousetrained(atOtherHomes: Boolean): Boolean!
}

type Query {
  dog: Dog
}

Following queries should raise errors:

query {
  dog(notId: 1) {
    name
  }
}
query {
  dog @deprecated(unless: false) {
    name
  }
}

"official" Introspection Query doesn't work

It seems that the official introspection Query doesn't work. Thus, we can't use the full potential of tools which use it to provide auto-completion, like insomnia, graphiql etc...

  • Tartiflette version: 0.3.2
  • Python version: 3.7.1
  • Executed in docker: No
  • Is a regression from a previous versions? No

GraphQL Query

query IntrospectionQuery {
  __schema {
    queryType {
      name
    }
    mutationType {
      name
    }
    subscriptionType {
      name
    }
    types {
      ...FullType
    }
    directives {
      name
      locations
      args {
        ...InputValue
      }
    }
  }
}

fragment FullType on __Type {
  kind
  name
  fields(includeDeprecated: true) {
    name
    args {
      ...InputValue
    }
    type {
      ...TypeRef
    }
    isDeprecated
    deprecationReason
  }
  inputFields {
    ...InputValue
  }
  interfaces {
    ...TypeRef
  }
  enumValues(includeDeprecated: true) {
    name
    isDeprecated
    deprecationReason
  }
  possibleTypes {
    ...TypeRef
  }
}

fragment InputValue on __InputValue {
  name
  type {
    ...TypeRef
  }
  defaultValue
}

fragment TypeRef on __Type {
  kind
  name
  ofType {
    kind
    name
    ofType {
      kind
      name
      ofType {
        kind
        name
        ofType {
          kind
          name
          ofType {
            kind
            name
            ofType {
              kind
              name
              ofType {
                kind
                name
              }
            }
          }
        }
      }
    }
  }
}

Stacktrace

{
  "data": null,
  "errors": [
    {
      "message": "Undefined fragment < FullType >.",
      "path": null,
      "locations": [
        {
          "line": 7,
          "column": 11
        }
      ]
    },
    {
      "message": "field `__Type.directives` was not found in GraphQL schema.",
      "path": [
        "__schema",
        "types",
        "directives"
      ],
      "locations": [
        {
          "line": 9,
          "column": 9
        }
      ]
    },
    {
      "message": "Undefined fragment < InputValue >.",
      "path": null,
      "locations": [
        {
          "line": 13,
          "column": 13
        }
      ]
    },
    {
      "message": "Fragment < TypeRef > is never used.",
      "path": null,
      "locations": [
        {
          "line": 53,
          "column": 5
        }
      ]
    }
  ]
}

More than one operation along an anonymous operation doesn't raise any error

A GraphQL request containing more than one operation along an anonymous operation doesn't raise any error (cf. GraphQL spec):

interface Sentient {
  name: String!
}

interface Pet {
  name: String!
}

type Human implements Sentient {
  name: String!
}

type Dog implements Pet {
  name: String!
  owner: Human
}

type Query {
  dog: Dog
}
{
  dog {
    name
  }
}

query getName {
  dog {
    owner {
      name
    }
  }
}

The `execute` method doesn't allow to have an `initial_value`

The execute method should have an optional initial_value parameter (cf. GraphQL spec.

An initial value corresponding to the root type being executed. Conceptually, an initial value represents the “universe” of data available via a GraphQL Service. It is common for a GraphQL Service to always use the same initial value for every request.

  • Tartiflette version: 0.3.6
  • Python version: 3.6.1
  • Executed in docker: No
  • Is a regression from a previous versions? No

Subscription without dedicated resolver returns `null`

When using subscriptions without a dedicated resolver the result is always null.

  • Tartiflette version: 0.4.0
  • Python version: 3.7.1
  • Executed in docker: No
  • Is a regression from a previous versions? No

SDL Schema:

type Subscription {
  countdown(startAt: Int!): Int!
}

Following queries:

subscription {
  countdown(startAt: 10)
}

With:

import asyncio

from tartiflette import Subscription


@Subscription("Subscription.countdown")
async def subscription_countdown_subscription(
    parent_result, args, ctx, info
):
    countdown = args["startAt"]
    while countdown > 0:
        yield countdown
        countdown -= 1
        await asyncio.sleep(1)
    yield 0

Result to an error because countdown is null.

Improve query parsing and validation

It might be interesting to change the way queries are parsed and validated.

Currently we use the libgraphqlparser to browse the nodes of the query using callback functions that are called each time the parser encounters a new node in the document. At each node encountered the callback function generates a VisitorElement object which is then passed when calling the update method of the Visitor instance which is responsible of business logic (validation, addition of information to the visitor context, creation of Node object...).

These different processes are complex and long to execute. The addition of the management of new errors gradually make the code less and less readable and more difficult to maintain.

To tackle this problem, we could dissociate this unique treatment into separate steps:

  1. Parse the query to generate a DocumentNode object containing all the information related to the query.
    This step can be easily done by calling the graphql_ast_to_json libgraphqlparser function which analyzes the query and returns a string in JSON format containing all the information related to the query. This string could then be computed into a DocumentNode object.
  2. Apply the set of validation rules defined by the GraphQL specification to this DocumentNode object.

Proceed in this way to several advantages:

  1. Allow to cache for each query the generation of the DocumentNode and the application of validation rules.
  2. Delete a lot of code (Visitor, VisitorContext & most of the code contained in cffi parser) and gain in simplicity/readability/maintainability.
  3. Have a DocumentNode object conforming to the GraphQL language specifications. This would allow to be more faithful to the specifications but also to facilitate the on-boarding of new collaborators.
  4. Be closer to the specification and allow to deal with some particular case still poorly managed currently (such as the merge of SelectionSet).

TODO

  • Implements functions which parse a SDL or query string to a DocumentNode instance
  • Cache SDL/query parsing & validation
  • Use DocumentNode to build schema
  • Use DocumentNode to execute query

Should execute only the specified operation

A GraphQL request containing multiple operation definition shouldn't execute all of them but only the one specified (cf. GraphQL spec):

  • Tartiflette version: 0.3.6
  • Python version: 3.6.1
  • Executed in docker: No
  • Is a regression from a previous versions? No

SDL Schema:

type Dog {
  name: String!
}

type Human {
  name: String!
}

type Query {
  cat: Cat
  human: Human
}

Following queries:

query Dog {
  dog {
    name
  }
}

query Human {
  human {
    name
  }
}

Should result to:

{
  "data": {
    "dog": {
      "name": "***"
    }
  }
}

If the operation_name requested is Dog. If the requested operation_name doesn't exists an error should be raised.

Directives on argument raise exception

Applying directives on arguments raise an exception.

  • Tartiflette version: 0.4.0
  • Python version: 3.7.1
  • Executed in docker: No
  • Is a regression from a previous versions? No

SDL example:

directive @maxLength(
  limit: Int!
) on ARGUMENT_DEFINITION

type Query {
  search(
    query: String @maxLength(limit: 512)
  ): [String]
}

Result in:

Traceback (most recent call last):
  File "/usr/local/lib/python3.7/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/local/lib/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/usr/src/app/***/__main__.py", line 10, in <module>
    sys.exit(run())
  File "/usr/src/app/***/app.py", line 425, in run
    "utils/sdl-generator/schema.sdl",
  File "/usr/src/app/***/engines/tartiflette.py", line 70, in __init__
    error_coercer=_error_coercer,
  File "/usr/local/lib/python3.7/site-packages/tartiflette/engine.py", line 26, in __init__
    schema_name, custom_default_resolver, exclude_builtins_scalars
  File "/usr/local/lib/python3.7/site-packages/tartiflette/schema/bakery.py", line 41, in bake
    schema = SchemaBakery._preheat(schema_name, exclude_builtins_scalars)
  File "/usr/local/lib/python3.7/site-packages/tartiflette/schema/bakery.py", line 21, in _preheat
    build_graphql_schema_from_sdl(sdl, schema=schema)
  File "/usr/local/lib/python3.7/site-packages/tartiflette/sdl/builder.py", line 30, in build_graphql_schema_from_sdl
    sdl, parse_graphql_sdl_to_ast(sdl), schema=schema
  File "/usr/local/lib/python3.7/site-packages/tartiflette/sdl/builder.py", line 76, in transform_ast_to_schema
    transformer.transform(raw_ast)
  File "/usr/local/lib/python3.7/site-packages/lark/visitors.py", line 93, in transform
    tree = t.transform(tree)
  File "/usr/local/lib/python3.7/site-packages/lark/visitors.py", line 107, in transform
    subtree.children = list(self._transform_children(subtree.children))
  File "/usr/local/lib/python3.7/site-packages/lark/visitors.py", line 44, in _transform_children
    yield self._transform_tree(c) if isinstance(c, Tree) else c
  File "/usr/local/lib/python3.7/site-packages/lark/visitors.py", line 103, in _transform_tree
    return self._call_userfunc(tree)
  File "/usr/local/lib/python3.7/site-packages/lark/visitors.py", line 37, in _call_userfunc
    return f(tree)
  File "/usr/local/lib/python3.7/site-packages/lark/visitors.py", line 232, in f
    return _f(self, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/tartiflette/sdl/transformers/schema_transformer.py", line 389, in input_value_definition
    child, child.__class__.__name__
tartiflette.types.exceptions.tartiflette.UnexpectedASTNode: Unexpected AST node `SchemaNode(type='directives', value={'maxLength': {'limit': 512}})`, type `SchemaNode`

(SDL / Execution) Handle "input" type for mutation

Hello,

As specified in the specification, we have to use the type input type for complex inputs in mutation, instead of using the regular Object type which can contain interface, union or arguments.

What we should do

input RecipeInput {
  id: Int
  name: String
  cookingTime: Int
}

type Mutation {
  updateRecipe(input: RecipeInput): Recipe
}

instead of

type RecipeInput {
  id: Int
  name: String
  cookingTime: Int
}

type Mutation {
  updateRecipe(input: RecipeInput): Recipe
}

Request sample

mutation {
  updateRecipe(input: {
        id: 1, 
        name: "The best Tartiflette by Eric Guelpa", 
        cookingTime: 12
  }) {
    id
    name
    cookingTime
  }
}
  • Tartiflette version: 0.4.0
  • Python version: 3.7.1
  • Executed in docker: No
  • Is a regression from a previous versions? No

Handle multiple exceptions

In some cases, it may be relevant to not only raise the first exception encountered and allow multiple exceptions to be handled at one time.

In the case of directives and in particular on_argument_execution the fact of handling several errors could be interesting:

type Query {
    search(
        query: String! @maxLength(limit: 50)
        page: Int! = 1 @maxValue(limit: 10)
    )
}

Currently, if the directives for these two arguments raise an exception, only the first raised exception will be returned as an error (bypassing the second). But it could be useful to return both errors at the same time.

Improve type resolving

Since tartiflette allow us to deal only with dict. Resolving the __typename field for an UnionType is difficult and is delegate to the Resolver implementation which need to return the __typename in the resolved value.

Maybe we could add an optional resolve_type argument to the Resolver decorator which could take a callable in charge of determining the __typename to return for the resolved value? This resolve_type parameter could be required for an UnionType resolver which returns a value where the __typename couldn't be resolved by the default resolve_type function (a dict without a __typename key for instance).

@Resolver(
    "Query.catOrDog",
    resolve_type=lambda result: "Dog" if is_dog(result) else "Cat",
)
def query_cat_or_dog_resolver(*_args, **_kwargs):
    return {
        "name": "Dogo",
    }

Low-hanging fruits

Hello, thanks for this amazing project 👋 Would you mind spot some simple, easy bugs for new-comers to ramp up with them?

Schema validation should refuse inputs which are not of type inputType

  • Tartiflette version: 0.6.5

While working on a project I made a mistake in a Mutation where I referenced an Object Type as an Input Type and the engine started normally.

Then when trying to use this I got:

✔ Loading Apollo Project 
✖ Saving schema to schema.json 
→ Introspection must provide input type for arguments, but received: AuthenticationType!. Error: Introspection must provide input type for arguments, but received: AuthenticationType!. at invariant (/usr/local/lib/node_modules/apollo/node_modules/graphql/jsutils/invariant.js:19:11) at getInputType (/usr/local/lib/node_modules/apollo/node_modules/graphql/utilities/buildClientSchema.js:115:66) at buildInputValue (/usr/local/lib/node_modules/apollo/node_modules/graphql/utilities/buildClientSchema.js:275:16) at /usr/local/lib/node_modules/apollo/node_modules/graphql/jsutils/keyValMap.js:36:31 at Array.reduce (<anonymous>) at keyValMap (/usr/local/lib/node_modules/apollo/node_modules/graphql/jsutils/keyValMap.js:35:15) at buildInputValueDefMap (/usr/local/lib/node_modules/apollo/node_modules/graphql/utilities/buildClientSchema.js:269:35) at /usr/local/lib/node_modules/apollo/node_modules/graphql/utilities/buildClientSchema.js:263:15 at /usr/local/lib/node_modules/apollo/node_modules/graphql/jsutils/keyValMap.js:36:31 at Array.reduce (<anonymous>)

So I guess we should validate that objects referenced as inputs are of type inputType and raise an error if that's not the case

Thanks!

Not unique argument on same node / directive doesn't raise any error

A GraphQL request containing multiple times the same argument on a node or directive doesn't raise any error (cf. GraphQL spec):

  • Tartiflette version: 0.3.2
  • Python version: 3.6.1
  • Executed in docker: No
  • Is a regression from a previous versions? No

SDL Schema:

interface Pet {
  name: String!
}

enum CatCommand { JUMP }

type Cat implements Pet {
  name: String!
  doesKnowCommand(catCommand: CatCommand!): Boolean!
}

type Query {
  cat: Cat
}

Following queries should raise errors:

{
  cat {
    name
    doesKnowCommand(catCommand: JUMP, catCommand: JUMP)
  }
}
{
  cat {
    name @include(if: true, if: false)
    doesKnowCommand
  }
}

Non-unique named operation definition doesn't raise any error

A GraphQL request containing non-unique named operation definition doesn't raise any error (cf. GraphQL spec):

interface Sentient {
  name: String!
}

interface Pet {
  name: String!
}

type Human implements Sentient {
  name: String!
}

type Dog implements Pet {
  name: String!
  owner: Human
}

type MutateDogPayload {
  id: String
}

type Query {
  dog: Dog
}

type Mutation {
  mutateDog: MutateDogPayload
}
query getName {
  dog {
    name
  }
}

query getName {
  dog {
    owner {
      name
    }
  }
}
query dogOperation {
  dog {
    name
  }
}

mutation dogOperation {
  mutateDog {
    id
  }
}

Invalid path/type on unknown nodes

When requesting unknown fields on a GraphQL query, the path contained on errors doesn't seems to be properly re-calculated. Also, the type from the error message is invalid too:

type UserStatsViews {
  total: Int
}

type UserStats {
  views: UserStatsViews
}

type User {
  name: String
  stats: UserStats
}

type Query {
  viewer: User
}

Query example:

query {
  viewer {
    name
    stats {
      views {
        total
        unknownField4
      }
      unknownField3
    }
    unknownField2
  }
  unknownField1
}

Result:

{
  "data": null,
  "errors": [
    {
      ...
      "path": [
        "viewer",
        "stats",
        "views",
        "unknownField4"
      ]
    },
    {
      ...
      "path": [
        "viewer",
        "stats",
        "views",
        "unknownField4",
        "unknownField3"
      ]
    },
    {
      ...
      "path": [
        "viewer",
        "stats",
        "views",
        "unknownField4",
        "unknownField3",
        "unknownField2"
      ]
    },
    {
      ...
      "path": [
        "viewer",
        "stats",
        "views",
        "unknownField4",
        "unknownField3",
        "unknownField2",
        "unknownField1"
      ]
    },
  ]
}

The result is different depending on using fragment or not.

Enum values aren't processed

Enum values aren't processed and not available through the args parameter on resolver functions.

  • Tartiflette version: 0.3.2
  • Python version: 3.6.1
  • Executed in docker: No
  • Is a regression from a previous versions? No

SDL Schema:

interface Pet {
  name: String!
}

enum DogCommand { SIT, DOWN, HEEL }

type Dog implements Pet {
  name: String!
  doesKnowCommand(dogCommand: DogCommand!): Boolean!
}

type Query {
  dog: Dog
}

Resolver:

from tartiflette import Resolver


@Resolver("Query.dog")
async def query_dog_resolver(*_args, **__kwargs):
    return {
        "name": "Doggy",
    }


@Resolver("Dog.doesKnowCommand")
async def dog_does_know_command_resolver(_parent_result, args, *__args, **___kwargs):
    return args["dogCommand"] == "SIT"

Following query:

query {
  dog {
    name
    doesKnowCommand(dogCommand: SIT)
  }
}

Should be equal too:

{
  "data": {
    "dog": {
      "name": "Doggy",
      "doesKnowCommand": true
    }
  }
}

Also, args parameter (on dog_does_know_command_resolver) should be equal to {"dogCommand": "SIT"} instead of {} actually.

(Execution) unable to return "null" on nullable field

Hello,

It seems that if a client request a node wchich is nullable, if the resoler ties to the field return None, the engine will return the leaf set at null instead of null for the entire object.

Schema

type Query {
  recipes: [Recipe]
  recipe(id: Int!): Recipe
}

enum IngredientType {
    GRAM
    LITER
    UNITY
}

type IngredientQuantity {
    name: String!
    quantity: Float!,
    type: IngredientType!
}

type Recipe {
  id: Int
  name: String
  ingredientsQuantity: [IngredientQuantity]
}

Query

# Write your query or mutation here

{
  recipe(id: 2) {
    id
    name
    ingredientsQuantity {
      name
      quantity
      type
    }
  }
}

Response

{
  "data": {
    "recipe": {
      "id": null,
      "name": null,
      "ingredientsQuantity": null
    }
  }
}

I expect this response

{
  "data": {
    "recipe": null
  }
}
  • Tartiflette version: 0.4.0
  • Python version: 3.7.1
  • Executed in docker: No

Nested nodes on fragment aren't resolved

Nested nodes on fragment aren't resolved. For instance, on this query, owner node on RepositoryFields fragment will returns {}:

fragment RepositoryFields on Repository {
  name
  owner {
    login
  }
}

query {
  viewer {
    repositories(first: 10) {
      edges {
        node {
          ...RepositoryFields
        }
      }
    }
  }
}

The issue is located here:
https://github.com/dailymotion/tartiflette/blob/0df0e32dfb2d73581918e0359a853e19802c7b28/tartiflette/parser/nodes/field.py#L63-L67

When we get into a Fragment, we set the type_condition attribute for all its nodes to the value of the fragment type (here Repository). Thus, for owner node, the condition will be false since owner is type of RepositoryOwner and not Repository (meaning that the condition will be "Repository" == "RepositoryOwner").

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.