Can I pass arguments to a function in monkeypatch.setattr for a function used multiple times in one view? - flask

My web application makes API calls to Spotify. In one of my Flask views I use the same method with different endpoints. Specifically:
sh = SpotifyHelper()
...
#bp.route('/profile', methods=['GET', 'POST'])
#login_required
def profile():
...
profile = sh.get_data(header, 'profile_endpoint')
...
playlist = sh.get_data(header, 'playlist_endpoint')
...
# There are 3 more like this to different endpoints -- history, top_artists, top_tracks
...
return render_template(
'profile.html',
playlists=playlists['items'],
history=history['items'],
...
)
I do not want to make an API call during testing so I wrote a mock.json that replaces the JSON response from the API. I have done this successfully when the method is only used once per view:
class MockResponse:
#staticmethod
def profile_response():
with open(path + '/music_app/static/JSON/mock.json') as f:
response = json.load(f)
return response
#pytest.fixture
def mock_profile(monkeypatch):
def mock_json(*args, **kwargs):
return MockResponse.profile_response()
monkeypatch.setattr(sh, "get_data", mock_json)
My problem is that I need to call get_data to different endpoints with different responses. My mock.json is written:
{'playlists': {'items': [# List of playlist data]},
'history': {'items': [# List of playlist data]},
...
So for each API endpoint I need something like
playlists = mock_json['playlists']
history = mock_json['history']
I can write mock_playlists(), mock_history(), etc., but how do I write a monkeypatch for each? Is there some way to pass the endpoint argument to monkeypatch.setattr(sh, "get_data", mock_???)?

from unittest.mock import MagicMock
#other code...
mocked_response = MagicMock(side_effect=[
# write it in the order of calls you need
profile_responce_1, profile_response_2 ... profile_response_n
])
monkeypatch.setattr(sh, "get_data", mocked_response)

Related

Django redirect by calling function view?

I need to redirect while keeping the original request. Is this approach inline with Django 3 logic or am I creating some furute catastrofe?
def final_view(request):
# use some_new_variable to do stuff and then display it in the template
return render(request, another_template_name, {})
def original_view(request):
# do stuff
request.some_new_variable = "x"
return final_view(request)
Not sure exactly what you're trying to accomplish, but you could always pass "some_new_variable" via the URL
For instance:
views.py
def original_view(request):
x = # do your stuff here
return redirect('final_view_url', var=x)
def final_view(request, var)
final_var = # do some additional stuff with var here
return render(request, another_template_name)
urls.py
path('/final_view/<var>', views.final_view, name="final_view_url")
So you send your original request to "original_view", do some stuff, then pass that variable into the URL to "final_view"

Convert POST to PUT with Tastypie

Full Disclosure: Cross posted to Tastypie Google Group
I have a situation where I have limited control over what is being sent to my api. Essentially there are two webservices that I need to be able to accept POST data from. Both use plain POST actions with urlencoded data (basic form submission essentially).
Thinking about it in "curl" terms it's like:
curl --data "id=1&foo=2" http://path/to/api
My problem is that I can't update records using POST. So I need to adjust the model resource (I believe) such that if an ID is specified, the POST acts as a PUT instead of a POST.
api.py
class urlencodeSerializer(Serializer):
formats = ['json', 'jsonp', 'xml', 'yaml', 'html', 'plist', 'urlencoded']
content_types = {
'json': 'application/json',
'jsonp': 'text/javascript',
'xml': 'application/xml',
'yaml': 'text/yaml',
'html': 'text/html',
'plist': 'application/x-plist',
'urlencoded': 'application/x-www-form-urlencoded',
}
# cheating
def to_urlencoded(self,content):
pass
# this comes from an old patch on github, it was never implemented
def from_urlencoded(self, data,options=None):
""" handles basic formencoded url posts """
qs = dict((k, v if len(v)>1 else v[0] )
for k, v in urlparse.parse_qs(data).iteritems())
return qs
class FooResource(ModelResource):
class Meta:
queryset = Foo.objects.all() # "id" = models.AutoField(primary_key=True)
resource_name = 'foo'
authorization = Authorization() # only temporary, I know.
serializer = urlencodeSerializer()
urls.py
foo_resource = FooResource
...
url(r'^api/',include(foo_resource.urls)),
)
In #tastypie on Freenode, Ghost[], suggested that I overwrite post_list() by creating a function in the model resource like so, however, I have not been successful in using this as yet.
def post_list(self, request, **kwargs):
if request.POST.get('id'):
return self.put_detail(request,**kwargs)
else:
return super(YourResource, self).post_list(request,**kwargs)
Unfortunately this method isn't working for me. I'm hoping the larger community could provide some guidance or a solution for this problem.
Note: I cannot overwrite the headers that come from the client (as per: http://django-tastypie.readthedocs.org/en/latest/resources.html#using-put-delete-patch-in-unsupported-places)
I had a similar problem on user creation where I wasn't able to check if the record already existed. I ended up creating a custom validation method which validated if the user didn't exist in which case post would work fine. If the user did exist I updated the record from the validation method. The api still returns a 400 response but the record is updated. It feels a bit hacky but...
from tastypie.validation import Validation
class MyValidation(Validation):
def is_valid(self, bundle, request=None):
errors = {}
#if this dict is empty validation passes.
my_foo = foo.objects.filter(id=1)
if not len(my_foo) == 0: #if object exists
foo[0].foo = 'bar' #so existing object updated
errors['status'] = 'object updated' #this will be returned in the api response
return errors
#so errors is empty if object does not exist and validation passes. Otherwise object
#updated and response notifies you of this
class FooResource(ModelResource):
class Meta:
queryset = Foo.objects.all() # "id" = models.AutoField(primary_key=True)
validation = MyValidation()
With Cathal's recommendation I was able to utilize a validation function to update the records I needed. While this does not return a valid code... it works.
from tastypie.validation import Validation
import string # wrapping in int() doesn't work
class Validator(Validation):
def __init__(self,**kwargs):
pass
def is_valid(self,bundle,request=None):
if string.atoi(bundle.data['id']) in Foo.objects.values_list('id',flat=True):
# ... update code here
else:
return {}
Make sure you specify the validation = Validator() in the ModelResource meta.

Using tastypie api from other views

I am calling tastypie api from normal django views.
def test(request):
view = resolve("/api/v1/albumimage/like/user/%d/" % 2 )
accept = request.META.get("HTTP_ACCEPT")
accept += ",application/json"
request.META["HTTP_ACCEPT"] = accept
res = view.func(request, **view.kwargs)
return HttpResponse(res._container)
Using tastypie resource in view
Call an API on my server from another view
achieve the same thing but seems harder.
Is my way of calling api acceptable?
Besides, it would be awesome if I could get the result in python dictionary instead of json.
Is it possible?
If you need a dictionary, it means that you must design your application better. Don't do important stuff in your views, nor in the Tastypie methods. Refactor it to have common funcionality.
As a general rule, views must be small. No more than 15 lines. That makes the code readable, reusable and easy to test.
I'll provide an example to make it clearer, suppose in that Tastypie method you must be creating a Like object, maybe sending a signal:
class AlbumImageResource(ModelResource):
def like_method(self, request, **kwargs):
# Do some method checking
Like.objects.create(
user=request.user,
object=request.data.get("object")
)
signals.liked_object(request.user, request.data.get("object"))
# Something more
But, if you need to reuse that behavior in a view, the proper thing would be to factorize that in a different function:
# myapp.utils
def like_object(user, object):
like = Like.objects.create(
user=request.user,
object=request.data.get("object")
)
signals.liked_object(request.user, request.data.get("object"))
return like
Now you can call it from your API method and your view:
class AlbumImageResource(ModelResource):
def like_method(self, request, **kwargs):
# Do some method checking
like_object(request.user, request.data.get("object")) # Here!
And in your view...
# Your view
def test(request, object_id):
obj = get_object_or_404(Object, id=object_id)
like_object(request.user, obj)
return HttpResponse()
Hope it helps.

how to unittest the template variables passed to jinja2 template from webapp2 request handler

I'm trying to test my webapp2 handlers. To do this, I thought it would be a good idea to send a request to the handler e.g.:
request = webapp2.Request.blank('/')
# Get a response for that request.
response = request.get_response(main.app)
The problem is, response is mostly just a bunch of HTML etc.
I want to look at what was passed to my jinja2 template from the handler before it was turned into HTML.
I want my test to get at the state within the handler class code. I wan't to be able to see what certain variables looked like in the response handler, and then I want to see what the dict templates looks like before it was passed to render_to_response()
I want to test these variables have the correct values.
Here is my test code so far, but I'm stuck because response = request.get_response() just gives me a bunch of html and not the raw variables.
import unittest
import main
import webapp2
class DemoTestCase(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def testNothing(self):
self.assertEqual(42, 21 + 21)
def testHomeHandler(self):
# Build a request object passing the URI path to be tested.
# You can also pass headers, query arguments etc.
request = webapp2.Request.blank('/')
# Get a response for that request.
response = request.get_response(main.app)
# Let's check if the response is correct.
self.assertEqual(response.status_int, 200)
self.assertEqual(response.body, 'Hello, world!')
if __name__ == '__main__':
unittest.main()
and here is my handler:
class HomeHandler(BaseHandler):
def get(self, file_name_filter=None, category_filter=None):
file_names = os.listdir('blog_posts')
blogs = []
get_line = lambda file_: file_.readline().strip().replace("<!--","").replace("-->","")
for fn in file_names:
with open('blog_posts/%s' % fn) as file_:
heading = get_line(file_)
link_name = get_line(file_)
category = get_line(file_)
date_ = datetime.strptime(fn.split("_")[0], "%Y%m%d")
blog_dict = {'date': date_, 'heading': heading,
'link_name': link_name,
'category': category,
'filename': fn.replace(".html", ""),
'raw_file_name': fn}
blogs.append(blog_dict)
categories = Counter(d['category'] for d in blogs)
templates = {'categories': categories,
'blogs': blogs,
'file_name_filter': file_name_filter,
'category_filter': category_filter}
assert(len(file_names) == len(set(d['link_name'] for d in blogs)))
self.render_template('home.html', **templates)
and here is my basehandler:
class BaseHandler(webapp2.RequestHandler):
#webapp2.cached_property
def jinja2(self):
return jinja2.get_jinja2(app=self.app)
def render_template(self, filename, **kwargs):
#kwargs.update({})
#TODO() datastore caching here for caching of (handlername, handler parameters, changeable parameters, app_upload_date)
#TODO() write rendered page to its own html file, and just serve that whole file. (includes all posts). JQuery can show/hide posts.
self.response.write(self.jinja2.render_template(filename, **kwargs))
Perhaps I have got the wrong idea of how to do unit testing, or perhaps I should have written my code in a way that makes it easier to test? or is there some way of getting the state of my code?
Also if someone were to re-write the code and change the variable names, then the tests would break.
You can mock BaseHandler.render_template method and test its parameters.
See this question for a list of popular Python mocking frameworks.
Thanks to proppy's suggestion I ended up using a mock.
http://www.voidspace.org.uk/python/mock/
(mock is included as part or unittest.mock in python 3)
So here is my main.py code which is similar to what I have in webapp2:
note instead of BaseHandler.render_template i have BaseHandler.say_yo
__author__ = 'Robert'
print "hello from main"
class BaseHandler():
def say_yo(self,some_number=99):
print "yo"
return "sup"
class TheHandler(BaseHandler):
def get(self, my_number=42):
print "in TheHandler's get()"
print self.say_yo(my_number)
return "TheHandler's return string"
and atest.py:
__author__ = 'Robert'
import unittest
import main
from mock import patch
class DemoTestCase(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def testNothing(self):
self.assertEqual(42, 21 + 21)
def testSomeRequests(self):
print "hi"
bh = main.BaseHandler()
print bh.say_yo()
print "1111111"
with patch('main.BaseHandler.say_yo') as patched_bh:
print dir(patched_bh)
patched_bh.return_value = 'double_sup'
bh2 = main.BaseHandler()
print bh2.say_yo()
print "222222"
bh3 = main.BaseHandler()
print bh3.say_yo()
print "3333"
th = main.TheHandler()
print th.get()
print "44444"
with patch('main.BaseHandler.say_yo') as patched_bh:
patched_bh.return_value = 'last_sup'
th = main.TheHandler()
print th.get()
print th.get(123)
print "---"
print patched_bh.called
print patched_bh.call_args_list
print "555555"
if __name__ == '__main__':
unittest.main()
this code gives lots of output, here is a sample:
44444
in TheHandler's get()
last_sup
TheHandler's return string
in TheHandler's get()
last_sup
TheHandler's return string
---
True
[call(42), call(123)]
555555

How to write a request filter / preprocessor in Django

I am writing an application in Django, which uses [year]/[month]/[title-text] in the url to identitfy news items. To manage the items I have defined a number of urls, each starting with the above prefix.
urlpatterns = patterns('msite.views',
(r'^(?P<year>[\d]{4})/(?P<month>[\d]{1,2})/(?P<slug>[\w]+)/edit/$', 'edit'),
(r'^(?P<year>[\d]{4})/(?P<month>[\d]{1,2})/(?P<slug>[\w]+)/$', 'show'),
(r'^(?P<year>[\d]{4})/(?P<month>[\d]{1,2})/(?P<slug>[\w]+)/save$', 'save'),
)
I was wondering, if there is a mechanism in Django, which allows me to preprocess a given request to the views edit, show and save. It could parse the parameters e.g. year=2010, month=11, slug='this-is-a-title' and extract a model object out of them.
The benefit would be, that I could define my views as
def show(news_item):
'''does some stuff with the news item, doesn't have to care
about how to extract the item from request data'''
...
instead of
def show(year, month, slug):
'''extract the model instance manually inside this method'''
...
What is the Django way of solving this?
Or in a more generic way, is there some mechanism to implement request filters / preprocessors such as in JavaEE and Ruby on Rails?
You need date based generic views and create/update/delete generic views maybe?
One way of doing this is to write a custom decorator. I tested this in one of my projects and it worked.
First, a custom decorator. This one will have to accept other arguments beside the function, so we declare another decorator to make it so.
decorator_with_arguments = lambda decorator: lambda * args, **kwargs: lambda func: decorator(func, *args, **kwargs)
Now the actual decorator:
#decorator_with_arguments
def parse_args_and_create_instance(function, klass, attr_names):
def _function(request, *args, **kwargs):
model_attributes_and_values = dict()
for name in attr_names:
value = kwargs.get(name, None)
if value: model_attributes_and_values[name] = value
model_instance = klass.objects.get(**model_attributes_and_values)
return function(model_instance)
return _function
This decorator expects two additional arguments besides the function it is decorating. These are respectively the model class for which the instance is to be prepared and injected and the names of the attributes to be used to prepare the instance. In this case the decorator uses the attributes to get the instance from the database.
And now, a "generic" view making use of a show function.
def show(model_instance):
return HttpResponse(model_instance.some_attribute)
show_order = parse_args_and_create_instance(Order, ['order_id'])(show)
And another:
show_customer = parse_args_and_create_instance(Customer, ['id'])(show)
In order for this to work the URL configuration parameters must contain the same key words as the attributes. Of course you can customize this by tweaking the decorator.
# urls.py
...
url(r'^order/(?P<order_id>\d+)/$', 'show_order', {}, name = 'show_order'),
url(r'^customer/(?P<id>\d+)/$', 'show_customer', {}, name = 'show_customer'),
...
Update
As #rebus correctly pointed out you also need to investigate Django's generic views.
Django is python after all, so you can easily do this:
def get_item(*args, **kwargs):
year = kwargs['year']
month = kwargs['month']
slug = kwargs['slug']
# return item based on year, month, slug...
def show(request, *args, **kwargs):
item = get_item(request, *args, **kwargs)
# rest of your logic using item
# return HttpResponse...