|
23 | 23 |
|
24 | 24 | # parse command line arguments, 'sys.argv'
|
25 | 25 | import argparse
|
| 26 | +import configparser |
26 | 27 | import logging
|
27 | 28 | import logging.handlers
|
28 | 29 | import os
|
29 | 30 | import os.path
|
30 | 31 | import sqlite3
|
31 | 32 | import sys
|
32 | 33 |
|
| 34 | +from collections import OrderedDict |
| 35 | + |
33 | 36 |
|
34 | 37 | app_name = 'mysql2sqlite'
|
35 | 38 |
|
36 | 39 | # TODO: Configure formatter to log function/class info
|
37 |
| -syslog_formatter = logging.Formatter('%(name)s - %(levelname)s - %(funcName)s - %(message)s') |
38 |
| -file_formatter = logging.Formatter('%(asctime)s - %(name)s - %(funcName)s - %(levelname)s - %(message)s') |
39 |
| -stdout_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(funcName)s - %(message)s') |
| 40 | +syslog_formatter = logging.Formatter('%(name)s - L%(lineno)d - %(levelname)s - %(funcName)s - %(message)s') |
| 41 | +file_formatter = logging.Formatter('%(asctime)s - %(name)s - L%(lineno)d - %(funcName)s - %(levelname)s - %(message)s') |
| 42 | +stdout_formatter = logging.Formatter('%(asctime)s - %(name)s - L%(lineno)d - %(levelname)s - %(funcName)s - %(message)s') |
40 | 43 |
|
41 | 44 | # Grab root logger and set initial logging level
|
42 | 45 | root_logger = logging.getLogger()
|
|
82 | 85 | log.debug("Logging initialized for %s", __name__)
|
83 | 86 |
|
84 | 87 |
|
| 88 | +######################################################## |
| 89 | +# Collect command-line arguments (e.g., passed by Cron) |
| 90 | +######################################################## |
| 91 | + |
| 92 | +parser = argparse.ArgumentParser( |
| 93 | + # Borrow docstring for this module |
| 94 | + description=__doc__.strip() |
| 95 | + ) |
| 96 | + |
| 97 | +parser.add_argument( |
| 98 | + '--config_file_dir', |
| 99 | + action='store', |
| 100 | + required=False, |
| 101 | + help='The directory path containing general and query config files.') |
| 102 | + |
| 103 | +try: |
| 104 | + log.info('Parsing commandline options') |
| 105 | + args = parser.parse_args() |
| 106 | +except argparse.ArgumentError as error: |
| 107 | + log.exception("Unable to parse command-line arguments: %s", error) |
| 108 | + sys.exit(1) |
| 109 | + |
| 110 | +if args.config_file_dir is not None: |
| 111 | + cmdline_config_file_dir = args.config_file_dir |
| 112 | +else: |
| 113 | + cmdline_config_file_dir = "" |
| 114 | + |
| 115 | + |
85 | 116 | ########################################
|
86 | 117 | # Modules - Third party
|
87 | 118 | ########################################
|
|
115 | 146 | # CONSTANTS - Modify INI config files instead
|
116 | 147 | #######################################################
|
117 | 148 |
|
118 |
| -# Where this script being called from. We will try to load local copies of all |
| 149 | +# Where this script is called from. We will try to load local copies of all |
119 | 150 | # dependencies from this location first before falling back to default
|
120 | 151 | # locations in order to support having all of the files bundled together for
|
121 | 152 | # testing and portable use.
|
|
124 | 155 | # The name of this script used (as needed) by error/debug messages
|
125 | 156 | script_name = os.path.basename(sys.argv[0])
|
126 | 157 |
|
127 |
| -# Read in configuration file. Attempt to read local copy first, then |
128 |
| -# fall back to using the copy provided by SVN+Symlinks |
| 158 | +general_config_file = 'mysql2sqlite_general.ini' |
| 159 | +query_config_file = 'mysql2sqlite_queries.ini' |
129 | 160 |
|
130 |
| -# TODO: Replace with command-line options |
131 |
| -default_config_file_dir = '/etc/whyaskwhy.org/mysql2sqlite/config' |
| 161 | +# Listed in in order of precedence: first match in list wins |
| 162 | +# https://stackoverflow.com/a/28231217 |
| 163 | +# https://www.blog.pythonlibrary.org/2016/03/24/python-201-ordereddict/ |
| 164 | +config_file_paths = OrderedDict({ |
| 165 | + 'cmdline_config_file_dir': cmdline_config_file_dir, |
| 166 | + 'local_config_file_dir': script_path, |
| 167 | + 'user_config_file_dir': os.path.expanduser('~/.config/mysql2sqlite'), |
| 168 | + 'default_config_file_dir': '/etc/mysql2sqlite', |
| 169 | +}) |
132 | 170 |
|
133 |
| -general_config_file = {} |
134 |
| -general_config_file['name'] = 'mysql2sqlite_general.ini' |
135 |
| -general_config_file['local'] = os.path.join(script_path, general_config_file['name']) |
136 |
| -general_config_file['global'] = os.path.join(default_config_file_dir, general_config_file['name']) |
| 171 | +general_config_file_candidates = [] |
| 172 | +query_config_file_candidates = [] |
| 173 | +for key in reversed(config_file_paths): |
137 | 174 |
|
138 |
| -queries_config_file = {} |
139 |
| -queries_config_file['name'] = 'mysql2sqlite_queries.ini' |
140 |
| -queries_config_file['local'] = os.path.join(script_path, queries_config_file['name']) |
141 |
| -queries_config_file['global'] = os.path.join(default_config_file_dir, queries_config_file['name']) |
| 175 | + general_config_file_candidates.append( |
| 176 | + os.path.join(config_file_paths[key], general_config_file)) |
| 177 | + |
| 178 | + query_config_file_candidates.append( |
| 179 | + os.path.join(config_file_paths[key], query_config_file)) |
142 | 180 |
|
143 | 181 | # Prefer the local copy over the "global" one by loading it last (where the
|
144 | 182 | # second config file overrides or "shadows" settings from the first)
|
145 |
| -general_config_file_candidates = [general_config_file['global'], general_config_file['local']] |
146 |
| - |
147 |
| -queries_config_file_candidates = [queries_config_file['global'], queries_config_file['local']] |
148 | 183 |
|
149 | 184 | # Generate configuration setting options
|
150 | 185 | log.debug(
|
|
153 | 188 |
|
154 | 189 | log.debug(
|
155 | 190 | "Passing in these query config file locations for evalution: %s",
|
156 |
| - queries_config_file_candidates) |
| 191 | + query_config_file_candidates) |
157 | 192 |
|
158 | 193 | # Generate configuration setting options
|
159 | 194 | log.info('Parsing config files')
|
160 |
| -general_settings = m2slib.GeneralSettings(general_config_file_candidates) |
161 |
| -query_settings = m2slib.QuerySettings(queries_config_file_candidates) |
| 195 | + |
| 196 | +# Apply handler early so that console logging is enabled prior to parsing |
| 197 | +# configuration files. The provided filter configuration allows logging |
| 198 | +# warning and error messages only while the settings object has yet to be |
| 199 | +# defined. |
| 200 | +app_logger.addHandler(console_handler) |
| 201 | +console_handler.addFilter(m2slib.ConsoleFilterFunc(settings=None)) |
| 202 | + |
| 203 | +try: |
| 204 | + general_settings = m2slib.GeneralSettings(general_config_file_candidates) |
| 205 | +except configparser.NoSectionError as error: |
| 206 | + log.exception("Error parsing configuration file: %s", error) |
| 207 | + sys.exit(1) |
| 208 | +except IOError as error: |
| 209 | + log.exception("Error reading configuration file: %s", error) |
| 210 | + sys.exit(1) |
| 211 | + |
| 212 | +try: |
| 213 | + query_settings = m2slib.QuerySettings(query_config_file_candidates) |
| 214 | +except configparser.NoSectionError as error: |
| 215 | + log.exception("Error parsing configuration file: %s", error) |
| 216 | + sys.exit(1) |
| 217 | +except IOError as error: |
| 218 | + log.exception("Error reading configuration file: %s", error) |
| 219 | + sys.exit(1) |
| 220 | + |
162 | 221 |
|
163 | 222 | # Now that the settings object has been properly created, lets use it to
|
164 | 223 | # finish configuring console logging for the main application logger.
|
| 224 | +console_handler.removeFilter(m2slib.ConsoleFilterFunc) |
165 | 225 | console_handler.addFilter(m2slib.ConsoleFilterFunc(settings=general_settings))
|
166 |
| -app_logger.addHandler(console_handler) |
| 226 | + |
167 | 227 |
|
168 | 228 | ####################################################################
|
169 | 229 | # Troubleshooting config file flag boolean conversion
|
|
0 commit comments