Skip to content

Commit

Permalink
Deprecates Attribute & NodeBase._namespace
Browse files Browse the repository at this point in the history
  • Loading branch information
funkyfuture committed Jan 14, 2025
1 parent 27fb08a commit f370278
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 127 deletions.
149 changes: 29 additions & 120 deletions _delb/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,9 +338,7 @@ def new_tag_node(
children: Iterable[NodeSource] = (),
) -> TagNode:
"""
Creates a new :class:`TagNode` instance outside any context. It is preferable to
use the method ``new_tag_node`` on instances of documents and nodes where the
namespace is inherited.
Creates a new :class:`TagNode` instance.
:param local_name: The tag name.
:param attributes: Optional attributes that are assigned to the new node.
Expand Down Expand Up @@ -601,7 +599,7 @@ def _set_new_key(self, namespace: str, name: str):
return

attributes = self._attributes
current = self._namespace, self.local_name
current = self.namespace, self.local_name
assert attributes is not None
attributes[(namespace, name)] = self.value
self._qualified_name = (namespace, name)
Expand All @@ -615,34 +613,21 @@ def local_name(self) -> str:

@local_name.setter
def local_name(self, name: str):
self._set_new_key(self._namespace, name)
self._set_new_key(self.namespace, name)

@property
def namespace(self) -> Optional[str]:
def namespace(self) -> str:
"""The attribute's namespace"""
if result := self._qualified_name[0] or None:
return result
else: # pragma: nocover
warnings.warn(
"With delb 0.6 this will return an empty string to represent an "
"empty/null namespace. You can use the the intermediate "
"`Attribute._namespace` with that future behaviour.",
category=DeprecationWarning,
)
return None
return self._qualified_name[0]

@namespace.setter
def namespace(self, namespace: Optional[str]):
if namespace is None:
warnings.warn(
"With delb 0.6 empty/null namespaces will have to be expressed as "
"empty strings"
)
self._set_new_key(namespace or "", self.local_name)
def namespace(self, namespace: str):
self._set_new_key(namespace, self.local_name)

@property
def _namespace(self) -> str:
return self._qualified_name[0]
warnings.warn("Use Attribute.namespace instead!", category=DeprecationWarning)
return self.namespace

@property
def universal_name(self) -> str:
Expand All @@ -651,7 +636,7 @@ def universal_name(self) -> str:
.. _Clark notation: http://www.jclark.com/xml/xmlns.htm
"""
if namespace := self._namespace:
if namespace := self.namespace:
return f"{{{namespace}}}{self.local_name}"
else:
return self.local_name
Expand Down Expand Up @@ -726,7 +711,7 @@ def __eq__(self, other: Any) -> bool:
# TODO optimize with native data model
for key, attribute in self.items():
assert isinstance(attribute, Attribute)
other_value = other.get((attribute._namespace, attribute.local_name))
other_value = other.get((attribute.namespace, attribute.local_name))
if (other_value is None) or (attribute != other_value):
return False
else:
Expand Down Expand Up @@ -796,7 +781,7 @@ def __resolve_accessor(self, item: AttributeAccessor) -> QualifiedName:
raise TypeError(ATTRIBUTE_ACCESSOR_MSG)

if namespace is None:
namespace = self._node._namespace
namespace = self._node.namespace

return namespace, name

Expand Down Expand Up @@ -1196,30 +1181,6 @@ def last_descendant(self) -> Optional[NodeBase]:
"""
pass

@abstractmethod
def new_tag_node(
self,
local_name: str,
attributes: Optional[dict[AttributeAccessor, str]] = None,
namespace: Optional[str] = None,
children: Sequence[NodeSource] = (),
) -> TagNode:
"""
Creates a new :class:`TagNode` instance in the node's context.
:param local_name: The tag name.
:param attributes: Optional attributes that are assigned to the new node.
:param namespace: An optional tag namespace. If none is provided, the context
node's namespace is inherited.
:param children: An optional sequence of objects that will be appended as child
nodes. This can be existing nodes, strings that will be
inserted as text nodes and in-place definitions of
:class:`TagNode` instances from :func:`tag`. The latter will be
assigned to the same namespace.
:return: The newly created tag node.
"""
pass

def _new_tag_node_from(
self,
context: _Element,
Expand Down Expand Up @@ -1435,34 +1396,6 @@ def iterate_descendants(self, *filter: Filter) -> Iterator[NodeBase]:
"""
yield from ()

def new_tag_node(
self,
local_name: str,
attributes: Optional[dict[AttributeAccessor, str]] = None,
namespace: Optional[str] = None,
children: Sequence[str | NodeBase | _TagDefinition] = (),
) -> TagNode:
warnings.warn(
"The node methods `new_tag_node` will be removed. Use :func:`new_tag_node`"
"instead.",
category=DeprecationWarning,
)
parent = self.parent
if parent is None:
return new_tag_node(
local_name=local_name,
attributes=attributes,
namespace=namespace,
children=children,
)
else:
return parent.new_tag_node(
local_name=local_name,
attributes=attributes,
namespace=namespace,
children=children,
)


class _ElementWrappingNode(NodeBase):
__slots__ = ("_etree_obj", "__namespaces", "_tail_node")
Expand Down Expand Up @@ -1984,7 +1917,7 @@ def clone(self, deep: bool = False) -> TagNode:
return new_tag_node(
local_name=self.local_name,
attributes=self.attributes,
namespace=self._namespace,
namespace=self.namespace,
children=(
[n.clone(deep=True) for n in self.iterate_children()] if deep else ()
),
Expand Down Expand Up @@ -2146,7 +2079,7 @@ def fetch_or_create_by_xpath(

return self._create_by_xpath(
ast=ast,
namespaces=Namespaces(namespaces or Namespaces({"": self._namespace})),
namespaces=Namespaces(namespaces or Namespaces({"": self.namespace})),
)

def _create_by_xpath(
Expand Down Expand Up @@ -2368,7 +2301,7 @@ def local_name(self) -> str:

@local_name.setter
def local_name(self, value: str):
self._etree_obj.tag = QName(self._namespace or None, value).text
self._etree_obj.tag = QName(self.namespace or None, value).text

@property
def location_path(self) -> str:
Expand Down Expand Up @@ -2407,53 +2340,29 @@ def merge_text_nodes(self):
node.detach()

@property
def namespace(self) -> Optional[str]:
def namespace(self) -> str:
"""
The node's namespace.
:meta category: Node properties
"""
if result := QName(self._etree_obj.tag).namespace:
return result
else: # pragma: nocover
warnings.warn(
"With the delb 0.6 this will return an empty string to represent an "
"empty/null namespace. You can use the the intermediate "
"`TagNode._namespace` with that future behaviour.",
category=DeprecationWarning,
)
return result
return QName(self._etree_obj.tag).namespace or ""

@namespace.setter
def namespace(self, value: Optional[str]):
def namespace(self, value: str):
self._etree_obj.tag = QName(value or None, self.local_name).text

@property
def _namespace(self) -> str:
return QName(self._etree_obj.tag).namespace or ""

def new_tag_node(
self,
local_name: str,
attributes: Optional[dict[AttributeAccessor, str]] = None,
namespace: Optional[str] = None,
children: Sequence[str | NodeBase | _TagDefinition] = (),
) -> TagNode:
warnings.warn(
"The node methods `new_tag_node` will be removed. Use :func:`new_tag_node`"
"instead.",
category=DeprecationWarning,
)
return self._new_tag_node_from(
self._etree_obj, local_name, attributes, namespace, children
)
warnings.warn("Use TagNode.namespace instead!")
return self.namespace

def _new_tag_node_from_definition(self, definition: _TagDefinition) -> TagNode:
return self._new_tag_node_from(
context=self._etree_obj,
local_name=definition.local_name,
attributes=definition.attributes,
namespace=self._namespace,
namespace=self.namespace,
children=definition.children,
)

Expand Down Expand Up @@ -3212,13 +3121,13 @@ def __init__(
self.writer = writer

def _collect_prefixes(self, root: TagNode):
if root._namespace not in self._namespaces.values():
self._prefixes[root._namespace] = ""
if root.namespace not in self._namespaces.values():
self._prefixes[root.namespace] = ""

for node in traverse_bf_ltr_ttb(root, is_tag_node):
assert isinstance(node, TagNode)
for namespace in {node._namespace} | {
a._namespace for a in node.attributes.values()
for namespace in {node.namespace} | {
a.namespace for a in node.attributes.values()
}:
if namespace in self._prefixes:
continue
Expand Down Expand Up @@ -3267,7 +3176,7 @@ def _generate_attributes_data(self, node: TagNode) -> dict[str, str]:
data = {}
for attribute in (node.attributes[a] for a in sorted(node.attributes)):
assert isinstance(attribute, Attribute)
data[self._prefixes[attribute._namespace] + attribute.local_name] = (
data[self._prefixes[attribute.namespace] + attribute.local_name] = (
f'"{attribute.value.translate(CCE_TABLE_FOR_ATTRIBUTES)}"'
)
return data
Expand Down Expand Up @@ -3325,7 +3234,7 @@ def _serialize_tag(
attributes_data: dict[str, str],
):
child_nodes = tuple(node.iterate_children())
prefixed_name = self._prefixes[node._namespace] + node.local_name
prefixed_name = self._prefixes[node.namespace] + node.local_name

self.writer(f"<{prefixed_name}")
if attributes_data:
Expand Down Expand Up @@ -3598,7 +3507,7 @@ def _required_space(self, node: NodeBase, up_to: int) -> None | int:

assert isinstance(node, TagNode)

name_length = len(node.local_name) + len(self._prefixes[node._namespace])
name_length = len(node.local_name) + len(self._prefixes[node.namespace])
if len(node) == 0:
used_space = 3 + name_length # <N/>
else:
Expand Down Expand Up @@ -3641,7 +3550,7 @@ def _required_space_for_attributes(self, node: TagNode, up_to: int) -> None | in
result += (
4 # preceding space and »="…"«
+ len(attribute.local_name)
+ len(self._prefixes[attribute._namespace])
+ len(self._prefixes[attribute.namespace])
+ len(attribute.value.translate(CCE_TABLE_FOR_ATTRIBUTES))
)
if result > up_to:
Expand Down
2 changes: 1 addition & 1 deletion _delb/xpath/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ def evaluate(
# global namespaces are guaranteed by the Namespaces implementation
if namespaces is None:
if _is_node_of_type(node, "TagNode"):
_namespaces = Namespaces({"": cast("TagNode", node)._namespace})
_namespaces = Namespaces({"": cast("TagNode", node).namespace})
else:
_namespaces = Namespaces({})
elif isinstance(namespaces, Namespaces):
Expand Down
8 changes: 4 additions & 4 deletions _delb/xpath/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ def evaluate(self, node: NodeBase, namespaces: Namespaces) -> bool:
node = cast("TagNode", node)

if self.prefix:
return node._namespace == namespaces[self.prefix]
return node.namespace == namespaces[self.prefix]
else:
return True

Expand Down Expand Up @@ -440,14 +440,14 @@ def evaluate(self, node: NodeBase, namespaces: Namespaces) -> bool:

if namespace is None: # noqa: SIM114
# XPath spec behaviour
if node._namespace:
if node.namespace:
return False
elif namespace == "":
# delb's specific behaviour
if node._namespace:
if node.namespace:
return False
else:
if node._namespace != namespace:
if node.namespace != namespace:
return False

return node.local_name == self.local_name
Expand Down
2 changes: 1 addition & 1 deletion delb/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def compare_trees(lhr: NodeBase, rhr: NodeBase) -> TreesComparisonResult:

if isinstance(lhr, TagNode):
assert isinstance(rhr, TagNode)
if lhr._namespace != rhr._namespace:
if lhr.namespace != rhr.namespace:
return TreesComparisonResult(TreeDifferenceKind.TagNamespace, lhr, rhr)
if lhr.local_name != rhr.local_name:
return TreesComparisonResult(TreeDifferenceKind.TagLocalName, lhr, rhr)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def test_attribute_object():
assert str(node) == '<root ham="spam"/>'

attribute = node["ham"]
assert attribute._namespace == ""
assert attribute.namespace == ""
assert attribute.universal_name == "ham"
assert str(attribute) == attribute.value == "spam"

Expand Down

0 comments on commit f370278

Please sign in to comment.