[Pyple-commits] SF.net SVN: pyple: [14] src
Status: Pre-Alpha
Brought to you by:
anseljh
From: <an...@us...> - 2007-01-25 00:54:13
|
Revision: 14 http://svn.sourceforge.net/pyple/?rev=14&view=rev Author: anseljh Date: 2007-01-24 16:54:13 -0800 (Wed, 24 Jan 2007) Log Message: ----------- add new replacement code Added Paths: ----------- src/pyple.py src/setup.py Added: src/pyple.py =================================================================== --- src/pyple.py (rev 0) +++ src/pyple.py 2007-01-25 00:54:13 UTC (rev 14) @@ -0,0 +1,566 @@ +#!/usr/bin/env python +""" +PyPLE (say "pipple") -- the Python Persistent Logic Engine + +pyple.py: All things PyPLE are in this file! + +Copyright (C) 2006-2007 Ansel Halliburton. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the PyPLE Project nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +NOTES +* PyPLE is lean and mean. Just say no to complexity! +* You can run this file to do some simple testing: python PyPLE.py + +HISTORY +v0.0.1 PyPLE was born in a flash of inspiration at L & L Hawaiian Barbecue in + Palo Alto, California on April 7, 2006. Version 0.1 was thrown together + more or less that night with the assistance of Nine Inch Nails and the + Komodo IDE. +v0.1.0 Port to SQLObject for database persistence begun - 1/22/07 +v0.2.0 SQLObject version merged into one file for simplicity - 1/22/07 +""" + +# Interesting tokens to look for in this source code: +# #TODO: Items that need doing +# #BUG: Identified bug +# #NOTE: Note by author + +__revision__ = "$Id$" +__author__ = 'Ansel Halliburton (anseljh at users dot sourceforge dot net)' +#__date__ = '$RevisionDate$'.split()[1].replace('/', '-') +__version__ = '$Revision: 7 $' +__release__ = (0,2,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 +RESERVED_OPERATORS = ['AND', 'OR', 'NOT', 'XOR', 'NAND'] + +########################################################################################## + +from sqlobject import * # SQLObject ORM +from sqlobject.inheritance import InheritableSQLObject # Inheritance for Element, Expression classes +from datetime import datetime +import types +import re + +########################################################################################## + +class BasePyPLEType(InheritableSQLObject): + """ + Base class/interface for Element and Expression persistent types + """ + + # Meta + class sqlmeta: + table = "pyple_base_pyple_type" + + # Methods + def eval(self): + """ + Both Element and Expression should have eval() methods. + """ + pass + +########################################################################################## + +class Expression(BasePyPLEType): + """ + Expressions are composed of a left-hand-side (LHS), a right-hand-side (RHS), and + an Operator (op). LHS and RHS can be either an Element or an Expression. + """ + + # Columns + operator = ForeignKey('Operator') + LHS = ForeignKey('BasePyPLEType') # can also be Expression! + RHS = ForeignKey('BasePyPLEType') # can also be Expression! + + # Meta + class sqlmeta: + table = "pyple_expression" + + # Methods + def __str__(self): + """ + Return string representation of an Expression + """ + return "Expression" #TODO: meaningful string representation + + def eval(self): + #define temp vars + L = None + R = None + + if type(self.LHS) is Expression: + L = self.LHS.eval() + elif type(self.LHS) is Element: + L = self.LHS + else: + raise TypeError("not an Element: %s" % str(L)) + + if type(self.RHS) is Expression: + R = self.RHS.eval() + elif type(self.RHS) is Element: + R = self.RHS + else: + raise TypeError("not an Element: %s" % str(R)) + + #perform operation and return Element + return self.operator.eval(L, R) + +########################################################################################## + +class Element(BasePyPLEType): + + # Columns + + types = ['element', 'expression', 'boolean', 'integer', 'string', 'datetime', 'null', 'float'] + + type = EnumCol(enumValues=types) + value = PickleCol(length=2**24) + + # Meta + class sqlmeta: + table = "pyple_element" + + # Methods + + def eval(self): + """ + eval() on an Element returns a native Python object. + """ + + if self.type == 'expression': + return Expression.Expression(self.value) + elif self.type == 'boolean': + return types.BooleanType(self.value) + elif self.type == 'integer': + return int(self.value) + elif self.type == 'string': + return str(self.value) + elif self.type == 'datetime': + return datetime.datetime(self.value) + elif self.type == 'null': + return None + elif self.type == 'float': + return float(self.value) + else: + raise IndexError("Unknown type: %s" % self.type) + + @staticmethod + def create(o): + temp_t = type(o) + + #TODO: DRY this up: refactor out into to_pyple_type() helper function + if temp_t is Expression: + t = 'expression' + elif temp_t is types.BooleanType: + t = 'boolean' + elif temp_t is types.IntType: + t = 'integer' + elif temp_t is types.StringType: + t = 'string' + elif temp_t is datetime.datetime: + t = 'datetime' + elif temp_t is types.NoneType: + t = 'null' + elif temp_t is types.FloatType: + t = 'float' + else: + raise TypeError("Unknown type: %s" % str(temp_t)) + + return Element(type=t, value=o) + + def __str__(self): + """Return string representation of Element""" + return "PyPLE Element of type " + str(self.type) + "; value = " + str(self.value) + +########################################################################################## + +class Operator(SQLObject): + + engines = ['internal', 'python', 'shell', 'perl'] + + # Columns + name = StringCol(length=200) + engine = EnumCol(enumValues=engines, default='python') + code = ForeignKey('Code') + + # Meta + class sqlmeta: + table = "pyple_operator" + + # Methods + + def _get_function(self): + try: + if self.fx is not None: + return self.fx + else: # no function set + #print "fx is None" + if self.code is not None: + self.fx = self.code.function + return self.fx + #print "* about to eval() Operator code..." + ##print "self.code is a:", str(type(self.code)) + #self.function = eval(self.code.content, {}, {}) + #return self.fx + else: + raise Exception("No function or Code set; don't know how to continue!") + except AttributeError: + #print "No fx" + if self.code is not None: + #print "* about to eval() Operator code... (no fx)" + #print "self.code is a:", str(type(self.code)) + self.fx = self.code.function + return self.fx + except: + raise Exception("Error in Operator._get_function()!!") + + #if self.fx is not None: + # return self.fx + #else: # no function set + # if self.code is not None: + # print "* about to eval() Operator code..." + # self.function = eval(self.code, {}, {}) + # else: + # raise Exception("No function or Code set; don't know how to continue!") + + def _set_function(self, f): + print "Setting function for Operator %s" % self.name + self.fx = f + + def eval(self, LHS=None, RHS=None): + """ + Perform an operation on LHS and RHS + """ + #print "**Operator.eval() called." + if self.function is None: + raise Exception("no function set!") + else: + #fn = self.fn + #val = fn(LHS.eval(), RHS.eval()) #reduce elements to bare datatypes + #return Element.Element(val) + #return Element.Element(self.eval_function(LHS.eval(), RHS.eval())) + + left = None + right = None + + if type(LHS) in [Element, Expression]: + left = LHS.eval() + else: + left = LHS + + if type(RHS) in [Element, Expression]: + right = RHS.eval() + else: + right = RHS + + return self.function(left, right) + + + # Functions for internal operators + + @staticmethod + def op_and(LHS, RHS): + """Internal AND operator""" + #print "** Internal AND operator called. LHS=",LHS,"; RHS=",RHS + return (LHS and RHS) + + @staticmethod + def op_or(LHS, RHS): + """Internal OR operator""" + return (LHS or RHS) + + @staticmethod + def op_not(LHS, RHS): + """Internal NOT operator""" + return (LHS and not RHS) + + @staticmethod + def op_xor(LHS, RHS): + """Internal XOR operator""" + return ( (LHS and not RHS) or (RHS and not LHS) ) + + @staticmethod + def op_nand(LHS, RHS): + """Internal NAND operator""" + #TODO: is this really what NAND is supposed to do? + if (LHS and RHS): + return False + else: + return True + + +########################################################################################## + +class Code(SQLObject): + + """ + Code for running Operators + """ + # Columns + name = StringCol(length=200) + content = StringCol() + type = EnumCol(enumValues=['interpreted','binary','pickled'], default='interpreted') + engine = EnumCol(enumValues=Operator.engines, default='python') #ForeignKey('Engine') + + # Meta + class sqlmeta: + table = "pyple_code" + + # Methods + #TODO: add methods, etc. + + def _get_function(self): + if self.type=='interpreted' and self.engine=='python': + #return eval(self.content, {}, {}) + return eval("lambda LHS, RHS: " + self.content) + else: + raise Exception("Non-Python function generation not yet implemented.") + +########################################################################################## + +class Engine: + """ + PyPLE engine class. + """ + + def operator(self, name): + return list(Operator.selectBy(name=name))[0] + + def __init__(self, debug=0, dburi=None): + """ + PyPLE Engine constructor + """ + self.debuglevel = debug + self.connect_to_db(dburi) + + # Bind functions to internal operators + internal_operators = list(Operator.selectBy(engine='internal')) + internal_functions = { + 'AND': Operator.op_and, + 'OR': Operator.op_or, + 'NOT': Operator.op_not, + 'XOR': Operator.op_xor, + 'NAND': Operator.op_nand + } + for op in internal_operators: + op.function = internal_functions[op.name] + + #def add_op_function(self, name, f, pid=None): + # """ + # Adds an operator based on an already extant function (no eval call) + # Parameters: + # name Name of operator (NOTE: Must be unique!) (string) + # f Function implementing logic of operator (method) + # [pid] Persistent ID used in database (int; mostly likely None if called directly) + # """ + # if name in self.functions['name']: + # raise Exception("Function with name '" + str(name) + "' already exists!") + # fid = self.next_f_id + # self.next_f_id = self.next_f_id + 1 + # self.functions['id'][fid] = f + # self.functions['name'][name] = f + # if pid is not None: self.functions['pid'][pid] = f + # + #def add_operator(self, name, o, pid=None): + # """Adds an operator to internal index""" + # self.operators['name'][name] = o + # if pid is not None: + # self.operators['pid'][pid] = o + # + #def evaluate_by_id(self, fid, LHS, RHS): + # """ + # MANUALLY evaluates an expression, looking up the function by id. + # Parameters: + # fid id of function (looked up in self.functions['id']) + # LHS left hand side of expression to be evaluated + # RHS right hand side of expression to be evaluated + # Returns: 2-tuple + # type Type of value returned (instance of Python built-in class Type) + # val Value of expression + # """ + # f = self.functions['id'][fid] + # val = f(LHS, RHS) + # return (type(val), val) + # + #def evaluate_by_name(self, name, LHS, RHS): + # """ + # MANUALLY evaluates an expression, looking up the function by name. + # Parameters: same as evaluate_by_id, except using name (string) instead of id (int) + # Returns: same as evaluate_by_id + # """ + # f = self.functions['name'][name] + # val = f(LHS, RHS) + # return (type(val), val) + + @staticmethod + def build_db_uri(d): + """ + Builds a SQLObject database connection URI from dictionary of connection parameters. + The dictionary should have the following keys: + dbtype e.g. 'mysql' + username + password + host + port + database + """ + uri = d['dbtype'] + "://" + d['username'] + ":" + d['password'] + "@" + d['host'] + ":" + str(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 = connectionForURI(uri) + sqlhub.processConnection = self.dbconnection + + if self.debuglevel > 0: + print "Connected to DB: %s" % self.dbconnection + +########################################################################################## + +def create_tables(): + """ + Issue CREATE TABLE commands via SQLObject + """ + + # Connect to DB (must have create table privileges on target DB) + import syck + dburi = Engine.build_db_uri(syck.load(open("root.yaml").read())) + sqlhub.processConnection = connectionForURI(dburi) + + # Create tables + BasePyPLEType.createTable() + Expression.createTable() + Element.createTable() + Operator.createTable() + Code.createTable() + print "CREATE TABLEs done." + + # Add internal operators + for opname in RESERVED_OPERATORS: + o = Operator(name=opname, engine='internal', code=None) + print "Added Operator %s" % o.name + +if __name__ == "__main__": + print PYPLE_TAGLINE + print PYPLE_COPYRIGHT + print + + #TODO: handle params; if "--create-tables", do create_tables() + + print "-"*40 + print "Instantiating PyPLE Engine object: ", + import syck + dburi = Engine.build_db_uri(syck.load(open("mysql.yaml").read())) + P = Engine(debug=1, dburi=dburi) + print "Done." + print "-"*40 + + print "Test 0:" + l1 = Element.create(2) + r1 = Element.create(False) + and_op = P.operator('AND') #selectBy(name='AND') + print "and_op:", and_op + print "function0:", and_op.function + restult0 = and_op.eval(l1, r1) + print "Test 0 result:" + print result0 + + + print "*" * ASTERISKS + print "Testing 1: " + code1 = Code(name='always_true', content="True") + o_at = Operator(name='always_true', engine='python', code=code1) + print "function:", o_at.function + result1 = o_at.eval(l1, r1) + print "Test 1:", result1 + + print "*" * ASTERISKS + print "Testing 2: " + code2 = "int(LHS) * int(RHS)" + l2 = 2 + r2 = 3 + o_mul = Operator.Operator(P, 'multiply_integers', Operator.ENGINE_PYTHON, None, code2) + result2 = P.evaluate_by_name('multiply_integers', l2, r2) + print result2 + + print "*" * ASTERISKS + print "Testing AND: " + print P.evaluate_by_name('and', True, False) + + print "*" * ASTERISKS + print "Testing OR: " + print P.evaluate_by_name('or', True, False) + + print "*" * ASTERISKS + print "Testing NOT: " + print P.evaluate_by_name('not', True, False) + + print "*" * ASTERISKS + print "Testing XOR: " + print P.evaluate_by_name('xor', True, False) + + print "*" * ASTERISKS + print "Testing NAND(T,F): " + print P.evaluate_by_name('nand', True, False) + print "*" * ASTERISKS + print "Testing NAND(T,T): ", + print P.evaluate_by_name('nand', True, True) + + print "*" * ASTERISKS + print "Testing Expression.eval() [multiply_integers]" + op_multiply = P.operators['name']['multiply_integers'] + e1 = Element.Element(10) + print e1 + e2 = Element.Element(5) + print e2 + x = Expression.Expression(op_multiply, e1, e2) + rx = x.eval() + print rx + + print "*" * ASTERISKS + print "Testing Expression.eval() [and]" + op_and = P.operators['name']['and'] + e2_1 = Element.Element(True) + print e2_1 + e2_2 = Element.Element(False) + print e2_2 + x2 = Expression.Expression(op_and, e2_1, e2_2) + rx2 = x2.eval() + print rx2 + + print "*" * ASTERISKS + from pprint import pprint + pprint(P.operators) + + print "*" * ASTERISKS + + print "The End." + +########################################################################################## \ No newline at end of file Added: src/setup.py =================================================================== --- src/setup.py (rev 0) +++ src/setup.py 2007-01-25 00:54:13 UTC (rev 14) @@ -0,0 +1,46 @@ +#!/usr/bin/env python +""" +PyPLE (say "pipple") -- the Python Persistent Logic Engine + +setup.py: Distutils script + +Copyright (C) 2006-2007 Ansel Halliburton. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the PyPLE Project nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +from distutils.core import setup + +setup( + name='pyple', + version='0.2.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...', + keywords=['logic','engine','expression','database'], + url='http://pyple.sourceforge.net/', + license='BSD License', + classifiers=['Development Status :: 2 - Pre-Alpha', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Topic :: Software Development :: Libraries :: Python Modules'], + py_modules=['pyple'], +) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |