Search code examples
pythonpython-3.xoopmember

How to extend python3 BaseHTTPRequestHandler class so that can call function with member variable


I am trying to extend BaseHTTPRequestHandler with my own member variables. I pass these member variables to free functions but when I do so I get:

Exception happened during processing of request from ('127.0.0.1', 30006)
Traceback (most recent call last):
  File "c:\Python37\lib\socketserver.py", line 313, in _handle_request_noblock
self.process_request(request, client_address)
  File "c:\Python37\lib\socketserver.py", line 344, in process_request
    self.finish_request(request, client_address)
  File "c:\Python37\lib\socketserver.py", line 357, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "fruiterer.py", line 41, in __init__
    BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
  File "c:\Python37\lib\socketserver.py", line 712, in __init__
    self.handle()
  File "c:\Python37\lib\http\server.py", line 426, in handle
    self.handle_one_request()
  File "c:\Python37\lib\http\server.py", line 414, in handle_one_request
    method()
  File "fruiterer.py", line 62, in do_POST
    fruit_handler(val, Event_dictionary_list, cached_timestamp)
NameError: name 'Event_dictionary_list' is not defined

Here is the python3 source code:

# sample python HTTP POST handler

__metaclass__= type
from http.server import BaseHTTPRequestHandler, HTTPServer  
import os  

import time
import threading    
import requests
import json

def calculate(Event_dictionary_list, cached_timestamp):
    # do a calculation here based on fruits in Event_dictionary_list
    print("there are: %i items" % len(Event_dictionary_list))
    Event_dictionary_list.clear() #- empty Event_dictionary_list
    cached_timestamp = 0   

def Async_func(Event_dictionary_list, cached_timestamp):
    calculate(Event_dictionary_list, cached_timestamp)

def fruit_handler(event, Event_dictionary_list, cached_timestamp):
    if not Event_dictionary_list: #Checking if Event_dictionary_list is empty 
        # cache first item added
        cached_timestamp = event['timestamp']
        #- set a 30 second timer
        threading.Timer(60,Async_func, Event_dictionary_list, cached_timestamp).start()

    # make decision as to when to calculate
    if event['timestamp'] - cached_timestamp < 60*1000: #- in milliseconds
        # append event to list
        Event_dictionary_list.append(event)


#############################################################################################################
# we create a server to handle POST requests from Xovis
class fruiterer(BaseHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
         BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
         self.Event_dictionary_list = []
         self.cached_timestamp = 0

     # we only need to support he POST http method
    def do_POST(self):
        #print("post msg received");
        self.data_string = self.rfile.read(int(self.headers['Content-Length']))

        self.send_response(200) # 200 = success - thanks for message response
        #self.send_header() #'Content-type','text-html')
        self.end_headers()

        data = json.loads(self.data_string)

        # we assume json will be decoded as object, eg:
        # {"fruit":{"timestamp":1538688902037,"name":"apple","colour":"red","weight":100}}
        if type(data) is dict:
               for key, val in data.items():
                 # we are only interested in fruits
                 if key == "fruit":
                   fruit_handler(val, Event_dictionary_list, cached_timestamp)
                   break

        return

def run():
    print('http server is starting...')
    # change port to listen on here - arbitrarily using port 7777
    port = 7777
    server_address = ('127.0.0.1', port)  
    #use the code from here .start()
    httpd = HTTPServer(server_address, fruiterer)  
    print('http server is listening on port %d' % port)  

    httpd.serve_forever()    

if __name__ == '__main__':  
  run()

This can be tested by sending the following json format in a POST body:

{"fruit":{"timestamp":1538688902037,"name":"apple","colour":"red","weight":100}}

What am I doing wrong?


Solution

  • You need to pass in self.Event_dictionary_list, not Event_dictionary_list, from your do_POST() method. The same would apply to cached_timestamp.

    You'll then find that you'll get an AttributeError on self.Event_dictionary_list, because that attribute is never being set in your __init__ method. The documentation is a bit cryptic about this, but calling BaseHTTPRequestHandler.__init__(self, *args, **kwargs) triggers the handling of a single request, so the method doesn't return until after the request handlers have finished.

    Move the setting of those attributes to before you call the base __init__ method:

    class fruiterer(BaseHTTPRequestHandler):
        def __init__(self, *args, **kwargs):
             self.Event_dictionary_list = []
             self.cached_timestamp = 0
             super().__init__(*args, **kwargs)
    

    I used the super().__init__() syntax here to allow for further co-operative inheritance in the future. You can still use BaseHTTPRequestHandler.__init__(self, *args, **kwargs) if you really want to, but the above is less verbose.

    Do take into account that self.Event_dictionary_list = [] is run for each separate request.

    If this object should live for the duration of your Python program (with the server running), make it a class attribute instead:

    class fruiterer(BaseHTTPRequestHandler):
        Event_dictionary_list = []
        cached_timestamp = 0
    

    Another issue is that setting cached_timestamp with cached_timestamp = ... in other functions will only alter a local variable. Integers are immmutable objects so you can only assign a new integer back to variables, but local variables in functions you call do not share a namespace with the request handler.

    But if you wanted that value to persist across requests anyway, then you can just reference fruiterer.cached_timestamp everywhere and assign to that attribute directly.