Repository: adamchainz/django-perf-rec

Issue:
Make record() kwargs-only
It would be neat. None of the arguments have long names and it would make the deprecation of `file_name` easier.



Discussion:

Resolving patch:
diff --git a/django_perf_rec/api.py b/django_perf_rec/api.py
--- a/django_perf_rec/api.py
+++ b/django_perf_rec/api.py
@@ -11,14 +11,17 @@
 from . import pytest_plugin
 from .cache import AllCacheRecorder
 from .db import AllDBRecorder
+from .functional import kwargs_only
 from .settings import perf_rec_settings
 from .utils import current_test, record_diff
+
 from .yaml import KVFile
 
 
 record_current = local()
 
 
+@kwargs_only
 def record(file_name=None, record_name=None, path=None):
     test_details = current_test()
 
@@ -137,5 +140,6 @@ class TestCaseMixin(object):
     Adds record_performance() method to TestCase class it's mixed into
     for easy import-free use.
     """
+    @kwargs_only
     def record_performance(self, file_name=None, record_name=None, path=None):
         return record(file_name=file_name, record_name=record_name, path=path)
diff --git a/django_perf_rec/functional.py b/django_perf_rec/functional.py
new file mode 100644
--- /dev/null
+++ b/django_perf_rec/functional.py
@@ -0,0 +1,27 @@
+# -*- coding:utf-8 -*-
+from __future__ import absolute_import, division, print_function, unicode_literals
+
+import inspect
+from functools import wraps
+
+
+def kwargs_only(func):
+    """
+    Make a function only accept keyword arguments.
+    This can be dropped in Python 3 in lieu of:
+        def foo(*, bar=default):
+    """
+    signature = inspect.getargspec(func)
+
+    if signature.args[:1] in (['self'], ['cls']):
+        allowable_args = 1
+    else:
+        allowable_args = 0
+
+    @wraps(func)
+    def wrapper(*args, **kwargs):
+        if len(args) > allowable_args:
+            raise TypeError("{} should only be called with keyword args".format(func.__name__))
+        return func(*args, **kwargs)
+
+    return wrapper


Test patch:
diff --git a/tests/test_functional.py b/tests/test_functional.py
new file mode 100644
--- /dev/null
+++ b/tests/test_functional.py
@@ -0,0 +1,81 @@
+# -*- encoding:utf-8 -*-
+from __future__ import absolute_import, division, print_function, unicode_literals
+
+import sys
+
+import pytest
+
+from django_perf_rec.functional import kwargs_only
+
+if sys.version_info[0] == 2:
+    text_type = unicode  # noqa
+else:
+    text_type = str
+
+
+def test_kwargs_only_allows_keyword_args():
+
+    @kwargs_only
+    def foo(x, y):
+        return (x, y)
+
+    assert foo(y=2, x=1) == (1, 2)
+
+
+def test_kwargs_only_does_not_allow_positional_args():
+
+    @kwargs_only
+    def foo(x):
+        pass
+
+    with pytest.raises(TypeError) as excinfo:
+        foo(1)
+    assert "should only be called with keyword args" in text_type(excinfo.value)
+
+
+def test_kwargs_only_method_allows_keyword_args():
+
+    class Foo(object):
+        @kwargs_only
+        def bar(self, x, y):
+            return (x, y)
+
+    assert Foo().bar(y=2, x=1) == (1, 2)
+
+
+def test_kwargs_only_method_does_not_allow_positional_args():
+
+    class Foo(object):
+        @kwargs_only
+        def bar(self, x):
+            pass
+
+    with pytest.raises(TypeError) as excinfo:
+        Foo().bar(1)
+    assert "should only be called with keyword args" in text_type(excinfo.value)
+
+
+def test_kwargs_only_classmethod_allows_keyword_args():
+
+    class Foo(object):
+
+        @classmethod
+        @kwargs_only
+        def bar(cls, x, y):
+            return (x, y)
+
+    assert Foo.bar(y=2, x=1) == (1, 2)
+
+
+def test_kwargs_only_classmethod_does_not_allow_positional_args():
+
+    class Foo(object):
+
+        @classmethod
+        @kwargs_only
+        def bar(self, x):
+            pass
+
+    with pytest.raises(TypeError) as excinfo:
+        Foo.bar(1)
+    assert "should only be called with keyword args" in text_type(excinfo.value)