diff --git a/.gitignore b/.gitignore index b7554723..9f465556 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,7 @@ target/ # PyCharm .idea +*.iml # Databases *.sqlite3 diff --git a/LICENSE b/LICENSE index 9127d7c5..ecf46fba 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 Syrus Akbary +Copyright (c) 2015-Present Syrus Akbary Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 017534f5..7b0ab346 100644 --- a/README.md +++ b/README.md @@ -10,24 +10,29 @@ Please read [UPGRADE-v1.0.md](/UPGRADE-v1.0.md) to learn how to upgrade. - **Easy to use:** Graphene helps you use GraphQL in Python without effort. - **Relay:** Graphene has builtin support for Relay -- **Django:** Automatic *Django model* mapping to Graphene Types. Check a fully working [Django](http://github.com/graphql-python/swapi-graphene) implementation +- **Data agnostic:** Graphene supports any kind of data source: SQL (Django, SQLAlchemy), NoSQL, custom Python objects, etc. + We believe that by providing a complete API you could plug Graphene anywhere your data lives and make your data available + through GraphQL. -Graphene also supports *SQLAlchemy*! -*What is supported in this Python version?* **Everything**: Interfaces, ObjectTypes, Scalars, Unions and Relay (Nodes, Connections), in addition to queries, mutations and subscriptions. +## Integrations + +Graphene has multiple integrations with different frameworks: + +| integration | Package | +|---------------|-------------------| +| Django | [graphene-django](https://github.com/graphql-python/graphene-django/) | +| SQLAlchemy | [graphene-sqlalchemy](https://github.com/graphql-python/graphene-sqlalchemy/) | +| Google App Engine | [graphene-gae](https://github.com/graphql-python/graphene-gae/) | +| Peewee | *In progress* ([Tracking Issue](https://github.com/graphql-python/graphene/issues/289)) | -**NEW**!: [Try graphene online](http://graphene-python.org/playground/) ## Installation For instaling graphene, just run this command in your shell ```bash -pip install graphene>=1.0.dev -# In case of need Django model support -pip install graphene-django>=1.0.dev -# Or in case of need SQLAlchemy support -pip install graphene-sqlalchemy>=1.0.dev +pip install "graphene>=1.0.dev" ``` ## 1.0 Upgrade Guide @@ -42,15 +47,10 @@ Here is one example for get you started: ```python class Query(graphene.ObjectType): hello = graphene.String(description='A typical hello world') - ping = graphene.String(description='Ping someone', - to=graphene.String()) def resolve_hello(self, args, context, info): return 'World' - def resolve_ping(self, args, context, info): - return 'Pinging {}'.format(args.get('to')) - schema = graphene.Schema(query=Query) ``` @@ -60,7 +60,6 @@ Then Querying `graphene.Schema` is as simple as: query = ''' query SayHello { hello - ping(to:"peter") } ''' result = schema.execute(query) diff --git a/README.rst b/README.rst index f85449e7..1d5f34f4 100644 --- a/README.rst +++ b/README.rst @@ -1,31 +1,38 @@ You are in the ``next`` unreleased version of Graphene (``1.0.dev``). -Please read `UPGRADE-v1.0.md `__ to learn how to -upgrade. +Please read `UPGRADE-v1.0.md`_ to learn how to upgrade. -------------- -|Graphene Logo| `Graphene `__ |Build Status| |PyPI version| |Coverage Status| -========================================================================================================= +|Graphene Logo| `Graphene`_ |Build Status| |PyPI version| |Coverage Status| +=========================================================================== -`Graphene `__ is a Python library for -building GraphQL schemas/types fast and easily. +`Graphene`_ is a Python library for building GraphQL schemas/types fast +and easily. - **Easy to use:** Graphene helps you use GraphQL in Python without effort. - **Relay:** Graphene has builtin support for Relay -- **Django:** Automatic *Django model* mapping to Graphene Types. Check - a fully working - `Django `__ - implementation +- **Data agnostic:** Graphene supports any kind of data source: SQL + (Django, SQLAlchemy), NoSQL, custom Python objects, etc. We believe that + by providing a complete API you could plug Graphene anywhere your + data lives and make your data available through GraphQL. -Graphene also supports *SQLAlchemy*! +Integrations +------------ -*What is supported in this Python version?* **Everything**: Interfaces, -ObjectTypes, Scalars, Unions and Relay (Nodes, Connections), in addition -to queries, mutations and subscriptions. +Graphene has multiple integrations with different frameworks: -**NEW**!: `Try graphene -online `__ ++---------------------+-------------------------------------+ +| integration | Package | ++=====================+=====================================+ +| Django | `graphene-django`_ | ++---------------------+-------------------------------------+ +| SQLAlchemy | `graphene-sqlalchemy`_ | ++---------------------+-------------------------------------+ +| Google App Engine | `graphene-gae`_ | ++---------------------+-------------------------------------+ +| Peewee | *In progress* (`Tracking Issue`_) | ++---------------------+-------------------------------------+ Installation ------------ @@ -34,17 +41,12 @@ For instaling graphene, just run this command in your shell .. code:: bash - pip install graphene>=1.0.dev - # In case of need Django model support - pip install graphene-django>=1.0.dev - # Or in case of need SQLAlchemy support - pip install graphene-sqlalchemy>=1.0.dev + pip install "graphene>=1.0.dev" 1.0 Upgrade Guide ----------------- -Please read `UPGRADE-v1.0.md `__ to learn how to -upgrade. +Please read `UPGRADE-v1.0.md`_ to learn how to upgrade. Examples -------- @@ -55,15 +57,10 @@ Here is one example for get you started: class Query(graphene.ObjectType): hello = graphene.String(description='A typical hello world') - ping = graphene.String(description='Ping someone', - to=graphene.String()) def resolve_hello(self, args, context, info): return 'World' - def resolve_ping(self, args, context, info): - return 'Pinging {}'.format(args.get('to')) - schema = graphene.Schema(query=Query) Then Querying ``graphene.Schema`` is as simple as: @@ -73,17 +70,15 @@ Then Querying ``graphene.Schema`` is as simple as: query = ''' query SayHello { hello - ping(to:"peter") } ''' result = schema.execute(query) If you want to learn even more, you can also check the following -`examples `__: +`examples`_: -- **Basic Schema**: `Starwars example `__ -- **Relay Schema**: `Starwars Relay - example `__ +- **Basic Schema**: `Starwars example`_ +- **Relay Schema**: `Starwars Relay example`_ Contributing ------------ @@ -100,6 +95,16 @@ After developing, the full test suite can be evaluated by running: python setup.py test # Use --pytest-args="-v -s" for verbose mode +.. _UPGRADE-v1.0.md: /UPGRADE-v1.0.md +.. _Graphene: http://graphene-python.org +.. _graphene-django: https://github.com/graphql-python/graphene-django/ +.. _graphene-sqlalchemy: https://github.com/graphql-python/graphene-sqlalchemy/ +.. _graphene-gae: https://github.com/graphql-python/graphene-gae/ +.. _Tracking Issue: https://github.com/graphql-python/graphene/issues/289 +.. _examples: examples/ +.. _Starwars example: examples/starwars +.. _Starwars Relay example: examples/starwars_relay + .. |Graphene Logo| image:: http://graphene-python.org/favicon.png .. |Build Status| image:: https://travis-ci.org/graphql-python/graphene.svg?branch=master :target: https://travis-ci.org/graphql-python/graphene diff --git a/UPGRADE-v1.0.md b/UPGRADE-v1.0.md index 3f153e99..e0f3c4de 100644 --- a/UPGRADE-v1.0.md +++ b/UPGRADE-v1.0.md @@ -9,6 +9,92 @@ This has been a community project from the start, we need your help making the u We have done our best to provide backwards compatibility with deprecated APIs. +## Deprecations + +* `with_context` is no longer needed. Resolvers now always take the context argument. + Before: + + ```python + def resolve_xxx(self, args, info): + # ... + ``` + + With 1.0: + ```python + def resolve_xxx(self, args, context, info): + # ... + ``` + +* `ObjectType` and `Interface` no longer accept the `abstract` option in the `Meta`. + Inheriting fields should be now achieved using `AbstractType` inheritance. + + Before: + + ```python + class MyBaseQuery(graphene.ObjectType): + my_field = String() + class Meta: + abstract = True + + class Query(MyBaseQuery): + pass + + ``` + + With 1.0: + ```python + class MyBaseQuery(graphene.AbstractType): + my_field = String() + + class Query(MyBaseQuery, graphene.ObjectType): + pass + ``` + +* The `type_name` option in the Meta in types is now `name` + +* Type references no longer work with strings, but with functions. + + Before: + + ```python + class Query(graphene.ObjectType): + user = graphene.Field('User') + users = graphene.List('User') + ``` + + With 1.0: + + ```python + class Query(graphene.ObjectType): + user = graphene.Field(lambda: User) + users = graphene.List(lambda: User) + ``` + + +## Schema + +Schemas in graphene `1.0` are `Immutable`, that means that once you create a `graphene.Schema` any +change in their attributes will not have any effect. +The `name` argument is removed from the Schema. + +The arguments `executor` and `middlewares` are also removed from the `Schema` definition. +You can still use them, but by calling explicitly in the `execute` method in `graphql`. + + +```python +# Old way +schema = graphene.Schema(name='My Schema') +schema.query = Query +schema.mutation = Mutation + +# New way +schema = graphene.Schema( + query=Query, + mutation=Mutation +) +``` + + ## Interfaces For implementing an Interface in a ObjectType, you have to it onto `Meta.interfaces`. @@ -81,14 +167,14 @@ For installing, you have to replace the old `graphene[django]` with `graphene-dj * As the package is now independent, you have to import now from `graphene_django`. * **DjangoNode no longer exists**, please use `relay.Node` instead: -```python -from graphene.relay import Node -from graphene_django import DjangoObjectType + ```python + from graphene.relay import Node + from graphene_django import DjangoObjectType -class Droid(DjangoObjectType): - class Meta: - interfaces = (Node, ) -``` + class Droid(DjangoObjectType): + class Meta: + interfaces = (Node, ) + ``` ## SQLAlchemy @@ -98,11 +184,11 @@ For installing, you have to replace the old `graphene[sqlalchemy]` with `graphen * As the package is now independent, you have to import now from `graphene_sqlalchemy`. * **SQLAlchemyNode no longer exists**, please use `relay.Node` instead: -```python -from graphene.relay import Node -from graphene_sqlalchemy import SQLAlchemyObjectType + ```python + from graphene.relay import Node + from graphene_sqlalchemy import SQLAlchemyObjectType -class Droid(SQLAlchemyObjectType): - class Meta: - interfaces = (Node, ) -``` + class Droid(SQLAlchemyObjectType): + class Meta: + interfaces = (Node, ) + ``` diff --git a/docs/conf.py b/docs/conf.py index c7a6a231..8f79896f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -134,6 +134,8 @@ todo_include_todos = True # a list of builtin themes. # html_theme = 'alabaster' +if on_rtd: + html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -438,4 +440,10 @@ epub_exclude_files = ['search.html'] # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/': None} +intersphinx_mapping = { + 'https://docs.python.org/': None, + 'python': ('https://docs.python.org/', None), + 'graphene_django': ('http://docs.graphene-python.org/projects/django/en/latest/', None), + 'graphene_sqlalchemy': ('http://docs.graphene-python.org/projects/sqlalchemy/en/latest/', None), + 'graphene_gae': ('http://docs.graphene-python.org/projects/gae/en/latest/', None), +} diff --git a/docs/index.rst b/docs/index.rst index d1d3b210..d7aa9dca 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,3 +9,10 @@ Contents: quickstart types/index relay/index + +Integrations +----- + +* `Graphene-Django `_ (`source `_) +* `Graphene-SQLAlchemy `_ (`source `_) +* `Graphene-GAE `_ (`source `_) diff --git a/docs/relay/mutations.rst b/docs/relay/mutations.rst index c475393e..c10b2e29 100644 --- a/docs/relay/mutations.rst +++ b/docs/relay/mutations.rst @@ -2,7 +2,9 @@ Mutations ========= Most APIs don’t just allow you to read data, they also allow you to -write. In GraphQL, this is done using mutations. Just like queries, +write. + +In GraphQL, this is done using mutations. Just like queries, Relay puts some additional requirements on mutations, but Graphene nicely manages that for you. All you need to do is make your mutation a subclass of ``relay.ClientIDMutation``. diff --git a/examples/starwars/tests/test_query.py b/examples/starwars/tests/test_query.py index b9887bc4..ac57c664 100644 --- a/examples/starwars/tests/test_query.py +++ b/examples/starwars/tests/test_query.py @@ -163,7 +163,7 @@ def test_fetch_some_id_query(): 'name': 'Luke Skywalker', } } - result = schema.execute(query, None, params) + result = schema.execute(query, None, variable_values=params) assert not result.errors assert result.data == expected @@ -184,7 +184,7 @@ def test_fetch_some_id_query2(): 'name': 'Han Solo', } } - result = schema.execute(query, None, params) + result = schema.execute(query, None, variable_values=params) assert not result.errors assert result.data == expected @@ -203,7 +203,7 @@ def test_invalid_id_query(): expected = { 'human': None } - result = schema.execute(query, None, params) + result = schema.execute(query, None, variable_values=params) assert not result.errors assert result.data == expected diff --git a/graphene-django/django_test_settings.py b/graphene-django/django_test_settings.py deleted file mode 100644 index 12efbf60..00000000 --- a/graphene-django/django_test_settings.py +++ /dev/null @@ -1,18 +0,0 @@ -import sys, os -ROOT_PATH = os.path.dirname(os.path.abspath(__file__)) -sys.path.insert(0, ROOT_PATH + '/examples/') - -SECRET_KEY = 1 - -INSTALLED_APPS = [ - 'graphene_django', - 'graphene_django.tests', - 'starwars', -] - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': 'django_test.sqlite', - } -} diff --git a/graphene-django/examples/cookbook/README.md b/graphene-django/examples/cookbook/README.md deleted file mode 100644 index 206d97c3..00000000 --- a/graphene-django/examples/cookbook/README.md +++ /dev/null @@ -1,64 +0,0 @@ -Cookbook Example Django Project -=============================== - -This example project demos integration between Graphene and Django. -The project contains two apps, one named `ingredients` and another -named `recepies`. - -Getting started ---------------- - -First you'll need to get the source of the project. Do this by cloning the -whole Graphene repository: - -```bash -# Get the example project code -git clone https://github.com/graphql-python/graphene.git -cd graphene/examples/cookbook -``` - -It is good idea (but not required) to create a virtual environment -for this project. We'll do this using -[virtualenv](http://docs.python-guide.org/en/latest/dev/virtualenvs/) -to keep things simple, -but you may also find something like -[virtualenvwrapper](https://virtualenvwrapper.readthedocs.org/en/latest/) -to be useful: - -```bash -# Create a virtualenv in which we can install the dependencies -virtualenv env -source env/bin/activate -``` - -Now we can install our dependencies: - -```bash -pip install -r requirements.txt -``` - -Now setup our database: - -```bash -# Setup the database -./manage.py migrate - -# Load some example data -./manage.py loaddata ingredients - -# Create an admin user (useful for logging into the admin UI -# at http://127.0.0.1:8000/admin) -./manage.py createsuperuser -``` - -Now you should be ready to start the server: - -```bash -./manage.py runserver -``` - -Now head on over to -[http://127.0.0.1:8000/graphiql](http://127.0.0.1:8000/graphiql) -and run some queries! -(See the [Django quickstart guide](http://graphene-python.org/docs/quickstart-django/) -for some example queries) diff --git a/graphene-django/examples/cookbook/cookbook/__init__.py b/graphene-django/examples/cookbook/cookbook/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene-django/examples/cookbook/cookbook/ingredients/__init__.py b/graphene-django/examples/cookbook/cookbook/ingredients/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene-django/examples/cookbook/cookbook/ingredients/admin.py b/graphene-django/examples/cookbook/cookbook/ingredients/admin.py deleted file mode 100644 index 766b23fb..00000000 --- a/graphene-django/examples/cookbook/cookbook/ingredients/admin.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.contrib import admin - -from cookbook.ingredients.models import Category, Ingredient - -admin.site.register(Ingredient) -admin.site.register(Category) diff --git a/graphene-django/examples/cookbook/cookbook/ingredients/apps.py b/graphene-django/examples/cookbook/cookbook/ingredients/apps.py deleted file mode 100644 index 21b4b08a..00000000 --- a/graphene-django/examples/cookbook/cookbook/ingredients/apps.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.apps import AppConfig - - -class IngredientsConfig(AppConfig): - name = 'cookbook.ingredients' - label = 'ingredients' - verbose_name = 'Ingredients' diff --git a/graphene-django/examples/cookbook/cookbook/ingredients/fixtures/ingredients.json b/graphene-django/examples/cookbook/cookbook/ingredients/fixtures/ingredients.json deleted file mode 100644 index 8625d3c7..00000000 --- a/graphene-django/examples/cookbook/cookbook/ingredients/fixtures/ingredients.json +++ /dev/null @@ -1 +0,0 @@ -[{"model": "ingredients.category", "pk": 1, "fields": {"name": "Dairy"}}, {"model": "ingredients.category", "pk": 2, "fields": {"name": "Meat"}}, {"model": "ingredients.ingredient", "pk": 1, "fields": {"name": "Eggs", "notes": "Good old eggs", "category": 1}}, {"model": "ingredients.ingredient", "pk": 2, "fields": {"name": "Milk", "notes": "Comes from a cow", "category": 1}}, {"model": "ingredients.ingredient", "pk": 3, "fields": {"name": "Beef", "notes": "Much like milk, this comes from a cow", "category": 2}}, {"model": "ingredients.ingredient", "pk": 4, "fields": {"name": "Chicken", "notes": "Definitely doesn't come from a cow", "category": 2}}] \ No newline at end of file diff --git a/graphene-django/examples/cookbook/cookbook/ingredients/migrations/0001_initial.py b/graphene-django/examples/cookbook/cookbook/ingredients/migrations/0001_initial.py deleted file mode 100644 index 04949239..00000000 --- a/graphene-django/examples/cookbook/cookbook/ingredients/migrations/0001_initial.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9 on 2015-12-04 18:15 -from __future__ import unicode_literals - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Category', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100)), - ], - ), - migrations.CreateModel( - name='Ingredient', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100)), - ('notes', models.TextField()), - ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ingredients', to='ingredients.Category')), - ], - ), - ] diff --git a/graphene-django/examples/cookbook/cookbook/ingredients/migrations/__init__.py b/graphene-django/examples/cookbook/cookbook/ingredients/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene-django/examples/cookbook/cookbook/ingredients/models.py b/graphene-django/examples/cookbook/cookbook/ingredients/models.py deleted file mode 100644 index cffdf1ea..00000000 --- a/graphene-django/examples/cookbook/cookbook/ingredients/models.py +++ /dev/null @@ -1,17 +0,0 @@ -from django.db import models - - -class Category(models.Model): - name = models.CharField(max_length=100) - - def __str__(self): - return self.name - - -class Ingredient(models.Model): - name = models.CharField(max_length=100) - notes = models.TextField() - category = models.ForeignKey(Category, related_name='ingredients') - - def __str__(self): - return self.name diff --git a/graphene-django/examples/cookbook/cookbook/ingredients/schema.py b/graphene-django/examples/cookbook/cookbook/ingredients/schema.py deleted file mode 100644 index 7af52624..00000000 --- a/graphene-django/examples/cookbook/cookbook/ingredients/schema.py +++ /dev/null @@ -1,36 +0,0 @@ -from cookbook.ingredients.models import Category, Ingredient -from graphene import ObjectType, Field -from graphene_django.filter import DjangoFilterConnectionField -from graphene_django.types import DjangoNode, DjangoObjectType - - -# Graphene will automatically map the User model's fields onto the UserType. -# This is configured in the UserType's Meta class (as you can see below) -class CategoryNode(DjangoNode, DjangoObjectType): - - class Meta: - model = Category - filter_fields = ['name', 'ingredients'] - filter_order_by = ['name'] - - -class IngredientNode(DjangoNode, DjangoObjectType): - - class Meta: - model = Ingredient - # Allow for some more advanced filtering here - filter_fields = { - 'name': ['exact', 'icontains', 'istartswith'], - 'notes': ['exact', 'icontains'], - 'category': ['exact'], - 'category__name': ['exact'], - } - filter_order_by = ['name', 'category__name'] - - -class Query(ObjectType): - category = Field(CategoryNode) - all_categories = DjangoFilterConnectionField(CategoryNode) - - ingredient = Field(IngredientNode) - all_ingredients = DjangoFilterConnectionField(IngredientNode) diff --git a/graphene-django/examples/cookbook/cookbook/ingredients/tests.py b/graphene-django/examples/cookbook/cookbook/ingredients/tests.py deleted file mode 100644 index 49290204..00000000 --- a/graphene-django/examples/cookbook/cookbook/ingredients/tests.py +++ /dev/null @@ -1,2 +0,0 @@ - -# Create your tests here. diff --git a/graphene-django/examples/cookbook/cookbook/ingredients/views.py b/graphene-django/examples/cookbook/cookbook/ingredients/views.py deleted file mode 100644 index b8e4ee02..00000000 --- a/graphene-django/examples/cookbook/cookbook/ingredients/views.py +++ /dev/null @@ -1,2 +0,0 @@ - -# Create your views here. diff --git a/graphene-django/examples/cookbook/cookbook/recipes/__init__.py b/graphene-django/examples/cookbook/cookbook/recipes/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene-django/examples/cookbook/cookbook/recipes/admin.py b/graphene-django/examples/cookbook/cookbook/recipes/admin.py deleted file mode 100644 index 862dd4cb..00000000 --- a/graphene-django/examples/cookbook/cookbook/recipes/admin.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.contrib import admin - -from cookbook.recipes.models import Recipe, RecipeIngredient - -admin.site.register(Recipe) -admin.site.register(RecipeIngredient) diff --git a/graphene-django/examples/cookbook/cookbook/recipes/apps.py b/graphene-django/examples/cookbook/cookbook/recipes/apps.py deleted file mode 100644 index 1f24f13e..00000000 --- a/graphene-django/examples/cookbook/cookbook/recipes/apps.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.apps import AppConfig - - -class RecipesConfig(AppConfig): - name = 'cookbook.recipes' - label = 'recipes' - verbose_name = 'Recipes' diff --git a/graphene-django/examples/cookbook/cookbook/recipes/migrations/0001_initial.py b/graphene-django/examples/cookbook/cookbook/recipes/migrations/0001_initial.py deleted file mode 100644 index 338c71a1..00000000 --- a/graphene-django/examples/cookbook/cookbook/recipes/migrations/0001_initial.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9 on 2015-12-04 18:20 -from __future__ import unicode_literals - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('ingredients', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Recipe', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=100)), - ('instructions', models.TextField()), - ], - ), - migrations.CreateModel( - name='RecipeIngredient', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('amount', models.FloatField()), - ('unit', models.CharField(choices=[('kg', 'Kilograms'), ('l', 'Litres'), ('', 'Units')], max_length=20)), - ('ingredient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='used_by', to='ingredients.Ingredient')), - ('recipes', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='amounts', to='recipes.Recipe')), - ], - ), - ] diff --git a/graphene-django/examples/cookbook/cookbook/recipes/migrations/__init__.py b/graphene-django/examples/cookbook/cookbook/recipes/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene-django/examples/cookbook/cookbook/recipes/models.py b/graphene-django/examples/cookbook/cookbook/recipes/models.py deleted file mode 100644 index a767dd23..00000000 --- a/graphene-django/examples/cookbook/cookbook/recipes/models.py +++ /dev/null @@ -1,19 +0,0 @@ -from django.db import models - -from cookbook.ingredients.models import Ingredient - - -class Recipe(models.Model): - title = models.CharField(max_length=100) - instructions = models.TextField() - - -class RecipeIngredient(models.Model): - recipes = models.ForeignKey(Recipe, related_name='amounts') - ingredient = models.ForeignKey(Ingredient, related_name='used_by') - amount = models.FloatField() - unit = models.CharField(max_length=20, choices=( - ('kg', 'Kilograms'), - ('l', 'Litres'), - ('', 'Units'), - )) diff --git a/graphene-django/examples/cookbook/cookbook/recipes/tests.py b/graphene-django/examples/cookbook/cookbook/recipes/tests.py deleted file mode 100644 index 49290204..00000000 --- a/graphene-django/examples/cookbook/cookbook/recipes/tests.py +++ /dev/null @@ -1,2 +0,0 @@ - -# Create your tests here. diff --git a/graphene-django/examples/cookbook/cookbook/recipes/views.py b/graphene-django/examples/cookbook/cookbook/recipes/views.py deleted file mode 100644 index b8e4ee02..00000000 --- a/graphene-django/examples/cookbook/cookbook/recipes/views.py +++ /dev/null @@ -1,2 +0,0 @@ - -# Create your views here. diff --git a/graphene-django/examples/cookbook/cookbook/schema.py b/graphene-django/examples/cookbook/cookbook/schema.py deleted file mode 100644 index ff2b2fe5..00000000 --- a/graphene-django/examples/cookbook/cookbook/schema.py +++ /dev/null @@ -1,9 +0,0 @@ -import graphene -import cookbook.ingredients.schema - -# print cookbook.ingredients.schema.Query._meta.graphql_type.get_fields()['allIngredients'].args - -class Query(cookbook.ingredients.schema.Query): - pass - -schema = graphene.Schema(query=Query) diff --git a/graphene-django/examples/cookbook/cookbook/settings.py b/graphene-django/examples/cookbook/cookbook/settings.py deleted file mode 100644 index bdc1f1c5..00000000 --- a/graphene-django/examples/cookbook/cookbook/settings.py +++ /dev/null @@ -1,125 +0,0 @@ -""" -Django settings for cookbook project. - -Generated by 'django-admin startproject' using Django 1.9. - -For more information on this file, see -https://docs.djangoproject.com/en/1.9/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/1.9/ref/settings/ -""" - -import os - -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '_$=$%eqxk$8ss4n7mtgarw^5$8^d5+c83!vwatr@i_81myb=e4' - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = [] - - -# Application definition - -INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django_graphiql', - - 'cookbook.ingredients.apps.IngredientsConfig', - 'cookbook.recipes.apps.RecipesConfig', -] - -MIDDLEWARE_CLASSES = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', -] - -ROOT_URLCONF = 'cookbook.urls' - -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - }, - }, -] - -WSGI_APPLICATION = 'cookbook.wsgi.application' - - -# Database -# https://docs.djangoproject.com/en/1.9/ref/settings/#databases - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - } -} - - -# Password validation -# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, -] - - -# Internationalization -# https://docs.djangoproject.com/en/1.9/topics/i18n/ - -LANGUAGE_CODE = 'en-us' - -TIME_ZONE = 'UTC' - -USE_I18N = True - -USE_L10N = True - -USE_TZ = True - - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/1.9/howto/static-files/ - -STATIC_URL = '/static/' diff --git a/graphene-django/examples/cookbook/cookbook/urls.py b/graphene-django/examples/cookbook/cookbook/urls.py deleted file mode 100644 index 8e096e35..00000000 --- a/graphene-django/examples/cookbook/cookbook/urls.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.conf.urls import include, url -from django.contrib import admin -from django.views.decorators.csrf import csrf_exempt - -from cookbook.schema import schema -from graphene_django.views import GraphQLView - -urlpatterns = [ - url(r'^admin/', admin.site.urls), - url(r'^graphql', csrf_exempt(GraphQLView.as_view(schema=schema))), - url(r'^graphiql', include('django_graphiql.urls')), -] diff --git a/graphene-django/examples/cookbook/cookbook/wsgi.py b/graphene-django/examples/cookbook/cookbook/wsgi.py deleted file mode 100644 index 954b0a80..00000000 --- a/graphene-django/examples/cookbook/cookbook/wsgi.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -WSGI config for cookbook project. - -It exposes the WSGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ -""" - -import os - -from django.core.wsgi import get_wsgi_application - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cookbook.settings") - -application = get_wsgi_application() diff --git a/graphene-django/examples/cookbook/manage.py b/graphene-django/examples/cookbook/manage.py deleted file mode 100755 index 8d8a34d6..00000000 --- a/graphene-django/examples/cookbook/manage.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cookbook.settings") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff --git a/graphene-django/examples/cookbook/requirements.txt b/graphene-django/examples/cookbook/requirements.txt deleted file mode 100644 index 0fd3c2da..00000000 --- a/graphene-django/examples/cookbook/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -graphene[django] -django_graphiql -graphql-core -django==1.9 -django-filter==0.11.0 diff --git a/graphene-django/examples/starwars/__init__.py b/graphene-django/examples/starwars/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene-django/examples/starwars/data.py b/graphene-django/examples/starwars/data.py deleted file mode 100644 index b464731f..00000000 --- a/graphene-django/examples/starwars/data.py +++ /dev/null @@ -1,114 +0,0 @@ -from .models import Character, Faction, Ship - - -def initialize(): - human = Character( - name='Human' - ) - human.save() - - droid = Character( - name='Droid' - ) - droid.save() - - rebels = Faction( - id='1', - name='Alliance to Restore the Republic', - hero=human - ) - rebels.save() - - empire = Faction( - id='2', - name='Galactic Empire', - hero=droid - ) - empire.save() - - xwing = Ship( - id='1', - name='X-Wing', - faction=rebels, - ) - xwing.save() - - ywing = Ship( - id='2', - name='Y-Wing', - faction=rebels, - ) - ywing.save() - - awing = Ship( - id='3', - name='A-Wing', - faction=rebels, - ) - awing.save() - - # Yeah, technically it's Corellian. But it flew in the service of the rebels, - # so for the purposes of this demo it's a rebel ship. - falcon = Ship( - id='4', - name='Millenium Falcon', - faction=rebels, - ) - falcon.save() - - homeOne = Ship( - id='5', - name='Home One', - faction=rebels, - ) - homeOne.save() - - tieFighter = Ship( - id='6', - name='TIE Fighter', - faction=empire, - ) - tieFighter.save() - - tieInterceptor = Ship( - id='7', - name='TIE Interceptor', - faction=empire, - ) - tieInterceptor.save() - - executor = Ship( - id='8', - name='Executor', - faction=empire, - ) - executor.save() - - -def create_ship(ship_name, faction_id): - new_ship = Ship( - name=ship_name, - faction_id=faction_id - ) - new_ship.save() - return new_ship - - -def get_ship(_id): - return Ship.objects.get(id=_id) - - -def get_ships(): - return Ship.objects.all() - - -def get_faction(_id): - return Faction.objects.get(id=_id) - - -def get_rebels(): - return get_faction(1) - - -def get_empire(): - return get_faction(2) diff --git a/graphene-django/examples/starwars/models.py b/graphene-django/examples/starwars/models.py deleted file mode 100644 index 31d06fe5..00000000 --- a/graphene-django/examples/starwars/models.py +++ /dev/null @@ -1,26 +0,0 @@ -from __future__ import absolute_import - -from django.db import models - - -class Character(models.Model): - name = models.CharField(max_length=50) - - def __str__(self): - return self.name - - -class Faction(models.Model): - name = models.CharField(max_length=50) - hero = models.ForeignKey(Character) - - def __str__(self): - return self.name - - -class Ship(models.Model): - name = models.CharField(max_length=50) - faction = models.ForeignKey(Faction, related_name='ships') - - def __str__(self): - return self.name diff --git a/graphene-django/examples/starwars/schema.py b/graphene-django/examples/starwars/schema.py deleted file mode 100644 index 382fec1c..00000000 --- a/graphene-django/examples/starwars/schema.py +++ /dev/null @@ -1,87 +0,0 @@ -import graphene -from graphene import relay, resolve_only_args, Schema -from graphene_django import DjangoObjectType - -from .data import (create_ship, get_empire, get_faction, get_rebels, get_ship, - get_ships) -from .models import ( - Character as CharacterModel, - Faction as FactionModel, - Ship as ShipModel -) - - -class Ship(DjangoObjectType): - - class Meta: - model = ShipModel - interfaces = (relay.Node, ) - - @classmethod - def get_node(cls, id, context, info): - node = get_ship(id) - print(node) - return node - - -class Character(DjangoObjectType): - - class Meta: - model = CharacterModel - - -class Faction(DjangoObjectType): - - class Meta: - model = FactionModel - interfaces = (relay.Node, ) - - @classmethod - def get_node(cls, id, context, info): - return get_faction(id) - - -class IntroduceShip(relay.ClientIDMutation): - - class Input: - ship_name = graphene.String(required=True) - faction_id = graphene.String(required=True) - - ship = graphene.Field(Ship) - faction = graphene.Field(Faction) - - @classmethod - def mutate_and_get_payload(cls, input, context, info): - ship_name = input.get('shipName') - faction_id = input.get('factionId') - ship = create_ship(ship_name, faction_id) - faction = get_faction(faction_id) - return IntroduceShip(ship=ship, faction=faction) - - -class Query(graphene.ObjectType): - rebels = graphene.Field(Faction) - empire = graphene.Field(Faction) - node = relay.Node.Field() - ships = relay.ConnectionField(Ship, description='All the ships.') - - @resolve_only_args - def resolve_ships(self): - return get_ships() - - @resolve_only_args - def resolve_rebels(self): - return get_rebels() - - @resolve_only_args - def resolve_empire(self): - return get_empire() - - -class Mutation(graphene.ObjectType): - introduce_ship = IntroduceShip.Field() - - -# We register the Character Model because if not would be -# inaccessible for the schema -schema = Schema(query=Query, mutation=Mutation, types=[Ship, Character]) diff --git a/graphene-django/examples/starwars/tests/__init__.py b/graphene-django/examples/starwars/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene-django/examples/starwars/tests/test_connections.py b/graphene-django/examples/starwars/tests/test_connections.py deleted file mode 100644 index fa760d45..00000000 --- a/graphene-django/examples/starwars/tests/test_connections.py +++ /dev/null @@ -1,47 +0,0 @@ -import pytest - -from ..data import initialize -from ..schema import schema - -pytestmark = pytest.mark.django_db - - -def test_correct_fetch_first_ship_rebels(): - initialize() - query = ''' - query RebelsShipsQuery { - rebels { - name, - hero { - name - } - ships(first: 1) { - edges { - node { - name - } - } - } - } - } - ''' - expected = { - 'rebels': { - 'name': 'Alliance to Restore the Republic', - 'hero': { - 'name': 'Human' - }, - 'ships': { - 'edges': [ - { - 'node': { - 'name': 'X-Wing' - } - } - ] - } - } - } - result = schema.execute(query) - assert not result.errors - assert result.data == expected diff --git a/graphene-django/examples/starwars/tests/test_mutation.py b/graphene-django/examples/starwars/tests/test_mutation.py deleted file mode 100644 index aa312ff5..00000000 --- a/graphene-django/examples/starwars/tests/test_mutation.py +++ /dev/null @@ -1,79 +0,0 @@ -import pytest - -from ..data import initialize -from ..schema import schema - -pytestmark = pytest.mark.django_db - - -def test_mutations(): - initialize() - - query = ''' - mutation MyMutation { - introduceShip(input:{clientMutationId:"abc", shipName: "Peter", factionId: "1"}) { - ship { - id - name - } - faction { - name - ships { - edges { - node { - id - name - } - } - } - } - } - } - ''' - expected = { - 'introduceShip': { - 'ship': { - 'id': 'U2hpcDo5', - 'name': 'Peter' - }, - 'faction': { - 'name': 'Alliance to Restore the Republic', - 'ships': { - 'edges': [{ - 'node': { - 'id': 'U2hpcDox', - 'name': 'X-Wing' - } - }, { - 'node': { - 'id': 'U2hpcDoy', - 'name': 'Y-Wing' - } - }, { - 'node': { - 'id': 'U2hpcDoz', - 'name': 'A-Wing' - } - }, { - 'node': { - 'id': 'U2hpcDo0', - 'name': 'Millenium Falcon' - } - }, { - 'node': { - 'id': 'U2hpcDo1', - 'name': 'Home One' - } - }, { - 'node': { - 'id': 'U2hpcDo5', - 'name': 'Peter' - } - }] - }, - } - } - } - result = schema.execute(query) - assert not result.errors - assert result.data == expected diff --git a/graphene-django/examples/starwars/tests/test_objectidentification.py b/graphene-django/examples/starwars/tests/test_objectidentification.py deleted file mode 100644 index fad1958d..00000000 --- a/graphene-django/examples/starwars/tests/test_objectidentification.py +++ /dev/null @@ -1,117 +0,0 @@ -import pytest - -from ..data import initialize -from ..schema import schema - -pytestmark = pytest.mark.django_db - - -def test_correctly_fetches_id_name_rebels(): - initialize() - query = ''' - query RebelsQuery { - rebels { - id - name - } - } - ''' - expected = { - 'rebels': { - 'id': 'RmFjdGlvbjox', - 'name': 'Alliance to Restore the Republic' - } - } - result = schema.execute(query) - assert not result.errors - assert result.data == expected - - -def test_correctly_refetches_rebels(): - initialize() - query = ''' - query RebelsRefetchQuery { - node(id: "RmFjdGlvbjox") { - id - ... on Faction { - name - } - } - } - ''' - expected = { - 'node': { - 'id': 'RmFjdGlvbjox', - 'name': 'Alliance to Restore the Republic' - } - } - result = schema.execute(query) - assert not result.errors - assert result.data == expected - - -def test_correctly_fetches_id_name_empire(): - initialize() - query = ''' - query EmpireQuery { - empire { - id - name - } - } - ''' - expected = { - 'empire': { - 'id': 'RmFjdGlvbjoy', - 'name': 'Galactic Empire' - } - } - result = schema.execute(query) - assert not result.errors - assert result.data == expected - - -def test_correctly_refetches_empire(): - initialize() - query = ''' - query EmpireRefetchQuery { - node(id: "RmFjdGlvbjoy") { - id - ... on Faction { - name - } - } - } - ''' - expected = { - 'node': { - 'id': 'RmFjdGlvbjoy', - 'name': 'Galactic Empire' - } - } - result = schema.execute(query) - assert not result.errors - assert result.data == expected - - -def test_correctly_refetches_xwing(): - initialize() - query = ''' - query XWingRefetchQuery { - node(id: "U2hpcDox") { - id - ... on Ship { - name - } - } - } - ''' - expected = { - 'node': { - 'id': 'U2hpcDox', - 'name': 'X-Wing' - } - } - result = schema.execute(query) - assert not result.errors - assert result.data == expected diff --git a/graphene-django/graphene_django/__init__.py b/graphene-django/graphene_django/__init__.py deleted file mode 100644 index e999888e..00000000 --- a/graphene-django/graphene_django/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from .types import ( - DjangoObjectType, -) -from .fields import ( - DjangoConnectionField, -) - -__all__ = ['DjangoObjectType', - 'DjangoConnectionField'] diff --git a/graphene-django/graphene_django/compat.py b/graphene-django/graphene_django/compat.py deleted file mode 100644 index 4b1f55a6..00000000 --- a/graphene-django/graphene_django/compat.py +++ /dev/null @@ -1,24 +0,0 @@ -from django.db import models - - -class MissingType(object): - pass - -try: - UUIDField = models.UUIDField -except AttributeError: - # Improved compatibility for Django 1.6 - UUIDField = MissingType - -try: - from django.db.models.related import RelatedObject -except: - # Improved compatibility for Django 1.6 - RelatedObject = MissingType - - -try: - # Postgres fields are only available in Django 1.8+ - from django.contrib.postgres.fields import ArrayField, HStoreField, JSONField, RangeField -except ImportError: - ArrayField, HStoreField, JSONField, RangeField = (MissingType, ) * 4 diff --git a/graphene-django/graphene_django/converter.py b/graphene-django/graphene_django/converter.py deleted file mode 100644 index dc1eb88b..00000000 --- a/graphene-django/graphene_django/converter.py +++ /dev/null @@ -1,189 +0,0 @@ -from django.db import models -from django.utils.encoding import force_text - -from graphene import Enum, List, ID, Boolean, Float, Int, String, Field, NonNull, Field, Dynamic -from graphene.types.json import JSONString -from graphene.types.datetime import DateTime -from graphene.utils.str_converters import to_const -from graphene.relay import is_node - -from .compat import (ArrayField, HStoreField, JSONField, RangeField, - RelatedObject, UUIDField) -from .utils import get_related_model, import_single_dispatch -from .fields import get_connection_field - -singledispatch = import_single_dispatch() - - -def convert_choice_name(name): - return to_const(force_text(name)) - - -def get_choices(choices): - for value, help_text in choices: - if isinstance(help_text, (tuple, list)): - for choice in get_choices(help_text): - yield choice - else: - name = convert_choice_name(help_text) - description = help_text - yield name, value, description - - -def convert_django_field_with_choices(field, registry=None): - choices = getattr(field, 'choices', None) - if choices: - meta = field.model._meta - name = '{}{}'.format(meta.object_name, field.name.capitalize()) - choices = list(get_choices(choices)) - named_choices = [(c[0], c[1]) for c in choices] - named_choices_descriptions = {c[0]:c[2] for c in choices} - - class EnumWithDescriptionsType(object): - @property - def description(self): - return named_choices_descriptions[self.name] - - enum = Enum(name, list(named_choices), type=EnumWithDescriptionsType) - return enum(description=field.help_text) - return convert_django_field(field, registry) - - -@singledispatch -def convert_django_field(field, registry=None): - raise Exception( - "Don't know how to convert the Django field %s (%s)" % - (field, field.__class__)) - - -@convert_django_field.register(models.CharField) -@convert_django_field.register(models.TextField) -@convert_django_field.register(models.EmailField) -@convert_django_field.register(models.SlugField) -@convert_django_field.register(models.URLField) -@convert_django_field.register(models.GenericIPAddressField) -@convert_django_field.register(models.FileField) -@convert_django_field.register(UUIDField) -def convert_field_to_string(field, registry=None): - return String(description=field.help_text) - - -@convert_django_field.register(models.AutoField) -def convert_field_to_id(field, registry=None): - return ID(description=field.help_text) - - -@convert_django_field.register(models.PositiveIntegerField) -@convert_django_field.register(models.PositiveSmallIntegerField) -@convert_django_field.register(models.SmallIntegerField) -@convert_django_field.register(models.BigIntegerField) -@convert_django_field.register(models.IntegerField) -def convert_field_to_int(field, registry=None): - return Int(description=field.help_text) - - -@convert_django_field.register(models.BooleanField) -def convert_field_to_boolean(field, registry=None): - return NonNull(Boolean, description=field.help_text) - - -@convert_django_field.register(models.NullBooleanField) -def convert_field_to_nullboolean(field, registry=None): - return Boolean(description=field.help_text) - - -@convert_django_field.register(models.DecimalField) -@convert_django_field.register(models.FloatField) -def convert_field_to_float(field, registry=None): - return Float(description=field.help_text) - - -@convert_django_field.register(models.DateField) -def convert_date_to_string(field, registry=None): - return DateTime(description=field.help_text) - - -@convert_django_field.register(models.OneToOneRel) -def convert_onetoone_field_to_djangomodel(field, registry=None): - model = get_related_model(field) - - def dynamic_type(): - _type = registry.get_type_for_model(model) - if not _type: - return - - return Field(_type) - - return Dynamic(dynamic_type) - - -@convert_django_field.register(models.ManyToManyField) -@convert_django_field.register(models.ManyToManyRel) -@convert_django_field.register(models.ManyToOneRel) -def convert_field_to_list_or_connection(field, registry=None): - model = get_related_model(field) - - def dynamic_type(): - _type = registry.get_type_for_model(model) - if not _type: - return - - if is_node(_type): - return get_connection_field(_type) - return Field(List(_type)) - - return Dynamic(dynamic_type) - - -# For Django 1.6 -@convert_django_field.register(RelatedObject) -def convert_relatedfield_to_djangomodel(field, registry=None): - model = field.model - - def dynamic_type(): - _type = registry.get_type_for_model(model) - if not _type: - return - - if is_node(_type): - return get_connection_field(_type) - return Field(List(_type)) - - return Dynamic(dynamic_type) - - -@convert_django_field.register(models.OneToOneField) -@convert_django_field.register(models.ForeignKey) -def convert_field_to_djangomodel(field, registry=None): - model = get_related_model(field) - - def dynamic_type(): - _type = registry.get_type_for_model(model) - if not _type: - return - - return Field(_type, description=field.help_text) - - return Dynamic(dynamic_type) - - -@convert_django_field.register(ArrayField) -def convert_postgres_array_to_list(field, registry=None): - base_type = convert_django_field(field.base_field) - if not isinstance(base_type, (List, NonNull)): - base_type = type(base_type) - return List(base_type, description=field.help_text) - - -@convert_django_field.register(HStoreField) -@convert_django_field.register(JSONField) -def convert_posgres_field_to_string(field, registry=None): - return JSONString(description=field.help_text) - - -@convert_django_field.register(RangeField) -def convert_posgres_range_to_string(field, registry=None): - inner_type = convert_django_field(field.base_field) - if not isinstance(inner_type, (List, NonNull)): - inner_type = type(inner_type) - return List(inner_type, description=field.help_text) diff --git a/graphene-django/graphene_django/debug/__init__.py b/graphene-django/graphene_django/debug/__init__.py deleted file mode 100644 index cd5015e1..00000000 --- a/graphene-django/graphene_django/debug/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .middleware import DjangoDebugMiddleware -from .types import DjangoDebug - -__all__ = ['DjangoDebugMiddleware', 'DjangoDebug'] diff --git a/graphene-django/graphene_django/debug/middleware.py b/graphene-django/graphene_django/debug/middleware.py deleted file mode 100644 index 9479fd18..00000000 --- a/graphene-django/graphene_django/debug/middleware.py +++ /dev/null @@ -1,56 +0,0 @@ -from promise import Promise -from django.db import connections - -from .sql.tracking import unwrap_cursor, wrap_cursor -from .types import DjangoDebug - - -class DjangoDebugContext(object): - - def __init__(self): - self.debug_promise = None - self.promises = [] - self.enable_instrumentation() - self.object = DjangoDebug(sql=[]) - - def get_debug_promise(self): - if not self.debug_promise: - self.debug_promise = Promise.all(self.promises) - return self.debug_promise.then(self.on_resolve_all_promises) - - def on_resolve_all_promises(self, values): - self.disable_instrumentation() - return self.object - - def add_promise(self, promise): - if self.debug_promise and not self.debug_promise.is_fulfilled: - self.promises.append(promise) - - def enable_instrumentation(self): - # This is thread-safe because database connections are thread-local. - for connection in connections.all(): - wrap_cursor(connection, self) - - def disable_instrumentation(self): - for connection in connections.all(): - unwrap_cursor(connection) - - -class DjangoDebugMiddleware(object): - - def resolve(self, next, root, args, context, info): - django_debug = getattr(context, 'django_debug', None) - if not django_debug: - if context is None: - raise Exception('DjangoDebug cannot be executed in None contexts') - try: - context.django_debug = DjangoDebugContext() - except Exception: - raise Exception('DjangoDebug need the context to be writable, context received: {}.'.format( - context.__class__.__name__ - )) - if info.schema.get_type('DjangoDebug') == info.return_type: - return context.django_debug.get_debug_promise() - promise = next(root, args, context, info) - context.django_debug.add_promise(promise) - return promise diff --git a/graphene-django/graphene_django/debug/sql/__init__.py b/graphene-django/graphene_django/debug/sql/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene-django/graphene_django/debug/sql/tracking.py b/graphene-django/graphene_django/debug/sql/tracking.py deleted file mode 100644 index 9d14e4b1..00000000 --- a/graphene-django/graphene_django/debug/sql/tracking.py +++ /dev/null @@ -1,169 +0,0 @@ -# Code obtained from django-debug-toolbar sql panel tracking -from __future__ import absolute_import, unicode_literals - -import json -from threading import local -from time import time - -from django.utils import six -from django.utils.encoding import force_text - -from .types import DjangoDebugSQL - - -class SQLQueryTriggered(Exception): - """Thrown when template panel triggers a query""" - - -class ThreadLocalState(local): - - def __init__(self): - self.enabled = True - - @property - def Wrapper(self): - if self.enabled: - return NormalCursorWrapper - return ExceptionCursorWrapper - - def recording(self, v): - self.enabled = v - - -state = ThreadLocalState() -recording = state.recording # export function - - -def wrap_cursor(connection, panel): - if not hasattr(connection, '_graphene_cursor'): - connection._graphene_cursor = connection.cursor - - def cursor(): - return state.Wrapper(connection._graphene_cursor(), connection, panel) - - connection.cursor = cursor - return cursor - - -def unwrap_cursor(connection): - if hasattr(connection, '_graphene_cursor'): - previous_cursor = connection._graphene_cursor - connection.cursor = previous_cursor - del connection._graphene_cursor - - -class ExceptionCursorWrapper(object): - """ - Wraps a cursor and raises an exception on any operation. - Used in Templates panel. - """ - - def __init__(self, cursor, db, logger): - pass - - def __getattr__(self, attr): - raise SQLQueryTriggered() - - -class NormalCursorWrapper(object): - """ - Wraps a cursor and logs queries. - """ - - def __init__(self, cursor, db, logger): - self.cursor = cursor - # Instance of a BaseDatabaseWrapper subclass - self.db = db - # logger must implement a ``record`` method - self.logger = logger - - def _quote_expr(self, element): - if isinstance(element, six.string_types): - return "'%s'" % force_text(element).replace("'", "''") - else: - return repr(element) - - def _quote_params(self, params): - if not params: - return params - if isinstance(params, dict): - return dict((key, self._quote_expr(value)) - for key, value in params.items()) - return list(map(self._quote_expr, params)) - - def _decode(self, param): - try: - return force_text(param, strings_only=True) - except UnicodeDecodeError: - return '(encoded string)' - - def _record(self, method, sql, params): - start_time = time() - try: - return method(sql, params) - finally: - stop_time = time() - duration = (stop_time - start_time) - _params = '' - try: - _params = json.dumps(list(map(self._decode, params))) - except Exception: - pass # object not JSON serializable - - alias = getattr(self.db, 'alias', 'default') - conn = self.db.connection - vendor = getattr(conn, 'vendor', 'unknown') - - params = { - 'vendor': vendor, - 'alias': alias, - 'sql': self.db.ops.last_executed_query( - self.cursor, sql, self._quote_params(params)), - 'duration': duration, - 'raw_sql': sql, - 'params': _params, - 'start_time': start_time, - 'stop_time': stop_time, - 'is_slow': duration > 10, - 'is_select': sql.lower().strip().startswith('select'), - } - - if vendor == 'postgresql': - # If an erroneous query was ran on the connection, it might - # be in a state where checking isolation_level raises an - # exception. - try: - iso_level = conn.isolation_level - except conn.InternalError: - iso_level = 'unknown' - params.update({ - 'trans_id': self.logger.get_transaction_id(alias), - 'trans_status': conn.get_transaction_status(), - 'iso_level': iso_level, - 'encoding': conn.encoding, - }) - - _sql = DjangoDebugSQL(**params) - # We keep `sql` to maintain backwards compatibility - self.logger.object.sql.append(_sql) - - def callproc(self, procname, params=()): - return self._record(self.cursor.callproc, procname, params) - - def execute(self, sql, params=()): - return self._record(self.cursor.execute, sql, params) - - def executemany(self, sql, param_list): - return self._record(self.cursor.executemany, sql, param_list) - - def __getattr__(self, attr): - return getattr(self.cursor, attr) - - def __iter__(self): - return iter(self.cursor) - - def __enter__(self): - return self - - def __exit__(self, type, value, traceback): - self.close() diff --git a/graphene-django/graphene_django/debug/sql/types.py b/graphene-django/graphene_django/debug/sql/types.py deleted file mode 100644 index 6ae4d313..00000000 --- a/graphene-django/graphene_django/debug/sql/types.py +++ /dev/null @@ -1,20 +0,0 @@ -from graphene import Boolean, Float, ObjectType, String - - -class DjangoDebugSQL(ObjectType): - vendor = String() - alias = String() - sql = String() - duration = Float() - raw_sql = String() - params = String() - start_time = Float() - stop_time = Float() - is_slow = Boolean() - is_select = Boolean() - - # Postgres - trans_id = String() - trans_status = String() - iso_level = String() - encoding = String() diff --git a/graphene-django/graphene_django/debug/tests/__init__.py b/graphene-django/graphene_django/debug/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene-django/graphene_django/debug/tests/test_query.py b/graphene-django/graphene_django/debug/tests/test_query.py deleted file mode 100644 index 87e96285..00000000 --- a/graphene-django/graphene_django/debug/tests/test_query.py +++ /dev/null @@ -1,225 +0,0 @@ -import pytest - -import graphene -from graphene.relay import Node -from graphene_django import DjangoConnectionField, DjangoObjectType -from graphene_django.utils import DJANGO_FILTER_INSTALLED - -from ...tests.models import Reporter -from ..middleware import DjangoDebugMiddleware -from ..types import DjangoDebug - - -class context(object): - pass - -# from examples.starwars_django.models import Character - -pytestmark = pytest.mark.django_db - - -def test_should_query_field(): - r1 = Reporter(last_name='ABA') - r1.save() - r2 = Reporter(last_name='Griffin') - r2.save() - - class ReporterType(DjangoObjectType): - - class Meta: - model = Reporter - interfaces = (Node, ) - - class Query(graphene.ObjectType): - reporter = graphene.Field(ReporterType) - debug = graphene.Field(DjangoDebug, name='__debug') - - def resolve_reporter(self, *args, **kwargs): - return Reporter.objects.first() - - query = ''' - query ReporterQuery { - reporter { - lastName - } - __debug { - sql { - rawSql - } - } - } - ''' - expected = { - 'reporter': { - 'lastName': 'ABA', - }, - '__debug': { - 'sql': [{ - 'rawSql': str(Reporter.objects.order_by('pk')[:1].query) - }] - } - } - schema = graphene.Schema(query=Query, middlewares=[DjangoDebugMiddleware()]) - result = schema.execute(query, context_value=context()) - assert not result.errors - assert result.data == expected - - -def test_should_query_list(): - r1 = Reporter(last_name='ABA') - r1.save() - r2 = Reporter(last_name='Griffin') - r2.save() - - class ReporterType(DjangoObjectType): - - class Meta: - model = Reporter - interfaces = (Node, ) - - class Query(graphene.ObjectType): - all_reporters = graphene.List(ReporterType) - debug = graphene.Field(DjangoDebug, name='__debug') - - def resolve_all_reporters(self, *args, **kwargs): - return Reporter.objects.all() - - query = ''' - query ReporterQuery { - allReporters { - lastName - } - __debug { - sql { - rawSql - } - } - } - ''' - expected = { - 'allReporters': [{ - 'lastName': 'ABA', - }, { - 'lastName': 'Griffin', - }], - '__debug': { - 'sql': [{ - 'rawSql': str(Reporter.objects.all().query) - }] - } - } - schema = graphene.Schema(query=Query, middlewares=[DjangoDebugMiddleware()]) - result = schema.execute(query, context_value=context()) - assert not result.errors - assert result.data == expected - - -def test_should_query_connection(): - r1 = Reporter(last_name='ABA') - r1.save() - r2 = Reporter(last_name='Griffin') - r2.save() - - class ReporterType(DjangoObjectType): - - class Meta: - model = Reporter - interfaces = (Node, ) - - class Query(graphene.ObjectType): - all_reporters = DjangoConnectionField(ReporterType) - debug = graphene.Field(DjangoDebug, name='__debug') - - def resolve_all_reporters(self, *args, **kwargs): - return Reporter.objects.all() - - query = ''' - query ReporterQuery { - allReporters(first:1) { - edges { - node { - lastName - } - } - } - __debug { - sql { - rawSql - } - } - } - ''' - expected = { - 'allReporters': { - 'edges': [{ - 'node': { - 'lastName': 'ABA', - } - }] - }, - } - schema = graphene.Schema(query=Query, middlewares=[DjangoDebugMiddleware()]) - result = schema.execute(query, context_value=context()) - assert not result.errors - assert result.data['allReporters'] == expected['allReporters'] - assert 'COUNT' in result.data['__debug']['sql'][0]['rawSql'] - query = str(Reporter.objects.all()[:1].query) - assert result.data['__debug']['sql'][1]['rawSql'] == query - - -@pytest.mark.skipif(not DJANGO_FILTER_INSTALLED, - reason="requires django-filter") -def test_should_query_connectionfilter(): - from ...filter import DjangoFilterConnectionField - - r1 = Reporter(last_name='ABA') - r1.save() - r2 = Reporter(last_name='Griffin') - r2.save() - - class ReporterType(DjangoObjectType): - - class Meta: - model = Reporter - interfaces = (Node, ) - - class Query(graphene.ObjectType): - all_reporters = DjangoFilterConnectionField(ReporterType) - s = graphene.String(resolver=lambda *_: "S") - debug = graphene.Field(DjangoDebug, name='__debug') - - def resolve_all_reporters(self, *args, **kwargs): - return Reporter.objects.all() - - query = ''' - query ReporterQuery { - allReporters(first:1) { - edges { - node { - lastName - } - } - } - __debug { - sql { - rawSql - } - } - } - ''' - expected = { - 'allReporters': { - 'edges': [{ - 'node': { - 'lastName': 'ABA', - } - }] - }, - } - schema = graphene.Schema(query=Query, middlewares=[DjangoDebugMiddleware()]) - result = schema.execute(query, context_value=context()) - assert not result.errors - assert result.data['allReporters'] == expected['allReporters'] - assert 'COUNT' in result.data['__debug']['sql'][0]['rawSql'] - query = str(Reporter.objects.all()[:1].query) - assert result.data['__debug']['sql'][1]['rawSql'] == query diff --git a/graphene-django/graphene_django/debug/types.py b/graphene-django/graphene_django/debug/types.py deleted file mode 100644 index 891fe38e..00000000 --- a/graphene-django/graphene_django/debug/types.py +++ /dev/null @@ -1,6 +0,0 @@ -from graphene import ObjectType, List -from .sql.types import DjangoDebugSQL - - -class DjangoDebug(ObjectType): - sql = List(DjangoDebugSQL) diff --git a/graphene-django/graphene_django/fields.py b/graphene-django/graphene_django/fields.py deleted file mode 100644 index b23a4b65..00000000 --- a/graphene-django/graphene_django/fields.py +++ /dev/null @@ -1,57 +0,0 @@ -from functools import partial - -from django.db.models.query import QuerySet -from graphene.relay import ConnectionField, PageInfo -from graphql_relay.connection.arrayconnection import connection_from_list_slice -from .utils import maybe_queryset, DJANGO_FILTER_INSTALLED - - -class DjangoConnectionField(ConnectionField): - - def __init__(self, *args, **kwargs): - self.on = kwargs.pop('on', False) - return super(DjangoConnectionField, self).__init__(*args, **kwargs) - - @property - def model(self): - return self.type._meta.node._meta.model - - def get_manager(self): - if self.on: - return getattr(self.model, self.on) - else: - return self.model._default_manager - - @staticmethod - def connection_resolver(resolver, connection, default_manager, root, args, context, info): - iterable = resolver(root, args, context, info) - if iterable is None: - iterable = default_manager - iterable = maybe_queryset(iterable) - if isinstance(iterable, QuerySet): - _len = iterable.count() - else: - _len = len(iterable) - connection = connection_from_list_slice( - iterable, - args, - slice_start=0, - list_length=_len, - list_slice_length=_len, - connection_type=connection, - edge_type=connection.Edge, - pageinfo_type=PageInfo, - ) - connection.iterable = iterable - connection.length = _len - return connection - - def get_resolver(self, parent_resolver): - return partial(self.connection_resolver, parent_resolver, self.type, self.get_manager()) - - -def get_connection_field(*args, **kwargs): - if DJANGO_FILTER_INSTALLED: - from .filter.fields import DjangoFilterConnectionField - return DjangoFilterConnectionField(*args, **kwargs) - return ConnectionField(*args, **kwargs) diff --git a/graphene-django/graphene_django/filter/__init__.py b/graphene-django/graphene_django/filter/__init__.py deleted file mode 100644 index 71616b6d..00000000 --- a/graphene-django/graphene_django/filter/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -import warnings -from ..utils import DJANGO_FILTER_INSTALLED - -if not DJANGO_FILTER_INSTALLED: - warnings.warn( - "Use of django filtering requires the django-filter package " - "be installed. You can do so using `pip install django-filter`", ImportWarning - ) -else: - from .fields import DjangoFilterConnectionField - from .filterset import GrapheneFilterSet, GlobalIDFilter, GlobalIDMultipleChoiceFilter - - __all__ = ['DjangoFilterConnectionField', 'GrapheneFilterSet', - 'GlobalIDFilter', 'GlobalIDMultipleChoiceFilter'] diff --git a/graphene-django/graphene_django/filter/fields.py b/graphene-django/graphene_django/filter/fields.py deleted file mode 100644 index f4f84e29..00000000 --- a/graphene-django/graphene_django/filter/fields.py +++ /dev/null @@ -1,39 +0,0 @@ -from functools import partial -from ..fields import DjangoConnectionField -from .utils import get_filtering_args_from_filterset, get_filterset_class - - -class DjangoFilterConnectionField(DjangoConnectionField): - - def __init__(self, type, fields=None, order_by=None, - extra_filter_meta=None, filterset_class=None, - *args, **kwargs): - - self.order_by = order_by or type._meta.filter_order_by - self.fields = fields or type._meta.filter_fields - meta = dict(model=type._meta.model, - fields=self.fields, - order_by=self.order_by) - if extra_filter_meta: - meta.update(extra_filter_meta) - self.filterset_class = get_filterset_class(filterset_class, **meta) - self.filtering_args = get_filtering_args_from_filterset(self.filterset_class, type) - kwargs.setdefault('args', {}) - kwargs['args'].update(self.filtering_args) - super(DjangoFilterConnectionField, self).__init__(type, *args, **kwargs) - - @staticmethod - def connection_resolver(resolver, connection, default_manager, filterset_class, filtering_args, - root, args, context, info): - filter_kwargs = {k: v for k, v in args.items() if k in filtering_args} - order = args.get('order_by', None) - qs = default_manager.get_queryset() - if order: - qs = qs.order_by(order) - qs = filterset_class(data=filter_kwargs, queryset=qs) - - return DjangoConnectionField.connection_resolver(resolver, connection, qs, root, args, context, info) - - def get_resolver(self, parent_resolver): - return partial(self.connection_resolver, parent_resolver, self.type, self.get_manager(), - self.filterset_class, self.filtering_args) diff --git a/graphene-django/graphene_django/filter/filterset.py b/graphene-django/graphene_django/filter/filterset.py deleted file mode 100644 index 7aa40310..00000000 --- a/graphene-django/graphene_django/filter/filterset.py +++ /dev/null @@ -1,115 +0,0 @@ -import six -from django.conf import settings -from django.db import models -from django.utils.text import capfirst -from django_filters import Filter, MultipleChoiceFilter -from django_filters.filterset import FilterSet, FilterSetMetaclass - -from ..forms import GlobalIDFormField, GlobalIDMultipleChoiceField -from graphql_relay.node.node import from_global_id - - -class GlobalIDFilter(Filter): - field_class = GlobalIDFormField - - def filter(self, qs, value): - _type, _id = from_global_id(value) - return super(GlobalIDFilter, self).filter(qs, _id) - - -class GlobalIDMultipleChoiceFilter(MultipleChoiceFilter): - field_class = GlobalIDMultipleChoiceField - - def filter(self, qs, value): - gids = [from_global_id(v)[1] for v in value] - return super(GlobalIDMultipleChoiceFilter, self).filter(qs, gids) - - -ORDER_BY_FIELD = getattr(settings, 'GRAPHENE_ORDER_BY_FIELD', 'order_by') - - -GRAPHENE_FILTER_SET_OVERRIDES = { - models.AutoField: { - 'filter_class': GlobalIDFilter, - }, - models.OneToOneField: { - 'filter_class': GlobalIDFilter, - }, - models.ForeignKey: { - 'filter_class': GlobalIDFilter, - }, - models.ManyToManyField: { - 'filter_class': GlobalIDMultipleChoiceFilter, - } -} - - -class GrapheneFilterSetMetaclass(FilterSetMetaclass): - - def __new__(cls, name, bases, attrs): - new_class = super(GrapheneFilterSetMetaclass, cls).__new__(cls, name, bases, attrs) - # Customise the filter_overrides for Graphene - for k, v in GRAPHENE_FILTER_SET_OVERRIDES.items(): - new_class.filter_overrides.setdefault(k, v) - return new_class - - -class GrapheneFilterSetMixin(object): - order_by_field = ORDER_BY_FIELD - - @classmethod - def filter_for_reverse_field(cls, f, name): - """Handles retrieving filters for reverse relationships - - We override the default implementation so that we can handle - Global IDs (the default implementation expects database - primary keys) - """ - rel = f.field.rel - default = { - 'name': name, - 'label': capfirst(rel.related_name) - } - if rel.multiple: - # For to-many relationships - return GlobalIDMultipleChoiceFilter(**default) - else: - # For to-one relationships - return GlobalIDFilter(**default) - - -class GrapheneFilterSet(six.with_metaclass(GrapheneFilterSetMetaclass, GrapheneFilterSetMixin, FilterSet)): - """ Base class for FilterSets used by Graphene - - You shouldn't usually need to use this class. The - DjangoFilterConnectionField will wrap FilterSets with this class as - necessary - """ - - -def setup_filterset(filterset_class): - """ Wrap a provided filterset in Graphene-specific functionality - """ - return type( - 'Graphene{}'.format(filterset_class.__name__), - (six.with_metaclass(GrapheneFilterSetMetaclass, GrapheneFilterSetMixin, filterset_class),), - {}, - ) - - -def custom_filterset_factory(model, filterset_base_class=GrapheneFilterSet, - **meta): - """ Create a filterset for the given model using the provided meta data - """ - meta.update({ - 'model': model, - }) - meta_class = type(str('Meta'), (object,), meta) - filterset = type( - str('%sFilterSet' % model._meta.object_name), - (filterset_base_class,), - { - 'Meta': meta_class - } - ) - return filterset diff --git a/graphene-django/graphene_django/filter/tests/__init__.py b/graphene-django/graphene_django/filter/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene-django/graphene_django/filter/tests/filters.py b/graphene-django/graphene_django/filter/tests/filters.py deleted file mode 100644 index 1a816691..00000000 --- a/graphene-django/graphene_django/filter/tests/filters.py +++ /dev/null @@ -1,31 +0,0 @@ -import django_filters - -from graphene_django.tests.models import Article, Pet, Reporter - - -class ArticleFilter(django_filters.FilterSet): - - class Meta: - model = Article - fields = { - 'headline': ['exact', 'icontains'], - 'pub_date': ['gt', 'lt', 'exact'], - 'reporter': ['exact'], - } - order_by = True - - -class ReporterFilter(django_filters.FilterSet): - - class Meta: - model = Reporter - fields = ['first_name', 'last_name', 'email', 'pets'] - order_by = False - - -class PetFilter(django_filters.FilterSet): - - class Meta: - model = Pet - fields = ['name'] - order_by = False diff --git a/graphene-django/graphene_django/filter/tests/test_fields.py b/graphene-django/graphene_django/filter/tests/test_fields.py deleted file mode 100644 index 4735ee0a..00000000 --- a/graphene-django/graphene_django/filter/tests/test_fields.py +++ /dev/null @@ -1,339 +0,0 @@ -from datetime import datetime - -import pytest - -from graphene import ObjectType, Schema, Field -from graphene.relay import Node -from graphene_django import DjangoObjectType -from graphene_django.forms import (GlobalIDFormField, - GlobalIDMultipleChoiceField) -from graphene_django.tests.models import Article, Pet, Reporter -from graphene_django.utils import DJANGO_FILTER_INSTALLED - -pytestmark = [] -if DJANGO_FILTER_INSTALLED: - import django_filters - from graphene_django.filter import (GlobalIDFilter, DjangoFilterConnectionField, - GlobalIDMultipleChoiceFilter) - from graphene_django.filter.tests.filters import ArticleFilter, PetFilter -else: - pytestmark.append(pytest.mark.skipif(True, reason='django_filters not installed')) - -pytestmark.append(pytest.mark.django_db) - - -class ArticleNode(DjangoObjectType): - - class Meta: - model = Article - interfaces = (Node, ) - - -class ReporterNode(DjangoObjectType): - - class Meta: - model = Reporter - interfaces = (Node, ) - - -class PetNode(DjangoObjectType): - - class Meta: - model = Pet - interfaces = (Node, ) - -# schema = Schema() - - -def get_args(field): - if isinstance(field.args, list): - return {arg.name: arg for arg in field.args} - else: - return field.args - - -def assert_arguments(field, *arguments): - ignore = ('after', 'before', 'first', 'last', 'order_by') - args = get_args(field) - actual = [ - name - for name in args - if name not in ignore and not name.startswith('_') - ] - assert set(arguments) == set(actual), \ - 'Expected arguments ({}) did not match actual ({})'.format( - arguments, - actual - ) - - -def assert_orderable(field): - args = get_args(field) - assert 'order_by' in args, \ - 'Field cannot be ordered' - - -def assert_not_orderable(field): - args = get_args(field) - assert 'order_by' not in args, \ - 'Field can be ordered' - - -def test_filter_explicit_filterset_arguments(): - field = DjangoFilterConnectionField(ArticleNode, filterset_class=ArticleFilter) - assert_arguments(field, - 'headline', 'headline__icontains', - 'pub_date', 'pub_date__gt', 'pub_date__lt', - 'reporter', - ) - - -def test_filter_shortcut_filterset_arguments_list(): - field = DjangoFilterConnectionField(ArticleNode, fields=['pub_date', 'reporter']) - assert_arguments(field, - 'pub_date', - 'reporter', - ) - - -def test_filter_shortcut_filterset_arguments_dict(): - field = DjangoFilterConnectionField(ArticleNode, fields={ - 'headline': ['exact', 'icontains'], - 'reporter': ['exact'], - }) - assert_arguments(field, - 'headline', 'headline__icontains', - 'reporter', - ) - - -def test_filter_explicit_filterset_orderable(): - field = DjangoFilterConnectionField(ArticleNode, filterset_class=ArticleFilter) - assert_orderable(field) - - -def test_filter_shortcut_filterset_orderable_true(): - field = DjangoFilterConnectionField(ArticleNode, order_by=True) - assert_orderable(field) - - -def test_filter_shortcut_filterset_orderable_headline(): - field = DjangoFilterConnectionField(ArticleNode, order_by=['headline']) - assert_orderable(field) - - -def test_filter_explicit_filterset_not_orderable(): - field = DjangoFilterConnectionField(PetNode, filterset_class=PetFilter) - assert_not_orderable(field) - - -def test_filter_shortcut_filterset_extra_meta(): - field = DjangoFilterConnectionField(ArticleNode, extra_filter_meta={ - 'order_by': True - }) - assert_orderable(field) - - -def test_filter_filterset_information_on_meta(): - class ReporterFilterNode(DjangoObjectType): - - class Meta: - model = Reporter - interfaces = (Node, ) - filter_fields = ['first_name', 'articles'] - filter_order_by = True - - field = DjangoFilterConnectionField(ReporterFilterNode) - assert_arguments(field, 'first_name', 'articles') - assert_orderable(field) - - -def test_filter_filterset_information_on_meta_related(): - class ReporterFilterNode(DjangoObjectType): - - class Meta: - model = Reporter - interfaces = (Node, ) - filter_fields = ['first_name', 'articles'] - filter_order_by = True - - class ArticleFilterNode(DjangoObjectType): - - class Meta: - model = Article - interfaces = (Node, ) - filter_fields = ['headline', 'reporter'] - filter_order_by = True - - class Query(ObjectType): - all_reporters = DjangoFilterConnectionField(ReporterFilterNode) - all_articles = DjangoFilterConnectionField(ArticleFilterNode) - reporter = Field(ReporterFilterNode) - article = Field(ArticleFilterNode) - - schema = Schema(query=Query) - articles_field = ReporterFilterNode._meta.fields['articles'].get_type() - assert_arguments(articles_field, 'headline', 'reporter') - assert_orderable(articles_field) - - -def test_filter_filterset_related_results(): - class ReporterFilterNode(DjangoObjectType): - - class Meta: - model = Reporter - interfaces = (Node, ) - filter_fields = ['first_name', 'articles'] - filter_order_by = True - - class ArticleFilterNode(DjangoObjectType): - - class Meta: - interfaces = (Node, ) - model = Article - filter_fields = ['headline', 'reporter'] - filter_order_by = True - - class Query(ObjectType): - all_reporters = DjangoFilterConnectionField(ReporterFilterNode) - all_articles = DjangoFilterConnectionField(ArticleFilterNode) - reporter = Field(ReporterFilterNode) - article = Field(ArticleFilterNode) - - r1 = Reporter.objects.create(first_name='r1', last_name='r1', email='r1@test.com') - r2 = Reporter.objects.create(first_name='r2', last_name='r2', email='r2@test.com') - Article.objects.create(headline='a1', pub_date=datetime.now(), reporter=r1) - Article.objects.create(headline='a2', pub_date=datetime.now(), reporter=r2) - - query = ''' - query { - allReporters { - edges { - node { - articles { - edges { - node { - headline - } - } - } - } - } - } - } - ''' - schema = Schema(query=Query) - result = schema.execute(query) - assert not result.errors - # We should only get back a single article for each reporter - assert len(result.data['allReporters']['edges'][0]['node']['articles']['edges']) == 1 - assert len(result.data['allReporters']['edges'][1]['node']['articles']['edges']) == 1 - - -def test_global_id_field_implicit(): - field = DjangoFilterConnectionField(ArticleNode, fields=['id']) - filterset_class = field.filterset_class - id_filter = filterset_class.base_filters['id'] - assert isinstance(id_filter, GlobalIDFilter) - assert id_filter.field_class == GlobalIDFormField - - -def test_global_id_field_explicit(): - class ArticleIdFilter(django_filters.FilterSet): - - class Meta: - model = Article - fields = ['id'] - - field = DjangoFilterConnectionField(ArticleNode, filterset_class=ArticleIdFilter) - filterset_class = field.filterset_class - id_filter = filterset_class.base_filters['id'] - assert isinstance(id_filter, GlobalIDFilter) - assert id_filter.field_class == GlobalIDFormField - - -def test_global_id_field_relation(): - field = DjangoFilterConnectionField(ArticleNode, fields=['reporter']) - filterset_class = field.filterset_class - id_filter = filterset_class.base_filters['reporter'] - assert isinstance(id_filter, GlobalIDFilter) - assert id_filter.field_class == GlobalIDFormField - - -def test_global_id_multiple_field_implicit(): - field = DjangoFilterConnectionField(ReporterNode, fields=['pets']) - filterset_class = field.filterset_class - multiple_filter = filterset_class.base_filters['pets'] - assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter) - assert multiple_filter.field_class == GlobalIDMultipleChoiceField - - -def test_global_id_multiple_field_explicit(): - class ReporterPetsFilter(django_filters.FilterSet): - - class Meta: - model = Reporter - fields = ['pets'] - - field = DjangoFilterConnectionField(ReporterNode, filterset_class=ReporterPetsFilter) - filterset_class = field.filterset_class - multiple_filter = filterset_class.base_filters['pets'] - assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter) - assert multiple_filter.field_class == GlobalIDMultipleChoiceField - - -def test_global_id_multiple_field_implicit_reverse(): - field = DjangoFilterConnectionField(ReporterNode, fields=['articles']) - filterset_class = field.filterset_class - multiple_filter = filterset_class.base_filters['articles'] - assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter) - assert multiple_filter.field_class == GlobalIDMultipleChoiceField - - -def test_global_id_multiple_field_explicit_reverse(): - class ReporterPetsFilter(django_filters.FilterSet): - - class Meta: - model = Reporter - fields = ['articles'] - - field = DjangoFilterConnectionField(ReporterNode, filterset_class=ReporterPetsFilter) - filterset_class = field.filterset_class - multiple_filter = filterset_class.base_filters['articles'] - assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter) - assert multiple_filter.field_class == GlobalIDMultipleChoiceField - - -def test_filter_filterset_related_results(): - class ReporterFilterNode(DjangoObjectType): - - class Meta: - model = Reporter - interfaces = (Node, ) - filter_fields = { - 'first_name': ['icontains'] - } - - class Query(ObjectType): - all_reporters = DjangoFilterConnectionField(ReporterFilterNode) - - r1 = Reporter.objects.create(first_name='A test user', last_name='Last Name', email='test1@test.com') - r2 = Reporter.objects.create(first_name='Other test user', last_name='Other Last Name', email='test2@test.com') - r3 = Reporter.objects.create(first_name='Random', last_name='RandomLast', email='random@test.com') - - query = ''' - query { - allReporters(firstName_Icontains: "test") { - edges { - node { - id - } - } - } - } - ''' - schema = Schema(query=Query) - result = schema.execute(query) - assert not result.errors - # We should only get two reporters - assert len(result.data['allReporters']['edges']) == 2 diff --git a/graphene-django/graphene_django/filter/utils.py b/graphene-django/graphene_django/filter/utils.py deleted file mode 100644 index 86a34f27..00000000 --- a/graphene-django/graphene_django/filter/utils.py +++ /dev/null @@ -1,31 +0,0 @@ -import six - -from graphene import Argument, String -from .filterset import custom_filterset_factory, setup_filterset - - -def get_filtering_args_from_filterset(filterset_class, type): - """ Inspect a FilterSet and produce the arguments to pass to - a Graphene Field. These arguments will be available to - filter against in the GraphQL - """ - from ..form_converter import convert_form_field - - args = {} - for name, filter_field in six.iteritems(filterset_class.base_filters): - field_type = convert_form_field(filter_field.field) - args[name] = field_type - - # Also add the 'order_by' field - if filterset_class._meta.order_by: - args[filterset_class.order_by_field] = String() - return args - - -def get_filterset_class(filterset_class, **meta): - """Get the class to be used as the FilterSet""" - if filterset_class: - # If were given a FilterSet class, then set it up and - # return it - return setup_filterset(filterset_class) - return custom_filterset_factory(**meta) diff --git a/graphene-django/graphene_django/form_converter.py b/graphene-django/graphene_django/form_converter.py deleted file mode 100644 index 2ddb912b..00000000 --- a/graphene-django/graphene_django/form_converter.py +++ /dev/null @@ -1,70 +0,0 @@ -from django import forms -from django.forms.fields import BaseTemporalField - -from graphene import ID, Boolean, Float, Int, String, List -from .forms import GlobalIDFormField, GlobalIDMultipleChoiceField -from .utils import import_single_dispatch - -singledispatch = import_single_dispatch() - -try: - UUIDField = forms.UUIDField -except AttributeError: - class UUIDField(object): - pass - - -@singledispatch -def convert_form_field(field): - raise Exception( - "Don't know how to convert the Django form field %s (%s) " - "to Graphene type" % - (field, field.__class__) - ) - - -@convert_form_field.register(BaseTemporalField) -@convert_form_field.register(forms.CharField) -@convert_form_field.register(forms.EmailField) -@convert_form_field.register(forms.SlugField) -@convert_form_field.register(forms.URLField) -@convert_form_field.register(forms.ChoiceField) -@convert_form_field.register(forms.RegexField) -@convert_form_field.register(forms.Field) -@convert_form_field.register(UUIDField) -def convert_form_field_to_string(field): - return String(description=field.help_text) - - -@convert_form_field.register(forms.IntegerField) -@convert_form_field.register(forms.NumberInput) -def convert_form_field_to_int(field): - return Int(description=field.help_text) - - -@convert_form_field.register(forms.BooleanField) -def convert_form_field_to_boolean(field): - return Boolean(description=field.help_text, required=True) - - -@convert_form_field.register(forms.NullBooleanField) -def convert_form_field_to_nullboolean(field): - return Boolean(description=field.help_text) - - -@convert_form_field.register(forms.DecimalField) -@convert_form_field.register(forms.FloatField) -def convert_form_field_to_float(field): - return Float(description=field.help_text) - - -@convert_form_field.register(forms.ModelMultipleChoiceField) -@convert_form_field.register(GlobalIDMultipleChoiceField) -def convert_form_field_to_list(field): - return List(ID) - - -@convert_form_field.register(forms.ModelChoiceField) -@convert_form_field.register(GlobalIDFormField) -def convert_form_field_to_id(field): - return ID() diff --git a/graphene-django/graphene_django/forms.py b/graphene-django/graphene_django/forms.py deleted file mode 100644 index a54f0a5a..00000000 --- a/graphene-django/graphene_django/forms.py +++ /dev/null @@ -1,42 +0,0 @@ -import binascii - -from django.core.exceptions import ValidationError -from django.forms import CharField, Field, MultipleChoiceField -from django.utils.translation import ugettext_lazy as _ - -from graphql_relay import from_global_id - - -class GlobalIDFormField(Field): - default_error_messages = { - 'invalid': _('Invalid ID specified.'), - } - - def clean(self, value): - if not value and not self.required: - return None - - try: - _type, _id = from_global_id(value) - except (TypeError, ValueError, UnicodeDecodeError, binascii.Error): - raise ValidationError(self.error_messages['invalid']) - - try: - CharField().clean(_id) - CharField().clean(_type) - except ValidationError: - raise ValidationError(self.error_messages['invalid']) - - return value - - -class GlobalIDMultipleChoiceField(MultipleChoiceField): - default_error_messages = { - 'invalid_choice': _('One of the specified IDs was invalid (%(value)s).'), - 'invalid_list': _('Enter a list of values.'), - } - - def valid_value(self, value): - # Clean will raise a validation error if there is a problem - GlobalIDFormField().clean(value) - return True diff --git a/graphene-django/graphene_django/management/__init__.py b/graphene-django/graphene_django/management/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene-django/graphene_django/management/commands/__init__.py b/graphene-django/graphene_django/management/commands/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene-django/graphene_django/management/commands/graphql_schema.py b/graphene-django/graphene_django/management/commands/graphql_schema.py deleted file mode 100644 index 07b802d4..00000000 --- a/graphene-django/graphene_django/management/commands/graphql_schema.py +++ /dev/null @@ -1,72 +0,0 @@ -import importlib -import json -from distutils.version import StrictVersion -from optparse import make_option - -from django import get_version as get_django_version -from django.core.management.base import BaseCommand, CommandError - -LT_DJANGO_1_8 = StrictVersion(get_django_version()) < StrictVersion('1.8') - -if LT_DJANGO_1_8: - class CommandArguments(BaseCommand): - option_list = BaseCommand.option_list + ( - make_option( - '--schema', - type=str, - dest='schema', - default='', - help='Django app containing schema to dump, e.g. myproject.core.schema', - ), - make_option( - '--out', - type=str, - dest='out', - default='', - help='Output file (default: schema.json)' - ), - ) -else: - class CommandArguments(BaseCommand): - - def add_arguments(self, parser): - from django.conf import settings - parser.add_argument( - '--schema', - type=str, - dest='schema', - default=getattr(settings, 'GRAPHENE_SCHEMA', ''), - help='Django app containing schema to dump, e.g. myproject.core.schema') - - parser.add_argument( - '--out', - type=str, - dest='out', - default=getattr(settings, 'GRAPHENE_SCHEMA_OUTPUT', 'schema.json'), - help='Output file (default: schema.json)') - - -class Command(CommandArguments): - help = 'Dump Graphene schema JSON to file' - can_import_settings = True - - def save_file(self, out, schema_dict): - with open(out, 'w') as outfile: - json.dump(schema_dict, outfile) - - def handle(self, *args, **options): - from django.conf import settings - schema = options.get('schema') or getattr(settings, 'GRAPHENE_SCHEMA', '') - out = options.get('out') or getattr(settings, 'GRAPHENE_SCHEMA_OUTPUT', 'schema.json') - - if schema == '': - raise CommandError('Specify schema on GRAPHENE_SCHEMA setting or by using --schema') - i = importlib.import_module(schema) - - schema_dict = {'data': i.schema.introspect()} - self.save_file(out, schema_dict) - - style = getattr(self, 'style', None) - SUCCESS = getattr(style, 'SUCCESS', lambda x: x) - - self.stdout.write(SUCCESS('Successfully dumped GraphQL schema to %s' % out)) diff --git a/graphene-django/graphene_django/registry.py b/graphene-django/graphene_django/registry.py deleted file mode 100644 index 464e8263..00000000 --- a/graphene-django/graphene_django/registry.py +++ /dev/null @@ -1,29 +0,0 @@ -class Registry(object): - def __init__(self): - self._registry = {} - self._registry_models = {} - - def register(self, cls): - from .types import DjangoObjectType - assert issubclass(cls, DjangoObjectType), 'Only DjangoObjectTypes can be registered, received "{}"'.format(cls.__name__) - assert cls._meta.registry == self, 'Registry for a Model have to match.' - # assert self.get_type_for_model(cls._meta.model) == cls, 'Multiple DjangoObjectTypes registered for "{}"'.format(cls._meta.model) - self._registry[cls._meta.model] = cls - - def get_type_for_model(self, model): - return self._registry.get(model) - - -registry = None - - -def get_global_registry(): - global registry - if not registry: - registry = Registry() - return registry - - -def reset_global_registry(): - global registry - registry = None diff --git a/graphene-django/graphene_django/tests/__init__.py b/graphene-django/graphene_django/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene-django/graphene_django/tests/models.py b/graphene-django/graphene_django/tests/models.py deleted file mode 100644 index a0559126..00000000 --- a/graphene-django/graphene_django/tests/models.py +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import absolute_import - -from django.db import models -from django.utils.translation import ugettext_lazy as _ - -CHOICES = ( - (1, 'this'), - (2, _('that')) -) - - -class Pet(models.Model): - name = models.CharField(max_length=30) - - -class FilmDetails(models.Model): - location = models.CharField(max_length=30) - film = models.OneToOneField('Film', related_name='details') - - -class Film(models.Model): - reporters = models.ManyToManyField('Reporter', - related_name='films') - - -class Reporter(models.Model): - first_name = models.CharField(max_length=30) - last_name = models.CharField(max_length=30) - email = models.EmailField() - pets = models.ManyToManyField('self') - a_choice = models.CharField(max_length=30, choices=CHOICES) - - def __str__(self): # __unicode__ on Python 2 - return "%s %s" % (self.first_name, self.last_name) - - -class Article(models.Model): - headline = models.CharField(max_length=100) - pub_date = models.DateField() - reporter = models.ForeignKey(Reporter, related_name='articles') - lang = models.CharField(max_length=2, help_text='Language', choices=[ - ('es', 'Spanish'), - ('en', 'English') - ], default='es') - importance = models.IntegerField('Importance', null=True, blank=True, - choices=[(1, u'Very important'), (2, u'Not as important')]) - - def __str__(self): # __unicode__ on Python 2 - return self.headline - - class Meta: - ordering = ('headline',) diff --git a/graphene-django/graphene_django/tests/schema.py b/graphene-django/graphene_django/tests/schema.py deleted file mode 100644 index 55841498..00000000 --- a/graphene-django/graphene_django/tests/schema.py +++ /dev/null @@ -1,39 +0,0 @@ -import graphene -from graphene import Schema, relay -from ..types import DjangoObjectType - -from .models import Article, Reporter - - -class Character(DjangoObjectType): - - class Meta: - model = Reporter - interfaces = (relay.Node, ) - - def get_node(self, id, context, info): - pass - - -class Human(DjangoObjectType): - raises = graphene.String() - - class Meta: - model = Article - interfaces = (relay.Node, ) - - def resolve_raises(self, *args): - raise Exception("This field should raise exception") - - def get_node(self, id): - pass - - -class Query(graphene.ObjectType): - human = graphene.Field(Human) - - def resolve_human(self, args, context, info): - return Human() - - -schema = Schema(query=Query) diff --git a/graphene-django/graphene_django/tests/test_command.py b/graphene-django/graphene_django/tests/test_command.py deleted file mode 100644 index 280a9d3d..00000000 --- a/graphene-django/graphene_django/tests/test_command.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.core import management -from mock import patch -from six import StringIO - - -@patch('graphene_django.management.commands.graphql_schema.Command.save_file') -def test_generate_file_on_call_graphql_schema(savefile_mock, settings): - settings.GRAPHENE_SCHEMA = 'graphene_django.tests.schema' - out = StringIO() - management.call_command('graphql_schema', schema='', stdout=out) - assert "Successfully dumped GraphQL schema to schema.json" in out.getvalue() diff --git a/graphene-django/graphene_django/tests/test_converter.py b/graphene-django/graphene_django/tests/test_converter.py deleted file mode 100644 index ee1553c8..00000000 --- a/graphene-django/graphene_django/tests/test_converter.py +++ /dev/null @@ -1,261 +0,0 @@ -import pytest -from django.db import models -from django.utils.translation import ugettext_lazy as _ -from py.test import raises - -import graphene -from graphene.relay import Node, ConnectionField -from graphene.types.datetime import DateTime -from graphene.types.json import JSONString -# from graphene.core.types.custom_scalars import DateTime, JSONString - -from ..compat import (ArrayField, HStoreField, JSONField, MissingType, - RangeField) -from ..converter import convert_django_field, convert_django_field_with_choices -from ..registry import Registry -from .models import Article, Reporter, Film, FilmDetails, Pet -from ..types import DjangoObjectType - - -def assert_conversion(django_field, graphene_field, *args, **kwargs): - field = django_field(help_text='Custom Help Text', *args, **kwargs) - graphene_type = convert_django_field(field) - assert isinstance(graphene_type, graphene_field) - field = graphene_type.Field() - assert field.description == 'Custom Help Text' - return field - - -def test_should_unknown_django_field_raise_exception(): - with raises(Exception) as excinfo: - convert_django_field(None) - assert 'Don\'t know how to convert the Django field' in str(excinfo.value) - - -def test_should_date_convert_string(): - assert_conversion(models.DateField, DateTime) - - -def test_should_char_convert_string(): - assert_conversion(models.CharField, graphene.String) - - -def test_should_text_convert_string(): - assert_conversion(models.TextField, graphene.String) - - -def test_should_email_convert_string(): - assert_conversion(models.EmailField, graphene.String) - - -def test_should_slug_convert_string(): - assert_conversion(models.SlugField, graphene.String) - - -def test_should_url_convert_string(): - assert_conversion(models.URLField, graphene.String) - - -def test_should_ipaddress_convert_string(): - assert_conversion(models.GenericIPAddressField, graphene.String) - - -def test_should_file_convert_string(): - assert_conversion(models.FileField, graphene.String) - - -def test_should_image_convert_string(): - assert_conversion(models.ImageField, graphene.String) - - -def test_should_auto_convert_id(): - assert_conversion(models.AutoField, graphene.ID, primary_key=True) - - -def test_should_positive_integer_convert_int(): - assert_conversion(models.PositiveIntegerField, graphene.Int) - - -def test_should_positive_small_convert_int(): - assert_conversion(models.PositiveSmallIntegerField, graphene.Int) - - -def test_should_small_integer_convert_int(): - assert_conversion(models.SmallIntegerField, graphene.Int) - - -def test_should_big_integer_convert_int(): - assert_conversion(models.BigIntegerField, graphene.Int) - - -def test_should_integer_convert_int(): - assert_conversion(models.IntegerField, graphene.Int) - - -def test_should_boolean_convert_boolean(): - field = assert_conversion(models.BooleanField, graphene.NonNull) - assert field.type.of_type == graphene.Boolean - - -def test_should_nullboolean_convert_boolean(): - assert_conversion(models.NullBooleanField, graphene.Boolean) - - -def test_field_with_choices_convert_enum(): - field = models.CharField(help_text='Language', choices=( - ('es', 'Spanish'), - ('en', 'English') - )) - - class TranslatedModel(models.Model): - language = field - - class Meta: - app_label = 'test' - - graphene_type = convert_django_field_with_choices(field) - assert isinstance(graphene_type, graphene.Enum) - assert graphene_type._meta.name == 'TranslatedModelLanguage' - assert graphene_type._meta.enum.__members__['SPANISH'].value == 'es' - assert graphene_type._meta.enum.__members__['SPANISH'].description == 'Spanish' - assert graphene_type._meta.enum.__members__['ENGLISH'].value == 'en' - assert graphene_type._meta.enum.__members__['ENGLISH'].description == 'English' - - -def test_field_with_grouped_choices(): - field = models.CharField(help_text='Language', choices=( - ('Europe', ( - ('es', 'Spanish'), - ('en', 'English'), - )), - )) - - class GroupedChoicesModel(models.Model): - language = field - - class Meta: - app_label = 'test' - - convert_django_field_with_choices(field) - - -def test_field_with_choices_gettext(): - field = models.CharField(help_text='Language', choices=( - ('es', _('Spanish')), - ('en', _('English')) - )) - - class TranslatedChoicesModel(models.Model): - language = field - - class Meta: - app_label = 'test' - - convert_django_field_with_choices(field) - - -def test_should_float_convert_float(): - assert_conversion(models.FloatField, graphene.Float) - - -def test_should_manytomany_convert_connectionorlist(): - registry = Registry() - dynamic_field = convert_django_field(Reporter._meta.local_many_to_many[0], registry) - assert not dynamic_field.get_type() - - -def test_should_manytomany_convert_connectionorlist_list(): - class A(DjangoObjectType): - class Meta: - model = Reporter - - graphene_field = convert_django_field(Reporter._meta.local_many_to_many[0], A._meta.registry) - assert isinstance(graphene_field, graphene.Dynamic) - dynamic_field = graphene_field.get_type() - assert isinstance(dynamic_field, graphene.Field) - assert isinstance(dynamic_field.type, graphene.List) - assert dynamic_field.type.of_type == A - - -def test_should_manytomany_convert_connectionorlist_connection(): - class A(DjangoObjectType): - class Meta: - model = Reporter - interfaces = (Node, ) - - graphene_field = convert_django_field(Reporter._meta.local_many_to_many[0], A._meta.registry) - assert isinstance(graphene_field, graphene.Dynamic) - dynamic_field = graphene_field.get_type() - assert isinstance(dynamic_field, ConnectionField) - assert dynamic_field.type == A.Connection - - -def test_should_manytoone_convert_connectionorlist(): - # Django 1.9 uses 'rel', <1.9 uses 'related - related = getattr(Reporter.articles, 'rel', None) or \ - getattr(Reporter.articles, 'related') - - class A(DjangoObjectType): - class Meta: - model = Article - - graphene_field = convert_django_field(related, A._meta.registry) - assert isinstance(graphene_field, graphene.Dynamic) - dynamic_field = graphene_field.get_type() - assert isinstance(dynamic_field, graphene.Field) - assert isinstance(dynamic_field.type, graphene.List) - assert dynamic_field.type.of_type == A - - -def test_should_onetoone_reverse_convert_model(): - # Django 1.9 uses 'rel', <1.9 uses 'related - related = getattr(Film.details, 'rel', None) or \ - getattr(Film.details, 'related') - - class A(DjangoObjectType): - class Meta: - model = FilmDetails - - graphene_field = convert_django_field(related, A._meta.registry) - assert isinstance(graphene_field, graphene.Dynamic) - dynamic_field = graphene_field.get_type() - assert isinstance(dynamic_field, graphene.Field) - assert dynamic_field.type == A - - -@pytest.mark.skipif(ArrayField is MissingType, - reason="ArrayField should exist") -def test_should_postgres_array_convert_list(): - field = assert_conversion(ArrayField, graphene.List, models.CharField(max_length=100)) - assert isinstance(field.type, graphene.List) - assert field.type.of_type == graphene.String - - -@pytest.mark.skipif(ArrayField is MissingType, - reason="ArrayField should exist") -def test_should_postgres_array_multiple_convert_list(): - field = assert_conversion(ArrayField, graphene.List, ArrayField(models.CharField(max_length=100))) - assert isinstance(field.type, graphene.List) - assert isinstance(field.type.of_type, graphene.List) - assert field.type.of_type.of_type == graphene.String - - -@pytest.mark.skipif(HStoreField is MissingType, - reason="HStoreField should exist") -def test_should_postgres_hstore_convert_string(): - assert_conversion(HStoreField, JSONString) - - -@pytest.mark.skipif(JSONField is MissingType, - reason="JSONField should exist") -def test_should_postgres_json_convert_string(): - assert_conversion(JSONField, JSONString) - - -@pytest.mark.skipif(RangeField is MissingType, - reason="RangeField should exist") -def test_should_postgres_range_convert_list(): - from django.contrib.postgres.fields import IntegerRangeField - field = assert_conversion(IntegerRangeField, graphene.List) - assert isinstance(field.type, graphene.List) - assert field.type.of_type == graphene.Int diff --git a/graphene-django/graphene_django/tests/test_form_converter.py b/graphene-django/graphene_django/tests/test_form_converter.py deleted file mode 100644 index 661e73a8..00000000 --- a/graphene-django/graphene_django/tests/test_form_converter.py +++ /dev/null @@ -1,103 +0,0 @@ -from django import forms -from py.test import raises - -import graphene -from ..form_converter import convert_form_field -from graphene import ID, List, NonNull - -from .models import Reporter - - -def assert_conversion(django_field, graphene_field, *args): - field = django_field(*args, help_text='Custom Help Text') - graphene_type = convert_form_field(field) - assert isinstance(graphene_type, graphene_field) - field = graphene_type.Field() - assert field.description == 'Custom Help Text' - return field - - -def test_should_unknown_django_field_raise_exception(): - with raises(Exception) as excinfo: - convert_form_field(None) - assert 'Don\'t know how to convert the Django form field' in str(excinfo.value) - - -def test_should_date_convert_string(): - assert_conversion(forms.DateField, graphene.String) - - -def test_should_time_convert_string(): - assert_conversion(forms.TimeField, graphene.String) - - -def test_should_date_time_convert_string(): - assert_conversion(forms.DateTimeField, graphene.String) - - -def test_should_char_convert_string(): - assert_conversion(forms.CharField, graphene.String) - - -def test_should_email_convert_string(): - assert_conversion(forms.EmailField, graphene.String) - - -def test_should_slug_convert_string(): - assert_conversion(forms.SlugField, graphene.String) - - -def test_should_url_convert_string(): - assert_conversion(forms.URLField, graphene.String) - - -def test_should_choice_convert_string(): - assert_conversion(forms.ChoiceField, graphene.String) - - -def test_should_base_field_convert_string(): - assert_conversion(forms.Field, graphene.String) - - -def test_should_regex_convert_string(): - assert_conversion(forms.RegexField, graphene.String, '[0-9]+') - - -def test_should_uuid_convert_string(): - if hasattr(forms, 'UUIDField'): - assert_conversion(forms.UUIDField, graphene.String) - - -def test_should_integer_convert_int(): - assert_conversion(forms.IntegerField, graphene.Int) - - -def test_should_boolean_convert_boolean(): - field = assert_conversion(forms.BooleanField, graphene.Boolean) - assert isinstance(field.type, NonNull) - - -def test_should_nullboolean_convert_boolean(): - field = assert_conversion(forms.NullBooleanField, graphene.Boolean) - assert not isinstance(field.type, NonNull) - - -def test_should_float_convert_float(): - assert_conversion(forms.FloatField, graphene.Float) - - -def test_should_decimal_convert_float(): - assert_conversion(forms.DecimalField, graphene.Float) - - -def test_should_multiple_choice_convert_connectionorlist(): - field = forms.ModelMultipleChoiceField(Reporter.objects.all()) - graphene_type = convert_form_field(field) - assert isinstance(graphene_type, List) - assert graphene_type.of_type == ID - - -def test_should_manytoone_convert_connectionorlist(): - field = forms.ModelChoiceField(Reporter.objects.all()) - graphene_type = convert_form_field(field) - assert isinstance(graphene_type, graphene.ID) diff --git a/graphene-django/graphene_django/tests/test_forms.py b/graphene-django/graphene_django/tests/test_forms.py deleted file mode 100644 index ada9e8a9..00000000 --- a/graphene-django/graphene_django/tests/test_forms.py +++ /dev/null @@ -1,29 +0,0 @@ -from django.core.exceptions import ValidationError -from py.test import raises - -from ..forms import GlobalIDFormField - - -# 'TXlUeXBlOmFiYw==' -> 'MyType', 'abc' - - -def test_global_id_valid(): - field = GlobalIDFormField() - field.clean('TXlUeXBlOmFiYw==') - - -def test_global_id_invalid(): - field = GlobalIDFormField() - with raises(ValidationError): - field.clean('badvalue') - - -def test_global_id_none(): - field = GlobalIDFormField() - with raises(ValidationError): - field.clean(None) - - -def test_global_id_none_optional(): - field = GlobalIDFormField(required=False) - field.clean(None) diff --git a/graphene-django/graphene_django/tests/test_query.py b/graphene-django/graphene_django/tests/test_query.py deleted file mode 100644 index 80a45e91..00000000 --- a/graphene-django/graphene_django/tests/test_query.py +++ /dev/null @@ -1,252 +0,0 @@ -import datetime - -import pytest -from django.db import models -from py.test import raises - -import graphene -from graphene.relay import Node - -from ..compat import MissingType, RangeField -from ..types import DjangoObjectType -from ..fields import DjangoConnectionField -from ..registry import reset_global_registry, get_global_registry -from .models import Article, Reporter - -pytestmark = pytest.mark.django_db - - -def test_should_query_only_fields(): - with raises(Exception): - class ReporterType(DjangoObjectType): - - class Meta: - model = Reporter - only_fields = ('articles', ) - - schema = graphene.Schema(query=ReporterType) - query = ''' - query ReporterQuery { - articles - } - ''' - result = schema.execute(query) - assert not result.errors - - -def test_should_query_well(): - class ReporterType(DjangoObjectType): - - class Meta: - model = Reporter - - class Query(graphene.ObjectType): - reporter = graphene.Field(ReporterType) - - def resolve_reporter(self, *args, **kwargs): - return Reporter(first_name='ABA', last_name='X') - - query = ''' - query ReporterQuery { - reporter { - firstName, - lastName, - email - } - } - ''' - expected = { - 'reporter': { - 'firstName': 'ABA', - 'lastName': 'X', - 'email': '' - } - } - schema = graphene.Schema(query=Query) - result = schema.execute(query) - assert not result.errors - assert result.data == expected - - -@pytest.mark.skipif(RangeField is MissingType, - reason="RangeField should exist") -def test_should_query_postgres_fields(): - from django.contrib.postgres.fields import IntegerRangeField, ArrayField, JSONField, HStoreField - - class Event(models.Model): - ages = IntegerRangeField(help_text='The age ranges') - data = JSONField(help_text='Data') - store = HStoreField() - tags = ArrayField(models.CharField(max_length=50)) - - class EventType(DjangoObjectType): - - class Meta: - model = Event - - class Query(graphene.ObjectType): - event = graphene.Field(EventType) - - def resolve_event(self, *args, **kwargs): - return Event( - ages=(0, 10), - data={'angry_babies': True}, - store={'h': 'store'}, - tags=['child', 'angry', 'babies'] - ) - - schema = graphene.Schema(query=Query) - query = ''' - query myQuery { - event { - ages - tags - data - store - } - } - ''' - expected = { - 'event': { - 'ages': [0, 10], - 'tags': ['child', 'angry', 'babies'], - 'data': '{"angry_babies": true}', - 'store': '{"h": "store"}', - }, - } - result = schema.execute(query) - assert not result.errors - assert result.data == expected - - -def test_should_node(): - # reset_global_registry() - # Node._meta.registry = get_global_registry() - - class ReporterNode(DjangoObjectType): - - class Meta: - model = Reporter - interfaces = (Node, ) - - @classmethod - def get_node(cls, id, context, info): - return Reporter(id=2, first_name='Cookie Monster') - - def resolve_articles(self, *args, **kwargs): - return [Article(headline='Hi!')] - - class ArticleNode(DjangoObjectType): - - class Meta: - model = Article - interfaces = (Node, ) - - @classmethod - def get_node(cls, id, context, info): - return Article(id=1, headline='Article node', pub_date=datetime.date(2002, 3, 11)) - - class Query(graphene.ObjectType): - node = Node.Field() - reporter = graphene.Field(ReporterNode) - article = graphene.Field(ArticleNode) - - def resolve_reporter(self, *args, **kwargs): - return Reporter(id=1, first_name='ABA', last_name='X') - - query = ''' - query ReporterQuery { - reporter { - id, - firstName, - articles { - edges { - node { - headline - } - } - } - lastName, - email - } - myArticle: node(id:"QXJ0aWNsZU5vZGU6MQ==") { - id - ... on ReporterNode { - firstName - } - ... on ArticleNode { - headline - pubDate - } - } - } - ''' - expected = { - 'reporter': { - 'id': 'UmVwb3J0ZXJOb2RlOjE=', - 'firstName': 'ABA', - 'lastName': 'X', - 'email': '', - 'articles': { - 'edges': [{ - 'node': { - 'headline': 'Hi!' - } - }] - }, - }, - 'myArticle': { - 'id': 'QXJ0aWNsZU5vZGU6MQ==', - 'headline': 'Article node', - 'pubDate': '2002-03-11', - } - } - schema = graphene.Schema(query=Query) - result = schema.execute(query) - assert not result.errors - assert result.data == expected - - -def test_should_query_connectionfields(): - class ReporterType(DjangoObjectType): - - class Meta: - model = Reporter - interfaces = (Node, ) - only_fields = ('articles', ) - - class Query(graphene.ObjectType): - all_reporters = DjangoConnectionField(ReporterType) - - def resolve_all_reporters(self, args, context, info): - return [Reporter(id=1)] - - schema = graphene.Schema(query=Query) - query = ''' - query ReporterConnectionQuery { - allReporters { - pageInfo { - hasNextPage - } - edges { - node { - id - } - } - } - } - ''' - result = schema.execute(query) - assert not result.errors - assert result.data == { - 'allReporters': { - 'pageInfo': { - 'hasNextPage': False, - }, - 'edges': [{ - 'node': { - 'id': 'UmVwb3J0ZXJUeXBlOjE=' - } - }] - } - } diff --git a/graphene-django/graphene_django/tests/test_schema.py b/graphene-django/graphene_django/tests/test_schema.py deleted file mode 100644 index d5af9c3d..00000000 --- a/graphene-django/graphene_django/tests/test_schema.py +++ /dev/null @@ -1,40 +0,0 @@ -from py.test import raises - -from ..types import DjangoObjectType -from ..registry import Registry - -from .models import Reporter - - -def test_should_raise_if_no_model(): - with raises(Exception) as excinfo: - class Character1(DjangoObjectType): - pass - assert 'valid Django Model' in str(excinfo.value) - - -def test_should_raise_if_model_is_invalid(): - with raises(Exception) as excinfo: - class Character2(DjangoObjectType): - - class Meta: - model = 1 - assert 'valid Django Model' in str(excinfo.value) - - -def test_should_map_fields_correctly(): - class ReporterType2(DjangoObjectType): - class Meta: - model = Reporter - registry = Registry() - assert list(ReporterType2._meta.fields.keys()) == ['id', 'first_name', 'last_name', 'email', 'pets', 'a_choice', 'articles', 'films'] - - -def test_should_map_only_few_fields(): - class Reporter2(DjangoObjectType): - - class Meta: - model = Reporter - only_fields = ('id', 'email') - - assert list(Reporter2._meta.fields.keys()) == ['id', 'email'] diff --git a/graphene-django/graphene_django/tests/test_types.py b/graphene-django/graphene_django/tests/test_types.py deleted file mode 100644 index ecc2225b..00000000 --- a/graphene-django/graphene_django/tests/test_types.py +++ /dev/null @@ -1,123 +0,0 @@ -from graphql.type import GraphQLObjectType -from mock import patch - -from graphene import ObjectType, Field, Int, ID, Schema, Interface -from graphene.relay import Node, ConnectionField -from ..types import DjangoObjectType - -from .models import Article as ArticleModel, Reporter as ReporterModel -from ..registry import reset_global_registry, Registry - -reset_global_registry() - - -class Reporter(DjangoObjectType): - '''Reporter description''' - class Meta: - model = ReporterModel - - -class Article(DjangoObjectType): - '''Article description''' - class Meta: - model = ArticleModel - interfaces = (Node, ) - - -class RootQuery(ObjectType): - node = Node.Field() - - -schema = Schema(query=RootQuery, types=[Article, Reporter]) - - -def test_django_interface(): - assert issubclass(Node, Interface) - assert issubclass(Node, Node) - - -@patch('graphene_django.tests.models.Article.objects.get', return_value=Article(id=1)) -def test_django_get_node(get): - article = Article.get_node(1, None, None) - get.assert_called_with(id=1) - assert article.id == 1 - - -def test_django_objecttype_map_correct_fields(): - fields = Reporter._meta.fields - assert list(fields.keys()) == ['id', 'first_name', 'last_name', 'email', 'pets', 'a_choice', 'articles', 'films'] - - -def test_django_objecttype_with_node_have_correct_fields(): - fields = Article._meta.fields - assert list(fields.keys()) == ['id', 'headline', 'pub_date', 'reporter', 'lang', 'importance'] - - -def test_schema_representation(): - expected = """ -schema { - query: RootQuery -} - -type Article implements Node { - id: ID! - headline: String - pubDate: DateTime - reporter: Reporter - lang: ArticleLang - importance: ArticleImportance -} - -type ArticleConnection { - pageInfo: PageInfo! - edges: [ArticleEdge] -} - -type ArticleEdge { - node: Article - cursor: String! -} - -enum ArticleImportance { - VERY_IMPORTANT - NOT_AS_IMPORTANT -} - -enum ArticleLang { - SPANISH - ENGLISH -} - -scalar DateTime - -interface Node { - id: ID! -} - -type PageInfo { - hasNextPage: Boolean! - hasPreviousPage: Boolean! - startCursor: String - endCursor: String -} - -type Reporter { - id: ID - firstName: String - lastName: String - email: String - pets: [Reporter] - aChoice: ReporterA_choice - articles(before: String, after: String, first: Int, last: Int): ArticleConnection -} - -enum ReporterA_choice { - THIS - THAT -} - -type RootQuery { - node(id: ID!): Node -} -""".lstrip() - assert str(schema) == expected diff --git a/graphene-django/graphene_django/tests/test_views.py b/graphene-django/graphene_django/tests/test_views.py deleted file mode 100644 index efa0381f..00000000 --- a/graphene-django/graphene_django/tests/test_views.py +++ /dev/null @@ -1,57 +0,0 @@ -import json - - -def format_response(response): - return json.loads(response.content.decode()) - - -def test_client_get_good_query(settings, client): - settings.ROOT_URLCONF = 'graphene_django.tests.urls' - response = client.get('/graphql', {'query': '{ human { headline } }'}) - json_response = format_response(response) - expected_json = { - 'data': { - 'human': { - 'headline': None - } - } - } - assert json_response == expected_json - - -def test_client_get_good_query_with_raise(settings, client): - settings.ROOT_URLCONF = 'graphene_django.tests.urls' - response = client.get('/graphql', {'query': '{ human { raises } }'}) - json_response = format_response(response) - assert json_response['errors'][0]['message'] == 'This field should raise exception' - assert json_response['data']['human']['raises'] is None - - -def test_client_post_good_query_json(settings, client): - settings.ROOT_URLCONF = 'graphene_django.tests.urls' - response = client.post( - '/graphql', json.dumps({'query': '{ human { headline } }'}), 'application/json') - json_response = format_response(response) - expected_json = { - 'data': { - 'human': { - 'headline': None - } - } - } - assert json_response == expected_json - - -def test_client_post_good_query_graphql(settings, client): - settings.ROOT_URLCONF = 'graphene_django.tests.urls' - response = client.post( - '/graphql', '{ human { headline } }', 'application/graphql') - json_response = format_response(response) - expected_json = { - 'data': { - 'human': { - 'headline': None - } - } - } - assert json_response == expected_json diff --git a/graphene-django/graphene_django/tests/urls.py b/graphene-django/graphene_django/tests/urls.py deleted file mode 100644 index fd0eb4c3..00000000 --- a/graphene-django/graphene_django/tests/urls.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.conf.urls import url - -from ..views import GraphQLView -from .schema import schema - -urlpatterns = [ - url(r'^graphql', GraphQLView.as_view(schema=schema)), -] diff --git a/graphene-django/graphene_django/types.py b/graphene-django/graphene_django/types.py deleted file mode 100644 index 91e71732..00000000 --- a/graphene-django/graphene_django/types.py +++ /dev/null @@ -1,114 +0,0 @@ -from collections import OrderedDict - -import six - -from graphene import ObjectType, Field -from graphene.types.objecttype import ObjectTypeMeta -from .converter import convert_django_field_with_choices -from graphene.types.options import Options -from .utils import get_model_fields, is_valid_django_model, DJANGO_FILTER_INSTALLED -from .registry import Registry, get_global_registry -from graphene.utils.is_base_type import is_base_type -from graphene.types.utils import yank_fields_from_attrs, merge - - -def construct_fields(options): - _model_fields = get_model_fields(options.model) - only_fields = options.only_fields - exclude_fields = options.exclude_fields - - fields = OrderedDict() - for field in _model_fields: - name = field.name - is_not_in_only = only_fields and name not in options.only_fields - is_already_created = name in options.fields - is_excluded = name in exclude_fields or is_already_created - if is_not_in_only or is_excluded: - # We skip this field if we specify only_fields and is not - # in there. Or when we exclude this field in exclude_fields - continue - converted = convert_django_field_with_choices(field, options.registry) - if not converted: - continue - fields[name] = converted - - return fields - - -class DjangoObjectTypeMeta(ObjectTypeMeta): - - @staticmethod - def __new__(cls, name, bases, attrs): - # Also ensure initialization is only performed for subclasses of - # DjangoObjectType - if not is_base_type(bases, DjangoObjectTypeMeta): - return type.__new__(cls, name, bases, attrs) - - defaults = dict( - name=name, - description=attrs.pop('__doc__', None), - model=None, - local_fields=None, - only_fields=(), - exclude_fields=(), - interfaces=(), - registry=None - ) - if DJANGO_FILTER_INSTALLED: - # In case Django filter is available, then - # we allow more attributes in Meta - defaults.update( - filter_fields=(), - filter_order_by=(), - ) - - options = Options( - attrs.pop('Meta', None), - **defaults - ) - if not options.registry: - options.registry = get_global_registry() - assert isinstance(options.registry, Registry), ( - 'The attribute registry in {}.Meta needs to be an instance of ' - 'Registry, received "{}".' - ).format(name, options.registry) - assert is_valid_django_model(options.model), ( - 'You need to pass a valid Django Model in {}.Meta, received "{}".' - ).format(name, options.model) - - cls = ObjectTypeMeta.__new__(cls, name, bases, dict(attrs, _meta=options)) - - options.registry.register(cls) - - options.django_fields = yank_fields_from_attrs( - construct_fields(options), - _as=Field, - ) - options.fields = merge( - options.interface_fields, - options.django_fields, - options.base_fields, - options.local_fields - ) - - return cls - - -class DjangoObjectType(six.with_metaclass(DjangoObjectTypeMeta, ObjectType)): - @classmethod - def is_type_of(cls, root, context, info): - if isinstance(root, cls): - return True - if not is_valid_django_model(type(root)): - raise Exception(( - 'Received incompatible instance "{}".' - ).format(root)) - model = root._meta.model - return model == cls._meta.model - - @classmethod - def get_node(cls, id, context, info): - try: - return cls._meta.model.objects.get(id=id) - except cls._meta.model.DoesNotExist: - return None diff --git a/graphene-django/graphene_django/utils.py b/graphene-django/graphene_django/utils.py deleted file mode 100644 index a7c37b06..00000000 --- a/graphene-django/graphene_django/utils.py +++ /dev/null @@ -1,81 +0,0 @@ -import inspect -from django.db import models -from django.db.models.manager import Manager - -# from graphene.utils import LazyList -class LazyList(object): - pass - -from .compat import RelatedObject - -try: - import django_filters # noqa - DJANGO_FILTER_INSTALLED = True -except (ImportError, AttributeError): - # AtributeError raised if DjangoFilters installed with a incompatible Django Version - DJANGO_FILTER_INSTALLED = False - - -def get_reverse_fields(model): - for name, attr in model.__dict__.items(): - # Django =>1.9 uses 'rel', django <1.9 uses 'related' - related = getattr(attr, 'rel', None) or \ - getattr(attr, 'related', None) - if isinstance(related, RelatedObject): - # Hack for making it compatible with Django 1.6 - new_related = RelatedObject(related.parent_model, related.model, related.field) - new_related.name = name - yield new_related - elif isinstance(related, models.ManyToOneRel): - yield related - elif isinstance(related, models.ManyToManyRel) and not related.symmetrical: - yield related - - -def maybe_queryset(value): - if isinstance(value, Manager): - value = value.get_queryset() - return value - - -def get_model_fields(model): - reverse_fields = get_reverse_fields(model) - all_fields = sorted(list(model._meta.fields) + - list(model._meta.local_many_to_many)) - all_fields += list(reverse_fields) - - return all_fields - - -def get_related_model(field): - if hasattr(field, 'rel'): - # Django 1.6, 1.7 - return field.rel.to - return field.related_model - - -def is_valid_django_model(model): - return inspect.isclass(model) and issubclass(model, models.Model) - - -def import_single_dispatch(): - try: - from functools import singledispatch - except ImportError: - singledispatch = None - - if not singledispatch: - try: - from singledispatch import singledispatch - except ImportError: - pass - - if not singledispatch: - raise Exception( - "It seems your python version does not include " - "functools.singledispatch. Please install the 'singledispatch' " - "package. More information here: " - "https://pypi.python.org/pypi/singledispatch" - ) - - return singledispatch diff --git a/graphene-django/graphene_django/views.py b/graphene-django/graphene_django/views.py deleted file mode 100644 index cc8b2b67..00000000 --- a/graphene-django/graphene_django/views.py +++ /dev/null @@ -1,9 +0,0 @@ -from graphql_django_view import GraphQLView as BaseGraphQLView - - -class GraphQLView(BaseGraphQLView): - def __init__(self, schema, **kwargs): - super(GraphQLView, self).__init__( - schema=schema, - **kwargs - ) diff --git a/graphene-django/setup.cfg b/graphene-django/setup.cfg deleted file mode 100644 index 4e47ff49..00000000 --- a/graphene-django/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[pytest] -DJANGO_SETTINGS_MODULE = django_test_settings diff --git a/graphene-django/setup.py b/graphene-django/setup.py deleted file mode 100644 index b67c0cbc..00000000 --- a/graphene-django/setup.py +++ /dev/null @@ -1,50 +0,0 @@ -from setuptools import find_packages, setup - -setup( - name='graphene-django', - version='1.0.dev20160910000001', - - description='Graphene Django integration', - # long_description=open('README.rst').read(), - - url='https://github.com/graphql-python/graphene-django', - - author='Syrus Akbary', - author_email='me@syrusakbary.com', - - license='MIT', - - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Libraries', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: Implementation :: PyPy', - ], - - keywords='api graphql protocol rest relay graphene', - - packages=find_packages(exclude=['tests']), - - install_requires=[ - 'six>=1.10.0', - 'graphene>=1.0.dev', - 'Django>=1.6.0', - 'iso8601', - 'singledispatch>=3.4.0.3', - 'graphql-django-view>=1.3', - ], - tests_require=[ - 'django-filter>=0.10.0', - 'pytest>=2.7.2', - 'pytest-django', - 'mock', - # Required for Django postgres fields testing - 'psycopg2', - ], -) diff --git a/graphene-sqlalchemy/examples/flask_sqlalchemy/README.md b/graphene-sqlalchemy/examples/flask_sqlalchemy/README.md deleted file mode 100644 index 992a8acd..00000000 --- a/graphene-sqlalchemy/examples/flask_sqlalchemy/README.md +++ /dev/null @@ -1,50 +0,0 @@ -Example Flask+SQLAlchemy Project -================================ - -This example project demos integration between Graphene, Flask and SQLAlchemy. -The project contains two models, one named `Department` and another -named `Employee`. - -Getting started ---------------- - -First you'll need to get the source of the project. Do this by cloning the -whole Graphene repository: - -```bash -# Get the example project code -git clone https://github.com/graphql-python/graphene.git -cd graphene/examples/flask_sqlalchemy -``` - -It is good idea (but not required) to create a virtual environment -for this project. We'll do this using -[virtualenv](http://docs.python-guide.org/en/latest/dev/virtualenvs/) -to keep things simple, -but you may also find something like -[virtualenvwrapper](https://virtualenvwrapper.readthedocs.org/en/latest/) -to be useful: - -```bash -# Create a virtualenv in which we can install the dependencies -virtualenv env -source env/bin/activate -``` - -Now we can install our dependencies: - -```bash -pip install -r requirements.txt -``` - -Now the following command will setup the database, and start the server: - -```bash -./app.py - -``` - - -Now head on over to -[http://127.0.0.1:5000/graphiql](http://127.0.0.1:5000/graphiql) -and run some queries! diff --git a/graphene-sqlalchemy/examples/flask_sqlalchemy/__init__.py b/graphene-sqlalchemy/examples/flask_sqlalchemy/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene-sqlalchemy/examples/flask_sqlalchemy/app.py b/graphene-sqlalchemy/examples/flask_sqlalchemy/app.py deleted file mode 100644 index 93626d62..00000000 --- a/graphene-sqlalchemy/examples/flask_sqlalchemy/app.py +++ /dev/null @@ -1,40 +0,0 @@ -from flask import Flask - -from database import db_session, init_db -from flask_graphql import GraphQLView -from schema import schema - -app = Flask(__name__) -app.debug = True - -default_query = ''' -{ - allEmployees { - edges { - node { - id, - name, - department { - id, - name - }, - role { - id, - name - } - } - } - } -}'''.strip() - - -app.add_url_rule('/graphql', view_func=GraphQLView.as_view('graphql', schema=schema, graphiql=True)) - - -@app.teardown_appcontext -def shutdown_session(exception=None): - db_session.remove() - -if __name__ == '__main__': - init_db() - app.run() diff --git a/graphene-sqlalchemy/examples/flask_sqlalchemy/database.py b/graphene-sqlalchemy/examples/flask_sqlalchemy/database.py deleted file mode 100644 index ca4d4122..00000000 --- a/graphene-sqlalchemy/examples/flask_sqlalchemy/database.py +++ /dev/null @@ -1,38 +0,0 @@ -from sqlalchemy import create_engine -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import scoped_session, sessionmaker - -engine = create_engine('sqlite:///database.sqlite3', convert_unicode=True) -db_session = scoped_session(sessionmaker(autocommit=False, - autoflush=False, - bind=engine)) -Base = declarative_base() -Base.query = db_session.query_property() - - -def init_db(): - # import all modules here that might define models so that - # they will be registered properly on the metadata. Otherwise - # you will have to import them first before calling init_db() - from models import Department, Employee, Role - Base.metadata.drop_all(bind=engine) - Base.metadata.create_all(bind=engine) - - # Create the fixtures - engineering = Department(name='Engineering') - db_session.add(engineering) - hr = Department(name='Human Resources') - db_session.add(hr) - - manager = Role(name='manager') - db_session.add(manager) - engineer = Role(name='engineer') - db_session.add(engineer) - - peter = Employee(name='Peter', department=engineering, role=engineer) - db_session.add(peter) - roy = Employee(name='Roy', department=engineering, role=engineer) - db_session.add(roy) - tracy = Employee(name='Tracy', department=hr, role=manager) - db_session.add(tracy) - db_session.commit() diff --git a/graphene-sqlalchemy/examples/flask_sqlalchemy/models.py b/graphene-sqlalchemy/examples/flask_sqlalchemy/models.py deleted file mode 100644 index 119aca02..00000000 --- a/graphene-sqlalchemy/examples/flask_sqlalchemy/models.py +++ /dev/null @@ -1,39 +0,0 @@ -from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, func -from sqlalchemy.orm import backref, relationship - -from database import Base - - -class Department(Base): - __tablename__ = 'department' - id = Column(Integer, primary_key=True) - name = Column(String) - - -class Role(Base): - __tablename__ = 'roles' - role_id = Column(Integer, primary_key=True) - name = Column(String) - - -class Employee(Base): - __tablename__ = 'employee' - id = Column(Integer, primary_key=True) - name = Column(String) - # Use default=func.now() to set the default hiring time - # of an Employee to be the current time when an - # Employee record was created - hired_on = Column(DateTime, default=func.now()) - department_id = Column(Integer, ForeignKey('department.id')) - role_id = Column(Integer, ForeignKey('roles.role_id')) - # Use cascade='delete,all' to propagate the deletion of a Department onto its Employees - department = relationship( - Department, - backref=backref('employees', - uselist=True, - cascade='delete,all')) - role = relationship( - Role, - backref=backref('roles', - uselist=True, - cascade='delete,all')) diff --git a/graphene-sqlalchemy/examples/flask_sqlalchemy/requirements.txt b/graphene-sqlalchemy/examples/flask_sqlalchemy/requirements.txt deleted file mode 100644 index f5bcaa85..00000000 --- a/graphene-sqlalchemy/examples/flask_sqlalchemy/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -graphene[sqlalchemy] -SQLAlchemy==1.0.11 -Flask==0.10.1 -Flask-GraphQL==1.3.0 diff --git a/graphene-sqlalchemy/examples/flask_sqlalchemy/schema.py b/graphene-sqlalchemy/examples/flask_sqlalchemy/schema.py deleted file mode 100644 index df967dc4..00000000 --- a/graphene-sqlalchemy/examples/flask_sqlalchemy/schema.py +++ /dev/null @@ -1,38 +0,0 @@ -import graphene -from graphene import relay -from graphene_sqlalchemy import (SQLAlchemyConnectionField, - SQLAlchemyObjectType) -from models import Department as DepartmentModel -from models import Employee as EmployeeModel -from models import Role as RoleModel - - -class Department(SQLAlchemyObjectType): - - class Meta: - model = DepartmentModel - interfaces = (relay.Node, ) - - -class Employee(SQLAlchemyObjectType): - - class Meta: - model = EmployeeModel - interfaces = (relay.Node, ) - - -class Role(SQLAlchemyObjectType): - - class Meta: - model = RoleModel - interfaces = (relay.Node, ) - - -class Query(graphene.ObjectType): - node = relay.Node.Field() - all_employees = SQLAlchemyConnectionField(Employee) - all_roles = SQLAlchemyConnectionField(Role) - role = graphene.Field(Role) - - -schema = graphene.Schema(query=Query, types=[Department, Employee, Role]) diff --git a/graphene-sqlalchemy/graphene_sqlalchemy/__init__.py b/graphene-sqlalchemy/graphene_sqlalchemy/__init__.py deleted file mode 100644 index 3635827d..00000000 --- a/graphene-sqlalchemy/graphene_sqlalchemy/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -from .types import ( - SQLAlchemyObjectType, -) -from .fields import ( - SQLAlchemyConnectionField -) -from .utils import ( - get_query, - get_session -) - -__all__ = ['SQLAlchemyObjectType', - 'SQLAlchemyConnectionField', - 'get_query', - 'get_session'] diff --git a/graphene-sqlalchemy/graphene_sqlalchemy/converter.py b/graphene-sqlalchemy/graphene_sqlalchemy/converter.py deleted file mode 100644 index 89f58177..00000000 --- a/graphene-sqlalchemy/graphene_sqlalchemy/converter.py +++ /dev/null @@ -1,133 +0,0 @@ -from singledispatch import singledispatch -from sqlalchemy import types -from sqlalchemy.orm import interfaces -from sqlalchemy.dialects import postgresql - -from graphene import Enum, ID, Boolean, Float, Int, String, List, Field, Dynamic -from graphene.relay import is_node -from graphene.types.json import JSONString -from .fields import SQLAlchemyConnectionField - -try: - from sqlalchemy_utils import ChoiceType, ScalarListType -except ImportError: - class ChoiceType(object): - pass - - class ScalarListType(object): - pass - - -def convert_sqlalchemy_relationship(relationship, registry): - direction = relationship.direction - model = relationship.mapper.entity - - def dynamic_type(): - _type = registry.get_type_for_model(model) - if not _type: - return None - if (direction == interfaces.MANYTOONE or not relationship.uselist): - return Field(_type) - elif (direction == interfaces.ONETOMANY or - direction == interfaces.MANYTOMANY): - if is_node(_type): - return SQLAlchemyConnectionField(_type) - return Field(List(_type)) - - return Dynamic(dynamic_type) - - -def convert_sqlalchemy_composite(composite, registry): - converter = registry.get_converter_for_composite(composite.composite_class) - if not converter: - try: - raise Exception( - "Don't know how to convert the composite field %s (%s)" % - (composite, composite.composite_class)) - except AttributeError: - # handle fields that are not attached to a class yet (don't have a parent) - raise Exception( - "Don't know how to convert the composite field %r (%s)" % - (composite, composite.composite_class)) - return converter(composite, registry) - - -def _register_composite_class(cls, registry=None): - if registry is None: - from .registry import get_global_registry - registry = get_global_registry() - - def inner(fn): - registry.register_composite_converter(cls, fn) - return inner - -convert_sqlalchemy_composite.register = _register_composite_class - - -def convert_sqlalchemy_column(column, registry=None): - return convert_sqlalchemy_type(getattr(column, 'type', None), column, registry) - - -@singledispatch -def convert_sqlalchemy_type(type, column, registry=None): - raise Exception( - "Don't know how to convert the SQLAlchemy field %s (%s)" % (column, column.__class__)) - - -@convert_sqlalchemy_type.register(types.Date) -@convert_sqlalchemy_type.register(types.DateTime) -@convert_sqlalchemy_type.register(types.Time) -@convert_sqlalchemy_type.register(types.String) -@convert_sqlalchemy_type.register(types.Text) -@convert_sqlalchemy_type.register(types.Unicode) -@convert_sqlalchemy_type.register(types.UnicodeText) -@convert_sqlalchemy_type.register(types.Enum) -@convert_sqlalchemy_type.register(postgresql.ENUM) -@convert_sqlalchemy_type.register(postgresql.UUID) -def convert_column_to_string(type, column, registry=None): - return String(description=column.doc, required=not(column.nullable)) - - -@convert_sqlalchemy_type.register(types.SmallInteger) -@convert_sqlalchemy_type.register(types.BigInteger) -@convert_sqlalchemy_type.register(types.Integer) -def convert_column_to_int_or_id(type, column, registry=None): - if column.primary_key: - return ID(description=column.doc, required=not(column.nullable)) - else: - return Int(description=column.doc, required=not(column.nullable)) - - -@convert_sqlalchemy_type.register(types.Boolean) -def convert_column_to_boolean(type, column, registry=None): - return Boolean(description=column.doc, required=not(column.nullable)) - - -@convert_sqlalchemy_type.register(types.Float) -@convert_sqlalchemy_type.register(types.Numeric) -def convert_column_to_float(type, column, registry=None): - return Float(description=column.doc, required=not(column.nullable)) - - -@convert_sqlalchemy_type.register(ChoiceType) -def convert_column_to_enum(type, column, registry=None): - name = '{}_{}'.format(column.table.name, column.name).upper() - return Enum(name, type.choices, description=column.doc) - - -@convert_sqlalchemy_type.register(ScalarListType) -def convert_scalar_list_to_list(type, column, registry=None): - return List(String, description=column.doc) - - -@convert_sqlalchemy_type.register(postgresql.ARRAY) -def convert_postgres_array_to_list(type, column, registry=None): - graphene_type = convert_sqlalchemy_type(column.type.item_type, column) - return List(graphene_type, description=column.doc, required=not(column.nullable)) - - -@convert_sqlalchemy_type.register(postgresql.HSTORE) -@convert_sqlalchemy_type.register(postgresql.JSON) -@convert_sqlalchemy_type.register(postgresql.JSONB) -def convert_json_to_string(type, column, registry=None): - return JSONString(description=column.doc, required=not(column.nullable)) diff --git a/graphene-sqlalchemy/graphene_sqlalchemy/fields.py b/graphene-sqlalchemy/graphene_sqlalchemy/fields.py deleted file mode 100644 index d97d2295..00000000 --- a/graphene-sqlalchemy/graphene_sqlalchemy/fields.py +++ /dev/null @@ -1,37 +0,0 @@ -from functools import partial -from sqlalchemy.orm.query import Query - -from graphene.relay import ConnectionField -from graphene.relay.connection import PageInfo -from graphql_relay.connection.arrayconnection import connection_from_list_slice -from .utils import get_query - - -class SQLAlchemyConnectionField(ConnectionField): - - @property - def model(self): - return self.type._meta.node._meta.model - - @staticmethod - def connection_resolver(resolver, connection, model, root, args, context, info): - iterable = resolver(root, args, context, info) - if iterable is None: - iterable = get_query(model, context) - if isinstance(iterable, Query): - _len = iterable.count() - else: - _len = len(iterable) - return connection_from_list_slice( - iterable, - args, - slice_start=0, - list_length=_len, - list_slice_length=_len, - connection_type=connection, - pageinfo_type=PageInfo, - edge_type=connection.Edge, - ) - - def get_resolver(self, parent_resolver): - return partial(self.connection_resolver, parent_resolver, self.type, self.model) diff --git a/graphene-sqlalchemy/graphene_sqlalchemy/registry.py b/graphene-sqlalchemy/graphene_sqlalchemy/registry.py deleted file mode 100644 index 43980665..00000000 --- a/graphene-sqlalchemy/graphene_sqlalchemy/registry.py +++ /dev/null @@ -1,39 +0,0 @@ -class Registry(object): - def __init__(self): - self._registry = {} - self._registry_models = {} - self._registry_composites = {} - - def register(self, cls): - from .types import SQLAlchemyObjectType - assert issubclass(cls, SQLAlchemyObjectType), 'Only SQLAlchemyObjectType can be registered, received "{}"'.format(cls.__name__) - assert cls._meta.registry == self, 'Registry for a Model have to match.' - # assert self.get_type_for_model(cls._meta.model) in [None, cls], ( - # 'SQLAlchemy model "{}" already associated with ' - # 'another type "{}".' - # ).format(cls._meta.model, self._registry[cls._meta.model]) - self._registry[cls._meta.model] = cls - - def get_type_for_model(self, model): - return self._registry.get(model) - - def register_composite_converter(self, composite, converter): - self._registry_composites[composite] = converter - - def get_converter_for_composite(self, composite): - return self._registry_composites.get(composite) - - -registry = None - - -def get_global_registry(): - global registry - if not registry: - registry = Registry() - return registry - - -def reset_global_registry(): - global registry - registry = None diff --git a/graphene-sqlalchemy/graphene_sqlalchemy/tests/__init__.py b/graphene-sqlalchemy/graphene_sqlalchemy/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene-sqlalchemy/graphene_sqlalchemy/tests/models.py b/graphene-sqlalchemy/graphene_sqlalchemy/tests/models.py deleted file mode 100644 index d4a2c952..00000000 --- a/graphene-sqlalchemy/graphene_sqlalchemy/tests/models.py +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import absolute_import - -from sqlalchemy import Column, Date, ForeignKey, Integer, String, Table -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import relationship - -Base = declarative_base() - -association_table = Table('association', Base.metadata, - Column('pet_id', Integer, ForeignKey('pets.id')), - Column('reporter_id', Integer, ForeignKey('reporters.id'))) - - -class Editor(Base): - __tablename__ = 'editors' - editor_id = Column(Integer(), primary_key=True) - name = Column(String(100)) - - -class Pet(Base): - __tablename__ = 'pets' - id = Column(Integer(), primary_key=True) - name = Column(String(30)) - reporter_id = Column(Integer(), ForeignKey('reporters.id')) - - -class Reporter(Base): - __tablename__ = 'reporters' - id = Column(Integer(), primary_key=True) - first_name = Column(String(30)) - last_name = Column(String(30)) - email = Column(String()) - pets = relationship('Pet', secondary=association_table, backref='reporters') - articles = relationship('Article', backref='reporter') - favorite_article = relationship("Article", uselist=False) - - -class Article(Base): - __tablename__ = 'articles' - id = Column(Integer(), primary_key=True) - headline = Column(String(100)) - pub_date = Column(Date()) - reporter_id = Column(Integer(), ForeignKey('reporters.id')) diff --git a/graphene-sqlalchemy/graphene_sqlalchemy/tests/test_converter.py b/graphene-sqlalchemy/graphene_sqlalchemy/tests/test_converter.py deleted file mode 100644 index 192f69e9..00000000 --- a/graphene-sqlalchemy/graphene_sqlalchemy/tests/test_converter.py +++ /dev/null @@ -1,266 +0,0 @@ -from py.test import raises -from sqlalchemy import Column, Table, types -from sqlalchemy.orm import composite -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy_utils import ChoiceType, ScalarListType -from sqlalchemy.dialects import postgresql - -import graphene -from graphene.relay import Node -from graphene.types.json import JSONString -from ..converter import (convert_sqlalchemy_column, - convert_sqlalchemy_composite, - convert_sqlalchemy_relationship) -from ..fields import SQLAlchemyConnectionField -from ..types import SQLAlchemyObjectType -from ..registry import Registry - -from .models import Article, Pet, Reporter - - -def assert_column_conversion(sqlalchemy_type, graphene_field, **kwargs): - column = Column(sqlalchemy_type, doc='Custom Help Text', **kwargs) - graphene_type = convert_sqlalchemy_column(column) - assert isinstance(graphene_type, graphene_field) - field = graphene_type.Field() - assert field.description == 'Custom Help Text' - return field - - -def assert_composite_conversion(composite_class, composite_columns, graphene_field, - registry, **kwargs): - composite_column = composite(composite_class, *composite_columns, - doc='Custom Help Text', **kwargs) - graphene_type = convert_sqlalchemy_composite(composite_column, registry) - assert isinstance(graphene_type, graphene_field) - field = graphene_type.Field() - # SQLAlchemy currently does not persist the doc onto the column, even though - # the documentation says it does.... - # assert field.description == 'Custom Help Text' - return field - - -def test_should_unknown_sqlalchemy_field_raise_exception(): - with raises(Exception) as excinfo: - convert_sqlalchemy_column(None) - assert 'Don\'t know how to convert the SQLAlchemy field' in str(excinfo.value) - - -def test_should_date_convert_string(): - assert_column_conversion(types.Date(), graphene.String) - - -def test_should_datetime_convert_string(): - assert_column_conversion(types.DateTime(), graphene.String) - - -def test_should_time_convert_string(): - assert_column_conversion(types.Time(), graphene.String) - - -def test_should_string_convert_string(): - assert_column_conversion(types.String(), graphene.String) - - -def test_should_text_convert_string(): - assert_column_conversion(types.Text(), graphene.String) - - -def test_should_unicode_convert_string(): - assert_column_conversion(types.Unicode(), graphene.String) - - -def test_should_unicodetext_convert_string(): - assert_column_conversion(types.UnicodeText(), graphene.String) - - -def test_should_enum_convert_string(): - assert_column_conversion(types.Enum(), graphene.String) - - -def test_should_small_integer_convert_int(): - assert_column_conversion(types.SmallInteger(), graphene.Int) - - -def test_should_big_integer_convert_int(): - assert_column_conversion(types.BigInteger(), graphene.Int) - - -def test_should_integer_convert_int(): - assert_column_conversion(types.Integer(), graphene.Int) - - -def test_should_integer_convert_id(): - assert_column_conversion(types.Integer(), graphene.ID, primary_key=True) - - -def test_should_boolean_convert_boolean(): - assert_column_conversion(types.Boolean(), graphene.Boolean) - - -def test_should_float_convert_float(): - assert_column_conversion(types.Float(), graphene.Float) - - -def test_should_numeric_convert_float(): - assert_column_conversion(types.Numeric(), graphene.Float) - - -def test_should_choice_convert_enum(): - TYPES = [ - (u'es', u'Spanish'), - (u'en', u'English') - ] - column = Column(ChoiceType(TYPES), doc='Language', name='language') - Base = declarative_base() - - Table('translatedmodel', Base.metadata, column) - graphene_type = convert_sqlalchemy_column(column) - assert issubclass(graphene_type, graphene.Enum) - assert graphene_type._meta.name == 'TRANSLATEDMODEL_LANGUAGE' - assert graphene_type._meta.description == 'Language' - assert graphene_type._meta.enum.__members__['es'].value == 'Spanish' - assert graphene_type._meta.enum.__members__['en'].value == 'English' - - -def test_should_scalar_list_convert_list(): - assert_column_conversion(ScalarListType(), graphene.List) - - -def test_should_manytomany_convert_connectionorlist(): - registry = Registry() - dynamic_field = convert_sqlalchemy_relationship(Reporter.pets.property, registry) - assert isinstance(dynamic_field, graphene.Dynamic) - assert not dynamic_field.get_type() - - -def test_should_manytomany_convert_connectionorlist_list(): - class A(SQLAlchemyObjectType): - class Meta: - model = Pet - - dynamic_field = convert_sqlalchemy_relationship(Reporter.pets.property, A._meta.registry) - assert isinstance(dynamic_field, graphene.Dynamic) - graphene_type = dynamic_field.get_type() - assert isinstance(graphene_type, graphene.Field) - assert isinstance(graphene_type.type, graphene.List) - assert graphene_type.type.of_type == A - - -def test_should_manytomany_convert_connectionorlist_connection(): - class A(SQLAlchemyObjectType): - class Meta: - model = Pet - interfaces = (Node, ) - - dynamic_field = convert_sqlalchemy_relationship(Reporter.pets.property, A._meta.registry) - assert isinstance(dynamic_field, graphene.Dynamic) - assert isinstance(dynamic_field.get_type(), SQLAlchemyConnectionField) - - -def test_should_manytoone_convert_connectionorlist(): - registry = Registry() - dynamic_field = convert_sqlalchemy_relationship(Article.reporter.property, registry) - assert isinstance(dynamic_field, graphene.Dynamic) - assert not dynamic_field.get_type() - - -def test_should_manytoone_convert_connectionorlist_list(): - class A(SQLAlchemyObjectType): - class Meta: - model = Reporter - - dynamic_field = convert_sqlalchemy_relationship(Article.reporter.property, A._meta.registry) - assert isinstance(dynamic_field, graphene.Dynamic) - graphene_type = dynamic_field.get_type() - assert isinstance(graphene_type, graphene.Field) - assert graphene_type.type == A - - -def test_should_manytoone_convert_connectionorlist_connection(): - class A(SQLAlchemyObjectType): - class Meta: - model = Reporter - interfaces = (Node, ) - - dynamic_field = convert_sqlalchemy_relationship(Article.reporter.property, A._meta.registry) - assert isinstance(dynamic_field, graphene.Dynamic) - graphene_type = dynamic_field.get_type() - assert isinstance(graphene_type, graphene.Field) - assert graphene_type.type == A - - -def test_should_onetoone_convert_field(): - class A(SQLAlchemyObjectType): - class Meta: - model = Article - interfaces = (Node, ) - - dynamic_field = convert_sqlalchemy_relationship(Reporter.favorite_article.property, A._meta.registry) - assert isinstance(dynamic_field, graphene.Dynamic) - graphene_type = dynamic_field.get_type() - assert isinstance(graphene_type, graphene.Field) - assert graphene_type.type == A - - -def test_should_postgresql_uuid_convert(): - assert_column_conversion(postgresql.UUID(), graphene.String) - - -def test_should_postgresql_enum_convert(): - assert_column_conversion(postgresql.ENUM(), graphene.String) - - -def test_should_postgresql_array_convert(): - assert_column_conversion(postgresql.ARRAY(types.Integer), graphene.List) - - -def test_should_postgresql_json_convert(): - assert_column_conversion(postgresql.JSON(), JSONString) - - -def test_should_postgresql_jsonb_convert(): - assert_column_conversion(postgresql.JSONB(), JSONString) - - -def test_should_postgresql_hstore_convert(): - assert_column_conversion(postgresql.HSTORE(), JSONString) - - -def test_should_composite_convert(): - - class CompositeClass(object): - def __init__(self, col1, col2): - self.col1 = col1 - self.col2 = col2 - - registry = Registry() - - @convert_sqlalchemy_composite.register(CompositeClass, registry) - def convert_composite_class(composite, registry): - return graphene.String(description=composite.doc) - - assert_composite_conversion(CompositeClass, - (Column(types.Unicode(50)), - Column(types.Unicode(50))), - graphene.String, - registry) - - -def test_should_unknown_sqlalchemy_composite_raise_exception(): - registry = Registry() - - with raises(Exception) as excinfo: - - class CompositeClass(object): - def __init__(self, col1, col2): - self.col1 = col1 - self.col2 = col2 - - assert_composite_conversion(CompositeClass, - (Column(types.Unicode(50)), - Column(types.Unicode(50))), - graphene.String, - registry) - - assert 'Don\'t know how to convert the composite field' in str(excinfo.value) diff --git a/graphene-sqlalchemy/graphene_sqlalchemy/tests/test_query.py b/graphene-sqlalchemy/graphene_sqlalchemy/tests/test_query.py deleted file mode 100644 index 8fa8d18e..00000000 --- a/graphene-sqlalchemy/graphene_sqlalchemy/tests/test_query.py +++ /dev/null @@ -1,330 +0,0 @@ -import pytest -from sqlalchemy import create_engine -from sqlalchemy.orm import scoped_session, sessionmaker - -import graphene -from graphene.relay import Node -from ..types import SQLAlchemyObjectType -from ..fields import SQLAlchemyConnectionField - -from .models import Article, Base, Editor, Reporter - -db = create_engine('sqlite:///test_sqlalchemy.sqlite3') - - -@pytest.yield_fixture(scope='function') -def session(): - connection = db.engine.connect() - transaction = connection.begin() - Base.metadata.create_all(connection) - - # options = dict(bind=connection, binds={}) - session_factory = sessionmaker(bind=connection) - session = scoped_session(session_factory) - - yield session - - # Finalize test here - transaction.rollback() - connection.close() - session.remove() - - -def setup_fixtures(session): - reporter = Reporter(first_name='ABA', last_name='X') - session.add(reporter) - reporter2 = Reporter(first_name='ABO', last_name='Y') - session.add(reporter2) - article = Article(headline='Hi!') - article.reporter = reporter - session.add(article) - editor = Editor(name="John") - session.add(editor) - session.commit() - - -def test_should_query_well(session): - setup_fixtures(session) - - class ReporterType(SQLAlchemyObjectType): - - class Meta: - model = Reporter - - class Query(graphene.ObjectType): - reporter = graphene.Field(ReporterType) - reporters = graphene.List(ReporterType) - - def resolve_reporter(self, *args, **kwargs): - return session.query(Reporter).first() - - def resolve_reporters(self, *args, **kwargs): - return session.query(Reporter) - - query = ''' - query ReporterQuery { - reporter { - firstName, - lastName, - email - } - reporters { - firstName - } - } - ''' - expected = { - 'reporter': { - 'firstName': 'ABA', - 'lastName': 'X', - 'email': None - }, - 'reporters': [{ - 'firstName': 'ABA', - }, { - 'firstName': 'ABO', - }] - } - schema = graphene.Schema(query=Query) - result = schema.execute(query) - assert not result.errors - assert result.data == expected - - -def test_should_node(session): - setup_fixtures(session) - - class ReporterNode(SQLAlchemyObjectType): - - class Meta: - model = Reporter - interfaces = (Node, ) - - @classmethod - def get_node(cls, id, info): - return Reporter(id=2, first_name='Cookie Monster') - - class ArticleNode(SQLAlchemyObjectType): - - class Meta: - model = Article - interfaces = (Node, ) - - # @classmethod - # def get_node(cls, id, info): - # return Article(id=1, headline='Article node') - - class Query(graphene.ObjectType): - node = Node.Field() - reporter = graphene.Field(ReporterNode) - article = graphene.Field(ArticleNode) - all_articles = SQLAlchemyConnectionField(ArticleNode) - - def resolve_reporter(self, *args, **kwargs): - return session.query(Reporter).first() - - def resolve_article(self, *args, **kwargs): - return session.query(Article).first() - - query = ''' - query ReporterQuery { - reporter { - id, - firstName, - articles { - edges { - node { - headline - } - } - } - lastName, - email - } - allArticles { - edges { - node { - headline - } - } - } - myArticle: node(id:"QXJ0aWNsZU5vZGU6MQ==") { - id - ... on ReporterNode { - firstName - } - ... on ArticleNode { - headline - } - } - } - ''' - expected = { - 'reporter': { - 'id': 'UmVwb3J0ZXJOb2RlOjE=', - 'firstName': 'ABA', - 'lastName': 'X', - 'email': None, - 'articles': { - 'edges': [{ - 'node': { - 'headline': 'Hi!' - } - }] - }, - }, - 'allArticles': { - 'edges': [{ - 'node': { - 'headline': 'Hi!' - } - }] - }, - 'myArticle': { - 'id': 'QXJ0aWNsZU5vZGU6MQ==', - 'headline': 'Hi!' - } - } - schema = graphene.Schema(query=Query) - result = schema.execute(query, context_value={'session': session}) - assert not result.errors - assert result.data == expected - - -def test_should_custom_identifier(session): - setup_fixtures(session) - - class EditorNode(SQLAlchemyObjectType): - - class Meta: - model = Editor - interfaces = (Node, ) - - class Query(graphene.ObjectType): - node = Node.Field() - all_editors = SQLAlchemyConnectionField(EditorNode) - - query = ''' - query EditorQuery { - allEditors { - edges { - node { - id, - name - } - } - }, - node(id: "RWRpdG9yTm9kZTox") { - ...on EditorNode { - name - } - } - } - ''' - expected = { - 'allEditors': { - 'edges': [{ - 'node': { - 'id': 'RWRpdG9yTm9kZTox', - 'name': 'John' - } - }] - }, - 'node': { - 'name': 'John' - } - } - - schema = graphene.Schema(query=Query) - result = schema.execute(query, context_value={'session': session}) - assert not result.errors - assert result.data == expected - - -def test_should_mutate_well(session): - setup_fixtures(session) - - class EditorNode(SQLAlchemyObjectType): - - class Meta: - model = Editor - interfaces = (Node, ) - - - class ReporterNode(SQLAlchemyObjectType): - - class Meta: - model = Reporter - interfaces = (Node, ) - - @classmethod - def get_node(cls, id, info): - return Reporter(id=2, first_name='Cookie Monster') - - class ArticleNode(SQLAlchemyObjectType): - - class Meta: - model = Article - interfaces = (Node, ) - - class CreateArticle(graphene.Mutation): - class Input: - headline = graphene.String() - reporter_id = graphene.ID() - - ok = graphene.Boolean() - article = graphene.Field(ArticleNode) - - @classmethod - def mutate(cls, instance, args, context, info): - new_article = Article( - headline=args.get('headline'), - reporter_id = args.get('reporter_id'), - ) - - session.add(new_article) - session.commit() - ok = True - - return CreateArticle(article=new_article, ok=ok) - - class Query(graphene.ObjectType): - node = Node.Field() - - class Mutation(graphene.ObjectType): - create_article = CreateArticle.Field() - - query = ''' - mutation ArticleCreator { - createArticle( - headline: "My Article" - reporterId: "1" - ) { - ok - article { - headline - reporter { - id - firstName - } - } - } - } - ''' - expected = { - 'createArticle': { - 'ok': True, - 'article': { - 'headline': 'My Article', - 'reporter': { - 'id': 'UmVwb3J0ZXJOb2RlOjE=', - 'firstName': 'ABA' - } - } - }, - } - - schema = graphene.Schema(query=Query, mutation=Mutation) - result = schema.execute(query, context_value={'session': session}) - assert not result.errors - assert result.data == expected diff --git a/graphene-sqlalchemy/graphene_sqlalchemy/tests/test_schema.py b/graphene-sqlalchemy/graphene_sqlalchemy/tests/test_schema.py deleted file mode 100644 index c51ca3eb..00000000 --- a/graphene-sqlalchemy/graphene_sqlalchemy/tests/test_schema.py +++ /dev/null @@ -1,40 +0,0 @@ -from py.test import raises - -from ..types import SQLAlchemyObjectType - -from .models import Reporter -from ..registry import Registry - - -def test_should_raise_if_no_model(): - with raises(Exception) as excinfo: - class Character1(SQLAlchemyObjectType): - pass - assert 'valid SQLAlchemy Model' in str(excinfo.value) - - -def test_should_raise_if_model_is_invalid(): - with raises(Exception) as excinfo: - class Character2(SQLAlchemyObjectType): - class Meta: - model = 1 - assert 'valid SQLAlchemy Model' in str(excinfo.value) - - -def test_should_map_fields_correctly(): - class ReporterType2(SQLAlchemyObjectType): - - class Meta: - model = Reporter - registry = Registry() - - assert list(ReporterType2._meta.fields.keys()) == ['id', 'first_name', 'last_name', 'email', 'pets', 'articles', 'favorite_article'] - - -def test_should_map_only_few_fields(): - class Reporter2(SQLAlchemyObjectType): - - class Meta: - model = Reporter - only_fields = ('id', 'email') - assert list(Reporter2._meta.fields.keys()) == ['id', 'email'] diff --git a/graphene-sqlalchemy/graphene_sqlalchemy/tests/test_types.py b/graphene-sqlalchemy/graphene_sqlalchemy/tests/test_types.py deleted file mode 100644 index 3bae5442..00000000 --- a/graphene-sqlalchemy/graphene_sqlalchemy/tests/test_types.py +++ /dev/null @@ -1,73 +0,0 @@ -from graphql.type import GraphQLObjectType, GraphQLInterfaceType -from graphql import GraphQLInt -from pytest import raises - -from graphene import Schema, Interface, ObjectType -from graphene.relay import Node, is_node -from ..types import SQLAlchemyObjectType -from ..registry import Registry - -from graphene import Field, Int - -from .models import Article, Reporter - -registry = Registry() - - -class Character(SQLAlchemyObjectType): - '''Character description''' - class Meta: - model = Reporter - registry = registry - - -class Human(SQLAlchemyObjectType): - '''Human description''' - - pub_date = Int() - - class Meta: - model = Article - exclude_fields = ('id', ) - registry = registry - interfaces = (Node, ) - - -def test_sqlalchemy_interface(): - assert issubclass(Node, Interface) - assert issubclass(Node, Node) - - -# @patch('graphene.contrib.sqlalchemy.tests.models.Article.filter', return_value=Article(id=1)) -# def test_sqlalchemy_get_node(get): -# human = Human.get_node(1, None) -# get.assert_called_with(id=1) -# assert human.id == 1 - - -def test_objecttype_registered(): - assert issubclass(Character, ObjectType) - assert Character._meta.model == Reporter - assert list(Character._meta.fields.keys()) == ['id', 'first_name', 'last_name', 'email', 'pets', 'articles', 'favorite_article'] - - -# def test_sqlalchemynode_idfield(): -# idfield = Node._meta.fields_map['id'] -# assert isinstance(idfield, GlobalIDField) - - -# def test_node_idfield(): -# idfield = Human._meta.fields_map['id'] -# assert isinstance(idfield, GlobalIDField) - - -def test_node_replacedfield(): - idfield = Human._meta.fields['pub_date'] - assert isinstance(idfield, Field) - assert idfield.type == Int - - -def test_object_type(): - assert issubclass(Human, ObjectType) - assert list(Human._meta.fields.keys()) == ['id', 'headline', 'reporter_id', 'reporter', 'pub_date'] - assert is_node(Human) diff --git a/graphene-sqlalchemy/graphene_sqlalchemy/tests/test_utils.py b/graphene-sqlalchemy/graphene_sqlalchemy/tests/test_utils.py deleted file mode 100644 index 484b9f6a..00000000 --- a/graphene-sqlalchemy/graphene_sqlalchemy/tests/test_utils.py +++ /dev/null @@ -1,24 +0,0 @@ -from graphene import ObjectType, Schema, String - -from ..utils import get_session - - -def test_get_session(): - session = 'My SQLAlchemy session' - - class Query(ObjectType): - x = String() - - def resolve_x(self, args, context, info): - return get_session(context) - - query = ''' - query ReporterQuery { - x - } - ''' - - schema = Schema(query=Query) - result = schema.execute(query, context_value={'session': session}) - assert not result.errors - assert result.data['x'] == session diff --git a/graphene-sqlalchemy/graphene_sqlalchemy/types.py b/graphene-sqlalchemy/graphene_sqlalchemy/types.py deleted file mode 100644 index bade191f..00000000 --- a/graphene-sqlalchemy/graphene_sqlalchemy/types.py +++ /dev/null @@ -1,145 +0,0 @@ -from collections import OrderedDict -import six -from sqlalchemy.inspection import inspect as sqlalchemyinspect -from sqlalchemy.orm.exc import NoResultFound - -from graphene import ObjectType, Field -from graphene.relay import is_node -from .converter import (convert_sqlalchemy_column, - convert_sqlalchemy_composite, - convert_sqlalchemy_relationship) -from .utils import is_mapped - -from graphene.types.objecttype import ObjectTypeMeta -from graphene.types.options import Options -from .registry import Registry, get_global_registry -from graphene.utils.is_base_type import is_base_type -from graphene.types.utils import yank_fields_from_attrs, merge -from .utils import get_query - - -def construct_fields(options): - only_fields = options.only_fields - exclude_fields = options.exclude_fields - inspected_model = sqlalchemyinspect(options.model) - - fields = OrderedDict() - - for name, column in inspected_model.columns.items(): - is_not_in_only = only_fields and name not in only_fields - is_already_created = name in options.fields - is_excluded = name in exclude_fields or is_already_created - if is_not_in_only or is_excluded: - # We skip this field if we specify only_fields and is not - # in there. Or when we excldue this field in exclude_fields - continue - converted_column = convert_sqlalchemy_column(column, options.registry) - fields[name] = converted_column - - for name, composite in inspected_model.composites.items(): - is_not_in_only = only_fields and name not in only_fields - is_already_created = name in options.fields - is_excluded = name in exclude_fields or is_already_created - if is_not_in_only or is_excluded: - # We skip this field if we specify only_fields and is not - # in there. Or when we excldue this field in exclude_fields - continue - converted_composite = convert_sqlalchemy_composite(composite, options.registry) - fields[name] = converted_composite - - # Get all the columns for the relationships on the model - for relationship in inspected_model.relationships: - is_not_in_only = only_fields and relationship.key not in only_fields - is_already_created = relationship.key in options.fields - is_excluded = relationship.key in exclude_fields or is_already_created - if is_not_in_only or is_excluded: - # We skip this field if we specify only_fields and is not - # in there. Or when we excldue this field in exclude_fields - continue - converted_relationship = convert_sqlalchemy_relationship(relationship, options.registry) - name = relationship.key - fields[name] = converted_relationship - - return fields - - -class SQLAlchemyObjectTypeMeta(ObjectTypeMeta): - - @staticmethod - def __new__(cls, name, bases, attrs): - # Also ensure initialization is only performed for subclasses of Model - # (excluding Model class itself). - if not is_base_type(bases, SQLAlchemyObjectTypeMeta): - return type.__new__(cls, name, bases, attrs) - - options = Options( - attrs.pop('Meta', None), - name=name, - description=attrs.pop('__doc__', None), - model=None, - local_fields=None, - only_fields=(), - exclude_fields=(), - id='id', - interfaces=(), - registry=None - ) - - if not options.registry: - options.registry = get_global_registry() - assert isinstance(options.registry, Registry), ( - 'The attribute registry in {}.Meta needs to be an' - ' instance of Registry, received "{}".' - ).format(name, options.registry) - assert is_mapped(options.model), ( - 'You need to pass a valid SQLAlchemy Model in ' - '{}.Meta, received "{}".' - ).format(name, options.model) - - - cls = ObjectTypeMeta.__new__(cls, name, bases, dict(attrs, _meta=options)) - - options.registry.register(cls) - - options.sqlalchemy_fields = yank_fields_from_attrs( - construct_fields(options), - _as=Field, - ) - options.fields = merge( - options.interface_fields, - options.sqlalchemy_fields, - options.base_fields, - options.local_fields - ) - - return cls - - -class SQLAlchemyObjectType(six.with_metaclass(SQLAlchemyObjectTypeMeta, ObjectType)): - @classmethod - def is_type_of(cls, root, context, info): - if isinstance(root, cls): - return True - if not is_mapped(type(root)): - raise Exception(( - 'Received incompatible instance "{}".' - ).format(root)) - return type(root) == cls._meta.model - - @classmethod - def get_query(cls, context): - model = cls._meta.model - return get_query(model, context) - - @classmethod - def get_node(cls, id, context, info): - try: - return cls.get_query(context).get(id) - except NoResultFound: - return None - - def resolve_id(root, args, context, info): - graphene_type = info.parent_type.graphene_type - if is_node(graphene_type): - return root.__mapper__.primary_key_from_instance(root)[0] - return getattr(root, graphene_type._meta.id, None) diff --git a/graphene-sqlalchemy/graphene_sqlalchemy/utils.py b/graphene-sqlalchemy/graphene_sqlalchemy/utils.py deleted file mode 100644 index 02183081..00000000 --- a/graphene-sqlalchemy/graphene_sqlalchemy/utils.py +++ /dev/null @@ -1,20 +0,0 @@ -from sqlalchemy.ext.declarative.api import DeclarativeMeta - - -def get_session(context): - return context.get('session') - - -def get_query(model, context): - query = getattr(model, 'query', None) - if not query: - session = get_session(context) - if not session: - raise Exception('A query in the model Base or a session in the schema is required for querying.\n' - 'Read more http://graphene-python.org/docs/sqlalchemy/tips/#querying') - query = session.query(model) - return query - - -def is_mapped(obj): - return isinstance(obj, DeclarativeMeta) diff --git a/graphene-sqlalchemy/setup.py b/graphene-sqlalchemy/setup.py deleted file mode 100644 index e0b03d42..00000000 --- a/graphene-sqlalchemy/setup.py +++ /dev/null @@ -1,44 +0,0 @@ -from setuptools import find_packages, setup - -setup( - name='graphene-sqlalchemy', - version='1.0.dev20160910000001', - - description='Graphene SQLAlchemy integration', - # long_description=open('README.rst').read(), - - url='https://github.com/graphql-python/graphene-sqlalchemy', - - author='Syrus Akbary', - author_email='me@syrusakbary.com', - - license='MIT', - - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Libraries', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: Implementation :: PyPy', - ], - - keywords='api graphql protocol rest relay graphene', - - packages=find_packages(exclude=['tests']), - - install_requires=[ - 'six>=1.10.0', - 'graphene>=1.0.dev', - 'SQLAlchemy', - 'singledispatch>=3.4.0.3', - ], - tests_require=[ - 'pytest>=2.7.2', - 'mock', - ], -) diff --git a/graphene/relay/connection.py b/graphene/relay/connection.py index 17d9854e..a1039e43 100644 --- a/graphene/relay/connection.py +++ b/graphene/relay/connection.py @@ -6,13 +6,14 @@ import six from graphql_relay import connection_from_list -from ..types import Boolean, Int, List, String, AbstractType +from ..types import (AbstractType, Boolean, Enum, Int, Interface, List, NonNull, Scalar, String, + Union) from ..types.field import Field from ..types.objecttype import ObjectType, ObjectTypeMeta from ..types.options import Options from ..utils.is_base_type import is_base_type from ..utils.props import props -from .node import Node, is_node +from .node import is_node class PageInfo(ObjectType): @@ -49,7 +50,7 @@ class ConnectionMeta(ObjectTypeMeta): options = Options( attrs.pop('Meta', None), - name=None, + name=name, description=None, node=None, ) @@ -57,11 +58,11 @@ class ConnectionMeta(ObjectTypeMeta): options.local_fields = OrderedDict() assert options.node, 'You have to provide a node in {}.Meta'.format(cls.__name__) - assert issubclass(options.node, (Node, ObjectType)), ( + assert issubclass(options.node, (Scalar, Enum, ObjectType, Interface, Union, NonNull)), ( 'Received incompatible node "{}" for Connection {}.' ).format(options.node, name) - base_name = re.sub('Connection$', '', name) + base_name = re.sub('Connection$', '', options.name) or options.node._meta.name if not options.name: options.name = '{}Connection'.format(base_name) diff --git a/graphene/relay/mutation.py b/graphene/relay/mutation.py index d6bf2cbe..2fa6e187 100644 --- a/graphene/relay/mutation.py +++ b/graphene/relay/mutation.py @@ -2,9 +2,10 @@ import re from functools import partial import six + from promise import Promise -from ..types import Argument, Field, InputObjectType, String, AbstractType +from ..types import AbstractType, Argument, Field, InputObjectType, String from ..types.objecttype import ObjectType, ObjectTypeMeta from ..utils.is_base_type import is_base_type from ..utils.props import props diff --git a/graphene/relay/node.py b/graphene/relay/node.py index 24db5239..857c2f71 100644 --- a/graphene/relay/node.py +++ b/graphene/relay/node.py @@ -59,6 +59,7 @@ class NodeMeta(InterfaceMeta): class NodeField(Field): + def __init__(self, node, type=False, deprecation_reason=None, name=None, **kwargs): assert issubclass(node, Node), 'NodeField can only operate in Nodes' diff --git a/graphene/relay/tests/test_connection.py b/graphene/relay/tests/test_connection.py index 2f1441ac..d2279254 100644 --- a/graphene/relay/tests/test_connection.py +++ b/graphene/relay/tests/test_connection.py @@ -1,5 +1,5 @@ -from ...types import Field, List, NonNull, ObjectType, String, AbstractType +from ...types import AbstractType, Field, List, NonNull, ObjectType, String from ..connection import Connection, PageInfo from ..node import Node @@ -41,6 +41,7 @@ def test_connection_inherit_abstracttype(): extra = String() class MyObjectConnection(BaseConnection, Connection): + class Meta: node = MyObject @@ -51,6 +52,7 @@ def test_connection_inherit_abstracttype(): def test_edge(): class MyObjectConnection(Connection): + class Meta: node = MyObject @@ -74,6 +76,7 @@ def test_edge_with_bases(): extra = String() class MyObjectConnection(Connection): + class Meta: node = MyObject diff --git a/graphene/relay/tests/test_connection_query.py b/graphene/relay/tests/test_connection_query.py index b2c10c31..7a197f27 100644 --- a/graphene/relay/tests/test_connection_query.py +++ b/graphene/relay/tests/test_connection_query.py @@ -1,14 +1,16 @@ from collections import OrderedDict +from graphql_relay.utils import base64 + +from ...types import ObjectType, Schema, String from ..connection import ConnectionField from ..node import Node -from graphql_relay.utils import base64 -from ...types import ObjectType, String, Schema letter_chars = ['A', 'B', 'C', 'D', 'E'] class Letter(ObjectType): + class Meta: interfaces = (Node, ) @@ -163,7 +165,13 @@ def test_returns_all_elements_if_cursors_are_invalid(): def test_returns_all_elements_if_cursors_are_on_the_outside(): - check('before: "{}" after: "{}"'.format(base64('arrayconnection:%s' % 6), base64('arrayconnection:%s' % -1)), 'ABCDE') + check( + 'before: "{}" after: "{}"'.format( + base64( + 'arrayconnection:%s' % 6), + base64( + 'arrayconnection:%s' % -1)), + 'ABCDE') def test_returns_no_elements_if_cursors_cross(): diff --git a/graphene/relay/tests/test_mutation.py b/graphene/relay/tests/test_mutation.py index 83633222..5a2be210 100644 --- a/graphene/relay/tests/test_mutation.py +++ b/graphene/relay/tests/test_mutation.py @@ -2,8 +2,8 @@ import pytest from graphql_relay import to_global_id -from ...types import (Argument, Field, InputField, InputObjectType, ObjectType, - Schema, AbstractType, NonNull) +from ...types import (AbstractType, Argument, Field, InputField, + InputObjectType, NonNull, ObjectType, Schema) from ...types.scalars import String from ..mutation import ClientIDMutation from ..node import GlobalID, Node @@ -129,6 +129,7 @@ def test_node_query(): assert not executed.errors assert dict(executed.data) == {'say': {'myNodeId': to_global_id('MyNode', '1'), 'clientMutationId': '1', 'phrase': 'hello'}} + def test_edge_query(): executed = schema.execute( 'mutation a { other(input: {clientMutationId:"1"}) { myNodeEdge { cursor node { name }} } }' diff --git a/graphene/relay/tests/test_node.py b/graphene/relay/tests/test_node.py index dc2d3e43..70448ebb 100644 --- a/graphene/relay/tests/test_node.py +++ b/graphene/relay/tests/test_node.py @@ -1,9 +1,8 @@ from collections import OrderedDict -import pytest from graphql_relay import to_global_id -from ...types import ObjectType, Schema, String, AbstractType +from ...types import AbstractType, ObjectType, Schema, String from ..connection import Connection from ..node import Node, GlobalID @@ -84,10 +83,11 @@ def test_node_query(): def test_subclassed_node_query(): executed = schema.execute( - '{ node(id:"%s") { ... on MyOtherNode { shared, extraField, somethingElse } } }' % to_global_id("MyOtherNode", 1) - ) + '{ node(id:"%s") { ... on MyOtherNode { shared, extraField, somethingElse } } }' % + to_global_id("MyOtherNode", 1)) assert not executed.errors - assert executed.data == OrderedDict({'node': OrderedDict([('shared', '1'), ('extraField', 'extra field info.'), ('somethingElse', '----')])}) + assert executed.data == OrderedDict({'node': OrderedDict( + [('shared', '1'), ('extraField', 'extra field info.'), ('somethingElse', '----')])}) def test_node_query_implemented_id(): diff --git a/graphene/types/__init__.py b/graphene/types/__init__.py index 179c6f6d..fcb3fbfd 100644 --- a/graphene/types/__init__.py +++ b/graphene/types/__init__.py @@ -36,4 +36,5 @@ __all__ = [ 'NonNull', 'Argument', 'Dynamic', + 'Union', ] diff --git a/graphene/types/abstracttype.py b/graphene/types/abstracttype.py index 6a7e7152..a5bbb73e 100644 --- a/graphene/types/abstracttype.py +++ b/graphene/types/abstracttype.py @@ -2,8 +2,7 @@ import six from ..utils.is_base_type import is_base_type from .options import Options -from .utils import (yank_fields_from_attrs, get_base_fields, - merge) +from .utils import get_base_fields, merge, yank_fields_from_attrs class AbstractTypeMeta(type): diff --git a/graphene/types/field.py b/graphene/types/field.py index b1122696..531c2f5c 100644 --- a/graphene/types/field.py +++ b/graphene/types/field.py @@ -3,8 +3,9 @@ from collections import Mapping, OrderedDict from functools import partial from ..utils.orderedtype import OrderedType -from .argument import to_arguments +from .argument import Argument, to_arguments from .structures import NonNull +from .unmountedtype import UnmountedType def source_resolver(source, root, args, context, info): @@ -30,6 +31,16 @@ class Field(OrderedType): if required: type = NonNull(type) + # Check if name is actually an argument of the field + if isinstance(name, (Argument, UnmountedType)): + extra_args['name'] = name + name = None + + # Check if source is actually an argument of the field + if isinstance(source, (Argument, UnmountedType)): + extra_args['source'] = source + source = None + self.name = name self._type = type self.args = to_arguments(args or OrderedDict(), extra_args) diff --git a/graphene/types/inputobjecttype.py b/graphene/types/inputobjecttype.py index 67982c8b..2dc91cd5 100644 --- a/graphene/types/inputobjecttype.py +++ b/graphene/types/inputobjecttype.py @@ -2,10 +2,10 @@ import six from ..utils.is_base_type import is_base_type from .abstracttype import AbstractTypeMeta +from .inputfield import InputField from .options import Options from .unmountedtype import UnmountedType -from .utils import yank_fields_from_attrs, get_base_fields, merge -from .inputfield import InputField +from .utils import get_base_fields, merge, yank_fields_from_attrs class InputObjectTypeMeta(AbstractTypeMeta): diff --git a/graphene/types/interface.py b/graphene/types/interface.py index a67ce914..f5a2d61a 100644 --- a/graphene/types/interface.py +++ b/graphene/types/interface.py @@ -2,9 +2,9 @@ import six from ..utils.is_base_type import is_base_type from .abstracttype import AbstractTypeMeta -from .options import Options -from .utils import yank_fields_from_attrs, get_base_fields, merge from .field import Field +from .options import Options +from .utils import get_base_fields, merge, yank_fields_from_attrs class InterfaceMeta(AbstractTypeMeta): diff --git a/graphene/types/objecttype.py b/graphene/types/objecttype.py index 91b74c0f..ca3f6b0c 100644 --- a/graphene/types/objecttype.py +++ b/graphene/types/objecttype.py @@ -4,10 +4,10 @@ import six from ..utils.is_base_type import is_base_type from .abstracttype import AbstractTypeMeta +from .field import Field from .interface import Interface from .options import Options -from .utils import yank_fields_from_attrs, get_base_fields, merge -from .field import Field +from .utils import get_base_fields, merge, yank_fields_from_attrs class ObjectTypeMeta(AbstractTypeMeta): diff --git a/graphene/types/schema.py b/graphene/types/schema.py index 8a3a6e31..a57e6c37 100644 --- a/graphene/types/schema.py +++ b/graphene/types/schema.py @@ -1,5 +1,5 @@ -from graphql import GraphQLSchema, MiddlewareManager, graphql, is_type +from graphql import GraphQLSchema, graphql, is_type from graphql.type.directives import (GraphQLDirective, GraphQLIncludeDirective, GraphQLSkipDirective) from graphql.type.introspection import IntrospectionSchema @@ -14,18 +14,15 @@ class Schema(GraphQLSchema): Schema Definition A Schema is created by supplying the root types of each type of operation, - query and mutation (optional). A schema definition is then supplied to the - validator and executor. + query and mutation (optional). ''' def __init__(self, query=None, mutation=None, subscription=None, - directives=None, types=None, executor=None, middlewares=None, - auto_camelcase=True): + directives=None, types=None, auto_camelcase=True): self._query = query self._mutation = mutation self._subscription = subscription self.types = types - self._executor = executor self.auto_camelcase = auto_camelcase if directives is None: directives = [ @@ -37,10 +34,6 @@ class Schema(GraphQLSchema): 'Schema directives must be List[GraphQLDirective] if provided but got: {}.'.format( directives ) - if middlewares: - self.middlewares = MiddlewareManager(*middlewares) - else: - self.middlewares = None self._directives = directives self.build_typemap() @@ -65,18 +58,8 @@ class Schema(GraphQLSchema): return graphql_type raise Exception("{} is not a valid GraphQL type.".format(_type)) - def execute(self, request_string='', root_value=None, variable_values=None, - context_value=None, operation_name=None, executor=None): - return graphql( - schema=self, - request_string=request_string, - root_value=root_value, - context_value=context_value, - variable_values=variable_values, - operation_name=operation_name, - executor=executor or self._executor, - middlewares=self.middlewares - ) + def execute(self, *args, **kwargs): + return graphql(self, *args, **kwargs) def register(self, object_type): self.types.append(object_type) diff --git a/graphene/types/structures.py b/graphene/types/structures.py index 63fc2c2a..6cf0e4aa 100644 --- a/graphene/types/structures.py +++ b/graphene/types/structures.py @@ -40,6 +40,7 @@ class NonNull(Structure): Note: the enforcement of non-nullability occurs within the executor. ''' + def __init__(self, *args, **kwargs): super(NonNull, self).__init__(*args, **kwargs) assert not isinstance(self.of_type, NonNull), ( diff --git a/graphene/types/tests/test_query.py b/graphene/types/tests/test_query.py index dd6de01c..3059776a 100644 --- a/graphene/types/tests/test_query.py +++ b/graphene/types/tests/test_query.py @@ -1,12 +1,12 @@ import json from functools import partial -from graphql import execute, Source, parse +from graphql import Source, execute, parse -from ..objecttype import ObjectType from ..inputfield import InputField from ..inputobjecttype import InputObjectType -from ..scalars import String, Int +from ..objecttype import ObjectType +from ..scalars import Int, String from ..schema import Schema from ..structures import List @@ -102,9 +102,9 @@ def test_query_middlewares(): p = next(*args, **kwargs) return p.then(lambda x: x[::-1]) - hello_schema = Schema(Query, middlewares=[reversed_middleware]) + hello_schema = Schema(Query) - executed = hello_schema.execute('{ hello, other }') + executed = hello_schema.execute('{ hello, other }', middleware=[reversed_middleware]) assert not executed.errors assert executed.data == {'hello': 'dlroW', 'other': 'rehto'} diff --git a/graphene/types/tests/test_typemap.py b/graphene/types/tests/test_typemap.py index 4d427a0a..974b633e 100644 --- a/graphene/types/tests/test_typemap.py +++ b/graphene/types/tests/test_typemap.py @@ -1,16 +1,16 @@ from graphql.type import (GraphQLArgument, GraphQLEnumType, GraphQLEnumValue, - GraphQLField, GraphQLObjectType, GraphQLString, - GraphQLInterfaceType, GraphQLInputObjectField, - GraphQLInputObjectType) + GraphQLField, GraphQLInputObjectField, + GraphQLInputObjectType, GraphQLInterfaceType, + GraphQLObjectType, GraphQLString) from ..dynamic import Dynamic from ..enum import Enum from ..field import Field from ..inputfield import InputField -from ..objecttype import ObjectType from ..inputobjecttype import InputObjectType from ..interface import Interface +from ..objecttype import ObjectType from ..scalars import String from ..typemap import TypeMap @@ -112,7 +112,7 @@ def test_interface(): foo_field = fields['foo'] assert isinstance(foo_field, GraphQLField) assert foo_field.description == 'Field description' - assert not foo_field.resolver # Resolver not attached in interfaces + assert not foo_field.resolver # Resolver not attached in interfaces assert foo_field.args == { 'bar': GraphQLArgument(GraphQLString, description='Argument description', default_value='x', out_name='bar') } diff --git a/graphene/types/typemap.py b/graphene/types/typemap.py index 360939f2..af193a9b 100644 --- a/graphene/types/typemap.py +++ b/graphene/types/typemap.py @@ -118,6 +118,11 @@ class TypeMap(GraphQLTypeMap): def construct_objecttype(self, map, type): from .definitions import GrapheneObjectType + if type._meta.name in map: + _type = map[type._meta.name] + if is_graphene_type(_type): + assert _type.graphene_type == type + return map map[type._meta.name] = GrapheneObjectType( graphene_type=type, name=type._meta.name,