0

I just created a script which triggers a report from specific API and then loads it into my database. I have already built something that works but I would like to know if there is something a bit more "precise" or efficient without the need of making my script loop over and over again.

My current script is the following:

import time

retry=1
trigger_report(report_id)

while report_id.status() != 'Complete':
    time.sleep(retry * 1.3)
    retry =+ 1

load_report(report_id)

EDIT:

The API doesn't provide with any wait for completion methods, the most it has is an endpoint which returns the status of the job. It is a SOAP API.

johan855
  • 1,578
  • 4
  • 26
  • 51
  • Check the API for a "wait for completion" method or a callback or somesuch. Maybe you can even invoke the functionality you want to use in a way that blocks until finished. –  Aug 25 '16 at 07:44
  • 3
    Not unless the API you are using provides a better method, no. Without details on the API, this is essentially all we can tell you. – Martijn Pieters Aug 25 '16 at 07:45

2 Answers2

0

While this post doesn't hold any relevance anymore as you said, it' a soap API. But I put the work into it, so I'll post it anyway. :)

To answer your question. I don't see any more efficient methods than polling (aka. looping over and over again)


There are multiple ways to do it.

The first way is implementing some sort of callback that is triggered when the task is completed. It will look something like this:

import time

def expensive_operation(callback):
    time.sleep(20)
    callback(6)

expensive_operation(lambda x:print("Done", x))

As you can see, the Message "Done 6" will be printed as soon as the operation has been completed.

You can rewrite this with Future-objects.

from concurrent.futures import Future
import threading
import time

def expensive_operation_impl():
    time.sleep(20)
    return 6

def expensive_operation():
    fut = Future()
    def _op_wrapper():
        try:
            result = expensive_operation_impl()
        except Exception as e:
            fut.set_exception(e)
        else:
            fut.set_result(result)

    thr = threading.Thread(target=_op_wrapper)
    thr.start()

    return fut

future = expensive_operation()
print(future.result())               # Will block until the operation is done.

Since this looks complicated, there are some high-level functions implementing thread scheduling for you.

import concurrent.futures import ThreadPoolExecutor
import time

def expensive_operation():
    time.sleep(20)
    return 6

executor = ThreadPoolExecutor(1)
future = executor.submit(expensive_operation)

print(future.result())
StuxCrystal
  • 846
  • 10
  • 20
-3

Rather use events, not polling. There are a lot of options for how to implement events in Python. There was a discussion here already on stack overflow.

Here is a synthetic example uses zope.event and an event handler

import zope.event
import time


def trigger_report(report_id):
    #do expensive operation like SOAP call
    print('start expensive operation')
    time.sleep(5)
    print('5 seconds later...')
    zope.event.notify('Success') #triggers 'replied' function

def replied(event): #this is the event handler
    #event contains the text 'Success'
    print(event)

def calling_function():
    zope.event.subscribers.append(replied)
    trigger_report('1')

But futures as in accepted answer is also neat. Depends on what floats your boat.

Community
  • 1
  • 1
D2TheC
  • 2,203
  • 20
  • 23
  • 3
    To be an answer to the question as written, you'd have to explain how to use events when you're not in control of the part of the code that would normally fire the event. –  Aug 25 '16 at 07:47
  • Ain't nobody got time for that – D2TheC Aug 25 '16 at 08:07