#!/usr/bin/env python """ ittydb.py - PyIttyBase Being: An implementation of John Cowan's DB design Author: Sean B. Palmer, inamidst.com This is an implementation of the property-tree database design, as described by John Cowan on #swhack [1], for Berkeley DB. It's "more flexible than a relational database, less anarchic than a pile of RDF triples." [1] http://swhack.com/logs/2005-03-16#T19-47-00 TODO The next extension is to be able to pass a query (a list of keys) and return a list of matches. The idea being that all the keys are ANDed, except that just in case two keys have a parent that has mEC, they are ORed. - http://swhack.com/logs/2005-05-23#T12-38-15 """ import os, dbhash def dbuuid(): import time, urllib, random, struct from base64 import b64encode ipaddr = urllib.thishost() ip = map(int, ipaddr.split('.')) pid, t = os.getpid(), time.time() rand = random.random() args = (ip[0], ip[1], ip[2], ip[3], pid, t, rand) binaryid = struct.pack('iiiiiff', *args) return b64encode(binaryid) # The Known Properties root = 'root' name = 'name' superprop = 'superprop' uuid = 'uuid' datatype = 'datatype' boolean = 'boolean' string = 'string' number = 'number' datetime = 'datetime' keylist = 'keylist' mutuallyExclusiveChildren = 'mutuallyExclusiveChildren' # @@ "key"? special-case it on a lookup? # @@ URI? sepchars = { string: ':', datetime: '@', number: '#', keylist: ',', boolean: '?' } class Supertype(str): pass class String(Supertype): pass class Datetime(Supertype): pass class Number(Supertype): pass class Keylist(Supertype): pass class Boolean(Supertype): pass def cardinality(arg): if isinstance(arg, Supertype): return 1 elif isinstance(arg, set): return len(arg) raise ValueError("Argument must be datatyped or list.") datatypes = { ':': String, '@': Datetime, '#': Number, ',': Keylist, '?': Boolean } class Database(object): # : for string, @ for time, # for number, , for list of items # the value of x% is keys which contain this key as a property value # x% gives the items whose properties include x # x%% gives the items whose property *values* include x # name! maps a name to its key(s) def __init__(self, filename): if not os.path.isfile(filename): new = True else: new = False self.db = dbhash.open(filename, 'c') # name is known to be typed as string if new: # self.set(datatype, datatype, string) self.set(name, datatype, string) self.set(superprop, datatype, string) def uuidgen(self): return dbuuid() # def __getitem__(self, key): # pass # def __setitem__(self, key, value): # raise Exception("You can't do this.") def get(self, item, prop, sets=False): results = set() for line in self.db[item].split('\n'): if len(prop) <= len(line): sepchar = line[len(prop)] else: sepchar = False if line.startswith(prop) and datatypes.has_key(sepchar): dtype = datatypes[sepchar] lexical = line[len(prop) + 1:] if not (dtype is Boolean): results.add(dtype(lexical)) elif lexical == 'true': results.add(True) else: results.add(False) try: proptype = self.get(prop, datatype) except KeyError: proptype = False if len(results) == 1 and not sets: return results.pop() elif (proptype == boolean) and (not results): return False else: return results def set(self, item, prop, value=True): # Type checking and munging if value is True: value = Boolean("true") elif isinstance(value, unicode): value = value.encode('utf-8') elif not isinstance(value, str): raise ValueError("Object value must be str or unicode.") self.__add(prop + '%', item) self.__add(value + '%%', '@@') if prop == name: self.__add(value + '!', item) # Lookup the property's datatype # : for string, @ for time, # for number, , for list of items if prop == datatype: sep = ':' else: sep = sepchars[self.get(prop, datatype)] if self.db.has_key(item): self.db[item] += prop + sep + value + '\n' else: self.db[item] = prop + sep + value + '\n' # self.db.sync() def __add(self, key, value): if self.db.has_key(key): oldval = self.db[key].rstrip('\n') valset = set(oldval.split('\n')) valset.add(value) self.db[key] = '\n'.join(valset) + '\n' else: self.db[key] = value + '\n' # self.db.sync() def getSuperproperties(self, prop): superprops = self.get(prop, superprop, sets=True) for super in superprops: superprops |= self.get(super, superprop) return superprops def keysForProperty(self, prop): if self.db.has_key(prop + '%'): val = self.db[prop + '%'].rstrip('\n') return frozenset(val.split('\n')) else: raise KeyError("Property not found: %s" % prop) def keysForValue(self, value): if self.db.has_key(value + '%%'): val = self.db[value + '%%'].rstrip('\n') return frozenset(val.split('\n')) else: raise KeyError("Value not found: %s" % value) def keysForName(self, nameval): if self.db.has_key(nameval + '!'): val = self.db[nameval + '!'].rstrip('\n') return frozenset(val.split('\n')) else: raise KeyError("Name not found: %s" % nameval) def newitem(self, nameval=None): item = self.uuidgen() # Item() if nameval is not None: # item[name] = nameval self.set(item, name, nameval) return item def newprop(self, name, dtype, superprop=None): item = self.newitem(name) self.set(item, datatype, dtype) return item class Item(object): def __init__(self, key=None, db=None): self.key = key def __hash__(self): return hash(self.key) def __getitem__(self, key): pass def __setitem__(self, key, value): pass def test(): print dbuuid() db = Database('/desktop/test.ittydb') sbp = db.newitem('Sean B. Palmer') gender = db.newprop('gender', string) db.set(sbp, gender, 'male') # this implicitly sets datatype. doing: # db.set(gender, datatype, string) sbpgender = db.get(sbp, gender) print sbpgender.__class__, sbpgender homepage = db.newprop('homepage', string) print gender, homepage db.set(sbp, homepage, 'http://inamidst.com/sbp/') db.set(sbp, homepage, 'http://purl.org/net/sbp/') print db.get(sbp, homepage) isAnimal = db.newprop('isAnimal', boolean) isHuman = db.newprop('isHuman', boolean) isDog = db.newprop('isDog', boolean) # # 20:02:46 So given that x has property isGoat, then it # # implicitly has properties isAnimal, isAlive, isPhysicalObject .... db.set(isHuman, superprop, isAnimal) db.set(sbp, isHuman) print db.getSuperproperties(isHuman) assert db.get(sbp, isHuman) == True assert db.get(sbp, isAnimal) == True assert db.get(sbp, isDog) == False # If you ask "Does item x have property y (with value z)?" the # answer is "yes" if item x has any ancestral property of y instead. # No, I mean if x has any property q, such that q is a # superproperty of y (or super-superproperty, etc.) # I think it would be a good thing to constrain people not to # assert the root property on any item. That way it does not matter what # (data)type it has. # 20:21:30 Let the subproperties of isCarnivore be isDog, isCat, # isWeasel. isCarnivore has the mutuallyExclusiveChildren property. # 20:31:49 mEC is not inherited. # 20:33:37 so properties have their ranges datatyped? # 20:33:46 Yes. if __name__=="__main__": test()