httpbinx 1.10.0__tar.gz → 1.10.2__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 (58) hide show
  1. {httpbinx-1.10.0 → httpbinx-1.10.2}/PKG-INFO +4 -9
  2. {httpbinx-1.10.0 → httpbinx-1.10.2}/README.md +2 -2
  3. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/__init__.py +1 -1
  4. httpbinx-1.10.2/httpbinx/cli.py +58 -0
  5. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/constants.py +1 -7
  6. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/helpers.py +10 -17
  7. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/main.py +4 -10
  8. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/meta.py +5 -0
  9. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/routers/__init__.py +13 -14
  10. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/routers/anything.py +10 -9
  11. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/routers/auth.py +6 -8
  12. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/routers/cookies.py +12 -19
  13. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/routers/dynamicdata.py +48 -76
  14. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/routers/httpmethods.py +6 -6
  15. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/routers/images.py +10 -12
  16. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/routers/inspection/__init__.py +2 -1
  17. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/routers/inspection/request.py +3 -6
  18. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/routers/inspection/response.py +12 -26
  19. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/routers/redirects.py +19 -35
  20. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/routers/responseformats.py +15 -32
  21. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/routers/statuscodes.py +11 -20
  22. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/schemas.py +4 -4
  23. {httpbinx-1.10.0 → httpbinx-1.10.2}/pyproject.toml +33 -10
  24. httpbinx-1.10.2/tests/test_anything.py +44 -0
  25. httpbinx-1.10.2/tests/test_auth.py +27 -0
  26. httpbinx-1.10.2/tests/test_cookies.py +36 -0
  27. httpbinx-1.10.2/tests/test_dynamic_data.py +109 -0
  28. httpbinx-1.10.2/tests/test_http_methods.py +83 -0
  29. httpbinx-1.10.2/tests/test_images.py +68 -0
  30. httpbinx-1.10.2/tests/test_redirects.py +60 -0
  31. {httpbinx-1.10.0 → httpbinx-1.10.2}/tests/test_request_inspection.py +10 -16
  32. httpbinx-1.10.2/tests/test_response_formats.py +101 -0
  33. httpbinx-1.10.2/tests/test_status_codes.py +37 -0
  34. httpbinx-1.10.0/httpbinx/cli.py +0 -65
  35. httpbinx-1.10.0/httpbinx/templates/trackingscripts.html +0 -18
  36. httpbinx-1.10.0/tests/test_anything.py +0 -28
  37. httpbinx-1.10.0/tests/test_dynamic_data.py +0 -70
  38. httpbinx-1.10.0/tests/test_http_methods.py +0 -39
  39. httpbinx-1.10.0/tests/test_images.py +0 -38
  40. httpbinx-1.10.0/tests/test_redirects.py +0 -21
  41. httpbinx-1.10.0/tests/test_response_formats.py +0 -46
  42. httpbinx-1.10.0/tests/test_status_codes.py +0 -41
  43. {httpbinx-1.10.0 → httpbinx-1.10.2}/LICENSE +0 -0
  44. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/examples/__init__.py +0 -0
  45. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/static/UTF-8-demo.txt +0 -0
  46. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/static/bombs/bomb-1GB.br +0 -0
  47. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/static/bombs/bomb-1GB.gz +0 -0
  48. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/static/favicon.png +0 -0
  49. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/static/images/httbinx_logo.png +0 -0
  50. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/static/images/httpbinx_cover.png +0 -0
  51. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/static/images/jackal.jpg +0 -0
  52. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/static/images/pig_icon.png +0 -0
  53. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/static/images/svg_logo.svg +0 -0
  54. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/static/images/wolf_1.webp +0 -0
  55. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/templates/moby.html +0 -0
  56. {httpbinx-1.10.0 → httpbinx-1.10.2}/httpbinx/templates/sample.xml +0 -0
  57. {httpbinx-1.10.0 → httpbinx-1.10.2}/tests/__init__.py +0 -0
  58. /httpbinx-1.10.0/tests/common.py → /httpbinx-1.10.2/tests/conftest.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: httpbinx
3
- Version: 1.10.0
3
+ Version: 1.10.2
4
4
  Summary: HTTP Request & Response Service, written in Python + FastAPI.
5
5
  Author-Email: Leo <imleowoo@outlook.com>
6
6
  Maintainer-Email: Leo <imleowoo@outlook.com>
@@ -38,23 +38,18 @@ Project-URL: Homepage, https://github.com/imleowoo/httpbinx
38
38
  Project-URL: Repository, https://github.com/imleowoo/httpbinx
39
39
  Requires-Python: >=3.10
40
40
  Requires-Dist: fastapi
41
+ Requires-Dist: pydantic
41
42
  Requires-Dist: uvicorn
42
43
  Requires-Dist: starlette
43
44
  Requires-Dist: jinja2
44
45
  Requires-Dist: brotli
45
46
  Requires-Dist: python-multipart
46
- Provides-Extra: test
47
- Requires-Dist: httpx; extra == "test"
48
- Requires-Dist: pytest-cov; extra == "test"
49
- Requires-Dist: pytest-mock; extra == "test"
50
- Requires-Dist: pytest-xdist; extra == "test"
51
- Requires-Dist: pytest; extra == "test"
52
47
  Description-Content-Type: text/markdown
53
48
 
54
49
  ![![cover](httpbinx/static/images/httpbinx_cover.png)](https://raw.githubusercontent.com/imleowoo/httpbinx/main/httpbinx/static/images/httpbinx_cover.png)
55
50
 
56
51
  [![thanks](https://img.shields.io/badge/thanks-httpbin-green)](https://github.com/postmanlabs/httpbin)
57
- ![python](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue)
52
+ ![python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue)
58
53
 
59
54
  # httpbinx
60
55
 
@@ -66,7 +61,7 @@ HTTP Request & Response Service, written in Python + FastAPI.
66
61
 
67
62
  ## Reference Project
68
63
 
69
- A [Kenneth Reitz](http://kennethreitz.org/bitcoin) Project. See https://github.com/postmanlabs/httpbin
64
+ A [Kenneth Reitz](http://kennethreitz.org/bitcoin) Project. See https://github.com/postmanlabs/httpbin
70
65
 
71
66
  ## Quick Start
72
67
 
@@ -1,7 +1,7 @@
1
1
  ![![cover](httpbinx/static/images/httpbinx_cover.png)](https://raw.githubusercontent.com/imleowoo/httpbinx/main/httpbinx/static/images/httpbinx_cover.png)
2
2
 
3
3
  [![thanks](https://img.shields.io/badge/thanks-httpbin-green)](https://github.com/postmanlabs/httpbin)
4
- ![python](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue)
4
+ ![python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue)
5
5
 
6
6
  # httpbinx
7
7
 
@@ -13,7 +13,7 @@ HTTP Request & Response Service, written in Python + FastAPI.
13
13
 
14
14
  ## Reference Project
15
15
 
16
- A [Kenneth Reitz](http://kennethreitz.org/bitcoin) Project. See https://github.com/postmanlabs/httpbin
16
+ A [Kenneth Reitz](http://kennethreitz.org/bitcoin) Project. See https://github.com/postmanlabs/httpbin
17
17
 
18
18
  ## Quick Start
19
19
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  from .main import app
4
4
 
5
- __version__ = '1.10.0'
5
+ __version__ = '1.10.2'
6
6
 
7
7
  app.version = __version__
@@ -0,0 +1,58 @@
1
+ """httpbinx CLI."""
2
+
3
+ import argparse
4
+ import platform
5
+ import sys
6
+
7
+ import fastapi
8
+
9
+ import httpbinx
10
+
11
+
12
+ def info(_args):
13
+ """Show httpbinx and environment information."""
14
+ print(f'httpbinx: {httpbinx.__version__}')
15
+ print(f'fastapi: {fastapi.__version__}')
16
+ print(f'python: {sys.version}')
17
+ print(f'platform: {platform.platform()}')
18
+ print(f'python executable: {sys.executable}')
19
+
20
+
21
+ def version(_args):
22
+ """Show httpbinx version."""
23
+ print(httpbinx.__version__)
24
+
25
+
26
+ def server(_args):
27
+ """Start the httpbinx server."""
28
+ import uvicorn
29
+
30
+ uvicorn.run('httpbinx.main:app', host=_args.host, port=_args.port)
31
+
32
+
33
+ def execute():
34
+ """Execute the CLI."""
35
+ parser = argparse.ArgumentParser(prog='httpbinx', description='httpbinx is a fastapi server for testing.')
36
+ parser.add_argument('--version', action='version', version='%(prog)s 1.0')
37
+ subparsers = parser.add_subparsers(dest='command')
38
+ # version
39
+ parser_version = subparsers.add_parser('version', help='show version')
40
+ parser_version.set_defaults(func=version)
41
+ # info
42
+ parser_info = subparsers.add_parser('info', help='show info')
43
+ parser_info.set_defaults(func=info)
44
+ # server
45
+ parser_server = subparsers.add_parser('server', help='start server')
46
+ parser_server.set_defaults(func=server)
47
+ parser_server.add_argument('--host', type=str, help='host', default='0.0.0.0')
48
+ parser_server.add_argument('--port', type=int, help='port', default=80)
49
+
50
+ parse_args = parser.parse_args()
51
+ if hasattr(parse_args, 'func'):
52
+ parse_args.func(parse_args)
53
+ else:
54
+ parser.print_help()
55
+
56
+
57
+ if __name__ == '__main__':
58
+ execute()
@@ -1,12 +1,6 @@
1
1
  REDIRECT_LOCATION = '/redirect/1'
2
2
 
3
- ACCEPTED_MEDIA_TYPES = [
4
- 'image/webp',
5
- 'image/svg+xml',
6
- 'image/jpeg',
7
- 'image/png',
8
- 'image/*'
9
- ]
3
+ ACCEPTED_MEDIA_TYPES = ['image/webp', 'image/svg+xml', 'image/jpeg', 'image/png', 'image/*']
10
4
 
11
5
  ENV_COOKIES = (
12
6
  '_gauges_unique',
@@ -8,12 +8,11 @@ from starlette.requests import Request
8
8
  from starlette.responses import Response
9
9
  from starlette.templating import Jinja2Templates
10
10
 
11
- from httpbinx.constants import (ACCEPTED_MEDIA_TYPES, ASCII_ART,
12
- REDIRECT_LOCATION)
11
+ from httpbinx.constants import ACCEPTED_MEDIA_TYPES, ASCII_ART, REDIRECT_LOCATION
13
12
  from httpbinx.schemas import RequestAttrs, RequestInfo
14
13
 
15
14
  # init Jinja2
16
- _templates = Jinja2Templates(directory='templates')
15
+ _templates = Jinja2Templates(directory=str(Path(__file__).parent / 'templates'))
17
16
  # image path
18
17
  _images_path = Path(__file__).parent / 'static' / 'images'
19
18
  # bomb files path
@@ -62,25 +61,19 @@ def status_code_response(code: int) -> Response:
62
61
  status.HTTP_304_NOT_MODIFIED: dict(data=''),
63
62
  status.HTTP_305_USE_PROXY: redirect,
64
63
  status.HTTP_307_TEMPORARY_REDIRECT: redirect,
65
- status.HTTP_401_UNAUTHORIZED: dict(
66
- headers={'WWW-Authenticate': 'Basic realm="Fake Realm"'}
67
- ),
64
+ status.HTTP_401_UNAUTHORIZED: dict(headers={'WWW-Authenticate': 'Basic realm="Fake Realm"'}),
68
65
  status.HTTP_402_PAYMENT_REQUIRED: dict(
69
- data='Fuck you, pay me!',
70
- headers={'x-more-info': 'https://vimeo.com/22053820'}
66
+ data='Fuck you, pay me!', headers={'x-more-info': 'https://vimeo.com/22053820'}
71
67
  ),
72
68
  status.HTTP_406_NOT_ACCEPTABLE: dict(
73
- data=json.dumps({
74
- 'message': 'Client did not request a supported media type.',
75
- 'accept': ACCEPTED_MEDIA_TYPES
76
- }),
77
- headers={'Content-Type': 'application/json'}),
78
- status.HTTP_407_PROXY_AUTHENTICATION_REQUIRED: dict(
79
- headers={'Proxy-Authenticate': 'Basic realm="Fake Realm"'}
69
+ data=json.dumps(
70
+ {'message': 'Client did not request a supported media type.', 'accept': ACCEPTED_MEDIA_TYPES}
71
+ ),
72
+ headers={'Content-Type': 'application/json'},
80
73
  ),
74
+ status.HTTP_407_PROXY_AUTHENTICATION_REQUIRED: dict(headers={'Proxy-Authenticate': 'Basic realm="Fake Realm"'}),
81
75
  status.HTTP_418_IM_A_TEAPOT: dict( # I'm a teapot!
82
- data=ASCII_ART,
83
- headers={'x-more-info': 'https://datatracker.ietf.org/doc/html/rfc2324'}
76
+ data=ASCII_ART, headers={'x-more-info': 'https://datatracker.ietf.org/doc/html/rfc2324'}
84
77
  ),
85
78
  }
86
79
 
@@ -9,28 +9,22 @@ from httpbinx.routers import router
9
9
 
10
10
  app = FastAPI(
11
11
  title='httpbinx',
12
- description='HTTP Request & Response Service, '
13
- 'written in Python + FastAPI.',
12
+ description='HTTP Request & Response Service, written in Python + FastAPI.',
14
13
  docs_url='/', # swagger docs page url
15
14
  swagger_ui_parameters={'docExpansion': 'none'},
16
- openapi_tags=get_tags_metadata()
15
+ openapi_tags=get_tags_metadata(),
17
16
  )
18
17
 
19
18
  app.add_middleware(
20
- CORSMiddleware,
19
+ CORSMiddleware, # type: ignore
21
20
  allow_origins=['*'],
22
21
  allow_credentials=True,
23
22
  allow_methods=['*'],
24
23
  allow_headers=['*'],
25
-
26
24
  )
27
25
 
28
26
  # mount static files
29
27
  static_dir = Path(__file__).parent / 'static'
30
- app.mount(
31
- '/static',
32
- StaticFiles(directory=static_dir),
33
- name='static'
34
- )
28
+ app.mount('/static', StaticFiles(directory=static_dir), name='static')
35
29
 
36
30
  app.include_router(router=router)
@@ -2,6 +2,7 @@
2
2
  This module defines the metadata for API tags used in the FastAPI application.
3
3
  These tags are used to organize and categorize different endpoints in the API documentation.
4
4
  """
5
+
5
6
  from functools import lru_cache
6
7
  from typing import List
7
8
 
@@ -9,11 +10,15 @@ from pydantic import BaseModel, Field
9
10
 
10
11
 
11
12
  class Tag(BaseModel):
13
+ """API tag with name and description."""
14
+
12
15
  name: str = Field(..., description='The name of the tag')
13
16
  description: str = Field(..., description='A brief description of the tag')
14
17
 
15
18
 
16
19
  class TagsMetadata(BaseModel):
20
+ """Container for a list of API tags."""
21
+
17
22
  tags: List[Tag] = Field(..., description='List of API tags')
18
23
 
19
24
 
@@ -1,29 +1,28 @@
1
- # -*- coding: utf-8 -*-
2
-
3
1
  __all__ = ['router']
4
2
 
5
3
  from fastapi import APIRouter
6
4
  from starlette import status
7
5
  from starlette.responses import RedirectResponse
8
6
 
9
- from httpbinx.routers import (anything, auth, cookies, dynamicdata,
10
- httpmethods, images, redirects, responseformats,
11
- statuscodes)
7
+ from httpbinx.routers import (
8
+ anything,
9
+ auth,
10
+ cookies,
11
+ dynamicdata,
12
+ httpmethods,
13
+ images,
14
+ redirects,
15
+ responseformats,
16
+ statuscodes,
17
+ )
12
18
  from httpbinx.routers.inspection import request_inspection, response_inspection
13
19
 
14
20
  router = APIRouter()
15
21
 
16
22
 
17
- @router.get(
18
- '/favicon.ico',
19
- summary='Returns a favicon.ico.',
20
- include_in_schema=False
21
- )
23
+ @router.get('/favicon.ico', summary='Returns a favicon.ico.', include_in_schema=False)
22
24
  async def favicon():
23
- return RedirectResponse(
24
- url='/static/favicon.png',
25
- status_code=status.HTTP_301_MOVED_PERMANENTLY
26
- )
25
+ return RedirectResponse(url='/static/favicon.png', status_code=status.HTTP_301_MOVED_PERMANENTLY)
27
26
 
28
27
 
29
28
  router.include_router(httpmethods.router)
@@ -13,6 +13,7 @@ router = APIRouter(tags=['Anything'])
13
13
 
14
14
  class BombTypes(str, Enum):
15
15
  """The allowed bomb file compression types are: brotli and gzip."""
16
+
16
17
  brotli = 'brotli'
17
18
  gzip = 'gzip'
18
19
 
@@ -25,7 +26,7 @@ bombs_path: Path = get_bomb_file_path()
25
26
  response_model=RequestInfo,
26
27
  summary='Returns anything passed in request data.',
27
28
  response_description='Anything passed in request',
28
- methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'TRACE']
29
+ methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'TRACE'],
29
30
  )
30
31
  async def anything(request: Request):
31
32
  return await to_request_info(request)
@@ -36,16 +37,16 @@ async def anything(request: Request):
36
37
  response_class=FileResponse,
37
38
  summary='Returns a bomb file.',
38
39
  description='**It may cause your client to crash! '
39
- 'References [I use Zip Bombs to Protect my Server](https://idiallo.com/blog/zipbomb-protection)**',
40
- response_description='Return a bomb for compressed files.'
40
+ 'References [I use Zip Bombs to Protect my Server](https://idiallo.com/blog/zipbomb-protection)**',
41
+ response_description='Return a bomb for compressed files.',
41
42
  )
42
43
  async def bomb_file(
43
- *,
44
- file: BombTypes = Path(
45
- ...,
46
- title='Compression types',
47
- description='The allowed bomb file compression types',
48
- )
44
+ *,
45
+ file: BombTypes = Path(
46
+ ...,
47
+ title='Compression types',
48
+ description='The allowed bomb file compression types',
49
+ ),
49
50
  ):
50
51
  if file == BombTypes.gzip:
51
52
  # dd if=/dev/zero bs=1M count=1000 | gzip -c > bomb-1GB.gz
@@ -1,22 +1,20 @@
1
- # -*- coding: utf-8 -*-
2
1
  """Auth"""
2
+
3
3
  from fastapi import APIRouter, HTTPException
4
4
  from fastapi.security import HTTPBasic, HTTPBasicCredentials
5
5
 
6
- router = APIRouter(tags=['Auth'],)
6
+ router = APIRouter(
7
+ tags=['Auth'],
8
+ )
7
9
  security = HTTPBasic()
8
10
 
9
11
 
10
12
  @router.get(
11
13
  '/basic-auth/{user}/{password}',
12
14
  summary='Prompts the user for authorization using HTTP Basic Auth.',
13
- response_description='TODO'
15
+ response_description='TODO',
14
16
  )
15
- async def basic_auth(
16
- user: str,
17
- password: str,
18
- credentials: HTTPBasicCredentials = security
19
- ):
17
+ async def basic_auth(user: str, password: str, credentials: HTTPBasicCredentials = security):
20
18
  if not (credentials and credentials.username == user and credentials.password == password):
21
19
  raise HTTPException(status_code=401)
22
20
 
@@ -1,5 +1,5 @@
1
- # -*- coding: utf-8 -*-
2
1
  """Cookies"""
2
+
3
3
  from fastapi import APIRouter, Path, Query
4
4
  from starlette.requests import Request
5
5
  from starlette.responses import JSONResponse, RedirectResponse
@@ -14,16 +14,11 @@ router = APIRouter(tags=['Cookies'])
14
14
  summary='Returns cookie data.',
15
15
  response_description='Set cookies.',
16
16
  response_class=JSONResponse,
17
-
18
17
  )
19
18
  async def cookies(
20
- *,
21
- show_env: bool = Query(
22
- default=False,
23
- title='Show Environment variable?',
24
- include_in_schema=False
25
- ),
26
- request: Request
19
+ *,
20
+ show_env: bool = Query(default=False, title='Show Environment variable?', include_in_schema=False),
21
+ request: Request,
27
22
  ):
28
23
  resp = JSONResponse(content={'cookies': request.cookies})
29
24
  if not show_env:
@@ -34,10 +29,9 @@ async def cookies(
34
29
 
35
30
  @router.get(
36
31
  '/cookies/set',
37
- summary='Sets cookie(s) as provided by the query string '
38
- 'and redirects to cookie list.',
32
+ summary='Sets cookie(s) as provided by the query string and redirects to cookie list.',
39
33
  response_description='Redirect to cookie list',
40
- response_class=RedirectResponse
34
+ response_class=RedirectResponse,
41
35
  )
42
36
  async def set_cookies(request: Request):
43
37
  params = request.query_params.items()
@@ -55,10 +49,10 @@ async def set_cookies(request: Request):
55
49
  response_class=RedirectResponse,
56
50
  )
57
51
  async def set_cookie(
58
- *,
59
- name: str = Path(..., description='Cookie Name'),
60
- value: str = Path(..., description='Cookie Value'),
61
- request: Request
52
+ *,
53
+ name: str = Path(..., description='Cookie Name'),
54
+ value: str = Path(..., description='Cookie Value'),
55
+ request: Request,
62
56
  ):
63
57
  resp = RedirectResponse(request.url_for(cookies.__name__))
64
58
  # TODO secure=True
@@ -68,10 +62,9 @@ async def set_cookie(
68
62
 
69
63
  @router.get(
70
64
  '/cookies/delete',
71
- summary='Deletes cookie(s) as provided by the query string '
72
- 'and redirects to cookie list.',
65
+ summary='Deletes cookie(s) as provided by the query string and redirects to cookie list.',
73
66
  response_description='Redirect to cookie list',
74
- response_class=RedirectResponse
67
+ response_class=RedirectResponse,
75
68
  )
76
69
  async def delete_cookies(request: Request):
77
70
  keys = request.query_params.keys()
@@ -1,5 +1,5 @@
1
- # -*- coding: utf-8 -*-
2
1
  """Dynamic Data"""
2
+
3
3
  import asyncio
4
4
  import base64
5
5
  import binascii
@@ -10,8 +10,7 @@ from fastapi import APIRouter, Path, Query
10
10
  from fastapi.responses import PlainTextResponse
11
11
  from starlette import status
12
12
  from starlette.requests import Request
13
- from starlette.responses import (HTMLResponse, JSONResponse, Response,
14
- StreamingResponse)
13
+ from starlette.responses import HTMLResponse, JSONResponse, Response, StreamingResponse
15
14
 
16
15
  from httpbinx.constants import AWESOME_BASE64ENCODED
17
16
  from httpbinx.helpers import to_request_info
@@ -22,6 +21,7 @@ router = APIRouter(tags=['Dynamic data'])
22
21
 
23
22
  class OctetStreamResponse(Response):
24
23
  """set response headers Content-Type: application/octet-stream"""
24
+
25
25
  media_type = 'application/octet-stream'
26
26
 
27
27
 
@@ -32,16 +32,12 @@ class OctetStreamResponse(Response):
32
32
  response_description='Decoded base64 content.',
33
33
  )
34
34
  async def decode_base64(
35
- value: str = Path(
36
- ...,
37
- title='base64-encoded string',
38
- # examples=[AWESOME_BASE64ENCODED],
39
- openapi_examples={
40
- 'awesome': {
41
- 'summary': 'A awesome base64 string',
42
- 'value': AWESOME_BASE64ENCODED
43
- }}
44
- )
35
+ value: str = Path(
36
+ ...,
37
+ title='base64-encoded string',
38
+ # examples=[AWESOME_BASE64ENCODED],
39
+ openapi_examples={'awesome': {'summary': 'A awesome base64 string', 'value': AWESOME_BASE64ENCODED}},
40
+ ),
45
41
  ):
46
42
  encoded: bytes = value.encode('utf-8')
47
43
  try:
@@ -49,8 +45,7 @@ async def decode_base64(
49
45
  return PlainTextResponse(content=decoded)
50
46
  except binascii.Error as err:
51
47
  return PlainTextResponse(
52
- content=f'Incorrect Base64 data: {value}, err_msg: {err}',
53
- status_code=status.HTTP_400_BAD_REQUEST
48
+ content=f'Incorrect Base64 data: {value}, err_msg: {err}', status_code=status.HTTP_400_BAD_REQUEST
54
49
  )
55
50
 
56
51
 
@@ -58,15 +53,11 @@ async def decode_base64(
58
53
  '/bytes/{n}',
59
54
  response_class=OctetStreamResponse,
60
55
  summary='Returns n random bytes generated with given seed',
61
- response_description='A delayed response.'
56
+ response_description='A delayed response.',
62
57
  )
63
58
  async def random_bytes(
64
- n: int = Path(..., title='binary file size', gt=0, lt=100 * 1024), # set 100KB limit
65
- seed: int = Query(
66
- None,
67
- title='random seed',
68
- description='Initialize the random number generator'
69
- )
59
+ n: int = Path(..., title='binary file size', gt=0, lt=100 * 1024), # set 100KB limit
60
+ seed: int = Query(None, title='random seed', description='Initialize the random number generator'),
70
61
  ):
71
62
  if seed is not None:
72
63
  random.seed(seed)
@@ -81,13 +72,9 @@ async def random_bytes(
81
72
  methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'TRACE'],
82
73
  response_model=RequestInfo,
83
74
  summary='Returns a delayed response (max of 10 seconds).',
84
- response_description='A delayed response.'
75
+ response_description='A delayed response.',
85
76
  )
86
- async def delay_response(
87
- *,
88
- delay: float = Path(..., ge=0, le=10, description='delay seconds'),
89
- request: Request
90
- ):
77
+ async def delay_response(*, delay: float = Path(..., ge=0, le=10, description='delay seconds'), request: Request):
91
78
  await asyncio.sleep(delay)
92
79
  return await to_request_info(request)
93
80
 
@@ -96,25 +83,18 @@ async def delay_response(
96
83
  '/drip',
97
84
  response_class=StreamingResponse,
98
85
  summary='Drips data over a duration after an optional initial delay.',
99
- response_description='A dripped response.'
86
+ response_description='A dripped response.',
100
87
  )
101
88
  async def drip(
102
- duration: float = Query(
103
- default=2,
104
- description='The amount of time (in seconds) over which to drip each byte'
105
- ),
106
- numbytes: int = Query(
107
- default=10, gt=0, lt=10 * 1024 * 1024, # set 10MB limit
108
- description='The number of bytes to respond with',
109
- ),
110
- code: int = Query(
111
- default=200, title='response code',
112
- description='The response code that will be returned'
113
- ),
114
- delay: float = Query(
115
- default=2, ge=0,
116
- description='The amount of time (in seconds) to delay before responding'
117
- ),
89
+ duration: float = Query(default=2, description='The amount of time (in seconds) over which to drip each byte'),
90
+ numbytes: int = Query(
91
+ default=10,
92
+ gt=0,
93
+ lt=10 * 1024 * 1024, # set 10MB limit
94
+ description='The number of bytes to respond with',
95
+ ),
96
+ code: int = Query(default=200, title='response code', description='The response code that will be returned'),
97
+ delay: float = Query(default=2, ge=0, description='The amount of time (in seconds) to delay before responding'),
118
98
  ):
119
99
  await asyncio.sleep(delay)
120
100
  # Number of seconds to pause during each data generation
@@ -125,26 +105,24 @@ async def drip(
125
105
  yield b'*'
126
106
  await asyncio.sleep(pause)
127
107
 
128
- return StreamingResponse(
129
- content=generate_content(),
130
- media_type='application/octet-stream',
131
- status_code=code
132
- )
108
+ return StreamingResponse(content=generate_content(), media_type='application/octet-stream', status_code=code)
133
109
 
134
110
 
135
111
  @router.get(
136
112
  '/links/{n}/{offset}',
137
113
  summary='Generate a page containing n links to other pages which do the same.',
138
114
  response_class=HTMLResponse,
139
- response_description='HTML links.'
115
+ response_description='HTML links.',
140
116
  )
141
117
  async def link_page(
142
- *,
143
- n: int = Path(
144
- ..., ge=1, le=200, # limit to between 1 and 200 links
145
- description='Number of links'
146
- ),
147
- offset: int = Path(..., ge=0),
118
+ *,
119
+ n: int = Path(
120
+ ...,
121
+ ge=1,
122
+ le=200, # limit to between 1 and 200 links
123
+ description='Number of links',
124
+ ),
125
+ offset: int = Path(..., ge=0),
148
126
  ):
149
127
  html = """
150
128
  <!DOCTYPE html>
@@ -161,7 +139,7 @@ async def link_page(
161
139
  else:
162
140
  body += link.format(
163
141
  href=f'/links/{n}/{i}', # TODO how to use router.url_path_for?
164
- text=i
142
+ text=i,
165
143
  )
166
144
  return HTMLResponse(content=html.format(body=body))
167
145
 
@@ -170,13 +148,10 @@ async def link_page(
170
148
  '/range/{numbytes}',
171
149
  summary='Streams n random bytes generated with given seed, at given chunk size per packet.',
172
150
  response_class=StreamingResponse,
173
- response_description='Streaming Bytes'
151
+ response_description='Streaming Bytes',
174
152
  )
175
153
  async def range_request(
176
- numbytes: int = Path(
177
- ..., ge=1, le=100 * 1024,
178
- description='number of bytes must be in the range (0, 102400]'
179
- )
154
+ numbytes: int = Path(..., ge=1, le=100 * 1024, description='number of bytes must be in the range (0, 102400]'),
180
155
  ):
181
156
  raise NotImplementedError
182
157
 
@@ -185,15 +160,17 @@ async def range_request(
185
160
  '/stream-bytes/{n}',
186
161
  summary='Streams n random bytes generated with given seed, at given chunk size per packet.',
187
162
  response_class=StreamingResponse,
188
- response_description='Streaming Bytes'
163
+ response_description='Streaming Bytes',
189
164
  )
190
165
  async def stream_random_bytes(
191
- n: int = Path(
192
- ..., ge=1, le=100 * 1024, # set 100KB limit
193
- description='Streams n random bytes generated'
194
- ),
195
- seed: int = Query(default=None, ge=0),
196
- chunk_size: int = Query(default=10 * 1024, ge=1, le=10 * 1024)
166
+ n: int = Path(
167
+ ...,
168
+ ge=1,
169
+ le=100 * 1024, # set 100KB limit
170
+ description='Streams n random bytes generated',
171
+ ),
172
+ seed: int = Query(default=None, ge=0),
173
+ chunk_size: int = Query(default=10 * 1024, ge=1, le=10 * 1024),
197
174
  ):
198
175
  if seed is not None:
199
176
  random.seed(seed)
@@ -211,12 +188,7 @@ async def stream_random_bytes(
211
188
  return StreamingResponse(content=generate_bytes(), media_type='application/octet-stream')
212
189
 
213
190
 
214
- @router.get(
215
- '/uuid',
216
- response_class=JSONResponse,
217
- summary='Return a UUID4.',
218
- response_description='A UUID4.'
219
- )
191
+ @router.get('/uuid', response_class=JSONResponse, summary='Return a UUID4.', response_description='A UUID4.')
220
192
  async def get_uuid4():
221
193
  out = {'uuid': str(uuid.uuid4())}
222
194
  return JSONResponse(content=out)