test_embedded_sigs.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import inspect
  2. import cytoolz
  3. from types import BuiltinFunctionType, FunctionType
  4. from cytoolz import curry, identity, keyfilter, valfilter, merge_with
  5. from dev_skip_test import dev_skip_test
  6. @curry
  7. def isfrommod(modname, func):
  8. mod = getattr(func, '__module__', '') or ''
  9. return mod.startswith(modname) or 'toolz.functoolz.curry' in str(type(func))
  10. @dev_skip_test
  11. def test_class_sigs():
  12. """ Test that all ``cdef class`` extension types in ``cytoolz`` have
  13. correctly embedded the function signature as done in ``toolz``.
  14. """
  15. import toolz
  16. # only consider items created in both `toolz` and `cytoolz`
  17. toolz_dict = valfilter(isfrommod('toolz'), toolz.__dict__)
  18. cytoolz_dict = valfilter(isfrommod('cytoolz'), cytoolz.__dict__)
  19. # only test `cdef class` extensions from `cytoolz`
  20. cytoolz_dict = valfilter(lambda x: not isinstance(x, BuiltinFunctionType),
  21. cytoolz_dict)
  22. # full API coverage should be tested elsewhere
  23. toolz_dict = keyfilter(lambda x: x in cytoolz_dict, toolz_dict)
  24. cytoolz_dict = keyfilter(lambda x: x in toolz_dict, cytoolz_dict)
  25. class wrap:
  26. """e.g., allow `factory=<class 'dict'>` to instead be `factory=dict` in signature"""
  27. def __init__(self, obj):
  28. self.obj = obj
  29. def __repr__(self):
  30. return getattr(self.obj, '__name__', repr(self.obj))
  31. d = merge_with(identity, toolz_dict, cytoolz_dict)
  32. for key, (toolz_func, cytoolz_func) in d.items():
  33. if isinstance(toolz_func, FunctionType):
  34. # function
  35. toolz_spec = inspect.signature(toolz_func)
  36. elif isinstance(toolz_func, toolz.curry):
  37. # curried object
  38. toolz_spec = inspect.signature(toolz_func.func)
  39. else:
  40. # class
  41. toolz_spec = inspect.signature(toolz_func.__init__)
  42. toolz_spec = toolz_spec.replace(
  43. parameters=[
  44. v.replace(default=wrap(v.default))
  45. if v.default is not inspect._empty
  46. else v
  47. for v in toolz_spec.parameters.values()
  48. ]
  49. )
  50. # Hmm, Cython is showing str as unicode, such as `default=u'__no__default__'`
  51. doc = cytoolz_func.__doc__
  52. doc_alt = doc.replace('Py_ssize_t ', '').replace("=u'", "='")
  53. toolz_sig = toolz_func.__name__ + str(toolz_spec)
  54. if not (toolz_sig in doc or toolz_sig in doc_alt):
  55. message = ('cytoolz.%s does not have correct function signature.'
  56. '\n\nExpected: %s'
  57. '\n\nDocstring in cytoolz is:\n%s'
  58. % (key, toolz_sig, cytoolz_func.__doc__))
  59. assert False, message
  60. skip_sigs = ['identity']
  61. aliases = {'comp': 'compose'}
  62. @dev_skip_test
  63. def test_sig_at_beginning():
  64. """ Test that the function signature is at the beginning of the docstring
  65. and is followed by exactly one blank line.
  66. """
  67. cytoolz_dict = valfilter(isfrommod('cytoolz'), cytoolz.__dict__)
  68. cytoolz_dict = keyfilter(lambda x: x not in skip_sigs, cytoolz_dict)
  69. for key, val in cytoolz_dict.items():
  70. doclines = val.__doc__.splitlines()
  71. assert len(doclines) > 2, (
  72. 'cytoolz.%s docstring too short:\n\n%s' % (key, val.__doc__))
  73. sig = '%s(' % aliases.get(key, key)
  74. assert sig in doclines[0], (
  75. 'cytoolz.%s docstring missing signature at beginning:\n\n%s'
  76. % (key, val.__doc__))
  77. assert not doclines[1], (
  78. 'cytoolz.%s docstring missing blank line after signature:\n\n%s'
  79. % (key, val.__doc__))
  80. assert doclines[2], (
  81. 'cytoolz.%s docstring too many blank lines after signature:\n\n%s'
  82. % (key, val.__doc__))