| | 585 | def primary_key_properties(self): |
| | 586 | """ |
| | 587 | Returns the list of (mapper) properties corresponding to the primary |
| | 588 | key columns of the table of the entity. |
| | 589 | |
| | 590 | This property caches its value, so it shouldn't be called before the |
| | 591 | entity is fully set up. |
| | 592 | """ |
| | 593 | if not hasattr(self, '_pk_props'): |
| | 594 | col_to_prop = {} |
| | 595 | mapper = self.entity.mapper |
| | 596 | for prop in mapper.iterate_properties: |
| | 597 | if isinstance(prop, ColumnProperty): |
| | 598 | for col in prop.columns: |
| | 599 | for col in col.proxy_set: |
| | 600 | col_to_prop[col] = prop |
| | 601 | pk_cols = [c for c in mapper.mapped_table.c if c.primary_key] |
| | 602 | self._pk_props = [col_to_prop[c] for c in pk_cols] |
| | 603 | return self._pk_props |
| | 604 | primary_key_properties = property(primary_key_properties) |
| 646 | | # Process attributes (using the assignment syntax), looking for |
| 647 | | # 'Property' instances and attaching them to this entity. |
| 648 | | properties = [(name, attr) for name, attr in dict_.iteritems() |
| 649 | | if isinstance(attr, Property)] |
| 650 | | sorted_props = sorted(properties, key=lambda i: i[1]._counter) |
| 651 | | for name, prop in sorted_props: |
| 652 | | prop.attach(cls, name) |
| 653 | | |
| | 673 | # Determine whether this entity is a *direct* subclass of its base |
| | 674 | # entity |
| 664 | | local_props = [(name, copy(attr)) for name, attr in base_props] |
| 665 | | sorted_props = sorted(local_props, key=lambda i: i[1]._counter) |
| 666 | | for name, prop in sorted_props: |
| 667 | | prop.attach(cls, name) |
| | 687 | base_props = [(name, copy(attr)) for name, attr in base_props] |
| | 688 | else: |
| | 689 | base_props = [] |
| | 690 | |
| | 691 | # Process attributes (using the assignment syntax), looking for |
| | 692 | # 'Property' instances and attaching them to this entity. |
| | 693 | properties = [(name, attr) for name, attr in cls.__dict__.iteritems() |
| | 694 | if isinstance(attr, Property)] |
| | 695 | sorted_props = sorted(base_props + properties, |
| | 696 | key=lambda i: i[1]._counter) |
| | 697 | for name, prop in sorted_props: |
| | 698 | prop.attach(cls, name) |
| | 879 | |
| | 880 | def update_or_create(cls, data, surrogate=True): |
| | 881 | pk_props = cls._descriptor.primary_key_properties |
| | 882 | |
| | 883 | # if all pk are present and not None |
| | 884 | if not [1 for p in pk_props if data.get(p.key) is None]: |
| | 885 | pk_tuple = tuple([data[prop.key] for prop in pk_props]) |
| | 886 | record = cls.query.get(pk_tuple) |
| | 887 | if record is None: |
| | 888 | if surrogate: |
| | 889 | raise Exception("cannot create surrogate with pk") |
| | 890 | else: |
| | 891 | record = cls() |
| | 892 | else: |
| | 893 | if surrogate: |
| | 894 | record = cls() |
| | 895 | else: |
| | 896 | raise Exception("cannot create non surrogate without pk") |
| | 897 | record.from_dict(data) |
| | 898 | return record |
| | 899 | update_or_create = classmethod(update_or_create) |
| 856 | | session = sqlalchemy.orm.object_session(self) |
| 857 | | |
| 858 | | for col in mapper.mapped_table.c: |
| 859 | | if not col.primary_key and data.has_key(col.name): |
| 860 | | setattr(self, col.name, data[col.name]) |
| 861 | | |
| 862 | | for rel in mapper.iterate_properties: |
| 863 | | rname = rel.key |
| 864 | | if isinstance(rel, sqlalchemy.orm.properties.PropertyLoader) \ |
| 865 | | and data.has_key(rname): |
| 866 | | dbdata = getattr(self, rname) |
| 867 | | if rel.uselist: |
| 868 | | pkey = [c for c in rel.table.columns if c.primary_key] |
| 869 | | |
| 870 | | # Build a lookup dict: {(pk1, pk2): value} |
| 871 | | lookup = dict([ |
| 872 | | (tuple([getattr(o, c.name) for c in pkey]), o) |
| 873 | | for o in dbdata]) |
| 874 | | for row in data[rname]: |
| 875 | | # If any primary key columns are missing or None, |
| 876 | | # create a new object |
| 877 | | if [1 for c in pkey if not row.get(c.name)]: |
| 878 | | subobj = rel.mapper.class_() |
| 879 | | dbdata.append(subobj) |
| 880 | | else: |
| 881 | | key = tuple([row[c.name] for c in pkey]) |
| 882 | | subobj = lookup.pop(key, None) |
| 883 | | |
| 884 | | # If the row isn't found, we must fail the request |
| 885 | | # in a web scenario, this could be a parameter |
| 886 | | # tampering attack |
| 887 | | if not subobj: |
| 888 | | raise sqlalchemy.exceptions.ArgumentError( |
| 889 | | '%s row not found in database: %s' \ |
| 890 | | % (rname, repr(row))) |
| 891 | | subobj.from_dict(row) |
| 892 | | |
| 893 | | # Make sure the object list attribute doesn't contain any |
| 894 | | # old value (which are not present in the new data). |
| 895 | | for delobj in lookup.itervalues(): |
| 896 | | dbdata.remove(delobj) |
| 897 | | session.delete(delobj) |
| | 910 | |
| | 911 | for key, value in data.iteritems(): |
| | 912 | if isinstance(value, dict): |
| | 913 | dbvalue = getattr(self, key) |
| | 914 | rel_class = mapper.get_property(key).mapper.class_ |
| | 915 | pk_props = rel_class._descriptor.primary_key_properties |
| | 916 | |
| | 917 | # If the data doesn't contain any pk, and the relationship |
| | 918 | # already has a value, update that record. |
| | 919 | if not [1 for p in pk_props if p.key in data] and \ |
| | 920 | dbvalue is not None: |
| | 921 | dbvalue.from_dict(value) |
| 899 | | if data[rname] is None: |
| 900 | | setattr(self, rname, None) |
| 901 | | else: |
| 902 | | if not dbdata: |
| 903 | | dbdata = rel.mapper.class_() |
| 904 | | setattr(self, rname, dbdata) |
| 905 | | dbdata.from_dict(data[rname]) |
| | 923 | record = rel_class.update_or_create(value) |
| | 924 | setattr(self, key, record) |
| | 925 | elif isinstance(value, list) and \ |
| | 926 | value and isinstance(value[0], dict): |
| | 927 | |
| | 928 | rel_class = mapper.get_property(key).mapper.class_ |
| | 929 | new_attr_value = [] |
| | 930 | for row in value: |
| | 931 | if not isinstance(row, dict): |
| | 932 | raise Exception( |
| | 933 | 'Cannot send mixed (dict/non dict) data ' |
| | 934 | 'to list relationships in from_dict data.') |
| | 935 | record = rel_class.update_or_create(row) |
| | 936 | new_attr_value.append(record) |
| | 937 | setattr(self, key, new_attr_value) |
| | 938 | else: |
| | 939 | setattr(self, key, value) |
| 909 | | columns = [] |
| 910 | | for table in self.mapper.tables: |
| 911 | | for col in table.c: |
| 912 | | columns.append(col) |
| 913 | | |
| 914 | | data = dict([(col.name, getattr(self, col.name)) |
| 915 | | for col in columns if col.name not in exclude]) |
| | 943 | col_prop_names = [p.key for p in self.mapper.iterate_properties \ |
| | 944 | if isinstance(p, ColumnProperty)] |
| | 945 | data = dict([(name, getattr(self, name)) |
| | 946 | for name in col_prop_names if name not in exclude]) |