Setting up crontab for my Django Application - python

I had an issue with setting up crontab for my Django application for a week and I have almost figured out the same. (Issue linked with Unable to make a function call using Django-cron)
My crontab -e syntax is
* * * * * /Users/ashwin/dashboard/proj_application/exec.sh >> /Users/ashwin/dashboard/proj_application/data.log 2>&1
And in my exec.sh, I have
#!/bin/bash
cd "$(dirname "$0")";
CWD="$(pwd)"
echo $CWD
python -c 'import proj_application.cron as cron; cron.test()'
And in cron.py,
from django.core.mail import send_mail
from smtplib import SMTP
from email.mime.text import MIMEText
import datetime
def test():
message = "<p>This is test mail scheduled to send every minute</p>"
my_email = MIMEText(message, "html")
my_email["From"] = "xxx#domain.com"
my_email["To"] = "yyy#domain.com"
my_email["Subject"] = "Title"
sender = "person1#domain.com"
receivers = ["person2#domain.com"]
with SMTP("localhost") as smtp:
smtp.login(sender, "yyy#1234")
smtp.sendmail(sender, receivers, my_email.as_string())
Actual Problem:
The crontab is now able to call the exec.sh file and I am able to print $CWD in echo, when the call comes to cron.py, It is unable to recognize django.core.mail and throws the following error.
from django.core.mail import send_mail
ImportError: No module named django.core.mail
I think, I need to set up virtual environment or python variable path somewhere, but since I am new to crontab, I am not sure how to do that.
Any assistance is appreciated. Thanks in advance.

The way I interpret this is that you are normally running your script in a virutal environment, and it is failing now that you added it to a cron job. If this is incorrect, just skip to the last paragraph.
To run a cron job through a virutal environment, you need to use the virtual environment's python as the python you want. E.g to run the cron.py file:
* * * * * /path/to/venv/bin/python3 /path/to/cron.py >> /Users/ashwin/dashboard/proj_application/data.log 2>&1
This is the way I would recommend doing it, as it doesn't seem like that shell script is entirely necessary (anything in there can easily be done at the top of the python script), but if it is you can do something similar where you call the virtual environment's python instead of the default python. like this:
/path/to/venv/bin/python3 -c 'import proj_application.cron as cron; cron.test()'
However, if this doesn't work, this may not be an issue with the cron job, and instead with the django setup of the mail client (e.g. making sure it is included in the installed apps, etc.)
Since it is a little unclear where your issue is (mail client setup, cron setup, venv configuration, etc), if you are sure it is an issue with running your scripts you usually run in a virtual environment, than these steps should help, otherwise I would make sure your mail client is configured correctly. Make sure your scripts are correct by running them in the console (not through a cron job), and then you can come back and update the post with more detailed info about where the problem you have lies.

yes you are correct you may need to use virtual environment(although its optional but best practice).
To create virtual environment(expects python to be installed prior)
python -m venv venv
To activate (path may differ based on OS(mine is windows))
source venv/Scripts/activate
To install dependency in virtual environment
pip install Django
Now you all set to use virtual env's Django.
which python should map to venv path.
Now you have two ways to run python script
#1 use direct python path
absolute/path/of/venv/bin/python3 -c 'import proj_application.cron as cron; cron.test()'
#2 activate virtual environment and use the bash script as same.
#!/bin/bash
cd "$(dirname "$0")";
CWD="$(pwd)"
echo $CWD
source venv/Scripts/python # this will be in windows
python -c 'import proj_application.cron as cron; cron.test()'

Related

How to source an env file and then execute Python script via cronjob? [duplicate]

I am trying to run a Django management command from cron. I am using virtualenv to keep my project sandboxed.
I have seen examples here and elsewhere that show running management commands from within virtualenv's like:
0 3 * * * source /home/user/project/env/bin/activate && /home/user/project/manage.py command arg
However, even though syslog shows an entry when the task should have started, this task never actually runs (the log file for the script is empty). If I run the line manually from the shell, it works as expected.
The only way I can currently get the command to run via cron, is to break the commands up and put them in a dumb bash wrapper script:
#!/bin/sh
source /home/user/project/env/bin/activate
cd /home/user/project/
./manage.py command arg
EDIT:
ars came up with a working combination of commands:
0 3 * * * cd /home/user/project && /home/user/project/env/bin/python /home/user/project/manage.py command arg
At least in my case, invoking the activate script for the virtualenv did nothing. This works, so on with the show.
You should be able to do this by using the python in your virtual environment:
/home/my/virtual/bin/python /home/my/project/manage.py command arg
EDIT: If your django project isn't in the PYTHONPATH, then you'll need to switch to the right directory:
cd /home/my/project && /home/my/virtual/bin/python ...
You can also try to log the failure from cron:
cd /home/my/project && /home/my/virtual/bin/python /home/my/project/manage.py > /tmp/cronlog.txt 2>&1
Another thing to try is to make the same change in your manage.py script at the very top:
#!/home/my/virtual/bin/python
Running source from a cronfile won't work as cron uses /bin/sh as its default shell, which doesn't support source. You need to set the SHELL environment variable to be /bin/bash:
SHELL=/bin/bash
*/10 * * * * root source /path/to/virtualenv/bin/activate && /path/to/build/manage.py some_command > /dev/null
It's tricky to spot why this fails as /var/log/syslog doesn't log the error details. Best to alias yourself to root so you get emailed with any cron errors. Simply add yourself to /etc/aliases and run sendmail -bi.
More info here:
http://codeinthehole.com/archives/43-Running-django-cronjobs-within-a-virtualenv.html
the link above is changed to:
https://codeinthehole.com/tips/running-django-cronjobs-within-a-virtualenv/
Don't look any further:
0 3 * * * /usr/bin/env bash -c 'cd /home/user/project && source /home/user/project/env/bin/activate && ./manage.py command arg' > /dev/null 2>&1
Generic approach:
* * * * * /usr/bin/env bash -c 'YOUR_COMMAND_HERE' > /dev/null 2>&1
The beauty about this is you DO NOT need to change the SHELL variable for crontab from sh to bash
I am sorry for that nth answer but I checked the answers and there is really simpler and neater.
Long story short
Use the python binary of your venv in your cron :
0 3 * * * /home/user/project/env/bin/python /home/user/project/manage.py
Long story
We activate the virtual environment when we want to set the current shell with the python config of that specific virtual environment(that is binaries and modules of that).
It is relevant to work with the current shell : execute multiple python commands on the current shell without the need to reference the full python path of the venv.
In the frame of a cron or even a bash, which value to activate the environment ?
Besides I read in some answers some references to bash rather than sh or still to define a wrapper to call the Python code. But why the hell should we bother with these ?
I repeat, just do it :
0 3 * * * /home/user/project/env/bin/python /home/user/project/manage.py
The documentation confirms that :
You don’t specifically need to activate an environment; activation
just prepends the virtual environment’s binary directory to your path,
so that “python” invokes the virtual environment’s Python interpreter
and you can run installed scripts without having to use their full
path. However, all scripts installed in a virtual environment should
be runnable without activating it, and run with the virtual
environment’s Python automatically.
The only correct way to run python cron jobs when using a virtualenv is to activate the environment and then execute the environment's python to run your code.
One way to do this is use virtualenv's activate_this in your python script, see: http://virtualenv.readthedocs.org/en/latest/userguide.html#using-virtualenv-without-bin-python
Another solution is echoing the complete command including activating the environment and piping it into /bin/bash. Consider this for your /etc/crontab:
***** root echo 'source /env/bin/activate; python /your/script' | /bin/bash
Rather than mucking around with virtualenv-specific shebangs, just prepend PATH onto the crontab.
From an activated virtualenv, run these three commands and python scripts should just work:
$ echo "PATH=$PATH" > myserver.cron
$ crontab -l >> myserver.cron
$ crontab myserver.cron
The crontab's first line should now look like this:
PATH=/home/me/virtualenv/bin:/usr/bin:/bin: # [etc...]
This is a simple way that keeps the crontab command very similar to regular command (tested in Ubuntu 18.04). Some key notes to keep in mind:
You can use the . command instead of source. (crontab uses sh by default, not bash, so it doesn't have source.)
~ and $variables are expanded in crontab commands. (It's only crontab environment statements that don't do variable expansion.)
Here are examples if you have a file ~/myproject/main.py:
* * * * * cd ~/myproject && . .venv/bin/activate && python main.py > /tmp/out1 2>&1
You could also directly call the specific path of the python in the venv directory, then you don't need to call activate.
* * * * * ~/myproject/.venv/bin/python ~/myproject/main.py > /tmp/out2 2>&1
The downside of that is you would need to specify the project path twice, which makes maintenance trickier. To avoid that, you could use a shell variable so you only specify the project path once:
* * * * * project_dir=~/myproject ; $project_dir/.venv/bin/python $project_dir/main.py > /tmp/out3 2>&1
The best solution for me was to both
use the python binary in the venv bin/ directory
set the python path
to include the venv modules directory.
man python mentions modifying the path in shell at $PYTHONPATH or in python with sys.path
Other answers mention ideas for doing this using the shell. From python, adding the following lines to my script allows me to successfully run it directly from cron.
import sys
sys.path.insert(0,'/path/to/venv/lib/python3.3/site-packages');
Here's how it looks in an interactive session --
Python 3.3.2+ (default, Feb 28 2014, 00:52:16)
[GCC 4.8.1] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', '/usr/lib/python3.3', '/usr/lib/python3.3/plat-x86_64-linux-gnu', '/usr/lib/python3.3/lib-dynload']
>>> import requests
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named 'requests'
>>> sys.path.insert(0,'/path/to/venv/modules/');
>>> import requests
>>>
I'd like to add this because I spent some time solving the issue and did not find an answer here for combination of variables usage in cron and virtualenv. So maybe it'll help someone.
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DIR_SMTH="cd /smth"
VENV=". venv/bin/activate"
CMD="some_python_bin do_something"
# m h dom mon dow command
0 * * * * $DIR_SMTH && $VENV && $CMD -k2 some_target >> /tmp/crontest.log 2>&1
It did not work well when it was configured like
DIR_SMTH="cd /smth && . venv/bin/activate"
Thanks #davidwinterbottom, #reed-sandberg and #mkb for giving the right direction. The accepted answer actually works fine until your python need to run a script which have to run another python binary from venv/bin directory.
If you're on python and using a Conda Virtual Environment where your python script contains the shebang #!/usr/bin/env python the following works:
* * * * * cd /home/user/project && /home/user/anaconda3/envs/envname/bin/python script.py 2>&1
Additionally, if you want to capture any outputs in your script (e.g. print, errors, etc) you can use the following:
* * * * * cd /home/user/project && /home/user/anaconda3/envs/envname/bin/python script.py >> /home/user/folder/script_name.log 2>&1
python script
from datetime import datetime
import boto # check wheather its taking the virtualenv or not
import sys
param1=sys.argv[1] #Param
myFile = open('appendtxt.txt', 'a')
myFile.write('\nAccessed on ' + param1+str(datetime.now()))
Cron command
*/1 * * * * cd /Workspace/testcron/ && /Workspace/testcron/venvcron/bin/python3 /Workspace/testcron/testcronwithparam.py param
In above command
*/1 * * * * - Execute every one minte
cd /Workspace/testcron/ - Path of the python script
/Workspace/testcron/venvcron/bin/python3 - Virtualenv path
Workspace/testcron/testcronwithparam.py - File path
param - parameter
I've added the following script as manage.sh inside my Django project, it sources the virtualenv and then runs the manage.py script with whatever arguments you pass to it. It makes it very easy in general to run commands inside the virtualenv (cron, systemd units, basically anywhere):
#! /bin/bash
# this is a convenience script that first sources the venv (assumed to be in
# ../venv) and then executes manage.py with whatever arguments you supply the
# script with. this is useful if you need to execute the manage.py from
# somewhere where the venv isn't sourced (e.g. system scripts)
# get the script's location
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
# source venv <- UPDATE THE PATH HERE WITH YOUR VENV's PATH
source $DIR/../venv/bin/activate
# run manage.py script
$DIR/manage.py "$#"
Then in your cron entry you can just run:
0 3 * * * /home/user/project/manage.sh command arg
Just remember that you need to make the manage.sh script executable
Add the path of the Python installation in the venv but don't activate the environment.
* * * * * /HDD1/shritam_kumar/VENOM/venv/bin/python /HDD1/shritam_kumar/Projects/Voelkner-DE/schedule_product_BA.py
It's that simple.
I had the same issue and spent a lot of time solving that.
None of the solutions here helped me, so I'm sharing what worked for me:
Open a new file "pick_name.sh" open it inside of your project directory.
Inside the "pick_name.sh" file, write and save the following lines:
#!/bin/bash
source /YOUR_VIRTUAL_ENV_PATH/bin/activate
export PYTHONPATH="${PYTHONPATH}:/PATH_TO_CUSTOM_MODULE_YOU_CREATED**OPTIONAL**"
export PYTHONPATH="${PYTHONPATH}:/PATH_TO_ANOTHER_CUSTOM_MODULE_YOU_CREATED**OPTIONAL**"
cd /PATH_TO_DIR_STORING_FILE_NAME.PY
python file_name.py
Go to /var/spool/cron/crontabs (or to where your cron management file sits) and open the 'root' file.
Add these lines to the root file which's inside the crontab folder:
# m h dom mon dow command
* * * * * /PATH_TO_DIR_WHERE_PICK_NAME.SH_SITS/pick_name.sh >> /YOUR_PROJECT_DIR/cron_output.txt 2>&1
Notes:
This command (section 4.) will run the "pick_name.sh" file.
In this example it runs every minute, so make sure you change it according to your needs.
It writes all logs to a log file called "cron_ouput".
No need to create the file before, it will be created automatically.
Make sure to replace all paths (I wrote them in capital letters) to your paths.
You can change file names, if so, make sure to change it in all appearances in my instructions to avoid errors.
If you want to add another py file to run by cron, you need to add it to the "pick_nam.sh" file* not to the cron. Simply duplicate section 2. lines in the "pick_nam.sh" but without the "#!/bin/bash" part.
Then, every time the cron will run "pick_name.sh" it will run all the files you specified inside of it.
Make sure to restart cron after changes, it could have saved me a lot of debugging time, use this command:
systemctl restart cron
Since a cron executes in its own minimal sh environment, here's what I do to run Python scripts in a virtual environment:
* * * * * . ~/.bash_profile; . ~/path/to/venv/bin/activate; python ~/path/to/script.py
(Note: if . ~/.bash_profile doesn't work for you, then try . ~/.bashrc or . ~/.profile depending on how your server is set up.)
This loads your bash shell environment, then activates your Python virtual environment, essentially leaving you with the same setup you tested your scripts in.
No need to define environment variables in crontab and no need to modify your existing scripts.
If you are a MacOS user like me, you can check the crontab error message at /var/mail/{username} file. like this
tail /var/mail/{username}
If there is an "operation not permitted" error, maybe you have to add cron to the Full Disk Access apps (Security & Privacy > Privacy > Full Disk Access apps/execs).
And click + button, go to /usr/sbin, double click the cron file.
then it will fix the "not permitted" error. detailed steps
And this is my code:
0 19 * * * cd /Users/user/Desktop/Project && source /Users/user/Desktop/Project/venv/bin/activate && python command arg
This is a solution that has worked well for me.
source /root/miniconda3/etc/profile.d/conda.sh && \
conda activate <your_env> && \
python <your_application> &
I am using miniconda with Conda version 4.7.12 on a Ubuntu 18.04.3 LTS.
I am able to place the above inside a script and run it via crontab as well without any trouble.
This will also work on crontab -e
* */5 * * * cd /home/project && sudo /home/project/venv/bin/python scripte.py
I had this same issue:
I had written a custom django command to check for geodjango position coordinates inside of geodjango polygons and had trouble automating the task to run, however using this command with crontab worked for me:
* * * * * ./home/project/locations/locations.sh >> /var/log/locations.log 2>&1

Why can't I get Environ variables within a Flask script?

I have this script:
import os
secret = os.environ.get('MFS')
print(secret)
For some reason, it returns None. I set that environment variable to a secret key that I want to use to set app.config['SECRET_KEY']. But, if I run this command: python3 -c 'import os;print(os.environ.get("MFS"))', it returns the correct environment variable. It doesn't matter if I cd to the project folder and run that or not, it just works. Even though I am using a venv to run the Flask app, that command still works when I run the venv Python. Why is this happening?

Run Python script from browser using virtual environment on EC2 instance

TLDR;
I created a virtual environment on my EC2 instance. How can I access this from the browser?
Hey everyone,
I created a virtual environment, following this tutorial, on my EC2 instance to run a simple Python script. Within the terminal, it works without errors. However, I have made a web application and I would like to activate this script from the browser using the virtual environment. When I try this, I get a "Permission denied" error.
PHP
$output=shell_exec('bash /var/app/current/scripts/script.sh');
echo "<pre>$output</pre>";
script.sh
#!/bin/bash
source /home/ec2-user/venv/python3/bin/activate
python3 /var/app/current/scripts/test.py
test.py
from datetime import datetime
from bs4 import BeautifulSoup
import requests
print('hello')
print(datetime.now())
url = "https://www.stackoverflow.com/"
website = requests.get(url).text
soup = BeautifulSoup(website, "html.parser")
print(soup.title)
error
/var/app/current/scripts/script.sh: line 2: /home/ec2-user/venv/python3/bin/activate: Permission denied
Traceback (most recent call last):
File "/var/app/current/scripts/test.py", line 2, in <module>
from bs4 import BeautifulSoup
ModuleNotFoundError: No module named 'bs4'
What I have tried:
I tried to change the permissions on the virtual environment using the following:
chmod a+x /home/ec2-user/venv
This should give all users access to the virtual environment folder: /home/ec2-user/venv
However, I am still getting the error:
/home/ec2-user/venv/python3/bin/activate: Permission denied
I have also tried to give all users the possibility of executing the activation script (/home/ec2-user/venv/python3/bin/activate):
chmod 665 /home/ec2-user/venv/python3/bin/activate
Which results in:
-rw-rw-r-x 1 ec2-user ec2-user /home/ec2-user/venv/python3/bin/activate
However, I still get the same error:
/home/ec2-user/venv/python3/bin/activate: Permission denied
Note:
Note that if I only import datetime and I comment out bs4 and requests (along with everything else regarding BeautifulSoup), then the script works great as it does not have to access the virtual environment to pull in the packages.
*Virtual environment tutorial
You get this error because you have not added libraries that are used in the python script to the virtual env.
In the tutorial you mentioned only boto library is installed.
You need to install libraries you use.
Run this from the command line:
source /home/ec2-user/venv/python3/bin/activate
pip install beautifulsoup4
pip install requests
Alternatively you can create a file and name it for example /home/ec2-user/requirements.txt for example and list all requirements your script use:
beautifulsoup4
requests
Then you can use this file to install all requirements into virtual env:
source /home/ec2-user/venv/python3/bin/activate
pip install -r /home/ec2-user/requirements.txt
Solved!
I got some help from this post, however, needed to modify a few things.
Let's dive into what his answer was:
sudo chown -R your_username:your_username path/to/virtuaelenv/
Okay, this is great, but I needed a bit of information.
For me, the web application's username is webapp.
Then, one thing that isn't very clear above is the path. So, my path is:
/home/ec2-user/venv/python3/bin/activate
as mentioned above. Here, you need to change permissions to the /home/ec2-user and NOT to /home/ec2-user/venv
So, to give my application permission to my virtual environment, I needed ran:
sudo chown -R webapp:webapp /home/ec2-user
That worked in the browser! However, this took away my ability to work with it on the server. To do so, I would have to switch it back to:
sudo chown -R ec2-user:ec2-user /home/ec2-user
Being far from ideal to switch back and forth, I tried to change the permissions with chmod instead.
sudo chmod 711 /home/ec2-user
Now I have read, write, and execution permissions whereas everyone else, including the web app, can only execute.
Now it all works 🤓

Django management command won't work in cron

I am having problems scheduling a manage.py celery call myapp.tasks.mytask with my user crontab, in that when cron tries to run the job, it gets this in stderr (which gets mailed to me, as /var/mail/kal)
Unknown command: 'celery'
Type 'manage.py help' for usage.
The same command works completely from a regular bash login shell, but it won't work in crontab.
I am doing this on Debian wheezy:
$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description: Debian GNU/Linux 7.0 (wheezy)
Release: 7.0
Codename: wheezy
I have read many similar questions on StackOverflow and tried many of the suggested solutions. None of them have worked for me so far. Here are the solutions I have tried so far:
First, I made sure to specify relevant environment variables in the crontab:
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
NOTE: these stay in place in all of the following solutions.
1. Using full paths to the python executable and manage.py scripts
* * * * * /home/kal/.virtualenvs/foo_dev/bin/python /home/kal/foo/manage.py celery call myapp.tasks.mytask
2. cd'ing into the project path first
* * * * * cd /home/kal/foo && /home/kal/.virtualenvs/foo_dev/bin/python ./manage.py celery call myapp.tasks.mytask
3. Wrapping everything in a bash script
Content of ~/mytask.sh:
#!/usr/bin/env bash
source /home/kal/.virtualenvs/foo_dev/bin/activate;
cd /home/kal/foo;
./manage.py celery call myapp.tasks.mytask;
The crontab line:
* * * * * ~/mytask.sh
I even modified myproj/settings.py to output sys.path and sys.executable to stderr and compared the output between cron and the login shell, and they are exactly the same:
Output from cron job:
sys.executable:
/home/kal/.virtualenvs/foo_dev/bin/python
Content of sys.path:
/home/kal/foo
/home/kal/.virtualenvs/foo_dev/src/bootstrap
/home/kal/.virtualenvs/foo_dev/src/django-json-rpc
/home/kal/.virtualenvs/foo_dev/lib/python2.7
/home/kal/.virtualenvs/foo_dev/lib/python2.7/plat-linux2
/home/kal/.virtualenvs/foo_dev/lib/python2.7/lib-tk
/home/kal/.virtualenvs/foo_dev/lib/python2.7/lib-old
/home/kal/.virtualenvs/foo_dev/lib/python2.7/lib-dynload
/usr/lib/python2.7
/usr/lib/python2.7/plat-linux2
/usr/lib/python2.7/lib-tk
/home/kal/.virtualenvs/foo_dev/local/lib/python2.7/site-packages
/home/kal/foo
Output from Bash login shell:
sys.executable:
/home/kal/.virtualenvs/foo_dev/bin/python
Content of sys.path:
/home/kal/foo
/home/kal/.virtualenvs/foo_dev/src/bootstrap
/home/kal/.virtualenvs/foo_dev/src/django-json-rpc
/home/kal/.virtualenvs/foo_dev/lib/python2.7
/home/kal/.virtualenvs/foo_dev/lib/python2.7/plat-linux2
/home/kal/.virtualenvs/foo_dev/lib/python2.7/lib-tk
/home/kal/.virtualenvs/foo_dev/lib/python2.7/lib-old
/home/kal/.virtualenvs/foo_dev/lib/python2.7/lib-dynload
/usr/lib/python2.7
/usr/lib/python2.7/plat-linux2
/usr/lib/python2.7/lib-tk
/home/kal/.virtualenvs/foo_dev/local/lib/python2.7/site-packages
/home/kal/foo
I am completely baffled.
I found the cause of the problem.
It is very very subtle.
The problem is two fold:
There is no USER environment variable in a cron job; only LOGNAME;
When manage.py is run with a management command specified, Django quietly fails over to blank settings if an exception is raised during the import of the settings module.
My settings module was trying to reference os.environ['USER'], which doesn't exist in cron's environment. So importing the settings module causes an exception to be raised, and Django quietly fails over to blank settings, which means blank INSTALLED_APPS and no celery command!
Forget cron.
Use the celerybeat_scheduler.
Make sure djcelery is in INSTALLED_APPS in settings.py as seen at https://pypi.python.org/pypi/django-celery
INSTALLED_APPS += ("djcelery", )
import djcelery
djcelery.setup_loader()
You can use fabric to get this up and running. Your virtualenv is not activating perhaps but with a tool like fabric you issue a command
def reset_app_local(appname):
run("source ~/env/bin/activate && python manage.py celery call myapp.tasks.mytask ",shell="/bin/bash")
And run the following if you run it on local server.
def reset_app_local(appname):
local("source ~/env/bin/activate && python manage.py celery call myapp.tasks.mytask ",shell="/bin/bash")

Cron and virtualenv

I am trying to run a Django management command from cron. I am using virtualenv to keep my project sandboxed.
I have seen examples here and elsewhere that show running management commands from within virtualenv's like:
0 3 * * * source /home/user/project/env/bin/activate && /home/user/project/manage.py command arg
However, even though syslog shows an entry when the task should have started, this task never actually runs (the log file for the script is empty). If I run the line manually from the shell, it works as expected.
The only way I can currently get the command to run via cron, is to break the commands up and put them in a dumb bash wrapper script:
#!/bin/sh
source /home/user/project/env/bin/activate
cd /home/user/project/
./manage.py command arg
EDIT:
ars came up with a working combination of commands:
0 3 * * * cd /home/user/project && /home/user/project/env/bin/python /home/user/project/manage.py command arg
At least in my case, invoking the activate script for the virtualenv did nothing. This works, so on with the show.
You should be able to do this by using the python in your virtual environment:
/home/my/virtual/bin/python /home/my/project/manage.py command arg
EDIT: If your django project isn't in the PYTHONPATH, then you'll need to switch to the right directory:
cd /home/my/project && /home/my/virtual/bin/python ...
You can also try to log the failure from cron:
cd /home/my/project && /home/my/virtual/bin/python /home/my/project/manage.py > /tmp/cronlog.txt 2>&1
Another thing to try is to make the same change in your manage.py script at the very top:
#!/home/my/virtual/bin/python
Running source from a cronfile won't work as cron uses /bin/sh as its default shell, which doesn't support source. You need to set the SHELL environment variable to be /bin/bash:
SHELL=/bin/bash
*/10 * * * * root source /path/to/virtualenv/bin/activate && /path/to/build/manage.py some_command > /dev/null
It's tricky to spot why this fails as /var/log/syslog doesn't log the error details. Best to alias yourself to root so you get emailed with any cron errors. Simply add yourself to /etc/aliases and run sendmail -bi.
More info here:
http://codeinthehole.com/archives/43-Running-django-cronjobs-within-a-virtualenv.html
the link above is changed to:
https://codeinthehole.com/tips/running-django-cronjobs-within-a-virtualenv/
Don't look any further:
0 3 * * * /usr/bin/env bash -c 'cd /home/user/project && source /home/user/project/env/bin/activate && ./manage.py command arg' > /dev/null 2>&1
Generic approach:
* * * * * /usr/bin/env bash -c 'YOUR_COMMAND_HERE' > /dev/null 2>&1
The beauty about this is you DO NOT need to change the SHELL variable for crontab from sh to bash
I am sorry for that nth answer but I checked the answers and there is really simpler and neater.
Long story short
Use the python binary of your venv in your cron :
0 3 * * * /home/user/project/env/bin/python /home/user/project/manage.py
Long story
We activate the virtual environment when we want to set the current shell with the python config of that specific virtual environment(that is binaries and modules of that).
It is relevant to work with the current shell : execute multiple python commands on the current shell without the need to reference the full python path of the venv.
In the frame of a cron or even a bash, which value to activate the environment ?
Besides I read in some answers some references to bash rather than sh or still to define a wrapper to call the Python code. But why the hell should we bother with these ?
I repeat, just do it :
0 3 * * * /home/user/project/env/bin/python /home/user/project/manage.py
The documentation confirms that :
You don’t specifically need to activate an environment; activation
just prepends the virtual environment’s binary directory to your path,
so that “python” invokes the virtual environment’s Python interpreter
and you can run installed scripts without having to use their full
path. However, all scripts installed in a virtual environment should
be runnable without activating it, and run with the virtual
environment’s Python automatically.
The only correct way to run python cron jobs when using a virtualenv is to activate the environment and then execute the environment's python to run your code.
One way to do this is use virtualenv's activate_this in your python script, see: http://virtualenv.readthedocs.org/en/latest/userguide.html#using-virtualenv-without-bin-python
Another solution is echoing the complete command including activating the environment and piping it into /bin/bash. Consider this for your /etc/crontab:
***** root echo 'source /env/bin/activate; python /your/script' | /bin/bash
Rather than mucking around with virtualenv-specific shebangs, just prepend PATH onto the crontab.
From an activated virtualenv, run these three commands and python scripts should just work:
$ echo "PATH=$PATH" > myserver.cron
$ crontab -l >> myserver.cron
$ crontab myserver.cron
The crontab's first line should now look like this:
PATH=/home/me/virtualenv/bin:/usr/bin:/bin: # [etc...]
This is a simple way that keeps the crontab command very similar to regular command (tested in Ubuntu 18.04). Some key notes to keep in mind:
You can use the . command instead of source. (crontab uses sh by default, not bash, so it doesn't have source.)
~ and $variables are expanded in crontab commands. (It's only crontab environment statements that don't do variable expansion.)
Here are examples if you have a file ~/myproject/main.py:
* * * * * cd ~/myproject && . .venv/bin/activate && python main.py > /tmp/out1 2>&1
You could also directly call the specific path of the python in the venv directory, then you don't need to call activate.
* * * * * ~/myproject/.venv/bin/python ~/myproject/main.py > /tmp/out2 2>&1
The downside of that is you would need to specify the project path twice, which makes maintenance trickier. To avoid that, you could use a shell variable so you only specify the project path once:
* * * * * project_dir=~/myproject ; $project_dir/.venv/bin/python $project_dir/main.py > /tmp/out3 2>&1
The best solution for me was to both
use the python binary in the venv bin/ directory
set the python path
to include the venv modules directory.
man python mentions modifying the path in shell at $PYTHONPATH or in python with sys.path
Other answers mention ideas for doing this using the shell. From python, adding the following lines to my script allows me to successfully run it directly from cron.
import sys
sys.path.insert(0,'/path/to/venv/lib/python3.3/site-packages');
Here's how it looks in an interactive session --
Python 3.3.2+ (default, Feb 28 2014, 00:52:16)
[GCC 4.8.1] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', '/usr/lib/python3.3', '/usr/lib/python3.3/plat-x86_64-linux-gnu', '/usr/lib/python3.3/lib-dynload']
>>> import requests
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named 'requests'
>>> sys.path.insert(0,'/path/to/venv/modules/');
>>> import requests
>>>
I'd like to add this because I spent some time solving the issue and did not find an answer here for combination of variables usage in cron and virtualenv. So maybe it'll help someone.
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DIR_SMTH="cd /smth"
VENV=". venv/bin/activate"
CMD="some_python_bin do_something"
# m h dom mon dow command
0 * * * * $DIR_SMTH && $VENV && $CMD -k2 some_target >> /tmp/crontest.log 2>&1
It did not work well when it was configured like
DIR_SMTH="cd /smth && . venv/bin/activate"
Thanks #davidwinterbottom, #reed-sandberg and #mkb for giving the right direction. The accepted answer actually works fine until your python need to run a script which have to run another python binary from venv/bin directory.
If you're on python and using a Conda Virtual Environment where your python script contains the shebang #!/usr/bin/env python the following works:
* * * * * cd /home/user/project && /home/user/anaconda3/envs/envname/bin/python script.py 2>&1
Additionally, if you want to capture any outputs in your script (e.g. print, errors, etc) you can use the following:
* * * * * cd /home/user/project && /home/user/anaconda3/envs/envname/bin/python script.py >> /home/user/folder/script_name.log 2>&1
python script
from datetime import datetime
import boto # check wheather its taking the virtualenv or not
import sys
param1=sys.argv[1] #Param
myFile = open('appendtxt.txt', 'a')
myFile.write('\nAccessed on ' + param1+str(datetime.now()))
Cron command
*/1 * * * * cd /Workspace/testcron/ && /Workspace/testcron/venvcron/bin/python3 /Workspace/testcron/testcronwithparam.py param
In above command
*/1 * * * * - Execute every one minte
cd /Workspace/testcron/ - Path of the python script
/Workspace/testcron/venvcron/bin/python3 - Virtualenv path
Workspace/testcron/testcronwithparam.py - File path
param - parameter
I've added the following script as manage.sh inside my Django project, it sources the virtualenv and then runs the manage.py script with whatever arguments you pass to it. It makes it very easy in general to run commands inside the virtualenv (cron, systemd units, basically anywhere):
#! /bin/bash
# this is a convenience script that first sources the venv (assumed to be in
# ../venv) and then executes manage.py with whatever arguments you supply the
# script with. this is useful if you need to execute the manage.py from
# somewhere where the venv isn't sourced (e.g. system scripts)
# get the script's location
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
# source venv <- UPDATE THE PATH HERE WITH YOUR VENV's PATH
source $DIR/../venv/bin/activate
# run manage.py script
$DIR/manage.py "$#"
Then in your cron entry you can just run:
0 3 * * * /home/user/project/manage.sh command arg
Just remember that you need to make the manage.sh script executable
Add the path of the Python installation in the venv but don't activate the environment.
* * * * * /HDD1/shritam_kumar/VENOM/venv/bin/python /HDD1/shritam_kumar/Projects/Voelkner-DE/schedule_product_BA.py
It's that simple.
I had the same issue and spent a lot of time solving that.
None of the solutions here helped me, so I'm sharing what worked for me:
Open a new file "pick_name.sh" open it inside of your project directory.
Inside the "pick_name.sh" file, write and save the following lines:
#!/bin/bash
source /YOUR_VIRTUAL_ENV_PATH/bin/activate
export PYTHONPATH="${PYTHONPATH}:/PATH_TO_CUSTOM_MODULE_YOU_CREATED**OPTIONAL**"
export PYTHONPATH="${PYTHONPATH}:/PATH_TO_ANOTHER_CUSTOM_MODULE_YOU_CREATED**OPTIONAL**"
cd /PATH_TO_DIR_STORING_FILE_NAME.PY
python file_name.py
Go to /var/spool/cron/crontabs (or to where your cron management file sits) and open the 'root' file.
Add these lines to the root file which's inside the crontab folder:
# m h dom mon dow command
* * * * * /PATH_TO_DIR_WHERE_PICK_NAME.SH_SITS/pick_name.sh >> /YOUR_PROJECT_DIR/cron_output.txt 2>&1
Notes:
This command (section 4.) will run the "pick_name.sh" file.
In this example it runs every minute, so make sure you change it according to your needs.
It writes all logs to a log file called "cron_ouput".
No need to create the file before, it will be created automatically.
Make sure to replace all paths (I wrote them in capital letters) to your paths.
You can change file names, if so, make sure to change it in all appearances in my instructions to avoid errors.
If you want to add another py file to run by cron, you need to add it to the "pick_nam.sh" file* not to the cron. Simply duplicate section 2. lines in the "pick_nam.sh" but without the "#!/bin/bash" part.
Then, every time the cron will run "pick_name.sh" it will run all the files you specified inside of it.
Make sure to restart cron after changes, it could have saved me a lot of debugging time, use this command:
systemctl restart cron
Since a cron executes in its own minimal sh environment, here's what I do to run Python scripts in a virtual environment:
* * * * * . ~/.bash_profile; . ~/path/to/venv/bin/activate; python ~/path/to/script.py
(Note: if . ~/.bash_profile doesn't work for you, then try . ~/.bashrc or . ~/.profile depending on how your server is set up.)
This loads your bash shell environment, then activates your Python virtual environment, essentially leaving you with the same setup you tested your scripts in.
No need to define environment variables in crontab and no need to modify your existing scripts.
If you are a MacOS user like me, you can check the crontab error message at /var/mail/{username} file. like this
tail /var/mail/{username}
If there is an "operation not permitted" error, maybe you have to add cron to the Full Disk Access apps (Security & Privacy > Privacy > Full Disk Access apps/execs).
And click + button, go to /usr/sbin, double click the cron file.
then it will fix the "not permitted" error. detailed steps
And this is my code:
0 19 * * * cd /Users/user/Desktop/Project && source /Users/user/Desktop/Project/venv/bin/activate && python command arg
This is a solution that has worked well for me.
source /root/miniconda3/etc/profile.d/conda.sh && \
conda activate <your_env> && \
python <your_application> &
I am using miniconda with Conda version 4.7.12 on a Ubuntu 18.04.3 LTS.
I am able to place the above inside a script and run it via crontab as well without any trouble.
This will also work on crontab -e
* */5 * * * cd /home/project && sudo /home/project/venv/bin/python scripte.py
I had this same issue:
I had written a custom django command to check for geodjango position coordinates inside of geodjango polygons and had trouble automating the task to run, however using this command with crontab worked for me:
* * * * * ./home/project/locations/locations.sh >> /var/log/locations.log 2>&1

Categories

Resources