🐛 Fix hanging shell on exit

The shell was hanging on exit, after at least one ORM query was
performed. The cleanup() task never triggered. It now triggers reliably.
This commit is contained in:
Brian Wiborg 2025-09-28 14:49:53 +02:00
parent 82c39540a9
commit b15ce0b044
No known key found for this signature in database

View file

@ -10,9 +10,6 @@ from ohmyapi.core import scaffolding, runtime
from pathlib import Path from pathlib import Path
app = typer.Typer(help="OhMyAPI — Django-flavored FastAPI scaffolding with tightly integrated TortoiseORM.") app = typer.Typer(help="OhMyAPI — Django-flavored FastAPI scaffolding with tightly integrated TortoiseORM.")
banner = """OhMyAPI Shell | Project: {project_name}
Find your loaded project singleton via identifier: `p`
"""
@app.command() @app.command()
@ -40,52 +37,50 @@ def serve(root: str = ".", host="127.0.0.1", port=8000):
@app.command() @app.command()
def shell(root: str = "."): def shell(root: str = "."):
"""
Launch an interactive IPython shell with the project and apps loaded.
"""
project_path = Path(root).resolve() project_path = Path(root).resolve()
project = runtime.Project(project_path) project = runtime.Project(project_path)
# Ensure the ORM is shutdown banner = f"""
async def close_project(): OhMyAPI Project Shell: {getattr(project.settings, 'PROJECT_NAME', 'MyProject')}
Find your loaded project singleton via identifier: `p`; i.e.: `p.apps`
"""
async def init_and_cleanup():
try:
await project.init_orm()
return True
except Exception as e:
print(f"Failed to initialize ORM: {e}")
return False
async def cleanup():
try: try:
await project.close_orm() await project.close_orm()
print("Tortoise ORM closed successfully.") print("Tortoise ORM closed successfully.")
except Exception as e: except Exception as e:
print(f"Error closing ORM: {e}") print(f"Error closing ORM: {e}")
def cleanup(): loop = asyncio.new_event_loop()
loop = None asyncio.set_event_loop(loop)
try: loop.run_until_complete(init_and_cleanup())
loop = asyncio.get_running_loop()
except RuntimeError:
pass
if loop and loop.is_running():
asyncio.create_task(close_project())
else:
asyncio.run(close_project())
# Ensure the ORM is initialized # Prepare shell vars that are to be directly available
asyncio.run(project.init_orm()) shell_vars = {"p": project}
try: try:
from IPython import start_ipython from IPython import start_ipython
shell_vars = {
"p": project,
}
from traitlets.config.loader import Config from traitlets.config.loader import Config
c = Config() c = Config()
c.TerminalIPythonApp.display_banner = True c.TerminalIPythonApp.display_banner = True
c.TerminalInteractiveShell.banner2 = banner.format(**{ c.TerminalInteractiveShell.banner2 = banner
"project_name": f"{f'{project.settings.PROJECT_NAME} ' if getattr(project.settings, 'PROJECT_NAME', '') else ''}[{Path(project_path).resolve()}]",
})
atexit.register(cleanup)
start_ipython(argv=[], user_ns=shell_vars, config=c) start_ipython(argv=[], user_ns=shell_vars, config=c)
except ImportError: except ImportError:
typer.echo("IPython is not installed. Falling back to built-in Python shell.")
import code import code
atexit.register(cleanup) code.interact(local=shell_vars, banner=banner)
code.interact(local={"p": project}) finally:
loop.run_until_complete(cleanup())
@app.command() @app.command()