~ruther/guix-local

ae3fbf21caacc16c97151c583dee94b4c4b75bff — Nicolas Graves 7 months ago d0283dd
gnu: python-mohawk: Update to 1.1.0-0.b789916.

* gnu/packages/python-web.scm (python-mohawk): Update to 1.1.0-0.b789916.
[source]: Switch to git-fetch. Use patch.
[build-system]: Switch to pyproject-build-system.
[arguments]: Set <#:test-flags>.
[native-inputs]: Remove them. Add python-pytest and
python-setuptools-next.

* gnu/packages/patches/python-mohawk-pytest.patch: Add patch from
upstream Github PR.
* gnu/local.mk: Record patch.

Change-Id: I866683573757909b9b5b808cd693991358131c46
Signed-off-by: Sharlatan Hellseher <sharlatanus@gmail.com>
3 files changed, 1044 insertions(+), 17 deletions(-)

M gnu/local.mk
A gnu/packages/patches/python-mohawk-pytest.patch
M gnu/packages/python-web.scm
M gnu/local.mk => gnu/local.mk +1 -0
@@ 2054,6 2054,7 @@ dist_patch_DATA =						\
  %D%/packages/patches/python-feedparser-missing-import.patch	\
  %D%/packages/patches/python-louvain-fix-test.patch		\
  %D%/packages/patches/python-matplotlib-fix-legend-loc-best-test.patch	\
  %D%/packages/patches/python-mohawk-pytest.patch	\
  %D%/packages/patches/python-numpy-gcc-14.patch		\
  %D%/packages/patches/python-random2-getrandbits-test.patch		\
  %D%/packages/patches/python-pillow-use-zlib-1.3.patch	\

A gnu/packages/patches/python-mohawk-pytest.patch => gnu/packages/patches/python-mohawk-pytest.patch +1018 -0
@@ 0,0 1,1018 @@
From ed2ec6f0243249d3673a23d99aefc919e24cdeca Mon Sep 17 00:00:00 2001
From: Pyrox <pyrox@pyrox.dev>
Date: Fri, 2 Aug 2024 14:40:37 -0400
Subject: [PATCH 1/4] autoconvert nose assertions with nose2pytest

---
 mohawk/tests.py | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/mohawk/tests.py b/mohawk/tests.py
index e680cf8..505c7d8 100644
--- a/mohawk/tests.py
+++ b/mohawk/tests.py
@@ -288,8 +288,8 @@ def test_expired_exception_reports_localtime(self):
             except:
                 etype, exc, tb = sys.exc_info()
 
-        eq_(type(exc), TokenExpired)
-        eq_(exc.localtime_in_seconds, now)
+        assert type(exc) == TokenExpired
+        assert exc.localtime_in_seconds == now
 
     def test_localtime_offset(self):
         now = utc_now() - 120
@@ -371,7 +371,7 @@ def test_ext_with_equality_sign(self):
         sn = self.Sender(ext="foo=bar&foo2=bar2;foo3=bar3")
         self.receive(sn.request_header)
         parsed = parse_authorization_header(sn.request_header)
-        eq_(parsed['ext'], "foo=bar&foo2=bar2;foo3=bar3")
+        assert parsed['ext'] == "foo=bar&foo2=bar2;foo3=bar3"
 
     @raises(HawkFail)
     def test_non_hawk_scheme(self):
@@ -385,7 +385,7 @@ def test_ext_with_all_valid_characters(self):
         valid_characters = "!#$%&'()*+,-./:;<=>?@[]^_`{|}~ azAZ09_"
         sender = self.Sender(ext=valid_characters)
         parsed = parse_authorization_header(sender.request_header)
-        eq_(parsed['ext'], valid_characters)
+        assert parsed['ext'] == valid_characters
 
     @raises(BadHeaderValue)
     def test_ext_with_illegal_chars(self):
@@ -421,7 +421,7 @@ def test_app_ok(self):
         sn = self.Sender(app=app)
         self.receive(sn.request_header)
         parsed = parse_authorization_header(sn.request_header)
-        eq_(parsed['app'], app)
+        assert parsed['app'] == app
 
     @raises(MacMismatch)
     def test_tampered_app(self):
@@ -435,7 +435,7 @@ def test_dlg_ok(self):
         sn = self.Sender(dlg=dlg)
         self.receive(sn.request_header)
         parsed = parse_authorization_header(sn.request_header)
-        eq_(parsed['dlg'], dlg)
+        assert parsed['dlg'] == dlg
 
     @raises(MacMismatch)
     def test_tampered_dlg(self):
@@ -589,7 +589,7 @@ def test_respond_with_expired_ts(self):
                 calculated = calculate_ts_mac(fn(), self.credentials)
                 if isinstance(calculated, six.binary_type):
                     calculated = calculated.decode('ascii')
-                eq_(hdr['tsm'], calculated)
+                assert hdr['tsm'] == calculated
                 raise
 
     def test_respond_with_bad_ts_skew_ok(self):
@@ -611,7 +611,7 @@ def test_respond_with_ext(self):
         ext = 'custom-ext'
         self.respond(ext=ext)
         header = parse_authorization_header(self.receiver.response_header)
-        eq_(header['ext'], ext)
+        assert header['ext'] == ext
 
     @raises(MacMismatch)
     def test_respond_with_wrong_app(self):
@@ -854,7 +854,7 @@ def test_bewit(self):
         bewit = get_bewit(res)
 
         expected = '123456\\1356420707\\IGYmLgIqLrCe8CxvKPs4JlWIA+UjWJJouwgARiVhCAg=\\'
-        eq_(b64decode(bewit).decode('ascii'), expected)
+        assert b64decode(bewit).decode('ascii') == expected
 
     def test_bewit_with_binary_id(self):
         # Check for exceptions in get_bewit call with binary id
@@ -877,7 +877,7 @@ def test_bewit_with_ext(self):
         bewit = get_bewit(res)
 
         expected = '123456\\1356420707\\kscxwNR2tJpP1T1zDLNPbB5UiKIU9tOSJXTUdG7X9h8=\\xandyandz'
-        eq_(b64decode(bewit).decode('ascii'), expected)
+        assert b64decode(bewit).decode('ascii') == expected
 
     @raises(BadHeaderValue)
     def test_bewit_with_invalid_ext(self):
@@ -905,7 +905,7 @@ def test_bewit_with_port(self):
         bewit = get_bewit(res)
 
         expected = '123456\\1356420707\\hZbJ3P2cKEo4ky0C8jkZAkRyCZueg4WSNbxV7vq3xHU=\\xandyandz'
-        eq_(b64decode(bewit).decode('ascii'), expected)
+        assert b64decode(bewit).decode('ascii') == expected
 
     @raises(ValueError)
     def test_bewit_with_nonce(self):

From 66157c7efbf6b0d18c30a9ffe5dfd84bef27bd3a Mon Sep 17 00:00:00 2001
From: Pyrox <pyrox@pyrox.dev>
Date: Fri, 2 Aug 2024 15:20:34 -0400
Subject: [PATCH 2/4] nose.raises -> unittest.assertRaises

---
 mohawk/tests.py | 385 ++++++++++++++++++++++++------------------------
 1 file changed, 191 insertions(+), 194 deletions(-)

diff --git a/mohawk/tests.py b/mohawk/tests.py
index 505c7d8..801c1dd 100644
--- a/mohawk/tests.py
+++ b/mohawk/tests.py
@@ -1,10 +1,8 @@
 import sys
 import warnings
-from unittest import TestCase
+from unittest import TestCase, mock
 from base64 import b64decode, urlsafe_b64encode
 
-import mock
-from nose.tools import eq_, raises
 import six
 
 from . import Receiver, Sender
@@ -60,27 +58,27 @@ def credentials_map(self, id):
 
 class TestConfig(Base):
 
-    @raises(InvalidCredentials)
     def test_no_id(self):
         c = self.credentials.copy()
         del c['id']
-        validate_credentials(c)
+        with self.assertRaises(InvalidCredentials):
+            validate_credentials(c)
 
-    @raises(InvalidCredentials)
     def test_no_key(self):
         c = self.credentials.copy()
         del c['key']
-        validate_credentials(c)
+        with self.assertRaises(InvalidCredentials):
+            validate_credentials(c)
 
-    @raises(InvalidCredentials)
     def test_no_algo(self):
         c = self.credentials.copy()
         del c['algorithm']
-        validate_credentials(c)
+        with self.assertRaises(InvalidCredentials):
+            validate_credentials(c)
 
-    @raises(InvalidCredentials)
     def test_no_credentials(self):
-        validate_credentials(None)
+        with self.assertRaises(InvalidCredentials):
+            validate_credentials(None)
 
     def test_non_dict_credentials(self):
         class WeirdThing(object):
@@ -144,10 +142,10 @@ def test_post_content_type_with_trailing_charset(self):
         self.receive(sn.request_header, method=method, content=content,
                      content_type='application/json; charset=other')
 
-    @raises(MissingContent)
     def test_missing_payload_details(self):
-        self.Sender(method='POST', content=EmptyValue,
-                    content_type=EmptyValue)
+        with self.assertRaises(MissingContent):
+            self.Sender(method='POST', content=EmptyValue,
+                        content_type=EmptyValue)
 
     def test_skip_payload_hashing(self):
         method = 'POST'
@@ -193,73 +191,72 @@ def test_empty_payload_hashing_accept_untrusted(self):
                      content_type=content_type,
                      accept_untrusted_content=True)
 
-    @raises(MissingContent)
     def test_cannot_skip_content_only(self):
-        self.Sender(method='POST', content=EmptyValue,
-                    content_type='application/json')
+        with self.assertRaises(MissingContent):
+            self.Sender(method='POST', content=EmptyValue,
+                        content_type='application/json')
 
-    @raises(MissingContent)
     def test_cannot_skip_content_type_only(self):
-        self.Sender(method='POST', content='{"foo": "bar"}',
-                    content_type=EmptyValue)
+        with self.assertRaises(MissingContent):
+            self.Sender(method='POST', content='{"foo": "bar"}',
+                        content_type=EmptyValue)
 
-    @raises(MacMismatch)
     def test_tamper_with_host(self):
         sn = self.Sender()
-        self.receive(sn.request_header, url='http://TAMPERED-WITH.com')
+        with self.assertRaises(MacMismatch):
+            self.receive(sn.request_header, url='http://TAMPERED-WITH.com')
 
-    @raises(MacMismatch)
     def test_tamper_with_method(self):
         sn = self.Sender(method='GET')
-        self.receive(sn.request_header, method='POST')
+        with self.assertRaises(MacMismatch):
+            self.receive(sn.request_header, method='POST')
 
-    @raises(MacMismatch)
     def test_tamper_with_path(self):
         sn = self.Sender()
-        self.receive(sn.request_header,
-                     url='http://site.com/TAMPERED?bar=1')
+        with self.assertRaises(MacMismatch):
+            self.receive(sn.request_header,
+                         url='http://site.com/TAMPERED?bar=1')
 
-    @raises(MacMismatch)
     def test_tamper_with_query(self):
         sn = self.Sender()
-        self.receive(sn.request_header,
-                     url='http://site.com/foo?bar=TAMPERED')
+        with self.assertRaises(MacMismatch):
+            self.receive(sn.request_header,
+                         url='http://site.com/foo?bar=TAMPERED')
 
-    @raises(MacMismatch)
     def test_tamper_with_scheme(self):
         sn = self.Sender()
-        self.receive(sn.request_header, url='https://site.com/foo?bar=1')
+        with self.assertRaises(MacMismatch):
+            self.receive(sn.request_header, url='https://site.com/foo?bar=1')
 
-    @raises(MacMismatch)
     def test_tamper_with_port(self):
         sn = self.Sender()
-        self.receive(sn.request_header,
-                     url='http://site.com:8000/foo?bar=1')
+        with self.assertRaises(MacMismatch):
+            self.receive(sn.request_header,
+                         url='http://site.com:8000/foo?bar=1')
 
-    @raises(MisComputedContentHash)
     def test_tamper_with_content(self):
         sn = self.Sender()
-        self.receive(sn.request_header, content='stuff=nope')
+        with self.assertRaises(MisComputedContentHash):
+            self.receive(sn.request_header, content='stuff=nope')
 
     def test_non_ascii_content(self):
         content = u'Ivan Kristi\u0107'
         sn = self.Sender(content=content)
         self.receive(sn.request_header, content=content)
 
-    @raises(MacMismatch)
     def test_tamper_with_content_type(self):
         sn = self.Sender(method='POST')
-        self.receive(sn.request_header, content_type='application/json')
+        with self.assertRaises(MacMismatch):
+            self.receive(sn.request_header, content_type='application/json')
 
-    @raises(AlreadyProcessed)
     def test_nonce_fail(self):
 
         def seen_nonce(id, nonce, ts):
             return True
 
         sn = self.Sender()
-
-        self.receive(sn.request_header, seen_nonce=seen_nonce)
+        with self.assertRaises(AlreadyProcessed):
+            self.receive(sn.request_header, seen_nonce=seen_nonce)
 
     def test_nonce_ok(self):
 
@@ -269,11 +266,11 @@ def seen_nonce(id, nonce, ts):
         sn = self.Sender(seen_nonce=seen_nonce)
         self.receive(sn.request_header)
 
-    @raises(TokenExpired)
     def test_expired_ts(self):
         now = utc_now() - 120
         sn = self.Sender(_timestamp=now)
-        self.receive(sn.request_header)
+        with self.assertRaises(TokenExpired):
+            self.receive(sn.request_header)
 
     def test_expired_exception_reports_localtime(self):
         now = utc_now()
@@ -303,13 +300,12 @@ def test_localtime_skew(self):
         # Without an offset this will raise an expired exception.
         self.receive(sn.request_header, timestamp_skew_in_seconds=120)
 
-    @raises(MacMismatch)
     def test_hash_tampering(self):
         sn = self.Sender()
         header = sn.request_header.replace('hash="', 'hash="nope')
-        self.receive(header)
+        with self.assertRaises(MacMismatch):
+            self.receive(header)
 
-    @raises(MacMismatch)
     def test_bad_secret(self):
         cfg = {
             'id': 'my-hawk-id',
@@ -317,55 +313,56 @@ def test_bad_secret(self):
             'algorithm': 'sha256',
         }
         sn = self.Sender(credentials=cfg)
-        self.receive(sn.request_header)
+        with self.assertRaises(MacMismatch):
+            self.receive(sn.request_header)
 
-    @raises(MacMismatch)
     def test_unexpected_algorithm(self):
         cr = self.credentials.copy()
         cr['algorithm'] = 'sha512'
         sn = self.Sender(credentials=cr)
 
         # Validate with mismatched credentials (sha256).
-        self.receive(sn.request_header)
+        with self.assertRaises(MacMismatch):
+            self.receive(sn.request_header)
 
-    @raises(InvalidCredentials)
     def test_invalid_credentials(self):
         cfg = self.credentials.copy()
         # Create an invalid credentials.
         del cfg['algorithm']
 
-        self.Sender(credentials=cfg)
+        with self.assertRaises(InvalidCredentials):
+            self.Sender(credentials=cfg)
 
-    @raises(CredentialsLookupError)
     def test_unknown_id(self):
         cr = self.credentials.copy()
         cr['id'] = 'someone-else'
         sn = self.Sender(credentials=cr)
 
-        self.receive(sn.request_header)
+        with self.assertRaises(CredentialsLookupError):
+            self.receive(sn.request_header)
 
-    @raises(MacMismatch)
     def test_bad_ext(self):
         sn = self.Sender(ext='my external data')
 
         header = sn.request_header.replace('my external data', 'TAMPERED')
-        self.receive(header)
+        with self.assertRaises(MacMismatch):
+            self.receive(header)
 
-    @raises(BadHeaderValue)
     def test_duplicate_keys(self):
         sn = self.Sender(ext='someext')
         header = sn.request_header + ', ext="otherext"'
-        self.receive(header)
+        with self.assertRaises(BadHeaderValue):
+            self.receive(header)
 
-    @raises(BadHeaderValue)
     def test_ext_with_quotes(self):
-        sn = self.Sender(ext='quotes=""')
-        self.receive(sn.request_header)
+        with self.assertRaises(BadHeaderValue):
+            sn = self.Sender(ext='quotes=""')
+            self.receive(sn.request_header)
 
-    @raises(BadHeaderValue)
     def test_ext_with_new_line(self):
-        sn = self.Sender(ext="new line \n in the middle")
-        self.receive(sn.request_header)
+        with self.assertRaises(BadHeaderValue):
+            sn = self.Sender(ext="new line \n in the middle")
+            self.receive(sn.request_header)
 
     def test_ext_with_equality_sign(self):
         sn = self.Sender(ext="foo=bar&foo2=bar2;foo3=bar3")
@@ -373,13 +370,13 @@ def test_ext_with_equality_sign(self):
         parsed = parse_authorization_header(sn.request_header)
         assert parsed['ext'] == "foo=bar&foo2=bar2;foo3=bar3"
 
-    @raises(HawkFail)
     def test_non_hawk_scheme(self):
-        parse_authorization_header('Basic user:base64pw')
+        with self.assertRaises(HawkFail):
+            parse_authorization_header('Basic user:base64pw')
 
-    @raises(HawkFail)
     def test_invalid_key(self):
-        parse_authorization_header('Hawk mac="validmac" unknownkey="value"')
+        with self.assertRaises(HawkFail):
+            parse_authorization_header('Hawk mac="validmac" unknownkey="value"')
 
     def test_ext_with_all_valid_characters(self):
         valid_characters = "!#$%&'()*+,-./:;<=>?@[]^_`{|}~ azAZ09_"
@@ -387,9 +384,9 @@ def test_ext_with_all_valid_characters(self):
         parsed = parse_authorization_header(sender.request_header)
         assert parsed['ext'] == valid_characters
 
-    @raises(BadHeaderValue)
     def test_ext_with_illegal_chars(self):
-        self.Sender(ext="something like \t is illegal")
+        with self.assertRaises(BadHeaderValue):
+            self.Sender(ext="something like \t is illegal")
 
     def test_unparseable_header(self):
         try:
@@ -401,20 +398,20 @@ def test_unparseable_header(self):
         else:
             self.fail('should raise')
 
-    @raises(BadHeaderValue)
     def test_ext_with_illegal_unicode(self):
-        self.Sender(ext=u'Ivan Kristi\u0107')
+        with self.assertRaises(BadHeaderValue):
+            self.Sender(ext=u'Ivan Kristi\u0107')
 
-    @raises(BadHeaderValue)
     def test_too_long_header(self):
         sn = self.Sender(ext='a'*5000)
-        self.receive(sn.request_header)
+        with self.assertRaises(BadHeaderValue):
+            self.receive(sn.request_header)
 
-    @raises(BadHeaderValue)
     def test_ext_with_illegal_utf8(self):
         # This isn't allowed because the escaped byte chars are out of
         # range.
-        self.Sender(ext=u'Ivan Kristi\u0107'.encode('utf8'))
+        with self.assertRaises(BadHeaderValue):
+            self.Sender(ext=u'Ivan Kristi\u0107'.encode('utf8'))
 
     def test_app_ok(self):
         app = 'custom-app'
@@ -423,12 +420,12 @@ def test_app_ok(self):
         parsed = parse_authorization_header(sn.request_header)
         assert parsed['app'] == app
 
-    @raises(MacMismatch)
     def test_tampered_app(self):
         app = 'custom-app'
         sn = self.Sender(app=app)
         header = sn.request_header.replace(app, 'TAMPERED-WITH')
-        self.receive(header)
+        with self.assertRaises(MacMismatch):
+            self.receive(header)
 
     def test_dlg_ok(self):
         dlg = 'custom-dlg'
@@ -437,12 +434,12 @@ def test_dlg_ok(self):
         parsed = parse_authorization_header(sn.request_header)
         assert parsed['dlg'] == dlg
 
-    @raises(MacMismatch)
     def test_tampered_dlg(self):
         dlg = 'custom-dlg'
         sn = self.Sender(dlg=dlg, app='some-app')
         header = sn.request_header.replace(dlg, 'TAMPERED-WITH')
-        self.receive(header)
+        with self.assertRaises(MacMismatch):
+            self.receive(header)
 
     def test_file_content(self):
         method = "POST"
@@ -456,12 +453,12 @@ def test_binary_file_content(self):
         sn = self.Sender(method, content=content)
         self.receive(sn.request_header, method=method, content=content.getvalue())
 
-    @raises(MisComputedContentHash)
     def test_bad_file_content(self):
         method = "POST"
         content = six.BytesIO(b"FILE CONTENT")
         sn = self.Sender(method, content=content)
-        self.receive(sn.request_header, method=method, content="BAD FILE CONTENT")
+        with self.assertRaises(MisComputedContentHash):
+            self.receive(sn.request_header, method=method, content="BAD FILE CONTENT")
 
 
 class TestReceiver(Base):
@@ -508,10 +505,10 @@ def respond(self, **kw):
 
         return receiver.response_header
 
-    @raises(InvalidCredentials)
     def test_invalid_credentials_lookup(self):
         # Return invalid credentials.
-        self.receive(credentials_map=lambda *a: {})
+        with self.assertRaises(InvalidCredentials):
+            self.receive(credentials_map=lambda *a: {})
 
     def test_get_ok(self):
         method = 'GET'
@@ -523,49 +520,49 @@ def test_post_ok(self):
         self.receive(method=method)
         self.respond()
 
-    @raises(MisComputedContentHash)
     def test_respond_with_wrong_content(self):
         self.receive()
-        self.respond(content='real content',
-                     accept_kw=dict(content='TAMPERED WITH'))
+        with self.assertRaises(MisComputedContentHash):
+            self.respond(content='real content',
+                         accept_kw=dict(content='TAMPERED WITH'))
 
-    @raises(MisComputedContentHash)
     def test_respond_with_wrong_content_type(self):
         self.receive()
-        self.respond(content_type='text/html',
-                     accept_kw=dict(content_type='application/json'))
+        with self.assertRaises(MisComputedContentHash):
+            self.respond(content_type='text/html',
+                        accept_kw=dict(content_type='application/json'))
 
-    @raises(MissingAuthorization)
     def test_missing_authorization(self):
-        Receiver(lambda id: self.credentials, None, '/', 'GET')
+        with self.assertRaises(MissingAuthorization):
+            Receiver(lambda id: self.credentials, None, '/', 'GET')
 
-    @raises(MacMismatch)
     def test_respond_with_wrong_url(self):
-        self.receive(url='http://fakesite.com')
-        wrong_receiver = self.receiver
+        with self.assertRaises(MacMismatch):
+            self.receive(url='http://fakesite.com')
+            wrong_receiver = self.receiver
 
-        self.receive(url='http://realsite.com')
+            self.receive(url='http://realsite.com')
 
-        self.respond(receiver=wrong_receiver)
+            self.respond(receiver=wrong_receiver)
 
-    @raises(MacMismatch)
     def test_respond_with_wrong_method(self):
-        self.receive(method='GET')
-        wrong_receiver = self.receiver
+        with self.assertRaises(MacMismatch):
+            self.receive(method='GET')
+            wrong_receiver = self.receiver
 
-        self.receive(method='POST')
+            self.receive(method='POST')
 
-        self.respond(receiver=wrong_receiver)
+            self.respond(receiver=wrong_receiver)
 
-    @raises(MacMismatch)
     def test_respond_with_wrong_nonce(self):
-        self.receive(sender_kw=dict(nonce='another-nonce'))
-        wrong_receiver = self.receiver
+        with self.assertRaises(MacMismatch):
+            self.receive(sender_kw=dict(nonce='another-nonce'))
+            wrong_receiver = self.receiver
 
-        self.receive()
+            self.receive()
 
-        # The nonce must match the one sent in the original request.
-        self.respond(receiver=wrong_receiver)
+            # The nonce must match the one sent in the original request.
+            self.respond(receiver=wrong_receiver)
 
     def test_respond_with_unhashed_content(self):
         self.receive()
@@ -574,23 +571,23 @@ def test_respond_with_unhashed_content(self):
                      content_type=None,
                      accept_kw=dict(accept_untrusted_content=True))
 
-    @raises(TokenExpired)
     def test_respond_with_expired_ts(self):
         self.receive()
         hdr = self.receiver.respond(content='', content_type='')
 
-        with mock.patch('mohawk.base.utc_now') as fn:
-            fn.return_value = 0  # force an expiry
-            try:
-                self.sender.accept_response(hdr, content='', content_type='')
-            except TokenExpired:
-                etype, exc, tb = sys.exc_info()
-                hdr = parse_authorization_header(exc.www_authenticate)
-                calculated = calculate_ts_mac(fn(), self.credentials)
-                if isinstance(calculated, six.binary_type):
-                    calculated = calculated.decode('ascii')
-                assert hdr['tsm'] == calculated
-                raise
+        with self.assertRaises(TokenExpired):
+            with mock.patch('mohawk.base.utc_now') as fn:
+                fn.return_value = 0  # force an expiry
+                try:
+                    self.sender.accept_response(hdr, content='', content_type='')
+                except TokenExpired:
+                    etype, exc, tb = sys.exc_info()
+                    hdr = parse_authorization_header(exc.www_authenticate)
+                    calculated = calculate_ts_mac(fn(), self.credentials)
+                    if isinstance(calculated, six.binary_type):
+                        calculated = calculated.decode('ascii')
+                    assert hdr['tsm'] == calculated
+                    raise
 
     def test_respond_with_bad_ts_skew_ok(self):
         now = utc_now() - 120
@@ -613,7 +610,6 @@ def test_respond_with_ext(self):
         header = parse_authorization_header(self.receiver.response_header)
         assert header['ext'] == ext
 
-    @raises(MacMismatch)
     def test_respond_with_wrong_app(self):
         self.receive(sender_kw=dict(app='TAMPERED-WITH', dlg='delegation'))
         self.receiver.respond(content='', content_type='')
@@ -621,10 +617,10 @@ def test_respond_with_wrong_app(self):
 
         self.receive(sender_kw=dict(app='real-app', dlg='delegation'))
 
-        self.sender.accept_response(wrong_receiver.response_header,
-                                    content='', content_type='')
+        with self.assertRaises(MacMismatch):
+            self.sender.accept_response(wrong_receiver.response_header,
+                                        content='', content_type='')
 
-    @raises(MacMismatch)
     def test_respond_with_wrong_dlg(self):
         self.receive(sender_kw=dict(app='app', dlg='TAMPERED-WITH'))
         self.receiver.respond(content='', content_type='')
@@ -632,27 +628,28 @@ def test_respond_with_wrong_dlg(self):
 
         self.receive(sender_kw=dict(app='app', dlg='real-dlg'))
 
-        self.sender.accept_response(wrong_receiver.response_header,
-                                    content='', content_type='')
+        with self.assertRaises(MacMismatch):
+            self.sender.accept_response(wrong_receiver.response_header,
+                                        content='', content_type='')
 
-    @raises(MacMismatch)
     def test_receive_wrong_method(self):
         self.receive(method='GET')
         wrong_sender = self.sender
-        self.receive(method='POST', sender=wrong_sender)
+        with self.assertRaises(MacMismatch):
+            self.receive(method='POST', sender=wrong_sender)
 
-    @raises(MacMismatch)
     def test_receive_wrong_url(self):
         self.receive(url='http://fakesite.com/')
         wrong_sender = self.sender
-        self.receive(url='http://realsite.com/', sender=wrong_sender)
+        with self.assertRaises(MacMismatch):
+            self.receive(url='http://realsite.com/', sender=wrong_sender)
 
-    @raises(MisComputedContentHash)
     def test_receive_wrong_content(self):
         self.receive(sender_kw=dict(content='real request'),
                      content='real request')
         wrong_sender = self.sender
-        self.receive(content='TAMPERED WITH', sender=wrong_sender)
+        with self.assertRaises(MisComputedContentHash):
+            self.receive(content='TAMPERED WITH', sender=wrong_sender)
 
     def test_expected_unhashed_empty_content(self):
         # This test sets up a scenario where the receiver will receive empty
@@ -669,7 +666,6 @@ def test_expected_unhashed_empty_content(self):
                                     content_type=EmptyValue,
                                     always_hash_content=False))
 
-    @raises(MisComputedContentHash)
     def test_expected_unhashed_empty_content_with_content_type(self):
         # This test sets up a scenario where the receiver will receive an
         # empty content string and no content hash in the auth header, but
@@ -677,13 +673,13 @@ def test_expected_unhashed_empty_content_with_content_type(self):
         # This is to confirm that the hash is calculated and compared (to the
         # hash of mock empty payload, which should fail) when it appears that
         # the sender has sent a 0-length payload body.
-        self.receive(content='',
-                     content_type='text/plain',
-                     sender_kw=dict(content=EmptyValue,
-                                    content_type=EmptyValue,
-                                    always_hash_content=False))
+        with self.assertRaises(MisComputedContentHash):
+            self.receive(content='',
+                         content_type='text/plain',
+                         sender_kw=dict(content=EmptyValue,
+                                        content_type=EmptyValue,
+                                        always_hash_content=False))
 
-    @raises(MisComputedContentHash)
     def test_expected_unhashed_content_with_empty_content_type(self):
         # This test sets up a scenario where the receiver will receive some
         # content but the empty string for the content_type and no content hash
@@ -691,11 +687,12 @@ def test_expected_unhashed_content_with_empty_content_type(self):
         # This is to confirm that the hash is calculated and compared (to the
         # hash of mock empty payload, which should fail) when the sender has
         # sent unhashed content.
-        self.receive(content='some content',
-                     content_type='',
-                     sender_kw=dict(content=EmptyValue,
-                                    content_type=EmptyValue,
-                                    always_hash_content=False))
+        with self.assertRaises(MisComputedContentHash):
+            self.receive(content='some content',
+                         content_type='',
+                         sender_kw=dict(content=EmptyValue,
+                                        content_type=EmptyValue,
+                                        always_hash_content=False))
 
     def test_empty_content_with_content_type(self):
         # This test sets up a scenario where the receiver will receive an
@@ -719,7 +716,6 @@ def test_expected_unhashed_no_content(self):
                                     content_type=EmptyValue,
                                     always_hash_content=False))
 
-    @raises(MisComputedContentHash)
     def test_expected_unhashed_no_content_with_content_type(self):
         # This test sets up a scenario where the receiver will receive None for
         # content and no content hash in the auth header, but some value for
@@ -729,13 +725,13 @@ def test_expected_unhashed_no_content_with_content_type(self):
         # hash in the request to compare against. While this may not be in
         # accordance with the js reference spec, it's the safest (ie. most
         # secure) way of handling this bizarre set of circumstances.
-        self.receive(content=None,
-                     content_type='text/plain',
-                     sender_kw=dict(content=EmptyValue,
-                                    content_type=EmptyValue,
-                                    always_hash_content=False))
+        with self.assertRaises(MisComputedContentHash):
+            self.receive(content=None,
+                         content_type='text/plain',
+                         sender_kw=dict(content=EmptyValue,
+                                        content_type=EmptyValue,
+                                        always_hash_content=False))
 
-    @raises(MisComputedContentHash)
     def test_expected_unhashed_content_with_no_content_type(self):
         # This test sets up a scenario where the receiver will receive some
         # content but no value for the content_type and no content hash in
@@ -743,11 +739,12 @@ def test_expected_unhashed_content_with_no_content_type(self):
         # This is to confirm that the hash is calculated and compared (to the
         # hash of mock empty payload, which should fail) when the sender has
         # sent unhashed content.
-        self.receive(content='some content',
-                     content_type=None,
-                     sender_kw=dict(content=EmptyValue,
-                                    content_type=EmptyValue,
-                                    always_hash_content=False))
+        with self.assertRaises(MisComputedContentHash):
+            self.receive(content='some content',
+                         content_type=None,
+                         sender_kw=dict(content=EmptyValue,
+                                        content_type=EmptyValue,
+                                        always_hash_content=False))
 
     def test_no_content_with_content_type(self):
         # This test sets up a scenario where the receiver will receive None for
@@ -760,28 +757,28 @@ def test_no_content_with_content_type(self):
                      sender_kw=dict(content='',
                                     content_type='text/plain'))
 
-    @raises(MissingContent)
     def test_cannot_receive_empty_content_only(self):
         content_type = 'text/plain'
-        self.receive(sender_kw=dict(content='<content>',
-                                    content_type=content_type),
-                     content=EmptyValue, content_type=content_type)
+        with self.assertRaises(MissingContent):
+            self.receive(sender_kw=dict(content='<content>',
+                                        content_type=content_type),
+                         content=EmptyValue, content_type=content_type)
 
-    @raises(MissingContent)
     def test_cannot_receive_empty_content_type_only(self):
         content = '<content>'
-        self.receive(sender_kw=dict(content=content,
-                                    content_type='text/plain'),
-                     content=content, content_type=EmptyValue)
+        with self.assertRaises(MissingContent):
+            self.receive(sender_kw=dict(content=content,
+                                        content_type='text/plain'),
+                         content=content, content_type=EmptyValue)
 
-    @raises(MisComputedContentHash)
     def test_receive_wrong_content_type(self):
         self.receive(sender_kw=dict(content_type='text/html'),
                      content_type='text/html')
         wrong_sender = self.sender
 
-        self.receive(content_type='application/json',
-                     sender=wrong_sender)
+        with self.assertRaises(MisComputedContentHash):
+            self.receive(content_type='application/json',
+                         sender=wrong_sender)
 
 
 class TestSendAndReceive(Base):
@@ -879,24 +876,24 @@ def test_bewit_with_ext(self):
         expected = '123456\\1356420707\\kscxwNR2tJpP1T1zDLNPbB5UiKIU9tOSJXTUdG7X9h8=\\xandyandz'
         assert b64decode(bewit).decode('ascii') == expected
 
-    @raises(BadHeaderValue)
     def test_bewit_with_invalid_ext(self):
         res = Resource(url='https://example.com/somewhere/over/the/rainbow',
                        method='GET', credentials=self.credentials,
                        timestamp=1356420407 + 300,
                        nonce='',
                        ext='xand\\yandz')
-        get_bewit(res)
+        with self.assertRaises(BadHeaderValue):
+            get_bewit(res)
 
-    @raises(BadHeaderValue)
     def test_bewit_with_backslashes_in_id(self):
         credentials = self.credentials
         credentials['id'] = '123\\456'
-        res = Resource(url='https://example.com/somewhere/over/the/rainbow',
-                       method='GET', credentials=self.credentials,
-                       timestamp=1356420407 + 300,
-                       nonce='')
-        get_bewit(res)
+        with self.assertRaises(BadHeaderValue):
+            res = Resource(url='https://example.com/somewhere/over/the/rainbow',
+                           method='GET', credentials=self.credentials,
+                           timestamp=1356420407 + 300,
+                           nonce='')
+            get_bewit(res)
 
     def test_bewit_with_port(self):
         res = Resource(url='https://example.com:8080/somewhere/over/the/rainbow',
@@ -907,20 +904,20 @@ def test_bewit_with_port(self):
         expected = '123456\\1356420707\\hZbJ3P2cKEo4ky0C8jkZAkRyCZueg4WSNbxV7vq3xHU=\\xandyandz'
         assert b64decode(bewit).decode('ascii') == expected
 
-    @raises(ValueError)
     def test_bewit_with_nonce(self):
         res = Resource(url='https://example.com/somewhere/over/the/rainbow',
                        method='GET', credentials=self.credentials,
                        timestamp=1356420407 + 300,
                        nonce='n1')
-        get_bewit(res)
+        with self.assertRaises(ValueError):
+            get_bewit(res)
 
-    @raises(ValueError)
     def test_bewit_invalid_method(self):
         res = Resource(url='https://example.com:8080/somewhere/over/the/rainbow',
                        method='POST', credentials=self.credentials,
                        timestamp=1356420407 + 300, nonce='')
-        get_bewit(res)
+        with self.assertRaises(ValueError):
+            get_bewit(res)
 
     def test_strip_bewit(self):
         bewit = b'123456\\1356420707\\IGYmLgIqLrCe8CxvKPs4JlWIA+UjWJJouwgARiVhCAg=\\'
@@ -931,10 +928,10 @@ def test_strip_bewit(self):
         self.assertEqual(raw_bewit, bewit)
         self.assertEqual(stripped_url, "https://example.com/somewhere/over/the/rainbow")
 
-    @raises(InvalidBewit)
     def test_strip_url_without_bewit(self):
         url = "https://example.com/somewhere/over/the/rainbow"
-        strip_bewit(url)
+        with self.assertRaises(InvalidBewit):
+            strip_bewit(url)
 
     def test_parse_bewit(self):
         bewit = b'123456\\1356420707\\IGYmLgIqLrCe8CxvKPs4JlWIA+UjWJJouwgARiVhCAg=\\'
@@ -954,23 +951,23 @@ def test_parse_bewit_with_ext(self):
         self.assertEqual(bewit.mac, 'IGYmLgIqLrCe8CxvKPs4JlWIA+UjWJJouwgARiVhCAg=')
         self.assertEqual(bewit.ext, 'xandyandz')
 
-    @raises(InvalidBewit)
     def test_parse_bewit_with_ext_and_backslashes(self):
         bewit = b'123456\\1356420707\\IGYmLgIqLrCe8CxvKPs4JlWIA+UjWJJouwgARiVhCAg=\\xand\\yandz'
         bewit = urlsafe_b64encode(bewit).decode('ascii')
-        parse_bewit(bewit)
+        with self.assertRaises(InvalidBewit):
+            parse_bewit(bewit)
 
-    @raises(InvalidBewit)
     def test_parse_invalid_bewit_with_only_one_part(self):
         bewit = b'12345'
         bewit = urlsafe_b64encode(bewit).decode('ascii')
-        bewit = parse_bewit(bewit)
+        with self.assertRaises(InvalidBewit):
+            bewit = parse_bewit(bewit)
 
-    @raises(InvalidBewit)
     def test_parse_invalid_bewit_with_only_two_parts(self):
         bewit = b'1\\2'
         bewit = urlsafe_b64encode(bewit).decode('ascii')
-        bewit = parse_bewit(bewit)
+        with self.assertRaises(InvalidBewit):
+            bewit = parse_bewit(bewit)
 
     def test_validate_bewit(self):
         bewit = b'123456\\1356420707\\IGYmLgIqLrCe8CxvKPs4JlWIA+UjWJJouwgARiVhCAg=\\'
@@ -990,7 +987,6 @@ def test_validate_bewit_with_ext(self):
         })
         self.assertTrue(check_bewit(url, credential_lookup=credential_lookup, now=1356420407 + 10))
 
-    @raises(InvalidBewit)
     def test_validate_bewit_with_ext_and_backslashes(self):
         bewit = b'123456\\1356420707\\b82LLIxG5UDkaChLU953mC+SMrbniV1sb8KiZi9cSsc=\\xand\\yandz'
         bewit = urlsafe_b64encode(bewit).decode('ascii')
@@ -998,9 +994,9 @@ def test_validate_bewit_with_ext_and_backslashes(self):
         credential_lookup = self.make_credential_lookup({
             self.credentials['id']: self.credentials,
         })
-        check_bewit(url, credential_lookup=credential_lookup, now=1356420407 + 10)
+        with self.assertRaises(InvalidBewit):
+            check_bewit(url, credential_lookup=credential_lookup, now=1356420407 + 10)
 
-    @raises(TokenExpired)
     def test_validate_expired_bewit(self):
         bewit = b'123456\\1356420707\\IGYmLgIqLrCe8CxvKPs4JlWIA+UjWJJouwgARiVhCAg=\\'
         bewit = urlsafe_b64encode(bewit).decode('ascii')
@@ -1008,9 +1004,9 @@ def test_validate_expired_bewit(self):
         credential_lookup = self.make_credential_lookup({
             self.credentials['id']: self.credentials,
         })
-        check_bewit(url, credential_lookup=credential_lookup, now=1356420407 + 1000)
+        with self.assertRaises(TokenExpired):
+            check_bewit(url, credential_lookup=credential_lookup, now=1356420407 + 1000)
 
-    @raises(CredentialsLookupError)
     def test_validate_bewit_with_unknown_credentials(self):
         bewit = b'123456\\1356420707\\IGYmLgIqLrCe8CxvKPs4JlWIA+UjWJJouwgARiVhCAg=\\'
         bewit = urlsafe_b64encode(bewit).decode('ascii')
@@ -1018,7 +1014,8 @@ def test_validate_bewit_with_unknown_credentials(self):
         credential_lookup = self.make_credential_lookup({
             'other_id': self.credentials,
         })
-        check_bewit(url, credential_lookup=credential_lookup, now=1356420407 + 10)
+        with self.assertRaises(CredentialsLookupError):
+            check_bewit(url, credential_lookup=credential_lookup, now=1356420407 + 10)
 
 
 class TestPayloadHash(Base):

From e5ea6c8b0fad6a4b2e575c4f4ff669b9ac775fad Mon Sep 17 00:00:00 2001
From: Pyrox <pyrox@pyrox.dev>
Date: Fri, 2 Aug 2024 15:20:41 -0400
Subject: [PATCH 3/4] update dependencies

---
 requirements/dev.txt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/requirements/dev.txt b/requirements/dev.txt
index 5636a39..4fa2e67 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -1,6 +1,6 @@
 # For testing.
-mock >= 3.0.5
-nose >= 1.3.7
+mock >= 3.0.5; python_version < '3'
+pytest >=8.0.0
 
 # For documentation.
 docutils >= 0.15.2

From 1788cdfc47273d18c0624bca9bb543f9a88a22e2 Mon Sep 17 00:00:00 2001
From: Pyrox <pyrox@pyrox.dev>
Date: Fri, 2 Aug 2024 15:20:47 -0400
Subject: [PATCH 4/4] update supported python versions

---
 setup.py | 5 +++++
 tox.ini  | 4 ++--
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/setup.py b/setup.py
index 122bb36..53617b1 100644
--- a/setup.py
+++ b/setup.py
@@ -33,6 +33,11 @@
           'Programming Language :: Python :: 3.5',
           'Programming Language :: Python :: 3.6',
           'Programming Language :: Python :: 3.7',
+          'Programming Language :: Python :: 3.8',
+          'Programming Language :: Python :: 3.9',
+          'Programming Language :: Python :: 3.10',
+          'Programming Language :: Python :: 3.11',
+          'Programming Language :: Python :: 3.12',
           'Topic :: Internet :: WWW/HTTP',
       ],
       packages=find_packages(exclude=['tests']),
diff --git a/tox.ini b/tox.ini
index 7357100..3fdeacf 100644
--- a/tox.ini
+++ b/tox.ini
@@ -2,7 +2,7 @@
 
 [tox]
 # Also see .travis.yml where this is maintained separately.
-envlist=py27,py34,py35,py36,py37,py38,docs
+envlist=py27,py34,py35,py36,py37,py38,py39,py310,py311,py312,docs
 
 [base]
 deps=
@@ -11,7 +11,7 @@ deps=
 [testenv]
 deps={[base]deps}
 commands=
-    nosetests []
+    pytest mohawk/tests.py
 
 [testenv:docs]
 basepython=python3.7

M gnu/packages/python-web.scm => gnu/packages/python-web.scm +25 -17
@@ 5450,24 5450,32 @@ protocol, both client and server for Python asyncio module.
    (license license:asl2.0)))

(define-public python-mohawk
  (package
    (name "python-mohawk")
    (version "1.1.0")
    (source (origin
              (method url-fetch)
              (uri (pypi-uri "mohawk" version))
              (sha256
               (base32
                "08wppsv65yd0gdxy5zwq37yp6jmxakfz4a2yx5wwq2d222my786j"))))
    (build-system python-build-system)
    (native-inputs (list python-mock  python-nose))
    (propagated-inputs (list python-six))
    (home-page "https://github.com/kumar303/mohawk")
    (synopsis "Library for Hawk HTTP authorization")
    (description
     "Mohawk is an alternate Python implementation of the Hawk HTTP
  (let ((commit "b7899166880e890f01cf2531b5686094ba08df8f")
        (revision "0"))
    (package
      (name "python-mohawk")
      (version (git-version "1.1.0" revision commit))
      (source
       (origin
         (method git-fetch)
         (uri (git-reference
                (url "https://github.com/kumar303/mohawk")
                (commit commit)))
         (file-name (git-file-name name version))
         (sha256
          (base32 "00y9fimcj851rk2770wqk61fac9pnxfnzca4fvsci57zw18i50m6"))
         (patches
          (search-patches "python-mohawk-pytest.patch"))))
      (build-system pyproject-build-system)
      (arguments
       (list #:test-flags #~(list "mohawk/tests.py")))
      (native-inputs (list python-pytest python-setuptools-next))
      (home-page "https://github.com/kumar303/mohawk")
      (synopsis "Library for Hawk HTTP authorization")
      (description
       "Mohawk is an alternate Python implementation of the Hawk HTTP
authorization scheme.")
    (license license:bsd-3)))
      (license license:bsd-3))))

(define-public python-msal
  (package