import configparser
import os
import platform
import re
import shutil
import subprocess
import sys
import time
from contextlib import contextmanager

import tomlkit

APP = 'napari'

# EXTRA_REQS will be added to the bundle, in addition to those specified in
# setup.cfg.  To add additional packages to the bundle, or to override any of
# the packages listed here or in `setup.cfg, use the `--add` command line
# argument with a series of "pip install" style strings when running this file.
# For example, the following will ADD ome-zarr, and CHANGE the version of
# PySide2:
# python bundle.py --add 'PySide2==5.15.0' 'ome-zarr'

# This is now defined in setup.cfg "options.extras_require.bundle_run"
# EXTRA_REQS = []

WINDOWS = os.name == 'nt'
MACOS = sys.platform == 'darwin'
LINUX = sys.platform.startswith("linux")
HERE = os.path.abspath(os.path.dirname(__file__))
PYPROJECT_TOML = os.path.join(HERE, 'pyproject.toml')
SETUP_CFG = os.path.join(HERE, 'setup.cfg')


if WINDOWS:
    BUILD_DIR = os.path.join(HERE, 'windows')
elif LINUX:
    BUILD_DIR = os.path.join(HERE, 'linux')
elif MACOS:
    BUILD_DIR = os.path.join(HERE, 'macOS')
    APP_DIR = os.path.join(BUILD_DIR, APP, f'{APP}.app')


with open(os.path.join(HERE, "napari", "_version.py")) as f:
    match = re.search(r'version\s?=\s?\'([^\']+)', f.read())
    if match:
        VERSION = match.groups()[0].split('+')[0]


@contextmanager
def patched_toml():
    parser = configparser.ConfigParser()
    parser.read(SETUP_CFG)
    requirements = parser.get("options", "install_requires").splitlines()
    requirements = [r.split('#')[0].strip() for r in requirements if r]

    with open(PYPROJECT_TOML) as f:
        original_toml = f.read()

    toml = tomlkit.parse(original_toml)

    # Initialize EXTRA_REQS from setup.cfg 'options.extras_require.bundle_run'
    bundle_run = parser.get("options.extras_require", "bundle_run")
    EXTRA_REQS = [
        requirement.split('#')[0].strip()
        for requirement in bundle_run.splitlines()
        if requirement
    ]

    # parse command line arguments
    if '--add' in sys.argv:
        for item in sys.argv[sys.argv.index('--add') + 1 :]:
            if item.startswith('-'):
                break
            EXTRA_REQS.append(item)

    for item in EXTRA_REQS:
        _base = re.split('<|>|=', item, maxsplit=1)[0]
        for r in requirements:
            if r.startswith(_base):
                requirements.remove(r)
                break
        if _base.lower().startswith('pyqt5'):
            try:
                i = next(x for x in requirements if x.startswith('PySide'))
                requirements.remove(i)
            except StopIteration:
                pass

    requirements += EXTRA_REQS

    toml['tool']['briefcase']['app'][APP]['requires'] = requirements
    toml['tool']['briefcase']['version'] = VERSION

    print("patching pyproject.toml to version: ", VERSION)
    print(
        "patching pyproject.toml requirements to:",
        *toml['tool']['briefcase']['app'][APP]['requires'],
        sep="\n ",
    )

    if MACOS:
        # Workaround https://github.com/napari/napari/issues/2965
        # Pin revisions to releases _before_ they switched to static libs
        revision = {
            (3, 6): 'b11',
            (3, 7): 'b5',
            (3, 8): 'b4',
            (3, 9): 'b1',
        }[sys.version_info[:2]]
        app_table = toml['tool']['briefcase']['app'][APP]
        app_table.add('macOS', tomlkit.table())
        app_table['macOS']['support_revision'] = revision
        print(
            "patching pyproject.toml to pin support package to revision:",
            revision,
        )

    with open(PYPROJECT_TOML, 'w') as f:
        f.write(tomlkit.dumps(toml))

    try:
        yield
    finally:
        with open(PYPROJECT_TOML, 'w') as f:
            f.write(original_toml)


def patch_dmgbuild():
    if not MACOS:
        return
    from dmgbuild import core

    with open(core.__file__) as f:
        src = f.read()
    with open(core.__file__, 'w') as f:
        f.write(
            src.replace(
                "shutil.rmtree(os.path.join(mount_point, '.Trashes'), True)",
                "shutil.rmtree(os.path.join(mount_point, '.Trashes'), True)"
                ";time.sleep(30)",
            )
        )
        print("patched dmgbuild.core")


def add_site_packages_to_path():
    # on mac, make sure the site-packages folder exists even before the user
    # has pip installed, so it is in sys.path on the first run
    # (otherwise, newly installed plugins will not be detected until restart)
    if MACOS:
        pkgs_dir = os.path.join(
            APP_DIR,
            'Contents',
            'Resources',
            'Support',
            'lib',
            f'python{sys.version_info.major}.{sys.version_info.minor}',
            'site-packages',
        )
        os.makedirs(pkgs_dir)
        print("created site-packages at", pkgs_dir)

    # on windows, briefcase uses a _pth file to determine the sys.path at
    # runtime.  https://docs.python.org/3/using/windows.html#finding-modules
    # We update that file with the eventual location of pip site-packages
    elif WINDOWS:
        py = "".join(map(str, sys.version_info[:2]))
        python_dir = os.path.join(BUILD_DIR, APP, 'src', 'python')
        pth = os.path.join(python_dir, f'python{py}._pth')
        with open(pth, "a") as f:
            # Append 'hello' at the end of file
            f.write(".\\\\Lib\\\\site-packages\n")
        print("added bundled site-packages to", pth)

        pkgs_dir = os.path.join(python_dir, 'Lib', 'site-packages')
        os.makedirs(pkgs_dir)
        print("created site-packages at", pkgs_dir)
        with open(os.path.join(pkgs_dir, 'readme.txt'), 'w') as f:
            f.write("this is where plugin packages will go")


def patch_wxs():
    # must run after briefcase create
    fname = os.path.join(BUILD_DIR, APP, f'{APP}.wxs')

    if os.path.exists(fname):
        with open(fname) as f:
            source = f.read()
        with open(fname, 'w') as f:
            f.write(source.replace('pythonw.exe', 'python.exe'))
            print("patched pythonw.exe -> python.exe")


def patch_python_lib_location():
    # must run after briefcase create
    support = os.path.join(
        BUILD_DIR, APP, APP + ".app", "Contents", "Resources", "Support"
    )
    python_resources = os.path.join(support, "Python", "Resources")
    os.makedirs(python_resources, exist_ok=True)
    for subdir in ("bin", "lib"):
        orig = os.path.join(support, subdir)
        dest = os.path.join(python_resources, subdir)
        os.symlink("../../" + subdir, dest)
        print("symlinking", orig, "to", dest)


def patch_environment_variables():
    os.environ["ARCH"] = architecture()


def architecture():
    arch = platform.machine() or "generic"
    # Try to canonicalize across OS
    replacements = {
        "amd64": "x86_64",
    }
    return replacements.get(arch.lower(), arch)


def make_zip():
    import glob
    import zipfile

    if WINDOWS:
        ext, OS = '*.msi', 'Windows'
    elif LINUX:
        ext, OS = '*.AppImage', 'Linux'
    elif MACOS:
        ext, OS = '*.dmg', 'macOS'
    artifact = glob.glob(os.path.join(BUILD_DIR, ext))[0]
    dest = f'napari-{VERSION}-{OS}-{architecture()}.zip'

    with zipfile.ZipFile(dest, 'w', zipfile.ZIP_DEFLATED) as zf:
        zf.write(artifact, arcname=os.path.basename(artifact))
    print("created zipfile: ", dest)
    return dest


def clean():
    shutil.rmtree(BUILD_DIR, ignore_errors=True)


def bundle():
    clean()

    if MACOS:
        patch_dmgbuild()

    if LINUX:
        patch_environment_variables()

    # smoke test, and build resources
    subprocess.check_call([sys.executable, '-m', APP, '--info'])

    # the briefcase calls need to happen while the pyproject toml is patched
    with patched_toml():
        # create
        cmd = ['briefcase', 'create'] + (['--no-docker'] if LINUX else [])
        subprocess.check_call(cmd)

        time.sleep(0.5)

        add_site_packages_to_path()

        if MACOS:
            patch_python_lib_location()

        # build
        cmd = ['briefcase', 'build'] + (['--no-docker'] if LINUX else [])
        subprocess.check_call(cmd)

        # package
        cmd = ['briefcase', 'package']
        cmd += ['--no-sign'] if MACOS else (['--no-docker'] if LINUX else [])
        subprocess.check_call(cmd)

        # compress
        dest = make_zip()
        clean()

        return dest


if __name__ == "__main__":
    if '--clean' in sys.argv:
        clean()
        sys.exit()
    if '--version' in sys.argv:
        print(VERSION)
        sys.exit()
    if '--arch' in sys.argv:
        print(architecture())
        sys.exit()
    print('created', bundle())

bundle.py
import configparser
import os
import platform
import re
import shutil
import subprocess
import sys
import time
from contextlib import contextmanager

import tomlkit

APP = 'napari'

# EXTRA_REQS will be added to the bundle, in addition to those specified in
# setup.cfg.  To add additional packages to the bundle, or to override any of
# the packages listed here or in `setup.cfg, use the `--add` command line
# argument with a series of "pip install" style strings when running this file.
# For example, the following will ADD ome-zarr, and CHANGE the version of
# PySide2:
# python bundle.py --add 'PySide2==5.15.0' 'ome-zarr'

# This is now defined in setup.cfg "options.extras_require.bundle_run"
# EXTRA_REQS = []

WINDOWS = os.name == 'nt'
MACOS = sys.platform == 'darwin'
LINUX = sys.platform.startswith("linux")
HERE = os.path.abspath(os.path.dirname(__file__))
PYPROJECT_TOML = os.path.join(HERE, 'pyproject.toml')
SETUP_CFG = os.path.join(HERE, 'setup.cfg')


if WINDOWS:
    BUILD_DIR = os.path.join(HERE, 'windows')
elif LINUX:
    BUILD_DIR = os.path.join(HERE, 'linux')
elif MACOS:
    BUILD_DIR = os.path.join(HERE, 'macOS')
    APP_DIR = os.path.join(BUILD_DIR, APP, f'{APP}.app')


with open(os.path.join(HERE, "napari", "_version.py")) as f:
    match = re.search(r'version\s?=\s?\'([^\']+)', f.read())
    if match:
        VERSION = match.groups()[0].split('+')[0]


@contextmanager
def patched_toml():
    parser = configparser.ConfigParser()
    parser.read(SETUP_CFG)
    requirements = parser.get("options", "install_requires").splitlines()
    requirements = [r.split('#')[0].strip() for r in requirements if r]

    with open(PYPROJECT_TOML) as f:
        original_toml = f.read()

    toml = tomlkit.parse(original_toml)

    # Initialize EXTRA_REQS from setup.cfg 'options.extras_require.bundle_run'
    bundle_run = parser.get("options.extras_require", "bundle_run")
    EXTRA_REQS = [
        requirement.split('#')[0].strip()
        for requirement in bundle_run.splitlines()
        if requirement
    ]

    # parse command line arguments
    if '--add' in sys.argv:
        for item in sys.argv[sys.argv.index('--add') + 1 :]:
            if item.startswith('-'):
                break
            EXTRA_REQS.append(item)

    for item in EXTRA_REQS:
        _base = re.split('<|>|=', item, maxsplit=1)[0]
        for r in requirements:
            if r.startswith(_base):
                requirements.remove(r)
                break
        if _base.lower().startswith('pyqt5'):
            try:
                i = next(x for x in requirements if x.startswith('PySide'))
                requirements.remove(i)
            except StopIteration:
                pass

    requirements += EXTRA_REQS

    toml['tool']['briefcase']['app'][APP]['requires'] = requirements
    toml['tool']['briefcase']['version'] = VERSION

    print("patching pyproject.toml to version: ", VERSION)
    print(
        "patching pyproject.toml requirements to:",
        *toml['tool']['briefcase']['app'][APP]['requires'],
        sep="\n ",
    )

    if MACOS:
        # Workaround https://github.com/napari/napari/issues/2965
        # Pin revisions to releases _before_ they switched to static libs
        revision = {
            (3, 6): 'b11',
            (3, 7): 'b5',
            (3, 8): 'b4',
            (3, 9): 'b1',
        }[sys.version_info[:2]]
        app_table = toml['tool']['briefcase']['app'][APP]
        app_table.add('macOS', tomlkit.table())
        app_table['macOS']['support_revision'] = revision
        print(
            "patching pyproject.toml to pin support package to revision:",
            revision,
        )

    with open(PYPROJECT_TOML, 'w') as f:
        f.write(tomlkit.dumps(toml))

    try:
        yield
    finally:
        with open(PYPROJECT_TOML, 'w') as f:
            f.write(original_toml)


def patch_dmgbuild():
    if not MACOS:
        return
    from dmgbuild import core

    with open(core.__file__) as f:
        src = f.read()
    with open(core.__file__, 'w') as f:
        f.write(
            src.replace(
                "shutil.rmtree(os.path.join(mount_point, '.Trashes'), True)",
                "shutil.rmtree(os.path.join(mount_point, '.Trashes'), True)"
                ";time.sleep(30)",
            )
        )
        print("patched dmgbuild.core")


def add_site_packages_to_path():
    # on mac, make sure the site-packages folder exists even before the user
    # has pip installed, so it is in sys.path on the first run
    # (otherwise, newly installed plugins will not be detected until restart)
    if MACOS:
        pkgs_dir = os.path.join(
            APP_DIR,
            'Contents',
            'Resources',
            'Support',
            'lib',
            f'python{sys.version_info.major}.{sys.version_info.minor}',
            'site-packages',
        )
        os.makedirs(pkgs_dir)
        print("created site-packages at", pkgs_dir)

    # on windows, briefcase uses a _pth file to determine the sys.path at
    # runtime.  https://docs.python.org/3/using/windows.html#finding-modules
    # We update that file with the eventual location of pip site-packages
    elif WINDOWS:
        py = "".join(map(str, sys.version_info[:2]))
        python_dir = os.path.join(BUILD_DIR, APP, 'src', 'python')
        pth = os.path.join(python_dir, f'python{py}._pth')
        with open(pth, "a") as f:
            # Append 'hello' at the end of file
            f.write(".\\\\Lib\\\\site-packages\n")
        print("added bundled site-packages to", pth)

        pkgs_dir = os.path.join(python_dir, 'Lib', 'site-packages')
        os.makedirs(pkgs_dir)
        print("created site-packages at", pkgs_dir)
        with open(os.path.join(pkgs_dir, 'readme.txt'), 'w') as f:
            f.write("this is where plugin packages will go")


def patch_wxs():
    # must run after briefcase create
    fname = os.path.join(BUILD_DIR, APP, f'{APP}.wxs')

    if os.path.exists(fname):
        with open(fname) as f:
            source = f.read()
        with open(fname, 'w') as f:
            f.write(source.replace('pythonw.exe', 'python.exe'))
            print("patched pythonw.exe -> python.exe")


def patch_python_lib_location():
    # must run after briefcase create
    support = os.path.join(
        BUILD_DIR, APP, APP + ".app", "Contents", "Resources", "Support"
    )
    python_resources = os.path.join(support, "Python", "Resources")
    os.makedirs(python_resources, exist_ok=True)
    for subdir in ("bin", "lib"):
        orig = os.path.join(support, subdir)
        dest = os.path.join(python_resources, subdir)
        os.symlink("../../" + subdir, dest)
        print("symlinking", orig, "to", dest)


def patch_environment_variables():
    os.environ["ARCH"] = architecture()


def architecture():
    arch = platform.machine() or "generic"
    # Try to canonicalize across OS
    replacements = {
        "amd64": "x86_64",
    }
    return replacements.get(arch.lower(), arch)


def make_zip():
    import glob
    import zipfile

    if WINDOWS:
        ext, OS = '*.msi', 'Windows'
    elif LINUX:
        ext, OS = '*.AppImage', 'Linux'
    elif MACOS:
        ext, OS = '*.dmg', 'macOS'
    artifact = glob.glob(os.path.join(BUILD_DIR, ext))[0]
    dest = f'napari-{VERSION}-{OS}-{architecture()}.zip'

    with zipfile.ZipFile(dest, 'w', zipfile.ZIP_DEFLATED) as zf:
        zf.write(artifact, arcname=os.path.basename(artifact))
    print("created zipfile: ", dest)
    return dest


def clean():
    shutil.rmtree(BUILD_DIR, ignore_errors=True)


def bundle():
    clean()

    if MACOS:
        patch_dmgbuild()

    if LINUX:
        patch_environment_variables()

    # smoke test, and build resources
    subprocess.check_call([sys.executable, '-m', APP, '--info'])

    # the briefcase calls need to happen while the pyproject toml is patched
    with patched_toml():
        # create
        cmd = ['briefcase', 'create'] + (['--no-docker'] if LINUX else [])
        subprocess.check_call(cmd)

        time.sleep(0.5)

        add_site_packages_to_path()

        if WINDOWS:
            patch_wxs()
        elif MACOS:
            patch_python_lib_location()

        # build
        cmd = ['briefcase', 'build'] + (['--no-docker'] if LINUX else [])
        subprocess.check_call(cmd)

        # package
        cmd = ['briefcase', 'package']
        cmd += ['--no-sign'] if MACOS else (['--no-docker'] if LINUX else [])
        subprocess.check_call(cmd)

        # compress
        dest = make_zip()
        clean()

        return dest


if __name__ == "__main__":
    if '--clean' in sys.argv:
        clean()
        sys.exit()
    if '--version' in sys.argv:
        print(VERSION)
        sys.exit()
    if '--arch' in sys.argv:
        print(architecture())
        sys.exit()
    print('created', bundle())

bundle.py
diff --git a/bundle.py b/bundle.py
index b744e714688..24916fda93c 100644
--- a/bundle.py
+++ b/bundle.py
@@ -263,7 +263,9 @@ def bundle():
 
         add_site_packages_to_path()
 
-        if MACOS:
+        if WINDOWS:
+            patch_wxs()
+        elif MACOS:
             patch_python_lib_location()
 
         # build
diff --git a/docs/release/release_0_4_12.md b/docs/release/release_0_4_12.md
index d582ef28c18..22428f124fd 100644
--- a/docs/release/release_0_4_12.md
+++ b/docs/release/release_0_4_12.md
@@ -16,8 +16,8 @@ This is a bug fix release with many minor improvements and bug fixes. The user
 experience for users of dask arrays might be significantly improved by a new
 approach to setting the contrast limits based on the current slice (#3425).
 
-A progress bar will now display when opening multiple files (#3355). 
-Thanks to first-time contributor @tibuch the data type of labels layers can now 
+A progress bar will now display when opening multiple files (#3355).
+Thanks to first-time contributor @tibuch the data type of labels layers can now
 be converted from a context menu on the layer list (#3402).
 
 See the full list of merged pull requests below for further delails!
@@ -57,6 +57,7 @@ See the full list of merged pull requests below for further delails!
 - Finds layer_controls based on layer's MRO (#3471)
 - Use `ensure_main_thread` instead of custom thread propagation mechanism in NapariQtNotification (#3473)
 - Drop pythonw patch in windows bundle (#3479)
+- Revert "drop pythonw patch (#3479)" (#3501)
 
 
 ## Bug Fixes

In Napari Windows bundle console does not show
## 🐛 Bug
from https://napari.zulipchat.com/#narrow/stream/215289-release/topic/0.2E4.2E12.20bugfix.20release/near/258449904
```
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~\AppData\Local\Programs\napari\\app\napari\utils\action_manager.py in ()
     51     def callable(self, context):
     52         if not hasattr(self, '_command_with_context'):
---> 53             self._command_with_context = lambda: call_with_context(
        global self._command_with_context = undefined
        global call_with_context = 
        global self.command = undefined
        global context = undefined
     54                 self.command, context
     55             )

~\AppData\Local\Programs\napari\\app\napari\utils\action_manager.py in call_with_context(function=>, context=)
     40     ]
     41     ctx = {k: v for k, v in context.items() if k in context_keys}
---> 42     return function(**ctx)
        function = >
        ctx = {}
     43 
     44 

~\AppData\Local\Programs\napari\\app\napari\_qt\qt_viewer.py in toggle_console_visibility(self=, event=None)
    735         """
    736         # force instantiation of console if not already instantiated
--> 737         _ = self.console
        _ = undefined
        self.console = undefined
    738 
    739         viz = not self.dockConsole.isVisible()

~\AppData\Local\Programs\napari\\app\napari\_qt\qt_viewer.py in console(self=)
    344                 with warnings.catch_warnings():
    345                     warnings.filterwarnings("ignore")
--> 346                     self.console = QtConsole(self.viewer)
        self.console = undefined
        QtConsole = 
        self.viewer = Viewer(axes=Axes(visible=False, labels=True, colored=True, dashed=False, arrows=True), camera=Camera(center=(0.0, 0.0, 0.0), zoom=1.0, angles=(0.0, 0.0, 90.0), perspective=0.0, interactive=True), cursor=Cursor(position=(1.0, 1.0), scaled=True, size=1, style=), dims=Dims(ndim=2, ndisplay=2, last_used=0, range=((0, 2, 1), (0, 2, 1)), current_step=(0, 0), order=(0, 1), axis_labels=('0', '1')), grid=GridCanvas(stride=1, shape=(-1, -1), enabled=False), layers=[], scale_bar=ScaleBar(visible=False, colored=False, ticks=True, position=, font_size=10.0, unit=None), text_overlay=TextOverlay(visible=False, color= (4,) float64, font_size=10.0, position=, text=''), help='', status='Ready', tooltip=Tooltip(visible=False, text=''), theme='light', title='napari', mouse_move_callbacks=[], mouse_drag_callbacks=[], mouse_double_click_callbacks=[], mouse_wheel_callbacks=[], _persisted_mouse_event={}, _mouse_drag_gen={}, _mouse_wheel_gen={}, keymap={'Control-Shift-C': >})
    347                     self.console.push(
    348                         {'napari': napari, 'action_manager': action_manager}

~\AppData\Local\Programs\napari\\app_packages\napari_console\qt_console.py in __init__(self=, viewer=Viewer(axes=Axes(visible=False, labels=True, col...QtViewer(0x2050140a150) at 0x000002050214BE40>>}))
     90             # kernel.
     91             kernel_manager = QtInProcessKernelManager()
---> 92             kernel_manager.start_kernel(show_banner=False)
        kernel_manager.start_kernel = >
        global show_banner = undefined
     93             kernel_manager.kernel.gui = 'qt'
     94 

~\AppData\Local\Programs\napari\\app_packages\ipykernel\inprocess\manager.py in start_kernel(self=, **kwds={'show_banner': False})
     44     def start_kernel(self, **kwds):
     45         from ipykernel.inprocess.ipkernel import InProcessKernel
---> 46         self.kernel = InProcessKernel(parent=self, session=self.session)
        self.kernel = None
        InProcessKernel = 
        global parent = undefined
        self = 
        global session = undefined
        self.session = 
     47 
     48     def shutdown_kernel(self):

~\AppData\Local\Programs\napari\\app_packages\ipykernel\inprocess\ipkernel.py in __init__(self=, **traits={'parent': , 'session': })
     70 
     71     def __init__(self, **traits):
---> 72         super(InProcessKernel, self).__init__(**traits)
        global super = undefined
        global InProcessKernel = 
        self.__init__ = >
        traits = {'parent': , 'session': }
     73 
     74         self._underlying_iopub_socket.observe(self._io_dispatch, names=['message_sent'])

~\AppData\Local\Programs\napari\\app_packages\ipykernel\ipkernel.py in __init__(self=, **kwargs={'parent': , 'session': })
     87 
     88         # Initialize the InteractiveShell subclass
---> 89         self.shell = self.shell_class.instance(parent=self,
        self.shell = None
        self.shell_class.instance = >
        global parent = undefined
        self = 
        global profile_dir = undefined
        self.profile_dir = None
        global user_module = undefined
        self.user_module = None
        global user_ns = undefined
        self.user_ns = None
        global kernel = undefined
        global compiler_class = undefined
        global XCachingCompiler = 
     90             profile_dir = self.profile_dir,
     91             user_module = self.user_module,

~\AppData\Local\Programs\napari\\app_packages\traitlets\config\configurable.py in instance(cls=, *args=(), **kwargs={'compiler_class': , 'kernel': , 'parent': , 'profile_dir': None, 'user_module': None, 'user_ns': None})
    538         # Create and save the instance
    539         if cls._instance is None:
--> 540             inst = cls(*args, **kwargs)
        inst = undefined
        cls = 
        args = ()
        kwargs = {'parent': , 'profile_dir': None, 'user_module': None, 'user_ns': None, 'kernel': , 'compiler_class': }
    541             # Now make sure that the instance will also be returned by
    542             # parent classes' _instance attribute.

~\AppData\Local\Programs\napari\\app_packages\IPython\core\interactiveshell.py in __init__(self=, ipython_dir=None, profile_dir=None, user_module=None, user_ns=None, custom_exceptions=((), None), **kwargs={'compiler_class': , 'kernel': , 'parent': })
    680         # because the traceback handlers hardcode the stdout/stderr streams.
    681         # This logic in in debugger.Pdb and should eventually be changed.
--> 682         self.init_io()
        self.init_io = >
    683         self.init_traceback_handlers(custom_exceptions)
    684         self.init_prompts()

~\AppData\Local\Programs\napari\\app_packages\IPython\core\interactiveshell.py in init_io(self=)
    856         with warnings.catch_warnings():
    857             warnings.simplefilter('ignore', DeprecationWarning)
--> 858             io.stdout = io.IOStream(sys.stdout)
        global io.stdout = IPython.utils.io.IOStream()
        global io.IOStream = 
        global sys.stdout = None
    859             io.stderr = io.IOStream(sys.stderr)
    860 

~\AppData\Local\Programs\napari\\app_packages\IPython\utils\io.py in __init__(self= instance, stream=None, fallback=None)
     29                 stream = fallback
     30             else:
---> 31                 raise ValueError("fallback required, but not specified")
        global ValueError = undefined
     32         self.stream = stream
     33         self._swrite = stream.write

ValueError: fallback required, but not specified
```

## To Reproduce

Steps to reproduce the behavior:

1. install last bundle on windows
2. open napari
3. open console

<!-- If you have a code sample, error messages, stack traces, please provide it here as well -->

## Expected behavior

<!-- A clear and concise description of what you expected to happen. -->
working console

## Environment
```
napari: 0.4.12rc1
Platform: Windows-10-10.0.19041-SP0
Python: 3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:37:02) [MSC v.1924 64 bit (AMD64)]
Qt: 5.15.2
PySide2: 5.15.2
NumPy: 1.19.3
SciPy: 1.7.1
Dask: 2021.09.1
VisPy: 0.9.1

OpenGL:
- GL version: 4.6.0 NVIDIA 456.71
- MAX_TEXTURE_SIZE: 32768

Screens:
- screen 1: resolution 2560x1440, scale 1.0
- screen 2: resolution 1920x1080, scale 1.0
- screen 3: resolution 1920x1080, scale 1.0

Plugins:
- console: 0.0.4
- scikit-image
- svg: 0.1.5
```

## Additional context

<!-- Add any other context about the problem here. -->

