I’m facing an import conflict caused by two Python packages that both expose the same top-level module name, and I’m looking for best practices to handle this.
Problem description
In my project, I need to use:
ulid-py
python-ulid (required by another internal/third-party library)
Both packages install a top-level package named ulid.
When both dependencies are installed using Poetry, one ulid package overwrites the other in site-packages, causing unexpected behavior depending on install order.
Example:
poetry run python -c "import ulid; print(ulid.__file__)"
Only one implementation is available at runtime.
Environment
Python version: 3.12
Dependency manager: Poetry
OS: Ubuntu
Questions
Is there any recommended way in Python to handle two dependencies that expose the same top-level module name?
Any guidance or confirmation would be appreciated.
welcome to Python. I believe that you can alleviate this by way of a virtual environment. In the following link, scroll down to where it states: Creating Virtual Environments
I’m using Poetry with a virtual environment created inside the project folder. In my case, I need to use both packages within the same project and the same virtual environment.
@onePythonUser As mentioned earlier, I’m using Poetry for dependency management, and this setup is part of a project that builds and ships dependencies inside a Docker image, not just for local development. Because of this, installing one dependency globally and another in a virtual environment is not a viable option.
I see the problem. What needs ulid-py? Its last commit was September 2021. If you control how it’s imported, can you vendor it (copy/paste/static link) and import it using relative imports where needed? Presumably you don’t want to ditch the third party dep or ulid-py?
Are other users going to install your project as a library (possibly amongst even more deps), or can it be typically installed as an application (e.g. installed and used via uvx or pipx)?
No, this is a closed-source application/web server used only internally, so that isn’t a concern.
I did consider the option of vendoring it, but I’m hoping to find a cleaner alternative solution, if one exists. We are using ulid-py and the another third party dependency is using python-ulid.
I forgot - vendoring it and using relative imports, relies on the vendored package using relative imports internally. Otherwise you have to fork it and do a minor rewrite (and then there are 15 competing standards).
You can’t control what third party deps do with their transitive deps. If you can’t drop them, it’s best not to break them.
I’d focus on what you can control without creating too much future work, which it sounds like is the code that uses ulid-py, or even the entire way the user installs the project, or the entire venv shipped to the user (even if they’re only yourself, your colleagues, or employer).
As a workaround, I switched to using a single ULID library in my own codebase (replacing ulid-py with python-ulid) to avoid the overwrite issue. However, this doesn’t fully solve the problem, because the conflict still occurs if a third-party dependency internally relies on ulid-py and both packages end up installed in the same environment.
More generally, this feels like a class of problem that the Python packaging ecosystem doesn’t handle well today—there’s no built-in protection or clear guidance when multiple distributions install the same top-level module name. Other ecosystems handle similar cases differently; for example, in Java, dependencies are namespaced by their package names, and in Rust, crates are isolated by default and explicitly renamed when conflicts arise.
Is there any ongoing discussion or proposal in Python packaging (e.g. pip, PyPA, or import system changes) to detect or mitigate this kind of conflict—such as warnings at install time, metadata-based conflict detection, or stronger recommendations around namespacing top-level modules?
There’s a convention that import names match PyPI install names which, since PyPI names have to be unique, is usually enough to keep import names unique. Honestly, this is the first time I’ve ever seen someone have this issue. Although I am surprised that pip doesn’t check for file trampling before it goes ahead and tramples files.
It’s unlikely that two packages with the same name will ever be allowed to coexist for the same reasons that multiple versions of the same package are practically unsupportable.