Commits

Bob Ippolito committed 5f86cde

Revert the change that made the str bridge disabled by default...

- ``NSData`` and ``NSMutableData`` instances now support the Python buffer
protocol.

- ``NSData`` instances now support a convenience API that allow them to
act like a ``buffer`` instance for ``str()`` and slicing.

- Objects that support the Python buffer protocol, such as ``buffer`` and
``array.array`` (but not ``str`` or ``unicode``) are now bridged as
``NSData`` subclasses.

- New InjectBrowser example that injects a class browser into another process

Comments (0)

Files changed (20)

Examples/AppKit/Todo/ToDoDocument.py

 
     def loadDocWithData_(self, data):
         if data:
-            dict = NSUnarchiver.unarchiveObjectWithData_(data)
-            self.initDataModelWithDictinary_(dict)
+            dct = NSUnarchiver.unarchiveObjectWithData_(data)
+            self.initDataModelWithDictinary_(dct)
             dayEnum = self._activeDays.keyEnumerator()
-            now = NSData.date()
+            now = NSDate.date()
 
             itemDate = dayEnum.nextObject()
             while itemDate:

Examples/Inject/InjectBrowser/ClassBrowser.nib/classes.nib

+{
+    IBClasses = (
+        {
+            ACTIONS = {browserAction = id; }; 
+            CLASS = ClassBrowserDelegate; 
+            LANGUAGE = ObjC; 
+            OUTLETS = {browser = id; pathLabel = id; table = id; }; 
+            SUPERCLASS = NSObject; 
+        }, 
+        {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }
+    ); 
+    IBVersion = 1; 
+}

Examples/Inject/InjectBrowser/ClassBrowser.nib/info.nib

+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBDocumentLocation</key>
+	<string>61 25 356 240 0 0 1152 746 </string>
+	<key>IBEditorPositions</key>
+	<dict>
+		<key>29</key>
+		<string>45 156 318 44 0 0 1152 746 </string>
+	</dict>
+	<key>IBFramework Version</key>
+	<string>291.0</string>
+	<key>IBOpenObjects</key>
+	<array>
+		<integer>21</integer>
+		<integer>29</integer>
+	</array>
+	<key>IBSystem Version</key>
+	<string>6L60</string>
+</dict>
+</plist>

Examples/Inject/InjectBrowser/ClassBrowser.nib/objects.nib

Binary file added.

Examples/Inject/InjectBrowser/InjectBrowserPlugin.py

+"""ClassBrowser.py -- A simple class browser, demonstrating the use of an NSBrowser.
+
+To build the demo program, run this line in Terminal.app:
+
+    $ python setup.py py2app -A
+
+This creates a directory "dist" containing ClassBrowser.app. (The
+-A option causes the files to be symlinked to the .app bundle instead
+of copied. This means you don't have to rebuild the app if you edit the
+sources or nibs.)
+
+See also the iClass demo.
+"""
+
+from PyObjCTools import NibClassBuilder, AppHelper
+from objc import getClassList, objc_object
+from Foundation import *
+from AppKit import *
+import os
+
+myBundle = NSBundle.bundleWithPath_(os.path.dirname(os.path.dirname(os.environ['RESOURCEPATH'])).decode('utf8'))
+
+NibClassBuilder.extractClasses("ClassBrowser.nib", bundle=myBundle)
+
+
+def _sortClasses(classList):
+    classes = [(cls.__name__, cls) for cls in classList]
+    classes.sort()
+    return [cls for name, cls in classes]
+
+
+# class defined in ClassBrowser.nib
+class ClassBrowserDelegate(NibClassBuilder.AutoBaseClass):
+    # the actual base class is NSObject
+    # The following outlets are added to the class:
+    # browser
+    # pathLabel
+    # table
+
+    selectedClassMethods = None
+
+    def awakeFromNib(self):
+        classes = getClassList()
+        rootClasses = []
+        for cls in classes:
+            if cls.__base__ == objc_object:
+                rootClasses.append(cls)
+        rootClasses = _sortClasses(rootClasses)
+        self.columns = [rootClasses]
+        self.browser.setMaxVisibleColumns_(7)
+        self.browser.setMinColumnWidth_(150)
+        self.selectedClass = None
+        self.selectedClassMethods = None
+
+    def browser_willDisplayCell_atRow_column_(self, browser, cell, row, col):
+        cell.setLeaf_(not self.columns[col][row].__subclasses__())
+        cell.setStringValue_(self.columns[col][row].__name__)
+
+    def browser_numberOfRowsInColumn_(self, browser, col):
+        if col == 0:
+            return len(self.columns[0])
+        del self.columns[col:]
+        cls = self.columns[col - 1][browser.selectedRowInColumn_(col - 1)]
+        subclasses = _sortClasses(cls.__subclasses__())
+        self.columns.append(subclasses)
+        return len(subclasses)
+
+    # action method, called when the user clicks somewhere in the browser
+    def browserAction_(self, browser):
+        self.pathLabel.setStringValue_(browser.path())
+
+        self.selectedClass = None
+        self.selectedClassMethods = None
+        col = len(self.columns)
+        row = -1
+        while row == -1:
+            col -= 1
+            if col < 0:
+                break
+            row = self.browser.selectedRowInColumn_(col)
+        if row >= 0:
+            self.selectedClass = self.columns[col][row]
+            # Classes get initialized lazily, upon the first attribute access,
+            # only after that cls.__dict__ actually contains the methods.
+            # Do a dummy hasattr() to make sure the class is initialized.
+            hasattr(self.selectedClass, "alloc")
+            self.selectedClassMethods = [obj.selector for obj in self.selectedClass.__dict__.values()
+                                         if hasattr(obj, "selector")]
+            self.selectedClassMethods.sort()
+
+        self.table.reloadData()
+
+    # table view delegate methods
+    def numberOfRowsInTableView_(self, tableView):
+        if self.selectedClassMethods is None:
+            return 0
+        return len(self.selectedClassMethods)
+
+    def tableView_objectValueForTableColumn_row_(self, tableView, col, row):
+        return str(self.selectedClassMethods[row])
+
+    def tableView_shouldEditTableColumn_row_(self, tableView, col, row):
+        return 0
+
+
+class ClassBrowserBootstrap(NSObject):
+    def startWithBundle_(self, bundle):
+        d = {}
+        NSApplicationLoad()
+        v = bundle.loadNibFile_externalNameTable_withZone_(u'ClassBrowser.nib', d, None)
+
+ClassBrowserBootstrap.alloc().init().performSelectorOnMainThread_withObject_waitUntilDone_(
+    'startWithBundle:',
+    myBundle,
+    False,
+)

Examples/Inject/InjectBrowser/setup.py

+from distutils.core import setup
+import py2app
+
+plist = dict(NSMainNibFile='ClassBrowser')
+setup(
+    plugin = ["InjectBrowserPlugin.py"],
+    data_files = ["ClassBrowser.nib"],
+    options = dict(py2app=dict(plist=plist)),
+)

Examples/Inject/InjectBrowser/test.py

+import sys
+import os
+import objc
+if __name__ == '__main__':
+    if len(sys.argv) < 2:
+        raise SystemExit, "%s: You must specify the pid of a process to inject InjectBrowserPlugin into" % (sys.argv[0],)
+    interp = os.path.abspath('dist/InjectBrowserPlugin.plugin/Contents/MacOS/InjectBrowserPlugin')
+    if not os.path.exists(interp):
+        if os.spawnl(os.P_WAIT, sys.executable, sys.executable, 'setup.py', 'py2app', '-A'):
+            raise SystemExit, "Could not build InjectBrowserPlugin"
+    objc.inject(int(sys.argv[1]), interp)

Examples/Scripts/stdinreader.py

         else:
             self.fileHandle.readInBackgroundAndNotify()
             if self.readCallback is not None:
-                self.readCallback(self, newData.bytes()[:])
+                self.readCallback(self, str(newData))
 
     def close(self):
         self.nc.removeObserver_(self)

Examples/WebKit/PyDocURLProtocol/PyDocURLProtocol.py

             )
             client.URLProtocol_didLoadData_(
                 self,
-                NSData.dataWithBytes_length_(data, len(data)),
+                buffer(data),
             )
             client.URLProtocolDidFinishLoading_(self)
 

Lib/Foundation/test/test_nsdata.py

 
 
 class TestMyData (unittest.TestCase):
-    # 'initWithBytes:lenght:' and 'dataWithBytes:lenght:' have custom IMP's
+    # 'initWithBytes:length:' and 'dataWithBytes:length:' have custom IMP's
     def testData(self):
         r = PyObjC_TestClass3.makeDataWithBytes_method_(MyData, 0)
         self.assertEquals(r, ('data', 'hello world', 11))
         b = PyObjC_TestClass3.makeDataWithBytes_method_(MyData5, 1)
         self.assertRaises(ValueError, b.bytes)
 
+import array
+class TestBuffer(unittest.TestCase):
+    def testArray(self):
+        a = array.array('c', 'foo')
+        m = NSMutableData.dataWithData_(a)
+        self.assertEquals(a.tostring(), m[:])
+        self.assert_(objc.repythonify(a) is a)
+        a.fromstring(m)
+        self.assertEquals(a.tostring(), 'foofoo')
+        m.appendData_(a)
+        self.assertEquals(m[:], 'foofoofoo')
+        m[3:6] = 'bar'
+        self.assertEquals(m[:], 'foobarfoo')
+
+    def testBuffer(self):
+        b = buffer('foo')
+        m = NSMutableData.dataWithData_(b)
+        self.assertEquals(b[:], m[:])
+        self.assert_(objc.repythonify(b) is b)
+        self.assertEquals(buffer(m)[:], m[:])
+        
+
 if __name__ == '__main__':
     unittest.main( )

Lib/objc/_bridges.py

         return (tuple(array('B').fromstring(fsRef.data)),)
     BRIDGED_TYPES.append((FSRef, FSRef_to_struct))
 
+
+class PyObjCData(lookUpClass('NSData')):
+    def dataWithPyObject_(cls, anObject):
+        self = cls.dataWithBytes_length_(anObject, len(anObject))
+        self.__pyobjc_object__ = anObject
+        return self
+    dataWithPyObject_ = classmethod(dataWithPyObject_)
+
+BRIDGED_TYPES.append((buffer, PyObjCData.dataWithPyObject_))
+
+
 def _bridgePythonTypes():
     # python TO Obj-C
     OC_PythonObject = lookUpClass('OC_PythonObject')

Lib/objc/_convenience.py

 )
 
 CLASS_METHODS['NSNull'] = (
-        (   '__nonzero__',  lambda self: False ),
+    ('__nonzero__',  lambda self: False ),
 )
 
 NSDecimalNumber = lookUpClass('NSDecimalNumber')
-def _makeD(v): 
+def _makeD(v):
     if isinstance(v, NSDecimalNumber):
         return v
     return NSDecimalNumber.decimalNumberWithDecimal_(v)
 
 CLASS_METHODS['NSDecimalNumber'] = (
-        ( '__add__',  lambda self, other: _makeD(self.decimalValue()+other) ),
-        ( '__radd__', lambda self, other: _makeD(other+self.decimalValue()) ),
-        ( '__sub__',  lambda self, other: _makeD(self.decimalValue()-other) ),
-        ( '__rsub__', lambda self, other: _makeD(other-self.decimalValue()) ),
-        ( '__mul__',  lambda self, other: _makeD(self.decimalValue()*other) ),
-        ( '__rmul__', lambda self, other: _makeD(other*self.decimalValue()) ),
-        ( '__div__',  lambda self, other: _makeD(self.decimalValue()/other) ),
-        ( '__rdiv__', lambda self, other: _makeD(other/self.decimalValue()) ),
-        ( '__mod__',  lambda self, other: _makeD(self.decimalValue()%other) ),
-        ( '__rmod__', lambda self, other: _makeD(other%self.decimalValue()) ),
-        ( '__neg__',  lambda self: _makeD(-(self.decimalValue())) ),
-        ( '__pos__',  lambda self: _makeD(+(self.decimalValue())) ),
-        ( '__abs__',  lambda self: _makeD(abs(self.decimalValue())) ),
-        
+    ('__add__', lambda self, other: _makeD(self.decimalValue() + other)),
+    ('__radd__', lambda self, other: _makeD(other + self.decimalValue())),
+    ('__sub__', lambda self, other: _makeD(self.decimalValue() - other)),
+    ('__rsub__', lambda self, other: _makeD(other - self.decimalValue())),
+    ('__mul__', lambda self, other: _makeD(self.decimalValue() * other)),
+    ('__rmul__', lambda self, other: _makeD(other * self.decimalValue())),
+    ('__div__', lambda self, other: _makeD(self.decimalValue() / other)),
+    ('__rdiv__', lambda self, other: _makeD(other / self.decimalValue())),
+    ('__mod__', lambda self, other: _makeD(self.decimalValue() % other)),
+    ('__rmod__', lambda self, other: _makeD(other % self.decimalValue())),
+    ('__neg__', lambda self: _makeD(-(self.decimalValue()))),
+    ('__pos__', lambda self: _makeD(+(self.decimalValue()))),
+    ('__abs__', lambda self: _makeD(abs(self.decimalValue()))),
 )
+
+def NSData__getslice__(self, i, j):
+    return self.bytes()[i:j]
+
+def NSData__getitem__(self, item):
+    buff = self.bytes()
+    try:
+        return buff[item]
+    except TypeError:
+        return buff[:][item]
+
+CLASS_METHODS['NSData'] = (
+    ('__str__', lambda self: self.bytes()[:]),
+    ('__getitem__', NSData__getitem__),
+    ('__getslice__', NSData__getslice__),
+)
+
+def NSMutableData__setslice__(self, i, j, sequence):
+    # XXX - could use replaceBytes:inRange:, etc.
+    self.mutableBytes()[i:j] = sequence
+    
+def NSMutableData__setitem__(self, item, value):
+    self.mutableBytes()[item] = value
+
+CLASS_METHODS['NSMutableData'] = (
+    ('__setslice__', NSMutableData__setslice__),
+    ('__setitem__', NSMutableData__setitem__),
+)

Modules/objc/OC_PythonData.h

+/*!
+ * @header OC_PythonArray.h 
+ * @abstract Objective-C proxy class for Python buffers
+ * @discussion
+ *     This file defines the class that is used to represent Python buffers
+ *     in Objective-C.
+ */
+
+#import "pyobjc.h"
+#import <Foundation/Foundation.h>
+
+/*!
+ * @class       OC_PythonData
+ * @abstract    Objective-C proxy class for Python buffers
+ * @discussion  Instances of this class are used as proxies for Python 
+ *          buffers when these are passed to Objective-C code. Because 
+ *          this class is a subclass of NSData, Python buffers
+ *          (except str, unicode) can be used everywhere where NSData
+ *          is expected.
+ */
+@interface OC_PythonData : NSData
+{
+	PyObject* value;
+	unsigned buffer_len;
+	const void *buffer;
+}
+
+/*!
+ * @method newWithPythonObject:
+ * @abstract Create a new OC_PythonArray for a specific Python buffer
+ * @param value A python buffer
+ * @result Returns an autoreleased instance representing value
+ *
+ * Caller must own the GIL.
+ */
++ newWithPythonObject:(PyObject*)value;
+
+/*!
+ * @method initWithPythonObject:
+ * @abstract Initialise a OC_PythonArray for a specific Python buffer
+ * @param value A python buffer
+ * @result Returns self
+ *
+ * Caller must own the GIL.
+ */
+- initWithPythonObject:(PyObject*)value;
+
+/*!
+ * @method dealloc
+ * @abstract Deallocate the object
+ */
+-(void)dealloc;
+
+/*!
+ * @method dealloc
+ * @abstract Access the wrapped Python buffer
+ * @result Returns a new reference to the wrapped Python buffer.
+ */
+-(PyObject*)__pyobjc_PythonObject__;
+
+/*!
+ * @method length
+ * @result Returns the length of the wrapped Python buffer
+ */
+-(unsigned)length;
+
+/*!
+ * @method bytes
+ * @result Returns a pointer to the contents of the Python buffer
+ */
+-(const void *)bytes;
+
+@end

Modules/objc/OC_PythonData.m

+#import "OC_PythonData.h"
+
+@implementation OC_PythonData 
+
++ newWithPythonObject:(PyObject*)v;
+{
+	OC_PythonData* res;
+
+	res = [[OC_PythonData alloc] initWithPythonObject:v];
+	[res autorelease];
+	return res;
+}
+
+- initWithPythonObject:(PyObject*)v;
+{
+	self = [super init];
+	if (!self) return nil;
+
+	if (PyObject_AsReadBuffer(v, &buffer, ((int *)&buffer_len))) {
+		[super dealloc];
+		return nil;
+	}
+
+	Py_INCREF(v);
+	Py_XDECREF(value);
+	value = v;
+	return self;
+}
+
+-(PyObject*)__pyobjc_PythonObject__
+{
+	Py_INCREF(value);
+	return value;
+}
+
+-(void)dealloc
+{
+	PyObjC_BEGIN_WITH_GIL
+		PyObjC_UnregisterObjCProxy(value, self);
+		Py_XDECREF(value);
+
+	PyObjC_END_WITH_GIL
+
+	[super dealloc];
+}
+
+-(unsigned)length
+{
+	unsigned rval;
+	PyObjC_BEGIN_WITH_GIL
+		if (PyObject_AsReadBuffer(value, &buffer, ((int *)&buffer_len))) {
+			PyErr_Clear();
+			rval = 0;
+		}
+		rval = buffer_len;
+	PyObjC_END_WITH_GIL
+	return rval;
+}
+
+-(const void *)bytes
+{
+	const void *rval;
+	PyObjC_BEGIN_WITH_GIL
+		if (PyObject_AsReadBuffer(value, &buffer, ((int *)&buffer_len))) {
+			PyErr_Clear();
+			rval = NULL;
+		}
+		rval = buffer;
+	PyObjC_END_WITH_GIL
+	return rval;
+}
+
+@end /* implementation OC_PythonData */

Modules/objc/OC_PythonObject.m

 	if (PyUnicode_Check(argument)) {
 #ifdef PyObjC_UNICODE_FAST_PATH
 		rval = [NSString stringWithCharacters:(const unichar *)PyUnicode_AS_UNICODE(argument) length:(unsigned)PyUnicode_GET_SIZE(argument)];
-        r = 0;
+		r = 0;
 #else
 		PyObject* utf8 = PyUnicode_AsUTF8String(argument);
 		if (utf8) {
 			newWithPythonObject:argument];
 		PyObjC_RegisterObjCProxy(argument, rval);
 		r = 0;
+	} else if (PyObject_CheckReadBuffer(argument) && !PyString_Check(argument)) {
+		rval = [OC_PythonData
+			newWithPythonObject:argument];
+		if (rval) {
+			PyObjC_RegisterObjCProxy(argument, rval);
+			r = 0;
+		} else {
+			r = -1;
+		}
 #ifdef MACOSX
 	} else if ((rval = PyObjC_CFTypeToID(argument))) {
 		// unwrapped cf
 
 + (BOOL)useStoredAccessor
 {
- 	return YES;
+	return YES;
 }
 
 + (BOOL)accessInstanceVariablesDirectly;

Modules/objc/module.m

 int PyObjC_VerboseLevel = 0;
 PyObject* PyObjCClass_DefaultModule = NULL;
 PyObject* PyObjC_NSNumberWrapper = NULL;
-int PyObjC_StrBridgeEnabled = 0;
+int PyObjC_StrBridgeEnabled = 1;
 
 static NSAutoreleasePool* global_release_pool = nil;
 

Modules/objc/objc-class.m

 
 #include <stddef.h>
 
+/*
+ * Support for NSData/NSMutableData to have buffer API
+ *
+ */
+
+static
+int nsdata_getreadbuffer(PyObject *pyself, int segment __attribute__((unused)), void **ptrptr) {
+	NSData *self = (NSData *)PyObjCObject_GetObject(pyself);
+	assert(segment == 0);
+	if (ptrptr != NULL) {
+		*ptrptr = (void *)[self bytes];
+	}
+	return (int)[self length];
+}
+
+static
+int nsmutabledata_getwritebuffer(PyObject *pyself, int segment __attribute__((unused)), void **ptrptr) {
+	NSMutableData *self = (NSMutableData *)PyObjCObject_GetObject(pyself);
+	assert(segment == 0);
+	if (ptrptr != NULL) {
+		*ptrptr = (void *)[self mutableBytes];
+	}
+	return (int)[self length];
+}
+
+static
+int nsdata_getsegcount(PyObject *pyself, int *lenp) {
+	NSData *self = (NSData *)PyObjCObject_GetObject(pyself);
+	if (lenp != NULL) {
+		*lenp = (int)[self length];
+	}
+	return 1;
+}
+
+static PyBufferProcs nsdata_as_buffer = {
+	(getreadbufferproc)&nsdata_getreadbuffer,
+	NULL,
+	(getsegcountproc)&nsdata_getsegcount,
+	NULL
+};
+
+static PyBufferProcs nsmutabledata_as_buffer = {
+	(getreadbufferproc)&nsdata_getreadbuffer,
+	(getwritebufferproc)&nsmutabledata_getwritebuffer,
+	(getsegcountproc)&nsdata_getsegcount,
+	NULL
+};
+
+
 PyDoc_STRVAR(class_doc,
 "objc_class(name, bases, dict) -> a new Objective-C class\n"
 "\n"
 	info->delmethod = NULL;
 	info->hasPythonImpl = 0;
 
+	/* XXX: Hack to support buffer API */
+	if (strcmp(objc_class->name, "NSData") == 0) {
+		((PyTypeObject *)result)->tp_as_buffer = &nsdata_as_buffer;
+	} else if (strcmp(objc_class->name, "NSMutableData") == 0) {
+		((PyTypeObject *)result)->tp_as_buffer = &nsmutabledata_as_buffer;
+	}
+
 	var = class_getInstanceVariable(objc_class, "__dict__");
 	if (var != NULL) {
 		info->dictoffset = var->ivar_offset;

Modules/objc/pyobjc.h

 #include "pointer-support.h"
 #include "OC_PythonObject.h"
 #include "OC_PythonArray.h"
+#include "OC_PythonData.h"
 #include "OC_PythonDictionary.h"
 #include "method-signature.h"
 #include "objc_util.h"
 <body>
 <h2>PyObjC NEWS</h2>
 <p>An overview of the relevant changes in new, and older, releases.</p>
-<h2><a name="version-1-3-2005-03-31">Version 1.3 (2005-03-31)</a></h2>
+<h2><a name="version-1-3-2005-04-02">Version 1.3 (2005-04-02)</a></h2>
 <ul>
-<li>The <code><span>str</span></code> bridge is now disabled by default, and will issue 
-<code><span>objc.PyObjCStrBridgeWarning</span></code> warnings.  To promote these to an
-exception, do the following:<pre>
-import objc
-import warnings
-warnings.filterwarnings('error', objc.PyObjCStrBridgeWarning)
-</pre>
-<p>To enable the str bridge, without warnings, use:</p>
-<pre>
-import objc
-objc.setStrBridgeEnabled(True)
-</pre>
-<p>In PyObjC 1.4, this warning will go away, and <code><span>str</span></code> objects will not be
-usable as <code><span>NSString</span></code> instances.  To pass text across the bridge,
-always use <code><span>unicode</span></code>.</p>
-</li>
+<li><code><span>NSData</span></code> and <code><span>NSMutableData</span></code> instances now support the Python buffer
+protocol.</li>
+<li><code><span>NSData</span></code> instances now support a convenience API that allow them to
+act like a <code><span>buffer</span></code> instance for <code><span>str()</span></code> and slicing.</li>
+<li>Objects that support the Python buffer protocol, such as <code><span>buffer</span></code> and
+<code><span>array.array</span></code> (but not <code><span>str</span></code> or <code><span>unicode</span></code>) are now bridged as
+<code><span>NSData</span></code> subclasses.</li>
 <li>New <code><span>objc.pyobjc_id</span></code> function that returns a the id of the underlying
 NSObject as an integer.  (Python wrapper objects are often made on the
 fly, meaning <code><span>id(obj)</span></code> is not constant during the lifetime of the
 
 An overview of the relevant changes in new, and older, releases.
 
-Version 1.3 (2005-03-31)
+Version 1.3 (2005-04-02)
 ------------------------
 
-- The ``str`` bridge is now disabled by default, and will issue 
-  ``objc.PyObjCStrBridgeWarning`` warnings.  To promote these to an
-  exception, do the following::
+- ``NSData`` and ``NSMutableData`` instances now support the Python buffer
+  protocol.
 
-      import objc
-      import warnings
-      warnings.filterwarnings('error', objc.PyObjCStrBridgeWarning)
+- ``NSData`` instances now support a convenience API that allow them to
+  act like a ``buffer`` instance for ``str()`` and slicing.
 
-  To enable the str bridge, without warnings, use::
-
-      import objc
-      objc.setStrBridgeEnabled(True)
-
-  In PyObjC 1.4, this warning will go away, and ``str`` objects will not be
-  usable as ``NSString`` instances.  To pass text across the bridge,
-  always use ``unicode``.
+- Objects that support the Python buffer protocol, such as ``buffer`` and
+  ``array.array`` (but not ``str`` or ``unicode``) are now bridged as
+  ``NSData`` subclasses.
 
 - New ``objc.pyobjc_id`` function that returns a the id of the underlying
   NSObject as an integer.  (Python wrapper objects are often made on the