What does Python's functools.wraps fix, and which metadata does it copy?
A decorator replaces a function with a wrapper. Unless you say otherwise, that wrapper is a completely different function object with a different name, no docstring, and a catch-all signature, so the identity of the original function is lost. functools.wraps puts that identity back by copying the metadata of the original function onto the wrapper.
The attributes that it copies are __module__, __name__, __qualname__, __doc__, __annotations__, __dict__, and __wrapped__. The first five are the ones that you notice in everyday work. They are the module the function came from, its name, its qualified name including the class, its docstring, and its type hints. The __dict__ is copied too, so any custom attributes that you set on the function survive the decoration.
__wrapped__ is the one with a special purpose. It is a direct reference to the original function before it was decorated, and the inspect module in the standard library follows it. That is why inspect.signature on a decorated function reports the original parameters instead of the catch-all signature of the wrapper.
The real effect is on everything that reads a function's metadata, such as tracebacks, help(), documentation generators, type checkers, mocking libraries, and test frameworks that look functions up by name. A decorator without wraps makes all of those see a function called wrapper with no docstring, which is why the convention in the Python community is that every decorator uses it. The earlier questions in this topic covered how decorators work, and this answer focuses on the one line that keeps them honest.