Repository: keleshev/schema

Issue:
doesn't work with operator.methodcaller
    from operator import methodcaller
    from schema import Schema

    f = methodcaller('endswith', '.csv')
    assert f('test.csv')

    Schema(f).validate('test.csv')

    AttributeError: 'operator.methodcaller' object has no attribute '__name__'

We can't assume that all callables have a `__name__`, it would seem.

Discussion:
codecov-io: ## [Current coverage][1] is `98.07%`
> Merging **#85** into **master** will increase coverage by **+0.05%** as of [`ea3e9e6`][3]


```diff
@@            master     #85   diff @@
======================================
  Files            1       1       
  Stmts          152     156     +4
  Branches         0       0       
  Methods          0       0       
======================================
+ Hit            149     153     +4
  Partial          0       0       
  Missed           3       3       
```

> Review entire [Coverage Diff][4] as of [`ea3e9e6`][3]


[1]: https://codecov.io/github/keleshev/schema?ref=ea3e9e6de11ed22e995df1b30ede806b9912bba5
[2]: https://codecov.io/github/keleshev/schema/features/suggestions?ref=ea3e9e6de11ed22e995df1b30ede806b9912bba5
[3]: https://codecov.io/github/keleshev/schema/commit/ea3e9e6de11ed22e995df1b30ede806b9912bba5
[4]: https://codecov.io/github/keleshev/schema/compare/e94b7144f3016654d1360eb1c070fd2db0d54a43...ea3e9e6de11ed22e995df1b30ede806b9912bba5

> Powered by [Codecov](https://codecov.io). Updated on successful CI builds.
sjakobi: Anybody know a better name than `callable_str`?

And does that function possibly need `_` as a prefix?
skorokithakis: I think that the method does need an underscore as a prefix, it's not meant to be exported by the module. Other than that, I'm ready to merge this.
skorokithakis: Oops, looks like there's a merge conflict. Can you resolve that so I can merge this?

Patch:
diff --git a/schema.py b/schema.py
index 1ecf845..d24744d 100644
--- a/schema.py
+++ b/schema.py
@@ -70,7 +70,7 @@ class Use(object):
         except SchemaError as x:
             raise SchemaError([None] + x.autos, [self._error] + x.errors)
         except BaseException as x:
-            f = self._callable.__name__
+            f = _callable_str(self._callable)
             raise SchemaError('%s(%r) raised %r' % (f, data, x), self._error)
 
 
@@ -176,7 +176,7 @@ class Schema(object):
                 raise SchemaError('%r.validate(%r) raised %r' % (s, data, x),
                                   self._error)
         if flavor == CALLABLE:
-            f = s.__name__
+            f = _callable_str(s)
             try:
                 if s(data):
                     return data
@@ -211,3 +211,9 @@ class Optional(Schema):
                     '"%r" is too complex.' % (self._schema,))
             self.default = default
             self.key = self._schema
+
+
+def _callable_str(callable_):
+    if hasattr(callable_, '__name__'):
+        return callable_.__name__
+    return str(callable_)


Test patch:
diff --git a/test_schema.py b/test_schema.py
index ad49343..967dec0 100644
--- a/test_schema.py
+++ b/test_schema.py
@@ -1,5 +1,6 @@
 from __future__ import with_statement
 from collections import defaultdict, namedtuple
+from operator import methodcaller
 import os
 
 from pytest import raises
@@ -383,8 +384,18 @@ def test_missing_keys_exception_with_non_str_dict_keys():
         try:
             Schema({1: 'x'}).validate(dict())
         except SchemaError as e:
-            assert (e.args[0] ==
-                    "Missing keys: 1")
+            assert e.args[0] == "Missing keys: 1"
+            raise
+
+
+def test_issue_56_cant_rely_on_callables_to_have_name():
+    s = Schema(methodcaller('endswith', '.csv'))
+    assert s.validate('test.csv') == 'test.csv'
+    with SE:
+        try:
+            s.validate('test.py')
+        except SchemaError as e:
+            assert "operator.methodcaller" in e.args[0]
             raise