Giter VIP home page Giter VIP logo

sgqlc's Introduction

sgqlc - Simple GraphQL Client

https://coveralls.io/repos/github/profusion/sgqlc/badge.svg?branch=master

Introduction

This package offers an easy to use GraphQL client. It's composed of the following modules:

  • sgqlc.types: declare GraphQL in Python, base to generate and interpret queries. Submodule sgqlc.types.datetime will provide bindings for datetime and ISO 8601, while sgqlc.types.relay will expose Node, PageInfo and Connection.
  • sgqlc.operation: use declared types to generate and interpret queries.
  • sgqlc.endpoint: provide access to GraphQL endpoints, notably sgqlc.endpoint.http provides HTTPEndpoint using urllib.request.urlopen().

What's GraphQL?

Straight from http://graphql.org:

A query language for your API

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.

It was created by Facebook based on their problems and solutions using REST to develop applications to consume their APIs. It was publicly announced at React.js Conf 2015 and started to gain traction since then. Right now there are big names transitioning from REST to GraphQL: Yelp Shopify and GitHub, that did an excellent post to explain why they changed.

A short list of advantages over REST:

  • Built-in schema, with documentation, strong typing and introspection. There is no need to use Swagger or any other external tools to play with it. Actually GraphQL provides a standard in-browser IDE for exploring GraphQL endpoints: https://github.com/graphql/graphiql;
  • Only the fields that you want. The queries must explicitly select which fields are required, and that's all you're getting. If more fields are added to the type, they won't break the API, since the new fields won't be returned to old clients, as they didn't ask for such fields. This makes much easier to keep APIs stable and avoids versioning. Standard REST usually delivers all available fields in the results, and when new fields are to be included, a new API version is added (reflected in the URL path, or in an HTTP header);
  • All data in one request. Instead of navigating hypermedia-driven RESTful services, like discovering new "_links": {"href"... and executing a new HTTP request, with GraphQL you specify nested queries and let the whole navigation be done by the server. This reduces latency a lot;
  • The resulting JSON object matches the given query exactly; if you requested { parent { child { info } } }, you're going to receive the JSON object {"parent": {"child": {"info": value }}}.

From GitHub's Migrating from REST to GraphQL one can see these in real life:

$ curl -v https://api.github.com/orgs/github/members
[
  {
    "login": "...",
    "id": 1234,
    "avatar_url": "https://avatars3.githubusercontent.com/u/...",
    "gravatar_id": "",
    "url": "https://api.github.com/users/...",
    "html_url": "https://github.com/...",
    "followers_url": "https://api.github.com/users/.../followers",
    "following_url": "https://api.github.com/users/.../following{/other_user}",
    "gists_url": "https://api.github.com/users/.../gists{/gist_id}",
    "starred_url": "https://api.github.com/users/.../starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/.../subscriptions",
    "organizations_url": "https://api.github.com/users/.../orgs",
    "repos_url": "https://api.github.com/users/.../repos",
    "events_url": "https://api.github.com/users/.../events{/privacy}",
    "received_events_url": "https://api.github.com/users/.../received_events",
    "type": "User",
    "site_admin": true
  },
  ...
]

brings the whole set of member information, however you just want name and avatar URL:

query {
  organization(login:"github") { # select the organization
    members(first: 100) {        # then select the organization's members
      edges {  # edges + node: convention for paginated queries
        node {
          name
          avatarUrl
        }
      }
    }
  }
}

Likewise, instead of 4 HTTP requests:

curl -v https://api.github.com/repos/profusion/sgqlc/pulls/9
curl -v https://api.github.com/repos/profusion/sgqlc/pulls/9/commits
curl -v https://api.github.com/repos/profusion/sgqlc/issues/9/comments
curl -v https://api.github.com/repos/profusion/sgqlc/pulls/9/reviews

A single GraphQL query brings all the needed information, and just the needed information:

query {
  repository(owner: "profusion", name: "sgqlc") {
    pullRequest(number: 9) {
      commits(first: 10) { # commits of profusion/sgqlc PR #9
        edges {
          node { commit { oid, message } }
        }
      }
      comments(first: 10) { # comments of profusion/sgqlc PR #9
        edges {
          node {
            body
            author { login }
          }
        }
      }
      reviews(first: 10) { # reviews of profusion/sgqlc/ PR #9
        edges { node { state } }
      }
    }
  }
}

Motivation to create sgqlc

As seen above, writing GraphQL queries is very easy, and it is equally easy to interpret the results. So what was the rationale to create sgqlc?

  • GraphQL has its domain-specific language (DSL), and mixing two languages is always painful, as seen with SQL + Python, HTML + Python... Being able to write just Python in Python is much better. Not to say that GraphQL naming convention is closer to Java/JavaScript, using aNameFormat instead of Python's a_name_format.
  • Navigating dict-of-stuff is a bit painful: d["repository"]["pullRequest"]["commits"]["edges"]["node"], since these are valid Python identifiers, we better write: repository.pull_request.commits.edges.node.
  • Handling new scalar types. GraphQL allows one to define new scalar types, such as Date, Time and DateTime. Often these are serialized as ISO 8601 strings and the user must parse them in their application. We offer sgqlc.types.datetime to automatically generate datetime.date, datetime.time and datetime.datetime.
  • Make it easy to write dynamic queries, including nested. As seen, GraphQL can be used to fetch lots of information in one go; however if what you need (arguments and fields) changes based on some variable, such as user input or cached data, then you need to concatenate strings to compose the final query. This can be error prone and servers may block you due to invalid queries. Some tools "solve" this by parsing the query locally before sending it to server. However usually the indentation is screwed and reviewing it is painful. We change that approach: use sgqlc.operation.Operation and it will always generate valid queries, which can be printed out and properly indented. Bonus point is that it can be used to later interpret the JSON results into native Python objects.
  • Usability improvements whenever needed. For instance Relay published their Cursor Connections Specification and its widely used. To load more data, you need to extend the previous data with newly fetched information, updating not only the nodes and edges, but also page information. This is done automatically by sgqlc.types.relay.Connection.

It also helps with code-generation, sgqlc-codegen can generate both the classes matching a GraphQL Schema or functions to return sgqlc.operation.Operation based on executable documents GraphQL Domain Specific Language (DSL).

Installation

Automatic:

pip install sgqlc

From source using pip:

pip install .

Usage

To reach a GraphQL endpoint using synchronous HTTPEndpoint with a hand-written query (see more at examples/basic/01_http_endpoint.py):

from sgqlc.endpoint.http import HTTPEndpoint

url = 'http://server.com/graphql'
headers = {'Authorization': 'bearer TOKEN'}

query = 'query { ... }'
variables = {'varName': 'value'}

endpoint = HTTPEndpoint(url, headers)
data = endpoint(query, variables)

However, writing GraphQL queries and later interpreting the results may be cumbersome. That's solved by our sgqlc.types, which is usually paired with sgqlc.operation to generate queries and then interpret results (see more at examples/basic/02_schema_types.py). The example below matches a subset of GitHub API v4. In GraphQL syntax it would be:

query {
  repository(owner: "profusion", name: "sgqlc") {
    issues(first: 100) {
      nodes {
        number
        title
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
}

The output JSON object is:

{
  "data": {
    "repository": {
      "issues": {
        "nodes": [
          {"number": 1, "title": "..."},
          {"number": 2, "title": "..."}
        ]
      },
      "pageInfo": {
         "hasNextPage": false,
         "endCursor": "..."
      }
    }
  }
}
from sgqlc.endpoint.http import HTTPEndpoint
from sgqlc.types import Type, Field, list_of
from sgqlc.types.relay import Connection, connection_args
from sgqlc.operation import Operation

# Declare types matching GitHub GraphQL schema:
class Issue(Type):
    number = int
    title = str

class IssueConnection(Connection):  # Connection provides page_info!
    nodes = list_of(Issue)

class Repository(Type):
    issues = Field(IssueConnection, args=connection_args())

class Query(Type):  # GraphQL's root
    repository = Field(Repository, args={'owner': str, 'name': str})

# Generate an operation on Query, selecting fields:
op = Operation(Query)
# select a field, here with selection arguments, then another field:
issues = op.repository(owner=owner, name=name).issues(first=100)
# select sub-fields explicitly: { nodes { number title } }
issues.nodes.number()
issues.nodes.title()
# here uses __fields__() to select by name (*args)
issues.page_info.__fields__('has_next_page')
# here uses __fields__() to select by name (**kwargs)
issues.page_info.__fields__(end_cursor=True)

# you can print the resulting GraphQL
print(op)

# Call the endpoint:
data = endpoint(op)

# Interpret results into native objects
repo = (op + data).repository
for issue in repo.issues.nodes:
    print(issue)

Why double-underscore and overloaded arithmetic methods?

Since we don't want to clobber GraphQL fields, we cannot provide nicely named methods. Therefore we use overloaded methods such as __iadd__, __add__, __bytes__ (compressed GraphQL representation) and __str__ (indented GraphQL representation).

To select fields by name, use __fields__(*names, **names_and_args). This helps with repetitive situations and can be used to "include all fields", or "include all except...":

# just 'a' and 'b'
type_selection.__fields__('a', 'b')
type_selection.__fields__(a=True, b=True) # equivalent

# a(arg1: value1), b(arg2: value2):
type_selection.__fields__(
    a={'arg1': value1},
    b={'arg2': value2})

# selects all possible fields
type_selection.__fields__()

# all but 'a' and 'b'
type_selection.__fields__(__exclude__=('a', 'b'))
type_selection.__fields__(a=False, b=False)

Code Generator

Manually converting an existing GraphQL schema to sgqlc.types subclasses is boring and error prone. To aid such task we offer a code generator that outputs a Python module straight from JSON of an introspection call:

user@host$ python3 -m sgqlc.introspection \
     --exclude-deprecated \
     --exclude-description \
     -H "Authorization: bearer ${GH_TOKEN}" \
     https://api.github.com/graphql \
     github_schema.json
user@host$ sgqlc-codegen schema github_schema.json github_schema.py

This generates github_schema that provides the sgqlc.types.Schema instance of the same name github_schema. Then it's a matter of using that in your Python code, as in the example below from examples/github/github_agile_dashboard.py:

from sgqlc.operation import Operation
from github_schema import github_schema as schema

op = Operation(schema.Query)  # note 'schema.'

# -- code below follows as the original usage example:

# select a field, here with selection arguments, then another field:
issues = op.repository(owner=owner, name=name).issues(first=100)
# select sub-fields explicitly: { nodes { number title } }
issues.nodes.number()
issues.nodes.title()
# here uses __fields__() to select by name (*args)
issues.page_info.__fields__('has_next_page')
# here uses __fields__() to select by name (**kwargs)
issues.page_info.__fields__(end_cursor=True)

# you can print the resulting GraphQL
print(op)

# Call the endpoint:
data = endpoint(op)

# Interpret results into native objects
repo = (op + data).repository
for issue in repo.issues.nodes:
    print(issue)

You can also generate these operations given a GraphQL Domain Specific Language (DSL) operation:

# sample_operations.gql

query ListIssues($owner: String!, $name: String!) {
    repository(owner: $owner, name: $name) {
        issues(first: 100) {
            nodes {
                number
                title
            }
            pageInfo {
                hasNextPage
                endCursor
            }
        }
    }
}
user@host$ sgqlc-codegen operation \
   --schema github_schema.json \
   github_schema \
   sample_operations.py \
   sample_operations.gql

This generates sample_operations.py that provides the Operation. Then it's a matter of using that in your Python code, as in the example below from examples/github/github-agile-dashboard.py:

from sample_operations import Operations

op = Operations.query.list_issues

# you can print the resulting GraphQL
print(op)

# Call the endpoint:
data = endpoint(op, {'owner': owner, 'name': name})

# Interpret results into native objects
repo = (op + data).repository
for issue in repo.issues.nodes:
    print(issue)

Authors

License

sgqlc is licensed under the ISC.

Getting started developing

You need to use poetry.

poetry install --all-extras --with dev
poetry shell

Install the pre-commit:

pre-commit install -f

Run the tests (one of the below):

pre-commit run -a            # run all tests: flake8, pytest, ...
pre-commit run -a flake8     # run only flake8
pre-commit run -a tests      # run only pytest (unit tests)

Keep 100% coverage. You can look at the coverage report at cover/index.html. To do that, prefer doctest so it serves as both documentation and test. However we use pytest to write explicit tests that would be hard to express using doctest.

Build and review the generated Sphinx documentation, and validate if your changes look right:

sphinx-build doc/source doc/build
open doc/build/html/index.html

To integrate changes from another branch, please rebase instead of creating merge commits ( read more).

Public Schemas

The following repositories provides public schemas generated using sgqlc-codegen:

sgqlc's People

Contributors

artran avatar barbieri avatar dependabot[bot] avatar dzhu avatar goodes avatar jean avatar jkbak avatar josephlbarnett avatar joycex99 avatar jsbueno avatar kgadek avatar lubo avatar metachris avatar moorereason avatar n-wach avatar neetabirajdar avatar netogallo avatar petrus-jvrensburg avatar phfix avatar reconman avatar richarddrj avatar sebastiandev avatar trmtab avatar volga avatar xuluwarrior 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

sgqlc's Issues

PyPi Version

Hi, was wondering if there was a plan/development schedule for releasing updates on PyPi? The last update on PyPi was in February and it seems a bit out of date.

Getting agrs values from Operation object

Hello is there way to get the argument values from generated Operation object?
For example:

>>> op = Operation(Query)
>>> repository = op.repository(id='repo1')
>>> repository.issues.number()
number
>>> repository.issues.title()
title
>>> op # or repr(), prints out GraphQL!
query {
  repository(id: "repo1") {
    issues {
      number
      title
    }
  }
}

If I use op in other class, how can i acces the repository id value object?

When mutation takes a list as an argument - passing in a list of the type does not work

I have an input type that takes a list as an argument. I haven't seen an example of this use case so I just guessed that I could pass in a python list of the right type. But I get the following error:

AttributeError: 'MyInput' object has no attribute 'items'

And, in fact, it is expecting a dict. Here's an edited version of the code I use to run the mutation:

list_arg = [
    gql.MyInput({"key1":"val1"})
    gql.MyInput({"key1":"val2"})
]

op.my_mutation(my_list=list_arg)

I'm assuming that passing a simple list into the argument is not the right way to go about it, but I'm not sure how to construct the list otherwise.

Thoughts?

Introspection overrides Accept header

Related to #49

When making queries to the GitHub API to use the VulnerabilityAlerts it is necessary to specify a custom Accept header.

When specifying on the HTTPEndpoint it is overridden so the header cannot be specified.

Change the behaviour to add if it doesnt exist.

AttributeError: 'list' object has no attribute 'get' on when mapping json back to objects

Given

from sgqlc.endpoint.http import HTTPEndpoint
from sgqlc.operation import Operation
from github_schema import github_schema as schema, PullRequest, SearchType

url = 'https://api.github.com/graphql'
headers = {'Authorization': 'blah'}

op = Operation(schema.Query)

prs = op.search(first=100, type=SearchType("ISSUE"), query="blah")
prs.nodes.__as__(schema.PullRequest).number()

endpoint = HTTPEndpoint(url, headers)
data = endpoint(op)

search_result = (op + data).search
for pr in search_result.nodes:
  print(pr)

(op + data).search produces AttributeError: 'list' object has no attribute 'get'

AttributeError                            Traceback (most recent call last)
site-packages/sgqlc/types/__init__.py in __populate_field_data(self, field, ftype, sel, json_data)
   1588             value = json_data[graphql_name]
-> 1589             value = ftype(value, sel)
   1590             setattr(self, name, value)

site-packages/sgqlc/types/__init__.py in __new__(cls, json_data, selection_list)
    873             raise ValueError(name + ' received null value')
--> 874         return t(json_data, selection_list)
    875 

site-packages/sgqlc/types/__init__.py in __init__(self, json_data, selection_list)
   1559         object.__setattr__(self, '__selection_list__', selection_list)
-> 1560         self.__populate_fields(json_data)
   1561 

site-packages/sgqlc/types/__init__.py in __populate_fields(self, json_data)
   1571             self.__populate_fields_from_selection_list(
-> 1572                 self.__selection_list__, json_data)
   1573         else:

site-packages/sgqlc/types/__init__.py in __populate_fields_from_selection_list(self, sl, json_data)
   1598             field = sel.__field__
-> 1599             ftype = self.__get_type_for_selection(sel, json_data)
   1600             if sel.__alias__ is not None:

site-packages/sgqlc/types/__init__.py in __get_type_for_selection(sel, json_data)
   1621         graphql_name = field.graphql_name
-> 1622         tname = json_data.get(graphql_name, {}).get('__typename')
   1623         if not tname:

AttributeError: 'list' object has no attribute 'get'

The response JSON had the form:

{'nodes': [{'__typename': 'PullRequest', 'number': 30527, 'updatedAt': '2019-04-05T17:52:05Z'}, {'__typename': 'PullRequest', 'number': 30521, 'updatedAt': '2019-04-05T20:59:45Z'}, {'__typename': 'PullRequest', 'number': 30224, 'updatedAt': '2019-03-30T01:00:39Z'}, {'__typename': 'PullRequest', 'number': 30129, 'updatedAt': '2019-04-04T20:42:40Z'}, {'__typename': 'PullRequest', 'number': 29954, 'updatedAt': '2019-04-05T02:37:49Z'}, {'__typename': 'PullRequest', 'number': 29931, 'updatedAt': '2019-03-25T23:41:54Z'}, {'__typename': 'PullRequest', 'number': 29824, 'updatedAt': '2019-04-05T02:36:29Z'}, {'__typename': 'PullRequest', 'number': 29822, 'updatedAt': '2019-04-02T19:13:15Z'}, {'__typename': 'PullRequest', 'number': 29809, 'updatedAt': '2019-03-24T14:03:15Z'}, {'__typename': 'PullRequest', 'number': 29767, 'updatedAt': '2019-04-05T02:26:44Z'}, {'__typename': 'PullRequest', 'number': 29451, 'updatedAt': '2019-04-05T12:21:25Z'}, {'__typename': 'PullRequest', 'number': 28556, 'updatedAt': '2019-04-02T21:20:24Z'}]}

etc

Cannot un-pickle a pickled Operation instance 🥒

Hey, I'm using sgqlc to introspect a schema and then generate queries from it and it is going well. However, I'm trying to parallelize tasks that contain an operation, which requires pickling it inside the task.

Here is the traceback that arises when trying to deserialize the (successfully) serialized op:

Traceback (most recent call last):
  File "/Users/myuser/myproj/venv/lib/python3.7/site-packages/sgqlc/types/__init__.py", line 657, in __getattr__
    return self.__kinds[key]  # .type, .scalar, etc...
  File "/Users/myuser/myproj/venv/lib/python3.7/site-packages/sgqlc/types/__init__.py", line 657, in __getattr__
    return self.__kinds[key]  # .type, .scalar, etc...
  File "/Users/myuser/myproj/venv/lib/python3.7/site-packages/sgqlc/types/__init__.py", line 657, in __getattr__
    return self.__kinds[key]  # .type, .scalar, etc...
  [Previous line repeated 486 more times]
RecursionError: maximum recursion depth exceeded while calling a Python object

I can reproduce this like so (using the generated python schema from sgqlc):

import my_python_schema
from cloudpickle import dumps, loads

schema_query = my_python_schema.Query
op = Operation(schema_query)

pickled_op = dumps(op) # works
unpickled_op = loads(pickled_op) # blows up with traceback above

Are there any options I'm missing that could maybe pre-flatten the types or specify the max depth?

Thanks for the sweet project 🙂

Continuous Integration

Setup and integrate a CI system such as travis/circle/jenkins to verify the quality (flake8 + nose tests)

Sgqlc does not throw an eror when a filter of an invalid field is given

For example if we have a input

class DogInput(sgqlc.types.Input):
     __schema__ = wex_schema
    birth_date = sgqlc.types.Field(ZonedDateTime, graphql_name='birthDate')

and we run a query with given filter

[{expression: GREATER_THAN_OR_EQUAL_TO, value: {adoptionAge: 3}}]

Then instead of throwing an error since DogInput doesn't have an adoption_age field, sgqlc will simply send the query with filter input

[{expression: GREATER_THAN_OR_EQUAL_TO, value: {}}]

Is this the intended result? And if so, is there a way of turning on error logging/debugging. It would be nice to know that the filters that you are trying to apply aren't valid.

Union + __fields__() has misleading error message

While the error is misleading (I should fix that), in GraphQL you can't select fields of an union type directly, you must use fragments to select depending on each type you want to handle.

In your case, could you try:

import sgqlc
from sgqlc.operation import Operation
from sgqlc.types import String, Type, Union, Field, non_null


class TypeA(Type):
    i = int

class TypeB(Type):
    s = str


class TypeU(Union):
    __types__ = (TypeA, TypeB)


class Query(sgqlc.types.Type):
    some_query = Field(non_null(TypeU), graphql_name='someQuery')


op = Operation(Query, name="op_name")
q = op.some_query()
q.__fields__()  # this line throws 'AttributeError: TypeA has no field name'

# correct behavior would be to:
q.__as__(TypeA).i()

Originally posted by @barbieri in #71 (comment)

Object creation using aliases

Thank you very much for your great work.
In my use case I need to use aliases since I have to get to sets of nodes filter by different parameters.
Using your example I use:

issues = op.repository(owner=owner, name=name).issues(__alias__='issues_alias', first=100)

But then later on when I use

repo = (op + data).repository
print(repo)

I don't get the desired Class...
May by I'm missing something, but can you suggest or make an example of the wright approach for using __alias__ ?
Thank you

Name-wrangling fields with invalid Python names

A GQL schema might have types with fields that aren't valid Python names. For example, you might have

type IntRange {
  from: Int
  to: Int
}

from is a Python keyword, so the code generated by sgqlc-codegen cannot be loaded. It would be nice if sgqlc-codegen could mangle these names to make them legal. As a straw man, maybe it could append an underscore (e.g. from_) to make it valid.

Converting to json

I want to convert my query

query {
  friend(id: "randomnumbers") {
    name
    nickname
  }
}

to

{ 'query': 
    '{ 
        friend(id: "randomnumbers) { 
              name 
              nickname
         } 
      }' 
}

I've been trying to go over the documentation but can't see any method that converts the query to json. Any help is appreciated! Thanks.

ImportError: cannot import name 'Value' from 'graphql.language.ast'

Hello and thank you for this library.

I encountered an issue when trying to generate Python code from a known GraphQL schema using the tool you provided. Simply running it without any inputs produces:

sgqlc-codegen 
Traceback (most recent call last):
  File "MY/VENV/PATH/bin/sgqlc-codegen", line 12, in <module>
    from graphql.language.ast import Value as GraphQLASTValue
ImportError: cannot import name 'Value' from 'graphql.language.ast'

I have sgqlc v9.0 installed locally. If I can provide any more info please don't hesitate to ask.

unit tests: sgqlc.operation

We need to add unit tests with good coverage to make sure things are not broken and keep working in the future.

KeyError: 'Query' when running github dashboard example

I'm trying to run the example like:

python examples/github/github-agile-dashboard.py --token <TOKEN> flask-admin/flask-admin save

But I'm getting:

Traceback (most recent call last):
  File "/Users/petrus/code/sgqlc/env/lib/python3.7/site-packages/sgqlc/types/__init__.py", line 664, in __getattr__
    return self.__all[key]
KeyError: 'Query'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "examples/github/github-agile-dashboard.py", line 935, in <module>
    args.func(endpoint, args)
  File "examples/github/github-agile-dashboard.py", line 450, in cmd_save
    args.label, args.issue_state, args.pr_state)
  File "examples/github/github-agile-dashboard.py", line 202, in download
    op = Operation()
  File "/Users/petrus/code/sgqlc/env/lib/python3.7/site-packages/sgqlc/operation/__init__.py", line 1596, in __init__
    typ = global_schema.Query
  File "/Users/petrus/code/sgqlc/env/lib/python3.7/site-packages/sgqlc/types/__init__.py", line 666, in __getattr__
    raise AttributeError(key) from exc
AttributeError: Query

Maximum recursion error

Hi, was trying out the library and quickly ran into the following issue trying to follow the README:

issues.nodes.number()
issues.nodes.title()
issues.page_info.__fields__('has_next_page')
issues.page_info.__fields__(end_cursor=True)

print(op)   

-------------------------------------------------------------------------                                                      RecursionError                            
Traceback (most recent call last)                                                      
<ipython-input-14-40703d8d77f2> in <module>                                                                                      ----> 1 print(op)                                                                                                                                                                                                                                                 /usr/local/lib/python3.6/site-packages/sgqlc/operation/__init__.py in __str__(self)                                                 1245
1246     def __str__(self):                                                                                                  
-> 1247         return self.__to_graphql__()                                                                                        
1248                                                                                                                            
1249     def __repr__(self):                                                                                                                                                                                                                                   

/usr/local/lib/python3.6/site-packages/sgqlc/operation/__init__.py in __to_graphql__(self, indent, indent_string)
1226         args = self.__args.__to_graphql__(indent, indent_string)                                                            1227         selections = self.__selection_list.__to_graphql__(                                                               
-> 1228             indent, indent_string)
1229         return prefix + kind + name + args + ' ' + selections                                                               1230                                                                                                                                                                                                                                                           

/usr/local/lib/python3.6/site-packages/sgqlc/operation/__init__.py in __to_graphql__(self, indent, indent_string)
1053         s = ['{']                                                                                                           
1054         for v in self.__selections:                                                                                     
-> 1055             s.append(v.__to_graphql__(indent + 1, indent_string))                                                           1056                                                                                                                             
1057         s.append(prefix + '}')                                                                                                                                                                                                                            

/usr/local/lib/python3.6/site-packages/sgqlc/operation/__init__.py in __to_graphql__(self, indent, indent_string)                   
816             if not selections:                                                                                               
817                 selections = self.__get_all_fields_selection_list()                                                      
--> 818             query = ' ' + selections.__to_graphql__(indent, indent_string)                                                   819         return prefix + alias + self.__field__.graphql_name + args + query                                                   820                                                                                                                                                                                                                                                           ... last 2 frames repeated, from the frame below ...                                                                                                                                                                                                              /usr/local/lib/python3.6/site-packages/sgqlc/operation/__init__.py in __to_graphql__(self, indent, indent_string)                  
1053         s = ['{']                                                                                                          
1054         for v in self.__selections:                                                                                     
 -> 1055             s.append(v.__to_graphql__(indent + 1, indent_string))                                                           1056                                                                                                                             
1057         s.append(prefix + '}')                                                                                                                                                                                                                            RecursionError: maximum recursion depth exceeded while calling a Python object

This is with Python 3.6.8 and sgqlc 4.0

Wondering if you have any idea what's triggering this? I generated the schema the way it was generated in the readme

Thanks

sgqlc-codegen with wrong shebang

When I tried running the code generator, I got the following error:

bash: /usr/bin/sgqlc-codegen: /Users/gustavo/.local/share/virtualenvs/sgqlc-LJ804sPL/bin/python3: bad interpreter: No such file or directory

Then I looked into the python script and, indeed, the script's shebang is using that path for the interpreter.

if data and data.get('errors'): -> AttributeError: 'str' object has no attribute 'get' / Also: Catching 500 server errors

If the HTTPEndpoint encounters a 500 error, it leads to the following traceback:

http://api.staging.nash.io/api/graphql: HTTP Error 500: Internal Server Error
Traceback (most recent call last):
File "...lib/python3.7/site-packages/sgqlc/endpoint/http.py", line 183, in __call__
    with self.urlopen(req, timeout=timeout or self.timeout) as f:
File ".../lib/python3.7/urllib/request.py", line 222, in urlopen
    return opener.open(url, data, timeout)
File ".../lib/python3.7/urllib/request.py", line 531, in open
    response = meth(req, response)
File ".../lib/python3.7/urllib/request.py", line 641, in http_response
    'http', request, response, code, msg, hdrs)
File ".../lib/python3.7/urllib/request.py", line 569, in error
    return self._call_chain(*args)
File ".../lib/python3.7/urllib/request.py", line 503, in _call_chain
    result = func(*args)
File ".../lib/python3.7/urllib/request.py", line 649, in http_error_default
    raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 500: Internal Server Error

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "test-gql.py", line 54, in <module>
    data = gql_endpoint(op)
File "...lib/python3.7/site-packages/sgqlc/endpoint/http.py", line 193, in __call__
    return self._log_http_error(query, req, exc)
File "...lib/python3.7/site-packages/sgqlc/endpoint/http.py", line 254, in _log_http_error
    if data and data.get('errors'):
AttributeError: 'str' object has no attribute 'get'

(gql_endpoint is a simple HTTPEndpoint)

Also, ideally we'd be able to catch the original HTTPError.

Unable to resolve fields on Union types

Recently I ran into something that appears to be either a bug or missing functionality where creating query selections on union types doesn't work properly.

This example throws a AttributeError: TypeA has no field name:

import sgqlc
from sgqlc.operation import Operation
from sgqlc.types import String, Type, Union, Field, non_null


class TypeA(Type):
    i = int

class TypeB(Type):
    s = str


class TypeU(Union):
    __types__ = (TypeA, TypeB)


class Query(sgqlc.types.Type):
    some_query = Field(non_null(TypeU), graphql_name='someQuery')


op = Operation(Query, name="op_name")
q = op.some_query()
q.__fields__()  # this line throws 'AttributeError: TypeA has no field name'

If I modify TypeA to have a name field:

class TypeA(Type):
    i = int
    name = str

I then get this error: KeyError: name: String from the same line as the above error.

It seems that field selection on Union types might be broken. If I omit q.__fields__() it will generate the correct query string, but then the fields are not deserialized and added to the resulting object when interpreting the result:

op = Operation(Query, name="op_name")
op.some_query()  # don't select any fields

data = {"data": {
    "someQuery": {
        "__typename": "TypeA",
        "i": 1234,
        "name": "name",
    }
}}

result = op + data  # result = TypeA()

Am I missing some kind of special syntax for Union types that I couldn't find in the documentation or is this just not working as it should?

How to make use of interfaces?

Hi, I'm not able to find a way to execute a query that uses interfaces from the automatically generated schema (of github). Ideally I would like to do the following:

query { 
  user(login: "fimuchka") {
    repositories(privacy: PUBLIC, first: 1){
      edges{
        node{
          refs(refPrefix: "refs/", first: 10) {
            edges {
              node{
                target {
                  ... on Commit {
                    message
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

What I have so far is:

op = Operation(schema.Query, user=non_null(str))
repos = op.user(login=Variable('user')).repositories(first=1, privacy='PUBLIC')
ref = repos.edges.node().refs(ref_prefix='refs/', first=10).edges.node()
ref.target.id() #now not sure what, commit isn't available

and so I'm left with

query Query($user: String!) {
  user(login: $user) {
    repositories(first: 1, privacy: PUBLIC) {                                                                                
      edges {                                                                                                                  
        node {                                                                                                                  
          refs(refPrefix: "refs/", first: 10) {                                                                                    
            edges {                                                                                                                  
              node {                                                                                                                                                                                                                                      
                target {                                                                                                                 
                  id                                                                                                                   
                }                                                                                                                    
              }                                                                                                                    
            }                                                                                                                    
         }                                                                                                                    
       }                                                                                                                    
     }                                                                                                                   
   }                                                                                                                    
 }                                                                                                                   
} 

Would appreciate any advice on how to proceed

Thank you

Generated github v4 API fails to import

Just starting out, and trying example from README.rst. Introspection and code generation complete without error, however the generated file can't be imported:

$ python
Python 3.6.4 (default, Feb 14 2018, 17:13:45)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from sgqlc.operation import Operation
>>> from github_schema import github_schema as schema
Traceback (most recent call last):
  File "/tmp/sgqc/.venv/lib/python3.6/site-packages/sgqlc/types/__init__.py", line 1565, in __populate_field_data
    value = json_data[graphql_name]
TypeError: string indices must be integers

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/sgqc/github_schema.py", line 2077, in <module>
    class Query(sgqlc.types.Type):
  File "/tmp/sgqc/github_schema.py", line 2161, in Query
    ('order_by', sgqlc.types.Arg(SecurityAdvisoryOrder, graphql_name='orderBy', default=SecurityAdvisoryOrder('{field:"UPDATED_AT",direction:"DESC"}'))),
  File "/tmp/sgqc/.venv/lib/python3.6/site-packages/sgqlc/types/__init__.py", line 1538, in __init__
    self.__populate_fields(json_data)
  File "/tmp/sgqc/.venv/lib/python3.6/site-packages/sgqlc/types/__init__.py", line 1553, in __populate_fields
    self.__populate_field_data(field, field.type, None, json_data)
  File "/tmp/sgqc/.venv/lib/python3.6/site-packages/sgqlc/types/__init__.py", line 1571, in __populate_field_data
    self.__class__, name, value, exc)) from exc
UnboundLocalError: local variable 'value' referenced before assignment

Using search query on GitHub API is missing top level map brackets

I'm not familiar with GraphQL so this may be more or less coherent.

Suppose a top level query of:

{
  search(first: 100, query: "blah", type:ISSUE) {
    nodes {
      ... on PullRequest {
        number
      }
    }
  }
}

using codegen against https://api.github.com/graphql and creating something to the effect of

op = Operation(schema.Query)
prs = op.search(first=100, type=SearchType("ISSUE"), query="blah")
prs.nodes.__as__(schema.PullRequest).number()

etc produces an outgoing string that doesn't have top level brackets and results in

GraphQL query failed with 1 errors
{'errors': [{'message': 'Parse error on "search" (IDENTIFIER) at [1, 1]', 'locations': [{'line': 1, 'column': 1}]}]}

Tweaking http.py's get_http_post_request to wrap the query with {} seems to make it work.

unit tests: sgqlc.types

We need to add unit tests with good coverage to make sure things are not broken and keep working in the future.

Constructors for auto generated classes?

We're using sgqlc to interact with a GQL service we've built and love the removal of dict-of-stuff navigation in the response objects and field selections. We can't quite figure out how to do the same for complex argument/input values though.
As an example:

class FilterDateInput(sgqlc.types.Input):
    start = sgqlc.types.Field(String, graphql_name='start')
    end = sgqlc.types.Field(String, graphql_name='end')
class FilterValueInput(sgqlc.types.Input):
    value = sgqlc.types.Field(String, graphql_name='value')
class FilterInput(sgqlc.types.Input):
    date = sgqlc.types.Field(FilterDateInput, graphql_name='date')
    id = sgqlc.types.Field(FilterInput, graphql_name='id')

Ideally we would be able to generate a filter by:

gql_filter = FilterInput(
    date=FilterDateInput(start='04-12-2012', end='04-15-2012'), 
    id=FilterValueInput(value='A17')
)
operation.queryThings(filter=gql_filter, user="bill")

This would benefit from tool tips and autocomplete where as the current method we're aware of is:

gql_filter = { 
    'date': {'start':'04-12-2012', 'end':'04-12-2012'}, 
    'id':{'value':'A17'}
}
operation.queryThings(filter=gql_filter, user="bill")

Which as the schema grows become increasingly unwieldy. Is there a way that we are missing to do this or is there a way to generate classes that will act in this way using introspection of the sgqlc’s autogenerated classes?

Example for all fields of an inline fragment?

Does anybody have an example of using all fields of an inline fragment and setting a parameter for one of them?

The examples show the use of selecting an individual field:
op = Operation(Query) repo = op.repository(id='repo1') repo.owner().__as__(Organization).location() # location field for Orgs
What if my ActualType had many fields (and might get more in the future) and I can't manually add them all?

It might be helpful to add this to the basic example. Thank you!

operation kind issues

I can't tell if this is our implementation of GraphQL doing something wrong or a bug/bad assumption in this library.

In V5 of this library, in the operation _to_graphql_ it was assumed that kind was either query or mutation. Now it takes the typ._name_.lower() and uses that as the kind. This causes issues because instead of the initial query just being query{} it is now the name of the class from introspection call, in this case, something like graphqlquery{}, and our system rejects it. If I rename the query class in the schema from graphqlquery to query, everything works.

So I guess the main question is, should our system be accepting graphqlquery as the initial request instead of just query, or should there be an override for the kind somewhere?

Union type takes no arguments exception

Hi! I have (IssueTimelineItem() takes no arguments) exception when trying to map query results on Union type fields:

from sgqlc.endpoint.http import HTTPEndpoint
from sgqlc.operation import Operation
from github_schema import Query

url = 'https://api.github.com/graphql'
headers = {'Authorization': 'bearer  ...'}
endpoint = HTTPEndpoint(url, headers)
op = Operation(Query)
op.repository(name='sgqlc', owner='profusion').issue(number=1).timeline(first=1).nodes()
data = endpoint(op)
nodes = (op + data)

Traceback:

    nodes = (op + data)
  File "/Users/user/p3.7.1/lib/python3.7/site-packages/sgqlc/operation/__init__.py", line 1650, in __add__
    return self.__type(other.get('data'), self.__selection_list)
  File "/Users/user/p3.7.1/lib/python3.7/site-packages/sgqlc/types/__init__.py", line 1560, in __init__
    self.__populate_fields(json_data)
  File "/Users/user/p3.7.1/lib/python3.7/site-packages/sgqlc/types/__init__.py", line 1572, in __populate_fields
    self.__selection_list__, json_data)
  File "/Users/user/p3.7.1/lib/python3.7/site-packages/sgqlc/types/__init__.py", line 1604, in __populate_fields_from_selection_list
    self.__populate_field_data(field, ftype, sel, json_data)
  File "/Users/user/p3.7.1/lib/python3.7/site-packages/sgqlc/types/__init__.py", line 1594, in __populate_field_data
    self.__class__, name, value, exc)) from exc
ValueError: Query selection 'repository': {'issue': {'timeline': {'nodes': [{'__typename': 'LabeledEvent', 'id': 'MDEyOkxhYmVsZWRFdmVudDE0Mjg4MDkwNTk=', 'actor': {'avatarUrl': 'https://avatars3.githubusercontent.com/u/1838?v=4', 'login': 'barbieri', 'resourcePath': '/barbieri', 'url': 'https://github.com/barbieri'}, 'createdAt': '2018-01-17T17:51:48Z', 'label': {'id': 'MDU6TGFiZWw4MDc4ODY3MzE=', 'color': '33aa3f', 'createdAt': '2018-01-17T17:49:18Z', 'description': 'Extra attention is needed', 'isDefault': True, 'name': 'help wanted', 'resourcePath': '/profusion/sgqlc/labels/help%20wanted', 'updatedAt': '2018-01-17T17:49:18Z', 'url': 'https://github.com/profusion/sgqlc/labels/help%20wanted'}}]}}} (Repository selection 'issue': {'timeline': {'nodes': [{'__typename': 'LabeledEvent', 'id': 'MDEyOkxhYmVsZWRFdmVudDE0Mjg4MDkwNTk=', 'actor': {'avatarUrl': 'https://avatars3.githubusercontent.com/u/1838?v=4', 'login': 'barbieri', 'resourcePath': '/barbieri', 'url': 'https://github.com/barbieri'}, 'createdAt': '2018-01-17T17:51:48Z', 'label': {'id': 'MDU6TGFiZWw4MDc4ODY3MzE=', 'color': '33aa3f', 'createdAt': '2018-01-17T17:49:18Z', 'description': 'Extra attention is needed', 'isDefault': True, 'name': 'help wanted', 'resourcePath': '/profusion/sgqlc/labels/help%20wanted', 'updatedAt': '2018-01-17T17:49:18Z', 'url': 'https://github.com/profusion/sgqlc/labels/help%20wanted'}}]}} (Issue selection 'timeline': {'nodes': [{'__typename': 'LabeledEvent', 'id': 'MDEyOkxhYmVsZWRFdmVudDE0Mjg4MDkwNTk=', 'actor': {'avatarUrl': 'https://avatars3.githubusercontent.com/u/1838?v=4', 'login': 'barbieri', 'resourcePath': '/barbieri', 'url': 'https://github.com/barbieri'}, 'createdAt': '2018-01-17T17:51:48Z', 'label': {'id': 'MDU6TGFiZWw4MDc4ODY3MzE=', 'color': '33aa3f', 'createdAt': '2018-01-17T17:49:18Z', 'description': 'Extra attention is needed', 'isDefault': True, 'name': 'help wanted', 'resourcePath': '/profusion/sgqlc/labels/help%20wanted', 'updatedAt': '2018-01-17T17:49:18Z', 'url': 'https://github.com/profusion/sgqlc/labels/help%20wanted'}}]} (IssueTimelineConnection selection 'nodes': [{'__typename': 'LabeledEvent', 'id': 'MDEyOkxhYmVsZWRFdmVudDE0Mjg4MDkwNTk=', 'actor': {'avatarUrl': 'https://avatars3.githubusercontent.com/u/1838?v=4', 'login': 'barbieri', 'resourcePath': '/barbieri', 'url': 'https://github.com/barbieri'}, 'createdAt': '2018-01-17T17:51:48Z', 'label': {'id': 'MDU6TGFiZWw4MDc4ODY3MzE=', 'color': '33aa3f', 'createdAt': '2018-01-17T17:49:18Z', 'description': 'Extra attention is needed', 'isDefault': True, 'name': 'help wanted', 'resourcePath': '/profusion/sgqlc/labels/help%20wanted', 'updatedAt': '2018-01-17T17:49:18Z', 'url': 'https://github.com/profusion/sgqlc/labels/help%20wanted'}}] (IssueTimelineItem() takes no arguments))))

AttributeError: module 're' has no attribute 'compile'

When trying to run basic examples from a virtualenv, I get this error caused by the local types.py module taking precedent over python's builtin types module.

(env) PJs-MacBook-Pro-2:examples petrus$ python basic/http-endpoint.py 
Traceback (most recent call last):
  File "basic/http-endpoint.py", line 4, in <module>
    import json
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/__init__.py", line 106, in <module>
    from .decoder import JSONDecoder, JSONDecodeError
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/decoder.py", line 3, in <module>
    import re
  File "/Users/petrus/code/sgqlc/examples/env/lib/python3.7/re.py", line 122, in <module>
    import enum
  File "/Users/petrus/code/sgqlc/examples/env/lib/python3.7/enum.py", line 2, in <module>
    from types import MappingProxyType, DynamicClassAttribute
  File "/Users/petrus/code/sgqlc/examples/basic/types.py", line 4, in <module>
    from sgqlc.endpoint.http import HTTPEndpoint
  File "/Users/petrus/code/sgqlc/examples/env/lib/python3.7/site-packages/sgqlc/endpoint/http.py", line 25, in <module>
    import logging
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/logging/__init__.py", line 26, in <module>
    import sys, os, time, io, traceback, warnings, weakref, collections.abc
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/traceback.py", line 5, in <module>
    import linecache
  File "/Users/petrus/code/sgqlc/examples/env/lib/python3.7/linecache.py", line 11, in <module>
    import tokenize
  File "/Users/petrus/code/sgqlc/examples/env/lib/python3.7/tokenize.py", line 37, in <module>
    cookie_re = re.compile(r'^[ \t\f]*#.*?coding[:=][ \t]*([-\w.]+)', re.ASCII)
AttributeError: module 're' has no attribute 'compile'

HTTPEndpoint.__call__ replaces 'Accept' header

HTTPEndpoint.call forces 'Accept' header thus preventing setting github preview schemas such as "application/vnd.github.echo-preview+json" etc.

I suggest setting it only if absent like:
if 'Accept' not in headers:
headers.update({
'Accept': 'application/json; charset=utf-8',
})

Quick Start Tutorial Needed

Loving this project.

I feel like this project would benefit from a short quick start tutorial that demonstrate common methods such as:

Create item

    mutation = Operation(schema.Mutation)  # note 'schema.'
    mutation.create_item(input=dict(id="23432")).__fields__()

List items with filter

    op = Operation(schema.Query)  # note 'schema.'
    op.list_items(filter={"number_id": {'eq': 123456 }})

Making HTTP Calls to AWS AppSync (Managed Graphql Service)

  query = Operation(schema.Query)
  items = query.list_items()

  query = bytes(query).decode("utf-8")

  post_data = json.dumps({"query": query}).encode("utf-8")

  headers = {}
  headers["Accept"] = "application/json; charset=utf-8"

  auth = AWS4Auth(
        access_key_id,
        secret_access_key,
        region_name,
        "appsync",
        session_token=session_token,
    )

  response = requests.post(url, auth=auth, data=post_data, headers=headers)

I'll add more examples as I work them out =)

Copying instance of Operation

Hello, I'm trying to copy an operation instance, so I can use it as a base query in order to paginate. But when I do so using copy, it terminates with a RecursionError like in #58 . Is there a good way to do it ?

Name collision in generated client when object type name matches query name (sgqlc-codegen)

sgqlc-codegen seems to generate a name collision in the Python client when an Object Type has the same name as a Query in cases where some other Query returns that Object Type.

For example:
Let's say my schema has a GraphQL Object Type named "widget". sgqlc-codegen would write a class definition near the top of the file like:

class widget(sgqlc.types.Type):
    __schema__ = my_schema
    id = sgqlc.types.Field(sgqlc.types.non_null(Int), graphql_name='id')

Let's say I also have a GraphQL query called "widget" (e.g. to get a list of widgets). sgqlc-codegen would write a query definition under the query_root class that looks something like this:

class query_root(sgqlc.types.Type):
    __schema__ = my_schema
    widget = sgqlc.types.Field(sgqlc.types.non_null(sgqlc.types.list_of(sgqlc.types.non_null(widget))), graphql_name='widget', args=sgqlc.types.ArgDict((
        ('distinct_on', sgqlc.types.Arg(sgqlc.types.list_of(sgqlc.types.non_null(widget_select_column)), graphql_name='distinct_on', default=None)),
        ('limit', sgqlc.types.Arg(Int, graphql_name='limit', default=None)),
        ('offset', sgqlc.types.Arg(Int, graphql_name='offset', default=None)),
        ('order_by', sgqlc.types.Arg(sgqlc.types.list_of(sgqlc.types.non_null(widget_order_by)), graphql_name='order_by', default=None)),
        ('where', sgqlc.types.Arg(order_bool_exp, graphql_name='where', default=None)),))
    )

Now, the problem comes in if I have another query that has a return type of "widget". For example, let's say I have a query for fetching widgets by their primary key; this might generate something in the Python client like this:

class query_root(sgqlc.types.Type):
    __schema__ = my_schema
   ...
   widget_by_pk = sgqlc.types.Field(widget, graphql_name='widget_by_pk', args=sgqlc.types.ArgDict((
        ('id', sgqlc.types.Arg(sgqlc.types.non_null(Int), graphql_name='id', default=None)),))
   )

The problem is that "widget" has been reassigned in the "query_root" class, so ...sgqlc.types.Field(widget, ... will actually reference that query (a Field instance) instead of the actual "widget" class defined near the top of the file.

A couple notes for context:

  • This issue was observed when attempting to generate a client for a schema generated by Hasura, so I would assume that other Hasura users might run into a similar issue.
  • I am brand new to this project (and GraphQL in general) so it's totally possible I'm misunderstanding something here.

Generating union fields with one type

In sgqlc-codegen def write_type_union:

self.writer('''\
class %(name)s(sgqlc.types.Union):
    __schema__ = %(schema_name)s
    __types__ = (%(types)s)
''' % {
            'name': name,
            'schema_name': self.schema_name,
            'types': ', '.join(v['name'] for v in t['possibleTypes']),
})

In case when only one type in Union it will generate code like this:

class UnionField(sgqlc.types.Union):
    __schema__ = gw_schema
    __types__ = (OneField)

But (OneField) is incorrect tuple. It is must be (OneField,).

It solves with adding comma in (%(types)s) . Like (%(types)s, ). But it is looks not so good when types more than one: (OneField, SecondField,) . Still works fine but looks ugly.

Ofcourse it is uncommon case but possible.

Automating pagination? (when there's a relay-style connection)

I'm trying to figure out how to automate pagination or at least introspect to find a Connection or PageInfo object. (that way I can generically paginate when there's a single PageInfo within the query). I've looked through code and docs and still haven't quite found anything.

(I'm happy to move this to StackOverflow as well if you'd prefer)

Here's a working example, but it's a little awkward to set up. You have to keep track of the pa

from sgqlc.endpoint.http import HTTPEndpoint
from sgqlc.types import Variable, non_null
from sgqlc.operation import Operation
import github_schema as schema
from typing import List

def _get_op():
    op = Operation(schema.Query, login=non_null(str), repoName=non_null(str), limit=int, endCursor=str)
    prs = op.organization(login=Variable('login')).repository(name=Variable('repoName')).pull_requests(first=Variable('limit'), after=Variable('endCursor'))
    prs.nodes.created_at()
    prs.nodes.merged()
    prs.nodes.merged_at()
    prs.nodes.id()
    prs.page_info.__fields__('has_next_page', 'end_cursor')
    return op

def paginate(op: Operation, page_info_path: List[str], variables: dict
        = None):
    """Paginate a pre-existing Operation, updating endCursor to newest cursor each time.
Assumes endCursor is variable name.

    Args:
        page_info_path: list of attribute strings (e.g.,
            ['organization, 'repository', 'pull_requests', 'page_info')
        variables: dict of additional variables to set
    Yields:
        dict: data from the operation

    Remaining keyword arguments should be additional variables to pass to operation
    """
    parsed = None
    end_cursor = None
    variables = dict(variables or {})

    def _page_info(parsed):
        curr = parsed
        for elem in page_info_path_components:
            curr = getattr(curr, elem)
        return curr

    while parsed is None or _page_info(parsed).has_next_page:
        variables['endCursor'] = end_cursor
        data = endpoint(op, variables=variables)
        parsed = op + data
        yield parsed
        end_cursor = _page_info(parsed).end_cursor

paginate(_get_op(), page_info_path=['organization', 'repository', 'pull_requests'])

Aside - I'm enjoying using sgqlc - the codegen is quite nice! :)

ImportError in /types/__init__.py

Hi! It is maybe stupid issue but I am getting this error after updating master brunch today:


Fatal Python error: initsite: Failed to import the site module
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site.py", line 168, in addpackage
    exec(line)
  File "<string>", line 1, in <module>
  File "/Users/homedir/sgqlc/types/__init__.py", line 510, in <module>
    import json
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/__init__.py", line 106, in <module>
    from .decoder import JSONDecoder, JSONDecodeError
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/decoder.py", line 3, in <module>
    import re
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/re.py", line 122, in <module>
    import enum
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/enum.py", line 2, in <module>
    from types import MappingProxyType, DynamicClassAttribute
ImportError: cannot import name 'MappingProxyType' from 'types' (/Users/homedir/sgqlc/types/__init__.py)

However importing json package in other script works well:

import json

print(json)

/usr/local/bin/python3.7 /Users/homedir/sandbox/ttt.py
<module 'json' from '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/__init__.py'>

unicode support

I had an issue with using strings containing non ASCII characters. I have german special characters like 'ü' and so on. I solved it by changing line 1192 in types/__init__.py
from:
return json.dumps(cls.__to_json_value__(value))
to:
return json.dumps(cls.__to_json_value__(value), ensure_ascii=False)

Best regards
Peter

Cannot read property 'length' of undefined

I'm getting this error when I'm calling a specific query within my sgqlc schema? Is there anything inherent within this package that could case that error? I only get the error with a specific query that I am doing, other queries work just fine.

I can post the schema .py file if that would help.

Cannot use Variable to stand for complex-typed argument


import sgqlc.types

from sgqlc.operation import Operation

repro_schema = sgqlc.types.Schema()

Boolean = sgqlc.types.Boolean

ID = sgqlc.types.ID

String = sgqlc.types.String

Variable = sgqlc.types.Variable

class IngestDataFromS3Input(sgqlc.types.Input):
    __schema__ = repro_schema
    src = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='src')
    dest = sgqlc.types.Field(String, graphql_name='dest')
    recursive = sgqlc.types.Field(Boolean, graphql_name='recursive')

class IngestDataFromS3Payload(sgqlc.types.Type):
    __schema__ = repro_schema
    ingestion_job_id = sgqlc.types.Field(ID, graphql_name='ingestionJobID')

class Mutation(sgqlc.types.Type):
    __schema__ = repro_schema
    ingest_data_from_s3 = sgqlc.types.Field(IngestDataFromS3Payload, graphql_name='ingestDataFromS3', args=sgqlc.types.ArgDict((
        ('input', sgqlc.types.Arg(sgqlc.types.non_null(IngestDataFromS3Input), graphql_name='input', default=None)),
))
    )
    ingest_data_from_s3_fixed = sgqlc.types.Field(IngestDataFromS3Payload, graphql_name='ingestDataFromS3Fixed', args=sgqlc.types.ArgDict((
        ('input', sgqlc.types.Arg(String, graphql_name='input', default=None)),
))
    )

op = Operation(repro_schema.Mutation, name="ingest_from_s3_fixed", input=str)
ingestDataFromS3Fixed = op.ingest_data_from_s3_fixed(input=Variable("input"))
ingestDataFromS3Fixed.ingestion_job_id()
print(str(op))

op = Operation(repro_schema.Mutation, name="ingest_from_s3", input=IngestDataFromS3Input)
ingestDataFromS3 = op.ingest_data_from_s3(input=Variable("input"))
ingestDataFromS3.ingestion_job_id()
print(str(op))

SGQLC appears to be trying to get the fields of the Input type, if that makes sense:

Traceback (most recent call last):
  File "repro.py", line 46, in <module>
    print(str(op))
  File "/Users/grant.wu/petuum/petctl_venv/lib/python3.7/site-packages/sgqlc/operation/__init__.py", line 1822, in __str__
    return self.__to_graphql__()
  File "/Users/grant.wu/petuum/petctl_venv/lib/python3.7/site-packages/sgqlc/operation/__init__.py", line 1803, in __to_graphql__
    indent, indent_string, auto_select_depth)
  File "/Users/grant.wu/petuum/petctl_venv/lib/python3.7/site-packages/sgqlc/operation/__init__.py", line 1529, in __to_graphql__
    next_indent, indent_string, auto_select_depth,
  File "/Users/grant.wu/petuum/petctl_venv/lib/python3.7/site-packages/sgqlc/operation/__init__.py", line 1241, in __to_graphql__
    self.__args__, indent, indent_string)
  File "/Users/grant.wu/petuum/petctl_venv/lib/python3.7/site-packages/sgqlc/types/__init__.py", line 2233, in __to_graphql_input__
    args.append(p.__to_graphql_input__(v))
  File "/Users/grant.wu/petuum/petctl_venv/lib/python3.7/site-packages/sgqlc/types/__init__.py", line 2075, in __to_graphql_input__
    v = self.type.__to_graphql_input__(value, indent, indent_string)
  File "/Users/grant.wu/petuum/petctl_venv/lib/python3.7/site-packages/sgqlc/types/__init__.py", line 896, in __to_graphql_input__
    return t.__to_graphql_input__(value, indent, indent_string)
  File "/Users/grant.wu/petuum/petctl_venv/lib/python3.7/site-packages/sgqlc/types/__init__.py", line 2453, in __to_graphql_input__
    v = value[f.graphql_name]
TypeError: 'Variable' object is not subscriptable

Note that when the argument is not a custom type, it works as expected (see https://sgqlc.readthedocs.io/en/latest/sgqlc.operation.html, CTRL-F "Operations can have arugments)

HTTPEndpoint utilising requests instead of urllib

Hello,

I'm looking to utilise sgqlc for communicating between AWS Lambda functions and AWS AppSync. In order to support this workflow I need to be able to authenticate my HTTP Requests using SigV4 and that currently isn't supported with the existing HTTPEndpoint.

I'm looking to get your opinion in regards to contributing either a separate or modified version of the existing HTTPEndpoint that utilises the Requests library. That will allow usage of authentication with Requests and doesn't require large amount of code changes. (I already have a working version)

Keen to get your opinions on the matter as I would like to see if you see value in this for other users.

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.