NAM106: a get_ that performs I/O¶
Junior¶
In Python, get means a cheap lookup: dict.get, getattr, os.environ.get. They read memory, return at once and never wait. A function named get_ that reads a file, calls a web service, runs a command or queries a database breaks that promise.
def get_user(uid): # NAM106: calls httpx.get
return httpx.get(f"/users/{uid}").json()
def fetch_user(uid): # says it goes somewhere
return httpx.get(f"/users/{uid}").json()
To fix it, rename the function with a verb that says where the data comes from: fetch_ for the network, load_ or read_ for a file, query_ for a database.
Senior¶
The caller of a get_ makes decisions based on the name. They call it in a loop, in a request handler, or while holding a lock, because a lookup is cheap. When it turns out to open a socket, the cost shows up as a slow page or a timeout that is hard to trace back to a name.
The evidence is a call to open, Path.read_* or write_*, urlopen, a DB-API execute/fetchone/fetchall, a socket recv or send, or anything rooted at requests, httpx, urllib, socket, subprocess or aiohttp. dict.get and getattr never count.
Renaming isn't always the best fix. A get_template that reads the same file on every call is better fixed by reading it once, with functools.cache or at import time. namelint can't see a cache, so a cached function still needs a new name or an allowlist entry.
In an application built on an ORM, get_ for a database lookup can be the house style. If almost every get_ in the codebase queries the database, a reader already knows. Record those findings with namelint allow --select NAM106 and keep the check for the file, network and subprocess calls.
Expert¶
The check looks for the first call in the function's own body (nested functions excluded) whose dotted name matches the I/O list. The message names that call, such as calls subprocess.run or calls self.key_file.read_text. Only calls written in this function count. A get_ that calls a helper that does I/O is silent, since following the call would need name resolution across modules.
In the corpus, this is why requests reports nothing: requests.get calls request(), which does the I/O one frame further down. The check fires in flask (2.33 per thousand) and httpx (0.95). All nine corpus projects are libraries. The first run on a database-backed application reported mostly session.execute calls, which suggests the database evidence is too strict for application code. That question is open.