5

I'm asking this question in a more broad spectrum because I'm not facing this specific issue right now, but I'm wondering how to do it in the future.

If I have a long running python script, that is supposed to do something all the time (could be a infine loop, if that helps). The code is started by running python main.py command on a terminal.

The code doesn't have an ending, so there will be no sys.exit().

I don't want to use KeyboardInterrupt and I don't want to kill the task. Because those options are abrupt, and you can't predict precisely at what point you are stoping the code.

Is there a way to 'softly' terminate the code when I eventually decide to fo it? For example using another command, preparing a class or running another script?

What would be the best practice for this?

PS.: Please, bear in mind that I'm a novice coder.

EDIT: I'm adding some generic code, in order to make my question clearer.

import time,csv

import GenericAPI

class GenericDataCollector:
    def __init__(self):
        self.generic_api = GenericAPI()

    def collect_data(self):
        while True: #Maybe this could be a var that is changed from outside of the class?
            data = self.generic_api.fetch_data() #Returns a JSON with some data
            self.write_on_csv(data)
            time.sleep(1)

    def write_on_csv(self, data):
        with open('file.csv','wt') as f:
            writer = csv.writer(f)
            writer.writerow(data)

def run():
    obj = GenericDataCollector()
    obj.collect_data()

if __name__ == "__main__":
    run()

In this particular case, the class is collecting data from some generic API (that comes in JSON) and writing it in a csv file, in a infinite loop. How could I code a way (method?) to stop it (when called uppon, so unexpected), without abruptly interrupting (Ctrl+C or killing task).

Justcurious
  • 1,952
  • 4
  • 11
  • 17

2 Answers2

4

I would recommend use the signal module. This allows you to handle signal interrupts (SIGINT) and clean up the program before your exit. Take the following code for example:

import signal

running = True

def handle(a, b):
    global running
    running = False

# catch the SIGINT signal and call handle() when the process
# receives it
signal.signal(signal.SIGINT, handle)

# your code here
while running:
    pass

You can still exit with a Ctrl+C, but what you put in the while loop will not be cut off half way.

Calder White
  • 1,287
  • 9
  • 21
  • @U10-Forward I have. They are asking for a way to not abruptly terminate their program if, say, in an infinite while loop. They ask to not use KeyBoardInterrupt, however you actually can ask long as you handle the `SIGINT` signal. – Calder White Jul 11 '19 at 02:17
  • I think this might be what I'm looking for. I have a couple questions though: a) If i use ``global running``, any class/func in the computer can find that exact var? b) Is SIGINT a Ctrl+C signal? c) Could I translate that into a ``stop_script.py`` (instead of ctrl+c) so I could run that, and it would stop my fisrt script? Something like ``global running`` and ``running = False`` would do it? – Justcurious Jul 11 '19 at 02:42
  • 1
    @ZeCarioca That global will be visibile inside your python program. I would not recommend using globals. If you were running it in an object, I would modify some property inside the object. In terms of the signal, a Ctrl+C sends a `SIGINT` to the program. However, you can send a `SIGINT` with anything else as well. For example, a `kill ` sends a `SIGINT` as well. You can read about POSIX signals [here](https://en.wikipedia.org/wiki/Signal_(IPC)#POSIX_signals) – Calder White Jul 11 '19 at 02:46
  • @ZeCarioca Could you mark my solution correct if this is what you will use? Thanks. – Calder White Jul 11 '19 at 03:01
  • this isn't really what I was looking for, but it certainly answers the question that I posted. I guess I'm going to post a new question later. Thanks. – Justcurious Jul 11 '19 at 14:46
1

Based on @Calder White, how about this (not tested):

import signal
import time,csv
import GenericAPI

class GenericDataCollector:
   def __init__(self):
     self.generic_api = GenericAPI()
     self.cont = True

   def collect_data(self):
     while self.cont:
       signal.signal(signal.SIGINT, self.handle)
       data = self.generic_api.fetch_data() #Returns a JSON with some data
       self.write_on_csv(data)
       time.sleep(1)

   def handle(self):
     self.cont = False

   def write_on_csv(self, data):
     with open('file.csv','wt') as f:
       writer = csv.writer(f)
       writer.writerow(data)

def run():
  obj = GenericDataCollector()
  obj.collect_data()

if __name__ == "__main__":
  run()
bJust
  • 161
  • 1
  • 5