LINQ To SQL Validator (Python)

here is a very simple LINQ To SQL model Validator.

Specifically, it checks if any tables have an IDENTITY clause set, where it is not expected.

This is script was used to sanity check a project, where LINQ To SQL was used to copy data into tables, and we wanted to preserve the ID of each record - hence we do NOT want IDENTITY property switched on.

This script might be handy as a template for validating a LINQ To SQL Model, as part of a build step.

Dependencies: Python 2.7



 """ 

 ValidateLinqDesigner.py 

 Script to validate a LINQ Designer.cs source code file,  

 to see are any table classes set to have IDENTITY when they should not do so. 

 USAGE:    ValidateLinqDesigner.py <Designer.cs file to validate> <file listing classes to ignore> [OPTIONS] 

 where OPTIONS are: 

   -w -warnings    Show warnings only (non-verbose output) 

 """ 

 import csv 

 import getopt 

 import getpass 

 import re 

 import os 

 import pyodbc 

 import shutil 

 import subprocess 

 import sys 

 import win32api 

 from string import split 

 ############################################################### 

 # settings: 

 #LOG_WARNINGS_ONLY - this means, only output if the verbosity is LOG_WARNINGS 

 LOG_WARNINGS, LOG_WARNINGS_ONLY, LOG_NORMAL, LOG_VERBOSE = range(4) 

 #enum to track current state of the source code parser: 

 PARSE_OUTSIDE_CLASS, PARSE_IN_CLASS = range(2) 

 logVerbosity = LOG_NORMAL #LOG_VERBOSE 

 numWarnings = 0 

 pathToDesignerCSfile = "" 

 #some classes should be ignored (as they are NOT targets for writing) 

 pathToIgnoreClassesFile = "" 

 #need to check for these illegal phrases within each class: 

 illegalPhrases = ['IDENTITY"', 'IsDbGenerated=true'] 

 ############################################################### 

 #usage() - prints out the usage text, from the top of this file :-) 

 def usage(): 

   print __doc__ 

 ############################################################### 

 #main() - main program entry point 

 #args = <Designer.cs file to validate> <file listing classes to ignore> [OPTIONS] 

 def main(argv): 

   global pathToDesignerCSfile, pathToIgnoreClassesFile 

   try: 

     opts, args = getopt.getopt(argv, "w", ["warnings"]) 

   except getopt.GetoptError: 

     usage() 

     sys.exit(2) 

   if(len(args) != 2): 

     usage() 

     sys.exit(3) 

   #assign the args to variables: 

   pathToDesignerCSfile = args[0] 

   pathToIgnoreClassesFile = args[1] 

   for opt, arg in opts: 

     if opt in ("-w", "--warnings"): 

       setLogVerbosity(LOG_WARNINGS) 

 if __name__ == "__main__": 

   main(sys.argv[1:]) 

 ############################################################### 

 #FUNCTIONS - support 

 def addWarning(warningMsg): 

   global numWarnings 

   numWarnings = numWarnings + 1 

   printOut("! WARNING - " + warningMsg, LOG_WARNINGS) 

 #provide access to the global that is in this module: 

 def getNumWarnings(): 

   return numWarnings 

 #printOut() 

 #this function prints out, according to user's options for verbosity 

 def printOut(txt, verb = LOG_NORMAL, bNewLine = True): 

   global logVerbosity 

   #txt = "> " + txt #prefix to make it easier to grep our output from 7zip's 

   if(bNewLine): 

     txt = txt + "\n" 

   if verb == LOG_WARNINGS_ONLY: 

     if logVerbosity == LOG_WARNINGS: #special case :-( 

       sys.stdout.write(txt) 

   elif(logVerbosity >= verb): 

     sys.stdout.write(txt) 

 #get the global in this module 

 def getLogVerbosity(): 

   global logVerbosity 

   return logVerbosity 

 #set the global in this module 

 def setLogVerbosity(verbosity): 

   global logVerbosity 

   logVerbosity = verbosity 

 #LOG_VERBOSE only - this is for debugging really, to see does the class parser work OK: 

 def printOutSourceCodeByClass(sourceCodeByClass):   

   separator = "*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x" 

   for className in sourceCodeByClass: 

     printOut("class " + className + separator, LOG_VERBOSE) 

     printOut(sourceCodeByClass[className], LOG_VERBOSE) 

     printOut("", LOG_VERBOSE) 

 ############################################################### 

 #FUNCTIONS - implementation 

 def readClassesToIgnoreFile(pathToIgnoreClassesFile): 

   classesToIgnore = [] 

   listFileReader = csv.reader(open(pathToIgnoreClassesFile, 'rb'), delimiter=',') 

   for row in listFileReader: 

     if(len(row) > 0): 

       classToIgnore = row[0] 

       if(classToIgnore[0] == '#'): 

         continue # a comment line 

       classesToIgnore.append(classToIgnore) 

   return classesToIgnore 

 def parseSourceCode(pathToDesignerCSfile): 

   if pathToDesignerCSfile.lower().endswith(".cs"): 

     return parseSourceCodeCS(pathToDesignerCSfile) 

   else: 

     raise Exception("NotImplemented - cannot parse source code file with this extension: " + pathToDesignerCSfile) 

 def parseClassNameCS(line): 

   #take the first word that occurs after "class ": 

   words = line.split() #consequtive whitespace is one delimiter 

   bFoundClass = False 

   className = "" 

   for word in words: 

     if word == "class": 

       bFoundClass = True 

     elif bFoundClass: 

       className = word 

       break 

   return className 

 def isLineCommentCS(line): 

   line = line.strip() 

   return line.startswith("//") 

 def isClassStartLineCS(line): 

   classStartToken = " class " 

   if not isLineCommentCS(line): 

     return classStartToken in line 

   else: 

     return False 

 def parseSourceCodeCS(pathToDesignerCSfile): 

   sourceCodeByClass = dict() 

   sourceFile = open(pathToDesignerCSfile, 'rb') 

   sourceCodeThisClass = "" 

   thisClassName = "" 

   parseState = PARSE_OUTSIDE_CLASS # PARSE_IN_CLASS 

   for line in sourceFile: 

     if isClassStartLineCS(line): 

       newClassName = parseClassNameCS(line) 

       if parseState == PARSE_OUTSIDE_CLASS: 

         parseState = PARSE_IN_CLASS 

         thisClassName = newClassName 

       elif parseState == PARSE_IN_CLASS: #we found a new class: (lazy way to divide up code by START of a class, which is good enough for our needs) 

         sourceCodeByClass[thisClassName] = sourceCodeThisClass 

         thisClassName = newClassName 

         sourceCodeThisClass = "" 

       else: 

         raise Exception("unhandled parse state "+ str(parseState)) 

     if parseState == PARSE_IN_CLASS: 

       sourceCodeThisClass = sourceCodeThisClass + line 

   #handle the last class: 

   if len(thisClassName) > 0: 

     sourceCodeByClass[thisClassName] = sourceCodeThisClass 

   printOutSourceCodeByClass(sourceCodeByClass) 

   return sourceCodeByClass 

 def filterSourceCodeByClass(sourceCodeByClass, classesToIgnore): 

   filteredSourceCodeByClass = dict() 

   for className in sourceCodeByClass: 

     if not className in classesToIgnore: 

       filteredSourceCodeByClass[className] = sourceCodeByClass[className] 

   return filteredSourceCodeByClass 

 def validateClassSourceCodeCS(className, sourceCodeForClass, illegalPhrases): 

   for illegalPhrase in illegalPhrases: 

     if illegalPhrase in sourceCodeForClass: 

       raise Exception("!!! Illegal phrase " + illegalPhrase + " found in class " + className) 

   return 

 def validateSourceCodeByClass(filteredSourceCodeByClass, illegalPhrases): 

   for className in filteredSourceCodeByClass: 

     printOut("Validating class " + className) 

     validateClassSourceCodeCS(className, filteredSourceCodeByClass[className], illegalPhrases) 

 ############################################################### 

 #main 

 classesToIgnore = readClassesToIgnoreFile(pathToIgnoreClassesFile) 

 sourceCodeByClass = parseSourceCode(pathToDesignerCSfile) 

 filteredSourceCodeByClass = filterSourceCodeByClass(sourceCodeByClass, classesToIgnore) 

 validateSourceCodeByClass(filteredSourceCodeByClass, illegalPhrases) 

 printOut("[ok]") 

 ###############################################################  

Comments