The private objects and attributes are easily accessed using inspect.getclosurevars
:
>>> c = CounterController()
>>> import inspect
>>> inspect.getclosurevars(c.add_one_endpoint)
ClosureVars(nonlocals={'counter_controller': <__main__.CounterController.<locals>.CounterControllerPrivate object at 0x10a047cf8>}, globals={}, builtins={}, unbound={'add_one'})
>>> _.nonlocals['counter_controller']
<__main__.CounterController.<locals>.CounterControllerPrivate object at 0x10a047cf8>
>>> _.counter
0
So you might consider whether it's worth spending a lot of effort on this approach. Personally I would spend the effort on API documentation instead.