Commits

masklinn committed be5482a

Correctly handle non-py.str being returned by __str__, fixes issue 17

Comments (0)

Files changed (2)

         return out;
     };
 
-    // Builtins
     py.PY_call = function (callable, args, kwargs) {
         if (!args) {
             args = []; kwargs = {};
         }
         return callable.__call__(args, kwargs);
     };
+    // Builtins
     py.type = function type(name, bases, dict) {
         var proto;
         if (typeof name !== 'string') {
             return this.__unicode__();
         },
         __unicode__: function () {
-            // TODO: return python string
-            return '<object ' + this.constructor.name + '>';
+            return py.str.fromJSON('<' + this.__class__.__name__ + ' object>');
         },
         __nonzero__: function () {
             return py.True;
                 this._value = v._value;
                 return;
             }
-            throw new Error('TypeError: __str__ returned non-string (type ' +
-                            v.__class__.__name__ + ')');
+            var typename;
+            if (v.__class__) { // py type
+                typename = v.__class__.__name__;
+            } else if(typeof v !== 'object') { // JS primitive
+                typename = typeof v;
+            } else { // JS object
+                typename = v.constructor.name;
+            }
+            throw new Error(
+                'TypeError: __str__ returned non-string (type '+typename+')');
         },
         __hash__: function () {
             return '\1\0\1' + this._value;

test/builtins/str.js

+var py = require('../../lib/py.js'),
+    expect = require('expect.js');
+
+var ev = function (str, context) {
+    return py.evaluate(py.parse(py.tokenize(str)), context);
+};
+var ctor = function (typ) {
+    var fn = function () {};
+    fn.prototype = typ;
+    return fn;
+};
+var makeT = function (fn) {
+    return py.type('t', null, {
+        __str__: fn
+    });
+};
+
+describe('Convert from JS', function () {
+    it('should produce a py.str', function () {
+        expect(py.str.fromJSON("foo"))
+            .to.be.a(ctor(py.str));
+    });
+    it('should do roundtrip', function () {
+        expect(py.str.fromJSON("foo").toJSON())
+            .to.be("foo");
+    });
+});
+describe('Convert other py types', function () {
+    describe('No __str__ defined', function () {
+        it("should use object's", function () {
+            var t = py.type('t', null, {});
+            expect(py.eval('str(t())', {t: t}))
+                .to.be("<t object>");
+        });
+    });
+    it('should accept a py.str', function () {
+        var t = makeT(function () {return py.str.fromJSON("Wheee");})
+        expect(py.eval('str(t())', {t: t}))
+               .to.be("Wheee");
+    });
+    it('should reject a py.object non py.str', function () {
+        var t = makeT(function () {return py.float.fromJSON(42);});
+        expect(function () { py.eval('str(t())', {t: t}); })
+            .to.throwException(
+                /^TypeError: __str__ returned non-string \(type float\)$/);
+    });
+    it('should reject a non-py js object', function () {
+        var t = makeT(function () { return new String("foo"); });
+        expect(function () { py.eval('str(t())', {t: t}); })
+            .to.throwException(
+                /^TypeError: __str__ returned non-string \(type String\)$/);
+    });
+    it('should reject a js primitive', function () {
+        var t = makeT(function () { return "foo"; });
+        expect(function () { py.eval('str(t())', {t: t}); })
+            .to.throwException(
+                /^TypeError: __str__ returned non-string \(type string\)/);
+    });
+});