Django Graphene not handling errors - python

I have a GraphQL system associated with a Django app that seems to be working fine, except that it's completely ignoring errors in mutations. That is, if the mutation executes with no errors, everything behaves as expected. But if I raise an exception on the first line of the mutation, I don't get any indication of the error -- nothing in the app logs, and the graphQL response is just a json will null contents, e.g.:
{
"data": {
"exampleMutation": {
"mutationResponseSchema": null
}
}
}
Even if I wrap the django pieces (e.g. trying to get a filterset) in a try: except:, the behavior is the same as if I raise the exception. IOW, an exception being thrown (even if it's handled) seems to trigger an empty response being sent.
I am at a total loss for where these exceptions are going -- it seems that the behavior on encountering an exception is to ignore it and just return a null JSON.
Furthermore, I have an app with the same basic layout but built off an older image of Python (3.8 vs. this one at 3.11, so the django/graphene versions and related dependencies are newer). The old app handled exceptions as usual and would return messages via the endpoints when raised using Django/Graphene classes with the same structure as the one I'm having the problem with.
I don't know if something changed in graphene's error handling, but I can't seem to find a clear answer to that.
For example, if I write the following mutation:
class ExampleMutation(graphene.Mutation):
class Arguments:
fail_here = graphene.String()
some_schema = graphene.Field(SomeSchema)
#authorized
def mutate(root, info, user, **kwargs):
# could also just raise Exception('automatic exception') here and get same behavior.
if kwargs.get('fail_here') == 'yes':
raise Exception('text') # Doesn't seem to matter what exception is raised
else:
django_model = SomeSchemaModel.objects.first()
return ExampleMutation(some_schema=django_model)
The response to e.g.
mutation exampleMutation($failHere: String){
exampleMutation(
failHere: $failHere,
) {
someSchema
{
field1
field2
}
}
}
is valid and behaves as expected if the mutation is called with e.g. {"failHere": "No"}. Ergo, the structure of the graphQL/Django stuff is not the problem.
The problem is that when the endpoint is called with {"failHere": "yes"} (or if I just raise an error on the first line of the mutation), the response is:
{
"data": {
"exampleMutation": {
"someSchema": null
}
}
}

The above might be a bug, I posted to github. But in case this comes up for someone else, this is the workaround that got things working:
In django's settings (with appropriate SCHEMA for your app):
GRAPHENE = {
'SCHEMA': 'core.graphql.index.schema',
'MIDDLEWARE': ['graphene_django.debug.middleware.DjangoDebugMiddleware'],
}
I'm not certain of this, but I found from some searching that perhaps with newer versions of graphene-django, the DjangoDebugMiddleware middleware is required to send these exceptions in the JSON response.
In any case, the real problem is that the GraphQLView handler seems to need the middleware sent not as a list (as the settings shown above require), but instead as double list, which can be achieved by overriding the instantiation like this:
from graphene_django.views import GraphQLView, graphene_settings
class GQLView(GraphQLView):
def __init__(self, *args, **kwargs):
kwargs.update({'middleware':[graphene_settings.MIDDLEWARE]}) #note the extra list level
and then in urls.py you'd have something like:
urlpatterns = [
# Graphql
(r'graphql', GQLView.as_view())
]

Related

Validating query string parameters and request body in AWS lambda using webargs

I am trying to figure out ways of validating query string parameters for an API created using AWS API gateway and backed by a Python Lambda function. API Gateway can validate the presence of the required query string parameters. However, I could not find a way for additional validations such as determining if the length of a certain parameter is within some limit (e.g. config_id should be minimum 7 characters long). Such validations are possible for the request body using the API Gateway request validation. Refer this link. However, for the query string paramaters only required/not required validation is possible as it does not use any json schema for validation.
Hence, to overcome this issue, I decided to try the webargs module in Python for validating the query string parameters. It is generally used for request validations for APIs created using Python frameworks such as flask or django. I am using the core parser (Refer webargs doc) as follows:
from webargs import fields, validate, core, ValidationError
parser = core.Parser()
params = {"config_id": fields.Str(required=True, validate=lambda p: len(p) >= 7)}
def main(event, context: Dict):
try:
# print(event["queryStringParameters"])
input_params = event.get("queryStringParameters")
print("queryStringParameters: ", str(input_params))
if input_params is None:
input_params = {}
parsed_params = parser.parse(params, input_params)
print("parsedParams: ", str(parsed_params))
except ValidationError as e:
return {
"statusCode": 400,
"headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": True,
"x-amzn-ErrorType": "ValidationError",
},
"body": str(e),
}
This is how the validation is done in the lambda function. However, only the required validation works correctly. When I pass a config_id of length 5 it does not return any error and proceeds further in the lambda function.
What could be going wrong with this? The parser seems to work, however, the validate function doesn't.
Any help is appreciated as I am new to this. Also, is there a better way of doing validations in lambda functions especially for queryStringParameters? It can be handled by the code, but we can have many parameters and many APIs which makes writing code for all such validations a cumbersome task. The webargs module comes in handy.
webargs Library is mostly used for validating HTTP Requests coming via popular Python frameworks like Flask, Django, Bottle etc. The core Parser that you are trying to use should not be used directly as it does not have the methods like load_json, load_query etc implemented (Source code showing the missing implementation here). There are child class implementations of the core parser for each of the frameworks, but using them on API GW does not make sense.
So it's better to use a simpler json validation library like jsonschema. I've modified your code to use jsonschema instead of webargs as follows -
from jsonschema import validate, ValidationError
schema = {
"type" : "object",
"properties" : {
"queryStringParameters" : {
"type" : "object",
"properties": {
"config_id": {
"type": "string",
"minLength": 7,
}
}
},
},
}
def main(event, context):
try:
validate(instance=event, schema=schema)
except ValidationError as e:
return {
"statusCode": 400,
"headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": True,
"x-amzn-ErrorType": "ValidationError",
},
"body": e.message,
}

Sentry - scrubbing local variables sensitive data

I would like to scrub sensitive data from Python before I send it to Sentry
However, in method before_send and truncate_breadcrumb_message I am not sure where I can get the list of local variables and scrub them.
sentry_sdk.init(
dsn=settings.get('SENTRY_DSN', ""),
before_breadcrumb=truncate_breadcrumb_message,
integrations=[FlaskIntegration()],
before_send=sanitize_sentry_event,
)
def sanitize_sentry_event(event, hint):
pass
def truncate_breadcrumb_message(crumb, hint):
pass
def raise_execption(password):
auth = 5
raise Exception()
In the above method, I wouldn't want password and auth to be send to Sentry at all.
How can I do it?
event is a JSON payload that contains the same exact JSON you see in the "JSON" download in Sentry's UI. So you have a event like this:
{
"exception": {
"values": [
{
"stacktrace": {
"frames": [
{"vars": ...}
]
}
}
]
}
}
And you want to remove vars, you need to do this:
def sanitize_sentry_event(event, hint):
for exception in event.get("exception", {}).get("values", []):
for frame in exception.get("stacktrace", {}).get("frames", []):
frame.pop("vars", None)
for exception in event.get("threads", {}).get("values", []):
for frame in exception.get("stacktrace", {}).get("frames", []):
frame.pop("vars", None)
return event
You probably want to wrap the entire function body with a try-except. If the function raises an exception the event is dropped. Make sure to test this using init(debug=True) to see all exceptions your before_send hook might throw
found it here
Code for anyone who migrated from raven and wants to use raven processors / sanitize_keys
from raven.processors import SanitizeKeysProcessor, SanitizePasswordsProcessor
class FakeRavenClient:
sanitize_keys = [
'card_number',
'card_cvv',
'card_expiration_date',
]
processors = [
SanitizePasswordsProcessor(FakeRavenClient),
SanitizeKeysProcessor(FakeRavenClient),
]
def before_send(event, hint):
for processor in processors:
event = processor.process(event)
return event
sentry_sdk.init(
before_send=before_send,
)

Nested Dict As HttpRequest Django

I am trying to write some test cases for some code I've developed using Elasticsearch and Django. The concept is straightforward - I just want to test a get request, which will be an Elasticsearch query. However, I am constructing the query as a nested dict. When I pass the nested dict to the Client object in the test script it gets passed through Django until it ends up at the urlencode function which doesn't look like it can handle nested dicts only MultivalueDicts. Any suggestions or solutions? I don't want to use any additional packages as I don't want to depend on potentially non-supported packages for this application.
Generic Code:
class MyViewTest(TestCase):
es_connection = elasticsearch.Elasticsearch("localhost:9200")
def test_es_query(self):
client = Client()
query = {
"query": {
"term": {
"city": "some city"
}
}
}
response = client.get("", query)
print(response)
Link for urlencode function: urlencode Django
The issue is clearly at the conditional statement when the urlencode function checks if the dictionary value is a str or bytes object. If it isn't it creates a generator object which can never access the nested portions of the dictionary.
EDIT: 07/25/2018
So I was able to come up with a temporary work around to at least run the test. However, it is ugly and I feel like there must be a better way. The first thing I tried was specifying the content_type and converting the dict to a json string first. However, Django still kicked back and error in the urlencode function.
class MyViewTest(TestCase):
es_connection = elasticsearch.Elasticsearch("localhost:9200")
def test_es_query(self):
client = Client()
query = {
"query": {
"term": {
"city": "some city"
}
}
}
response = client.get("", data=json.dumps(query), content_type="application/json")
print(response)
So instead I had to do:
class MyViewTest(TestCase):
es_connection = elasticsearch.Elasticsearch("localhost:9200")
def test_es_query(self):
client = Client()
query = {
"query": {
"term": {
"city": "some city"
}
}
}
query = json.dumps(query)
response = client.get("", data={"q": query}, content_type="application/json")
print(response)
This let me send the HttpRequest to my View and parse it back out using:
json.loads(request.GET["q"])
Then I was able to successfully get the requested data from Elasticsearch and return it as an HttpResponse. I feel like in Django though there has to be a way to just pass a json formatted string directly to the Client object's get function. I thought specifying the content_type as application/json would work but it still calls the urlencode function. Any ideas? I really don't want to implement this current system into production.

resolve ticket as fixed with python jira api

import jira
def resolve_issue(jira,issue):
jira.transition_issue(issue, '5', assignee={'name': 'pm_user'}, resolution={'id': '3'},comment={'name':"Resolved the ticket."}))
[(u'5', u'Resolve Issue'), (u'821', u'Request Info'), (u'1011', u'Rejected'), (u'1031', u' Duplicate ')]
are the available transitions. does not work to resolve an issue as fixed with python jira rest api. I have tried to list out the transitions, but I don't see 'fixed' resolution id. Any suggestions?
added error below
text: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: N/A; line: -1, column: -1] (through reference chain: com.atlassian.jira.issue.fields.rest.json.beans.CommentJsonBean["body"])
I'm not sure if this is actually causing your problem but you have to wrap your 'assignee' and 'resolution' changes in a "fields" dict. So it would have to be something like:
fields = {
"resolution:: {
"id": "3"
},
"assignee: {
"name": "pm_user"
}
}
jira.transition_issue(issue, fields=fields, comment="Resolved the ticket.")
The JIRA REST API doesn't have very good error handling for transitions and I've seen a number of different errors (usually a 500) when the request is malformed.

400 Error while trying to POST to JIRA issue

I am trying to set the 'transition' property in a JIRA issue from whatever it is, to completed(which according to the doc is 10000). According to the documentation, this error is 'If there is no transition specified.'
Also I have used ?expand=transitions.fields to verify that 10000 is for complete.
using these docs
https://docs.atlassian.com/jira/REST/latest/#api/2/issue-doTransition
https://jira.atlassian.com/plugins/servlet/restbrowser#/resource/api-2-issue-issueidorkey-transitions/POST
Here is my request
url = 'http://MYURL/rest/api/2/issue/ISSUE-ID/transitions'
payload1 = open('data3.json', 'r').read()
payload = json.loads(payload1)
textFile = requests.post(url, auth=('username', 'password'), json=payload)
The contents on my data3.json file are
{
"transition": 10000
}
edit: I also changed my JSON to this and I get a 500 error
{
"transition": {
"id": "10000"
}
}
The error I get
{"errorMessages":["Can not instantiate value of type [simple type,classcom.atlassian.jira.rest.v2.issue.TransitionBean] from JSON integral number;no single-int-arg constructor/factory method (through reference chain:com.atlassian.jira.rest.v2.issue.IssueUpdateBean[\"transition\"])"]}400
I'm pretty confident that my issue is in my json file since I have used GET in the code above this snippit multiple times, but I could be wrong.
Possible cause - https://jira.atlassian.com/browse/JRA-32132
I believe the issue I was having was a process flow one. I cannot jump right from my issue being opened, to 'completed'. However, I can go from the issue being created to 'Done'.
{
"transition": {
"name": "Done",
"id": "151"
}
}
As this does what I need, I will use it. If I find how to make ticket complete I will post back.
Also, I think the fact we customize our JIRA lead to my getting 'Completed' as a valid transition even though it wasn't.
Yes, you're right that the JSON is wrong, it's not even a valid json since the value is not a number, string, object, or array. The doc says:
The fields that can be set on transtion, in either the fields
parameter or the update parameter can be determined using the
/rest/api/2/issue/{issueIdOrKey}/transitions?expand=transitions.fields
resource.
So you need to do a get request on /rest/api/2/issue/{issueIdOrKey}/transitions?expand=transitions.fields to get the list of possible values and then set that in the json
{
"transition": {
"id" : "an_id_from_response"
}
}

Categories

Resources