casement 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
casement/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ __all__ = ["__version__"]
2
+ from .version import version as __version__
casement/__main__.py ADDED
@@ -0,0 +1,16 @@
1
+ """ Enables support for calling the casement cli using `python -m casement`
2
+ """
3
+
4
+ from __future__ import absolute_import
5
+
6
+ import sys
7
+
8
+ import casement.cli
9
+
10
+ if __name__ == '__main__':
11
+ # TODO: move casement cli to click
12
+ # prog_name prevents __main__.py from being shown as the command name in the help
13
+ # text. We don't know the exact command the user passed so we provide a generic
14
+ # `python -m casement` command.
15
+ # sys.exit(casement.cli.main(prog_name="python -m casement"))
16
+ sys.exit(casement.cli.main())
casement/app_id.py ADDED
@@ -0,0 +1,88 @@
1
+ import ctypes
2
+ import os
3
+ from ctypes import wintypes
4
+
5
+ import pythoncom
6
+ from win32com.propsys import propsys
7
+ from win32com.shell import shellcon
8
+
9
+
10
+ class AppId(object):
11
+ @staticmethod
12
+ def for_shortcut(shortcut):
13
+ """Gets the AppUserModel.ID for the given shortcut.
14
+
15
+ This will allow windows to group windows with the same app id on a shortcut
16
+ pinned to the taskbar. Use :py:meth:`AppId.for_application` to set the
17
+ app id for a running application.
18
+ """
19
+ if os.path.exists(shortcut):
20
+ # These imports won't work inside python 2 DCC's
21
+ from win32com.propsys import propsys
22
+
23
+ # Original info from https://stackoverflow.com/a/61714895
24
+ key = propsys.PSGetPropertyKeyFromName("System.AppUserModel.ID")
25
+ store = propsys.SHGetPropertyStoreFromParsingName(shortcut)
26
+ return store.GetValue(key).GetValue()
27
+
28
+ @staticmethod
29
+ def set_for_shortcut(shortcut, app_id):
30
+ """Sets AppUserModel.ID info for a windows shortcut.
31
+
32
+ Note: This doesn't seem to work on a pinned taskbar shortcut. Set it on
33
+ a desktop shortcut then pin that shortcut.
34
+
35
+ This will allow windows to group windows with the same app id on a shortcut
36
+ pinned to the taskbar. Use :py:meth:`AppId.set_for_application` to set
37
+ the app id for a running application.
38
+
39
+ Args:
40
+ shortcut (str): The .lnk filename to set the app id on.
41
+ app_id (str): The app id to set on the shortcut
42
+ """
43
+ if os.path.exists(shortcut):
44
+ # Original info from https://stackoverflow.com/a/61714895
45
+ key = propsys.PSGetPropertyKeyFromName("System.AppUserModel.ID")
46
+ store = propsys.SHGetPropertyStoreFromParsingName(
47
+ shortcut, None, shellcon.GPS_READWRITE, propsys.IID_IPropertyStore
48
+ )
49
+
50
+ new_value = propsys.PROPVARIANTType(app_id, pythoncom.VT_BSTR)
51
+ store.SetValue(key, new_value)
52
+ store.Commit()
53
+
54
+ @staticmethod
55
+ def for_application():
56
+ """Returns the current Windows 7+ app user model id used for taskbar
57
+ grouping."""
58
+
59
+ lp_buffer = wintypes.LPWSTR()
60
+ app_user_model_id = (
61
+ ctypes.windll.shell32.GetCurrentProcessExplicitAppUserModelID
62
+ )
63
+ app_user_model_id(ctypes.cast(ctypes.byref(lp_buffer), wintypes.LPWSTR))
64
+ appid = lp_buffer.value
65
+ ctypes.windll.kernel32.LocalFree(lp_buffer)
66
+ return appid
67
+
68
+ @staticmethod
69
+ def set_for_application(appid, prefix=None):
70
+ """Controls Windows taskbar grouping.
71
+
72
+ Specifies a Explicit App User Model ID that Windows 7+ uses to control
73
+ grouping of windows on the taskbar. This must be set before any ui is
74
+ displayed. The best place to call it is in the first widget to be displayed
75
+ __init__ method.
76
+
77
+ See :py:meth:`AppId.set_for_shortcut` to set the app id on a windows shortcut.
78
+
79
+ Args:
80
+ appid (str): The id of the application. Should use full camel-case.
81
+ http://msdn.microsoft.com/en-us/library/dd378459%28v=vs.85%29.aspx#how
82
+ prefix (str, optional): The prefix attached to the id.
83
+ """
84
+
85
+ # https://stackoverflow.com/a/27872625
86
+ if prefix:
87
+ appid = u'%s.%s' % (prefix, appid)
88
+ return not ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appid)
casement/cli.py ADDED
@@ -0,0 +1,95 @@
1
+ import importlib
2
+ import logging
3
+ import sys
4
+ from argparse import ArgumentParser
5
+
6
+
7
+ class CasementParser(object):
8
+ '''
9
+ To implement a cli for casement, create a submodule with the name of your
10
+ cli command. For example to add a ``casement test echo`` command, you would
11
+ create casement\test.py with the following code::
12
+
13
+ import argparse
14
+ from argparse import ArgumentParser
15
+
16
+ class _CLI(object):
17
+ def __init__(self, args):
18
+ # Parse the command and create the parser casement will use
19
+ parser = ArgumentParser()
20
+ parser.add_argument('command')
21
+ args = parser.parse_args(args)
22
+ # This will be added to args passed to the run command
23
+ self.command = args.command
24
+ # Create self.parser by calling the requested function
25
+ getattr(self, args.command)()
26
+
27
+ def echo(self):
28
+ """Creates a ArgumentParser to handle the echo command as self.parser"""
29
+ self.parser = ArgumentParser(
30
+ usage='casement shortcut test [-h] [-v] text',
31
+ )
32
+ self.parser.add_argument('text', nargs=argparse.REMAINDER)
33
+
34
+ def run(self, args):
35
+ """Do something with the arguments parsed from self.parser"""
36
+ if args.command == 'echo':
37
+ print('ECHO: {}'.format(' '.join(args.text)))
38
+ '''
39
+
40
+ def __init__(self):
41
+ self.target_cli = None
42
+ self._base_parser = ArgumentParser(
43
+ description='Windows management tool',
44
+ usage="casement <command> [<args>]\n\n"
45
+ "Valid commands are:\n"
46
+ " shortcut or sc: Manage windows shortcuts.",
47
+ )
48
+ self._base_parser.add_argument('command', help='Command to run')
49
+ args = self._base_parser.parse_args(sys.argv[1:2])
50
+
51
+ # Convert short command names to full command names
52
+ short_names = {
53
+ 'sc': 'shortcut',
54
+ }
55
+ self.command = short_names.get(args.command, args.command)
56
+ # use dispatch pattern to invoke method with same name
57
+ try:
58
+ module = importlib.import_module(
59
+ '.{}'.format(self.command), package='casement'
60
+ )
61
+ except ImportError:
62
+ self._base_parser.print_help()
63
+ sys.exit(1)
64
+ # parse_args defaults to [1:] for args, but we need to remove
65
+ # the 2 levels of commands for proper validation
66
+ self.args_list = sys.argv[3:]
67
+
68
+ self.target_cli = module._CLI(sys.argv[2:3])
69
+ for action in self.target_cli.parser._actions:
70
+ if action.dest == 'verbose':
71
+ break
72
+ else:
73
+ self.target_cli.parser.add_argument(
74
+ '-v', '--verbose', action='store_true', help='Give more output.'
75
+ )
76
+
77
+ def parse_args(self):
78
+ """Parse sub-command arguments adding global options."""
79
+ args = self.target_cli.parser.parse_args(self.args_list)
80
+ # Add command to the args from the target_cli
81
+ try:
82
+ args.command = self.target_cli.command
83
+ except AttributeError:
84
+ args.command = None
85
+ return args
86
+
87
+
88
+ def main():
89
+ casement = CasementParser()
90
+ args = casement.parse_args()
91
+ logging.basicConfig(
92
+ format='%(message)s', level=logging.DEBUG if args.verbose else logging.INFO
93
+ )
94
+
95
+ casement.target_cli.run(args)
casement/enums.py ADDED
@@ -0,0 +1,22 @@
1
+ from enum import Enum
2
+
3
+
4
+ class FlashTimes(Enum):
5
+ """Windows flags for casement.utils.flashWindow().
6
+
7
+ https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-flashwinfo
8
+ """
9
+
10
+ "'Stop flashing. The system restores the window to its original state.'"
11
+ FLASHW_STOP = 0
12
+ "Flash the window caption."
13
+ FLASHW_CAPTION = 0x00000001
14
+ "Flash the taskbar button."
15
+ FLASHW_TRAY = 0x00000002
16
+ """Flash both the window caption and taskbar button. This is equivalent
17
+ to setting the ``FLASHW_CAPTION | FLASHW_TRAY flags``."""
18
+ FLASHW_ALL = 0x00000003
19
+ "Flash continuously, until the ``FLASHW_STOP`` flag is set."
20
+ FLASHW_TIMER = 0x00000004
21
+ "Flash continuously until the window comes to the foreground."
22
+ FLASHW_TIMERNOFG = 0x0000000C
casement/env_var.py ADDED
@@ -0,0 +1,154 @@
1
+ import logging
2
+ import os
3
+ from contextlib import contextmanager
4
+
5
+ import win32api
6
+ import win32con
7
+ import win32file
8
+ import win32gui
9
+ from six.moves import winreg
10
+ from six.moves.collections_abc import MutableMapping
11
+
12
+ from .registry import REG_LOCATIONS, RegKey
13
+
14
+ _logger_modify = logging.getLogger(__name__ + ".modify")
15
+ _logger_broadcast = logging.getLogger(__name__ + ".broadcast")
16
+
17
+
18
+ class EnvVar(MutableMapping):
19
+ """A dictionary like mapping of environment variables as defined in the
20
+ registry. Stored environment variables are not expanded by default. You can
21
+ use `normalize_path` for various path manipulation like expanding variables
22
+ and dealing with short/long file paths.
23
+
24
+ Arguments:
25
+ system (bool, optional): Controls if this instance modifies system or
26
+ user environment variables.
27
+
28
+ Every time you set or delete a environment variable it will send the
29
+ `WM_SETTINGCHANGE` message notifying other applications that system-wide
30
+ settings have changed.
31
+ See: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-settingchange
32
+
33
+ You can prevent this message from being sent every time you modify env vars
34
+ by using the `EnvVar.delayed_broadcast()` with context:
35
+
36
+ user_env = EnvVar(system=False)
37
+ with EnvVar.delayed_broadcast():
38
+ user_env['VAR_B'] = 'value b'
39
+ user_env['VAR_C'] = 'value c'
40
+ del user_env['VAR_A']
41
+
42
+ By using `EnvVar.delayed_broadcast()` broadcast will only be called once,
43
+ instead of three times.
44
+ """
45
+
46
+ # These class variables are used to control if broadcast sends its message
47
+ _broadcast_enabled = True
48
+ _broadcast_required = False
49
+
50
+ def __init__(self, system=False):
51
+ key, sub_key = REG_LOCATIONS["system" if system else "user"]['env_var']
52
+ self.__reg__ = RegKey(key, sub_key)
53
+
54
+ def __delitem__(self, key):
55
+ entry = self.__reg__.entry(key)
56
+ with self.delayed_broadcast():
57
+ _logger_modify.debug('Deleting env var: "{}"'.format(key))
58
+ entry.delete()
59
+ type(self)._broadcast_required = True
60
+
61
+ def __getitem__(self, key):
62
+ entry = self.__reg__.entry(key)
63
+ try:
64
+ return entry.value()
65
+ except OSError:
66
+ raise KeyError(key)
67
+
68
+ def __iter__(self):
69
+ return iter(self.__reg__.entry_names())
70
+
71
+ def __len__(self):
72
+ reg_key = self.__reg__._key()
73
+ if not reg_key:
74
+ return 0
75
+
76
+ _, count, _ = winreg.QueryInfoKey(reg_key)
77
+ return count
78
+
79
+ def __setitem__(self, key, value, value_type=winreg.REG_EXPAND_SZ):
80
+ entry = self.__reg__.entry(key)
81
+ with self.delayed_broadcast():
82
+ _logger_modify.debug('Setting env var: "{}" to "{}"'.format(key, value))
83
+ entry.set(value, value_type=value_type)
84
+ type(self)._broadcast_required = True
85
+
86
+ @classmethod
87
+ def broadcast(cls):
88
+ """Notify the system about the changes. Uses SendMessageTimeout to avoid
89
+ hanging if applications fail to respond to the broadcast.
90
+ """
91
+ if cls._broadcast_enabled is False:
92
+ _logger_broadcast.debug(
93
+ 'Skipping broadcasting that the environment was changed'
94
+ )
95
+ return
96
+
97
+ _logger_broadcast.debug('Broadcasting that the environment was changed')
98
+ win32gui.SendMessageTimeout(
99
+ win32con.HWND_BROADCAST,
100
+ win32con.WM_SETTINGCHANGE,
101
+ 0,
102
+ 'Environment',
103
+ win32con.SMTO_ABORTIFHUNG,
104
+ 1000,
105
+ )
106
+ # Reset the needs broadcast variable now that we have broadcast
107
+ cls._broadcast_required = False
108
+
109
+ @classmethod
110
+ @contextmanager
111
+ def delayed_broadcast(cls):
112
+ """Context manager that disables broadcast calls for each set/delete of
113
+ environment variables. Only if any set/delete calls were made inside the
114
+ with context, will it call broadcast.
115
+ """
116
+ try:
117
+ current = cls._broadcast_enabled
118
+ cls._broadcast_enabled = False
119
+ yield
120
+ finally:
121
+ cls._broadcast_enabled = current
122
+ if cls._broadcast_required:
123
+ cls.broadcast()
124
+
125
+ @classmethod
126
+ def normalize_path(cls, path, expandvars=True, tilde=None, normpath=True):
127
+ """Normalize a path handling short/long names, env vars and normpath.
128
+
129
+ Args:
130
+ path (str): Input path to expand.
131
+ expandvars (bool, optional): Expand environment variables.
132
+ tilde (bool, optional): Converts windows short paths(`C:\\PROGRA~1`).
133
+ If True, short paths are expanded to full file paths. If False
134
+ file paths are converted to short paths. Nothing is done if None.
135
+ normpath (bool, optional): If True calls os.path.normpath.
136
+
137
+ Returns:
138
+ str: Input path with any tilde-shortenings expanded.
139
+
140
+ Raises:
141
+ pywintypes.error: Raised if path doesn't exist when tilde is not None.
142
+ """
143
+ if tilde:
144
+ path = win32api.GetShortPathName(path)
145
+ elif tilde is False:
146
+ path = win32file.GetLongPathName(path)
147
+
148
+ if expandvars:
149
+ path = os.path.expandvars(path)
150
+
151
+ if normpath:
152
+ path = os.path.normpath(path)
153
+
154
+ return path
casement/registry.py ADDED
@@ -0,0 +1,272 @@
1
+ """
2
+ key: Nodes in tree
3
+ Each node in the tree is called a key.
4
+ Each key can contain both subkeys and data entries called values.
5
+ """
6
+ import six
7
+ from six.moves import winreg
8
+
9
+
10
+ def _generate_key_map():
11
+ """Creates a map of strings to winreg.HKEY_* objects."""
12
+ key_map = {}
13
+
14
+ for key in dir(winreg):
15
+ if key.startswith('HKEY_'):
16
+ # Add the long names
17
+ key_map[key] = getattr(winreg, key)
18
+ # Add the winreg value so we can lookup nice names for keys
19
+ key_map[getattr(winreg, key)] = key
20
+ # Generate shortnames
21
+ short = 'HK' + ''.join([s[0] for s in key.split('_')[1:]])
22
+ if short not in key_map:
23
+ key_map[short] = key_map[key]
24
+ return key_map
25
+
26
+
27
+ key_map = _generate_key_map()
28
+
29
+ """Stores commonly looked up locations in the registry."""
30
+ REG_LOCATIONS = {
31
+ "system": {
32
+ "classes": (winreg.HKEY_LOCAL_MACHINE, 'Software\\Classes'),
33
+ "env_var": (
34
+ winreg.HKEY_LOCAL_MACHINE,
35
+ 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment',
36
+ ),
37
+ "uninstall": (
38
+ winreg.HKEY_LOCAL_MACHINE,
39
+ 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
40
+ ),
41
+ },
42
+ "user": {
43
+ "classes": (winreg.HKEY_CURRENT_USER, 'Software\\Classes'),
44
+ "env_var": (winreg.HKEY_CURRENT_USER, 'Environment'),
45
+ "regedit": (
46
+ winreg.HKEY_CURRENT_USER,
47
+ 'Software\\Microsoft\\Windows\\CurrentVersion\\Applets\\Regedit',
48
+ ),
49
+ },
50
+ }
51
+
52
+
53
+ class RegKey(object):
54
+ """Object for accessing a specific registry key.
55
+
56
+ Example:
57
+
58
+ import winreg
59
+ from casement.registry import RegKey, REG_LOCATIONS
60
+
61
+ # Examples of different ways to get access to the same registry key.
62
+ classes_system = RegKey(*REG_LOCATIONS['system']['classes'])
63
+ classes_system = RegKey('HKEY_LOCAL_MACHINE\\Software\\Classes')
64
+ classes_system = RegKey('HKEY_LOCAL_MACHINE', 'Software\\Classes')
65
+ classes_system = RegKey(winreg.HKEY_LOCAL_MACHINE, 'Software\\Classes')
66
+
67
+ Args:
68
+ key: The path to the registry key as a string, or a winreg HKEY_* constant.
69
+ If a string is passed it needs to start with one of keys stored in
70
+ `key_map`(Example: HKEY_CLASSES_ROOT, HKCR). Alternatively you can
71
+ pass a winreg HKEY_* constant (Example: `winreg.HKEY_CURRENT_USER`).
72
+ sub_key (str, optional): A string to access sub-keys of key. If None and
73
+ key is a string it will split key into key and sub-key.
74
+ computer_name (str, optional): Name of another computer to connect to it's
75
+ registry instead of the current one.
76
+ architecture (int, optional): Access this key in the the 32 or 64 bit registry.
77
+ Valid values are 32 or 64.
78
+ """
79
+
80
+ def __init__(self, key, sub_key=None, computer_name=None, architecture=64):
81
+ # If sub_key is not passed, it must be part of key
82
+ if sub_key is None:
83
+ key, sub_key = key.split('\\', 1)
84
+
85
+ # Convert key strings into winreg keys
86
+ if isinstance(key, six.string_types):
87
+ key = key.upper()
88
+ key = key_map[key]
89
+
90
+ self.architecture = architecture
91
+ self.computer_name = computer_name
92
+ self.key = key
93
+ self.sub_key = sub_key
94
+
95
+ def __str__(self):
96
+ return 'RegKey({!r}, {!r}, {!r}, {})'.format(
97
+ key_map.get(self.key, self.key),
98
+ self.sub_key,
99
+ self.computer_name,
100
+ self.architecture,
101
+ )
102
+
103
+ def _key(self, write=False, create=False, delete=False):
104
+ """Returns a winreg.OpenKeyEx or None.
105
+
106
+ Returns:
107
+ A winreg handle object
108
+ """
109
+ connection = winreg.ConnectRegistry(self.computer_name, self.key)
110
+ sam = self._sam(self.architecture)
111
+ access = winreg.KEY_READ
112
+ if write:
113
+ access = winreg.KEY_WRITE
114
+
115
+ if create:
116
+ func = winreg.CreateKeyEx
117
+ else:
118
+ func = winreg.OpenKeyEx
119
+
120
+ try:
121
+ return func(connection, self.sub_key, 0, access | sam)
122
+ except OSError:
123
+ return None
124
+
125
+ @classmethod
126
+ def _sam(cls, architecture):
127
+ """Return the correct wow64 key for the requested architecture."""
128
+ if architecture == 32:
129
+ return winreg.KEY_WOW64_32KEY
130
+ elif architecture == 64:
131
+ return winreg.KEY_WOW64_64KEY
132
+ return 0
133
+
134
+ def child(self, name):
135
+ """Returns a RegKey for a sub_key with the given name.
136
+
137
+ Args:
138
+ name (str): The name of the child key to return.
139
+ """
140
+ cls = type(self)
141
+ sub_key = '{}\\{}'.format(self.sub_key, name)
142
+ return cls(
143
+ self.key,
144
+ sub_key=sub_key,
145
+ computer_name=self.computer_name,
146
+ architecture=self.architecture,
147
+ )
148
+
149
+ def child_names(self):
150
+ """Generator returning the name for all sub_keys of this key."""
151
+ reg_key = self._key()
152
+ if not reg_key:
153
+ return
154
+
155
+ index = 0
156
+ while True:
157
+ try:
158
+ yield winreg.EnumKey(reg_key, index)
159
+ index += 1
160
+ except OSError:
161
+ break
162
+
163
+ def create(self):
164
+ self._key(create=True)
165
+
166
+ def delete(self):
167
+ """Delete the entire registry key. Raises a RuntimeError if the key has
168
+ child keys."""
169
+ # If the key doesn't exist, there is nothing to do
170
+ if not self.exists():
171
+ return False
172
+
173
+ # TODO: add recursive delete?
174
+ for child in self.child_names():
175
+ # We can't delete this key if it has sub-keys. Using for loop instead
176
+ # of using next() so this can be used inside a contextmanager
177
+ if child:
178
+ msg = "Unable to delete key, it has sub-keys. {}".format(self)
179
+ raise RuntimeError(msg)
180
+
181
+ sam = self._sam(self.architecture)
182
+ winreg.DeleteKeyEx(self.key, self.sub_key, sam)
183
+ return True
184
+
185
+ def entry(self, name=None):
186
+ """Returns a RegEntry for the given name. To access the (Default) entry
187
+ pass an empty string or None.
188
+
189
+ Args:
190
+ name (str, optional): The name of the entry to return.
191
+ """
192
+ return RegEntry(self, name)
193
+
194
+ def entry_names(self):
195
+ """Generator returning the name for all entry's stored on this key."""
196
+ reg_key = self._key()
197
+ if not reg_key:
198
+ return
199
+
200
+ _, count, _ = winreg.QueryInfoKey(reg_key)
201
+ for index in range(count):
202
+ name, _, _ = winreg.EnumValue(reg_key, index)
203
+ yield name
204
+
205
+ def exists(self):
206
+ """Returns True if the key exists in the registry."""
207
+ key = self._key()
208
+ return key is not None
209
+
210
+ def regedit(self, multiple=True):
211
+ """Opens regedit to this registry key."""
212
+ if not self.exists():
213
+ raise RuntimeError("Can't open regedit to a key that doesn't exist.")
214
+
215
+ regedit_key = REG_LOCATIONS['user']['regedit']
216
+ reg = RegKey(*regedit_key)
217
+ if not reg.exists():
218
+ raise RuntimeError("Unable to find regedit preference {}".format(reg))
219
+
220
+ # Update the registry prefs storing the last key it had open.
221
+ last_key = reg.entry('LastKey')
222
+ computer = self.computer_name if self.computer_name else "Computer"
223
+ new = '\\'.join((computer, key_map.get(self.key, self.key), self.sub_key))
224
+ last_key.set(new, winreg.REG_SZ)
225
+
226
+ # Launch regedit
227
+ import subprocess
228
+
229
+ cmd = ['regedit']
230
+ if multiple:
231
+ cmd.append('/m')
232
+ subprocess.Popen(cmd)
233
+
234
+
235
+ class RegEntry(object):
236
+ def __init__(self, key, name):
237
+ self.key = key
238
+ self.name = name
239
+
240
+ def delete(self):
241
+ key = self.key._key(write=True)
242
+ winreg.DeleteValue(key, self.name)
243
+ winreg.CloseKey(key)
244
+
245
+ def type(self):
246
+ """Returns the winreg value type."""
247
+ return self.value_info()[1]
248
+
249
+ def set(self, value, value_type=None):
250
+ key = self.key._key(write=True, create=True)
251
+
252
+ if isinstance(value_type, six.string_types):
253
+ value_type = getattr(winreg, value_type)
254
+
255
+ winreg.SetValueEx(key, self.name, 0, value_type, value)
256
+ winreg.CloseKey(key)
257
+ # TODO: add notify, or keep that in EnvVar?
258
+
259
+ def value(self):
260
+ """Returns the data value."""
261
+ value, _ = self.value_info()
262
+ # We may need to do some conversion based on type in the future.
263
+ return value
264
+
265
+ def value_info(self):
266
+ """Returns the entry value and value type.
267
+
268
+ Returns:
269
+ object: Value stored in key
270
+ int: registry type for value. See winreg's Value Types
271
+ """
272
+ return winreg.QueryValueEx(self.key._key(), self.name)