nebius/SWE-bench-extra huggingface dataset:
Repository: laterpay/laterpay-client-python

Issue:
python-requests requires string only headers
As of python-requests 2.11 headers that are neither string not bytes are not accepted anymore. See kennethreitz/requests#3477

Comments:

Patch:
diff --git a/laterpay/__init__.py b/laterpay/__init__.py
index c92f556..f2df4a4 100644
--- a/laterpay/__init__.py
+++ b/laterpay/__init__.py
@@ -421,10 +421,7 @@ class LaterPayClient(object):
         """
         params = self._sign_and_encode(params=params, url=url, method=method)
 
-        headers = {
-            'X-LP-APIVersion': 2,
-            'User-Agent': 'LaterPay Client - Python - v0.2'
-        }
+        headers = self.get_request_headers()
 
         if method == 'POST':
             req = Request(url, data=params, headers=headers)
@@ -503,7 +500,7 @@ class LaterPayClient(object):
         Return a ``dict`` of request headers to be sent to the API.
         """
         return {
-            'X-LP-APIVersion': 2,
+            'X-LP-APIVersion': '2',
             # TODO: Add client version information.
             'User-Agent': 'LaterPay Client Python',
         }


Test patch:
diff --git a/tests/test_client.py b/tests/test_client.py
index e6ebc1c..f3069e7 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -302,7 +302,7 @@ class TestLaterPayClient(unittest.TestCase):
 
         call = responses.calls[0]
 
-        self.assertEqual(call.request.headers['X-LP-APIVersion'], 2)
+        self.assertEqual(call.request.headers['X-LP-APIVersion'], '2')
 
         qd = parse_qs(urlparse(call.request.url).query)
Repository: laterpay/laterpay-client-python

Issue:
Decode byte strings before using them during signing
On Python 3, when singing a request containing a byte string (`b'foo'`) the value is casted into a string, resulting in `"b'foo'"`, instead of properly decoding the string to `'foo'`.


Comments:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8484c44..532fd0e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,11 @@
 # Changelog
 
 
-## Unreleased
+## 4.6.0 (under development)
+
+* Fixed encoding issues when passing byte string parameters on Python 3
+  ([#84](https://github.com/laterpay/laterpay-client-python/issues/84))
+
 
 ## 4.5.0
 
diff --git a/laterpay/signing.py b/laterpay/signing.py
index 24b1bf2..347aa31 100644
--- a/laterpay/signing.py
+++ b/laterpay/signing.py
@@ -53,12 +53,8 @@ def sort_params(param_dict):
     for name, value_list in six.iteritems(param_dict):
         if isinstance(value_list, (list, tuple)):
             for value in value_list:
-                if not isinstance(value, six.string_types):
-                    value = str(value)
                 param_list.append((name, value))
         else:
-            if not isinstance(value_list, six.string_types):
-                value_list = str(value_list)
             param_list.append((name, value_list))
 
     return sorted(param_list)
@@ -127,10 +123,13 @@ def create_base_message(params, url, method='POST'):
 
         values_str = []
 
-        # If any non six.string_types objects, ``str()`` them.
         for value in values:
-            if not isinstance(value, six.string_types):
+            if not isinstance(value, (six.string_types, six.binary_type)):
+                # If any non-string or non-bytes like objects, ``str()`` them.
                 value = str(value)
+            if six.PY3 and isinstance(value, six.binary_type):
+                # Issue #84, decode byte strings before using them on Python 3
+                value = value.decode()
             values_str.append(value)
 
         data[key] = [quote(compat.encode_if_unicode(value_str), safe='') for value_str in values_str]


Test patch:
diff --git a/tests/test_signing.py b/tests/test_signing.py
index ecbff1f..2f0fe02 100644
--- a/tests/test_signing.py
+++ b/tests/test_signing.py
@@ -25,7 +25,7 @@ class TestSigningHelper(unittest.TestCase):
         )
 
     def test_create_message_bytestrings(self):
-        params = {'parĄm1': 'valuĘ'}
+        params = {b'par\xc4\x84m1': b'valu\xc4\x98'}
         url = 'https://endpoint.com/ąpi'
 
         msg = signing.create_base_message(params, url)
Repository: laterpay/laterpay-client-python

Issue:
Ignore capitalization when checking HMACs
`laterpay.signing.time_independent_HMAC_compare()` treats hashes `12ab3` and `12AB3` as different. Let's not do that.

Comments:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index edb66fd..2e263cb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,9 @@
 
 ## 5.1.0 (under development)
 
+* Ignored HMAC character capitalization
+  ([#93](https://github.com/laterpay/laterpay-client-python/issues/93))
+
 ## 5.0.0
 
 * Removed the following long deprecated methods from the
diff --git a/laterpay/signing.py b/laterpay/signing.py
index 863fa05..164cba9 100644
--- a/laterpay/signing.py
+++ b/laterpay/signing.py
@@ -27,6 +27,7 @@ def time_independent_HMAC_compare(a, b):
     if len(a) != len(b):
         return False
     result = 0
+    a, b = a.lower(), b.lower()
     for x, y in zip(a, b):
         result |= ord(x) ^ ord(y)
     return result == 0


Test patch:
diff --git a/tests/test_signing.py b/tests/test_signing.py
index ce4742d..7874b13 100644
--- a/tests/test_signing.py
+++ b/tests/test_signing.py
@@ -138,8 +138,9 @@ class TestSigningHelper(unittest.TestCase):
 
         secret = 'secret'
 
+        # Use some upper case characters to test for issue #93
         verified = signing.verify(
-            b'346f3d53ad762f3ed3fb7f2427dec2bbfaf0338bb7f91f0460aff15c',
+            b'346f3d53ad762f3ed3fb7f2427deC2BBFAF0338BB7F91F0460AFF15C',
             secret,
             params,
             url,
@@ -153,8 +154,9 @@ class TestSigningHelper(unittest.TestCase):
             'param2': ['value2', 'value3'],
         }
         url = u'https://endpoint.com/api'
+        # Use some upper case characters to test for issue #93
         verified = signing.verify(
-            u'346f3d53ad762f3ed3fb7f2427dec2bbfaf0338bb7f91f0460aff15c',
+            u'346F3D53AD762F3ED3FB7F2427DEc2bbfaf0338bb7f91f0460aff15c',
             u'secret',
             params,
             url,
Repository: lenskit/lkpy

Issue:
include_missing fails in a few cases
I have seen include_missing fail for ML20M user-user in the LK demo experiment. Still debugging why.

Comments:

Patch:
diff --git a/lenskit/topn.py b/lenskit/topn.py
index 5c0dc13..cccd928 100644
--- a/lenskit/topn.py
+++ b/lenskit/topn.py
@@ -148,9 +148,21 @@ class RecListAnalysis:
             ug_cols = [c for c in rec_key if c not in truth_key]
             tcount = truth.groupby(truth_key)['item'].count()
             tcount.name = 'ntruth'
+            _log.debug('truth data:\n%s', tcount)
             if ug_cols:
                 _log.debug('regrouping by %s to fill', ug_cols)
-                res = res.groupby(ug_cols).apply(lambda f: f.join(tcount, how='outer', on=truth_key))
+                _log.debug('pre-group series:\n%s', res)
+
+                rdict = {}
+
+                for key, df in res.groupby(ug_cols):
+                    df2 = df.drop(columns=ug_cols).join(tcount, how='outer', on=truth_key)
+                    rdict[key] = df2
+
+                res = pd.concat(rdict, names=ug_cols)
+                res = res.reset_index()
+                _log.debug('joined result:\n%s', res)
+
             else:
                 _log.debug('no ungroup cols, directly merging to fill')
                 res = res.join(tcount, how='outer', on=truth_key)


Test patch:
diff --git a/tests/test_topn_analysis.py b/tests/test_topn_analysis.py
index 64549ee..cfe2ee7 100644
--- a/tests/test_topn_analysis.py
+++ b/tests/test_topn_analysis.py
@@ -197,6 +197,7 @@ def test_fill_users():
     algo.fit(train)
 
     rec_users = test['user'].sample(50).unique()
+    assert len(rec_users) < 50
     recs = batch.recommend(algo, rec_users, 25)
 
     scores = rla.compute(recs, test, include_missing=True)
@@ -252,6 +253,10 @@ def test_adv_fill_users():
     assert scores['recall'].isna().sum() > 0
     _log.info('scores:\n%s', scores)
 
+    ucounts = scores.reset_index().groupby('algo')['user'].agg(['count', 'nunique'])
+    assert all(ucounts['count'] == 100)
+    assert all(ucounts['nunique'] == 100)
+
     mscores = rla.compute(recs, test)
     mscores = mscores.reset_index().set_index(inames)
     mscores.sort_index(inplace=True)
Repository: lenskit/lkpy

Issue:
nDCG metric does not correctly truncate lists
The nDCG metric has two faults whereby it fails to correctly truncate the truth list and recommendation list under certain conditions:

1. If no k is supplied, the full truth list and recommendation list are used to compute the ideal DCG and recommendation DCG, respectively. If the sizes of the input lists do not match, this causes an incorrect result. See the following example, based on "test_ndcg_wrong" in tests/test_topn_ndcg.py:
```
truth = pd.DataFrame({'item': [1, 2, 3], 'rating': [3.0, 5.0, 4.0]})
truth = truth.set_index('item')
recs = pd.DataFrame({'item': [1, 2]})

print(ndcg(recs, truth))  # incorrectly gives ~0.7344
print(ndcg(recs, truth, k=2))  # correctly gives ~0.8888
```

2. If a k is supplied that is less than the length of the recommendation list, the recommendation DCG is not computed correctly (due to "tpos" being defined before the recommendation list is truncated). See the following example, based on "test_ndcg_perfect" found in tests/test_topn_ndcg.py:
```
truth = pd.DataFrame({'item': [1, 2, 3], 'rating': [3.0, 5.0, 4.0]})
truth = truth.set_index('item')
recs = pd.DataFrame({'item': [2, 3, 1]})

print(ndcg(recs, truth))  # correctly gives 1.0
print(ndcg(recs, truth, k=2))  # incorrectly gives ~1.21
print(ndcg(recs[:2], truth, k=2))  # correctly gives 1.0
```

Comments:

Patch:
diff --git a/lenskit/metrics/topn.py b/lenskit/metrics/topn.py
index ae1b70f..7f616c5 100644
--- a/lenskit/metrics/topn.py
+++ b/lenskit/metrics/topn.py
@@ -300,11 +300,11 @@ def ndcg(recs, truth, discount=np.log2, k=None):
             The maximum list length.
     """
 
-    tpos = truth.index.get_indexer(recs['item'])
-
     if k is not None:
         recs = recs.iloc[:k]
 
+    tpos = truth.index.get_indexer(recs['item'])
+
     if 'rating' in truth.columns:
         i_rates = np.sort(truth.rating.values)[::-1]
         if k is not None:


Test patch:
diff --git a/tests/test_topn_ndcg.py b/tests/test_topn_ndcg.py
index 5d1601d..2fdf9e5 100644
--- a/tests/test_topn_ndcg.py
+++ b/tests/test_topn_ndcg.py
@@ -87,6 +87,14 @@ def test_ndcg_perfect():
     assert ndcg(recs, truth) == approx(1.0)
 
 
+def test_ndcg_perfect_k_short():
+    recs = pd.DataFrame({'item': [2, 3, 1]})
+    truth = pd.DataFrame({'item': [1, 2, 3], 'rating': [3.0, 5.0, 4.0]})
+    truth = truth.set_index('item')
+    assert ndcg(recs, truth, k=2) == approx(1.0)
+    assert ndcg(recs[:2], truth, k=2) == approx(1.0)
+
+
 def test_ndcg_wrong():
     recs = pd.DataFrame({'item': [1, 2]})
     truth = pd.DataFrame({'item': [1, 2, 3], 'rating': [3.0, 5.0, 4.0]})