Movatterモバイル変換


[0]ホーム

URL:


Skip to content
Join theFastAPI Cloud waiting list 🚀
Follow@fastapi onX (Twitter) to stay updated
FollowFastAPI onLinkedIn to stay updated
Subscribe to theFastAPI and friends newsletter 🎉
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor

Settings and Environment Variables

In many cases your application could need some external settings or configurations, for example secret keys, database credentials, credentials for email services, etc.

Most of these settings are variable (can change), like database URLs. And many could be sensitive, like secrets.

For this reason it's common to provide them in environment variables that are read by the application.

Tip

To understand environment variables you can readEnvironment Variables.

Types and validation

These environment variables can only handle text strings, as they are external to Python and have to be compatible with other programs and the rest of the system (and even with different operating systems, as Linux, Windows, macOS).

That means that any value read in Python from an environment variable will be astr, and any conversion to a different type or any validation has to be done in code.

PydanticSettings

Fortunately, Pydantic provides a great utility to handle these settings coming from environment variables withPydantic: Settings management.

Installpydantic-settings

First, make sure you create yourvirtual environment, activate it, and then install thepydantic-settings package:

$pipinstallpydantic-settings---> 100%

It also comes included when you install theall extras with:

$pipinstall"fastapi[all]"---> 100%

Create theSettings object

ImportBaseSettings from Pydantic and create a sub-class, very much like with a Pydantic model.

The same way as with Pydantic models, you declare class attributes with type annotations, and possibly default values.

You can use all the same validation features and tools you use for Pydantic models, like different data types and additional validations withField().

fromfastapiimportFastAPIfrompydantic_settingsimportBaseSettingsclassSettings(BaseSettings):app_name:str="Awesome API"admin_email:stritems_per_user:int=50settings=Settings()app=FastAPI()@app.get("/info")asyncdefinfo():return{"app_name":settings.app_name,"admin_email":settings.admin_email,"items_per_user":settings.items_per_user,}

Tip

If you want something quick to copy and paste, don't use this example, use the last one below.

Then, when you create an instance of thatSettings class (in this case, in thesettings object), Pydantic will read the environment variables in a case-insensitive way, so, an upper-case variableAPP_NAME will still be read for the attributeapp_name.

Next it will convert and validate the data. So, when you use thatsettings object, you will have data of the types you declared (e.g.items_per_user will be anint).

Use thesettings

Then you can use the newsettings object in your application:

fromfastapiimportFastAPIfrompydantic_settingsimportBaseSettingsclassSettings(BaseSettings):app_name:str="Awesome API"admin_email:stritems_per_user:int=50settings=Settings()app=FastAPI()@app.get("/info")asyncdefinfo():return{"app_name":settings.app_name,"admin_email":settings.admin_email,"items_per_user":settings.items_per_user,}

Run the server

Next, you would run the server passing the configurations as environment variables, for example you could set anADMIN_EMAIL andAPP_NAME with:

$ADMIN_EMAIL="deadpool@example.com"APP_NAME="ChimichangApp"fastapirunmain.py<span style="color: green;">INFO</span>:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

Tip

To set multiple env vars for a single command just separate them with a space, and put them all before the command.

And then theadmin_email setting would be set to"deadpool@example.com".

Theapp_name would be"ChimichangApp".

And theitems_per_user would keep its default value of50.

Settings in another module

You could put those settings in another module file as you saw inBigger Applications - Multiple Files.

For example, you could have a fileconfig.py with:

frompydantic_settingsimportBaseSettingsclassSettings(BaseSettings):app_name:str="Awesome API"admin_email:stritems_per_user:int=50settings=Settings()

And then use it in a filemain.py:

fromfastapiimportFastAPIfrom.configimportsettingsapp=FastAPI()@app.get("/info")asyncdefinfo():return{"app_name":settings.app_name,"admin_email":settings.admin_email,"items_per_user":settings.items_per_user,}

Tip

You would also need a file__init__.py as you saw inBigger Applications - Multiple Files.

Settings in a dependency

In some occasions it might be useful to provide the settings from a dependency, instead of having a global object withsettings that is used everywhere.

This could be especially useful during testing, as it's very easy to override a dependency with your own custom settings.

The config file

Coming from the previous example, yourconfig.py file could look like:

frompydantic_settingsimportBaseSettingsclassSettings(BaseSettings):app_name:str="Awesome API"admin_email:stritems_per_user:int=50
🤓 Other versions and variants

Tip

Prefer to use theAnnotated version if possible.

frompydantic_settingsimportBaseSettingsclassSettings(BaseSettings):app_name:str="Awesome API"admin_email:stritems_per_user:int=50

Notice that now we don't create a default instancesettings = Settings().

The main app file

Now we create a dependency that returns a newconfig.Settings().

fromfunctoolsimportlru_cachefromtypingimportAnnotatedfromfastapiimportDepends,FastAPIfrom.configimportSettingsapp=FastAPI()@lru_cachedefget_settings():returnSettings()@app.get("/info")asyncdefinfo(settings:Annotated[Settings,Depends(get_settings)]):return{"app_name":settings.app_name,"admin_email":settings.admin_email,"items_per_user":settings.items_per_user,}
🤓 Other versions and variants

Tip

Prefer to use theAnnotated version if possible.

fromfunctoolsimportlru_cachefromfastapiimportDepends,FastAPIfrom.configimportSettingsapp=FastAPI()@lru_cachedefget_settings():returnSettings()@app.get("/info")asyncdefinfo(settings:Settings=Depends(get_settings)):return{"app_name":settings.app_name,"admin_email":settings.admin_email,"items_per_user":settings.items_per_user,}

Tip

We'll discuss the@lru_cache in a bit.

For now you can assumeget_settings() is a normal function.

And then we can require it from thepath operation function as a dependency and use it anywhere we need it.

fromfunctoolsimportlru_cachefromtypingimportAnnotatedfromfastapiimportDepends,FastAPIfrom.configimportSettingsapp=FastAPI()@lru_cachedefget_settings():returnSettings()@app.get("/info")asyncdefinfo(settings:Annotated[Settings,Depends(get_settings)]):return{"app_name":settings.app_name,"admin_email":settings.admin_email,"items_per_user":settings.items_per_user,}
🤓 Other versions and variants

Tip

Prefer to use theAnnotated version if possible.

fromfunctoolsimportlru_cachefromfastapiimportDepends,FastAPIfrom.configimportSettingsapp=FastAPI()@lru_cachedefget_settings():returnSettings()@app.get("/info")asyncdefinfo(settings:Settings=Depends(get_settings)):return{"app_name":settings.app_name,"admin_email":settings.admin_email,"items_per_user":settings.items_per_user,}

Settings and testing

Then it would be very easy to provide a different settings object during testing by creating a dependency override forget_settings:

fromfastapi.testclientimportTestClientfrom.configimportSettingsfrom.mainimportapp,get_settingsclient=TestClient(app)defget_settings_override():returnSettings(admin_email="testing_admin@example.com")app.dependency_overrides[get_settings]=get_settings_overridedeftest_app():response=client.get("/info")data=response.json()assertdata=={"app_name":"Awesome API","admin_email":"testing_admin@example.com","items_per_user":50,}
🤓 Other versions and variants

Tip

Prefer to use theAnnotated version if possible.

fromfastapi.testclientimportTestClientfrom.configimportSettingsfrom.mainimportapp,get_settingsclient=TestClient(app)defget_settings_override():returnSettings(admin_email="testing_admin@example.com")app.dependency_overrides[get_settings]=get_settings_overridedeftest_app():response=client.get("/info")data=response.json()assertdata=={"app_name":"Awesome API","admin_email":"testing_admin@example.com","items_per_user":50,}

In the dependency override we set a new value for theadmin_email when creating the newSettings object, and then we return that new object.

Then we can test that it is used.

Reading a.env file

If you have many settings that possibly change a lot, maybe in different environments, it might be useful to put them on a file and then read them from it as if they were environment variables.

This practice is common enough that it has a name, these environment variables are commonly placed in a file.env, and the file is called a "dotenv".

Tip

A file starting with a dot (.) is a hidden file in Unix-like systems, like Linux and macOS.

But a dotenv file doesn't really have to have that exact filename.

Pydantic has support for reading from these types of files using an external library. You can read more atPydantic Settings: Dotenv (.env) support.

Tip

For this to work, you need topip install python-dotenv.

The.env file

You could have a.env file with:

ADMIN_EMAIL="deadpool@example.com"APP_NAME="ChimichangApp"

Read settings from.env

And then update yourconfig.py with:

frompydantic_settingsimportBaseSettings,SettingsConfigDictclassSettings(BaseSettings):app_name:str="Awesome API"admin_email:stritems_per_user:int=50model_config=SettingsConfigDict(env_file=".env")
🤓 Other versions and variants

Tip

Prefer to use theAnnotated version if possible.

frompydantic_settingsimportBaseSettings,SettingsConfigDictclassSettings(BaseSettings):app_name:str="Awesome API"admin_email:stritems_per_user:int=50model_config=SettingsConfigDict(env_file=".env")

Tip

Themodel_config attribute is used just for Pydantic configuration. You can read more atPydantic: Concepts: Configuration.

Here we define the configenv_file inside of your PydanticSettings class, and set the value to the filename with the dotenv file we want to use.

Creating theSettings only once withlru_cache

Reading a file from disk is normally a costly (slow) operation, so you probably want to do it only once and then reuse the same settings object, instead of reading it for each request.

But every time we do:

Settings()

a newSettings object would be created, and at creation it would read the.env file again.

If the dependency function was just like:

defget_settings():returnSettings()

we would create that object for each request, and we would be reading the.env file for each request. ⚠️

But as we are using the@lru_cache decorator on top, theSettings object will be created only once, the first time it's called. ✔️

fromfunctoolsimportlru_cachefromtypingimportAnnotatedfromfastapiimportDepends,FastAPIfrom.importconfigapp=FastAPI()@lru_cachedefget_settings():returnconfig.Settings()@app.get("/info")asyncdefinfo(settings:Annotated[config.Settings,Depends(get_settings)]):return{"app_name":settings.app_name,"admin_email":settings.admin_email,"items_per_user":settings.items_per_user,}
🤓 Other versions and variants

Tip

Prefer to use theAnnotated version if possible.

fromfunctoolsimportlru_cachefromfastapiimportDepends,FastAPIfrom.importconfigapp=FastAPI()@lru_cachedefget_settings():returnconfig.Settings()@app.get("/info")asyncdefinfo(settings:config.Settings=Depends(get_settings)):return{"app_name":settings.app_name,"admin_email":settings.admin_email,"items_per_user":settings.items_per_user,}

Then for any subsequent call ofget_settings() in the dependencies for the next requests, instead of executing the internal code ofget_settings() and creating a newSettings object, it will return the same object that was returned on the first call, again and again.

lru_cache Technical Details

@lru_cache modifies the function it decorates to return the same value that was returned the first time, instead of computing it again, executing the code of the function every time.

So, the function below it will be executed once for each combination of arguments. And then the values returned by each of those combinations of arguments will be used again and again whenever the function is called with exactly the same combination of arguments.

For example, if you have a function:

@lru_cachedefsay_hi(name:str,salutation:str="Ms."):returnf"Hello{salutation}{name}"

your program could execute like this:

sequenceDiagramparticipant code as Codeparticipant function as say_hi()participant execute as Execute function    rect rgba(0, 255, 0, .1)        code ->> function: say_hi(name="Camila")        function ->> execute: execute function code        execute ->> code: return the result    end    rect rgba(0, 255, 255, .1)        code ->> function: say_hi(name="Camila")        function ->> code: return stored result    end    rect rgba(0, 255, 0, .1)        code ->> function: say_hi(name="Rick")        function ->> execute: execute function code        execute ->> code: return the result    end    rect rgba(0, 255, 0, .1)        code ->> function: say_hi(name="Rick", salutation="Mr.")        function ->> execute: execute function code        execute ->> code: return the result    end    rect rgba(0, 255, 255, .1)        code ->> function: say_hi(name="Rick")        function ->> code: return stored result    end    rect rgba(0, 255, 255, .1)        code ->> function: say_hi(name="Camila")        function ->> code: return stored result    end

In the case of our dependencyget_settings(), the function doesn't even take any arguments, so it always returns the same value.

That way, it behaves almost as if it was just a global variable. But as it uses a dependency function, then we can override it easily for testing.

@lru_cache is part offunctools which is part of Python's standard library, you can read more about it in thePython docs for@lru_cache.

Recap

You can use Pydantic Settings to handle the settings or configurations for your application, with all the power of Pydantic models.

  • By using a dependency you can simplify testing.
  • You can use.env files with it.
  • Using@lru_cache lets you avoid reading the dotenv file again and again for each request, while allowing you to override it during testing.

[8]ページ先頭

©2009-2026 Movatter.jp