@@ -2067,31 +2067,48 @@ def MkTempFile(data=None,
20672067 prefix=__program_name__,
20682068 delete=True,
20692069 encoding="utf-8",
2070- newline=None, # text mode only; in-memory objects ignore newline semantics
2070+ newline=None,
2071+ text_errors="strict",
20712072 dir=None,
20722073 suffix="",
20732074 use_spool=__use_spoolfile__,
2075+ autoswitch_spool=False,
20742076 spool_max=__spoolfile_size__,
2075- spool_dir=__use_spooldir__):
2077+ spool_dir=__use_spooldir__,
2078+ reset_to_start=True,
2079+ memfd_name=None,
2080+ memfd_allow_sealing=False,
2081+ memfd_flags_extra=0,
2082+ on_create=None):
20762083 """
20772084 Return a file-like handle with consistent behavior on Py2.7 and Py3.x.
20782085
20792086 Storage:
2080- - inmem=True -> BytesIO (bytes) or StringIO (text), or memfd for bytes if available
2081- - inmem=False, use_spool=True -> SpooledTemporaryFile (binary), optionally TextIOWrapper for text
2082- - inmem=False, use_spool=False -> NamedTemporaryFile (binary), optionally TextIOWrapper for text
2087+ - inmem=True, usememfd=True, isbytes=True and memfd available
2088+ -> memfd-backed anonymous file (binary)
2089+ - inmem=True, otherwise
2090+ -> BytesIO (bytes) or StringIO (text)
2091+ - inmem=False, use_spool=True
2092+ -> SpooledTemporaryFile (binary), optionally TextIOWrapper for text
2093+ - inmem=False, use_spool=False
2094+ -> NamedTemporaryFile (binary), optionally TextIOWrapper for text
20832095
20842096 Text vs bytes:
20852097 - isbytes=True -> file expects bytes; 'data' must be bytes-like
2086- - isbytes=False -> file expects text; 'data' must be text (unicode/str). Newline translation and encoding
2087- apply only for spooled/named files (not BytesIO/StringIO).
2098+ - isbytes=False -> file expects text; 'data' must be text (unicode/str). Newline translation and
2099+ encoding apply only for spooled/named files (not BytesIO/StringIO).
20882100
20892101 Notes:
2090- - On Windows, NamedTemporaryFile(delete=True) keeps the file open and cannot be reopened by other processes.
2091- Use delete=False if you need to pass the path elsewhere.
2092- - For text: in-memory StringIO ignores 'newline' (as usual).
2093- - When available, memfd is used only for inmem=True and isbytes=True, providing an anonymous in-memory
2094- file descriptor (Linux-only). Text in-memory still uses StringIO to preserve newline semantics.
2102+ - On Windows, NamedTemporaryFile(delete=True) keeps the file open and cannot be reopened by
2103+ other processes. Use delete=False if you need to pass the path elsewhere.
2104+ - For text: in-memory StringIO ignores 'newline' and 'text_errors' (as usual).
2105+ - When available, and if usememfd=True, memfd is used only for inmem=True and isbytes=True,
2106+ providing an anonymous in-memory file descriptor (Linux-only). Text in-memory still uses
2107+ StringIO to preserve newline semantics.
2108+ - If autoswitch_spool=True and initial data size exceeds spool_max, in-memory storage is
2109+ skipped and a spooled file is used instead (if use_spool=True).
2110+ - If on_create is not None, it is called as on_create(fp, kind) where kind is one of:
2111+ "memfd", "bytesio", "stringio", "spool", "disk".
20952112 """
20962113
20972114 # -- sanitize simple params (avoid None surprises) --
@@ -2123,39 +2140,65 @@ def MkTempFile(data=None,
21232140 else:
21242141 init = None
21252142
2143+ # Size of init for autoswitch; only meaningful for bytes
2144+ init_len = len(init) if (init is not None and isbytes) else None
2145+
21262146 # -------- In-memory --------
21272147 if inmem:
2128- # Use memfd only for bytes, and only where available (Linux, Python 3.8+)
2129- if usememfd and isbytes and hasattr(os, "memfd_create"):
2130- flags = 0
2131- # Close-on-exec is almost always what you want for temps
2132- if hasattr(os, "MFD_CLOEXEC"):
2133- flags |= os.MFD_CLOEXEC
2134-
2135- fd = os.memfd_create(prefix, flags)
2136- # Binary read/write file-like object backed by RAM
2137- f = os.fdopen(fd, "w+b")
2138-
2139- if init is not None:
2140- f.write(init)
2141- f.seek(0)
2142- return f
2148+ # If autoswitch is enabled and data is larger than spool_max, and
2149+ # spooling is allowed, skip the in-memory branch and fall through
2150+ # to the spool/disk logic below.
2151+ if autoswitch_spool and use_spool and init_len is not None and init_len > spool_max:
2152+ pass # fall through to spool/disk sections
2153+ else:
2154+ # Use memfd only for bytes, and only where available (Linux, Python 3.8+)
2155+ if usememfd and isbytes and hasattr(os, "memfd_create"):
2156+ name = memfd_name or prefix or "MkTempFile"
2157+ flags = 0
2158+ # Close-on-exec is almost always what you want for temps
2159+ if hasattr(os, "MFD_CLOEXEC"):
2160+ flags |= os.MFD_CLOEXEC
2161+ # Optional sealing support if requested and available
2162+ if memfd_allow_sealing and hasattr(os, "MFD_ALLOW_SEALING"):
2163+ flags |= os.MFD_ALLOW_SEALING
2164+ # Extra custom flags (e.g. hugepage flags) if caller wants them
2165+ if memfd_flags_extra:
2166+ flags |= memfd_flags_extra
2167+
2168+ fd = os.memfd_create(name, flags)
2169+ # Binary read/write file-like object backed by RAM
2170+ f = os.fdopen(fd, "w+b")
2171+
2172+ if init is not None:
2173+ f.write(init)
2174+ if reset_to_start:
2175+ f.seek(0)
2176+
2177+ if on_create is not None:
2178+ on_create(f, "memfd")
2179+ return f
2180+
2181+ # Fallback: pure Python in-memory objects
2182+ if isbytes:
2183+ f = io.BytesIO(init if init is not None else b"")
2184+ kind = "bytesio"
2185+ else:
2186+ # newline/text_errors not enforced for StringIO; matches stdlib semantics
2187+ f = io.StringIO(init if init is not None else "")
2188+ kind = "stringio"
21432189
2144- # Fallback: pure Python in-memory objects
2145- if isbytes:
2146- f = io.BytesIO(init if init is not None else b"")
2147- else:
2148- # newline not enforced for StringIO; matches stdlib semantics
2149- f = io.StringIO(init if init is not None else "")
2190+ if reset_to_start:
2191+ f.seek(0)
21502192
2151- f.seek(0)
2152- return f
2193+ if on_create is not None:
2194+ on_create(f, kind)
2195+ return f
21532196
21542197 # Helper: wrap a binary file into a text file with encoding/newline
21552198 def _wrap_text(handle):
21562199 # For both Py2 & Py3, TextIOWrapper gives consistent newline/encoding behavior
2157- tw = io.TextIOWrapper(handle, encoding=encoding, newline=newline)
2158- return tw
2200+ return io.TextIOWrapper(handle, encoding=encoding,
2201+ newline=newline, errors=text_errors)
21592202
21602203 # -------- Spooled (RAM then disk) --------
21612204 if use_spool:
@@ -2166,19 +2209,31 @@ def _wrap_text(handle):
21662209
21672210 if init is not None:
21682211 f.write(init)
2212+ if reset_to_start:
2213+ f.seek(0)
2214+ elif reset_to_start:
21692215 f.seek(0)
2216+
2217+ if on_create is not None:
2218+ on_create(f, "spool")
21702219 return f
21712220
21722221 # -------- On-disk temp (NamedTemporaryFile) --------
21732222 # Always create binary file; wrap for text if needed for uniform Py2/3 behavior
2174- b = tempfile.NamedTemporaryFile(mode="w+b", prefix=prefix, suffix=suffix, dir=dir, delete=delete)
2223+ b = tempfile.NamedTemporaryFile(mode="w+b", prefix=prefix, suffix=suffix,
2224+ dir=dir, delete=delete)
21752225 f = b if isbytes else _wrap_text(b)
21762226
21772227 if init is not None:
21782228 f.write(init)
2229+ if reset_to_start:
2230+ f.seek(0)
2231+ elif reset_to_start:
21792232 f.seek(0)
2180- return f
21812233
2234+ if on_create is not None:
2235+ on_create(f, "disk")
2236+ return f
21822237
21832238
21842239def RemoveWindowsPath(dpath):
0 commit comments