diff --git a/Lib/tkinter/filedialog.py b/Lib/tkinter/filedialog.py index e2eff98e601c07..6301b8e21b5d70 100644 --- a/Lib/tkinter/filedialog.py +++ b/Lib/tkinter/filedialog.py @@ -384,10 +384,41 @@ def askopenfilename(**options): return Open(**options).show() + + def asksaveasfilename(**options): - "Ask for a filename to save as" + """Ask for a filename to save as, handling missing extensions properly.""" + + # If no default extension is provided, set it to the first valid filetype extension + if "defaultextension" not in options: + filetypes = options.get("filetypes") + if filetypes: + first_ext = filetypes[0][1] # Example: "*.txt" from ("Text files", "*.txt") + + # Validate first_ext: Must start with '*' and contain only one '.' + if first_ext.startswith("*") and first_ext.count(".") == 1: + ext = first_ext[1:] # Extract ".txt" + + # Ensure the extracted extension is a valid one (not patterns like "*.a[0-9]") + if ext.isalnum() or (ext.startswith(".") and ext[1:].isalnum()): + options["defaultextension"] = ext # Set default extension + + filename = SaveAs(**options).show() + + # Append extension if missing, but allow extensionless filenames + if filename: + # Ensure filename is not a relative path ("../", "./", "/absolute/path") + if not os.path.isabs(filename) and not filename.startswith((".", "..")): + # Check if there's no explicit extension + if '.' not in os.path.basename(filename): + ext = options.get("defaultextension", "") + if ext and not filename.endswith(ext): + filename += ext + + return filename + + - return SaveAs(**options).show() def askopenfilenames(**options):