aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README.md45
-rw-r--r--__init__.py5
-rw-r--r--system_profiler.py93
-rw-r--r--tests.py58
5 files changed, 202 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0d20b64
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*.pyc
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b323aea
--- /dev/null
+++ b/README.md
@@ -0,0 +1,45 @@
+### Introduction
+
+`system_profiler` is a simple wrapper around OS X's `system_profiler (1)` tool. It provides a simple API for accessing system profile information as well as caching to improve performance (especially when dealing with application profile data).
+
+
+### System Requirements
+
+- OS X
+
+
+### Usage
+
+Simple example to find and list all versions of ArchiCAD 19:
+
+```
+ import system_profiler
+ results = system_profiler.find('Applications', '_name', 'ArchiCAD 19')
+ print([x['version'] for x in results])
+ ['19.0.0 R1 FIN (6006)']
+```
+
+Check `tests.py` for more usage examples.
+
+
+### License
+
+Copyright (c) 2016 Filipp Lepalaan
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..1dd60eb
--- /dev/null
+++ b/__init__.py
@@ -0,0 +1,5 @@
+__version__ = '0.1'
+__title__ = 'system_profiler'
+__author__ = 'Filipp Lepalaan'
+__license__ = 'MIT'
+__copyright__ = 'Copyright 2016 Filipp Lepalaan'
diff --git a/system_profiler.py b/system_profiler.py
new file mode 100644
index 0000000..81dfb69
--- /dev/null
+++ b/system_profiler.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+
+import json
+import shelve
+import logging
+import os.path
+import tempfile
+import datetime
+import plistlib
+import subprocess
+
+from datetime import datetime, timedelta
+
+
+DEFAULT_DT = 'Hardware'
+CACHE_EXPIRE = timedelta(seconds=60*60*1)
+PROFILER_PATH = '/usr/sbin/system_profiler'
+
+
+class SystemProfile(object):
+ def __init__(self, dt=DEFAULT_DT):
+ types = subprocess.check_output([PROFILER_PATH, '-listDataTypes']).strip()
+ self.types = [x[2:].replace('DataType', '') for x in types.split("\n") if x.startswith('SP')]
+ self.types.sort()
+
+ if dt not in self.types:
+ raise ValueError('Invalid type %s. Should be one of %s' % (dt, ', '.join(self.types)))
+
+ tmp = tempfile.mkstemp()
+ self.dt = 'SP%sDataType' % dt
+
+ shelf_path = os.path.join(tempfile.gettempdir(), '%s.system_profiler' % self.dt)
+ shelf = shelve.open(shelf_path)
+
+ if shelf.get(dt) and shelf.get('expires'):
+ if shelf.get('expires') > datetime.now():
+ self._data = shelf[dt]
+ return
+
+ try:
+ subprocess.call([PROFILER_PATH, self.dt, '-detaillevel', 'full', '-xml'], stdout=tmp[0])
+ except Exception as e:
+ raise Exception('Failed to fetch system profile: %s' % e)
+
+ xml = plistlib.readPlist(tmp[1])
+ self._data = xml[0]['_items']
+ logging.debug(self._data)
+
+ shelf['expires'] = datetime.now() + CACHE_EXPIRE
+ shelf[dt] = self._data
+ shelf.close()
+
+ def json(self):
+ return json.dumps(self._data)
+
+ def get_keys(self):
+ keys = self._data[0].keys()
+ keys.sort()
+ return keys
+
+ def get_types(self):
+ return self.types
+
+ def find(self, k, v):
+ """
+ Return value(s) of property with key k containing v
+ """
+ return [x for x in self._data if v in x[k]]
+
+ def __str__(self):
+ return str(self._data)
+
+ def __getattr__(self, attr):
+ try:
+ return self._data[0][attr]
+ except KeyError as e:
+ raise ValueError('Property "%s" not found' % attr)
+
+ def __getitem__(self, attr):
+ return self._data[attr]
+
+
+def types():
+ return SystemProfile().get_types()
+
+def keys(dt=DEFAULT_DT):
+ return SystemProfile(dt).get_keys()
+
+def get(dt, param):
+ return getattr(SystemProfile(dt), param)
+
+def find(dt, k, v):
+ return SystemProfile(dt).find(k, v)
diff --git a/tests.py b/tests.py
new file mode 100644
index 0000000..553b5ed
--- /dev/null
+++ b/tests.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+
+import logging
+import subprocess
+from unittest import main, skip, TestCase
+
+import system_profiler
+
+
+class SystemProfilerTestCase(TestCase):
+ def testSerialNumber(self):
+ sn = system_profiler.get('Hardware', 'serial_number')
+ self.assertTrue(len(sn) > 8)
+
+ def testInvalidType(self):
+ with self.assertRaises(Exception):
+ system_profiler.SystemProfile('Whatever')
+
+ def testKeys(self):
+ self.assertTrue(len(system_profiler.keys()) > 3)
+
+ def testTypes(self):
+ self.assertIn('Hardware', system_profiler.types())
+
+ def testOsVersion(self):
+ """
+ Check that the OS version we get from SP is contained
+ in the output of sw_vers
+ """
+ build = subprocess.check_output(['sw_vers', '-buildVersion']).strip()
+ software = system_profiler.SystemProfile('Software')
+ self.assertIn(build, software.os_version)
+
+ def testOsVersionShortcut(self):
+ build = subprocess.check_output(['sw_vers', '-buildVersion']).strip()
+ self.assertTrue(build in system_profiler.get('Software', 'os_version'))
+
+
+class AppsTestCase(TestCase):
+ def setUp(self):
+ self.profile = system_profiler.SystemProfile('Applications')
+
+ def testFindStickes(self):
+ results = self.profile.find('_name', 'Stickies')
+ self.assertTrue(len(results) > 0)
+
+ def testStickiesVersion(self):
+ results = self.profile.find('_name', 'Stickies')
+ self.assertEquals(results[0]['version'], '10.0')
+
+ def testFindApplications(self):
+ results = self.profile.find('path', '/Applications')
+ self.assertTrue(len(results) > 10)
+
+
+if __name__ == '__main__':
+ logging.basicConfig(level=logging.DEBUG)
+ main()