Django: Load production settings for celery - django

My Django project has multiple settings file for development, production and testing. And I am using supervisor to manage the celery worker. My question how to load the settings file for celery based on the environment I am in.

By using environment variables. Let's say you have the following setting files at the root of your repository.
config.settings.development.py
config.settings.production.py
...
The recommended way to have your celery instance defined is in your config like celery.py module:
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
# set the default Django settings module for the 'celery' program.
# os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.production')
app = Celery('proj')
# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
app.config_from_object('django.conf:settings', namespace='CELERY')
# Load task modules from all registered Django app configs.
app.autodiscover_tasks()
#app.task(bind=True)
def debug_task(self):
print('Request: {0!r}'.format(self.request))
Instead of setting the DJANGO_SETTINGS_MODULE variable within the module (I have commented that out) make sure that those are present in the environment at the time that supervisord is started.
To set those variables in your staging, testing, and production system you can execute the following bash command.
E.g. on your production system:
$ export DJANGO_SETTINGS_MODULE=config.settings.production
$ echo $DJANGO_SETTINGS_MODULE
I would also suggest you load them from an .env file. In my opinion thats more convenient. You could do that with for example python-dotenv.
Update
The .env file is mostly unique on your different systems and is usually not under source/version control. By unique I mean for development you may have a more verbose LOG_LEVEL or a different SECRET_KEY, because you don't want them to show up in your source control or want to be able to adjust them without modifying your source files.
So, in your base.py (production.py and development.py are inheriting) you can load the variables from the file with for example:
import os
from dotenv import load_dotenv
load_dotenv() # .env file has to be in the same directory
# ...
import os
DJANGO_SETTINGS_MODULE = os.getenv("DJANGO_SETTINGS_MODULE")
print(DJANGO_SETTINGS_MODULE)
# ...
I personally don't use the package since I use docker, which has a declarative way of defining an .env file but the code above should give you an idea how it could work. There are similar packages out there like django-environ which is featured in the book Two Scoops of Django. So I would tend to use this instead of python-dotenv, a matter of taste.

You likely want to configure different settings files. From here you have two options. You can use the django-admin settings param at runtime
django-admin runserver --settings=thecelery.settings
Also, you may have the option to configure settings in the settings.py. If you have a 1 settings file currently, this would require you to setup additional settings files, and set environment variables on the instance. Then in your base settings file, you can do soemthing like this
import os
environment your_env = os.environ["environment"]
if your_env == "celery":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "thecelerysettings")
else:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "defaultsettings")

Related

Running Heroku for local and deployment with settings modules

I've setted up a Django project using settings modules, a base.py, development.py, and production.py but I'm a little confused on how to run the project locally and remotely.
So, I can run python manage.py runserver --settings=<project>.settings.development and everything goes alright but my doubt is: How can I run it through heroku local giving the settings option? It will also help to run it on remotely with production settings.
You can make use of Environment variables
In __init__.py of your settings directory add:
import os
from .base import *
environment = os.environ.get("ENV")
if environment == development:
from .development import *
else:
from .production import *
this will load the development settings module if the value of ENV is set to development, otherwise by default it will load the production settings module.
Configuring environment variables in Heroku

why we use local file settings when initalizing celery app

Most of the places i see
from celery import Celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings.local') --> ??
app = Celery('mysite')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
what is the use of exporting local settings in project, i have seen this in many projects in production we are using local settings, although local mostly inherit base settings where all celery config is defined, by why not mysite.settings.production ?
os.environ.setdefault will first look to content of DJANGO_SETTINGS_MODULE environment variable if not found set it to default value
You don't want to have hassle of setting DJANGO_SETTINGS_MODULE environment variable on every development machine, but in production you will set this variable to production config.

How to create a django package without setting DJANGO_SETTINGS_MODULE as environment variable?

I am creating a package that itself uses Django and I will be using it within other Django applications. The main issue I am facing is that I need to use to settings for various reasons such as logging and other extensive requirements. Since, this package does not have any views/urls, we are writing tests and using pytest to run them. The tests will not run without the settings configured. So initially I put the following snippet in the __init__ file in the root app.
import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "my_package.settings")
django.setup()
Now, the test ran properly and the package as standalone app was working. But the moment I installed it in the main project, it overrides the enviroment variable with it's own settings and you can imagine the kind of havoc it would ensue.
This is the first time I am packaging a django app. So I am not well-versed with best practices and the docs are a little convoluted. I read the structure and code of various packages that use settings in their package but I am still not able to understand how to ensure the package accesses the intended settings and the project's settings is not affected at the same time.
While going throught the docs, I came accross this alternative to setting DJANGO_SETTINGS_MODULE, like this:
from django.conf import settings
settings.configure(DEBUG=True)
As shown here: https://docs.djangoproject.com/en/2.2/topics/settings/#using-settings-without-setting-django-settings-module
But where exactly am I supposed to add this? To every file where the settings are imported or will it work in the __init__ (Tried this but something isn't right, shows Apps aren't loaded )
I tried this as well where I imported my settings as defaults and called configure using them as defaults and called django.setup() as well but didn't do the trick:
# my_package/__init__.py
from django.conf import settings
from my_package import settings as default
if not settings.configured:
settings.configure(default_settings=default, DEBUG=True)
import django
django.setup()
Also, I need settings mainly because I have few parameters that can be overridden in the project that is using the package. When the package is installed, the overridden variables is what I should be able to access in the package during runtime.
If someone can guide on how to tackle this or have a better process of creating packages that need django settings, please do share.
So I ended up finding a way to work without setting the settings module as an environement variable. This enables me to use the specified settings by importing all the overridden settings as well as the default settings from:
Create a apps file for configuring your package as an app.
# my_package/apps.py
from django.apps import AppConfig
class MyPackageConfig(AppConfig):
name = 'my_package'
verbose_name = 'My package'
And, in your package's root. The following snippet in your __init__.py will only set the overridden settings:
# my_package/__init__.py
from django.conf import settings
import django
from my_package import settings as overridden_settings
from django.conf import settings
default_app_config = 'my_package.apps.MyPackageConfig'
if not settings.configured:
# Get the list of attributes the module has
attributes = dir(overridden_settings)
conf = {}
for attribute in attributes:
# If the attribute is upper-cased i.e. a settings variable, then copy it into conf
if attribute.isupper():
conf[attribute] = getattr(overridden_settings, attribute)
# Configure settings using the settings
settings.configure(**conf)
# This is needed since it is a standalone django package
django.setup()
Reference for what django.setup() will do:
https://docs.djangoproject.com/en/2.2/topics/settings/#calling-django-setup-is-required-for-standalone-django-usage
Points to keep in mind:
Since it is in the __init__, this will make sure if you import something from the package, the settings are configured.
As mentioned in the documentation above, you have to make sure that the settings is configured only once and similarly the setup method is called once or it will raise an Exception.
Let me know if this helps or you are able to come up with a better solution to this.

Heroku Config Vars and Django

I want to make specific settings for each environment (local vs staging). I set up Config Vars in my heroku staging app and set DEBUG setting to false to try it out, but it didn't work. Am I missing something or making it wrong?
My seetings.py file
Config Vars in the staging app
Result when I tried somthing wrong
You should create a directory where your current settings.py file is located and name it settings. Then create a base.py, dev.py, and prod.py file in this directory.
Also create an __init__.py in the same location as these 3 settings files and inside that __init__.py put from your_project_name.settings.base import *. In base.py you'll have all the shared settings between prod and dev, and in prod.py and dev.py you would just from .base import * to 'inherit' the settings from the base.py file. This is one of the only cases where it's recommended to import like this.
Then you can set the DJANGO_SETTINGS_MODULE environment variable in production to use my_project_name.settings.prod instead of the default settings variable.
DEBUG in the settings file needs to be set via the environment variable, if available.
So change DEBUG = True to DEBUG = os.environ.get('DEBUG', True) and you should be fine. This is usually called a feature flag (pattern).
Responding:
If you are using a "two scoops" pattern, #wjh18 is on the right path.
The pattern I outlined is solid, in use for years.
Can you see what the python terminal grabs on Heroku via heroku run bash --app APPNAME, then python then import os then os.environ.get('DEBUG'). The should match your settings on Heroku. If so, there may be something in the stack that is inhibiting settings (lazy load) from working correct.
A number of gotcha exist in Django is you deviate from established patterns.
Just in case, the env var is ONLY for the Django settings page, otherwise access the Django DEBUG via proper import of settings (from django.conf import settings).

How to configure multiple brokers in Django and Celery?

Requirement: Django making use of RabbitMQ(Internal) and SQS/Kafka
Both tasks shares common DB/Django models.
Django settings supports only one broker configuration as of Oct 2016
How to have shared tasks with different Queue configurations and broker settings ?
I used the code below to specify multiple brokers. If I remember correctly, Celery tries them in the specified order and uses the first one working.
Note: The IPs and hostnames used in the example below are for illustration purposes and need to be adapted to your environment.
from __future__ import absolute_import
import os
from celery import Celery
# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
app = Celery('proj', broker=["redis://redis:6379/0", "redis://192.168.99.100:6379/0", "redis://192.168.99.102:6379/0"])
# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
app.config_from_object('django.conf:settings', namespace='CELERY')
# Load task modules from all registered Django app configs.
app.autodiscover_tasks()