From 0ccea3cef8d0ca5fcf22b95a3d95bcf8e939f3db Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 15 Dec 2022 16:02:27 +1100 Subject: [PATCH] Add documentation for pset API module --- .../api/owner/update_owner_history.py | 4 +- .../ifcopenshell/api/pset/add_pset.py | 61 +++++- .../ifcopenshell/api/pset/add_qto.py | 60 +++++- .../ifcopenshell/api/pset/edit_pset.py | 175 ++++++++++++++++-- .../ifcopenshell/api/pset/edit_qto.py | 106 ++++++++++- .../ifcopenshell/api/pset/remove_pset.py | 26 ++- 6 files changed, 392 insertions(+), 40 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/api/owner/update_owner_history.py b/src/ifcopenshell-python/ifcopenshell/api/owner/update_owner_history.py index 47e37884d3..7cf3f8ac8d 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/owner/update_owner_history.py +++ b/src/ifcopenshell-python/ifcopenshell/api/owner/update_owner_history.py @@ -68,9 +68,7 @@ class Usecase: if not application: return if not self.settings["element"].OwnerHistory: - self.settings["element"].OwnerHistory = ifcopenshell.api.run( - "owner.create_owner_history", self.file, **self.settings - ) + self.settings["element"].OwnerHistory = ifcopenshell.api.run("owner.create_owner_history", self.file) return self.settings["element"].OwnerHistory if self.file.get_total_inverses(self.settings["element"].OwnerHistory) > 1: new = ifcopenshell.util.element.copy(self.file, self.settings["element"].OwnerHistory) diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset/add_pset.py b/src/ifcopenshell-python/ifcopenshell/api/pset/add_pset.py index ae8a1cb96d..a13e2416ce 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset/add_pset.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset/add_pset.py @@ -20,11 +20,64 @@ import ifcopenshell class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, product=None, name=None): + """Adds a new property set to a product + + Products, such as physical objects or types in IFC may have properties + associated with them. These properties are typically simple key value + metadata with data types. For example, a wall type may have a property + called FireRating with a text value of "2HR". Properties are grouped + into property sets, so that related properties are grouped together. + + If a property is assigned to a type, the property is inherited by all + occurrences of that type. For example, a wall type with a FireRating + property of "2HR" automatically implies that all walls of that wall type + also have a FireRating of "2HR". It is not necessary to explictly define + the property again for each occurrence. This also means that properties + are typically defined on types. If the same property is defined at an + occurrence, this overrides the property defined on the type. + + buildingSMART has come up with a long list of standardised properties + for the most common properties required internationally. This solves the + age-old question of "where do I store my FireRating data for walls"? The + answer, in this case, is in the "FireRating" property with an "IfcLabel" + data type grouped in the "Pset_WallCommon" property set. It is + recommended to view the list of standardised buildingSMART properties + and see if any suit your needs first. If none are appropriate, then you + are free to create your own custom properties. + + This function adds a blank named property set. One you have a property + set you may add properties using ifcopenshell.api.pset.edit_pset. + + See also ifcopenshell.api.pset.add_qto if you want to add quantification + data, rather than arbitrary metadata. + + :param product: The IfcObject that you want to assign a property set to. + :type product: ifcopenshell.entity_instance.entity_instance + :param name: The name of the property set. Property sets that are + standardised by buildingSMART typically have a prefix of "Pset_", + like "Pset_WallCommon". If you create your own, you must not use + that prefix. It is recommended to use your own prefix tailored to + your project, company, or local government requirement. + :type name: str + :return: The newly created IfcPropertySet + :rtype: ifcopenshell.entity_instance.entity_instance + + Example:: + + # Let's imagine we have a new wall type. + wall_type = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWallType") + + # Note that this only creates and assigns an empty property set. We + # still need to add properties into the property set. Having blank + # property sets are invalid. + pset = ifcopenshell.api.run("pset.add_pset", model, product=wall_type, name="Pset_WallCommon") + + # Add a fire rating property standardised by buildingSMART. + ifcopenshell.api.run("pset.edit_pset", model, pset=pset, properties={"FireRating": "2HR"}) + """ self.file = file - self.settings = {"product": None, "name": None} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"product": product, "name": name} def execute(self): if self.settings["product"].is_a("IfcObject") or self.settings["product"].is_a("IfcContext"): diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset/add_qto.py b/src/ifcopenshell-python/ifcopenshell/api/pset/add_qto.py index d07aa079eb..1f90ffb3d0 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset/add_qto.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset/add_qto.py @@ -21,11 +21,63 @@ import ifcopenshell.api class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, product=None, name=None): + """Adds a new quantity set to a product + + Products, such as physical objects or types in IFC may have quantities + associated with them. These quantities are typically simple key value + metadata with data types. For example, a wall type may have a quantity + called NetSideArea with a area value of "4.2". Quantities are grouped + into quantity sets, so that related quantities are grouped together. + + Quantities can only be assigned to occurrences, not types. + + Quantities are similar to, but different from properties in that they + may store a method of measurement or formula. Quantities may also have + parametric relationships to other calculated values, such as cost + schedules, resource utilisation, or construction task durations. + + buildingSMART has come up with a long list of standardised quantities + for the most common quantities required internationally. This solves the + age-old question of "what's the standard way of storing quantity + take-off data"? It is recommended to view the list of standardised + buildingSMART quantities and see if any suit your needs first. If none + are appropriate, then you are free to create your own custom quantities. + + This function adds a blank named quantity set. One you have a quantity + set you may add quantities using ifcopenshell.api.pset.edit_qto. + + See also ifcopenshell.api.pset.add_qto if you want to arbitrary + metadata, rather than quantification data. + + :param product: The IfcObject that you want to assign a quantity set to. + :type product: ifcopenshell.entity_instance.entity_instance + :param name: The name of the quantity set. Quantity sets that are + standardised by buildingSMART typically have a prefix of "Qto_", + like "Qto_WallBaseQuantities". If you create your own, you must not + use that prefix. It is recommended to use your own prefix tailored + to your project, company, or local government requirement. + :type name: str + :return: The newly created IfcElementQuantity + :rtype: ifcopenshell.entity_instance.entity_instance + + Example:: + + # Let's imagine we have a new wall. + wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall") + + # Note that this only creates and assigns an empty quantity set. We + # still need to add quantities into the property set. Having blank + # quantity sets are invalid. + qto = ifcopenshell.api.run("pset.add_qto", model, product=wall_type, name="Qto_WallBaseQuantities") + + # Add a side area property standardised by buildingSMART. This + # allows quantity take-off to occur, even though no geometry has + # even been modelled! + ifcopenshell.api.run("pset.edit_qto", model, qto=qto, properties={"NetSideArea": 4.2}) + """ self.file = file - self.settings = {"product": None, "name": None} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"product": product, "name": name} def execute(self): if self.settings["product"].is_a("IfcObject") or self.settings["product"].is_a("IfcContext"): diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py b/src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py index af30b7ae75..0d30495a44 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py @@ -21,11 +21,140 @@ import ifcopenshell.util.pset class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, pset=None, name=None, properties=None, pset_template=None, should_purge=False): + """Edits a property set and its properties + + At its simplest usage, this may be used to edit the name of a property + set. It may also be used to add, edit, or remove properties, either + arbitrarily or using a property set template. + + A list of properties are provided as a dictionary, where the keys are + property names, and values are property values. Keys that don't already + exist are interpreted as properties to be added. Keys that already exist + are interpreted as properties to be edited. A "None" value may specify a + property to be deleted. + + Properties must have a data type. There are lots of data types in IFCs, + not just simple unitless data types like integers, booleans, text, but + also distinguishing between types of text, like labels versus + descriptive text. There are also lots of unit-based data types like + areas, volumes, lengths, power, density, flow rates, pressure, etc. + + To ensure the appropriate data type is used for properties, a property + set template may be used. These can be seen as "property + specifications". A default selection is provided by buildingSMART, so + that all buildingSMART defined standard properties have exactly the same + data types and exactly the right property names without fear of invalid + data or typos. The built-in buildingSMART templates are always loaded. + However, you may also specify your own templates. If you try to add a + non-standard property that does not exist in either your own template or + in the built-in buildingSMART template, then you have the responsibility + to ensure that data types are always consistent and correct. + + :param pset: The IfcPropertySet to edit. + :type pset: ifcopenshell.entity_instance.entity_instance + :param name: A new name for the property set. If no name is specified, + the property set name is not changed. + :type name: str, optional + :param properties: A dictionary of properties. The keys must be a string + of the name of the property. The data type of the value will be + determined by the property set template. If no property set + template is found, the data types of the Python values will + influence the IFC data type of the property. String values will + become IfcLabel, float values will become IfcReal, booleans will + become IfcBoolean, and integers will become IfcInteger. If more + control is desired, you may explicitly specify IFC data objects + directly. + :type properties: dict + :param pset_template: If a property set template is provided, this will + be used to determine data types. If no user-defined template is + provided, the built-in buildingSMART templates will be loaded. + :type pset_template: ifcopenshell.entity_instance.entity_instance + :param should_purge: If left as False, properties set to None will be + left as None but not removed. If set to true, properties set to None + will actually be removed. + :type should_purge: bool, optional + :return: None + :rtype: None + + Example:: + + # Let's imagine we have a new wall type. + wall_type = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWallType") + + # This is a standard buildingSMART property set. + pset = ifcopenshell.api.run("pset.add_pset", model, product=wall_type, name="Pset_WallCommon") + + # In this scenario, we don't specify any pset_template because it is + # part of the built-in buildingSMART templates, and so the + # FireRating will automatically be an IfcLabel, and the thermal + # transmittance value will automatically be an + # IfcThermalTransmittanceMeasure. Neither of these properties exist + # yet, so they will be created. + ifcopenshell.api.run("pset.edit_pset", model, + pset=pset, properties={"FireRating": "2HR", "ThermalTransmittance": 42.3}) + + # We can edit existing properties. In this case, "FireRating" is + # edited from "2HR" to "1HR". Combustible is new, and will be added. + # The existing "ThermalTransmittance" property will be left + # unchanged. + ifcopenshell.api.run("pset.edit_pset", model, + pset=pset, properties={"FireRating": "1HR", "Combustible": False}) + + # Setting to None will change the value but not delete the property. + ifcopenshell.api.run("pset.edit_pset", model, pset=pset, properties={"Combustible": None}) + + # If you actually want to delete the property, enable purging. + ifcopenshell.api.run("pset.edit_pset", model, pset=pset, + properties={"Combustible": None}, should_purge=True) + + # What if we wanted to manage our own properties? Let's create our + # own "Company Standard" property set templates. Notice how we + # prefix our property set with "Foo_", if our company name was "Foo" + # this would make sense. + pset_template = ifcopenshell.api.run("pset_template.add_pset_template", model) + ifcopenshell.api.run("pset_template.edit_pset_template", model, attributes={"Name": "Foo_Bar"}) + + # Let's imagine we want all model authors to specify two properties, + # one being a length measurement and another being a boolean. + prop1 = ifcopenshell.api.run("pset_template.add_prop_template", model, pset_template=pset_template) + ifcopenshell.api.run("pset_template.edit_prop_template", model, + prop_template=prop1, attributes={"Name": "DemoA", "PrimaryMeasureType": "IfcLengthMeasure"}) + prop2 = ifcopenshell.api.run("pset_template.add_prop_template", model, pset_template=pset_template) + ifcopenshell.api.run("pset_template.edit_prop_template", model, + prop_template=prop2, attributes={"Name": "DemoB", "PrimaryMeasureType": "IfcBoolean"}) + + # Now we can use our property set template to add our properties, + # and the data types will always match our template. + pset = ifcopenshell.api.run("pset.add_pset", model, product=wall_type, name="Foo_Bar") + ifcopenshell.api.run("pset.edit_pset", model, + pset=pset, properties={"DemoA": 42.3, "DemoB": True}, pset_template=pset_template) + + # Here's a third scenario where we want to add arbitrary properties + # that are not standardised by anything, not even our own custom + # templates. + pset = ifcopenshell.api.run("pset.add_pset", model, product=wall_type, name="Custom_Pset") + ifcopenshell.api.run("pset.edit_pset", model, + pset=pset, properties={ + # Basic Python data types are mapped to a sensible default + "SomeLabel": "Foo", + "SomeNumber": 12.3, + # But we can always specify exactly what we're after too + "ExplicitLength": model.createIfcLengthMeasure(42.3) + }) + + # Editing existing properties will retain their current data types + # if possible. So this will still be a length measure. + ifcopenshell.api.run("pset.edit_pset", model, pset=pset, properties={"ExplicitLength": 12.3}) + """ self.file = file - self.settings = {"pset": None, "name": None, "properties": {}, "pset_template": None} - for key, value in settings.items(): - self.settings[key] = value + self.settings = { + "pset": pset, + "name": name, + "properties": properties or {}, + "pset_template": pset_template, + "should_purge": should_purge, + } def execute(self): self.update_pset_name() @@ -46,8 +175,8 @@ class Usecase: self.psetqto = ifcopenshell.util.pset.get_template("IFC4") self.pset_template = self.psetqto.get_by_name(self.settings["pset"].Name) - #TODO - Add support for changing property types? - # For example - IfcPropertyEnumeratedValue to + # TODO - Add support for changing property types? + # For example - IfcPropertyEnumeratedValue to # IfcPropertySingleValue. Or maybe the user should # just delete the property first? - vulevukusej def update_existing_properties(self): @@ -56,7 +185,7 @@ class Usecase: self.update_existing_enum(prop) else: self.update_existing_property(prop) - + def update_existing_enum(self, prop): if prop.Name not in self.settings["properties"]: return @@ -64,26 +193,35 @@ class Usecase: if isinstance(value, list): sel_vals = [] for val in value: - primary_measure_type = prop.EnumerationReference.EnumerationValues[0].is_a() #Only need the first enum type since all enums are of the same type + primary_measure_type = prop.EnumerationReference.EnumerationValues[ + 0 + ].is_a() # Only need the first enum type since all enums are of the same type ifc_val = self.file.create_entity(primary_measure_type, val) sel_vals.append(ifc_val) prop.EnumerationValues = tuple(sel_vals) else: if value.EnumerationReference.EnumerationValues == (): + if self.settings["should_purge"]: + del self.settings["properties"][prop.Name] + self.file.remove(prop) + return prop.EnumerationReference.EnumerationValues = () prop.EnumerationValues = () - elif isinstance(value, ifcopenshell.entity_instance): prop.EnumerationReference.EnumerationValues = value.EnumerationReference.EnumerationValues prop.EnumerationValues = value.EnumerationValues del self.settings["properties"][prop.Name] - - def update_existing_property(self, prop): + + def update_existing_property(self, prop): if prop.Name not in self.settings["properties"]: return value = self.settings["properties"][prop.Name] if value is None: + if self.settings["should_purge"]: + del self.settings["properties"][prop.Name] + self.file.remove(prop) + return prop.NominalValue = None elif isinstance(value, ifcopenshell.entity_instance): prop.NominalValue = value @@ -103,7 +241,7 @@ class Usecase: if isinstance(value, ifcopenshell.entity_instance): if value.is_a(True) == "IFC4.IfcPropertyEnumeratedValue": properties.append(value) - continue + continue else: properties.append( self.file.create_entity( @@ -111,21 +249,22 @@ class Usecase: **{"Name": name, "NominalValue": value}, ) ) - #TODO-The following "elif" is temporary code, will need to refactor at some point - vulevukusej + # TODO-The following "elif" is temporary code, will need to refactor at some point - vulevukusej elif isinstance(value, list): for pset_template in self.settings["pset_template"].HasPropertyTemplates: if pset_template.Name == name: prop_enum = self.file.create_entity( "IFCPROPERTYENUMERATION", Name=name, - EnumerationValues=pset_template.Enumerators.EnumerationValues + EnumerationValues=pset_template.Enumerators.EnumerationValues, ) prop_enum_value = self.file.create_entity( "IFCPROPERTYENUMERATEDVALUE", Name=name, - EnumerationValues=tuple(self.file.create_entity( - pset_template.PrimaryMeasureType, v) for v in value), - EnumerationReference=prop_enum + EnumerationValues=tuple( + self.file.create_entity(pset_template.PrimaryMeasureType, v) for v in value + ), + EnumerationReference=prop_enum, ) properties.append(prop_enum_value) continue @@ -133,7 +272,7 @@ class Usecase: primary_measure_type = self.get_primary_measure_type(name, new_value=value) value = self.cast_value_to_primary_measure_type(value, primary_measure_type) nominal_value = self.file.create_entity(primary_measure_type, value) - + properties.append( self.file.create_entity( "IfcPropertySingleValue", diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset/edit_qto.py b/src/ifcopenshell-python/ifcopenshell/api/pset/edit_qto.py index 832905fa4f..f6423df46b 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset/edit_qto.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset/edit_qto.py @@ -21,11 +21,100 @@ import ifcopenshell.util.pset class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, qto=None, name=None, properties=None, pset_template=None): + """Edits a quantity set and its quantities + + At its simplest usage, this may be used to edit the name of a quantity + set. It may also be used to add, edit, or remove quantities. + + See ifcopenshell.api.pset.edit_pset for documentation on how this is + intended to be used. + + One major difference is that quantities set to None are always purged. + It is not allowed to have None quantities in IFC. + + :param qto: The IfcElementQuantity to edit. + :type qto: ifcopenshell.entity_instance.entity_instance + :param name: A new name for the quantity set. If no name is specified, + the quantity set name is not changed. + :type name: str, optional + :param properties: A dictionary of properties. The keys must be a string + of the name of the quantity. The data type of the value will be + determined by the quantity set template. If no quantity set + template is found, the data types of the Python values will + influence the IFC data type of the quantity. String values will + become IfcLabel, float values will become IfcReal, booleans will + become IfcBoolean, and integers will become IfcInteger. If more + control is desired, you may explicitly specify IFC data objects + directly. + :type properties: dict + :param pset_template: If a quantity set template is provided, this will + be used to determine data types. If no user-defined template is + provided, the built-in buildingSMART templates will be loaded. + :type pset_template: ifcopenshell.entity_instance.entity_instance + :return: None + :rtype: None + + Example:: + + # Let's imagine we have a new wall type. + wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall") + + # This is a standard buildingSMART property set. + qto = ifcopenshell.api.run("pset.add_qto", model, product=wall, name="Qto_WallBaseQuantities") + + # In this scenario, we don't specify any pset_template because it is + # part of the built-in buildingSMART templates, and so the Length + # will automatically be an IfcLengthMeasure, and the NetVolume will + # automatically be an IfcVolumeMeasure. Neither of these properties + # exist yet, so they will be created. + ifcopenshell.api.run("pset.edit_qto", model, qto=qto, properties={"Length": 12, "NetVolume": 7.2}) + + # Setting to None will delete the quantity. + ifcopenshell.api.run("pset.edit_qto", model, qto=qto, properties={"Length": None}) + + # What if we wanted to manage our own properties? Let's create our + # own "Company Standard" property set templates. Notice how we + # prefix our property set with "Foo_", if our company name was "Foo" + # this would make sense. In this example, we say that our template + # only applies to walls and is for quantities. + pset_template = ifcopenshell.api.run("pset_template.add_pset_template", model) + ifcopenshell.api.run("pset_template.edit_pset_template", model, + attributes={ + "Name": "Foo_Wall", "TemplateType": "QTO_OCCURRENCEDRIVEN", "ApplicableEntity": "IfcWall" + }) + + # Let's imagine we want all model authors to specify a length + # measurement for the portion of a wall that is overhanging. + prop = ifcopenshell.api.run("pset_template.add_prop_template", model, pset_template=pset_template) + ifcopenshell.api.run("pset_template.edit_prop_template", model, + prop_template=prop, attributes={ + "Name": "OverhangLength", "TemplateType": "Q_LENGTH", "PrimaryMeasureType": "IfcLengthMeasure" + }) + + # Now we can use our property set template to add our properties, + # and the data types will always match our template. + qto = ifcopenshell.api.run("pset.add_qto", model, product=wall, name="Foo_Wall") + ifcopenshell.api.run("pset.edit_qto", model, + qto=qto, properties={"OverhangLength": 42.3}, pset_template=pset_template) + + # Here's a third scenario where we want to add arbitrary quantities + # that are not standardised by anything, not even our own custom + # templates. + qto = ifcopenshell.api.run("pset.add_qto", model, product=wall, name="Custom_Qto") + ifcopenshell.api.run("pset.edit_qto", model, + qto=qto, properties={ + "SomeLength": model.createIfcLengthMeasure(42.3), + "SomeArea": model.createIfcAreaMeasure(21.0) + }) + + # Editing existing quantities will retain their current data types + # if possible. So this will still be a length measure. + ifcopenshell.api.run("pset.edit_qto", model, qto=qto, properties={"SomeLength": 12.3}) + """ + self.file = file - self.settings = {"qto": None, "name": None, "properties": {}} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"qto": qto, "name": name, "properties": properties or {}, "pset_template": pset_template} def execute(self): self.qto_idx = 5 @@ -43,9 +132,12 @@ class Usecase: self.settings["qto"].Name = self.settings["name"] def load_qto_template(self): - # TODO: add IFC2X3 PsetQto template support - self.psetqto = ifcopenshell.util.pset.get_template("IFC4") - self.qto_template = self.psetqto.get_by_name(self.settings["qto"].Name) + if self.settings["pset_template"]: + self.pset_template = self.settings["pset_template"] + else: + # TODO: add IFC2X3 PsetQto template support + self.psetqto = ifcopenshell.util.pset.get_template("IFC4") + self.qto_template = self.psetqto.get_by_name(self.settings["qto"].Name) def update_existing_properties(self): for prop in self.settings["qto"][self.qto_idx] or []: diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset/remove_pset.py b/src/ifcopenshell-python/ifcopenshell/api/pset/remove_pset.py index 6f2cdfec05..b90e9e2894 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset/remove_pset.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset/remove_pset.py @@ -18,11 +18,29 @@ class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, product=None, pset=None): + """Removes a property set from a product + + All properties that are part of this property set are also removed. + + :param product: The IfcObject to remove the property set from. + :type product: ifcopenshell.entity_instance.entity_instance + :param pset: The IfcPropertySet or IfcElementQuantity to remove. + :type pset: ifcopenshell.entity_instance.entity_instance + :return: None + :rtype: None + + Example:: + + # Let's imagine we have a new wall type with a property set. + wall_type = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWallType") + pset = ifcopenshell.api.run("pset.add_pset", model, product=wall_type, name="Pset_WallCommon") + + # Remove it! + ifcopenshell.api.run("pset.remove_pset", model, product=wall_type, pset=pset) + """ self.file = file - self.settings = {"product": None, "pset": None} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"product": product, "pset": pset} def execute(self): to_purge = []