Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

How can I pass the configuration in the app? #508

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Anti-user opened this issue Sep 5, 2019 · 28 comments
Closed

How can I pass the configuration in the app? #508

Anti-user opened this issue Sep 5, 2019 · 28 comments
Labels
question Question or problem question-migrate

Comments

@Anti-user
Copy link

Anti-user commented Sep 5, 2019

How can I pass the configuration in the app? Can I do it in some way like it was in Flask with app.config, or this framework manages it differently?

@Anti-user Anti-user added the question Question or problem label Sep 5, 2019
@euri10
Copy link
Contributor

euri10 commented Sep 5, 2019 via email

@dmontagu
Copy link
Collaborator

dmontagu commented Sep 5, 2019

I typically use a subclass of pydantic.BaseSettings so I can read config from the environment, then create an lru_cached function that returns a config instance, then wherever I need access to a config setting I just call the lru_cached function to get the config and read the setting off of it.

@sandys
Copy link

sandys commented Sep 17, 2019

i have the same question here - what is the equivalent of app.config in flask ? https://flask.palletsprojects.com/en/1.1.x/config/

to specific, are there any convenience functions similar to

app.config.from_object('yourapplication.default_settings')
app.config.from_envvar('YOURAPPLICATION_SETTINGS')

we need to load different configurations based on production or staging environment.

@euri10
Copy link
Contributor

euri10 commented Sep 17, 2019 via email

@dmontagu
Copy link
Collaborator

dmontagu commented Sep 17, 2019

@sandys does pydantic BaseSettings not work for you? It allows you to pass values at instantiation time or via environment variable (and is type safe to boot!).

A more complete example:

from functools import lru_cache
from typing import List

from pydantic import BaseSettings


class APISettings(BaseSettings):
    api_v1_route: str = "/api/v1"
    openapi_route: str = "/api/v1/openapi.json"

    backend_cors_origins_str: str = ""  # Should be a comma-separated list of origins

    debug: bool = False
    debug_exceptions: bool = False
    disable_superuser_dependency: bool = False
    include_admin_routes: bool = False

    @property
    def backend_cors_origins(self) -> List[str]:
        return [x.strip() for x in self.backend_cors_origins_str.split(",") if x]

    class Config:
        env_prefix = ""


@lru_cache()
def get_api_settings() -> APISettings:
    return APISettings()  # reads variables from environment

If you want something more fundamentally different as a function of the production vs. staging vs. testing environment (e.g., rather than setting each environment variable differently), you could just put some logic inside get_api_settings based on the environment variable value.

Accessing the config in your app is then as simple as, for example, debug = get_api_settings().debug.

@sandys
Copy link

sandys commented Sep 17, 2019 via email

@dmontagu
Copy link
Collaborator

dmontagu commented Sep 17, 2019

You should not use app.state yet -- I don't think it's even supported currently (@euri10 has a pull request to add support).

However, even if it were supported, I would advise against using app.state for configuration if possible -- it is intended for storing application state, which may change over time, and was created so that that state could be accessed and modified from requests. I am having a hard time imagining a scenario in which accessing/modifying the application configuration in that way is a good idea -- I think in most cases it would be an anti-pattern.

Also, it is not mypy / completion-friendly, which I think is especially useful for config settings.

@sandys
Copy link

sandys commented Sep 17, 2019 via email

@dmontagu
Copy link
Collaborator

dmontagu commented Sep 17, 2019

Is the example I posted above not clear enough?

Without going into all the nuances of everything my utility functions are doing, this is roughly what it looks like when I'm creating my server using configuration:

def get_app() -> FastAPI:
    app_settings = get_app_settings()
    api_settings = get_api_settings()  # see example above

    server = FastAPI(title=app_settings.project_name, openapi_url=api_settings.openapi_route, debug=api_settings.debug)
    server.include_router(get_api_router(), prefix=api_settings.api_v1_route)

    @server.get("/", include_in_schema=False)
    def redirect_to_docs() -> RedirectResponse:
        return RedirectResponse("/docs")

    @server.on_event("startup")
    async def connect_to_database() -> None:
        database = get_database()
        if not database.is_connected:
            await database.connect()

    @server.on_event("shutdown")
    async def shutdown() -> None:
        database = get_database()
        if database.is_connected:
            await database.disconnect()

    static_files_app = StaticFiles(directory=str(app_settings.static_dir))
    server.mount(path=app_settings.static_mount_path, app=static_files_app, name="static")

    setup_api(server, api_settings, use_session_middleware=app_settings.use_session_middleware)
    setup_openapi_schemas(server)
    add_timing_middleware(server, exclude="StaticFiles")
    return server

app = get_app()

(Placing the setup in a function like this makes it easy to modify the configuration and re-instantiate the app with the new configuration in tests.)

@dmontagu
Copy link
Collaborator

Also, pydantic has docs about using BaseSettings that you might find useful.

@euri10
Copy link
Contributor

euri10 commented Sep 17, 2019

Classy and very elegant example of leveraging pydantic @dmontagu as always (I think the checks on whether the db is connected are unnecessary 😉)!
Another option is to use Starlette Config.

@sandys
Copy link

sandys commented Sep 20, 2019

@dmontagu @euri10 which one would you both recommend ? Starlette Config or Pydantic ? Since fastapi is fundamentally based on Starlette, would Starlette Config be the more long-term safer way of doing this ?

@euri10
Copy link
Contributor

euri10 commented Sep 20, 2019

@dmontagu @euri10 which one would you both recommend ? Starlette Config or Pydantic ? Since fastapi is fundamentally based on Starlette, would Starlette Config be the more long-term safer way of doing this ?

I wouldn't recommend Config based only on the fact that FastAPI is based on Starlette since the BaseSettings class is from pydantic and FastAPI relies on it at least as much as it does on Starlette.

FastAPI is not opinionated, find what works best for you, if the real question about safety is : do you think one or the other has more or less chances to be maintained over a long period of time, then I think both have equal chances, if not more for pydantic BaseSettings, but that's pure guess.

@dmontagu
Copy link
Collaborator

@sandys I personally think pydantic's is more flexible, powerful, plays better with autocompletion/IDEs, and the list goes on. If you are already using fastapi, I'd definitely recommend it; might as well take advantage of the dependencies you are already including.

I now use pydantic in most of my projects (even if they aren't based on fastapi) because of how useful BaseModel (and BaseSettings) are.

The starlette config has no nice way of handling type-safety or autocompletion. It's not an unreasonable choice, and if you were only using pydantic for the settings I might drop it in favor of fewer dependencies (then again, I might not, but I'm biased :D). But I think pydantic has a lot to offer here.

Pydantic is very close to a 1.0 release right now (I think the target is some time in the next month); I think it will be safe to depend on pydantic in the long term.

@imsedim
Copy link

imsedim commented Oct 2, 2019

@tiangolo @euri10 Any chance to get app.state pull request #502 fixed and merged? Thanks!

@euri10
Copy link
Contributor

euri10 commented Oct 3, 2019 via email

@dmontagu
Copy link
Collaborator

dmontagu commented Oct 3, 2019

For what it's worth, in the interim you can easily subclass FastAPI and add your own state class:

class State(object):
    """
    Straight out of Starlette
    """

    def __init__(self, state: typing.Dict = None):
        if state is None:
            state = {}
        super(State, self).__setattr__("_state", state)

    def __setattr__(self, key: typing.Any, value: typing.Any) -> None:
        self._state[key] = value

    def __getattr__(self, key: typing.Any) -> typing.Any:
        try:
            return self._state[key]
        except KeyError:
            message = "'{}' object has no attribute '{}'"
            raise AttributeError(message.format(self.__class__.__name__, key))

    def __delattr__(self, key: typing.Any) -> None:
        del self._state[key]

class MyFastAPI(FastAPI):
    if not typing.TYPE_CHECKING:  # ensures you still get init-signature type checking from mypy
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.state = State()

(I have done this myself.)

@imsedim
Copy link

imsedim commented Oct 3, 2019

@dmontagu Good idea! I guess I would subclass FastAPI and then just reuse starlette.datastructures.State directly.

UPD: starlette.requests.State for order version

@tiangolo
Copy link
Member

About config, you don't need to use app.state, you are not limited by that. You can use pure Python, and any package that you want. You can use it, it's available since some versions ago, but you don't have to use it.

You could use Starlette's config or Pydantic BaseSettings. Or even just os.getenv(). I would suggest Pydantic BaseSettings, we'll soon have docs with it.

I would also advise against using app.state for anything, the mentioned PR is already merged and you can use app.state if you want. But an APIRouter wouldn't have access to that state. And I don't really have a use case for anything in app.state yet that is not solvable in a simpler way.

Also, by not having to put anything through app.state makes your logic more independent of FastAPI. That way you could be able to use the same logic and configs in other environments. For example, in a task queue, that doesn't need to have FastAPI installed, but could still read settings from a Pydantic BaseSettings object to connect to a DB, etc.

@sandys
Copy link

sandys commented Feb 10, 2020 via email

@Shackelford-Arden
Copy link

Hmm. Reading through here, I'm getting the sense that it's safe to assume that if I needed access to the settings, say using BaseSettings, I'd need to call my settings method or use Depends to be able to use the app's settings throughout the life of the a request on the relevant endpoints?

@sandys
Copy link

sandys commented Feb 25, 2020

You could use Starlette's config or Pydantic BaseSettings. Or even just os.getenv(). I would suggest Pydantic BaseSettings, we'll soon have docs with it.
Also, by not having to put anything through app.state makes your logic more independent of FastAPI. That way you could be able to use the same logic and configs in other environments. For example, in a task queue, that doesn't need to have FastAPI installed, but could still read settings from a Pydantic BaseSettings object to connect to a DB, etc.

@tiangolo did you have the chance to update docs (or alternatively - "full-stack-fastapi-postgresql") ? would love to use Starlette config system in fastapi (especially in the task-queue usecase ?

it seems that there is a pull-request for pydantic basesettings (fastapi/full-stack-fastapi-template#87), however was wondering if Starlette config is equally viable .

@tiangolo
Copy link
Member

tiangolo commented Apr 4, 2020

Here are the new docs for using Pydantic settings for handling configurations in FastAPI: https://fastapi.tiangolo.com/advanced/settings/ 🎉

@Shackelford-Arden yeah, you could do that. Check the new docs for examples.

@sandys yeah, you could use Starlette's config too, but I recommend Pydantic's Settings. And also it would be the suggested config system for FastAPI as it works the same way as Pydantic models, that FastAPI users already know how to use. 🤓

@sandys
Copy link

sandys commented Apr 4, 2020 via email

@Shackelford-Arden
Copy link

Really though, thanks @tiangolo ! I did already make the move to Pydantic but thank you for adding it here as well!

@sandys
Copy link

sandys commented Apr 4, 2020 via email

@tiangolo
Copy link
Member

tiangolo commented Apr 4, 2020

Great! @Shackelford-Arden

@sandys yeah, of course that's on the backlog 🤓 . I have several things to update there as soon as I can, but I also have a lot of things to handle in FastAPI itself, and a lot of issues to answer... 😰

So, for now, I think this specific issue is solved... ✔️

@github-actions
Copy link
Contributor

Assuming the original issue was solved, it will be automatically closed now. But feel free to add more comments or create new issues.

@tiangolo tiangolo reopened this Feb 28, 2023
@fastapi fastapi locked and limited conversation to collaborators Feb 28, 2023
@tiangolo tiangolo converted this issue into discussion #8056 Feb 28, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
question Question or problem question-migrate
Projects
None yet
Development

No branches or pull requests

7 participants