sgqlc.types module

GraphQL Types in Python

This module fulfill two purposes:

  • declare GraphQL schema in Python, just declare classes inheriting Type, Interface and fill them with Field (or base types: str, int, float, bool). You may as well declare Enum with __choices__ or Union and __types__. Then __str__() will provide nice printout and __repr__() will return the GraphQL declarations (which can be tweaked with __to_graphql__(), giving indent details). __bytes__() is also provided, mapping to a compact __to_graphql__() version, without indent.
  • Interpret GraphQL JSON data, by instantiating the declared classes with such information. While for scalar types it’s just a pass-thru, for Type and Interface these will use the fields to provide native object with attribute or key access mapping to JSON, instead of json_data['key']['other'] you may use obj.key.other. Newly declared types, such as DateTime will take care to generate native Python objects (ie: datetime.datetime). Setting such attributes will also update the backing store object, including converting back to valid JSON values.

These two improve usability of GraphQL a lot, pretty much like Django’s Model helps to access data bases.

Field may be created explicitly, with information such as target type, arguments and GraphQL name. However, more commonly these are auto-generated by the container: GraphQL name, usually aFieldName will be created from Python name, usually a_field_name. Basic types such as int, str, float or bool will map to Int, String, Float and Boolean.

The end-user classes and functions provided by this module are:

  • Schema: top level object that will contain all declarations. For single-schema applications, you don’t have to care about this since types declared without an explicit __schema__ = SchemaInstance member will end in the global_schema.

  • Scalar: “pass thru” everything received. Base for other scalar types:

  • Enum: also handled as a str, but GraphQL syntax needs them without the quotes, so special handling is done. Validation is done using __choices__ member, which is either a string (which will be splitted using str.split()) or a list/tuple of strings with values.

  • Union: defines the target type of a field may be one of the given __types__.

  • Container types: Type, Interface and Input. These are similar in usage, but GraphQL needs them defined differently. They are composed of Field. A field may have arguments (ArgDict), which is a set of Arg. Arguments may contain default values or Variable, which will be sent alongside the query (this allows to generate the query once and use variables, letting the server to use both together).

  • non_null(), maps to GraphQL Type! and enforces the object is not None.

  • list_of(), maps to GraphQL [Type] and enforces the object is a list of Type.

This module only provide built-in scalar types. However, two other modules will extend the behavior for common conventions:

Examples

Common Usage

Common usage is to create Type subclasses with fields without arguments and do not use an explicit __schema__, resulting in the types being added to the global_schema. Built-in scalars can be declared using the Python classes, with sgqlc.types classes or with explicit Field instances, the ContainerTypeMeta takes care to make sure they are all instance of Field at the final class:

>>> class TypeUsingPython(Type):
...     a_int = int
...     a_float = float
...     a_string = str
...     a_boolean = bool
...     a_id = id
...     not_a_field = 1 # not a BaseType subclass or mapped python class
...
>>> TypeUsingPython  # or repr(TypeUsingPython), prints out GraphQL!
type TypeUsingPython {
  aInt: Int
  aFloat: Float
  aString: String
  aBoolean: Boolean
  aId: ID
}
>>> TypeUsingPython.a_int  # or repr(Field), prints out GraphQL!
aInt: Int
>>> TypeUsingPython.a_int.name
'a_int'
>>> TypeUsingPython.a_int.graphql_name  # auto-generated from name
'aInt'
>>> TypeUsingPython.a_int.type  # always a :mod:`sgqlc.types` class
scalar Int
>>> TypeUsingPython.__schema__ is global_schema
True
>>> global_schema  # or repr(Schema), prints out GraphQL!
schema {
  scalar Int
  scalar Float
  scalar String
  scalar Boolean
  scalar ID
  scalar Time
  scalar Date
  scalar DateTime
  interface Node {
    id: ID!
  }
  type PageInfo {
    endCursor: String
    startCursor: String
    hasNextPage: Boolean!
    hasPreviousPage: Boolean!
  }
  type TypeUsingPython {
    aInt: Int
    aFloat: Float
    aString: String
    aBoolean: Boolean
    aId: ID
  }
}

You can then use some standard Python operators to check fields in a Type:

>>> 'a_float' in TypeUsingPython
True
>>> 'x' in TypeUsingPython
False
>>> for field in TypeUsingPython:  # iterates over :class:`Field`
...     print(repr(field))
...
aInt: Int
aFloat: Float
aString: String
aBoolean: Boolean
aId: ID

As mentioned, fields can be created with basic Python types (simpler), with sgqlc.types or with Field directly:

>>> class TypeUsingSGQLC(Type):
...     a_int = Int
...     a_float = Float
...     a_string = String
...     a_boolean = Boolean
...     a_id = ID
...
>>> TypeUsingSGQLC  # or repr(TypeUsingSGQLC), prints out GraphQL!
type TypeUsingSGQLC {
  aInt: Int
  aFloat: Float
  aString: String
  aBoolean: Boolean
  aId: ID
}

Using Field instances

Allows for greater control, such as explicitly define the graphql_name instead of generating one from the Python name. It is also used to declare field arguments:

>>> class TypeUsingFields(Type):
...     a_int = Field(int)         # can use Python classes
...     a_float = Field(float)
...     a_string = Field(String)   # or sgqlc.types classes
...     a_boolean = Field(Boolean)
...     a_id = Field(ID, graphql_name='anotherName') # allows customizations
...     pow = Field(int, args={'base': int, 'exp': int}) # with arguments
...     # more than 3 arguments renders each into new line
...     many = Field(int, args={'a': int, 'b': int, 'c': int, 'd': int})
...
>>> TypeUsingFields  # or repr(TypeUsingFields), prints out GraphQL!
type TypeUsingFields {
  aInt: Int
  aFloat: Float
  aString: String
  aBoolean: Boolean
  anotherName: ID
  pow(base: Int, exp: Int): Int
  many(
    a: Int
    b: Int
    c: Int
    d: Int
  ): Int
}

Adding types to specific Schema

Create a schema instance and assign as __schema__ class member. Note that previously defined types in base_schema are inherited, and by default global_schema is used as base schema:

>>> my_schema = Schema(global_schema)
>>> class MySchemaType(Type):
...     __schema__ = my_schema
...     i = int
...
>>> class MyOtherType(Type):
...     i = int
...
>>> 'TypeUsingPython' in my_schema
True
>>> 'MySchemaType' in global_schema
False
>>> 'MySchemaType' in my_schema
True
>>> 'MyOtherType' in global_schema
True
>>> 'MyOtherType' in my_schema  # added after my_schema was created!
False
>>> my_schema.MySchemaType  # access types as schema attributes
type MySchemaType {
  i: Int
}
>>> my_schema['MySchemaType']  # access types as schema items
type MySchemaType {
  i: Int
}
>>> for t in my_schema:  # doctest: +ELLIPSIS
...     print(repr(t))
...
scalar Int
scalar Float
...
type MySchemaType {
  i: Int
}

Inheritance and Interfaces

Inheriting another type inherits all fields:

>>> class MySubclass(TypeUsingPython):
...     sub_field = int
...
>>> MySubclass
type MySubclass {
  aInt: Int
  aFloat: Float
  aString: String
  aBoolean: Boolean
  aId: ID
  subField: Int
}

Interfaces are similar, however they emit implements IfaceName:

>>> class MyIface(Interface):
...     sub_field = int
...
>>> MyIface
interface MyIface {
  subField: Int
}
>>> class MySubclassWithIface(TypeUsingPython, MyIface):
...     pass
...
>>> MySubclassWithIface
type MySubclassWithIface implements MyIface {
  aInt: Int
  aFloat: Float
  aString: String
  aBoolean: Boolean
  aId: ID
  subField: Int
}

Although usually types are declared first, they can be declared after interfaces as well. Note order of fields respect inheritance order:

>>> class MySubclassWithIface2(MyIface, TypeUsingPython):
...     pass
...
>>> MySubclassWithIface2
type MySubclassWithIface2 implements MyIface {
  subField: Int
  aInt: Int
  aFloat: Float
  aString: String
  aBoolean: Boolean
  aId: ID
}

Cross References (Loops)

If types link to themselves, declare as strings so they are lazy-evaluated:

>>> class LinkToItself(Type):
...    other = Field('LinkToItself')
...    non_null_other = non_null('LinkToItself')
...    list_other = list_of('LinkToItself')
...    list_other_non_null = list_of(non_null('LinkToItself'))
...    non_null_list_other_non_null = non_null(list_of(non_null(
...        'LinkToItself')))
...
>>> LinkToItself
type LinkToItself {
  other: LinkToItself
  nonNullOther: LinkToItself!
  listOther: [LinkToItself]
  listOtherNonNull: [LinkToItself!]
  nonNullListOtherNonNull: [LinkToItself!]!
}
>>> LinkToItself.other.type
type LinkToItself {
  other: LinkToItself
  nonNullOther: LinkToItself!
  listOther: [LinkToItself]
  listOtherNonNull: [LinkToItself!]
  nonNullListOtherNonNull: [LinkToItself!]!
}

Also works for two unrelated types:

>>> class CrossLinkA(Type):
...    other = Field('CrossLinkB')
...    non_null_other = non_null('CrossLinkB')
...    list_other = list_of('CrossLinkB')
...    list_other_non_null = list_of(non_null('CrossLinkB'))
...    non_null_list_other_non_null = non_null(list_of(non_null(
...        'CrossLinkB')))
...
>>> class CrossLinkB(Type):
...    other = Field('CrossLinkA')
...    non_null_other = non_null('CrossLinkA')
...    list_other = list_of('CrossLinkA')
...    list_other_non_null = list_of(non_null('CrossLinkA'))
...    non_null_list_other_non_null = non_null(list_of(non_null(
...        'CrossLinkA')))
...
>>> CrossLinkA
type CrossLinkA {
  other: CrossLinkB
  nonNullOther: CrossLinkB!
  listOther: [CrossLinkB]
  listOtherNonNull: [CrossLinkB!]
  nonNullListOtherNonNull: [CrossLinkB!]!
}
>>> CrossLinkB
type CrossLinkB {
  other: CrossLinkA
  nonNullOther: CrossLinkA!
  listOther: [CrossLinkA]
  listOtherNonNull: [CrossLinkA!]
  nonNullListOtherNonNull: [CrossLinkA!]!
}
>>> CrossLinkA.other.type
type CrossLinkB {
  other: CrossLinkA
  nonNullOther: CrossLinkA!
  listOther: [CrossLinkA]
  listOtherNonNull: [CrossLinkA!]
  nonNullListOtherNonNull: [CrossLinkA!]!
}
>>> CrossLinkB.other.type
type CrossLinkA {
  other: CrossLinkB
  nonNullOther: CrossLinkB!
  listOther: [CrossLinkB]
  listOtherNonNull: [CrossLinkB!]
  nonNullListOtherNonNull: [CrossLinkB!]!
}

Special Attribute Names

Attributes starting with _ are ignored, however if for some reason you must use such attribute name, then declare ALL attributes in that class (no need to repeat inherited attributes from interfaces) using the __field_names__, which should have a tuple of strings:

>>> class TypeUsingSpecialAttributes(Type):
...     __field_names__ = ('_int', '_two_words')
...     _int = int
...     _two_words = str
...     not_handled = float # not declared!
...
>>> TypeUsingSpecialAttributes  # or repr(TypeUsingSpecialAttributes)
type TypeUsingSpecialAttributes {
  _int: Int
  _twoWords: String
}
>>> TypeUsingSpecialAttributes._int  # or repr(Field), prints out GraphQL!
_int: Int
>>> TypeUsingSpecialAttributes._int.name
'_int'
>>> TypeUsingSpecialAttributes._int.graphql_name  # auto-generated from name
'_int'

Note that while the leading underscores (_) are preserved, the rest of internal underscores are converted to camel case:

>>> TypeUsingSpecialAttributes._two_words
_twoWords: String
>>> TypeUsingSpecialAttributes._two_words.name
'_two_words'
>>> TypeUsingSpecialAttributes._two_words.graphql_name
'_twoWords'

Note

Take care with the double underscores __ as Python mangles the name with the class name in order to “protect” and it will result in AttributeError

Note that undeclared fields won’t be handled, but they still exist as regular python attributes, in this case it references the float class:

>>> TypeUsingSpecialAttributes.not_handled
<class 'float'>

Non GraphQL Attributes

The __field_names__ may also be used to allow non-GraphQL attributes that would otherwise be handled as such, this explicitly limits the scope where SGQLC will handle.

Utilities

One can obtain fields as container attributes or items:

>>> TypeUsingPython.a_int
aInt: Int
>>> TypeUsingPython['a_int']
aInt: Int

However they raise exceptions if doesn’t exist:

>>> TypeUsingPython.does_not_exist
Traceback (most recent call last):
  ...
AttributeError: TypeUsingPython has no field does_not_exist
>>> TypeUsingPython['does_not_exist']
Traceback (most recent call last):
  ...
KeyError: 'TypeUsingPython has no field does_not_exist'

Fields show in dir() alongside with non-fields (sorted):

>>> for name in dir(TypeUsingPython):
...     if not name.startswith('_'):
...         print(name)
a_boolean
a_float
a_id
a_int
a_string
not_a_field

Unless non_null() is used, containers can be created for None:

>>> TypeUsingPython(None)
TypeUsingPython()
>>> TypeUsingPython.__to_json_value__(None) # returns None

For instances, field values can be obtained or set attributes or items, when setting a known field, it also updates the backing store:

>>> json_data = {'aInt': 1}
>>> obj = TypeUsingPython(json_data)
>>> obj.a_int
1
>>> obj['a_int']
1
>>> obj.a_int = 2
>>> json_data['aInt']
2
>>> obj['a_int'] = 3
>>> json_data['aInt']
3
>>> obj['a_float'] = 2.1 # known field!
>>> json_data['aFloat']
2.1
>>> obj.a_float = 3.3 # known field!
>>> json_data['aFloat']
3.3

Unknown fields raise exceptions when obtained, but are allowed to be set, however doesn’t update the backing store:

>>> obj.does_not_exist
Traceback (most recent call last):
  ...
AttributeError: 'TypeUsingPython' object has no attribute 'does_not_exist'
>>> obj['does_not_exist']
Traceback (most recent call last):
  ...
KeyError: 'TypeUsingPython(a_int=3, a_float=3.3) has no field does_not_exist'
>>> obj['does_not_exist'] = 'abc' # unknown field, no updates to json_data
>>> json_data['does_not_exist']
Traceback (most recent call last):
  ...
KeyError: 'does_not_exist'

While repr() prints out summary in Python-friendly syntax, bytes() can be used to get compressed JSON with sorted keys:

>>> print(repr(obj))
TypeUsingPython(a_int=3, a_float=3.3)
>>> print(bytes(obj).decode('utf-8'))
{"aFloat":3.3,"aInt":3}
license:ISC
class sgqlc.types.Schema(base_schema=None)[source]

Bases: object

The schema will contain declared types.

There is a default schema called global_schema, a singleton that is automatically assigned to every type that does not provide its own schema.

Once types are constructed, they are automatically added to the schema as properties of the same name, for example Int is exposed as schema.Int, schema['Int'] or schema.scalar['Int'].

New schema will inherit the types defined at base_schema, which defaults to global_schema, at the time of their creation. However types added to base_schema after the schema creation are not automatically picked by existing schema. The copy happens at construction time.

New types may be added to schema using schema += type and removed with schema -= type. However those will not affect their member type.__schema__, which remains the same (where they where originally created).

The schema is an iterator that will report all registered types.

__bytes__()[source]

GraphQL schema without indentation.

>>> print(bytes(global_schema).decode('utf-8'))  # doctest: +ELLIPSIS
schema {
scalar Int
scalar Float
scalar String
...
}
__contains__(key)[source]

Checks if the type name is known in this schema.

Considering TypeUsingPython, previously declared in the module documentation:

>>> 'TypeUsingPython' in global_schema
True
>>> 'UnknownTypeName' in global_schema
False
__getattr__(key)[source]

Get the type using schema attribute.

Considering TypeUsingPython, previously declared in the module documentation:

>>> global_schema.TypeUsingPython
type TypeUsingPython {
  aInt: Int
  aFloat: Float
  aString: String
  aBoolean: Boolean
  aId: ID
}
>>> global_schema.UnknownTypeName
Traceback (most recent call last):
  ...
AttributeError: UnknownTypeName

One can use Schema.kind.Type syntax as well, it exposes an ODict object:

>>> global_schema.scalar.Int
scalar Int
>>> global_schema.scalar['Int']
scalar Int
>>> global_schema.scalar.UnknownTypeName  # doctest: +ELLIPSIS
Traceback (most recent call last):
  ...
AttributeError: ... has no field UnknownTypeName
>>> global_schema.type.TypeUsingPython  # doctest: +ELLIPSIS
type TypeUsingPython {
...
>>> for t in global_schema.type.values():  # doctest: +ELLIPSIS
...     print(repr(t))
...
type PageInfo {
...
type TypeUsingPython {
...
type TypeUsingSGQLC {
...
type TypeUsingFields {
...
type MyOtherType {
...
type MyType {
...
}
__getitem__(key)[source]

Get the type given its name.

Considering TypeUsingPython, previously declared in the module documentation:

>>> global_schema['TypeUsingPython']
type TypeUsingPython {
  aInt: Int
  aFloat: Float
  aString: String
  aBoolean: Boolean
  aId: ID
}
>>> global_schema['UnknownTypeName']
Traceback (most recent call last):
  ...
KeyError: 'UnknownTypeName'
__iadd__(typ)[source]

Manually add a type to the schema.

Types are automatically once their class is created. Only use this if you’re copying a type from one schema to another.

Note that the type name str(typ) must not exist in the schema, otherwise ValueError is raised.

To remove a type, use schema -= typ.

As explained in the sgqlc.types documentation, the newly created schema will inherit types from the base schema only at creation time:

>>> my_schema = Schema(global_schema)
>>> class MySchemaType(Type):
...     __schema__ = my_schema
...     i = int
...
>>> 'MySchemaType' in global_schema
False
>>> 'MySchemaType' in my_schema
True

But __iadd__ and __isub__ can be used to add or remove types:

>>> global_schema += MySchemaType
>>> 'MySchemaType' in global_schema
True
>>> global_schema -= MySchemaType
>>> 'MySchemaType' in global_schema
False

Note that different type with the same name can’t be added:

>>> my_schema2 = Schema(global_schema)
>>> class MySchemaType(Type):   # redefining, different schema: ok
...     __schema__ = my_schema2
...     f = float
...
>>> my_schema += MySchemaType
Traceback (most recent call last):
  ...
ValueError: Schema already has MySchemaType=MySchemaType
__init__(base_schema=None)[source]

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

__isub__(typ)[source]

Remove a type from the schema.

This may be of use to override some type, such as sgqlc.types.datetime.Date or sgqlc.types.datetime.DateTime.

__iter__()[source]

Schema provides an iterator over BaseType subclasses:

>>> for t in global_schema:  # doctest: +ELLIPSIS
...     print(repr(t))
...
scalar Int
scalar Float
...
type MyType {
...
}
__repr__()[source]

Return repr(self).

__str__()[source]

Short schema, using only type names.

Instead of declaring the whole schema in GraphQL notation as done by repr(), just list the type names:

>>> print(str(global_schema))  # doctest: +ELLIPSIS
{Int, Float, String, Boolean, ID, ...}
class sgqlc.types.Scalar[source]

Bases: sgqlc.types.BaseType

Basic scalar types, passed thru (no conversion).

This may be used directly if no special checks or conversions are needed. Otherwise use subclasses, like Int, Float, String, Boolean, ID

Scalar classes will never produce instance of themselves, rather return the converted value (int, bool…)

>>> class MyTypeWithScalar(Type):
...     v = Scalar
...
>>> MyTypeWithScalar({'v': 1}).v
1
>>> MyTypeWithScalar({'v': 'abc'}).v
'abc'
static __new__(cls, json_data, selection_list=None)[source]

Create and return a new object. See help(type) for accurate signature.

class sgqlc.types.Enum[source]

Bases: sgqlc.types.BaseType

This is an abstract class that enumerations should inherit and define __choices__ class member with a list of strings matching the choices allowed by this enumeration. A single string may also be used, in such case it will be split using str.split().

Note that __choices__ is not set in the final class, the metaclass will use that to build members and provide the __iter__, __contains__ and __len__ instead.

The instance constructor will never return instance of Enum, rather the string, if that matches.

Examples:

>>> class Colors(Enum):
...     __choices__ = ('RED', 'GREEN', 'BLUE')
...
>>> Colors('RED')
'RED'
>>> Colors(None) # returns None
>>> Colors('MAGENTA')
Traceback (most recent call last):
  ...
ValueError: Colors does not accept value MAGENTA

Using a string will automatically split and convert to tuple:

>>> class Fruits(Enum):
...     __choices__ = 'APPLE ORANGE BANANA'
...
>>> Fruits.__choices__
('APPLE', 'ORANGE', 'BANANA')
>>> len(Fruits)
3

Failing to define choices will raise exception:

>>> class FailureEnum(Enum):
...     pass
Traceback (most recent call last):
  ...
ValueError: FailureEnum: missing __choices__

Enumerations have a special syntax in GraphQL, no quotes:

>>> print(Fruits.__to_graphql_input__(Fruits.APPLE))
APPLE

And for JSON it’s a string as well (so JSON encoder adds quotes):

>>> print(json.dumps(Fruits.__to_json_value__(Fruits.APPLE)))
"APPLE"
static __new__(json_data, selection_list=None)[source]

Create and return a new object. See help(type) for accurate signature.

class sgqlc.types.Union[source]

Bases: sgqlc.types.BaseTypeWithTypename

This is an abstract class that union of multiple types should inherit and define __types__, a list of pre-defined Type.

>>> class IntOrFloatOrString(Union):
...     __types__ = (Int, float, 'String')
...
>>> IntOrFloatOrString # or repr(), prints out GraphQL!
union IntOrFloatOrString = Int | Float | String
>>> Int in IntOrFloatOrString
True
>>> 'Int' in IntOrFloatOrString  # may use type names as well
True
>>> int in IntOrFloatOrString  # may use native Python types as well
True
>>> ID in IntOrFloatOrString
False
>>> len(IntOrFloatOrString)
3
>>> for t in IntOrFloatOrString:
...     print(repr(t))
scalar Int
scalar Float
scalar String

Failing to define types will raise exception:

>>> class FailureUnion(Union):
...     pass
Traceback (most recent call last):
  ...
ValueError: FailureUnion: missing __types__

Whenever instantiating the type, pass a JSON object with __typename (done automatically using fragments via __as__):

>>> class TypeA(Type):
...     i = int
...
>>> class TypeB(Type):
...     s = str
...
>>> class TypeU(Union):
...     __types__ = (TypeA, TypeB)
...
>>> data = {'__typename': 'TypeA', 'i': 1}
>>> TypeU(data)
TypeA(i=1)
>>> data = {'__typename': 'TypeB', 's': 'hi'}
>>> TypeU(data)
TypeB(s='hi')

It nicely handles unknown types:

>>> data = {'v': 123}
>>> TypeU(data) # no __typename
UnknownType()
>>> data = {'__typename': 'TypeUnknown', 'v': 123}
>>> TypeU(data) # auto-generates empty types
TypeUnknown()
>>> data = None
>>> TypeU(data)
static __new__(json_data, selection_list=None)[source]

Create and return a new object. See help(type) for accurate signature.

class sgqlc.types.Variable(name, graphql_name=None)[source]

Bases: object

GraphQL variable: $varName

Usually given as Arg default value:

>>> class MyTypeWithVariable(Type):
...     f = Field(str, args={'first': Arg(int, default=Variable('var'))})
...
>>> MyTypeWithVariable
type MyTypeWithVariable {
  f(first: Int = $var): String
}
>>> print(repr(MyTypeWithVariable.f.args['first'].default))
$var
>>> print(str(MyTypeWithVariable.f.args['first'].default))
$var
>>> print(bytes(MyTypeWithVariable.f.args['first'].default).decode('utf8'))
$var
__init__(name, graphql_name=None)[source]

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

__repr__()[source]

Return repr(self).

__str__()[source]

Return str(self).

static _to_graphql_name(name)[source]

Converts a Python name, a_name to GraphQL: aName.

class sgqlc.types.Arg(typ, graphql_name=None, default=None)[source]

Bases: sgqlc.types.BaseItem

GraphQL Field argument.

>>> class MyTypeWithArgument(Type):
...     a = Field(str, args={'arg_name': int}) # implicit
...     b = Field(str, args={'arg': Arg(int)})  # explicit + Python
...     c = Field(str, args={'arg': Arg(Int)})  # explicit + sgqlc.types
...     d = Field(str, args={'arg': Arg(int, default=1)})
...
>>> MyTypeWithArgument
type MyTypeWithArgument {
  a(argName: Int): String
  b(arg: Int): String
  c(arg: Int): String
  d(arg: Int = 1): String
}
__init__(typ, graphql_name=None, default=None)[source]
Parameters:
  • typ (Scalar, Type or str) – the Scalar or Type derived class. If this would cause a cross reference and the other type is not declared yet, then use the string name to query in the schema.
  • graphql_name (str) – the name to use in JSON object, usually aName. If None or empty, will be created from python, converting a_name to aName using BaseItem._to_graphql_name()
  • default – The default value for field. May be a value or Variable.
class sgqlc.types.ArgDict(*lst, **mapping)[source]

Bases: collections.OrderedDict

The Field Argument Dict.

Common usage is inside Field:

>>> class MyType(Type):
...     a = Field(Int, args={'argument1': String})     # implicit
...     b = Field(Int, args=ArgDict(argument1=String)) # explicit
...
>>> print(repr(MyType))
type MyType {
  a(argument1: String): Int
  b(argument1: String): Int
}
>>> print(repr(MyType.a))
a(argument1: String): Int
>>> print(repr(MyType.a.args))
(argument1: String)
>>> print(repr(MyType.b))
b(argument1: String): Int
>>> print(repr(MyType.b.args))
(argument1: String)
>>> print(repr(MyType.b.args['argument1']))
argument1: String
>>> print(bytes(MyType.b.args['argument1']).decode('utf-8'))
argument1: String

This takes care to ensure values are Arg. In the example above, we’re not passing Arg, rather just a type (String) and it’s working internally to create Arg. For ease of use, can be created in various forms. Note they must be added to a container field to be useful, which would call ArgDict._set_container() for you, here called manually for testing purposes:

>>> ad = ArgDict(name=str)
>>> ad._set_container(global_schema, None)  # done automatically by Field
>>> print(ad)
(name: String)
>>> ad = ArgDict(name=String)
>>> ad._set_container(global_schema, None)  # done automatically by Field
>>> print(ad)
(name: String)
>>> ad = ArgDict({'name': str})
>>> ad._set_container(global_schema, None)  # done automatically by Field
>>> print(ad)
(name: String)
>>> ad = ArgDict(('name', str), ('other', int))
>>> ad._set_container(global_schema, None)  # done automatically by Field
>>> print(ad)
(name: String, other: Int)
>>> ad = ArgDict((('name', str), ('other', int)))
>>> ad._set_container(global_schema, None)  # done automatically by Field
>>> print(ad)
(name: String, other: Int)

Note that for better understanding, more than 3 arguments are printed in multiple lines:

>>> ad = ArgDict(a=int, b=float, c=str, d=list_of(int))
>>> ad._set_container(global_schema, None)  # done automatically by Field
>>> print(ad)
(
  a: Int
  b: Float
  c: String
  d: [Int]
)
>>> print(bytes(ad).decode('utf-8'))
(
a: Int
b: Float
c: String
d: [Int]
)

This is also the case for input values:

>>> print('fieldName' + ad.__to_graphql_input__({
...     'a': 1, 'b': 2.2, 'c': 'hi', 'd': [1, 2],
... }))
fieldName(
    a: 1
    b: 2.2
    c: "hi"
    d: [1, 2]
  )
__init__(*lst, **mapping)[source]

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

__repr__()[source]

Return repr(self).

__str__()[source]

Return str(self).

class sgqlc.types.Field(typ, graphql_name=None, args=None)[source]

Bases: sgqlc.types.BaseItem

Field in a Type container.

Each field has a GraphQL type, such as a derived class from Scalar or Type, this is used for nesting, conversion to native Python types, generating queries, etc.

__bytes__()[source]

Prints GraphQL without indentation.

>>> print(repr(global_schema.TypeUsingFields.many))
many(
    a: Int
    b: Int
    c: Int
    d: Int
  ): Int
>>> print(bytes(global_schema.TypeUsingFields.many).decode('utf-8'))
many(
a: Int
b: Int
c: Int
d: Int
): Int
__init__(typ, graphql_name=None, args=None)[source]
Parameters:
  • typ (Scalar, Type or str) – the Scalar or Type derived class. If this would cause a cross reference and the other type is not declared yet, then use the string name to query in the schema.
  • graphql_name (str) – the name to use in JSON object, usually aName. If None or empty, will be created from python, converting a_name to aName using BaseItem._to_graphql_name()
  • args (ArgDict) – The field parameters as a ArgDict or compatible type (dict, or iterable of key-value pairs). The value may be a mapped Python type (ie: str), explicit type (ie: String), type name (ie: "String", to allow cross references) or Arg instances.
class sgqlc.types.Type(json_data, selection_list=None)[source]

Bases: sgqlc.types.ContainerType

GraphQL type Name.

If the subclass also adds Interface to the class declarations, then it will emit type Name implements Iface1, Iface2, also making their fields automatically available in the final class.

class sgqlc.types.Interface(json_data, selection_list=None)[source]

Bases: sgqlc.types.ContainerType

GraphQL interface Name.

If the subclass also adds Interface to the class declarations, then it will emit interface Name implements Iface1, Iface2, also making their fields automatically available in the final class.

Whenever interfaces are instantiated, if there is a __typename in json_data and the type is known, it will automatically create the more specific type. Otherwise it instantiates the interface itself:

>>> class SomeIface(Interface):
...     i = int
...
>>> class TypeWithIface(Type, SomeIface):
...     pass
...
>>> data = {'__typename': 'TypeWithIface', 'i': 123}
>>> SomeIface(data)
TypeWithIface(i=123)
>>> data = {'__typename': 'UnknownType', 'i': 123}
>>> SomeIface(data)
SomeIface(i=123)
static __new__(cls, *args, **kwargs)[source]

Create and return a new object. See help(type) for accurate signature.

class sgqlc.types.Input(_json_obj=None, _selection_list=None, **kwargs)[source]

Bases: sgqlc.types.ContainerType

GraphQL input Name.

Input types are similar to Type, but they are used as argument values. They have more restrictions, such as no Interface, Union or Type are allowed as field types. Only scalars or Input.

Note

SGQLC currently doesn’t enforce the field type restrictions imposed by the server.

>>> class MyInput(Input):
...     a_int = int
...     a_float = float
...
>>> MyInput
input MyInput {
  aInt: Int
  aFloat: Float
}
>>> print(MyInput.__to_graphql_input__({'a_int': 1, 'a_float': 2.2}))
{aInt: 1, aFloat: 2.2}
>>> a_var = Variable('input')
>>> print(MyInput.__to_graphql_input__(a_var))
$input
__init__(_json_obj=None, _selection_list=None, **kwargs)[source]

Create the type given a json object or keyword arguments.

>>> class AnotherInput(Input):
...     a_str = str
...
>>> class TheInput(Input):
...     a_int = int
...     a_float = float
...     a_nested = AnotherInput
...     a_nested_list = list_of(AnotherInput)
...
>>> TheInput(a_int=1, a_float=1.2, a_nested=AnotherInput(a_str='hi'))
TheInput(a_int=1, a_float=1.2, a_nested=AnotherInput(a_str='hi'))
>>> TheInput({'aInt': 1, 'aFloat': 1.2, 'aNested': {'aStr': 'hi'}})
TheInput(a_int=1, a_float=1.2, a_nested=AnotherInput(a_str='hi'))
>>> value = TheInput(a_int=1, a_float=1.2,
...                  a_nested=AnotherInput(a_str='hi'),
...                  a_nested_list=[AnotherInput(a_str='there')])
>>> print(TheInput.__to_graphql_input__(value))
{aInt: 1, aFloat: 1.2, aNested: {aStr: "hi"}, aNestedList: [{aStr: "there"}]}
>>> value = TheInput({'aInt': 1, 'aFloat': 1.2, 'aNested': {'aStr': 'hi'},
...           'aNestedList': [{'aStr': 'there'}]})
>>> print(TheInput.__to_graphql_input__(value))
{aInt: 1, aFloat: 1.2, aNested: {aStr: "hi"}, aNestedList: [{aStr: "there"}]}

Note

selection_list parameter makes no sense and is ignored, it’s only provided to cope with the ContainerType interface.

class sgqlc.types.Int[source]

Bases: sgqlc.types.Scalar

Maps GraphQL Int to Python int.

>>> Int # or repr()
scalar Int
>>> str(Int)
'Int'
>>> bytes(Int)
b'scalar Int'
converter

alias of builtins.int

class sgqlc.types.Float[source]

Bases: sgqlc.types.Scalar

Maps GraphQL Float to Python float.

converter

alias of builtins.float

class sgqlc.types.String[source]

Bases: sgqlc.types.Scalar

Maps GraphQL String to Python str.

converter

alias of builtins.str

class sgqlc.types.Boolean[source]

Bases: sgqlc.types.Scalar

Maps GraphQL Boolean to Python bool.

converter

alias of builtins.bool

class sgqlc.types.ID[source]

Bases: sgqlc.types.Scalar

Maps GraphQL ID to Python str.

converter

alias of builtins.str

sgqlc.types.non_null(t)[source]

Generates non-null type (t!)

>>> class TypeWithNonNullFields(Type):
...     a_int = non_null(int)
...     a_float = non_null(Float)
...     a_string = Field(non_null(String))
...
>>> TypeWithNonNullFields
type TypeWithNonNullFields {
  aInt: Int!
  aFloat: Float!
  aString: String!
}

Giving proper JSON data:

>>> json_data = {'aInt': 1, 'aFloat': 2.1, 'aString': 'hello'}
>>> obj = TypeWithNonNullFields(json_data)
>>> obj
TypeWithNonNullFields(a_int=1, a_float=2.1, a_string='hello')

Giving incorrect JSON data:

>>> json_data = {'aInt': None, 'aFloat': 2.1, 'aString': 'hello'}
>>> obj = TypeWithNonNullFields(json_data)  # doctest: +ELLIPSIS
Traceback (most recent call last):
  ...
ValueError: TypeWithNonNullFields selection 'a_int': ...
>>> json_data = {'aInt': 1, 'aFloat': None, 'aString': 'hello'}
>>> obj = TypeWithNonNullFields(json_data)  # doctest: +ELLIPSIS
Traceback (most recent call last):
  ...
ValueError: TypeWithNonNullFields selection 'a_float': ...
>>> json_data = {'aInt': 1, 'aFloat': 2.1, 'aString': None}
>>> obj = TypeWithNonNullFields(json_data)  # doctest: +ELLIPSIS
Traceback (most recent call last):
  ...
ValueError: TypeWithNonNullFields selection 'a_string': ...

Note

Note that missing keys in JSON data are not considered None, and they won’t show in iter(obj), __str__() or __repr__()

>>> json_data = {'aInt': 1, 'aFloat': 2.1}
>>> obj = TypeWithNonNullFields(json_data)
>>> obj # repr()
TypeWithNonNullFields(a_int=1, a_float=2.1)
>>> for field_name in obj:
...     print(field_name, repr(obj[field_name]))
...
a_int 1
a_float 2.1
sgqlc.types.list_of(t)[source]

Generates list of types ([t])

The example below highlights the usage including its usage with lists:

  • non_null_list_of_int means it must be a list, not None, however list elements may be None, ie: [None, 1, None, 2];
  • list_of_non_null_int means it may be None or be a list, however list elements must not be None, ie: None or [1, 2];
  • non_null_list_of_non_null_int means it must be a list, not None and the list elements must not be Non, ie: [1, 2].
>>> class TypeWithListFields(Type):
...     list_of_int = list_of(int)
...     list_of_float = list_of(Float)
...     list_of_string = Field(list_of(String))
...     non_null_list_of_int = non_null(list_of(int))
...     list_of_non_null_int = list_of(non_null(int))
...     non_null_list_of_non_null_int = non_null(list_of(non_null(int)))
...
>>> TypeWithListFields
type TypeWithListFields {
  listOfInt: [Int]
  listOfFloat: [Float]
  listOfString: [String]
  nonNullListOfInt: [Int]!
  listOfNonNullInt: [Int!]
  nonNullListOfNonNullInt: [Int!]!
}

It takes care to enforce proper type, including non-null checking on its elements when creating instances. Giving proper JSON data:

>>> json_data = {
...     'listOfInt': [1, 2],
...     'listOfFloat': [1.1, 2.1],
...     'listOfString': ['hello', 'world'],
...     'nonNullListOfInt': [None, 1, None, 2],
...     'listOfNonNullInt': [1, 2, 3],
...     'nonNullListOfNonNullInt': [1, 2, 3, 4],
... }
>>> obj = TypeWithListFields(json_data)
>>> for field_name in obj:
...     print(field_name, repr(obj[field_name]))
...
list_of_int [1, 2]
list_of_float [1.1, 2.1]
list_of_string ['hello', 'world']
non_null_list_of_int [None, 1, None, 2]
list_of_non_null_int [1, 2, 3]
non_null_list_of_non_null_int [1, 2, 3, 4]

Note that lists that are not enclosed in non_null() can be None:

>>> json_data = {
...     'listOfInt': None,
...     'listOfFloat': None,
...     'listOfString': None,
...     'nonNullListOfInt': [None, 1, None, 2],
...     'listOfNonNullInt': None,
...     'nonNullListOfNonNullInt': [1, 2, 3],
... }
>>> obj = TypeWithListFields(json_data)
>>> for field_name in obj:
...     print(field_name, repr(obj[field_name]))
...
list_of_int None
list_of_float None
list_of_string None
non_null_list_of_int [None, 1, None, 2]
list_of_non_null_int None
non_null_list_of_non_null_int [1, 2, 3]

Types will be converted, so although not usual (since GraphQL gives you the proper JSON type), this can be done:

>>> json_data = {
...     'listOfInt': ['1', '2'],
...     'listOfFloat': [1, '2.1'],
...     'listOfString': ['hello', 2],
...     'nonNullListOfInt': [None, '1', None, 2.1],
...     'listOfNonNullInt': ['1', 2.1, 3],
...     'nonNullListOfNonNullInt': ['1', 2.1, 3, 4],
... }
>>> obj = TypeWithListFields(json_data)
>>> for field_name in obj:
...     print(field_name, repr(obj[field_name]))
...
list_of_int [1, 2]
list_of_float [1.0, 2.1]
list_of_string ['hello', '2']
non_null_list_of_int [None, 1, None, 2]
list_of_non_null_int [1, 2, 3]
non_null_list_of_non_null_int [1, 2, 3, 4]

Giving incorrect (nonconvertible) JSON data will raise exceptions:

>>> json_data = { 'listOfInt': 1 }
>>> obj = TypeWithListFields(json_data)  # doctest: +ELLIPSIS
Traceback (most recent call last):
   ...
ValueError: TypeWithListFields selection 'list_of_int': ...
>>> json_data = { 'listOfInt': ['x'] }
>>> obj = TypeWithListFields(json_data)  # doctest: +ELLIPSIS
Traceback (most recent call last):
   ...
ValueError: TypeWithListFields selection 'list_of_int': ...
>>> json_data = { 'listOfNonNullInt': [1, None] }
>>> obj = TypeWithListFields(json_data)  # doctest: +ELLIPSIS
Traceback (most recent call last):
   ...
ValueError: TypeWithListFields selection 'list_of_non_null_int': ...

Lists are usable as input types as well:

>>> class TypeWithListInput(Type):
...     a = Field(str, args={'values': Arg(list_of(int), default=[1, 2])})
...     b = Field(str, args={'values': Arg(list_of(int))})
...
>>> TypeWithListInput
type TypeWithListInput {
  a(values: [Int] = [1, 2]): String
  b(values: [Int]): String
}
>>> print(json.dumps(list_of(int).__to_json_value__([1, 2])))
[1, 2]
>>> print(json.dumps(list_of(int).__to_json_value__(None)))
null
class sgqlc.types.BaseType[source]

Bases: object

Base shared by all GraphQL classes.

__weakref__

list of weak references to the object (if defined)

class sgqlc.types.BaseMeta(name, bases, namespace)[source]

Bases: type

Automatically adds class to its schema

__ensure__(t)[source]

Checks if t is subclass of BaseType or if a mapping is known.

>>> BaseType.__ensure__(Int)
scalar Int
>>> BaseType.__ensure__(int)
scalar Int
>>> BaseType.__ensure__(bytes)
Traceback (most recent call last):
   ...
TypeError: Not BaseType or mapped: <class 'bytes'>
__init__(name, bases, namespace)[source]

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

__repr__()[source]

Return repr(self).

__str__()[source]

Return str(self).

class sgqlc.types.BaseItem(typ, graphql_name=None)[source]

Bases: object

Base item for Arg and Field.

Each parameter has a GraphQL type, such as a derived class from Scalar or Type, this is used for nesting, conversion to native Python types, generating queries, etc.

__init__(typ, graphql_name=None)[source]
Parameters:
  • typ (Scalar, Type or str) – the Scalar or Type derived class. If this would cause a cross reference and the other type is not declared yet, then use the string name to query in the schema.
  • graphql_name (str) – the name to use in JSON object, usually aName. If None or empty, will be created from python, converting a_name to aName using Arg._to_graphql_name()
__repr__()[source]

Return repr(self).

__str__()[source]

Return str(self).

static _to_graphql_name(name)[source]

Converts a Python name, a_name to GraphQL: aName.

Note that leading underscores (_) are preserved.

class sgqlc.types.ContainerType(json_data, selection_list=None)[source]

Bases: sgqlc.types.BaseTypeWithTypename

Container of Field.

For ease of use, fields can be declared by sub classes in the following ways:

  • name = str to create a simple string field. Other basic types are allowed as well: int, float, str, bool, datetime.time, datetime.date and datetime.datetime. These are only used as identifiers to translate using map_python_to_graphql dict. Note that id, although is not a type, maps to ID.
  • name = TypeName for subclasses of BaseType, such as pre-defined scalars (Int, etc) or your own defined types, from Type.
  • name = Field(TypeName, graphql_name='differentName', args={...}) to explicitly define more field information, such as GraphQL JSON name, query parameters, etc.

The metaclass ContainerTypeMeta will normalize all of those members to be instances of Field, as well as provide useful container protocol such as __contains__, __getitem__, __iter__ and so on.

Fields from all bases (interfaces, etc) are merged.

Members started with underscore (_) are not processed.

__contains__(name)[source]

Checks if for a known field name in the instance.

Unlike name in SubclassOfType, which checks amongst all declared fields, this matches only fields that exist in the object, based on the json_data used to create the object, and the one that provides the backing store:

>>> json_data = { 'aInt': 1, 'aFloat': 2.1 }
>>> obj = global_schema.TypeUsingPython(json_data)
>>> 'a_int' in obj
True
>>> 'a_float' in obj
True
>>> 'a_string' in obj  # in class, but not instance
False
>>> 'a_string' in obj.__class__
True

After it’s set for the given instance, then becomes true:

>>> obj.a_string = 'hello world'  # known field
>>> 'a_string' in obj  # now in instance
True
__getitem__(name)[source]

Get the field given its name.

Considering TypeUsingPython, previously declared in the module documentation:

>>> global_schema.TypeUsingPython['a_int']
aInt: Int
>>> global_schema.TypeUsingPython['unknown_field']
Traceback (most recent call last):
  ...
KeyError: 'TypeUsingPython has no field unknown_field'
__init__(json_data, selection_list=None)[source]

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

__iter__()[source]

Iterate over known fields of the instance.

Unlike iter(SubclassOfType), which iterates over all declared fields, this iterator matches only fields that exist in the object, based on the json_data used to create the object, and the one that provides the backing store:

>>> json_data = { 'aInt': 1, 'aFloat': 2.1 }
>>> obj = global_schema.TypeUsingPython(json_data)
>>> for field_name in obj:
...    print(field_name, repr(obj[field_name]))
a_int 1
a_float 2.1
>>> for field in obj.__class__:
...    print(repr(field))
aInt: Int
aFloat: Float
aString: String
aBoolean: Boolean
aId: ID

After it’s set for the given instance, then it’s included in the iterator:

>>> obj.a_string = 'hello world'  # known field
>>> for field_name in obj:
...    print(field_name, repr(obj[field_name]))
a_int 1
a_float 2.1
a_string 'hello world'

However that’s valid for known Field for the given ContainerType subclasses:

>>> obj.new_attr = 'some value'  # unknown field, not in 'iter'
>>> for field_name in obj:
...    print(field_name, repr(obj[field_name]))
a_int 1
a_float 2.1
a_string 'hello world'
__len__()[source]

Checks how many fields are set in the instance.

>>> json_data = { 'aInt': 1, 'aFloat': 2.1 }
>>> obj = global_schema.TypeUsingPython(json_data)
>>> len(obj)
2
>>> obj.a_string = 'hello world'  # known field
>>> len(obj)
3
__repr__()[source]

Return repr(self).

__setattr__(name, value)[source]

Sets the attribute value, if a Field updates backing store.

Considering TypeUsingPython, previously declared in the module documentation:

>>> json_data = {'aInt': 1,  'aFloat': 2.1}
>>> obj = global_schema.TypeUsingPython(json_data)
>>> obj.a_int, obj.a_float
(1, 2.1)
>>> obj.a_int = 123
>>> obj.a_int, obj.a_float
(123, 2.1)
>>> json_data['aInt']
123

However that’s valid for known Field for the given ContainerType subclasses:

>>> obj.new_attr = 'some value'  # no field, no backing store updates
>>> obj.new_attr
'some value'
>>> json_data['new_attr']
Traceback (most recent call last):
  ...
KeyError: 'new_attr'
>>> json_data['newAttr']
Traceback (most recent call last):
  ...
KeyError: 'newAttr'
__setitem__(name, value)[source]

Set the item, maps to setattr(self, name, value)

__str__()[source]

Return str(self).

class sgqlc.types.ContainerTypeMeta(name, bases, namespace)[source]

Bases: sgqlc.types.BaseMetaWithTypename

Creates container types, ensures fields are instance of Field.

__dir__() → list[source]

specialized __dir__ implementation for types

__init__(name, bases, namespace)[source]

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