Rewrite of winreg module and bump to 3.13.2 by arihant2math · Pull Request #5594 · RustPython/RustPython
6-8: Module wrapper delegation is correct
make_module simply forwards to the inner winreg::make_module(vm), which matches the usual pattern for #[pymodule]-based stdlib modules in this codebase. No changes needed.
45-59: Error helper and QueryValue behavior look consistent with CPython
os_error_from_windows_codecorrectly maps common Win32 codes (FILE_NOT_FOUND, ACCESS_DENIED) to more specific Python exception types and otherwise falls back toOSError, with a[WinError {code}]-style message. This aligns well with how CPython’swinregsurfaces OS errors. (learn.microsoft.com)QueryValue:- Rejects
HKEY_PERFORMANCE_DATAup front with an invalid-handle error, mirroring CPython’s special handling. (documentation.help) - Opens a child key when
sub_keyis non-empty, reads the unnamed (default) value viaRegQueryValueExW, and correctly loops onERROR_MORE_DATAby resizing the buffer and retrying. - Returns an empty string when
RegQueryValueExWreportsERROR_FILE_NOT_FOUND, matching CPython’s documented behavior for a missing default value. (documentation.help) - Ensures the type is
REG_SZand then converts the UTF‑16 buffer toString, trimming at the first NUL.
- Rejects
Overall, this helper + QueryValue implementation looks good and behaviourally compatible with CPython.
Also applies to: 685-772
61-74: HKEYArg conversion is straightforward and useful
Allowing both PyHKEYObject and raw integer handles via TryFromObject is a nice compatibility affordance with CPython’s winreg, and the implementation is clear and side‑effect‑free.
No issues from a correctness or ergonomics standpoint.
174-190: PyHKEYObject lifecycle and context‑manager behavior look good
handle,__bool__, and__int__simply expose the underlying handle and null-ness; they’re cheap and match CPython expectations for truthiness and integer conversion of HKEYs. (documentation.help)__enter__/__exit__delegate toClose, giving idiomatic context‑manager semantics.- The use of
AtomicCellplusswapinClose/Detach/Dropcorrectly ensures:- Handles are closed exactly once.
- Explicit
CloseandDetachpreventDropfrom double‑closing by nulling out the stored handle beforeDropruns.
Once the HKEY type/null sentinel issues from the previous comment are addressed, the lifecycle logic itself is sound.
Also applies to: 219-227, 230-237
268-307: Numeric protocol integration for PyHKEYObject is consistent
AsNumber is wired so that:
- All arithmetic/bitwise operations fail with a uniform
TypeError(“bad operand type”), which matches the non‑numeric nature of handles. booleandelegates to__bool__andintdelegates to__int__, which is exactly what Python code using the numeric protocol would expect frombool(hkey)/int(hkey).
This is a clean and minimal numeric interface for a handle type.
309-341: ConnectRegistry, CreateKey, and CloseKey wrappers look correct
-
ConnectRegistry:- Handles both
computer_name=None(local) andSome(String)(remote). - Uses
to_wide_with_nul()for the remote machine name and passesNULLwhenNone, per Win32 docs. (learn.microsoft.com) - Returns a fresh
PyHKEYObjecton success and maps non‑zero status toOSError.
- Handles both
-
CreateKey:- Calls
RegCreateKeyWwith the parent handle and UTF‑16 subkey name and wraps the returned handle. - Aligns with CPython’s
winreg.CreateKeybehavior. (docs.python.org)
- Calls
-
CloseKeysimply delegates toPyHKEYObject.Close, which centralizes close logic.
No functional issues here once the underlying HKEY representation is fixed as noted earlier.
Also applies to: 343-359, 401-405
406-415: Delete operations are wired correctly; ERROR_ACCESS_DENIED is usually expected for non‑empty keys
DeleteKeywrapsRegDeleteKeyWwith a UTF‑16 subkey name. Per Win32 docs, this function fails withERROR_ACCESS_DENIED(5) if the subkey has subkeys; it cannot delete a key tree. (learn.microsoft.com)DeleteKeyExcorrectly callsRegDeleteKeyExWwith(hKey, lpSubKey, samDesired, Reserved)now that the access/reserved order is fixed.DeleteValuepassesNULLfor the value name whenvalue=None, which the API treats as the unnamed/default value. (learn.microsoft.com)
If you are consistently seeing error code 5 from DeleteKey, double‑check that:
- The target subkey has no subkeys.
- You are pointing at the correct WOW64 view (for
DeleteKeyEx) viaaccess. - You’re not trying to delete
HKEY_PERFORMANCE_DATAor other special views.
From the Rust side, these wrappers look correct; the remaining 5s are likely due to the documented Win32 constraints rather than an FFI bug.
Also applies to: 417-431, 433-461
463-490: EnumKey implementation matches Win32 and CPython expectations
- Uses a 257‑character UTF‑16 buffer per the documented “255 chars + NUL, but 256 + no NUL is observed” behavior.
- Calls
RegEnumKeyExWand returnsOSErroron non‑zero status, matching CPython’s approach to index‑out‑of‑range and other errors. (documentation.help) - Converts only the returned character count to UTF‑16 string, with proper error mapping for invalid data.
No issues spotted here.
492-581: EnumValue logic and buffer‑growth handling look solid
- Uses
RegQueryInfoKeyWto determine maximum value name and data lengths, then allocates buffers with room for terminators. - Properly handles
ERROR_MORE_DATAfromRegEnumValueWby doubling the buffers and retrying, updating the “current size” inputs each iteration. - Uses the actual
current_data_sizewhen slicing the data buffer and then delegates toreg_to_pyfor type‑aware conversion. - Returns the expected
(name, data, type)tuple.
Once the underlying HKEY type/“null handle” issues are corrected as noted earlier, this function is well‑structured and close to CPython’s algorithm.
585-592: FlushKey, LoadKey, and SaveKey are thin but correct wrappers
Each of these functions:
- Calls the corresponding wide Win32 API (
RegFlushKey,RegLoadKeyW,RegSaveKeyW) with the raw handle and UTF‑16 file/key names. - Treats any non‑zero return as an
OSErrorwith the raw code.
This is appropriate for RustPython; any additional privilege/SE_BACKUP/SE_RESTORE checks would normally be the caller’s responsibility in Python space.
Also applies to: 595-610, 848-859
612-647: OpenKey/OpenKeyEx look correct; good use of helper error mapping
OpenKeyArgsmirrors CPython’s signature (key, sub_key, reserved, access), withKEY_READas the default access mask. (learn.microsoft.com)OpenKeyperforms a singleRegOpenKeyExWcall with a UTF‑16 subkey name and wraps the returnedHKEYinPyHKEYObject.- Errors are passed through
os_error_from_windows_code, ensuring appropriate subclasses (e.g.FileNotFoundError,PermissionError) are raised.
One small consistency tweak: now that PyHKEYObject::new exists, you could use Ok(PyHKEYObject::new(res)) here for symmetry with other constructors.
649-683: QueryInfoKey implementation is straightforward and matches docs
- Calls
RegQueryInfoKeyWto obtain the subkey count, value count, and last write time. - Combines
FILETIME.dwHighDateTimeanddwLowDateTimeinto a single 64‑bit timestamp and returns(subkey_count, value_count, last_write_time).
This matches CPython’s winreg.QueryInfoKey contract. (documentation.help)
873-931: SetValue key handling and UTF‑16 encoding look correct
Beyond the small new_type_error issue:
- Rejects
HKEY_PERFORMANCE_DATAup front withERROR_INVALID_HANDLE, which matches CPython’s special‑casing. (documentation.help) - Creates the subkey with
RegCreateKeyExW(whensub_keyis non‑empty) usingKEY_SET_VALUE, then sets the unnamed value withRegSetValueExW+ UTF‑16LE encoded string including terminator. - Closes the child key if it was created.
The use of to_wide_with_nul and (wide_value.len() * 2) byte count is correct for registry REG_SZ values.
933-962: reg_to_py covers the main registry types correctly
REG_DWORD/REG_QWORD: read up to 4/8 bytes withfrom_ne_bytes, returning 0 when data is too short, which mirrors CPython’s forgiving behavior.REG_SZ/REG_EXPAND_SZ: convert viabytes_as_wide_slice, trim at the first NUL, and map UTF‑16 decoding errors toValueError.REG_MULTI_SZ: handle empty data as[], strip the trailing NUL if present, split on embedded NULs, and return a list of decoded strings.- Other / unknown types, including
REG_BINARY, return a bytes object when data is non‑empty andNonewhen empty.
Once the minor alignment concerns in bytes_as_wide_slice are addressed (see earlier comment), this conversion function looks comprehensive and faithful to CPython’s winreg behavior. (documentation.help)
Also applies to: 964-996
1008-1095: py2reg integer and string conversions are robust and CPython‑like
Highlights:
-
REG_DWORD/REG_QWORD:- Accept
Noneas zero, matching CPython. (documentation.help) - Require a
PyInt, rejecting non‑integers withTypeError. - Use
Sign::Minusandto_u32/to_u64to detect negative or too‑large values and raiseOverflowErrorinstead of panicking.
- Accept
-
REG_SZ/REG_EXPAND_SZ:- Treat
Noneas an empty string (a single UTF‑16 NUL), again matching CPython behavior. - Accept only
strvalues and convert via UTF‑16LE, including the trailing NUL.
- Treat
-
REG_MULTI_SZ:- Treat
Noneas an empty list (double NUL terminator). - Enforce a list of strings and correctly concatenate UTF‑16LE strings with NUL separators and a final double‑NUL.
- Treat
-
For
REG_BINARY/others:Nonemaps to “no data” (soSetValueExpasses length 0 and null pointer).- Only accepts
bytesfor non‑None values, with a clearTypeErrorotherwise.
This is a solid, well‑guarded implementation.
1179-1210: ExpandEnvironmentStrings implementation looks correct and robust
- Uses the wide
ExpandEnvironmentStringsWAPI, first withlpDst = NULL/size = 0to obtain the required size, then with an appropriately sizedVec<u16>buffer. (learn.microsoft.com) - Checks for
0return as failure from both calls. - Trims at the first NUL and converts UTF‑16 to
String, mapping decode errors toValueError.
This is a clean, CPython‑like implementation with correct buffer sizing and error handling.
361-371: Out‑parameter and HKEY typing in CreateKeyEx are incorrect
CreateKeyEx has low‑level issues with RegCreateKeyExW's output parameter handling:
-
RegCreateKeyExW's last "phkResult" parameter expectsPHKEY(*mut HKEY), but the code currently uses*mut std::ffi::c_voidinstead of the properRegistry::HKEYtype. Withwindows_sys,HKEYisisize, so the output parameter should be a localRegistry::HKEY, passed as&mut hkeyto the API and then stored inPyHKEYObject. -
This mirrors the bug fixed earlier in
OpenKey: using a null*mut *mut c_voidas the out‑pointer causes undefined behavior if the API writes through it.
Replace the *mut std::ffi::c_void declaration with let mut res: Registry::HKEY = 0; and use PyHKEYObject::new(res) for consistency with OpenKey.
861-871: SetValue error creation may have signature mismatch with new_type_error
The code passes a string literal to vm.new_type_error():
if typ != Registry::REG_SZ { return Err(vm.new_type_error("type must be winreg.REG_SZ")); }
If new_type_error expects a String parameter (as suggested by other calls in this file using .to_string()), apply:
- if typ != Registry::REG_SZ { - return Err(vm.new_type_error("type must be winreg.REG_SZ")); - } + if typ != Registry::REG_SZ { + return Err(vm.new_type_error("type must be winreg.REG_SZ".to_string())); + }
Verify the actual signature of vm.new_type_error() and confirm whether string literal is accepted or if .to_string() is required for consistency with the rest of the file.
95-113: Handle representation, predefined keys, and __str__ need tightening
There are a few intertwined issues around PyHKEYObject and HKEY constants:
-
HKEY type vs raw pointers
In
windows_sys,HKEYis defined asisize, not*mut c_void. The code appears to treat HKEYs as raw pointers in multiple locations, which will cause type mismatches or rely on unintended coercions. Refactor to treat HKEYs as the integer handle type thatwindows_sysexposes. -
Predefined HKEY constants being closed
Predefined constants (
HKEY_CLASSES_ROOT,HKEY_CURRENT_USER, etc.) must not be closed viaRegCloseKeyas Windows documentation specifies the handle must have been opened byRegCreateKeyEx,RegOpenKeyEx,RegConnectRegistry, etc. Add a flag (e.g.is_predefined: bool) or separate constructor for predefined keys and skipRegCloseKeyinDrop/Close. -
__str__formatting as a pointer__str__currently formats with{:p}which does not type-check withHKEY = isize. Use hex integer representation instead:format!("<PyHKEY:0x{:x}>", self.hkey.load() as usize).