Search code examples
pythonrecursionpython-requestsplex

Python HTTP Request that runs every second, infinitely, and compares to previous iteration


I'm using Plex and trying to use the Plex api to get and continually update the status of my Plex Server. If the status changes, I have a couple of things that it does. Here's my code (I've redacted the irrelevant portions):

import requests
import time

ip = '127.0.0.1'
port = '####'


def get_status(last_media_type, last_state):
    data = requests.get('http://' + ip + ':' + port + '/status/sessions', verify=False)
    if home:  # Ignore this part. Redacted code
        if '<MediaContainer size="0">' in data.text:  # If nothing is playing
            media_type = 'None'
            state = 'STOPPED'
        else:  #  Get what's playing and its status
            media_type_start = data.text.find('type=\"')
            media_type_end = data.text.find('updatedAt=')
            media_type = data.text[media_type_start + 6:media_type_end - 2].capitalize()
            print('Type: ' + str(media_type))
            state_prefix_start = data.text.find('<Player address=')
            state_prefix_end = data.text.find('<TranscodeSession key=')
            state_prefix = data.text[state_prefix_start:state_prefix_end]
            state_start = state_prefix.find('state=\"')
            state_end = state_prefix.find('title=')
            state = state_prefix[state_start + 7:state_end - 2].upper()
        if last_media_type != media_type or last_state != state:  # THIS IS IMPORTANT. I don't want to execute the next part if nothing has changed.
            # DO STUFF, redacted
        if state == 'PLAYING':
            interval = 1
        elif state == 'PAUSED':
            interval = 10
        elif state == 'STOPPED':
            interval = 15
    else:
        interval = 60 * 3  # if nobody home, only check once every 3 minutes
    time.sleep(interval)
    get_status(media_type, state)

get_status('a', 'b')  # filler arguments used to initiate script

The Problem

Unfortunately, after half an hour or so, I get:

RecursionError: maximum recursion depth exceeded in comparison

This is the traceback (although the line numbers won't match due to redaction):

Traceback (most recent call last):
  File "H:/Users/micha/Documents/Scripts/Python/plexStatus.pyw", line 63, in <module>
    p = psutil.Process(os.getpid())  # Get PID for this script
  File "C:\Python351\lib\site-packages\psutil\__init__.py", line 349, in __init__
    self._init(pid)
  File "C:\Python351\lib\site-packages\psutil\__init__.py", line 375, in _init
    self.create_time()
  File "C:\Python351\lib\site-packages\psutil\__init__.py", line 636, in create_time
    self._create_time = self._proc.create_time()
  File "C:\Python351\lib\site-packages\psutil\_pswindows.py", line 282, in wrapper
    return fun(self, *args, **kwargs)
  File "C:\Python351\lib\site-packages\psutil\_pswindows.py", line 422, in create_time
    if self.pid in (0, 4):
RecursionError: maximum recursion depth exceeded in comparison

The Questions

How can I get this to work without the recursion error while still only waiting 1 second between executions?

My guess is that there's a better way to call this script, while still being able to use the variables from the previous call. I just can't figure out how. I think I can use pickle which I'm already using in another redacted part, but I'd like to avoid a ton of read/writes if possible. Or really, just make this as little of a memory hog as possible while still retaining functionality.

Notes: I'm sure there's a better way to parse. I'm new to programming. Researching that concurrently as I fix this.

I've edited this code a few times while trying to fix this, so I'm not sure this won't throw up some other error. It's the recursion I'm trying to fix.

I know there are tools like PlexPy for this. Just trying to write one myself that is smaller and I can run continuously without being a major resource hog.


Solution

  • Recursion means that each function call stays on the stack, hence you eventually will run out of memory leading to the RecursionError. In other There are ways around this using "tail-call recursion", almost like you've done - except Python will never support this.

    The best way to run this is to modify your code to have an infinite loop:

    def get_status(last_media_type, last_state):
        pass # your method here
             # except for the recursive call
        return state, media_type
    
    last_state = None
    last_media_type = 'sensible_default'
    while True:
        state, media_type = get_status(last_media_type, last_state)
        pass # do what you need to compare them here
    
        if last_state != state and last_media_type != media_type:
             print "they aren't equal!"       
    
        last_state, last_media_type = state, media_type
    

    The loop itself (while True:) will consume practically no memory, and now instead of storing every single past state, you just have the last one.

    Additionally, any information in the method call is garbage collected as it loses scope when your method returns - this is a direct improvement over your code, as in the recursive call nothing ever loses scope so nothing cal ever be collected.