{"record":{"id":"c49298eb45578a92","repo":"aio-libs/aiohttp","slug":"wsgi-app-should-be-either-application-or-async-fun","errorCode":null,"errorMessage":"wsgi app should be either Application or async function returning Application, got {self.wsgi}","messagePattern":"wsgi app should be either Application or async function returning Application, got (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"aiohttp/worker.py","lineNumber":83,"sourceCode":"            self.loop.close()\n\n        sys.exit(self.exit_code)\n\n    async def _run(self) -> None:\n        runner = None\n        if isinstance(self.wsgi, Application):\n            app = self.wsgi\n        elif inspect.iscoroutinefunction(self.wsgi) or (\n            sys.version_info < (3, 14) and asyncio.iscoroutinefunction(self.wsgi)  # type: ignore[deprecated]\n        ):\n            wsgi = await self.wsgi()\n            if isinstance(wsgi, web.AppRunner):\n                runner = wsgi\n                app = runner.app\n            else:\n                app = wsgi\n        else:\n            raise RuntimeError(\n                \"wsgi app should be either Application or \"\n                f\"async function returning Application, got {self.wsgi}\"\n            )\n\n        if runner is None:\n            access_log = self.log.access_log if self.cfg.accesslog else None\n            runner = web.AppRunner(\n                app,\n                logger=self.log,\n                keepalive_timeout=self.cfg.keepalive,\n                access_log=access_log,\n                access_log_format=self._get_valid_log_format(\n                    self.cfg.access_log_format\n                ),\n                shutdown_timeout=self.cfg.graceful_timeout / 100 * 95,\n            )\n        await runner.setup()\n","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/worker.py#L65-L101","documentation":"Raised by GunicornWebWorker._run() when self.wsgi (the app object passed to gunicorn) is neither an aiohttp.web.Application instance nor a coroutine function returning an Application (or AppRunner). The gunicorn aiohttp worker only knows how to serve those two shapes; anything else is rejected at worker boot. The %s placeholder echoes the offending value.","triggerScenarios":"Pointing gunicorn at a plain function (not async), a class, a string module path, an async function that returns something other than Application/AppRunner, or a sync factory. Set via `gunicorn ... module:app` where `app` resolves to the wrong type.","commonSituations":"Exporting a factory function but forgetting `async def`; exporting the module or a router instead of the Application; returning a web.Server or TCPSite instead of Application/AppRunner from the factory.","solutions":["Export an Application instance directly: `app = web.Application(); ...` and reference it in the gunicorn command.","Or export an async factory that returns an Application: `async def app(): return web.Application()`.","If returning a pre-configured runner, return a web.AppRunner from the coroutine (it is unwrapped at line 77-79).","Verify with `inspect.iscoroutinefunction(obj)` and `isinstance(obj, web.Application)` before deploying."],"exampleFix":"// before\n# module:app\napp = web.Application  # the class, not an instance -> raises\n\n// after\n# module:app\napp = web.Application()\n# or a factory:\nasync def app():\n    application = web.Application()\n    application.router.add_get('/', handler)\n    return application","handlingStrategy":"validation","validationCode":"import inspect\nfrom aiohttp import web\nassert isinstance(app, web.Application) or inspect.iscoroutinefunction(app), \\\n    f'wsgi must be Application or async factory, got {type(app)}'","typeGuard":"def is_valid_wsgi(obj) -> bool:\n    import inspect\n    from aiohttp import web\n    return isinstance(obj, web.Application) or inspect.iscoroutinefunction(obj)","tryCatchPattern":"# raised at worker boot; fix the exported symbol rather than catching at runtime.\n# In a startup self-test:\ntry:\n    import module\n    assert is_valid_wsgi(getattr(module, 'app'))\nexcept (ImportError, AssertionError) as e:\n    fail_build(f'gunicorn app target invalid: {e}')","preventionTips":["Export an Application instance or an `async def` factory from the module pointed at by gunicorn.","Add a smoke import in CI: load the module and assert the wsgi target type.","If using a factory, ensure it returns web.Application or web.AppRunner."],"tags":["gunicorn","worker","deployment","typing","boot"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}