5.1 Django

When we are implementing simple Django application and we want to start developer server (runserver command), we need to take care of few steps before using the server:

  • update requiretments from setup.py or requiretments.txt
  • apply migrations
  • start celery
  • start runserver

By using Baelfire, we can automate this process, so all these steps will be made before starting the runserver but only when needed.

Code from this example is avalible here: https://github.com/socek/bael-django

5.1.1 Create the application

First of all, we need to create simple Django application for tests. It will not have any views, because all we want for now is to make sure the runserver will start. We will not go into details here, because it is not a Django tutorial. We will start with the setup.py file.

setup.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# -*- encoding: utf-8 -*-
from setuptools import find_packages
from setuptools import setup

install_requires = [
    'baelfire==0.5',
    'Django==1.11.2',
]

if __name__ == '__main__':
    setup(
        name='bdjango',
        packages=find_packages(),
        install_requires=install_requires,
    )

I like to have simple makefile to create virtualenv and install first, so here it is:

Makefile
1
2
3
4
5
6
venv_bdjango/bin/python: venv_bdjango setup.py
	./venv_bdjango/bin/python setup.py develop
	@touch venv_bdjango/bin/python

venv_bdjango:
	virtualenv venv_bdjango

Now we can run it and activate virtualenv.

$ make
virtualenv venv_bdjango
Using base prefix '/usr'
New python executable in /home/socek/projects/bael-django/venv_bdjango/bin/python3
Also creating executable in /home/socek/projects/bael-django/venv_bdjango/bin/python
Installing setuptools, pip, wheel...done.
./venv_bdjango/bin/python setup.py develop
running develop
running egg_info
creating bdjango.egg-info

(...)

Using /home/socek/projects/bael-django/venv_bdjango/lib/python3.6/site-packages
Finished processing dependencies for bdjango==0.0.0

$ source ./venv_bdjango/bin/activate
(venv_bdjango) $

Now we can create our application and start runserver.

(venv_bdjango) $ django-admin startproject mysite
(venv_bdjango) $ python mysite/manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).

You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

June 30, 2017 - 10:34:49
Django version 1.11.2, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Our simple Django application is ready.

5.1.2 Baelfire package and first task

First step in creating automatizion is to create package that will be outside of the django app, which we have created a moment ego.

(venv_bdjango) $ mkdir -p bdjango/
(venv_bdjango) $ touch bdjango/__init__.py

And the first task which will run python setup.py develop

bdjango/tasks.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from baelfire.dependencies import AlwaysTrue
from baelfire.task import SubprocessTask


class UpdateRequirements(SubprocessTask):

    def create_dependecies(self):
        self.build_if(AlwaysTrue())

    def build(self):
        self.popen('python setup.py develop')

Now we can start our first task and see what it will do:

(venv_bdjango) $ bael -t bdjango.tasks:UpdateRequirements
 * INFO bdjango.tasks.UpdateRequirements: Running *
running develop
running egg_info
writing bdjango.egg-info/PKG-INFO
writing dependency_links to bdjango.egg-info/dependency_links.txt
writing requirements to bdjango.egg-info/requires.txt
writing top-level names to bdjango.egg-info/top_level.txt
reading manifest file 'bdjango.egg-info/SOURCES.txt'
writing manifest file 'bdjango.egg-info/SOURCES.txt'
running build_ext
(...)

Ok, but now we have hardcoded path for python executable and for setup.py. This will not work if we change our workdir.

(venv_bdjango) $ cd mysite
(venv_bdjango) mysite/ $ bael -t bdjango.tasks:UpdateRequirements
 * INFO bdjango.tasks.UpdateRequirements: Running *
python: can't open file 'setup.py': [Errno 2] No such file or directory
 * ERROR bdjango.tasks.UpdateRequirements: Error: Command error (2):  *
 * ERROR baelfire.application.application: Error in .baelfire.report *
Traceback (most recent call last):
  File "/home/socek/projects/bael-django/venv_bdjango/bin/bael", line 11, in <module>
    load_entry_point('baelfire', 'console_scripts', 'bael')()
  File "/home/socek/projects/bael-django/venv_bdjango/lib/python3.6/site-packages/baelfire-0.5.0-py3.6.egg/baelfire/application/application.py", line 117, in run
    Application().run()
  File "/home/socek/projects/bael-django/venv_bdjango/lib/python3.6/site-packages/baelfire-0.5.0-py3.6.egg/baelfire/application/application.py", line 113, in run
    self.run_command_or_print_help(args)
  File "/home/socek/projects/bael-django/venv_bdjango/lib/python3.6/site-packages/baelfire-0.5.0-py3.6.egg/baelfire/application/application.py", line 86, in run_command_or_print_help
    task.run()
  File "/home/socek/projects/bael-django/venv_bdjango/lib/python3.6/site-packages/baelfire-0.5.0-py3.6.egg/baelfire/task/task.py", line 59, in run
    self._step_build()
  File "/home/socek/projects/bael-django/venv_bdjango/lib/python3.6/site-packages/baelfire-0.5.0-py3.6.egg/baelfire/task/task.py", line 69, in _step_build
    self.phase_build()
  File "/home/socek/projects/bael-django/venv_bdjango/lib/python3.6/site-packages/baelfire-0.5.0-py3.6.egg/baelfire/task/task.py", line 108, in phase_build
    self.build()
  File "/home/socek/projects/bael-django/bdjango/tasks.py", line 11, in build
    self.popen('python setup.py develop')
  File "/home/socek/projects/bael-django/venv_bdjango/lib/python3.6/site-packages/baelfire-0.5.0-py3.6.egg/baelfire/task/process.py", line 41, in popen
    self._post_popen()
  File "/home/socek/projects/bael-django/venv_bdjango/lib/python3.6/site-packages/baelfire-0.5.0-py3.6.egg/baelfire/task/process.py", line 66, in _post_popen
    raise CommandError(self.spp.returncode)
baelfire.error.CommandError: Error: Command error (2):

So now we will implement Core class and do some path configurations.

bdjango/core.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
from os.path import dirname
from baelfire.core import Core


class BdCore(Core):

    def phase_settings(self):
        super(BdCore, self).phase_settings()
        self.paths.set('project', self.get_project_dir(), is_root=True)

        self.paths.set('venv', 'venv_bdjango', parent='project')
        self.paths.set('venv:bin', 'bin', parent='venv')
        self.paths.set('exe:python', 'python', parent='venv:bin')

        self.paths.set('setuppy', 'setup.py', parent='project')

    def get_project_dir(self):
        project_dir = __file__
        for index in range(2):
            project_dir = dirname(project_dir)
        return project_dir

At line 9 we are setting the main project path. The rest of the paths are created depending on this main path. Now we need to implement these settings in our task.

bdjango/tasks.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from baelfire.dependencies import AlwaysTrue
from baelfire.task import SubprocessTask


class UpdateRequirements(SubprocessTask):

    def create_dependecies(self):
        self.build_if(AlwaysTrue())

    def build(self):
        self.popen('{python} {setuppy} develop'.format(
            python=self.paths.get('exe:python'),
            setuppy=self.paths.get('setuppy')),
        )

Last step for this paragraph is to make task with proper Core installed, so we need to create new file with an endpoint.

bdjango/cmd.py
1
2
3
4
5
6
from bdjango.core import BdCore
from bdjango.tasks import UpdateRequirements


def update_requirements():
    return UpdateRequirements(BdCore())

And now we can run it:

(venv_bdjango) $ cd mysite
(venv_bdjango) mysite/ $  bael -t bdjango.cmd:update_requirements
  * INFO bdjango.tasks.UpdateRequirements: Running *
 running develop
 running egg_info
 creating bdjango.egg-info
 writing bdjango.egg-info/PKG-INFO
 writing dependency_links to bdjango.egg-info/dependency_links.txt
 writing requirements to bdjango.egg-info/requires.txt
 writing top-level names to bdjango.egg-info/top_level.txt
 writing manifest file 'bdjango.egg-info/SOURCES.txt'
 reading manifest file 'bdjango.egg-info/SOURCES.txt'
 writing manifest file 'bdjango.egg-info/SOURCES.txt'
 running build_ext
 Creating /home/socek/projects/bael-django/venv_bdjango/lib/python3.6/site-packages/bdjango.egg-link (link to .)
 Removing bdjango 0.0.0 from easy-install.pth file
 Adding bdjango 0.0.0 to easy-install.pth file

(...)

5.1.3 First real dependency

So now we have a script, which will always run python setup.py develop. But we need to run this only, when setup.py has rebuilded. That is why we need to change the task implementation.

bdjango/tasks.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from baelfire.task import FileTask
from baelfire.task import SubprocessTask
from baelfire.dependencies import FileChanged


class UpdateRequirements(SubprocessTask, FileTask):

    output_name = 'flags:requirements'

    def create_dependecies(self):
        self.build_if(FileChanged('setuppy'))

    def build(self):
        self.popen('{python} {setuppy} develop'.format(
            python=self.paths.get('exe:python'),
            setuppy=self.paths.get('setuppy')),
        )

        open(self.output, 'w').close()

And also, we need to add some configurations to the core.

bdjango/core.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from os.path import dirname
from baelfire.core import Core


class BdCore(Core):

    def phase_settings(self):
        super(BdCore, self).phase_settings()
        self.paths.set('project', self.get_project_dir(), is_root=True)

        self.paths.set('venv', 'venv_bdjango', parent='project')
        self.paths.set('venv:bin', 'bin', parent='venv')
        self.paths.set('exe:python', 'python', parent='venv:bin')

        self.paths.set('setuppy', 'setup.py', parent='project')

        self.paths.set('bdjango', 'bdjango', parent='project')
        self.paths.set('flags', 'flags', parent='bdjango')
        self.paths.set('flags:requirements', 'req.flag', parent='flags')

    def get_project_dir(self):
        project_dir = __file__
        for index in range(2):
            project_dir = dirname(project_dir)
        return project_dir

In this example, when we make update and we will not change the setup.py file, the script will not start the rebuild. In those scripts we use bdjango/flags directory, as a place for storing the flags. You should create a folder before running the bael application.

(venv_bdjango) $ mkdir -p bdjango/flags
(venv_bdjango) $ bael -t bdjango.cmd:update_requirements
 * INFO bdjango.tasks.UpdateRequirements: Running *
running develop
running egg_info
writing bdjango.egg-info/PKG-INFO
writing dependency_links to bdjango.egg-info/dependency_links.txt
writing requirements to bdjango.egg-info/requires.txt
writing top-level names to bdjango.egg-info/top_level.txt
reading manifest file 'bdjango.egg-info/SOURCES.txt'
writing manifest file 'bdjango.egg-info/SOURCES.txt'
running build_ext
Creating /home/socek/projects/bael-django/venv_bdjango/lib/python3.6/site-packages/bdjango.egg-link (link to .)
bdjango 0.0.0 is already the active version in easy-install.pth

(...)
(venv_bdjango) $ bael -t bdjango.cmd:update_requirements
(venv_bdjango) $ touch setup.py
(venv_bdjango) $ bael -t bdjango.cmd:update_requirements
 * INFO bdjango.tasks.UpdateRequirements: Running *
running develop
running egg_info
writing bdjango.egg-info/PKG-INFO
writing dependency_links to bdjango.egg-info/dependency_links.txt
writing requirements to bdjango.egg-info/requires.txt
writing top-level names to bdjango.egg-info/top_level.txt
reading manifest file 'bdjango.egg-info/SOURCES.txt'
writing manifest file 'bdjango.egg-info/SOURCES.txt'
running build_ext
Creating /home/socek/projects/bael-django/venv_bdjango/lib/python3.6/site-packages/bdjango.egg-link (link to .)
bdjango 0.0.0 is already the active version in easy-install.pth

(...)

5.1.4 Run Task in chain

If we can create a task for updating the requiretments, why not create a task for creating flags folder? It is very simple to do that.

bdjango/tasks.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from os import mkdir

from baelfire.dependencies import FileChanged
from baelfire.dependencies import TaskRebuilded
from baelfire.task import FileTask
from baelfire.task import SubprocessTask


class CreateFlagsFolder(FileTask):

    output_name = 'flags'

    def build(self):
        mkdir(self.output)


class UpdateRequirements(SubprocessTask, FileTask):

    output_name = 'flags:requirements'

    def create_dependecies(self):
        self.build_if(TaskRebuilded(CreateFlagsFolder()))
        self.build_if(FileChanged('setuppy'))

    def build(self):
        self.popen('{python} {setuppy} develop'.format(
            python=self.paths.get('exe:python'),
            setuppy=self.paths.get('setuppy')),
        )

        open(self.output, 'w').close()

CreateFlagsFolder is pretty simple. FileTask has a buildf_if(FileDoesNotExists(self.output)) in the dependency list, so we do not need to implement it. Also we added 1 more dependency in line 22, which indicates that the UpdateRequirements task will first run CreateFlagsFolder and rebuild itself if the CreateFlagsFolder has rebuild.

 (venv_bdjango) $ bael -t bdjango.cmd:update_requirements
 (venv_bdjango) $ rm -rf bdjango/flags
 (venv_bdjango) $ bael -t bdjango.cmd:update_requirements
 * INFO bdjango.tasks.CreateFlagsFolder: Running *
 * INFO bdjango.tasks.UpdateRequirements: Running *
running develop
running egg_info
writing bdjango.egg-info/PKG-INFO
writing dependency_links to bdjango.egg-info/dependency_links.txt
writing requirements to bdjango.egg-info/requires.txt
writing top-level names to bdjango.egg-info/top_level.txt
reading manifest file 'bdjango.egg-info/SOURCES.txt'
writing manifest file 'bdjango.egg-info/SOURCES.txt'
running build_ext
Creating /home/socek/projects/bael-django/venv_bdjango/lib/python3.6/site-packages/bdjango.egg-link (link to .)
bdjango 0.0.0 is already the active version in easy-install.pth

At this point, we have already created the flags folder by hand, that is why the first run did nothing. But after removing the folder, the whole chain has been rebuild.

Most of the Python projects use requiretments.txt file instead of setup.py. In our sample project we will use both, just for the sake of creating Baelfire tasks.

bdjango/tasks.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
from os import mkdir

from baelfire.dependencies import FileChanged
from baelfire.dependencies import TaskRebuilded
from baelfire.task import FileTask
from baelfire.task import SubprocessTask
from baelfire.task import Task


class CreateFlagsFolder(FileTask):

    output_name = 'flags'

    def build(self):
        mkdir(self.output)


class BaseRequirements(SubprocessTask, FileTask):

    def create_dependecies(self):
        super(BaseRequirements, self).create_dependecies()
        self.build_if(TaskRebuilded(CreateFlagsFolder()))

    def _update_flag(self):
        open(self.output, 'w').close()


class SetupPyDevelop(BaseRequirements):
    output_name = 'flags:setuppy'

    def create_dependecies(self):
        super(SetupPyDevelop, self).create_dependecies()
        self.build_if(FileChanged('requirementst_production'))

    def build(self):
        self.popen(
            '{pip} install -r {file}'.format(
                pip=self.paths.get('exe:pip'),
                file=self.paths.get('requirementst_production')))

        self._update_flag()


class UpdateRequirementsProduction(BaseRequirements):
    output_name = 'flags:requirements'

    def create_dependecies(self):
        super(UpdateRequirementsProduction, self).create_dependecies()
        self.build_if(FileChanged('setuppy'))

    def build(self):
        self.popen(
            '{python} {setuppy} develop'.format(
                python=self.paths.get('exe:python'),
                setuppy=self.paths.get('setuppy')))

        self._update_flag()


class UpdateRequirements(Task):

    def create_dependecies(self):
        self.run_before(SetupPyDevelop())
        self.run_before(UpdateRequirementsProduction())

    def build(self):
        pass

Our goal is to update setup.py develop and requiretments.txt in different tasks. I did not wanted to change cmd.py file, so I moved what we had in UpdateRequirements into SetupPyDevelop and created base class BaseRequirements in order to not repeat the same code for UpdateRequirementsProduction. UpdateRequirements was created to link those two tasks, but it does not need to build anything.

Tasks

Some changes needs to be done in the core file as well. Also, we have a new file: requiretments.txt

bdjango/core.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from os.path import dirname
from baelfire.core import Core


class BdCore(Core):

    def phase_settings(self):
        super(BdCore, self).phase_settings()
        self.paths.set('project', self.get_project_dir(), is_root=True)

        self.paths.set('venv', 'venv_bdjango', parent='project')
        self.paths.set('venv:bin', 'bin', parent='venv')
        self.paths.set('exe:python', 'python', parent='venv:bin')
        self.paths.set('exe:pip', 'pip', parent='venv:bin')

        self.paths.set('setuppy', 'setup.py', parent='project')
        self.paths.set('requirementst_production', 'requirements.txt', parent='project')

        self.paths.set('bdjango', 'bdjango', parent='project')
        self.paths.set('flags', 'flags', parent='bdjango')
        self.paths.set('flags:requirements', 'req.flag', parent='flags')
        self.paths.set('flags:setuppy', 'setuppy.flag', parent='flags')

    def get_project_dir(self):
        project_dir = __file__
        for index in range(2):
            project_dir = dirname(project_dir)
        return project_dir
requirements.txt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
baelfire==0.5.0
coverage==4.4.1
Django==1.11.2
Jinja2==2.9.6
MarkupSafe==1.0
mock==2.0.0
MorfDict==0.4.1
pbr==3.1.1
py==1.4.34
pytest==3.1.2
pytest-cov==2.5.1
pytz==2017.2
PyYAML==3.12
six==1.10.0

So now we can run our newly created baelfire tasks.

(venv_bdjango) $ touch requirements.txt
(venv_bdjango) $ bael -t bdjango.cmd:update_requirements
 * INFO bdjango.tasks.UpdateRequirementsProduction: Running *
Requirement already satisfied: baelfire==0.5.0 in ./venv_bdjango/lib/python3.6/site-packages/baelfire-0.5.0-py3.6.egg (from -r /home/socek/projects/bael-django/requirements.txt (line 1))
Requirement already satisfied: coverage==4.4.1 in ./venv_bdjango/lib/python3.6/site-packages/coverage-4.4.1-py3.6-linux-x86_64.egg (from -r /home/socek/projects/bael-django/requirements.txt (line 2))
Requirement already satisfied: Django==1.11.2 in ./venv_bdjango/lib/python3.6/site-packages/Django-1.11.2-py3.6.egg (from -r /home/socek/projects/bael-django/requirements.txt (line 3))
Requirement already satisfied: Jinja2==2.9.6 in ./venv_bdjango/lib/python3.6/site-packages/Jinja2-2.9.6-py3.6.egg (from -r /home/socek/projects/bael-django/requirements.txt (line 4))
Requirement already satisfied: MarkupSafe==1.0 in ./venv_bdjango/lib/python3.6/site-packages/MarkupSafe-1.0-py3.6-linux-x86_64.egg (from -r /home/socek/projects/bael-django/requirements.txt (line 5))
Requirement already satisfied: mock==2.0.0 in ./venv_bdjango/lib/python3.6/site-packages/mock-2.0.0-py3.6.egg (from -r /home/socek/projects/bael-django/requirements.txt (line 6))
Requirement already satisfied: MorfDict==0.4.1 in ./venv_bdjango/lib/python3.6/site-packages/MorfDict-0.4.1-py3.6.egg (from -r /home/socek/projects/bael-django/requirements.txt (line 7))
Requirement already satisfied: pbr==3.1.1 in ./venv_bdjango/lib/python3.6/site-packages/pbr-3.1.1-py3.6.egg (from -r /home/socek/projects/bael-django/requirements.txt (line 8))
Requirement already satisfied: py==1.4.34 in ./venv_bdjango/lib/python3.6/site-packages/py-1.4.34-py3.6.egg (from -r /home/socek/projects/bael-django/requirements.txt (line 9))
Requirement already satisfied: pytest==3.1.2 in ./venv_bdjango/lib/python3.6/site-packages/pytest-3.1.2-py3.6.egg (from -r /home/socek/projects/bael-django/requirements.txt (line 10))
Requirement already satisfied: pytest-cov==2.5.1 in ./venv_bdjango/lib/python3.6/site-packages/pytest_cov-2.5.1-py3.6.egg (from -r /home/socek/projects/bael-django/requirements.txt (line 11))
Requirement already satisfied: pytz==2017.2 in ./venv_bdjango/lib/python3.6/site-packages/pytz-2017.2-py3.6.egg (from -r /home/socek/projects/bael-django/requirements.txt (line 12))
Requirement already satisfied: PyYAML==3.12 in ./venv_bdjango/lib/python3.6/site-packages/PyYAML-3.12-py3.6-linux-x86_64.egg (from -r /home/socek/projects/bael-django/requirements.txt (line 13))
Requirement already satisfied: six==1.10.0 in ./venv_bdjango/lib/python3.6/site-packages/six-1.10.0-py3.6.egg (from -r /home/socek/projects/bael-django/requirements.txt (line 14))
Requirement already satisfied: setuptools in ./venv_bdjango/lib/python3.6/site-packages (from pytest==3.1.2->-r /home/socek/projects/bael-django/requirements.txt (line 10))

(venv_bdjango) $ touch setup.py
(venv_bdjango) $ bael -t bdjango.cmd:update_requirements
 * INFO bdjango.tasks.SetupPyDevelop: Running *
running develop
running egg_info
writing bdjango.egg-info/PKG-INFO
writing dependency_links to bdjango.egg-info/dependency_links.txt
writing requirements to bdjango.egg-info/requires.txt
writing top-level names to bdjango.egg-info/top_level.txt
reading manifest file 'bdjango.egg-info/SOURCES.txt'
writing manifest file 'bdjango.egg-info/SOURCES.txt'
running build_ext
Creating /home/socek/projects/bael-django/venv_bdjango/lib/python3.6/site-packages/bdjango.egg-link (link to .)
bdjango 0.0.0 is already the active version in easy-install.pth
(...)

5.1.5 Runserver

Main purpose of the Baelfire in our sample project is to start runserver with all the dependencies. For now, we have implemented dependency of updating packages to proper version. It is enough to implement a task for starting the developer’s server.

bdjango/tasks.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
from os import mkdir

from baelfire.dependencies import AlwaysTrue
from baelfire.dependencies import FileChanged
from baelfire.dependencies import TaskRebuilded
from baelfire.task import FileTask
from baelfire.task import SubprocessTask
from baelfire.task import Task


class CreateFlagsFolder(FileTask):

    output_name = 'flags'

    def build(self):
        mkdir(self.output)


class BaseRequirements(SubprocessTask, FileTask):

    def create_dependecies(self):
        super(BaseRequirements, self).create_dependecies()
        self.build_if(TaskRebuilded(CreateFlagsFolder()))

    def _update_flag(self):
        open(self.output, 'w').close()


class SetupPyDevelop(BaseRequirements):
    output_name = 'flags:setuppy'

    def create_dependecies(self):
        super(SetupPyDevelop, self).create_dependecies()
        self.build_if(FileChanged('setuppy'))

    def build(self):
        self.popen(
            '{python} {setuppy} develop'.format(
                python=self.paths.get('exe:python'),
                setuppy=self.paths.get('setuppy')))

        self._update_flag()


class UpdateRequirementsProduction(BaseRequirements):
    output_name = 'flags:requirements'

    def create_dependecies(self):
        super(UpdateRequirementsProduction, self).create_dependecies()
        self.build_if(FileChanged('requirementst_production'))

    def build(self):
        self.popen(
            '{pip} install -r {file}'.format(
                pip=self.paths.get('exe:pip'),
                file=self.paths.get('requirementst_production')))

        self._update_flag()


class UpdateRequirements(Task):

    def create_dependecies(self):
        self.run_before(SetupPyDevelop())
        self.run_before(UpdateRequirementsProduction())

    def build(self):
        pass


class StartRunserver(SubprocessTask):

    def create_dependecies(self):
        self.run_before(UpdateRequirements())
        self.build_if(AlwaysTrue())

    def build(self):
        self.popen(
            '{python} {manage} runserver'.format(
                python=self.paths.get('exe:python'),
                manage=self.paths.get('manage')))

The StartRunserver task has been implemented. In line 75 we have added AlwaysTrue dependency which will result in rebuilding this task every time it will be started no matter the dependency checking.

Now we need to implement endpoint for starting new task.

bdjango/cmd.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from bdjango.core import BdCore
from bdjango.tasks import StartRunserver
from bdjango.tasks import UpdateRequirements


def update_requirements():
    return UpdateRequirements(BdCore())


def start_runserver():
    return StartRunserver(BdCore())

No new settings needs to be done, so we will just test this run.

(venv_bdjango) $ bael -t bdjango.cmd:start_runserver
 * INFO bdjango.tasks.StartRunserver: Running *
Performing system checks...

System check identified no issues (0 silenced).

You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

July 03, 2017 - 11:00:12
Django version 1.11.2, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

(CTRL +C -> ...)

(venv_bdjango) $ touch setup.py
bael -t bdjango.cmd:start_runserver
 * INFO bdjango.tasks.SetupPyDevelop: Running *
running develop
running egg_info
writing bdjango.egg-info/PKG-INFO
writing dependency_links to bdjango.egg-info/dependency_links.txt
writing requirements to bdjango.egg-info/requires.txt
writing top-level names to bdjango.egg-info/top_level.txt
reading manifest file 'bdjango.egg-info/SOURCES.txt'
writing manifest file 'bdjango.egg-info/SOURCES.txt'
running build_ext
Creating /home/socek/projects/bael-django/venv_bdjango/lib/python3.6/site-packages/bdjango.egg-link (link to .)
bdjango 0.0.0 is already the active version in easy-install.pth
(...)

 * INFO bdjango.tasks.StartRunserver: Running *
Performing system checks...

System check identified no issues (0 silenced).

You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

July 03, 2017 - 11:00:21
Django version 1.11.2, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

As you can see on the listing above, running the runserver command will start reinstall requiretments before starting the server application.

5.1.6 Migrations

You have probably noticed that runserver is yelling at us (color of the message is red, which means yelling!), that we did not made migrations.

bdjango/tasks.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
from os import mkdir

from baelfire.dependencies import AlwaysTrue
from baelfire.dependencies import FileChanged
from baelfire.dependencies import TaskRebuilded
from baelfire.task import FileTask
from baelfire.task import SubprocessTask
from baelfire.task import Task


class CreateFlagsFolder(FileTask):

    output_name = 'flags'

    def build(self):
        mkdir(self.output)


class BaseRequirements(SubprocessTask, FileTask):

    def create_dependecies(self):
        super(BaseRequirements, self).create_dependecies()
        self.build_if(TaskRebuilded(CreateFlagsFolder()))

    def _update_flag(self):
        open(self.output, 'w').close()


class SetupPyDevelop(BaseRequirements):
    output_name = 'flags:setuppy'

    def create_dependecies(self):
        super(SetupPyDevelop, self).create_dependecies()
        self.build_if(FileChanged('setuppy'))

    def build(self):
        self.popen(
            '{python} {setuppy} develop'.format(
                python=self.paths.get('exe:python'),
                setuppy=self.paths.get('setuppy')))

        self._update_flag()


class UpdateRequirementsProduction(BaseRequirements):
    output_name = 'flags:requirements'

    def create_dependecies(self):
        super(UpdateRequirementsProduction, self).create_dependecies()
        self.build_if(FileChanged('requirementst_production'))

    def build(self):
        self.popen(
            '{pip} install -r {file}'.format(
                pip=self.paths.get('exe:pip'),
                file=self.paths.get('requirementst_production')))

        self._update_flag()


class UpdateRequirements(Task):

    def create_dependecies(self):
        self.run_before(SetupPyDevelop())
        self.run_before(UpdateRequirementsProduction())

    def build(self):
        pass


class BaseManagePy(SubprocessTask):

    def create_dependecies(self):
        self.run_before(UpdateRequirements())

    def _manage(self, command):
        self.popen(
            '{python} {manage} {command}'.format(
                python=self.paths.get('exe:python'),
                manage=self.paths.get('manage'),
                command=command))


class ApplyMigrations(BaseManagePy):

    def create_dependecies(self):
        super(ApplyMigrations, self).create_dependecies()
        self.build_if(AlwaysTrue())

    def build(self):
        self._manage('migrate')


class StartRunserver(BaseManagePy):

    def create_dependecies(self):
        super(StartRunserver, self).create_dependecies()
        self.run_before(ApplyMigrations())
        self.build_if(AlwaysTrue())

    def build(self):
        self._manage('runserver')

Now we have 2 tasks which needs to use manage.py, so I made base class BaseManagePy with default dependency, which make sure that the requiretments will be updated before running manage.py command. At this point we may want to add AlwaysTrue dependency here, but it will be a design flaw. In the future, you may want to use manage.py command which does not need to be rebuild every time it will be started.

(venv_bdjango) $ bael -t bdjango.cmd:start_runserver
 * INFO bdjango.tasks.ApplyMigrations: Running *
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying sessions.0001_initial... OK
 * INFO bdjango.tasks.StartRunserver: Running *
Performing system checks...

System check identified no issues (0 silenced).
July 03, 2017 - 13:00:18
Django version 1.11.2, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Now, every time we will start runserver, we will run migrations before.

Tasks

As you can see, we are using UpdateRequirements in two places, but the Baelfire does not complain and run it only once.

5.1.7 Custom dependency - migration

It is a good idea to rebuild migrations everytime we start the runserver, but it is a better idea to rebuild this only when new migrations will be avalible. The task should search for newly created migration scripts and then rebuild. If at this point you want to add some ifs in the build method, then stop it please and read the whole documentation again. We will make custom dependency instead. This new dependency will search thru all migration scripts and find the newest one. Then it will compare mtime of the file with the mtime of flag file. This is how we will know if the migrations has been applied without connecting to the database. This algorythm is not covering all of the situation that can happend, but for the sake of the example, let’s just say it is enough.

bdjango/dependency.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from os import walk
from os.path import getmtime
from os.path import join

from baelfire.dependencies import FileChanged


class MigrationsChanged(FileChanged):

    migrations_dirname = 'migrations'

    def _map_mtime(self, root):
        """
        convert filename into full path with mtime
        """
        def make(file):
            path = join(root, file)
            mtime = getmtime(path)
            return path, mtime
        return make

    @property
    def path(self):
        # list of files to check
        checkfiles = []

        for root, dirs, files in walk(self.paths.get('src')):
            # check only paths ending with "migrations"
            if root.endswith(self.migrations_dirname):
                # filter for only  '.py' files
                files = filter(lambda file: file.endswith('.py'), files)

                checkfiles += list(map(self._map_mtime(root), files))

        # get the newest file, and return its full path, so the output_file will be compared only to one file
        return max(checkfiles, key=lambda obj: obj[1])[0]

The dependency is ready, so we can change our tasks.py and core.py.

bdjango/dependency.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
from os import mkdir

from baelfire.dependencies import AlwaysTrue
from baelfire.dependencies import FileChanged
from baelfire.dependencies import FileDoesNotExists
from baelfire.dependencies import TaskRebuilded
from baelfire.task import FileTask
from baelfire.task import SubprocessTask
from baelfire.task import Task

from bdjango.dependency import MigrationsChanged


class CreateFlagsFolder(FileTask):

    output_name = 'flags'

    def build(self):
        mkdir(self.output)


class BaseRequirements(SubprocessTask, FileTask):

    def create_dependecies(self):
        super(BaseRequirements, self).create_dependecies()
        self.build_if(TaskRebuilded(CreateFlagsFolder()))

    def _update_flag(self):
        open(self.output, 'w').close()


class SetupPyDevelop(BaseRequirements):
    output_name = 'flags:setuppy'

    def create_dependecies(self):
        super(SetupPyDevelop, self).create_dependecies()
        self.build_if(FileChanged('setuppy'))

    def build(self):
        self.popen(
            '{python} {setuppy} develop'.format(
                python=self.paths.get('exe:python'),
                setuppy=self.paths.get('setuppy')))

        self._update_flag()


class UpdateRequirementsProduction(BaseRequirements):
    output_name = 'flags:requirements'

    def create_dependecies(self):
        super(UpdateRequirementsProduction, self).create_dependecies()
        self.build_if(FileChanged('requirementst_production'))

    def build(self):
        self.popen(
            '{pip} install -r {file}'.format(
                pip=self.paths.get('exe:pip'),
                file=self.paths.get('requirementst_production')))

        self._update_flag()


class UpdateRequirements(Task):

    def create_dependecies(self):
        self.run_before(SetupPyDevelop())
        self.run_before(UpdateRequirementsProduction())

    def build(self):
        pass


class BaseManagePy(SubprocessTask):

    def create_dependecies(self):
        self.run_before(UpdateRequirements())

    def _manage(self, command):
        self.popen(
            '{python} {manage} {command}'.format(
                python=self.paths.get('exe:python'),
                manage=self.paths.get('manage'),
                command=command))


class ApplyMigrations(FileTask, BaseManagePy):
    output_name = 'flags:migrations'

    def create_dependecies(self):
        super(ApplyMigrations, self).create_dependecies()
        self.build_if(FileDoesNotExists(self.output_name))
        self.build_if(MigrationsChanged())

    def build(self):
        self._manage('migrate')
        open(self.output, 'w').close()


class StartRunserver(BaseManagePy):

    def create_dependecies(self):
        super(StartRunserver, self).create_dependecies()
        self.run_before(ApplyMigrations())
        self.build_if(AlwaysTrue())

    def build(self):
        self._manage('runserver')
bdjango/dependency.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from os.path import dirname
from baelfire.core import Core


class BdCore(Core):

    def phase_settings(self):
        super(BdCore, self).phase_settings()
        self.paths.set('project', self.get_project_dir(), is_root=True)

        self.paths.set('venv', 'venv_bdjango', parent='project')
        self.paths.set('venv:bin', 'bin', parent='venv')
        self.paths.set('exe:python', 'python', parent='venv:bin')
        self.paths.set('exe:pip', 'pip', parent='venv:bin')

        self.paths.set('setuppy', 'setup.py', parent='project')
        self.paths.set('requirementst_production', 'requirements.txt', parent='project')

        self.paths.set('bdjango', 'bdjango', parent='project')
        self.paths.set('flags', 'flags', parent='bdjango')
        self.paths.set('flags:requirements', 'req.flag', parent='flags')
        self.paths.set('flags:setuppy', 'setuppy.flag', parent='flags')
        self.paths.set('flags:migrations', 'migrations.flag', parent='flags')

        self.paths.set('src', 'mysite', parent='project')
        self.paths.set('manage', 'manage.py', parent='src')

    def get_project_dir(self):
        project_dir = __file__
        for index in range(2):
            project_dir = dirname(project_dir)
        return project_dir

Now everytime we will start our runserver the migration will be started when needed.

(venv_bdjango) $ bael -t bdjango.cmd:start_runserver
 * INFO bdjango.tasks.ApplyMigrations: Running *
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, hello, sessions
Running migrations:
  No migrations to apply.
 * INFO bdjango.tasks.StartRunserver: Running *
Performing system checks...

System check identified no issues (0 silenced).
July 05, 2017 - 06:58:09
Django version 1.11.2, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
(...)

(venv_bdjango) $ bael -t bdjango.cmd:start_runserver
 * INFO bdjango.tasks.StartRunserver: Running *
Performing system checks...

System check identified no issues (0 silenced).
July 05, 2017 - 06:59:58
Django version 1.11.2, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

(venv_bdjango) $ python manage.py makemigrations
Migrations for 'hello':
  hello/migrations/0002_hello_year.py
    - Add field year to hello
(venv_bdjango) $ bael -t bdjango.cmd:start_runserver
 * INFO bdjango.tasks.ApplyMigrations: Running *
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, hello, sessions
Running migrations:
  Applying hello.0002_hello_year... OK
 * INFO bdjango.tasks.StartRunserver: Running *
Performing system checks...

System check identified no issues (0 silenced).
July 05, 2017 - 07:00:28
Django version 1.11.2, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/

I did not include the code of the models which created the migration scripts. The whole code is avalible on github (link above).

5.1.8 Tasks in the background - celery

Many projects needs to use task schedulers, which works outside of the webserver. For this tutorial we will use celery 4.0.2. For the simplicity, we don’t care if the celery is connecting to the broker.

For the celery we could use normal task, but the downside is that we would need to run this in one terminal and the runserver in second one. In most cases we would need the celery to be run, but not to be visible. We could start the celery in the background, but sometimes we would like to see what is happening with the celery process. I prefer to use screen for starting the celery process and attach/detach whenever I like. For this we will use ScreenTask and AttachScreenTask.

bdjango/dependency.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from os.path import dirname

from baelfire.task.screen import ScreenCore


class BdCore(ScreenCore):

    def phase_settings(self):
        super(BdCore, self).phase_settings()
        self.paths.set('project', self.get_project_dir(), is_root=True)

        self.paths.set('venv', 'venv_bdjango', parent='project')
        self.paths.set('venv:bin', 'bin', parent='venv')
        self.paths.set('exe:python', 'python', parent='venv:bin')
        self.paths.set('exe:pip', 'pip', parent='venv:bin')
        self.paths.set('exe:celery', 'celery', parent='venv:bin')

        self.paths.set('setuppy', 'setup.py', parent='project')
        self.paths.set('requirementst_production', 'requirements.txt', parent='project')

        self.paths.set('bdjango', 'bdjango', parent='project')
        self.paths.set('flags', 'flags', parent='bdjango')
        self.paths.set('flags:requirements', 'req.flag', parent='flags')
        self.paths.set('flags:setuppy', 'setuppy.flag', parent='flags')
        self.paths.set('flags:migrations', 'migrations.flag', parent='flags')

        self.paths.set('pid:celery', 'celery.pid', parent='flags')

        self.paths.set('src', 'mysite', parent='project')
        self.paths.set('manage', 'manage.py', parent='src')

    def get_project_dir(self):
        project_dir = __file__
        for index in range(2):
            project_dir = dirname(project_dir)
        return project_dir

We had to update core.py, because Screen tasks needs to have exe:screen value in paths. Also we have added path for celery pidfile.

bdjango/dependency.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
from os import mkdir

from baelfire.dependencies import AlwaysTrue
from baelfire.dependencies import FileChanged
from baelfire.dependencies import FileDoesNotExists
from baelfire.dependencies import PidIsNotRunning
from baelfire.dependencies import TaskRebuilded
from baelfire.task import FileTask
from baelfire.task import SubprocessTask
from baelfire.task import Task
from baelfire.task.screen import AttachScreenTask
from baelfire.task.screen import ScreenTask

from bdjango.dependency import MigrationsChanged


class CreateFlagsFolder(FileTask):

    output_name = 'flags'

    def build(self):
        mkdir(self.output)


class BaseRequirements(SubprocessTask, FileTask):

    def create_dependecies(self):
        super(BaseRequirements, self).create_dependecies()
        self.build_if(TaskRebuilded(CreateFlagsFolder()))

    def _update_flag(self):
        open(self.output, 'w').close()


class SetupPyDevelop(BaseRequirements):
    output_name = 'flags:setuppy'

    def create_dependecies(self):
        super(SetupPyDevelop, self).create_dependecies()
        self.build_if(FileChanged('setuppy'))

    def build(self):
        self.popen(
            '{python} {setuppy} develop'.format(
                python=self.paths.get('exe:python'),
                setuppy=self.paths.get('setuppy')))

        self._update_flag()


class UpdateRequirementsProduction(BaseRequirements):
    output_name = 'flags:requirements'

    def create_dependecies(self):
        super(UpdateRequirementsProduction, self).create_dependecies()
        self.build_if(FileChanged('requirementst_production'))

    def build(self):
        self.popen(
            '{pip} install -r {file}'.format(
                pip=self.paths.get('exe:pip'),
                file=self.paths.get('requirementst_production')))

        self._update_flag()


class UpdateRequirements(Task):

    def create_dependecies(self):
        self.run_before(SetupPyDevelop())
        self.run_before(UpdateRequirementsProduction())

    def build(self):
        pass


class BaseManagePy(SubprocessTask):

    def create_dependecies(self):
        self.run_before(UpdateRequirements())

    def _manage(self, command):
        self.popen(
            '{python} {manage} {command}'.format(
                python=self.paths.get('exe:python'),
                manage=self.paths.get('manage'),
                command=command))


class ApplyMigrations(FileTask, BaseManagePy):
    output_name = 'flags:migrations'

    def create_dependecies(self):
        super(ApplyMigrations, self).create_dependecies()
        self.build_if(FileDoesNotExists(self.output_name))
        self.build_if(MigrationsChanged())
        self.run_before(UpdateRequirements())

    def build(self):
        self._manage('migrate')
        open(self.output, 'w').close()


class StartCelery(ScreenTask):
    screen_name = 'mysite_celery'

    def create_dependecies(self):
        self.run_before(ApplyMigrations())
        self.build_if(PidIsNotRunning(pid_file_name='pid:celery'))

    def build(self):
        self._screen_run(
            ['{celery} -A mysite worker -l info --pidfile={pidfile}'.format(
                celery=self.paths.get('exe:celery'),
                pidfile=self.paths.get('pid:celery'))],
            cwd=self.paths.get('src'))


class AttachCelery(AttachScreenTask):
    detached_task = StartCelery


class StartRunserver(BaseManagePy):

    def create_dependecies(self):
        super(StartRunserver, self).create_dependecies()
        self.run_before(ApplyMigrations())
        self.run_before(StartCelery())
        self.build_if(AlwaysTrue())

    def build(self):
        self._manage('runserver')

In this file we have added StartCelery task, which will start celery worker in the background and AttachCelery for attaching the celery worker. StartRunserver also have a new dependency which will start celery before starting runserver.

bdjango/cmd.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from bdjango.core import BdCore
from bdjango.tasks import AttachCelery
from bdjango.tasks import StartRunserver
from bdjango.tasks import UpdateRequirements


def update_requirements():
    return UpdateRequirements(BdCore())


def start_runserver():
    return StartRunserver(BdCore())


def attach_celery():
    return AttachCelery(BdCore())

Here we only are adding endpoint for attaching the celery.

Starting runserver:

Start runserver

Attaching celery:

Attach Celery
(venv_bdjango) $ bael -t bdjango.cmd:start_runserver
 * INFO bdjango.tasks.StartCelery: Running *
 * INFO bdjango.tasks.StartRunserver: Running *
Performing system checks...

System check identified no issues (0 silenced).
July 05, 2017 - 11:06:20
Django version 1.11.2, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
(...)

(venv_bdjango) $ bael -t bdjango.cmd:start_runserver
 * INFO bdjango.tasks.StartRunserver: Running *
Performing system checks...

System check identified no issues (0 silenced).
July 05, 2017 - 11:06:20
Django version 1.11.2, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
(...)

# attaching to the celery
(venv_bdjango) $ bael -t bdjango.cmd:attach_celery
 -------------- celery@gringo v4.0.2 (latentcall)
---- **** -----
--- * ***  * -- Linux-4.11.7-1-ARCH-x86_64-with-arch 2017-07-05 11:07:20
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app:         mysite:0x7fa4b1700d68
- ** ---------- .> transport:   amqp://guest:**@localhost:5672//
- ** ---------- .> results:     disabled://
- *** --- * --- .> concurrency: 8 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery


[tasks]
  . mysite.celery.debug_task

5.1.9 Finally - your own command line

It is not very comfortable to use bael command for all this tasks. But it is very simple to make your own command line tool. You can just inherite from baelfire.application.application.Application and make your own task names for -t switch.

bdjango/cmd.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from baelfire.application.application import Application

from bdjango.core import BdCore
from bdjango.tasks import AttachCelery
from bdjango.tasks import StartRunserver
from bdjango.tasks import UpdateRequirements


class BdApplication(Application):
    tasks = {
        'update': UpdateRequirements,
        'runserver': StartRunserver,
        'celery': AttachCelery,
    }

    def get_task(self, name):
        task = self.tasks[name]
        return task(BdCore())


def run():
    BdApplication().run()

Also you need to update setup.py file and run python setup.py develop in order to have new command line tool.

bdjango/setup.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- encoding: utf-8 -*-
from setuptools import find_packages
from setuptools import setup

install_requires = [
    'baelfire==0.5.1',
    'Django==1.11.2',
    'celery==4.0.2',
]

if __name__ == '__main__':
    setup(
        name='bdjango',
        packages=find_packages(),
        install_requires=install_requires,
        entry_points={
            'console_scripts': [
                'bdcmd=bdjango.cmd:run',
            ]
        },
    )

Finally, the quick and simple run:

(venv_bdjango) $ bdcmd -t runserver
 * INFO bdjango.tasks.StartRunserver: Running *
Performing system checks...

System check identified no issues (0 silenced).
July 05, 2017 - 12:30:05
Django version 1.11.2, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.