[Python-Dev] Winreg update (original) (raw)

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.0Revision: 1.0Revision: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"