BSD in one place and unset in another, four dependencies against a claim of a couple, and an example that needs IPython
Operate and manipulate physical quantities in Python
At a glance
- What is it?
- Pint is a Python units library whose selling point is that unit definitions live in a text file and prefixes are parsed for you, and the interesting material is the distance between the README's account of itself and the packaging metadata: three different license statements, a dependency list twice the size the prose admits, a dask extra capped from below an upstream bug, and no GitHub releases at all.
- Who is it for?
- Pint fits a scientific Python project that wants units attached to values without adopting a framework, and the editable definition file is the reason to choose it over a fixed unit table. Four things to check before depending on it.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 1 day 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 4, 2026, and from our analysis. They are not legal advice.
Editorial analysis
BSD in the metadata, BSD in the prose and nothing on the repository
The license is described in three places and they do not all agree.
The packaging metadata is the precise one:
license = "BSD-3-Clause"The README says only that it is licensed under BSD, without the clause. The repository's own license field is empty, so the badge that reads from PyPI and the field on the repository page are not the same source. A LICENSE file does sit at the repository root, so the grant itself is present in the tree; what is missing is agreement between the three descriptions.
For a package whose selling point is that it is a small dependency for scientific code, that matters more than it would for an application. Anyone checking whether a BSD-3-Clause package can go into a product they ship has to open the LICENSE file to find out which clause they are agreeing to, because the shortcut through the repository page returns nothing at all.
Four dependencies against a claim of a couple of small packages
One of the design principles is titled Minimal dependencies, and the text says the library depends only on Python, its standard library and a couple of small packages that implement essential features, giving two examples: parsing unit definition files and caching. It adds that it only interacts with other external packages, like numpy and uncertainties, if they are installed.
The dependency list has four entries:
dependencies = [
"platformdirs>=2.1.0",
"typing_extensions>=4.0.0",
"flexcache>=0.3",
"flexparser>=0.4",
]The two named features map cleanly onto two of them. flexparser is the parsing of definition files and flexcache is the caching, and both are small packages by the standards of that ecosystem. The other two are not mentioned in that paragraph at all. platformdirs is where a user-level cache directory would come from, and typing_extensions is there for annotation support on older interpreters.
So the principle holds in spirit and not in count. The claim is that nothing heavy is required, which is true, and the number stated is two, which is not.
The numpy example only works under IPython
The opening example is a doctest and runs anywhere:
>>> import pint
>>> ureg = pint.UnitRegistry()
>>> 3 * ureg.meter + 4 * ureg.cm
Quantity(3.04, "meter")The second one is not:
>>> import numpy as np
>>> [3, 4] * ureg.meter + [4, 3] * ureg.cm
Quantity([ 3.04 4.03], "meter")
>>> np.sum(_)
Quantity(7.07, "meter")The last line adds up the array in the value the interpreter kept from the previous line. That variable exists only in an interactive session, so pasting this block into a script raises a name error on the third line even though every step in it is correct. Copying the documentation into a file is the usual way people meet that, and the fix is to name the result rather than rely on the session.
The arithmetic underneath is the point being made, since an array multiplied by a unit produces a quantity holding an array and a unit, and the sum comes back in meters rather than in the mixed units it was written in.
Prefix parsing is what keeps the unit definition file short
The design principles name unit parsing first, and it is the mechanism behind the modular claim in the opening paragraph. Prefixed and pluralized forms of units are recognized without being defined explicitly: as the prefix kilo and the unit meter are defined, the library understands kilometers. The stated benefit is a much shorter and maintainable unit definition list compared with other packages.
The second principle is standalone unit definitions, loaded from a text file that is simple and easy to edit, so adding or changing units does not involve changing the code. The README points at the file that ships with the package, pint/default_en.txt on the master branch, and the build configuration names both data files explicitly:
[tool.hatch.build]
packages = ["pint"]
include = ["pint/default_en.txt", "pint/constants_en.txt"]So the extension story is a data file plus a parser, which is why flexparser is a dependency rather than an optional one. A user who wants a unit the shipped file lacks adds it to a text file, and the library never has to be forked to accommodate it.
Every extra has a floor except dask, which has a ceiling
The optional dependency groups are almost all lower bounds. numpy is numpy >= 2.0.0, uncertainties is >= 3.1.6, pandas pulls pint-pandas >= 0.3, optype pulls its own numpy extra, and babel, xarray, scipy and matplotlib are named plainly.
One group goes the other way, and it carries its reason in a comment in the file itself:
# Impose Dask < 2025.3.0, otherwise it causes "RuntimeError: Attempting to use an asynchronous Client in a synchronous context of `dask.compute`" (see Issue #1016 in Dask).
dask = ["dask < 2025.3.0"]The failure named is an asynchronous client being used inside a synchronous call, which is a behaviour change on Dask's side rather than a decision about Pint. Capping below a release is the blunt version of that fix, and it means an installation that wants a newer Dask cannot get it through this extra.
Two benchmarking tools appear in the test groups rather than the runtime ones, pytest-benchmark in the test extra and pytest-codspeed in a codspeed extra built on top of test-all. Two harnesses for the same job is a small sign of a project measuring itself from both ends.
Complete test coverage is claimed and measured by a badge
The README states that it has a complete test coverage, runs in Python 3.12 or higher with minimal dependencies, and is licensed under BSD, in three consecutive short sentences. The first of those is the one a reader has no way to check from the documentation.
The repository carries the machinery that would produce the number: a .coveragerc at the root and a Coveralls badge pinned to the master branch. It also carries separate CI and Lint workflow badges, so the test run and the lint run are two different pipelines.
The Python range is consistent between the prose and the metadata, which is worth saying because it is rare. The metadata requires 3.12 or newer and the classifiers name 3.12, 3.13 and 3.14, and the README says Python 3.12 or higher. What does not line up is the maturity signal: the classifiers say Development Status 4, Beta, while the README describes the library as extremely easy and natural to use and claims complete coverage.
Versioning lives in a CHANGES file because there are no releases
The repository has no GitHub releases. Version history is a file.
The README points at CHANGES for an ordered list of notable changes for each version of the project, and that file sits at the repository root next to AUTHORS, which carries the community list described as scientists, programmers and enthusiasts around the world. Both are plain files in the main tree rather than release notes attached to tags.
The version number itself is not written in the packaging metadata either, which declares it dynamic:
dynamic = ["version"]So there are three places a version can be read from and none of them is pyproject.toml: the CHANGES file, the PyPI page the version badge points at, and whatever the package reports at runtime.
The documentation links are also older than the rest of the packaging. The repository metadata gives the homepage as http://pint.readthedocs.org/ and the README links the same address over plain http, while the readme badge points at the readthedocs project page. The branch is master, its last push is dated 2026-09-18, and the build system is configured for hatch with the data files listed by hand.
downstream_status.md sits in the root of a library
One entry in the repository root does not belong to a library in the usual sense. Alongside .coveragerc, .editorconfig, .gitattributes, .pre-commit-config.yaml, .readthedocs.yaml, AUTHORS, CHANGES, LICENSE, MANIFEST.in and README.rst there is a file called downstream_status.md.
Nothing in the README mentions it, and there is no section describing how it is produced or who maintains it. A file with that name in the root of a package is a record of the projects that depend on it, which makes it the only place in the repository where the answer to who uses Pint is kept, and that answer lives in a file rather than in an issue tracker or on the website.
The rest of the root is ordinary and well chosen for a project of this shape: a docs directory matching the readthedocs configuration, a .devcontainer for working on the code itself, and a pint directory that the build configuration lists as the only package. The design decision that everything else follows from is that the unit table is data, so the data ships as two named text files and the code ships as one importable package.
Editorial conclusion
Pint fits a scientific Python project that wants units attached to values without adopting a framework, and the editable definition file is the reason to choose it over a fixed unit table. Four things to check before depending on it. The license is BSD-3-Clause in the packaging metadata, BSD in the README and unset on the repository, so read the LICENSE file rather than the badge. The prose promises a couple of small dependencies and the metadata lists four. Versioning lives in a CHANGES file because there are no GitHub releases, and the version field itself is dynamic. And the second worked example only runs under IPython, because it relies on the value the interpreter keeps between lines. Optional integrations are all extras with a floor, with dask the single exception, capped below 2025.3.0 to avoid a specific upstream error. The branch was pushed on 2026-09-18.
Frequently asked questions
How to use pint?
Import the package and create a registry, then multiply a number by a unit: importing pint and calling pint.UnitRegistry() gives a registry on which 3 * ureg.meter + 4 * ureg.cm returns a Quantity of 3.04 meter. It installs with pip install pint, or with conda install -c conda-forge pint.
How does the pint package in Python handle units?
Prefixed and pluralized forms are recognized without being defined explicitly, so defining kilo and meter is enough for the library to understand kilometers, which keeps the definition list short. Unit definitions live in an editable text file rather than in code, and formatting follows PEP 3101 with extra flags for symbolic, LaTeX and pretty output.
Does Pint require numpy to be installed?
No. The design notes say numpy is not required but supported, and that the package only interacts with numpy and uncertainties if they are installed. numpy is an optional extra at numpy >= 2.0.0, and the array integration covers ndarray methods and ufuncs with automatic unit conversion, so numpy.arccos on a dimensionless quantity returns radians.
Why is dask capped below 2025.3.0 in Pint's optional dependencies?
The comment beside that extra says later versions raise a RuntimeError about attempting to use an asynchronous Client in a synchronous context of dask.compute, and points at issue 1016 in the Dask repository. Every other optional extra in the list sets a floor instead, so dask is the only integration with an upper bound.
How is Pint released, given that the repository has no releases?
Through a changelog file rather than tags. The root carries CHANGES, described as an ordered list of notable changes for each version, and the version is declared dynamic in pyproject.toml instead of being written there. The latest version badge points at the PyPI page, and the branch is master with a push on 2026-09-18.
Official sources
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.
[](https://hysenlabs.com/projects/hgrecco-pint)