The author has just replaced one mechanism for OO with another one.

If you think you don't need OO, then prove it by doing this:

# [0] is the root_url
# [1] is the url_layout for formatting it: must contain {entity}.

client_a = [ "https://client-a", "{root_url}/{entity}" ]

If you have client.root_url type stuff in your program, then it's using some degree of OO.

In he new program, this is obviously OOP:

def construct_url(client: Client, entity: str) -> str:
return client.url_layout.format(root_url=client.root_url, entity=entity)

In the original program, inheritance was used to specialize the formatting behavior. That had to be somehow preserved.

How it was preserved was by aggregating a layout string and delegating the formatting task to its layout. (I smell a Design Pattern, but we are not doing OO!)

But that can be back-ported to the original program:

class ApiClient:
def __init__(self, root_url: str, session_cls: sessionmaker, layout: str):
self.root_url = root_url
self.session_cls = session_cls
self.layout = layout

def construct_url(self, entity: str) -> str:
return self.layout.format(root_url=self.root_url, entity=entity)

def get_items(self, entity: str) -> List[Item]:
resp = requests.get(self.construct_url(entity))
resp.raise_for_status()
return [Item(**n) for n in resp.json()["items"]]

def save_items(self, entity: str) -> None:
with scoped_session(self.session_cls) as session:
session.add(self.get_items(entity))

client_a = ApiClient("https://client-a", session_cls,
"{root_url}/{entity}")
client_b = ApiClient("https://client-a", session_cls, "{root_url}/a/special/place/{entity}")

client_a.save_items("bars")

Also, the author eliminated the session_cls member variable, which is only used in the save_items function. That also can be done to the original:

class ApiClient:
def __init__(self, root_url: str, layout: str):
self.root_url = root_url
self.layout = layout

def construct_url(self, entity: str) -> str:
return self.layout.format(root_url=self.root_url, entity=entity)

def get_items(self, entity: str) -> List[Item]:
resp = requests.get(self.construct_url(entity))
resp.raise_for_status()
return [Item(**n) for n in resp.json()["items"]]

def save_items(self, session_cls: sessionmaker, entity: str) -> None:
with scoped_session(self.session_cls) as session:
session.add(self.get_items(entity))

client_a = ApiClient("https://client-a",
"{root_url}/{entity}")
client_b = ApiClient("https://client-a",
"{root_url}/a/special/place/{entity}")

client_a.save_items(session_cls, "bars")

In the end, all the author shows it that if all we need is minor variations in formatting some string by sticking values into a template string, we might want to do exactly that and not whip out inheritance.

But, note, that the original code can compile:

The following performs the formatting job using code that can compile:

class ClientA(ApiClient):
def construct_url(self, entity: str) -> str:
return f"{self.root_url}/{entity}"

(If an f-string doesn't map to compiled code that doesn't interpret the format string at run-time, someone in Python-land has obvious work to do.)

This uses run-time interpretation of a dynamically supplied format string":

def construct_url(self, entity: str) -> str:
return self.layout.format(root_url=self.root_url, entity=entity)

Though that could be optimized by, say, storing JIT-ted code into the layout string the first time format is called, it probably isn't.