<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-33031751</id><updated>2012-02-16T02:47:33.431-08:00</updated><category term='exuro'/><category term='regex'/><category term='opencv'/><category term='Arduino'/><category term='mysql'/><category term='python'/><category term='nagios'/><category term='ssh'/><category term='LED'/><category term='django'/><category term='osx'/><category term='kinect'/><category term='Curmudgeon'/><category term='google apps'/><title type='text'>Peter Kropf</title><subtitle type='html'>Thoughts, ideas, code samples, and other random nonsense…</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://pkropf.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://pkropf.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Peter Kropf</name><uri>http://www.blogger.com/profile/00798538828224209506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>10</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-33031751.post-8401203913207675421</id><published>2011-02-11T21:50:00.000-08:00</published><updated>2011-02-11T21:50:32.570-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='kinect'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='exuro'/><category scheme='http://www.blogger.com/atom/ns#' term='Arduino'/><category scheme='http://www.blogger.com/atom/ns#' term='opencv'/><title type='text'>The Eyes, They be Moving!</title><content type='html'>Here's a &lt;a href="http://www.youtube.com/watch?v=4JJtT3K6un0"&gt;short video&lt;/a&gt; that shows the eyes on Exuro moving. You can see Mac moving around in the screen on the lower right of the video and the eyes moving to track the closest part of him. I'll post more details soon but right now I'm just psyched that they're moving and tracking pretty well!&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;object width="320" height="266" class="BLOGGER-youtube-video" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0" data-thumbnail-src="http://i.ytimg.com/vi/4JJtT3K6un0/0.jpg"&gt;&lt;param name="movie" value="http://www.youtube.com/v/4JJtT3K6un0?f=user_uploads&amp;c=google-webdrive-0&amp;app=youtube_gdata" /&gt;&lt;param name="bgcolor" value="#FFFFFF" /&gt;&lt;embed width="320" height="266" src="http://www.youtube.com/v/4JJtT3K6un0?f=user_uploads&amp;c=google-webdrive-0&amp;app=youtube_gdata" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/33031751-8401203913207675421?l=pkropf.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://www.youtube.com/watch?v=4JJtT3K6un0' title='The Eyes, They be Moving!'/><link rel='replies' type='application/atom+xml' href='http://pkropf.blogspot.com/feeds/8401203913207675421/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=33031751&amp;postID=8401203913207675421' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default/8401203913207675421'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default/8401203913207675421'/><link rel='alternate' type='text/html' href='http://pkropf.blogspot.com/2011/02/eyes-they-be-moving.html' title='The Eyes, They be Moving!'/><author><name>Peter Kropf</name><uri>http://www.blogger.com/profile/00798538828224209506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-33031751.post-1992210905015092126</id><published>2011-01-23T21:48:00.000-08:00</published><updated>2011-01-23T22:28:40.220-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='kinect'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='exuro'/><category scheme='http://www.blogger.com/atom/ns#' term='Arduino'/><title type='text'>Kinect, Python and Pushing Through</title><content type='html'>There's tangent project that I'm working on that involves robotics, arduinos, kinect and fire. It's a small robot called Exuro that has a pair of stainless steel eyes that are meant to track a person coming up to a donation box and when they make a donation, set off a small poofer. The idea is to track people using a kinect and have the eyes move as if they're watching the closest person to the donation box. Working with an arduino to control external systems is pretty straight forward for me, it's something that I've done before. But pulling sensor data from something like a kinect and interpreting the data is something I've never done. It's rather intimidating. Processing video data at something like 30 frames per second, not something I'm used to do. But it sounds like fun!&lt;br /&gt;&lt;br /&gt;There's an open source driver to access the kinect called &lt;a href="https://github.com/OpenKinect/libfreenect"&gt;libfreenect&lt;/a&gt; that's available from &lt;a href="http://openkinect.org"&gt;openkinect.org&lt;/a&gt;. Included are wrappers for using the library from Python which most definitely my preferred programming language. That works.&lt;br /&gt;&lt;br /&gt;Getting libfreenect to build on a Ubuntu 10.10 system was pretty straight forward. Just follow the instructions in the &lt;a href="https://github.com/OpenKinect/libfreenect/blob/master/README.asciidoc"&gt;README.asciidoc&lt;/a&gt; file. Getting the Python wrappers to work took a bit more effort. &lt;a href="http://cython.org"&gt;cython&lt;/a&gt; is used to create the bindings between libfreenect and Python. Unfortunately, the version that's currently included with Ubuntu 10.10 isn't up to the task. Once I removed the Ubuntu and installed from the latest source, the Python bindings built and worked as just fine. I'm sure the fine folks maintaining Ubuntu will make a newer version available at some point, I'm just not willing to put this project on hold till they do ;-)&lt;br /&gt;&lt;br /&gt;There's a few demo files that are included with the wrapper so you can start to play with the interface, library and the kinect data. Two of them, demo_cv_sync.py and demo_cv_thresh_sweep.py, make for demo. The first opens two windows and shows a live video feed of the rgb camera in one and the depth camera in the other. The other demo shows a video of the depth camera but sweeps through the data showing what's seen at different depths. These are really interesting demos to help wrap your head around what's available from the kinect.&lt;br /&gt;&lt;br /&gt;I got to wondering about the depth data and if there wasn't a way to combine the two demos to be able to slide through the depth manually to see what's there. The result is &lt;a href="https://github.com/pkropf/libfreenect/blob/master/wrappers/python/demo_cv_threshold.py"&gt;demo_cv_threshold.py&lt;/a&gt;. It allows you to slide along at any depth to see what's there and then to contract or expand to see what's around that depth. Here's a sample video showing my hand pushing through a virtual wall:&lt;br /&gt;&lt;br /&gt;&lt;iframe title="YouTube video player" class="youtube-player" type="text/html" width="640" height="390" src="http://www.youtube.com/embed/FtdE4ee4_e0" frameborder="0" allowFullScreen&gt;&lt;/iframe&gt;&lt;br /&gt;&lt;br /&gt;The depth slider sets the focal point for what data to display and the threshold provides a +/- tolerance for how much data to display. A depth of 535 and a threshold of 0 would show just the data at 535 while a depth of 535 and a threshold of 6 would show the data from 529 thru 541.&lt;br /&gt;&lt;br /&gt;It's an interesting application to play with the gain a basic understanding of the data being returned and possible ways to use it. I've submitted a pull request on github to the maintainers of libfreenect to see if they're willing to include it in the next release. Here's hoping that they will.&lt;br /&gt;&lt;br /&gt;There's a lot more work I need to do for this project. The next steps will be to find the closest person in the data stream and calculate their location in the real world in reference to the location of the kinect. And I have almost no idea how to go about doing that. Time to read up on &lt;a href="http://numpy.scipy.org/"&gt;numpy&lt;/a&gt; and &lt;a href="http://opencv.willowgarage.com/wiki/"&gt;opencv&lt;/a&gt;...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/33031751-1992210905015092126?l=pkropf.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pkropf.blogspot.com/feeds/1992210905015092126/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=33031751&amp;postID=1992210905015092126' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default/1992210905015092126'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default/1992210905015092126'/><link rel='alternate' type='text/html' href='http://pkropf.blogspot.com/2011/01/kinect-python-and-pushing-through.html' title='Kinect, Python and Pushing Through'/><author><name>Peter Kropf</name><uri>http://www.blogger.com/profile/00798538828224209506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://img.youtube.com/vi/FtdE4ee4_e0/default.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-33031751.post-7994587389348650693</id><published>2010-05-13T16:10:00.000-07:00</published><updated>2010-05-13T16:34:39.971-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>OS X and the mystery bits</title><content type='html'>Sometimes I have to learn a lesson over and over again before I get a glimmer of what's going on. I spent some time again today trying to get the &lt;a href="http://sourceforge.net/projects/mysql-python/"&gt;MySQL client library&lt;/a&gt; module for Python working. I need it to be able to talk w/ a MySQL database for an online store. I'd rather use &lt;a href="http://www.postgresql.org/"&gt;PostgreSQL&lt;/a&gt; but in this case, I don't have a choice.&lt;br /&gt;&lt;br /&gt;I downloaded MySQL OS X package and ran the installation program. It did what it was supposed to - install the software under /usr/local. The MySQL client runs and all seems right with the world. I download and install the MySQL Python client module and it builds as expected. But when I try to run Django, I get this error:&lt;br /&gt;&lt;br /&gt;&lt;pre name='code' language='python'&gt;django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module: dynamic module does not define init function (init_mysql)&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Grumble, grumble, grumble.&lt;br /&gt;&lt;br /&gt;I try to load the MySQL Python module directly:&lt;br /&gt;&lt;br /&gt;&lt;pre name='code' language='python'&gt;peter@drag:~&gt; python&lt;br /&gt;Python 2.6.1 (r261:67515, Feb 11 2010, 00:51:29) &lt;br /&gt;[GCC 4.2.1 (Apple Inc. build 5646)] on darwin&lt;br /&gt;Type "help", "copyright", "credits" or "license" for more information.&lt;br /&gt;port&gt;&gt;&gt; import _mysql&lt;br /&gt;Traceback (most recent call last):&lt;br /&gt;  File "&lt;stdin&gt;", line 1, in &lt;module&gt;&lt;br /&gt;  File "build/bdist.macosx-10.6-universal/egg/_mysql.py", line 7, in &lt;module&gt;&lt;br /&gt;  File "build/bdist.macosx-10.6-universal/egg/_mysql.py", line 6, in __bootstrap__&lt;br /&gt;ImportError: dynamic module does not define init function (init_mysql)&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I checked the build output. Checked the install output. Everything looks fine. This should work. Searching the web doesn't help much since the references I find are from 2007 and 2008. The patches that they describe have already been incorporated into the MySQL Python module. &lt;br /&gt;&lt;br /&gt;About this time, I realize that I've solved this problem before. It has to do with bits. I run:&lt;br /&gt;&lt;br /&gt;&lt;pre name='code' language='python'&gt;peter@drag:~&gt; python&lt;br /&gt;Python 2.6.1 (r261:67515, Feb 11 2010, 00:51:29) &lt;br /&gt;[GCC 4.2.1 (Apple Inc. build 5646)] on darwin&lt;br /&gt;Type "help", "copyright", "credits" or "license" for more information.&lt;br /&gt;&gt;&gt;&gt; import platform&lt;br /&gt;&gt;&gt;&gt; platform.architecture()&lt;br /&gt;('64bit', '')&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I look at the original MySQL disk image that I downloaded and see that its a 32bit application. Mixing a 32bit dynamic library with a 64bit executable doesn't work.&lt;br /&gt;&lt;br /&gt;After downloading the 64bit MySQL disk image, installing it and rebuilding the MySQL Python module, everything works. Now its on to writing code for the online store. Yea.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/33031751-7994587389348650693?l=pkropf.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pkropf.blogspot.com/feeds/7994587389348650693/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=33031751&amp;postID=7994587389348650693' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default/7994587389348650693'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default/7994587389348650693'/><link rel='alternate' type='text/html' href='http://pkropf.blogspot.com/2010/05/os-x-and-mystery-bits.html' title='OS X and the mystery bits'/><author><name>Peter Kropf</name><uri>http://www.blogger.com/profile/00798538828224209506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-33031751.post-1655848729408953975</id><published>2010-03-28T21:57:00.000-07:00</published><updated>2010-03-28T22:28:16.217-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='regex'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Bad data and the wonder of RegEx</title><content type='html'>Regular expressions aren't something that I use regularly but I think that's about the change. I came up a programming world where C, Cobol and Fortran were just the best thing considering that the alternative was writing assembler. And in that world, regular expressions don't really exist. The basic toolkit I developed internally for solving programming problems didn't include it. To this day, I sometimes still think about iterating over a character sequence in memory until a NULL is found. But in the modern programming world, languages like Python have tools built into the language and standard libraries that provide a lot of help to a developer. One of those tools is regular expression matching.&lt;br /&gt;&lt;br /&gt;I'm currently working on a project to convert a FileMaker 5.5 database to SalesForce. Inside the FileMaker database, the data is in a very strange and odd states because there was no data validation done. So a date field can contain strings like '1/1/01', '6/24/2007' or '9-1-2005' or '2/27' or 'Jan 1.' These different formats cause a world of grief in trying to move the data to a database that expects a date to be structured. Something like 1/1/2001. It doesn't know about the various format of our dates and it rejects these as invalid. So the dates had to be filtered into something more standard.&lt;br /&gt;&lt;br /&gt;I started to write some code that searches the strings for '/' and '-' and the text names of months. And naturally, it quickly became a rats nest of nested if and conditions that made understanding the code very cumbersome. So I went looking for another way to solve the problem. I remembered that Python had this module called re that provides regular expression processing but I hadn't really used it and wasn't sure about how it would work. So I start searching the web to find some help on using regular expressions and the re module. What I found was just wonderful. &lt;a href="http://www.amk.ca/"&gt;A.M. Kuchling&lt;/a&gt; wrote up the fantastic &lt;a href="http://www.amk.ca/python/howto/regex/"&gt;Regular Expression HOWTO&lt;/a&gt;. In almost no time flat I was starting to put together a regular expression that would match most the permutations of dates (those based on '1/1/01') that I've seen in our database and a second that would match the ones that had the months written out. The quickest way that I found to test the regular expressions was using Python's unittest module. I would edit the regular expression, run the unittests, re-editing, re-run, etc until it worked and all the tests past.&lt;br /&gt;&lt;br /&gt;The code that I developed looks like this:&lt;br /&gt;&lt;br /&gt;&lt;pre name='code' language='python'&gt;import unittest&lt;br /&gt;import re&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;class TestDateRegEx(unittest.TestCase):&lt;br /&gt;    def test_slashes(self):&lt;br /&gt;        slashes = re.compile(r'(\d{1,2})[/-](\d{1,2})[/-](\d{2,4})$')&lt;br /&gt;&lt;br /&gt;        self.assertEqual(slashes.match('1/1/10').group(),     '1/1/10')&lt;br /&gt;        self.assertEqual(slashes.match('1/1/10').group(1),    '1')&lt;br /&gt;        self.assertEqual(slashes.match('1/1/10').group(2),    '1')&lt;br /&gt;        self.assertEqual(slashes.match('1/1/10').group(3),    '10')&lt;br /&gt;        self.assertEqual(slashes.match('01/1/10').group(),    '01/1/10')&lt;br /&gt;        self.assertEqual(slashes.match('11/1/10').group(),    '11/1/10')&lt;br /&gt;        self.assertEqual(slashes.match('11/13/10').group(),   '11/13/10')&lt;br /&gt;        self.assertEqual(slashes.match('1/1/2010').group(),   '1/1/2010')&lt;br /&gt;        self.assertEqual(slashes.match('12/1/2010').group(),  '12/1/2010')&lt;br /&gt;        self.assertEqual(slashes.match('1/21/2010').group(),  '1/21/2010')&lt;br /&gt;        self.assertEqual(slashes.match('1/21/2010').group(1), '1')&lt;br /&gt;        self.assertEqual(slashes.match('1/21/2010').group(2), '21')&lt;br /&gt;        self.assertEqual(slashes.match('1/21/2010').group(3), '2010')&lt;br /&gt;&lt;br /&gt;        self.assertEqual(slashes.match('111/1/10'),    None)&lt;br /&gt;        self.assertEqual(slashes.match('11/111/10'),   None)&lt;br /&gt;        self.assertEqual(slashes.match('11/11/10100'), None)&lt;br /&gt;&lt;br /&gt;        self.assertEqual(slashes.match('1-1-10').group(),     '1-1-10')&lt;br /&gt;        self.assertEqual(slashes.match('01-1-10').group(),    '01-1-10')&lt;br /&gt;        self.assertEqual(slashes.match('11-1-10').group(),    '11-1-10')&lt;br /&gt;        self.assertEqual(slashes.match('11-13-10').group(),   '11-13-10')&lt;br /&gt;        self.assertEqual(slashes.match('1-1-2010').group(),   '1-1-2010')&lt;br /&gt;        self.assertEqual(slashes.match('12-1-2010').group(),  '12-1-2010')&lt;br /&gt;        self.assertEqual(slashes.match('1-21-2010').group(),  '1-21-2010')&lt;br /&gt;        self.assertEqual(slashes.match('1-21-2010').group(1), '1')&lt;br /&gt;        self.assertEqual(slashes.match('1-21-2010').group(2), '21')&lt;br /&gt;        self.assertEqual(slashes.match('1-21-2010').group(3), '2010')&lt;br /&gt;&lt;br /&gt;        self.assertEqual(slashes.match('111-1-10'),    None)&lt;br /&gt;        self.assertEqual(slashes.match('11-111-10'),   None)&lt;br /&gt;        self.assertEqual(slashes.match('11-11-10100'), None)&lt;br /&gt;&lt;br /&gt;        self.assertEqual(slashes.match('1'),           None)&lt;br /&gt;        self.assertEqual(slashes.match('1/'),          None)&lt;br /&gt;        self.assertEqual(slashes.match('1/1'),         None)&lt;br /&gt;        self.assertEqual(slashes.match('1'),           None)&lt;br /&gt;        self.assertEqual(slashes.match('1/'),          None)&lt;br /&gt;        self.assertEqual(slashes.match('1/1'),         None)&lt;br /&gt;        self.assertEqual(slashes.match('Jan 1'),       None)&lt;br /&gt;        self.assertEqual(slashes.match('Jan 1, 2010'), None)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    def test_names(self):&lt;br /&gt;        names = re.compile(r'(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\s(\d{1,2})(,\s(\d{2,4}))?$', re.IGNORECASE)&lt;br /&gt;&lt;br /&gt;        self.assertEqual(names.match('Jan 1').group(),         'Jan 1')&lt;br /&gt;        self.assertEqual(names.match('Jan 1').group(1),        'Jan')&lt;br /&gt;        self.assertEqual(names.match('Jan 1').group(2),        '1')&lt;br /&gt;        self.assertEqual(names.match('feb 21').group(),        'feb 21')&lt;br /&gt;        self.assertEqual(names.match('feb 21').group(1),       'feb')&lt;br /&gt;        self.assertEqual(names.match('feb 21').group(2),       '21')&lt;br /&gt;        self.assertEqual(names.match('Jan 1, 2010').group(),   'Jan 1, 2010')&lt;br /&gt;        self.assertEqual(names.match('Jan 1, 2010').group(1),  'Jan')&lt;br /&gt;        self.assertEqual(names.match('Jan 1, 2010').group(2),  '1')&lt;br /&gt;        self.assertEqual(names.match('Jan 1, 2010').group(4),  '2010')&lt;br /&gt;        self.assertEqual(names.match('MAR 14, 2010').group(),  'MAR 14, 2010')&lt;br /&gt;        self.assertEqual(names.match('MAR 14, 2010').group(1), 'MAR')&lt;br /&gt;        self.assertEqual(names.match('MAR 14, 2010').group(2), '14')&lt;br /&gt;        self.assertEqual(names.match('MAR 14, 2010').group(4), '2010')&lt;br /&gt;&lt;br /&gt;        self.assertEqual(names.match('MA 20, 2010'), None)&lt;br /&gt;        self.assertEqual(names.match('xyz 2,'),      None)&lt;br /&gt;        self.assertEqual(names.match('jan 2,'),      None)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;if __name__ == '__main__':&lt;br /&gt;    unittest.main()&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The use of regular expressions and the ability to match and group the matches makes the code needed to clean up the dates much simpler and a lot easier to maintain. It might take a moment next time I need to parse strings to think about using regular expressions and I'm pretty sure I'll have to refer to the howto a couple more times but I'm really happy with how powerful and how much simpler text processing can be by using them.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/33031751-1655848729408953975?l=pkropf.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pkropf.blogspot.com/feeds/1655848729408953975/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=33031751&amp;postID=1655848729408953975' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default/1655848729408953975'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default/1655848729408953975'/><link rel='alternate' type='text/html' href='http://pkropf.blogspot.com/2010/03/bad-data-and-wonder-of-regex.html' title='Bad data and the wonder of RegEx'/><author><name>Peter Kropf</name><uri>http://www.blogger.com/profile/00798538828224209506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-33031751.post-4050228597399631492</id><published>2009-10-16T23:42:00.000-07:00</published><updated>2009-10-17T08:36:39.058-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='nagios'/><title type='text'>Web service monitoring w/ Nagios and JSON</title><content type='html'>I'm using &lt;a href="http://nagios.org"&gt;Nagios&lt;/a&gt; to act as a watch dog for my network and the various services that live on it. Nagios does the job pretty well. It lets me know when there's a problem, when things are back to normal and generally keeps on eye on things for me.&lt;br /&gt;&lt;br /&gt;The checks that Nagios performs are done through a series of check commands. These commands are your typical Unix style program with the exceptions that they produce a single line of text that describes the state of the item being checked and the exit value let's Nagios know what's going on.&lt;br /&gt;&lt;br /&gt;So for instance, to check the health of the web service on the localhost:&lt;br /&gt;&lt;br /&gt;&lt;pre name='code' language='python'&gt;peter@sybil:~$ /usr/lib/nagios/plugins/check_http -H localhost&lt;br /&gt;HTTP OK HTTP/1.1 200 OK - 361 bytes in 0.001 seconds |time=0.001021s;;;0.000000 size=361B;;;0&lt;br /&gt;peter@sybil:~$ echo $?&lt;br /&gt;2&lt;br /&gt;peter@sybil:~$ &lt;/pre&gt;&lt;br /&gt;The single line of text that is displayed follows a specific format. It starts with the prefix of what's being tested, HTTP. Next is the status, OK. This can be OK, WARNING, CRITICAL or UNKNOWN. Everything after the status is eye candy that provide details that are specific to the test being done. Nagios doesn't really care about it but it does provide important details when looking at problems that may be occurring.&lt;br /&gt;&lt;br /&gt;Writing these check program in Python is pretty straight forward.&lt;br /&gt;&lt;br /&gt;I recently had a situation where our ISP moved our web servers from one physical machine to another. This resulted in the credit cards processing for our online store to fail. The payment provider uses the IP address of the server as part of the authentication process when submitting credit cards for processing. Since the server changed, the IP address changed. Things went around in circles for a while until we figured out the problem and gave the new IP address to the payment&lt;br /&gt;provider.&lt;br /&gt;&lt;br /&gt;I thought is would be a good additional Nagios check for the store web site to check on the IP address of the physical server. Unfortunately, the ISP doesn't provide access to the IP address. But they do provide access to the hostname.&lt;br /&gt;&lt;br /&gt;To get the hostname, I added a simple CGI program that determines the hostname and then packages it up as a JSON data structure.&lt;br /&gt;&lt;br /&gt;&lt;pre  name='code' language='python'&gt;#!/usr/bin/env python&lt;br /&gt;&lt;br /&gt;"""&lt;br /&gt;Bundle the hostname up as a JSON data structure.&lt;br /&gt;&lt;br /&gt;Copyright (c) 2009 Peter Kropf. All rights reserved.&lt;br /&gt;"""&lt;br /&gt;&lt;br /&gt;import cgi&lt;br /&gt;import popen2&lt;br /&gt;import sys&lt;br /&gt;sys.path.insert(1, '/home/crucible/tools/lib/python2.4/site-packages')&lt;br /&gt;sys.path.insert(1, '/home/crucible/tools/lib/python2.4/site-packages/simplejson-2.0.9-py2.4-linux-x86_64.egg')&lt;br /&gt;&lt;br /&gt;import simplejson as json&lt;br /&gt;&lt;br /&gt;field = cgi.FieldStorage()&lt;br /&gt;print "Content-Type: application/json\n\n"&lt;br /&gt;&lt;br /&gt;r, w, e = popen2.popen3('hostname')&lt;br /&gt;host = r.readline()&lt;br /&gt;r.close()&lt;br /&gt;w.close()&lt;br /&gt;e.close()&lt;br /&gt;&lt;br /&gt;fields = {'hostname': host.split('\n')[0]}&lt;br /&gt;&lt;br /&gt;print json.dumps(fields)&lt;/pre&gt;&lt;br /&gt;There's a couple of things to note. Since the ISP provides a very restrictive environment, I have to add the location of the simplejson module before it can be imported. It's a bit annoying but it does work.&lt;br /&gt;&lt;br /&gt;On the Nagios service side, I created a new check program called check_json. It takes the name of a field, the expected value and the URI from which to pull the JSON data.&lt;br /&gt;&lt;br /&gt;&lt;pre  name='code' language='python'&gt;#! /usr/bin/env python&lt;br /&gt;&lt;br /&gt;"""&lt;br /&gt;Nagios plugin to check a value returned from a uri in json format.&lt;br /&gt;&lt;br /&gt;Copyright (c) 2009 Peter Kropf. All rights reserved.&lt;br /&gt;&lt;br /&gt;Example:&lt;br /&gt;&lt;br /&gt;Compare the "hostname" field in the json structure returned from&lt;br /&gt;http://store.example.com/hostname.py against a known value.&lt;br /&gt;&lt;br /&gt;    ./check_json hostname buenosaires http://store.example.com/hostname.py&lt;br /&gt;"""&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;import urllib2&lt;br /&gt;import simplejson&lt;br /&gt;import sys&lt;br /&gt;from optparse import OptionParser&lt;br /&gt;&lt;br /&gt;prefix = 'JSON'&lt;br /&gt;&lt;br /&gt;class nagios:&lt;br /&gt;    ok       = (0, 'OK')&lt;br /&gt;    warning  = (1, 'WARNING')&lt;br /&gt;    critical = (2, 'CRITICAL')&lt;br /&gt;    unknown  = (3, 'UNKNOWN')&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;def exit(status, message):&lt;br /&gt;    print prefix + ' ' + status[1] + ' - ' + message&lt;br /&gt;    sys.exit(status[0])&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;parser = OptionParser(usage='usage: %prog field_name expected_value uri')&lt;br /&gt;options, args = parser.parse_args()&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;if len(sys.argv) &lt; 3:&lt;br /&gt;    exit(nagios.unknown, 'missing command line arguments')&lt;br /&gt;&lt;br /&gt;field = args[0]&lt;br /&gt;value = args[1]&lt;br /&gt;uri = args[2]&lt;br /&gt;&lt;br /&gt;try:&lt;br /&gt;    j = simplejson.load(urllib2.urlopen(uri))&lt;br /&gt;except urllib2.HTTPError, ex:&lt;br /&gt;    exit(nagios.unknown, 'invalid uri')&lt;br /&gt;&lt;br /&gt;if field not in j:&lt;br /&gt;    exit(nagios.unknown, 'field: ' + field + ' not present')&lt;br /&gt;&lt;br /&gt;if j[field] != value:&lt;br /&gt;    exit(nagios.critical, j[field] + ' != ' + value)&lt;br /&gt;&lt;br /&gt;exit(nagios.ok, j[field] + ' == ' + value)&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Some checking is done to ensure that the JSON data can be retrieved, that the needed field is in the data and then that the field's value matches what's expected.&lt;br /&gt;&lt;br /&gt;These examples show the basic testing that's done and the return values:&lt;br /&gt;&lt;br /&gt;&lt;pre  name='code' language='python'&gt;peter@sybil:~$ /usr/lib/nagios/plugins/check_json hostname buenosaires http://store.thecrucible.org/hostname.py&lt;br /&gt;JSON OK - buenosaires == buenosaires&lt;br /&gt;peter@sybil:~$ echo $?&lt;br /&gt;0&lt;br /&gt;peter@sybil:~$ /usr/lib/nagios/plugins/check_json hostname buenosaires http://store.thecrucible.org/hostname.p&lt;br /&gt;JSON UNKNOWN - invalid uri&lt;br /&gt;peter@sybil:~$ echo $?&lt;br /&gt;3&lt;br /&gt;peter@sybil:~$ /usr/lib/nagios/plugins/check_json hostname buenosairs http://store.thecrucible.org/hostname.py&lt;br /&gt;JSON CRITICAL - buenosaires != buenosairs&lt;br /&gt;peter@sybil:~$ echo $?&lt;br /&gt;2&lt;br /&gt;peter@sybil:~$ /usr/lib/nagios/plugins/check_json ostname buenosaires http://store.thecrucible.org/hostname.py&lt;br /&gt;JSON UNKNOWN - field: ostname not present&lt;br /&gt;peter@sybil:~$ echo $?&lt;br /&gt;3&lt;br /&gt;peter@sybil:~$ &lt;/pre&gt;&lt;br /&gt;Once the Nagios server is configured with the new command, the hostname on the server can be monitored and hopefully ease any problems that may occur then next time things change at the ISP.&lt;br /&gt;&lt;br /&gt;More details on Nagios can be found at &lt;a href="http://nagios.org "&gt;http://nagios.org&lt;/a&gt; and on developing check program at &lt;a href="http://nagiosplug.sourceforge.net/developer-guidelines.html"&gt;http://nagiosplug.sourceforge.net/developer-guidelines.html&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/33031751-4050228597399631492?l=pkropf.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pkropf.blogspot.com/feeds/4050228597399631492/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=33031751&amp;postID=4050228597399631492' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default/4050228597399631492'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default/4050228597399631492'/><link rel='alternate' type='text/html' href='http://pkropf.blogspot.com/2009/10/web-service-monitoring-w-nagios-and_16.html' title='Web service monitoring w/ Nagios and JSON'/><author><name>Peter Kropf</name><uri>http://www.blogger.com/profile/00798538828224209506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-33031751.post-8965390058034163524</id><published>2009-06-01T23:11:00.000-07:00</published><updated>2009-06-02T16:58:50.283-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Running External Django Scripts</title><content type='html'>&lt;a href="http://djangoproject.com/"&gt;Django&lt;/a&gt; is pretty good at creating a database driven website. The documentation is clear and the tutorials show how to use the framework to create web based applications. But one part that I wish was a bit more straight forward is running scripts outside the web server. The issue is that Django code expects to have a certain environment configured and setup for the framework. With this in place, you can preform tasks like polling an IMAP server for incoming email messages or monitoring a directory for new files or whatever else needs to be done. There are several posts online to help you get the environment setup &lt;a href="http://www.djangosnippets.org/snippets/270/"&gt;here&lt;/a&gt;, &lt;a href="http://www.b-list.org/weblog/2007/sep/22/standalone-django-scripts/"&gt;here&lt;/a&gt; and &lt;a href="http://patrickbeeson.com/blog/2008/mar/05/how-run-python-script-django-cron/"&gt;here&lt;/a&gt;. But some of them seem not to work correctly because of the changes to Django for the 1.0 release or other reasons. &lt;br /&gt;&lt;br /&gt;I have a fairly straight forward example of how to setup the Django environment and allow the rest of your code to access the Django framework for your web application. Its remarkably simple and straight forward.&lt;br /&gt;&lt;br /&gt;Suppose that I've created a Django project in my tmp directory called demo_scripts and within that project, I create an app called someapp.&lt;br /&gt;&lt;br /&gt;&lt;pre  name='code' language='python'&gt;peter@fog:~/tmp&gt; django-admin-2.5.py startproject demo_scripts&lt;br /&gt;peter@fog:~/tmp&gt; cd demo_scripts/&lt;br /&gt;peter@fog:~/tmp/demo_scripts&gt; django-admin-2.5.py startapp someapp&lt;br /&gt;peter@fog:~/tmp/demo_scripts&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I create a model in someapp that looks like:&lt;br /&gt;&lt;br /&gt;&lt;pre  name='code' language='python'&gt;from django.db import models&lt;br /&gt;&lt;br /&gt;class Foo(models.Model):&lt;br /&gt;    name = models.CharField(max_length=21,&lt;br /&gt;        unique=True,&lt;br /&gt;        help_text="Name of the foo.")&lt;br /&gt;&lt;br /&gt;    def __unicode__(self):&lt;br /&gt;        return self.name&lt;br /&gt;&lt;br /&gt;    class Meta:&lt;br /&gt;        ordering = ('name',)&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Next step is to sync the database:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="python"&gt;peter@fog:~/tmp/demo_scripts&gt; ./manage.py syncdb&lt;br /&gt;Creating table auth_permission&lt;br /&gt;Creating table auth_group&lt;br /&gt;Creating table auth_user&lt;br /&gt;Creating table auth_message&lt;br /&gt;Creating table django_content_type&lt;br /&gt;Creating table django_session&lt;br /&gt;Creating table django_site&lt;br /&gt;Creating table someapp_foo&lt;br /&gt;&lt;br /&gt;You just installed Django's auth system, which means you don't have any superusers defined.&lt;br /&gt;Would you like to create one now? (yes/no): yes&lt;br /&gt;Username (Leave blank to use 'peter'): &lt;br /&gt;E-mail address: pkropf@gmail.com&lt;br /&gt;Password: &lt;br /&gt;Password (again): &lt;br /&gt;Superuser created successfully.&lt;br /&gt;Installing index for auth.Permission model&lt;br /&gt;Installing index for auth.Message model&lt;br /&gt;peter@fog:~/tmp/demo_scripts&gt; &lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And add some initial data to the database:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="python"&gt;peter@fog:~/tmp/demo_scripts&gt; ./manage.py shell&lt;br /&gt;Python 2.5.4 (r254:67916, May  1 2009, 17:14:50) &lt;br /&gt;[GCC 4.0.1 (Apple Inc. build 5490)] on darwin&lt;br /&gt;Type "help", "copyright", "credits" or "license" for more information.&lt;br /&gt;(InteractiveConsole)&lt;br /&gt;&gt;&gt;&gt; from someapp.models import Foo&lt;br /&gt;&gt;&gt;&gt; Foo(name='A Foo').save()&lt;br /&gt;&gt;&gt;&gt; Foo(name='Another Foo').save()&lt;br /&gt;&gt;&gt;&gt; &lt;br /&gt;peter@fog:~/tmp/demo_scripts&gt; &lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now we can write a standalone script to do something with the data model. For simplicity's sake, I'll just print out all the Foo objects. The script is going to live in a new directory called scripts. Here's the source:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="python"&gt;#! /usr/bin/env python&lt;br /&gt;#coding:utf-8&lt;br /&gt;&lt;br /&gt;import sys&lt;br /&gt;import os&lt;br /&gt;import datetime&lt;br /&gt;&lt;br /&gt;sys.path.insert(0, os.path.expanduser('~/tmp/demo_scripts'))&lt;br /&gt;os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'&lt;br /&gt;&lt;br /&gt;from someapp.models import *&lt;br /&gt;print Foo.objects.all()&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;When I run the script, it prints the array of the two Foo objects that I previously created:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="python"&gt;peter@fog:~/tmp/demo_scripts&gt; ./scripts/show_foo.py &lt;br /&gt;[&amp;lt;Foo: A Foo&amp;gt;, &amp;lt;Foo: Another Foo&amp;gt;]&lt;br /&gt;peter@fog:~/tmp/demo_scripts&gt; &lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Lines 8 and 9 are the critical lines in the script code. The first adds the project directory to the Python system path so that the settings module can be found. The second tells the Django code which module to import to determine the project settings.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/33031751-8965390058034163524?l=pkropf.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pkropf.blogspot.com/feeds/8965390058034163524/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=33031751&amp;postID=8965390058034163524' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default/8965390058034163524'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default/8965390058034163524'/><link rel='alternate' type='text/html' href='http://pkropf.blogspot.com/2009/06/running-external-django-scripts.html' title='Running External Django Scripts'/><author><name>Peter Kropf</name><uri>http://www.blogger.com/profile/00798538828224209506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-33031751.post-2447340509886235846</id><published>2009-05-27T10:07:00.001-07:00</published><updated>2009-06-02T16:53:52.006-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='google apps'/><title type='text'>Django Google Apps Authentication</title><content type='html'>&lt;p&gt;Django has an excellent &lt;a href="http://docs.djangoproject.com/en/dev/topics/auth/"&gt;user management and authentication system&lt;/a&gt; built into the framework. With it you can easily create users that can be authenticated against the website.  But there are times when you just need to authenticate against a different system. In the case of an app I recently developed, I originally wanted to authenticate against an OS X Server. The OpenDirectory service on OS X Server is an LDAP server, under the hood you'll find &lt;a href="http://www.openldap.org/software/man.cgi?query=slapd"&gt;slapd&lt;/a&gt; from &lt;a href="http://www.openldap.org/"&gt;OpenLDAP&lt;/a&gt; running. So should be pretty straight forward to create an authentication module that uses Python's &lt;a href="http://www.python-ldap.org/"&gt;LDAP&lt;/a&gt; module. And this &lt;a href="http://www.carthage.edu/webdev/?p=12"&gt;article from the Carthage WebDev&lt;/a&gt; site shows you how to do it.&lt;/p&gt;&lt;p&gt;After I got the ldap_auth.py module working on my site, I realized the site would be better served if the authentication happened against Google Apps. Since Google Apps is currently being used by the organization for email, calendaring and sharing documents, everyone already has an account there. And with the ldap_auth.py module from Carthage Webdev, I thought it would be pretty simple to provide a google_auth.py module.&lt;/p&gt;&lt;p&gt;To get started, I had to install gdata. The installation instructions found on the &lt;a href="http://code.google.com/apis/apps/index.html"&gt;Google Apps APIs&lt;/a&gt; page were pretty easy to follow. Specifically, I had to install the &lt;a href="http://code.google.com/apis/apps/libraries_and_samples.html#provisioningv2"&gt;Provisioning API&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;cite&gt;On a side note, I'm using Python 2.5 as installed via MacPorts. Before I could use the gdata APIs, I had to install py25-socket-ssl.&lt;/cite&gt;&lt;/p&gt;&lt;p&gt;The APIs are pretty well documented via the examples from the &lt;a href="http://code.google.com/apis/apps/gdata_provisioning_api_v2.0_reference_python.html"&gt;Python Developer's Guide&lt;/a&gt;. Here's how I'm authenticating a Django project with users on Google Apps.&lt;/p&gt;&lt;p&gt;To start, there are three configuration variables that I added to the Django project's settings.py module:&lt;br /&gt;&lt;/p&gt;&lt;pre name='code' language='python'&gt;# Google Apps Settings&lt;br /&gt;GAPPS_DOMAIN = 'your_domain.com'&lt;br /&gt;GAPPS_USERNAME = 'name_of_an_admin_user'&lt;br /&gt;GAPPS_PASSWORD = 'admin_users_password'&lt;/pre&gt;&lt;p&gt;These will allow the module to authenticate against Google Apps and ask for specific details about the user.&lt;/p&gt;&lt;p&gt;Here's the code for google_auth.py:&lt;/p&gt;&lt;pre name='code' language='python'&gt;&lt;br /&gt;import logging&lt;br /&gt;from django.contrib.auth.models import User&lt;br /&gt;from django.conf import settings&lt;br /&gt;from gdata.apps.service import AppsService, AppsForYourDomainException&lt;br /&gt;from gdata.docs.service import DocsService&lt;br /&gt;from gdata.service import BadAuthentication&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;logging.debug('GoogleAppsBackend')&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;class GoogleAppsBackend:&lt;br /&gt; """ Authenticate against Google Apps """&lt;br /&gt;&lt;br /&gt; def authenticate(self, username=None, password=None):&lt;br /&gt;     logging.debug('GoogleAppsBackend.authenticate: %s - %s' % (username, '*' * len(password)))&lt;br /&gt;     admin_email = '%s@%s' % (settings.GAPPS_USERNAME, settings.GAPPS_DOMAIN)&lt;br /&gt;     email = '%s@%s' % (username, settings.GAPPS_DOMAIN)&lt;br /&gt;&lt;br /&gt;     try:&lt;br /&gt;         # Check user's password&lt;br /&gt;         logging.debug('GoogleAppsBackend.authenticate: gdocs')&lt;br /&gt;         gdocs = DocsService()&lt;br /&gt;         gdocs.email = email&lt;br /&gt;         gdocs.password = password&lt;br /&gt;         gdocs.ProgrammaticLogin()&lt;br /&gt;         # Get the user object&lt;br /&gt;&lt;br /&gt;         logging.debug('GoogleAppsBackend.authenticate: gapps')&lt;br /&gt;         gapps = AppsService(email=admin_email, password=settings.GAPPS_PASSWORD, domain=settings.GAPPS_DOMAIN)&lt;br /&gt;         gapps.ProgrammaticLogin()&lt;br /&gt;         guser = gapps.RetrieveUser(username)&lt;br /&gt;&lt;br /&gt;         logging.debug('GoogleAppsBackend.authenticate: user - %s' % username)&lt;br /&gt;         user, created = User.objects.get_or_create(username=username)&lt;br /&gt;&lt;br /&gt;         if created:&lt;br /&gt;             logging.debug('GoogleAppsBackend.authenticate: created')&lt;br /&gt;             user.email = email&lt;br /&gt;             user.last_name = guser.name.family_name&lt;br /&gt;             user.first_name = guser.name.given_name&lt;br /&gt;             user.is_active = not guser.login.suspended == 'true'&lt;br /&gt;             user.is_superuser = guser.login.admin == 'true'&lt;br /&gt;             user.is_staff = True&lt;br /&gt;             user.save()&lt;br /&gt;&lt;br /&gt;     except BadAuthentication:&lt;br /&gt;         logging.debug('GoogleAppsBackend.authenticate: BadAuthentication')&lt;br /&gt;         return None&lt;br /&gt;&lt;br /&gt;     except AppsForYourDomainException:&lt;br /&gt;         logging.debug('GoogleAppsBackend.authenticate: AppsForYourDomainException')&lt;br /&gt;         return None&lt;br /&gt;&lt;br /&gt;     return user&lt;br /&gt;&lt;br /&gt;&lt;br /&gt; def get_user(self, user_id):&lt;br /&gt;&lt;br /&gt;     user = None&lt;br /&gt;     try:&lt;br /&gt;         logging.debug('GoogleAppsBackend.get_user')&lt;br /&gt;         user = User.objects.get(pk=user_id)&lt;br /&gt;&lt;br /&gt;     except User.DoesNotExist:&lt;br /&gt;         logging.debug('GoogleAppsBackend.get_user - DoesNotExist')&lt;br /&gt;         return None&lt;br /&gt;&lt;br /&gt;     return user&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;It was pretty easy to write and debug this code using the ldap_auth.py module as a working example.&lt;/p&gt;&lt;p&gt;One downside to this code is that any newly created users in the Django auth database don't have any rights. So if the Django project is expecting to be able to dynamically change the contents based on the rights that the user has, the account will have to manually modified via the Django admin interface. Not too bad, but annoying.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/33031751-2447340509886235846?l=pkropf.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pkropf.blogspot.com/feeds/2447340509886235846/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=33031751&amp;postID=2447340509886235846' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default/2447340509886235846'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default/2447340509886235846'/><link rel='alternate' type='text/html' href='http://pkropf.blogspot.com/2009/05/django-google-apps-authentication.html' title='Django Google Apps Authentication'/><author><name>Peter Kropf</name><uri>http://www.blogger.com/profile/00798538828224209506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-33031751.post-3164011379502855434</id><published>2009-01-18T16:53:00.000-08:00</published><updated>2009-01-18T17:21:35.191-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ssh'/><category scheme='http://www.blogger.com/atom/ns#' term='osx'/><title type='text'>SSH and OS X</title><content type='html'>This has been driving me nuts for the past several months but I hadn't made the time to figure out the problem. Basically, the only account that could be used to ssh into our OS X server was the admin account. The admin account lives in the traditional Unix /etc/passwd database. Any account that was created via Workgroup Manager, like mine, (that is one that lives in Open Directory, OS X's LDAP authentication database) wouldn't work. As I said, this has been driving me nuts and I finally spent some time digging through the man pages, configuration files and log files to figure out what was going on.&lt;br /&gt;&lt;br /&gt;It seems that a previous sysadmin had added the AllowUsers keywords to the sshd configuration file in /etc/sshd_config. On the AllowUsers line were listed the users who were able to connect via ssh. And wouldn't you know, my account wasn't listed.&lt;br /&gt;&lt;br /&gt;I got to this point by reading through the /var/log/secure.log file to see what OS X was recording as the problem with connecting. There was one line in particular that stood out:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;Jan 18 15:13:09 xyzzy sshd[4656]: User peter from 192.168.1.154 not allowed because not listed in AllowUsers&lt;br /&gt;&lt;/pre&gt;AllowUsers? That's strange. I don't remember anywhere in OS X that would use a convention like this to control the environment. But a quick search on Google shows that this was a keyword used in the sshd configuration file. Adding my account name to the list and I was able to ssh in without any problem. Oh yeah, life is good!&lt;br /&gt;&lt;br /&gt;One cool side note, sshd didn't have to be restarted. It's smart enough to know the configuration file has changed. Makes it very easy to test configuration changes.&lt;br /&gt;&lt;br /&gt;But modifying the /etc/sshd_config file every time I need to allow ssh access to someone isn't an easy way to manage account priveleges on OS X. Looking a bit more at the sshd_config man page shows that there's also a AllowGroup option. So I removed the AllowUsers line and replaced it with:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;AllowGroup ssh&lt;br /&gt;&lt;/pre&gt;Then using the standard Workgroup Manager, I added a new group called ssh and put the various accounts that need ssh access into the group. Now any accounts that needs ssh access can easily be added (or removed) from the ssh group and sshd will automatically give them access.&lt;br /&gt;&lt;br /&gt;Yea!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/33031751-3164011379502855434?l=pkropf.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pkropf.blogspot.com/feeds/3164011379502855434/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=33031751&amp;postID=3164011379502855434' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default/3164011379502855434'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default/3164011379502855434'/><link rel='alternate' type='text/html' href='http://pkropf.blogspot.com/2009/01/ssh-and-os-x.html' title='SSH and OS X'/><author><name>Peter Kropf</name><uri>http://www.blogger.com/profile/00798538828224209506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-33031751.post-8585530109553452565</id><published>2008-12-30T00:14:00.001-08:00</published><updated>2008-12-30T12:40:24.678-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='LED'/><category scheme='http://www.blogger.com/atom/ns#' term='Arduino'/><title type='text'>Arduino and LEDs</title><content type='html'>&lt;p&gt;I’m a software developer by training and one aspect of the software that I’ve always developed is that it just lives inside a computer or network. It doesn’t interact with the real world. That’s been well and good for the last 20 years but of late I’ve been wanting to break out and have software that I develop effect some change in the physical world.&lt;/p&gt;&lt;p&gt;There have been fits and starts where I’ve tried writing code for some embedded system or chip but the mountain of basic knowledge thats needed to get started has always been overwhelming. I would read some book (like &lt;a href="http://www.amazon.com/Embedded-System-Design-Shoestring-Technology/dp/0750676094"&gt;Embedded System Design on a Shoestring&lt;/a&gt; ) and while the books make it seem easy to get going, the projects would always die a quick and quiet death. There’s nothing wrong with Embedded System Design on a Shoestring or any of the other books I tried, many people have used them quite successfully. Unfortunately it just never clicked for me.&lt;/p&gt;&lt;p&gt;But that’s all changed.&lt;/p&gt;&lt;p&gt;I recently discovered the &lt;a href="http://www.arduino.cc/"&gt;Arduino&lt;/a&gt; platform. The system provides a small microprocessor, a bunch of I/O pins, a USB connection and a very easy to use IDE. The basic features of the platform allow for most any type of interaction with the physical world that I can currently envision. I’m sure there will be some scenario that it won’t support but for right now, I’m very happy with the capabilities.&lt;/p&gt;&lt;p&gt;One of the problems that I encountered in the past was connecting an embedded system to a host platform. It may have involved a vendor specific hardware or something else that wasn’t supported on my systems. The Arduino uses a USB port. After installing the needed drivers on my OS X system, I was able to connect the Arduino without any problems. Seems way easier than other solutions.&lt;/p&gt;&lt;p&gt;The place that I see the Arduino really shining is with the IDE. It’s different that any other IDE that I’ve used but the interface is really straight forward and very easy to use. I was able to quickly try out one of the sample C programs that turns an LED on and off again. The thing that really got me was that it compiled, loaded and ran on the first try. This was just brilliant. Easy to use and works the first time. Outstanding.&lt;/p&gt;&lt;p&gt;When a friend asked for help with a project he was doing and told me that he’d like to have 3 LEDs turning on and off in a random fashion, I knew that an Arduino would be the perfect tool to use. I was able to rework the sample code for turning an LED on and off to work on this project.&lt;/p&gt;The idea behind the project is to have an LED inside of a wax pumpkin where the LED would turn on and off like the light on a firefly. There would be three pumpkins on a small platform, hence the three LEDs.&lt;br /&gt;&lt;p&gt;Here’s the code for the project:&lt;/p&gt;&lt;pre&gt;&lt;br /&gt;/*&lt;br /&gt;* firefly multibutt illumination&lt;br /&gt;*&lt;br /&gt;* Copyright (c) 2008 Peter Kropf. All rights reserved.&lt;br /&gt;*&lt;br /&gt;* Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;* of this software and associated documentation files (the "Software"), to deal&lt;br /&gt;* in the Software without restriction, including without limitation the rights&lt;br /&gt;* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;* copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;* furnished to do so, subject to the following conditions:&lt;br /&gt;*&lt;br /&gt;* The above copyright notice and this permission notice shall be included in&lt;br /&gt;* all copies or substantial portions of the Software.&lt;br /&gt;*&lt;br /&gt;* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN&lt;br /&gt;* THE SOFTWARE.&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;// all times are in milliseconds&lt;br /&gt;#define SPIN_DELAY 10&lt;br /&gt;&lt;br /&gt;#define ILLUMINATION_MAX       255&lt;br /&gt;#define ILLUMINATION_MIN       0&lt;br /&gt;#define ILLUMINATION_INCRIMENT 5&lt;br /&gt;&lt;br /&gt;#define MAX_ILLUMINATION_TIME 2000&lt;br /&gt;#define MIN_ILLUMINATION_TIME 500 &lt;br /&gt;&lt;br /&gt;#define MAX_DARK_TIME 10000&lt;br /&gt;#define MIN_DARK_TIME 2000&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;#define LED_COUNT    6&lt;br /&gt;#define LED_OFF      1&lt;br /&gt;#define LED_ON       2&lt;br /&gt;#define LED_SPINUP   3&lt;br /&gt;#define LED_SPINDOWN 4&lt;br /&gt;&lt;br /&gt;int led_pin[LED_COUNT]   = {3, 5, 6, 9, 10, 11};&lt;br /&gt;int led_state[LED_COUNT] = {LED_OFF, LED_OFF, LED_OFF, LED_OFF, LED_OFF, LED_OFF};&lt;br /&gt;int led_spin[LED_COUNT]  = {0, 0, 0, 0, 0, 0};&lt;br /&gt;int led_level[LED_COUNT] = {0, 0, 0, 0, 0, 0};&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;void setup()&lt;br /&gt;{&lt;br /&gt;   randomSeed(analogRead(0));&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;void loop()&lt;br /&gt;{&lt;br /&gt;   for (int led = 0; led &lt; LED_COUNT; led++) {&lt;br /&gt;       switch (led_state[led]) {&lt;br /&gt;       case LED_OFF:&lt;br /&gt;           if (led_spin[led] &gt; SPIN_DELAY) {&lt;br /&gt;               led_spin[led] -= SPIN_DELAY;&lt;br /&gt;           } else {&lt;br /&gt;               led_state[led] = LED_SPINUP;&lt;br /&gt;           }&lt;br /&gt;           break;&lt;br /&gt;&lt;br /&gt;       case LED_ON:&lt;br /&gt;           if (led_spin[led] &gt; SPIN_DELAY) {&lt;br /&gt;               led_spin[led] -= SPIN_DELAY;&lt;br /&gt;           } else {&lt;br /&gt;               led_state[led] = LED_SPINDOWN;&lt;br /&gt;           }&lt;br /&gt;           break;&lt;br /&gt;&lt;br /&gt;       case LED_SPINUP:&lt;br /&gt;           if (led_level[led] &lt; ILLUMINATION_MAX) {&lt;br /&gt;               led_level[led] += ILLUMINATION_INCRIMENT;&lt;br /&gt;           } else {&lt;br /&gt;               led_level[led] = ILLUMINATION_MAX;&lt;br /&gt;               led_state[led] = LED_ON;&lt;br /&gt;               led_spin[led]  = random(MIN_ILLUMINATION_TIME, MAX_ILLUMINATION_TIME);&lt;br /&gt;           }&lt;br /&gt;           break;&lt;br /&gt;&lt;br /&gt;       case LED_SPINDOWN:&lt;br /&gt;           if (led_level[led] &gt; ILLUMINATION_MIN) {&lt;br /&gt;               led_level[led] -= ILLUMINATION_INCRIMENT;&lt;br /&gt;           } else {&lt;br /&gt;               led_level[led] = ILLUMINATION_MIN;&lt;br /&gt;               led_state[led] = LED_OFF;&lt;br /&gt;               led_spin[led]  = random(MIN_DARK_TIME, MAX_DARK_TIME);&lt;br /&gt;           }&lt;br /&gt;           break;&lt;br /&gt;       }&lt;br /&gt;&lt;br /&gt;       analogWrite(led_pin[led], led_level[led]);&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   delay(SPIN_DELAY);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;The code is pretty straight forward. Each of the LEDs has a particular state that it’s in: off, on, spinning up or spinning down. That’s tracked in the &lt;em&gt;led_&lt;/em&gt;&lt;span style="font-style: italic;"&gt;state&lt;/span&gt; array. The &lt;em&gt;led_&lt;/em&gt;&lt;span style="font-style: italic;"&gt;level&lt;/span&gt; array holds the current level of illumination for each of the LEDs and the &lt;em&gt;led_spin&lt;/em&gt; array holds the random amount of time that the LED will remain on or off. The rest of the code manages the arrays, sets the current level of the LEDs and waits for a small delay before doing it all over again.&lt;p&gt;Note that the code is currently setup to illuminate up to 6 LEDs since that’s the number of digital I/O lines on the Arduino that supports pulse width modulation, PWM. PWM is how the Arduino is able to vary the light level produced by the LEDs.&lt;/p&gt;&lt;p&gt;Here's a short video I took while working on the LED timing. It shows only one LED instead of the three that were used in the finished project.&lt;br /&gt;&lt;/p&gt; &lt;object width="320" height="266" class="BLOG_video_class" id="BLOG_video-7a2a1fd54d474bc5" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"&gt;&lt;param name="movie" value="http://www.youtube.com/get_player"&gt;&lt;param name="bgcolor" value="#FFFFFF"&gt;&lt;param name="allowfullscreen" value="true"&gt;&lt;param name="flashvars" value="flvurl=http://v13.nonxt6.googlevideo.com/videoplayback?id%3D7a2a1fd54d474bc5%26itag%3D5%26app%3Dblogger%26ip%3D0.0.0.0%26ipbits%3D0%26expire%3D1331689804%26sparams%3Did,itag,ip,ipbits,expire%26signature%3D258827758121766A9B6D989D9BB45BD6764A2D70.8028D5A9330CEA158C6F8C898CC37AF8F1BA5E1D%26key%3Dck1&amp;amp;iurl=http://video.google.com/ThumbnailServer2?app%3Dblogger%26contentid%3D7a2a1fd54d474bc5%26offsetms%3D5000%26itag%3Dw160%26sigh%3DysMBSqSFGeVf93acSoSM8ZB3Ys8&amp;amp;autoplay=0&amp;amp;ps=blogger"&gt;&lt;embed src="http://www.youtube.com/get_player" type="application/x-shockwave-flash"width="320" height="266" bgcolor="#FFFFFF"flashvars="flvurl=http://v13.nonxt6.googlevideo.com/videoplayback?id%3D7a2a1fd54d474bc5%26itag%3D5%26app%3Dblogger%26ip%3D0.0.0.0%26ipbits%3D0%26expire%3D1331689804%26sparams%3Did,itag,ip,ipbits,expire%26signature%3D258827758121766A9B6D989D9BB45BD6764A2D70.8028D5A9330CEA158C6F8C898CC37AF8F1BA5E1D%26key%3Dck1&amp;iurl=http://video.google.com/ThumbnailServer2?app%3Dblogger%26contentid%3D7a2a1fd54d474bc5%26offsetms%3D5000%26itag%3Dw160%26sigh%3DysMBSqSFGeVf93acSoSM8ZB3Ys8&amp;autoplay=0&amp;ps=blogger"allowFullScreen="true" /&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;With this project successfully completed, I’m looking forward to my next project: having a flashing light turned on when a specific voicemail box on a Asterisk based phone system has messages waiting. This’ll involve connecting an Arduino to ethernet and controlling a 120v circuit. I can’t wait.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/33031751-8585530109553452565?l=pkropf.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='enclosure' type='video/mp4' href='http://www.blogger.com/video-play.mp4?contentId=7a2a1fd54d474bc5&amp;type=video%2Fmp4' length='0'/><link rel='replies' type='application/atom+xml' href='http://pkropf.blogspot.com/feeds/8585530109553452565/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=33031751&amp;postID=8585530109553452565' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default/8585530109553452565'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default/8585530109553452565'/><link rel='alternate' type='text/html' href='http://pkropf.blogspot.com/2008/12/arduino-and-leds.html' title='Arduino and LEDs'/><author><name>Peter Kropf</name><uri>http://www.blogger.com/profile/00798538828224209506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-33031751.post-883752217085901526</id><published>2008-12-27T10:10:00.000-08:00</published><updated>2008-12-30T00:49:39.821-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Curmudgeon'/><title type='text'>Curmudgeon</title><content type='html'>&lt;p&gt;There are times that I think I'm turning into an old curmudgeon. Complaining about the kids riding up and down the street on their two-stroke gas powered scooters or bemoaning some new technology like twitter. It seems that as we get older, there's a mindset that's easy to adopt where things that are different or new are somehow bad. And when I realize that I'm there, I don't like it.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/33031751-883752217085901526?l=pkropf.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pkropf.blogspot.com/feeds/883752217085901526/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=33031751&amp;postID=883752217085901526' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default/883752217085901526'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/33031751/posts/default/883752217085901526'/><link rel='alternate' type='text/html' href='http://pkropf.blogspot.com/2008/12/there-are-times-that-i-think-im-turning.html' title='Curmudgeon'/><author><name>Peter Kropf</name><uri>http://www.blogger.com/profile/00798538828224209506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
