diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/openpyxl/descriptors')
9 files changed, 1018 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/__init__.py new file mode 100644 index 00000000..df86a3c7 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/__init__.py @@ -0,0 +1,58 @@ +# Copyright (c) 2010-2024 openpyxl + +from .base import * +from .sequence import Sequence + + +class MetaStrict(type): + + def __new__(cls, clsname, bases, methods): + for k, v in methods.items(): + if isinstance(v, Descriptor): + v.name = k + return type.__new__(cls, clsname, bases, methods) + + +class Strict(metaclass=MetaStrict): + + pass + + +class MetaSerialisable(type): + + def __new__(cls, clsname, bases, methods): + attrs = [] + nested = [] + elements = [] + namespaced = [] + for k, v in methods.items(): + if isinstance(v, Descriptor): + ns= getattr(v, 'namespace', None) + if ns: + namespaced.append((k, "{%s}%s" % (ns, k))) + if getattr(v, 'nested', False): + nested.append(k) + elements.append(k) + elif isinstance(v, Sequence): + elements.append(k) + elif isinstance(v, Typed): + if hasattr(v.expected_type, 'to_tree'): + elements.append(k) + elif isinstance(v.expected_type, tuple): + if any((hasattr(el, "to_tree") for el in v.expected_type)): + # don't bind elements as attrs + continue + else: + attrs.append(k) + else: + if not isinstance(v, Alias): + attrs.append(k) + + if methods.get('__attrs__') is None: + methods['__attrs__'] = tuple(attrs) + methods['__namespaced__'] = tuple(namespaced) + if methods.get('__nested__') is None: + methods['__nested__'] = tuple(sorted(nested)) + if methods.get('__elements__') is None: + methods['__elements__'] = tuple(sorted(elements)) + return MetaStrict.__new__(cls, clsname, bases, methods) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/base.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/base.py new file mode 100644 index 00000000..f1e86ed3 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/base.py @@ -0,0 +1,272 @@ +# Copyright (c) 2010-2024 openpyxl + + +""" +Based on Python Cookbook 3rd Edition, 8.13 +http://chimera.labs.oreilly.com/books/1230000000393/ch08.html#_discussiuncion_130 +""" + +import datetime +import re + +from openpyxl import DEBUG +from openpyxl.utils.datetime import from_ISO8601 + +from .namespace import namespaced + +class Descriptor: + + def __init__(self, name=None, **kw): + self.name = name + for k, v in kw.items(): + setattr(self, k, v) + + def __set__(self, instance, value): + instance.__dict__[self.name] = value + + +class Typed(Descriptor): + """Values must of a particular type""" + + expected_type = type(None) + allow_none = False + nested = False + + def __init__(self, *args, **kw): + super().__init__(*args, **kw) + self.__doc__ = f"Values must be of type {self.expected_type}" + + def __set__(self, instance, value): + if not isinstance(value, self.expected_type): + if (not self.allow_none + or (self.allow_none and value is not None)): + msg = f"{instance.__class__}.{self.name} should be {self.expected_type} but value is {type(value)}" + if DEBUG: + msg = f"{instance.__class__}.{self.name} should be {self.expected_type} but {value} is {type(value)}" + raise TypeError(msg) + super().__set__(instance, value) + + def __repr__(self): + return self.__doc__ + + +def _convert(expected_type, value): + """ + Check value is of or can be converted to expected type. + """ + if not isinstance(value, expected_type): + try: + value = expected_type(value) + except: + raise TypeError('expected ' + str(expected_type)) + return value + + +class Convertible(Typed): + """Values must be convertible to a particular type""" + + def __set__(self, instance, value): + if ((self.allow_none and value is not None) + or not self.allow_none): + value = _convert(self.expected_type, value) + super().__set__(instance, value) + + +class Max(Convertible): + """Values must be less than a `max` value""" + + expected_type = float + allow_none = False + + def __init__(self, **kw): + if 'max' not in kw and not hasattr(self, 'max'): + raise TypeError('missing max value') + super().__init__(**kw) + + def __set__(self, instance, value): + if ((self.allow_none and value is not None) + or not self.allow_none): + value = _convert(self.expected_type, value) + if value > self.max: + raise ValueError('Max value is {0}'.format(self.max)) + super().__set__(instance, value) + + +class Min(Convertible): + """Values must be greater than a `min` value""" + + expected_type = float + allow_none = False + + def __init__(self, **kw): + if 'min' not in kw and not hasattr(self, 'min'): + raise TypeError('missing min value') + super().__init__(**kw) + + def __set__(self, instance, value): + if ((self.allow_none and value is not None) + or not self.allow_none): + value = _convert(self.expected_type, value) + if value < self.min: + raise ValueError('Min value is {0}'.format(self.min)) + super().__set__(instance, value) + + +class MinMax(Min, Max): + """Values must be greater than `min` value and less than a `max` one""" + pass + + +class Set(Descriptor): + """Value can only be from a set of know values""" + + def __init__(self, name=None, **kw): + if not 'values' in kw: + raise TypeError("missing set of values") + kw['values'] = set(kw['values']) + super().__init__(name, **kw) + self.__doc__ = "Value must be one of {0}".format(self.values) + + def __set__(self, instance, value): + if value not in self.values: + raise ValueError(self.__doc__) + super().__set__(instance, value) + + +class NoneSet(Set): + + """'none' will be treated as None""" + + def __init__(self, name=None, **kw): + super().__init__(name, **kw) + self.values.add(None) + + def __set__(self, instance, value): + if value == 'none': + value = None + super().__set__(instance, value) + + +class Integer(Convertible): + + expected_type = int + + +class Float(Convertible): + + expected_type = float + + +class Bool(Convertible): + + expected_type = bool + + def __set__(self, instance, value): + if isinstance(value, str): + if value in ('false', 'f', '0'): + value = False + super().__set__(instance, value) + + +class String(Typed): + + expected_type = str + + +class Text(String, Convertible): + + pass + + +class ASCII(Typed): + + expected_type = bytes + + +class Tuple(Typed): + + expected_type = tuple + + +class Length(Descriptor): + + def __init__(self, name=None, **kw): + if "length" not in kw: + raise TypeError("value length must be supplied") + super().__init__(**kw) + + + def __set__(self, instance, value): + if len(value) != self.length: + raise ValueError("Value must be length {0}".format(self.length)) + super().__set__(instance, value) + + +class Default(Typed): + """ + When called returns an instance of the expected type. + Additional default values can be passed in to the descriptor + """ + + def __init__(self, name=None, **kw): + if "defaults" not in kw: + kw['defaults'] = {} + super().__init__(**kw) + + def __call__(self): + return self.expected_type() + + +class Alias(Descriptor): + """ + Aliases can be used when either the desired attribute name is not allowed + or confusing in Python (eg. "type") or a more descriptive name is desired + (eg. "underline" for "u") + """ + + def __init__(self, alias): + self.alias = alias + + def __set__(self, instance, value): + setattr(instance, self.alias, value) + + def __get__(self, instance, cls): + return getattr(instance, self.alias) + + +class MatchPattern(Descriptor): + """Values must match a regex pattern """ + allow_none = False + + def __init__(self, name=None, **kw): + if 'pattern' not in kw and not hasattr(self, 'pattern'): + raise TypeError('missing pattern value') + + super().__init__(name, **kw) + self.test_pattern = re.compile(self.pattern, re.VERBOSE) + + + def __set__(self, instance, value): + + if value is None and not self.allow_none: + raise ValueError("Value must not be none") + + if ((self.allow_none and value is not None) + or not self.allow_none): + if not self.test_pattern.match(value): + raise ValueError('Value does not match pattern {0}'.format(self.pattern)) + + super().__set__(instance, value) + + +class DateTime(Typed): + + expected_type = datetime.datetime + + def __set__(self, instance, value): + if value is not None and isinstance(value, str): + try: + value = from_ISO8601(value) + except ValueError: + raise ValueError("Value must be ISO datetime format") + super().__set__(instance, value) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/container.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/container.py new file mode 100644 index 00000000..4b1839f5 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/container.py @@ -0,0 +1,41 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +Utility list for top level containers that contain one type of element + +Provides the necessary API to read and write XML +""" + +from openpyxl.xml.functions import Element + + +class ElementList(list): + + + @property + def tagname(self): + raise NotImplementedError + + + @property + def expected_type(self): + raise NotImplementedError + + + @classmethod + def from_tree(cls, tree): + l = [cls.expected_type.from_tree(el) for el in tree] + return cls(l) + + + def to_tree(self): + container = Element(self.tagname) + for el in self: + container.append(el.to_tree()) + return container + + + def append(self, value): + if not isinstance(value, self.expected_type): + raise TypeError(f"Value must of type {self.expected_type} {type(value)} provided") + super().append(value) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/excel.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/excel.py new file mode 100644 index 00000000..d8aa2028 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/excel.py @@ -0,0 +1,112 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +Excel specific descriptors +""" + +from openpyxl.xml.constants import REL_NS +from openpyxl.compat import safe_string +from openpyxl.xml.functions import Element + +from . import ( + MatchPattern, + MinMax, + Integer, + String, + Sequence, +) +from .serialisable import Serialisable + + +class HexBinary(MatchPattern): + + pattern = "[0-9a-fA-F]+$" + + +class UniversalMeasure(MatchPattern): + + pattern = r"[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)" + + +class TextPoint(MinMax): + """ + Size in hundredths of points. + In theory other units of measurement can be used but these are unbounded + """ + expected_type = int + + min = -400000 + max = 400000 + + +Coordinate = Integer + + +class Percentage(MinMax): + + pattern = r"((100)|([0-9][0-9]?))(\.[0-9][0-9]?)?%" # strict + min = -1000000 + max = 1000000 + + def __set__(self, instance, value): + if isinstance(value, str) and "%" in value: + value = value.replace("%", "") + value = int(float(value) * 1000) + super().__set__(instance, value) + + +class Extension(Serialisable): + + uri = String() + + def __init__(self, + uri=None, + ): + self.uri = uri + + +class ExtensionList(Serialisable): + + ext = Sequence(expected_type=Extension) + + def __init__(self, + ext=(), + ): + self.ext = ext + + +class Relation(String): + + namespace = REL_NS + allow_none = True + + +class Base64Binary(MatchPattern): + # http://www.w3.org/TR/xmlschema11-2/#nt-Base64Binary + pattern = "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$" + + +class Guid(MatchPattern): + # https://msdn.microsoft.com/en-us/library/dd946381(v=office.12).aspx + pattern = r"{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}" + + +class CellRange(MatchPattern): + + pattern = r"^[$]?([A-Za-z]{1,3})[$]?(\d+)(:[$]?([A-Za-z]{1,3})[$]?(\d+)?)?$|^[A-Za-z]{1,3}:[A-Za-z]{1,3}$" + allow_none = True + + def __set__(self, instance, value): + + if value is not None: + value = value.upper() + super().__set__(instance, value) + + +def _explicit_none(tagname, value, namespace=None): + """ + Override serialisation because explicit none required + """ + if namespace is not None: + tagname = "{%s}%s" % (namespace, tagname) + return Element(tagname, val=safe_string(value)) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/namespace.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/namespace.py new file mode 100644 index 00000000..93cc9e41 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/namespace.py @@ -0,0 +1,12 @@ +# Copyright (c) 2010-2024 openpyxl + + +def namespaced(obj, tagname, namespace=None): + """ + Utility to create a namespaced tag for an object + """ + + namespace = getattr(obj, "namespace", None) or namespace + if namespace is not None: + tagname = "{%s}%s" % (namespace, tagname) + return tagname diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/nested.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/nested.py new file mode 100644 index 00000000..bda63a2d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/nested.py @@ -0,0 +1,129 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +Generic serialisable classes +""" +from .base import ( + Convertible, + Bool, + Descriptor, + NoneSet, + MinMax, + Set, + Float, + Integer, + String, + ) +from openpyxl.compat import safe_string +from openpyxl.xml.functions import Element, localname, whitespace + + +class Nested(Descriptor): + + nested = True + attribute = "val" + + def __set__(self, instance, value): + if hasattr(value, "tag"): + tag = localname(value) + if tag != self.name: + raise ValueError("Tag does not match attribute") + + value = self.from_tree(value) + super().__set__(instance, value) + + + def from_tree(self, node): + return node.get(self.attribute) + + + def to_tree(self, tagname=None, value=None, namespace=None): + namespace = getattr(self, "namespace", namespace) + if value is not None: + if namespace is not None: + tagname = "{%s}%s" % (namespace, tagname) + value = safe_string(value) + return Element(tagname, {self.attribute:value}) + + +class NestedValue(Nested, Convertible): + """ + Nested tag storing the value on the 'val' attribute + """ + pass + + +class NestedText(NestedValue): + """ + Represents any nested tag with the value as the contents of the tag + """ + + + def from_tree(self, node): + return node.text + + + def to_tree(self, tagname=None, value=None, namespace=None): + namespace = getattr(self, "namespace", namespace) + if value is not None: + if namespace is not None: + tagname = "{%s}%s" % (namespace, tagname) + el = Element(tagname) + el.text = safe_string(value) + whitespace(el) + return el + + +class NestedFloat(NestedValue, Float): + + pass + + +class NestedInteger(NestedValue, Integer): + + pass + + +class NestedString(NestedValue, String): + + pass + + +class NestedBool(NestedValue, Bool): + + + def from_tree(self, node): + return node.get("val", True) + + +class NestedNoneSet(Nested, NoneSet): + + pass + + +class NestedSet(Nested, Set): + + pass + + +class NestedMinMax(Nested, MinMax): + + pass + + +class EmptyTag(Nested, Bool): + + """ + Boolean if a tag exists or not. + """ + + def from_tree(self, node): + return True + + + def to_tree(self, tagname=None, value=None, namespace=None): + if value: + namespace = getattr(self, "namespace", namespace) + if namespace is not None: + tagname = "{%s}%s" % (namespace, tagname) + return Element(tagname) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/sequence.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/sequence.py new file mode 100644 index 00000000..d77116b2 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/sequence.py @@ -0,0 +1,136 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.compat import safe_string +from openpyxl.xml.functions import Element +from openpyxl.utils.indexed_list import IndexedList + +from .base import Descriptor, Alias, _convert +from .namespace import namespaced + + +class Sequence(Descriptor): + """ + A sequence (list or tuple) that may only contain objects of the declared + type + """ + + expected_type = type(None) + seq_types = (list, tuple) + idx_base = 0 + unique = False + container = list + + + def __set__(self, instance, seq): + if not isinstance(seq, self.seq_types): + raise TypeError("Value must be a sequence") + seq = self.container(_convert(self.expected_type, value) for value in seq) + if self.unique: + seq = IndexedList(seq) + + super().__set__(instance, seq) + + + def to_tree(self, tagname, obj, namespace=None): + """ + Convert the sequence represented by the descriptor to an XML element + """ + for idx, v in enumerate(obj, self.idx_base): + if hasattr(v, "to_tree"): + el = v.to_tree(tagname, idx) + else: + tagname = namespaced(obj, tagname, namespace) + el = Element(tagname) + el.text = safe_string(v) + yield el + + +class UniqueSequence(Sequence): + """ + Use a set to keep values unique + """ + seq_types = (list, tuple, set) + container = set + + +class ValueSequence(Sequence): + """ + A sequence of primitive types that are stored as a single attribute. + "val" is the default attribute + """ + + attribute = "val" + + + def to_tree(self, tagname, obj, namespace=None): + tagname = namespaced(self, tagname, namespace) + for v in obj: + yield Element(tagname, {self.attribute:safe_string(v)}) + + + def from_tree(self, node): + + return node.get(self.attribute) + + +class NestedSequence(Sequence): + """ + Wrap a sequence in an containing object + """ + + count = False + + def to_tree(self, tagname, obj, namespace=None): + tagname = namespaced(self, tagname, namespace) + container = Element(tagname) + if self.count: + container.set('count', str(len(obj))) + for v in obj: + container.append(v.to_tree()) + return container + + + def from_tree(self, node): + return [self.expected_type.from_tree(el) for el in node] + + +class MultiSequence(Sequence): + """ + Sequences can contain objects with different tags + """ + + def __set__(self, instance, seq): + if not isinstance(seq, (tuple, list)): + raise ValueError("Value must be a sequence") + seq = list(seq) + Descriptor.__set__(self, instance, seq) + + + def to_tree(self, tagname, obj, namespace=None): + """ + Convert the sequence represented by the descriptor to an XML element + """ + for v in obj: + el = v.to_tree(namespace=namespace) + yield el + + +class MultiSequencePart(Alias): + """ + Allow a multisequence to be built up from parts + + Excluded from the instance __elements__ or __attrs__ as is effectively an Alias + """ + + def __init__(self, expected_type, store): + self.expected_type = expected_type + self.store = store + + + def __set__(self, instance, value): + value = _convert(self.expected_type, value) + instance.__dict__[self.store].append(value) + + + def __get__(self, instance, cls): + return self diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/serialisable.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/serialisable.py new file mode 100644 index 00000000..1bc9ef0d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/serialisable.py @@ -0,0 +1,240 @@ +# Copyright (c) 2010-2024 openpyxl + +from copy import copy +from keyword import kwlist +KEYWORDS = frozenset(kwlist) + +from . import Descriptor +from . import MetaSerialisable +from .sequence import ( + Sequence, + NestedSequence, + MultiSequencePart, +) +from .namespace import namespaced + +from openpyxl.compat import safe_string +from openpyxl.xml.functions import ( + Element, + localname, +) + +seq_types = (list, tuple) + +class Serialisable(metaclass=MetaSerialisable): + """ + Objects can serialise to XML their attributes and child objects. + The following class attributes are created by the metaclass at runtime: + __attrs__ = attributes + __nested__ = single-valued child treated as an attribute + __elements__ = child elements + """ + + __attrs__ = None + __nested__ = None + __elements__ = None + __namespaced__ = None + + idx_base = 0 + + @property + def tagname(self): + raise(NotImplementedError) + + namespace = None + + @classmethod + def from_tree(cls, node): + """ + Create object from XML + """ + # strip known namespaces from attributes + attrib = dict(node.attrib) + for key, ns in cls.__namespaced__: + if ns in attrib: + attrib[key] = attrib[ns] + del attrib[ns] + + # strip attributes with unknown namespaces + for key in list(attrib): + if key.startswith('{'): + del attrib[key] + elif key in KEYWORDS: + attrib["_" + key] = attrib[key] + del attrib[key] + elif "-" in key: + n = key.replace("-", "_") + attrib[n] = attrib[key] + del attrib[key] + + if node.text and "attr_text" in cls.__attrs__: + attrib["attr_text"] = node.text + + for el in node: + tag = localname(el) + if tag in KEYWORDS: + tag = "_" + tag + desc = getattr(cls, tag, None) + if desc is None or isinstance(desc, property): + continue + + if hasattr(desc, 'from_tree'): + #descriptor manages conversion + obj = desc.from_tree(el) + else: + if hasattr(desc.expected_type, "from_tree"): + #complex type + obj = desc.expected_type.from_tree(el) + else: + #primitive + obj = el.text + + if isinstance(desc, NestedSequence): + attrib[tag] = obj + elif isinstance(desc, Sequence): + attrib.setdefault(tag, []) + attrib[tag].append(obj) + elif isinstance(desc, MultiSequencePart): + attrib.setdefault(desc.store, []) + attrib[desc.store].append(obj) + else: + attrib[tag] = obj + + return cls(**attrib) + + + def to_tree(self, tagname=None, idx=None, namespace=None): + + if tagname is None: + tagname = self.tagname + + # keywords have to be masked + if tagname.startswith("_"): + tagname = tagname[1:] + + tagname = namespaced(self, tagname, namespace) + namespace = getattr(self, "namespace", namespace) + + attrs = dict(self) + for key, ns in self.__namespaced__: + if key in attrs: + attrs[ns] = attrs[key] + del attrs[key] + + el = Element(tagname, attrs) + if "attr_text" in self.__attrs__: + el.text = safe_string(getattr(self, "attr_text")) + + for child_tag in self.__elements__: + desc = getattr(self.__class__, child_tag, None) + obj = getattr(self, child_tag) + if hasattr(desc, "namespace") and hasattr(obj, 'namespace'): + obj.namespace = desc.namespace + + if isinstance(obj, seq_types): + if isinstance(desc, NestedSequence): + # wrap sequence in container + if not obj: + continue + nodes = [desc.to_tree(child_tag, obj, namespace)] + elif isinstance(desc, Sequence): + # sequence + desc.idx_base = self.idx_base + nodes = (desc.to_tree(child_tag, obj, namespace)) + else: # property + nodes = (v.to_tree(child_tag, namespace) for v in obj) + for node in nodes: + el.append(node) + else: + if child_tag in self.__nested__: + node = desc.to_tree(child_tag, obj, namespace) + elif obj is None: + continue + else: + node = obj.to_tree(child_tag) + if node is not None: + el.append(node) + return el + + + def __iter__(self): + for attr in self.__attrs__: + value = getattr(self, attr) + if attr.startswith("_"): + attr = attr[1:] + elif attr != "attr_text" and "_" in attr: + desc = getattr(self.__class__, attr) + if getattr(desc, "hyphenated", False): + attr = attr.replace("_", "-") + if attr != "attr_text" and value is not None: + yield attr, safe_string(value) + + + def __eq__(self, other): + if not self.__class__ == other.__class__: + return False + elif not dict(self) == dict(other): + return False + for el in self.__elements__: + if getattr(self, el) != getattr(other, el): + return False + return True + + + def __ne__(self, other): + return not self == other + + + def __repr__(self): + s = u"<{0}.{1} object>\nParameters:".format( + self.__module__, + self.__class__.__name__ + ) + args = [] + for k in self.__attrs__ + self.__elements__: + v = getattr(self, k) + if isinstance(v, Descriptor): + v = None + args.append(u"{0}={1}".format(k, repr(v))) + args = u", ".join(args) + + return u"\n".join([s, args]) + + + def __hash__(self): + fields = [] + for attr in self.__attrs__ + self.__elements__: + val = getattr(self, attr) + if isinstance(val, list): + val = tuple(val) + fields.append(val) + + return hash(tuple(fields)) + + + def __add__(self, other): + if type(self) != type(other): + raise TypeError("Cannot combine instances of different types") + vals = {} + for attr in self.__attrs__: + vals[attr] = getattr(self, attr) or getattr(other, attr) + for el in self.__elements__: + a = getattr(self, el) + b = getattr(other, el) + if a and b: + vals[el] = a + b + else: + vals[el] = a or b + return self.__class__(**vals) + + + def __copy__(self): + # serialise to xml and back to avoid shallow copies + xml = self.to_tree(tagname="dummy") + cp = self.__class__.from_tree(xml) + # copy any non-persisted attributed + for k in self.__dict__: + if k not in self.__attrs__ + self.__elements__: + v = copy(getattr(self, k)) + setattr(cp, k, v) + return cp diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/slots.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/slots.py new file mode 100644 index 00000000..cadc1ef3 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/slots.py @@ -0,0 +1,18 @@ +# Metaclass for mixing slots and descriptors +# From "Programming in Python 3" by Mark Summerfield Ch.8 p. 383 + +class AutoSlotProperties(type): + + def __new__(mcl, classname, bases, dictionary): + slots = list(dictionary.get("__slots__", [])) + for getter_name in [key for key in dictionary if key.startswith("get_")]: + name = getter_name + slots.append("__" + name) + getter = dictionary.pop(getter_name) + setter = dictionary.get(setter_name, None) + if (setter is not None + and isinstance(setter, collections.Callable)): + del dictionary[setter_name] + dictionary[name] = property(getter. setter) + dictionary["__slots__"] = tuple(slots) + return super().__new__(mcl, classname, bases, dictionary) |