How to use background tasks with Starlette when there's no background object? - starlette

I'm hoping to avoid any use of Celery at the moment. In Starlette's docs they give two ways to add background tasks:
Via Graphene: https://www.starlette.io/graphql/
class Query(graphene.ObjectType):
user_agent = graphene.String()
def resolve_user_agent(self, info):
"""
Return the User-Agent of the incoming request.
"""
user_agent = request.headers.get("User-Agent", "<unknown>")
background = info.context["background"]
background.add_task(log_user_agent, user_agent=user_agent)
return user_agent
Via a JSON response: https://www.starlette.io/background/
async def signup(request):
data = await request.json()
username = data['username']
email = data['email']
task = BackgroundTask(send_welcome_email, to_address=email)
message = {'status': 'Signup successful'}
return JSONResponse(message, background=task)
Does anyone know of a way to add tasks to Starlette's background with Ariadne? I am unable to return a JSONResponse in my resolver, and I do not have access to a info.context["background"]. The only thing I have attached to my context is my request object.

Solved!
Starlette Middleware:
class BackgroundTaskMiddleware(BaseHTTPMiddleware):
async def dispatch(
self, request: Request, call_next: RequestResponseEndpoint
) -> Response:
request.state.background = None
response = await call_next(request)
if request.state.background:
response.background = request.state.background
return response
Ariadne Resolver:
#query.field("getUser")
#check_authentication
async def resolve_get_user(user, obj, info):
task = BackgroundTasks()
task.add_task(test_func)
task.add_task(testing_func_two, "I work now")
request = info.context["request"]
request.state.background = task
return True
async def test_func():
await asyncio.sleep(10)
print("once!!")
async def testing_func_two(message: str):
print(message)
The functions still execute synchronously, but because they're background tasks I'm not too worried.
More discussion here.

The above which is marked as a solution does not work for me since BackgroundTask does not work properly when you use a middleware that subclasses BaseHTTPMiddleware see here:
https://github.com/encode/starlette/issues/919
In my case basically the task is not ran in the background and it is awaited to be completed, also I am not using Ariadne, but this should let you do the job and run a task in the background
Edit:
This worked for me.
executor = ProcessPoolExecutor()
main.executor.submit(
bg_process_funcs,
export_file_format,
export_headers,
data,
alert_type,
floor_subtitle,
date_subtitle,
pref_datetime,
pref_timezone,
export_file_name,
export_limit,)
executor.shutdown()
logger.info("Process Pool Shutdown")

Related

Django/Django Channels - weird looking json response with double \ between each field

Hello I'm trying to do a real time friend request notification system and having this weird looking json response. I'm new to backend development and django (1st year software engineering student). Im just wondering if this is normal since i havent seen anything like this and if theres a way to fix it. Ive worked on a chat app before but it was just all text messages and so I got confused when it comes to django models. I have tried multiple ways I found but only this works. I think it might be because I called json.dumps twice but if i remove either of them, it wont work. Thank you
When a user sends a friend request, this is what i got back from the web socket(with double \ for each field)
Heres the code
//views.py
class SendRequestView(views.APIView):
permission_class = (permissions.IsAuthenticated,)
def post(self, request, *args, **kwargs):
receiver_username = self.kwargs['receiver_username']
if receiver_username is not None:
receiver = get_object_or_404(User, username=receiver_username)
request = ConnectRequest.objects.create(sender=self.request.user, receiver=receiver)
notification = ConnectNotification.objects.create(type='connect request', receiver=receiver, initiated_by=self.request.user)
channel_layer = get_channel_layer()
channel = f'notifications_{receiver.username}'
async_to_sync(channel_layer.group_send)(
channel, {
'type': 'notify',
'notification': json.dumps(ConnectNotificationSerializer(notification).data, cls=DjangoJSONEncoder),
}
)
data = {
'status': True,
'message': 'Success',
}
return JsonResponse(data)
// consumer.py
class ConnectNotificationConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
user = self.scope['user']
group_layer = f'notifications_{user.username}'
await self.accept()
await self.channel_layer.group_add(group_layer, self.channel_name)
async def disconnect(self, close_code):
user = self.scope['user']
group_layer = f'notifications_{user.username}'
await self.channel_layer.group_discard(group_layer, self.channel_name)
async def notify(self, event):
notification = event['notification']
await self.send(text_data=json.dumps({
'notification': notification
})
)

How to test that asyncio.Queue did NOT get something pushed

I'm currently writing some async tests with pytest and found myself running into the following situation.
Consider we have an asyncio.Queue called peer2_subscriber that we want to check if it received a certain message (after triggering some action, omitted for brevity)
peer, cmd, msg = await asyncio.wait_for(
peer2_subscriber.get(),
timeout=1,
)
assert peer == peer2
assert isinstance(cmd, Transactions)
assert msg[0].hash == txs[0].hash
Now, consider that I want to test that another asyncio.Queue did NOT something pushed.
I found myself creating such a helper method.
async def wait_with_fallback(fn, fallback):
try:
return await asyncio.wait_for(
fn(),
timeout=1
)
except asyncio.TimeoutError:
return fallback
And then in the test I write something like:
val = await wait_with_fallback(
peer1_subscriber.get,
None
)
assert val == None
I wonder if there's an existing pattern that I'm missing?
Your pattern works, so I would say it's "correct", for certain values of correct… It's mostly stylistic views here. I would write either
await asyncio.sleep(1)
assert peer1_subscriber.empty()
or
await asyncio.sleep(1)
val = peer1_subscriber.get_nowait()
assert val is None

Format ValidationError in Serializer

DjangoRestFramework seems to handle errors with a variety of ways. The ValidationError in the serializer class does not consistently return JSON the same.
Current response includes a JSON list/object string:
{"detail":["Unable to log in with provided credentials."]}
Looking to achieve:
{"detail":"Unable to log in with provided credentials."}
I realize that this response is a result of default functions. However, I've overridden the validate function:
class AuthCustomTokenSerializer(serializers.Serializer):
username = serializers.CharField(write_only=True)
password = serializers.CharField(write_only=True)
token = serializers.CharField(read_only=True)
def validate(self, validated_data):
username = validated_data.get('username')
password = validated_data.get('password')
# raise serializers.ValidationError({'detail': 'Unable to log in with provided credentials.'})
if username and password:
user = authenticate(phone_number=username, password=password)
try:
if UserInfo.objects.get(phone_number=username):
userinfo = UserInfo.objects.get(phone_number=username)
user = User.objects.filter(user=userinfo.user, password=password).latest('date_joined')
if user:
if user.is_active:
validated_data['user'] = user
return validated_data
else:
raise serializers.ValidationError({"detail": "User account disabled."})
except UserInfo.DoesNotExist:
try:
user = User.objects.filter(email=username, password=password).latest('date_joined')
if user.is_active:
validated_data['user'] = user
return validated_data
except User.DoesNotExist:
#raise serializers.ValidationError("s")
raise serializers.ValidationError({'detail': 'Unable to log in with provided credentials.'})
else:
raise serializers.ValidationError({"detail" : "Must include username and password."})
class Meta:
model = Token
fields = ("username", "password", "token")
I've tried adding a custom exception handler:
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
# Now add the HTTP status code to the response.
if response is not None:
response.data['status_code'] = response.status_code
return response
views.py: if serializer.is_valid(raise_exception=True):
However, that only appends the currently raised error:
{"detail":["Unable to log in with provided credentials."],"status_code":400}
How should I use change the format of the returning text?
It only returns the JSON like this for this particular serializer within the validate function.
I've also looked into formatting the non_field_errors template, but it works with all my other serializers e.g:
{"detail": "Account exists with email address."}
Maybe you should try overriding json renderer class and hook up a custom one, where you can check for status code and detail key in response data, then re-format the value appropriately.
I never tried that, so I can't give you the exact codebase, but this is the only approach I can think of to have consistent response.

Tastypie how to get custom Json Response after injecting Post data

i want to have custom json response after data post sendind to my Tastypie API models django.
class MyModelResource(ModelResource):
my_field=""
class Meta:
queryset = MyModel.objects.all()
resource_name = 'nick_name'
authentication = ApiKeyAuthentication()
authorization = DjangoAuthorization()
def hydrate(self, bundle):
#on recupere les donnée injectée par bundle.data['title']
#et on inject les donnée via bundle.obj.title
#bundle.data['my_field'] ="1234"
bundle.obj.my_field=bundle.data['my_field']
self.my_field = bundle.data['my_field']
return bundle
def wrap_view(self, view):
"""
Wraps views to return custom error codes instead of generic 500's
"""
#csrf_exempt
def wrapper(request, *args, **kwargs):
try:
callback = getattr(self, view)
response = callback(request, *args, **kwargs)
if request.is_ajax():
patch_cache_control(response, no_cache=True)
lst_dic=[]
mon_dic = dict(success=True, my_field=self.my_field
)
# response is a HttpResponse object, so follow Django's instructions
# to change it to your needs before you return it.
# https://docs.djangoproject.com/en/dev/ref/request-response/
lst_dic.append(mon_dic)
response = HttpResponse(simplejson.dumps(lst_dic), content_type='application/json')
return response
except (BadRequest, fields.ApiFieldError), e:
return HttpBadRequest({'success':False,'code': 666, 'message':e.args[0]})
except ValidationError, e:
# Or do some JSON wrapping around the standard 500
return HttpBadRequest({'success':False,'code': 777, 'message':', '.join(e.messages)})
except Exception, e:
# Rather than re-raising, we're going to things similar to
# what Django does. The difference is returning a serialized
# error message.
return self._handle_500(request, e)
return wrapper
My problem here, i can't grab the self.my_field value to put in mon_dic, i always have data object, not value...
thx for help
EDIT : Add my_field global variable, and then grab value from bundle that's it ;)
Maybe I am not understanding what you want to do here. But wrap_view is for handling customer error responses. If all you want to do is return the data that was posted, you can set always_return_data to true in your Meta:
class Meta:
always_return_data = True
Or if you want to control what data gets sent back, you can use the dehydrate method:
def dehydrate(self, bundle):
bundle.data['custom_field'] = "Whatever you want"
return bundle

Multiple response for a single request in Twisted

I wanted to be able to receive multiple response from a server after sending one single request. This is implemented all in twisted.
The Server:
class HandleReq(resource.Resource):
def __init__(self):
resource.Resource.__init__(self)
def render_GET(self, request):
"""
Here I basically connect to another server and get multiple
responses"""
d = defer.Deferred()
interface = RemoteService(request, i_deferred)
self._connect_to_RemoteService(bf_command, interface)
self.handleCallbacks(i_deferred, request)
return server.NOT_DONE_YET
def render_POST(self, request):
'''to make sure both GET/POST are handled'''
return self.render_GET(request)
def handleCallbacks(self, d, req):
msg = d.addCallback(self.getEvent)
d.addCallback(self.postResponse(req, msg))
return None
def getEvent(self, msg):
return msg
def postResponse(self, request, response):
def post(event):
request.setHeader('Content-Type', 'application/json')
request.write(response)
request.finish()
self.postResponse(request, response)
return server.NOT_DONE_YET
return post
And the Client:
from urllib2 import URLError, HTTPError
api_req = 'http://localhost:8000/req' + '?' + urllib.urlencode({"request": request})
req = urllib2.Request(api_req)
try:
response = urllib2.urlopen(api_req)
except HTTPError, e:
print 'Problem with the request'
print 'Error code: ', e.code
except URLError, e:
print 'Reason: ', e.reason
else:
j_response = json.loads(response.read())
Basically what I want is that the server not to close the connection (request.finish()), but instead to continue sending responses; and the client should be able to receive those messages.
HTTP does not work this way. An HTTP request has exactly one response. Twisted Web will not let you send more than one response, because that would be against the HTTP specification and no HTTP clients would be able to figure out what was going on.
There may be another way to accomplish your underlying goals, but whatever it is, it won't involve sending more than one HTTP response to a single HTTP request.