Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a new example using Falcon and graphene-sqlalchemy #166

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ To learn more check out the following [examples](examples/):

- [Flask SQLAlchemy example](examples/flask_sqlalchemy)
- [Nameko SQLAlchemy example](examples/nameko_sqlalchemy)
- [Falcon SQLAlchemy example](examples/falcon_sqlalchemy)

## Contributing

Expand Down
6 changes: 5 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,12 @@ Then you can simply query the schema:

To learn more check out the following `examples <examples/>`__:

- **Full example**: `Flask SQLAlchemy
- **Full example (Flask)**: `Flask SQLAlchemy
example <examples/flask_sqlalchemy>`__
- **Full example (Nameko)**: `Nameko SQLAlchemy
example <examples/nameko_sqlalchemy>`__
- **Full example (Falcon)**: `Falcon SQLAlchemy
example <examples/falcon_sqlalchemy>`__

Contributing
------------
Expand Down
313 changes: 313 additions & 0 deletions examples/falcon_sqlalchemy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
# demo-graphql-sqlalchemy-falcon

## Overview

This is a simple project demonstrating the implementation of a GraphQL server in Python using:

- [SQLAlchemy](https://github.com/zzzeek/sqlalchemy).
- [Falcon](https://github.com/falconry/falcon).
- [Graphene](https://github.com/graphql-python/graphene).
- [Graphene-SQLAlchemy](https://github.com/graphql-python/graphene-sqlalchemy).
- [Gunicorn](https://github.com/benoitc/gunicorn).

The objective is to demonstrate how these different libraries can be integrated.

## Features

The primary feature offered by this demo are:

- [SQLAlchemy](https://github.com/zzzeek/sqlalchemy) ORM against a local SQLite database. The ORM is super simple but showcases a many-to-many relationship between `authors` and `books` via an `author_books` association table.
- [Falcon](https://github.com/falconry/falcon) resources to serve both [GraphQL](https://github.com/facebook/graphql) and [GraphiQL](https://github.com/graphql/graphiql).

> The [Falcon](https://github.com/falconry/falcon) resources are slightly modified versions of the ones under [https://github.com/alecrasmussen/falcon-graphql-server](https://github.com/alecrasmussen/falcon-graphql-server) so all credits to [Alec Rasmussen](https://github.com/alecrasmussen).

- Basic [GraphQL](https://github.com/facebook/graphql) schema automatically derived from the [SQLAlchemy](https://github.com/zzzeek/sqlalchemy) ORM via [Graphene](https://github.com/graphql-python/graphene) and [Graphene-SQLAlchemy](https://github.com/graphql-python/graphene-sqlalchemy).
- API setup via [Falcon](https://github.com/falconry/falcon) with the whole thing served via [Gunicorn](https://github.com/benoitc/gunicorn).

## Usage

All instructions and commands below are meant to be run from the root dir of this repo.

### Prerequisites

You are strongly encouraged to use a virtualenv here but I can be assed writing down the instructions for that.

Install all requirements through:

```
pip install -r requirements.txt
```

### Sample Database

The sample SQLite database has been committed in this repo but can easily be rebuilt through:

```
python -m demo.orm
```

at which point it will create a `demo.db` in the root of this repo.

> The sample data are defined under `data.py` while they're ingested with the code under the `main` sentinel in `orm.py`. Feel free to tinker.

### Running Server

The [Gunicorn](https://github.com/benoitc/gunicorn) is configured via the `gunicorn_config.py` module and binds by default to `localhost:5432/`. You can change all gunicorn configuration options under the aforementioned module.

The server can be run through:

```
gunicorn -c gunicorn_config.py "demo.demo:main()"
```

The server exposes two endpoints:

- `/graphql`: The standard GraphQL endpoint which can receive the queries directly (accessible by default under [http://localhost:5432/graphql](http://localhost:5432/graphql)).
- `/graphiql`: The [GraphiQL](https://github.com/graphql/graphiql) interface (accessible by default under [http://localhost:5432/graphiql](http://localhost:5432/graphiql)).

### Queries

Here's a couple example queries you can either run directly in [GraphiQL](https://github.com/graphql/graphiql) or by performing POST requests against the [GraphQL](https://github.com/facebook/graphql) server.

#### Get an author by ID

Query:

```
query getAuthor{
author(authorId: 1) {
nameFirst,
nameLast
}
}
```

Response:

```
{
"data": {
"author": {
"nameFirst": "Robert",
"nameLast": "Jordan"
}
}
}
```

#### Get an author by first name

```
query getAuthor{
author(nameFirst: "Robert") {
nameFirst,
nameLast
}
}
```

Response:

```
{
"data": {
"author": {
"nameFirst": "Robert",
"nameLast": "Jordan"
}
}
}
```

### Get an author and their books

Query:

```
query getAuthor{
author(nameFirst: "Brandon") {
nameFirst,
nameLast,
books {
title,
year
}
}
}
```

Response:

```
{
"data": {
"author": {
"nameFirst": "Brandon",
"nameLast": "Sanderson",
"books": [
{
"title": "The Gathering Storm",
"year": 2009
},
{
"title": "Towers of Midnight",
"year": 2010
},
{
"title": "A Memory of Light",
"year": 2013
}
]
}
}
}
```

#### Get books by year

Query:

```
query getBooks{
books(year: 1990) {
title,
year
}
}
```

Response:

```
{
"data": {
"books": [
{
"title": "The Eye of the World",
"year": 1990
},
{
"title": "The Great Hunt",
"year": 1990
}
]
}
}
```

#### Get books and their authors by their title

Query:

```
query getBooks{
books(title: "A Memory of Light") {
title,
year,
authors {
nameFirst,
nameLast
}
}
}
```

Response:

```
{
"data": {
"books": [
{
"title": "A Memory of Light",
"year": 2013,
"authors": [
{
"nameFirst": "Robert",
"nameLast": "Jordan"
},
{
"nameFirst": "Brandon",
"nameLast": "Sanderson"
}
]
}
]
}
}
```

#### Get number of books by cover-artist

Query:

```
query getCountBooksByCoverArtist{
stats {
countBooksByCoverArtist {
coverArtist,
countBooks
}
}
}
```

Response:

```
{
"data": {
"stats": {
"countBooksByCoverArtist": [
{
"coverArtist": null,
"countBooks": 1
},
{
"coverArtist": "Darrell K. Sweet",
"countBooks": 12
},
{
"coverArtist": "Michael Whelan",
"countBooks": 1
}
]
}
}
}
```

#### Add new author

Query:

```
mutation createAuthor{
createAuthor(author: {
nameFirst: "First Name",
nameLast: "Last Name"
}) {
author {
authorId
nameFirst
nameLast
}
}
}
```

Response:

```
{
"data": {
"createAuthor": {
"author": {
"authorId": "3",
"nameFirst": "First Name",
"nameLast": "Last Name"
}
}
}
}
```
Binary file added examples/falcon_sqlalchemy/demo.db
Binary file not shown.
11 changes: 11 additions & 0 deletions examples/falcon_sqlalchemy/demo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# coding=utf-8

from demo import api
from demo import data
from demo import demo
from demo import orm
from demo import orm_base
from demo import resources
from demo import schema
from demo import schema_types
from demo import utils
41 changes: 41 additions & 0 deletions examples/falcon_sqlalchemy/demo/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# coding=utf-8

import sqlalchemy.orm
import falcon
import graphene

from demo.resources import ResourceGraphQlSqlAlchemy
from demo.resources import ResourceGraphiQL


def create_app(
schema: graphene.Schema,
scoped_session: sqlalchemy.orm.scoped_session,
do_enable_graphiql: bool,
):
# Create the API.
app = falcon.API()

app.add_route(
uri_template="/graphql",
resource=ResourceGraphQlSqlAlchemy(
schema=schema,
scoped_session=scoped_session,
)
)

if do_enable_graphiql:
app.add_route(
uri_template="/graphiql/",
resource=ResourceGraphiQL(
path_graphiql="graphiql",
)
)
app.add_route(
uri_template="/graphiql/{static_file}",
resource=ResourceGraphiQL(
path_graphiql="graphiql",
)
)

return app
Loading