From 5b8733331a1690cf5129e3d7e46271308374dc71 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Wed, 3 Aug 2022 12:30:53 +0200 Subject: [PATCH] Declare internal attributes as internal and prevent adding new attributes --- drafthorse/models/container.py | 10 +- drafthorse/models/elements.py | 202 +++++++++++++++++---------------- drafthorse/models/fields.py | 38 +++---- 3 files changed, 127 insertions(+), 123 deletions(-) diff --git a/drafthorse/models/container.py b/drafthorse/models/container.py index 857da8d..8219378 100644 --- a/drafthorse/models/container.py +++ b/drafthorse/models/container.py @@ -57,8 +57,8 @@ class CurrencyContainer(SimpleContainer): return CurrencyElement(namespace=self.namespace, tag=self.tag) def set_element(self, el, child): - el.amount = child[0] - el.currency = child[1] + el._amount = child[0] + el._currency = child[1] def add_from_etree(self, root): if root.attrib.get("currencyID"): @@ -74,8 +74,8 @@ class IDContainer(SimpleContainer): return IDElement(namespace=self.namespace, tag=self.tag) def set_element(self, el, child): - el.text = child[1] - el.scheme_id = child[0] + el._text = child[1] + el._scheme_id = child[0] def add_from_etree(self, root): self.add((root.attrib["schemeID"], root.text)) @@ -88,7 +88,7 @@ class StringContainer(SimpleContainer): return StringElement(namespace=self.namespace, tag=self.tag) def set_element(self, el, child): - el.text = child + el._text = child def add_from_etree(self, root): self.add(root.text) diff --git a/drafthorse/models/elements.py b/drafthorse/models/elements.py index f6c0c47..3d0062c 100644 --- a/drafthorse/models/elements.py +++ b/drafthorse/models/elements.py @@ -70,6 +70,11 @@ class Element(metaclass=BaseElementMeta): self.to_etree(), "utf-8" ) return validate_xml(xmlout=xml, schema=schema) + + def __setattr__(self, key, value): + if not hasattr(self, key) and not key.startswith("_") and not key in ("required",): + raise AttributeError(f"Element {type(self)} has no attribute '{key}'. If you set it, it would not be included in the output.") + return super().__setattr__(key, value) def from_etree(self, root): if ( @@ -110,10 +115,10 @@ class Element(metaclass=BaseElementMeta): class StringElement(Element): def __init__(self, namespace, tag, text=""): super().__init__() - self.namespace = namespace - self.tag = tag - self.text = text - self.set_on_input = False + self._namespace = namespace + self._tag = tag + self._text = text + self._set_on_input = False def __repr__(self): return "<{}: {}>".format(type(self).__name__, str(self)) @@ -122,82 +127,82 @@ class StringElement(Element): return str(self.text) def is_empty(self, el): - return super().is_empty(el) and not self.set_on_input + return super().is_empty(el) and not self._set_on_input def get_tag(self): - return "{%s}%s" % (self.namespace, self.tag) + return "{%s}%s" % (self._namespace, self._tag) def to_etree(self): node = self._etree_node() - node.text = self.text + node.text = self._text return node def from_etree(self, root): - self.text = root.text - self.set_on_input = True + self._text = root.text + self._set_on_input = True return self class DecimalElement(StringElement): def __init__(self, namespace, tag, value=None): super().__init__(namespace, tag) - self.value = value + self._value = value def to_etree(self): node = self._etree_node() - node.text = str(self.value) if self.value is not None else "" + node.text = str(self._value) if self._value is not None else "" return node def __str__(self): return self.value def from_etree(self, root): - self.value = Decimal(root.text) - self.set_on_input = True + self._value = Decimal(root.text) + self._set_on_input = True return self class QuantityElement(StringElement): def __init__(self, namespace, tag, amount="", unit_code=""): super().__init__(namespace, tag) - self.amount = amount - self.unit_code = unit_code + self._amount = amount + self._unit_code = unit_code def to_etree(self): node = self._etree_node() - node.text = str(self.amount) - node.attrib["unitCode"] = self.unit_code + node.text = str(self._amount) + node.attrib["unitCode"] = self._unit_code return node def __str__(self): - return "{} {}".format(self.amount, self.unit_code) + return "{} {}".format(self._amount, self._unit_code) def from_etree(self, root): - self.amount = Decimal(root.text) - self.unit_code = root.attrib["unitCode"] - self.set_on_input = True + self._amount = Decimal(root.text) + self._unit_code = root.attrib["unitCode"] + self._set_on_input = True return self class CurrencyElement(StringElement): def __init__(self, namespace, tag, amount="", currency=None): super().__init__(namespace, tag) - self.amount = amount - self.currency = currency + self._amount = amount + self._currency = currency def to_etree(self): node = self._etree_node() - node.text = str(self.amount) - if self.currency is not None: - node.attrib["currencyID"] = self.currency + node.text = str(self._amount) + if self._currency is not None: + node.attrib["currencyID"] = self._currency elif "currencyID" in node.attrib: del node.attrib["currencyID"] return node def from_etree(self, root): - self.amount = Decimal(root.text) - self.currency = root.attrib.get("currencyID") or None - self.set_on_input = True + self._amount = Decimal(root.text) + self._currency = root.attrib.get("currencyID") or None + self._set_on_input = True return self def __str__(self): @@ -207,121 +212,121 @@ class CurrencyElement(StringElement): class ClassificationElement(StringElement): def __init__(self, namespace, tag, text="", list_id="", list_version_id=""): super().__init__(namespace, tag) - self.text = text - self.list_id = list_id - self.list_version_id = list_version_id + self._text = text + self._list_id = list_id + self._list_version_id = list_version_id def to_etree(self): node = self._etree_node() node.text = self.text - node.attrib["listID"] = self.list_id - node.attrib["listVersionID"] = self.list_version_id + node.attrib["listID"] = self._list_id + node.attrib["listVersionID"] = self._list_version_id return node def from_etree(self, root): - self.text = Decimal(root.text) - self.list_id = root.attrib["listID"] - self.list_version_id = root.attrib["listVersionID"] - self.set_on_input = True + self._text = Decimal(root.text) + self._list_id = root.attrib["listID"] + self._list_version_id = root.attrib["listVersionID"] + self._set_on_input = True return self def __str__(self): - return "{} ({} {})".format(self.text, self.list_id, self.list_version_id) + return "{} ({} {})".format(self._text, self._list_id, self._list_version_id) class BinaryObjectElement(StringElement): def __init__(self, namespace, tag, text="", filename="", mime_code=""): super().__init__(namespace, tag) - self.mime_code = mime_code - self.filename = filename - self.text = text + self._mime_code = mime_code + self._filename = filename + self._text = text def to_etree(self): node = self._etree_node() - node.attrib["mimeCode"] = self.mime_code - node.attrib["filename"] = self.filename - node.text = self.text + node.attrib["mimeCode"] = self._mime_code + node.attrib["filename"] = self._filename + node.text = self._text return node def from_etree(self, root): - self.mime_code = root.attrib["mimeCode"] - self.filename = root.attrib["filename"] - self.text = root.text - self.set_on_input = True + self._mime_code = root.attrib["mimeCode"] + self._filename = root.attrib["filename"] + self._text = root.text + self._set_on_input = True return self def __str__(self): - return "{} ({} {})".format(self.text, self.mime_code) + return "{} ({} {})".format(self._text, self._mime_code) class AgencyIDElement(StringElement): def __init__(self, namespace, tag, text="", scheme_id=""): super().__init__(namespace, tag) - self.text = text - self.scheme_id = scheme_id + self._text = text + self._scheme_id = scheme_id def to_etree(self): node = self._etree_node() - node.text = self.text - node.attrib["schemeAgencyID"] = self.scheme_id + node.text = self._text + node.attrib["schemeAgencyID"] = self._scheme_id return node def from_etree(self, root): - self.text = root.text - self.scheme_id = root.attrib["schemeAgencyID"] - self.set_on_input = True + self._text = root.text + self._scheme_id = root.attrib["schemeAgencyID"] + self._set_on_input = True return self def __str__(self): - return "{} ({})".format(self.text, self.scheme_id) + return "{} ({})".format(self._text, self._scheme_id) class IDElement(StringElement): def __init__(self, namespace, tag, text="", scheme_id=""): super().__init__(namespace, tag) - self.text = text - self.scheme_id = scheme_id + self._text = text + self._scheme_id = scheme_id def to_etree(self): node = self._etree_node() - node.text = self.text - node.attrib["schemeID"] = self.scheme_id + node.text = self._text + node.attrib["schemeID"] = self._scheme_id return node def from_etree(self, root): - self.text = root.text + self._text = root.text try: - self.scheme_id = root.attrib["schemeID"] + self._scheme_id = root.attrib["schemeID"] except: root.attrib["schemeID"] = "" - self.scheme_id = root.attrib["schemeID"] - self.set_on_input = True + self._scheme_id = root.attrib["schemeID"] + self._set_on_input = True return self def __str__(self): - return "{} ({})".format(self.text, self.scheme_id) + return "{} ({})".format(self._text, self._scheme_id) class DateTimeElement(StringElement): def __init__(self, namespace, tag, value=None, format="102"): super().__init__(namespace, tag) - self.value = value - self.format = format + self._value = value + self._format = format def to_etree(self): t = self._etree_node() node = ET.Element("{%s}%s" % (NS_UDT, "DateTimeString")) - if self.value: - if self.format == "102": - node.text = self.value.strftime("%Y%m%d") - elif self.format == "616": + if self._value: + if self._format == "102": + node.text = self._value.strftime("%Y%m%d") + elif self._format == "616": if sys.version_info < (3, 6): node.text = "{}{}".format( - self.value.isocalendar()[0], self.value.isocalendar()[1] + self._value.isocalendar()[0], self._value.isocalendar()[1] ) else: - node.text = self.value.strftime("%G%V") - node.attrib["format"] = self.format + node.text = self._value.strftime("%G%V") + node.attrib["format"] = self._format t.append(node) return t @@ -330,77 +335,76 @@ class DateTimeElement(StringElement): raise TypeError("Date containers should have one child") if root[0].tag != "{%s}%s" % (NS_UDT, "DateTimeString"): raise TypeError("Tag %s not recognized" % root[0].tag) - self.format = root[0].attrib["format"] - if self.format == "102": - self.value = datetime.strptime(root[0].text, "%Y%m%d").date() - elif self.format == "616": + self._format = root[0].attrib["format"] + if self._format == "102": + self._value = datetime.strptime(root[0].text, "%Y%m%d").date() + elif self._format == "616": if sys.version_info < (3, 6): from isoweek import Week w = Week(int(root[0].text[:4]), int(root[0].text[4:])) - self.value = w.monday() + self._value = w.monday() else: - self.value = datetime.strptime(root[0].text + "1", "%G%V%u").date() + self._value = datetime.strptime(root[0].text + "1", "%G%V%u").date() else: raise TypeError( "Date format %s cannot be parsed" % root[0].attrib["format"] ) - self.set_on_input = True + self._set_on_input = True return self def __str__(self): - return "{}".format(self.value) + return "{}".format(self._value) class DirectDateTimeElement(StringElement): def __init__(self, namespace, tag, value=None): super().__init__(namespace, tag) - self.value = value - self.format = format + self._value = value def to_etree(self): t = self._etree_node() - if self.value: - t.text = self.value.strftime("%Y-%m-%dT%H:%M:%S") + if self._value: + t.text = self._value.strftime("%Y-%m-%dT%H:%M:%S") return t def from_etree(self, root): try: - self.value = datetime.strptime(root.text, "%Y-%m-%dT%H:%M:%S").date() + self._value = datetime.strptime(root.text, "%Y-%m-%dT%H:%M:%S").date() except: - self.value = "" - self.set_on_input = True + self._value = "" + self._set_on_input = True return self def __str__(self): - return "{}".format(self.value) + return "{}".format(self._value) class IndicatorElement(StringElement): def __init__(self, namespace, tag, value=None): super().__init__(namespace, tag) - self.value = value + self._value = value def get_tag(self): - return "{%s}%s" % (self.namespace, self.tag) + return "{%s}%s" % (self._namespace, self._tag) def to_etree(self): t = self._etree_node() - if self.value is None: + if self._value is None: return t node = ET.Element("{%s}%s" % (NS_UDT, "Indicator")) - node.text = str(self.value).lower() + node.text = str(self._value).lower() t.append(node) return t def __str__(self): - return "{}".format(self.value) + return "{}".format(self._value) def from_etree(self, root): if len(root) != 1: raise TypeError("Indicator containers should have one child") if root[0].tag != "{%s}%s" % (NS_UDT, "Indicator"): raise TypeError("Tag %s not recognized" % root[0].tag) - self.value = root[0].text == "true" - self.set_on_input = True + self._value = root[0].text == "true" + self._set_on_input = True return self diff --git a/drafthorse/models/fields.py b/drafthorse/models/fields.py index e4b4980..bf238d7 100644 --- a/drafthorse/models/fields.py +++ b/drafthorse/models/fields.py @@ -47,7 +47,7 @@ class StringField(Field): def __set__(self, instance, value): if instance._data.get(self.name, None) is None: instance._data[self.name] = self.initialize() - instance._data[self.name].text = value + instance._data[self.name]._text = value class AgencyIDField(Field): @@ -69,8 +69,8 @@ class AgencyIDField(Field): if not isinstance(value, (tuple, list)): raise TypeError("Please pass a 2-tuple of scheme agency ID and ID.") - instance._data[self.name].text = value[1] - instance._data[self.name].scheme_id = value[0] + instance._data[self.name]._text = value[1] + instance._data[self.name]._scheme_id = value[0] class ClassificationField(Field): @@ -92,9 +92,9 @@ class ClassificationField(Field): if not isinstance(value, (tuple, list)): raise TypeError("Please pass a 3-tuple of list ID, list version and ID.") - instance._data[self.name].text = value[2] - instance._data[self.name].list_id = value[0] - instance._data[self.name].list_version_id = value[1] + instance._data[self.name]._text = value[2] + instance._data[self.name]._list_id = value[0] + instance._data[self.name]._list_version_id = value[1] class IDField(Field): @@ -116,8 +116,8 @@ class IDField(Field): if not isinstance(value, (tuple, list)): raise TypeError("Please pass a 2-tuple of including scheme ID and ID.") - instance._data[self.name].text = value[1] - instance._data[self.name].scheme_id = value[0] + instance._data[self.name]._text = value[1] + instance._data[self.name]._scheme_id = value[0] class CurrencyField(Field): @@ -139,8 +139,8 @@ class CurrencyField(Field): if not isinstance(value, (tuple, list)): raise TypeError("Please pass a 2-tuple of including amount and currency.") - instance._data[self.name].amount = value[0] - instance._data[self.name].currency = value[1] + instance._data[self.name]._amount = value[0] + instance._data[self.name]._currency = value[1] def initialize(self): return self.cls(self.namespace, self.tag) @@ -159,7 +159,7 @@ class DecimalField(Field): def __set__(self, instance, value): if instance._data.get(self.name, None) is None: instance._data[self.name] = self.initialize() - instance._data[self.name].value = value + instance._data[self.name]._value = value def initialize(self): return self.cls(self.namespace, self.tag) @@ -181,8 +181,8 @@ class QuantityField(Field): if not isinstance(value, (tuple, list)): raise TypeError("Please pass a 2-tuple of including amount and unit code.") - instance._data[self.name].amount = value[0] - instance._data[self.name].unit_code = value[1] + instance._data[self.name]._amount = value[0] + instance._data[self.name]._unit_code = value[1] def initialize(self): return self.cls(self.namespace, self.tag) @@ -204,9 +204,9 @@ class BinaryObjectField(Field): if not isinstance(value, (tuple, list)): raise TypeError("Please pass a 2-tuple of including amount and unit code.") - instance._data[self.name].text = value[2] - instance._data[self.name].mime_code = value[0] - instance._data[self.name].filename = value[1] + instance._data[self.name]._text = value[2] + instance._data[self.name]._mime_code = value[0] + instance._data[self.name]._filename = value[1] def initialize(self): return self.cls(self.namespace, self.tag) @@ -225,7 +225,7 @@ class IndicatorField(Field): def __set__(self, instance, value): if instance._data.get(self.name, None) is None: instance._data[self.name] = self.initialize() - instance._data[self.name].value = value + instance._data[self.name]._value = value def initialize(self): return self.cls(self.namespace, self.tag) @@ -244,7 +244,7 @@ class DateTimeField(Field): def __set__(self, instance, value): if instance._data.get(self.name, None) is None: instance._data[self.name] = self.initialize() - instance._data[self.name].value = value + instance._data[self.name]._value = value def initialize(self): return self.cls(self.namespace, self.tag) @@ -263,7 +263,7 @@ class DirectDateTimeField(Field): def __set__(self, instance, value): if instance._data.get(self.name, None) is None: instance._data[self.name] = self.initialize() - instance._data[self.name].value = value + instance._data[self.name]._value = value def initialize(self): return self.cls(self.namespace, self.tag) -- 2.47.3