Revised Struct get and set now that Python handles lookup through eval; general cleanup
This commit is contained in:
@@ -123,23 +123,26 @@ def get_schema(cls):
|
|||||||
|
|
||||||
def parse(string):
|
def parse(string):
|
||||||
"""
|
"""
|
||||||
Break a string up into a struct-path:
|
Break a string up into a struct-path. Used by get_schema() and setitem().
|
||||||
|
|
||||||
>>> parse("primary.first_name.startswith('Sarah')")
|
>>> parse("primary_name.first_name.startswith('Sarah')")
|
||||||
["primary_name", "first_name", "startswith", "('Sarah')"]
|
["primary_name", "first_name", "startswith", "('Sarah')"]
|
||||||
|
>>> parse("primary_name.surname_list[0].surname.startswith('Smith')")
|
||||||
|
["primary_name", "surname_list", "[0]", "surname", "startswith", "('Smith')"]
|
||||||
"""
|
"""
|
||||||
|
# FIXME: handle nested same-structures, (len(list) + 1)
|
||||||
retval = []
|
retval = []
|
||||||
stack = []
|
stack = []
|
||||||
current = ""
|
current = ""
|
||||||
for p in range(len(string)):
|
for p in range(len(string)):
|
||||||
c = string[p]
|
c = string[p]
|
||||||
if c == ")":
|
if c == "]":
|
||||||
if stack and stack[-1] == "(": # end
|
if stack and stack[-1] == "[": # end
|
||||||
stack.pop(-1)
|
stack.pop(-1)
|
||||||
current += c
|
current += c
|
||||||
retval.append(current)
|
retval.append(current)
|
||||||
current = ""
|
current = ""
|
||||||
elif c == "(":
|
elif c == "[":
|
||||||
stack.append(c)
|
stack.append(c)
|
||||||
retval.append(current)
|
retval.append(current)
|
||||||
current = ""
|
current = ""
|
||||||
@@ -148,7 +151,7 @@ def parse(string):
|
|||||||
if stack and stack[-1] == c: # end
|
if stack and stack[-1] == c: # end
|
||||||
stack.pop(-1)
|
stack.pop(-1)
|
||||||
current += c
|
current += c
|
||||||
if stack and stack[-1] in ["'", '"', '(']: # in quote or args
|
if stack and stack[-1] in ["'", '"', '[']: # in quote or args
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
current += c
|
current += c
|
||||||
@@ -157,12 +160,11 @@ def parse(string):
|
|||||||
else: # start
|
else: # start
|
||||||
stack.append(c)
|
stack.append(c)
|
||||||
current += c
|
current += c
|
||||||
elif stack and stack[-1] in ["'", '"', '(']: # in quote or args
|
elif c == ".":
|
||||||
current += c
|
retval.append(current)
|
||||||
elif c in [".", "[", "]"]:
|
|
||||||
if current:
|
|
||||||
retval.append(current)
|
|
||||||
current = ""
|
current = ""
|
||||||
|
elif stack and stack[-1] in ["'", '"', '[']: # in quote or args
|
||||||
|
current += c
|
||||||
else:
|
else:
|
||||||
current += c
|
current += c
|
||||||
if current:
|
if current:
|
||||||
@@ -369,11 +371,11 @@ class Struct(object):
|
|||||||
"""
|
"""
|
||||||
Class for getting and setting parts of a struct by dotted path.
|
Class for getting and setting parts of a struct by dotted path.
|
||||||
|
|
||||||
>>> s = Struct({"gramps_id": "I0001", ...})
|
>>> s = Struct({"gramps_id": "I0001", ...}, database)
|
||||||
>>> s["primary_name.surname_list.0.surname"]
|
>>> s.primary_name.surname_list[0].surname
|
||||||
Jones
|
Jones
|
||||||
>>> s["primary_name.surname_list.0.surname"] = "Smith"
|
>>> s.primary_name.surname_list[0].surname = "Smith"
|
||||||
>>> s["primary_name.surname_list.0.surname"]
|
>>> s.primary_name.surname_list[0]surname
|
||||||
Smith
|
Smith
|
||||||
"""
|
"""
|
||||||
def __init__(self, struct, db=None):
|
def __init__(self, struct, db=None):
|
||||||
@@ -429,32 +431,42 @@ class Struct(object):
|
|||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
"""
|
"""
|
||||||
Called when getattr fails. Lookup attr in struct; returns Struct
|
Called when getattr fails. Lookup attr in struct; returns Struct
|
||||||
if more struct. This is used only in eval for where clause.
|
if more struct.
|
||||||
|
|
||||||
|
>>> Struct({}, db).primary_name
|
||||||
|
returns: Struct([], db) or value
|
||||||
|
|
||||||
|
struct can be list/tuple, dict with _class, or value (including dict).
|
||||||
|
|
||||||
self.setitem_from_path(path, v) should be used to set value of
|
self.setitem_from_path(path, v) should be used to set value of
|
||||||
item.
|
item.
|
||||||
"""
|
"""
|
||||||
# for where eval:
|
if isinstance(self.struct, dict) and "_class" in self.struct.keys():
|
||||||
if attr in self.struct:
|
# this is representing an object
|
||||||
attr = self.getitem(attr)
|
if attr in self.struct.keys():
|
||||||
if isinstance(attr, (list, tuple, dict)): # more struct...
|
return self.handle_join(self.struct[attr])
|
||||||
return Struct(attr, self.db)
|
else:
|
||||||
elif isinstance(attr, HandleClass): # more struct...
|
raise AttributeError("attempt to access a property of an object: '%s', '%s'" % (self.struct, attr))
|
||||||
return self.get_ref_struct(attr)
|
elif isinstance(self.struct, HandleClass):
|
||||||
else: # just a value:
|
struct = self.handle_join(self.struct)
|
||||||
return attr
|
return getattr(struct, attr)
|
||||||
else:
|
else:
|
||||||
raise AttributeError("no such attribute '%s'" % attr)
|
# better be a property of the list/tuple/dict/value:
|
||||||
|
return getattr(self.struct, attr)
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
"""
|
"""
|
||||||
Given a path to a struct part, return the part, or None.
|
Called when getitem fails. Lookup item in struct; returns Struct
|
||||||
|
if more struct.
|
||||||
|
|
||||||
>>> Struct(struct)["primary_name"]
|
>>> Struct({}, db)[12]
|
||||||
|
returns: Struct([], db) or value
|
||||||
|
|
||||||
|
struct can be list/tuple, dict with _class, or value (including dict).
|
||||||
"""
|
"""
|
||||||
# For where eval:
|
return self.handle_join(self.struct[item])
|
||||||
return self.getitem(item)
|
|
||||||
|
|
||||||
def get_ref_struct(self, item):
|
def handle_join(self, item):
|
||||||
"""
|
"""
|
||||||
If the item is a handle, look up reference object.
|
If the item is a handle, look up reference object.
|
||||||
"""
|
"""
|
||||||
@@ -463,39 +475,14 @@ class Struct(object):
|
|||||||
if obj:
|
if obj:
|
||||||
return Struct(obj.to_struct(), self.db)
|
return Struct(obj.to_struct(), self.db)
|
||||||
else:
|
else:
|
||||||
return None
|
raise AttributeError("missing object: %s" % item)
|
||||||
elif isinstance(item, (dict, tuple, list)):
|
elif isinstance(item, (list, tuple)):
|
||||||
|
return Struct(item, self.db)
|
||||||
|
elif isinstance(item, dict) and "_class" in item.keys():
|
||||||
return Struct(item, self.db)
|
return Struct(item, self.db)
|
||||||
else:
|
else:
|
||||||
return item
|
return item
|
||||||
|
|
||||||
def getitem(self, item, struct=None, ref_struct=True):
|
|
||||||
"""
|
|
||||||
>>> Struct(struct).getitem("primary_name")
|
|
||||||
{...}
|
|
||||||
"""
|
|
||||||
if struct is None:
|
|
||||||
struct = self.struct
|
|
||||||
# Get part
|
|
||||||
if isinstance(struct, (list, tuple, tuple)):
|
|
||||||
pos = int(item)
|
|
||||||
if pos < len(struct):
|
|
||||||
return self.get_ref_struct(struct[int(item)])
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
elif isinstance(struct, dict):
|
|
||||||
if item in struct.keys():
|
|
||||||
if ref_struct:
|
|
||||||
return self.get_ref_struct(struct[item])
|
|
||||||
else:
|
|
||||||
return struct[item]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
elif hasattr(struct, item):
|
|
||||||
return Struct(getattr(struct, item), self.db)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def setitem(self, path, value, trans=None):
|
def setitem(self, path, value, trans=None):
|
||||||
"""
|
"""
|
||||||
Given a path to a struct part, set the last part to value.
|
Given a path to a struct part, set the last part to value.
|
||||||
@@ -505,15 +492,22 @@ class Struct(object):
|
|||||||
return self.setitem_from_path(parse(path), value, trans)
|
return self.setitem_from_path(parse(path), value, trans)
|
||||||
|
|
||||||
def setitem_from_path(self, path, value, trans=None):
|
def setitem_from_path(self, path, value, trans=None):
|
||||||
|
"""
|
||||||
|
Given a path to a struct part, set the last part to value.
|
||||||
|
|
||||||
|
>>> Struct(struct).setitem_from_path(["primary_name", "surname_list", "[0]", "surname"], "Smith", transaction)
|
||||||
|
"""
|
||||||
path, item = path[:-1], path[-1]
|
path, item = path[:-1], path[-1]
|
||||||
struct = self.struct
|
struct = self.struct
|
||||||
|
# FIXME: handle assignment on join on HandleClass
|
||||||
for p in range(len(path)):
|
for p in range(len(path)):
|
||||||
part = path[p]
|
part = path[p]
|
||||||
struct = self.getitem(part, struct, ref_struct=False) # just get dicts, no Struct
|
if part.startswith("["): # getitem
|
||||||
if isinstance(struct, Struct):
|
struct = struct[eval(part[1:-1])] # for int or string use
|
||||||
return struct.setitem_from_path(path[p+1:] + [item], value, trans)
|
else: # getattr
|
||||||
if struct is None:
|
struct = struct[part]
|
||||||
return None
|
if struct is None: # invalid part to set, skip
|
||||||
|
return
|
||||||
# struct is set
|
# struct is set
|
||||||
if isinstance(struct, (list, tuple)):
|
if isinstance(struct, (list, tuple)):
|
||||||
pos = int(item)
|
pos = int(item)
|
||||||
@@ -522,8 +516,6 @@ class Struct(object):
|
|||||||
elif isinstance(struct, dict):
|
elif isinstance(struct, dict):
|
||||||
if item in struct.keys():
|
if item in struct.keys():
|
||||||
struct[item] = value
|
struct[item] = value
|
||||||
else:
|
|
||||||
raise AttributeError("no such property: '%s'" % item)
|
|
||||||
elif hasattr(struct, item):
|
elif hasattr(struct, item):
|
||||||
setattr(struct, item, value)
|
setattr(struct, item, value)
|
||||||
else:
|
else:
|
||||||
@@ -537,7 +529,7 @@ class Struct(object):
|
|||||||
new_obj = from_struct(self.struct)
|
new_obj = from_struct(self.struct)
|
||||||
name, handle = self.struct["_class"], self.struct["handle"]
|
name, handle = self.struct["_class"], self.struct["handle"]
|
||||||
old_obj = self.db.get_from_name_and_handle(name, handle)
|
old_obj = self.db.get_from_name_and_handle(name, handle)
|
||||||
# FIXME: this needs to find the closest _class before each diff
|
# FIXME: this needs to find the closest primary _class before each diff
|
||||||
# and commit that, not the topmost _class
|
# and commit that, not the topmost _class
|
||||||
if old_obj:
|
if old_obj:
|
||||||
commit_func = self.db._tables[name]["commit_func"]
|
commit_func = self.db._tables[name]["commit_func"]
|
||||||
|
Reference in New Issue
Block a user