flask-timeloop 0.2.4
Timeloop module that integrates with Flask
License: MIT
Requires Python: >=3.10,<4.0

# Flask-Timeloop
Timeloop is a service that can be used to run periodic tasks after a certain interval. It is **meant** to be used to with an underlying flask application.

Each job runs on a separate thread and when the service is shut down, it waits till all tasks currently being executed are completed.

Forked and enhanced from [`here`](https://github.com/sankalpjonn/timeloop.git)
Forked and enchanced from [`here`](https://github.com/Ruggiero-Santo/timeloop.git)

#### PyPi

https://pypi.org/project/flask-timeloop/

## Installation

### pip

```sh
pip install flask-timeloop
```

#### Clone and install
```sh
git clone https://github.com/TafkaMax/timeloop.git
sudo python setup.py install
```

#### Direct installation 
```sh
pip install git+https://github.com/TafkaMax/timeloop.git
```

#### Poetry
```sh
poetry add flask-timeloop
```

# Usage

The recommended way is to use this library with flask factory pattern.

**NB! This is not the correct way to implement the flask extension, as I add the application context to the extension data. BUT Flask only works during requests, but this functionality is internal and does not care about requests. It's like a cron, but inside the application.**

## Writing jobs

### Factory pattern

```python
#python_project_folder/your_app_name/extensions.py
from flask_timeloop import Timeloop

timeloop = Timeloop()
```

```python
#python_project_folder/your_app_name/__init__.py
#(this can also be main.py or whatever you want.)

from flask import Flask

def create_app():
    app = Flask(__name__)

    # Import timeloop and join timeloop to flask application
    from your_app_name.extensions import flask-timeloop
    timeloop.init_app(app)
    # Start the timeloop
    timeloop.start()
    return app
```

```python
#python_project_folder/main.py
from your_app_name import create_app
app = create_app()
```

```python
from your_app_name.extensions import timeloop

@timeloop.job(interval=timedelta(minutes=10))
def do_something():
    with timeloop.app.app_context():
        do_something_that_needs_application_context()
```

### Basic one file application.

```python
#main.py
import time

from timeloop import Timeloop
from datetime import timedelta

from flask import Flask


app = Flask(__name__)

tl = Timeloop(app)

@tl.job(interval = timedelta(seconds = 2))
def sample_job_every_2s():
    print( "2s job current time : {}".format(time.ctime()) )

@tl.job(interval = 5)
def sample_job_every_5s():
    print( "5s job current time : {}".format(time.ctime()) )


@tl.job(interval = timedelta(seconds = 10))
def sample_job_every_10s():
    print( "10s job current time : {}".format(time.ctime()) )
```

## Writing jobs with arguments
Allow to create a job with specified parameters in input
```python
class FileToMove:
    tl = Timeloop()

    def start(self):
        self.tl.start(True)

    # ATTENTION: If a job wants the self param must be declared as swarm.
    # This is because the instance isn't already created when the job is registered
    @tl.job(interval = 1, swarm = True, param_2 = "param")
    def timedMethod(self, param_1, param_2):
        print(self, "param_1:", param_1, "; param_2:", param_2)

    @tl.job(interval = 2, param_1 = "uno", param_2 = "param")
    def timedMethod_1(param_1, param_2):
        print("param_1:", param_1, "; param_2:", param_2)

    # produce the same effect of timedMethod_1. 
    @tl.job(interval = 3 )
    def timedMethod_2(param_1 = "uno", param_2 = "param"):
        print("param_1:", param_1, "; param_2:", param_2)

if __name__ == "__main__":
    ob1 = FileToMove()
    ob1.timedMethod("try")
    ob1.start()
```
or multiple jobs of the same function but with different parameters. It can be really useful in a situation like above when you want to call a class function.
```python
@tl.job(interval = timedelta(seconds = 5), swarm = True)
def sample_job(idx):
    print( "Task id: {} | time: {}".format(idx, time.ctime()) )

# example: queue jobs with different ids
for id in range(1, 3):
	sample_job(id)
```
In the job declared with  `swarm = True` the param `interval` can be omitted. This allows you to create a swarm of job with different interval, including `interval = 2` or `interval = timedelta(seconds = 2)` in the creation, like example.
```python
@tl.job(swarm = True)
def sample_job(idx):
    print( "Task id: {} | time: {}".format(idx, time.ctime()) )

# example: same jobs with different interval
for id in range(1, 3):
	sample_job(id, interval = id)
```

## Writing jobs that stop if an exception occurs
```python
@tl.job(interval = timedelta(seconds = 2), exception = True)
def sample_job():
    print( "I will die if any Exception occurs,time : {}".format(time.ctime()) )

@tl.job(interval = 2, exception = AttributeError)
def sample_job():
    print( "I will die soon, but only if AttributeError occurs" )
    raise AttributeError

@tl.job(interval = timedelta(seconds = 2))
def sample_job():
    print( "I will die only if OSError occurs, becouse of start function" )

tl.start(stop_on_exception = OSError)
```
## Mode to start jobs

### Start timeloop in a separate thread
By default timeloop starts in a separate thread. When it's in this mode do not forget to call `tl.stop()` before exiting the program, or else the jobs wont shut down gracefully (or they will even not shutdown).
```python
tl.start() or tl.start(block=False)
```

### Start time loop in the main thread
Doing this will automatically shut down the jobs gracefully when the program is killed, so no need to  call `tl.stop()`. The main thread that calls the `tl.start()` will be stuck until you kill him (kill command or Ctrl+C on shell).
```python
tl.start(block=True)
```