def merge(source_a, source_b, target, doc, validation=True): doc_a = source_a.get_doc() doc_b = source_b.get_doc() # get the options from bothe sources options_a = deepcopy(source_a.get_options_dict()) options_b = deepcopy(source_b.get_options_dict()) errors = [] # carry any errors through errors_a = doc_a.get("errors") if errors_a: errors += errors_a errors_b = doc_b.get("errors") if errors_b: errors += errors_b merged_options = inline._merge_option_dicts(source_a, options_a, options_b, doc_a, doc_b, errors=errors) # put this merged options into a copy of the doc new_doc = deepcopy(doc) new_doc[OPTIONS_KEY] = merged_options if errors: new_doc["errors"] = errors target.clone_history_from(source_a) _add_start_if_needed(source_a, target) _set_doc(target, new_doc, validation=validation) target.add_history_action(action=HISTORY_ACTION_MERGE, input=source_b, output_type=doc.get("type"))
def test_allows_subset(self): configured_option = deepcopy(BASE_PRODUCT) configured_option[u"options"][u"color"] = u"red" configured_version = WinnowVersion.add_doc(self.db, configured_option, {}) self.assertTrue(winnow.allows(self.base_version, configured_version)) configured_option = deepcopy(BASE_PRODUCT) configured_option[u"options"][u"color"] = [u"red", u"green"] # the values for given keys are a subset configured_version = WinnowVersion.add_doc(self.db, configured_option, {}) self.assertTrue(winnow.allows(self.base_version, configured_version)) self.assertFalse(winnow.allows(configured_version, self.base_version)) self.assertFalse( winnow.is_allowed_by(self.base_version, configured_version)) self.assertTrue( winnow.is_allowed_by(configured_version, self.base_version)) configured_option = deepcopy(BASE_PRODUCT) configured_option[u"options"][u"color"] = [u"red", u"green"] configured_option[u"options"][u"tool"] = [u"cnc"] configured_version = WinnowVersion.add_doc(self.db, configured_option, {}) self.assertTrue(winnow.allows(self.base_version, configured_version))
def test_allows_subset(self): configured_option = deepcopy(BASE_PRODUCT) configured_option[u"options"][u"color"] = u"red" configured_version = WinnowVersion.add_doc(self.db, configured_option, {}) self.assertTrue(winnow.allows(self.base_version, configured_version)) configured_option = deepcopy(BASE_PRODUCT) configured_option[u"options"][u"color"] = [u"red", u"green"] # the values for given keys are a subset configured_version = WinnowVersion.add_doc(self.db, configured_option, {}) self.assertTrue(winnow.allows(self.base_version, configured_version)) self.assertFalse(winnow.allows(configured_version, self.base_version)) self.assertFalse(winnow.is_allowed_by(self.base_version, configured_version)) self.assertTrue(winnow.is_allowed_by(configured_version, self.base_version)) configured_option = deepcopy(BASE_PRODUCT) configured_option[u"options"][u"color"] = [u"red", u"green"] configured_option[u"options"][u"tool"] = [u"cnc"] configured_version = WinnowVersion.add_doc(self.db, configured_option, {}) self.assertTrue(winnow.allows(self.base_version, configured_version))
def munge_values(self, this_value, other_value): #WTF is this really meant to do!!!!! from winnow.options import OptionsSet try: new_value = deepcopy(other_value) new_value.update(deepcopy(this_value)) except Exception, e: print "this_value", this_value print "other_value", other_value raise e
def mega_store(self, other): # # print "****STORES****" # print self.store # print other.store # expanded = deepcopy(self.store) for k in self.store.keys(): if "*" in k: matching = other.matcher.get_matching_paths(k) for match in matching: expanded[match] = self.store[k] # this consumes matched wildcards values if matching: del expanded[k] mega_store = {} for k, v in expanded.iteritems(): new_key, real_value = value_path_factory(k, v) if real_value is not None: if not new_key in mega_store.keys(): mega_store[new_key] = [] mega_store[new_key].append(real_value) return mega_store
def __new__(cls, value): if isinstance(value, list) or isinstance(value, set): as_list = value if len(as_list) > MAX_VALUE_SET_SIZE: raise OptionsExceptionNotAllowed("maximum value set size exceeded") elif len(as_list) == 0: return None elif len(as_list) == 1: return NumericNumberWinnowValue(list(as_list)[0]) else: return NumericWinnowValue.__new__(cls) else: as_list = value[u"value"] if not (isinstance(as_list, list) or isinstance(as_list, set)): raise OptionsExceptionFailedValidation("NumericNumberSieveValue must be a Decimal") if len(as_list) > MAX_VALUE_SET_SIZE: raise OptionsExceptionNotAllowed("maximum value set size exceeded") elif len(as_list) == 0: return None elif len(as_list) == 1: v = deepcopy(value) v[u"value"] = list(as_list)[0] return NumericNumberWinnowValue(v) else: return NumericWinnowValue.__new__(cls)
def __new__(cls, value): if isinstance(value, list) or isinstance(value, set): as_list = value if len(as_list) > MAX_VALUE_SET_SIZE: raise OptionsExceptionNotAllowed( "maximum value set size exceeded") elif len(as_list) == 0: return None elif len(as_list) == 1: return NumericNumberWinnowValue(list(as_list)[0]) else: return NumericWinnowValue.__new__(cls) else: as_list = value[u"value"] if not (isinstance(as_list, list) or isinstance(as_list, set)): raise OptionsExceptionFailedValidation( "NumericNumberSieveValue must be a Decimal") if len(as_list) > MAX_VALUE_SET_SIZE: raise OptionsExceptionNotAllowed( "maximum value set size exceeded") elif len(as_list) == 0: return None elif len(as_list) == 1: v = deepcopy(value) v[u"value"] = list(as_list)[0] return NumericNumberWinnowValue(v) else: return NumericWinnowValue.__new__(cls)
def test_allows_fails(self): configured_option = deepcopy(BASE_PRODUCT) configured_option[u"options"][u"color"] = u"purple" configured_version = WinnowVersion.add_doc(self.db, configured_option, {}) self.assertFalse(winnow.allows(self.base_version, configured_version)) self.assertFalse(self.base_version.allows(configured_version))
def get_quantified_configuration(db, product_path, choices): doc = { "schema": "https://opendesk.cc/schemata/options.json", "type": "choice", "name": "paul's choices", "options": choices } choice_document = WinnowVersion.add_doc(db, doc) product = WinnowProduct.get_from_path(db, product_path) qc_doc = deepcopy(product.get_doc()) product_doc = product.get_doc() version = product_doc["version"] qc_doc[u"type"] = "quantified_configuration" qc_doc[u"schema"] = "https://opendesk.cc/schemata/options.json" qc_doc[u"product"] = "%s@%s.%s.%s" % (product_doc["path"], version[0], version[1], version[2]) return WinnowQuantifiedConfiguration.merged(db, qc_doc, {}, product, choice_document)
def test_allows_subset_without_a_key(self): configured_option = deepcopy(BASE_PRODUCT) del configured_option[u"options"][u"color"] configured_version = WinnowVersion.add_doc(self.db, configured_option, {}) self.assertTrue(winnow.allows(configured_version, self.base_version))
def get_inlined_options(source): ref_doc = source.get_doc() # expand all the refs ref_hashes = {} inlined = deepcopy(ref_doc["options"]) inline_refs(inlined, ref_doc, source, ref_hashes) return inlined
def expand(source, target, validation=True): new_doc = deepcopy(source.get_doc()) target.clone_history_from(source) ## inline references ref_hashes = {} inline.inline_refs(new_doc, new_doc, source, ref_hashes) _set_doc(target, new_doc, validation=validation) return ref_hashes
def test_allows_subset_with_an_extra_key(self): configured_option = deepcopy(BASE_PRODUCT) configured_option[u"options"][u"wheels"] = [u"big", u"small"] configured_version = WinnowVersion.add_doc(self.db, configured_option, {}) self.assertTrue(winnow.allows(self.base_version, configured_version)) self.assertTrue(self.base_version.allows(configured_version))
def _extract_internal_path(doc, path): walker = doc parts = [p for p in path.split("/") if p] for part in parts: if not isinstance(walker, dict): raise OptionsExceptionReferenceError("internal_path reference couldn't find %s in %s" % (path, doc)) walker = walker.get(part) if walker is None: raise OptionsExceptionReferenceError("internal_path reference couldn't find %s in %s" % (path, doc)) return deepcopy(walker)
def _find_expanded_ref(reference, doc, source, options, ref_hashes, default_scopes=None): # looks up the contents of a reference if u"~" in reference: ref, internal_path = reference.split(u"~") elif u"#" in reference: ref, internal_path = reference.split(u"#") else: ref = reference internal_path = None if ref == "": referenced_doc = doc else: wv = source.lookup(ref) if wv is None: raise OptionsExceptionReferenceError( "Winnow Reference Error: Failed to find resource %s" % ref) existing_doc = wv.get_doc() referenced_doc = deepcopy(existing_doc) if referenced_doc is None: raise OptionsExceptionReferenceError( "Winnow Reference Error: Cannot find reference %s as ref doc is None" % ref) else: if options is not None: # if the ref also has some options then pre merge them into the reference referenced_options = referenced_doc.get(u"options") if referenced_options is None: referenced_doc[u"options"] = options else: options_a = OptionsSet(referenced_options) options_b = OptionsSet(options) referenced_doc[u"options"] = _merge_option_dicts( source, options_a.store, options_b.store, referenced_doc, doc) ## TODO look again at the the default scopes thing # if default_scopes is not None: # for k, v in referenced_doc[u"options"].iteritems(): # print v # if v.get("scopes") is None: # v["scopes"] = default_scopes new_doc = _extract_internal_path( referenced_doc, internal_path) if internal_path else referenced_doc # TODO revisit this use of doc as the reference doc to use new_doc = inline_refs(new_doc, doc, source, ref_hashes) return new_doc
def _extract_internal_path(doc, path): walker = doc parts = [p for p in path.split("/") if p] for part in parts: if not isinstance(walker, dict): raise OptionsExceptionReferenceError( "internal_path reference couldn't find %s in %s" % (path, doc)) walker = walker.get(part) if walker is None: raise OptionsExceptionReferenceError( "internal_path reference couldn't find %s in %s" % (path, doc)) return deepcopy(walker)
def find_key_named(node, key_name, collecting_node=None): if isinstance(node, dict): if key_name in node.keys(): if collecting_node is not None: collecting_node[key_name] = deepcopy(node[key_name]) for key in node.keys(): child = node[key] find_key_named(child, key_name, collecting_node) if isinstance(node, list): for i, child in enumerate(node[:]): find_key_named(child, key_name, collecting_node)
def test_unexpand(self): breed1 = WinnowVersion.get_from_path(self.db, "/breeds/collie") ref_hashes = {} expanded_doc = deepcopy(breed1.get_doc()) winnow.inline.inline_refs(expanded_doc, expanded_doc, breed1, ref_hashes) ref_value = u"$ref:/choices/dog_choices#/options/colours" colour_options = self.dog_base_choices["options"]["colours"] hash = winnow.utils.get_doc_hash(winnow.utils.json_dumps(colour_options)) self.assertEqual(ref_hashes.get(hash), ref_value) self.assertEqual(expanded_doc["options"]["colours"], ["brown", "red", "white"]) winnow.inline.restore_unchanged_refs(expanded_doc, ref_hashes) self.assertEqual(expanded_doc["options"]["colours"], ref_value)
def _find_expanded_ref(reference, doc, source, options, ref_hashes, default_scopes=None): # looks up the contents of a reference if u"~" in reference: ref, internal_path = reference.split(u"~") elif u"#" in reference: ref, internal_path = reference.split(u"#") else: ref = reference internal_path = None if ref == "": referenced_doc = doc else: wv = source.lookup(ref) if wv is None: raise OptionsExceptionReferenceError("Winnow Reference Error: Failed to find resource %s" % ref) existing_doc = wv.get_doc() referenced_doc = deepcopy(existing_doc) if referenced_doc is None: raise OptionsExceptionReferenceError("Winnow Reference Error: Cannot find reference %s as ref doc is None" % ref) else: if options is not None: # if the ref also has some options then pre merge them into the reference referenced_options = referenced_doc.get(u"options") if referenced_options is None: referenced_doc[u"options"] = options else: options_a = OptionsSet(referenced_options) options_b = OptionsSet(options) referenced_doc[u"options"] = _merge_option_dicts(source, options_a.store, options_b.store, referenced_doc, doc) ## TODO look again at the the default scopes thing # if default_scopes is not None: # for k, v in referenced_doc[u"options"].iteritems(): # print v # if v.get("scopes") is None: # v["scopes"] = default_scopes new_doc = _extract_internal_path(referenced_doc, internal_path) if internal_path else referenced_doc # TODO revisit this use of doc as the reference doc to use new_doc = inline_refs(new_doc, doc, source, ref_hashes) return new_doc
def default_choices(source, scopes): #take a copy of the options doc = source.get_doc() options_dict = deepcopy(source.get_options_dict()) #expand it ref_hashes = {} inline.inline_refs(options_dict, doc, source, ref_hashes) #scope it _trim_out_off_scope(options_dict, set(scopes)) # wrap it in an options set options_set = OptionsSet(options_dict) #get default options set default = options_set.default() return default.store
def quantify(source, target, doc, validation=True): default_quantity = { u"type": u"numeric::range", u"name": u"Quantity", u"default": Decimal("1"), u"max": Decimal("100"), u"min": Decimal("1"), } options_dict = source.get_options_dict() options_dict.setdefault(u'quantity', default_quantity) new_doc = deepcopy(doc) new_doc[OPTIONS_KEY] = options_dict target.clone_history_from(source) _add_start_if_needed(source, target) _set_doc(target, new_doc, validation=validation) target.add_history_action(action=HISTORY_ACTION_QUANTIFY, output_type=doc.get("type"))
def get_quantified_configuration(db, product_path, choices): doc = { "schema": "https://opendesk.cc/schemata/options.json", "type": "choice", "name": "paul's choices", "options":choices } choice_document = WinnowVersion.add_doc(db, doc) product = WinnowProduct.get_from_path(db, product_path) qc_doc = deepcopy(product.get_doc()) product_doc = product.get_doc() version = product_doc["version"] qc_doc[u"type"] = "quantified_configuration" qc_doc[u"schema"] = "https://opendesk.cc/schemata/options.json" qc_doc[u"product"] = "%s@%s.%s.%s" % (product_doc["path"], version[0], version[1], version[2]) return WinnowQuantifiedConfiguration.merged(db, qc_doc, {}, product, choice_document)
def scope(source, scopes, target, doc): new_doc = deepcopy(doc) _trim_out_off_scope(new_doc[OPTIONS_KEY], set(scopes)) target.clone_history_from(source) _add_start_if_needed(source, target) _set_doc(target, new_doc)
def intersection(self, other): # # # print "++++++++++++++++++++++++++++++++++++++ me +++++++++++++++++++++++++++++++++++++++++++" # print self.values_lookup.keys() # from winnow.options import OptionsSet self.check_class(other) values = [] default = None if type(other) == OptionResourceWinnowValue: msg = "You cannot merge a string with a resource: self: %s\n other: %s" % ( self, other) raise OptionsExceptionIncompatibleTypes(msg) elif isinstance(other, OptionNullWinnowValue): other_options = other._get_options() if other_options is None: return self self_keys = list(self.values_lookup.keys()) for value_id in self_keys: this_value = self.values_lookup.get(value_id) if isinstance(this_value, dict): new_value = deepcopy(this_value) this_options = self.get_value_options(value_id) elif isinstance(this_value, unicode): new_value = {"type": u"string", "value": this_value} this_options = None else: raise Exception("This shouldn't ever happen") if this_options is not None: try: new_value[u"options"] = OptionsSet(this_options).merge( OptionsSet(other_options)).store except OptionsExceptionSetWithException as e: new_value = None default = self._default if new_value is not None: values.append(new_value) else: other_keys = list(other.values_lookup.keys()) # self_keys = list(self.values_lookup.keys()) other_keys.sort() this_default_allowed = False that_default_allowed = False # find matching values # get the one to use # add them to the list ## when putting together the intersecting values perform a merge on their nested options for value_id in other_keys: this_value = self.values_lookup.get(value_id) if this_value is None: continue other_value = other.values_lookup[value_id] if self._default == value_id: this_default_allowed = True if other._default == value_id: that_default_allowed = True ## if they are both unicode just add the value if isinstance(other_value, unicode) and isinstance( this_value, unicode): values.append(this_value) elif isinstance(other_value, unicode) and isinstance( this_value, dict): values.append(deepcopy(this_value)) elif isinstance(other_value, dict) and isinstance( this_value, unicode): values.append(deepcopy(other_value)) elif isinstance(other_value, dict) and isinstance( this_value, dict): new_value = self.munge_values(this_value, other_value) values.append(new_value) else: raise Exception("this should never happen") if this_default_allowed and that_default_allowed: default = self._default elif this_default_allowed: default = self._default elif that_default_allowed: default = other._default else: default = None ## if there is no intersection return None if len(values) == 0: return None info = self.get_merged_info(other) info[u"type"] = self.type, info[VALUES_KEY_NAME] = values if default is not None: info[u"default"] = default return self.__class__(info)
def add_doc(target, doc, validation=True): _set_doc(target, deepcopy(doc), validation=validation)
def intersection(self, other): # print " " # print "**************** resource intersection ******************" # print "self keys", self.values_lookup.keys() from winnow.options import OptionsSet self.check_class(other) values = [] if type(other) == OptionNullWinnowValue: # print "a null resource" other_options = other._get_options() if other_options is None: return self self_keys = list(self.values_lookup.keys()) for value_id in self_keys: this_value = self.values_lookup.get(value_id) if isinstance(this_value, dict): new_value = deepcopy(this_value) this_options = self.get_value_options(value_id) else: raise Exception("This shouldn't ever happen") # if this_options is not None: # # new_value[u"options"] = OptionsSet(this_options).merge(OptionsSet(other_options)).store # # values.append(new_value) # # # # if this_options is not None: try: new_value[u"options"] = OptionsSet(this_options).merge( OptionsSet(other_options)).store except OptionsExceptionSetWithException as e: new_value = None if new_value is not None: values.append(new_value) elif type(other) == OptionStringWinnowValue: raise OptionsExceptionIncompatibleTypes( "You cannot merge and resource with a string: %s" % other) else: # take a cope of the the possible values in a lookup table keyed by path all_values = deepcopy(self.values_lookup) all_values.update(deepcopy(other.values_lookup)) # prune and child nodes from each set other_paths = list(other.values_lookup.keys()) self_paths = list(self.values_lookup.keys()) other_paths_pruned = self._prune_child_nodes(other_paths) self_paths_pruned = self._prune_child_nodes(self_paths) # then add values if it or parent is in other intersecting_paths = self._intersection_of_path_sets( other_paths_pruned, self_paths_pruned) # for each intersecting path find the most specific option set on each side and merge them values = [] for path in intersecting_paths: # find best fit values to get merged options this_value = self.values_lookup[self._nearest_match( path, self_paths)] other_value = other.values_lookup[self._nearest_match( path, other_paths)] merged_value = self.munge_values(this_value, other_value) # and then add these options to a copy of the origional value from all_values new_value = all_values[path] if "options" in merged_value: new_value["options"] = merged_value["options"] values.append(new_value) ## if there is no intersection return None if len(values) == 0: return None info = self.get_merged_info(other) info[u"type"] = self.type, info[VALUES_KEY_NAME] = values return self.__class__(info)
def intersection(self, other): # print " " # print "**************** resource intersection ******************" # print "self keys", self.values_lookup.keys() from winnow.options import OptionsSet self.check_class(other) values = [] if type(other) == OptionNullWinnowValue: # print "a null resource" other_options = other._get_options() if other_options is None: return self self_keys = list(self.values_lookup.keys()) for value_id in self_keys: this_value = self.values_lookup.get(value_id) if isinstance(this_value, dict): new_value = deepcopy(this_value) this_options = self.get_value_options(value_id) else: raise Exception("This shouldn't ever happen") # if this_options is not None: # # new_value[u"options"] = OptionsSet(this_options).merge(OptionsSet(other_options)).store # # values.append(new_value) # # # # if this_options is not None: try: new_value[u"options"] = OptionsSet(this_options).merge(OptionsSet(other_options)).store except OptionsExceptionSetWithException as e: new_value = None if new_value is not None: values.append(new_value) elif type(other) == OptionStringWinnowValue: raise OptionsExceptionIncompatibleTypes("You cannot merge and resource with a string: %s" % other) else: # take a cope of the the possible values in a lookup table keyed by path all_values = deepcopy(self.values_lookup) all_values.update(deepcopy(other.values_lookup)) # prune and child nodes from each set other_paths = list(other.values_lookup.keys()) self_paths = list(self.values_lookup.keys()) other_paths_pruned = self._prune_child_nodes(other_paths) self_paths_pruned = self._prune_child_nodes(self_paths) # then add values if it or parent is in other intersecting_paths = self._intersection_of_path_sets(other_paths_pruned, self_paths_pruned) # for each intersecting path find the most specific option set on each side and merge them values = [] for path in intersecting_paths: # find best fit values to get merged options this_value = self.values_lookup[self._nearest_match(path, self_paths)] other_value = other.values_lookup[self._nearest_match(path, other_paths)] merged_value = self.munge_values(this_value, other_value) # and then add these options to a copy of the origional value from all_values new_value = all_values[path] if "options" in merged_value: new_value["options"] = merged_value["options"] values.append(new_value) ## if there is no intersection return None if len(values) == 0: return None info = self.get_merged_info(other) info[u"type"] = self.type, info[VALUES_KEY_NAME] = values return self.__class__(info)
def intersection(self, other): # # # print "++++++++++++++++++++++++++++++++++++++ me +++++++++++++++++++++++++++++++++++++++++++" # print self.values_lookup.keys() # from winnow.options import OptionsSet self.check_class(other) values = [] default = None if type(other) == OptionResourceWinnowValue: msg = "You cannot merge a string with a resource: self: %s\n other: %s" % (self, other) raise OptionsExceptionIncompatibleTypes(msg) elif isinstance(other, OptionNullWinnowValue): other_options = other._get_options() if other_options is None: return self self_keys = list(self.values_lookup.keys()) for value_id in self_keys: this_value = self.values_lookup.get(value_id) if isinstance(this_value, dict): new_value = deepcopy(this_value) this_options = self.get_value_options(value_id) elif isinstance(this_value, unicode): new_value = { "type": u"string", "value": this_value } this_options = None else: raise Exception("This shouldn't ever happen") if this_options is not None: try: new_value[u"options"] = OptionsSet(this_options).merge(OptionsSet(other_options)).store except OptionsExceptionSetWithException as e: new_value = None default = self._default if new_value is not None: values.append(new_value) else: other_keys = list(other.values_lookup.keys()) # self_keys = list(self.values_lookup.keys()) other_keys.sort() this_default_allowed = False that_default_allowed = False # find matching values # get the one to use # add them to the list ## when putting together the intersecting values perform a merge on their nested options for value_id in other_keys: this_value = self.values_lookup.get(value_id) if this_value is None: continue other_value = other.values_lookup[value_id] if self._default == value_id: this_default_allowed = True if other._default == value_id: that_default_allowed = True ## if they are both unicode just add the value if isinstance(other_value, unicode) and isinstance(this_value, unicode): values.append(this_value) elif isinstance(other_value, unicode) and isinstance(this_value, dict): values.append(deepcopy(this_value)) elif isinstance(other_value, dict) and isinstance(this_value, unicode): values.append(deepcopy(other_value)) elif isinstance(other_value, dict) and isinstance(this_value, dict): new_value = self.munge_values(this_value, other_value) values.append(new_value) else: raise Exception("this should never happen") if this_default_allowed and that_default_allowed: default = self._default elif this_default_allowed: default = self._default elif that_default_allowed: default = other._default else: default = None ## if there is no intersection return None if len(values) == 0: return None info = self.get_merged_info(other) info[u"type"] = self.type, info[VALUES_KEY_NAME] = values if default is not None: info[u"default"] = default return self.__class__(info)
def __init__(self, db=None, kwargs={}): self.kwargs = deepcopy(kwargs) self.db = db if db else self.cls_db if self.kwargs.get("uuid") is None: self.kwargs["uuid"] = unicode(uuid.uuid4())
def clone_history_from(self, options_interface): history = options_interface.kwargs.get(u"history") if history is not None: self.kwargs[u"history"] = deepcopy(history)