Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/local/bin/python 

2# encoding: utf-8 

3""" 

4*Given a directory of MySQL scripts, execute the scripts and process the script files according to their success or failure* 

5 

6Run the following code once to set a login-path for your mysql server: 

7 

8 mysql_config_editor set --login-path=<uniqueLoginName> --host=localhost --user=<myUsername> --password 

9 

10This store's your credentials in an encrypted file located at '~/.mylogin.cnf'. 

11Use `mysql_config_editor print --all` to see all of the login-paths set. 

12 

13Usage: 

14 mysqlSucker <pathToDirectory> <loginPath> <databaseName> [-s successRule -f failureRule] 

15 

16 pathToDirectory path to the directory containing the sql scripts to run (scripts must have `.sql` extension) 

17 loginPath the local-path as set with `mysql_config_editor` (`mysqlSucker -h` for more details) 

18 databaseName the name of the database to execute the scripts within 

19 

20Options: 

21 -h, --help show this help message 

22 -s successRule, --success successRule what to do if script succeeds. Default *None* [None|delete|subFolderName] 

23 -f failureRule, --failure failureRule what to do if script fails. Default *None* [None|delete|subFolderName] 

24 

25:Examples: 

26 

27 To simply execute the scripts in a directory you can run: 

28 

29 mysqlSucker /path/to/scriptdir myLoginPath myDatabaseName 

30 

31 To delete script after thay have executed successfully: 

32 

33 mysqlSucker /path/to/scriptdir myLoginPath myDatabaseName -s delete 

34 

35 To move successful script to a `passed` sub-directory of `/path/to/scriptdir` and failed scripts to a `failed` sub-directory 

36 

37 mysqlSucker /path/to/scriptdir myLoginPath myDatabaseName -s pass -f failed 

38 

39:Author: 

40 David Young 

41""" 

42from builtins import str 

43import sys 

44import os 

45import time 

46import collections 

47import datetime 

48from subprocess import Popen, PIPE, STDOUT 

49os.environ['TERM'] = 'vt100' 

50from fundamentals import tools 

51 

52 

53def directory_script_runner( 

54 log, 

55 pathToScriptDirectory, 

56 dbConn=False, 

57 waitForResult=True, 

58 successRule=None, 

59 failureRule=None, 

60 loginPath=False, 

61 databaseName=False, 

62 force=True): 

63 """A function to run all mysql scripts in a given directory (in a modified date order, oldest first) and then act on the script files in accordance with the succcess or failure of their execution 

64 

65 The function can be run with either with an established database connection (`dbConn`) or with a mysql generated `login-path` name (`loginPath`). 

66 

67 **with `dbConn`** 

68 

69 Simply pass the connection `dbConn` established elsewhere in your code. 

70 

71 **with `loginPath`** 

72 

73 As it's insecure to pass in mysql database credentials via the command-line, run the following command from the terminal 

74 

75 ```bash 

76 mysql_config_editor set --login-path=<uniqueLoginName> --host=localhost --user=<myUsername> --password 

77 > Enter password: 

78 ``` 

79 

80 This will store your database credentials in an encrypted file located at '~/.mylogin.cnf'. Use `mysql_config_editor print --all` to see all of the login-paths set. 

81 

82 The `directory_script_runner` function can work by taking advantage of mysql's `--login-path` argument so not to require knowledge of the database credentials. 

83 

84 Pass the login-path name via the `loginPath` parameter to use `directory_script_runner` in this manner. 

85 

86 If both `dbConn` and `loginPath` parameters are given, `dbConn` will be given precedent. 

87 

88 **Key Arguments** 

89 

90 - ``log`` -- logger 

91 - ``pathToScriptDirectory`` -- the path to the directory containing the sql script to be run 

92 - ``databaseName`` -- the name of the database 

93 - ``force`` -- force the script to run, skipping over lines with errors, Default *True* 

94 - ``loginPath`` -- the local-path as set with `mysql_config_editor` 

95 - ``dbConn`` -- the database connection 

96 - ``waitForResult`` -- wait for the mysql script to finish execution? If 'False' the MySQL script will run in background (do not wait for completion), or if 'delete' the script will run then delete regardless of success status. Default *True*. [True|False|delete] 

97 - ``successRule`` -- what to do if script succeeds. Default *None* [None|delete|subFolderName] 

98 - ``failureRule`` -- what to do if script fails. Default *None* [None|delete|subFolderName] 

99 

100 

101 **Return** 

102 

103 - None 

104 

105 

106 **Usage** 

107 

108 To run the scripts in the directroy and not act on the script file use something similar to: 

109 

110 ```python 

111 from fundamentals.mysql import directory_script_runner 

112 directory_script_runner( 

113 log=log, 

114 pathToScriptDirectory="/path/to/mysql_scripts", 

115 databaseName="imports", 

116 loginPath="myLoginDetails" 

117 ) 

118 ``` 

119 

120 To delete successful scripts and archive failed scripts for later inspection: 

121 

122 ```python 

123 from fundamentals.mysql import directory_script_runner 

124 directory_script_runner( 

125 log=log, 

126 pathToScriptDirectory="/path/to/mysql_scripts", 

127 databaseName="imports", 

128 loginPath="myLoginDetails", 

129 successRule="delete", 

130 failureRule="failed" 

131 ) 

132 ``` 

133 

134 This creates a folder at `/path/to/mysql_scripts/failed` and moves the failed scripts into that folder. 

135 

136 Finally to execute the scripts within a directory but not wait for the results to return (much fast but you lose error checking in the MySQL scripts): 

137 

138 ```python 

139 from fundamentals.mysql import directory_script_runner 

140 directory_script_runner( 

141 log=log, 

142 pathToScriptDirectory="/path/to/mysql_scripts", 

143 databaseName="imports", 

144 loginPath="myLoginDetails", 

145 waitForResults=False 

146 ) 

147 ``` 

148 

149 Setting ``waitForResults`` = 'delete' will trash the script once it has run (or failed ... be very careful!) 

150 

151 """ 

152 log.debug('starting the ``directory_script_runner`` function') 

153 

154 # COMPILE A DICTIONARY OF SCRIPTS / MODIFIED TIMES 

155 scriptList = {} 

156 for d in os.listdir(pathToScriptDirectory): 

157 filePath = os.path.join(pathToScriptDirectory, d) 

158 filename = os.path.basename(filePath) 

159 extension = filePath.split(".")[-1] 

160 if os.path.isfile(filePath) and extension == "sql": 

161 modified = datetime.datetime.strptime(time.ctime( 

162 os.path.getmtime(filePath)), "%a %b %d %H:%M:%S %Y") 

163 scriptList[str(modified) + filename] = filePath 

164 

165 # ORDER THE DICTIONARY BY MODIFIED TIME - OLDEST FIRST 

166 scriptList = collections.OrderedDict(sorted(scriptList.items())) 

167 

168 # FORCE MYSQL SCRIPT? 

169 if force: 

170 force = "--force" 

171 else: 

172 force = "" 

173 

174 if dbConn: 

175 for k, v in list(scriptList.items()): 

176 scriptname = os.path.basename(v) 

177 exception = execute_mysql_script( 

178 pathToScript=v, 

179 log=log, 

180 dbConn=dbConn 

181 ) 

182 # FAILED SCRIPTS 

183 if exception: 

184 log.error( 

185 'The script %(scriptname)s failed with the following exception: "%(exception)s"' % locals()) 

186 rule = failureRule 

187 print(exception) 

188 else: 

189 rule = successRule 

190 _process_script_file( 

191 pathToScriptDirectory=pathToScriptDirectory, 

192 scriptname=scriptname, 

193 rule=rule, 

194 log=log) 

195 else: 

196 for k, v in list(scriptList.items()): 

197 scriptname = os.path.basename(v) 

198 if waitForResult == True: 

199 cmd = """mysql --login-path=%(loginPath)s %(force)s %(databaseName)s < "%(v)s" """ % locals( 

200 ) 

201 p = Popen(cmd, stdout=PIPE, stderr=PIPE, close_fds=True, 

202 env={'PATH': os.getenv('PATH') + ":/usr/local/bin:/usr/bin:/usr/bin:/usr/local/mysql/bin", "MYSQL_TEST_LOGIN_FILE": os.getenv('HOME') + "/.mylogin.cnf"}, shell=True) 

203 stdout, stderr = p.communicate() 

204 

205 if len(stderr): 

206 log.error( 

207 "MySQL Script `%(scriptname)s` Failed: '%(stderr)s'" % locals()) 

208 rule = failureRule 

209 else: 

210 rule = successRule 

211 _process_script_file( 

212 pathToScriptDirectory=pathToScriptDirectory, 

213 scriptname=scriptname, 

214 rule=rule, 

215 log=log) 

216 else: 

217 if waitForResult == "delete": 

218 cmd = """mysql --login-path=%(loginPath)s %(force)s %(databaseName)s < "%(v)s" > /dev/null 2>&1 & rm "%(v)s" """ % locals() 

219 else: 

220 cmd = """mysql --login-path=%(loginPath)s %(force)s %(databaseName)s < "%(v)s" > /dev/null 2>&1 """ % locals() 

221 p = Popen(cmd, close_fds=True, 

222 env={'PATH': os.getenv('PATH') + ":/usr/local/bin:/usr/bin:", "MYSQL_TEST_LOGIN_FILE": os.getenv('HOME') + "/.mylogin.cnf"}, shell=True, stdin=None, stdout=None, stderr=None) 

223 

224 log.debug('completed the ``directory_script_runner`` function') 

225 return None 

226 

227 

228def execute_mysql_script( 

229 pathToScript, 

230 dbConn, 

231 log): 

232 """*execute a mysql script given its file path and return the success or failure status of the execution* 

233 

234 **Key Arguments:** 

235 

236 - `pathToScript` -- path to the sql script to be executed 

237 - `dbConn` -- mysql database connection 

238 - `log` -- logger 

239 

240 **Return:** 

241 

242 - `exception` -- None or the exception thrown during execution of the sql script.  

243 

244 **Usage:** 

245 

246 To execute a mysql script file located on your local file system: 

247 

248 ```python 

249 from fundamentals.mysql import execute_mysql_script 

250 exception = execute_mysql_script( 

251 pathToScript="/path/to/my/script.sql", 

252 log=log, 

253 dbConn=dbConn 

254 ) 

255 

256 if exception: 

257 ... 

258 ``` 

259 """ 

260 log.debug('starting the ``execute_mysql_script`` function') 

261 

262 from fundamentals.mysql import writequery 

263 

264 pathToScript = pathToScript.replace("//", "/") 

265 

266 import codecs 

267 pathToReadFile = pathToScript 

268 try: 

269 log.debug("attempting to open the file %s" % (pathToReadFile,)) 

270 readFile = codecs.open(pathToReadFile, encoding='utf-8', mode='r') 

271 thisData = readFile.read() 

272 readFile.close() 

273 except IOError as e: 

274 message = 'could not open the file %s' % (pathToReadFile,) 

275 log.critical(message) 

276 raise IOError(message) 

277 

278 thisData = thisData.replace(";;", ";").replace("DELIMITER ;", "") 

279 

280 # EXECUTE AND RETURN NONE IF SUCCESSFUL OR THE EXCEPTION IF NOT 

281 try: 

282 writequery( 

283 log=log, 

284 sqlQuery=thisData, 

285 dbConn=dbConn 

286 ) 

287 except Exception as e: 

288 log.debug('completed the ``execute_mysql_script`` function - failure') 

289 return e 

290 

291 log.debug('completed the ``execute_mysql_script`` function - successful') 

292 return None 

293 

294 

295def _process_script_file( 

296 pathToScriptDirectory, 

297 scriptname, 

298 rule, 

299 log): 

300 """*summary of function* 

301 

302 **Key Arguments:** 

303 

304 - `pathToScriptDirectory` -- the path to the directory containing the sql scripts 

305 - `scriptname` -- name to the sql script to be executed 

306 - `rule` -- how to process the script. Default *None* [None|delete|subFolderName] 

307 - `log` -- logger 

308 

309 **Usage:** 

310 

311 ```eval_rst 

312 .. todo:: 

313 

314 add usage info 

315 create a sublime snippet for usage 

316 ``` 

317 

318 ```python 

319 usage code  

320 ```  

321 """ 

322 log.debug('starting the ``_process_script_file`` function') 

323 

324 scriptPath = pathToScriptDirectory + "/" + scriptname 

325 

326 if rule == None or rule == False: 

327 pass 

328 elif rule == "delete": 

329 os.remove(scriptPath) 

330 elif "/" not in rule: 

331 moveTo = pathToScriptDirectory + "/" + rule 

332 # Recursively create missing directories 

333 if not os.path.exists(moveTo): 

334 os.makedirs(moveTo) 

335 moveTo = moveTo + "/" + scriptname 

336 try: 

337 log.debug("attempting to rename file %s to %s" % 

338 (scriptPath, moveTo)) 

339 os.rename(scriptPath, moveTo) 

340 except Exception as e: 

341 log.error( 

342 "could not rename file %s to %s - failed with this error: %s " % (scriptPath, moveTo, str(e),)) 

343 

344 log.debug('completed the ``_process_script_file`` function') 

345 return None 

346 

347# use the tab-trigger below for new function 

348# xt-def-function 

349 

350 

351def main(arguments=None): 

352 """ 

353 The main function used when ``directory_script_runner.py`` is run as a single script from the cl, or when installed as a cl command 

354 """ 

355 

356 # setup the command-line util settings 

357 su = tools( 

358 arguments=arguments, 

359 docString=__doc__, 

360 logLevel="WARNING", 

361 options_first=False, 

362 projectName="fundmentals" 

363 ) 

364 arguments, settings, log, dbConn = su.setup() 

365 

366 # UNPACK REMAINING CL ARGUMENTS USING `EXEC` TO SETUP THE VARIABLE NAMES 

367 # AUTOMATICALLY 

368 for arg, val in list(arguments.items()): 

369 if arg[0] == "-": 

370 varname = arg.replace("-", "") + "Flag" 

371 else: 

372 varname = arg.replace("<", "").replace(">", "") 

373 if isinstance(val, ("".__class__, u"".__class__)): 

374 exec(varname + " = '%s'" % (val,)) 

375 else: 

376 exec(varname + " = %s" % (val,)) 

377 if arg == "--dbConn": 

378 dbConn = val 

379 log.debug('%s = %s' % (varname, val,)) 

380 

381 if successFlag and successFlag.lower() == "none": 

382 successFlag = None 

383 if failureFlag and failureFlag.lower() == "none": 

384 failureFlag = None 

385 

386 directory_script_runner( 

387 log=log, 

388 pathToScriptDirectory=pathToDirectory, 

389 databaseName=databaseName, 

390 loginPath=loginPath, 

391 successRule=successFlag, 

392 failureRule=failureFlag 

393 ) 

394 

395 return 

396 

397if __name__ == '__main__': 

398 main()