Here is a pretty dirty workaround that nevertheless achieves desired behavior.
(credit to @user2357112 for explaining process spawning)
mymodule is modified as follows:
/mymodule
├── __init__.py
├── __main__.py
├── app.py
└── worker.py
__init__.py is empty
worker.py is unchanged
app.py contains the code from original __main__.py
__main__.py is a new file:
import sys
import mymodule.app
__spec__.name = mymodule.app.__spec__.name
sys.exit( mymodule.app.main() )
Now running module any of the three ways: python -m mymodule or python -m mymodule.__main__ or python -m mymodule.app produces the same result on Windows:
[__main__] [DEBUG]: I am main. I manage workers
[__main__] [INFO]: I am main. I manage workers
[__main__] [WARNING]: I am main. I manage workers
[mymodule.worker] [DEBUG]: I am a worker. I do stuff
[mymodule.worker] [INFO]: I am a worker. I do stuff
[mymodule.worker] [ERROR]: I am a worker. I do stuff
[mymodule.worker] [ERROR]: Here is my logger: <Logger mymodule.worker (DEBUG)>
In practice my code is more complex and takes care of cli arguments using argparse, but that is out of scope of this questions. I have tested this with python 3.11 and so far have not encountered any unexpected side effects.