Is This Library Worth Installing?

A five-minute check before you take on a dependency you will keep for years

Advanced 14 min

In this lesson

pip install is four seconds of typing and a commitment measured in years. Every package you add is code you did not write, cannot see, and will have to keep working — and the decision is usually made in about two seconds, from the first search result.

This lesson is the check to run instead. It takes five minutes, and it is most of the difference between a project that still installs in three years and one that does not.

Explain it like I’m 5

Installing a library is adopting a pet. It is free to take home and it needs feeding for years, so it is worth a look before you say yes.

Every dependency is a small permanent commitment

A dependency is not just a feature you gained. It is:

  • Code that has to keep working when Python releases a new version.
  • A thing your requirements file has to pin, and someone has to bump.
  • A surface where a security advisory can arrive.
  • Something a future reader has to learn to understand your project.

None of that is an argument against dependencies. Writing your own HTTP client instead of using requests would be a much worse idea. It is an argument for spending five minutes on the ones you add.

Ask the standard library first

The most common unnecessary dependency is one that replaces something Python already ships. Python's standard library is unusually large, and a surprising amount of what people install is already installed.

Things you already have, with no pip install at all:

  • Files and pathspathlib, shutil, glob, tempfile, zipfile.
  • Data formatsjson, csv, tomllib, sqlite3, configparser.
  • Command-line toolsargparse, logging, os.environ.
  • Structure and iterationdataclasses, collections, itertools, functools, enum.
  • Dates, numbers, randomnessdatetime, zoneinfo, decimal, statistics, random, secrets.
  • Testing and checkingunittest, doctest, hashlib, difflib.

So the first question is never “which package does this?” It is “does Python already do this?” When the answer is yes and the package is a slightly nicer interface over it, the package is usually not worth the commitment.

The five-minute check

Five things, all of them visible on the package's PyPI page and its repository in a couple of minutes.

  1. When was the last release? Under a year is healthy. Over two years, assume nobody is coming when it breaks.
  2. How many people maintain it? One is a risk, not a disqualification — it depends how much code you would have to replace.
  3. Do the docs have runnable examples? No examples means you will be reading the source, which is time you did not budget.
  4. How many packages does it drag in? See the next section.
  5. Is there a license? No license means no permission. See below.
Example
PACKAGES = [
    dict(name="requests", months_since_release=2, maintainers=12,
         runnable_examples=True, dependencies=4, license="Apache-2.0"),
    dict(name="fastcsvthing", months_since_release=41, maintainers=1,
         runnable_examples=False, dependencies=17, license=""),
    dict(name="tinyslug", months_since_release=5, maintainers=1,
         runnable_examples=True, dependencies=0, license="MIT"),
]

def concerns(package):
    """Everything about a package that should give you pause."""
    found = []
    if not package["license"]:
        found.append("no license at all: you have no permission to use it")
    if package["months_since_release"] > 24:
        found.append(f"nothing released for {package['months_since_release']} months")
    if package["maintainers"] == 1:
        found.append("one maintainer: everything stops if they stop")
    if not package["runnable_examples"]:
        found.append("documentation with no runnable example")
    if package["dependencies"] > 10:
        found.append(f"installs {package['dependencies']} other packages with it")
    return found

for package in PACKAGES:
    problems = concerns(package)
    print(f"{package['name']:13s} {len(problems)} concern(s)")
    for problem in problems:
        print("    -", problem)
Output
requests      0 concern(s)
fastcsvthing  5 concern(s)
    - no license at all: you have no permission to use it
    - nothing released for 41 months
    - one maintainer: everything stops if they stop
    - documentation with no runnable example
    - installs 17 other packages with it
tinyslug      1 concern(s)
    - one maintainer: everything stops if they stop
The check as code, which is also the check as a habit.

How many packages am I really installing?

A dependency has dependencies. Asking for one package regularly installs five or twenty, and every one of them is code you now ship. pip show tells you the direct ones for anything installed:

Example
$ pip show requests
Name: requests
Version: 2.34.2
Summary: Python HTTP for Humans.
License: Apache-2.0
Requires: certifi, charset_normalizer, idna, urllib3
Required-by: msal, wagtail

$ pip show certifi charset_normalizer idna urllib3 | grep -E "^(Name|Requires)"
Name: certifi
Requires:
Name: charset-normalizer
Requires:
Name: idna
Requires:
Name: urllib3
Requires:
pip show on a package almost everyone has (trimmed).

Work out what a package really costs. everything_installed walks the dependency graph and returns every package that arrives alongside the one you asked for; uninvited then reports how many of those you never mentioned. Handle a package with no dependencies too — the graph should not care.

DEPENDS_ON = {
    "reportmaker": ["pandas", "jinja2", "click"],
    "pandas": ["numpy", "python-dateutil", "pytz"],
    "jinja2": ["markupsafe"],
    "python-dateutil": ["six"],
    "click": [], "numpy": [], "pytz": [], "markupsafe": [], "six": [],
}

def everything_installed(package, graph):
    """Every package that arrives alongside `package`, sorted."""
    found = set()
    queue = list(graph.get(package, []))
    # TODO: while the queue is not empty, take a name off it; if it is new,
    #       record it and add ITS dependencies to the queue
    return sorted(found)

def uninvited(package, graph):
    """How many arrive that you never asked for."""
    return 0     # TODO: everything installed, minus what you actually named

print("you asked for:", sorted(DEPENDS_ON["reportmaker"]))
print("you installed:", everything_installed("reportmaker", DEPENDS_ON))
print("uninvited:    ", uninvited("reportmaker", DEPENDS_ON))
print("a leaf package:", everything_installed("six", DEPENDS_ON),
      uninvited("six", DEPENDS_ON))

Read the license line

The license decides what you are allowed to do with the code. It takes ten seconds to check and it is the one item on the list with consequences you cannot fix later by swapping the package out.

  • MIT, BSD, Apache-2.0 — permissive. Use it in anything, including commercial work; keep the license notice. This is most of the Python ecosystem.
  • GPL, AGPL — copyleft. Fine for your own tools and for open projects, but there are real conditions if you distribute or host software built on them. Read before shipping commercially.
  • No license at all — the important case. Code published with no license is not public domain; by default nobody has permission to use it. “It was on GitHub” is not permission.

That is a summary, not legal advice, and anything with money attached deserves a real check. But the habit is simple: look for the license, and treat its absence as a red flag rather than a formality.

Learning it properly once you have chosen

Having picked a library, the fastest route in is almost always the same order:

  1. Its own getting-started page, run rather than read, using the technique from the previous lesson: run the example, change one thing, predict the result.
  2. Its API reference, once you have a question. Not before.
  3. One book or one course, if the subject is big enough to deserve it — a web framework or pandas, say. One. A second course on the same subject is procrastination with a receipt.
  4. Its source and its issue tracker, when behavior surprises you. A search of closed issues answers “is this a bug or is it me?” faster than anything else.

Judging a course or a book is the same problem as judging a package: is it current (check the Python version it uses), does it build something (a course with no project produces no skill), and can you see a sample (a free chapter or a preview lesson). Free official documentation beats a paid course that is two major versions out of date.

Common mistake: Installing the first search result

Why it happens:

It appeared to solve the problem, and installing is faster than checking.

How to fix it:

Spend the five minutes: last release, maintainers, examples, dependency count, license. Search ranking measures popularity of the page, not health of the project.

Common mistake: Adding a whole package for one small function

Why it happens:

Somebody has already written it, so writing it yourself feels wasteful.

How to fix it:

If the function is ten lines and you understand it, write the ten lines. If it is date-and-timezone handling or anything cryptographic, take the package — those are the ones where subtle mistakes are expensive.

Common mistake: Never checking the license

Why it happens:

It feels like paperwork, and public code feels free by default.

How to fix it:

Look at one line on the PyPI page. Permissive licenses cover most of what you will meet; no license at all is the case worth catching, because it means nobody granted permission.

Common mistake: Judging a package by its GitHub stars

Why it happens:

Stars are the most visible number on the page.

How to fix it:

Stars accumulate and never decay, so an abandoned project keeps them all. Look at the date of the last release and the last few closed issues instead.

Common mistake: Never pinning versions, or upgrading everything at once

Why it happens:

Both are the path of least effort: install whatever is current, then one day run a bulk upgrade.

How to fix it:

Pin what you depend on in your requirements file, then upgrade one package at a time with your tests running. Ten upgrades in one commit produce one failure and ten suspects.

What should you ask before choosing between two packages?

A package you need has exactly one maintainer. What does that mean?

Why count a package's dependencies before installing it?

A useful-looking repository has no license file. What can you do with it?

Mini exercise (medium)

Turn the five-minute check into a single verdict. verdict(package) returns one of "avoid", "not needed", "with care" or "use it", testing the rules in that order: no license or nothing released for over two years is "avoid"; a standard-library alternative is "not needed"; a single maintainer is "with care"; anything else is "use it".

Try it yourself. Complete the snippet and hit Run; it executes in your browser, no setup required.

CANDIDATES = [
    dict(name="httpx", months=1, maintainers=9, license="BSD-3-Clause",
         dependencies=5, stdlib_alternative=""),
    dict(name="pathtools", months=8, maintainers=1, license="MIT",
         dependencies=0, stdlib_alternative="pathlib"),
    dict(name="fastjson", months=3, maintainers=1, license="",
         dependencies=2, stdlib_alternative="json"),
    dict(name="oldretry", months=52, maintainers=3, license="MIT",
         dependencies=1, stdlib_alternative=""),
    dict(name="tinyretry", months=7, maintainers=1, license="MIT",
         dependencies=0, stdlib_alternative=""),
]

def verdict(package):
    """One of: "avoid", "not needed", "with care", "use it"."""
    return "?"     # TODO, in this order:
                   #   no license            -> "avoid"
                   #   nothing for 2 years   -> "avoid"
                   #   the stdlib does it    -> "not needed"
                   #   one maintainer        -> "with care"
                   #   otherwise             -> "use it"

for package in CANDIDATES:
    print(f"{package['name']:10s} {verdict(package)}")

What to learn next

You have the five-minute check (last release, maintainers, runnable examples, dependency count, license), plus the question that comes before all of it: does the standard library already do this? You walked a dependency graph to see how many packages really arrive when you ask for one, learned what “no license” actually means, and wrote the single verdict where the order of the rules decides the answer.

That is the last of the judgment lessons. Time to build: the Unit 16 project turns the plan you wrote in Unit 15 into something that runs, in five sessions, with someone else able to clone it and use it.