python3 - decorator function: Variable referenced before assignment - python-3.x

Im working on a gRPC microservice.
Because every method has to load a JSON string from the request argument first – and then in the end dump it again, I want to use a decorator on the methods of the class, so that the method itself besides the return only contains the ... more stuff to do ... below:
class VPPSetupService(SetupService_pb2_grpc.VPPSetupServiceServicer):
...
def method(self, request, context):
request = json.loads(request.context)
request = request["request"]
header = request["headers"]
... more stuff to do ...
response = json.dumps(response)
return SetupService_pb2.JsonContextResponse(context=response)
So I wrote a decorator function.
However, I could not find a solution for this error:
request = json.loads(request.context)
UnboundLocalError: local variable 'request' referenced before assignment
The error is produced by this: (Just as an example, the real thing is a bit more complex)
from functools import wraps
def json_and_error(func):
#wraps(func)
def args_wrapper(*args, **kwargs): # Problem: This
request = json.loads(request.context) <<# variable is referenced
request = request["request"] # before assignment
header = request["headers"]
func(*args, **kwargs)
return func(*args, **kwargs)
return args_wrapper
class VPPSetupService(SetupService_pb2_grpc.VPPSetupServiceServicer):
...
#json_and_error
def method(self, request, context):
... more stuff to do ...
return SetupService_pb2.JsonContextResponse(context=response)
I tried using request = json.loads(args[1].context) instead. But then I get this error:
if request["source_machine"] and request["dest_machine"]: TypeError:
'JsonContextRequest' object is not subscriptable
The input given as request argument is an object of type <class 'SetupService_pb2.JsonContextRequest'> The JSON String in the request would be accessible through the request.context attribute.
I'm thinking the problem is related to how the decorator function is being called. I suppose if it was called at runtime of the class method the variable request should already have been assigned the request object.
But maybe I'm completely wrong here. So how would you solve this?

There are few errors in your code:
def args_wrapper(*args, **kwargs):
request = json.loads(request.context)
You're using request.context while request variable is undefined (it will be defined only after json.loads(request.context) will be executed). You tried to fix it using request = json.loads(args[1].context), but got JsonContextRequest object is not subscriptable in wrapped function. Look at this line closer:
return func(*args, **kwargs)
You decorator returns wrapped function called with the same params, so this code result isn't used, you don't pass request and header to wrapped function:
request = json.loads(request.context)
request = request["request"]
header = request["headers"]
Also you shouldn't call wrapped function:
func(*args, **kwargs)
I guess you want to do something like this:
def json_and_error(func):
def args_wrapper(this, request):
context = request.context
request = json.loads(context)
request = request["request"]
return func(this, request, context)
return args_wrapper

Related

How can I read response content (that is JSON) from Django middleware so I can convert it to HTML?

From my django view I am returning a serialized JSON as below:
def features(request):
features = db_service.fetch_all_features()
data = serializers.serialize('json', features)
return HttpResponse(data, content_type='application/json')
I have registered a middleware, where I want to fetch this JSON and convert it into HTML
class ReturnResponseAsHTML:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
response = self.get_response(request)
# Code to be executed for each request/response after
#return HttpResponse(data, content_type='application/json')
return response
Once I have access to the response object, how may I fetch the JSON from it so I may convert it to HTML
response.getvalue()
where response is an instance of django.http.HttpResponse

Django Middleware: RecursionError when accessing `self.request.user` in database query wrapper

I'm testing my database query middleware (Django docs here) on a sample django app with a Postgres db. The app is the cookiecutter boilerplate. My goal with the middleware is simply to log the user ID for all database queries. Using Python3.9 and Django3.2.13. My middleware code is below:
# Middleware code
import logging
import django
from django.db import connection
django_version = django.get_version()
logger = logging.getLogger(__name__)
class Logger:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
with connection.execute_wrapper(QueryWrapper(request)):
return self.get_response(request)
class QueryWrapper:
def __init__(self, request):
self.request = request
def __call__(self, execute, sql, params, many, context):
# print(self.request.user)
return execute(sql, params, many, context)
If print(self.request.user.id) is commented out, everything works fine. However, I've found that uncommenting it, or any type of interaction with the user field in the self.request object, causes a Recursion Error:
RecursionError at /about/
maximum recursion depth exceeded
Request Method: GET
Request URL: http://127.0.0.1:8000/about/
Django Version: 3.2.13
Exception Type: RecursionError
Exception Value:
maximum recursion depth exceeded
Exception Location: /opt/homebrew/lib/python3.9/site-packages/django/db/models/sql/query.py, line 192, in __init__
Python Executable: /opt/homebrew/opt/python#3.9/bin/python3.9
Python Version: 3.9.13
In the error page, that is followed by many repetitions of the below error:
During handling of the above exception ('SessionStore' object has no attribute '_session_cache'), another exception occurred:
/opt/homebrew/lib/python3.9/site-packages/django/contrib/sessions/backends/base.py, line 233, in _get_session
return self._session_cache …
During handling of the above exception ('SessionStore' object has no attribute '_session_cache'), another exception occurred:
/opt/homebrew/lib/python3.9/site-packages/django/contrib/sessions/backends/base.py, line 233, in _get_session
return self._session_cache …
From consulting other SO posts, it seems accessing the user field should work fine. I've checked that the django_session table exists, and my middleware is also located at the very bottom of my middlewares (that include "django.contrib.sessions.middleware.SessionMiddleware" and "django.contrib.auth.middleware.AuthenticationMiddleware")
What's wrong here?
The following things happen when you write self.request.user:
The request is checked for an attribute _cached_user, if present the cached user is returned if not auth.get_user is called with the request.
Using the session key, Django checks the session to get the authentication backend used for the current user. Here if you are using database based sessions a database query is fired.
Using the above authentication backend Django makes a database query to get the current user using their ID.
As noted from the above points, unless there is a cache hit this process is going to cause a database query.
Using database instrumentation you have installed a wrapper around database queries, the problem is that this wrapper itself is trying to make more queries (trying to get the current user), causing it to call itself. One solution would be to get the current user before installing your wrapper function:
class Logger:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
_user_authenticated = request.user.is_authenticated # Making sure user is fetched (Lazy object)
with connection.execute_wrapper(QueryWrapper(request)):
return self.get_response(request)
class QueryWrapper:
def __init__(self, request):
self.request = request
def __call__(self, execute, sql, params, many, context):
print(self.request.user)
return execute(sql, params, many, context)

How to mock a (PyActiveResource) pyactiveresource.connection UnauthorizedAccess response return, using Django TestCase?

I need to create a unit test that mock a REST API failure call, with a side effect of returning an UnauthorizedAccess exception, from the PyActiveResource project (https://github.com/Shopify/pyactiveresource) and store it in the DB. What I've create so far worked and I've got the desired returned side effect. Then, I catch it on the function foo.function_that_call_myfuncion() which looks like this
my_func.py:
from pyactiveresource.connection import UnauthorizedAccess
class MyFuncAnyThing:
...
def function_that_call_myfuncion(self, attr=None):
try:
module.MyTestClass.myfunction(attr)
except UnauthorizedAccess as error:
#Catch the error response object and store it on DB
resp_body = error.response.body
resp_code = error.response.code
#store it on DB
...
And my test file looks like this
unit_test.py:
from pyactiveresource.connection import UnauthorizedAccess
class TestFoo(TestCase):
def test_exception_unauthorized_access(self):
foo = SomeThingFactory()
with patch('module.MyTestClass.myfunction', side_effect=UnauthorizedAccess()):
foo.function_that_call_myfuncion()
#assertions goes below here
...
So, when the execution reached the try block on function_that_call_myfuncion from my_func.py module, the mock function return the desired exception (UnauthorizedAccess) and the object returned looks like this:
error
UnauthorizedAccess('Response(code=None, body="", headers={}, msg="")')
My problems begins when I try to mock the Response body returned on the UnauthorizedAccess exception. This is what I'm doing:
unit_test.py:
from pyactiveresource.connection import UnauthorizedAccess
class TestFoo(TestCase):
def test_exception_unauthorized_access(self):
foo = SomeThingFactory()
bar = MagicMock()
bar.code = 401
bar.body = '{"errors": "Login or wrong password"}'
with patch('module.MyTestClass.myfunction', side_effect=UnauthorizedAccess(bar)):
foo.function_that_call_myfuncion()
#assertions goes below here
...
And that's is how the mocked object looks like:
error
UnauthorizedAccess('Response(code=401, body="<MagicMock name=\'mock.read()\' id=\'2243840764512\'>", headers={}, msg="<MagicMock name=\'mock.msg\' id=\'2243840808464\'>")')
Note that the code attribute on Response is 401, but the body is empty, even though I've set it here bar.body = '{"errors": "Login or wrong password"}'. I also tried to create a Response object and pass it as parameter on the constructor for UnauthorizedAccess class, which is a subclass of
class ConnectionError(Error): of the pyactiveresource.connection lib code (https://github.com/Shopify/pyactiveresource/blob/e609d844ebace603f74bc5f0a67e9eafe7fb25e1/pyactiveresource/connection.py#L34)
unit_test.py:
from pyactiveresource.connection import UnauthorizedAccess, Response
class TestFoo(TestCase):
def test_exception_unauthorized_access(self):
foo = SomeThingFactory()
resp = Response(code=401,body='{"errors": "Login or wrong password"}')
with patch('module.MyTestClass.myfunction', side_effect=UnauthorizedAccess(response=resp)):
foo.function_that_call_myfuncion()
#assertions goes below here
...
But then I got this error from the Class Response:
#classmethod
def from_httpresponse(cls, response):
"""Create a Response object based on an httplib.HTTPResponse object.
Args:
response: An httplib.HTTPResponse object.
Returns:
A Response object.
"""
> return cls(response.code, response.read(),
dict(response.headers), response.msg, response)
E AttributeError: 'Response' object has no attribute 'read'
What am I missing? I just couldn't figure out how to set the 'read' attribute on the constructor, so that I can get the body value.
I'm using Python 3.8, Django 2.2
I managed to mock Shopify's ClientError exceptions by doing something along the lines of:
import urllib.error
from io import BytesIO
import pyactiveresource.testing.http_fake
pyactiveresource.testing.http_fake.initialize()
response = urllib.error.HTTPError('', 401, '', {}, BytesIO(b''))
pyactiveresource.testing.http_fake.TestHandler.set_response(response)
Which I learned about by digging into the Shopify/pyactiveresource tests.

InvalidTemplateEngineError in oscar

I am using django-oscar for my development.
I have created a new app as per my requirement
views.py
class MediaImportView(TemplateView):
template_name = 'lookup/import_media_file.html'
def get(self, request):
ctx = {}
return render(self.template_name, ctx, request, using=request)
getting the error as below.
InvalidTemplateEngineError at /import_media_file
Could not find config for '<WSGIRequest: GET '/import_media_file'>' in settings.TEMPLATES
From the documentation, the using argument takes:
The NAME of a template engine to use for loading the template.
Instead of passing the name of a template engine, you are passing a request object, hence the error. In addition, all your other arguments are wrong as well - the signature of this function is:
render(request, template_name, context=None, content_type=None, status=None, using=None)
So the first argument needs to be a request, the second a template name, and the third the context. Something like this:
return render(request, self.template_name, ctx)

Add variable to Django request object in Middleware (once)

class CustomMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
request.variable = 1
response = self.get_response(request)
return response
This works but it it processing the request twice. I am unsure of how to set this variable after the view has been processed (for every view), only once. process_template_response is not a valid option because it will not work with every view. Is there a better way to do this?

Resources