Library / SDK
stchris/untangle avatar
stchris/untangle

untangle turns XML into attribute-accessible Python objects

Converts XML to Python objects

629 stars90 forksPythonMIT

At a glance

What is it?
untangle is a small MIT licensed Python library that parses XML into Python objects, so children are reached with parent.child and attributes with element['attribute'], and it names every element with a valid Python identifier. It suits a script that needs to read an XML document without writing an ElementTree traversal, and it depends on defusedxml for parsing.
Who is it for?
Adopt untangle when you are writing a short script that reads XML and you want attribute style access rather than an ElementTree walk, and when you are content with the library's naming rules for elements. Do not adopt it for XML that carries information in element text or in structure untangle does not model, since it is a convenience layer over parsed elements rather than a data binding system.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 10 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The API is three rules long

The README lists the behaviour of untangle in a handful of lines, and the whole library is easier to grasp than most. It converts XML to a Python object. Siblings with similar names are grouped into a list. Children can be accessed with parent.child and attributes with element['attribute']. You can call the parse() method with a filename, an URL or an XML string. That is the entire surface, and it is deliberately the surface of a dotted attribute access rather than a query API. There is no XPath, no namespace handling to learn and no document object model to traverse. The practical effect is that reading a document reads like reading a nested structure, and the code that consumes it does not need a helper function for every field. The library dates from 2011, when it was written to solve the problem of XML in Python being painful to read, and it has kept that shape while the packaging around it has been modernised.

Sibling grouping is the decision that shapes the data

The rule about siblings with similar names being grouped into a list is the one that most affects how you write the consuming code, and it is worth understanding before you parse anything. A document with one child of a given name gives you that child directly, and a document with several children of the same name gives you a list of them. That means the type of a field depends on the data rather than on the schema, which is convenient when the data is regular and awkward when it is not. If your document sometimes has one customer element and sometimes two, you write the access defensively, and that is a cost the library's simplicity does not remove. The alternative design, always returning a list, is more predictable but makes the common case noisier. untangle chose the convenient reading, and the example in the README shows it, with a document whose root holds a single child and the child accessed as an object rather than as a list. The XML in that example is deliberately minimal, with a root element holding one child element carrying a name attribute.

Names are rewritten so they are valid Python identifiers

The substitution rule is specific and it matters when you write the consuming code. Hyphens, dots and colons are replaced with underscores, so a foo-bar element inside a foobar parent is reached as foobar.foo_bar, a foo.bar.baz element becomes foo_bar_baz, and a foo:bar element containing foo:baz becomes foo_bar.foo_baz. That covers the three characters that most often appear in XML names and are illegal in Python identifiers, and it covers namespace prefixes, which is convenient since it means a namespaced element and a plain one are reached the same way. The rule is a rewrite rather than a rejection, so there is no error when a name is not legal, but there is also no way to recover the original name from the object alone. If two different elements differ only by one of those characters, for example a-b and a.b, they would land on the same attribute. The naming is applied consistently across the object tree, and it is the reason the attribute access in the README examples reads like ordinary Python rather than a lookup function.

Installing and parsing

Installation is a single pip command, and the package is on PyPI under the name untangle:

bash
pip install untangle

Parsing is one import and one call:

python
import untangle

obj = untangle.parse(resource)

The resource argument can be a URL, a filename as a string or an os.PathLike object such as a pathlib.Path, or an XML string. Accepting all four is the convenience that removes the branching from a script that has to handle a local file in development and a URL in production. The README points to examples.py in the repository and to an earlier blog post for more detail, so the documentation is split between the repository, the module documentation on readthedocs and a long-standing write-up from 2011. The packaging metadata shows a package at version 1.3.0 requiring Python 3.11 or newer, with classifiers for 3.11 through 3.15, and exactly one runtime dependency, defusedxml pinned at 0.7.1. That dependency is the point of interest, since it is a hardened replacement for the standard library XML parsers, and its presence in a package whose entire job is parsing XML from files and URLs is a deliberate choice rather than an accident.

The build tooling has moved on, the Makefile has not

The repository tells two stories about how the project is maintained, and they do not quite agree. The README says untangle uses uv for builds and virtualenv management, and lists four commands, uv sync to set the environment up, uv run pytest to run the tests, uv run ruff check . for the linter and uv run ruff format --check for the formatter checks, plus uv run ty check . for the type hint checks. The pyproject.toml matches that, with the dev dependency group holding pytest at 9.1.1 or newer, ruff at 0.16.8 or newer and ty at 0.0.83 or newer, and the build system requiring uv_build between 0.12.17 and 0.14.0. But the Makefile at the root is still the older setup, with a default goal of compile that runs compileall over untangle.py and tests/tests.py, a setup target that installs poetry and runs poetry install, lint and test targets that call poetry run flake8 and poetry run black and poetry run pytest, and a package_deb target that needs python-stdeb to build a Debian package. There is also a .flake8 file and an untangle.rst at the root, both of which are artefacts of the earlier packaging. The repository also keeps .readthedocs.yaml, docs/, a CHANGELOG.md, an AUTHORS file and a MANIFEST.in.

A small library with a long tail of conventions

The project metadata names the author, Christian Stefanescu, and describes the package in four words, converts XML to Python objects. The licence is MIT. The release history shows 1.3.0 published on 2026-09-23, then 1.2.0 in July 2022 and 1.1.1 in June 2022, so this is a library that sat still for a while and then moved again, and the modernising of the build to uv with ruff and ty is what a 2026 release looks like after a period of dormancy. The interesting judgement for an adopter is about scope. untangle is not a binding framework, it does not give you typed models or validation, and it does not try to preserve the document's structure faithfully beyond the sibling grouping and the name substitution. What it gives you is the smallest possible answer to one question, how do I read a value out of this XML without writing a traversal, and it answers it in a way that a script can use in two lines. If that is your question, the library is a good fit and the dependency count is a genuine advantage. If your question is how do I model this document for a whole application, you will want something else.

Editorial conclusion

Adopt untangle when you are writing a short script that reads XML and you want attribute style access rather than an ElementTree walk, and when you are content with the library's naming rules for elements. Do not adopt it for XML that carries information in element text or in structure untangle does not model, since it is a convenience layer over parsed elements rather than a data binding system. Verify first that your elements and attributes map to legal Python identifiers after the substitutions, since a name with a hyphen or a dot is rewritten rather than rejected, and that your Python is 3.11 or newer, which the packaging metadata requires.

Frequently asked questions

How do I parse XML with untangle?

Import the module and call the parse method, obj = untangle.parse(resource), where resource can be a URL, a filename as a string or an os.PathLike such as a pathlib.Path, or an XML string. Children are then reached with parent.child and attributes with element['attribute'].

What happens to XML names that are not valid Python identifiers?

Hyphens, dots and colons are substituted with underscores. So a foo-bar element inside foobar is accessed as foobar.foo_bar, a foo.bar.baz element becomes foo_bar_baz, and a foo:bar element containing foo:baz becomes foo_bar.foo_baz.

What are untangle's dependencies and Python requirement?

One runtime dependency, defusedxml pinned at 0.7.1, and the package requires Python 3.11 or newer, with classifiers covering 3.11 through 3.15. The version is 1.3.0, published on 2026-09-23, and the licence is MIT.

How do I run the untangle tests and linters?

The README says untangle uses uv for builds and virtualenv management, so uv sync sets the environment up, uv run pytest runs the tests, uv run ruff check . runs the linter, uv run ruff format --check checks formatting and uv run ty check . checks type hints. The root Makefile still carries an older Poetry based setup with setup, lint and test targets.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. stchris/untangle on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/stchris-untangle.svg)](https://hysenlabs.com/projects/stchris-untangle)

Was this useful?