This recipe uses the system_profiler application to retrieve detailed information about a Mac OS X system. There are two useful ways to use it: the first is to ask for a complete Python datastructure containing information about the system (see OSXSystemProfiler.all()) and the other is two ask for particular keys in the system information database.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | #!/usr/bin/env python
from xml import dom
from xml.dom.xmlbuilder import DOMInputSource, DOMBuilder
import datetime
import time
import os
def group(lst, n):
"""group([0,3,4,10,2,3], 2) => [(0,3), (4,10), (2,3)]
Group a list into consecutive n-tuples. Incomplete tuples are
discarded e.g.
>>> group(range(10), 3)
[(0, 1, 2), (3, 4, 5), (6, 7, 8)]
"""
return zip(*[lst[i::n] for i in range(n)])
def remove_whilespace_nodes(node, unlink=False):
"""Removes all of the whitespace-only text decendants of a DOM node.
When creating a DOM from an XML source, XML parsers are required to
consider several conditions when deciding whether to include
whitespace-only text nodes. This function ignores all of those
conditions and removes all whitespace-only text decendants of the
specified node. If the unlink flag is specified, the removed text
nodes are unlinked so that their storage can be reclaimed. If the
specified node is a whitespace-only text node then it is left
unmodified."""
remove_list = []
for child in node.childNodes:
if child.nodeType == dom.Node.TEXT_NODE and \
not child.data.strip():
remove_list.append(child)
elif child.hasChildNodes():
remove_whilespace_nodes(child)
for node in remove_list:
node.parentNode.removeChild(node)
if unlink:
node.unlink()
class POpenInputSource(DOMInputSource):
"Use stdout from a system command as a DOMInputSource"
def __init__(self, command):
super(DOMInputSource, self).__init__()
self.byteStream = os.popen(command)
class OSXSystemProfiler(object):
"Provide information from the Mac OS X System Profiler"
def __init__(self, detail=-1):
"""detail can range from -2 to +1, with larger numbers returning more
information. Beware of +1, it can take several minutes for
system_profiler to generate the data."""
b = DOMBuilder()
self.document = b.parse(
POpenInputSource('system_profiler -xml -detailLevel %d' % detail))
remove_whilespace_nodes(self.document, True)
def _content(self, node):
"Get the text node content of an element or an empty string"
if node.firstChild:
return node.firstChild.nodeValue
else:
return ''
def _convert_value_node(self, node):
"""Convert a 'value' node (i.e. anything but 'key') into a Python data
structure"""
if node.tagName == 'string':
return self._content(node)
elif node.tagName == 'integer':
return int(self._content(node))
elif node.tagName == 'real':
return float(self._content(node))
elif node.tagName == 'date': # <date>2004-07-05T13:29:29Z</date>
return datetime.datetime(
*time.strptime(self._content(node), '%Y-%m-%dT%H:%M:%SZ')[:5])
elif node.tagName == 'array':
return [self._convert_value_node(n) for n in node.childNodes]
elif node.tagName == 'dict':
return dict([(self._content(n), self._convert_value_node(m))
for n, m in group(node.childNodes, 2)])
else:
raise ValueError(node.tagName)
def __getitem__(self, key):
from xml import xpath
# pyxml xpath does not support /element1[...]/element2
nodes = xpath.Evaluate(
'//dict[key=%r]' % key, self.document)
results = []
for node in nodes:
v = self._convert_value_node(node)[key]
if isinstance(v, dict) and v.has_key('_order'):
# this is just information for display
pass
else:
results.append(v)
return results
def all(self):
"""Return the complete information from the system profiler
as a Python data structure"""
return self._convert_value_node(
self.document.documentElement.firstChild)
def main():
from optparse import OptionParser
from pprint import pprint
info = OSXSystemProfiler()
parser = OptionParser()
parser.add_option("-f", "--field", action="store", dest="field",
help="display the value of the specified field")
(options, args) = parser.parse_args()
if len(args) != 0:
parser.error("no arguments are allowed")
if options.field is not None:
pprint(info[options.field])
else:
# just print some comment keys known to exist in only one important
# dictionary
for k in ['cpu_type', 'current_processor_speed', 'l2_cache_size',
'physical_memory', 'user_name', 'os_version', 'ip_address']:
print '%s: %s' % (k, info[k][0])
if __name__ == '__main__':
main()
|
system_profiler returns it's XML data info pinfo format so this recipe contains a partial pinfo parser. This recipe also requires PyXML to do the key-based querying. group() and remove_whilespace_nodes() are already cookbook recipes.
xpath error. Hi,
I tried the recipe on my Mac, it's giving me this error:
==== Traceback (most recent call last): File "sys_profile.py", line 137, in ? main() File "sys_profile.py", line 134, in main print '%s: %s' % (k, info[k][0]) File "sys_profile.py", line 91, in __getitem__ from xml import xpath
ImportError: cannot import name xpath
did I need to do some setup before using the recipe?
Thanks.