@@ -520,3 +520,187 @@ def test_dotenv_values_file_stream(dotenv_path):
520520 result = dotenv .dotenv_values (stream = f )
521521
522522 assert result == {"a" : "b" }
523+
524+
525+ class TestLoadDotenvUnlinkAfterLoad :
526+ """Test cases for the unlink_after_load parameter in load_dotenv."""
527+
528+ def test_unlink_after_load_true_removes_file (self , tmp_path ):
529+ """Test that file is removed when unlink_after_load=True."""
530+ dotenv_file = tmp_path / ".env"
531+ dotenv_file .write_text ("TEST_VAR=test_value\n " )
532+
533+ # Ensure file exists before loading
534+ assert dotenv_file .exists ()
535+
536+ # Load dotenv with unlink_after_load=True
537+ result = dotenv .load_dotenv (dotenv_path = str (dotenv_file ), unlink_after_load = True )
538+
539+ # Verify loading was successful
540+ assert result is True
541+ assert os .environ .get ("TEST_VAR" ) == "test_value"
542+
543+ # Verify file was removed
544+ assert not dotenv_file .exists ()
545+
546+ # Clean up environment
547+ if "TEST_VAR" in os .environ :
548+ del os .environ ["TEST_VAR" ]
549+
550+ def test_unlink_after_load_false_keeps_file (self , tmp_path ):
551+ """Test that file is kept when unlink_after_load=False (default)."""
552+ dotenv_file = tmp_path / ".env"
553+ dotenv_file .write_text ("TEST_VAR2=test_value2\n " )
554+
555+ # Ensure file exists before loading
556+ assert dotenv_file .exists ()
557+
558+ # Load dotenv with unlink_after_load=False (default)
559+ result = dotenv .load_dotenv (dotenv_path = str (dotenv_file ), unlink_after_load = False )
560+
561+ # Verify loading was successful
562+ assert result is True
563+ assert os .environ .get ("TEST_VAR2" ) == "test_value2"
564+
565+ # Verify file still exists
566+ assert dotenv_file .exists ()
567+
568+ # Clean up environment
569+ if "TEST_VAR2" in os .environ :
570+ del os .environ ["TEST_VAR2" ]
571+
572+ def test_unlink_after_load_default_keeps_file (self , tmp_path ):
573+ """Test that file is kept when unlink_after_load is not specified (default behavior)."""
574+ dotenv_file = tmp_path / ".env"
575+ dotenv_file .write_text ("TEST_VAR3=test_value3\n " )
576+
577+ # Ensure file exists before loading
578+ assert dotenv_file .exists ()
579+
580+ # Load dotenv without specifying unlink_after_load
581+ result = dotenv .load_dotenv (dotenv_path = str (dotenv_file ))
582+
583+ # Verify loading was successful
584+ assert result is True
585+ assert os .environ .get ("TEST_VAR3" ) == "test_value3"
586+
587+ # Verify file still exists (default behavior)
588+ assert dotenv_file .exists ()
589+
590+ # Clean up environment
591+ if "TEST_VAR3" in os .environ :
592+ del os .environ ["TEST_VAR3" ]
593+
594+ def test_unlink_after_load_with_nonexistent_file (self , tmp_path ):
595+ """Test that no error occurs when trying to unlink a non-existent file."""
596+ nonexistent_file = tmp_path / "nonexistent.env"
597+
598+ # Ensure file doesn't exist
599+ assert not nonexistent_file .exists ()
600+
601+ # Load dotenv with unlink_after_load=True on non-existent file
602+ result = dotenv .load_dotenv (dotenv_path = str (nonexistent_file ), unlink_after_load = True )
603+
604+ # Verify loading returns False (no variables loaded)
605+ assert result is False
606+
607+ # Verify no exception was raised and file still doesn't exist
608+ assert not nonexistent_file .exists ()
609+
610+ def test_unlink_after_load_with_stream_ignores_unlink (self , tmp_path ):
611+ """Test that unlink_after_load is ignored when using stream instead of file path."""
612+ dotenv_file = tmp_path / ".env"
613+ dotenv_file .write_text ("TEST_VAR4=test_value4\n " )
614+
615+ # Load using stream with unlink_after_load=True
616+ with open (dotenv_file , 'r' ) as f :
617+ result = dotenv .load_dotenv (stream = f , unlink_after_load = True )
618+
619+ # Verify loading was successful
620+ assert result is True
621+ assert os .environ .get ("TEST_VAR4" ) == "test_value4"
622+
623+ # Verify file still exists (unlink should be ignored with stream)
624+ assert dotenv_file .exists ()
625+
626+ # Clean up environment
627+ if "TEST_VAR4" in os .environ :
628+ del os .environ ["TEST_VAR4" ]
629+
630+ def test_unlink_after_load_with_empty_file (self , tmp_path ):
631+ """Test unlink behavior with empty dotenv file."""
632+ dotenv_file = tmp_path / ".env"
633+ dotenv_file .write_text ("" )
634+
635+ # Ensure file exists before loading
636+ assert dotenv_file .exists ()
637+
638+ # Load empty dotenv with unlink_after_load=True
639+ result = dotenv .load_dotenv (dotenv_path = str (dotenv_file ), unlink_after_load = True )
640+
641+ # Verify loading returns False (no variables loaded)
642+ assert result is False
643+
644+ # Verify file was still removed even though no variables were loaded
645+ assert not dotenv_file .exists ()
646+
647+ def test_unlink_after_load_with_verbose_logging (self , tmp_path , caplog ):
648+ """Test that verbose logging shows unlink operation."""
649+ dotenv_file = tmp_path / ".env"
650+ dotenv_file .write_text ("TEST_VAR5=test_value5\n " )
651+
652+ with caplog .at_level (logging .INFO ):
653+ result = dotenv .load_dotenv (
654+ dotenv_path = str (dotenv_file ),
655+ unlink_after_load = True ,
656+ verbose = True
657+ )
658+
659+ # Verify loading was successful
660+ assert result is True
661+ assert os .environ .get ("TEST_VAR5" ) == "test_value5"
662+
663+ # Verify file was removed
664+ assert not dotenv_file .exists ()
665+
666+ # Verify log message about removal
667+ assert any ("Removed dotenv file" in record .message for record in caplog .records )
668+
669+ # Clean up environment
670+ if "TEST_VAR5" in os .environ :
671+ del os .environ ["TEST_VAR5" ]
672+
673+ def test_unlink_after_load_permission_error (self , tmp_path , caplog , monkeypatch ):
674+ """Test handling of permission errors when unlinking."""
675+ dotenv_file = tmp_path / ".env"
676+ dotenv_file .write_text ("TEST_VAR6=test_value6\n " )
677+
678+ # Mock os.unlink to raise a permission error
679+ original_unlink = os .unlink
680+ def mock_unlink (path ):
681+ if str (dotenv_file ) in str (path ):
682+ raise PermissionError ("Permission denied" )
683+ return original_unlink (path )
684+
685+ monkeypatch .setattr (os , "unlink" , mock_unlink )
686+
687+ with caplog .at_level (logging .WARNING ):
688+ result = dotenv .load_dotenv (
689+ dotenv_path = str (dotenv_file ),
690+ unlink_after_load = True ,
691+ verbose = True
692+ )
693+
694+ # Verify loading was successful despite unlink failure
695+ assert result is True
696+ assert os .environ .get ("TEST_VAR6" ) == "test_value6"
697+
698+ # Verify file still exists due to permission error
699+ assert dotenv_file .exists ()
700+
701+ # Verify warning log message about failed removal
702+ assert any ("Failed to remove dotenv file" in record .message for record in caplog .records )
703+
704+ # Clean up environment
705+ if "TEST_VAR6" in os .environ :
706+ del os .environ ["TEST_VAR6" ]
0 commit comments