App vs Library

What is the difference between an app and a library? This seemingly simple question confuses some, and it turns out to be a harder question to answer than you might expect. While the actual distinction between these common terms will always be muddled in practice, I propose a specific definition to be used when considering dependencies. This distinction is important when discussing bound version constraints in the next post.


A library is something a user can import as part of a larger project, either another library or an application. The defining feature is that a library cannot dictate what other packages live alongside it. A user might use your library, but they also may need to use other libraries as well, and as a package author, you can’t control what they use (though you can make version constraints on a known subset of dependencies).

Some common examples: packaging, requests, and numpy. We will look at some less clear examples later, but for now, let’s reiterate that the defining feature is that libraries have to live alongside arbitrary other libraries. It is not based on usage (import vs. command line or other app), but on environment.

As a side note, Pip does not allow you to make version constraints on packages you do not require, though Conda does (run_constrained). This is specifically useful for optional dependencies; for example, you might be happy to use the faster virtualenv instead of venv, but you might need at least the 2020 version of virtualenv to use it. You can’t specify you need to update virtualenv to 2020+ if it is present, but no need to install it if it not with Pip.


This is a package that is intended to be used directly by a user, and is not required to live in an environment with arbitrary libraries.These will almost always have a terminal interface or graphical user interface, and may not support being imported from Python at all; this is not the defining feature, however - it is possible for a library to have a terminal interface, though rare.

We can further break up applications into two categories. One would be general applications; these are things that are meant to be installed and used by a wide variety of users. The other category is deployable applications; these might not even be PyPI installable, but instead only have a “development” style install, ideally with a lock file (web applications often fall into this category).

To be clear: many users will not understand packaging well enough to install an general application into a new virtual environment, and will instead add it to an existing environment, or even their system environment. However, unlike a library, if there is a collision between a previously installed package, a user can be instructed to install the application in isolation, say with pipx, as an acceptable workaround. This cannot be done with a library, because a user might need both conflicting packages in a single environment.

Some common examples: pip, tox/nox, twine, cibuildwheel, and pre-commit.

Libraries and Applications

A package can be both; sometimes a package has a library interface and application interface. That’s fine; it just means that the stricter rules (generally library) apply.

A detailed common example: wheel has an interface for working with wheel files; if you just need that, you can use pipx run wheel to access it, no shared environment required. However, it also is a helper library (currently) for setuptools, where it has to install into arbitrary builder environments.


A framework is a package that usually is a library, but can possibly be an application - it may be intended to be “complete”, allowing a user to interact with it by writing a Python file without importing any extra packages. However, in almost all cases, it’s really, really helpful to be able to import other packages, making it much nicer to treat as a library instead of an application. Many frameworks (like basically all web frameworks) are “incomplete”, which makes them fully intended to be libraries.

Another common feature of frameworks is a plugin system, where functionality can be added by extensions. Setuptools, sphinx, tox, and pytest would be in this category. Even though these have a clear interface outside of Python, they are extended by (not quite arbitrary) packages, and so are much closer to a library usage.

Some other common examples: pytorch and tensorflow.

Where next?

With these definitions in place, we can discuss bound version constraints in the next post.