[Python-Dev] Winreg update

Paul Prescod paul@prescod.net
Fri, 11 Aug 2000 08:25:27 -0500


I am in transit so I don't have time for a lot of back and forth email
relating to winreg. It also seems that there are a lot of people (let's
call them "back seat coders") who have vague ideas of what they want but
don't want to spend a bunch of time in a long discussion about registry
arcana. Therefore I am endevouring to make it as easy and fast to
contribute to the discussion as possible. 

I'm doing this through a Python Module Proposal format. This can also
serve as the basis of documentation.

This is really easy so I want
some real feedback this time. Distutils people, this means you! Mark! I
would love to hear Bill Tutt, Greg Stein and anyone else who claims some
knowledge of Windows!

If you're one of the people who has asked for winreg in the core then
you should respond. It isn't (IMO) sufficient to put in a hacky API to
make your life easier. You need to give something to get something. You
want windows registry support in the core -- fine, let's do it properly.

Even people with a minimal understanding of the registry should be able
to contribute: the registry isn't rocket surgery. I'll include a short
primer in this email.

All you need to do is read this email and comment on whether you agree
with the overall principle and then give your opinion on fifteen
possibly controversial issues. The "overall principle" is to steal
shamelessly from Microsoft's new C#/VB/OLE/Active-X/CRL API instead of
innovating for Python. That allows us to avoid starting the debate from
scratch. It also eliminates the feature that Mark complained about
(which was a Python-specific innovation).

The fifteen issues are mostly extensions to the API to make it easier
(convenience extensions) or more powerful (completeness extensions).
Many of them are binary: "do this, don't do that." Others are choices:
e.g. "Use tuples", "Use lists", "Use an instance".

I will try to make sense of the various responses. Some issues will have
strong consensus and I'll close those quickly. Others will require more
(hopefully not much!) discussion.

Windows Registry Primer:
========================

There are things called "keys". They aren't like Python keys so don't
think of them that way. Keys have a list of subkeys indexed by name.
Keys also have a list of "values". Values have names. Every value has a
type. In some type-definition syntax:

key is (name: string, 
     subkeys: (string : key), 
     values: (string : value ))

value is ( name: string,
       type: enumeration,
       data: (depends on enumeration) )

That's the basic model. There are various helper facilities provided by
the APIs, but really, the model is as above.

=========================================================================
Python Module Proposal
Title: Windows registry
Version: $Revision: 1.0$
Owner: paul@prescod.net (Paul Prescod)
Python-Version: 2.0
Status: Incomplete

Overview

    It is convenient for Windows users to know that a Python module to
    access the registry is always available whenever Python is installed
    on Windows.  This is especially useful for installation programs.
    There is a Windows registry module from the win32 extensions to
    Python. It is based directly on the original Microsoft APIs. This
    means that there are many backwards compatibility hacks, "reserved"
    parameters and other legacy features that are not interesting to
    most Python programmers. Microsoft is moving to a higher level API
    for languages other than C, as part of Microsoft's Common Runtime
    Library (CRL) initiative. This newer, higher level API serves as
    the basis for the module described herein.

    This higher level API would be implemented in Python and based upon 
    the low-level API. They would not be in competition: a user would 
    choose based on their preferences and needs.

Module Exports

    These are taken directly from the Common Runtime Library:

    ClassesRoot     The Windows Registry base key HKEY_CLASSES_ROOT.
    CurrentConfig   The Windows Registry base key HKEY_CURRENT_CONFIG.
    CurrentUser     The Windows Registry base key HKEY_CURRENT_USER.
    LocalMachine    The Windows Registry base key HKEY_LOCAL_MACHINE.
    CurrentUser     The Windows Registry base key HKEY_CURRENT_USER.
    DynData         The Windows Registry base key HKEY_DYN_DATA.
    PerformanceData The Windows Registry base key HKEY_PERFORMANCE_DATA.
    Users           The Windows Registry base key HKEY_USERS.

    RegistryKey     Registry key class (important class in module)

RegistryKey class Data Members

    These are taken directly from the Common Runtime Library:

    Name            Retrieves the name of the key. 
                    [Issue: full path or just name within parent?]
    SubKeyCount     Retrieves the count of subkeys.
    ValueCount      Retrieves the count of values in the key.

RegistryKey Methods

    These are taken directly from the Common Runtime Library:

    Close()
        Closes this key and flushes it to disk if the contents have 
        been modified.

    CreateSubKey( subkeyname )
        Creates a new subkey or opens an existing subkey.

     [Issue: SubKey_full_path]: Should it be possible to create a subkey 
        deeply:
        >>> LocalMachine.CreateSubKey( r"foo\bar\baz" )

        Presumably the result of this issue would also apply to every
        other method that takes a subkey parameter.

        It is not clear what the CRL API says yet (Mark?). If it says
        "yes" then we would follow it of course. If it says "no" then
        we could still consider the feature as an extension.

       [Yes] allow subkey parameters to be full paths
       [No]  require them to be a single alphanumeric name, no slashes

    DeleteSubKey( subkeyname )
        Deletes the specified subkey. To delete subkeys and all their 
        children (recursively), use DeleteSubKeyTree.

    DeleteSubKeyTree( subkeyname )
        Recursively deletes a subkey and any child subkeys. 

    DeleteValue( valuename )
        Deletes the specified value from this key.

    __cmp__( other )
	Determines whether the specified key is the same key as the
	current key.

    GetSubKeyNames()
        Retrieves an array of strings containing all the subkey names.

    GetValue( valuename )
        Retrieves the specified value.

     Registry types are converted according to the following table:

         REG_NONE: None
         REG_SZ: UnicodeType
         REG_MULTI_SZ: [UnicodeType, UnicodeType, ...]
         REG_DWORD: IntegerType
         REG_DWORD_LITTLE_ENDIAN: IntegerType
         REG_DWORD_BIG_ENDIAN: IntegerType
         REG_EXPAND_SZ: Same as REG_SZ
         REG_RESOURCE_LIST: Same as REG_BINARY
         REG_FULL_RESOURCE_DESCRIPTOR: Same as REG_BINARY
         REG_RESOURCE_REQUIREMENTS_LIST: Same as REG_BINARY
         REG_LINK: Same as REG_BINARY??? [Issue: more info needed!]

         REG_BINARY: StringType or array.array( 'c' )

     [Issue: REG_BINARY Representation]:
         How should binary data be represented as Python data?

         [String] The win32 module uses "string".
         [Array] I propose that an array of bytes would be better.

         One benefit of "binary" is that allows SetValue to detect
         string data as REG_SZ and array.array('c') as REG_BINARY

    [Issue: Type getting method]
         Should there be a companion method called GetType that fetches 
         the type of a registry value? Otherwise client code would not
         be able to distinguish between (e.g.) REG_SZ and 
         REG_SZ_BINARY.

         [Yes] Add GetType( string )
         [No]  Do not add GetType

    GetValueNames()
        Retrieves a list of strings containing all the value names.

    OpenRemoteBaseKey( machinename, name )
        Opens a new RegistryKey that represents the requested key on a 
        foreign machine.

    OpenSubKey( subkeyname )
        Retrieves a subkey.

    SetValue( keyname, value )
        Sets the specified value

	Types are automatically mapped according to the following
	algorithm:

          None: REG_NONE
          String: REG_SZ
          UnicodeType: REG_SZ
          [UnicodeType, UnicodeType, ...]: REG_MULTI_SZ
          [StringType, StringType, ...]: REG_MULTI_SZ
          IntegerType: REG_DWORD
          array.array('c'): REG_BINARY

       [Issue: OptionalTypeParameter]

          Should there be an optional parameter that allows you to
          specify the type explicitly? Presume that the types are 
          constants in the winreg modules (perhaps strings or 
          integers).

          [Yes] Allow other types to be specified
          [No]  People who want more control should use the underlying 
                win32 module.

Proposed Extensions

    The API above is a direct transliteration of the .NET API. It is
    somewhat underpowered in some senses and also is not entirely
    Pythonic. It is a good start as a basis for consensus, however,
    and these proposed extensions can be voted up or down individually.

    Two extensions are just the convenience functions (OpenRemoteKey
    and the top-level functions). Other extensions attempt to extend
    the API to support ALL features of the underlying API so that users
    never have to switch from one API to another to get a particular
    feature.

    Convenience Extension: OpenRemoteKey

        It is not clear to me why Microsoft restricts remote key opening
        to base keys. Why does it not allow a full path like this:

        >>> winreg.OpenRemoteKey( "machinename", 
                             r"HKEY_LOCAL_MACHINE\SOFTWARE\Python" )

        [Issue: Add_OpenRemoteKey]: 
              [Yes] add RemoteKey 
              [No] do not add?

        [Issue: Remove_OpenRemoteBaseKey]
              [Remove] It's redundant!
              [Retain] For backwards compatibility

    Convenience Extension: Top-level Functions

        A huge number of registry-manipulating programs treat the
        registry namespace as "flat" and go directly to the interesting
        registry key.  These top-level functions allow the Python user
        to skip all of the OO key object and get directly to what
        they want:

        key=OpenKey( keypath, machinename=None )
        key=CreateKey( keypath, machinename=None )
        DeleteKey( keypath, machinename=None )
        val=GetValue( keypath, valname, machinename=None )
        SetValue( keypath, valname, valdata, machinename=None )

        [Yes] Add these functions
        [No] Do not add
        [Variant] I like the idea but would change the function
                  signatures


    Completeness Extension: Type names

        If the type extensions are added to SetValue and GetValue then
        we need to decide how to represent types. It is fairly clear
        that they should be represented as constants in the module. The
        names of those constants could be the cryptic (but standard)
        Microsoft names or more descriptive, conventional names.

	Microsoft Names:

            REG_NONE
            REG_SZ
            REG_EXPAND_SZ
            REG_BINARY
            REG_DWORD
            REG_DWORD_LITTLE_ENDIAN
            REG_DWORD_BIG_ENDIAN
            REG_LINK
            REG_MULTI_SZ
            REG_RESOURCE_LIST
            REG_FULL_RESOURCE_DESCRIPTOR
            REG_RESOURCE_REQUIREMENTS_LIST

	Proposed Descriptive Names:

            NONE
            STRING
            EXPANDABLE_TEMPLATE_STRING
            BINARY_DATA
            INTEGER
            LITTLE_ENDIAN_INTEGER
            BIG_ENDIAN_INTEGER
            LINK
            STRING_LIST
            RESOURCE_LIST
            FULL_RESOURCE_DESCRIPTOR
            RESOURCE_REQUIREMENTS_LIST
             
        We could also allow both. One set would be aliases for the
        other.

        [Issue: TypeNames]:
            [MS Names]: Use the Microsoft names
            [Descriptive Names]: Use the more descriptive names
            [Both]: Use both

    Completeness Extension: Type representation

        No matter what the types are called, they must have values.

	The simplest thing would be to use the integers provided by the
	Microsoft header files.  Unfortunately integers are not at all
	self-describing so getting from the integer value to something
	human readable requires some sort of switch statement or mapping.
 
        An alternative is to use strings and map them internally to the 
        Microsoft integer constants.

        A third option is to use object instances. These instances would
        be useful for introspection and would have the following 
        attributes:

            msname (e.g. REG_SZ)
            friendlyname (e.g. String)
            msinteger (e.g. 6 )

        They would have only the following method:

            def __repr__( self ):
                "Return a useful representation of the type object"
                return "<RegType %d: %s %s>" % \
                  (self.msinteger, self.msname, self.friendlyname )

        A final option is a tuple with the three attributes described
        above.

        [Issue: Type_Representation]:
            [Integers]: Use Microsoft integers
            [Strings]: Use string names
            [Instances]: Use object instances with three introspective 
                         attributes
            [Tuples]: Use 3-tuples

    Completeness Extension: Type Namespace

        Should the types be declared in the top level of the module 
        (and thus show up in a "dir" or "from winreg import *") or 
        should they live in their own dictionary, perhaps called 
        "types" or "regtypes". They could also be attributes of some 
        instance.

        [Issue: Type_Namespace]:
            [Module]: winreg.REG_SZ
            [Dictionary]: winreg.types["REG_SZ"]
            [Instance]: winreg.types["REG_SZ"]

    Completeness Extension: Saving/Loading Keys

        The underlying win32 registry API allows the loading and saving
        of keys to filenames. Therefore these could be implemented
        easily as methods:

            def save( self, filename ):
                "Save a key to a filename"
                _winreg.SaveKey( self.keyobj, filename )

            def load( self, subkey, filename ):
                "Load a key from a filename"
                return _winreg.RegLoadKey( self.handle, subkey, 
                                           filename )

            >>> key.OpenSubKey("Python").save( "Python.reg" )
            >>> key.load( "Python", "Python.reg" )

        [Issue: Save_Load_Keys]
            [Yes] Support the saving and loading of keys
            [No]  Do not add these methods

    Completeness Extension: Security Access Flags

        The underlying win32 registry API allows security flags to be
        applied to the OpenKey method. The flags are:

             "KEY_ALL_ACCESS"
             "KEY_CREATE_LINK"
             "KEY_CREATE_SUB_KEY"
             "KEY_ENUMERATE_SUB_KEYS"
             "KEY_EXECUTE"
             "KEY_NOTIFY"
             "KEY_QUERY_VALUE"
             "KEY_READ"
             "KEY_SET_VALUE"

        These are not documented in the underlying API but should be for
        this API. This documentation would be derived from the Microsoft
        documentation. They would be represented as integer or string
        constants in the Python API and used something like this:

        key=key.OpenKey( subkeyname, winreg.KEY_READ )

        [Issue: Security_Access_Flags]
             [Yes] Allow the specification of security access flags.
             [No]  Do not allow this specification.

        [Issue: Security_Access_Flags_Representation]
             [Integer] Use the Microsoft integers
             [String]  Use string values
             [Tuples] Use (string, integer) tuples
             [Instances] Use instances with "name", "msinteger"
                         attributes

        [Issue: Security_Access_Flags_Location]
             [Top-Level] winreg.KEY_READ
             [Dictionary] winreg.flags["KEY_READ"]
             [Instance] winreg.flags.KEY_READ

    Completeness Extension: Flush

        The underlying win32 registry API has a flush method for keys.
        The documentation is as follows:

            """Writes all the attributes of a key to the registry.

            It is not necessary to call RegFlushKey to change a key.
            Registry changes are flushed to disk by the registry using
            its lazy flusher.  Registry changes are also flushed to
            disk at system shutdown.  Unlike \function{CloseKey()}, the
            \function{FlushKey()} method returns only when all the data
            has been written to the registry.  An application should
            only call \function{FlushKey()} if it requires absolute
            certainty that registry changes are on disk."""

    If all completeness extensions are implemented, the author believes
    that this API will be as complete as the underlying API so
    programmers can choose which to use based on familiarity rather 
    than feature-completeness.


-- 
 Paul Prescod - Not encumbered by corporate consensus
"I don't want you to describe to me -- not ever -- what you were doing
to that poor boy to make him sound like that; but if you ever do it
again, please cover his mouth with your hand," Grandmother said.
	-- John Irving, "A Prayer for Owen Meany"