I came across this hint about display properties on StackOverflow and thought it was worthwhile to write down for later. If you want to get the screen or Desktop resolution of a Mac via Python, you can do so with PyObjC.
First, let’s get the information about the main screen:
If you want just the horizontal and vertical resolution from that blob of data, you can pull the width and height out:
This might be useful in situations where you don’t have any of the “hundred of portable libs in Python that give you access to that information” — such as in your stock Mac OS X Python installation. To clarify: I’m in no way meaning to belittle that there are portable libraries that would let you do the same thing, but you also have to program for your audience and its constraints. One of the reasons I appreciate Python over some scripting languages is that you get so much capability in the Standard Library. However, on Mac OS X, you don’t get modules like pygame by default (yet … and maybe never) while you do get PyObjC.
A question came up on the AppleScript-Users mailing list and I wanted to make note if it, because I came up with a quick Python-based way to answer it. Here, I reiterate that answer on how to get the number of pages from a PDF.
This Python 2.5 sequence (shown as run interactively from the Terminal on Leopard — not as a script) works for me using both a local file and a file from an AFP-mounted volume. It uses the Quartz 2D bindings for Python, which are part of the Python install on Mac OS X (since 10.3?), so it is not pure Python.
Substitute your own path for the pdf_filename variable’s contents, and you can try it yourself. (Above, $ is the shell prompt, and >>> is the Python interpreter’s command prompt, so don’t copy/paste those.)
Since I was doing this in Terminal, I could also type "pdf_filename = '" then drag and drop a file from the Finder into Terminal window to insert its path, complete the quoting, and pressed Return. (Saves some time and potential for mistyping.)
You could wrap this sequence into one longish command line and run it from AppleScript with “do shell script.” Or you could save it as a Python script file and again run it with “do shell script.” Or, you could skip AppleScript and just use Python, which I prefer over AppleScript. (Python seemed very natural to me with my minor AppleScript background, and I'm pretty sure I've written more of it than AppleScript by this point.)
This is derived from “Example 2: Splitting a PDF File” in the Using Python with Quartz 2D on Mac OS X document at the Apple Developer Connection.
That ADC example also shows how you could specify a PDF file’s path at the command line (with sys.argv), rather than hardcoding it as I did for my example.
There may be a shorter way to do this since I’m no expert on Quartz 2D. (I just appreciate that the bindings are there for a scripting language I happen to like a lot.) I honestly don’t know what’s happening when the provider and pdf objects are being set, but I really don’t need to know for this.
I have been struggling with the issue of module availability in Python. While the “batteries included” nature of the standard library is great, there are occasionally times when I need to resort to a module that isn’t included with Python.
There are also times where I’m using modules whose status has changed. I expect that to happen more in the eventual transition to Python 2.6 and 3.0, because I’ve used modules that are being deprecated.
So I wondered how I could conditionally import a module if it was available, without stopping the flow of my scripts — and gracefully handle situations where it is missing. And here’s one basic answer: use a “try” block to catch the “ImportError” exception. For example, if I were concerned that DNSPython wasn’t going to be installed on my target system:
try:
import dns.resolver # Import DNSPython
dnspython_available = True
except ImportError:
dnspython_available = False
The “except ImportError” clause could specify a different module to load, or other workarounds entirely. You could map the namespaces in the “try” bock so the rest of your script doesn’t notice the change in module functions, if you have a way to work around the missing module. Perhaps, you could even try to obtain and install the module, at least for temporary use by your script.
Thanks to authors of the article Python modules – how do they work? for the assist. The information under heading 2.11, “Is my module available?” answered my question and has given me something to think about.
Drat! I’ve learned that the commands module for Python, which I use, is deprecated and removed in Python according to PEP 3108. That means I can no longer safely call commands.getstatusoutput() anymore. I’ve frequently used this call in the past because it seemed the sanest, easiest way to call for a shell utility and get both its output and return status (for success or error).
I’ll have to find some other way to perform the same function — preferably one that will work on Python 2.3 from Mac OS X Tiger, Python 2.5.1 from Leopard, and future Pythons. The stated replacement for a number of similar modules (including popen2, which frankly kind-of frightened me off with its name) is the subprocess module from PEP 324, but I don’t know if that will work for my purposes.
There are also a bunch of Mac-specific modules being removed. I don’t use any of them right now, but that doesn’t mean they wouldn’t have been useful.
This kind of thing is spirit-crushing to me for some reason. I’m especially annoyed that Python has been around for so long and it is still reorganizing the ways it calls shell commands. Just settle on something! It seems hard to take it seriously as a system administration scripting language when things like this happen.
On the other hand, I love so much of the Python Standard Library, which has afforded me a lot for system administration …
I’m constantly astounded by the breadth of features available in the Python Standard Library. Although the functions I find there are not always easy to grasp, it is almost always worth searching around a bit for a function or method in the standard library before I write my own code to do something someone else has probably had to do before.
Take the “locale” module. It lets you format certain kinds of data based on your locale and its customs. Numbers (including currency) happen to be one of its specialties. Since I had a need to output long numbers whose digits were grouped with commas — which makes them easier to read — and locale.format() does just that. Even better, it’s internationalized and formats them for your system’s own locale.
>>> import locale
>>> a = {'size': 123456789, 'unit': 'bytes'}
>>> print(locale.format("%(size).2f", a, 1))
123456789.00
>>> locale.setlocale(locale.LC_ALL, '') # Set the locale for your system
'en_US.UTF-8'
>>> print(locale.format("%(size).2f", a, 1))
123,456,789.00
In trying to use it in the bundled Python 2.5.1 on Mac OS X Leopard, I noticed that the default for scripts and the interpreter doesn’t format numbers as I expected for my locale. I found that I needed to set a locale to get the expected formatting. To do this, I run locale.setlocale(), as above. I’m not sure if this is required for other Python installations, but it’s worth mentioning.
One difficulty I created for myself was when I tried mixing more into my format string. For example, locale.format() would fail to reformat a number when I added in the string for the unit from my original dictionary:
>>> print(locale.format("%(size).2f %(unit)s", a, 1))
123456789.00 bytes
In retrospect, this makes total sense, but it took Mark looking over my code to discover my error.
The Distutils Python module includes functions to obtain information about the Python installation. This may be useful for system administrators, and it certainly caught my eye when I read about it.
The results below are from Apple’s bundled build of Python 2.5.1 in Mac OS X Leopard. Credit for the comments describing each function comes from the distutils.sysconfig documentation.
>>> import distutils.sysconfig
>>> distutils.sysconfig.get_python_version() # Get the major Python version without patchlevel
'2.5'
>>> distutils.sysconfig.get_python_lib(standard_lib=True) # Return the directory containing the Python library; if 'standard_lib' is true, return the directory containing standard Python library modules
'/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5'
>>> distutils.sysconfig.get_python_lib() # Otherwise, return the directory for site-specific modules
'/Library/Python/2.5/site-packages'
>>> distutils.sysconfig.get_python_lib(plat_specific=True) # Return any platform-specific modules from a non-pure-Python module distribution
'/Library/Python/2.5/site-packages'
You’d install your own modules for system-wide use in the directory returned by distutils.sysconfig.get_python_lib().
In Python, there is a built-in string method, “startswith,” that lets you determine whether a line starts with a character. I’ve used it before because I tend to want to use the features of the standard library, despite having awesome features like slicing at my disposal. What I didn’t realize immediately, though, was how to compare the string against two or more sets of characters. All I knew is that entering a list as the prefix — instead of the more common single string — didn’t work.
Luckily, my question was answered right away in the string method documentation. I found that to perform these comparisons together, you must supply the “startswith” string method with a tuple. It works the same for the “endswith” method. However, this does require Python 2.5.
>>> starts_with = ('f', 'a')
>>> def does_startwith(selected_text):
... if selected_text.startswith(starts_with):
... print True
...
>>> does_startwith('z is a letter')
>>> does_startwith('f is a letter')
True
>>> does_startwith('b is a letter')
>>> does_startwith('a is a letter')
True
This has some application to a larger question I had, so I wanted to note it.
Every time I do hashing in Python, I have to look it up. I forget how to do it. That's probably a bad thing, at least compared to the shell. The shell way isn't exactly simple, but I find it something I can do by rote.
I'm going to write down how I got MD5 and SHA-1 hashes for a file — which is something I occasionally have to do when posting a download, for example — thereby making it possible to find my own perfectly-tailored how-to next time:
>>> import hashlib # hashlib is new in Python 2.5
>>> file_reference=open('/path/to/file', 'rb').read() # open the file for reading, in binary mode
>>> hashlib.md5(file_reference).hexdigest()
'11fb57ba7927ad04534d0a341dd9c943'
>>> hashlib.sha1(file_reference).hexdigest()
'bff8e8bcd74662ee52dde369e9387cb10d5a5ece'
There, that wasn't so bad. I just have to remember the name of the built-in hashlib module and how to call for a hash of some data with it. You're missing the twenty other lines I tried which didn't work, of course, but you don't really need to see that. Sigh.
Without specifying hexdigest(), the result is a hash object rather than the hash value.
>>> hashlib.md5(file_reference)
<md5 HASH object @ 0x639c0>
I compared the Python hashlib results above with the following output from OpenSSL, and they are the same:
$ openssl md5 /path/to/file
MD5(/path/to/file)= 11fb57ba7927ad04534d0a341dd9c943
$ openssl sha1 /path/to/file
SHA1(/path/to/file)= bff8e8bcd74662ee52dde369e9387cb10d5a5ece
On balance, I think I'd still like comparing that hash against another string better in Python, but getting the hashes was quite a bit more confusing to me. It was enough to interrupt my flow.
I have to say that after my upgrade and implementation of Drupal 5.1, and my recent discoveries of the joys of system administration scripting with Python, I’m feeling hugely empowered.
I have a lot of newfound software tools at my disposal that just make me feel like I can do a lot more than I could have done in the same amount of time six months ago. It’s a good feeling.
Maybe this is what it feels like to have The Force (of Star Wars) “surround us, penetrate us, and bind the galaxy together.” Or maybe I’m just reacting to the nice weather we’ve been having this week.
After talking with Steve last Friday about how I really wanted a way to compare Mac OS X version numbers, I set off to find one myself. Determining if your script is running on an operating system version that meets your minimum requirements and finding out of if one version of the system is older/newer than another are very practical yet repetitive tasks for systems administrators. I thought I would have to code a common function for a module, so that we didn’t keep re-creating it in every script where it was needed.
It turns out that task is amazingly simple in Python … again, because batteries are included. The comparison is already there, because distutils.version lets you create version objects that can be compared easily with standard operators. I’ll stand on the shoulders of giants, sure! That’s the whole point of software development. (And you thought Perl programmers were lazy. Grin.)
Here’s an example, using the Python version that shipped with Mac OS X Tiger:
>>> from distutils import version
>>> a = version.StrictVersion('10.4.10')
>>> b = version.StrictVersion('10.4.9')
>>> c = version.StrictVersion('10.4.1')
>>> d = version.StrictVersion('10.4')
>>> e = version.StrictVersion('10.3.9')
>>> a > b
True
>>> a > c
True
>>> a > d
True
>>> a > e
True
>>> b > a
False
>>> b > c
True
>>> b > d
True
>>> b > e
True
>>> e > a
False
>>> d > e
True
As you can see, the mythical version “10.4.10” — were there to be one — would indeed show up as newer than “10.4.9” and all previous version objects I created with version.StrictVersion. Even if that version is never shipped by Apple, we’ve all wondered when or if they would jump past a .9 revision. If they do, distutils.version will still handle it: the 10.4.10 version here is greater than both 10.4.9 and 10.4.1, which are two important test cases.
Each greater than or less than test — using the standard comparison operators — came out as I would expect and hope. Using version.StrictVersion objects in such comparisons works quite well … and means I don’t have to code much logic at all. That’s less code for me and for a future maintenance programmer (read “harried systems administrator”) to review.
This should serve as a valid model for comparing version numbers throughout the entire Mac OS X line on a modern system, assuming you have Python with distutils (and I will readily admit that I don’t know how far back that goes).
Update: If you use the output of platform.version() for distutils.version objects, you can also compare version numbers for Microsoft Windows in Python. At least, the format for the Windows Vista version numbers appear to work, and what I recall of the numbering for earlier versions should match.