Surprise from Python on new years eve, 2016

Alright, so another usual night and I was trying to figure out a strange behavior with a Python class I was working. Looks like I had OOP lectures only in C++, and things are little bit different here. So here you go!

I had this strange class method here described as:


class BenchmarkResults(object):

    @classmethod
    def _generate_db_entries(cls, tests, skip_aggregated=False, parent=None, test_table = []):
         # This function recursively calls _generate_db_entries with
         # test_table param set to a list 

and this was processing in a JSON file from one of my POST API and doing some crazy stuff. The API calls this class like

class BotReportView(APIView):
    def post(self, request, format=None):
        test_data_results = BenchmarkResults(json.load(self.request.FILES.get('test_data')))
        results_table = test_data_results._generate_db_entries(test_data_results)

Now the funny part. I found that on subsequent calls to the API, the test_data_results kept on multiplying 😮 like the data from the previous POST also got appended to the current JSON which-should-be-processed. Thinking it was a problem with the client, I tried switching to curl, but with little luck.

The problem
Finally pycharm got me figuring out what went wrong, as it showed a warning on the test_table = [] something like:
The default method argument is mutable. Bah, taking at look at http://stackoverflow.com/questions/9039191/mutable-default-method-arguments-in-python I see that when an empty list [] is kept as a default formal argument for a function, the list is not killed in between, and keeps on growing on subsequent calls – surprise :\

Quoting http://stackoverflow.com/a/9039224/3355893, when we have something like:

def status(options=[]):
    options.append('new_option')
    return options

print status()
print status()
print status()

will print something like

['new_option']
['new_option', 'new_option']
['new_option', 'new_option', 'new_option']

Tough luck finding it out, but it took couple of hours 😀 I got my code easily fixed by not allowing [] to stay as a default param, but separately passing an empty list to the function during the call. Like

class BenchmarkResults(object):

    @classmethod
    def _generate_db_entries_helper(cls, tests, skip_aggregated=False, parent=None):
        test_table = []
        self._generate_db_entries(cls, tests, test_tables skip_aggregated=False, parent=None):

    @classmethod
    def _generate_db_entries(cls, tests, test_table, skip_aggregated=False, parent=None):
         # This function recursively calls _generate_db_entries with test_table param set to a list 

Yay. that was it, and latter called in the helper function, and things started working. You can see the change here, and finally, Happy New year 2017! Make every commit count!

API custom authentication with django-rest-framework

Long time since I blogged about anything in here! This time I am working on building a python-django powered dashboard for browser test result analysis with Igalia, and came across this the frist day! Let me get into this straight, following my usual template of blog posts

Scenario
You have a default User login model, but have some bot POSTing at say yourwebsite/bot-api. Your bot has its own authentication details sent as parameters in the POST as

{bot_name: fooboot, password: bar}

and you want to authenticate this from a Bot model in your django application, and make sure that this thing go throgh only after a green flag.

Solution
The best method here is to write a custom authentication in your views.py which would look like

from rest_framework import authentication
from your_models import Bot 

class BotAuthentication(authentication.BaseAuthentication):
    def authenticate(self, request):
        bot_name = request.POST.get('bot_name')
        bot_password = request.POST.get('password')

        if not bot_name or not bot_password:
            return None

        try:
            bot = Bot.objects.get(name=bot_name)
        
        # If you have some other methods to auth, use here
        if bot.password != bot_password:
                return None
        except Bot.DoesNotExist:
            raise exceptions.AuthenticationFailed('The bot failed to authenticate')

        return (bot, None)

and you can directly use this in your ApiView which should be ran on recieving a hit on the API.

from django.http import HttpResponse
from rest_framework.views import APIView
from rest_framework import authentication, permissions

class BotReportView(APIView):
    authentication_classes = (BotAuthentication,)
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

    def post(self, request, format=None):
         return HttpResponse("If it reached here, the bot was authenticated = yay")

Well. There are issues with using such a plain-text authentication, and doing a 1:1 password comparison. In real world, you might want to use django password field and authentication functions to compute hash of the recieved password, and compare with the one in the database table.

Hope this helps!