Skip to content

Commit

Permalink
Merge pull request #155 from evo-company/introduce-schema
Browse files Browse the repository at this point in the history
Introduce Schema (new high-level api)
  • Loading branch information
kindermax authored Sep 14, 2024
2 parents 48d8ccb + 1fce663 commit 063f7c9
Show file tree
Hide file tree
Showing 59 changed files with 1,632 additions and 1,896 deletions.
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[flake8]
max-line-length = 80
exclude = *_pb2.py,.tox,.git,env,docs,.venv,__pypackages__,tests
extend-ignore = E203
extend-ignore = E203,E231
ignore =
F811,
W503,
1 change: 0 additions & 1 deletion docs/changelog/changes_07.rst
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ Changes in 0.7
- Added support for extensions :ref:`Check extensions documentation <extensions-doc>`

- Added ``QueryParseCache`` extension - cache parsed graphql queries ast.
- Added ``QueryTransformCache`` extension - cache transformed graphql ast into query Node.
- Added ``QueryValidationCache`` extension - cache query validation.
- Added ``QueryDepthValidator`` extension - validate query depth
- Added ``PrometheusMetrics`` extension - wrapper around ``GraphMetrics`` visitor
Expand Down
35 changes: 35 additions & 0 deletions docs/changelog/changes_08.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,38 @@ Changes in 0.8

0.8.0rcX
~~~~~~~~

- Introduce `Schema`. This is a new high-level api with aim to provide single entrypoint for validation/execution
and query/mutations. Previously we had to manage two serapate graphs - one for Query other for Mutation or use `endpoint`
api but `endpoint` api is more like an integration-like api for http handlers.
- `Endpoint` now is much simpler under the hood and it basically delegates execution to schema, only providing support for batching.
- Drop custom `validate` function for federation since we now have better support for `_entities` and `_service` fields and their corresponding types.
- Add new `M` query builder that indicates that this is a `mutation`. It must be used to build a `mutation` query that will be passed to
`Schema.execute` method which will then infer that this is a mutation query Node.
- Drop `hiku.federation.validate.validate`
- Drop `hiku.federation.denormalize`
- Drop `hiku.federation.engine`
- Drop `hiku.federation.endpoint` - use `hiku.endpoint` instead
- Merge `tests.test_federation.test_endpoint` and `tests.test_federation.test_engine` into `tests.test_federation.test_schema`
- Change `QueryDepthValidator` hook to `on_validate`
- Change `GraphQLResponse` type - it now has both `data` and `errors` fields
- Rename `on_dispatch` hook to `on_operation`
- Remove old `on_operation` hook
- Remove `execute` method from `BaseGraphQLEndpoint` class
- Add `process_result` method to `BaseGraphQLEndpoint` class
- Move `GraphQLError` to `hiku.error` module
- Drop `GraphQLError.errors` field. Earlier we used to store multiple errors in single `GraphQLError` but now its one message - one `GraphQLError`.
- Add `GraphQLError.message` field

Backward-incompatible changes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

- Drop `hiku.federation.endpoint.enormalize_entities`
- Drop `hiku.federation.validate.validate`
- Drop `hiku.federation.endpoint` - use `hiku.endpoint` instead
- Drop `hiku.federation.denormalize`
- Drop `hiku.federation.engine` - use `hiku.engine` instead
- Remove `execute` method from `BaseGraphQLEndpoint` class
- Move `GraphQLError` to `hiku.error` module
- Drop `GraphQLError.errors` field
- Add `GraphQLError.message` field
8 changes: 3 additions & 5 deletions docs/extensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,16 @@ graph processing.

Here are all the methods that can be implemented:

- :meth:`~hiku.extensions.Extension.on_graph` - when endpoint is created and transformations applied to graph
- :meth:`~hiku.extensions.Extension.on_dispatch` - when query is dispatched to the endpoint
- :meth:`~hiku.extensions.Extension.on_parse` - when query string is parsed into ast
- :meth:`~hiku.extensions.Extension.on_operation` - when query ast parsed into query Node
- :meth:`~hiku.extensions.Extension.on_init` - when schema is created
- :meth:`~hiku.extensions.Extension.on_operation` - when query is executed by the schema
- :meth:`~hiku.extensions.Extension.on_parse` - when query string is parsed into ast and the into query Node
- :meth:`~hiku.extensions.Extension.on_validate` - when query is validated
- :meth:`~hiku.extensions.Extension.on_execute` - when query is executed by engine

Built-in extensions
~~~~~~~~~~~~~~~~~~~

- ``QueryParseCache`` - cache parsed graphql queries ast.
- ``QueryTransformCache`` - cache transformed graphql ast into query Node.
- ``QueryValidationCache`` - cache query validation.
- ``QueryDepthValidator`` - validate query depth
- ``PrometheusMetrics`` - wrapper around ``GraphMetrics`` visitor
Expand Down
20 changes: 6 additions & 14 deletions docs/federation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,8 @@ Now let's implement the Order service using Hiku:
from hiku.graph import Graph, Root, Field, Link, Node, Option
from hiku.types import ID, Integer, TypeRef, String, Optional, Sequence
from hiku.executors.sync import SyncExecutor
from hiku.federation.schema import Schema
from hiku.federation.directives import Key
from hiku.federation.endpoint import FederatedGraphQLEndpoint
from hiku.federation.engine import Engine
QUERY_GRAPH = Graph([
Node('Order', [
Expand All @@ -104,15 +103,12 @@ Now let's implement the Order service using Hiku:
app = Flask(__name__)
graphql_endpoint = FederatedGraphQLEndpoint(
Engine(SyncExecutor()),
QUERY_GRAPH,
)
schema = Schema(SyncExecutor(), QUERY_GRAPH)
@app.route('/graphql', methods={'POST'})
def handle_graphql():
data = request.get_json()
result = graphql_endpoint.dispatch(data)
result = schema.execute_sync(data)
resp = jsonify(result)
return resp
Expand All @@ -131,9 +127,8 @@ Next, let's implement the ShoppingCart service using Hiku:
from hiku.graph import Graph, Root, Field, Link, Node, Option
from hiku.types import ID, Integer, TypeRef, String, Optional, Sequence
from hiku.executors.sync import SyncExecutor
from hiku.federation.schema import Schema
from hiku.federation.directives import Key
from hiku.federation.endpoint import FederatedGraphQLEndpoint
from hiku.federation.engine import Engine
QUERY_GRAPH = Graph([
Node('ShoppingCart', [
Expand Down Expand Up @@ -165,15 +160,12 @@ Next, let's implement the ShoppingCart service using Hiku:
app = Flask(__name__)
graphql_endpoint = FederatedGraphQLEndpoint(
Engine(SyncExecutor()),
QUERY_GRAPH,
)
schema = Schema(SyncExecutor(), QUERY_GRAPH)
@app.route('/graphql', methods={'POST'})
def handle_graphql():
data = request.get_json()
result = graphql_endpoint.dispatch(data)
result = schema.execute_sync(data)
resp = jsonify(result)
return resp
Expand Down
26 changes: 14 additions & 12 deletions examples/federation-compatibility/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from flask import Flask, request, jsonify

from hiku.directives import Deprecated, Location
from hiku.endpoint.graphql import GraphQLEndpoint
from hiku.federation.schema import Schema
from hiku.federation.directive import (
Extends,
FederationSchemaDirective,
Expand All @@ -20,8 +22,6 @@
Tag,
schema_directive,
)
from hiku.federation.endpoint import FederatedGraphQLEndpoint
from hiku.federation.engine import Engine
from hiku.federation.graph import Graph, FederatedNode
from hiku.graph import (
Nothing,
Expand Down Expand Up @@ -409,7 +409,7 @@ class Custom(FederationSchemaDirective):
),
],
directives=[Key("email"), Extends()],
resolve_reference=resolve_reference_by('email')
resolve_reference=resolve_reference_by("email"),
),
FederatedNode(
"Product",
Expand Down Expand Up @@ -455,7 +455,7 @@ class Custom(FederationSchemaDirective):
Key("sku package"),
Key("sku variation { id }"),
],
resolve_reference=resolve_reference_direct
resolve_reference=resolve_reference_direct,
),
Node(
"ProductDimension",
Expand Down Expand Up @@ -490,7 +490,7 @@ class Custom(FederationSchemaDirective):
),
],
directives=[Key("study { caseNumber }")],
resolve_reference=resolve_reference_direct
resolve_reference=resolve_reference_direct,
),
FederatedNode(
"DeprecatedProduct",
Expand Down Expand Up @@ -569,27 +569,29 @@ class Custom(FederationSchemaDirective):

app = Flask(__name__)

graphql_endpoint = FederatedGraphQLEndpoint(
Engine(SyncExecutor()),
schema = Schema(
SyncExecutor(),
QUERY_GRAPH,
)

endpoint = GraphQLEndpoint(schema)


@app.route("/graphql", methods={"POST"})
def handle_graphql():
data = request.get_json()
result = graphql_endpoint.dispatch(data)
result = endpoint.dispatch(data)
resp = jsonify(result)
return resp


@app.route('/', methods={'GET'})
@app.route("/", methods={"GET"})
def graphiql():
path = Path(__file__).parent.parent / 'graphiql.html'
path = Path(__file__).parent.parent / "graphiql.html"
with open(path) as f:
page = f.read()
page = page.replace("localhost:5000", "localhost:4001")
return page.encode('utf-8')
return page.encode("utf-8")


def main():
Expand All @@ -601,7 +603,7 @@ def main():
def dump():
from hiku.federation.sdl import print_sdl

out_file = Path(__file__).resolve().parent / 'products.graphql'
out_file = Path(__file__).resolve().parent / "products.graphql"
print(f"Dumping schema to {out_file}")
sdl = print_sdl(QUERY_GRAPH)
with open(out_file, "w") as f:
Expand Down
77 changes: 50 additions & 27 deletions examples/graphql_aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,57 +19,80 @@


async def value_func(fields):
return ['Hello World!' for _ in fields]
return ["Hello World!" for _ in fields]


async def action_func(fields):
results = []
for field in fields:
print('action performed!', field.options)
print("action performed!", field.options)
results.append(True)
return results


DATA_TYPES = {
'Point': Record[{
'x': Integer,
'y': Integer,
}],
'Data': Record[{
'point': TypeRef['Point'],
}],
"Point": Record[
{
"x": Integer,
"y": Integer,
}
],
"Data": Record[
{
"point": TypeRef["Point"],
}
],
}

QUERY_GRAPH = Graph([
Root([
Field('value', String, value_func),
]),
], data_types=DATA_TYPES)
QUERY_GRAPH = Graph(
[
Root(
[
Field("value", String, value_func),
]
),
],
data_types=DATA_TYPES,
)

MUTATION_GRAPH = Graph(QUERY_GRAPH.nodes + [
Root([
Field('action', Boolean, action_func,
options=[Option('data', TypeRef['Data'])]),
]),
], data_types=DATA_TYPES)
MUTATION_GRAPH = Graph(
QUERY_GRAPH.nodes
+ [
Root(
[
Field(
"action",
Boolean,
action_func,
options=[Option("data", TypeRef["Data"])],
),
]
),
],
data_types=DATA_TYPES,
)


async def handle_graphql(request):
data = await request.json()
result = await request.app['graphql-endpoint'].dispatch(data)
result = await request.app["graphql-endpoint"].dispatch(data)
return web.json_response(result)


def main():
logging.basicConfig(level=logging.DEBUG)
app = web.Application()
app.add_routes([
web.post('/graphql', handle_graphql),
])
app['graphql-endpoint'] = AsyncGraphQLEndpoint(
Engine(AsyncIOExecutor()), QUERY_GRAPH, MUTATION_GRAPH,
app.add_routes(
[
web.post("/graphql", handle_graphql),
]
)
app["graphql-endpoint"] = AsyncGraphQLEndpoint(
Engine(AsyncIOExecutor()),
QUERY_GRAPH,
MUTATION_GRAPH,
)
web.run_app(app, host='0.0.0.0', port=5000)
web.run_app(app, host="0.0.0.0", port=5000)


if __name__ == "__main__":
Expand Down
Loading

0 comments on commit 063f7c9

Please sign in to comment.