fastapi icon indicating copy to clipboard operation
fastapi copied to clipboard

Flask python manage.py shell equivalent in Fastapi

Open bharadwajpoduri opened this issue 3 years ago β€’ 14 comments

Is there any equivalent approach for accessing fastapi application from IPython shell context? Like we use python manage.py shell in flask?

bharadwajpoduri avatar Jan 27 '21 17:01 bharadwajpoduri

are you referring to https://flask-script.readthedocs.io/en/latest/? Which looks like it was supplanted by https://flask.palletsprojects.com/en/1.1.x/cli/

Can you use the issue template when creating new issues?

What is it that you are trying to do?

falkben avatar Jan 28 '21 16:01 falkben

@tiangolo registered fastapi-cli on PyPi, so maybe he's planning something around it. :smiling_imp:

We have what we (@ycd) developed on manage-fastapi, is that enough?

Kludex avatar Jan 28 '21 17:01 Kludex

Have been looking into this also. One of the great benefits of Flask I have found is the interactive shell for testing and creating models, etc. https://flask.palletsprojects.com/en/1.1.x/cli/.

I am just starting with Fastapi so not sure yet if there are any limiting factors in creating a similar experience.

jeffthorne avatar Jan 29 '21 16:01 jeffthorne

Hello,

I do not know the idea of ​​the @tiangolo about how the cli will be. As I'm using it in all my jobs, I'm getting up with some people to set up a cli to work on top of a cookiecutter that I set up with a colleague at work. https://github.com/jonatasoli/fastapi-template-cookiecutter

The idea was to have these features:

  • create a new project
  • create a new subapp (see the cookiecutter)
  • run tests
  • run migrations with the versioning message
  • apply migrations
  • Run endpoints at the client level
  • rotate the prospector
  • generate coverage
  • Generate the orphans report
  • Show all routes that have been implemented

I know that there are very specific things in my template, but maybe it was possible to generate a version with the more general features and generate an extension of the general cli with the specific features of my template.

jonatasoli avatar Feb 12 '21 13:02 jonatasoli

any update on this issue??

g3tl0st avatar Dec 22 '21 04:12 g3tl0st

As a workaround you can do this, https://stackoverflow.com/a/70604378/7699859 .

chrishna1 avatar Jan 06 '22 08:01 chrishna1

I'm starting a client lib to fastapi when is minimal functional I check to send PR. https://github.com/jonatasoli/fastcli

jonatasoli avatar Jan 13 '22 02:01 jonatasoli

Would love to see this happen. Coming from a Flask background, having an interactive shell that has access to the whole application is very handy.

gareth-leake avatar May 06 '22 16:05 gareth-leake

I'm start a PR but I was convinced that this is not a good option for fastapi, in flask it has only one server which is Werkzeug where it is totally dependent. In fastapi you have the freedom to use whatever server you want, which makes it impossible to make a cli to access the application, so I think this question can even be closed.

jonatasoli avatar May 06 '22 16:05 jonatasoli

I think the really useful feature a FastAPI shell could provide is resolving dependencies.

For example imagine some class with FastAPI dependencies declared in its init (db connection pool etc), that are themselves cumbersome to initiate.

It would be SUPER useful to be able to enter a repl/shell and initialise this client and have its dependencies resolved in the shell (instead of manually passing them in).

Obviously some finer details like, what to do if one of the dependencies is a Request or Header would need to be worked out.

adamserafini avatar May 28 '22 16:05 adamserafini

I tried this

https://ipython.readthedocs.io/en/stable/api/generated/IPython.html#IPython.start_ipython

import os, sys, threading, IPython

if os.isatty(sys.stdin.fileno()) and os.isatty(sys.stdout.fileno()):
    threading.Thread(target=IPython.start_ipython).start()

# uvicorn.run only works in main thread
uvicorn.run( # ...

in ipython shell. example

await database.fetch_all(table_x.select())

Not enough ?

murata100 avatar Sep 01 '22 09:09 murata100

Thanks for the discussion everyone!

So, it seems this discussion involves several different things, all around CLI apps, but for different purposes. I'll focus on the original issue.

The manage.py described seems to be useful for interacting with mock data that would help simulate the internals of Flask, right? Because it wouldn't be handling an actual HTTP request, but just simulating data.

I see this would be useful in Flask, that it has a magical pseudo-global request object and a g variable, etc. But I don't see how it would really be useful in FastAPI, where everything is just pure code that you can call directly as a function, without needing to have a special type of shell.

If you need to interact with a request object directly, and if you need to see how it behaves, you can actually just create the object in a normal shell, you don't really need a special FastAPI shell or CLI for that.

Now, if you want to debug and explore your code, I would find it much easier and useful to use an integrated debugger: https://fastapi.tiangolo.com/tutorial/debugging/

Also, most of your code would actually be independent of FastAPI. There's no config in the FastAPI app, the docs even recommend using Pydantic directly. You could create instances of the Pydantic models directly. FastAPI doesn't depend on any particular database, so you can use whatever database you want directly, etc.

VS Code and PyCharm both have now pre-configured debuggers for FastAPI, you can put breakpoints, see variable values, execute code in the middle of an actual request, etc.

What use case would a custom shell solve or satisfy better than an integrated debugger in an editor?


About other possible uses for a FastAPI CLI, I have been waiting to see if there's something that could be solved by that that would justify it.

Here's how I think about it and approach it. When there's something that I could build, a CLI, a library, etc. that would normally use/wrap other tools underneath in some way, if learning and using the new tool I build is comparable to learning and using the tools underneath, I prefer to instead teach those tools underneath.

For example, I didn't add things to FastAPI to handle passwords, when they would just wrap passlib, instead I prefer to teach how to use passlib directly.

In many cases, having a CLI that generates code, would require learning the CLI commands, arguments, options, capabilities, etc. And passing almost all the details that you would put in code as parameters. The intention with the design of FastAPI was to remove as much code duplication as possible. That means that there's not much boilerplate. And if the little code that you would write by hand, with an editor that can provide you with autocompletion, syntax errors, etc, you have to pass it as values to parameters in a CLI, then I would think the effort is equivalent, and I would rather just teach people how to write that little code.

Another idea would be to wrap Uvicorn, but again, that would mean people would have to learn how to use this new CLI when they could instead learn how to use Uvicorn. So I prefer to teach how to use Uvicorn instead.

It's also because in these cases it would be easy for people to have to learn this new tool, but then also have to learn the thing underneath, and then they just had to put double the effort in the end, with no real benefit.

When I find something that I consider would save users a lot of time, or would improve code quality, developer experience, etc. then I end up writing that tool. That was the case with Typer, SQLModel, and Asyncer. And actually with FastAPI as well. I avoided building them as much as possible, until I thought that to get those benefits it was unavoidable to build a new tool.


On the other hand, there are many useful valid projects that can work as a CLI, for example, these multiple project generators with different flavors mentioned here. I even have one too. But I would think that it would be a more opinionated thing and more related to a particular type of project than being very specific to FastAPI as a CLI.

Anyway, just a bunch of thoughts, and why I haven't made an official FastAPI CLI. In short, I haven't found yet something that justifies a FastAPI CLI existing. :sweat_smile:

tiangolo avatar Sep 14 '22 13:09 tiangolo

πŸ‘ My use case about a CLI for resolving dependencies above was for a hypothetical situation on a prod server when you need to debug something but where you may not be able to attach a debugger.

But I think your stance is solid, I also can't think of a good "CLI UX" that deals with the problem that Dependencies sometimes need actual requests to instantiate, so I no longer have a good use case for a FastAPI CLI (currently) 😁

adamserafini avatar Sep 15 '22 07:09 adamserafini

(sorry I just realised that my comment automatically removed the answered label: apologies)

adamserafini avatar Sep 15 '22 07:09 adamserafini

Hehe, no worries, thanks for the feedback @adamserafini ! β˜•

tiangolo avatar Nov 20 '22 15:11 tiangolo

In case it helps someone, here is what I'm doing to get a shell, in a fastapi "context" (separate tho), async (you can await things)..

Embedding the whole thing so you see how it can be used, you will need to customize it for your env.

Just save this in it's own file, and remember to install ptpython

#!/usr/bin/env python3

"""
Everything imported and defined in this file will be available in the shell.

We use ptpython instead of ipython because ipython doesnt really have any good
ways to being embedded the way we want.
"""

from ptpython.repl import embed

from backend.services.rabbit.lifetime import init_rabbit, shutdown_rabbit
from backend.services.redis.lifetime import init_redis, shutdown_redis
from backend.db.config import database

from backend.db.models.app_model import *

import asyncio

loop = asyncio.get_event_loop()


async def setup():
    await database.connect()

    try:
        await embed(globals=globals(), return_asyncio_coroutine=True, patch_stdout=True)
    except EOFError:
        loop.stop()


def main():
    asyncio.ensure_future(setup())
    loop.run_forever()
    loop.close()


if __name__ == "__main__":
    main()

xeor avatar Nov 20 '22 21:11 xeor