Deployment deep(er) dive - bitwise options
Bitwise logic can be used to store multiple boolean values in a single integer value - which is handy for storing the state of combinations of on/off switches.
Bitwise arithmetic is easy, useful and ubiquitous among programming languages, and yet surprisingly uncommon in the wild, in Python. (It may be due to the lack of enum support, as having the ability to store multiple enum values in a single field is quite common in those that do).
We use bitwise logic for managing deployment options, as it allows us to store the state of five independent deployment switches in a single number, which in turn makes it easier to pass around and store.
We have a scripted deployment process that handles the migration of code through environments, automatically inferring which operations to perform based on the contents of a changeset. "Does it include static files - then run ./manage.py collectstatic
"; "Database migrations - run ./manage.py migrate
"; and so on. In addition, the deployer can choose to override any of these options by supplying their own "deployment options" value. Passing and parsing deployment options easily is at the heart of our deployment process.
We have five of these switches, all of which may be on or off in any given deployment (that's not strictly true - the 'git push' switch is always on - as no push = no deployment). We store the output of all of these switches in a single integer value.
The key to using integers and bitwise logic is in the values that you give to each individual option. Let's use weekdays as an easy example. Suppose you wanted to build an alarm clock app, and wanted to allow the user to set which days of the week they wanted to be 'on'. (We'll exclude the weekend, just to make things a little shorter.)
Our options would be:
MONDAY = 1
TUESDAY = 2
WEDNESDAY = 4
THURSDAY = 8
FRIDAY = 16
Each value has to be a power of 2 greater than the previous - so the sequence is 1, 2, 4, 8, 16, 32, 64, etc. (This does pose a fundamental limit on the number of switches you could theoritcally use!) Using these values, every number from 1 to 31 represents a unique combination of options, and crucially, ever possible combination of those options. For example:
1 = MONDAY
2 = TUESDAY
3 = MONDAY, TUESDAY
4 = WEDNESDAY
5 = MONDAY, WEDNESDAY
6 = TUESDAY, WEDNESDAY
7 = MONDAY, TUESDAY, WEDNESDAY
...
30 = TUESDAY, WEDNESDAY, THURSDAY, FRIDAY
31 = MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY
In reality, this is how we present the deployment options to the user during a live deployment:
Options have been automatically set to a value of 21 from the contents of the deployment.
----------------------------
Target environment: demo
Git branch: master
Options: 21
[1] Git push: True
[2] Syncdb: False
[4] Run migrations: True
[8] Load fixtures: False
[16] Collect static: True
----------------------------
Having the values as numbers makes it easy to combine switches if you want to 'manually override' the system - you just add up the numbers pertaining to the options you want:
- Git push + Syncdb + Migrations = 1 + 2 + 4 = 7
- Git push + Collect static = 1 + 16 = 17
Bits are set by simply adding up the numbers, as above. They can be checked just as easily using the following one-line function (with some real values, for clarity):
BITMASK_APP_DEPLOY = 1
BITMASK_SYNCDB = 2
BITMASK_MIGRATE = 4
BITMASK_LOAD_FIXTURES = 8
BITMASK_COLLECT_STATIC = 16
def is_set(bits, bitmask):
"""
Parse value to determine whether bits are set.
Used to parse the BITMASK_* values, as follows:
>>> is_set(7, BITMASK_APP_DEPLOY)
True
>>> is_set(3, BITMASK_MIGRATE)
False
"""
return (bits & bitmask) == bitmask
It seems as if Python's (2.x) lack of built-in enum support has led to the use of bitwise logic being less common that in other languages - which is a shame, as it's a useful technique.
Making Freelance Work