| 842 | | for key, value in kwargs.items(): |
| 843 | | setattr(self, key, value) |
| | 842 | self.from_dict(kwargs) |
| | 843 | |
| | 844 | def from_dict(self, data): |
| | 845 | """ |
| | 846 | Update a mapped class with data from a JSON-style nested dict/list |
| | 847 | structure. |
| | 848 | """ |
| | 849 | mapper = sqlalchemy.orm.object_mapper(self) |
| | 850 | session = sqlalchemy.orm.object_session(self) |
| | 851 | pkey = [c for c in mapper.mapped_table.columns if c.primary_key] |
| | 852 | |
| | 853 | for col in mapper.mapped_table.c: |
| | 854 | if not col.primary_key and data.has_key(col.name): |
| | 855 | setattr(self, col.name, data[col.name]) |
| | 856 | |
| | 857 | for rel in mapper.iterate_properties: |
| | 858 | rname = rel.key |
| | 859 | if isinstance(rel, sqlalchemy.orm.properties.PropertyLoader) \ |
| | 860 | and data.has_key(rname): |
| | 861 | dbdata = getattr(self, rname) |
| | 862 | if rel.uselist: |
| | 863 | # Build a lookup dict: {(pk1, pk2): value} |
| | 864 | lookup = dict([ |
| | 865 | (tuple([getattr(o, c.name) for c in pkey]), o) |
| | 866 | for o in dbdata]) |
| | 867 | for row in data[rname]: |
| | 868 | # If any primary key columns are missing or None, |
| | 869 | # create a new object |
| | 870 | if [1 for c in pkey if not row.get(c.name)]: |
| | 871 | subobj = rel.mapper.class_() |
| | 872 | dbdata.append(subobj) |
| | 873 | else: |
| | 874 | key = tuple([row[c.name] for c in pkey]) |
| | 875 | subobj = lookup.pop(key, None) |
| | 876 | |
| | 877 | # If the row isn't found, we must fail the request |
| | 878 | # in a web scenario, this could be a parameter |
| | 879 | # tampering attack |
| | 880 | if not subobj: |
| | 881 | raise sqlalchemy.exceptions.ArgumentError( |
| | 882 | '%s row not found in database: %s' \ |
| | 883 | % (rname, repr(row))) |
| | 884 | subobj.from_dict(row) |
| | 885 | |
| | 886 | # Make sure the object list attribute doesn't contain any |
| | 887 | # old value (which are not present in the new data). |
| | 888 | for delobj in lookup.itervalues(): |
| | 889 | dbdata.remove(delobj) |
| | 890 | session.delete(delobj) |
| | 891 | else: |
| | 892 | if data[rname] is None: |
| | 893 | setattr(self, rname, None) |
| | 894 | else: |
| | 895 | if not dbdata: |
| | 896 | dbdata = rel.mapper.class_() |
| | 897 | setattr(self, rname, dbdata) |
| | 898 | dbdata.from_dict(data[rname]) |
| | 899 | |
| | 900 | def to_dict(self, deep={}, exclude=[]): |
| | 901 | """Generate a JSON-style nested dict/list structure from an object.""" |
| | 902 | data = dict([(col.name, getattr(self, col.name)) |
| | 903 | for col in self.table.c if col.name not in exclude]) |
| | 904 | for rname, rdeep in deep.iteritems(): |
| | 905 | dbdata = getattr(self, rname) |
| | 906 | fks = self.mapper.get_property(rname).foreign_keys |
| | 907 | exclude = [c.name for c in fks] |
| | 908 | if isinstance(dbdata, list): |
| | 909 | data[rname] = [o.to_dict(rdeep, exclude) for o in dbdata] |
| | 910 | else: |
| | 911 | data[rname] = dbdata.to_dict(rdeep, exclude) |
| | 912 | return data |