|
15 | 15 |
|
16 | 16 | # Item 54: Consider module-scoped code to configure deployment environments |
17 | 17 |
|
| 18 | +# A deployment environment is a configuration in which your program runs. |
| 19 | +# Every program has at least one deployment environment, the production |
| 20 | +# environment. The goal of writing a program in the first place is to put it |
| 21 | +# to work in the production environment and achieve some kind of outcome. |
| 22 | + |
| 23 | +# Writing or modifying a program requires being able to run it on the computer |
| 24 | +# you use for developing. The configuration of your development may be much |
| 25 | +# different from your production environments have the same Python packages |
| 26 | +# installed. The trouble is that production environment often require many |
| 27 | +# external assumptions that are hard to reproduce in development environments. |
| 28 | + |
| 29 | +# For example, say you want to run your program in a web server container and |
| 30 | +# give it access to a database. This means that very time you want to modify |
| 31 | +# your program's code, you need to run a server container, the database must |
| 32 | +# be set up properly, and your program needs to password for access. That's a |
| 33 | +# very high cost if all you're trying to do is verify that a one-line change |
| 34 | +# to your program works correctly. |
| 35 | + |
| 36 | +# The best way to work around these issues is to override parts of your |
| 37 | +# program at startup time to provide different functionality depending on the |
| 38 | +# deployment environment. For example, you could have two different __main__ |
| 39 | +# files, one for production and one for development. |
| 40 | + |
| 41 | +# # dev_main.py |
| 42 | +# TESTING = True |
| 43 | +# import db_connection |
| 44 | +# db = db_connection.Database() |
| 45 | + |
| 46 | +# # prod_main.py |
| 47 | +# TESTING = False |
| 48 | +# import db_connection |
| 49 | +# db = db_connection.Database() |
| 50 | + |
| 51 | +# The only difference between the two files is the value of the TESTING |
| 52 | +# constant. Other modules in your program can then import the __main__ module |
| 53 | +# and use the value of TESTING to decide how they define their own attributes. |
| 54 | + |
| 55 | +# # db_connection.py |
| 56 | +# import __main__ |
18 | 57 | # |
| 58 | +# |
| 59 | +# class TestingDatabase(object): |
| 60 | +# #... |
| 61 | +# pass |
| 62 | +# |
| 63 | +# |
| 64 | +# class RealDatabase(object): |
| 65 | +# #... |
| 66 | +# pass |
| 67 | +# |
| 68 | +# |
| 69 | +# if __main__.TESTING: |
| 70 | +# Database = TestingDatabase |
| 71 | +# else: |
| 72 | +# Database = RealDatabase |
| 73 | + |
| 74 | +# The key behavior to notice here is that code running in module scope--not |
| 75 | +# inside any function or method--is just normal Python code. You can use an |
| 76 | +# if statement at the module level to decide how the module will define names. |
| 77 | +# This makes it easy to tailor modules to your various deployment |
| 78 | +# environments. You avoid having to reproduce costly assumptions like |
| 79 | +# database configurations when they aren't needed. You can inject fake or mock |
| 80 | +# implementations that ease interactive development and testing (see Item 56: |
| 81 | +# "Test everything with unittest") |
| 82 | + |
| 83 | +# Note |
| 84 | +# Once your deployment environments get complicated, you should consider moving |
| 85 | +# them out of Python constants (like TESTING) and into dedicated configuration |
| 86 | +# files. Tools like the configparser built-in module let you maintain |
| 87 | +# production configurations separate from code, a distinction that's crucial for |
| 88 | +# collaborating with an operations team. |
| 89 | + |
| 90 | +# This approach can be used for more than working around external assumptions. |
| 91 | +# For example, if you know that your program must work differently based on its |
| 92 | +# host platform, you can inspect the sys module before defining top-level |
| 93 | +# constructs in a module. |
| 94 | + |
| 95 | +# db_connection.py |
| 96 | +import sys |
| 97 | + |
| 98 | + |
| 99 | +class Win32Database(object): |
| 100 | + #... |
| 101 | + pass |
| 102 | + |
| 103 | + |
| 104 | +class PosixDatabase(object): |
| 105 | + #... |
| 106 | + pass |
| 107 | + |
| 108 | + |
| 109 | +if sys.platform.startswith('win32'): |
| 110 | + Database = Win32Database |
| 111 | +else: |
| 112 | + Database = PosixDatabase |
| 113 | + |
| 114 | + |
| 115 | +# Similarly, you can use environment variable from os.environ to guide your |
| 116 | +# module definitions. |
| 117 | + |
| 118 | + |
| 119 | +# Things to remember |
| 120 | + |
| 121 | +# 1. Programs often need to run in multiple deployment environments that each |
| 122 | +# have unique assumptions and configurations. |
| 123 | +# 2. You can tailor a module's contents to different deployment environments |
| 124 | +# by using normal Python statements in module scope. |
| 125 | +# 3. Module contents can be the product of any external condition, including |
| 126 | +# host introspection through the sys and os modules. |
0 commit comments