Bob Ippolito avatar Bob Ippolito committed 034780f

New PyObjCTools.Conversion functionality

Comments (0)

Files changed (3)

Lib/PyObjCTools/Conversion.py

-#!/usr/bin/env python
+"""
+Conversion.py -- Tools for converting between Python and Objective-C objects.
 
-"""Conversion.py -- Tools for converting between Python and Objective-C objects.
-
-Conversion offers API to convert between Python and Objective-C instances of various classes.   Currently, the focus is on Python and Objective-C collections.
+Conversion offers API to convert between Python and Objective-C instances of 
+various classes.   Currently, the focus is on Python and Objective-C 
+collections.
 """
 
-from Foundation import NSArray, NSDictionary, NSMutableArray
-from Foundation import NSMutableDictionary, NSNull, NSNumber
-from types import *
-import sys
+__all__ = [
+    'pythonCollectionFromPropertyList', 'propertyListFromPythonCollection',
+    'serializePropertyList', 'deserializePropertyList',
+    'toPythonDecimal', 'fromPythonDecimal',
+]
 
+from Foundation import *
+import datetime
+import time
+try:
+    import decimal
+except ImportError:
+    decimal = None
 
+PYTHON_TYPES = (
+    basestring, bool, int, float, long, list, tuple, dict,
+    datetime.date, datetime.datetime, bool, buffer, type(None),
+)
+
+DECIMAL_LOCALE = NSDictionary.dictionaryWithObject_forKey_(
+    u'.', NSDecimalSeparator)
+
+def toPythonDecimal(aNSDecimalNumber):
+    """
+    Convert a NSDecimalNumber to a Python decimal.Decimal
+    """
+    return decimal.Decimal(
+        aNSDecimalNumber.descriptionWithLocale_(DECIMAL_LOCALE))
+
+def fromPythonDecimal(aPythonDecimal):
+    """
+    Convert a Python decimal.Decimal to a NSDecimalNumber
+    """
+    return NSDecimalNumber.decimalNumberWithString_locale_(
+        unicode(aPythonDecimal), DECIMAL_LOCALE)
+
+FORMATS = dict(
+    xml=NSPropertyListXMLFormat_v1_0,
+    binary=NSPropertyListBinaryFormat_v1_0,
+    ascii=NSPropertyListOpenStepFormat,
+)
+
+def serializePropertyList(aPropertyList, format='xml'):
+    """
+    Serialize a property list to an NSData object.  Format is one of the
+    following strings:
+    
+    xml (default):
+        NSPropertyListXMLFormat_v1_0, the XML representation
+
+    binary:
+        NSPropertyListBinaryFormat_v1_0, the efficient binary representation
+
+    ascii:
+        NSPropertyListOpenStepFormat, the old-style ASCII property list
+
+    It is expected that this property list is comprised of Objective-C
+    objects.  In most cases Python data structures will work, but
+    decimal.Decimal and datetime.datetime objects are not transparently
+    bridged so it will fail in that case.  If you expect to have these
+    objects in your property list, then use propertyListFromPythonCollection
+    before serializing it.
+    """
+    try:
+        formatOption = FORMATS[format]
+    except KeyError:
+        raise TypeError("Invalid format: %s" % (format,))
+    data, err = NSPropertyListSerialization.dataFromPropertyList_format_errorDescription_(aPropertyList, formatOption)
+    if err is not None:
+        # braindead API!
+        errStr = err.encode('utf-8')
+        err.release()
+        raise TypeError(errStr)
+    return data
+
+def deserializePropertyList(propertyListData):
+    """
+    Deserialize a property list from a NSData, str, unicode or buffer
+
+    Returns an Objective-C property list.
+    """
+    if isinstance(propertyListData, str):
+        propertyListData = buffer(propertyListData)
+    elif isinstance(propertyListData, unicode):
+        propertyListData = buffer(propertyListData.encode('utf-8'))
+    plist, fmt, err = NSPropertyListSerialization.propertyListFromData_mutabilityOption_format_errorDescription_(propertyListData, NSPropertyListMutableContainers)
+    if err is not None:
+        # braindead API!
+        errStr = err.encode('utf-8')
+        err.release()
+        raise TypeError(errStr)
+    return plist
 
 def propertyListFromPythonCollection(aPyCollection, conversionHelper=None):
-    """Convert a Python collection (dictionary, array, tuple, string) into an Objective-C collection.
+    """
+    Convert a Python collection (dict, list, tuple, string) into an
+    Objective-C collection.
 
-    If conversionHelper is defined, it must be a callable.  It will be called for any object encountered for which propertyListFromPythonCollection() cannot automatically convert the object.   The supplied helper function should convert the object and return the converted form.  If the conversion helper cannot convert the type, it should raise an exception or return None.
+    If conversionHelper is defined, it must be a callable.  It will be called
+    for any object encountered for which propertyListFromPythonCollection()
+    cannot automatically convert the object.   The supplied helper function
+    should convert the object and return the converted form.  If the conversion
+    helper cannot convert the type, it should raise an exception or return
+    None.
     """
     if isinstance(aPyCollection, dict):
         collection = NSMutableDictionary.dictionary()
         for aKey in aPyCollection:
-            convertedValue = propertyListFromPythonCollection( aPyCollection[aKey], conversionHelper=conversionHelper )
-            if convertedValue is not None:
-                collection.setObject_forKey_( convertedValue , aKey )
+            if not isinstance(aKey, basestring):
+                raise TypeError("Property list keys must be strings")
+            convertedValue = propertyListFromPythonCollection(
+                aPyCollection[aKey], conversionHelper=conversionHelper)
+            collection[aKey] = convertedValue
         return collection
-    elif isinstance(aPyCollection,  (list, tuple)):
+    elif isinstance(aPyCollection, (list, tuple)):
         collection = NSMutableArray.array()
         for aValue in aPyCollection:
-            convertedValue = propertyListFromPythonCollection( aValue, conversionHelper=conversionHelper )
-            if convertedValue is not None:
-                collection.addObject_( convertedValue  )
+            convertedValue = propertyListFromPythonCollection(aValue,
+                conversionHelper=conversionHelper)
+            collection.append(aValue)
         return collection
-    elif isinstance(aPyCollection, (str, unicode)):
-        return aPyCollection # bridge will convert to NSString
-    elif sys.version_info[:2] >= (2,3) and isinstance(aPyCollection, bool):
-        return NSNumber.numberWithBool_( aPyCollection )
-    elif isinstance(aPyCollection, int):
-        return NSNumber.numberWithLong_( aPyCollection )
-    elif isinstance(aPyCollection, int):
-        return NSNumber.numberWithLong_( aPyCollection )
-    elif isinstance(aPyCollection, long):
-        return NSNumber.numberWithLongLong_( aPyCollection )
-    elif isinstance(aPyCollection, float):
-        return NSNumber.numberWithLongDouble_( aPyCollection )
-    elif aPyCollection is None:
-        # XXX: None cannot be represented in PLists.
-        return NSNull.null()
-    else:
-        if conversionHelper:
-            return conversionHelper(aPyCollection)
-    raise TypeError, "Type '%s' encountered in python collection;  don't know how to convert." % type(aPyCollection)
+    elif isinstance(aPyCollection, (datetime.datetime, datetime.date)):
+        return NSDate.dateWithTimeIntervalSince1970_(
+            time.mktime(aPyCollection.timetuple()))
+    elif decimal is not None and isinstance(aPyCollection, decimal.Decimal):
+        return fromPythonDecimal(aPyCollection)
+    elif isinstance(aPyCollection, PYTHON_TYPES):
+        # bridge will convert
+        return aPyCollection
+    elif conversionHelper is not None:
+        return conversionHelper(aPyCollection)
+    raise TypeError("Type '%s' encountered in Python collection; don't know how to convert." % type(aPyCollection))
+
 
 def pythonCollectionFromPropertyList(aCollection, conversionHelper=None):
-    """Converts a Foundation based collection-- a property list-- into a Python collection.
+    """
+    Converts a Foundation based property list into a Python
+    collection (all members will be instances or subclasses of standard Python
+    types)
 
-    Like propertyListFromPythonCollection(), conversionHelper is an optional callable that will be invoked any time an encountered object cannot be converted.
+    Like propertyListFromPythonCollection(), conversionHelper is an optional
+    callable that will be invoked any time an encountered object cannot be
+    converted.
     """
     if isinstance(aCollection, NSDictionary):
         pyCollection = {}
         for k in aCollection:
-            convertedValue = pythonCollectionFromPropertyList(aCollection[k], conversionHelper)
+            if not isinstance(k, basestring):
+                raise TypeError("Property list keys must be strings")
+            convertedValue = pythonCollectionFromPropertyList(
+                aCollection[k], conversionHelper)
             pyCollection[k] = convertedValue
         return pyCollection
     elif isinstance(aCollection, NSArray):
-        pyCollection = [] * len(aCollection)
-        for i in range(len(aCollection)):
-            convertedValue = pythonCollectionFromPropertyList(aCollection[i], conversionHelper)
-            pyCollection.append(convertedValue)
-        return pyCollection
-    elif isinstance(aCollection, NSNumber):
-        objCType = aCollection.objCType()
-        if objCType is 'c': return aCollection.charValue()
-        elif objCType is 'C': return aCollection.charValue()
-        elif objCType is 's': return aCollection.shortValue()
-        elif objCType is 'S': return aCollection.unsignedShortValue()
-        elif objCType is 'i': return aCollection.intValue()
-        elif objCType is 'I': return aCollection.unsignedIntValue()
-        elif objCType is 'l': return aCollection.longValue()
-        elif objCType is 'L': return aCollection.unsignedLongValue()
-        elif objCType is 'f': return aCollection.floatValue()
-        elif objCType is 'd': return aCollection.doubleValue()
-        elif objCType is 'b': return aCollection.boolValue()
-        elif objCType is 'q': return aCollection.longLongValue()
-        raise TypeError, "Type '%s' encountered within an instance of the NSValue class." % type(objCType)
-    elif isinstance(aCollection, (str, unicode)):
+        return [
+            pythonCollectionFromPropertyList(item, conversionHelper)
+            for item in aCollection
+        ]
+    elif isinstance(aCollection, NSData):
+        return buffer(aCollection)
+    elif isinstance(aCollection, NSDate):
+        return datetime.datetime.fromtimestamp(
+            aCollection.timeIntervalSince1970())
+    elif isinstance(aCollection, NSDecimalNumber) and decimal is not None:
+        return toPythonDecimal(aCollection)
+    elif aCollection is NSNull.null():
+        return None
+    elif isinstance(aCollection, PYTHON_TYPES):
         return aCollection
-    elif isinstance(aCollection, (int, float, long)):
-        return aCollection
-    else:
-        if conversionHelper:
-            return conversionHelper(aCollection)
-    raise TypeError, "Type '%s' encountered in ObjC collection;  don't know how to convert." % type(aCollection)
+    elif conversionHelper:
+        return conversionHelper(aCollection)
+    raise TypeError("Type '%s' encountered in ObjC collection;  don't know how to convert." % type(aCollection))
 <p>An overview of the relevant changes in new, and older, releases.</p>
 <h2><a name="version-1-3-5-2005-05">Version 1.3.5 (2005-05-??)</a></h2>
 <ul>
+<li><code><span>PyObjCTools.Conversion</span></code> functions now support all property list
+types with the following conversions:<ul>
+<li>NSData &lt;-&gt; buffer</li>
+<li>NSDecimalNumber &lt;-&gt; decimal.Decimal (if present)</li>
+<li>NSDate &lt;-&gt; datetime.datetime</li>
+</ul>
+<p>New <code><span>toPythonDecimal</span></code>, <code><span>fromPythonDecimal</span></code> functions which convert
+between NSDecimalNumber and decimal.Decimal using an intermediate string.</p>
+<p>New <code><span>serializePropertyList</span></code> and <code><span>deserializePropertyList</span></code> functions
+which serialize (Objective-C) property lists to and from NSData.</p>
+</li>
 <li><code><span>OC_PythonObject</span></code>, the proxy for Python objects that do not have
 an Objective-C superclass and are not otherwise special-cased, now
 act slightly more like typical Objective-C objects (supporting
 Version 1.3.5 (2005-05-??)
 --------------------------
 
+- ``PyObjCTools.Conversion`` functions now support all property list
+  types with the following conversions:
+
+  - NSData <-> buffer
+  - NSDecimalNumber <-> decimal.Decimal (if present)
+  - NSDate <-> datetime.datetime
+
+  New ``toPythonDecimal``, ``fromPythonDecimal`` functions which convert
+  between NSDecimalNumber and decimal.Decimal using an intermediate string.
+
+  New ``serializePropertyList`` and ``deserializePropertyList`` functions
+  which serialize (Objective-C) property lists to and from NSData.
+
 - ``OC_PythonObject``, the proxy for Python objects that do not have
   an Objective-C superclass and are not otherwise special-cased, now
   act slightly more like typical Objective-C objects (supporting
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.