Codebase list python-graphene-sqlalchemy / bc2650f
New upstream version 1.1.1 Sophie Brun 7 years ago
35 changed file(s) with 2763 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 [run]
1 omit = */tests/*
0 # Created by https://www.gitignore.io
1
2 ### Python ###
3 # Byte-compiled / optimized / DLL files
4 __pycache__/
5 *.py[cod]
6
7 # C extensions
8 *.so
9
10 # Distribution / packaging
11 .Python
12 env/
13 build/
14 develop-eggs/
15 dist/
16 downloads/
17 eggs/
18 .eggs/
19 lib/
20 lib64/
21 parts/
22 sdist/
23 var/
24 *.egg-info/
25 .installed.cfg
26 *.egg
27
28 # PyInstaller
29 # Usually these files are written by a python script from a template
30 # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 *.manifest
32 *.spec
33
34 # Installer logs
35 pip-log.txt
36 pip-delete-this-directory.txt
37
38 # Unit test / coverage reports
39 htmlcov/
40 .tox/
41 .coverage
42 .coverage.*
43 .cache
44 nosetests.xml
45 coverage.xml
46 *,cover
47
48 # Translations
49 *.mo
50 *.pot
51
52 # Django stuff:
53 *.log
54
55 # Sphinx documentation
56 docs/_build/
57
58 # PyBuilder
59 target/
60
61 # PyCharm
62 .idea
63
64 # Databases
65 *.sqlite3
66 .vscode
0 language: python
1 sudo: false
2 python:
3 - 2.7
4 - 3.4
5 - 3.5
6 - pypy
7 before_install:
8 - |
9 if [ "$TRAVIS_PYTHON_VERSION" = "pypy" ]; then
10 export PYENV_ROOT="$HOME/.pyenv"
11 if [ -f "$PYENV_ROOT/bin/pyenv" ]; then
12 cd "$PYENV_ROOT" && git pull
13 else
14 rm -rf "$PYENV_ROOT" && git clone --depth 1 https://github.com/yyuu/pyenv.git "$PYENV_ROOT"
15 fi
16 export PYPY_VERSION="4.0.1"
17 "$PYENV_ROOT/bin/pyenv" install "pypy-$PYPY_VERSION"
18 virtualenv --python="$PYENV_ROOT/versions/pypy-$PYPY_VERSION/bin/python" "$HOME/virtualenvs/pypy-$PYPY_VERSION"
19 source "$HOME/virtualenvs/pypy-$PYPY_VERSION/bin/activate"
20 fi
21 install:
22 - |
23 if [ "$TEST_TYPE" = build ]; then
24 pip install pytest==3.0.2 pytest-cov pytest-benchmark coveralls six mock sqlalchemy_utils
25 pip install -e .
26 python setup.py develop
27 elif [ "$TEST_TYPE" = lint ]; then
28 pip install flake8
29 fi
30 script:
31 - |
32 if [ "$TEST_TYPE" = lint ]; then
33 echo "Checking Python code lint."
34 flake8 graphene_sqlalchemy
35 exit
36 elif [ "$TEST_TYPE" = build ]; then
37 py.test --cov=graphene_sqlalchemy graphene_sqlalchemy examples
38 fi
39 after_success:
40 - |
41 if [ "$TEST_TYPE" = build ]; then
42 coveralls
43 fi
44 env:
45 matrix:
46 - TEST_TYPE=build
47 matrix:
48 fast_finish: true
49 include:
50 - python: '2.7'
51 env: TEST_TYPE=lint
52 deploy:
53 provider: pypi
54 user: syrusakbary
55 on:
56 tags: true
57 password:
58 secure: q0ey31cWljGB30l43aEd1KIPuAHRutzmsd2lBb/2zvD79ReBrzvCdFAkH2xcyo4Volk3aazQQTNUIurnTuvBxmtqja0e+gUaO5LdOcokVdOGyLABXh7qhd2kdvbTDWgSwA4EWneLGXn/SjXSe0f3pCcrwc6WDcLAHxtffMvO9gulpYQtUoOqXfMipMOkRD9iDWTJBsSo3trL70X1FHOVr6Yqi0mfkX2Y/imxn6wlTWRz28Ru94xrj27OmUnCv7qcG0taO8LNlUCquNFAr2sZ+l+U/GkQrrM1y+ehPz3pmI0cCCd7SX/7+EG9ViZ07BZ31nk4pgnqjmj3nFwqnCE/4IApGnduqtrMDF63C9TnB1TU8oJmbbUCu4ODwRpBPZMnwzaHsLnrpdrB89/98NtTfujdrh3U5bVB+t33yxrXVh+FjgLYj9PVeDixpFDn6V/Xcnv4BbRMNOhXIQT7a7/5b99RiXBjCk6KRu+Jdu5DZ+3G4Nbr4oim3kZFPUHa555qbzTlwAfkrQxKv3C3OdVJR7eGc9ADsbHyEJbdPNAh/T+xblXTXLS3hPYDvgM+WEGy3CytBDG3JVcXm25ZP96EDWjweJ7MyfylubhuKj/iR1Y1wiHeIsYq9CqRrFQUWL8gFJBfmgjs96xRXXXnvyLtKUKpKw3wFg5cR/6FnLeYZ8k=
0 Please read [UPGRADE-v1.0.md](https://github.com/graphql-python/graphene/blob/master/UPGRADE-v1.0.md)
1 to learn how to upgrade to Graphene `1.0`.
2
3 ---
4
5 # ![Graphene Logo](http://graphene-python.org/favicon.png) Graphene-SQLAlchemy [![Build Status](https://travis-ci.org/graphql-python/graphene-sqlalchemy.svg?branch=master)](https://travis-ci.org/graphql-python/graphene-sqlalchemy) [![PyPI version](https://badge.fury.io/py/graphene-sqlalchemy.svg)](https://badge.fury.io/py/graphene-sqlalchemy) [![Coverage Status](https://coveralls.io/repos/graphql-python/graphene-sqlalchemy/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-python/graphene-sqlalchemy?branch=master)
6
7
8 A [SQLAlchemy](http://www.sqlalchemy.org/) integration for [Graphene](http://graphene-python.org/).
9
10 ## Installation
11
12 For instaling graphene, just run this command in your shell
13
14 ```bash
15 pip install "graphene-sqlalchemy>=1.0"
16 ```
17
18 ## Examples
19
20 Here is a simple SQLAlchemy model:
21
22 ```python
23 from sqlalchemy import Column, Integer, String
24 from sqlalchemy.orm import relationship
25
26 from sqlalchemy.ext.declarative import declarative_base
27
28 Base = declarative_base()
29
30 class UserModel(Base):
31 __tablename__ = 'department'
32 id = Column(Integer, primary_key=True)
33 name = Column(String)
34 last_name = Column(String)
35 ```
36
37 To create a GraphQL schema for it you simply have to write the following:
38
39 ```python
40 from graphene_sqlalchemy import SQLAlchemyObjectType
41
42 class User(SQLAlchemyObjectType):
43 class Meta:
44 model = UserModel
45
46 class Query(graphene.ObjectType):
47 users = graphene.List(User)
48
49 def resolve_users(self, args, context, info):
50 query = User.get_query(context) # SQLAlchemy query
51 return query.all()
52
53 schema = graphene.Schema(query=Query)
54 ```
55
56 Then you can simply query the schema:
57
58 ```python
59 query = '''
60 query {
61 users {
62 name,
63 lastName
64 }
65 }
66 '''
67 result = schema.execute(query, context_value={'session': db_session})
68 ```
69
70 To learn more check out the following [examples](examples/):
71
72 * **Full example**: [Flask SQLAlchemy example](examples/flask_sqlalchemy)
73
74
75 ## Contributing
76
77 After cloning this repo, ensure dependencies are installed by running:
78
79 ```sh
80 python setup.py install
81 ```
82
83 After developing, the full test suite can be evaluated by running:
84
85 ```sh
86 python setup.py test # Use --pytest-args="-v -s" for verbose mode
87 ```
0 Please read
1 `UPGRADE-v1.0.md <https://github.com/graphql-python/graphene/blob/master/UPGRADE-v1.0.md>`__
2 to learn how to upgrade to Graphene ``1.0``.
3
4 --------------
5
6 |Graphene Logo| Graphene-SQLAlchemy |Build Status| |PyPI version| |Coverage Status|
7 ===================================================================================
8
9 A `SQLAlchemy <http://www.sqlalchemy.org/>`__ integration for
10 `Graphene <http://graphene-python.org/>`__.
11
12 Installation
13 ------------
14
15 For instaling graphene, just run this command in your shell
16
17 .. code:: bash
18
19 pip install "graphene-sqlalchemy>=1.0"
20
21 Examples
22 --------
23
24 Here is a simple SQLAlchemy model:
25
26 .. code:: python
27
28 from sqlalchemy import Column, Integer, String
29 from sqlalchemy.orm import backref, relationship
30
31 from sqlalchemy.ext.declarative import declarative_base
32
33 Base = declarative_base()
34
35 class UserModel(Base):
36 __tablename__ = 'department'
37 id = Column(Integer, primary_key=True)
38 name = Column(String)
39 last_name = Column(String)
40
41 To create a GraphQL schema for it you simply have to write the
42 following:
43
44 .. code:: python
45
46 from graphene_sqlalchemy import SQLAlchemyObjectType
47
48 class User(SQLAlchemyObjectType):
49 class Meta:
50 model = UserModel
51
52 class Query(graphene.ObjectType):
53 users = graphene.List(User)
54
55 def resolve_users(self, args, context, info):
56 query = User.get_query(context) # SQLAlchemy query
57 return query.all()
58
59 schema = graphene.Schema(query=Query)
60
61 Then you can simply query the schema:
62
63 .. code:: python
64
65 query = '''
66 query {
67 users {
68 name,
69 lastName
70 }
71 }
72 '''
73 result = schema.execute(query, context_value={'session': db_session})
74
75 To learn more check out the following `examples <examples/>`__:
76
77 - **Full example**: `Flask SQLAlchemy
78 example <examples/flask_sqlalchemy>`__
79
80 Contributing
81 ------------
82
83 After cloning this repo, ensure dependencies are installed by running:
84
85 .. code:: sh
86
87 python setup.py install
88
89 After developing, the full test suite can be evaluated by running:
90
91 .. code:: sh
92
93 python setup.py test # Use --pytest-args="-v -s" for verbose mode
94
95 .. |Graphene Logo| image:: http://graphene-python.org/favicon.png
96 .. |Build Status| image:: https://travis-ci.org/graphql-python/graphene-sqlalchemy.svg?branch=master
97 :target: https://travis-ci.org/graphql-python/graphene-sqlalchemy
98 .. |PyPI version| image:: https://badge.fury.io/py/graphene-sqlalchemy.svg
99 :target: https://badge.fury.io/py/graphene-sqlalchemy
100 .. |Coverage Status| image:: https://coveralls.io/repos/graphql-python/graphene-sqlalchemy/badge.svg?branch=master&service=github
101 :target: https://coveralls.io/github/graphql-python/graphene-sqlalchemy?branch=master
0 #!/bin/bash
1
2 # Install the required scripts with
3 # pip install autoflake autopep8 isort
4 autoflake ./examples/ ./graphene_sqlalchemy/ -r --remove-unused-variables --remove-all-unused-imports --in-place
5 autopep8 ./examples/ ./graphene_sqlalchemy/ -r --in-place --experimental --aggressive --max-line-length 120
6 isort -rc ./examples/ ./graphene_sqlalchemy/
0 #!/bin/bash
1
2 pandoc README.md --from markdown --to rst -s -o README.rst
0 # Makefile for Sphinx documentation
1 #
2
3 # You can set these variables from the command line.
4 SPHINXOPTS =
5 SPHINXBUILD = sphinx-build
6 PAPER =
7 BUILDDIR = _build
8
9 # Internal variables.
10 PAPEROPT_a4 = -D latex_paper_size=a4
11 PAPEROPT_letter = -D latex_paper_size=letter
12 ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
13 # the i18n builder cannot share the environment and doctrees with the others
14 I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
15
16 .PHONY: help
17 help:
18 @echo "Please use \`make <target>' where <target> is one of"
19 @echo " html to make standalone HTML files"
20 @echo " dirhtml to make HTML files named index.html in directories"
21 @echo " singlehtml to make a single large HTML file"
22 @echo " pickle to make pickle files"
23 @echo " json to make JSON files"
24 @echo " htmlhelp to make HTML files and a HTML help project"
25 @echo " qthelp to make HTML files and a qthelp project"
26 @echo " applehelp to make an Apple Help Book"
27 @echo " devhelp to make HTML files and a Devhelp project"
28 @echo " epub to make an epub"
29 @echo " epub3 to make an epub3"
30 @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
31 @echo " latexpdf to make LaTeX files and run them through pdflatex"
32 @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
33 @echo " text to make text files"
34 @echo " man to make manual pages"
35 @echo " texinfo to make Texinfo files"
36 @echo " info to make Texinfo files and run them through makeinfo"
37 @echo " gettext to make PO message catalogs"
38 @echo " changes to make an overview of all changed/added/deprecated items"
39 @echo " xml to make Docutils-native XML files"
40 @echo " pseudoxml to make pseudoxml-XML files for display purposes"
41 @echo " linkcheck to check all external links for integrity"
42 @echo " doctest to run all doctests embedded in the documentation (if enabled)"
43 @echo " coverage to run coverage check of the documentation (if enabled)"
44 @echo " dummy to check syntax errors of document sources"
45
46 .PHONY: clean
47 clean:
48 rm -rf $(BUILDDIR)/*
49
50 .PHONY: html
51 html:
52 $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
53 @echo
54 @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
55
56 .PHONY: dirhtml
57 dirhtml:
58 $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
59 @echo
60 @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
61
62 .PHONY: singlehtml
63 singlehtml:
64 $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
65 @echo
66 @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
67
68 .PHONY: pickle
69 pickle:
70 $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
71 @echo
72 @echo "Build finished; now you can process the pickle files."
73
74 .PHONY: json
75 json:
76 $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
77 @echo
78 @echo "Build finished; now you can process the JSON files."
79
80 .PHONY: htmlhelp
81 htmlhelp:
82 $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
83 @echo
84 @echo "Build finished; now you can run HTML Help Workshop with the" \
85 ".hhp project file in $(BUILDDIR)/htmlhelp."
86
87 .PHONY: qthelp
88 qthelp:
89 $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
90 @echo
91 @echo "Build finished; now you can run "qcollectiongenerator" with the" \
92 ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
93 @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Graphene.qhcp"
94 @echo "To view the help file:"
95 @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Graphene.qhc"
96
97 .PHONY: applehelp
98 applehelp:
99 $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
100 @echo
101 @echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
102 @echo "N.B. You won't be able to view it unless you put it in" \
103 "~/Library/Documentation/Help or install it in your application" \
104 "bundle."
105
106 .PHONY: devhelp
107 devhelp:
108 $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
109 @echo
110 @echo "Build finished."
111 @echo "To view the help file:"
112 @echo "# mkdir -p $$HOME/.local/share/devhelp/Graphene"
113 @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Graphene"
114 @echo "# devhelp"
115
116 .PHONY: epub
117 epub:
118 $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
119 @echo
120 @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
121
122 .PHONY: epub3
123 epub3:
124 $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
125 @echo
126 @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
127
128 .PHONY: latex
129 latex:
130 $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
131 @echo
132 @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
133 @echo "Run \`make' in that directory to run these through (pdf)latex" \
134 "(use \`make latexpdf' here to do that automatically)."
135
136 .PHONY: latexpdf
137 latexpdf:
138 $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
139 @echo "Running LaTeX files through pdflatex..."
140 $(MAKE) -C $(BUILDDIR)/latex all-pdf
141 @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
142
143 .PHONY: latexpdfja
144 latexpdfja:
145 $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
146 @echo "Running LaTeX files through platex and dvipdfmx..."
147 $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
148 @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
149
150 .PHONY: text
151 text:
152 $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
153 @echo
154 @echo "Build finished. The text files are in $(BUILDDIR)/text."
155
156 .PHONY: man
157 man:
158 $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
159 @echo
160 @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
161
162 .PHONY: texinfo
163 texinfo:
164 $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
165 @echo
166 @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
167 @echo "Run \`make' in that directory to run these through makeinfo" \
168 "(use \`make info' here to do that automatically)."
169
170 .PHONY: info
171 info:
172 $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
173 @echo "Running Texinfo files through makeinfo..."
174 make -C $(BUILDDIR)/texinfo info
175 @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
176
177 .PHONY: gettext
178 gettext:
179 $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
180 @echo
181 @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
182
183 .PHONY: changes
184 changes:
185 $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
186 @echo
187 @echo "The overview file is in $(BUILDDIR)/changes."
188
189 .PHONY: linkcheck
190 linkcheck:
191 $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
192 @echo
193 @echo "Link check complete; look for any errors in the above output " \
194 "or in $(BUILDDIR)/linkcheck/output.txt."
195
196 .PHONY: doctest
197 doctest:
198 $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
199 @echo "Testing of doctests in the sources finished, look at the " \
200 "results in $(BUILDDIR)/doctest/output.txt."
201
202 .PHONY: coverage
203 coverage:
204 $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
205 @echo "Testing of coverage in the sources finished, look at the " \
206 "results in $(BUILDDIR)/coverage/python.txt."
207
208 .PHONY: xml
209 xml:
210 $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
211 @echo
212 @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
213
214 .PHONY: pseudoxml
215 pseudoxml:
216 $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
217 @echo
218 @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
219
220 .PHONY: dummy
221 dummy:
222 $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
223 @echo
224 @echo "Build finished. Dummy builder generates no files."
0 import os
1
2 on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
3
4 # -*- coding: utf-8 -*-
5 #
6 # Graphene documentation build configuration file, created by
7 # sphinx-quickstart on Sun Sep 11 18:30:51 2016.
8 #
9 # This file is execfile()d with the current directory set to its
10 # containing dir.
11 #
12 # Note that not all possible configuration values are present in this
13 # autogenerated file.
14 #
15 # All configuration values have a default; values that are commented out
16 # serve to show the default.
17
18 # If extensions (or modules to document with autodoc) are in another directory,
19 # add these directories to sys.path here. If the directory is relative to the
20 # documentation root, use os.path.abspath to make it absolute, like shown here.
21 #
22 # import os
23 # import sys
24 # sys.path.insert(0, os.path.abspath('.'))
25
26 # -- General configuration ------------------------------------------------
27
28 # If your documentation needs a minimal Sphinx version, state it here.
29 #
30 # needs_sphinx = '1.0'
31
32 # Add any Sphinx extension module names here, as strings. They can be
33 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
34 # ones.
35 extensions = [
36 'sphinx.ext.autodoc',
37 'sphinx.ext.intersphinx',
38 'sphinx.ext.todo',
39 'sphinx.ext.coverage',
40 'sphinx.ext.viewcode',
41 ]
42 if not on_rtd:
43 extensions += [
44 'sphinx.ext.githubpages',
45 ]
46
47 # Add any paths that contain templates here, relative to this directory.
48 templates_path = ['_templates']
49
50 # The suffix(es) of source filenames.
51 # You can specify multiple suffix as a list of string:
52 #
53 # source_suffix = ['.rst', '.md']
54 source_suffix = '.rst'
55
56 # The encoding of source files.
57 #
58 # source_encoding = 'utf-8-sig'
59
60 # The master toctree document.
61 master_doc = 'index'
62
63 # General information about the project.
64 project = u'Graphene Django'
65 copyright = u'Graphene 2016'
66 author = u'Syrus Akbary'
67
68 # The version info for the project you're documenting, acts as replacement for
69 # |version| and |release|, also used in various other places throughout the
70 # built documents.
71 #
72 # The short X.Y version.
73 version = u'1.0'
74 # The full version, including alpha/beta/rc tags.
75 release = u'1.0.dev'
76
77 # The language for content autogenerated by Sphinx. Refer to documentation
78 # for a list of supported languages.
79 #
80 # This is also used if you do content translation via gettext catalogs.
81 # Usually you set "language" from the command line for these cases.
82 language = None
83
84 # There are two options for replacing |today|: either, you set today to some
85 # non-false value, then it is used:
86 #
87 # today = ''
88 #
89 # Else, today_fmt is used as the format for a strftime call.
90 #
91 # today_fmt = '%B %d, %Y'
92
93 # List of patterns, relative to source directory, that match files and
94 # directories to ignore when looking for source files.
95 # This patterns also effect to html_static_path and html_extra_path
96 exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
97
98 # The reST default role (used for this markup: `text`) to use for all
99 # documents.
100 #
101 # default_role = None
102
103 # If true, '()' will be appended to :func: etc. cross-reference text.
104 #
105 # add_function_parentheses = True
106
107 # If true, the current module name will be prepended to all description
108 # unit titles (such as .. function::).
109 #
110 # add_module_names = True
111
112 # If true, sectionauthor and moduleauthor directives will be shown in the
113 # output. They are ignored by default.
114 #
115 # show_authors = False
116
117 # The name of the Pygments (syntax highlighting) style to use.
118 pygments_style = 'sphinx'
119
120 # A list of ignored prefixes for module index sorting.
121 # modindex_common_prefix = []
122
123 # If true, keep warnings as "system message" paragraphs in the built documents.
124 # keep_warnings = False
125
126 # If true, `todo` and `todoList` produce output, else they produce nothing.
127 todo_include_todos = True
128
129
130 # -- Options for HTML output ----------------------------------------------
131
132 # The theme to use for HTML and HTML Help pages. See the documentation for
133 # a list of builtin themes.
134 #
135 # html_theme = 'alabaster'
136 # if on_rtd:
137 # html_theme = 'sphinx_rtd_theme'
138 import sphinx_graphene_theme
139
140 html_theme = "sphinx_graphene_theme"
141
142 html_theme_path = [sphinx_graphene_theme.get_html_theme_path()]
143
144
145 # Theme options are theme-specific and customize the look and feel of a theme
146 # further. For a list of options available for each theme, see the
147 # documentation.
148 #
149 # html_theme_options = {}
150
151 # Add any paths that contain custom themes here, relative to this directory.
152 # html_theme_path = []
153
154 # The name for this set of Sphinx documents.
155 # "<project> v<release> documentation" by default.
156 #
157 # html_title = u'Graphene v1.0.dev'
158
159 # A shorter title for the navigation bar. Default is the same as html_title.
160 #
161 # html_short_title = None
162
163 # The name of an image file (relative to this directory) to place at the top
164 # of the sidebar.
165 #
166 # html_logo = None
167
168 # The name of an image file (relative to this directory) to use as a favicon of
169 # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
170 # pixels large.
171 #
172 # html_favicon = None
173
174 # Add any paths that contain custom static files (such as style sheets) here,
175 # relative to this directory. They are copied after the builtin static files,
176 # so a file named "default.css" will overwrite the builtin "default.css".
177 html_static_path = ['_static']
178
179 # Add any extra paths that contain custom files (such as robots.txt or
180 # .htaccess) here, relative to this directory. These files are copied
181 # directly to the root of the documentation.
182 #
183 # html_extra_path = []
184
185 # If not None, a 'Last updated on:' timestamp is inserted at every page
186 # bottom, using the given strftime format.
187 # The empty string is equivalent to '%b %d, %Y'.
188 #
189 # html_last_updated_fmt = None
190
191 # If true, SmartyPants will be used to convert quotes and dashes to
192 # typographically correct entities.
193 #
194 # html_use_smartypants = True
195
196 # Custom sidebar templates, maps document names to template names.
197 #
198 # html_sidebars = {}
199
200 # Additional templates that should be rendered to pages, maps page names to
201 # template names.
202 #
203 # html_additional_pages = {}
204
205 # If false, no module index is generated.
206 #
207 # html_domain_indices = True
208
209 # If false, no index is generated.
210 #
211 # html_use_index = True
212
213 # If true, the index is split into individual pages for each letter.
214 #
215 # html_split_index = False
216
217 # If true, links to the reST sources are added to the pages.
218 #
219 # html_show_sourcelink = True
220
221 # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
222 #
223 # html_show_sphinx = True
224
225 # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
226 #
227 # html_show_copyright = True
228
229 # If true, an OpenSearch description file will be output, and all pages will
230 # contain a <link> tag referring to it. The value of this option must be the
231 # base URL from which the finished HTML is served.
232 #
233 # html_use_opensearch = ''
234
235 # This is the file name suffix for HTML files (e.g. ".xhtml").
236 # html_file_suffix = None
237
238 # Language to be used for generating the HTML full-text search index.
239 # Sphinx supports the following languages:
240 # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
241 # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh'
242 #
243 # html_search_language = 'en'
244
245 # A dictionary with options for the search language support, empty by default.
246 # 'ja' uses this config value.
247 # 'zh' user can custom change `jieba` dictionary path.
248 #
249 # html_search_options = {'type': 'default'}
250
251 # The name of a javascript file (relative to the configuration directory) that
252 # implements a search results scorer. If empty, the default will be used.
253 #
254 # html_search_scorer = 'scorer.js'
255
256 # Output file base name for HTML help builder.
257 htmlhelp_basename = 'Graphenedoc'
258
259 # -- Options for LaTeX output ---------------------------------------------
260
261 latex_elements = {
262 # The paper size ('letterpaper' or 'a4paper').
263 #
264 # 'papersize': 'letterpaper',
265
266 # The font size ('10pt', '11pt' or '12pt').
267 #
268 # 'pointsize': '10pt',
269
270 # Additional stuff for the LaTeX preamble.
271 #
272 # 'preamble': '',
273
274 # Latex figure (float) alignment
275 #
276 # 'figure_align': 'htbp',
277 }
278
279 # Grouping the document tree into LaTeX files. List of tuples
280 # (source start file, target name, title,
281 # author, documentclass [howto, manual, or own class]).
282 latex_documents = [
283 (master_doc, 'Graphene.tex', u'Graphene Documentation',
284 u'Syrus Akbary', 'manual'),
285 ]
286
287 # The name of an image file (relative to this directory) to place at the top of
288 # the title page.
289 #
290 # latex_logo = None
291
292 # For "manual" documents, if this is true, then toplevel headings are parts,
293 # not chapters.
294 #
295 # latex_use_parts = False
296
297 # If true, show page references after internal links.
298 #
299 # latex_show_pagerefs = False
300
301 # If true, show URL addresses after external links.
302 #
303 # latex_show_urls = False
304
305 # Documents to append as an appendix to all manuals.
306 #
307 # latex_appendices = []
308
309 # It false, will not define \strong, \code, itleref, \crossref ... but only
310 # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added
311 # packages.
312 #
313 # latex_keep_old_macro_names = True
314
315 # If false, no module index is generated.
316 #
317 # latex_domain_indices = True
318
319
320 # -- Options for manual page output ---------------------------------------
321
322 # One entry per manual page. List of tuples
323 # (source start file, name, description, authors, manual section).
324 man_pages = [
325 (master_doc, 'graphene_django', u'Graphene Django Documentation',
326 [author], 1)
327 ]
328
329 # If true, show URL addresses after external links.
330 #
331 # man_show_urls = False
332
333
334 # -- Options for Texinfo output -------------------------------------------
335
336 # Grouping the document tree into Texinfo files. List of tuples
337 # (source start file, target name, title, author,
338 # dir menu entry, description, category)
339 texinfo_documents = [
340 (master_doc, 'Graphene-Django', u'Graphene Django Documentation',
341 author, 'Graphene Django', 'One line description of project.',
342 'Miscellaneous'),
343 ]
344
345 # Documents to append as an appendix to all manuals.
346 #
347 # texinfo_appendices = []
348
349 # If false, no module index is generated.
350 #
351 # texinfo_domain_indices = True
352
353 # How to display URL addresses: 'footnote', 'no', or 'inline'.
354 #
355 # texinfo_show_urls = 'footnote'
356
357 # If true, do not generate a @detailmenu in the "Top" node's menu.
358 #
359 # texinfo_no_detailmenu = False
360
361
362 # -- Options for Epub output ----------------------------------------------
363
364 # Bibliographic Dublin Core info.
365 epub_title = project
366 epub_author = author
367 epub_publisher = author
368 epub_copyright = copyright
369
370 # The basename for the epub file. It defaults to the project name.
371 # epub_basename = project
372
373 # The HTML theme for the epub output. Since the default themes are not
374 # optimized for small screen space, using the same theme for HTML and epub
375 # output is usually not wise. This defaults to 'epub', a theme designed to save
376 # visual space.
377 #
378 # epub_theme = 'epub'
379
380 # The language of the text. It defaults to the language option
381 # or 'en' if the language is not set.
382 #
383 # epub_language = ''
384
385 # The scheme of the identifier. Typical schemes are ISBN or URL.
386 # epub_scheme = ''
387
388 # The unique identifier of the text. This can be a ISBN number
389 # or the project homepage.
390 #
391 # epub_identifier = ''
392
393 # A unique identification for the text.
394 #
395 # epub_uid = ''
396
397 # A tuple containing the cover image and cover page html template filenames.
398 #
399 # epub_cover = ()
400
401 # A sequence of (type, uri, title) tuples for the guide element of content.opf.
402 #
403 # epub_guide = ()
404
405 # HTML files that should be inserted before the pages created by sphinx.
406 # The format is a list of tuples containing the path and title.
407 #
408 # epub_pre_files = []
409
410 # HTML files that should be inserted after the pages created by sphinx.
411 # The format is a list of tuples containing the path and title.
412 #
413 # epub_post_files = []
414
415 # A list of files that should not be packed into the epub file.
416 epub_exclude_files = ['search.html']
417
418 # The depth of the table of contents in toc.ncx.
419 #
420 # epub_tocdepth = 3
421
422 # Allow duplicate toc entries.
423 #
424 # epub_tocdup = True
425
426 # Choose between 'default' and 'includehidden'.
427 #
428 # epub_tocscope = 'default'
429
430 # Fix unsupported image types using the Pillow.
431 #
432 # epub_fix_images = False
433
434 # Scale large images.
435 #
436 # epub_max_image_width = 0
437
438 # How to display URL addresses: 'footnote', 'no', or 'inline'.
439 #
440 # epub_show_urls = 'inline'
441
442 # If false, no index is generated.
443 #
444 # epub_use_index = True
445
446
447 # Example configuration for intersphinx: refer to the Python standard library.
448 intersphinx_mapping = {'https://docs.python.org/': None}
0 Graphene-SQLAlchemy
1 ===================
2
3 Contents:
4
5 .. toctree::
6 :maxdepth: 0
7
8 tutorial
9 tips
0 # Docs template
1 https://github.com/graphql-python/graphene-python.org/archive/docs.zip
0 ====
1 Tips
2 ====
3
4 Tips
5 ====
6
7 Querying
8 --------
9
10 For make querying to the database work, there are two alternatives:
11
12 - Set the db session when you do the execution:
13
14 .. code:: python
15
16 schema = graphene.Schema()
17 schema.execute(context_value={'session': session})
18
19 - Create a query for the models.
20
21 .. code:: python
22
23 Base = declarative_base()
24 Base.query = db_session.query_property()
25
26 class MyModel(Base):
27 # ...
28
29 If you don't specify any, the following error will be displayed:
30
31 ``A query in the model Base or a session in the schema is required for querying.``
0 SQLAlchemy + Flask Tutorial
1 ===========================
2
3 Graphene comes with builtin support to SQLAlchemy, which makes quite
4 easy to operate with your current models.
5
6 Note: The code in this tutorial is pulled from the `Flask SQLAlchemy
7 example
8 app <https://github.com/graphql-python/graphene-sqlalchemy/tree/master/examples/flask_sqlalchemy>`__.
9
10 Setup the Project
11 -----------------
12
13 We will setup the project, execute the following:
14
15 .. code:: bash
16
17 # Create the project directory
18 mkdir flask_sqlalchemy
19 cd flask_sqlalchemy
20
21 # Create a virtualenv to isolate our package dependencies locally
22 virtualenv env
23 source env/bin/activate # On Windows use `env\Scripts\activate`
24
25 # SQLAlchemy and Graphene with SQLAlchemy support
26 pip install SQLAlchemy
27 pip install graphene_sqlalchemy
28
29 # Install Flask and GraphQL Flask for exposing the schema through HTTP
30 pip install Flask
31 pip install Flask-GraphQL
32
33 Defining our models
34 -------------------
35
36 Let's get started with these models:
37
38 .. code:: python
39
40 # flask_sqlalchemy/models.py
41 from sqlalchemy import *
42 from sqlalchemy.orm import (scoped_session, sessionmaker, relationship,
43 backref)
44 from sqlalchemy.ext.declarative import declarative_base
45
46 engine = create_engine('sqlite:///database.sqlite3', convert_unicode=True)
47 db_session = scoped_session(sessionmaker(autocommit=False,
48 autoflush=False,
49 bind=engine))
50
51 Base = declarative_base()
52 # We will need this for querying
53 Base.query = db_session.query_property()
54
55
56 class Department(Base):
57 __tablename__ = 'department'
58 id = Column(Integer, primary_key=True)
59 name = Column(String)
60
61
62 class Employee(Base):
63 __tablename__ = 'employee'
64 id = Column(Integer, primary_key=True)
65 name = Column(String)
66 hired_on = Column(DateTime, default=func.now())
67 department_id = Column(Integer, ForeignKey('department.id'))
68 department = relationship(
69 Department,
70 backref=backref('employees',
71 uselist=True,
72 cascade='delete,all'))
73
74 Schema
75 ------
76
77 GraphQL presents your objects to the world as a graph structure rather
78 than a more hierarchical structure to which you may be accustomed. In
79 order to create this representation, Graphene needs to know about each
80 *type* of object which will appear in the graph.
81
82 This graph also has a *root type* through which all access begins. This
83 is the ``Query`` class below. In this example, we provide the ability to
84 list all employees via ``all_employees``, and the ability to obtain a
85 specific node via ``node``.
86
87 Create ``flask_sqlalchemy/schema.py`` and type the following:
88
89 .. code:: python
90
91 # flask_sqlalchemy/schema.py
92 import graphene
93 from graphene import relay
94 from graphene_sqlalchemy import SQLAlchemyObjectType, SQLAlchemyConnectionField
95 from models import db_session, Department as DepartmentModel, Employee as EmployeeModel
96
97 schema = graphene.Schema()
98
99
100 class Department(SQLAlchemyObjectType):
101 class Meta:
102 model = DepartmentModel
103 interfaces = (relay.Node, )
104
105
106 class Employee(SQLAlchemyObjectType):
107 class Meta:
108 model = EmployeeModel
109 interfaces = (relay.Node, )
110
111
112 class Query(graphene.ObjectType):
113 node = relay.Node.Field()
114 all_employees = SQLAlchemyConnectionField(Employee)
115
116 schema.query = Query
117
118 Creating GraphQL and GraphiQL views in Flask
119 --------------------------------------------
120
121 Unlike a RESTful API, there is only a single URL from which GraphQL is
122 accessed.
123
124 We are going to use Flask to create a server that expose the GraphQL
125 schema under ``/graphql`` and a interface for querying it easily:
126 GraphiQL (also under ``/graphql`` when accessed by a browser).
127
128 Fortunately for us, the library ``Flask-GraphQL`` that we previously
129 installed makes this task quite easy.
130
131 .. code:: python
132
133 # flask_sqlalchemy/app.py
134 from flask import Flask
135 from flask_graphql import GraphQLView
136
137 from models import db_session
138 from schema import schema, Department
139
140 app = Flask(__name__)
141 app.debug = True
142
143 app.add_url_rule(
144 '/graphql',
145 view_func=GraphQLView.as_view(
146 'graphql',
147 schema=schema,
148 graphiql=True # for having the GraphiQL interface
149 )
150 )
151
152 @app.teardown_appcontext
153 def shutdown_session(exception=None):
154 db_session.remove()
155
156 if __name__ == '__main__':
157 app.run()
158
159 Creating some data
160 ------------------
161
162 .. code:: bash
163
164 $ python
165
166 >>> from models import engine, db_session, Base, Department, Employee
167 >>> Base.metadata.create_all(bind=engine)
168
169 >>> # Fill the tables with some data
170 >>> engineering = Department(name='Engineering')
171 >>> db_session.add(engineering)
172 >>> hr = Department(name='Human Resources')
173 >>> db_session.add(hr)
174
175 >>> peter = Employee(name='Peter', department=engineering)
176 >>> db_session.add(peter)
177 >>> roy = Employee(name='Roy', department=engineering)
178 >>> db_session.add(roy)
179 >>> tracy = Employee(name='Tracy', department=hr)
180 >>> db_session.add(tracy)
181 >>> db_session.commit()
182
183 Testing our GraphQL schema
184 --------------------------
185
186 We're now ready to test the API we've built. Let's fire up the server
187 from the command line.
188
189 .. code:: bash
190
191 $ python ./app.py
192
193 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
194
195 Go to `localhost:5000/graphql <http://localhost:5000/graphql>`__ and
196 type your first query!
197
198 .. code::
199
200 {
201 allEmployees {
202 edges {
203 node {
204 id
205 name
206 department {
207 name
208 }
209 }
210 }
211 }
212 }
0 Example Flask+SQLAlchemy Project
1 ================================
2
3 This example project demos integration between Graphene, Flask and SQLAlchemy.
4 The project contains two models, one named `Department` and another
5 named `Employee`.
6
7 Getting started
8 ---------------
9
10 First you'll need to get the source of the project. Do this by cloning the
11 whole Graphene repository:
12
13 ```bash
14 # Get the example project code
15 git clone https://github.com/graphql-python/graphene-sqlalchemy.git
16 cd graphene-sqlalchemy/examples/flask_sqlalchemy
17 ```
18
19 It is good idea (but not required) to create a virtual environment
20 for this project. We'll do this using
21 [virtualenv](http://docs.python-guide.org/en/latest/dev/virtualenvs/)
22 to keep things simple,
23 but you may also find something like
24 [virtualenvwrapper](https://virtualenvwrapper.readthedocs.org/en/latest/)
25 to be useful:
26
27 ```bash
28 # Create a virtualenv in which we can install the dependencies
29 virtualenv env
30 source env/bin/activate
31 ```
32
33 Now we can install our dependencies:
34
35 ```bash
36 pip install -r requirements.txt
37 ```
38
39 Now the following command will setup the database, and start the server:
40
41 ```bash
42 ./app.py
43
44 ```
45
46
47 Now head on over to
48 [http://127.0.0.1:5000/graphql](http://127.0.0.1:5000/graphql)
49 and run some queries!
0 #!/usr/bin/env python
1
2 from flask import Flask
3
4 from database import db_session, init_db
5 from flask_graphql import GraphQLView
6 from schema import schema
7
8 app = Flask(__name__)
9 app.debug = True
10
11 default_query = '''
12 {
13 allEmployees {
14 edges {
15 node {
16 id,
17 name,
18 department {
19 id,
20 name
21 },
22 role {
23 id,
24 name
25 }
26 }
27 }
28 }
29 }'''.strip()
30
31
32 app.add_url_rule('/graphql', view_func=GraphQLView.as_view('graphql', schema=schema, graphiql=True))
33
34
35 @app.teardown_appcontext
36 def shutdown_session(exception=None):
37 db_session.remove()
38
39 if __name__ == '__main__':
40 init_db()
41 app.run()
0 from sqlalchemy import create_engine
1 from sqlalchemy.ext.declarative import declarative_base
2 from sqlalchemy.orm import scoped_session, sessionmaker
3
4 engine = create_engine('sqlite:///database.sqlite3', convert_unicode=True)
5 db_session = scoped_session(sessionmaker(autocommit=False,
6 autoflush=False,
7 bind=engine))
8 Base = declarative_base()
9 Base.query = db_session.query_property()
10
11
12 def init_db():
13 # import all modules here that might define models so that
14 # they will be registered properly on the metadata. Otherwise
15 # you will have to import them first before calling init_db()
16 from models import Department, Employee, Role
17 Base.metadata.drop_all(bind=engine)
18 Base.metadata.create_all(bind=engine)
19
20 # Create the fixtures
21 engineering = Department(name='Engineering')
22 db_session.add(engineering)
23 hr = Department(name='Human Resources')
24 db_session.add(hr)
25
26 manager = Role(name='manager')
27 db_session.add(manager)
28 engineer = Role(name='engineer')
29 db_session.add(engineer)
30
31 peter = Employee(name='Peter', department=engineering, role=engineer)
32 db_session.add(peter)
33 roy = Employee(name='Roy', department=engineering, role=engineer)
34 db_session.add(roy)
35 tracy = Employee(name='Tracy', department=hr, role=manager)
36 db_session.add(tracy)
37 db_session.commit()
0 from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, func
1 from sqlalchemy.orm import backref, relationship
2
3 from database import Base
4
5
6 class Department(Base):
7 __tablename__ = 'department'
8 id = Column(Integer, primary_key=True)
9 name = Column(String)
10
11
12 class Role(Base):
13 __tablename__ = 'roles'
14 role_id = Column(Integer, primary_key=True)
15 name = Column(String)
16
17
18 class Employee(Base):
19 __tablename__ = 'employee'
20 id = Column(Integer, primary_key=True)
21 name = Column(String)
22 # Use default=func.now() to set the default hiring time
23 # of an Employee to be the current time when an
24 # Employee record was created
25 hired_on = Column(DateTime, default=func.now())
26 department_id = Column(Integer, ForeignKey('department.id'))
27 role_id = Column(Integer, ForeignKey('roles.role_id'))
28 # Use cascade='delete,all' to propagate the deletion of a Department onto its Employees
29 department = relationship(
30 Department,
31 backref=backref('employees',
32 uselist=True,
33 cascade='delete,all'))
34 role = relationship(
35 Role,
36 backref=backref('roles',
37 uselist=True,
38 cascade='delete,all'))
0 graphene[sqlalchemy]
1 SQLAlchemy==1.0.11
2 Flask==0.10.1
3 Flask-GraphQL==1.3.0
0 import graphene
1 from graphene import relay
2 from graphene_sqlalchemy import SQLAlchemyConnectionField, SQLAlchemyObjectType
3 from models import Department as DepartmentModel
4 from models import Employee as EmployeeModel
5 from models import Role as RoleModel
6
7
8 class Department(SQLAlchemyObjectType):
9
10 class Meta:
11 model = DepartmentModel
12 interfaces = (relay.Node, )
13
14
15 class Employee(SQLAlchemyObjectType):
16
17 class Meta:
18 model = EmployeeModel
19 interfaces = (relay.Node, )
20
21
22 class Role(SQLAlchemyObjectType):
23
24 class Meta:
25 model = RoleModel
26 interfaces = (relay.Node, )
27
28
29 class Query(graphene.ObjectType):
30 node = relay.Node.Field()
31 all_employees = SQLAlchemyConnectionField(Employee)
32 all_roles = SQLAlchemyConnectionField(Role)
33 role = graphene.Field(Role)
34
35
36 schema = graphene.Schema(query=Query, types=[Department, Employee, Role])
0 from .types import (
1 SQLAlchemyObjectType,
2 )
3 from .fields import (
4 SQLAlchemyConnectionField
5 )
6 from .utils import (
7 get_query,
8 get_session
9 )
10
11 __all__ = ['SQLAlchemyObjectType',
12 'SQLAlchemyConnectionField',
13 'get_query',
14 'get_session']
0 from singledispatch import singledispatch
1 from sqlalchemy import types
2 from sqlalchemy.dialects import postgresql
3 from sqlalchemy.orm import interfaces
4
5 from graphene import (ID, Boolean, Dynamic, Enum, Field, Float, Int, List,
6 String)
7 from graphene.relay import is_node
8 from graphene.types.json import JSONString
9
10 from .fields import SQLAlchemyConnectionField
11
12 try:
13 from sqlalchemy_utils import ChoiceType, JSONType, ScalarListType
14 except ImportError:
15 class ChoiceType(object):
16 pass
17
18 class ScalarListType(object):
19 pass
20
21 class JSONType(object):
22 pass
23
24
25 def get_column_doc(column):
26 return getattr(column, 'doc', None)
27
28
29 def is_column_nullable(column):
30 return bool(getattr(column, 'nullable', False))
31
32
33 def convert_sqlalchemy_relationship(relationship, registry):
34 direction = relationship.direction
35 model = relationship.mapper.entity
36
37 def dynamic_type():
38 _type = registry.get_type_for_model(model)
39 if not _type:
40 return None
41 if (direction == interfaces.MANYTOONE or not relationship.uselist):
42 return Field(_type)
43 elif (direction == interfaces.ONETOMANY or
44 direction == interfaces.MANYTOMANY):
45 if is_node(_type):
46 return SQLAlchemyConnectionField(_type)
47 return Field(List(_type))
48
49 return Dynamic(dynamic_type)
50
51
52 def convert_sqlalchemy_composite(composite, registry):
53 converter = registry.get_converter_for_composite(composite.composite_class)
54 if not converter:
55 try:
56 raise Exception(
57 "Don't know how to convert the composite field %s (%s)" %
58 (composite, composite.composite_class))
59 except AttributeError:
60 # handle fields that are not attached to a class yet (don't have a parent)
61 raise Exception(
62 "Don't know how to convert the composite field %r (%s)" %
63 (composite, composite.composite_class))
64 return converter(composite, registry)
65
66
67 def _register_composite_class(cls, registry=None):
68 if registry is None:
69 from .registry import get_global_registry
70 registry = get_global_registry()
71
72 def inner(fn):
73 registry.register_composite_converter(cls, fn)
74 return inner
75
76
77 convert_sqlalchemy_composite.register = _register_composite_class
78
79
80 def convert_sqlalchemy_column(column, registry=None):
81 return convert_sqlalchemy_type(getattr(column, 'type', None), column, registry)
82
83
84 @singledispatch
85 def convert_sqlalchemy_type(type, column, registry=None):
86 raise Exception(
87 "Don't know how to convert the SQLAlchemy field %s (%s)" % (column, column.__class__))
88
89
90 @convert_sqlalchemy_type.register(types.Date)
91 @convert_sqlalchemy_type.register(types.Time)
92 @convert_sqlalchemy_type.register(types.String)
93 @convert_sqlalchemy_type.register(types.Text)
94 @convert_sqlalchemy_type.register(types.Unicode)
95 @convert_sqlalchemy_type.register(types.UnicodeText)
96 @convert_sqlalchemy_type.register(types.Enum)
97 @convert_sqlalchemy_type.register(postgresql.ENUM)
98 @convert_sqlalchemy_type.register(postgresql.UUID)
99 def convert_column_to_string(type, column, registry=None):
100 return String(description=get_column_doc(column),
101 required=not(is_column_nullable(column)))
102
103
104 @convert_sqlalchemy_type.register(types.DateTime)
105 def convert_column_to_datetime(type, column, registry=None):
106 from graphene.types.datetime import DateTime
107 return DateTime(description=get_column_doc(column),
108 required=not(is_column_nullable(column)))
109
110
111 @convert_sqlalchemy_type.register(types.SmallInteger)
112 @convert_sqlalchemy_type.register(types.Integer)
113 def convert_column_to_int_or_id(type, column, registry=None):
114 if column.primary_key:
115 return ID(description=get_column_doc(column), required=not (is_column_nullable(column)))
116 else:
117 return Int(description=get_column_doc(column),
118 required=not (is_column_nullable(column)))
119
120
121 @convert_sqlalchemy_type.register(types.Boolean)
122 def convert_column_to_boolean(type, column, registry=None):
123 return Boolean(description=get_column_doc(column), required=not(is_column_nullable(column)))
124
125
126 @convert_sqlalchemy_type.register(types.Float)
127 @convert_sqlalchemy_type.register(types.Numeric)
128 @convert_sqlalchemy_type.register(types.BigInteger)
129 def convert_column_to_float(type, column, registry=None):
130 return Float(description=get_column_doc(column), required=not(is_column_nullable(column)))
131
132
133 @convert_sqlalchemy_type.register(ChoiceType)
134 def convert_column_to_enum(type, column, registry=None):
135 name = '{}_{}'.format(column.table.name, column.name).upper()
136 return Enum(name, type.choices, description=get_column_doc(column))
137
138
139 @convert_sqlalchemy_type.register(ScalarListType)
140 def convert_scalar_list_to_list(type, column, registry=None):
141 return List(String, description=get_column_doc(column))
142
143
144 @convert_sqlalchemy_type.register(postgresql.ARRAY)
145 def convert_postgres_array_to_list(_type, column, registry=None):
146 graphene_type = convert_sqlalchemy_type(column.type.item_type, column)
147 inner_type = type(graphene_type)
148 return List(inner_type, description=get_column_doc(column), required=not(is_column_nullable(column)))
149
150
151 @convert_sqlalchemy_type.register(postgresql.HSTORE)
152 @convert_sqlalchemy_type.register(postgresql.JSON)
153 @convert_sqlalchemy_type.register(postgresql.JSONB)
154 def convert_json_to_string(type, column, registry=None):
155 return JSONString(description=get_column_doc(column), required=not(is_column_nullable(column)))
156
157
158 @convert_sqlalchemy_type.register(JSONType)
159 def convert_json_type_to_string(type, column, registry=None):
160 return JSONString(description=get_column_doc(column), required=not(is_column_nullable(column)))
0 from functools import partial
1
2 from sqlalchemy.orm.query import Query
3
4 from graphene.relay import ConnectionField
5 from graphene.relay.connection import PageInfo
6 from graphql_relay.connection.arrayconnection import connection_from_list_slice
7
8 from .utils import get_query
9
10
11 class SQLAlchemyConnectionField(ConnectionField):
12
13 @property
14 def model(self):
15 return self.type._meta.node._meta.model
16
17 @classmethod
18 def get_query(cls, model, context, info, args):
19 return get_query(model, context)
20
21 @classmethod
22 def connection_resolver(cls, resolver, connection, model, root, args, context, info):
23 iterable = resolver(root, args, context, info)
24 if iterable is None:
25 iterable = cls.get_query(model, context, info, args)
26 if isinstance(iterable, Query):
27 _len = iterable.count()
28 else:
29 _len = len(iterable)
30 return connection_from_list_slice(
31 iterable,
32 args,
33 slice_start=0,
34 list_length=_len,
35 list_slice_length=_len,
36 connection_type=connection,
37 pageinfo_type=PageInfo,
38 edge_type=connection.Edge,
39 )
40
41 def get_resolver(self, parent_resolver):
42 return partial(self.connection_resolver, parent_resolver, self.type, self.model)
0 class Registry(object):
1
2 def __init__(self):
3 self._registry = {}
4 self._registry_models = {}
5 self._registry_composites = {}
6
7 def register(self, cls):
8 from .types import SQLAlchemyObjectType
9 assert issubclass(
10 cls, SQLAlchemyObjectType), 'Only SQLAlchemyObjectType can be registered, received "{}"'.format(
11 cls.__name__)
12 assert cls._meta.registry == self, 'Registry for a Model have to match.'
13 # assert self.get_type_for_model(cls._meta.model) in [None, cls], (
14 # 'SQLAlchemy model "{}" already associated with '
15 # 'another type "{}".'
16 # ).format(cls._meta.model, self._registry[cls._meta.model])
17 self._registry[cls._meta.model] = cls
18
19 def get_type_for_model(self, model):
20 return self._registry.get(model)
21
22 def register_composite_converter(self, composite, converter):
23 self._registry_composites[composite] = converter
24
25 def get_converter_for_composite(self, composite):
26 return self._registry_composites.get(composite)
27
28
29 registry = None
30
31
32 def get_global_registry():
33 global registry
34 if not registry:
35 registry = Registry()
36 return registry
37
38
39 def reset_global_registry():
40 global registry
41 registry = None
0 from __future__ import absolute_import
1
2 from sqlalchemy import Column, Date, ForeignKey, Integer, String, Table
3 from sqlalchemy.ext.declarative import declarative_base
4 from sqlalchemy.orm import relationship
5
6 Base = declarative_base()
7
8 association_table = Table('association', Base.metadata,
9 Column('pet_id', Integer, ForeignKey('pets.id')),
10 Column('reporter_id', Integer, ForeignKey('reporters.id')))
11
12
13 class Editor(Base):
14 __tablename__ = 'editors'
15 editor_id = Column(Integer(), primary_key=True)
16 name = Column(String(100))
17
18
19 class Pet(Base):
20 __tablename__ = 'pets'
21 id = Column(Integer(), primary_key=True)
22 name = Column(String(30))
23 reporter_id = Column(Integer(), ForeignKey('reporters.id'))
24
25
26 class Reporter(Base):
27 __tablename__ = 'reporters'
28 id = Column(Integer(), primary_key=True)
29 first_name = Column(String(30))
30 last_name = Column(String(30))
31 email = Column(String())
32 pets = relationship('Pet', secondary=association_table, backref='reporters')
33 articles = relationship('Article', backref='reporter')
34 favorite_article = relationship("Article", uselist=False)
35
36
37 class Article(Base):
38 __tablename__ = 'articles'
39 id = Column(Integer(), primary_key=True)
40 headline = Column(String(100))
41 pub_date = Column(Date())
42 reporter_id = Column(Integer(), ForeignKey('reporters.id'))
0 from py.test import raises
1 from sqlalchemy import Column, Table, case, types
2 from sqlalchemy.dialects import postgresql
3 from sqlalchemy.ext.declarative import declarative_base
4 from sqlalchemy.orm import composite
5 from sqlalchemy.sql.elements import Label
6 from sqlalchemy_utils import ChoiceType, JSONType, ScalarListType
7
8 import graphene
9 from graphene.relay import Node
10 from graphene.types.datetime import DateTime
11 from graphene.types.json import JSONString
12
13 from ..converter import (convert_sqlalchemy_column,
14 convert_sqlalchemy_composite,
15 convert_sqlalchemy_relationship)
16 from ..fields import SQLAlchemyConnectionField
17 from ..registry import Registry
18 from ..types import SQLAlchemyObjectType
19 from .models import Article, Pet, Reporter
20
21
22 def assert_column_conversion(sqlalchemy_type, graphene_field, **kwargs):
23 column = Column(sqlalchemy_type, doc='Custom Help Text', **kwargs)
24 graphene_type = convert_sqlalchemy_column(column)
25 assert isinstance(graphene_type, graphene_field)
26 field = graphene_type.Field()
27 assert field.description == 'Custom Help Text'
28 return field
29
30
31 def assert_composite_conversion(composite_class, composite_columns, graphene_field,
32 registry, **kwargs):
33 composite_column = composite(composite_class, *composite_columns,
34 doc='Custom Help Text', **kwargs)
35 graphene_type = convert_sqlalchemy_composite(composite_column, registry)
36 assert isinstance(graphene_type, graphene_field)
37 field = graphene_type.Field()
38 # SQLAlchemy currently does not persist the doc onto the column, even though
39 # the documentation says it does....
40 # assert field.description == 'Custom Help Text'
41 return field
42
43
44 def test_should_unknown_sqlalchemy_field_raise_exception():
45 with raises(Exception) as excinfo:
46 convert_sqlalchemy_column(None)
47 assert 'Don\'t know how to convert the SQLAlchemy field' in str(excinfo.value)
48
49
50 def test_should_date_convert_string():
51 assert_column_conversion(types.Date(), graphene.String)
52
53
54 def test_should_datetime_convert_string():
55 assert_column_conversion(types.DateTime(), DateTime)
56
57
58 def test_should_time_convert_string():
59 assert_column_conversion(types.Time(), graphene.String)
60
61
62 def test_should_string_convert_string():
63 assert_column_conversion(types.String(), graphene.String)
64
65
66 def test_should_text_convert_string():
67 assert_column_conversion(types.Text(), graphene.String)
68
69
70 def test_should_unicode_convert_string():
71 assert_column_conversion(types.Unicode(), graphene.String)
72
73
74 def test_should_unicodetext_convert_string():
75 assert_column_conversion(types.UnicodeText(), graphene.String)
76
77
78 def test_should_enum_convert_string():
79 assert_column_conversion(types.Enum(), graphene.String)
80
81
82 def test_should_small_integer_convert_int():
83 assert_column_conversion(types.SmallInteger(), graphene.Int)
84
85
86 def test_should_big_integer_convert_int():
87 assert_column_conversion(types.BigInteger(), graphene.Float)
88
89
90 def test_should_integer_convert_int():
91 assert_column_conversion(types.Integer(), graphene.Int)
92
93
94 def test_should_integer_convert_id():
95 assert_column_conversion(types.Integer(), graphene.ID, primary_key=True)
96
97
98 def test_should_boolean_convert_boolean():
99 assert_column_conversion(types.Boolean(), graphene.Boolean)
100
101
102 def test_should_float_convert_float():
103 assert_column_conversion(types.Float(), graphene.Float)
104
105
106 def test_should_numeric_convert_float():
107 assert_column_conversion(types.Numeric(), graphene.Float)
108
109
110 def test_should_label_convert_string():
111 label = Label('label_test', case([], else_="foo"), type_=types.Unicode())
112 graphene_type = convert_sqlalchemy_column(label)
113 assert isinstance(graphene_type, graphene.String)
114
115
116 def test_should_label_convert_int():
117 label = Label('int_label_test', case([], else_="foo"), type_=types.Integer())
118 graphene_type = convert_sqlalchemy_column(label)
119 assert isinstance(graphene_type, graphene.Int)
120
121 def test_should_choice_convert_enum():
122 TYPES = [
123 (u'es', u'Spanish'),
124 (u'en', u'English')
125 ]
126 column = Column(ChoiceType(TYPES), doc='Language', name='language')
127 Base = declarative_base()
128
129 Table('translatedmodel', Base.metadata, column)
130 graphene_type = convert_sqlalchemy_column(column)
131 assert issubclass(graphene_type, graphene.Enum)
132 assert graphene_type._meta.name == 'TRANSLATEDMODEL_LANGUAGE'
133 assert graphene_type._meta.description == 'Language'
134 assert graphene_type._meta.enum.__members__['es'].value == 'Spanish'
135 assert graphene_type._meta.enum.__members__['en'].value == 'English'
136
137
138 def test_should_scalar_list_convert_list():
139 assert_column_conversion(ScalarListType(), graphene.List)
140
141
142 def test_should_jsontype_convert_jsonstring():
143 assert_column_conversion(JSONType(), JSONString)
144
145
146 def test_should_manytomany_convert_connectionorlist():
147 registry = Registry()
148 dynamic_field = convert_sqlalchemy_relationship(Reporter.pets.property, registry)
149 assert isinstance(dynamic_field, graphene.Dynamic)
150 assert not dynamic_field.get_type()
151
152
153 def test_should_manytomany_convert_connectionorlist_list():
154 class A(SQLAlchemyObjectType):
155
156 class Meta:
157 model = Pet
158
159 dynamic_field = convert_sqlalchemy_relationship(Reporter.pets.property, A._meta.registry)
160 assert isinstance(dynamic_field, graphene.Dynamic)
161 graphene_type = dynamic_field.get_type()
162 assert isinstance(graphene_type, graphene.Field)
163 assert isinstance(graphene_type.type, graphene.List)
164 assert graphene_type.type.of_type == A
165
166
167 def test_should_manytomany_convert_connectionorlist_connection():
168 class A(SQLAlchemyObjectType):
169
170 class Meta:
171 model = Pet
172 interfaces = (Node, )
173
174 dynamic_field = convert_sqlalchemy_relationship(Reporter.pets.property, A._meta.registry)
175 assert isinstance(dynamic_field, graphene.Dynamic)
176 assert isinstance(dynamic_field.get_type(), SQLAlchemyConnectionField)
177
178
179 def test_should_manytoone_convert_connectionorlist():
180 registry = Registry()
181 dynamic_field = convert_sqlalchemy_relationship(Article.reporter.property, registry)
182 assert isinstance(dynamic_field, graphene.Dynamic)
183 assert not dynamic_field.get_type()
184
185
186 def test_should_manytoone_convert_connectionorlist_list():
187 class A(SQLAlchemyObjectType):
188
189 class Meta:
190 model = Reporter
191
192 dynamic_field = convert_sqlalchemy_relationship(Article.reporter.property, A._meta.registry)
193 assert isinstance(dynamic_field, graphene.Dynamic)
194 graphene_type = dynamic_field.get_type()
195 assert isinstance(graphene_type, graphene.Field)
196 assert graphene_type.type == A
197
198
199 def test_should_manytoone_convert_connectionorlist_connection():
200 class A(SQLAlchemyObjectType):
201
202 class Meta:
203 model = Reporter
204 interfaces = (Node, )
205
206 dynamic_field = convert_sqlalchemy_relationship(Article.reporter.property, A._meta.registry)
207 assert isinstance(dynamic_field, graphene.Dynamic)
208 graphene_type = dynamic_field.get_type()
209 assert isinstance(graphene_type, graphene.Field)
210 assert graphene_type.type == A
211
212
213 def test_should_onetoone_convert_field():
214 class A(SQLAlchemyObjectType):
215
216 class Meta:
217 model = Article
218 interfaces = (Node, )
219
220 dynamic_field = convert_sqlalchemy_relationship(Reporter.favorite_article.property, A._meta.registry)
221 assert isinstance(dynamic_field, graphene.Dynamic)
222 graphene_type = dynamic_field.get_type()
223 assert isinstance(graphene_type, graphene.Field)
224 assert graphene_type.type == A
225
226
227 def test_should_postgresql_uuid_convert():
228 assert_column_conversion(postgresql.UUID(), graphene.String)
229
230
231 def test_should_postgresql_enum_convert():
232 assert_column_conversion(postgresql.ENUM(), graphene.String)
233
234
235 def test_should_postgresql_array_convert():
236 assert_column_conversion(postgresql.ARRAY(types.Integer), graphene.List)
237
238
239 def test_should_postgresql_json_convert():
240 assert_column_conversion(postgresql.JSON(), JSONString)
241
242
243 def test_should_postgresql_jsonb_convert():
244 assert_column_conversion(postgresql.JSONB(), JSONString)
245
246
247 def test_should_postgresql_hstore_convert():
248 assert_column_conversion(postgresql.HSTORE(), JSONString)
249
250
251 def test_should_composite_convert():
252
253 class CompositeClass(object):
254
255 def __init__(self, col1, col2):
256 self.col1 = col1
257 self.col2 = col2
258
259 registry = Registry()
260
261 @convert_sqlalchemy_composite.register(CompositeClass, registry)
262 def convert_composite_class(composite, registry):
263 return graphene.String(description=composite.doc)
264
265 assert_composite_conversion(CompositeClass,
266 (Column(types.Unicode(50)),
267 Column(types.Unicode(50))),
268 graphene.String,
269 registry)
270
271
272 def test_should_unknown_sqlalchemy_composite_raise_exception():
273 registry = Registry()
274
275 with raises(Exception) as excinfo:
276
277 class CompositeClass(object):
278
279 def __init__(self, col1, col2):
280 self.col1 = col1
281 self.col2 = col2
282
283 assert_composite_conversion(CompositeClass,
284 (Column(types.Unicode(50)),
285 Column(types.Unicode(50))),
286 graphene.String,
287 registry)
288
289 assert 'Don\'t know how to convert the composite field' in str(excinfo.value)
0 import pytest
1 from sqlalchemy import create_engine
2 from sqlalchemy.orm import scoped_session, sessionmaker
3
4 import graphene
5 from graphene.relay import Node
6
7 from ..registry import reset_global_registry
8 from ..fields import SQLAlchemyConnectionField
9 from ..types import SQLAlchemyObjectType
10 from .models import Article, Base, Editor, Reporter
11
12 db = create_engine('sqlite:///test_sqlalchemy.sqlite3')
13
14
15 @pytest.yield_fixture(scope='function')
16 def session():
17 reset_global_registry()
18 connection = db.engine.connect()
19 transaction = connection.begin()
20 Base.metadata.create_all(connection)
21
22 # options = dict(bind=connection, binds={})
23 session_factory = sessionmaker(bind=connection)
24 session = scoped_session(session_factory)
25
26 yield session
27
28 # Finalize test here
29 transaction.rollback()
30 connection.close()
31 session.remove()
32
33
34 def setup_fixtures(session):
35 reporter = Reporter(first_name='ABA', last_name='X')
36 session.add(reporter)
37 reporter2 = Reporter(first_name='ABO', last_name='Y')
38 session.add(reporter2)
39 article = Article(headline='Hi!')
40 article.reporter = reporter
41 session.add(article)
42 editor = Editor(name="John")
43 session.add(editor)
44 session.commit()
45
46
47 def test_should_query_well(session):
48 setup_fixtures(session)
49
50 class ReporterType(SQLAlchemyObjectType):
51
52 class Meta:
53 model = Reporter
54
55 class Query(graphene.ObjectType):
56 reporter = graphene.Field(ReporterType)
57 reporters = graphene.List(ReporterType)
58
59 def resolve_reporter(self, *args, **kwargs):
60 return session.query(Reporter).first()
61
62 def resolve_reporters(self, *args, **kwargs):
63 return session.query(Reporter)
64
65 query = '''
66 query ReporterQuery {
67 reporter {
68 firstName,
69 lastName,
70 email
71 }
72 reporters {
73 firstName
74 }
75 }
76 '''
77 expected = {
78 'reporter': {
79 'firstName': 'ABA',
80 'lastName': 'X',
81 'email': None
82 },
83 'reporters': [{
84 'firstName': 'ABA',
85 }, {
86 'firstName': 'ABO',
87 }]
88 }
89 schema = graphene.Schema(query=Query)
90 result = schema.execute(query)
91 assert not result.errors
92 assert result.data == expected
93
94
95 def test_should_node(session):
96 setup_fixtures(session)
97
98 class ReporterNode(SQLAlchemyObjectType):
99
100 class Meta:
101 model = Reporter
102 interfaces = (Node, )
103
104 @classmethod
105 def get_node(cls, id, info):
106 return Reporter(id=2, first_name='Cookie Monster')
107
108 class ArticleNode(SQLAlchemyObjectType):
109
110 class Meta:
111 model = Article
112 interfaces = (Node, )
113
114 # @classmethod
115 # def get_node(cls, id, info):
116 # return Article(id=1, headline='Article node')
117
118 class Query(graphene.ObjectType):
119 node = Node.Field()
120 reporter = graphene.Field(ReporterNode)
121 article = graphene.Field(ArticleNode)
122 all_articles = SQLAlchemyConnectionField(ArticleNode)
123
124 def resolve_reporter(self, *args, **kwargs):
125 return session.query(Reporter).first()
126
127 def resolve_article(self, *args, **kwargs):
128 return session.query(Article).first()
129
130 query = '''
131 query ReporterQuery {
132 reporter {
133 id,
134 firstName,
135 articles {
136 edges {
137 node {
138 headline
139 }
140 }
141 }
142 lastName,
143 email
144 }
145 allArticles {
146 edges {
147 node {
148 headline
149 }
150 }
151 }
152 myArticle: node(id:"QXJ0aWNsZU5vZGU6MQ==") {
153 id
154 ... on ReporterNode {
155 firstName
156 }
157 ... on ArticleNode {
158 headline
159 }
160 }
161 }
162 '''
163 expected = {
164 'reporter': {
165 'id': 'UmVwb3J0ZXJOb2RlOjE=',
166 'firstName': 'ABA',
167 'lastName': 'X',
168 'email': None,
169 'articles': {
170 'edges': [{
171 'node': {
172 'headline': 'Hi!'
173 }
174 }]
175 },
176 },
177 'allArticles': {
178 'edges': [{
179 'node': {
180 'headline': 'Hi!'
181 }
182 }]
183 },
184 'myArticle': {
185 'id': 'QXJ0aWNsZU5vZGU6MQ==',
186 'headline': 'Hi!'
187 }
188 }
189 schema = graphene.Schema(query=Query)
190 result = schema.execute(query, context_value={'session': session})
191 assert not result.errors
192 assert result.data == expected
193
194
195 def test_should_custom_identifier(session):
196 setup_fixtures(session)
197
198 class EditorNode(SQLAlchemyObjectType):
199
200 class Meta:
201 model = Editor
202 interfaces = (Node, )
203
204 class Query(graphene.ObjectType):
205 node = Node.Field()
206 all_editors = SQLAlchemyConnectionField(EditorNode)
207
208 query = '''
209 query EditorQuery {
210 allEditors {
211 edges {
212 node {
213 id,
214 name
215 }
216 }
217 },
218 node(id: "RWRpdG9yTm9kZTox") {
219 ...on EditorNode {
220 name
221 }
222 }
223 }
224 '''
225 expected = {
226 'allEditors': {
227 'edges': [{
228 'node': {
229 'id': 'RWRpdG9yTm9kZTox',
230 'name': 'John'
231 }
232 }]
233 },
234 'node': {
235 'name': 'John'
236 }
237 }
238
239 schema = graphene.Schema(query=Query)
240 result = schema.execute(query, context_value={'session': session})
241 assert not result.errors
242 assert result.data == expected
243
244
245 def test_should_mutate_well(session):
246 setup_fixtures(session)
247
248 class EditorNode(SQLAlchemyObjectType):
249
250 class Meta:
251 model = Editor
252 interfaces = (Node, )
253
254 class ReporterNode(SQLAlchemyObjectType):
255
256 class Meta:
257 model = Reporter
258 interfaces = (Node, )
259
260 @classmethod
261 def get_node(cls, id, info):
262 return Reporter(id=2, first_name='Cookie Monster')
263
264 class ArticleNode(SQLAlchemyObjectType):
265
266 class Meta:
267 model = Article
268 interfaces = (Node, )
269
270 class CreateArticle(graphene.Mutation):
271
272 class Input:
273 headline = graphene.String()
274 reporter_id = graphene.ID()
275
276 ok = graphene.Boolean()
277 article = graphene.Field(ArticleNode)
278
279 @classmethod
280 def mutate(cls, instance, args, context, info):
281 new_article = Article(
282 headline=args.get('headline'),
283 reporter_id=args.get('reporter_id'),
284 )
285
286 session.add(new_article)
287 session.commit()
288 ok = True
289
290 return CreateArticle(article=new_article, ok=ok)
291
292 class Query(graphene.ObjectType):
293 node = Node.Field()
294
295 class Mutation(graphene.ObjectType):
296 create_article = CreateArticle.Field()
297
298 query = '''
299 mutation ArticleCreator {
300 createArticle(
301 headline: "My Article"
302 reporterId: "1"
303 ) {
304 ok
305 article {
306 headline
307 reporter {
308 id
309 firstName
310 }
311 }
312 }
313 }
314 '''
315 expected = {
316 'createArticle': {
317 'ok': True,
318 'article': {
319 'headline': 'My Article',
320 'reporter': {
321 'id': 'UmVwb3J0ZXJOb2RlOjE=',
322 'firstName': 'ABA'
323 }
324 }
325 },
326 }
327
328 schema = graphene.Schema(query=Query, mutation=Mutation)
329 result = schema.execute(query, context_value={'session': session})
330 assert not result.errors
331 assert result.data == expected
0 from py.test import raises
1
2 from ..registry import Registry
3 from ..types import SQLAlchemyObjectType
4 from .models import Reporter
5
6
7 def test_should_raise_if_no_model():
8 with raises(Exception) as excinfo:
9 class Character1(SQLAlchemyObjectType):
10 pass
11 assert 'valid SQLAlchemy Model' in str(excinfo.value)
12
13
14 def test_should_raise_if_model_is_invalid():
15 with raises(Exception) as excinfo:
16 class Character2(SQLAlchemyObjectType):
17
18 class Meta:
19 model = 1
20 assert 'valid SQLAlchemy Model' in str(excinfo.value)
21
22
23 def test_should_map_fields_correctly():
24 class ReporterType2(SQLAlchemyObjectType):
25
26 class Meta:
27 model = Reporter
28 registry = Registry()
29
30 assert list(
31 ReporterType2._meta.fields.keys()) == [
32 'id',
33 'first_name',
34 'last_name',
35 'email',
36 'pets',
37 'articles',
38 'favorite_article']
39
40
41 def test_should_map_only_few_fields():
42 class Reporter2(SQLAlchemyObjectType):
43
44 class Meta:
45 model = Reporter
46 only_fields = ('id', 'email')
47 assert list(Reporter2._meta.fields.keys()) == ['id', 'email']
0
1 from graphene import Field, Int, Interface, ObjectType
2 from graphene.relay import Node, is_node
3
4 from ..registry import Registry
5 from ..types import SQLAlchemyObjectType
6 from .models import Article, Reporter
7
8 registry = Registry()
9
10
11 class Character(SQLAlchemyObjectType):
12 '''Character description'''
13 class Meta:
14 model = Reporter
15 registry = registry
16
17
18 class Human(SQLAlchemyObjectType):
19 '''Human description'''
20
21 pub_date = Int()
22
23 class Meta:
24 model = Article
25 exclude_fields = ('id', )
26 registry = registry
27 interfaces = (Node, )
28
29
30 def test_sqlalchemy_interface():
31 assert issubclass(Node, Interface)
32 assert issubclass(Node, Node)
33
34
35 # @patch('graphene.contrib.sqlalchemy.tests.models.Article.filter', return_value=Article(id=1))
36 # def test_sqlalchemy_get_node(get):
37 # human = Human.get_node(1, None)
38 # get.assert_called_with(id=1)
39 # assert human.id == 1
40
41
42 def test_objecttype_registered():
43 assert issubclass(Character, ObjectType)
44 assert Character._meta.model == Reporter
45 assert list(
46 Character._meta.fields.keys()) == [
47 'id',
48 'first_name',
49 'last_name',
50 'email',
51 'pets',
52 'articles',
53 'favorite_article']
54
55
56 # def test_sqlalchemynode_idfield():
57 # idfield = Node._meta.fields_map['id']
58 # assert isinstance(idfield, GlobalIDField)
59
60
61 # def test_node_idfield():
62 # idfield = Human._meta.fields_map['id']
63 # assert isinstance(idfield, GlobalIDField)
64
65
66 def test_node_replacedfield():
67 idfield = Human._meta.fields['pub_date']
68 assert isinstance(idfield, Field)
69 assert idfield.type == Int
70
71
72 def test_object_type():
73 assert issubclass(Human, ObjectType)
74 assert list(Human._meta.fields.keys()) == ['id', 'headline', 'reporter_id', 'reporter', 'pub_date']
75 assert is_node(Human)
0 from graphene import ObjectType, Schema, String
1
2 from ..utils import get_session
3
4
5 def test_get_session():
6 session = 'My SQLAlchemy session'
7
8 class Query(ObjectType):
9 x = String()
10
11 def resolve_x(self, args, context, info):
12 return get_session(context)
13
14 query = '''
15 query ReporterQuery {
16 x
17 }
18 '''
19
20 schema = Schema(query=Query)
21 result = schema.execute(query, context_value={'session': session})
22 assert not result.errors
23 assert result.data['x'] == session
0 from collections import OrderedDict
1
2 import six
3 from sqlalchemy.inspection import inspect as sqlalchemyinspect
4 from sqlalchemy.orm.exc import NoResultFound
5
6 from graphene import Field, ObjectType
7 from graphene.relay import is_node
8 from graphene.types.objecttype import ObjectTypeMeta
9 from graphene.types.options import Options
10 from graphene.types.utils import merge, yank_fields_from_attrs
11 from graphene.utils.is_base_type import is_base_type
12
13 from .converter import (convert_sqlalchemy_column,
14 convert_sqlalchemy_composite,
15 convert_sqlalchemy_relationship)
16 from .registry import Registry, get_global_registry
17 from .utils import get_query, is_mapped
18
19
20 def construct_fields(options):
21 only_fields = options.only_fields
22 exclude_fields = options.exclude_fields
23 inspected_model = sqlalchemyinspect(options.model)
24
25 fields = OrderedDict()
26
27 for name, column in inspected_model.columns.items():
28 is_not_in_only = only_fields and name not in only_fields
29 is_already_created = name in options.fields
30 is_excluded = name in exclude_fields or is_already_created
31 if is_not_in_only or is_excluded:
32 # We skip this field if we specify only_fields and is not
33 # in there. Or when we excldue this field in exclude_fields
34 continue
35 converted_column = convert_sqlalchemy_column(column, options.registry)
36 fields[name] = converted_column
37
38 for name, composite in inspected_model.composites.items():
39 is_not_in_only = only_fields and name not in only_fields
40 is_already_created = name in options.fields
41 is_excluded = name in exclude_fields or is_already_created
42 if is_not_in_only or is_excluded:
43 # We skip this field if we specify only_fields and is not
44 # in there. Or when we excldue this field in exclude_fields
45 continue
46 converted_composite = convert_sqlalchemy_composite(composite, options.registry)
47 fields[name] = converted_composite
48
49 # Get all the columns for the relationships on the model
50 for relationship in inspected_model.relationships:
51 is_not_in_only = only_fields and relationship.key not in only_fields
52 is_already_created = relationship.key in options.fields
53 is_excluded = relationship.key in exclude_fields or is_already_created
54 if is_not_in_only or is_excluded:
55 # We skip this field if we specify only_fields and is not
56 # in there. Or when we excldue this field in exclude_fields
57 continue
58 converted_relationship = convert_sqlalchemy_relationship(relationship, options.registry)
59 name = relationship.key
60 fields[name] = converted_relationship
61
62 return fields
63
64
65 class SQLAlchemyObjectTypeMeta(ObjectTypeMeta):
66
67 @staticmethod
68 def __new__(cls, name, bases, attrs):
69 # Also ensure initialization is only performed for subclasses of Model
70 # (excluding Model class itself).
71 if not is_base_type(bases, SQLAlchemyObjectTypeMeta):
72 return type.__new__(cls, name, bases, attrs)
73
74 options = Options(
75 attrs.pop('Meta', None),
76 name=name,
77 description=attrs.pop('__doc__', None),
78 model=None,
79 local_fields=None,
80 only_fields=(),
81 exclude_fields=(),
82 id='id',
83 interfaces=(),
84 registry=None
85 )
86
87 if not options.registry:
88 options.registry = get_global_registry()
89 assert isinstance(options.registry, Registry), (
90 'The attribute registry in {}.Meta needs to be an'
91 ' instance of Registry, received "{}".'
92 ).format(name, options.registry)
93 assert is_mapped(options.model), (
94 'You need to pass a valid SQLAlchemy Model in '
95 '{}.Meta, received "{}".'
96 ).format(name, options.model)
97
98 cls = ObjectTypeMeta.__new__(cls, name, bases, dict(attrs, _meta=options))
99
100 options.registry.register(cls)
101
102 options.sqlalchemy_fields = yank_fields_from_attrs(
103 construct_fields(options),
104 _as=Field,
105 )
106 options.fields = merge(
107 options.interface_fields,
108 options.sqlalchemy_fields,
109 options.base_fields,
110 options.local_fields
111 )
112
113 return cls
114
115
116 class SQLAlchemyObjectType(six.with_metaclass(SQLAlchemyObjectTypeMeta, ObjectType)):
117
118 @classmethod
119 def is_type_of(cls, root, context, info):
120 if isinstance(root, cls):
121 return True
122 if not is_mapped(type(root)):
123 raise Exception((
124 'Received incompatible instance "{}".'
125 ).format(root))
126 return isinstance(root, cls._meta.model)
127
128 @classmethod
129 def get_query(cls, context):
130 model = cls._meta.model
131 return get_query(model, context)
132
133 @classmethod
134 def get_node(cls, id, context, info):
135 try:
136 return cls.get_query(context).get(id)
137 except NoResultFound:
138 return None
139
140 def resolve_id(self, args, context, info):
141 graphene_type = info.parent_type.graphene_type
142 if is_node(graphene_type):
143 return self.__mapper__.primary_key_from_instance(self)[0]
144 return getattr(self, graphene_type._meta.id, None)
0 from sqlalchemy.ext.declarative.api import DeclarativeMeta
1
2
3 def get_session(context):
4 return context.get('session')
5
6
7 def get_query(model, context):
8 query = getattr(model, 'query', None)
9 if not query:
10 session = get_session(context)
11 if not session:
12 raise Exception('A query in the model Base or a session in the schema is required for querying.\n'
13 'Read more http://graphene-python.org/docs/sqlalchemy/tips/#querying')
14 query = session.query(model)
15 return query
16
17
18 def is_mapped(obj):
19 return isinstance(obj, DeclarativeMeta)
0 [flake8]
1 exclude = setup.py,docs/*,examples/*,tests
2 max-line-length = 120
3
4 [coverage:run]
5 omit = */tests/*
6
7 [isort]
8 known_first_party=graphene,graphene_sqlalchemy
0 from setuptools import find_packages, setup
1
2 setup(
3 name='graphene-sqlalchemy',
4 version='1.1.1',
5
6 description='Graphene SQLAlchemy integration',
7 long_description=open('README.rst').read(),
8
9 url='https://github.com/graphql-python/graphene-sqlalchemy',
10
11 author='Syrus Akbary',
12 author_email='[email protected]',
13
14 license='MIT',
15
16 classifiers=[
17 'Development Status :: 3 - Alpha',
18 'Intended Audience :: Developers',
19 'Topic :: Software Development :: Libraries',
20 'Programming Language :: Python :: 2',
21 'Programming Language :: Python :: 2.7',
22 'Programming Language :: Python :: 3',
23 'Programming Language :: Python :: 3.3',
24 'Programming Language :: Python :: 3.4',
25 'Programming Language :: Python :: 3.5',
26 'Programming Language :: Python :: Implementation :: PyPy',
27 ],
28
29 keywords='api graphql protocol rest relay graphene',
30
31 packages=find_packages(exclude=['tests']),
32
33 install_requires=[
34 'six>=1.10.0',
35 'graphene>=1.0',
36 'SQLAlchemy',
37 'singledispatch>=3.4.0.3',
38 'iso8601',
39 ],
40 tests_require=[
41 'pytest>=2.7.2',
42 'mock',
43 'sqlalchemy_utils',
44 ],
45 )