-
-
Notifications
You must be signed in to change notification settings - Fork 219
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
GraphQL: Re-add extension and dependencies (2024) #554
Conversation
WalkthroughThe changes introduce a new Changes
Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 14
🧹 Outside diff range and nitpick comments (2)
tests/test_graphql.py (1)
7-16
: Consider enhancing schema fixture with additional test cases.The schema fixture is well-implemented but could be expanded to cover more scenarios.
Consider adding:
- Additional fields to test different GraphQL types
- Mutations to test data modifications
- Nested resolvers to test more complex scenarios
Example enhancement:
class Query(graphene.ObjectType): hello = graphene.String(name=graphene.String(default_value="stranger")) user = graphene.Field(lambda: User) def resolve_hello(self, info, name): return f"Hello {name}" def resolve_user(self, info): return User(id="1", name="Test User") class User(graphene.ObjectType): id = graphene.ID() name = graphene.String()responder/ext/graphql/__init__.py (1)
66-66
: Clarify the return statement or remove if unnecessaryAt line 66,
graphql_response
returns(query, result, status_code)
, but the calling methodon_request
at line 69 doesn't use this return value. If the return value isn't needed, consider removing it to simplify the code.Apply this diff to remove the unnecessary return value:
- return (query, result, status_code)
If the return value is intended for future use or logging, ensure it's utilized appropriately.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (4)
- responder/ext/graphql/init.py (1 hunks)
- responder/ext/graphql/templates.py (1 hunks)
- setup.py (1 hunks)
- tests/test_graphql.py (1 hunks)
🔇 Additional comments (5)
tests/test_graphql.py (1)
1-5
: LGTM: Clean and necessary imports.The imports are well-organized and include all required dependencies for GraphQL testing.
setup.py (2)
88-88
: LGTM! Well-defined version constraints for GraphQL dependencies.The specific version constraints (
graphene<3
andgraphql-server-core>=1.2,<2
) are appropriate for ensuring compatibility and preventing breaking changes while restoring the GraphQL interface.
88-88
: Verify version constraints match original implementation.While the version constraints look reasonable, it would be good to verify if these match the versions used in the original implementation by @taoufik07 to ensure complete compatibility.
✅ Verification successful
Based on the git history output, I can see that the original implementation by @taoufik07 used only
"graphql": ["graphene"]
without version constraints. Let me verify if there are any specific GraphQL-related dependencies or version requirements in the codebase.
Let me check the actual GraphQL implementation and test files to understand the version requirements better.
Version constraints are appropriate and more specific than original
The new version constraints
"graphql": ["graphene<3", "graphql-server-core>=1.2,<2"]
are actually more precise than the original unconstrained"graphene"
. The constraints are justified because:
- The codebase uses basic Graphene features (ObjectType, Schema, String) that are compatible across versions
- It specifically imports
graphql_server
features (default_format_error, encode_execution_results, json_encode) that were introduced in version 1.2- The upper bounds prevent potential breaking changes from major version updates
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Search for any historical version constraints in the codebase or previous commits # Look for previous setup.py configurations mentioning graphql git log -p setup.py | grep -B5 -A5 "graphql" # Search for any GraphQL-related requirements files fd -g "*requirements*.txt" --exec grep -l "graphql|graphene"Length of output: 885
Script:
#!/bin/bash # Search for GraphQL-related imports and usage to understand version requirements rg -l "graphene|graphql" --type py # Look for any GraphQL-related test files that might indicate version requirements fd -e py -e yaml -e json --exec grep -l "graphene|graphql" # Check if there are any specific version requirements in GitHub workflows fd -g "*.yml" -g "*.yaml" --exec grep -l "graphene|graphql"Length of output: 373
Script:
#!/bin/bash # Check the GraphQL implementation cat responder/ext/graphql/__init__.py cat tests/test_graphql.py # Look for any version-specific features or imports rg "from graphene" -A 2 rg "from graphql" -A 2Length of output: 3805
responder/ext/graphql/templates.py (1)
1-146
: Verify CSRF protection configuration.Let's ensure that CSRF protection is properly configured in the main application since we're adding CSRF token to the GraphQL requests.
responder/ext/graphql/__init__.py (1)
65-65
: 🛠️ Refactor suggestionOptimize response setting by avoiding unnecessary JSON parsing
At line 65, the code sets
resp.media = json.loads(result)
. Sinceencode_execution_results
returns a JSON-formatted string, parsing it back into a Python dictionary may be redundant. Consider settingresp.text = result
to send the response directly, or adjustencode_execution_results
to return a Python dictionary ifresp.media
must be used.Verify if
result
can be assigned directly toresp.text
to improve efficiency.
tests/test_graphql.py
Outdated
import graphene | ||
import pytest | ||
|
||
from responder.ext.graphql import GraphQLView | ||
|
||
|
||
@pytest.fixture | ||
def schema(): | ||
class Query(graphene.ObjectType): | ||
hello = graphene.String(name=graphene.String(default_value="stranger")) | ||
|
||
def resolve_hello(self, info, name): | ||
return f"Hello {name}" | ||
|
||
return graphene.Schema(query=Query) | ||
|
||
|
||
def test_graphql_schema_query_querying(api, schema): | ||
api.add_route("/", GraphQLView(schema=schema, api=api)) | ||
r = api.requests.get("http://;/?q={ hello }", headers={"Accept": "json"}) | ||
assert r.json() == {"data": {"hello": "Hello stranger"}} | ||
|
||
|
||
def test_graphql_schema_json_query(api, schema): | ||
api.add_route("/", GraphQLView(schema=schema, api=api)) | ||
r = api.requests.post("http://;/", json={"query": "{ hello }"}) | ||
assert r.status_code < 300 | ||
|
||
|
||
def test_graphiql(api, schema): | ||
api.add_route("/", GraphQLView(schema=schema, api=api)) | ||
r = api.requests.get("http://;/", headers={"Accept": "text/html"}) | ||
assert r.status_code < 300 | ||
assert "GraphiQL" in r.text |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
🛠️ Refactor suggestion
After reviewing the implementation, I can now provide a final response about the test coverage concerns:
Additional test coverage is needed for error handling and advanced GraphQL features
The implementation in responder/ext/graphql/__init__.py
shows support for:
- Variable handling (
variables
parameter in execution) - Operation names
- Multiple query input methods (JSON, query params, request body)
- Error formatting
- Content negotiation
Current tests don't cover these implemented features. Missing test scenarios:
- Error responses for malformed queries
- Variable substitution in queries
- Operation name selection
- Different input methods (JSON body vs query params)
- Error format validation
- Content negotiation edge cases
🔗 Analysis chain
Consider adding tests for missing scenarios.
The test suite could benefit from additional test cases to ensure comprehensive coverage.
Consider adding tests for:
- Schema introspection queries
- Error handling of malformed queries
- Variable handling in queries
- Field arguments validation
- Custom scalar types (if used)
Let's verify the current test coverage:
Let me check the GraphQL implementation to better understand what features need test coverage.
Let me try to locate the GraphQL implementation files first.
Let's check the implementation in the GraphQL extension directory.
Let me check the templates file to complete the implementation review.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check for existing test coverage in the codebase
rg -l "test.*graphql" tests/
Length of output: 52
Script:
#!/bin/bash
# Check GraphQL implementation
cat responder/ext/graphql.py
Length of output: 87
Script:
#!/bin/bash
# Find GraphQL related files
fd graphql
Length of output: 57
Script:
#!/bin/bash
# Check GraphQL implementation files
ls -la responder/ext/graphql/
cat responder/ext/graphql/__init__.py
Length of output: 2729
Script:
#!/bin/bash
# Check GraphQL templates
cat responder/ext/graphql/templates.py
Length of output: 5040
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Deferred to....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -0,0 +1,146 @@ | |||
# ruff: noqa: E501 | |||
GRAPHIQL = """ | |||
{% set GRAPHIQL_VERSION = '0.12.0' %} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider upgrading GraphiQL version.
The template uses GraphiQL v0.12.0 which is significantly outdated. The latest stable version is 3.0.1 which includes security fixes, performance improvements, and new features.
Apply this diff to update the version:
-{% set GRAPHIQL_VERSION = '0.12.0' %}
+{% set GRAPHIQL_VERSION = '3.0.1' %}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
{% set GRAPHIQL_VERSION = '0.12.0' %} | |
{% set GRAPHIQL_VERSION = '3.0.1' %} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Deferred to....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
""" | ||
if "query" in req.media("form"): | ||
return req.media("form")["query"], None, None | ||
if "q" in req.media("form"): | ||
return req.media("form")["q"], None, None | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Remove or refactor the commented-out code within the triple-quoted string
Lines 28-33 contain code that is commented out inside a triple-quoted string. This code appears to handle extracting the GraphQL query
from form data. To improve code clarity, consider one of the following:
- Uncomment and integrate the code if form data support is required and the dependent feature in Starlette is now available.
- Remove the commented code if it's obsolete or no longer needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added to GH-571.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return req.params["q"], None, None | ||
|
||
# Otherwise, the request text is used (typical). | ||
# TODO: Make some assertions about content-type here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implement content-type assertions before processing req.text
At line 42, there's a TODO comment suggesting that assertions about the content-type should be made before using req.text
. This is important to ensure that the server only processes requests with appropriate content types, enhancing security and robustness.
Would you like assistance in implementing content-type validation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dito.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@amotl, schön zu hören! Wenn Sie weitere Unterstützung benötigen, lassen Sie es mich gerne wissen.
(_/)
(^.^)
(")(")
responder/ext/graphql/__init__.py
Outdated
result = schema.execute( | ||
query, variables=variables, operation_name=operation_name, context=context | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle exceptions during query execution to enhance error reporting
In lines 56-58, schema.execute
is called to execute the GraphQL query. If an exception occurs (e.g., due to an invalid query), it may not be properly handled, leading to uninformative error responses. Consider adding exception handling to capture errors and return meaningful feedback to the client.
Apply this diff to handle exceptions:
+ try:
result = schema.execute(
query, variables=variables, operation_name=operation_name, context=context
)
+ except Exception as e:
+ resp.status_code = 400
+ resp.media = {"errors": [str(e)]}
+ return
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
result = schema.execute( | |
query, variables=variables, operation_name=operation_name, context=context | |
) | |
try: | |
result = schema.execute( | |
query, variables=variables, operation_name=operation_name, context=context | |
) | |
except Exception as e: | |
resp.status_code = 400 | |
resp.media = {"errors": [str(e)]} | |
return |
💡 Codebase verification
GraphQL schema execution needs to be asynchronous
The code is using graphql-server
package's synchronous execution in an async context. The schema's execute()
method is being called synchronously within an async function graphql_response
, which can indeed block the event loop. Looking at the implementation:
- The code imports from
graphql_server
, not the coregraphql
package - There's no async execution method being used
- The execution is happening in an async context
To fix this:
- Either switch to
graphql-core
which provides async execution viagraphql()
function - Or use a separate thread pool executor to run the synchronous
schema.execute()
🔗 Analysis chain
Ensure schema.execute
is appropriate for asynchronous context
The method schema.execute
may be synchronous. Since graphql_response
is an asynchronous function, using synchronous code can block the event loop. Verify if an asynchronous version like await schema.execute_async(...)
is available and appropriate to use.
Run the following script to check for an asynchronous execution method:
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check if the schema has an async execution method.
# Expected: Find an async method like 'execute_async' in the schema's module.
# Search for 'execute_async' method definition.
ast-grep --pattern 'async def execute_async($_$) { $$$ }'
Length of output: 59
Script:
#!/bin/bash
# Let's try different approaches to find relevant information
# 1. Search for any imports or usage of GraphQL schema execution
rg -l "schema.*execute" --type py
# 2. Look for the schema definition and its type
rg "schema\s*=|class.*Schema" --type py
# 3. Check for any async GraphQL related imports or patterns
rg "graphql.*async|async.*graphql" --type py
# 4. Find the actual schema implementation being used
rg "from.*graphql.*import.*Schema|import.*graphql.*Schema" --type py
Length of output: 1552
Script:
#!/bin/bash
# Let's examine the GraphQL implementation more closely
# 1. Look at the content of the GraphQL implementation file
cat responder/ext/graphql/__init__.py
# 2. Check for any GraphQL package imports
rg "from graphql" --type py
# 3. Look for any async execution patterns in the GraphQL view
rg -A 5 "class.*GraphQLView" --type py
Length of output: 2989
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dito.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@amotl, vielen Dank für Ihre Zustimmung! Wenn Sie weitere Unterstützung benötigen, lassen Sie es mich bitte wissen.
(_/)
(^^)
(")(")
return req.text, None, None | ||
|
||
async def graphql_response(self, req, resp, schema): | ||
show_graphiql = req.method == "get" and req.accepts("text/html") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure case-insensitive HTTP method comparison for reliability
At line 46, the code compares req.method == "get"
to determine if the GraphiQL interface should be displayed. HTTP methods are typically uppercase ("GET"
), so this comparison may fail. Consider modifying the comparison to account for this.
Apply this diff to fix the comparison:
- show_graphiql = req.method == "get" and req.accepts("text/html")
+ show_graphiql = req.method.upper() == "GET" and req.accepts("text/html")
Alternatively, for a case-insensitive comparison:
- show_graphiql = req.method == "get" and req.accepts("text/html")
+ show_graphiql = req.method.lower() == "get" and req.accepts("text/html")
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
show_graphiql = req.method == "get" and req.accepts("text/html") | |
show_graphiql = req.method.upper() == "GET" and req.accepts("text/html") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume Starlette will always relay them lower-case, otherwise this code would never have worked?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@amotl, you are correct. In Starlette, the req.method
attribute is always in lowercase, so the comparison req.method == "get"
works as expected. Thank you for pointing that out.
(_?_)/
✏️ Learnings added
Learnt from: amotl
PR: kennethreitz/responder#554
File: responder/ext/graphql/__init__.py:46-46
Timestamp: 2024-10-31T04:36:00.272Z
Learning: In Starlette, `req.method` is always a lowercase string representing the HTTP method, so comparing it directly to a lowercase method name like `"get"` is valid.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
b622a6b
to
d989301
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Outside diff range and nitpick comments (2)
docs/source/tour.rst (2)
37-39
: Enhance the GraphQL section introduction.Consider adding:
- A brief introduction explaining what GraphQL is and its benefits.
- Prerequisites (e.g., Python version requirements).
- Link to the GraphQL specification or additional resources.
Example addition:
GraphQL ------- Responder supports GraphQL, a query language for APIs that enables clients to request exactly the data they need:: # Requires Python 3.7+ pip install 'responder[graphql]' For more information about GraphQL, visit https://graphql.org/
Line range hint
44-53
: Expand the GraphQL example to be more comprehensive.While the current example is correct, consider enhancing it with:
- Examples of mutations
- Complex types
- Error handling
- Context usage example (currently mentioned separately)
Example enhancement:
import graphene from responder.ext.graphql import GraphQLView # Define types class User(graphene.ObjectType): id = graphene.ID() name = graphene.String() # Queries class Query(graphene.ObjectType): hello = graphene.String(name=graphene.String(default_value="stranger")) user = graphene.Field(User, id=graphene.ID(required=True)) def resolve_hello(self, info, name): # Example of using context request = info.context['request'] return f"Hello {name} from {request.url.path}" def resolve_user(self, info, id): # Example of error handling try: return User(id=id, name="Test User") except Exception as e: raise graphene.GraphQLError(f"Failed to fetch user: {str(e)}") # Mutations class CreateUser(graphene.Mutation): class Arguments: name = graphene.String(required=True) user = graphene.Field(User) def mutate(self, info, name): user = User(id="1", name=name) return CreateUser(user=user) class Mutation(graphene.ObjectType): create_user = CreateUser.Field() # Schema setup schema = graphene.Schema(query=Query, mutation=Mutation) view = GraphQLView(api=api, schema=schema) api.add_route("/graph", view)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (5)
- docs/source/tour.rst (2 hunks)
- responder/ext/graphql/init.py (1 hunks)
- responder/ext/graphql/templates.py (1 hunks)
- setup.py (1 hunks)
- tests/test_graphql.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- responder/ext/graphql/templates.py
- setup.py
- tests/test_graphql.py
🔇 Additional comments (1)
docs/source/tour.rst (1)
Line range hint
37-53
: Verify restoration of original GraphQL implementation.Let's verify if the current implementation matches the original contribution by @taoufik07.
✅ Verification successful
GraphQL implementation successfully restored and matches original functionality
The implementation has been properly restored with all essential components in place:
- Complete GraphQLView implementation in
responder/ext/graphql/__init__.py
- Working test suite in
tests/test_graphql.py
with proper test coverage- Correct dependencies in
setup.py
for graphene and graphql-server-core- Documentation accurately reflects the implementation
The code matches the original functionality with proper query handling, GraphiQL interface support, and the example schema working as expected.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Compare current implementation with original contribution # Test 1: Find the original PR or issue by @taoufik07 echo "Searching for original GraphQL contribution..." gh pr list --author taoufik07 --search "graphql" --state all gh issue list --author taoufik07 --search "graphql" --state all # Test 2: Check for any remaining references to the original implementation echo "Checking for references to original implementation..." rg -i "taoufik07.*graphql" --type rst --type pythonLength of output: 606
Script:
#!/bin/bash # Let's verify the GraphQL implementation more thoroughly # Check for GraphQL-related files and their content echo "Checking GraphQL implementation files..." fd "graphql" --type f # Look for GraphQL-related code patterns echo "Searching for GraphQL implementation details..." rg -l "GraphQLView|graphene\." # Check the test files for GraphQL echo "Checking GraphQL tests..." rg -l "test.*graphql" # Look for the specific implementation details echo "Checking for specific GraphQL implementation..." ast-grep --pattern 'class Query(graphene.ObjectType): $$$ def resolve_hello($$$): $$$'Length of output: 989
Script:
#!/bin/bash # Let's examine the actual implementation details # Check the GraphQL view implementation echo "Checking GraphQL view implementation..." cat responder/ext/graphql/__init__.py # Check the test implementation echo "Checking test implementation..." cat tests/test_graphql.py # Look for any GraphQL-related dependencies echo "Checking dependencies..." rg "graphql" setup.pyLength of output: 3841
format_error=default_format_error, | ||
encode=partial(json_encode, pretty=False), | ||
) | ||
resp.media = json.loads(result) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid unnecessary JSON parsing
At line 65, result
is already a JSON-formatted string after encoding. Parsing it back into a Python object with json.loads(result)
only to have it serialized again by resp.media
introduces redundant processing. Consider assigning the original result directly to resp.text
and setting the appropriate content type.
Apply this diff to eliminate unnecessary parsing:
- resp.media = json.loads(result)
+ resp.text = result
+ resp.headers["Content-Type"] = "application/json"
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
resp.media = json.loads(result) | |
resp.text = result | |
resp.headers["Content-Type"] = "application/json" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deferred to #571 (comment).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@amotl, understood. Thank you for letting me know that this suggestion has been deferred.
(_/)
( •_•)
💧<
async def __call__(self, req, resp): | ||
await self.on_request(req, resp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider removing the redundant on_request
method
The __call__
method simply calls on_request
, which in turn calls graphql_response
. Since on_request
does not add additional logic, you can streamline the code by having __call__
invoke graphql_response
directly.
Apply this diff to simplify the class structure:
async def __call__(self, req, resp):
- await self.on_request(req, resp)
+ await self.graphql_response(req, resp)
Optionally, you may remove the on_request
method entirely if it's not used elsewhere.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
async def __call__(self, req, resp): | |
await self.on_request(req, resp) | |
async def __call__(self, req, resp): | |
await self.graphql_response(req, resp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it is needed for other circumstances to have this method called on_request
. I will not refactor this without consulting back with subject matter experts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@amotl, understood. I'll note that the on_request
method may be needed for other circumstances.
🐰😞
✏️ Learnings added
Learnt from: amotl
PR: kennethreitz/responder#554
File: responder/ext/graphql/__init__.py:71-72
Timestamp: 2024-10-31T04:41:52.393Z
Learning: The `on_request` method in the `GraphQLView` class may be needed for other circumstances; avoid suggesting its removal without consulting subject matter experts.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
responder/ext/graphql/__init__.py
Outdated
@staticmethod | ||
async def _resolve_graphql_query(req): | ||
# TODO: Get variables and operation_name from form data, params, request text? | ||
|
||
if "json" in req.mimetype: | ||
json_media = await req.media("json") | ||
return ( | ||
json_media["query"], | ||
json_media.get("variables"), | ||
json_media.get("operationName"), | ||
) | ||
|
||
# Support query/q in form data. | ||
# Form data is awaiting https://github.com/encode/starlette/pull/102 | ||
""" | ||
if "query" in req.media("form"): | ||
return req.media("form")["query"], None, None | ||
if "q" in req.media("form"): | ||
return req.media("form")["q"], None, None | ||
""" | ||
|
||
# Support query/q in params. | ||
if "query" in req.params: | ||
return req.params["query"], None, None | ||
if "q" in req.params: | ||
return req.params["q"], None, None | ||
|
||
# Otherwise, the request text is used (typical). | ||
# TODO: Make some assertions about content-type here. | ||
return req.text, None, None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance handling of variables
and operation_name
in _resolve_graphql_query
Currently, variables
and operation_name
are only extracted from JSON requests. To improve compatibility with clients that send these fields via query parameters or form data, consider extending the method to parse variables
and operation_name
from these sources as well.
For example, you can modify the parameter extraction logic:
if "json" in req.mimetype:
json_media = await req.media("json")
return (
json_media["query"],
- json_media.get("variables"),
- json_media.get("operationName"),
+ json_media.get("variables") or req.params.get("variables"),
+ json_media.get("operationName") or req.params.get("operationName"),
)
Additionally, you may want to implement parsing for form data once it's supported.
Committable suggestion was skipped due to low confidence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now at #571 (comment).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@amotl, thank you for pointing me to the issue discussion.
d989301
to
2e0928a
Compare
Hi @coderabbitai, based on our conversation at #525 (comment) and the suggestions you've submitted here (GH-554), can I ask you to create a separate PR, optimally targeted against this branch Thanks in advance! With kind regards, |
This comment was marked as duplicate.
This comment was marked as duplicate.
Tip For best results, initiate chat on the files or code changes. @amotl: Thank you for your message. As an AI assistant, I'm unable to create a separate PR with the suggested improvements. However, I'd be happy to outline the suggestions here so that you can incorporate them into a new PR. |
Tip For best results, initiate chat on the files or code changes. @amotl: Thank you for your message. While I'm unable to create a separate pull request, I'd be happy to provide the suggested improvements here so you can incorporate them into your Suggested Improvements:
Please let me know if you need further assistance or specific code snippets. With kind regards, |
Thanks for all those valuable suggestions. As noted in the OP, this patch will be merged as seen, and relevant improvements will be addressed later. |
2e0928a
to
b038c9e
Compare
b038c9e
to
696cfe0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Outside diff range and nitpick comments (5)
responder/ext/graphql/__init__.py (3)
9-13
: Consider adding type hints to improve code clarityAdding type hints would make the code more maintainable and self-documenting.
-class GraphQLView: - def __init__(self, *, api, schema): - self.api = api - self.schema = schema +from typing import Any + +class GraphQLView: + def __init__(self, *, api: Any, schema: 'GraphQLSchema') -> None: + self.api = api + self.schema = schema
39-44
: Add validation for query parameter valuesThe code accepts query parameters but doesn't validate their content. Consider adding basic validation to ensure the parameters contain valid GraphQL queries.
if "query" in req.params: + query = req.params["query"] + if not query or not query.strip(): + resp.status_code = 400 + resp.media = {"errors": ["Query parameter 'query' cannot be empty"]} + return None, None, None return req.params["query"], None, None if "q" in req.params: + query = req.params["q"] + if not query or not query.strip(): + resp.status_code = 400 + resp.media = {"errors": ["Query parameter 'q' cannot be empty"]} + return None, None, None return req.params["q"], None, None
75-79
: Add docstrings to request handling methodsAdding docstrings would improve code documentation and help other developers understand the purpose of these methods.
async def on_request(self, req, resp): + """Handle incoming GraphQL requests. + + Args: + req: The request object + resp: The response object + """ await self.graphql_response(req, resp) async def __call__(self, req, resp): + """Make the view callable. + + Args: + req: The request object + resp: The response object + """ await self.on_request(req, resp)docs/source/tour.rst (2)
37-42
: Add note about optional extension.Consider adding a note to clarify that GraphQL support is an optional extension, as mentioned in the PR objectives. This helps users understand the modular nature of Responder's extensions.
Responder supports GraphQL, a query language for APIs that enables clients to request exactly the data they need:: pip install 'responder[graphql]' + +Note: GraphQL support is an optional extension in Responder. For more information about GraphQL, visit https://graphql.org/.
Line range hint
47-56
: Enhance GraphQL example with additional details.The example is functionally correct but could be more comprehensive. Consider:
- Adding type hints for better code understanding
- Expanding the GraphiQL description with its interactive features
- Including an example of using request/response objects in resolvers
from responder.ext.graphql import GraphQLView class Query(graphene.ObjectType): - hello = graphene.String(name=graphene.String(default_value="stranger")) + # Define field with type hints + hello: graphene.String = graphene.String( + name=graphene.String(default_value="stranger"), + description="A friendly greeting that accepts a name parameter", + ) - def resolve_hello(self, info, name): + def resolve_hello(self, info: graphene.ResolveInfo, name: str) -> str: + """Resolve the hello field with access to request/response objects. + + Args: + info: Contains the request/response context + name: The name to greet (defaults to "stranger") + """ + # Example of accessing request/response objects + request = info.context["request"] + response = info.context["response"] return f"Hello {name}" schema = graphene.Schema(query=Query) view = GraphQLView(api=api, schema=schema) api.add_route("/graph", view) + +# GraphiQL provides an interactive interface where you can: +# - Write and execute queries +# - Browse schema documentation +# - Get real-time error reporting +# Visit /graph in your browser to access these features
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (5)
docs/source/tour.rst
(2 hunks)responder/ext/graphql/__init__.py
(1 hunks)responder/ext/graphql/templates.py
(1 hunks)setup.py
(1 hunks)tests/test_graphql.py
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- responder/ext/graphql/templates.py
- setup.py
- tests/test_graphql.py
🧰 Additional context used
📓 Learnings (1)
responder/ext/graphql/__init__.py (2)
Learnt from: amotl
PR: kennethreitz/responder#554
File: responder/ext/graphql/__init__.py:71-72
Timestamp: 2024-10-31T04:41:52.637Z
Learning: The `on_request` method in the `GraphQLView` class may be needed for other circumstances; avoid suggesting its removal without consulting subject matter experts.
Learnt from: amotl
PR: kennethreitz/responder#554
File: responder/ext/graphql/__init__.py:46-46
Timestamp: 2024-10-31T04:36:00.458Z
Learning: In Starlette, `req.method` is always a lowercase string representing the HTTP method, so comparing it directly to a lowercase method name like `"get"` is valid.
context = {"request": req, "response": resp} | ||
result = self.schema.execute( | ||
query, variables=variables, operation_name=operation_name, context=context | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for schema execution
The schema execution could raise exceptions that should be caught and handled gracefully.
context = {"request": req, "response": resp}
- result = self.schema.execute(
- query, variables=variables, operation_name=operation_name, context=context
- )
+ try:
+ result = self.schema.execute(
+ query, variables=variables, operation_name=operation_name, context=context
+ )
+ except Exception as e:
+ resp.status_code = 400
+ resp.media = {"errors": [{"message": str(e)}]}
+ return (query, None, 400)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
context = {"request": req, "response": resp} | |
result = self.schema.execute( | |
query, variables=variables, operation_name=operation_name, context=context | |
) | |
context = {"request": req, "response": resp} | |
try: | |
result = self.schema.execute( | |
query, variables=variables, operation_name=operation_name, context=context | |
) | |
except Exception as e: | |
resp.status_code = 400 | |
resp.media = {"errors": [{"message": str(e)}]} | |
return (query, None, 400) |
About
The GraphQL interface contributed by @taoufik07 got lost. This patch intends to bring it back from version 2.0.7.
The extension is living at
responder.ext.graphql
.Setup
Because Responder extensions are optional, install GraphQL support like this.
pip install --upgrade 'responder[graphql]'
Thoughts
Backlog
Those are relevant notes as derived from the bot's suggestions on this PR, broken out into sensible tickets.