@@ -2059,31 +2059,48 @@ def MkTempFile(data=None,
20592059 prefix=__program_name__,
20602060 delete=True,
20612061 encoding="utf-8",
2062- newline=None, # text mode only; in-memory objects ignore newline semantics
2062+ newline=None,
2063+ text_errors="strict",
20632064 dir=None,
20642065 suffix="",
20652066 use_spool=__use_spoolfile__,
2067+ autoswitch_spool=False,
20662068 spool_max=__spoolfile_size__,
2067- spool_dir=__use_spooldir__):
2069+ spool_dir=__use_spooldir__,
2070+ reset_to_start=True,
2071+ memfd_name=None,
2072+ memfd_allow_sealing=False,
2073+ memfd_flags_extra=0,
2074+ on_create=None):
20682075 """
20692076 Return a file-like handle with consistent behavior on Py2.7 and Py3.x.
20702077
20712078 Storage:
2072- - inmem=True -> BytesIO (bytes) or StringIO (text), or memfd for bytes if available
2073- - inmem=False, use_spool=True -> SpooledTemporaryFile (binary), optionally TextIOWrapper for text
2074- - inmem=False, use_spool=False -> NamedTemporaryFile (binary), optionally TextIOWrapper for text
2079+ - inmem=True, usememfd=True, isbytes=True and memfd available
2080+ -> memfd-backed anonymous file (binary)
2081+ - inmem=True, otherwise
2082+ -> BytesIO (bytes) or StringIO (text)
2083+ - inmem=False, use_spool=True
2084+ -> SpooledTemporaryFile (binary), optionally TextIOWrapper for text
2085+ - inmem=False, use_spool=False
2086+ -> NamedTemporaryFile (binary), optionally TextIOWrapper for text
20752087
20762088 Text vs bytes:
20772089 - isbytes=True -> file expects bytes; 'data' must be bytes-like
2078- - isbytes=False -> file expects text; 'data' must be text (unicode/str). Newline translation and encoding
2079- apply only for spooled/named files (not BytesIO/StringIO).
2090+ - isbytes=False -> file expects text; 'data' must be text (unicode/str). Newline translation and
2091+ encoding apply only for spooled/named files (not BytesIO/StringIO).
20802092
20812093 Notes:
2082- - On Windows, NamedTemporaryFile(delete=True) keeps the file open and cannot be reopened by other processes.
2083- Use delete=False if you need to pass the path elsewhere.
2084- - For text: in-memory StringIO ignores 'newline' (as usual).
2085- - When available, memfd is used only for inmem=True and isbytes=True, providing an anonymous in-memory
2086- file descriptor (Linux-only). Text in-memory still uses StringIO to preserve newline semantics.
2094+ - On Windows, NamedTemporaryFile(delete=True) keeps the file open and cannot be reopened by
2095+ other processes. Use delete=False if you need to pass the path elsewhere.
2096+ - For text: in-memory StringIO ignores 'newline' and 'text_errors' (as usual).
2097+ - When available, and if usememfd=True, memfd is used only for inmem=True and isbytes=True,
2098+ providing an anonymous in-memory file descriptor (Linux-only). Text in-memory still uses
2099+ StringIO to preserve newline semantics.
2100+ - If autoswitch_spool=True and initial data size exceeds spool_max, in-memory storage is
2101+ skipped and a spooled file is used instead (if use_spool=True).
2102+ - If on_create is not None, it is called as on_create(fp, kind) where kind is one of:
2103+ "memfd", "bytesio", "stringio", "spool", "disk".
20872104 """
20882105
20892106 # -- sanitize simple params (avoid None surprises) --
@@ -2115,39 +2132,65 @@ def MkTempFile(data=None,
21152132 else:
21162133 init = None
21172134
2135+ # Size of init for autoswitch; only meaningful for bytes
2136+ init_len = len(init) if (init is not None and isbytes) else None
2137+
21182138 # -------- In-memory --------
21192139 if inmem:
2120- # Use memfd only for bytes, and only where available (Linux, Python 3.8+)
2121- if usememfd and isbytes and hasattr(os, "memfd_create"):
2122- flags = 0
2123- # Close-on-exec is almost always what you want for temps
2124- if hasattr(os, "MFD_CLOEXEC"):
2125- flags |= os.MFD_CLOEXEC
2126-
2127- fd = os.memfd_create(prefix, flags)
2128- # Binary read/write file-like object backed by RAM
2129- f = os.fdopen(fd, "w+b")
2130-
2131- if init is not None:
2132- f.write(init)
2133- f.seek(0)
2134- return f
2140+ # If autoswitch is enabled and data is larger than spool_max, and
2141+ # spooling is allowed, skip the in-memory branch and fall through
2142+ # to the spool/disk logic below.
2143+ if autoswitch_spool and use_spool and init_len is not None and init_len > spool_max:
2144+ pass # fall through to spool/disk sections
2145+ else:
2146+ # Use memfd only for bytes, and only where available (Linux, Python 3.8+)
2147+ if usememfd and isbytes and hasattr(os, "memfd_create"):
2148+ name = memfd_name or prefix or "MkTempFile"
2149+ flags = 0
2150+ # Close-on-exec is almost always what you want for temps
2151+ if hasattr(os, "MFD_CLOEXEC"):
2152+ flags |= os.MFD_CLOEXEC
2153+ # Optional sealing support if requested and available
2154+ if memfd_allow_sealing and hasattr(os, "MFD_ALLOW_SEALING"):
2155+ flags |= os.MFD_ALLOW_SEALING
2156+ # Extra custom flags (e.g. hugepage flags) if caller wants them
2157+ if memfd_flags_extra:
2158+ flags |= memfd_flags_extra
2159+
2160+ fd = os.memfd_create(name, flags)
2161+ # Binary read/write file-like object backed by RAM
2162+ f = os.fdopen(fd, "w+b")
2163+
2164+ if init is not None:
2165+ f.write(init)
2166+ if reset_to_start:
2167+ f.seek(0)
2168+
2169+ if on_create is not None:
2170+ on_create(f, "memfd")
2171+ return f
2172+
2173+ # Fallback: pure Python in-memory objects
2174+ if isbytes:
2175+ f = io.BytesIO(init if init is not None else b"")
2176+ kind = "bytesio"
2177+ else:
2178+ # newline/text_errors not enforced for StringIO; matches stdlib semantics
2179+ f = io.StringIO(init if init is not None else "")
2180+ kind = "stringio"
21352181
2136- # Fallback: pure Python in-memory objects
2137- if isbytes:
2138- f = io.BytesIO(init if init is not None else b"")
2139- else:
2140- # newline not enforced for StringIO; matches stdlib semantics
2141- f = io.StringIO(init if init is not None else "")
2182+ if reset_to_start:
2183+ f.seek(0)
21422184
2143- f.seek(0)
2144- return f
2185+ if on_create is not None:
2186+ on_create(f, kind)
2187+ return f
21452188
21462189 # Helper: wrap a binary file into a text file with encoding/newline
21472190 def _wrap_text(handle):
21482191 # For both Py2 & Py3, TextIOWrapper gives consistent newline/encoding behavior
2149- tw = io.TextIOWrapper(handle, encoding=encoding, newline=newline)
2150- return tw
2192+ return io.TextIOWrapper(handle, encoding=encoding,
2193+ newline=newline, errors=text_errors)
21512194
21522195 # -------- Spooled (RAM then disk) --------
21532196 if use_spool:
@@ -2158,17 +2201,30 @@ def _wrap_text(handle):
21582201
21592202 if init is not None:
21602203 f.write(init)
2204+ if reset_to_start:
2205+ f.seek(0)
2206+ elif reset_to_start:
21612207 f.seek(0)
2208+
2209+ if on_create is not None:
2210+ on_create(f, "spool")
21622211 return f
21632212
21642213 # -------- On-disk temp (NamedTemporaryFile) --------
21652214 # Always create binary file; wrap for text if needed for uniform Py2/3 behavior
2166- b = tempfile.NamedTemporaryFile(mode="w+b", prefix=prefix, suffix=suffix, dir=dir, delete=delete)
2215+ b = tempfile.NamedTemporaryFile(mode="w+b", prefix=prefix, suffix=suffix,
2216+ dir=dir, delete=delete)
21672217 f = b if isbytes else _wrap_text(b)
21682218
21692219 if init is not None:
21702220 f.write(init)
2221+ if reset_to_start:
2222+ f.seek(0)
2223+ elif reset_to_start:
21712224 f.seek(0)
2225+
2226+ if on_create is not None:
2227+ on_create(f, "disk")
21722228 return f
21732229
21742230
0 commit comments