Code Monkey home page Code Monkey logo

starlette-graphene3's Introduction

starlette-graphene3

A simple ASGI app for using Graphene v3 with Starlette / FastAPI.

Test codecov pypi package

It supports:

File uploading requires python-multipart to be installed.

Alternatives

Installation

pip3 install -U starlette-graphene3

Example

import asyncio

import graphene
from graphene_file_upload.scalars import Upload

from starlette.applications import Starlette
from starlette_graphene3 import GraphQLApp, make_graphiql_handler


class User(graphene.ObjectType):
    id = graphene.ID()
    name = graphene.String()


class Query(graphene.ObjectType):
    me = graphene.Field(User)

    def resolve_me(root, info):
        return {"id": "john", "name": "John"}


class FileUploadMutation(graphene.Mutation):
    class Arguments:
        file = Upload(required=True)

    ok = graphene.Boolean()

    def mutate(self, info, file, **kwargs):
        return FileUploadMutation(ok=True)


class Mutation(graphene.ObjectType):
    upload_file = FileUploadMutation.Field()


class Subscription(graphene.ObjectType):
    count = graphene.Int(upto=graphene.Int())

    async def subscribe_count(root, info, upto=3):
        for i in range(upto):
            yield i
            await asyncio.sleep(1)


app = Starlette()
schema = graphene.Schema(query=Query, mutation=Mutation, subscription=Subscription)

app.mount("/", GraphQLApp(schema, on_get=make_graphiql_handler()))  # Graphiql IDE

# app.mount("/", GraphQLApp(schema, on_get=make_playground_handler()))  # Playground IDE
# app.mount("/", GraphQLApp(schema)) # no IDE

GraphQLApp

GraphQLApp(schema, [options...])

class GraphQLApp:
    def __init__(
        self,
        schema: graphene.Schema,  # Requied
        *,
        # Optional keyword parameters
        on_get: Optional[
            Callable[[Request], Union[Response, Awaitable[Response]]]
        ] = None,  # optional HTTP handler for GET requests
        context_value: ContextValue = None,
        root_value: RootValue = None,
        middleware: Optional[Middleware] = None,
        error_formatter: Callable[[GraphQLError], Dict[str, Any]] = format_error,
        logger_name: Optional[str] = None,
        playground: bool = False,  # deprecating
        execution_context_class: Optional[Type[ExecutionContext]] = None,
    ):

starlette-graphene3's People

Contributors

abompard avatar ciscorn avatar ddelange avatar dependabot[bot] avatar eplatonoff avatar erikwrede avatar hasauino avatar hettlage avatar rrmerugu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

starlette-graphene3's Issues

Mention dependency on python-multipart for file uploading somewhere

I recently switched my Graphene application to a FastAPI-based stack.
When testing my file uploads, I got the error Request body is not a valid multipart/form-data, thrown here:

raise ValueError("Request body is not a valid multipart/form-data")

The underlying exception was The python-multipart library must be installed to use form parsing., which was not displayed to me. I think the ValueError raised in starlette-graphene should be changed to include the message of the underlying exception.

Is there a playground for websocket routes?

Hi There,

I'm using the following code to run a websocket GraphQL endpoint:

"""Main server application."""

import asyncio

import graphene

from starlette.applications import Starlette
from starlette_graphene3 import GraphQLApp
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware

from .schema import schema_basic

middleware = [
    Middleware(
        CORSMiddleware,
        allow_origins=['*'],
        allow_methods=['POST', 'GET'],
        allow_headers=['access-control-allow-origin', 'authorization', 'content-type']
    )
]

app = Starlette(debug=True, middleware=middleware)
gapp = GraphQLApp(schema_basic(), playground=True)
app.add_route("/graphql", gapp)
app.add_websocket_route("/graphql-ws", gapp)

How do I now debug the new websocket route in the playground?

Opening up playground with localhost:5000/graphql-ws doesn't work

Opening up playground with localhost:5000/graphql works but then when running the subcription, it's attempting to connect to the server on the incorrect route (/graphql).

image

Any thoughts on how to figure out if the websocket endpoint is working?

logger can't be disable

Hey, I've been using starlette-graphene3 and I've got into a small issue.

When I want to signal an error (i.e. an entity not found) I raise a Python exception. The exact exception type depends on the type of error. I then catch all these errors in a middleware to log them, and re-raise them as GraphQLError's to send an appropriate error response to the client. This package then logs the error too, so it's logged twice. I would like these errors not to be logged again by the package.

I think that a possible solution is for you to implement an optional error handler, such that only the errors that I don't catch are logged. That could be in the form of a callback, passed to the app's constructor.

Exceptions are not catched when using a websocket

It catches exceptions over http now #13 is resolved, but it is still swallows exceptions when subscribing / sending the query over a websocket:

app.mount("/graphql", graphql_app)
app.add_websocket_route("/graphql-ws", graphql_app)

Another suggestion would be to be able to mask the error details in the graphql response, so using logging getting the full error message, but return a generic error message over the graphql connection, so sensitive data cannot leak that way?

Context_value cannot be injected per query.

GraphQLApp Class has context_value parameter of type ContextValue which is defined as:

ContextValue = Union[Any, Callable[[HTTPConnection], Any]]

It leads to conclusion that I can set some function responsible for generating values per each connection, but actually it doesn't work, function is not evaluated and directly passed as value to resolver.

beta version?

Unfortunately starlette is going to deprecate graphql support, so starlette-graphene3 looks like the only viable sol... unfortunately, pipenv can't lock beta this is using a beta graphene v....

why is this using a beta v of graphene which is 3.0b8?

Websockets

Great project! I tried to use the websocket functionality, which works when i run the tests, but when i try to do so from a simple html page, the websocket endpoint this error:
WebSocket connection to 'ws://127.0.0.1:3000/graphql-ws' failed: Error during WebSocket handshake: Response must not include 'Sec-WebSocket-Protocol' header if not present in request: graphql-ws.

import graphene
from graphene import String

#from starlette.graphql import GraphQLApp
from starlette_graphene3 import GraphQLApp

from app.main import app

html = '''
<!DOCTYPE html>
<html>
    <head>
        <title>Websocket test</title>
    </head>
    <body>
        <form action="" onsubmit="sendMessage(event)">
            <input type="text" id="messageText" autocomplete="off"/>
            <button>Send</button>
        </form>
        <ul id='messages'>
        </ul>
        <script>
            var GQL_CONNECTION_ACK = "connection_ack";
            var GQL_CONNECTION_ERROR = "connection_error";
            var GQL_CONNECTION_INIT = "connection_init";
            var GQL_CONNECTION_TERMINATE = "connection_terminate";
            var GQL_COMPLETE = "complete";
            var GQL_DATA = "data";
            var GQL_ERROR = "error";
            var GQL_START = "start";
            var GQL_STOP = "stop";

            var ws = new WebSocket("ws://127.0.0.1:3000/graphql-ws");

            ws.onconnect = function(event) {
                ws.send('{"type": ' + GQL_CONNECTION_INIT+ '}')
            }
            ws.onmessage = function(event) {
                console.log("RECEIVED MESSAGE: ", event.data);
            };
        </script>
    </body>
</html>
'''


@app.get("/")
def get() -> Any:
    return HTMLResponse(html)


class GraphQ(graphene.ObjectType):
    echo = graphene.String(msg=graphene.String())

    def resolve_echo(self, info, msg):
        return f'Message was: {msg}'


gapp = GraphQLApp(schema=graphene.Schema(query=GraphQLQueries))
app.add_route("/graphql", gapp)
app.add_websocket_route("/graphql-ws", gapp)

I can see starlette.test_client.py adds some headers:

    def websocket_connect(
        self, url: str, subprotocols: typing.Sequence[str] = None, **kwargs: typing.Any
    ) -> typing.Any:
        url = urljoin("ws://testserver", url)
        headers = kwargs.get("headers", {})
        headers.setdefault("connection", "upgrade")
        headers.setdefault("sec-websocket-key", "testserver==")
        headers.setdefault("sec-websocket-version", "13")
        if subprotocols is not None:
            headers.setdefault("sec-websocket-protocol", ", ".join(subprotocols))
        kwargs["headers"] = headers
        try:
            super().request("GET", url, **kwargs)
        except _Upgrade as exc:
            session = exc.session
        else:
            raise RuntimeError("Expected WebSocket upgrade")  # pragma: no cover

        return session

But i dont think i can/should add these when adding the websocket route?

Catching exceptions with sentry

Currently all exceptions are catched and returned in a formatted form. This is fine for invalid formatted queries, but for programming errors in my code i'd like these to be reraised, so i can catch them with Sentry for example. I could not find a way to achieve this currently, any ideas?

Version 0.2.1 on PyPI does not contain the latest commit

It's a bit weird but it looks like the tarball of 0.2.1 that can be found on PyPI does not contain commit ae328c, even though it should be according to the commit history. Not sure how that could happen, maybe I'm missing something too obvious.

Disable WebSockets?

How to disable websockets in Graphql endpoint?

WARNING:  Unsupported upgrade request.
WARNING:  No supported WebSocket library detected. Please use 'pip install uvicorn[standard]', or install 'websockets' or 'wsproto' manually.

make_graphiql_handler doesn't support named queries

Recently switched over to running Starlette to get working subscriptions, but I've run into a very strange bug.

Versions:

  • starlette==0.14.2
  • starlette-graphene3==0.5.1
  • graphene==3.0b8
  • graphql-core==3.1.6
  • graphql-relay==3.1.0

Running our app as follows

app = Starlette()
schema = graphene.Schema(
    query=Query,
    mutation=Mutation,
    subscription=Subscription,
    auto_camelcase=False,
)

app.mount("/", GraphQLApp(schema, on_get=make_graphiql_handler()))

if __name__ == "__main__":

    uvicorn.run(app, host="0.0.0.0", port=8000)

Expected behavior:

Running the following in GraphiQL works:

{
  star {
    id
  }
}

This works as well:

query {
  star {
    id
  }
}

This fails:

query name1 {
  star {
    id
  }
}


query name2 {
  star {
    id
  }
}

With the following error:

Syntax Error GraphQL (1:1) Unexpected <EOF>

1: 
   ^

I can also see similar errors given this input:

query {
  star {
    id
  }
}


query name2 {
  star {
    id
  }
}

Which results in

{
  "data": null,
  "errors": [
    {
      "message": "This anonymous operation must be the only defined operation.",
      "locations": [
        {
          "line": 1,
          "column": 1
        }
      ],
      "path": null
    }
  ]
}

I do note a few odd behaviors here.

  • Refreshing the page should preserve the queries, it doesn't and gives me a fresh welcome screen or an incorrectly edited version of the query.
  • Naming queries at all seems unsupported, but other instances of GraphiQL seem to support this without issue.
  • The GraphiQL playground handler supports named queries without issue.
  • No change to code base, this is all working correctly with starlette's provided GUI client. But release notes indicate that this support is deprecated.

ImportError: cannot import name 'ExecutionResult' from 'graphql'

Im trying to import "from starlette_graphene3 import GraphQLApp" but this error throws me.

Traceback (most recent call last):
File "/usr/local/lib/python3.8/multiprocessing/process.py", line 315, in _bootstrap
self.run()
File "/usr/local/lib/python3.8/multiprocessing/process.py", line 108, in run
self._target(*self._args, **self._kwargs)
File "/usr/local/lib/python3.8/site-packages/uvicorn/subprocess.py", line 61, in subprocess_started
target(sockets=sockets)
File "/usr/local/lib/python3.8/site-packages/uvicorn/server.py", line 49, in run
loop.run_until_complete(self.serve(sockets=sockets))
File "/usr/local/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
return future.result()
File "/usr/local/lib/python3.8/site-packages/uvicorn/server.py", line 56, in serve
config.load()
File "/usr/local/lib/python3.8/site-packages/uvicorn/config.py", line 308, in load
self.loaded_app = import_from_string(self.app)
File "/usr/local/lib/python3.8/site-packages/uvicorn/importer.py", line 23, in import_from_string
raise exc from None
File "/usr/local/lib/python3.8/site-packages/uvicorn/importer.py", line 20, in import_from_string
module = importlib.import_module(module_str)
File "/usr/local/lib/python3.8/importlib/init.py", line 127, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "", line 1014, in _gcd_import
File "", line 991, in _find_and_load
File "", line 975, in _find_and_load_unlocked
File "", line 671, in _load_unlocked
File "", line 848, in exec_module
File "", line 219, in _call_with_frames_removed
File "/app/./src/main.py", line 3, in
from starlette_graphene3 import GraphQLApp
File "/usr/local/lib/python3.8/site-packages/starlette_graphene3.py", line 17, in
from graphql import (
ImportError: cannot import name 'ExecutionResult' from 'graphql' (/usr/local/lib/python3.8/site-packages/graphql/init.py)

Is there a Recommended Way of Handling Dependency Injections?

I'm trying to understand the best way to inject the database as a dependency for my graphql queries/mutations.

It looks like context is the appropriate way to do so, and based on reviewing other issues raised on the starlette repo encode/starlette#591, it sounds like the problem was never really solved. Given that starlette is deprecating graphql support and this repo appears to be filling in the gaps of that connection, it felt right to ask the question here.

The workaround described in issue 591 mentions adding the db session to the request via injection on the route, however, that appears to require calling execute on the GraphQL app directly, rather than the app.mount() pattern described in ythe README. I wasn't able to find another way attach the GraphQL app to appropriately, so I'm wondering if I'm missing something or if db dependency injection just isn't possible at the moment.

Is this functionality currently possible to manage or is the expectation that the db will be accessed via things like the SQLAlchemy Graphene connection?

Missing CDN files

I installed the latest version, but it appears that some of the required frontend libraries are missing for the graphiql frontend:

graphql_app = GraphQLApp(schema=graphene.Schema(query=GraphQLQueries, subscription=Subscription), on_get=make_playground_handler())
app.mount("/graphql", graphql_app)

127.0.0.1/:15 GET https://unpkg.com/graphiql/graphiql.css net::ERR_ABORTED 404
127.0.0.1/:17 GET https://unpkg.com/react-dom@16/umd/react-dom.production.min.js net::ERR_ABORTED 404
127.0.0.1/:22 GET https://unpkg.com/graphiql/graphiql.min.js net::ERR_ABORTED 404
127.0.0.1/:18 GET https://unpkg.com/[email protected]/browser/client.js net::ERR_ABORTED 404
127.0.0.1/:16 GET https://unpkg.com/react@16/umd/react.production.min.js net::ERR_ABORTED 404
127.0.0.1/:17 GET https://unpkg.com/react-dom@16/umd/react-dom.production.min.js net::ERR_ABORTED 404
127.0.0.1/:18 GET https://unpkg.com/[email protected]/browser/client.js net::ERR_ABORTED 404
127.0.0.1/:22 GET https://unpkg.com/graphiql/graphiql.min.js net::ERR_ABORTED 404

Maybe it would also be nice if there is an option to provide custom urls for these libraries, so you could host them yourself or point to another cdn?

PS: there are no problems when using the make_playground_handler

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.