sgqlc.operation module

Generate Operations (Query and Mutations) using Python

Note

This module could be called “query”, however it should also generate mutations and a class Query could lead to mistakes, since the users should define their own root Query class with the top-level queries in their GraphQL schema.

Users create instance of Operation using the Schema.Query or Schema.Mutation types. From there they proceed accessing members, which will produce Selector instances, that once called will produce Selection instances, which are automatically added to a SelectionList in the parent (operation or selection). The following annotated GraphQL code helps to understand the Python mapping:

query { # Operation
   parent(arg: "value") { # Selector, called with arguments
      child { # Selector, called without arguments
         field # Selector called without arguments, Selection without alias
         alias: field(other: 123)
      }
      sibling { x { y } }
   }
}
op = Operation(Query)
parent = op.parent(arg='value')

child = parent.child
child.field()
child.field(other=123, __alias__='alias')

parent.sibling.x.y()

Operation implements __str__() and __repr__() to generate the GraphQL query for you. It also provide __bytes__() to produce compact output, without indentation. It can be passed to sgqlc.endpoint.base.BaseEndpoint.__call__() as is.

Another convenience is the __add__() to apply the operation to a resulting JSON data, interpreting the results and producing convenient objects:

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

obj = op + data
print(obj.parent.child.field)
print(obj.parent.sibling.x.y)

Examples

Let’s start defining the types, including the schema root Query:

>>> from sgqlc.types import *
>>> from datetime import datetime
>>> class Actor(Interface):
...    login = non_null(str)
...
>>> class User(Type, Actor):
...    name = str
...
>>> class Organization(Type, Actor):
...    location = str
...
>>> class ActorConnection(Type):
...    actors = Field(list_of(non_null(Actor)), args={'login': non_null(str)})
...
>>> class Assignee(Type):
...    email = non_null(str)
...
>>> class UserOrAssignee(Union):
...    __types__ = (User, Assignee)
...
>>> class Issue(Type):
...     number = non_null(int)
...     title = non_null(str)
...     body = str
...     reporter = non_null(User)
...     assigned = UserOrAssignee
...     commenters = ActorConnection
...
>>> class ReporterFilterInput(Input):
...     name_contains = str
...
>>> class IssuesFilter(Input):
...     reporter = list_of(ReporterFilterInput)
...     start_date = non_null(datetime)
...     end_date = datetime
...
>>> class Repository(Type):
...     id = ID
...     name = non_null(str)
...     owner = non_null(Actor)
...     issues = Field(list_of(non_null(Issue)), args={
...         'title_contains': str,
...         'reporter_login': str,
...         'filter': IssuesFilter,
...     })
...
>>> class Query(Type):
...     repository = Field(Repository, args={'id': non_null(ID)})
...
>>> class Mutation(Type):
...     add_issue = Field(Issue, args={
...         'repository_id': non_null(ID),
...         'title': non_null(str),
...         'body': str,
...     })
...
>>> global_schema  # doctest: +ELLIPSIS
schema {
  ...
  interface Actor {
    login: String!
  }
  type User implements Actor {
    login: String!
    name: String
  }
  type Organization implements Actor {
    login: String!
    location: String
  }
  type ActorConnection {
    actors(login: String!): [Actor!]
  }
  type Assignee {
    email: String!
  }
  union UserOrAssignee = User | Assignee
  type Issue {
    number: Int!
    title: String!
    body: String
    reporter: User!
    assigned: UserOrAssignee
    commenters: ActorConnection
  }
  input ReporterFilterInput {
    nameContains: String
  }
  input IssuesFilter {
    reporter: [ReporterFilterInput]
    startDate: DateTime!
    endDate: DateTime
  }
  type Repository {
    id: ID
    name: String!
    owner: Actor!
    issues(titleContains: String, reporterLogin: String, filter: IssuesFilter): [Issue!]
  }
  type Query {
    repository(id: ID!): Repository
  }
  type Mutation {
    addIssue(repositoryId: ID!, title: String!, body: String): Issue
  }
}

Selecting to Generate Queries

Then let’s select numbers and titles of issues of repository with identifier repo1:

>>> 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
    }
  }
}

You can see we stored op.repository(id='repo1') result in a variable, later reusing it. Executing this statement will emit a new Selection and only one field selection is allowed in the selection list (unless an alias is used). Trying the code below will error:

>>> op = Operation(Query)
>>> op.repository(id='repo1').issues.number() # ok!
number
>>> op.repository(id='repo1').issues.title() # fails
Traceback (most recent call last):
  ...
ValueError: repository already have a selection repository(id: "repo1") {
  issues {
    number
  }
}. Maybe use __alias__ as param?

That is, if you wanted to query for two repositories, you should use __alias__ argument in the call. But here would not produce the query we want, as seen below:

>>> op = Operation(Query)
>>> op.repository(id='repo1').issues.number()
number
>>> op.repository(id='repo1', __alias__='alias').issues.title()
title
>>> op  # not what we want in this example, 2 independent queries
query {
  repository(id: "repo1") {
    issues {
      number
    }
  }
  alias: repository(id: "repo1") {
    issues {
      title
    }
  }
}

In our case, to get the correct query, do as in the first example and save the result of op.repository(id='repo1').

Aliases may be used to rename fields everywhere, not just in the topmost query, and for other reasons other than allow two calls with the same name. One may use it to translate API fields to something else, example:

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

Last but not least, in the first example you can also note that we’re not calling issues, just accessing its members. This is a shortcut for an empty call, and the handle is saved for you (ease of use):

>>> 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
    }
  }
}

This could be rewritten saving the issues selector:

>>> op = Operation(Query)
>>> issues = op.repository(id='repo1').issues()
>>> issues.number()
number
>>> issues.title()
title
>>> op
query {
  repository(id: "repo1") {
    issues {
      number
      title
    }
  }
}

Or even simpler with __fields__(*names, **names_and_args):

>>> op = Operation(Query)
>>> op.repository(id='repo1').issues.__fields__('number', 'title')
>>> op
query {
  repository(id: "repo1") {
    issues {
      number
      title
    }
  }
}
>>> op = Operation(Query)
>>> op.repository(id='repo1').issues.__fields__(
...     number=True,
...     title=True,
... )
>>> op
query {
  repository(id: "repo1") {
    issues {
      number
      title
    }
  }
}

Which also allows to include all but some fields:

>>> op = Operation(Query)
>>> op.repository(id='repo1').issues.__fields__(
...     __exclude__=('body', 'reporter', 'commenters'),
... )
>>> op
query {
  repository(id: "repo1") {
    issues {
      number
      title
      assigned {
        __typename
        ... on User {
          login
          name
        }
        ... on Assignee {
          email
        }
      }
    }
  }
}

Or using named arguments:

>>> op = Operation(Query)
>>> op.repository(id='repo1').issues.__fields__(
...     body=False,
...     reporter=False,
...     commenters=False,
... )
>>> op
query {
  repository(id: "repo1") {
    issues {
      number
      title
      assigned {
        __typename
        ... on User {
          login
          name
        }
        ... on Assignee {
          email
        }
      }
    }
  }
}

If no arguments are given to __fields__(), then it defaults to include every member, and this is done recursively:

>>> op = Operation(Query)
>>> op.repository(id='repo1').issues.__fields__()
>>> op
query {
  repository(id: "repo1") {
    issues {
      number
      title
      body
      reporter {
        login
        name
      }
      assigned {
        __typename
        ... on User {
          login
          name
        }
        ... on Assignee {
          email
        }
      }
      commenters {
        actors {
          login
        }
      }
    }
  }
}

Named arguments may be used to provide fields with argument values:

>>> op = Operation(Query)
>>> op.repository(id='repo1').__fields__(
...    issues={'title_contains': 'bug'}, # adds field and children
... )
>>> op
query {
  repository(id: "repo1") {
    issues(titleContains: "bug") {
      number
      title
      body
      reporter {
        login
        name
      }
      assigned {
        __typename
      }
    }
  }
}

Arguments can be given as tuple of key-value pairs as well:

>>> op = Operation(Query)
>>> op.repository(id='repo1').__fields__(
...    issues=(('title_contains', 'bug'),), # adds field and children
... )
>>> op
query {
  repository(id: "repo1") {
    issues(titleContains: "bug") {
      number
      title
      body
      reporter {
        login
        name
      }
      assigned {
        __typename
      }
    }
  }
}

If a field of a container type (interface or type) is used without explicit fields as documented above, all of its fields will be added automatically. It will avoid dependency loops and limit the allowed nest depth to 2 by default, but that can be overridden with an explicit auto_select_depth to __to_graphql__() (which is used by str(), repr() and the likes):

>>> op = Operation(Query)
>>> op.repository(id='repo1') # printed with depth=2 (default)
repository(id: "repo1") {
  id
  name
  owner {
    login
  }
  issues {
    number
    title
    body
  }
}
>>> op # the whole query printed with depth=2 (default)
query {
  repository(id: "repo1") {
    id
    name
    owner {
      login
    }
    issues {
      number
      title
      body
    }
  }
}
>>> print(op.__to_graphql__(auto_select_depth=1)) # omits owner/issues
query {
  repository(id: "repo1") {
    id
    name
  }
}
>>> print(op.__to_graphql__(auto_select_depth=3)) # shows reporter
query {
  repository(id: "repo1") {
    id
    name
    owner {
      login
    }
    issues {
      number
      title
      body
      reporter {
        login
        name
      }
      assigned {
        __typename
      }
    }
  }
}
>>> print(op.__to_graphql__(auto_select_depth=4)) # shows assigned sub-types
query {
  repository(id: "repo1") {
    id
    name
    owner {
      login
    }
    issues {
      number
      title
      body
      reporter {
        login
        name
      }
      assigned {
        __typename
        ... on User {
          login
          name
        }
        ... on Assignee {
          email
        }
      }
      commenters {
        actors {
          login
        }
      }
    }
  }
}

Interpret Query Results

Given the operation explained above:

>>> op = Operation(Query)
>>> op.repository(id='repo1').issues.__fields__('number', 'title')
>>> op
query {
  repository(id: "repo1") {
    issues {
      number
      title
    }
  }
}

After calling the GraphQL endpoint, you should get a JSON object that matches the one below:

>>> json_data = {'data': {
...     'repository': {'issues': [
...         {'number': 1, 'title': 'found a bug'},
...         {'number': 2, 'title': 'a feature request'},
...     ]},
... }}

To interpret this, simply add the data to the operation:

>>> obj = op + json_data
>>> repository = obj.repository
>>> for issue in repository.issues:
...     print(issue)
Issue(number=1, title=found a bug)
Issue(number=2, title=a feature request)

Which are instances of classes declared in the beginning of example section:

>>> repository.__class__ is Repository
True
>>> repository.issues[0].__class__ is Issue
True

While it’s mostly the same as creating instances yourself:

>>> repository = Repository(json_data['data']['repository'])
>>> for issue in repository.issues:
...     print(issue)
Issue(number=1, title=found a bug)
Issue(number=2, title=a feature request)

The difference is that it will handle aliases for you:

>>> op = Operation(Query)
>>> op.repository(id='repo1', __alias__='r_name1').issues.__fields__(
...     number='code', title='headline',
... )
>>> op.repository(id='repo2', __alias__='r_name2').issues.__fields__(
...     'number', 'title',
... )
>>> op
query {
  r_name1: repository(id: "repo1") {
    issues {
      code: number
      headline: title
    }
  }
  r_name2: repository(id: "repo2") {
    issues {
      number
      title
    }
  }
}
>>> json_data = {'data': {
...     'r_name1': {'issues': [
...         {'code': 1, 'headline': 'found a bug'},
...         {'code': 2, 'headline': 'a feature request'},
...     ]},
...     'r_name2': {'issues': [
...         {'number': 10, 'title': 'something awesome'},
...         {'number': 20, 'title': 'other thing broken'},
...     ]},
... }}
>>> obj = op + json_data
>>> for issue in obj.r_name1.issues:
...     print(issue)
Issue(code=1, headline=found a bug)
Issue(code=2, headline=a feature request)
>>> for issue in obj.r_name2.issues:
...     print(issue)
Issue(number=10, title=something awesome)
Issue(number=20, title=other thing broken)

Updating also reflects on the correct backing store:

>>> obj.r_name2.name = 'repo2 name'
>>> json_data['data']['r_name2']['name']
'repo2 name'

It also works with auto selection:

>>> op = Operation(Query)
>>> op.repository(id='repo1')
repository(id: "repo1") {
  id
  name
  owner {
    login
  }
  issues {
    number
    title
    body
  }
}
>>> json_data = {'data': {
...     'repository': {'id': 'repo1', 'name': 'Repo #1'},
... }}
>>> obj = op + json_data
>>> obj.repository.name
'Repo #1'

Mutations

Mutations are handled as well, just use that as Operation root type:

>>> op = Operation(Mutation)
>>> op.add_issue(repository_id='repo1', title='an issue').__fields__()
>>> op
mutation {
  addIssue(repositoryId: "repo1", title: "an issue") {
    number
    title
    body
    reporter {
      login
      name
    }
    assigned {
      __typename
      ... on User {
        login
        name
      }
      ... on Assignee {
        email
      }
    }
    commenters {
      actors {
        login
      }
    }
  }
}

Inline Fragments & Interfaces

When a field specifies an interface such as the Repository.owner in our example, only the interface fields can be queried. However, the actual type may implement much more, and to solve that in GraphQL we usually do an inline fragment ... on ActualType { field1, field2 }.

To achieve that we use the __as__(ActualType) on the selection list, example:

>>> op = Operation(Query)
>>> repo = op.repository(id='repo1')
>>> repo.owner.login() # interface fields can be declared as usual
login
>>> repo.owner().__as__(Organization).location() # location field for Orgs
location
>>> repo.owner.__as__(User).name() # name field for Users
name
>>> repo.issues().assigned.__as__(Assignee).email()
email
>>> repo.issues().assigned.__as__(User).login()
login
>>> repo.issues().commenters().actors().login()
login
>>> repo.issues().commenters().actors().__as__(Organization).location()
location
>>> repo.issues().commenters().actors().__as__(User).name()
name
>>> op
query {
  repository(id: "repo1") {
    owner {
      login
      __typename
      ... on Organization {
        location
      }
      ... on User {
        name
      }
    }
    issues {
      assigned {
        __typename
        ... on Assignee {
          email
        }
        ... on User {
          login
        }
      }
      commenters {
        actors {
          login
          __typename
          ... on Organization {
            location
          }
          ... on User {
            name
          }
        }
      }
    }
  }
}

Note that __typename is automatically selected so it can create the proper type when interprets the results:

>>> json_data = {'data': {'repository': {'owner': {
...    '__typename': 'User',
...    'login': 'user',
...    'name': 'User Name',
...    },
...    'issues': [
...      {
...          'assigned': {'__typename': 'Assignee', 'email': 'e@mail.com'},
...          'commenters': {
...              'actors': [
...                  {'login': 'user', '__typename': 'User', 'name': 'User Name'},
...                  {'login': 'a-company', '__typename': 'Organization', 'location': 'that place'}
...              ]
...          }
...      },
...      {
...          'assigned': {'__typename': 'User', 'login': 'xpto'},
...          'commenters': {
...              'actors': [
...                  {'login': 'user', '__typename': 'User', 'name': 'User Name'},
...                  {'login': 'xpto', '__typename': 'User'}
...              ]
...          }
...      },
...    ],
... }}}
>>> obj = op + json_data
>>> obj.repository.owner
User(login='user', __typename__='User', name='User Name')
>>> for i in obj.repository.issues:
...     print(i)
Issue(assigned=Assignee(__typename__=Assignee, email=e@mail.com), commenters=ActorConnection(actors=[User(login='user', __typename__='User', name='User Name'), Organization(login='a-company', __typename__='Organization', location='that place')]))
Issue(assigned=User(__typename__=User, login=xpto), commenters=ActorConnection(actors=[User(login='user', __typename__='User', name='User Name'), User(login='xpto', __typename__='User')]))
>>> json_data = {'data': {'repository': {'owner': {
...    '__typename': 'Organization',
...    'login': 'a-company',
...    'name': 'A Company',
... }}}}
>>> obj = op + json_data
>>> obj.repository.owner
Organization(login='a-company', __typename__='Organization')

If the returned type doesn’t have an explicit type fields, the Interface field is returned:

>>> json_data = {'data': {'repository': {'owner': {
...    '__typename': 'SomethingElse',
...    'login': 'something-else',
...    'field': 'value',
... }}}}
>>> obj = op + json_data
>>> obj.repository.owner
Actor(login='something-else', __typename__='SomethingElse')

In the unusual situation where __typename is not returned, it’s going to behave as the interface type as well:

>>> json_data = {'data': {'repository': {'owner': {
...    'login': 'user',
...    'name': 'User Name',
... }}}}
>>> obj = op + json_data
>>> obj.repository.owner
Actor(login='user')

Utilities

Starting with the first selection example:

>>> op = Operation(Query)
>>> repository = op.repository(id='repo1')
>>> repository.issues.number()
number
>>> repository.issues.title()
title

One can get a indented print out using repr():

>>> print(repr(op))
query {
  repository(id: "repo1") {
    issues {
      number
      title
    }
  }
}
>>> print(repr(repository))
repository(id: "repo1") {
  issues {
    number
    title
  }
}
>>> print(repr(repository.issues.number()))
number

Note that Selector is different:

>>> print(repr(repository.issues.number))
Selector(field=number)

Or can get a compact print out without indentation using bytes():

>>> print(bytes(op).decode('utf-8'))
query {
repository(id: "repo1") {
issues {
number
title
}
}
}
>>> print(bytes(repository).decode('utf-8'))
repository(id: "repo1") {
issues {
number
title
}
}
>>> print(bytes(repository.issues.number()).decode('utf-8'))
number

Selection and Selector both implement len():

>>> len(op)                        # number of selections (here: top level)
1
>>> len(repository.issues())       # number of selections
2
>>> len(repository.issues)         # number of selections (implicit empty call)
2
>>> len(repository.issues.title()) # leaf is always 1
1

Selection and Selector both implement dir() to also list fields:

>>> for name in dir(repository.issues()): # on selection also yields fields
...     if not name.startswith('_'):
...         print(name)
assigned
body
commenters
number
reporter
title
>>> for name in dir(repository.issues): # same for selector
...     if not name.startswith('_'):
...         print(name)
assigned
body
commenters
number
reporter
title
>>> for name in dir(repository.issues.number()): # no fields for scalar
...     if not name.startswith('_'):
...         print(name)
>>> for name in dir(repository.issues.number): # no fields for scalar
...     if not name.startswith('_'):
...         print(name)

Classes also implement iter() to iterate over selections:

>>> for i, sel in enumerate(op):
...     print('#%d: %s' % (i, sel))
#0: repository(id: "repo1") {
  issues {
    number
    title
  }
}
>>> for i, sel in enumerate(repository):
...     print('#%d: %s' % (i, sel))
#0: issues {
  number
  title
}
>>> for i, sel in enumerate(repository.issues):
...     print('#%d: %s' % (i, sel))
#0: number
#1: title
>>> for i, sel in enumerate(repository.issues()):
...     print('#%d: %s' % (i, sel))
#0: number
#1: title
>>> for i, sel in enumerate(repository.issues.number()):
...     print('#%d: %s' % (i, sel))
#0: number

Given a Selector one can query a selection given its alias:

>>> op = Operation(Query)
>>> op.repository(id='repo1').issues.number()
number
>>> op.repository(id='repo2', __alias__='alias').issues.title()
title
>>> type(op['repository'])  # it's the selector, not a selection!
<class 'sgqlc.operation.Selector'>
>>> op['repository'].__selection__() # default selection
repository(id: "repo1") {
  issues {
    number
  }
}
>>> op['repository'].__selection__('alias') # aliased selection
alias: repository(id: "repo2") {
  issues {
    title
  }
}

Which is useful to query the selection alias and arguments:

>>> op['repository'].__selection__('alias').__alias__
'alias'
>>> op['repository'].__selection__('alias').__args__
{'id': 'repo2'}
>>> op['repository'].__selection__().__args__
{'id': 'repo1'}

To get the arguments of the default (non-aliased) one can use the shortcut:

>>> op['repository'].__args__
{'id': 'repo1'}
license:ISC
class sgqlc.operation.Operation(typ=None, name=None, **args)[source]

Bases: object

GraphQL Operation: query or mutation.

The given type must be one of schema.Query or schema.Mutation, defaults to global_schema.Query or whatever is defined as global_schema.query_type.

The operation has an internal sgqlc.operation.SelectionList and will proxy attributes and item access to it, thus offering selectors and automatically handling selections:

op = Operation()
op.parent.field.child()
op.parent.field(param1=value1, __alias__'q2').child()

Once data is fetched and parsed as JSON object containing the field data, the operation can be used to interpret this data using the addition operator (no clearly named method to avoid clashing with selections):

op = Operation()
op.parent.field.child()

endpoint = HTTPEndpoint('http://my.server.com/graphql')
json_data = endpoint(op)

parent = op + json_data
print(parent.field.child)

Example usage:

>>> op = Operation(global_schema.Query)
>>> op.repository
Selector(field=repository)
>>> 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
    }
  }
}

The root type can be omitted, then global_schema.Query or whatever is defined as `global_schema.query_type is used:

>>> op = Operation()  # same as Operation(global_schema.Query)
>>> op.repository
Selector(field=repository)

Operations can be named:

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

Operations can also have argument (variables), in this case it must be named (otherwise a name is created based on root type name, such as "Query"):

>>> from sgqlc.types import Variable
>>> op = Operation(repo_id=str, reporter_login=str)
>>> repository = op.repository(id=Variable('repo_id'))
>>> issues = repository.issues(reporter_login=Variable('reporter_login'))
>>> issues.__fields__('number', 'title')
>>> op # or repr(), prints out GraphQL!
query Query($repoId: String, $reporterLogin: String) {
  repository(id: $repoId) {
    issues(reporterLogin: $reporterLogin) {
      number
      title
    }
  }
}

Complex argument types are also supported as JSON object (GraphQL names and raw types) or actual types:

>>> op = Operation()
>>> repository = op.repository(id='sgqlc')
>>> issues = repository.issues(filter={
...     'reporter': [{'nameContains': 'Gustavo'}],
...     'startDate': '2019-01-01T00:00:00+00:00',
... })
>>> issues.__fields__('number', 'title')
>>> op # or repr(), prints out GraphQL!
query {
  repository(id: "sgqlc") {
    issues(filter: {reporter: [{nameContains: "Gustavo"}], startDate: "2019-01-01T00:00:00+00:00"}) {
      number
      title
    }
  }
}
>>> from datetime import datetime, timezone
>>> from sgqlc.types import global_schema
>>> op = Operation()
>>> repository = op.repository(id='sgqlc')
>>> issues = repository.issues(filter=global_schema.IssuesFilter(
...     reporter=[global_schema.ReporterFilterInput(name_contains='Gustavo')],
...     start_date=datetime(2019, 1, 1, tzinfo=timezone.utc),
... ))
>>> issues.__fields__('number', 'title')
>>> op # or repr(), prints out GraphQL!
query {
  repository(id: "sgqlc") {
    issues(filter: {reporter: [{nameContains: "Gustavo"}], startDate: "2019-01-01T00:00:00+00:00"}) {
      number
      title
    }
  }
}

Selectors can be acquired as attributes or items, but they must exist in the target type:

>>> op = Operation()
>>> op.repository
Selector(field=repository)
>>> op['repository']
Selector(field=repository)
>>> op.does_not_exist
Traceback (most recent call last):
  ...
AttributeError: query {
} has no field does_not_exist
>>> op['does_not_exist']
Traceback (most recent call last):
  ...
KeyError: 'Query has no field does_not_exist'
__init__(typ=None, name=None, **args)[source]

Initialize self. See help(type(self)) for accurate signature.

__repr__()[source]

Return repr(self).

__str__()[source]

Return str(self).

__weakref__

list of weak references to the object (if defined)

class sgqlc.operation.Selection(alias, field, args)[source]

Bases: object

Select a field with in a container type.

Warning

Do not create instances directly, use sgqlc.operation.Selector instead.

A selection matches the GraphQL statement to select a field from a type, it may contain an alias and parameters:

query {
  parent {
    field
    field(param1: value1, param2: value2)
    alias: field(param1: value1, param2: value2)
  }
}

Attributes or items access will result in sgqlc.operation.Selector matching the target type field:

parent.field.child

For container types one can provide a batch of fields using sgqlc.operation.Selection.__fields__():

# just field1 and field2
parent.field.child.__fields__('field1', 'field2')
parent.field.child.__fields__(field1=True, field2=True)

# field1 with parameters
parent.field.child.__fields__(field1=dict(param1='value1'))

# all but field2
parent.field.child.__fields__(field2=False)
parent.field.child.__fields__(field2=None)
parent.field.child.__fields__(__exclude__=('field2',))

If __fields__() is not explicitly called, then all fields are included. Note that this may lead to huge queries since it will result in recursive inclusion of all fields.

Selectors will create selections when items or attributes are accessed, this is done by implicitly calling the selector with empty parameters.

However leafs (ie: scalars) must be explicitly called, otherwise they won’t generate a selection

# OK
parent.field.child()
# NOT OK: doesn't create a selection for child.
parent.field.child
__dir__() → list[source]

default dir() implementation

__fields__(*names, **names_and_args)[source]

Select fields of a container type.

This is a helper to automate selection of fields of container types, such as giving a list of names to include, with or without parameters (passed as a mapping name=args).

If no arguments are given, all fields are included.

If the keyword argument __exclude__ is given a list of names, then all but those fields will be included. Alternatively one can exclude fields using name=None or name=False as keyword argument.

If a list of names is given as positional arguments, then only those names will be included. Alternatively one can include fields using name=True. To include fields with selection parameters, then use name=dict(...) or name=list(...). To include fields without arguments and with aliases, use the shortcut name='alias'.

# just field1 and field2
parent.field.child.__fields__('field1', 'field2')
parent.field.child.__fields__(field1=True, field2=True)

# field1 with parameters
parent.field.child.__fields__(field1=dict(param1='value1'))

# field1 renamed (aliased) to alias1
parent.field.child.__fields__(field1='alias1')

# all but field2
parent.field.child.__fields__(field2=False)
parent.field.child.__fields__(field2=None)
parent.field.child.__fields__(__exclude__=('field2',))
__init__(alias, field, args)[source]

Initialize self. See help(type(self)) for accurate signature.

__repr__()[source]

Return repr(self).

__str__()[source]

Return str(self).

class sgqlc.operation.Selector(parent, field)[source]

Bases: object

Creates selection for a given field.

Warning

Do not create instances directly, use sgqlc.operation.SelectionList instead.

Selectors are callable objects that will create sgqlc.operation.Selection entries in the parent sgqlc.operation.SelectionList.

Selectors will create selections when items or attributes are accessed, this is done by implicitly calling the selector with empty parameters.

However leafs (ie: scalars) must be explicitly called, otherwise they won’t generate a selection

# OK
parent.field.child()
# NOT OK: doesn't create a selection for child.
parent.field.child

To select all fields from a container type, use sgqlc.operation.Selection.__fields__(), example:

# just field1 and field2
parent.field.child.__fields__('field1', 'field2')
parent.field.child.__fields__(field1=True, field2=True)

# field1 with parameters
parent.field.child.__fields__(field1=dict(param1='value1'))

# all but field2
parent.field.child.__fields__(field2=False)
parent.field.child.__fields__(field2=None)
parent.field.child.__fields__(__exclude__=('field2',))

Note

GraphQL limits a single selection per type, as the field name is used in the return object. If you want to select the same field multiple times, like as using different parameters, then provide the __alias__ parameter to the selector:

# FAILS:
parent.field.child(param1='value1')
parent.field.child(param2='value2')

# OK
parent.field.child(param1='value1')
parent.field.child(param2='value2', __alias__='child2')
__args__

Shortcut for self.__selection__().__args__

__as__(typ)[source]

Create a selection list on the given type.

The selection list will be result in an inline fragment in the query with an additional query for __typename, which is later used to create the proper type when the results are interpreted.

__call__(**args)[source]

Create a selection with the given parameters.

To provide an alias, use __alias__ keyword argument.

__dir__() → list[source]

default dir() implementation

__fields__

Calls the selector without arguments, creating a Selection instance and return Selection.__fields__() method, ready to be called.

To query the actual field this selector operates, use self.__field__

__init__(parent, field)[source]

Initialize self. See help(type(self)) for accurate signature.

__repr__()[source]

Return repr(self).

__selection__(alias=None)[source]

Return the selection given its alias

__str__()[source]

Return str(self).

class sgqlc.operation.SelectionList(typ)[source]

Bases: object

List of sgqlc.operation.Selection in a type.

Warning

Do not create instances directly, use sgqlc.operation.Operation instead.

Create a selection list using a type to query its fields. Once fields are accessed, they will create sgqlc.operation.Selector object for that field, this allows to match the type structure, with easy to use API:

parent.field.child()
parent.field(param1=value1).child()

Direct usage example (not recommended):

>>> sl = SelectionList(global_schema.Repository)
>>> sl += Selection('x', global_schema.Repository.id, {})
>>> sl # or repr()
{
  x: id
}
>>> print(bytes(sl).decode('utf-8')) # no indentation
{
x: id
}
>>> sl.id    # or any other field from Repository returns a Selector
Selector(field=id)
>>> sl['id'] # also as get item
Selector(field=id)
>>> sl.x    # not the alias
Traceback (most recent call last):
  ...
AttributeError: {
  x: id
} has no field x
>>> sl['x'] #
Traceback (most recent call last):
  ...
KeyError: 'Repository has no field x'
>>> sl.__type__ # returns the type the selection operates on
type Repository {
  id: ID
  name: String!
  owner: Actor!
  issues(titleContains: String, reporterLogin: String, filter: IssuesFilter): [Issue!]
}
__as__(typ)[source]

Create a child selection list on the given type.

The selection list will be result in an inline fragment in the query with an additional query for __typename, which is later used to create the proper type when the results are interpreted.

The newly created selection list is shared for all users of the same type in this selection list.

__init__(typ)[source]

Initialize self. See help(type(self)) for accurate signature.

__repr__()[source]

Return repr(self).

__str__()[source]

Return str(self).