@@ -297,13 +297,17 @@ class ScriptInfo:
297297 a bigger role. Typically it's created automatically by the
298298 :class:`FlaskGroup` but you can also manually create it and pass it
299299 onwards as click object.
300+
301+ .. versionchanged:: 3.1
302+ Added the ``load_dotenv_defaults`` parameter and attribute.
300303 """
301304
302305 def __init__ (
303306 self ,
304307 app_import_path : str | None = None ,
305308 create_app : t .Callable [..., Flask ] | None = None ,
306309 set_debug_flag : bool = True ,
310+ load_dotenv_defaults : bool = True ,
307311 ) -> None :
308312 #: Optionally the import path for the Flask application.
309313 self .app_import_path = app_import_path
@@ -314,6 +318,16 @@ def __init__(
314318 #: this script info.
315319 self .data : dict [t .Any , t .Any ] = {}
316320 self .set_debug_flag = set_debug_flag
321+
322+ self .load_dotenv_defaults = get_load_dotenv (load_dotenv_defaults )
323+ """Whether default ``.flaskenv`` and ``.env`` files should be loaded.
324+
325+ ``ScriptInfo`` doesn't load anything, this is for reference when doing
326+ the load elsewhere during processing.
327+
328+ .. versionadded:: 3.1
329+ """
330+
317331 self ._loaded_app : Flask | None = None
318332
319333 def load_app (self ) -> Flask :
@@ -479,23 +493,22 @@ def _set_debug(ctx: click.Context, param: click.Option, value: bool) -> bool | N
479493def _env_file_callback (
480494 ctx : click .Context , param : click .Option , value : str | None
481495) -> str | None :
482- if value is None :
483- return None
484-
485- import importlib
486-
487496 try :
488- importlib . import_module ( " dotenv" )
497+ import dotenv # noqa: F401
489498 except ImportError :
490- raise click .BadParameter (
491- "python-dotenv must be installed to load an env file." ,
492- ctx = ctx ,
493- param = param ,
494- ) from None
499+ # Only show an error if a value was passed, otherwise we still want to
500+ # call load_dotenv and show a message without exiting.
501+ if value is not None :
502+ raise click .BadParameter (
503+ "python-dotenv must be installed to load an env file." ,
504+ ctx = ctx ,
505+ param = param ,
506+ ) from None
507+
508+ # Load if a value was passed, or we want to load default files, or both.
509+ if value is not None or ctx .obj .load_dotenv_defaults :
510+ load_dotenv (value , load_defaults = ctx .obj .load_dotenv_defaults )
495511
496- # Don't check FLASK_SKIP_DOTENV, that only disables automatically
497- # loading .env and .flaskenv files.
498- load_dotenv (value )
499512 return value
500513
501514
@@ -504,7 +517,11 @@ def _env_file_callback(
504517_env_file_option = click .Option (
505518 ["-e" , "--env-file" ],
506519 type = click .Path (exists = True , dir_okay = False ),
507- help = "Load environment variables from this file. python-dotenv must be installed." ,
520+ help = (
521+ "Load environment variables from this file, taking precedence over"
522+ " those set by '.env' and '.flaskenv'. Variables set directly in the"
523+ " environment take highest precedence. python-dotenv must be installed."
524+ ),
508525 is_eager = True ,
509526 expose_value = False ,
510527 callback = _env_file_callback ,
@@ -528,6 +545,9 @@ class FlaskGroup(AppGroup):
528545 directory to the directory containing the first file found.
529546 :param set_debug_flag: Set the app's debug flag.
530547
548+ .. versionchanged:: 3.1
549+ ``-e path`` takes precedence over default ``.env`` and ``.flaskenv`` files.
550+
531551 .. versionchanged:: 2.2
532552 Added the ``-A/--app``, ``--debug/--no-debug``, ``-e/--env-file`` options.
533553
@@ -654,14 +674,11 @@ def make_context(
654674 # when importing, blocking whatever command is being called.
655675 os .environ ["FLASK_RUN_FROM_CLI" ] = "true"
656676
657- # Attempt to load .env and .flask env files. The --env-file
658- # option can cause another file to be loaded.
659- if get_load_dotenv (self .load_dotenv ):
660- load_dotenv ()
661-
662677 if "obj" not in extra and "obj" not in self .context_settings :
663678 extra ["obj" ] = ScriptInfo (
664- create_app = self .create_app , set_debug_flag = self .set_debug_flag
679+ create_app = self .create_app ,
680+ set_debug_flag = self .set_debug_flag ,
681+ load_dotenv_defaults = self .load_dotenv ,
665682 )
666683
667684 return super ().make_context (info_name , args , parent = parent , ** extra )
@@ -684,18 +701,26 @@ def _path_is_ancestor(path: str, other: str) -> bool:
684701 return os .path .join (path , other [len (path ) :].lstrip (os .sep )) == other
685702
686703
687- def load_dotenv (path : str | os .PathLike [str ] | None = None ) -> bool :
688- """Load "dotenv" files in order of precedence to set environment variables.
689-
690- If an env var is already set it is not overwritten, so earlier files in the
691- list are preferred over later files.
704+ def load_dotenv (
705+ path : str | os .PathLike [str ] | None = None , load_defaults : bool = True
706+ ) -> bool :
707+ """Load "dotenv" files to set environment variables. A given path takes
708+ precedence over ``.env``, which takes precedence over ``.flaskenv``. After
709+ loading and combining these files, values are only set if the key is not
710+ already set in ``os.environ``.
692711
693712 This is a no-op if `python-dotenv`_ is not installed.
694713
695714 .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
696715
697- :param path: Load the file at this location instead of searching.
698- :return: ``True`` if a file was loaded.
716+ :param path: Load the file at this location.
717+ :param load_defaults: Search for and load the default ``.flaskenv`` and
718+ ``.env`` files.
719+ :return: ``True`` if at least one env var was loaded.
720+
721+ .. versionchanged:: 3.1
722+ Added the ``load_defaults`` parameter. A given path takes precedence
723+ over default files.
699724
700725 .. versionchanged:: 2.0
701726 The current directory is not changed to the location of the
@@ -715,34 +740,33 @@ def load_dotenv(path: str | os.PathLike[str] | None = None) -> bool:
715740 except ImportError :
716741 if path or os .path .isfile (".env" ) or os .path .isfile (".flaskenv" ):
717742 click .secho (
718- " * Tip: There are .env or .flaskenv files present."
719- ' Do "pip install python-dotenv" to use them.' ,
743+ " * Tip: There are .env files present. Install python-dotenv "
744+ " to use them." ,
720745 fg = "yellow" ,
721746 err = True ,
722747 )
723748
724749 return False
725750
726- # Always return after attempting to load a given path, don't load
727- # the default files.
728- if path is not None :
729- if os .path .isfile (path ):
730- return dotenv .load_dotenv (path , encoding = "utf-8" )
751+ data : dict [str , str | None ] = {}
731752
732- return False
753+ if load_defaults :
754+ for default_name in (".flaskenv" , ".env" ):
755+ if not (default_path := dotenv .find_dotenv (default_name , usecwd = True )):
756+ continue
733757
734- loaded = False
758+ data |= dotenv . dotenv_values ( default_path , encoding = "utf-8" )
735759
736- for name in ( ".env" , ".flaskenv" ):
737- path = dotenv .find_dotenv ( name , usecwd = True )
760+ if path is not None and os . path . isfile ( path ):
761+ data | = dotenv .dotenv_values ( path , encoding = "utf-8" )
738762
739- if not path :
763+ for key , value in data .items ():
764+ if key in os .environ or value is None :
740765 continue
741766
742- dotenv .load_dotenv (path , encoding = "utf-8" )
743- loaded = True
767+ os .environ [key ] = value
744768
745- return loaded # True if at least one file was located and loaded.
769+ return bool ( data ) # True if at least one env var was loaded.
746770
747771
748772def show_server_banner (debug : bool , app_import_path : str | None ) -> None :
0 commit comments