[Pyple-commits] SF.net SVN: pyple: [17] src
Status: Pre-Alpha
Brought to you by:
anseljh
From: <an...@us...> - 2007-03-16 04:23:26
|
Revision: 17 http://svn.sourceforge.net/pyple/?rev=17&view=rev Author: anseljh Date: 2007-03-15 21:23:24 -0700 (Thu, 15 Mar 2007) Log Message: ----------- 0.3.0 - near-complete rewrite, but it actually works! Modified Paths: -------------- src/pyple.py src/setup.py Modified: src/pyple.py =================================================================== --- src/pyple.py 2007-03-01 02:31:09 UTC (rev 16) +++ src/pyple.py 2007-03-16 04:23:24 UTC (rev 17) @@ -32,6 +32,11 @@ NOTES * PyPLE is lean and mean. Just say no to complexity! * You can run this file to do some simple testing: python pyple.py +* The tests depend on PyYAML, and expect a YAML file called 'pyple-db.yaml' + with database connection parameters. (try 'easy_install pyyaml') You + do NOT need PyYAML to import PyPLE. +* Special thanks to Stanford Law School for allowing me to continue work on + PyPLE in 2007! HISTORY v0.0.1 PyPLE was born in a flash of inspiration at L & L Hawaiian Barbecue in @@ -40,10 +45,10 @@ Komodo IDE. v0.1.0 Port to SQLObject for database persistence begun - 1/22/07 v0.2.0 Complete rewrite of SQLObject version +v0.3.0 Another major rewrite -- simplifying Operator, parameters, etc. 3/15/07. It works! :) """ # Interesting tokens to look for in this source code: -# # #TODO: Items that need doing # #BUG: Identified bug # #NOTE: Note by author @@ -52,188 +57,108 @@ __author__ = 'Ansel Halliburton (anseljh at users dot sourceforge dot net)' #__date__ = '$RevisionDate$'.split()[1].replace('/', '-') __version__ = '$Revision: 7 $' -__release__ = (0,2,0,'alpha',0) +__release__ = (0,3,0,'alpha',0) PYPLE_TAGLINE = "PyPLE (say \"pipple\") -- the Python Persistent Logic Engine" PYPLE_COPYRIGHT = "Copyright (c) 2006-2007 Ansel Halliburton" ASTERISKS = 40 # number of asterisks to use as separator in debug/test output +DEBUG = 1 import re -import types from sqlobject import * # SQLObject ORM from sqlobject.inheritance import InheritableSQLObject -DEBUG = 1 +class Operator(InheritableSQLObject): + + parameters = RelatedJoin('Operator', joinColumn='parameter_id') + name = StringCol(length=255, notNone=False, default=None) -class Parameter(SQLObject): + def eval(self, data=None): + pass # to be overloaded - key = StringCol(length=100, notNone=False, default=None, dbName='param_key') - value = PickleCol(default=None, dbName='param_value') + def addParameter(self, param): + self.addOperator(param) - #def __init__(self, key=None, value=None): - # self.key = key - # self.value = value +class AlwaysTrueOp(Operator): + def eval(self, data=None): + return True -class Operator(InheritableSQLObject): +class AlwaysFalseOp(Operator): + def eval(self, data=None): + return False + +class Regex(Operator): - parameters = MultipleJoin('Parameter') + pattern = StringCol(length=255) + case_sensitive = BoolCol(default=False) - #def __init__(self, parameters): - def _init(self, *args, **kw): - if DEBUG: print "*"*5, "Start of Operator._init" - if DEBUG: print "*"*5, "I'm a", self.sqlmeta.table - if 'params' in kw: - if DEBUG: print "*"*5, "Handling kw:", kw - if isinstance(kw['params'], types.ListType): - if DEBUG: print "*"*5, "It's a list with", len(kw['params']), "elements" - for p in kw['params']: - if DEBUG: print "*"*5, "Element:", p - if isinstance(p, Parameter): - if DEBUG: print "*"*5, "It's a Parameter already" - self.add_parameter(p) - else: - if DEBUG: print "*"*5, "Making it a Parameter..." - self.add_parameter(Parameter(value=p)) - else: - raise TypeError("params must be a list.") - else: - if DEBUG: - print "*"*5, "No params!" - print "*"*5, "KW:", kw - print "*"*5, "ARGS", args - - ##self.parameters = parameters - #self.parameters = [] - #for p in parameters: - # self.parameters.append(Parameter(value=p)) - - self.config = {} # misc storage - if DEBUG: print "*"*5, "End of Operator._init" + class sqlmeta: + table = 'op_regex' - def __getstate__(self): - """ - Allows for pickling of any Operator; stores class type (table) and row ID -- basically a pointer to the object in the DB. - """ - return dict(table=self.sqlmeta.table, id=self.id) - - def __setstate__(self, d): - """ - Unpickler - """ - self = Operator.get(d['id']) - - def eval_fn(self, data): - pass # to be overloaded - def eval(self, data): - #for param in parameters: - # pass - pass # to be overloaded - -class Regex(Operator): - def eval(self, data): - - # JIT regex compilation - if 'regex' not in self.config: - if 'case_sensitive' in self.config and self.config['case_sensitive'] is False: - self.config['regex'] = re.compile(self.parameters[0].value, re.IGNORECASE) - else: - self.config['regex'] = re.compile(self.parameters[0].value) - - # Run regex on data - result = self.config['regex'].search(data) - - # Return True if match; else return False + tmp_re = None + if self.case_sensitive: + tmp_re = re.compile(self.pattern) + else: + tmp_re = re.compile(self.pattern, re.IGNORECASE) + result = tmp_re.search(data) if result: return True - return False - class sqlmeta: - table = 'op_regex' + else: + return False -class CIRegex(Regex): - """Case-insensitive regex""" - #def __init__(self, parameters): - def _init(self, *args, **kw): - #Regex.__init__(self, parameters) - Regex._init(self, *args, **kw) - self.config['case_sensitive'] = False - class sqlmeta: - table = 'op_ci_regex' - class AND(Operator): class sqlmeta: table = 'op_and' - def eval(self, data): + def eval(self, data=None): for param in self.parameters: - if not param.value.eval(data): # - return False # - return True + if not param.eval(data): + return False + return True # implicit else after for loop +class OR(Operator): + class sqlmeta: + table = 'op_or' + def eval(self, data=None): + for param in self.parameters: + if param.eval(data): + return True + return False # implicit else after for loop -class Action(SQLObject): - - # Columns - function = ForeignKey('ActionFunction') - - def invoke(self, data): - self.action_function.invoke(self.get_parameters(data), data) - - def get_parameters(self, data): - """ - Pre-process data and generate list of parameters for invoke() method - """ - #TODO: stub - print "STUB: Action.get_parameters() called" +class NOT(Operator): + class sqlmeta: + table = 'op_not' + def eval(self, data=None): + assert len(self.parameters) == 1, "NOT only works with one Operator parameter" + if self.parameters[0].eval(data): + return False + else: + return True -class ActionFunction(SQLObject): - #TODO: stub - - code = StringCol(notNone=False, default="print 'Default ActionFunction; len(str(data)) =', len(str(data))") - - def invoke(self, parameters, data): - try: - junk = self.evaled_code - except: - print "** about to eval() Python code: ", code - self.evaled_code = eval(code, {}, {}) #run Python eval() on code (in sandbox) +class XOR(Operator): + class sqlmeta: + table = 'op_xor' + def eval(self, data=None): + assert len(self.parameters) == 2, "XOR only works with two Operator parameters" + if self.parameters[0].eval(data) and not self.parameters[1].eval(data): + return True + elif self.parameters[1].eval(data) and not self.parameters[0].eval(data): + return True + else: + return False +class NAND(Operator): + class sqlmeta: + table = 'op_nand' + def eval(self, data=None): + assert len(self.parameters) == 2, "NAND only works with two Operator parameters" + if self.parameters[0].eval(data) and self.parameters[1].eval(data): + return False + else: + return True -class RuleSet(SQLObject): - root_rule = ForeignKey('Operator') # e.g. AND rule at root of rule tree - actions = RelatedJoin('Action') - name = StringCol(length=100) - - def eval(self, data): - """ - Evaluate rules against data; return True/False - """ - return self.root_rule.eval(data) - - def invoke(self, data): - """ - Invoke actions on data (does not evaluate rules) - """ - if root_rule.eval(data): - for action in actions: - action.invoke(data) - - def eval_and_invoke(self, data): - """ - Evaluate rules against data, then invoke actions if result is True - """ - if self.eval(data): - self.invoke(data) - - @staticmethod - def get_by_name(name): - """ - Fetches a single RuleSet instance by name. - """ - return RuleSet.selectBy(name=name)[0] +PYPLE_TABLES = [Operator, AlwaysTrueOp, AlwaysFalseOp, Regex, AND, OR, NOT, XOR, NAND] -PYPLE_TABLES = [Parameter, Operator, Regex, CIRegex, Action, ActionFunction, RuleSet, AND] - - class Engine: def __init__(self, debug=DEBUG): @@ -271,193 +196,80 @@ if __name__ == "__main__": import yaml #PyYAML: YAML parser/emitter - because its easy_install isn't b0rked like PySyck's: see http://pyyaml.org/ticket/44 - E = Engine() - E.connect_to_db(Engine.build_db_uri(yaml.load(open('pyple-db.yaml').read()))) - E.drop_tables() - E.create_tables() - andtf = AND(params=[True, False]) - print "andtf:", andtf - print "andtf.eval():", andtf.eval() + E = Engine() # instantiate the engine + E.connect_to_db(Engine.build_db_uri(yaml.load(open('pyple-db.yaml').read()))) # Connect to the DB + E.drop_tables() # drop all the tables + E.create_tables() # rebuild all the tables + test_true = AlwaysTrueOp() + assert test_true.eval() == True, "AlwaysTrueOp.eval() should always return True" + + test_false = AlwaysFalseOp() + assert test_false.eval() == False, "AlwaysFalseOp.eval() should always return False" + + test_and = AND() + test_and.addParameter(test_true) + test_and.addParameter(test_false) + assert test_and.eval() == False, "AND.eval() of True and False should return False" + + test_or = OR() + test_or.addParameter(test_true) + test_or.addParameter(test_false) + assert test_or.eval() == True, "OR.eval() of True and False should return True" + + test_and_2 = AND() + test_and_2.addParameter(test_and) + test_and_2.addParameter(test_or) + assert test_and_2.eval() == False, "Complex AND should return False" + txt = "ORDER, for the reasons set forth in the related Memoranda Opinions issued in this matter on 06/30/06 and 10/10/06, and for good cause, the final Markman definitions applicable to the disputed claim terms and phrases are as follows: (see Order for details). Signed by Judge T. S. Ellis III on 10/10/06. Copies mailed: yes (pmil) (Entered: 10/12/2006)" - ### Operator tests ### + starts_with_order = Regex(pattern="^ORDER") + assert starts_with_order.eval(txt) == True, "starts_with_order.eval(txt) should be True" - starts_with_order = Regex(params=["^ORDER"]) ### - print "starts_with_order:", starts_with_order.eval(txt) - contains_markman = CIRegex(params=["markman"]) - print "contains_markman:", contains_markman.eval(txt) - both = AND(params=[starts_with_order, contains_markman]) - print "both:", both.eval(txt) - not_matching = CIRegex(params=["foobar"]) - print "not_matching:", not_matching.eval(txt) - false_and = AND(params=[starts_with_order, not_matching]) - print "false_and:", false_and.eval(txt) - print "false_and (alt. data):", false_and.eval("ORDER re: foobar and stuff") + contains_markman = Regex(pattern="markman") + assert contains_markman.eval(txt) == True, "contains_markman.eval(txt) should be True" - ### RuleSet tests ### + both = AND() + both.addParameter(starts_with_order) + both.addParameter(contains_markman) + assert both.eval(txt) == True, "both.eval(txt) should be True" - print "initializing RuleSet" - rs = RuletSet(name="test_ruleset") - rs.root_rule = both - actionfunction1 = ActionFunction() - action1 = Action(function=actionfunction1) - rs.addAction(action1) - print "rs.eval():", rs.eval(txt) - rs.eval_and_invoke(txt) - - -################################################################################################## -# v0.1.0 below... -################################################################################################## - -#class Operator: -# -# def __init__(self, arguments=None): -# if arguments is None: -# self.arguments = [] -# else: -# self.arguments = arguments -# -# def pre_eval(self, data): -# """ -# Runs eval() on any child Operators in arguments -# """ -# evaled_args = [] -# for arg in arguments: -# if isinstance(arg, Operator): -# evaled_args.append(arg.eval(data)) # pass data down the logic stack -# else: -# evaled_args.append(arg) -# return evaled_args -# -# def eval(self, data): -# return self.eval_function(data) -# -# -#class BooleanOperator(Operator): -# def __init__(self, arguments=None): -# Operator.__init__(self, arguments=arguments) -# -#class AND(BooleanOperator): -# def eval_function(self, data): -# for datum in data: -# if not datum: -# return False -# return True -# -#class OR(BooleanOperator): -# def eval_function(self, data): -# for datum in data: -# if datum: -# return True -# return False -# -#class NOT(BooleanOperator): -# def eval_function(self, data): -# if len(data) != 2: -# raise ValueError("wrong size data list (must be 2 elements long") #TODO: refactor to something like check_length(data, 2) -# return data[0] and not data[1] -# -#class XOR(BooleanOperator): -# def eval_function(self, data): -# if len(data) != 2: -# raise ValueError("wrong size data list (must be 2 elements long") #TODO: refactor to something like check_length(data, 2) -# return (data[0] or data[1]) and not (data[0] and data[1]) -# -#class NAND(BooleanOperator): -# def eval_function(self, data): -# if len(data) != 2: -# raise ValueError("wrong size data list (must be 2 elements long") #TODO: refactor to something like check_length(data, 2) -# if data[0] and data[1]: -# return False -# else: -# return True -# -#class MatchRegex(Operator): -# def __init__(self, arguments=None, case_sensitive=True): -# """ -# arguments should be a 1-element list string with the regex pattern as a string -# """ -# Operator.__init__(self, arguments=arguments) -# self.regexes = [] -# for arg in self.arguments: -# if case_sensitive: -# self.regexes.append(re.compile(arg)) -# else: -# self.regexes.append(re.compile(arg, re.IGNORECASE)) -# -# def eval_function(self, data): -# """ -# Data should be a string to match against -# """ -# for expr in self.regexes: -# result = expr.search(data) -# if result: -# return True -# return False -# -#class MatchRegexCI(MatchRegex): # case-insensitive -# def __init__(self, arguments=None): -# MatchRegex.__init__(self, arguments=arguments, case_sensitive=False) -# -# -#class Engine: -# @staticmethod -# def build_db_uri(self, d): -# """ -# Build database connection URI from dictionary of connection parameters -# """ -# uri = "%s://%s:%s@%s:%d/%s" % (d['dbtype'], d['username'], d['password'], d['host'], d['port'], d['database']) -# return uri -# -# def connect_to_db(self, uri): -# if self.debuglevel > 0: -# print "Connecting to DB with: %s" % uri -# -# self.dbconnection = sqlobject.connectionForURI(uri) -# if self.debuglevel > 0: -# print "Connected to DB: %s" % self.dbconnection -# -# def create_tables(self): -# for table in PYPLE_TABLES: -# table.createTable() -# -# -####### TEST CASES ###### -# -#if __name__ == "__main__": -# -# import syck # YAML parser -# dburi = Engine.build_db_uri(syck.load(open("pyple-db.yaml"))) -# E = Engine(debug=1, dburi=dburi) -# -# entries = {} -# entries[300] = "ORDER that Synthon's claims of infringement of the '738 Patent, as set forth in Count Two of its Complaint, are hereby DISMISSED with prejudice (see Order for details). Signed by Judge T. S. Ellis III on 08/31/06. Copies mailed: yes (pmil) (Entered: 09/01/2006)" -# entries[301] = "MEMORANDUM OPINION RE: Post-Verdict Markman Opinion. Signed by Judge T. S. Ellis III on 10/10/06. Copies mailed: yes (pmil) (Entered: 10/12/2006)" -# entries[302] = "ORDER, for the reasons set forth in the related Memoranda Opinions issued in this matter on 06/30/06 and 10/10/06, and for good cause, the final Markman definitions applicable to the disputed claim terms and phrases are as follows: (see Order for details). Signed by Judge T. S. Ellis III on 10/10/06. Copies mailed: yes (pmil) (Entered: 10/12/2006)" -# order_defs = ['order', 'opinion'] -# markman_defs = ['markman', 'claim.{0,3}constr'] -# excl = ['Minute Entry', 'ORDER ENLARGING TIME', 'MARKMAN HEARING', 'hearing is set', 'Motion for Leave', 'Motion to Seal', 'DEFERRING a Markman determination', 'Claim Construction Statement', 'in Limine', 'SCHEDULING ORDER', 'parties shall', 'Docket Control', 'page limit', 'page restriction', 'extend time', 'Show Cause'] -# -# starts_with_order = MatchRegex(["^ORDER"]) -# -# is_order = OR( [ MatchRegexCI([pattern]) for pattern in order_defs ] ) -# is_markman = OR( [ MatchRegexCI([pattern]) for pattern in markman_defs ] ) -# not_excluded = AND( [NOT(MatchRegexCI([pattern])) for pattern in excl] ) -# big_statement = AND([is_order, is_markman, not_excluded]) -# -# for key in entries: -# print "Entry #", key -# swo = starts_with_order.eval(entries[key]) -# if swo: -# print "Starts with 'ORDER'." -# else: -# print "Does not start with 'ORDER'." -# -# result = big_statement.eval([entries[key]]) -# print "Is #", key, "a Markman order???", result -# print "-"*40 -# -# print "The end." \ No newline at end of file + not_matching = Regex(pattern="foobar") + assert not_matching.eval(txt) == False, "not_matching.eval(txt) should be False" + + false_and = AND() + false_and.addParameter(starts_with_order) + false_and.addParameter(not_matching) + assert false_and.eval(txt) == False, "false_and.eval(txt) should be False" + + alt_data = "ORDER re: foobar and stuff" + + assert false_and.eval(alt_data) == True, "false_and.eval(alt_data) should be True" + + test_not = NOT() + test_not.addParameter(test_true) + assert test_not.eval() == False, "NOT.eval() of False should be True" + + test_xor = XOR() + test_xor.addParameter(test_false) + test_xor.addParameter(test_true) + assert test_xor.eval() == True, "XOR.eval() of False, True should be True" + + test_xor2 = XOR() + test_xor2.addParameter(test_true) + test_xor2.addParameter(test_true) + assert test_xor2.eval() == False, "XOR.eval() of True, True should be False" + + test_nand1 = NAND() + test_nand1.addParameter(test_true) + test_nand1.addParameter(test_true) + assert test_nand1.eval() == False, "NAND.eval() of True, True should be False" + + test_nand2 = NAND() + test_nand2.addParameter(test_true) + test_nand2.addParameter(test_false) + assert test_nand2.eval() == True, "NAND.eval() of True, False should be True" + + print "The end!" Modified: src/setup.py =================================================================== --- src/setup.py 2007-03-01 02:31:09 UTC (rev 16) +++ src/setup.py 2007-03-16 04:23:24 UTC (rev 17) @@ -34,7 +34,7 @@ setup( name='pyple', - version='0.2.0', + version='0.3.0', description='PyPLE (pronounced "pipple") is the Python Persistent Logic Engine -- a framework for composing, evaluating, and storing logical expressions.', author='Ansel Halliburton', author_email='an...@us...', This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |