Passed
Push — main ( 2bcd5c...05fcff )
by Eran
01:27
created

graphinate.materializers.graphql.graphql()   A

Complexity

Conditions 3

Size

Total Lines 41
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 26
dl 0
loc 41
rs 9.256
c 0
b 0
f 0
cc 3
nop 3
1
import contextlib
2
import webbrowser
3
4
import strawberry
5
from starlette.applications import Starlette
6
from starlette.requests import Request
7
from starlette.responses import RedirectResponse
8
from starlette.schemas import SchemaGenerator
9
from starlette.types import ASGIApp
10
from strawberry.asgi import GraphQL
11
from strawberry.extensions.tracing import OpenTelemetryExtension
12
13
from graphinate.server.starlette import routes
14
15
DEFAULT_PORT: int = 8072
16
17
GRAPHQL_ROUTE_PATH = "/graphql"
18
19
20
def _openapi_schema(request: Request) -> ASGIApp:
21
    """
22
    Generates an OpenAPI schema for the GraphQL API and other routes.
23
24
    Args:
25
        request (Request): The HTTP request object.
26
27
    Returns:
28
        ASGIApp: An OpenAPI response containing the schema for the specified routes.
29
    """
30
    schema_data = {
31
        'openapi': '3.0.0',
32
        'info': {'title': 'Graphinate API', 'version': '0.6.0'},
33
        'paths': {
34
            '/graphql': {'get': {'responses': {200: {'description': 'GraphQL'}}}},
35
            '/graphiql': {'get': {'responses': {200: {'description': 'GraphiQL UI.'}}}},
36
            '/metrics': {'get': {'responses': {200: {'description': 'Prometheus metrics.'}}}},
37
            '/viewer': {'get': {'responses': {200: {'description': '3D Force-Directed Graph Viewer'}}}},
38
            '/voyager': {'get': {'responses': {200: {'description': 'Voyager GraphQL Schema Viewer'}}}}
39
        }
40
    }
41
42
    schema = SchemaGenerator(schema_data)
43
    return schema.OpenAPIResponse(request=request)
44
45
46
def graphql(graphql_schema: strawberry.Schema, port: int = DEFAULT_PORT, **kwargs):
47
    """
48
    Args:
49
        graphql_schema: The Strawberry GraphQL schema.
50
        port: The port number to run the server on. Defaults to 8072.
51
52
    Returns:
53
    """
54
    graphql_schema.extensions.append(OpenTelemetryExtension)
55
56
    def open_url():
57
        for app_name in ('viewer',):
58
            webbrowser.open(f'http://localhost:{port}/{app_name}')
59
60
    @contextlib.asynccontextmanager
61
    async def lifespan(app: Starlette):  # pragma: no cover
62
        if kwargs.get('browse'):
63
            open_url()
64
        yield
65
66
    graphql_app = GraphQL(graphql_schema, graphiql=True, graphql_ide='pathfinder')
67
    app = Starlette(
68
        lifespan=lifespan,
69
        routes=routes()
70
    )
71
    app.add_route(GRAPHQL_ROUTE_PATH, graphql_app)
72
    app.add_websocket_route(GRAPHQL_ROUTE_PATH, graphql_app)
73
74
    from starlette_prometheus import PrometheusMiddleware, metrics
75
    app.add_middleware(PrometheusMiddleware)
76
    app.add_route("/metrics", metrics)
77
    app.add_route("/schema", route=_openapi_schema, include_in_schema=False)
78
    app.add_route("/openapi.json", route=_openapi_schema, include_in_schema=False)
79
80
    async def redirect_to_viewer(request):
81
        return RedirectResponse(url='/viewer')
82
83
    app.add_route('/', redirect_to_viewer)
84
85
    import uvicorn
86
    uvicorn.run(app, host='0.0.0.0', port=port)
87