Skip to content
This issue has been moved to a discussionGo to the discussion

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

Closed
@Anti-user

Description

@Anti-user

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?

Activity

euri10

euri10 commented on Sep 5, 2019

@euri10
Contributor
dmontagu

dmontagu commented on Sep 5, 2019

@dmontagu
Collaborator

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

sandys commented on Sep 17, 2019

@sandys

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

euri10 commented on Sep 17, 2019

@euri10
Contributor
dmontagu

dmontagu commented on Sep 17, 2019

@dmontagu
Collaborator

@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

sandys commented on Sep 17, 2019

@sandys
dmontagu

dmontagu commented on Sep 17, 2019

@dmontagu
Collaborator

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

sandys commented on Sep 17, 2019

@sandys
dmontagu

dmontagu commented on Sep 17, 2019

@dmontagu
Collaborator

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

dmontagu commented on Sep 17, 2019

@dmontagu
Collaborator

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

euri10

euri10 commented on Sep 17, 2019

@euri10
Contributor

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

sandys commented on Sep 20, 2019

@sandys

@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

euri10 commented on Sep 20, 2019

@euri10
Contributor

@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

dmontagu commented on Sep 20, 2019

@dmontagu
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.

21 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @sandys@euri10@tiangolo@Shackelford-Arden@imsedim

        Issue actions

          How can I pass the configuration in the app? · Issue #508 · fastapi/fastapi