def test_obj_info_primary_vars(cls_info, obj_info): assert isinstance(obj_info.primary_vars, tuple) for column, variable in iter_zip(cls_info.primary_key, obj_info.primary_vars): assert obj_info.variables.get(column) == variable assert len(obj_info.primary_vars) == len(cls_info.primary_key)
def __init__(self, local_key, remote_key, many, on_remote): assert type(local_key) is tuple and type(remote_key) is tuple self.local_key = local_key self.remote_key = remote_key self.local_cls = getattr(self.local_key[0], "cls", None) self.remote_cls = self.remote_key[0].cls self.remote_key_is_primary = False primary_key = get_cls_info(self.remote_cls).primary_key if len(primary_key) == len(self.remote_key): for column1, column2 in iter_zip(self.remote_key, primary_key): if column1.name != column2.name: break else: self.remote_key_is_primary = True self.many = many self.on_remote = on_remote # XXX These should probably be weak dictionaries. self._local_columns = {} self._remote_columns = {} self._l_to_r = {} self._r_to_l = {}
def get(self, name, namespace=None): """Translate a property name path to the actual property. This method accepts a property name like C{"id"} or C{"Class.id"} or C{"module.path.Class.id"}, and tries to find a unique class/property with the given name. When the C{namespace} argument is given, the registry will be able to disambiguate names by choosing the one that is closer to the given namespace. For instance C{get("Class.id", "a.b.c")} will choose C{a.Class.id} rather than C{d.Class.id}. """ key = ".".join(reversed(name.split("."))) + "." i = bisect_left(self._properties, (key, )) l = len(self._properties) best_props = [] if namespace is None: while i < l and self._properties[i][0].startswith(key): path, prop_ref = self._properties[i] prop = prop_ref() if prop is not None: best_props.append((path, prop)) i += 1 else: namespace_parts = ("." + namespace).split(".") best_path_info = (0, sys.maxsize) while i < l and self._properties[i][0].startswith(key): path, prop_ref = self._properties[i] prop = prop_ref() if prop is None: i += 1 continue path_parts = path.split(".") path_parts.reverse() common_prefix = 0 for part, ns_part in iter_zip(path_parts, namespace_parts): if part == ns_part: common_prefix += 1 else: break path_info = (-common_prefix, len(path_parts) - common_prefix) if path_info < best_path_info: best_path_info = path_info best_props = [(path, prop)] elif path_info == best_path_info: best_props.append((path, prop)) i += 1 if not best_props: raise PropertyPathError("Path '%s' matches no known property." % name) elif len(best_props) > 1: paths = [ ".".join(reversed(path.split(".")[:-1])) for path, prop in best_props ] raise PropertyPathError("Path '%s' matches multiple " "properties: %s" % (name, ", ".join(paths))) return best_props[0][1]
def _get_local_column(self, local_cls, remote_column): try: return self._r_to_l[local_cls].get(remote_column) except KeyError: map = {} for local_prop, _remote_column in iter_zip(self.local_key, self.remote_key): map[_remote_column] = local_prop.__get__(None, local_cls) return self._r_to_l.setdefault(local_cls, map).get(remote_column)
def _get_remote_column(self, local_cls, local_column): try: return self._l_to_r[local_cls].get(local_column) except KeyError: map = {} for local_prop, _remote_column in iter_zip(self.local_key, self.remote_key): map[local_prop.__get__(None, local_cls)] = _remote_column return self._l_to_r.setdefault(local_cls, map).get(local_column)
def get_insert_identity(self, primary_key, primary_variables): equals = [] for column, variable in iter_zip(primary_key, primary_variables): if not variable.is_defined(): # The Select here prevents PostgreSQL from going nuts and # performing a sequential scan when there *is* an index. # http://tinyurl.com/2n8mv3 variable = Select(currval(column)) equals.append(Eq(column, variable)) return And(*equals)
def execute(self, statement, params=None, noresult=False): """Execute a statement with the given parameters. This extends the L{Connection.execute} method to add support for automatic retrieval of inserted primary keys to link in-memory objects with their specific rows. """ if (isinstance(statement, Insert) and self._database._version >= 80200 and statement.primary_variables is not Undef and statement.primary_columns is not Undef): # Here we decorate the Insert statement with a Returning # expression, so that we get back in the result the values # for the primary key just inserted. This prevents a round # trip to the database for obtaining these values. result = Connection.execute(self, Returning(statement), params) for variable, value in iter_zip(statement.primary_variables, result.get_one()): result.set_variable(variable, value) return result return Connection.execute(self, statement, params, noresult)
def assert_variables_equal(checked, expected): assert len(checked) == len(expected) for check, expect in iter_zip(checked, expected): assert check.__class__ == expect.__class__ assert check.get() == expect.get()
def link(self, local, remote, setting=False): """Link objects to represent their relation. @param local: Object representing the I{local} side of the reference. @param remote: Object representing the I{remote} side of the reference, or the actual value to be set as the local key. @param setting: Pass true when the relationship is being newly created. """ local_info = get_obj_info(local) try: remote_info = get_obj_info(remote) except ClassInfoError: # Must be a plain key. Just set it. # XXX I guess this is broken if self.on_remote is True. local_variables = self.get_local_variables(local) if type(remote) is not tuple: remote = (remote,) assert len(remote) == len(local_variables) for variable, value in iter_zip(local_variables, remote): variable.set(value) return local_store = Store.of(local) remote_store = Store.of(remote) if setting: if local_store is None: if remote_store is None: local_info.event.hook("added", self._add_all, local_info) remote_info.event.hook("added", self._add_all, local_info) else: remote_store.add(local) local_store = remote_store elif remote_store is None: local_store.add(remote) elif local_store is not remote_store: raise WrongStoreError("%r and %r cannot be linked because they " "are in different stores." % (local, remote)) # In cases below, we maintain a reference to the remote object # to make sure it won't get deallocated while the link is active. relation_data = local_info.get(self) if self.many: if relation_data is None: relation_data = local_info[self] = {"remote": {remote_info: remote}} else: relation_data["remote"][remote_info] = remote else: if relation_data is None: relation_data = local_info[self] = {"remote": remote} else: old_remote = relation_data.get("remote") if old_remote is not None: self.unlink(local_info, get_obj_info(old_remote)) relation_data["remote"] = remote if setting: local_vars = local_info.variables remote_vars = remote_info.variables pairs = iter_zip(self._get_local_columns(local.__class__), self.remote_key) if self.on_remote: local_has_changed = False for local_column, remote_column in pairs: local_var = local_vars[local_column] if not local_var.is_defined(): remote_vars[remote_column].set(PendingReferenceValue) else: remote_vars[remote_column].set(local_var.get()) if local_var.has_changed(): local_has_changed = True if local_has_changed: self._add_flush_order(local_info, remote_info) local_info.event.hook("changed", self._track_local_changes, remote_info) local_info.event.hook("flushed", self._break_on_local_flushed, remote_info) #local_info.event.hook("removed", self._break_on_local_removed, # remote_info) remote_info.event.hook("removed", self._break_on_remote_removed, weakref.ref(local_info)) else: remote_has_changed = False for local_column, remote_column in pairs: remote_var = remote_vars[remote_column] if not remote_var.is_defined(): local_vars[local_column].set(PendingReferenceValue) else: local_vars[local_column].set(remote_var.get()) if remote_var.has_changed(): remote_has_changed = True if remote_has_changed: self._add_flush_order(local_info, remote_info, remote_first=True) remote_info.event.hook("changed", self._track_remote_changes, local_info) remote_info.event.hook("flushed", self._break_on_remote_flushed, local_info) #local_info.event.hook("removed", self._break_on_remote_removed, # local_info) local_info.event.hook("changed", self._break_on_local_diverged, remote_info) else: local_info.event.hook("changed", self._break_on_local_diverged, remote_info) remote_info.event.hook("changed", self._break_on_remote_diverged, weakref.ref(local_info)) if self.on_remote: remote_info.event.hook("removed", self._break_on_remote_removed, weakref.ref(local_info))