Skip to content

Commit

Permalink
auto_camelcase plugin setting, closes #15
Browse files Browse the repository at this point in the history
Also added basic configuration validation, refs #4
  • Loading branch information
simonw committed Aug 3, 2020
1 parent 23fa9b8 commit 88cefb3
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 4 deletions.
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ Install this plugin in the same environment as Datasette.

## Usage

This sets up `/graphql` as a GraphQL endpoint for the first attached database. Individual tables can be queried like this:
This plugin up `/graphql` as a GraphQL endpoint for the first attached database.

### Querying for tables and columns

Individual tables can be queried like this:
```grophql
{
repos {
Expand All @@ -29,6 +33,8 @@ This sets up `/graphql` as a GraphQL endpoint for the first attached database. I
}
```

### Accessing nested objects

If a column is a foreign key to another table, you can request columns of that table using a nested query like this:
```graphql
{
Expand All @@ -43,6 +49,8 @@ If a column is a foreign key to another table, you can request columns of that t
}
```

### Filtering tables

You can filter the rows returned for a specific table using the `filters:` argument. This accepts a list of filters, where a filter is a string of the form `column=value` or `column__op=value`. For example, to return just repositories with the Apache 2 license and more than 10 stars:

```graphql
Expand All @@ -58,6 +66,25 @@ You can filter the rows returned for a specific table using the `filters:` argum
```
This is the same format used for querystring arguments to the Datasette table view, see [column filter arguments](https://datasette.readthedocs.io/en/stable/json_api.html#column-filter-arguments) in the Datasette documentation.


### Auto camelCase

The names of your columns and tables default to being matched by their representations in GraphQL.

If you have tables with `names_like_this` you may want to work with them in GraphQL using `namesLikeThis`, for consistency with GraphQL and JavaScript conventions.

You can turn on automatic camelCase using the `"auto_camelcase"` plugin configuration setting in `metadata.json`, like this:

```json
{
"plugins": {
"datasette-graphql": {
"auto_camelcase": true
}
}
}
```

## Still to come

See [issues](https://github.com/simonw/datasette-graphql/issues) for a full list. Planned improvements include:
Expand Down
21 changes: 21 additions & 0 deletions datasette_graphql/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from click import ClickException
from datasette import hookimpl
from datasette.utils.asgi import Response
from graphql.execution.executors.asyncio import AsyncioExecutor
Expand Down Expand Up @@ -51,3 +52,23 @@ def register_routes():
return [
(r"^/graphql$", view_graphql),
]


@hookimpl
def startup(datasette):
# Validate configuration
config = datasette.plugin_config("datasette-graphql") or {}
if "databases" in config:
if len(config["databases"].keys()) > 1:
raise ClickException(
"datasette-graphql currently only supports a single database"
)
database_name = list(config["databases"].keys())[0]
try:
datasette.get_database(database_name)
except KeyError:
raise ClickException(
"datasette-graphql config error: '{}' is not a connected database".format(
database_name
)
)
7 changes: 6 additions & 1 deletion datasette_graphql/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,12 @@ def introspect_table(conn):
Query = type(
"Query", (graphene.ObjectType,), {key: value for key, value in to_add},
)
return graphene.Schema(query=Query, auto_camelcase=False)
return graphene.Schema(
query=Query,
auto_camelcase=(datasette.plugin_config("datasette-graphql") or {}).get(
"auto_camelcase", False
),
)


def make_all_rows_resolver(db, table, klass):
Expand Down
16 changes: 14 additions & 2 deletions tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,20 @@ def db_path(tmp_path_factory):
)
db["repos"].insert_all(
[
{"id": 1, "name": "datasette", "owner": 2, "license": "apache2"},
{"id": 2, "name": "dogspotter", "owner": 1, "license": "mit"},
{
"id": 1,
"full_name": "simonw/datasette",
"name": "datasette",
"owner": 2,
"license": "apache2",
},
{
"id": 2,
"full_name": "cleopaws/dogspotter",
"name": "dogspotter",
"owner": 1,
"license": "mit",
},
],
pk="id",
foreign_keys=(("owner", "users"), ("license", "licenses")),
Expand Down
44 changes: 44 additions & 0 deletions tests/test_graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,47 @@ async def test_graphql_error(ds):
}
],
}


@pytest.mark.asyncio
@pytest.mark.parametrize(
"on,expected",
[
(
True,
{
"repos": [
{"id": 1, "fullName": "simonw/datasette"},
{"id": 2, "fullName": "cleopaws/dogspotter"},
]
},
),
(
False,
{
"repos": [
{"id": 1, "full_name": "simonw/datasette"},
{"id": 2, "full_name": "cleopaws/dogspotter"},
]
},
),
],
)
async def test_graphql_auto_camelcase(db_path, on, expected):
ds = Datasette(
[db_path], metadata={"plugins": {"datasette-graphql": {"auto_camelcase": on}}}
)
query = """
{
repos {
id
NAME
}
}
""".replace(
"NAME", "fullName" if on else "full_name"
)
async with httpx.AsyncClient(app=ds.app()) as client:
response = await client.post("http://localhost/graphql", json={"query": query})
assert response.status_code == 200
assert response.json() == {"data": expected}

0 comments on commit 88cefb3

Please sign in to comment.