httpbin 0.10.1__tar.gz → 0.10.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. {httpbin-0.10.1 → httpbin-0.10.3}/PKG-INFO +27 -6
  2. {httpbin-0.10.1 → httpbin-0.10.3}/README.md +6 -0
  3. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin/core.py +5 -7
  4. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin/helpers.py +16 -5
  5. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin.egg-info/PKG-INFO +27 -6
  6. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin.egg-info/requires.txt +2 -2
  7. {httpbin-0.10.1 → httpbin-0.10.3}/pyproject.toml +6 -5
  8. {httpbin-0.10.1 → httpbin-0.10.3}/tests/test_httpbin.py +21 -10
  9. {httpbin-0.10.1 → httpbin-0.10.3}/AUTHORS +0 -0
  10. {httpbin-0.10.1 → httpbin-0.10.3}/LICENSE +0 -0
  11. {httpbin-0.10.1 → httpbin-0.10.3}/LICENSE.ISC +0 -0
  12. {httpbin-0.10.1 → httpbin-0.10.3}/LICENSE.MIT +0 -0
  13. {httpbin-0.10.1 → httpbin-0.10.3}/MANIFEST.in +0 -0
  14. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin/__init__.py +0 -0
  15. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin/filters.py +0 -0
  16. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin/static/favicon.ico +0 -0
  17. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin/structures.py +0 -0
  18. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin/templates/UTF-8-demo.txt +0 -0
  19. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin/templates/flasgger/index.html +0 -0
  20. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin/templates/footer.html +0 -0
  21. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin/templates/forms-post.html +0 -0
  22. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin/templates/httpbin.1.html +0 -0
  23. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin/templates/images/jackal.jpg +0 -0
  24. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin/templates/images/pig_icon.png +0 -0
  25. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin/templates/images/svg_logo.svg +0 -0
  26. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin/templates/images/wolf_1.webp +0 -0
  27. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin/templates/index.html +0 -0
  28. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin/templates/moby.html +0 -0
  29. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin/templates/sample.xml +0 -0
  30. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin/templates/trackingscripts.html +0 -0
  31. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin/utils.py +0 -0
  32. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin.egg-info/SOURCES.txt +0 -0
  33. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin.egg-info/dependency_links.txt +0 -0
  34. {httpbin-0.10.1 → httpbin-0.10.3}/httpbin.egg-info/top_level.txt +0 -0
  35. {httpbin-0.10.1 → httpbin-0.10.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: httpbin
3
- Version: 0.10.1
3
+ Version: 0.10.3
4
4
  Summary: HTTP Request and Response Service
5
5
  Author-email: Kenneth Reitz <me@kennethreitz.org>
6
6
  License: MIT or ISC
@@ -12,20 +12,35 @@ Classifier: Natural Language :: English
12
12
  Classifier: License :: OSI Approved :: ISC License (ISCL)
13
13
  Classifier: License :: OSI Approved :: MIT License
14
14
  Classifier: Programming Language :: Python
15
- Classifier: Programming Language :: Python :: 3.7
16
15
  Classifier: Programming Language :: Python :: 3.8
17
16
  Classifier: Programming Language :: Python :: 3.9
18
17
  Classifier: Programming Language :: Python :: 3.10
19
18
  Classifier: Programming Language :: Python :: 3.11
20
19
  Classifier: Programming Language :: Python :: 3.12
21
- Requires-Python: >=3.7
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Requires-Python: >=3.8
22
23
  Description-Content-Type: text/markdown
23
- Provides-Extra: test
24
- Provides-Extra: mainapp
25
24
  License-File: LICENSE
26
25
  License-File: LICENSE.ISC
27
26
  License-File: LICENSE.MIT
28
27
  License-File: AUTHORS
28
+ Requires-Dist: brotlicffi
29
+ Requires-Dist: decorator
30
+ Requires-Dist: flasgger
31
+ Requires-Dist: flask>=2.2.4
32
+ Requires-Dist: greenlet<3.0; python_version < "3.12"
33
+ Requires-Dist: greenlet>=3.0.0a1; python_version >= "3.12.0rc0"
34
+ Requires-Dist: importlib-metadata; python_version < "3.8"
35
+ Requires-Dist: six
36
+ Requires-Dist: werkzeug>=2.2.2
37
+ Provides-Extra: test
38
+ Requires-Dist: pytest; extra == "test"
39
+ Requires-Dist: tox; extra == "test"
40
+ Provides-Extra: mainapp
41
+ Requires-Dist: gunicorn; extra == "mainapp"
42
+ Requires-Dist: gevent; extra == "mainapp"
43
+ Dynamic: license-file
29
44
 
30
45
  # httpbin(1): HTTP Request & Response Service
31
46
 
@@ -104,6 +119,12 @@ Releases are triggered on commits tagged with `release-` (for example
104
119
 
105
120
 
106
121
  ## Changelog
122
+ * 0.10.3:
123
+ - Fixed the /bytes endpoint to return bytes (not bytearray) for WSGI compliance with newer Werkzeug, thanks @swt2c
124
+ - Dropped support for Python 3.7
125
+ - Build and publish arm64 Docker images
126
+ * 0.10.2:
127
+ - Added support for Flask 3.0
107
128
  * 0.10.1:
108
129
  - Substantial housekeeping, dependency cleanup, image building, and packaging revamp, thanks to @exhuma and @mgorny
109
130
  * 0.10.0:
@@ -75,6 +75,12 @@ Releases are triggered on commits tagged with `release-` (for example
75
75
 
76
76
 
77
77
  ## Changelog
78
+ * 0.10.3:
79
+ - Fixed the /bytes endpoint to return bytes (not bytearray) for WSGI compliance with newer Werkzeug, thanks @swt2c
80
+ - Dropped support for Python 3.7
81
+ - Build and publish arm64 Docker images
82
+ * 0.10.2:
83
+ - Added support for Flask 3.0
78
84
  * 0.10.1:
79
85
  - Substantial housekeeping, dependency cleanup, image building, and packaging revamp, thanks to @exhuma and @mgorny
80
86
  * 0.10.0:
@@ -32,7 +32,7 @@ try:
32
32
  from werkzeug.wrappers import Response
33
33
  except ImportError: # werkzeug < 2.1
34
34
  from werkzeug.wrappers import BaseResponse as Response
35
- from werkzeug.http import parse_authorization_header
35
+
36
36
  from flasgger import Swagger, NO_SANITIZER
37
37
 
38
38
  from . import filters
@@ -47,6 +47,7 @@ from .helpers import (
47
47
  H,
48
48
  ROBOT_TXT,
49
49
  ANGRY_ASCII,
50
+ parse_authorization_header,
50
51
  parse_multi_value_header,
51
52
  next_stale_after_value,
52
53
  digest_challenge_response,
@@ -88,7 +89,7 @@ tmpl_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templates")
88
89
 
89
90
  app = Flask(__name__, template_folder=tmpl_dir)
90
91
  app.debug = bool(os.environ.get("DEBUG"))
91
- app.config["JSONIFY_PRETTYPRINT_REGULAR"] = True
92
+ app.json.compact = False
92
93
 
93
94
  app.add_template_global("HTTPBIN_TRACKING" in os.environ, name="tracking_enabled")
94
95
 
@@ -636,16 +637,13 @@ def redirect_to():
636
637
  args_dict = request.args.items()
637
638
  args = CaseInsensitiveDict(args_dict)
638
639
 
639
- # We need to build the response manually and convert to UTF-8 to prevent
640
- # werkzeug from "fixing" the URL. This endpoint should set the Location
641
- # header to the exact string supplied.
642
640
  response = app.make_response("")
643
641
  response.status_code = 302
644
642
  if "status_code" in args:
645
643
  status_code = int(args["status_code"])
646
644
  if status_code >= 300 and status_code < 400:
647
645
  response.status_code = status_code
648
- response.headers["Location"] = args["url"].encode("utf-8")
646
+ response.headers["Location"] = args["url"]
649
647
 
650
648
  return response
651
649
 
@@ -1451,7 +1449,7 @@ def random_bytes(n):
1451
1449
  response = make_response()
1452
1450
 
1453
1451
  # Note: can't just use os.urandom here because it ignores the seed
1454
- response.data = bytearray(random.randint(0, 255) for i in range(n))
1452
+ response.data = bytes(random.randint(0, 255) for i in range(n))
1455
1453
  response.content_type = "application/octet-stream"
1456
1454
  return response
1457
1455
 
@@ -13,8 +13,14 @@ import re
13
13
  import time
14
14
  import os
15
15
  from hashlib import md5, sha256, sha512
16
- from werkzeug.http import parse_authorization_header
17
16
  from werkzeug.datastructures import WWWAuthenticate
17
+ from werkzeug.http import dump_header
18
+
19
+ try:
20
+ from werkzeug.http import parse_authorization_header
21
+ except ImportError: # werkzeug < 2.3
22
+ from werkzeug.datastructures import Authorization
23
+ parse_authorization_header = Authorization.from_header
18
24
 
19
25
  from flask import request, make_response
20
26
  from six.moves.urllib.parse import urlparse, urlunparse
@@ -466,9 +472,14 @@ def digest_challenge_response(app, qop, algorithm, stale = False):
466
472
  ]), algorithm)
467
473
  opaque = H(os.urandom(10), algorithm)
468
474
 
469
- auth = WWWAuthenticate("digest")
470
- auth.set_digest('me@kennethreitz.com', nonce, opaque=opaque,
471
- qop=('auth', 'auth-int') if qop is None else (qop,), algorithm=algorithm)
472
- auth.stale = stale
475
+ values = {
476
+ 'realm': 'me@kennethreitz.com',
477
+ 'nonce': nonce,
478
+ 'opaque': opaque,
479
+ 'qop': dump_header(('auth', 'auth-int') if qop is None else (qop,)),
480
+ 'algorithm': algorithm,
481
+ 'stale': stale,
482
+ }
483
+ auth = WWWAuthenticate("digest", values=values)
473
484
  response.headers['WWW-Authenticate'] = auth.to_header()
474
485
  return response
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: httpbin
3
- Version: 0.10.1
3
+ Version: 0.10.3
4
4
  Summary: HTTP Request and Response Service
5
5
  Author-email: Kenneth Reitz <me@kennethreitz.org>
6
6
  License: MIT or ISC
@@ -12,20 +12,35 @@ Classifier: Natural Language :: English
12
12
  Classifier: License :: OSI Approved :: ISC License (ISCL)
13
13
  Classifier: License :: OSI Approved :: MIT License
14
14
  Classifier: Programming Language :: Python
15
- Classifier: Programming Language :: Python :: 3.7
16
15
  Classifier: Programming Language :: Python :: 3.8
17
16
  Classifier: Programming Language :: Python :: 3.9
18
17
  Classifier: Programming Language :: Python :: 3.10
19
18
  Classifier: Programming Language :: Python :: 3.11
20
19
  Classifier: Programming Language :: Python :: 3.12
21
- Requires-Python: >=3.7
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Requires-Python: >=3.8
22
23
  Description-Content-Type: text/markdown
23
- Provides-Extra: test
24
- Provides-Extra: mainapp
25
24
  License-File: LICENSE
26
25
  License-File: LICENSE.ISC
27
26
  License-File: LICENSE.MIT
28
27
  License-File: AUTHORS
28
+ Requires-Dist: brotlicffi
29
+ Requires-Dist: decorator
30
+ Requires-Dist: flasgger
31
+ Requires-Dist: flask>=2.2.4
32
+ Requires-Dist: greenlet<3.0; python_version < "3.12"
33
+ Requires-Dist: greenlet>=3.0.0a1; python_version >= "3.12.0rc0"
34
+ Requires-Dist: importlib-metadata; python_version < "3.8"
35
+ Requires-Dist: six
36
+ Requires-Dist: werkzeug>=2.2.2
37
+ Provides-Extra: test
38
+ Requires-Dist: pytest; extra == "test"
39
+ Requires-Dist: tox; extra == "test"
40
+ Provides-Extra: mainapp
41
+ Requires-Dist: gunicorn; extra == "mainapp"
42
+ Requires-Dist: gevent; extra == "mainapp"
43
+ Dynamic: license-file
29
44
 
30
45
  # httpbin(1): HTTP Request & Response Service
31
46
 
@@ -104,6 +119,12 @@ Releases are triggered on commits tagged with `release-` (for example
104
119
 
105
120
 
106
121
  ## Changelog
122
+ * 0.10.3:
123
+ - Fixed the /bytes endpoint to return bytes (not bytearray) for WSGI compliance with newer Werkzeug, thanks @swt2c
124
+ - Dropped support for Python 3.7
125
+ - Build and publish arm64 Docker images
126
+ * 0.10.2:
127
+ - Added support for Flask 3.0
107
128
  * 0.10.1:
108
129
  - Substantial housekeeping, dependency cleanup, image building, and packaging revamp, thanks to @exhuma and @mgorny
109
130
  * 0.10.0:
@@ -1,9 +1,9 @@
1
- Flask
2
1
  brotlicffi
3
2
  decorator
4
3
  flasgger
5
- werkzeug>=0.14.1
4
+ flask>=2.2.4
6
5
  six
6
+ werkzeug>=2.2.2
7
7
 
8
8
  [:python_version < "3.12"]
9
9
  greenlet<3.0
@@ -8,8 +8,8 @@ Repository = "https://github.com/psf/httpbin"
8
8
 
9
9
  [project]
10
10
  name = "httpbin"
11
- version = "0.10.1"
12
- requires-python = ">=3.7"
11
+ version = "0.10.3"
12
+ requires-python = ">=3.8"
13
13
  description = "HTTP Request and Response Service"
14
14
  readme = "README.md"
15
15
  license = {text = "MIT or ISC"}
@@ -23,23 +23,24 @@ classifiers = [
23
23
  "License :: OSI Approved :: ISC License (ISCL)",
24
24
  "License :: OSI Approved :: MIT License",
25
25
  "Programming Language :: Python",
26
- "Programming Language :: Python :: 3.7",
27
26
  "Programming Language :: Python :: 3.8",
28
27
  "Programming Language :: Python :: 3.9",
29
28
  "Programming Language :: Python :: 3.10",
30
29
  "Programming Language :: Python :: 3.11",
31
30
  "Programming Language :: Python :: 3.12",
31
+ "Programming Language :: Python :: 3.13",
32
+ "Programming Language :: Python :: 3.14",
32
33
  ]
33
34
  dependencies = [
34
- "Flask",
35
35
  "brotlicffi",
36
36
  "decorator",
37
37
  "flasgger",
38
+ "flask >= 2.2.4",
38
39
  'greenlet < 3.0; python_version<"3.12"',
39
40
  'greenlet >= 3.0.0a1; python_version>="3.12.0rc0"',
40
41
  'importlib-metadata; python_version<"3.8"',
41
- "werkzeug >= 0.14.1",
42
42
  "six",
43
+ "werkzeug >= 2.2.2",
43
44
  ]
44
45
 
45
46
  [project.optional-dependencies]
@@ -146,10 +146,9 @@ class HttpbinTestCase(unittest.TestCase):
146
146
  self.assertEqual(response.status_code, 200)
147
147
  data = json.loads(response.data.decode('utf-8'))
148
148
  self.assertEqual(data['args'], {})
149
- self.assertEqual(data['headers']['Host'], 'localhost')
150
149
  self.assertEqual(data['headers']['User-Agent'], 'test')
151
150
  # self.assertEqual(data['origin'], None)
152
- self.assertEqual(data['url'], 'http://localhost/get')
151
+ self.assertRegex(data['url'], '^http://[^/]*/get$')
153
152
  self.assertTrue(response.data.endswith(b'\n'))
154
153
 
155
154
  def test_anything(self):
@@ -159,8 +158,7 @@ class HttpbinTestCase(unittest.TestCase):
159
158
  self.assertEqual(response.status_code, 200)
160
159
  data = json.loads(response.data.decode('utf-8'))
161
160
  self.assertEqual(data['args'], {})
162
- self.assertEqual(data['headers']['Host'], 'localhost')
163
- self.assertEqual(data['url'], 'http://localhost/anything/foo/bar')
161
+ self.assertRegex(data['url'], '^http://[^/]*/anything/foo/bar$')
164
162
  self.assertEqual(data['method'], 'GET')
165
163
  self.assertTrue(response.data.endswith(b'\n'))
166
164
 
@@ -521,6 +519,19 @@ class HttpbinTestCase(unittest.TestCase):
521
519
  response.data, b'\xc5\xd7\x14\x84\xf8\xcf\x9b\xf4\xb7o'
522
520
  )
523
521
 
522
+ def test_bytes_endpoint_yields_bytes(self):
523
+ """WSGI bodies must be bytes (not bytearray) so strict servers
524
+ (wsgiref / pytest-httpbin) don't 500. The test client coerces the
525
+ body, so we inspect the raw WSGI iterable. Regression for /bytes."""
526
+ from werkzeug.test import create_environ
527
+ env = create_environ('/bytes/64', 'http://localhost/')
528
+ chunks = list(httpbin.app(env, lambda *a, **k: None))
529
+ self.assertTrue(chunks, "no body produced")
530
+ self.assertTrue(
531
+ all(type(c) is bytes for c in chunks),
532
+ [type(c).__name__ for c in chunks]
533
+ )
534
+
524
535
  def test_delete_endpoint_returns_body(self):
525
536
  response = self.app.delete(
526
537
  '/delete',
@@ -581,8 +592,8 @@ class HttpbinTestCase(unittest.TestCase):
581
592
 
582
593
  def test_redirect_absolute_param_n_higher_than_1(self):
583
594
  response = self.app.get('/redirect/5?absolute=true')
584
- self.assertEqual(
585
- response.headers.get('Location'), 'http://localhost/absolute-redirect/4'
595
+ self.assertRegex(
596
+ response.headers.get('Location'), '^http://[^/]*/absolute-redirect/4$'
586
597
  )
587
598
 
588
599
  def test_redirect_n_equals_to_1(self):
@@ -607,15 +618,15 @@ class HttpbinTestCase(unittest.TestCase):
607
618
 
608
619
  def test_absolute_redirect_n_higher_than_1(self):
609
620
  response = self.app.get('/absolute-redirect/5')
610
- self.assertEqual(
611
- response.headers.get('Location'), 'http://localhost/absolute-redirect/4'
621
+ self.assertRegex(
622
+ response.headers.get('Location'), '^http://[^/]*/absolute-redirect/4$'
612
623
  )
613
624
 
614
625
  def test_absolute_redirect_n_equals_to_1(self):
615
626
  response = self.app.get('/absolute-redirect/1')
616
627
  self.assertEqual(response.status_code, 302)
617
- self.assertEqual(
618
- response.headers.get('Location'), 'http://localhost/get'
628
+ self.assertRegex(
629
+ response.headers.get('Location'), '^http://[^/]*/get$'
619
630
  )
620
631
 
621
632
  def test_request_range(self):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes