Skip to content

Commit 96c1726

Browse files
committed
Added SQLAlchemy documentation and fixed installs
1 parent ab9a142 commit 96c1726

File tree

10 files changed

+299
-13
lines changed

10 files changed

+299
-13
lines changed

docs/config.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,10 @@ ga = "UA-12613282-7"
2323
"/docs/django/tutorial/",
2424
"/docs/django/filtering/",
2525
]
26+
27+
[docs.sqlalchemy]
28+
name = "SQLAlchemy"
29+
pages = [
30+
"/docs/sqlalchemy/tutorial/",
31+
"/docs/sqlalchemy/tips/",
32+
]

docs/css/graphiql.css

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
}
9898

9999
#graphiql-container .resultWrap {
100+
position: relative;
100101
display: -webkit-flex;
101102
display: flex;
102103
-webkit-flex-direction: column;
@@ -1010,6 +1011,52 @@ span.CodeMirror-selectedtext { background: none; }
10101011
background-position: right bottom;
10111012
width: 100%; height: 100%;
10121013
}
1014+
#graphiql-container .spinner-container {
1015+
position: absolute;
1016+
top: 50%;
1017+
height: 36px;
1018+
width: 36px;
1019+
left: 50%;
1020+
transform: translate(-50%, -50%);
1021+
z-index: 10;
1022+
}
1023+
1024+
#graphiql-container .spinner {
1025+
vertical-align: middle;
1026+
display: inline-block;
1027+
height: 24px;
1028+
width: 24px;
1029+
position: absolute;
1030+
-webkit-animation: rotation .6s infinite linear;
1031+
-moz-animation: rotation .6s infinite linear;
1032+
-o-animation: rotation .6s infinite linear;
1033+
animation: rotation .6s infinite linear;
1034+
border-left: 6px solid rgba(150, 150, 150, .15);
1035+
border-right: 6px solid rgba(150, 150, 150, .15);
1036+
border-bottom: 6px solid rgba(150, 150, 150, .15);
1037+
border-top: 6px solid rgba(150, 150, 150, .8);
1038+
border-radius: 100%;
1039+
}
1040+
1041+
@-webkit-keyframes rotation {
1042+
from { -webkit-transform: rotate(0deg); }
1043+
to { -webkit-transform: rotate(359deg); }
1044+
}
1045+
1046+
@-moz-keyframes rotation {
1047+
from { -moz-transform: rotate(0deg); }
1048+
to { -moz-transform: rotate(359deg); }
1049+
}
1050+
1051+
@-o-keyframes rotation {
1052+
from { -o-transform: rotate(0deg); }
1053+
to { -o-transform: rotate(359deg); }
1054+
}
1055+
1056+
@keyframes rotation {
1057+
from { transform: rotate(0deg); }
1058+
to { transform: rotate(359deg); }
1059+
}
10131060
.CodeMirror-hints {
10141061
background: white;
10151062
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
@@ -1076,4 +1123,4 @@ li.CodeMirror-hint-active {
10761123
border-bottom: solid 1px #c0c0c0;
10771124
border-top: none;
10781125
margin-bottom: -1px;
1079-
}
1126+
}

docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"es6-promise": "^3.0.2",
1919
"extract-text-webpack-plugin": "^0.9.1",
2020
"gatsby": "^0.7.7",
21-
"graphiql": "^0.4.2",
21+
"graphiql": "^0.4.5",
2222
"graphql": "^0.4.13",
2323
"jeet": "^6.1.2",
2424
"lodash": "^3.10.1",

docs/pages/docs/sqlalchemy/tips.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
title: Tips
3+
description: Tips when SQLAlchemy in Graphene
4+
---
5+
6+
# Tips
7+
8+
## Querying
9+
10+
For make querying to the database work, there are two alternatives:
11+
12+
* Expose the db session when you create the `graphene.Schema`:
13+
14+
```python
15+
schema = graphene.Schema(session=session)
16+
```
17+
18+
* Create a query for the models.
19+
20+
```python
21+
Base = declarative_base()
22+
Base.query = db_session.query_property()
23+
24+
class MyModel(Base):
25+
# ...
26+
```
27+
28+
If you don't specify any, the following error will be displayed:
29+
30+
`A query in the model Base or a session in the schema is required for querying.`
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
---
2+
title: Tutorial
3+
description: Using SQLAlchemy with Graphene
4+
---
5+
6+
# SQLAlchemy + Flask Tutorial
7+
8+
Graphene comes with builtin support to SQLAlchemy, which makes quite easy to operate with your current models.
9+
10+
**Note: The code in this tutorial is pulled from the
11+
[Flask SQLAlchemy example app](https://github.com/graphql-python/graphene/tree/master/examples/flask_sqlalchemy)**.
12+
13+
14+
## Setup the Project
15+
16+
We will setup the project, execute the following:
17+
18+
```bash
19+
# Create the project directory
20+
mkdir flask_sqlalchemy
21+
cd flask_sqlalchemy
22+
23+
# Create a virtualenv to isolate our package dependencies locally
24+
virtualenv env
25+
source env/bin/activate # On Windows use `env\Scripts\activate`
26+
27+
# SQLAlchemy and Graphene with SQLAlchemy support
28+
pip install SQLAlchemy
29+
pip install graphene[sqlalchemy]
30+
31+
# Install Flask and GraphQL Flask for exposing the schema through HTTP
32+
pip install Flask
33+
pip install graphql-flask
34+
```
35+
36+
## Defining our models
37+
38+
Let's get started with these models:
39+
40+
```python
41+
# flask_sqlalchemy/models.py
42+
from sqlalchemy import *
43+
from sqlalchemy.orm import (scoped_session, sessionmaker, relationship,
44+
backref)
45+
from sqlalchemy.ext.declarative import declarative_base
46+
47+
engine = create_engine('sqlite:///database.sqlite3', convert_unicode=True)
48+
db_session = scoped_session(sessionmaker(autocommit=False,
49+
autoflush=False,
50+
bind=engine))
51+
52+
Base = declarative_base()
53+
# We will need this for querying
54+
Base.query = db_session.query_property()
55+
56+
57+
class Department(Base):
58+
__tablename__ = 'department'
59+
id = Column(Integer, primary_key=True)
60+
name = Column(String)
61+
62+
63+
class Employee(Base):
64+
__tablename__ = 'employee'
65+
id = Column(Integer, primary_key=True)
66+
name = Column(String)
67+
hired_on = Column(DateTime, default=func.now())
68+
department_id = Column(Integer, ForeignKey('department.id'))
69+
department = relationship(
70+
Department,
71+
backref=backref('employees',
72+
uselist=True,
73+
cascade='delete,all'))
74+
```
75+
76+
## Schema
77+
78+
GraphQL presents your objects to the world as a graph structure rather than a more
79+
hierarchical structure to which you may be accustomed. In order to create this
80+
representation, Graphene needs to know about each *type* of object which will appear in
81+
the graph.
82+
83+
This graph also has a *root type* through which all access begins. This is the `Query` class below.
84+
In this example, we provide the ability to list all employees via `all_employees`, and the
85+
ability to obtain a specific node via `node`.
86+
87+
Create `flask_sqlalchemy/schema.py` and type the following:
88+
89+
```python
90+
# flask_sqlalchemy/schema.py
91+
import graphene
92+
from graphene import relay
93+
from graphene.contrib.sqlalchemy import SQLAlchemyNode, SQLAlchemyConnectionField
94+
from models import db_session, Department as DepartmentModel, Employee as EmployeeModel
95+
96+
schema = graphene.Schema()
97+
98+
99+
@schema.register
100+
class Department(SQLAlchemyNode):
101+
class Meta:
102+
model = DepartmentModel
103+
104+
105+
@schema.register
106+
class Employee(SQLAlchemyNode):
107+
class Meta:
108+
model = EmployeeModel
109+
110+
111+
class Query(graphene.ObjectType):
112+
node = relay.NodeField()
113+
all_employees = SQLAlchemyConnectionField(Employee)
114+
115+
schema.query = Query
116+
```
117+
118+
## Creating GraphQL and GraphiQL views in Flask
119+
120+
Unlike a RESTful API, there is only a single URL from which GraphQL is accessed.
121+
122+
We are going to use Flask to create a server that expose the GraphQL schema under `/graphql` and a interface for querying it easily: GraphiQL under `/graphiql`.
123+
124+
Afortunately for us, the library `graphql-flask` that we installed previously is making the task quite easy.
125+
126+
```python
127+
# flask_sqlalchemy/app.py
128+
from flask import Flask
129+
from graphql_flask import GraphQL
130+
131+
from models import db_session
132+
from schema import schema, Department
133+
134+
app = Flask(__name__)
135+
app.debug = True
136+
137+
# This is creating the `/graphql` and `/graphiql` endpoints
138+
GraphQL(app, schema=schema)
139+
140+
@app.teardown_appcontext
141+
def shutdown_session(exception=None):
142+
db_session.remove()
143+
144+
if __name__ == '__main__':
145+
app.run()
146+
```
147+
148+
149+
## Creating some data
150+
151+
```bash
152+
$ python
153+
154+
>>> from models import engine, db_session, Base, Department, Employee
155+
>>> Base.metadata.create_all(bind=engine)
156+
157+
>>> # Fill the tables with some data
158+
>>> engineering = Department(name='Engineering')
159+
>>> db_session.add(engineering)
160+
>>> hr = Department(name='Human Resources')
161+
>>> db_session.add(hr)
162+
163+
>>> peter = Employee(name='Peter', department=engineering)
164+
>>> db_session.add(peter)
165+
>>> roy = Employee(name='Roy', department=engineering)
166+
>>> db_session.add(roy)
167+
>>> tracy = Employee(name='Tracy', department=hr)
168+
>>> db_session.add(tracy)
169+
>>> db_session.commit()
170+
```
171+
172+
173+
## Testing our GraphQL schema
174+
175+
We're now ready to test the API we've built. Let's fire up the server from the command line.
176+
177+
```bash
178+
$ python ./app.py
179+
180+
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
181+
```
182+
183+
Go to [localhost:5000/graphiql](http://localhost:5000/graphiql) and type your first query!
184+
185+
```graphql
186+
{
187+
allEmployees {
188+
edges {
189+
node {
190+
id
191+
name
192+
department {
193+
name
194+
}
195+
}
196+
}
197+
}
198+
}
199+
```

examples/flask_sqlalchemy/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from flask import Flask
22
from database import db_session, init_db
33

4-
from schema import schema, Department
4+
from schema import schema
55
from graphql_flask import GraphQL
66

77
app = Flask(__name__)

examples/flask_sqlalchemy/schema.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
from graphene.contrib.sqlalchemy import SQLAlchemyNode, SQLAlchemyConnectionField
44
from models import Department as DepartmentModel, Employee as EmployeeModel
55

6-
from database import db_session
7-
8-
schema = graphene.Schema(session=db_session)
6+
schema = graphene.Schema()
97

108

119
@schema.register
@@ -21,7 +19,7 @@ class Meta:
2119

2220

2321
class Query(graphene.ObjectType):
24-
node = relay.NodeField(Department, Employee)
22+
node = relay.NodeField()
2523
all_employees = SQLAlchemyConnectionField(Employee)
2624

2725
schema.query = Query

graphene/contrib/sqlalchemy/types.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from .converter import (convert_sqlalchemy_column,
1111
convert_sqlalchemy_relationship)
1212
from .options import SQLAlchemyOptions
13-
from .utils import is_mapped, get_session
13+
from .utils import is_mapped, get_query
1414

1515

1616
class SQLAlchemyObjectTypeMeta(ObjectTypeMeta):
@@ -118,8 +118,8 @@ class Meta:
118118
def get_node(cls, id, info=None):
119119
try:
120120
model = cls._meta.model
121-
session = get_session(info)
122-
instance = session.query(model).filter(model.id == id).one()
121+
query = get_query(model, info)
122+
instance = query.filter(model.id == id).one()
123123
return cls(instance)
124124
except NoResultFound:
125125
return None

graphene/contrib/sqlalchemy/utils.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@ def get_session(info):
2020

2121

2222
def get_query(model, info):
23-
query = getattr(model, 'query')
23+
query = getattr(model, 'query', None)
2424
if not query:
25-
query = get_session(info).query(model)
25+
session = get_session(info)
26+
if not session:
27+
raise Exception('A query in the model Base or a session in the schema is required for querying.\n'
28+
'Read more http://graphene-python.org/docs/sqlalchemy/tips/#querying')
29+
query = session.query(model)
2630
return query
2731

2832

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ def run_tests(self):
7272
'graphql-django-view>=1.1.0',
7373
],
7474
'sqlalchemy': [
75-
'sqlalchemy'
75+
'sqlalchemy',
76+
'singledispatch>=3.4.0.3',
7677
]
7778
},
7879

0 commit comments

Comments
 (0)